Skip to content

Commit f6c679f

Browse files
committed
Fix up issues with ContentDispositionHeader parsing
1 parent ce6263c commit f6c679f

2 files changed

Lines changed: 88 additions & 39 deletions

File tree

Lines changed: 6 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,20 @@
11
package dev.aikido.agent_api.helpers;
22

3+
import java.util.Optional;
34
import java.util.regex.Matcher;
45
import java.util.regex.Pattern;
56

67
public final class ContentDispositionFilename {
78
private ContentDispositionFilename() {}
89

9-
public static String extractFilenameFromHeader(String contentDisposition) {
10-
if (contentDisposition == null || contentDisposition.isEmpty()) {
11-
return null;
12-
}
13-
14-
// Regex to match the filename in the Content-Disposition header
15-
String regex = "filename[\\s]*=[\\s]*\"([^\"]*)\"";
16-
Pattern pattern = Pattern.compile(regex);
17-
Matcher matcher = pattern.matcher(contentDisposition);
18-
19-
if (matcher.find()) {
20-
return matcher.group(1);
21-
}
22-
23-
// Fallback for cases where the filename is not quoted
24-
regex = "filename[\\s]*=[\\s]*([^;\\s]+)";
25-
pattern = Pattern.compile(regex);
26-
matcher = pattern.matcher(contentDisposition);
10+
public static Optional<String> extractFilenameFromHeader(String contentDisposition) {
11+
ContentDispositionHeader.ParseResult res = ContentDispositionHeader.parse(contentDisposition);
2712

28-
if (matcher.find()) {
29-
return matcher.group(1);
13+
if (res.params() == null) {
14+
return Optional.empty();
3015
}
3116

32-
return null;
17+
return Optional.empty();
3318
}
3419

3520
}

agent_api/src/main/java/dev/aikido/agent_api/helpers/ContentDispositionHeader.java

Lines changed: 82 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -12,18 +12,85 @@
1212
import java.util.regex.*;
1313

1414
public class ContentDispositionHeader {
15-
16-
// Regular expressions as static final strings
17-
private static final String HEX_ESCAPE_REPLACE_REGEXP = "%([0-9A-Fa-f]{2})";
18-
private static final String NON_LATIN1_REGEXP = "[^\\x20-\\x7e\\xa0-\\xff]";
19-
private static final String QESC_REGEXP = "\\\\\\([\\u0000-\\u007f])";
20-
private static final String PARAM_REGEXP = ";[\\x09\\x20]*([!#$%&'*+.0-9A-Z^_`a-z|~-]+)[\\x09\\x20]*=(?:[\\x09\\x20]*\"(?:[\\x20!\\x23-\\x5b\\x5d-\\x7e\\x80-\\xff]|\\\\[\\x20-\\x7e])*\"|[\\x09\\x20]*[!#$%&'*+.0-9A-Z^_`a-z|~-]+)[\\x09\\x20]*)";
21-
private static final String EXT_VALUE_REGEXP = "^([A-Za-z0-9!#$%&+\\-^_`{}~]+)'(?:[A-Za-z]{2,3}(?:-[A-Za-z]{3}){0,3}|[A-Za-z]{4,8}|)'((?:%[0-9A-Fa-f]{2}|[A-Za-z0-9!#$&+.^_`|~-])+)$";
22-
private static final String DISPOSITION_TYPE_REGEXP = "^([!#$%&'*+.0-9A-Z^_`a-z|~-]+)[\\x09\\x20]*(?:$|;)";
15+
private static final Pattern HEX_ESCAPE_REPLACE_REGEXP = Pattern.compile("%([0-9A-Fa-f]{2})");
16+
17+
/**
18+
* RegExp to match non-latin1 characters.
19+
* @private
20+
*/
21+
private static final Pattern NON_LATIN1_REGEXP = Pattern.compile("[^\\x20-\\x7e\\xa0-\\xff]");
22+
23+
/**
24+
* RegExp to match quoted-pair in RFC 2616
25+
*
26+
* quoted-pair = "\" CHAR
27+
* CHAR = <any US-ASCII character (octets 0 - 127)>
28+
*/
29+
private static final Pattern QESC_REGEXP = Pattern.compile("\\\\([\\u0000-\\u007f])");
30+
31+
/**
32+
* RegExp for various RFC 2616 grammar
33+
*
34+
* parameter = token "=" ( token | quoted-string )
35+
* token = 1*<any CHAR except CTLs or separators>
36+
* separators = "(" | ")" | "<" | ">" | "@"
37+
* | "," | ";" | ":" | "\" | <">
38+
* | "/" | "[" | "]" | "?" | "="
39+
* | "{" | "}" | SP | HT
40+
* quoted-string = ( <"> *(qdtext | quoted-pair ) <"> )
41+
* qdtext = <any TEXT except <">>
42+
* quoted-pair = "\" CHAR
43+
* CHAR = <any US-ASCII character (octets 0 - 127)>
44+
* TEXT = <any OCTET except CTLs, but including LWS>
45+
* LWS = [CRLF] 1*( SP | HT )
46+
* CRLF = CR LF
47+
* CR = <US-ASCII CR, carriage return (13)>
48+
* LF = <US-ASCII LF, linefeed (10)>
49+
* SP = <US-ASCII SP, space (32)>
50+
* HT = <US-ASCII HT, horizontal-tab (9)>
51+
* CTL = <any US-ASCII control character (octets 0 - 31) and DEL (127)>
52+
* OCTET = <any 8-bit sequence of data>
53+
*/
54+
private static final Pattern PARAM_REGEXP = Pattern.compile(";[\\t ]*([!#$%&'*+.0-9A-Z^_`a-z|~-]+)[\\t ]*=[\\t ]*(\"(?:[\\x20!\\x23-\\x5b\\x5d-\\x7e\\x80-\\xff]|\\\\[\\x20-\\x7e])*\"|[!#$%&'*+.0-9A-Z^_`a-z|~-]+)[\\t ]*");
55+
56+
/**
57+
* RegExp for various RFC 5987 grammar
58+
*
59+
* ext-value = charset "'" [ language ] "'" value-chars
60+
* charset = "UTF-8" / "ISO-8859-1" / mime-charset
61+
* mime-charset = 1*mime-charsetc
62+
* mime-charsetc = ALPHA / DIGIT
63+
* / "!" / "#" / "$" / "%" / "&"
64+
* / "+" / "-" / "^" / "_" / "`"
65+
* / "{" / "}" / "~"
66+
* language = ( 2*3ALPHA [ extlang ] )
67+
* / 4ALPHA
68+
* / 5*8ALPHA
69+
* extlang = *3( "-" 3ALPHA )
70+
* value-chars = *( pct-encoded / attr-char )
71+
* pct-encoded = "%" HEXDIG HEXDIG
72+
* attr-char = ALPHA / DIGIT
73+
* / "!" / "#" / "$" / "&" / "+" / "-" / "."
74+
* / "^" / "_" / "`" / "|" / "~"
75+
*/
76+
private static final Pattern EXT_VALUE_REGEXP = Pattern.compile("^([A-Za-z0-9!#$%&+\\-^_`{}~]+)'(?:[A-Za-z]{2,3}(?:-[A-Za-z]{3}){0,3}|[A-Za-z]{4,8}|)'((?:%[0-9A-Fa-f]{2}|[A-Za-z0-9!#$&+.^_`|~-])+)$");
77+
78+
/**
79+
* RegExp for various RFC 6266 grammar
80+
*
81+
* disposition-type = "inline" | "attachment" | disp-ext-type
82+
* disp-ext-type = token
83+
* disposition-parm = filename-parm | disp-ext-parm
84+
* filename-parm = "filename" "=" value
85+
* | "filename*" "=" ext-value
86+
* disp-ext-parm = token "=" value
87+
* | ext-token "=" ext-value
88+
* ext-token = <the characters in token, followed by "*">
89+
*/
90+
private static final Pattern DISPOSITION_TYPE_REGEXP = Pattern.compile("^([!#$%&'*+.0-9A-Z^_`a-z|~-]+)[\\t ]*(?:$|;)");
2391

2492
private static String decodeField(String str) {
25-
Pattern pattern = Pattern.compile(EXT_VALUE_REGEXP);
26-
Matcher matcher = pattern.matcher(str);
93+
Matcher matcher = EXT_VALUE_REGEXP.matcher(str);
2794

2895
if (!matcher.find()) {
2996
throw new IllegalArgumentException("invalid extended field value");
@@ -47,7 +114,7 @@ private static String decodeField(String str) {
47114

48115
private static String getLatin1(String val) {
49116
// simple Unicode -> ISO-8859-1 transformation
50-
return val.replaceAll(NON_LATIN1_REGEXP, "?");
117+
return val.replaceAll(NON_LATIN1_REGEXP.pattern(), "?");
51118
}
52119

53120
public record ParseResult(String type, Map<String, String> params) {}
@@ -57,8 +124,7 @@ public static ParseResult parse(String string) {
57124
throw new IllegalArgumentException("argument string is required");
58125
}
59126

60-
Pattern pattern = Pattern.compile(DISPOSITION_TYPE_REGEXP);
61-
Matcher matcher = pattern.matcher(string);
127+
Matcher matcher = DISPOSITION_TYPE_REGEXP.matcher(string);
62128

63129
if (!matcher.find()) {
64130
throw new IllegalArgumentException("invalid type format");
@@ -74,8 +140,7 @@ public static ParseResult parse(String string) {
74140
String value;
75141

76142
// calculate index to start at
77-
pattern = Pattern.compile(PARAM_REGEXP);
78-
matcher = pattern.matcher(string);
143+
matcher = PARAM_REGEXP.matcher(string);
79144
matcher.region(index, string.length());
80145

81146
// match parameters
@@ -112,7 +177,7 @@ public static ParseResult parse(String string) {
112177
// remove quotes and escapes
113178
value = value
114179
.substring(1, value.length() - 1)
115-
.replaceAll(QESC_REGEXP, "$1");
180+
.replaceAll(QESC_REGEXP.pattern(), "$1");
116181
}
117182

118183
params.put(key, value);
@@ -130,8 +195,7 @@ private static String pDecode(String hex) {
130195
}
131196

132197
private static String replaceAll(String input, Replacer replacer) {
133-
Pattern pattern = Pattern.compile(ContentDispositionHeader.HEX_ESCAPE_REPLACE_REGEXP);
134-
Matcher matcher = pattern.matcher(input);
198+
Matcher matcher = HEX_ESCAPE_REPLACE_REGEXP.matcher(input);
135199
StringBuilder sb = new StringBuilder();
136200

137201
while (matcher.find()) {

0 commit comments

Comments
 (0)