Skip to content

Allow drag and drop of Windows shortcut (.lnk) files : Fix for issue #15036#15053

Draft
faneeshh wants to merge 4 commits intoJabRef:mainfrom
faneeshh:fix-for-issue-15036
Draft

Allow drag and drop of Windows shortcut (.lnk) files : Fix for issue #15036#15053
faneeshh wants to merge 4 commits intoJabRef:mainfrom
faneeshh:fix-for-issue-15036

Conversation

@faneeshh
Copy link
Contributor

@faneeshh faneeshh commented Feb 8, 2026

Description

Closes #15036

I have implemented support for dragging and dropping Windows shortcut (.lnk) files into JabRef to open libraries. To ensure a seamless user experience and prevent the invalidation of binary data parsing, I added a PowerShell-based resolution step in FileUtil that follows the shortcut to its target .bib file before the application attempts to open it. This allows users to manage their libraries using native Windows shortcuts without encountering empty file errors.

Steps to test

  1. Create a .bib file on your desktop (e.g., test.bib).

  2. Right-click the file and select Create shortcut.

  3. Open JabRef and drag the newly created .lnk file onto the main window.

  4. Observation: You should see the "Open files..." indicator appear, and upon dropping, the library should open with all entries visible.

image

Mandatory checks

[x] I own the copyright of the code submitted and I license it under the MIT license
[x] I manually tested my changes in running JabRef (always required)
[/] I added JUnit tests for changes (if applicable)
[x] I added screenshots in the PR description (if change is visible to the user)
[x] I added a screenshot in the PR description showing a library with a single entry with me as author and as title the issue number.
[x] I described the change in CHANGELOG.md in a way that is understandable for the average user (if change is visible to the user)
[x] I checked the user documentation: Is the information available and up to date?

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

Review Summary by Qodo

Support drag and drop of Windows shortcut (.lnk) files

✨ Enhancement

Grey Divider

Walkthroughs

Description
• Add support for dragging and dropping Windows shortcut (.lnk) files
• Resolve .lnk shortcuts to target .bib files using PowerShell
• Update FrameDndHandler and MainTable to handle shortcut resolution
• Add utility methods in FileUtil for shortcut detection and resolution
Diagram
flowchart LR
  A["User drags .lnk file"] --> B["FrameDndHandler/MainTable receives file"]
  B --> C["FileUtil.isShortcutFile checks extension"]
  C --> D["FileUtil.resolveWindowsShortcut uses PowerShell"]
  D --> E["Target .bib file path returned"]
  E --> F["Library opens with entries"]
Loading

Grey Divider

File Changes

1. jabgui/src/main/java/org/jabref/gui/frame/FrameDndHandler.java ✨ Enhancement +19/-1

Add shortcut resolution to drag-drop handler

• Modified getBibFiles() method to handle shortcut file resolution
• Added flatMap logic to resolve .lnk files to their targets
• Added isAcceptedFile() helper method to check for .bib or .lnk files
• Imported Stream class for stream operations

jabgui/src/main/java/org/jabref/gui/frame/FrameDndHandler.java


2. jabgui/src/main/java/org/jabref/gui/maintable/MainTable.java ✨ Enhancement +3/-2

Integrate shortcut resolution in MainTable drag-drop

• Updated handleOnDragDropped() to resolve shortcuts before processing
• Updated handleOnDragDroppedTableView() to resolve shortcuts before import
• Added import for FileUtil class
• Both methods now call FileUtil.resolveWindowsShortcuts() on dropped files

jabgui/src/main/java/org/jabref/gui/maintable/MainTable.java


3. jablib/src/main/java/org/jabref/logic/util/io/FileUtil.java ✨ Enhancement +74/-0

Add Windows shortcut detection and resolution utilities

• Added isShortcutFile() method to detect .lnk files by extension
• Added resolveWindowsShortcut() method using PowerShell to resolve shortcuts
• Added resolveWindowsShortcuts() utility method to batch resolve multiple paths
• PowerShell implementation handles Windows-only execution with proper error handling

jablib/src/main/java/org/jabref/logic/util/io/FileUtil.java


View more (1)
4. CHANGELOG.md 📝 Documentation +1/-0

Document shortcut drag-drop feature

• Added entry documenting support for drag and drop of Windows shortcut files
• References issue #15036 with proper link formatting

CHANGELOG.md


Grey Divider

Qodo Logo

@github-actions github-actions bot added the good first issue An issue intended for project-newcomers. Varies in difficulty. label Feb 8, 2026
@jabref-machine
Copy link
Collaborator

The title of the pull request must not start with "Fix for issue xyz". Please use a concise one-line summary that explains what the fix or change actually does. Example of a good title: "Prevent crash when importing malformed BibTeX entries".

@jabref-machine
Copy link
Collaborator

You have removed the "Mandatory Checks" section from your pull request description. Please adhere to our pull request template.

@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.

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

Code Review by Qodo

🐞 Bugs (4) 📘 Rule violations (5) 📎 Requirement gaps (0)

Grey Divider


Action required

1. resolveWindowsShortcut catches Exception 📘 Rule violation ⛯ Reliability
Description
resolveWindowsShortcut wraps the whole PowerShell invocation in a broad catch (Exception) and
  degrades by returning Optional.empty(), which can silently drop .lnk files during drag-and-drop
  without a clear recovery path.
• The method blocks on process.waitFor() with no timeout and does not restore the interrupt flag
  on interruption, which risks hangs and makes failures harder to diagnose reliably.
• This violates the requirement for specific, non-disruptive exception handling and robust edge-case
  management around external process execution.
Code

jablib/src/main/java/org/jabref/logic/util/io/FileUtil.java[R548-584]

+        try {
+            // Use PowerShell to resolve the shortcut target
+            ProcessBuilder pb = new ProcessBuilder(
+                    "powershell", "-NoProfile", "-Command",
+                    "$ws = New-Object -ComObject WScript.Shell; " +
+                            "$shortcut = $ws.CreateShortcut('" + shortcutPath.toAbsolutePath().toString().replace("'", "''") + "'); " +
+                            "Write-Output $shortcut.TargetPath"
+            );
+
+            pb.redirectErrorStream(true);
+            Process process = pb.start();
+
+            String targetPath;
+            try (java.io.BufferedReader reader = new java.io.BufferedReader(
+                    new java.io.InputStreamReader(process.getInputStream()))) {
+                targetPath = reader.readLine();
+            }
+
+            int exitCode = process.waitFor();
+
+            if (exitCode == 0 && targetPath != null && !targetPath.trim().isEmpty()) {
+                Path resolvedPath = Path.of(targetPath.trim());
+                if (Files.exists(resolvedPath)) {
+                    LOGGER.debug("Resolved shortcut {} to {}", shortcutPath, resolvedPath);
+                    return Optional.of(resolvedPath);
+                } else {
+                    LOGGER.warn("Shortcut target does not exist: {}", resolvedPath);
+                    return Optional.empty();
+                }
+            } else {
+                LOGGER.warn("Failed to resolve shortcut: {} (exit code: {})", shortcutPath, exitCode);
+                return Optional.empty();
+            }
+        } catch (Exception e) {
+            LOGGER.warn("Exception while resolving shortcut: {}", shortcutPath, e);
+            return Optional.empty();
+        }
Evidence
The compliance checklist requires specific exception handling and robust handling of failure points.
The new code uses a broad catch (Exception) and performs an unbounded waitFor(), returning empty
results on failures, which can lead to silent failures and potential hangs.

Rule 3: Generic: Robust Error Handling and Edge Case Management
AGENTS.md
jablib/src/main/java/org/jabref/logic/util/io/FileUtil.java[566-566]
jablib/src/main/java/org/jabref/logic/util/io/FileUtil.java[581-583]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`FileUtil.resolveWindowsShortcut` catches a broad `Exception` and blocks on `process.waitFor()` without a timeout. This can lead to silent failures (returning `Optional.empty()`), and it risks hanging the application thread.

## Issue Context
The shortcut resolution executes an external process (PowerShell) which is a high-failure-risk dependency (missing executable, permission issues, COM errors, long-running/hung process). Exception handling should be specific and interruptions should preserve the thread interrupt status.

## Fix Focus Areas
- jablib/src/main/java/org/jabref/logic/util/io/FileUtil.java[548-584]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. Missing tests for .lnk 📘 Rule violation ✓ Correctness
Description
• New behavior was introduced for resolving Windows shortcuts via
  FileUtil.resolveWindowsShortcut/resolveWindowsShortcuts and used in drag-and-drop flows, but no
  corresponding JUnit tests were added/updated to cover this behavior.
• The existing FileUtilTest covers file extension handling, yet does not include .lnk cases,
  leaving key edge cases (non-Windows behavior, failure modes) unverified.
Code

jablib/src/main/java/org/jabref/logic/util/io/FileUtil.java[R524-596]

+    /// Test if the file is a shortcut file by simply checking the extension to be ".lnk"
+    ///
+    /// @param file The file to check
+    /// @return True if file extension is ".lnk", false otherwise
+    public static boolean isShortcutFile(Path file) {
+        return getFileExtension(file).filter("lnk"::equals).isPresent();
+    }
+
+    /// Resolves a Windows shortcut (.lnk) file to its target path.
+    /// Only works on Windows systems. On other systems or if resolution fails, returns empty Optional.
+    ///
+    /// @param shortcutPath The path to the .lnk file
+    /// @return Optional containing the target path, or empty if resolution fails
+    public static Optional<Path> resolveWindowsShortcut(Path shortcutPath) {
+        if (!isShortcutFile(shortcutPath)) {
+            return Optional.empty();
+        }
+
+        // Only attempt on Windows
+        if (!OS.WINDOWS) {
+            LOGGER.debug("Shortcut resolution only supported on Windows");
+            return Optional.empty();
+        }
+
+        try {
+            // Use PowerShell to resolve the shortcut target
+            ProcessBuilder pb = new ProcessBuilder(
+                    "powershell", "-NoProfile", "-Command",
+                    "$ws = New-Object -ComObject WScript.Shell; " +
+                            "$shortcut = $ws.CreateShortcut('" + shortcutPath.toAbsolutePath().toString().replace("'", "''") + "'); " +
+                            "Write-Output $shortcut.TargetPath"
+            );
+
+            pb.redirectErrorStream(true);
+            Process process = pb.start();
+
+            String targetPath;
+            try (java.io.BufferedReader reader = new java.io.BufferedReader(
+                    new java.io.InputStreamReader(process.getInputStream()))) {
+                targetPath = reader.readLine();
+            }
+
+            int exitCode = process.waitFor();
+
+            if (exitCode == 0 && targetPath != null && !targetPath.trim().isEmpty()) {
+                Path resolvedPath = Path.of(targetPath.trim());
+                if (Files.exists(resolvedPath)) {
+                    LOGGER.debug("Resolved shortcut {} to {}", shortcutPath, resolvedPath);
+                    return Optional.of(resolvedPath);
+                } else {
+                    LOGGER.warn("Shortcut target does not exist: {}", resolvedPath);
+                    return Optional.empty();
+                }
+            } else {
+                LOGGER.warn("Failed to resolve shortcut: {} (exit code: {})", shortcutPath, exitCode);
+                return Optional.empty();
+            }
+        } catch (Exception e) {
+            LOGGER.warn("Exception while resolving shortcut: {}", shortcutPath, e);
+            return Optional.empty();
+        }
+    }
+
+    public static List<Path> resolveWindowsShortcuts(List<Path> paths) {
+        return paths.stream()
+                    .flatMap(path -> {
+                        if (isShortcutFile(path)) {
+                            return resolveWindowsShortcut(path).stream();
+                        }
+                        return Stream.of(path);
+                    })
+                    .toList();
+    }
Evidence
The checklist requires appropriate tests for behavior changes. The PR adds new shortcut-resolution
behavior in FileUtil, but existing FileUtilTest extension cases do not include .lnk,
indicating the new functionality is not covered by tests.

AGENTS.md
jablib/src/main/java/org/jabref/logic/util/io/FileUtil.java[524-596]
jablib/src/test/java/org/jabref/logic/util/io/FileUtilTest.java[179-190]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
New Windows shortcut (`.lnk`) drag-and-drop support introduces new `FileUtil` behavior without corresponding test coverage.

## Issue Context
At minimum, tests should cover:
- `isShortcutFile` correctly detecting `.lnk` (case handling if intended)
- `resolveWindowsShortcuts` returning the original paths for non-shortcut inputs
- non-Windows behavior: `resolveWindowsShortcut` should return `Optional.empty()` without throwing

## Fix Focus Areas
- jablib/src/main/java/org/jabref/logic/util/io/FileUtil.java[524-596]
- jablib/src/test/java/org/jabref/logic/util/io/FileUtilTest.java[179-210]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


3. Case-sensitive .lnk check 🐞 Bug ✓ Correctness
Description
isShortcutFile uses exact string match ("lnk"::equals) but getFileExtension does not
  actually lowercase the extension, despite its documentation.
• As a result, shortcuts like FILE.LNK may not be recognized, breaking the new drag-and-drop
  behavior on Windows.
Code

jablib/src/main/java/org/jabref/logic/util/io/FileUtil.java[R524-530]

+    /// Test if the file is a shortcut file by simply checking the extension to be ".lnk"
+    ///
+    /// @param file The file to check
+    /// @return True if file extension is ".lnk", false otherwise
+    public static boolean isShortcutFile(Path file) {
+        return getFileExtension(file).filter("lnk"::equals).isPresent();
+    }
Evidence
The extension helper claims to return lowercase but returns the raw extension from FilenameUtils.
The shortcut predicate compares with a lowercase literal using a case-sensitive equals.

jablib/src/main/java/org/jabref/logic/util/io/FileUtil.java[74-94]
jablib/src/main/java/org/jabref/logic/util/io/FileUtil.java[524-530]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
`.lnk` detection is effectively case-sensitive because `getFileExtension` returns the raw extension and `isShortcutFile` compares using `&quot;lnk&quot;::equals`.

### Issue Context
Windows file extensions are case-insensitive and `.LNK` is common. The current implementation can fail to detect valid shortcuts.

### Fix Focus Areas
- jablib/src/main/java/org/jabref/logic/util/io/FileUtil.java[74-94]
- jablib/src/main/java/org/jabref/logic/util/io/FileUtil.java[524-530]

### Suggested approach
- Update `getFileExtension` to return `extension.toLowerCase(Locale.ROOT)` (and adjust imports/tests accordingly), aligning with its Javadoc.
- Alternatively, change `isShortcutFile` to `filter(ext -&gt; ext.equalsIgnoreCase(&quot;lnk&quot;))` (but prefer central normalization in `getFileExtension`).

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


View more (1)
4. Shortcut returns non-file 🐞 Bug ✓ Correctness
Description
resolveWindowsShortcut returns any existing target path, including directories, because it only
  checks Files.exists.
• Downstream import/link logic in ImportHandler treats unknown paths by creating an empty entry
  with a file link, which can create incorrect entries if a shortcut points to a directory.
Code

jablib/src/main/java/org/jabref/logic/util/io/FileUtil.java[R568-573]

+            if (exitCode == 0 && targetPath != null && !targetPath.trim().isEmpty()) {
+                Path resolvedPath = Path.of(targetPath.trim());
+                if (Files.exists(resolvedPath)) {
+                    LOGGER.debug("Resolved shortcut {} to {}", shortcutPath, resolvedPath);
+                    return Optional.of(resolvedPath);
+                } else {
Evidence
The resolver returns paths that exist without verifying they are regular files. ImportHandler
creates an entry for non-PDF/non-BIB paths, which would include directories returned by the
resolver.

jablib/src/main/java/org/jabref/logic/util/io/FileUtil.java[568-573]
jabgui/src/main/java/org/jabref/gui/externalfiles/ImportHandler.java[183-199]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
Shortcut resolution currently returns any existing path, including directories. This can leak directory targets into file-import/link flows, creating empty entries with folder links.

### Issue Context
`ImportHandler` treats unknown paths as generic files and creates an empty entry with a link, which is incorrect when the path is a folder.

### Fix Focus Areas
- jablib/src/main/java/org/jabref/logic/util/io/FileUtil.java[568-576]
- jabgui/src/main/java/org/jabref/gui/externalfiles/ImportHandler.java[183-199]

### Suggested approach
- Replace `Files.exists(resolvedPath)` with `Files.isRegularFile(resolvedPath)` (or `Files.isRegularFile(resolvedPath, LinkOption.NOFOLLOW_LINKS)` if appropriate).
- Consider logging a clear warning when the shortcut target is not a regular file.
- Optionally: for callers that can accept directories, add an explicit parameter/alternate method rather than returning directories by default.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

5. Logs absolute file paths 📘 Rule violation ⛨ Security
Description
• The new shortcut-resolution flow logs shortcutPath and resolvedPath, which may include user
  profile directories/usernames and thus constitutes potentially sensitive information in logs.
• This increases risk of leaking personal data via logs when users drag and drop files from home
  directories.
Code

jablib/src/main/java/org/jabref/logic/util/io/FileUtil.java[R571-582]

+                    LOGGER.debug("Resolved shortcut {} to {}", shortcutPath, resolvedPath);
+                    return Optional.of(resolvedPath);
+                } else {
+                    LOGGER.warn("Shortcut target does not exist: {}", resolvedPath);
+                    return Optional.empty();
+                }
+            } else {
+                LOGGER.warn("Failed to resolve shortcut: {} (exit code: {})", shortcutPath, exitCode);
+                return Optional.empty();
+            }
+        } catch (Exception e) {
+            LOGGER.warn("Exception while resolving shortcut: {}", shortcutPath, e);
Evidence
The secure logging rule requires avoiding sensitive data in logs. Logging full paths can expose
personal information (e.g., usernames) and local directory structure.

Rule 5: Generic: Secure Logging Practices
jablib/src/main/java/org/jabref/logic/util/io/FileUtil.java[571-579]
jablib/src/main/java/org/jabref/logic/util/io/FileUtil.java[581-582]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The shortcut-resolution logs include full filesystem paths (`shortcutPath`, `resolvedPath`) which may contain sensitive user information (e.g., usernames).

## Issue Context
Drag-and-drop file paths commonly point into a user&#x27;s home directory. Logging them at `warn` (and even `debug`) can leak PII into log files.

## Fix Focus Areas
- jablib/src/main/java/org/jabref/logic/util/io/FileUtil.java[571-582]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


6. CHANGELOG link has space 📘 Rule violation ✓ Correctness
Description
• The added CHANGELOG entry formats the issue link as [#15036] (https://...) which is malformed
  Markdown and will not render as a proper hyperlink.
• This leaves written project artifacts in a malformed state, contrary to the requirement to correct
  typos/malformed text.
Code

CHANGELOG.md[43]

+- Allowed drag and drop of Windows shortcut (.lnk) files to open libraries [#15036] (https://github.com/JabRef/jabref/issues/15036)
Evidence
The compliance rule requires fixing typos and malformed text in changelog/documentation. The new
entry includes an incorrectly formatted Markdown link.

CHANGELOG.md[43-43]
Best Practice: Learned patterns

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The CHANGELOG entry contains malformed Markdown for the issue link due to an extra space between the link text and URL.

## Issue Context
Broken links in CHANGELOG reduce readability and professionalism.

## Fix Focus Areas
- CHANGELOG.md[43-43]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


7. UI thread blocked 🐞 Bug ⛯ Reliability
Description
resolveWindowsShortcut spawns PowerShell and calls process.waitFor() with no timeout; if
  PowerShell startup hangs/slow, drag-and-drop can freeze the JavaFX UI.
• The shortcut resolution is invoked directly inside drag-and-drop handlers (before background
  tasks), so this blocking behavior is user-facing.
Code

jablib/src/main/java/org/jabref/logic/util/io/FileUtil.java[R548-567]

+        try {
+            // Use PowerShell to resolve the shortcut target
+            ProcessBuilder pb = new ProcessBuilder(
+                    "powershell", "-NoProfile", "-Command",
+                    "$ws = New-Object -ComObject WScript.Shell; " +
+                            "$shortcut = $ws.CreateShortcut('" + shortcutPath.toAbsolutePath().toString().replace("'", "''") + "'); " +
+                            "Write-Output $shortcut.TargetPath"
+            );
+
+            pb.redirectErrorStream(true);
+            Process process = pb.start();
+
+            String targetPath;
+            try (java.io.BufferedReader reader = new java.io.BufferedReader(
+                    new java.io.InputStreamReader(process.getInputStream()))) {
+                targetPath = reader.readLine();
+            }
+
+            int exitCode = process.waitFor();
+
Evidence
The shortcut resolution uses an external process and waits synchronously. The DnD handlers call
resolution inline, which runs on the JavaFX event thread, making UI freezes plausible.

jablib/src/main/java/org/jabref/logic/util/io/FileUtil.java[548-567]
jabgui/src/main/java/org/jabref/gui/maintable/MainTable.java[538-559]
jabgui/src/main/java/org/jabref/gui/frame/FrameDndHandler.java[182-188]
jablib/src/main/java/org/jabref/logic/os/OS.java[81-96]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
`FileUtil.resolveWindowsShortcut` executes PowerShell and blocks on `process.waitFor()` without a timeout. This is called inline from JavaFX drag-and-drop handlers, which can freeze the UI if PowerShell is slow/hangs.

### Issue Context
The codebase already depends on `mslinks.ShellLink` (see `OS.detectProgramPath`) to resolve `.lnk` targets in-process, which avoids external process overhead/hangs.

### Fix Focus Areas
- jablib/src/main/java/org/jabref/logic/util/io/FileUtil.java[548-567]
- jabgui/src/main/java/org/jabref/gui/maintable/MainTable.java[538-559]
- jabgui/src/main/java/org/jabref/gui/frame/FrameDndHandler.java[182-188]
- jablib/src/main/java/org/jabref/logic/os/OS.java[81-96]

### Suggested approach
- Replace the PowerShell-based resolution with `mslinks.ShellLink` resolution (Windows-only), similar to `OS.detectProgramPath`.
- If PowerShell must remain:
 - run resolution on a background executor (not the FX thread)
 - add a timeout (e.g., `process.waitFor(timeout, unit)` and `process.destroyForcibly()`)
 - drain full output to avoid blocking on filled buffers.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Advisory comments

8. Trivial .lnk comments added 📘 Rule violation ✓ Correctness
Description
• The new documentation comment for isShortcutFile restates what the code obviously does (checks
  the extension) instead of explaining intent, limitations, or rationale.
• This adds noise without improving maintainability, contrary to the requirement that comments
  explain the “why”.
Code

jablib/src/main/java/org/jabref/logic/util/io/FileUtil.java[R524-530]

+    /// Test if the file is a shortcut file by simply checking the extension to be ".lnk"
+    ///
+    /// @param file The file to check
+    /// @return True if file extension is ".lnk", false otherwise
+    public static boolean isShortcutFile(Path file) {
+        return getFileExtension(file).filter("lnk"::equals).isPresent();
+    }
Evidence
The compliance rule requires comments to explain rationale rather than narrate code. The added
comment states it checks the .lnk extension, which is already evident from the implementation.

AGENTS.md
jablib/src/main/java/org/jabref/logic/util/io/FileUtil.java[524-530]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Comments for `isShortcutFile` narrate the implementation rather than documenting intent/limitations.

## Issue Context
Per codebase conventions, comments should focus on non-obvious rationale (e.g., why extension-check is sufficient, platform constraints, or security considerations).

## Fix Focus Areas
- jablib/src/main/java/org/jabref/logic/util/io/FileUtil.java[524-530]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


9. Inconsistent DnD shortcut support 🐞 Bug ✓ Correctness
Description
• Main table drag-and-drop resolves .lnk files before importing, but group tree drag-and-drop
  import still forwards raw paths.
• Users dropping the same shortcut onto different UI areas will see different behavior; this is
  likely an unintended inconsistency.
Code

jabgui/src/main/java/org/jabref/gui/maintable/MainTable.java[R542-543]

+            List<Path> files = FileUtil.resolveWindowsShortcuts(event.getDragboard().getFiles().stream().map(File::toPath).collect(Collectors.toList()));
Evidence
MainTable explicitly resolves shortcuts, while GroupTreeView constructs the file list without
resolution before invoking the same import handler.

jabgui/src/main/java/org/jabref/gui/maintable/MainTable.java[541-576]
jabgui/src/main/java/org/jabref/gui/groups/GroupTreeView.java[408-422]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
`.lnk` resolution is applied in some drag-and-drop entry points (MainTable) but not others (GroupTreeView), leading to inconsistent behavior.

### Issue Context
Both paths eventually call `ImportHandler.importFilesInBackground`, so normalizing the file list upstream improves consistency.

### Fix Focus Areas
- jabgui/src/main/java/org/jabref/gui/maintable/MainTable.java[538-576]
- jabgui/src/main/java/org/jabref/gui/groups/GroupTreeView.java[408-422]
- jablib/src/main/java/org/jabref/logic/util/io/FileUtil.java[587-596]

### Suggested approach
- Use `FileUtil.resolveWindowsShortcuts(...)` in GroupTreeView as well.
- Or extract a shared helper for `dragboard.getFiles() -&gt; List&lt;Path&gt;` normalization used by all DnD handlers.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

ⓘ The new review experience is currently in Beta. Learn more

Grey Divider

Qodo Logo

Comment on lines 548 to 584
try {
// Use PowerShell to resolve the shortcut target
ProcessBuilder pb = new ProcessBuilder(
"powershell", "-NoProfile", "-Command",
"$ws = New-Object -ComObject WScript.Shell; " +
"$shortcut = $ws.CreateShortcut('" + shortcutPath.toAbsolutePath().toString().replace("'", "''") + "'); " +
"Write-Output $shortcut.TargetPath"
);

pb.redirectErrorStream(true);
Process process = pb.start();

String targetPath;
try (java.io.BufferedReader reader = new java.io.BufferedReader(
new java.io.InputStreamReader(process.getInputStream()))) {
targetPath = reader.readLine();
}

int exitCode = process.waitFor();

if (exitCode == 0 && targetPath != null && !targetPath.trim().isEmpty()) {
Path resolvedPath = Path.of(targetPath.trim());
if (Files.exists(resolvedPath)) {
LOGGER.debug("Resolved shortcut {} to {}", shortcutPath, resolvedPath);
return Optional.of(resolvedPath);
} else {
LOGGER.warn("Shortcut target does not exist: {}", resolvedPath);
return Optional.empty();
}
} else {
LOGGER.warn("Failed to resolve shortcut: {} (exit code: {})", shortcutPath, exitCode);
return Optional.empty();
}
} catch (Exception e) {
LOGGER.warn("Exception while resolving shortcut: {}", shortcutPath, e);
return Optional.empty();
}

Choose a reason for hiding this comment

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

Action required

1. resolvewindowsshortcut catches exception 📘 Rule violation ⛯ Reliability

resolveWindowsShortcut wraps the whole PowerShell invocation in a broad catch (Exception) and
  degrades by returning Optional.empty(), which can silently drop .lnk files during drag-and-drop
  without a clear recovery path.
• The method blocks on process.waitFor() with no timeout and does not restore the interrupt flag
  on interruption, which risks hangs and makes failures harder to diagnose reliably.
• This violates the requirement for specific, non-disruptive exception handling and robust edge-case
  management around external process execution.
Agent Prompt
## Issue description
`FileUtil.resolveWindowsShortcut` catches a broad `Exception` and blocks on `process.waitFor()` without a timeout. This can lead to silent failures (returning `Optional.empty()`), and it risks hanging the application thread.

## Issue Context
The shortcut resolution executes an external process (PowerShell) which is a high-failure-risk dependency (missing executable, permission issues, COM errors, long-running/hung process). Exception handling should be specific and interruptions should preserve the thread interrupt status.

## Fix Focus Areas
- jablib/src/main/java/org/jabref/logic/util/io/FileUtil.java[548-584]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment on lines 524 to 596
/// Test if the file is a shortcut file by simply checking the extension to be ".lnk"
///
/// @param file The file to check
/// @return True if file extension is ".lnk", false otherwise
public static boolean isShortcutFile(Path file) {
return getFileExtension(file).filter("lnk"::equals).isPresent();
}

/// Resolves a Windows shortcut (.lnk) file to its target path.
/// Only works on Windows systems. On other systems or if resolution fails, returns empty Optional.
///
/// @param shortcutPath The path to the .lnk file
/// @return Optional containing the target path, or empty if resolution fails
public static Optional<Path> resolveWindowsShortcut(Path shortcutPath) {
if (!isShortcutFile(shortcutPath)) {
return Optional.empty();
}

// Only attempt on Windows
if (!OS.WINDOWS) {
LOGGER.debug("Shortcut resolution only supported on Windows");
return Optional.empty();
}

try {
// Use PowerShell to resolve the shortcut target
ProcessBuilder pb = new ProcessBuilder(
"powershell", "-NoProfile", "-Command",
"$ws = New-Object -ComObject WScript.Shell; " +
"$shortcut = $ws.CreateShortcut('" + shortcutPath.toAbsolutePath().toString().replace("'", "''") + "'); " +
"Write-Output $shortcut.TargetPath"
);

pb.redirectErrorStream(true);
Process process = pb.start();

String targetPath;
try (java.io.BufferedReader reader = new java.io.BufferedReader(
new java.io.InputStreamReader(process.getInputStream()))) {
targetPath = reader.readLine();
}

int exitCode = process.waitFor();

if (exitCode == 0 && targetPath != null && !targetPath.trim().isEmpty()) {
Path resolvedPath = Path.of(targetPath.trim());
if (Files.exists(resolvedPath)) {
LOGGER.debug("Resolved shortcut {} to {}", shortcutPath, resolvedPath);
return Optional.of(resolvedPath);
} else {
LOGGER.warn("Shortcut target does not exist: {}", resolvedPath);
return Optional.empty();
}
} else {
LOGGER.warn("Failed to resolve shortcut: {} (exit code: {})", shortcutPath, exitCode);
return Optional.empty();
}
} catch (Exception e) {
LOGGER.warn("Exception while resolving shortcut: {}", shortcutPath, e);
return Optional.empty();
}
}

public static List<Path> resolveWindowsShortcuts(List<Path> paths) {
return paths.stream()
.flatMap(path -> {
if (isShortcutFile(path)) {
return resolveWindowsShortcut(path).stream();
}
return Stream.of(path);
})
.toList();
}

Choose a reason for hiding this comment

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

Action required

2. Missing tests for .lnk 📘 Rule violation ✓ Correctness

• New behavior was introduced for resolving Windows shortcuts via
  FileUtil.resolveWindowsShortcut/resolveWindowsShortcuts and used in drag-and-drop flows, but no
  corresponding JUnit tests were added/updated to cover this behavior.
• The existing FileUtilTest covers file extension handling, yet does not include .lnk cases,
  leaving key edge cases (non-Windows behavior, failure modes) unverified.
Agent Prompt
## Issue description
New Windows shortcut (`.lnk`) drag-and-drop support introduces new `FileUtil` behavior without corresponding test coverage.

## Issue Context
At minimum, tests should cover:
- `isShortcutFile` correctly detecting `.lnk` (case handling if intended)
- `resolveWindowsShortcuts` returning the original paths for non-shortcut inputs
- non-Windows behavior: `resolveWindowsShortcut` should return `Optional.empty()` without throwing

## Fix Focus Areas
- jablib/src/main/java/org/jabref/logic/util/io/FileUtil.java[524-596]
- jablib/src/test/java/org/jabref/logic/util/io/FileUtilTest.java[179-210]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment on lines 524 to 530
/// Test if the file is a shortcut file by simply checking the extension to be ".lnk"
///
/// @param file The file to check
/// @return True if file extension is ".lnk", false otherwise
public static boolean isShortcutFile(Path file) {
return getFileExtension(file).filter("lnk"::equals).isPresent();
}

Choose a reason for hiding this comment

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

Action required

3. Case-sensitive .lnk check 🐞 Bug ✓ Correctness

isShortcutFile uses exact string match ("lnk"::equals) but getFileExtension does not
  actually lowercase the extension, despite its documentation.
• As a result, shortcuts like FILE.LNK may not be recognized, breaking the new drag-and-drop
  behavior on Windows.
Agent Prompt
### Issue description
`.lnk` detection is effectively case-sensitive because `getFileExtension` returns the raw extension and `isShortcutFile` compares using `"lnk"::equals`.

### Issue Context
Windows file extensions are case-insensitive and `.LNK` is common. The current implementation can fail to detect valid shortcuts.

### Fix Focus Areas
- jablib/src/main/java/org/jabref/logic/util/io/FileUtil.java[74-94]
- jablib/src/main/java/org/jabref/logic/util/io/FileUtil.java[524-530]

### Suggested approach
- Update `getFileExtension` to return `extension.toLowerCase(Locale.ROOT)` (and adjust imports/tests accordingly), aligning with its Javadoc.
- Alternatively, change `isShortcutFile` to `filter(ext -> ext.equalsIgnoreCase("lnk"))` (but prefer central normalization in `getFileExtension`).

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment on lines 568 to 573
if (exitCode == 0 && targetPath != null && !targetPath.trim().isEmpty()) {
Path resolvedPath = Path.of(targetPath.trim());
if (Files.exists(resolvedPath)) {
LOGGER.debug("Resolved shortcut {} to {}", shortcutPath, resolvedPath);
return Optional.of(resolvedPath);
} else {

Choose a reason for hiding this comment

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

Action required

4. Shortcut returns non-file 🐞 Bug ✓ Correctness

resolveWindowsShortcut returns any existing target path, including directories, because it only
  checks Files.exists.
• Downstream import/link logic in ImportHandler treats unknown paths by creating an empty entry
  with a file link, which can create incorrect entries if a shortcut points to a directory.
Agent Prompt
### Issue description
Shortcut resolution currently returns any existing path, including directories. This can leak directory targets into file-import/link flows, creating empty entries with folder links.

### Issue Context
`ImportHandler` treats unknown paths as generic files and creates an empty entry with a link, which is incorrect when the path is a folder.

### Fix Focus Areas
- jablib/src/main/java/org/jabref/logic/util/io/FileUtil.java[568-576]
- jabgui/src/main/java/org/jabref/gui/externalfiles/ImportHandler.java[183-199]

### Suggested approach
- Replace `Files.exists(resolvedPath)` with `Files.isRegularFile(resolvedPath)` (or `Files.isRegularFile(resolvedPath, LinkOption.NOFOLLOW_LINKS)` if appropriate).
- Consider logging a clear warning when the shortcut target is not a regular file.
- Optionally: for callers that can accept directories, add an explicit parameter/alternate method rather than returning directories by default.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

@github-actions github-actions bot added the status: changes-required Pull requests that are not yet complete label Feb 8, 2026
@testlens-app
Copy link

testlens-app bot commented Feb 8, 2026

✅ All tests passed ✅

🏷️ Commit: 510012a
▶️ Tests: 11187 executed
⚪️ Checks: 56/56 completed


Learn more about TestLens at testlens.app.

@Siedlerchr
Copy link
Member

There is another java library which apparently can parse this stuf https://github.com/DmitriiShamrikov/mslinks
But they don't have an up to date version published version on maven central argh

@koppor
Copy link
Member

koppor commented Feb 8, 2026

Note that resolving works at JabRef (!)

If I double click a .lnk file - JabRef opens. Thus "only" the drag and drop disallowance is wrong currently.

@koppor
Copy link
Member

koppor commented Feb 8, 2026

There is another java library which apparently can parse this stuf DmitriiShamrikov/mslinks But they don't have an up to date version published version on maven central argh

Who works on DmitriiShamrikov/mslinks#29 ?

@koppor
Copy link
Member

koppor commented Feb 8, 2026

We need the newver version, because of JDK11 compatibility - https://github.com/DmitriiShamrikov/mslinks/releases/tag/1.0.8

@koppor koppor marked this pull request as draft February 8, 2026 16:16
@faneeshh
Copy link
Contributor Author

faneeshh commented Feb 9, 2026

Note that resolving works at JabRef (!)

If I double click a .lnk file - JabRef opens. Thus "only" the drag and drop disallowance is wrong currently.

I tested it by only allowing the .lnk extension. While the file is accepted by the UI, it currently results in an empty table because the Drag and Drop path doesn't seem to trigger the same resolution logic as double-clicking or the file picker. I am currently looking into how CommandLineProcessor or the main entry point handles .lnk files to see if I can reuse that existing resolution logic here without adding new dependencies like mslinks.

@jabref-machine
Copy link
Collaborator

You ticked that you modified CHANGELOG.md, but no new entry was found there.

If you made changes that are visible to the user, please add a brief description along with the issue number to the CHANGELOG.md file. If you did not, please replace the cross ([x]) by a slash ([/]) to indicate that no CHANGELOG.md entry is necessary. More details can be found in our Developer Documentation about the changelog.

@faneeshh
Copy link
Contributor Author

faneeshh commented Feb 9, 2026

@koppor I was able to resolve it by synchronizing the two seperate drag and drop code paths by integrating Windows shortcut resolution into the MainTable handlers. Previously dropping a .lnk file directly onto the table was bypassing the resolution logic in OpenDatabaseAction which resulted in "misc" entries. I think applying FileUtil : : resolveIfShortcut within both handleOnDragDroppedTable view and handleOnDragDropped in MainTable.java, and refining the resolution logic in FileUtil.java using mslinks.ShellLink now correctly resolves shortcuts. Let me know what you think!

@jabref-machine
Copy link
Collaborator

You modified Markdown (*.md) files and did not meet JabRef's rules for consistently formatted Markdown files. To ensure consistent styling, we have markdown-lint in place. Markdown lint's rules help to keep our Markdown files consistent within this repository and consistent with the Markdown files outside here.

You can check the detailed error output by navigating to your pull request, selecting the tab "Checks", section "Source Code Tests" (on the left), subsection "Markdown".

@koppor
Copy link
Member

koppor commented Feb 11, 2026

recent mslink library available at org.jabref:mslinks:1.1.0-SNAPSHOT. See DmitriiShamrikov/mslinks#29 (comment) for details.

}

private boolean isAcceptedFile(Path path) {
return FileUtil.isBibFile(path) || FileUtil.isShortcutFile(path);
Copy link
Member

Choose a reason for hiding this comment

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

Condition wrong - i think, resolving should take place

Suggested change
return FileUtil.isBibFile(path) || FileUtil.isShortcutFile(path);
return FileUtil.isBibFile(FileUtils.resolveIfShortcut(path));

// Resolve any shortcuts to their targets
List<Path> resolvedFiles = filesToOpen.stream()
.map(FileUtil::resolveIfShortcut)
.collect(Collectors.toList());
Copy link
Member

Choose a reason for hiding this comment

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

Add comment that list needs to be modifiable.


### Fixed

- Allowed drag and drop of Windows shortcut (.lnk) files to open libraries [#15036] (https://github.com/JabRef/jabref/issues/15036)
Copy link
Member

Choose a reason for hiding this comment

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

Space too much

import org.jabref.model.entry.LinkedFile;
import org.jabref.model.entry.field.StandardField;

import mslinks.ShellLink;
Copy link
Member

Choose a reason for hiding this comment

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

@Siedlerchr Some dependency seems to already include mslinks :)

@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.

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

Labels

good first issue An issue intended for project-newcomers. Varies in difficulty. status: changes-required Pull requests that are not yet complete

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Windows] Allow drag and drop of symblinks

4 participants