mirror of https://github.com/keeweb/keeweb
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