Чтение сжатого текстового файла LZ4 (mozlz4) в WebExtensions (JavaScript, Firefox)

1

Я переношу расширение SDK надстройки Firefox в WebExtensions. Раньше я мог получить доступ к поисковым системам браузера, но теперь я не могу, поэтому полезный пользователь предложил попробовать прочитать файл search.json.mozlz4, в котором есть каждый установленный движок. Тем не менее, этот файл json с компрессией LZ4, а в Mozilla собственный формат LZ4 с настраиваемым магическим номером "mozLz40\0".

Раньше это можно было использовать для чтения текстового файла, который использует сжатие LZ4, включая файл mozlz4:

let bytes = OS.File.read(path, { compression: "lz4" });
let content = new TextDecoder().decode(bytes);

(хотя я не мог найти документацию о поле "сжатие", он работает)

Теперь, используя WebExtensions, лучшее, что я мог придумать для чтения файла, - это

var reader = new FileReader();
reader.readAsText(file);
reader.onload = function(ev) {
    let content = ev.target.result;
};

Это никак не обрабатывает сжатие. Эта библиотека обрабатывает LZ4 , но это для node.js, поэтому я не могу это использовать. [edit: он тоже работает автономно]. Тем не менее, даже если я удаляю пользовательскую обработку магического числа, я не могу заставить его распаковать файл, в то время как этот код Python, по сравнению, работает как ожидалось:

import lz4
file_obj = open("search.json.mozlz4", "rb")
if file_obj.read(8) != b"mozLz40\0":
    raise InvalidHeader("Invalid magic number")
print(lz4.block.decompress(file_obj.read()))

Как я могу это сделать в JS?

  • 0
    К счастью, я ошибся, и node-lz4 на самом деле тоже работает автономно, реализуя require чтобы избежать упаковщиков. Тем не менее, он, похоже, не обрабатывает определенный формат mozlz4, который имеет собственный магический номер lz4 (даже если удалить магический номер из данных и соответствующую проверку). Я обновил вопрос со всем этим.
Теги:
file
firefox-webextensions
lz4

1 ответ

2
Лучший ответ

После долгих проб и ошибок я наконец смог прочитать и декодировать файл search.json.mozlz4 в WebExtension. Вы можете использовать библиотеку узлов LZ4, хотя вам нужно всего лишь одну функцию - uncompress (псевдонимами, как decodeBlock для внешнего доступа) - поэтому я переименовал его в decodeLz4Block и включил его здесь с небольшими изменениями:

// This method code was taken from node-lz4 by Pierre Curto. MIT license.
// CHANGES: Added ; to all lines. Reformated one-liners. Removed n = eIdx. Fixed eIdx skipping end bytes if sIdx != 0.
function decodeLz4Block(input, output, sIdx, eIdx)
{
    sIdx = sIdx || 0;
    eIdx = eIdx || input.length;

    // Process each sequence in the incoming data
    for (var i = sIdx, j = 0; i < eIdx;)
    {
        var token = input[i++];

        // Literals
        var literals_length = (token >> 4);
        if (literals_length > 0) {
            // length of literals
            var l = literals_length + 240;
            while (l === 255) {
                l = input[i++];
                literals_length += l;
            }

            // Copy the literals
            var end = i + literals_length;
            while (i < end) {
                output[j++] = input[i++];
            }

            // End of buffer?
            if (i === eIdx) {
                return j;
            }
        }

        // Match copy
        // 2 bytes offset (little endian)
        var offset = input[i++] | (input[i++] << 8);

        // 0 is an invalid offset value
        if (offset === 0 || offset > j) {
            return -(i-2);
        }

        // length of match copy
        var match_length = (token & 0xf);
        var l = match_length + 240;
        while (l === 255) {
            l = input[i++];
            match_length += l;
        }

        // Copy the match
        var pos = j - offset; // position of the match copy in the current output
        var end = j + match_length + 4; // minmatch = 4
        while (j < end) {
            output[j++] = output[pos++];
        }
    }

    return j;
}

Затем объявите эту функцию, которая получает объект File (а не путь) и обратные вызовы для успеха/ошибки:

function readMozlz4File(file, onRead, onError)
{
    let reader = new FileReader();

    reader.onload = function() {
        let input = new Uint8Array(reader.result);
        let output;
        let uncompressedSize = input.length*3;  // size estimate for uncompressed data!

        // Decode whole file.
        do {
            output = new Uint8Array(uncompressedSize);
            uncompressedSize = decodeLz4Block(input, output, 8+4);  // skip 8 byte magic number + 4 byte data size field
            // if there more data than our output estimate, create a bigger output array and retry (at most one retry)
        } while (uncompressedSize > output.length);

        output = output.slice(0, uncompressedSize); // remove excess bytes

        let decodedText = new TextDecoder().decode(output);
        onRead(decodedText);
    };

    if (onError) {
        reader.onerror = onError;
    }

    reader.readAsArrayBuffer(file); // read as bytes
};

Затем вы можете добавить кнопку HTML на страницу настроек надстройки, которая позволяет пользователю искать и выбирать search.json.mozlz4 (в WebExtensions вы не можете просто открыть любой файл в файловой системе без вмешательства пользователя):

<input name="selectMozlz4FileButton" type="file" accept=".json.mozlz4">

Чтобы ответить пользователю на выбор файла, используйте что-то вроде этого, которое вызывает метод, который мы ранее объявили (здесь я не использую обратный вызов ошибки, но вы можете):

let button = document.getElementsByName("selectMozlz4FileButton")[0];
button.onchange = function onButtonPress(ev) {
    let file = ev.target.files[0];
    readMozlz4File(file, function(text){
        console.log(text);
    });
};

Я надеюсь, что это помогает кому-то. Я, конечно, потратил много времени на проработку этой простой вещи. :)

Ещё вопросы

Сообщество Overcoder
Наверх
Меню