[feature] request: add request:get-uploaded-file-headers() - #6583
Conversation
|
[This response was prompted by Joe, drafted by Claude Code, and reviewed by Joe.] Thanks @reinhapa — both addressed.
Verified with |
|
Nice to learn about Cleaner, did not know this. Copilot provides some review feedback, you might want to review this: Gaps in Test Coverage Comparing to the similar GetParameterTest (which has 10+ test methods covering combinatorics), the GetUploadedFileHeaders test is quite sparse. Here are the gaps:
The PR body explicitly states cardinality design for multi-file support, but there's no test for: Multiple files with the same parameter name ()
The PR explicitly mentions header names are kept "as-submitted" (not normalized to lowercase). But there's no test for: What happens when a servlet container reports header names in different cases (Content-Type vs. content-type vs. CONTENT-TYPE)
The current test uses MediaType.TEXT_PLAIN. No tests for: Binary files (application/octet-stream, application/pdf, etc.)
No explicit test for: Calling request:get-uploaded-file-headers("nonexistent") → should return empty sequence ()
No test for: An uploaded file with no headers (unlikely in practice, but technically possible)
No test for: Zero-byte file upload
No test for: Filenames with non-ASCII characters (e.g., café.txt, Chinese characters) |
|
after re-request: I'll analyze this pull request by examining the changes and identifying test coverage gaps with real risk.Now let me fetch the files changed to understand the implementation:Now I need to fetch the review comments to understand what the "CHANGES_REQUESTED" is about:## Test Gap Analysis Overall Assessment: The PR has one meaningful test gap with real risk. Gap IdentifiedMissing test for the Risk: The core logic in if (!isFormDataParsed) {
return null;
}This check protects against calling the function on non-multipart requests. However, the test only exercises multipart POST requests — the success path where What's untested:
Both should return Recommended test additions:
Other Observations (Not Gaps)
|
|
[This response was prompted by Joe, drafted by Claude Code, and reviewed by Joe.] Thanks @dizzzz — and glad the Added tests for the gaps with real risk (611226b):
The five cases now share one fixture, parameterized by an I deliberately left out the remaining Copilot suggestions (MIME-type variety, empty file, sparse/missing headers, non-ASCII filenames): the function just passes each part's headers through as the servlet container reports them, so those would mostly be testing Jetty's multipart parsing rather than this code. Happy to add any of them if you see a specific risk I'm missing. |
| private static final class TemporaryUploadedFilesCleaner implements Runnable { | ||
| private final Map<Part, Path> temporaryUploadedFiles; | ||
|
|
||
| private TemporaryUploadedFilesCleaner(final Map<Part, Path> temporaryUploadedFiles) { | ||
| this.temporaryUploadedFiles = temporaryUploadedFiles; | ||
| } |
This comment was marked as resolved.
This comment was marked as resolved.
Sorry, something went wrong.
There was a problem hiding this comment.
[This response was prompted by Joe, drafted by Claude Code, and reviewed by Joe.]
Done (5f4a7ce) — TemporaryUploadedFilesCleaner is now a record. It holds a single component (the map of parts to their temporary copies) and implements Runnable, so the canonical constructor and field replace the hand-written ones with no behavior change. Verified with GetUploadedFileHeadersTest and GetParameterTest (the latter exercises the temp-file cleanup path).
reinhapa
left a comment
There was a problem hiding this comment.
Convert TemporaryUploadedFilesCleaner into a record
|
@joewiz please can you have a look? |
Adds request:get-uploaded-file-headers($name), which returns the part headers of each uploaded file submitted under a parameter name in a multipart request, as one map(xs:string, xs:string) per uploaded file — aligned with the sequence returned by request:get-uploaded-file-name. This exposes per-part information that was previously discarded, such as a file part's own Content-Type. Header names are keyed as submitted; the empty sequence is returned when the request is not multipart or the parameter is not a file part. Closes eXist-db#6578 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…st query Address review feedback: convert the embedded XQuery from string concatenation to a Java text block. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Address review feedback: the deprecated finalize() that deleted the temporary copies of uploaded file parts is replaced with a java.lang.ref.Cleaner. The cleaning action holds only the map of temporary files, never the wrapper, so it cannot keep the wrapper from being garbage collected. The temporary-file cache is now eagerly created and final, which removes the previous lazy null-checks. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Address review remarks on get-uploaded-file-headers: add tests for multiple files under one parameter name (verifying one header map per file, positionally aligned with request:get-uploaded-file-name), an unknown parameter name, and a non-multipart request (which exercises the isFormDataParsed guard). The stored query is parameterized by an "inspect" URL parameter so the cases share one fixture. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The cleaner holds a single field (the map of uploaded-file parts to their temporary copies) and implements Runnable, so a record expresses it more concisely — the canonical constructor and field replace the hand-written equivalents, with no behavior change. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
42ad4df to
5f4a7ce
Compare
[This PR was prompted by Joe, drafted by Claude Code, and reviewed by Joe.]
Summary
Adds
request:get-uploaded-file-headers($name), which returns the part headers of each uploaded file in a multipart request — the remaining item from #6578, where a file part's own headers (e.g. itsContent-Type) were discarded. Implements the accessor @line-o proposed on #6578.Closes #6578. Companion to #6581, which fixes the underlying multipart parsing so uploads are available for every HTTP method (
PUT/PATCH, not justPOST) — the two together fully resolve #6578, and this function is most useful with #6581 in place.What changed
RequestWrapper— newgetUploadedFileHeaders(String)interface method (HttpRequestWrapperis the only implementor).HttpRequestWrapper— reads each filePart's headers into a map, reusing the same file-part discrimination asgetUploadedFileName.GetUploadedFileHeaders— the newrequest:function; registered inRequestModule.Signature
Returns one map (header name → value) per uploaded file submitted under
$name, in submission order; the empty sequence when the request is not multipart or the name is not a file part.Design choices to confirm on review
@line-o's suggestion was
as map(xs:string, xs:string). A few decisions worth a look, since a couple depart slightly from that literal shape:map(...)*rather than a single map. The sibling accessorsrequest:get-uploaded-file-name/-sizealready return sequences, because one field name can carry multiple files (<input type="file" multiple>). Returning one map per file, positionally aligned with those functions, keeps the family consistent; a single map couldn't represent a multi-file upload. Easy to change to a single map if preferred, but the sequence seems more correct.Part.getHeader, the first value). Parts can in principle repeat a header;map(xs:string, xs:string)collapses that to one value. If repeated part headers matter, the value type would need to widen (e.g.map(xs:string, xs:string*)).Naming: went with
request:get-uploaded-file-headers(over the alternative-headers-for) to match theget-uploaded-file-*family. Happy to switch.Test plan
GetUploadedFileHeadersTest(new) — multipartPOSTwith a file part and a plain field; asserts one header map for the file (exposing itsContent-TypeandContent-Disposition) and none for the field.org.exist.xquery.functions.request.**— full package green.