From 1d98e5307ad42052b1463df1f7f3744828a04c5a Mon Sep 17 00:00:00 2001 From: AsamK Date: Wed, 2 Nov 2022 23:16:38 +0100 Subject: [PATCH 01/16] Handle missing separator in query string parser --- lib/src/main/java/org/asamk/signal/manager/util/Utils.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/src/main/java/org/asamk/signal/manager/util/Utils.java b/lib/src/main/java/org/asamk/signal/manager/util/Utils.java index 4be7c39a..792a107d 100644 --- a/lib/src/main/java/org/asamk/signal/manager/util/Utils.java +++ b/lib/src/main/java/org/asamk/signal/manager/util/Utils.java @@ -123,7 +123,7 @@ public class Utils { for (var param : params) { final var paramParts = param.split("="); var name = URLDecoder.decode(paramParts[0], StandardCharsets.UTF_8); - var value = URLDecoder.decode(paramParts[1], StandardCharsets.UTF_8); + var value = paramParts.length == 1 ? null : URLDecoder.decode(paramParts[1], StandardCharsets.UTF_8); map.put(name, value); } return map; -- 2.51.0 From a780be70dd98679b732baa9ef0a4645126d31b08 Mon Sep 17 00:00:00 2001 From: AsamK Date: Thu, 3 Nov 2022 00:03:37 +0100 Subject: [PATCH 02/16] Add http endpoint events with SSE --- .../asamk/signal/http/HttpServerHandler.java | 121 ++++++++++++++++++ .../signal/http/ServerSentEventSender.java | 55 ++++++++ 2 files changed, 176 insertions(+) create mode 100644 src/main/java/org/asamk/signal/http/ServerSentEventSender.java diff --git a/src/main/java/org/asamk/signal/http/HttpServerHandler.java b/src/main/java/org/asamk/signal/http/HttpServerHandler.java index 32000a1f..f7f2768d 100644 --- a/src/main/java/org/asamk/signal/http/HttpServerHandler.java +++ b/src/main/java/org/asamk/signal/http/HttpServerHandler.java @@ -5,19 +5,25 @@ import com.sun.net.httpserver.HttpExchange; import com.sun.net.httpserver.HttpServer; import org.asamk.signal.commands.Commands; +import org.asamk.signal.json.JsonReceiveMessageHandler; import org.asamk.signal.jsonrpc.JsonRpcReader; import org.asamk.signal.jsonrpc.JsonRpcResponse; import org.asamk.signal.jsonrpc.JsonRpcSender; import org.asamk.signal.jsonrpc.SignalJsonRpcCommandHandler; import org.asamk.signal.manager.Manager; import org.asamk.signal.manager.MultiAccountManager; +import org.asamk.signal.manager.api.Pair; +import org.asamk.signal.manager.util.Utils; import org.asamk.signal.util.Util; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.IOException; import java.net.InetSocketAddress; +import java.util.List; +import java.util.Map; import java.util.concurrent.Executors; +import java.util.concurrent.atomic.AtomicBoolean; public class HttpServerHandler { @@ -28,15 +34,21 @@ public class HttpServerHandler { private final InetSocketAddress address; private final SignalJsonRpcCommandHandler commandHandler; + private final MultiAccountManager c; + private final Manager m; public HttpServerHandler(final InetSocketAddress address, final Manager m) { this.address = address; commandHandler = new SignalJsonRpcCommandHandler(m, Commands::getCommand); + this.c = null; + this.m = m; } public HttpServerHandler(final InetSocketAddress address, final MultiAccountManager c) { this.address = address; commandHandler = new SignalJsonRpcCommandHandler(c, Commands::getCommand); + this.c = c; + this.m = null; } public void init() throws IOException { @@ -46,6 +58,7 @@ public class HttpServerHandler { server.setExecutor(Executors.newFixedThreadPool(10)); server.createContext("/api/v1/rpc", this::handleRpcEndpoint); + server.createContext("/api/v1/events", this::handleEventsEndpoint); server.start(); } @@ -110,4 +123,112 @@ public class HttpServerHandler { httpExchange); } } + + private void handleEventsEndpoint(HttpExchange httpExchange) throws IOException { + if (!"/api/v1/events".equals(httpExchange.getRequestURI().getPath())) { + sendResponse(404, null, httpExchange); + return; + } + if (!"GET".equals(httpExchange.getRequestMethod())) { + sendResponse(405, null, httpExchange); + return; + } + + try { + final var queryString = httpExchange.getRequestURI().getQuery(); + final var query = queryString == null ? Map.of() : Utils.getQueryMap(queryString); + + List managers = getManagerFromQuery(query); + if (managers == null) { + sendResponse(400, null, httpExchange); + return; + } + + httpExchange.getResponseHeaders().add("Content-Type", "text/event-stream"); + httpExchange.sendResponseHeaders(200, 0); + final var sender = new ServerSentEventSender(httpExchange.getResponseBody()); + + final var shouldStop = new AtomicBoolean(false); + final var handlers = subscribeReceiveHandlers(managers, sender, () -> { + shouldStop.set(true); + synchronized (this) { + this.notify(); + } + }); + + try { + while (true) { + synchronized (this) { + wait(15_000); + } + if (shouldStop.get()) { + break; + } + + try { + sender.sendKeepAlive(); + } catch (IOException e) { + break; + } + } + } finally { + for (final var pair : handlers) { + unsubscribeReceiveHandler(pair); + } + try { + httpExchange.getResponseBody().close(); + } catch (IOException ignored) { + } + } + } catch (Throwable aEx) { + logger.error("Failed to process request.", aEx); + sendResponse(500, null, httpExchange); + } + } + + private List getManagerFromQuery(final Map query) { + List managers; + if (m != null) { + managers = List.of(m); + } else { + final var account = query.get("account"); + if (account == null || account.isEmpty()) { + managers = c.getManagers(); + } else { + final var manager = c.getManager(account); + if (manager == null) { + return null; + } + managers = List.of(manager); + } + } + return managers; + } + + private List> subscribeReceiveHandlers( + final List managers, final ServerSentEventSender sender, Callable unsubscribe + ) { + return managers.stream().map(m1 -> { + final var receiveMessageHandler = new JsonReceiveMessageHandler(m1, s -> { + try { + sender.sendEvent(null, "receive", List.of(objectMapper.writeValueAsString(s))); + } catch (IOException e) { + unsubscribe.call(); + } + }); + m1.addReceiveHandler(receiveMessageHandler); + return new Pair<>(m1, (Manager.ReceiveMessageHandler) receiveMessageHandler); + }).toList(); + } + + private void unsubscribeReceiveHandler(final Pair pair) { + final var m = pair.first(); + final var handler = pair.second(); + m.removeReceiveHandler(handler); + } + + private interface Callable { + + void call(); + } } diff --git a/src/main/java/org/asamk/signal/http/ServerSentEventSender.java b/src/main/java/org/asamk/signal/http/ServerSentEventSender.java new file mode 100644 index 00000000..b33d3310 --- /dev/null +++ b/src/main/java/org/asamk/signal/http/ServerSentEventSender.java @@ -0,0 +1,55 @@ +package org.asamk.signal.http; + +import java.io.BufferedWriter; +import java.io.IOException; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.nio.charset.StandardCharsets; +import java.util.List; + +/** + * This class send Server-sent events payload to an OutputStream. + * See spec + */ +public class ServerSentEventSender { + + private final BufferedWriter writer; + + public ServerSentEventSender(final OutputStream outputStream) { + this.writer = new BufferedWriter(new OutputStreamWriter(outputStream, StandardCharsets.UTF_8)); + } + + /** + * @param id Event id + * @param event Event type + * @param data Event data, each entry must not contain newline chars. + */ + public synchronized void sendEvent(String id, String event, List data) throws IOException { + if (id != null) { + writer.write("id:"); + writer.write(id); + writer.write("\n"); + } + if (event != null) { + writer.write("event:"); + writer.write(event); + writer.write("\n"); + } + if (data.size() == 0) { + writer.write("data\n"); + } else { + for (final var d : data) { + writer.write("data:"); + writer.write(d); + writer.write("\n"); + } + } + writer.write("\n"); + writer.flush(); + } + + public synchronized void sendKeepAlive() throws IOException { + writer.write(":\n"); + writer.flush(); + } +} -- 2.51.0 From c5eb0fd351df8f1a782d6bcd962bf82bd4217b54 Mon Sep 17 00:00:00 2001 From: AsamK Date: Thu, 3 Nov 2022 11:40:19 +0100 Subject: [PATCH 03/16] Rework release workflow --- .github/workflows/ci.yml | 7 ++- ...{repackage-native-libs.yml => release.yml} | 62 ++++++++++--------- 2 files changed, 38 insertions(+), 31 deletions(-) rename .github/workflows/{repackage-native-libs.yml => release.yml} (70%) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5a1fd42a..3f3e7d8b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,6 +1,11 @@ name: signal-cli CI -on: [ push, pull_request, workflow_call ] +on: + push: + branches: + - '**' + pull_request: + workflow_call: jobs: build: diff --git a/.github/workflows/repackage-native-libs.yml b/.github/workflows/release.yml similarity index 70% rename from .github/workflows/repackage-native-libs.yml rename to .github/workflows/release.yml index a7428051..20a894e0 100644 --- a/.github/workflows/repackage-native-libs.yml +++ b/.github/workflows/release.yml @@ -1,24 +1,22 @@ -name: repackage-native-libs +name: release on: push: tags: - v* - jobs: ci_wf: uses: AsamK/signal-cli/.github/workflows/ci.yml@master # ${{ github.repository }} not accepted here - lib_to_jar: needs: ci_wf runs-on: ubuntu-latest outputs: - signal_cli_version: ${{ steps.cli_ver.outputs.signal_cli_version }} + signal_cli_version: ${{ steps.cli_ver.outputs.version }} release_id: ${{ steps.create_release.outputs.id }} steps: @@ -29,13 +27,16 @@ jobs: - name: Get signal-cli version id: cli_ver run: | - #echo ${GITHUB_REF#refs/tag/} + ver="${GITHUB_REF_NAME#v}" + echo "version=${ver}" >> $GITHUB_OUTPUT + + - name: Extract archive + run: | tree . - mv ./$(ls signal-cli-archive-*/ -d | tail -n1)/*.tar.gz . - ver=$(ls ./*.tar.gz | xargs basename | sed -E 's/signal-cli-(.*).tar.gz/\1/') - echo $ver - echo "::set-output name=signal_cli_version::${ver}" - tar -xzf ./*.tar.gz + ARCHIVE_DIR=$(ls signal-cli-archive-*/ -d | tail -n1) + tar -xzf ./"${ARCHIVE_DIR}"/*.tar.gz + mv ./"${ARCHIVE_DIR}"/*.tar.gz signal-cli-${{ steps.cli_ver.outputs.version }}-Linux.tar.gz + rm -rf signal-cli-archive-*/ - name: Get signal-client jar version id: lib_ver @@ -44,7 +45,7 @@ jobs: jar_file=$(find ./signal-cli-*/lib/ -name "$JAR_PREFIX*.jar") jar_version=$(echo "$jar_file" | xargs basename | sed "s/$JAR_PREFIX//; s/.jar//") echo "$jar_version" - echo "::set-output name=signal_client_version::$jar_version" + echo "signal_client_version=${jar_version}" >> $GITHUB_OUTPUT - name: Download signal-client builds env: @@ -59,28 +60,29 @@ jobs: - name: Compress native app env: - SIGNAL_CLI_VER: ${{ steps.cli_ver.outputs.signal_cli_version }} + SIGNAL_CLI_VER: ${{ steps.cli_ver.outputs.version }} run: | tar -czf signal-cli-${SIGNAL_CLI_VER}-Linux-native.tar.gz -C signal-cli-native signal-cli + rm -rf signal-cli-native/ - name: Replace Windows lib env: - SIGNAL_CLI_VER: ${{ steps.cli_ver.outputs.signal_cli_version }} + SIGNAL_CLI_VER: ${{ steps.cli_ver.outputs.version }} SIGNAL_CLIENT_VER: ${{ steps.lib_ver.outputs.signal_client_version }} run: | mv signal_jni.dll libsignal_jni.so - zip -u ./signal-cli-${SIGNAL_CLI_VER}/lib/libsignal-client-${SIGNAL_CLIENT_VER}.jar ./libsignal_jni.so - tar -czf signal-cli-${SIGNAL_CLI_VER}-Windows.tar.gz signal-cli-${SIGNAL_CLI_VER}/ + zip -u ./signal-cli-*/lib/libsignal-client-${SIGNAL_CLIENT_VER}.jar ./libsignal_jni.so + tar -czf signal-cli-${SIGNAL_CLI_VER}-Windows.tar.gz signal-cli-*/ - name: Replace macOS lib env: - SIGNAL_CLI_VER: ${{ steps.cli_ver.outputs.signal_cli_version }} + SIGNAL_CLI_VER: ${{ steps.cli_ver.outputs.version }} SIGNAL_CLIENT_VER: ${{ steps.lib_ver.outputs.signal_client_version }} run: | - jar_file=./signal-cli-${SIGNAL_CLI_VER}/lib/libsignal-client-${SIGNAL_CLIENT_VER}.jar - zip -d "$jar_file" libsignal_jni.so - zip "$jar_file" libsignal_jni.dylib - tar -czf signal-cli-${SIGNAL_CLI_VER}-macOS.tar.gz signal-cli-${SIGNAL_CLI_VER}/ + jar_file=./signal-cli-*/lib/libsignal-client-${SIGNAL_CLIENT_VER}.jar + zip -d $jar_file libsignal_jni.so + zip $jar_file libsignal_jni.dylib + tar -czf signal-cli-${SIGNAL_CLI_VER}-macOS.tar.gz signal-cli-*/ - name: Create release id: create_release @@ -88,8 +90,8 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: - tag_name: v${{ steps.cli_ver.outputs.signal_cli_version }} # note: added `v` - release_name: v${{ steps.cli_ver.outputs.signal_cli_version }} # note: added `v` + tag_name: v${{ steps.cli_ver.outputs.version }} # note: added `v` + release_name: v${{ steps.cli_ver.outputs.version }} # note: added `v` draft: true - name: Upload Linux archive @@ -98,8 +100,8 @@ jobs: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: upload_url: ${{ steps.create_release.outputs.upload_url }} - asset_path: signal-cli-${{ steps.cli_ver.outputs.signal_cli_version }}.tar.gz - asset_name: signal-cli-${{ steps.cli_ver.outputs.signal_cli_version }}-Linux.tar.gz + asset_path: signal-cli-${{ steps.cli_ver.outputs.version }}-Linux.tar.gz + asset_name: signal-cli-${{ steps.cli_ver.outputs.version }}-Linux.tar.gz asset_content_type: application/x-compressed-tar # .tar.gz - name: Upload Linux native archive @@ -108,8 +110,8 @@ jobs: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: upload_url: ${{ steps.create_release.outputs.upload_url }} - asset_path: signal-cli-${{ steps.cli_ver.outputs.signal_cli_version }}-Linux-native.tar.gz - asset_name: signal-cli-${{ steps.cli_ver.outputs.signal_cli_version }}-Linux-native.tar.gz + asset_path: signal-cli-${{ steps.cli_ver.outputs.version }}-Linux-native.tar.gz + asset_name: signal-cli-${{ steps.cli_ver.outputs.version }}-Linux-native.tar.gz asset_content_type: application/x-compressed-tar # .tar.gz - name: Upload windows archive @@ -118,8 +120,8 @@ jobs: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: upload_url: ${{ steps.create_release.outputs.upload_url }} - asset_path: signal-cli-${{ steps.cli_ver.outputs.signal_cli_version }}-Windows.tar.gz - asset_name: signal-cli-${{ steps.cli_ver.outputs.signal_cli_version }}-Windows.tar.gz + asset_path: signal-cli-${{ steps.cli_ver.outputs.version }}-Windows.tar.gz + asset_name: signal-cli-${{ steps.cli_ver.outputs.version }}-Windows.tar.gz asset_content_type: application/x-compressed-tar # .tar.gz - name: Upload macos archive @@ -128,8 +130,8 @@ jobs: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: upload_url: ${{ steps.create_release.outputs.upload_url }} - asset_path: signal-cli-${{ steps.cli_ver.outputs.signal_cli_version }}-macOS.tar.gz - asset_name: signal-cli-${{ steps.cli_ver.outputs.signal_cli_version }}-macOS.tar.gz + asset_path: signal-cli-${{ steps.cli_ver.outputs.version }}-macOS.tar.gz + asset_name: signal-cli-${{ steps.cli_ver.outputs.version }}-macOS.tar.gz asset_content_type: application/x-compressed-tar # .tar.gz -- 2.51.0 From e6cf11cb3de2d2a684189de7561fca1c520c60d0 Mon Sep 17 00:00:00 2001 From: AsamK Date: Thu, 3 Nov 2022 15:10:02 +0100 Subject: [PATCH 04/16] Add missing check to httpAddres --- src/main/java/org/asamk/signal/commands/DaemonCommand.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/java/org/asamk/signal/commands/DaemonCommand.java b/src/main/java/org/asamk/signal/commands/DaemonCommand.java index 0cdf9cc9..d32dc69c 100644 --- a/src/main/java/org/asamk/signal/commands/DaemonCommand.java +++ b/src/main/java/org/asamk/signal/commands/DaemonCommand.java @@ -152,6 +152,7 @@ public class DaemonCommand implements MultiLocalCommand, LocalCommand { !isDbusSystem && socketFile == null && tcpAddress == null + && httpAddress == null && !(inheritedChannel instanceof ServerSocketChannel) )) { runDbusSingleAccount(m, false, receiveMode != ReceiveMode.ON_START); @@ -233,6 +234,7 @@ public class DaemonCommand implements MultiLocalCommand, LocalCommand { !isDbusSystem && socketFile == null && tcpAddress == null + && httpAddress == null && !(inheritedChannel instanceof ServerSocketChannel) )) { runDbusMultiAccount(c, receiveMode != ReceiveMode.ON_START, false); -- 2.51.0 From 00535c9a42d44ed0e95062f2e2bffcbabeb03e6e Mon Sep 17 00:00:00 2001 From: AsamK Date: Thu, 3 Nov 2022 15:18:47 +0100 Subject: [PATCH 05/16] Package native file as executable --- .github/workflows/release.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 20a894e0..f76e686c 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -62,6 +62,7 @@ jobs: env: SIGNAL_CLI_VER: ${{ steps.cli_ver.outputs.version }} run: | + chmod +x signal-cli-native/signal-cli tar -czf signal-cli-${SIGNAL_CLI_VER}-Linux-native.tar.gz -C signal-cli-native signal-cli rm -rf signal-cli-native/ -- 2.51.0 From 6502f3f487b6403a24b84bde2091fad2f3bdcc5f Mon Sep 17 00:00:00 2001 From: AsamK Date: Thu, 3 Nov 2022 13:37:12 +0100 Subject: [PATCH 06/16] Publish docker image to ghcr --- .github/workflows/release.yml | 98 +++++++++++++++++++++++++++++++++++ Containerfile | 7 +++ native.Containerfile | 7 +++ 3 files changed, 112 insertions(+) create mode 100644 Containerfile create mode 100644 native.Containerfile diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index f76e686c..117f26ad 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -5,6 +5,12 @@ on: tags: - v* +env: + IMAGE_NAME: signal-cli + IMAGE_REGISTRY: ghcr.io/asamk + REGISTRY_USER: ${{ github.actor }} + REGISTRY_PASSWORD: ${{ github.token }} + jobs: ci_wf: @@ -191,3 +197,95 @@ jobs: EXECUTABLE_SUFFIX=".bat" fi ./signal-cli${EXECUTABLE_SUFFIX} listAccounts + + build-container: + needs: ci_wf + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + - name: Download signal-cli build from CI workflow + uses: actions/download-artifact@v3 + + - name: Get signal-cli version + id: cli_ver + run: | + ver="${GITHUB_REF_NAME#v}" + echo "version=${ver}" >> $GITHUB_OUTPUT + + - name: Move archive file + run: | + ARCHIVE_DIR=$(ls signal-cli-archive-*/ -d | tail -n1) + tar xf ./"${ARCHIVE_DIR}"/*.tar.gz + rm -r signal-cli-archive-* signal-cli-native + mkdir -p build/install/ + mv ./signal-cli-*/ build/install/signal-cli + + - name: Build Image + id: build_image + uses: redhat-actions/buildah-build@v2 + with: + image: ${{ env.IMAGE_NAME }} + tags: latest ${{ github.sha }} ${{ steps.cli_ver.outputs.version }} + containerfiles: + ./Containerfile + oci: true + + - name: Push To GHCR + uses: redhat-actions/push-to-registry@v2 + id: push + with: + image: ${{ steps.build_image.outputs.image }} + tags: ${{ steps.build_image.outputs.tags }} + registry: ${{ env.IMAGE_REGISTRY }} + username: ${{ env.REGISTRY_USER }} + password: ${{ env.REGISTRY_PASSWORD }} + + - name: Echo outputs + run: | + echo "${{ toJSON(steps.push.outputs) }}" + + build-container-native: + needs: ci_wf + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + - name: Download signal-cli build from CI workflow + uses: actions/download-artifact@v3 + + - name: Get signal-cli version + id: cli_ver + run: | + ver="${GITHUB_REF_NAME#v}" + echo "version=${ver}" >> $GITHUB_OUTPUT + + - name: Move archive file + run: | + mkdir -p build/native/nativeCompile/ + chmod +x ./signal-cli-native/signal-cli + mv ./signal-cli-native/signal-cli build/native/nativeCompile/ + + - name: Build Image + id: build_image + uses: redhat-actions/buildah-build@v2 + with: + image: ${{ env.IMAGE_NAME }} + tags: latest-native ${{ github.sha }}-native ${{ steps.cli_ver.outputs.version }}-native + containerfiles: + ./native.Containerfile + oci: true + + - name: Push To GHCR + uses: redhat-actions/push-to-registry@v2 + id: push + with: + image: ${{ steps.build_image.outputs.image }} + tags: ${{ steps.build_image.outputs.tags }} + registry: ${{ env.IMAGE_REGISTRY }} + username: ${{ env.REGISTRY_USER }} + password: ${{ env.REGISTRY_PASSWORD }} + + - name: Echo outputs + run: | + echo "${{ toJSON(steps.push.outputs) }}" diff --git a/Containerfile b/Containerfile new file mode 100644 index 00000000..c7ba8260 --- /dev/null +++ b/Containerfile @@ -0,0 +1,7 @@ +FROM docker.io/eclipse-temurin:17-jre + +RUN useradd signal-cli --system --create-home --home-dir /var/lib/signal-cli +ADD build/install/signal-cli /opt/signal-cli + +USER signal-cli +ENTRYPOINT ["/opt/signal-cli/bin/signal-cli", "--config=/var/lib/signal-cli"] diff --git a/native.Containerfile b/native.Containerfile new file mode 100644 index 00000000..8f52449b --- /dev/null +++ b/native.Containerfile @@ -0,0 +1,7 @@ +FROM docker.io/debian:testing-slim + +RUN useradd signal-cli --system --create-home --home-dir /var/lib/signal-cli +ADD build/native/nativeCompile/signal-cli /usr/bin/signal-cli + +USER signal-cli +ENTRYPOINT ["/usr/bin/signal-cli", "--config=/var/lib/signal-cli"] -- 2.51.0 From 6281cbfd5f875d6fc8ae79ac29ca3bfb03738ee9 Mon Sep 17 00:00:00 2001 From: AsamK Date: Thu, 3 Nov 2022 15:55:12 +0100 Subject: [PATCH 07/16] Catch all exceptions when reading session record Fixes #1083 --- .../asamk/signal/manager/storage/sessions/SessionStore.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/src/main/java/org/asamk/signal/manager/storage/sessions/SessionStore.java b/lib/src/main/java/org/asamk/signal/manager/storage/sessions/SessionStore.java index ec933742..564df1d0 100644 --- a/lib/src/main/java/org/asamk/signal/manager/storage/sessions/SessionStore.java +++ b/lib/src/main/java/org/asamk/signal/manager/storage/sessions/SessionStore.java @@ -3,7 +3,6 @@ package org.asamk.signal.manager.storage.sessions; import org.asamk.signal.manager.api.Pair; import org.asamk.signal.manager.storage.Database; import org.asamk.signal.manager.storage.Utils; -import org.signal.libsignal.protocol.InvalidMessageException; import org.signal.libsignal.protocol.NoSessionException; import org.signal.libsignal.protocol.SignalProtocolAddress; import org.signal.libsignal.protocol.ecc.ECPublicKey; @@ -339,7 +338,7 @@ public class SessionStore implements SignalServiceSessionStore { try { final var record = resultSet.getBytes("record"); return new SessionRecord(record); - } catch (InvalidMessageException e) { + } catch (Exception e) { logger.warn("Failed to load session, resetting session: {}", e.getMessage()); return null; } -- 2.51.0 From ccb37c00f6fed576664427a907fcecc21d2dbc67 Mon Sep 17 00:00:00 2001 From: AsamK Date: Mon, 7 Nov 2022 19:43:15 +0100 Subject: [PATCH 08/16] Update dependencies --- build.gradle.kts | 4 ++-- lib/build.gradle.kts | 8 ++++---- .../main/java/org/asamk/signal/manager/ManagerImpl.java | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index 49ff1f46..3e446aaf 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -3,7 +3,7 @@ plugins { application eclipse `check-lib-versions` - id("org.graalvm.buildtools.native") version "0.9.16" + id("org.graalvm.buildtools.native") version "0.9.17" } version = "0.11.4" @@ -40,7 +40,7 @@ repositories { dependencies { implementation("org.bouncycastle", "bcprov-jdk15on", "1.70") - implementation("com.fasterxml.jackson.core", "jackson-databind", "2.13.4.2") + implementation("com.fasterxml.jackson.core", "jackson-databind", "2.14.0") implementation("net.sourceforge.argparse4j", "argparse4j", "0.9.0") implementation("com.github.hypfvieh", "dbus-java-transport-native-unixsocket", "4.2.1") implementation("org.slf4j", "slf4j-api", "2.0.3") diff --git a/lib/build.gradle.kts b/lib/build.gradle.kts index 9b12a328..16837b05 100644 --- a/lib/build.gradle.kts +++ b/lib/build.gradle.kts @@ -14,15 +14,15 @@ repositories { } dependencies { - implementation("com.github.turasa", "signal-service-java", "2.15.3_unofficial_63") - implementation("com.fasterxml.jackson.core", "jackson-databind", "2.13.4.2") + implementation("com.github.turasa", "signal-service-java", "2.15.3_unofficial_64") + implementation("com.fasterxml.jackson.core", "jackson-databind", "2.14.0") implementation("com.google.protobuf", "protobuf-javalite", "3.21.6") implementation("org.bouncycastle", "bcprov-jdk15on", "1.70") implementation("org.slf4j", "slf4j-api", "2.0.3") - implementation("org.xerial", "sqlite-jdbc", "3.39.3.0") + implementation("org.xerial", "sqlite-jdbc", "3.39.4.0") implementation("com.zaxxer", "HikariCP", "5.0.1") - testImplementation("org.junit.jupiter", "junit-jupiter", "5.9.0") + testImplementation("org.junit.jupiter", "junit-jupiter", "5.9.1") } tasks.named("test") { diff --git a/lib/src/main/java/org/asamk/signal/manager/ManagerImpl.java b/lib/src/main/java/org/asamk/signal/manager/ManagerImpl.java index aead1a28..652b4e76 100644 --- a/lib/src/main/java/org/asamk/signal/manager/ManagerImpl.java +++ b/lib/src/main/java/org/asamk/signal/manager/ManagerImpl.java @@ -699,7 +699,7 @@ class ManagerImpl implements Manager { byte[] receipt, String note, RecipientIdentifier.Single recipient ) throws IOException { final var paymentNotification = new SignalServiceDataMessage.PaymentNotification(receipt, note); - final var payment = new SignalServiceDataMessage.Payment(paymentNotification); + final var payment = new SignalServiceDataMessage.Payment(paymentNotification, null); final var messageBuilder = SignalServiceDataMessage.newBuilder().withPayment(payment); try { return sendMessage(messageBuilder, Set.of(recipient)); -- 2.51.0 From 54a08f560ebaf42cb06faef8b7d69aa406fd4854 Mon Sep 17 00:00:00 2001 From: AsamK Date: Mon, 7 Nov 2022 19:55:53 +0100 Subject: [PATCH 09/16] Bump version --- CHANGELOG.md | 7 +++++-- build.gradle.kts | 2 +- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cc75f9e8..15eb4115 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,9 +2,12 @@ ## [Unreleased] +## [0.11.5] - 2022-11-07 +**Attention**: Now requires native libsignal-client version 0.21.1 + ### Added -- Add `--http` flag to `daemon` command to provide a JSON-RPC http endpoint. (Thanks @ced-b) -- The `receive` command is now also available in daemon mode, for polling new messages. +- Add `--http` flag to `daemon` command to provide a JSON-RPC http endpoint (`/api/v1/rpc`). (Thanks @ced-b) +- The `receive` method is now also available in JSON-RPC daemon mode, for polling new messages. - Add `getAttachment` command to get attachment file base64 encoded. (Thanks @ced-b) - Add `--disable-send-log` to disable the message send log. - Add `--story` to `sendReaction` command, to react to stories. diff --git a/build.gradle.kts b/build.gradle.kts index 3e446aaf..feba8e39 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -6,7 +6,7 @@ plugins { id("org.graalvm.buildtools.native") version "0.9.17" } -version = "0.11.4" +version = "0.11.5" java { sourceCompatibility = JavaVersion.VERSION_17 -- 2.51.0 From 8997d7f91f20e4c121415aa2464f4b6ba90b7d72 Mon Sep 17 00:00:00 2001 From: AsamK Date: Tue, 8 Nov 2022 17:17:18 +0100 Subject: [PATCH 10/16] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 50442c34..a2799d4a 100644 --- a/README.md +++ b/README.md @@ -56,7 +56,7 @@ of all country codes.) signal-cli -a ACCOUNT register - You can register Signal using a land line number. In this case you can skip SMS verification process and jump directly + You can register Signal using a landline number. In this case you can skip SMS verification process and jump directly to the voice call verification by adding the `--voice` switch at the end of above register command. Registering may require solving a CAPTCHA -- 2.51.0 From 5e1fc79c3382a66c4bfbb4847864b2356015861b Mon Sep 17 00:00:00 2001 From: AsamK Date: Tue, 8 Nov 2022 17:18:03 +0100 Subject: [PATCH 11/16] Fix SignalAccount initialization Fixes #1092 --- .../java/org/asamk/signal/manager/storage/SignalAccount.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/src/main/java/org/asamk/signal/manager/storage/SignalAccount.java b/lib/src/main/java/org/asamk/signal/manager/storage/SignalAccount.java index dac8de94..9d44c297 100644 --- a/lib/src/main/java/org/asamk/signal/manager/storage/SignalAccount.java +++ b/lib/src/main/java/org/asamk/signal/manager/storage/SignalAccount.java @@ -506,6 +506,7 @@ public class SignalAccount implements Closeable { ) throws IOException { this.dataPath = dataPath; this.accountPath = accountPath; + this.settings = settings; final JsonNode rootNode; synchronized (fileChannel) { fileChannel.position(0); @@ -685,7 +686,6 @@ public class SignalAccount implements Closeable { this.aciIdentityKeyPair = aciIdentityKeyPair; this.localRegistrationId = registrationId; - this.settings = settings; migratedLegacyConfig = loadLegacyStores(rootNode, legacySignalProtocolStore) || migratedLegacyConfig; -- 2.51.0 From dcaf1cc189e93b0fc71b351b37a06f0d758ace50 Mon Sep 17 00:00:00 2001 From: AsamK Date: Wed, 9 Nov 2022 19:00:59 +0100 Subject: [PATCH 12/16] Bump version --- CHANGELOG.md | 5 +++++ build.gradle.kts | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 15eb4115..674acf88 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,11 @@ ## [Unreleased] +## [0.11.5.1] - 2022-11-09 + +### Fixed +- Fix updating from older signal-cli version + ## [0.11.5] - 2022-11-07 **Attention**: Now requires native libsignal-client version 0.21.1 diff --git a/build.gradle.kts b/build.gradle.kts index feba8e39..4945b04d 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -6,7 +6,7 @@ plugins { id("org.graalvm.buildtools.native") version "0.9.17" } -version = "0.11.5" +version = "0.11.5.1" java { sourceCompatibility = JavaVersion.VERSION_17 -- 2.51.0 From 5771bb858f6894c5eeb52e8614cd1c4132bf6b7f Mon Sep 17 00:00:00 2001 From: AsamK Date: Mon, 14 Nov 2022 19:31:40 +0100 Subject: [PATCH 13/16] Allow using data URIs for updateGroup/updateProfile avatars Fixes #1082 --- .../org/asamk/signal/manager/Manager.java | 2 +- .../org/asamk/signal/manager/ManagerImpl.java | 2 +- .../asamk/signal/manager/api/UpdateGroup.java | 13 +++--- .../signal/manager/api/UpdateProfile.java | 10 ++--- .../signal/manager/helper/GroupHelper.java | 45 +++++++++++-------- .../signal/manager/helper/GroupV2Helper.java | 24 +++------- .../signal/manager/helper/ProfileHelper.java | 11 ++--- .../signal/commands/UpdateGroupCommand.java | 7 +-- .../signal/commands/UpdateProfileCommand.java | 3 +- .../asamk/signal/dbus/DbusManagerImpl.java | 8 ++-- .../org/asamk/signal/dbus/DbusSignalImpl.java | 8 ++-- 11 files changed, 60 insertions(+), 73 deletions(-) diff --git a/lib/src/main/java/org/asamk/signal/manager/Manager.java b/lib/src/main/java/org/asamk/signal/manager/Manager.java index 75503790..d4f8b76f 100644 --- a/lib/src/main/java/org/asamk/signal/manager/Manager.java +++ b/lib/src/main/java/org/asamk/signal/manager/Manager.java @@ -102,7 +102,7 @@ public interface Manager extends Closeable { void deleteGroup(GroupId groupId) throws IOException; Pair createGroup( - String name, Set members, File avatarFile + String name, Set members, String avatarFile ) throws IOException, AttachmentInvalidException, UnregisteredRecipientException; SendGroupMessageResults updateGroup( diff --git a/lib/src/main/java/org/asamk/signal/manager/ManagerImpl.java b/lib/src/main/java/org/asamk/signal/manager/ManagerImpl.java index 652b4e76..5191e2d9 100644 --- a/lib/src/main/java/org/asamk/signal/manager/ManagerImpl.java +++ b/lib/src/main/java/org/asamk/signal/manager/ManagerImpl.java @@ -394,7 +394,7 @@ class ManagerImpl implements Manager { @Override public Pair createGroup( - String name, Set members, File avatarFile + String name, Set members, String avatarFile ) throws IOException, AttachmentInvalidException, UnregisteredRecipientException { return context.getGroupHelper() .createGroup(name, diff --git a/lib/src/main/java/org/asamk/signal/manager/api/UpdateGroup.java b/lib/src/main/java/org/asamk/signal/manager/api/UpdateGroup.java index 71b18a67..0a2cb5ee 100644 --- a/lib/src/main/java/org/asamk/signal/manager/api/UpdateGroup.java +++ b/lib/src/main/java/org/asamk/signal/manager/api/UpdateGroup.java @@ -3,7 +3,6 @@ package org.asamk.signal.manager.api; import org.asamk.signal.manager.groups.GroupLinkState; import org.asamk.signal.manager.groups.GroupPermission; -import java.io.File; import java.util.Set; public class UpdateGroup { @@ -20,7 +19,7 @@ public class UpdateGroup { private final GroupLinkState groupLinkState; private final GroupPermission addMemberPermission; private final GroupPermission editDetailsPermission; - private final File avatarFile; + private final String avatarFile; private final Integer expirationTimer; private final Boolean isAnnouncementGroup; @@ -77,7 +76,7 @@ public class UpdateGroup { final GroupLinkState groupLinkState, final GroupPermission addMemberPermission, final GroupPermission editDetailsPermission, - final File avatarFile, + final String avatarFile, final Integer expirationTimer, final Boolean isAnnouncementGroup ) { @@ -146,7 +145,7 @@ public class UpdateGroup { return editDetailsPermission; } - public File getAvatarFile() { + public String getAvatarFile() { return avatarFile; } @@ -172,7 +171,7 @@ public class UpdateGroup { private GroupLinkState groupLinkState; private GroupPermission addMemberPermission; private GroupPermission editDetailsPermission; - private File avatarFile; + private String avatarFile; private Integer expirationTimer; private Boolean isAnnouncementGroup; @@ -192,7 +191,7 @@ public class UpdateGroup { final GroupLinkState groupLinkState, final GroupPermission addMemberPermission, final GroupPermission editDetailsPermission, - final File avatarFile, + final String avatarFile, final Integer expirationTimer, final Boolean isAnnouncementGroup ) { @@ -273,7 +272,7 @@ public class UpdateGroup { return this; } - public Builder withAvatarFile(final File val) { + public Builder withAvatarFile(final String val) { avatarFile = val; return this; } diff --git a/lib/src/main/java/org/asamk/signal/manager/api/UpdateProfile.java b/lib/src/main/java/org/asamk/signal/manager/api/UpdateProfile.java index d5de308e..3cfffe5f 100644 --- a/lib/src/main/java/org/asamk/signal/manager/api/UpdateProfile.java +++ b/lib/src/main/java/org/asamk/signal/manager/api/UpdateProfile.java @@ -1,14 +1,12 @@ package org.asamk.signal.manager.api; -import java.io.File; - public class UpdateProfile { private final String givenName; private final String familyName; private final String about; private final String aboutEmoji; - private final File avatar; + private final String avatar; private final boolean deleteAvatar; private final byte[] mobileCoinAddress; @@ -54,7 +52,7 @@ public class UpdateProfile { return aboutEmoji; } - public File getAvatar() { + public String getAvatar() { return avatar; } @@ -72,7 +70,7 @@ public class UpdateProfile { private String familyName; private String about; private String aboutEmoji; - private File avatar; + private String avatar; private boolean deleteAvatar; private byte[] mobileCoinAddress; @@ -99,7 +97,7 @@ public class UpdateProfile { return this; } - public Builder withAvatar(final File val) { + public Builder withAvatar(final String val) { avatar = val; return this; } diff --git a/lib/src/main/java/org/asamk/signal/manager/helper/GroupHelper.java b/lib/src/main/java/org/asamk/signal/manager/helper/GroupHelper.java index a342064c..bb9a3ea4 100644 --- a/lib/src/main/java/org/asamk/signal/manager/helper/GroupHelper.java +++ b/lib/src/main/java/org/asamk/signal/manager/helper/GroupHelper.java @@ -26,6 +26,7 @@ import org.asamk.signal.manager.storage.groups.GroupInfoV2; import org.asamk.signal.manager.storage.recipients.RecipientId; import org.asamk.signal.manager.util.AttachmentUtils; import org.asamk.signal.manager.util.IOUtils; +import org.asamk.signal.manager.util.Utils; import org.signal.libsignal.zkgroup.InvalidInputException; import org.signal.libsignal.zkgroup.groups.GroupMasterKey; import org.signal.libsignal.zkgroup.groups.GroupSecretParams; @@ -47,7 +48,6 @@ import org.whispersystems.signalservice.api.push.DistributionId; import org.whispersystems.signalservice.api.push.ServiceId; import org.whispersystems.signalservice.api.push.exceptions.ConflictException; -import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; @@ -164,7 +164,7 @@ public class GroupHelper { } public Pair createGroup( - String name, Set members, File avatarFile + String name, Set members, String avatarFile ) throws IOException, AttachmentInvalidException { final var selfRecipientId = account.getSelfRecipientId(); if (members != null && members.contains(selfRecipientId)) { @@ -172,14 +172,15 @@ public class GroupHelper { members.remove(selfRecipientId); } + final var avatarBytes = readAvatarBytes(avatarFile); var gv2Pair = context.getGroupV2Helper() - .createGroup(name == null ? "" : name, members == null ? Set.of() : members, avatarFile); + .createGroup(name == null ? "" : name, members == null ? Set.of() : members, avatarBytes); if (gv2Pair == null) { // Failed to create v2 group, creating v1 group instead var gv1 = new GroupInfoV1(GroupIdV1.createRandom()); gv1.addMembers(List.of(selfRecipientId)); - final var result = updateGroupV1(gv1, name, members, avatarFile); + final var result = updateGroupV1(gv1, name, members, avatarBytes); return new Pair<>(gv1.getGroupId(), result); } @@ -187,10 +188,9 @@ public class GroupHelper { final var decryptedGroup = gv2Pair.second(); gv2.setGroup(decryptedGroup); - if (avatarFile != null) { + if (avatarBytes != null) { context.getAvatarStore() - .storeGroupAvatar(gv2.getGroupId(), - outputStream -> IOUtils.copyFileToStream(avatarFile, outputStream)); + .storeGroupAvatar(gv2.getGroupId(), outputStream -> outputStream.write(avatarBytes)); } account.getGroupStore().updateGroup(gv2); @@ -217,11 +217,12 @@ public class GroupHelper { final GroupLinkState groupLinkState, final GroupPermission addMemberPermission, final GroupPermission editDetailsPermission, - final File avatarFile, + final String avatarFile, final Integer expirationTimer, final Boolean isAnnouncementGroup ) throws IOException, GroupNotFoundException, AttachmentInvalidException, NotAGroupMemberException, GroupSendingNotAllowedException { var group = getGroupForUpdating(groupId); + final var avatarBytes = readAvatarBytes(avatarFile); if (group instanceof GroupInfoV2) { try { @@ -238,7 +239,7 @@ public class GroupHelper { groupLinkState, addMemberPermission, editDetailsPermission, - avatarFile, + avatarBytes, expirationTimer, isAnnouncementGroup); } catch (ConflictException e) { @@ -257,14 +258,14 @@ public class GroupHelper { groupLinkState, addMemberPermission, editDetailsPermission, - avatarFile, + avatarBytes, expirationTimer, isAnnouncementGroup); } } final var gv1 = (GroupInfoV1) group; - final var result = updateGroupV1(gv1, name, members, avatarFile); + final var result = updateGroupV1(gv1, name, members, avatarBytes); if (expirationTimer != null) { setExpirationTimer(gv1, expirationTimer); } @@ -521,7 +522,7 @@ public class GroupHelper { } private SendGroupMessageResults updateGroupV1( - final GroupInfoV1 gv1, final String name, final Set members, final File avatarFile + final GroupInfoV1 gv1, final String name, final Set members, final byte[] avatarFile ) throws IOException, AttachmentInvalidException { updateGroupV1Details(gv1, name, members, avatarFile); @@ -534,7 +535,7 @@ public class GroupHelper { } private void updateGroupV1Details( - final GroupInfoV1 g, final String name, final Collection members, final File avatarFile + final GroupInfoV1 g, final String name, final Collection members, final byte[] avatarFile ) throws IOException { if (name != null) { g.name = name; @@ -545,9 +546,7 @@ public class GroupHelper { } if (avatarFile != null) { - context.getAvatarStore() - .storeGroupAvatar(g.getGroupId(), - outputStream -> IOUtils.copyFileToStream(avatarFile, outputStream)); + context.getAvatarStore().storeGroupAvatar(g.getGroupId(), outputStream -> outputStream.write(avatarFile)); } } @@ -581,7 +580,7 @@ public class GroupHelper { final GroupLinkState groupLinkState, final GroupPermission addMemberPermission, final GroupPermission editDetailsPermission, - final File avatarFile, + final byte[] avatarFile, final Integer expirationTimer, final Boolean isAnnouncementGroup ) throws IOException { @@ -716,8 +715,7 @@ public class GroupHelper { var groupGroupChangePair = groupV2Helper.updateGroup(group, name, description, avatarFile); if (avatarFile != null) { context.getAvatarStore() - .storeGroupAvatar(group.getGroupId(), - outputStream -> IOUtils.copyFileToStream(avatarFile, outputStream)); + .storeGroupAvatar(group.getGroupId(), outputStream -> outputStream.write(avatarFile)); } result = sendUpdateGroupV2Message(group, groupGroupChangePair.first(), groupGroupChangePair.second()); } @@ -819,4 +817,13 @@ public class GroupHelper { account.getRecipientAddressResolver())) .toList()); } + + private byte[] readAvatarBytes(final String avatarFile) throws IOException { + if (avatarFile == null) { + return null; + } + try (final var avatar = Utils.createStreamDetails(avatarFile).first().getStream()) { + return IOUtils.readFully(avatar); + } + } } diff --git a/lib/src/main/java/org/asamk/signal/manager/helper/GroupV2Helper.java b/lib/src/main/java/org/asamk/signal/manager/helper/GroupV2Helper.java index 84747e9c..e410d232 100644 --- a/lib/src/main/java/org/asamk/signal/manager/helper/GroupV2Helper.java +++ b/lib/src/main/java/org/asamk/signal/manager/helper/GroupV2Helper.java @@ -12,7 +12,6 @@ import org.asamk.signal.manager.groups.GroupUtils; import org.asamk.signal.manager.groups.NotAGroupMemberException; import org.asamk.signal.manager.storage.groups.GroupInfoV2; import org.asamk.signal.manager.storage.recipients.RecipientId; -import org.asamk.signal.manager.util.IOUtils; import org.asamk.signal.manager.util.Utils; import org.signal.libsignal.zkgroup.InvalidInputException; import org.signal.libsignal.zkgroup.VerificationFailedException; @@ -47,10 +46,7 @@ import org.whispersystems.signalservice.api.push.SignalServiceAddress; import org.whispersystems.signalservice.api.push.exceptions.NonSuccessfulResponseCodeException; import org.whispersystems.signalservice.api.util.UuidUtil; -import java.io.File; -import java.io.FileInputStream; import java.io.IOException; -import java.io.InputStream; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; @@ -138,10 +134,9 @@ class GroupV2Helper { } Pair createGroup( - String name, Set members, File avatarFile - ) throws IOException { - final var avatarBytes = readAvatarBytes(avatarFile); - final var newGroup = buildNewGroup(name, members, avatarBytes); + String name, Set members, byte[] avatarFile + ) { + final var newGroup = buildNewGroup(name, members, avatarFile); if (newGroup == null) { return null; } @@ -170,14 +165,6 @@ class GroupV2Helper { return new Pair<>(g, decryptedGroup); } - private byte[] readAvatarBytes(final File avatarFile) throws IOException { - final byte[] avatarBytes; - try (InputStream avatar = avatarFile == null ? null : new FileInputStream(avatarFile)) { - avatarBytes = avatar == null ? null : IOUtils.readFully(avatar); - } - return avatarBytes; - } - private GroupsV2Operations.NewGroup buildNewGroup( String name, Set members, byte[] avatar ) { @@ -210,7 +197,7 @@ class GroupV2Helper { } Pair updateGroup( - GroupInfoV2 groupInfoV2, String name, String description, File avatarFile + GroupInfoV2 groupInfoV2, String name, String description, byte[] avatarFile ) throws IOException { final var groupSecretParams = GroupSecretParams.deriveFromMasterKey(groupInfoV2.getMasterKey()); var groupOperations = dependencies.getGroupsV2Operations().forGroup(groupSecretParams); @@ -222,9 +209,8 @@ class GroupV2Helper { } if (avatarFile != null) { - final var avatarBytes = readAvatarBytes(avatarFile); var avatarCdnKey = dependencies.getGroupsV2Api() - .uploadAvatar(avatarBytes, groupSecretParams, getGroupAuthForToday(groupSecretParams)); + .uploadAvatar(avatarFile, groupSecretParams, getGroupAuthForToday(groupSecretParams)); change.setModifyAvatar(GroupChange.Actions.ModifyAvatarAction.newBuilder().setAvatar(avatarCdnKey)); } diff --git a/lib/src/main/java/org/asamk/signal/manager/helper/ProfileHelper.java b/lib/src/main/java/org/asamk/signal/manager/helper/ProfileHelper.java index 8113000b..da7440ed 100644 --- a/lib/src/main/java/org/asamk/signal/manager/helper/ProfileHelper.java +++ b/lib/src/main/java/org/asamk/signal/manager/helper/ProfileHelper.java @@ -30,7 +30,6 @@ import org.whispersystems.signalservice.api.push.exceptions.PushNetworkException import org.whispersystems.signalservice.api.services.ProfileService; import org.whispersystems.signalservice.api.util.ExpiringProfileCredentialUtil; -import java.io.File; import java.io.IOException; import java.io.OutputStream; import java.nio.file.Files; @@ -146,7 +145,7 @@ public final class ProfileHelper { final String familyName, String about, String aboutEmoji, - Optional avatar, + Optional avatar, byte[] mobileCoinAddress ) throws IOException { setProfile(true, false, givenName, familyName, about, aboutEmoji, avatar, mobileCoinAddress); @@ -159,7 +158,7 @@ public final class ProfileHelper { final String familyName, String about, String aboutEmoji, - Optional avatar, + Optional avatar, byte[] mobileCoinAddress ) throws IOException { var profile = getSelfProfile(); @@ -183,7 +182,8 @@ public final class ProfileHelper { if (uploadProfile) { final var streamDetails = avatar != null && avatar.isPresent() - ? Utils.createStreamDetailsFromFile(avatar.get()) + ? Utils.createStreamDetails(avatar.get()) + .first() : forceUploadAvatar && avatar == null ? context.getAvatarStore() .retrieveProfileAvatar(account.getSelfRecipientAddress()) : null; try (streamDetails) { @@ -212,9 +212,10 @@ public final class ProfileHelper { if (avatar != null) { if (avatar.isPresent()) { + final var streamDetails = Utils.createStreamDetails(avatar.get()).first(); context.getAvatarStore() .storeProfileAvatar(account.getSelfRecipientAddress(), - outputStream -> IOUtils.copyFileToStream(avatar.get(), outputStream)); + outputStream -> IOUtils.copyStream(streamDetails.getStream(), outputStream)); } else { context.getAvatarStore().deleteProfileAvatar(account.getSelfRecipientAddress()); } diff --git a/src/main/java/org/asamk/signal/commands/UpdateGroupCommand.java b/src/main/java/org/asamk/signal/commands/UpdateGroupCommand.java index 411ae747..14ad14f8 100644 --- a/src/main/java/org/asamk/signal/commands/UpdateGroupCommand.java +++ b/src/main/java/org/asamk/signal/commands/UpdateGroupCommand.java @@ -26,7 +26,6 @@ import org.asamk.signal.util.SendMessageResultUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.io.File; import java.io.IOException; import java.util.HashMap; import java.util.stream.Stream; @@ -131,9 +130,7 @@ public class UpdateGroupCommand implements JsonRpcLocalCommand { SendGroupMessageResults groupMessageResults = null; if (groupId == null) { isNewGroup = true; - var results = m.createGroup(groupName, - groupMembers, - groupAvatar == null ? null : new File(groupAvatar)); + var results = m.createGroup(groupName, groupMembers, groupAvatar); groupMessageResults = results.second(); groupId = results.first(); groupName = null; @@ -155,7 +152,7 @@ public class UpdateGroupCommand implements JsonRpcLocalCommand { .withGroupLinkState(groupLinkState) .withAddMemberPermission(groupAddMemberPermission) .withEditDetailsPermission(groupEditDetailsPermission) - .withAvatarFile(groupAvatar == null ? null : new File(groupAvatar)) + .withAvatarFile(groupAvatar) .withExpirationTimer(groupExpiration) .withIsAnnouncementGroup(groupSendMessagesPermission == null ? null diff --git a/src/main/java/org/asamk/signal/commands/UpdateProfileCommand.java b/src/main/java/org/asamk/signal/commands/UpdateProfileCommand.java index d8e87430..cea2da0d 100644 --- a/src/main/java/org/asamk/signal/commands/UpdateProfileCommand.java +++ b/src/main/java/org/asamk/signal/commands/UpdateProfileCommand.java @@ -10,7 +10,6 @@ import org.asamk.signal.manager.Manager; import org.asamk.signal.manager.api.UpdateProfile; import org.asamk.signal.output.OutputWriter; -import java.io.File; import java.io.IOException; import java.util.Base64; @@ -50,7 +49,7 @@ public class UpdateProfileCommand implements JsonRpcLocalCommand { var avatarPath = ns.getString("avatar"); boolean removeAvatar = Boolean.TRUE.equals(ns.getBoolean("remove-avatar")); - File avatarFile = removeAvatar || avatarPath == null ? null : new File(avatarPath); + String avatarFile = removeAvatar || avatarPath == null ? null : avatarPath; try { m.updateProfile(UpdateProfile.newBuilder() diff --git a/src/main/java/org/asamk/signal/dbus/DbusManagerImpl.java b/src/main/java/org/asamk/signal/dbus/DbusManagerImpl.java index c0dc1cf8..d2a08131 100644 --- a/src/main/java/org/asamk/signal/dbus/DbusManagerImpl.java +++ b/src/main/java/org/asamk/signal/dbus/DbusManagerImpl.java @@ -147,7 +147,7 @@ public class DbusManagerImpl implements Manager { emptyIfNull(updateProfile.getFamilyName()), emptyIfNull(updateProfile.getAbout()), emptyIfNull(updateProfile.getAboutEmoji()), - updateProfile.getAvatar() == null ? "" : updateProfile.getAvatar().getPath(), + updateProfile.getAvatar() == null ? "" : updateProfile.getAvatar(), updateProfile.isDeleteAvatar()); } @@ -231,11 +231,11 @@ public class DbusManagerImpl implements Manager { @Override public Pair createGroup( - final String name, final Set members, final File avatarFile + final String name, final Set members, final String avatarFile ) throws IOException, AttachmentInvalidException { final var newGroupId = signal.createGroup(emptyIfNull(name), members.stream().map(RecipientIdentifier.Single::getIdentifier).toList(), - avatarFile == null ? "" : avatarFile.getPath()); + avatarFile == null ? "" : avatarFile); return new Pair<>(GroupId.unknownVersion(newGroupId), new SendGroupMessageResults(0, List.of())); } @@ -253,7 +253,7 @@ public class DbusManagerImpl implements Manager { if (updateGroup.getAvatarFile() != null) { group.Set("org.asamk.Signal.Group", "Avatar", - updateGroup.getAvatarFile() == null ? "" : updateGroup.getAvatarFile().getPath()); + updateGroup.getAvatarFile() == null ? "" : updateGroup.getAvatarFile()); } if (updateGroup.getExpirationTimer() != null) { group.Set("org.asamk.Signal.Group", "MessageExpirationTimer", updateGroup.getExpirationTimer()); diff --git a/src/main/java/org/asamk/signal/dbus/DbusSignalImpl.java b/src/main/java/org/asamk/signal/dbus/DbusSignalImpl.java index 1a9118be..2c0c30b2 100644 --- a/src/main/java/org/asamk/signal/dbus/DbusSignalImpl.java +++ b/src/main/java/org/asamk/signal/dbus/DbusSignalImpl.java @@ -618,7 +618,7 @@ public class DbusSignalImpl implements Signal { avatar = nullIfEmpty(avatar); final var memberIdentifiers = getSingleRecipientIdentifiers(members, m.getSelfNumber()); if (groupId == null) { - final var results = m.createGroup(name, memberIdentifiers, avatar == null ? null : new File(avatar)); + final var results = m.createGroup(name, memberIdentifiers, avatar); updateGroups(); checkGroupSendMessageResults(results.second().timestamp(), results.second().results()); return results.first().serialize(); @@ -627,7 +627,7 @@ public class DbusSignalImpl implements Signal { UpdateGroup.newBuilder() .withName(name) .withMembers(memberIdentifiers) - .withAvatarFile(avatar == null ? null : new File(avatar)) + .withAvatarFile(avatar) .build()); if (results != null) { checkGroupSendMessageResults(results.timestamp(), results.results()); @@ -687,7 +687,7 @@ public class DbusSignalImpl implements Signal { about = nullIfEmpty(about); aboutEmoji = nullIfEmpty(aboutEmoji); avatarPath = nullIfEmpty(avatarPath); - File avatarFile = removeAvatar || avatarPath == null ? null : new File(avatarPath); + final var avatarFile = removeAvatar || avatarPath == null ? null : avatarPath; m.updateProfile(UpdateProfile.newBuilder() .withGivenName(givenName) .withFamilyName(familyName) @@ -1270,7 +1270,7 @@ public class DbusSignalImpl implements Signal { } private void setGroupAvatar(final String avatar) { - updateGroup(UpdateGroup.newBuilder().withAvatarFile(new File(avatar)).build()); + updateGroup(UpdateGroup.newBuilder().withAvatarFile(avatar).build()); } private void setMessageExpirationTime(final int expirationTime) { -- 2.51.0 From b6e9dfa97d64fe60be9405f9f714d10eece9673f Mon Sep 17 00:00:00 2001 From: AsamK Date: Sun, 20 Nov 2022 11:27:33 +0100 Subject: [PATCH 14/16] Add fallback locale for voice verification Fixes #1101 --- .../asamk/signal/manager/util/NumberVerificationUtils.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/src/main/java/org/asamk/signal/manager/util/NumberVerificationUtils.java b/lib/src/main/java/org/asamk/signal/manager/util/NumberVerificationUtils.java index 9dcee1fb..280c9fbc 100644 --- a/lib/src/main/java/org/asamk/signal/manager/util/NumberVerificationUtils.java +++ b/lib/src/main/java/org/asamk/signal/manager/util/NumberVerificationUtils.java @@ -15,6 +15,7 @@ import org.whispersystems.signalservice.internal.push.RequestVerificationCodeRes import org.whispersystems.signalservice.internal.push.VerifyAccountResponse; import java.io.IOException; +import java.util.Locale; import java.util.Optional; public class NumberVerificationUtils { @@ -23,10 +24,9 @@ public class NumberVerificationUtils { SignalServiceAccountManager accountManager, String captcha, boolean voiceVerification ) throws IOException, CaptchaRequiredException, NonNormalizedPhoneNumberException { captcha = captcha == null ? null : captcha.replace("signalcaptcha://", ""); - final ServiceResponse response; if (voiceVerification) { - response = accountManager.requestVoiceVerificationCode(Utils.getDefaultLocale(null), + response = accountManager.requestVoiceVerificationCode(Utils.getDefaultLocale(Locale.US), Optional.ofNullable(captcha), Optional.empty(), Optional.empty()); -- 2.51.0 From 3e60303b90ff8c7c6fad390b45bc9338afdfb1c6 Mon Sep 17 00:00:00 2001 From: ced-b Date: Tue, 22 Nov 2022 01:58:34 -0500 Subject: [PATCH 15/16] Add alive check (#1107) Adds a simple HTTP endpoint that can be used by the container environment to see if the app is started and available. Co-authored-by: cedb --- .../org/asamk/signal/http/HttpServerHandler.java | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/main/java/org/asamk/signal/http/HttpServerHandler.java b/src/main/java/org/asamk/signal/http/HttpServerHandler.java index f7f2768d..a6213929 100644 --- a/src/main/java/org/asamk/signal/http/HttpServerHandler.java +++ b/src/main/java/org/asamk/signal/http/HttpServerHandler.java @@ -59,6 +59,7 @@ public class HttpServerHandler { server.createContext("/api/v1/rpc", this::handleRpcEndpoint); server.createContext("/api/v1/events", this::handleEventsEndpoint); + server.createContext("/api/v1/check", this::handleCheckEndpoint); server.start(); } @@ -186,6 +187,19 @@ public class HttpServerHandler { } } + private void handleCheckEndpoint(HttpExchange httpExchange) throws IOException { + if (!"/api/v1/check".equals(httpExchange.getRequestURI().getPath())) { + sendResponse(404, null, httpExchange); + return; + } + if (!"GET".equals(httpExchange.getRequestMethod())) { + sendResponse(405, null, httpExchange); + return; + } + + sendResponse(200, null, httpExchange); + } + private List getManagerFromQuery(final Map query) { List managers; if (m != null) { -- 2.51.0 From 35def4445d13011f4feb9f6422546b88ce32bda0 Mon Sep 17 00:00:00 2001 From: ced-b Date: Thu, 24 Nov 2022 11:29:45 -0500 Subject: [PATCH 16/16] Fix handling of attachments in JSON RPC (#1109) * Fix handling of attachments in JSON RPC It turns out that using a custom serializer on an input stream did not work well. For one the stream seems to be getting closed before the JSON gets written. But also the method for writing it was throwing an UnsupportedOperationException further down in Jackson. The above simplifies the matter by simply outputting the Base64 string first and then setting it on the model. * Add missing files to attachment fix Co-authored-by: cedb --- .../signal/commands/GetAttachmentCommand.java | 6 +++--- .../asamk/signal/json/JsonAttachmentData.java | 6 +----- .../signal/json/JsonStreamSerializer.java | 18 ------------------ 3 files changed, 4 insertions(+), 26 deletions(-) delete mode 100644 src/main/java/org/asamk/signal/json/JsonStreamSerializer.java diff --git a/src/main/java/org/asamk/signal/commands/GetAttachmentCommand.java b/src/main/java/org/asamk/signal/commands/GetAttachmentCommand.java index 6deed90a..f2f0450c 100644 --- a/src/main/java/org/asamk/signal/commands/GetAttachmentCommand.java +++ b/src/main/java/org/asamk/signal/commands/GetAttachmentCommand.java @@ -40,12 +40,12 @@ public class GetAttachmentCommand implements JsonRpcLocalCommand { final var id = ns.getString("id"); try (InputStream attachment = m.retrieveAttachment(id)) { + final var bytes = attachment.readAllBytes(); + final var base64 = Base64.getEncoder().encodeToString(bytes); if (outputWriter instanceof PlainTextWriter writer) { - final var bytes = attachment.readAllBytes(); - final var base64 = Base64.getEncoder().encodeToString(bytes); writer.println(base64); } else if (outputWriter instanceof JsonWriter writer) { - writer.write(new JsonAttachmentData(attachment)); + writer.write(new JsonAttachmentData(base64)); } } catch (FileNotFoundException ex) { throw new UserErrorException("Could not find attachment with ID: " + id, ex); diff --git a/src/main/java/org/asamk/signal/json/JsonAttachmentData.java b/src/main/java/org/asamk/signal/json/JsonAttachmentData.java index fd759674..05a90c21 100644 --- a/src/main/java/org/asamk/signal/json/JsonAttachmentData.java +++ b/src/main/java/org/asamk/signal/json/JsonAttachmentData.java @@ -1,9 +1,5 @@ package org.asamk.signal.json; -import com.fasterxml.jackson.databind.annotation.JsonSerialize; - -import java.io.InputStream; - public record JsonAttachmentData( - @JsonSerialize(using = JsonStreamSerializer.class) InputStream data + String data ) {} diff --git a/src/main/java/org/asamk/signal/json/JsonStreamSerializer.java b/src/main/java/org/asamk/signal/json/JsonStreamSerializer.java deleted file mode 100644 index 4cd2797e..00000000 --- a/src/main/java/org/asamk/signal/json/JsonStreamSerializer.java +++ /dev/null @@ -1,18 +0,0 @@ -package org.asamk.signal.json; - -import com.fasterxml.jackson.core.JsonGenerator; -import com.fasterxml.jackson.databind.JsonSerializer; -import com.fasterxml.jackson.databind.SerializerProvider; - -import java.io.IOException; -import java.io.InputStream; - -public class JsonStreamSerializer extends JsonSerializer { - - @Override - public void serialize( - final InputStream value, final JsonGenerator jsonGenerator, final SerializerProvider serializers - ) throws IOException { - jsonGenerator.writeBinary(value, -1); - } -} -- 2.51.0