Как использовать shell-скрипт в качестве хост-приложения Chrome Native Messaging

Как вы обрабатываете вызов API Chrome Native Messaging с помощью bash-скрипта?

Мне удалось сделать это с Python на этом примере

Конечно, я могу позвонить bash из кода Python с subprocess, но возможно ли пропустить python и обработать сообщение в bash напрямую?

Проблемной частью является чтение сериализованного сообщения JSON в переменную. Сообщение сериализуется с использованием JSON в кодировке UTF-8 и ему предшествует 32-битная длина сообщения в собственном байтовом порядке через stdin.

echo $* только выходы:chrome-extension://knldjmfmopnpolahpmmgbagdohdnhkik/

Также что-то вроде

read
echo $REPLY

ничего не выводит. Никаких признаков сообщения JSON. Python использует struct.unpack за это. Можно ли это сделать в bash?

2 ответа

Решение

Я предлагаю не использовать сценарии оболочки (bash) в качестве собственного хоста обмена сообщениями, поскольку bash слишком ограничен, чтобы быть полезным.

read без каких-либо параметров считывает целую строку перед завершением, в то время как собственный протокол обмена сообщениями указывает, что первые четыре байта указывают длину следующего сообщения (в собственном порядке байтов).

Bash - ужасный инструмент для обработки двоичных данных. Улучшенная версия вашего read Команда будет указывать -n N параметр, чтобы остановить чтение после N символы (примечание: не байты) и -r удалить некоторую обработку. Например, следующее будет хранить первые четыре символа в переменной с именем var_prefix:

IFS= read -rn 4 var_prefix

Даже если вы предполагаете, что это сохраняет первые четыре байта в переменной (это не так!), Вам придется конвертировать байты в целое число. Я уже упоминал, что bash автоматически сбрасывает все байты NUL? Эти характеристики делают Bash совершенно бесполезным для того, чтобы быть полностью способным нативным хостом обмена сообщениями.

Вы можете справиться с этим недостатком, проигнорировав первые несколько байтов, и начните анализировать результат, когда заметите { символ, начало запроса в формате JSON. После этого вы должны прочитать весь ввод, пока не будет найден конец ввода. Вам нужен анализатор JSON, который прекращает чтение ввода, когда встречается с концом строки JSON. Удачи в написании этого.

Генерация вывода проще, просто используйте echo -n или же printf,

Вот минимальный пример, который предполагает, что ввод заканчивается }, читает его (без обработки) и отвечает с результатом. Хотя эта демонстрация работает, я настоятельно рекомендую не использовать bash, а использовать более богатый язык сценариев, такой как Python или C++.

#!/bin/bash
# Loop forever, to deal with chrome.runtime.connectNative
while IFS= read -r -n1 c; do
    # Read the first message
    # Assuming that the message ALWAYS ends with a },
    # with no }s in the string. Adopt this piece of code if needed.
    if [ "$c" != '}' ] ; then
        continue
    fi

    message='{"message": "Hello world!"}'
    # Calculate the byte size of the string.
    # NOTE: This assumes that byte length is identical to the string length!
    # Do not use multibyte (unicode) characters, escape them instead, e.g.
    # message='"Some unicode character:\u1234"'
    messagelen=${#message}

    # Convert to an integer in native byte order.
    # If you see an error message in Chrome's stdout with
    # "Native Messaging host tried sending a message that is ... bytes long.",
    # then just swap the order, i.e. messagelen1 <-> messagelen4 and
    # messagelen2 <-> messagelen3
    messagelen1=$(( ($messagelen      ) & 0xFF ))               
    messagelen2=$(( ($messagelen >>  8) & 0xFF ))               
    messagelen3=$(( ($messagelen >> 16) & 0xFF ))               
    messagelen4=$(( ($messagelen >> 24) & 0xFF ))               

    # Print the message byte length followed by the actual message.
    printf "$(printf '\\x%x\\x%x\\x%x\\x%x' \
        $messagelen1 $messagelen2 $messagelen3 $messagelen4)%s" "$message"

done

Чтение и запись сообщений можно выполнять на чистом bash без использования каких-либо подоболочек.

Сложнее всего прочитать первые 4 байта, но это можно сделать, установивLC_ALL=C, что заставит bash обрабатывать каждый байт как символ и использовать следующие параметры для :

  • -n 1читать по одному символу (=байту) за раз
  • -rдля предотвращения обработки обратной косой черты
  • -d ''иметьreadвернуть 0 после чтения нулевого байта

Мы можем преобразовать символ в его числовое представление с помощьюprintf %d "'C".

После того, как мы получим длину, мы можем использоватьread -r -N "$len"чтобы прочитать именно столько байтов из ввода.

      LC_ALL=C IFS=
readmsg() {
    # return non-zero if fewer than 4 bytes of input available
    # or if message is shorter than length specified by first 
    # 4 bytes of input
    # otherwise, set the variable $msg and return 0

    local -i i n len=0
    for ((i=0; i<4; i++)); do
        read -r -d '' -n 1 || return
        printf -v n %d "'$REPLY"
        len+='n<<i*8'
    done
    read -r -N "$len" && ((${#REPLY}==len)) && msg=$REPLY
}

sendmsg() {
    local x
    printf -v x %08X "${#1}"
    printf %b%s "\x${x:6:2}\x${x:4:2}\x${x:2:2}\x${x:0:2}" "$1"
}

while readmsg; do
    # do_something "$msg"
    response='{"echo": "foo"}'
    sendmsg "$response"
done
Другие вопросы по тегам