mirror of https://github.com/keeweb/keeweb
deleted native-messaging-host
parent
00159d26dc
commit
00ccbf4be2
@ -1,58 +0,0 @@
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
|
||||
module.exports = function (grunt) {
|
||||
grunt.registerMultiTask('cmake', 'Builds with CMake', async function () {
|
||||
const done = this.async();
|
||||
const opt = this.options();
|
||||
|
||||
for (const file of this.files) {
|
||||
const dest = file.dest;
|
||||
const src = file.src[0];
|
||||
|
||||
const binPath = path.resolve(src, 'build', opt.outputName);
|
||||
if (fs.existsSync(binPath)) {
|
||||
fs.unlinkSync(binPath);
|
||||
}
|
||||
|
||||
try {
|
||||
await spawnCmake(['-B', 'build', '.', ...(opt.cmakeConfigure || [])], src);
|
||||
} catch (e) {
|
||||
grunt.warn(`Configure error: ${e}`);
|
||||
}
|
||||
|
||||
try {
|
||||
await spawnCmake(['--build', 'build', '--config', 'MinSizeRel'], src);
|
||||
} catch (e) {
|
||||
grunt.warn(`Build error: ${e}`);
|
||||
}
|
||||
|
||||
grunt.file.copy(binPath, dest);
|
||||
fs.chmodSync(dest, 0o755);
|
||||
|
||||
grunt.log.writeln(`Built ${dest}`);
|
||||
|
||||
done();
|
||||
}
|
||||
});
|
||||
|
||||
function spawnCmake(args, cwd) {
|
||||
return new Promise((resolve, reject) => {
|
||||
grunt.log.writeln(`cmake ${args.join(' ')}`);
|
||||
const child = grunt.util.spawn(
|
||||
{ cmd: 'cmake', args, opts: { cwd } },
|
||||
(err, result, code) => {
|
||||
if (code) {
|
||||
reject(new Error(`CMake exit code ${code}`));
|
||||
} else if (err) {
|
||||
reject(err);
|
||||
} else {
|
||||
resolve();
|
||||
}
|
||||
}
|
||||
);
|
||||
child.stdout.pipe(process.stdout);
|
||||
child.stderr.pipe(process.stderr);
|
||||
});
|
||||
}
|
||||
};
|
@ -1,14 +0,0 @@
|
||||
BasedOnStyle: LLVM
|
||||
IndentWidth: 4
|
||||
ColumnLimit: 100
|
||||
IncludeBlocks: Regroup
|
||||
IncludeCategories:
|
||||
- Regex: '^<ext/.*\.h>'
|
||||
Priority: 2
|
||||
- Regex: '^<.*\.h>'
|
||||
Priority: 1
|
||||
- Regex: '^<.*'
|
||||
Priority: 2
|
||||
- Regex: '.*'
|
||||
Priority: 3
|
||||
SortIncludes: true
|
@ -1,34 +0,0 @@
|
||||
cmake_minimum_required(VERSION 3.7)
|
||||
|
||||
project(keeweb-native-messaging-host)
|
||||
|
||||
set(CMAKE_CXX_STANDARD 20)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
|
||||
include(FetchContent)
|
||||
|
||||
FetchContent_Declare(
|
||||
libuv
|
||||
GIT_REPOSITORY https://github.com/libuv/libuv.git
|
||||
GIT_TAG v1.41.0
|
||||
)
|
||||
|
||||
FetchContent_MakeAvailable(libuv)
|
||||
|
||||
set(OUTPUT_NAME ${PROJECT_NAME})
|
||||
set(SOURCES src/native-messaging-host.cpp)
|
||||
|
||||
add_executable(${PROJECT_NAME} ${SOURCES})
|
||||
|
||||
target_link_libraries(${PROJECT_NAME} PRIVATE uv_a)
|
||||
target_include_directories(${PROJECT_NAME} PRIVATE ${libuv_SOURCE_DIR}/include)
|
||||
if(WIN32)
|
||||
target_compile_options(${PROJECT_NAME} PRIVATE /W4 /WX /permissive-)
|
||||
else()
|
||||
target_compile_options(${PROJECT_NAME} PRIVATE -Wall -Wextra -pedantic -Werror)
|
||||
endif()
|
||||
|
||||
if(CMAKE_BUILD_TYPE STREQUAL "Debug")
|
||||
target_compile_options(${PROJECT_NAME} PRIVATE -fsanitize=address,undefined)
|
||||
target_link_options(${PROJECT_NAME} PRIVATE -fsanitize=address,undefined)
|
||||
endif()
|
@ -1,16 +0,0 @@
|
||||
all:
|
||||
cmake -B build .
|
||||
cmake --build build --config MinSizeRel
|
||||
|
||||
debug:
|
||||
cmake -B build .
|
||||
cmake --build build --config Debug
|
||||
|
||||
format:
|
||||
clang-format -i src/*.cpp
|
||||
|
||||
run:
|
||||
echo -n 020000007b7d | xxd -r -p | build/keeweb-native-messaging-host keeweb-connect@keeweb.info
|
||||
|
||||
tests:
|
||||
../../node_modules/.bin/mocha test/native-messaging-host-test.mjs
|
@ -1,250 +0,0 @@
|
||||
#include <uv.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <filesystem>
|
||||
#include <iostream>
|
||||
#include <queue>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
||||
// https://developer.chrome.com/docs/apps/nativeMessaging/#native-messaging-host-protocol
|
||||
// https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/Native_messaging#app_side
|
||||
|
||||
struct State {
|
||||
std::string origin;
|
||||
uv_stream_t *tty_in = nullptr;
|
||||
uv_stream_t *tty_out = nullptr;
|
||||
uv_stream_t *keeweb_pipe = nullptr;
|
||||
std::queue<uv_buf_t> pending_to_keeweb{};
|
||||
std::queue<uv_buf_t> pending_to_stdout{};
|
||||
bool write_to_keeweb_in_progress = false;
|
||||
bool write_to_stdout_in_progress = false;
|
||||
bool keeweb_launched = false;
|
||||
uint32_t keeweb_connect_attempts = 0;
|
||||
};
|
||||
|
||||
State state{};
|
||||
|
||||
void process_keeweb_queue();
|
||||
void process_stdout_queue();
|
||||
void close_keeweb_pipe();
|
||||
void connect_keeweb_pipe();
|
||||
|
||||
void alloc_buf(uv_handle_t *, size_t size, uv_buf_t *buf) {
|
||||
buf->base = new char[size];
|
||||
buf->len = static_cast<decltype(uv_buf_t::len)>(size);
|
||||
}
|
||||
|
||||
void quit_on_error() {
|
||||
if (state.keeweb_pipe) {
|
||||
close_keeweb_pipe();
|
||||
} else {
|
||||
uv_read_stop(state.tty_in);
|
||||
uv_loop_close(uv_default_loop());
|
||||
}
|
||||
}
|
||||
|
||||
void stdin_read_cb(uv_stream_t *, ssize_t nread, const uv_buf_t *buf) {
|
||||
if (nread > 0) {
|
||||
state.pending_to_keeweb.emplace(
|
||||
uv_buf_init(buf->base, static_cast<decltype(uv_buf_t::len)>(nread)));
|
||||
process_keeweb_queue();
|
||||
} else if (nread == UV_EOF) {
|
||||
quit_on_error();
|
||||
} else if (nread < 0) {
|
||||
std::cerr << "STDIN read error: " << uv_err_name(static_cast<int>(nread)) << std::endl;
|
||||
quit_on_error();
|
||||
}
|
||||
}
|
||||
|
||||
void stdout_write_cb(uv_write_t *req, int status) {
|
||||
delete req;
|
||||
|
||||
auto buf = state.pending_to_stdout.front();
|
||||
state.pending_to_stdout.pop();
|
||||
delete[] buf.base;
|
||||
|
||||
state.write_to_stdout_in_progress = false;
|
||||
|
||||
auto success = status >= 0;
|
||||
if (success) {
|
||||
process_stdout_queue();
|
||||
} else {
|
||||
std::cerr << "STDOUT write error: " << uv_err_name(status) << std::endl;
|
||||
quit_on_error();
|
||||
}
|
||||
}
|
||||
|
||||
void process_stdout_queue() {
|
||||
if (state.write_to_stdout_in_progress || state.pending_to_stdout.empty()) {
|
||||
return;
|
||||
}
|
||||
auto buf = state.pending_to_stdout.front();
|
||||
|
||||
auto write_req = new uv_write_t{};
|
||||
uv_write(write_req, state.tty_out, &buf, 1, stdout_write_cb);
|
||||
|
||||
state.write_to_stdout_in_progress = true;
|
||||
}
|
||||
|
||||
void keeweb_pipe_close_cb(uv_handle_t *pipe) {
|
||||
delete pipe;
|
||||
uv_read_stop(state.tty_in);
|
||||
uv_loop_close(uv_default_loop());
|
||||
}
|
||||
|
||||
void close_keeweb_pipe() {
|
||||
if (!state.keeweb_pipe) {
|
||||
return;
|
||||
}
|
||||
auto pipe = state.keeweb_pipe;
|
||||
state.keeweb_pipe = nullptr;
|
||||
uv_read_stop(pipe);
|
||||
uv_close(reinterpret_cast<uv_handle_t *>(pipe), keeweb_pipe_close_cb);
|
||||
}
|
||||
|
||||
void keeweb_write_cb(uv_write_t *req, int status) {
|
||||
delete req;
|
||||
|
||||
auto buf = state.pending_to_keeweb.front();
|
||||
state.pending_to_keeweb.pop();
|
||||
delete[] buf.base;
|
||||
|
||||
state.write_to_keeweb_in_progress = false;
|
||||
|
||||
auto success = status >= 0;
|
||||
if (success) {
|
||||
process_keeweb_queue();
|
||||
} else {
|
||||
std::cerr << "Error writing to KeeWeb: " << uv_err_name(status) << std::endl;
|
||||
close_keeweb_pipe();
|
||||
}
|
||||
}
|
||||
|
||||
void process_keeweb_queue() {
|
||||
if (!state.keeweb_pipe || state.write_to_keeweb_in_progress ||
|
||||
state.pending_to_keeweb.empty()) {
|
||||
return;
|
||||
}
|
||||
auto buf = state.pending_to_keeweb.front();
|
||||
|
||||
auto write_req = new uv_write_t{};
|
||||
uv_write(write_req, state.keeweb_pipe, &buf, 1, keeweb_write_cb);
|
||||
|
||||
state.write_to_keeweb_in_progress = true;
|
||||
}
|
||||
|
||||
void keeweb_pipe_read_cb(uv_stream_t *, ssize_t nread, const uv_buf_t *buf) {
|
||||
if (nread > 0) {
|
||||
state.pending_to_stdout.emplace(
|
||||
uv_buf_init(buf->base, static_cast<decltype(uv_buf_t::len)>(nread)));
|
||||
process_stdout_queue();
|
||||
} else if (nread == UV_EOF) {
|
||||
close_keeweb_pipe();
|
||||
} else if (nread < 0) {
|
||||
std::cerr << "KeeWeb read error: " << uv_err_name(static_cast<int>(nread)) << std::endl;
|
||||
close_keeweb_pipe();
|
||||
}
|
||||
}
|
||||
|
||||
void keeweb_pipe_connect_cb(uv_connect_t *req, int status) {
|
||||
auto pipe = req->handle;
|
||||
delete req;
|
||||
auto connected = status >= 0;
|
||||
if (connected) {
|
||||
state.keeweb_pipe = pipe;
|
||||
uv_read_start(pipe, alloc_buf, keeweb_pipe_read_cb);
|
||||
process_keeweb_queue();
|
||||
} else {
|
||||
std::cerr << "Cannot connect to KeeWeb: " << uv_err_name(status) << std::endl;
|
||||
quit_on_error();
|
||||
}
|
||||
}
|
||||
|
||||
std::string keeweb_pipe_name() {
|
||||
std::string pipe_name;
|
||||
|
||||
uv_passwd_t user_info;
|
||||
auto err = uv_os_get_passwd(&user_info);
|
||||
|
||||
if (err) {
|
||||
std::cerr << "Error getting user info: " << uv_err_name(err) << std::endl;
|
||||
} else {
|
||||
#if defined(WIN32) || defined(_WIN32) || defined(__WIN32)
|
||||
pipe_name = "\\\\.\\pipe\\keeweb-connect-" + std::string{user_info.username};
|
||||
#elif __APPLE__
|
||||
pipe_name = "/Users/" + std::string{user_info.username} +
|
||||
"/Library/Group Containers/3LE7JZ657W.keeweb/conn.sock";
|
||||
#else
|
||||
pipe_name = std::filesystem::temp_directory_path() /
|
||||
("keeweb-connect-" + std::to_string(user_info.uid) + ".sock");
|
||||
#endif
|
||||
uv_os_free_passwd(&user_info);
|
||||
}
|
||||
|
||||
return pipe_name;
|
||||
}
|
||||
|
||||
void connect_keeweb_pipe() {
|
||||
state.keeweb_connect_attempts++;
|
||||
|
||||
auto pipe_name = keeweb_pipe_name();
|
||||
if (pipe_name.empty()) {
|
||||
quit_on_error();
|
||||
return;
|
||||
}
|
||||
|
||||
auto keeweb_pipe = new uv_pipe_t{};
|
||||
uv_pipe_init(uv_default_loop(), keeweb_pipe, false);
|
||||
|
||||
auto connect_req = new uv_connect_t();
|
||||
uv_pipe_connect(connect_req, keeweb_pipe, pipe_name.c_str(), keeweb_pipe_connect_cb);
|
||||
}
|
||||
|
||||
void start_reading_stdin() { uv_read_start(state.tty_in, alloc_buf, stdin_read_cb); }
|
||||
|
||||
void push_first_message_to_keeweb() {
|
||||
auto origin = state.origin;
|
||||
std::replace(origin.begin(), origin.end(), '"', '\'');
|
||||
|
||||
auto message = "{\"pid\":" + std::to_string(uv_os_getpid()) +
|
||||
",\"ppid\":" + std::to_string(uv_os_getppid()) + ",\"origin\":\"" + origin +
|
||||
"\"}";
|
||||
|
||||
auto message_length = message.length() + sizeof(uint32_t);
|
||||
auto data = new char[message_length];
|
||||
auto size_ptr = reinterpret_cast<uint32_t *>(data);
|
||||
|
||||
*size_ptr = message.length();
|
||||
memcpy(data + sizeof(uint32_t), message.c_str(), message.length());
|
||||
|
||||
state.pending_to_keeweb.emplace(
|
||||
uv_buf_init(data, static_cast<decltype(uv_buf_t::len)>(message_length)));
|
||||
}
|
||||
|
||||
void init_tty() {
|
||||
auto stdin_tty = new uv_tty_t{};
|
||||
uv_tty_init(uv_default_loop(), stdin_tty, 0, 0);
|
||||
state.tty_in = reinterpret_cast<uv_stream_t *>(stdin_tty);
|
||||
|
||||
auto stdout_tty = new uv_tty_t{};
|
||||
uv_tty_init(uv_default_loop(), stdout_tty, 1, 0);
|
||||
state.tty_out = reinterpret_cast<uv_stream_t *>(stdout_tty);
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
if (argc < 2) {
|
||||
std::cerr << "Expected origin argument" << std::endl;
|
||||
return 1;
|
||||
}
|
||||
state.origin = argv[1];
|
||||
|
||||
push_first_message_to_keeweb();
|
||||
|
||||
init_tty();
|
||||
start_reading_stdin();
|
||||
connect_keeweb_pipe();
|
||||
|
||||
uv_run(uv_default_loop(), UV_RUN_DEFAULT);
|
||||
}
|
@ -1,102 +0,0 @@
|
||||
import os from 'os';
|
||||
import net from 'net';
|
||||
import path from 'path';
|
||||
import fs from 'fs';
|
||||
import childProcess from 'child_process';
|
||||
import { expect } from 'chai';
|
||||
|
||||
describe('KeeWeb extension native module host', function () {
|
||||
const extensionOrigin = 'keeweb-connect@keeweb.info';
|
||||
|
||||
const userInfo = os.userInfo();
|
||||
let sockPath;
|
||||
let hostPath;
|
||||
if (process.platform === 'darwin') {
|
||||
sockPath = `/Users/${userInfo.username}/Library/Group Containers/3LE7JZ657W.keeweb/browser.sock`;
|
||||
hostPath = 'build/keeweb-native-messaging-host';
|
||||
} else if (process.platform === 'win32') {
|
||||
sockPath = `\\\\.\\pipe\\keeweb-connect-${userInfo.username}`;
|
||||
hostPath = 'build\\Debug\\keeweb-native-messaging-host.exe';
|
||||
} else {
|
||||
sockPath = path.join(os.tmpdir(), `keeweb-connect-${userInfo.uid}.sock`);
|
||||
hostPath = 'build/keeweb-native-messaging-host';
|
||||
}
|
||||
|
||||
let server;
|
||||
let serverConnection;
|
||||
|
||||
this.timeout(5000);
|
||||
|
||||
afterEach((done) => {
|
||||
serverConnection = undefined;
|
||||
if (server) {
|
||||
server.close(done);
|
||||
server = null;
|
||||
} else {
|
||||
done();
|
||||
}
|
||||
});
|
||||
|
||||
it('exits without arguments', (done) => {
|
||||
const process = childProcess.spawn(hostPath);
|
||||
process.on('exit', (code) => {
|
||||
expect(code).to.eql(1);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('exits with bad origin', (done) => {
|
||||
const process = childProcess.spawn(hostPath, ['something']);
|
||||
process.on('exit', (code) => {
|
||||
expect(code).to.eql(1);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('exits on host exit', (done) => {
|
||||
startServer();
|
||||
const process = childProcess.spawn(hostPath, [extensionOrigin]);
|
||||
process.stderr.on('data', (data) => console.error(data.toString()));
|
||||
process.on('exit', (code) => {
|
||||
expect(code).to.eql(0);
|
||||
done();
|
||||
});
|
||||
setTimeout(() => {
|
||||
expect(serverConnection).to.be.ok;
|
||||
server.close();
|
||||
server = null;
|
||||
serverConnection.end();
|
||||
}, 500);
|
||||
});
|
||||
|
||||
it('sends messages between stdio and socket', (done) => {
|
||||
startServer();
|
||||
const process = childProcess.spawn(hostPath, [extensionOrigin]);
|
||||
process.stderr.on('data', (data) => console.error(data.toString()));
|
||||
process.on('exit', (code) => {
|
||||
expect(code).to.eql(0);
|
||||
done();
|
||||
});
|
||||
process.stdin.write('ping');
|
||||
process.stdout.on('data', (data) => {
|
||||
expect(data.toString()).to.eql('ping response');
|
||||
server.close();
|
||||
server = null;
|
||||
serverConnection.end();
|
||||
});
|
||||
});
|
||||
|
||||
function startServer() {
|
||||
try {
|
||||
fs.unlinkSync(sockPath);
|
||||
} catch {}
|
||||
|
||||
server = net.createServer((socket) => {
|
||||
serverConnection = socket;
|
||||
socket.on('data', (data) => {
|
||||
socket.write(Buffer.concat([data, Buffer.from(' response')]));
|
||||
});
|
||||
});
|
||||
server.listen(sockPath);
|
||||
}
|
||||
});
|
Loading…
Reference in New Issue