Accept an http(s)/ftp URL as jabkit Input File
Context and Problem Statement
jabkit commands read their input file via the shared InputOption mixin (positional FILE argument or --input alias, see ADR 57). Both forms are typed as java.nio.file.Path, converted eagerly at parse time. Passing a URL (e.g. jabkit convert --input https://example.org/refs.bib) silently fails: the URL string is mangled into a bogus relative path and reported as “Unable to open file”, even though this is documented elsewhere as supported behavior and JabRef already has a working, unrelated precedent for it (--importToOpen in the GUI CLI, jabgui’s CliImportHelper).
A first attempt at this exact feature already existed in the codebase as an unused overload, ImportService.importFile(String, ...): it detects http:///https:///ftp:// prefixes and downloads via the existing URLDownload utility (jablib), but no command ever called it - it was dead code.
How should jabkit resolve a URL passed as input, in a way that covers every command consistently and fits the existing CLI error-handling design (ADR 63)?
Decision Drivers
- Consistency across all
InputOption-consuming commands (convert,pdf-update,search,check-consistency,check-integrity,pseudonymize,generate-citation-keys,generate-bib-from-aux) - Reuse the existing
URLDownloadutility instead of writing a second download implementation - Fit the centralized
CliException/IExecutionExceptionHandlererror model from ADR-0063 instead of introducing a second, parse-time error path - Minimal surface: only download for the
FILE/--inputargument, not for output-file options that happen to shareCygWinPathConverter
Considered Options
- Detect and download the URL inside a picocli
ITypeConverter<Path>used byInputOption’s fields - Keep the fields as raw
Stringand resolve (Cygwin path conversion or URL download) insideInputOption#getInputFile(), called from each command’scall() - Special-case URL handling only in the
convertcommand
Decision Outcome
Chosen option: “resolve inside InputOption#getInputFile()”, because a picocli type converter runs during argument parsing, before call() executes; exceptions thrown there bypass the CliExceptionHandler/CliException machinery introduced in ADR-0063 and would instead surface as generic picocli parameter errors with the wrong exit code. Resolving inside getInputFile() (called at the top of every command’s call()) lets a download failure throw the existing ImportServiceException with CommandLine.ExitCode.SOFTWARE, exactly like every other import failure.
Because InputOption is the single shared mixin behind all eight input-taking commands, every one of them gains URL support automatically, with no per-command changes.
Consequences
- Good, because all eight
InputOption-consuming commands support URLs identically, for free. - Good, because failures are reported through the existing
CliException/exit-code model (ADR-0063) instead of a second error path. - Good, because it reuses
URLDownload.toTemporaryFile()- no new download logic was written. - Neutral, because the previously-dead
ImportService.importFile(String, ...)overload was deleted rather than kept alongside the new path, to avoid two divergent implementations of the same scheme check. - Bad, because
InputOption’s fields moved fromPathtoString, so--output/other file options (which stay onCygWinPathConverter) and--input/FILEnow follow different resolution code paths for what looks like the same kind of argument. - Bad, because
jabkit(and, transitively, any server process embedding this code) now fetches attacker-suppliable URLs - a minor SSRF-relevant surface. Mitigated by reusingURLDownload, which already enforceshttp/https/ftpschemes only, a connect timeout, and JabRef’s existing TLS trust store.
Confirmation
Tests in jabkit start a local okhttp3.mockwebserver3.MockWebServer and exercise --input https://.../file.bib for both a successful download and a failed request, asserting the resulting exit code and database content. A code review confirms no InputOption-consuming command bypasses the mixin to parse --input itself.
Pros and Cons of the Options
Type converter (parse-time)
- Good, because it is the smallest structural change (no field-type change).
- Bad, because converter exceptions are wrapped as picocli
ParameterExceptions, not routed throughCliExceptionHandler, giving download failures the “invalid usage” exit code instead ofSOFTWARE. - Bad, because a converter cannot easily distinguish “download failed” from “bad path syntax” for exit-code purposes.
Resolve inside InputOption#getInputFile() (chosen)
- Good, because it fits the ADR-0063 error model without changes to it.
- Good, because it is implemented once and shared by all eight commands.
- Neutral, because the mixin’s fields change from
PathtoString, deferring path/URL resolution from parse time to call time.
Special-case only convert
- Good, because it touches the fewest files.
- Bad, because it recreates the exact per-command inconsistency that ADR-0057 removed - only
convertwould support URLs, contradicting the shared-mixin design.