Skip to content

Conversation

@LyzardKing
Copy link
Collaborator

@LyzardKing LyzardKing commented Jan 7, 2026

User description

This replaces #14817

This PR adds a websocket for remote communication. Specifically to link with browsers (replacing the problematic native messaging).

A prototype extension is available at: https://github.com/LyzardKing/JabRef-Connector
Updating the old one required too many changes, so the new version exists (for now).

Steps to test

Running Jabref, in the General Settings page enable the HTTP server
The default port is 23119 (as before) and is the same in the extension.

Steps to test

Mandatory checks

  • I own the copyright of the code submitted and I license it under the MIT license
  • I manually tested my changes in running JabRef (always required)
  • I added JUnit tests for changes (if applicable)
  • [/] I added screenshots in the PR description (if change is visible to the user)
  • [/] I described the change in CHANGELOG.md in a way that is understandable for the average user (if change is visible to the user)
  • [/] I checked the user documentation: Is the information available and up to date? If not, I created an issue at https://github.com/JabRef/user-documentation/issues or, even better, I submitted a pull request updating file(s) in https://github.com/JabRef/user-documentation/tree/main/en.

PR Type

Enhancement


Description

  • Add WebSocket server support for remote browser communication

  • Implement JSON-based command protocol (ping, focus, open, add)

  • Register RemoteMessageHandler in ServiceLocator for WebSocket handlers

  • Add comprehensive WebSocket documentation and unit tests


Diagram Walkthrough

flowchart LR
  GUI["GUI/CLI"] -->|"passes RemoteMessageHandler"| HSM["HttpServerManager"]
  HSM -->|"forwards handler"| HST["HttpServerThread"]
  HST -->|"passes to Server.run()"| SRV["Server"]
  SRV -->|"registers in ServiceLocator"| WS["JabRefWebSocketApp"]
  WS -->|"delegates commands"| RMH["RemoteMessageHandler"]
  RMH -->|"executes"| CMD["handleFocus/handleCommandLineArguments"]
Loading

File Walkthrough

Relevant files
Enhancement
6 files
JabRefGUI.java
Pass CLI message handler to HTTP server                                   
+1/-1     
GeneralTabViewModel.java
Pass message handler when starting HTTP server                     
+1/-1     
HttpServerManager.java
Accept and forward RemoteMessageHandler parameter               
+3/-2     
HttpServerThread.java
Store and pass RemoteMessageHandler to Server                       
+5/-2     
Server.java
Register WebSocket add-on and handler in ServiceLocator   
+64/-4   
JabRefWebSocketApp.java
Implement WebSocket application with command handlers       
+122/-0 
Dependencies
2 files
module-info.java
Add WebSocket and servlet module dependencies                       
+2/-0     
build.gradle.kts
Add Grizzly WebSocket and Jakarta Servlet dependencies     
+2/-0     
Tests
1 files
JabRefWebSocketAppTest.java
Add unit tests for WebSocket message handling                       
+63/-0   
Documentation
1 files
jabsrv-websocket.md
Document WebSocket endpoint and usage patterns                     
+65/-0   

@qodo-free-for-open-source-projects
Copy link
Contributor

qodo-free-for-open-source-projects bot commented Jan 7, 2026

PR Compliance Guide 🔍

(Compliance updated until commit 08ee138)

Below is a summary of compliance checks for this PR:

Security Compliance
🔴
Unauthenticated remote command execution

Description: The WebSocket handler accepts arbitrary file paths and BibTeX content from remote clients
without authentication, authorization, or input validation, allowing potential path
traversal attacks, arbitrary file access, and injection of malicious BibTeX entries that
could exploit parsing vulnerabilities.
JabRefWebSocketApp.java [78-86]

Referred Code
    if (argument != null && !argument.isEmpty() && handler != null) {
        handler.handleCommandLineArguments(new String[] {"--import", argument});
        return "{\"status\":\"success\",\"response\":\"opened\"}";
    }
    return "{\"status\":\"error\",\"message\":\"No file specified\"}";
case "add":
    if (argument != null && !argument.isEmpty() && handler != null) {
        handler.handleCommandLineArguments(new String[] {"--importBibtex", argument});
        return "{\"status\":\"success\",\"response\":\"added\"}";
Missing WebSocket authentication

Description: The WebSocket endpoint is registered with an empty context path and no authentication
mechanism, making it accessible to any client that can reach the HTTP server port,
enabling unauthorized remote control of the application.
Server.java [175-175]

Referred Code
WebSocketEngine.getEngine().register("", "/ws", new org.jabref.http.server.ws.JabRefWebSocketApp(serviceLocator));
Insufficient error handling

Description: JSON parsing errors are caught broadly and logged with debug level, potentially hiding
malformed or malicious payloads that could be used to probe the system or cause denial of
service through repeated parsing failures.
JabRefWebSocketApp.java [98-112]

Referred Code
private @Nullable String extractJsonValue(String json, String key) {
    try {
        JsonElement parsed = JsonParser.parseString(json);
        if (!parsed.isJsonObject()) {
            return null;
        }
        JsonObject obj = parsed.getAsJsonObject();
        if (!obj.has(key) || obj.get(key).isJsonNull()) {
            return null;
        }
        return obj.get(key).getAsString();
    } catch (Exception e) {
        LOGGER.debug("Failed to parse JSON in WebSocket message: {}", e.toString());
        return null;
    }
Ticket Compliance
🎫 No ticket provided
  • Create ticket/issue
Codebase Duplication Compliance
Codebase context is not defined

Follow the guide to enable codebase context checks.

Custom Compliance
🟢
Generic: Comprehensive Audit Trails

Objective: To create a detailed and reliable record of critical system actions for security analysis
and compliance.

Status: Passed

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Meaningful Naming and Self-Documenting Code

Objective: Ensure all identifiers clearly express their purpose and intent, making code
self-documenting

Status: Passed

Learn more about managing compliance generic rules or creating your own custom rules

🔴
Generic: Robust Error Handling and Edge Case Management

Objective: Ensure comprehensive error handling that provides meaningful context and graceful
degradation

Status:
Missing null validation: The handleTextMessage method does not validate if handler is null before calling methods
on it in the switch cases, which could lead to NullPointerException.

Referred Code
    if (handler != null) {
        handler.handleFocus();
    }
    return "{\"status\":\"success\",\"response\":\"focused\"}";
case "open":
    if (argument != null && !argument.isEmpty() && handler != null) {
        handler.handleCommandLineArguments(new String[] {"--import", argument});
        return "{\"status\":\"success\",\"response\":\"opened\"}";
    }
    return "{\"status\":\"error\",\"message\":\"No file specified\"}";
case "add":
    if (argument != null && !argument.isEmpty() && handler != null) {
        handler.handleCommandLineArguments(new String[] {"--importBibtex", argument});
        return "{\"status\":\"success\",\"response\":\"added\"}";

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Secure Error Handling

Objective: To prevent the leakage of sensitive system information through error messages while
providing sufficient detail for internal debugging.

Status:
Error exposes command: The error response at line 90 includes the user-supplied command value in the error
message, which could expose internal command structure to clients.

Referred Code
        return "{\"status\":\"error\",\"message\":\"Unknown command: " + command + "\"}";
}

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Secure Logging Practices

Objective: To ensure logs are useful for debugging and auditing without exposing sensitive
information like PII, PHI, or cardholder data.

Status:
Logs user input: The code logs the full WebSocket message text at line 38, which may contain sensitive user
data such as file paths or BibTeX entries with personal information.

Referred Code
LOGGER.debug("WS received text: {}", text);

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Security-First Input Validation and Data Handling

Objective: Ensure all data inputs are validated, sanitized, and handled securely to prevent
vulnerabilities

Status:
Missing input sanitization: The argument value extracted from JSON is passed directly to handleCommandLineArguments
without validation or sanitization, potentially allowing command injection or path
traversal attacks.

Referred Code
    if (argument != null && !argument.isEmpty() && handler != null) {
        handler.handleCommandLineArguments(new String[] {"--import", argument});
        return "{\"status\":\"success\",\"response\":\"opened\"}";
    }
    return "{\"status\":\"error\",\"message\":\"No file specified\"}";
case "add":
    if (argument != null && !argument.isEmpty() && handler != null) {
        handler.handleCommandLineArguments(new String[] {"--importBibtex", argument});
        return "{\"status\":\"success\",\"response\":\"added\"}";

Learn more about managing compliance generic rules or creating your own custom rules

  • Update
Compliance status legend 🟢 - Fully Compliant
🟡 - Partial Compliant
🔴 - Not Compliant
⚪ - Requires Further Human Verification
🏷️ - Compliance label

Previous compliance checks

Compliance check up to commit c71f355
Security Compliance
🔴
Unauthenticated remote command execution

Description: The WebSocket handler accepts arbitrary file paths and BibTeX content from remote clients
without authentication or validation, allowing unauthenticated users to import malicious
files or inject crafted BibTeX entries that could exploit parsing vulnerabilities or
trigger unintended application behavior.
JabRefWebSocketApp.java [86-96]

Referred Code
    if (argument != null && !argument.isEmpty() && handler != null) {
        handler.handleCommandLineArguments(new String[]{"--import", argument});
        return "{\"status\":\"success\",\"response\":\"opened\"}";
    }
    return "{\"status\":\"error\",\"message\":\"No file specified\"}";
case "add":
    if (argument != null && !argument.isEmpty() && handler != null) {
        handler.handleCommandLineArguments(new String[]{"--importBibtex", argument});
        return "{\"status\":\"success\",\"response\":\"added\"}";
    }
    return "{\"status\":\"error\",\"message\":\"No BibTeX entry specified.\"}";
Path traversal vulnerability

Description: The open command constructs file paths directly from user input without sanitization,
enabling path traversal attacks where malicious clients could access sensitive files
outside intended directories by using sequences like '../../../etc/passwd'.
JabRefWebSocketApp.java [86-88]

Referred Code
if (argument != null && !argument.isEmpty() && handler != null) {
    handler.handleCommandLineArguments(new String[]{"--import", argument});
    return "{\"status\":\"success\",\"response\":\"opened\"}";
Missing WebSocket authentication

Description: The WebSocket endpoint is registered at '/ws' without any authentication mechanism, CORS
restrictions, or origin validation, allowing any web page or client to establish
connections and send commands to the local JabRef instance.
Server.java [165-173]

Referred Code
// Create server without starting so we can attach add-ons before listeners bind
HttpServer server = GrizzlyHttpServerFactory.createHttpServer(uri, resourceConfig, /* start */ false);

// Attach WebSocket add-on to each network listener and register WS application at /ws
for (NetworkListener listener : server.getListeners()) {
    listener.registerAddOn(new WebSocketAddOn());
}

WebSocketEngine.getEngine().register("", "/ws", new org.jabref.http.server.ws.JabRefWebSocketApp(serviceLocator));
Information disclosure via errors

Description: JSON parsing errors are caught and logged but the error details from exceptions could leak
information about the server's internal state or file system structure through error
messages returned to untrusted clients.
JabRefWebSocketApp.java [106-121]

Referred Code
private @Nullable String extractJsonValue(String json, String key) {
    try {
        JsonElement parsed = JsonParser.parseString(json);
        if (!parsed.isJsonObject()) {
            return null;
        }
        JsonObject obj = parsed.getAsJsonObject();
        if (!obj.has(key) || obj.get(key).isJsonNull()) {
            return null;
        }
        return obj.get(key).getAsString();
    } catch (Exception e) {
        LOGGER.debug("Failed to parse JSON in WebSocket message: {}", e.toString());
        return null;
    }
}
Ticket Compliance
🎫 No ticket provided
  • Create ticket/issue
Codebase Duplication Compliance
Codebase context is not defined

Follow the guide to enable codebase context checks.

Custom Compliance
🟢
Generic: Comprehensive Audit Trails

Objective: To create a detailed and reliable record of critical system actions for security analysis
and compliance.

Status: Passed

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Meaningful Naming and Self-Documenting Code

Objective: Ensure all identifiers clearly express their purpose and intent, making code
self-documenting

Status: Passed

Learn more about managing compliance generic rules or creating your own custom rules

🔴
Generic: Robust Error Handling and Edge Case Management

Objective: Ensure comprehensive error handling that provides meaningful context and graceful
degradation

Status:
Missing null validation: The handleTextMessage method checks if handler is null before calling methods, but does
not provide meaningful error responses when handler is unavailable for critical operations

Referred Code
    if (handler != null) {
        handler.handleFocus();
    }
    return "{\"status\":\"success\",\"response\":\"focused\"}";
case "open":
    if (argument != null && !argument.isEmpty() && handler != null) {
        handler.handleCommandLineArguments(new String[]{"--import", argument});
        return "{\"status\":\"success\",\"response\":\"opened\"}";
    }
    return "{\"status\":\"error\",\"message\":\"No file specified\"}";
case "add":
    if (argument != null && !argument.isEmpty() && handler != null) {
        handler.handleCommandLineArguments(new String[]{"--importBibtex", argument});
        return "{\"status\":\"success\",\"response\":\"added\"}";
    }
    return "{\"status\":\"error\",\"message\":\"No BibTeX entry specified.\"}";

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Secure Error Handling

Objective: To prevent the leakage of sensitive system information through error messages while
providing sufficient detail for internal debugging.

Status:
Command exposure in error: The error message at line 98 exposes the unknown command value directly to the client,
potentially revealing internal command structure

Referred Code
        return "{\"status\":\"error\",\"message\":\"Unknown command: " + command + "\"}";
}

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Security-First Input Validation and Data Handling

Objective: Ensure all data inputs are validated, sanitized, and handled securely to prevent
vulnerabilities

Status:
Missing input sanitization: The argument values from WebSocket messages are passed directly to
handleCommandLineArguments without validation or sanitization for path traversal or
command injection

Referred Code
    if (argument != null && !argument.isEmpty() && handler != null) {
        handler.handleCommandLineArguments(new String[]{"--import", argument});
        return "{\"status\":\"success\",\"response\":\"opened\"}";
    }
    return "{\"status\":\"error\",\"message\":\"No file specified\"}";
case "add":
    if (argument != null && !argument.isEmpty() && handler != null) {
        handler.handleCommandLineArguments(new String[]{"--importBibtex", argument});
        return "{\"status\":\"success\",\"response\":\"added\"}";

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Secure Logging Practices

Objective: To ensure logs are useful for debugging and auditing without exposing sensitive
information like PII, PHI, or cardholder data.

Status:
Potential sensitive data logging: The argument parameter is logged at line 73 which may contain file paths or BibTeX entries
that could include sensitive user data

Referred Code
}

Learn more about managing compliance generic rules or creating your own custom rules

@qodo-free-for-open-source-projects
Copy link
Contributor

qodo-free-for-open-source-projects bot commented Jan 7, 2026

PR Code Suggestions ✨

Explore these optional code suggestions:

CategorySuggestion                                                                                                                                    Impact
High-level
Implement security for WebSocket endpoint

The new WebSocket endpoint lacks authentication, posing a security risk.
Implement an origin check or token-based authentication to restrict access to
trusted clients only.

Examples:

jabsrv/src/main/java/org/jabref/http/server/ws/JabRefWebSocketApp.java [27-52]
    @Override
    public void onConnect(WebSocket socket) {
        LOGGER.debug("WebSocket connected: {}", socket);
        if (handler == null) {
            handler = serviceLocator.getService(RemoteMessageHandler.class);
            if (handler == null) {
                LOGGER.warn("RemoteMessageHandler not available in ServiceLocator");
            }
        }
    }

 ... (clipped 16 lines)

Solution Walkthrough:

Before:

// in JabRefWebSocketApp.java
public class JabRefWebSocketApp extends WebSocketApplication {
    @Override
    public void onConnect(WebSocket socket) {
        LOGGER.debug("WebSocket connected: {}", socket);
        // No authentication or origin check is performed.
    }

    @Override
    public void onMessage(WebSocket socket, String text) {
        LOGGER.debug("WS received text: {}", text);
        // Any connected client can send commands.
        String response = handleTextMessage(text, handler);
        if (response != null) {
            socket.send(response);
        }
    }
    // ...
}

After:

// in JabRefWebSocketApp.java
public class JabRefWebSocketApp extends WebSocketApplication {
    private static final List<String> ALLOWED_ORIGINS = List.of("chrome-extension://...", "moz-extension://...");

    @Override
    public boolean onUpgrade(GrizzlyRequest request, String[] subProtocols) {
        String origin = request.getHeader("Origin");
        if (!isOriginAllowed(origin)) {
            LOGGER.warn("WebSocket connection from untrusted origin rejected: {}", origin);
            return false; // Reject the connection
        }
        return super.onUpgrade(request, subProtocols);
    }

    @Override
    public void onConnect(WebSocket socket) {
        LOGGER.debug("WebSocket connected from trusted origin: {}", socket);
    }
    // ...
}
Suggestion importance[1-10]: 9

__

Why: The suggestion correctly identifies a critical security vulnerability (unauthenticated command execution) in the new WebSocket endpoint, which has a significant impact on the application's safety.

High
Possible issue
Isolate WebSocket engine registrations
Suggestion Impact:The commit directly implements the suggestion by adding the exact code changes proposed: clearing previous registrations from the static WebSocketEngine before registering a new application, including the suggested comment explaining the purpose.

code diff:

+        // Clear previous registrations from the static engine to ensure isolation
+        WebSocketEngine.getEngine().unregisterAll();

Clear previous registrations from the static WebSocketEngine before registering
a new application to ensure isolation between server instances.

jabsrv/src/main/java/org/jabref/http/server/Server.java [122-184]

     private HttpServer startServer(ServiceLocator serviceLocator, URI uri) {
 ...
         // Create server without starting so we can attach add-ons before listeners bind
         HttpServer server = GrizzlyHttpServerFactory.createHttpServer(uri, resourceConfig, /* start */ false);
 
         // Attach WebSocket add-on to each network listener and register WS application at /ws
         for (NetworkListener listener : server.getListeners()) {
             listener.registerAddOn(new WebSocketAddOn());
         }
 
+        // Clear previous registrations from the static engine to ensure isolation
+        WebSocketEngine.getEngine().unregisterAll();
         WebSocketEngine.getEngine().register("", "/ws", new org.jabref.http.server.ws.JabRefWebSocketApp(serviceLocator));
 
         // Now start the server
         try {
             server.start();
         } catch (Exception e) {
             LOGGER.error("Failed to start HTTP server", e);
             throw new RuntimeException(e);
         }
 
         return server;
     }

[To ensure code accuracy, apply this suggestion manually]

Suggestion importance[1-10]: 7

__

Why: The suggestion correctly identifies a potential issue with the static WebSocketEngine causing state leakage between server instances, and proposes a valid fix to improve robustness, especially in testing scenarios.

Medium
General
Avoid repeated service locator lookups
Suggestion Impact:The commit implements the suggestion by moving the RemoteMessageHandler initialization to the constructor, making it final, and removing the repeated ServiceLocator lookups from onConnect() and onMessage() methods. The handler is now fetched once and reused throughout the lifecycle.

code diff:

-    private RemoteMessageHandler handler;
+    private final RemoteMessageHandler handler;
 
     public JabRefWebSocketApp(ServiceLocator serviceLocator) {
-        this.serviceLocator = serviceLocator;
+        this.handler = serviceLocator.getService(RemoteMessageHandler.class);
+        if (this.handler == null) {
+            LOGGER.warn("RemoteMessageHandler not available in ServiceLocator");
+        }
     }
 
     @Override
     public void onConnect(WebSocket socket) {
         LOGGER.debug("WebSocket connected: {}", socket);
-        if (handler == null) {
-            handler = serviceLocator.getService(RemoteMessageHandler.class);
-            if (handler == null) {
-                LOGGER.warn("RemoteMessageHandler not available in ServiceLocator");
-            }
-        }
     }
 
     @Override
     public void onMessage(WebSocket socket, String text) {
         LOGGER.debug("WS received text: {}", text);
 
-        // Always fetch handler from ServiceLocator for each message
-        RemoteMessageHandler currentHandler = serviceLocator.getService(RemoteMessageHandler.class);
-        if (currentHandler == null) {
-            LOGGER.warn("RemoteMessageHandler not available in ServiceLocator");
-        }
-
-        String response = handleTextMessage(text, currentHandler);
+        String response = handleTextMessage(text, handler);

Initialize the RemoteMessageHandler once in the JabRefWebSocketApp constructor
and reuse it, instead of fetching it from the ServiceLocator on every message.

jabsrv/src/main/java/org/jabref/http/server/ws/JabRefWebSocketApp.java [19-52]

-    private final ServiceLocator serviceLocator;
-
-    private RemoteMessageHandler handler;
+    private final RemoteMessageHandler handler;
 
     public JabRefWebSocketApp(ServiceLocator serviceLocator) {
-        this.serviceLocator = serviceLocator;
+        this.handler = serviceLocator.getService(RemoteMessageHandler.class);
+        if (this.handler == null) {
+            LOGGER.warn("RemoteMessageHandler not available in ServiceLocator");
+        }
     }
 
     @Override
     public void onConnect(WebSocket socket) {
         LOGGER.debug("WebSocket connected: {}", socket);
-        if (handler == null) {
-            handler = serviceLocator.getService(RemoteMessageHandler.class);
-            if (handler == null) {
-                LOGGER.warn("RemoteMessageHandler not available in ServiceLocator");
-            }
-        }
     }
 
     @Override
     public void onMessage(WebSocket socket, String text) {
         LOGGER.debug("WS received text: {}", text);
 
-        // Always fetch handler from ServiceLocator for each message
-        RemoteMessageHandler currentHandler = serviceLocator.getService(RemoteMessageHandler.class);
-        if (currentHandler == null) {
-            LOGGER.warn("RemoteMessageHandler not available in ServiceLocator");
-        }
-
-        String response = handleTextMessage(text, currentHandler);
+        String response = handleTextMessage(text, handler);
         if (response != null) {
             socket.send(response);
         }
     }

[Suggestion processed]

Suggestion importance[1-10]: 6

__

Why: The suggestion correctly identifies that the RemoteMessageHandler is fetched on every message, while it could be cached. This improves performance and code clarity.

Low
Learned
best practice
Extract handler to factory method

Extract the no-op handler implementation to a separate static class or factory
method to improve code organization and reusability.

jabsrv/src/main/java/org/jabref/http/server/Server.java [130-140]

-RemoteMessageHandler noopHandler = new RemoteMessageHandler() {
-    @Override
-    public void handleCommandLineArguments(String[] message) {
-        LOGGER.info("Received remote command (no-op handler): {}", java.util.Arrays.toString(message));
-    }
+RemoteMessageHandler noopHandler = createNoOpHandler();
+...
+private static RemoteMessageHandler createNoOpHandler() {
+    return new RemoteMessageHandler() {
+        @Override
+        public void handleCommandLineArguments(String[] message) {
+            LOGGER.info("Received remote command (no-op handler): {}", java.util.Arrays.toString(message));
+        }
+        @Override
+        public void handleFocus() {
+            LOGGER.info("Received focus request (no-op handler)");
+        }
+    };
+}
 
-    @Override
-    public void handleFocus() {
-        LOGGER.info("Received focus request (no-op handler)");
-    }
-};
-

[To ensure code accuracy, apply this suggestion manually]

Suggestion importance[1-10]: 6

__

Why:
Relevant best practice - Avoid creating anonymous inner classes for simple handlers when a lambda or method reference can be used for better readability and conciseness.

Low
Improve exception logging detail
Suggestion Impact:The suggestion was directly implemented. The logging statement in the catch block was changed from "LOGGER.error("Error processing WebSocket message", e)" to "LOGGER.error("Error processing WebSocket message: {}", e.getMessage(), e)" exactly as suggested.

code diff:

         } catch (Exception e) {
-            LOGGER.error("Error processing WebSocket message", e);
+            LOGGER.error("Error processing WebSocket message: {}", e.getMessage(), e);

The catch block logs the full exception but returns a generic error message.
Consider logging the exception message for debugging while ensuring no sensitive
stack traces are exposed to clients.

jabsrv/src/main/java/org/jabref/http/server/ws/JabRefWebSocketApp.java [100-104]

 } catch (Exception e) {
-    LOGGER.error("Error processing WebSocket message", e);
+    LOGGER.error("Error processing WebSocket message: {}", e.getMessage(), e);
     return "{\"status\":\"error\",\"message\":\"Internal error\"}";
 }

[To ensure code accuracy, apply this suggestion manually]

Suggestion importance[1-10]: 5

__

Why:
Relevant best practice - Avoid exposing internal error details in user-facing error messages to prevent information leakage and maintain security.

Low
  • Update

@github-actions github-actions bot added the status: changes-required Pull requests that are not yet complete label Jan 7, 2026
@koppor
Copy link
Member

koppor commented Jan 12, 2026

A prototype extension is available at: LyzardKing/JabRef-Connector

Repo is 404 - could you make it public - or at least give access to us? ^^

@koppor
Copy link
Member

koppor commented Jan 12, 2026

Nice work - I just added one null check and some @Nullable annotations...

@LyzardKing
Copy link
Collaborator Author

Thanks @koppor

I thought I created a public repo.. Now it should be accessible.
The extension still needs testing, especially for the different zotero parsers.

@jabref-machine
Copy link
Collaborator

Note that your PR will not be reviewed/accepted until you have gone through the mandatory checks in the description and marked each of them them exactly in the format of [x] (done), [ ] (not done yet) or [/] (not applicable). Please adhere to our pull request template.

@koppor
Copy link
Member

koppor commented Jan 13, 2026

return GrizzlyHttpServerFactory
.createHttpServer(uri, resourceConfig, serviceLocator);
// Create server without starting so we can attach add-ons before listeners bind
HttpServer server = GrizzlyHttpServerFactory.createHttpServer(uri, resourceConfig, /* start */ false);
Copy link
Member

@subhramit subhramit Jan 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: instead of using /* start */ false, extract the boolean as a constant START=true and use !START here.


// Clear previous registrations from the static engine to ensure isolation
WebSocketEngine.getEngine().unregisterAll();
WebSocketEngine.getEngine().register("", "/ws", new org.jabref.http.server.ws.JabRefWebSocketApp(serviceLocator));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

use import

// Now start the server
try {
server.start();
} catch (Exception e) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

use more specific IOException as signature-wise it can't throw any other exception


// Ensure a RemoteMessageHandler is always available; register a no-op logger handler if none provided
RemoteMessageHandler existingHandler = serviceLocator.getService(RemoteMessageHandler.class);
if (existingHandler == null) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My IntelliJ says, "Condition 'existingHandler == null' is always 'false' "
Can you check?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mine also said, but I don't believe ^

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems to me like getService either always returns a ServiceLocator object or throws a MultiException, but I may be wrong, just skimmed through

}
return obj.get(key).getAsString();
} catch (Exception e) {
LOGGER.debug("Failed to parse JSON in WebSocket message: {}", e.toString());
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

prefer the full stacktrace

Suggested change
LOGGER.debug("Failed to parse JSON in WebSocket message: {}", e.toString());
LOGGER.debug("Failed to parse JSON in WebSocket message", e);

return null;
}
return obj.get(key).getAsString();
} catch (Exception e) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

JsonSyntaxException

@koppor
Copy link
Member

koppor commented Jan 14, 2026

I tried to implement a simple browser extension and a http echo server using Python - with the help of Zencoder - see It's life at https://github.com/koppor/browser-extension-http-localhost-interaction.

No server running:

grafik

Server running:

grafik

Thus, I don't see any reason to introduce web sockets for the commands

Manifest excerpt:

  "host_permissions": [
    "http://localhost:*/*"
  ],

The reason, when Web Sockets should be used, is for the communication from JabRef to the browser.

Proposal:

  1. Remove all command code from this PR
  2. Adapt https://github.com/LyzardKing/JabRef-Connector to use http POST to /commands.

Regarding the implementation of the commands, I am working on following to Mirrir the UiCommands

  • "append" - append BibTeX to current library (AppendBibTeXToCurrentLibrary)
  • "open" - open given path org.jabref.logic.UiCommand.OpenLibraries
  • "focus" - bring jabref to front - Focus()

@koppor koppor mentioned this pull request Jan 14, 2026
4 tasks
@koppor koppor marked this pull request as draft January 15, 2026 02:17
@koppor
Copy link
Member

koppor commented Jan 15, 2026

I was about to merge #14855 here - and to remove all command handling, because its also possible with REST (now) -- but then nothing was left over... I thought, there was some webSocket variable so that one can send something from JabRef to the plugin...

Thus, I convert to draft...

Next step: Look at https://github.com/LyzardKing/JabRef-Connector?rgh-link-date=2026-01-14T11%3A15%3A39Z to use the API endpoint of #14855.

@github-actions
Copy link
Contributor

Your pull request conflicts with the target branch.

Please merge with your code. For a step-by-step guide to resolve merge conflicts, see https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/addressing-merge-conflicts/resolving-a-merge-conflict-using-the-command-line.

@LyzardKing
Copy link
Collaborator Author

I thought it would have needed a websocket, but thinking more about it, for one way communication it doesn't really make sense.

Sending messages form jabref to the the extension was something I looked into, to check for example if an element already exists, or other information. In the end it didn't seem particularly useful.

This PR can be closed then, and I can change the extension to run with the post commands.
Looking at #14855 there is no "append" command (what I called add, to import a bib item) yet, right?

@koppor
Copy link
Member

koppor commented Jan 16, 2026

Looking at #14855

Just for completeness here - and to start an ADR:

I am a bit "pedantic" using REST. My aim is that the whole URI design is based on proper resources (see https://martinfowler.com/articles/richardsonMaturityModel.html for details).

API:

### Add BibTeX to current library

The "thing" is that adding something to a library is very well doable with the entries resource. And I added it there as POST command. However, the other actions, such as "focus" or "select entries" are not "resources" one modifies. At least not entry resources. Therefore, we introduced the command "resource".

API:

#### Focus current JabRef instance

Books I like:


outlook: "Proper" resource design will also be challenging for queries of the existence of hundreds of DOIs. Then the URI might get too long - thus GET and ?dois=... won't work.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants