diff --git a/.gitignore b/.gitignore index 69e162bf..b24869ac 100644 --- a/.gitignore +++ b/.gitignore @@ -17,4 +17,3 @@ xcuserdata/ .vscode/ *.iml test/dist/ -extension/native-messaging-host/build/ diff --git a/build/tasks/grunt-cmake.js b/build/tasks/grunt-cmake.js deleted file mode 100644 index 10597906..00000000 --- a/build/tasks/grunt-cmake.js +++ /dev/null @@ -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); - }); - } -}; diff --git a/extension/native-messaging-host/.clang-format b/extension/native-messaging-host/.clang-format deleted file mode 100644 index be1ea221..00000000 --- a/extension/native-messaging-host/.clang-format +++ /dev/null @@ -1,14 +0,0 @@ -BasedOnStyle: LLVM -IndentWidth: 4 -ColumnLimit: 100 -IncludeBlocks: Regroup -IncludeCategories: - - Regex: '^' - Priority: 2 - - Regex: '^<.*\.h>' - Priority: 1 - - Regex: '^<.*' - Priority: 2 - - Regex: '.*' - Priority: 3 -SortIncludes: true \ No newline at end of file diff --git a/extension/native-messaging-host/CMakeLists.txt b/extension/native-messaging-host/CMakeLists.txt deleted file mode 100644 index d4078514..00000000 --- a/extension/native-messaging-host/CMakeLists.txt +++ /dev/null @@ -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() diff --git a/extension/native-messaging-host/Makefile b/extension/native-messaging-host/Makefile deleted file mode 100644 index 09dae446..00000000 --- a/extension/native-messaging-host/Makefile +++ /dev/null @@ -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 diff --git a/extension/native-messaging-host/src/native-messaging-host.cpp b/extension/native-messaging-host/src/native-messaging-host.cpp deleted file mode 100644 index a0f1e025..00000000 --- a/extension/native-messaging-host/src/native-messaging-host.cpp +++ /dev/null @@ -1,250 +0,0 @@ -#include - -#include -#include -#include -#include -#include -#include -#include - -// 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 pending_to_keeweb{}; - std::queue 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(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(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(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(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(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(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(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(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(stdin_tty); - - auto stdout_tty = new uv_tty_t{}; - uv_tty_init(uv_default_loop(), stdout_tty, 1, 0); - state.tty_out = reinterpret_cast(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); -} diff --git a/extension/native-messaging-host/test/native-messaging-host-test.mjs b/extension/native-messaging-host/test/native-messaging-host-test.mjs deleted file mode 100644 index ebf459b2..00000000 --- a/extension/native-messaging-host/test/native-messaging-host-test.mjs +++ /dev/null @@ -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); - } -}); diff --git a/grunt.entrypoints.js b/grunt.entrypoints.js index 77171259..b0ec7598 100644 --- a/grunt.entrypoints.js +++ b/grunt.entrypoints.js @@ -55,7 +55,7 @@ module.exports = function(grunt) { 'build-darwin-installer', 'copy:desktop-darwin-installer-helper-x64', 'copy:native-modules-darwin-x64', - 'cmake:native-messaging-host-darwin-x64' + 'copy:native-messaging-host-darwin-x64' ]); grunt.registerTask('dev-desktop-darwin-signed', 'Build a signed macOS app in dev environment', [ @@ -68,7 +68,7 @@ module.exports = function(grunt) { 'build-desktop-app-content', 'electron:win32-x64', 'copy:native-modules-win32-x64', - 'cmake:native-messaging-host-win32-x64' + 'copy:native-messaging-host-win32-x64' ]); grunt.registerTask('dev-desktop-linux', 'Build a Linux app in dev environment', [ @@ -77,7 +77,7 @@ module.exports = function(grunt) { 'electron:linux', 'chmod:linux-desktop-x64', 'copy:native-modules-linux-x64', - 'cmake:native-messaging-host-linux-x64' + 'copy:native-messaging-host-linux-x64' ]); grunt.registerTask('test', 'Build and run tests', [ diff --git a/grunt.tasks.js b/grunt.tasks.js index 40437e3f..aa57387b 100644 --- a/grunt.tasks.js +++ b/grunt.tasks.js @@ -29,7 +29,7 @@ module.exports = function (grunt) { 'electron-patch:linux', 'chmod:linux-desktop-x64', 'copy:native-modules-linux-x64', - 'cmake:native-messaging-host-linux-x64' + 'copy:native-messaging-host-linux-x64' ]); grunt.registerTask('build-desktop-executables-darwin', [ @@ -41,9 +41,9 @@ module.exports = function (grunt) { 'copy:desktop-darwin-installer-helper-x64', 'copy:desktop-darwin-installer-helper-arm64', 'copy:native-modules-darwin-x64', - 'cmake:native-messaging-host-darwin-x64', + 'copy:native-messaging-host-darwin-x64', 'copy:native-modules-darwin-arm64', - 'cmake:native-messaging-host-darwin-arm64', + 'copy:native-messaging-host-darwin-arm64', sign ? 'osx-sign:desktop-x64' : 'noop', sign ? 'osx-sign:desktop-arm64' : 'noop', sign ? 'notarize:desktop-x64' : 'noop', @@ -69,9 +69,9 @@ module.exports = function (grunt) { 'copy:native-modules-win32-x64', 'copy:native-modules-win32-ia32', 'copy:native-modules-win32-arm64', - 'cmake:native-messaging-host-win32-x64', - 'cmake:native-messaging-host-win32-ia32', - 'cmake:native-messaging-host-win32-arm64' + 'copy:native-messaging-host-win32-x64', + 'copy:native-messaging-host-win32-ia32', + 'copy:native-messaging-host-win32-arm64' ]); grunt.registerTask('build-desktop-executables', [