davsclaus commented on code in PR #26189:
URL: https://github.com/apache/camel/pull/26189#discussion_r3956209701


##########
components/camel-jetty/src/main/java/org/apache/camel/component/jetty12/AttachmentHttpBinding.java:
##########
@@ -59,6 +61,16 @@ protected void populateAttachments(HttpServletRequest 
request, Message message)
             try {
                 parts = request.getParts();
                 for (Part part : parts) {
+                    // the whitelist accepts file name extensions, so it must 
be checked against the submitted
+                    // file name and not against Part.getName(), which is the 
multipart field name
+                    String fileName = part.getSubmittedFileName();
+                    if (!isFileNameAccepted(fileName)) {
+                        LOG.debug(
+                                "Cannot add file as attachment: {} because the 
file is not accepted according to fileNameExtWhitelist: {}",
+                                fileName, getFileNameExtWhitelist());

Review Comment:
   `fileName` is now `part.getSubmittedFileName()`, which is fully 
client-controlled, and it goes into the log raw.
   
   This exact file has a deliberate convention against that, added twice: 
`76557040cd10` ("sanitize user values before logging") and `9ab764c15b93` 
("prevents potential injections"), both wrapping user-supplied values in 
`HttpHelper.sanitizeLog(...)`. `HttpHelper` is already imported here. 
`CLAUDE.md`'s Quality Expectations also ask that contributions avoid 
introducing new CWE issues, and this is CWE-117 (log injection).
   
   ```suggestion
                           LOG.debug(
                                   "Cannot add file as attachment: {} because 
the file is not accepted according to fileNameExtWhitelist: {}",
                                   HttpHelper.sanitizeLog(fileName), 
getFileNameExtWhitelist());
   ```



##########
components/camel-jetty/src/main/java/org/apache/camel/component/jetty12/AttachmentHttpBinding.java:
##########
@@ -67,23 +79,42 @@ protected void populateAttachments(HttpServletRequest 
request, Message message)
                         }
                     }
                     AttachmentMessage am = new 
DefaultAttachmentMessage(message);
-                    am.addAttachmentObject(part.getName(), attachment);
-                    String name = part.getSubmittedFileName();
-                    Object value = am.getAttachment(name);
-                    Map<String, Object> headers = message.getHeaders();
-                    if (getHeaderFilterStrategy() != null
-                            && 
!getHeaderFilterStrategy().applyFilterToExternalHeaders(name, value, 
message.getExchange())
-                            && name != null) {
-                        HttpHelper.appendHeader(headers, name, value);
+                    String name = part.getName();
+                    am.addAttachmentObject(name, attachment);
+                    // a file part is also exposed as a header carrying the 
DataHandler. The attachment is keyed on
+                    // the multipart field name, so the header must be looked 
up and named by that same key and not
+                    // by the client supplied file name. A plain form field 
carries no file name and is mapped by
+                    // populateRequestParameters instead, so it is left alone 
here.
+                    if (fileName != null && name != null) {
+                        Object value = am.getAttachment(name);
+                        Map<String, Object> headers = message.getHeaders();
+                        if (getHeaderFilterStrategy() != null
+                                && 
!getHeaderFilterStrategy().applyFilterToExternalHeaders(name, value,
+                                        message.getExchange())) {
+                            HttpHelper.appendHeader(headers, name, value);
+                        }
                     }
-
                 }
             } catch (Exception e) {
                 throw new RuntimeCamelException("Cannot populate attachments", 
e);
             }
         }
     }
 
+    private boolean isFileNameAccepted(String fileName) {
+        String whitelist = getFileNameExtWhitelist();
+        if (whitelist == null) {
+            return true;
+        }
+        String ext = FileUtil.onlyExt(fileName);
+        if (ext == null) {
+            return true;
+        }
+        ext = ext.toLowerCase(Locale.US);
+        whitelist = whitelist.toLowerCase(Locale.US);
+        return whitelist.equals("*") || whitelist.contains(ext);

Review Comment:
   Two follow-up thoughts, neither a blocker for this PR.
   
   **The match is a substring test.** With `fileNameExtWhitelist=txt`, an 
upload named `evil.x`, `evil.t` or `evil.tx` is accepted, because 
`"txt".contains("x")` is true. Relatedly, `FileUtil.onlyExt` runs in non-single 
mode (everything after the *first* dot), so `archive.tar.gz` yields `tar.gz` 
and a whitelist of `gz` rejects it. Neither is introduced here — both mirror 
`servlet/AttachmentHttpBinding` and `VertxPlatformHttpConsumer` — but the 
option just went from "never rejects" to "actually enforces", which makes the 
looseness newly reachable. Splitting the whitelist on `,` and comparing tokens 
would be the fix; a separate JIRA seems right to keep this PR focused.
   
   **This is now the third copy of the block.** The premise of this PR is that 
three implementations disagreed, and `isFileNameAccepted` adds a third copy 
(camel-servlet inline, vertx inline, jetty here). Lifting it into 
`DefaultHttpBinding` — which both bindings extend — and having camel-servlet 
call it too would leave one implementation and prevent the next drift. Arguably 
out of scope, but it is the natural home.



##########
components/camel-servlet/pom.xml:
##########
@@ -69,6 +69,11 @@
             <artifactId>camel-test-junit6</artifactId>
             <scope>test</scope>
         </dependency>
+        <dependency>
+            <groupId>org.assertj</groupId>
+            <artifactId>assertj-core</artifactId>
+            <scope>test</scope>
+        </dependency>

Review Comment:
   Could the new camel-servlet test use JUnit assertions instead, so this 
dependency isn't needed?
   
   camel-servlet currently has 42 test classes using 
`org.junit.jupiter.api.Assertions` and none using AssertJ — the closest 
sibling, `MultipartUploadTest`, uses `assertEquals`. `CLAUDE.md` is explicit 
here: *"if they are predominantly JUnit assertions, write new tests in that 
same JUnit style rather than introducing AssertJ as an outlier"*. 
`project-standards.md` also asks not to add dependencies without justification, 
and switching the two `assertThat(...).isEqualTo(...)` calls in 
`MultipartUploadFileNameExtWhitelistTest` to `assertEquals` removes the need 
for this block entirely.
   
   Same reasoning applies to `MultiPartFormFileNameExtWhitelistTest` on the 
camel-jetty side (180 JUnit test classes vs 1 AssertJ), though the dependency 
is already present there so the cost is lower.



##########
docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_23.adoc:
##########
@@ -1713,3 +1713,27 @@ Routes that set `useRecovery=false` are unaffected. 
Routes that left recovery en
 default, stop seeing in-progress aggregations re-delivered, and start seeing 
genuine recovery. The cache
 now also holds one entry per completed and not yet confirmed exchange; those 
entries are removed on
 confirmation.
+
+=== camel-servlet, camel-jetty - the multipart upload whitelist is enforced 
against the submitted file name
+
+`fileNameExtWhitelist` accepts file name extensions, but `camel-servlet`'s 
`AttachmentHttpBinding`
+checked it against `Part.getName()`, which is the multipart *field* name 
rather than the submitted
+file name. A field named `file` carries no extension, so the check found 
nothing to compare and every
+upload was accepted. The option is now checked against 
`Part.getSubmittedFileName()`, which is what
+`camel-platform-http-vertx` already does.
+
+A `camel-servlet` consumer that sets `fileNameExtWhitelist` together with 
`attachmentMultipartBinding=true`
+therefore starts rejecting uploads whose file extension is not listed, which 
is what the option always
+advertised. Uploads with no file name, such as plain form fields, are 
unaffected, and a route that does
+not set the option is unaffected. Review the configured extension list before 
upgrading.
+
+The `camel-jetty` binding performed no whitelist check at all, although 
`fileNameExtWhitelist` can be
+set on its `HttpBinding`. It now applies the same check.
+
+The `camel-jetty` binding also stored the attachment under the multipart field 
name but looked it up
+again by the submitted file name, and passed that file name to 
`HttpHelper.appendHeader`. The lookup
+therefore only succeeded when the two happened to be equal, and when it did 
the header was named by
+the client-supplied file name. The attachment is now looked up and exposed 
under the field name it is
+stored with, and only for parts that carry a file name — a plain form field is 
mapped by
+`populateRequestParameters` as before. A route that read the attachment header 
under the uploaded file
+name must read it under the multipart field name instead.

Review Comment:
   Small wording nit: as written this reads as though a working behaviour is 
being taken away, but when the two names differed the old lookup returned 
`null`, so the header was set under the file name with a **null** value — no 
route could have read a usable `DataHandler` there. When the names were equal, 
this PR doesn't change the header name at all.
   
   ```suggestion
   `populateRequestParameters` as before. A route that read the attachment 
header under the uploaded file
   name was in fact reading a `null` value, because that lookup always missed; 
the header must now be read
   under the multipart field name. A client-supplied file name no longer 
becomes a header name.
   ```



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to