1 // Copyright 2012 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #ifdef UNSAFE_BUFFERS_BUILD
6 // TODO(crbug.com/40284755): Remove this and spanify to fix the errors.
7 #pragma allow_unsafe_buffers
8 #endif
9
10 #include "net/http/http_content_disposition.h"
11
12 #include "base/strings/utf_string_conversions.h"
13 #include "testing/gtest/include/gtest/gtest.h"
14
15 namespace net {
16
17 namespace {
18
19 struct FileNameCDCase {
20 const char* header;
21 const char* referrer_charset;
22 const wchar_t* expected;
23 };
24
25 } // anonymous namespace
26
TEST(HttpContentDispositionTest,Filename)27 TEST(HttpContentDispositionTest, Filename) {
28 const FileNameCDCase tests[] = {
29 // Test various forms of C-D header fields emitted by web servers.
30 {"inline; filename=\"abcde.pdf\"", "", L"abcde.pdf"},
31 {"attachment; filename=abcde.pdf", "", L"abcde.pdf"},
32 {"attachment; filename=abc,de.pdf", "", L"abc,de.pdf"},
33 {"filename=abcde.pdf", "", L"abcde.pdf"},
34 {"filename= abcde.pdf", "", L"abcde.pdf"},
35 {"filename =abcde.pdf", "", L"abcde.pdf"},
36 {"filename = abcde.pdf", "", L"abcde.pdf"},
37 {"filename\t=abcde.pdf", "", L"abcde.pdf"},
38 {"filename \t\t =abcde.pdf", "", L"abcde.pdf"},
39 {"inline; filename=\"abc%20de.pdf\"", "",
40 L"abc de.pdf"},
41 // Name values are no longer synonyms for filename.
42 {"inline; name=\"abcde.pdf\"", "", L""},
43 {"attachment; name=abcde.pdf", "", L""},
44 {"name=abcde.pdf", "", L""},
45 // Unbalanced quotation mark
46 {"filename=\"abcdef.pdf", "", L"abcdef.pdf"},
47 // Whitespaces are converted to a space.
48 {"inline; filename=\"abc \t\nde.pdf\"", "",
49 L"abc de.pdf"},
50 // %-escaped UTF-8
51 {"attachment; filename=\"%EC%98%88%EC%88%A0%20"
52 "%EC%98%88%EC%88%A0.jpg\"", "", L"\xc608\xc220 \xc608\xc220.jpg"},
53 {"attachment; filename=\"%F0%90%8C%B0%F0%90%8C%B1"
54 "abc.jpg\"", "", L"\U00010330\U00010331abc.jpg"},
55 {"attachment; filename=\"%EC%98%88%EC%88%A0 \n"
56 "%EC%98%88%EC%88%A0.jpg\"", "", L"\xc608\xc220 \xc608\xc220.jpg"},
57 // Characters that are not supposed to be displayed should still be decoded.
58 {"attachment; filename=%E5%B2%A1%E3%80%80%E5%B2%A1.txt", "",
59 L"\u5ca1\u3000\u5ca1.txt"},
60 // RFC 2047 with various charsets and Q/B encodings
61 {"attachment; filename=\"=?EUC-JP?Q?=B7=DD=BD="
62 "D13=2Epng?=\"", "", L"\x82b8\x8853" L"3.png"},
63 {"attachment; filename==?eUc-Kr?b?v7m8+iAzLnBuZw==?=",
64 "", L"\xc608\xc220 3.png"},
65 {"attachment; filename==?utf-8?Q?=E8=8A=B8=E8"
66 "=A1=93_3=2Epng?=", "", L"\x82b8\x8853 3.png"},
67 {"attachment; filename==?utf-8?Q?=F0=90=8C=B0"
68 "_3=2Epng?=", "", L"\U00010330 3.png"},
69 {"inline; filename=\"=?iso88591?Q?caf=e9_=2epng?=\"",
70 "", L"caf\x00e9 .png"},
71 // Space after an encoded word should be removed.
72 {"inline; filename=\"=?iso88591?Q?caf=E9_?= .png\"",
73 "", L"caf\x00e9 .png"},
74 // Two encoded words with different charsets (not very likely to be emitted
75 // by web servers in the wild). Spaces between them are removed.
76 {"inline; filename=\"=?euc-kr?b?v7m8+iAz?="
77 " =?ksc5601?q?=BF=B9=BC=FA=2Epng?=\"", "",
78 L"\xc608\xc220 3\xc608\xc220.png"},
79 {"attachment; filename=\"=?windows-1252?Q?caf=E9?="
80 " =?iso-8859-7?b?4eI=?= .png\"", "", L"caf\x00e9\x03b1\x03b2.png"},
81 // Non-ASCII string is passed through and treated as UTF-8 as long as
82 // it's valid as UTF-8 and regardless of |referrer_charset|.
83 {"attachment; filename=caf\xc3\xa9.png",
84 "iso-8859-1", L"caf\x00e9.png"},
85 {"attachment; filename=caf\xc3\xa9.png",
86 "", L"caf\x00e9.png"},
87 // Non-ASCII/Non-UTF-8 string. Fall back to the referrer charset.
88 {"attachment; filename=caf\xe5.png",
89 "windows-1253", L"caf\x03b5.png"},
90 #if 0
91 // Non-ASCII/Non-UTF-8 string. Fall back to the native codepage.
92 // TODO(jungshik): We need to set the OS default codepage
93 // to a specific value before testing. On Windows, we can use
94 // SetThreadLocale().
95 {"attachment; filename=\xb0\xa1\xb0\xa2.png",
96 "", L"\xac00\xac01.png"},
97 #endif
98 // Failure cases
99 // Invalid hex-digit "G"
100 {"attachment; filename==?iiso88591?Q?caf=EG?=", "",
101 L""},
102 // Incomplete RFC 2047 encoded-word (missing '='' at the end)
103 {"attachment; filename==?iso88591?Q?caf=E3?", "", L""},
104 // Extra character at the end of an encoded word
105 {"attachment; filename==?iso88591?Q?caf=E3?==",
106 "", L""},
107 // Extra token at the end of an encoded word
108 {"attachment; filename==?iso88591?Q?caf=E3?=?",
109 "", L""},
110 {"attachment; filename==?iso88591?Q?caf=E3?=?=",
111 "", L""},
112 // Incomplete hex-escaped chars
113 {"attachment; filename==?windows-1252?Q?=63=61=E?=",
114 "", L""},
115 {"attachment; filename=%EC%98%88%EC%88%A", "", L""},
116 // %-escaped non-UTF-8 encoding is an "error"
117 {"attachment; filename=%B7%DD%BD%D1.png", "", L""},
118 // Two RFC 2047 encoded words in a row without a space is an error.
119 {"attachment; filename==?windows-1252?Q?caf=E3?="
120 "=?iso-8859-7?b?4eIucG5nCg==?=", "", L""},
121
122 // RFC 5987 tests with Filename* : see http://tools.ietf.org/html/rfc5987
123 {"attachment; filename*=foo.html", "", L""},
124 {"attachment; filename*=foo'.html", "", L""},
125 {"attachment; filename*=''foo'.html", "", L""},
126 {"attachment; filename*=''foo.html'", "", L""},
127 {"attachment; filename*=''f\"oo\".html'", "", L""},
128 {"attachment; filename*=bogus_charset''foo.html'",
129 "", L""},
130 {"attachment; filename*='en'foo.html'", "", L""},
131 {"attachment; filename*=iso-8859-1'en'foo.html", "",
132 L"foo.html"},
133 {"attachment; filename*=utf-8'en'foo.html", "",
134 L"foo.html"},
135 {"attachment; filename*=utf-8'en'%E5%B2%A1%E3%80%80%E5%B2%A1.txt", "",
136 L"\u5ca1\u3000\u5ca1.txt"},
137 // charset cannot be omitted.
138 {"attachment; filename*='es'f\xfa.html'", "", L""},
139 // Non-ASCII bytes are not allowed.
140 {"attachment; filename*=iso-8859-1'es'f\xfa.html", "",
141 L""},
142 {"attachment; filename*=utf-8'es'f\xce\xba.html", "",
143 L""},
144 // TODO(jshin): Space should be %-encoded, but currently, we allow
145 // spaces.
146 {"inline; filename*=iso88591''cafe foo.png", "",
147 L"cafe foo.png"},
148
149 // Filename* tests converted from Q-encoded tests above.
150 {"attachment; filename*=EUC-JP''%B7%DD%BD%D13%2Epng",
151 "", L"\x82b8\x8853" L"3.png"},
152 {"attachment; filename*=utf-8''"
153 "%E8%8A%B8%E8%A1%93%203%2Epng", "", L"\x82b8\x8853 3.png"},
154 {"attachment; filename*=utf-8''%F0%90%8C%B0 3.png", "",
155 L"\U00010330 3.png"},
156 {"inline; filename*=Euc-Kr'ko'%BF%B9%BC%FA%2Epng", "",
157 L"\xc608\xc220.png"},
158 {"attachment; filename*=windows-1252''caf%E9.png", "",
159 L"caf\x00e9.png"},
160
161 // Multiple filename, filename*, name parameters specified.
162 {"attachment; name=\"foo\"; filename=\"bar\"", "", L"bar"},
163 {"attachment; filename=\"bar\"; name=\"foo\"", "", L"bar"},
164 {"attachment; filename=\"bar\"; filename*=utf-8''baz", "", L"baz"},
165
166 // http://greenbytes.de/tech/tc2231/ filename* test cases.
167 // attwithisofn2231iso
168 {"attachment; filename*=iso-8859-1''foo-%E4.html", "",
169 L"foo-\xe4.html"},
170 // attwithfn2231utf8
171 {"attachment; filename*="
172 "UTF-8''foo-%c3%a4-%e2%82%ac.html", "", L"foo-\xe4-\x20ac.html"},
173 // attwithfn2231noc : no encoding specified but UTF-8 is used.
174 {"attachment; filename*=''foo-%c3%a4-%e2%82%ac.html",
175 "", L""},
176 // attwithfn2231utf8comp
177 {"attachment; filename*=UTF-8''foo-a%cc%88.html", "",
178 L"foo-\xe4.html"},
179 #ifdef ICU_SHOULD_FAIL_CONVERSION_ON_INVALID_CHARACTER
180 // This does not work because we treat ISO-8859-1 synonymous with
181 // Windows-1252 per HTML5. For HTTP, in theory, we're not
182 // supposed to.
183 // attwithfn2231utf8-bad
184 {"attachment; filename*="
185 "iso-8859-1''foo-%c3%a4-%e2%82%ac.html", "", L""},
186 #endif
187 // attwithfn2231ws1
188 {"attachment; filename *=UTF-8''foo-%c3%a4.html", "",
189 L""},
190 // attwithfn2231ws2
191 {"attachment; filename*= UTF-8''foo-%c3%a4.html", "",
192 L"foo-\xe4.html"},
193 // attwithfn2231ws3
194 {"attachment; filename* =UTF-8''foo-%c3%a4.html", "",
195 L"foo-\xe4.html"},
196 // attwithfn2231quot
197 {"attachment; filename*=\"UTF-8''foo-%c3%a4.html\"",
198 "", L""},
199 // attfnboth
200 {"attachment; filename=\"foo-ae.html\"; "
201 "filename*=UTF-8''foo-%c3%a4.html", "", L"foo-\xe4.html"},
202 // attfnboth2
203 {"attachment; filename*=UTF-8''foo-%c3%a4.html; "
204 "filename=\"foo-ae.html\"", "", L"foo-\xe4.html"},
205 // attnewandfn
206 {"attachment; foobar=x; filename=\"foo.html\"", "",
207 L"foo.html"},
208 };
209 for (const auto& test : tests) {
210 HttpContentDisposition header(test.header, test.referrer_charset);
211 EXPECT_EQ(test.expected, base::UTF8ToWide(header.filename()))
212 << "Failed on input: " << test.header;
213 }
214 }
215
216 // Test cases from http://greenbytes.de/tech/tc2231/
TEST(HttpContentDispositionTest,tc2231)217 TEST(HttpContentDispositionTest, tc2231) {
218 const struct FileNameCDCase {
219 const char* header;
220 HttpContentDisposition::Type expected_type;
221 const wchar_t* expected_filename;
222 } tests[] = {
223 // http://greenbytes.de/tech/tc2231/#inlonly
224 {"inline", HttpContentDisposition::INLINE, L""},
225 // http://greenbytes.de/tech/tc2231/#inlonlyquoted
226 {"\"inline\"", HttpContentDisposition::INLINE, L""},
227 // http://greenbytes.de/tech/tc2231/#inlwithasciifilename
228 {"inline; filename=\"foo.html\"", HttpContentDisposition::INLINE,
229 L"foo.html"},
230 // http://greenbytes.de/tech/tc2231/#inlwithfnattach
231 {"inline; filename=\"Not an attachment!\"",
232 HttpContentDisposition::INLINE, L"Not an attachment!"},
233 // http://greenbytes.de/tech/tc2231/#inlwithasciifilenamepdf
234 {"inline; filename=\"foo.pdf\"", HttpContentDisposition::INLINE,
235 L"foo.pdf"},
236 // http://greenbytes.de/tech/tc2231/#attonly
237 {"attachment", HttpContentDisposition::ATTACHMENT, L""},
238 // http://greenbytes.de/tech/tc2231/#attonlyquoted
239 {"\"attachment\"", HttpContentDisposition::INLINE, L""},
240 // http://greenbytes.de/tech/tc2231/#attonly403
241 // TODO(abarth): This isn't testable in this unit test.
242 // http://greenbytes.de/tech/tc2231/#attonlyucase
243 {"ATTACHMENT", HttpContentDisposition::ATTACHMENT, L""},
244 // http://greenbytes.de/tech/tc2231/#attwithasciifilename
245 {"attachment; filename=\"foo.html\"", HttpContentDisposition::ATTACHMENT,
246 L"foo.html"},
247 // http://greenbytes.de/tech/tc2231/#attwithasciifnescapedchar
248 {"attachment; filename=\"f\\oo.html\"",
249 HttpContentDisposition::ATTACHMENT, L"foo.html"},
250 // http://greenbytes.de/tech/tc2231/#attwithasciifnescapedquote
251 {"attachment; filename=\"\\\"quoting\\\" tested.html\"",
252 HttpContentDisposition::ATTACHMENT, L"\"quoting\" tested.html"},
253 // http://greenbytes.de/tech/tc2231/#attwithquotedsemicolon
254 {"attachment; filename=\"Here's a semicolon;.html\"",
255 HttpContentDisposition::ATTACHMENT, L"Here's a semicolon;.html"},
256 // http://greenbytes.de/tech/tc2231/#attwithfilenameandextparam
257 {"attachment; foo=\"bar\"; filename=\"foo.html\"",
258 HttpContentDisposition::ATTACHMENT, L"foo.html"},
259 // http://greenbytes.de/tech/tc2231/#attwithfilenameandextparamescaped
260 {"attachment; foo=\"\\\"\\\\\";filename=\"foo.html\"",
261 HttpContentDisposition::ATTACHMENT, L"foo.html"},
262 // http://greenbytes.de/tech/tc2231/#attwithasciifilenameucase
263 {"attachment; FILENAME=\"foo.html\"", HttpContentDisposition::ATTACHMENT,
264 L"foo.html"},
265 // http://greenbytes.de/tech/tc2231/#attwithasciifilenamenq
266 {"attachment; filename=foo.html", HttpContentDisposition::ATTACHMENT,
267 L"foo.html"},
268 // http://greenbytes.de/tech/tc2231/#attwithasciifilenamenqs
269 // Note: tc2231 says we should fail to parse this header.
270 {"attachment; filename=foo.html ;", HttpContentDisposition::ATTACHMENT,
271 L"foo.html"},
272 // http://greenbytes.de/tech/tc2231/#attemptyparam
273 // Note: tc2231 says we should fail to parse this header.
274 {"attachment; ;filename=foo", HttpContentDisposition::ATTACHMENT, L"foo"},
275 // http://greenbytes.de/tech/tc2231/#attwithasciifilenamenqws
276 // Note: tc2231 says we should fail to parse this header.
277 {"attachment; filename=foo bar.html", HttpContentDisposition::ATTACHMENT,
278 L"foo bar.html"},
279 // http://greenbytes.de/tech/tc2231/#attwithfntokensq
280 {"attachment; filename='foo.bar'", HttpContentDisposition::ATTACHMENT,
281 L"'foo.bar'"},
282 #ifdef ICU_SHOULD_FAIL_CONVERSION_ON_INVALID_CHARACTER
283 // http://greenbytes.de/tech/tc2231/#attwithisofnplain
284 {
285 "attachment; filename=\"foo-\xE4html\"",
286 HttpContentDisposition::ATTACHMENT,
287 L"" // Should be L"foo-\xE4.html"
288 },
289 #endif
290 // http://greenbytes.de/tech/tc2231/#attwithutf8fnplain
291 // Note: We'll UTF-8 decode the file name, even though tc2231 says not to.
292 {"attachment; filename=\"foo-\xC3\xA4.html\"",
293 HttpContentDisposition::ATTACHMENT, L"foo-\xE4.html"},
294 // http://greenbytes.de/tech/tc2231/#attwithfnrawpctenca
295 {
296 "attachment; filename=\"foo-%41.html\"",
297 HttpContentDisposition::ATTACHMENT,
298 L"foo-A.html" // Should be L"foo-%41.html"
299 },
300 // http://greenbytes.de/tech/tc2231/#attwithfnusingpct
301 {"attachment; filename=\"50%.html\"", HttpContentDisposition::ATTACHMENT,
302 L"50%.html"},
303 // http://greenbytes.de/tech/tc2231/#attwithfnrawpctencaq
304 {
305 "attachment; filename=\"foo-%\\41.html\"",
306 HttpContentDisposition::ATTACHMENT,
307 L"foo-A.html" // Should be L"foo-%41.html"
308 },
309 // http://greenbytes.de/tech/tc2231/#attwithnamepct
310 // Value is skipped like other UAs.
311 {"attachment; name=\"foo-%41.html\"", HttpContentDisposition::ATTACHMENT,
312 L""},
313 #ifdef ICU_SHOULD_FAIL_CONVERSION_ON_INVALID_CHARACTER
314 // http://greenbytes.de/tech/tc2231/#attwithfilenamepctandiso
315 {
316 "attachment; filename=\"\xE4-%41.html\"",
317 HttpContentDisposition::ATTACHMENT,
318 L"" // Should be L"\xE4-%41.htm"
319 },
320 #endif
321 // http://greenbytes.de/tech/tc2231/#attwithfnrawpctenclong
322 {
323 "attachment; filename=\"foo-%c3%a4-%e2%82%ac.html\"",
324 HttpContentDisposition::ATTACHMENT,
325 L"foo-\xE4-\u20AC.html" // Should be L"foo-%c3%a4-%e2%82%ac.html"
326 },
327 // http://greenbytes.de/tech/tc2231/#attwithasciifilenamews1
328 {"attachment; filename =\"foo.html\"", HttpContentDisposition::ATTACHMENT,
329 L"foo.html"},
330 // http://greenbytes.de/tech/tc2231/#attwith2filenames
331 // Note: tc2231 says we should fail to parse this header.
332 {"attachment; filename=\"foo.html\"; filename=\"bar.html\"",
333 HttpContentDisposition::ATTACHMENT, L"foo.html"},
334 // http://greenbytes.de/tech/tc2231/#attfnbrokentoken
335 // Note: tc2231 says we should fail to parse this header.
336 {"attachment; filename=foo[1](2).html",
337 HttpContentDisposition::ATTACHMENT, L"foo[1](2).html"},
338 #ifdef ICU_SHOULD_FAIL_CONVERSION_ON_INVALID_CHARACTER
339 // http://greenbytes.de/tech/tc2231/#attfnbrokentokeniso
340 // Note: tc2231 says we should fail to parse this header.
341 {"attachment; filename=foo-\xE4.html", HttpContentDisposition::ATTACHMENT,
342 L""},
343 #endif
344 // http://greenbytes.de/tech/tc2231/#attfnbrokentokenutf
345 // Note: tc2231 says we should fail to parse this header.
346 {"attachment; filename=foo-\xC3\xA4.html",
347 HttpContentDisposition::ATTACHMENT, L"foo-\xE4.html"},
348 // http://greenbytes.de/tech/tc2231/#attmissingdisposition
349 // Note: tc2231 says we should fail to parse this header.
350 {"filename=foo.html", HttpContentDisposition::INLINE, L"foo.html"},
351 // http://greenbytes.de/tech/tc2231/#attmissingdisposition2
352 // Note: tc2231 says we should fail to parse this header.
353 {"x=y; filename=foo.html", HttpContentDisposition::INLINE, L"foo.html"},
354 // http://greenbytes.de/tech/tc2231/#attmissingdisposition3
355 // Note: tc2231 says we should fail to parse this header.
356 {
357 "\"foo; filename=bar;baz\"; filename=qux",
358 HttpContentDisposition::INLINE,
359 L"" // Firefox gets qux
360 },
361 // http://greenbytes.de/tech/tc2231/#attmissingdisposition4
362 // Note: tc2231 says we should fail to parse this header.
363 {"filename=foo.html, filename=bar.html", HttpContentDisposition::INLINE,
364 L"foo.html, filename=bar.html"},
365 // http://greenbytes.de/tech/tc2231/#emptydisposition
366 // Note: tc2231 says we should fail to parse this header.
367 {"; filename=foo.html", HttpContentDisposition::INLINE, L"foo.html"},
368 // http://greenbytes.de/tech/tc2231/#attandinline
369 // Note: tc2231 says we should fail to parse this header.
370 {"inline; attachment; filename=foo.html", HttpContentDisposition::INLINE,
371 L""},
372 // http://greenbytes.de/tech/tc2231/#attandinline2
373 // Note: tc2231 says we should fail to parse this header.
374 {"attachment; inline; filename=foo.html",
375 HttpContentDisposition::ATTACHMENT, L""},
376 // http://greenbytes.de/tech/tc2231/#attbrokenquotedfn
377 // Note: tc2231 says we should fail to parse this header.
378 {"attachment; filename=\"foo.html\".txt",
379 HttpContentDisposition::ATTACHMENT, L"foo.html\".txt"},
380 // http://greenbytes.de/tech/tc2231/#attbrokenquotedfn2
381 // Note: tc2231 says we should fail to parse this header.
382 {"attachment; filename=\"bar", HttpContentDisposition::ATTACHMENT,
383 L"bar"},
384 // http://greenbytes.de/tech/tc2231/#attbrokenquotedfn3
385 // Note: tc2231 says we should fail to parse this header.
386 {"attachment; filename=foo\"bar;baz\"qux",
387 HttpContentDisposition::ATTACHMENT, L"foo\"bar;baz\"qux"},
388 // http://greenbytes.de/tech/tc2231/#attmultinstances
389 // Note: tc2231 says we should fail to parse this header.
390 {"attachment; filename=foo.html, attachment; filename=bar.html",
391 HttpContentDisposition::ATTACHMENT, L"foo.html, attachment"},
392 // http://greenbytes.de/tech/tc2231/#attmissingdelim
393 {"attachment; foo=foo filename=bar", HttpContentDisposition::ATTACHMENT,
394 L""},
395 // http://greenbytes.de/tech/tc2231/#attreversed
396 // Note: tc2231 says we should fail to parse this header.
397 {"filename=foo.html; attachment", HttpContentDisposition::INLINE,
398 L"foo.html"},
399 // http://greenbytes.de/tech/tc2231/#attconfusedparam
400 {"attachment; xfilename=foo.html", HttpContentDisposition::ATTACHMENT,
401 L""},
402 // http://greenbytes.de/tech/tc2231/#attabspath
403 {"attachment; filename=\"/foo.html\"", HttpContentDisposition::ATTACHMENT,
404 L"/foo.html"},
405 // http://greenbytes.de/tech/tc2231/#attabspathwin
406 {"attachment; filename=\"\\\\foo.html\"",
407 HttpContentDisposition::ATTACHMENT, L"\\foo.html"},
408 // http://greenbytes.de/tech/tc2231/#dispext
409 {"foobar", HttpContentDisposition::ATTACHMENT, L""},
410 // http://greenbytes.de/tech/tc2231/#dispextbadfn
411 {"attachment; example=\"filename=example.txt\"",
412 HttpContentDisposition::ATTACHMENT, L""},
413 // http://greenbytes.de/tech/tc2231/#attnewandfn
414 {"attachment; foobar=x; filename=\"foo.html\"",
415 HttpContentDisposition::ATTACHMENT, L"foo.html"},
416 // TODO(abarth): Add the filename* tests, but check
417 // HttpContentDispositionTest.Filename for overlap.
418 // TODO(abarth): http://greenbytes.de/tech/tc2231/#attrfc2047token
419 // TODO(abarth): http://greenbytes.de/tech/tc2231/#attrfc2047quoted
420 };
421 for (const auto& test : tests) {
422 HttpContentDisposition header(test.header, std::string());
423 EXPECT_EQ(test.expected_type, header.type())
424 << "Failed on input: " << test.header;
425 EXPECT_EQ(test.expected_filename, base::UTF8ToWide(header.filename()))
426 << "Failed on input: " << test.header;
427 }
428 }
429
TEST(HttpContentDispositionTest,ParseResult)430 TEST(HttpContentDispositionTest, ParseResult) {
431 const struct ParseResultTestCase {
432 const char* header;
433 int expected_flags;
434 } kTestCases[] = {
435 // Basic feature tests
436 {"", HttpContentDisposition::INVALID},
437 {"example=x", HttpContentDisposition::INVALID},
438 {"attachment; filename=", HttpContentDisposition::HAS_DISPOSITION_TYPE},
439 {"attachment; name=", HttpContentDisposition::HAS_DISPOSITION_TYPE},
440 {"attachment; filename*=", HttpContentDisposition::HAS_DISPOSITION_TYPE},
441 {"attachment; filename==?utf-8?Q?\?=",
442 HttpContentDisposition::HAS_DISPOSITION_TYPE},
443 {"filename=x", HttpContentDisposition::HAS_FILENAME},
444 {"example; filename=x",
445 HttpContentDisposition::HAS_DISPOSITION_TYPE |
446 HttpContentDisposition::HAS_UNKNOWN_DISPOSITION_TYPE |
447 HttpContentDisposition::HAS_FILENAME},
448 {"attachment; filename=x", HttpContentDisposition::HAS_DISPOSITION_TYPE |
449 HttpContentDisposition::HAS_FILENAME},
450 {"attachment; filename='x'",
451 HttpContentDisposition::HAS_DISPOSITION_TYPE |
452 HttpContentDisposition::HAS_FILENAME |
453 HttpContentDisposition::HAS_SINGLE_QUOTED_FILENAME},
454 {"attachment; filename=x; name=y",
455 HttpContentDisposition::HAS_DISPOSITION_TYPE |
456 HttpContentDisposition::HAS_FILENAME},
457 {"attachment; name=y; filename*=utf-8''foo; name=x",
458 HttpContentDisposition::HAS_DISPOSITION_TYPE |
459 HttpContentDisposition::HAS_EXT_FILENAME},
460
461 // Feature tests for 'filename' attribute.
462 {"filename=foo\xcc\x88",
463 HttpContentDisposition::HAS_FILENAME |
464 HttpContentDisposition::HAS_NON_ASCII_STRINGS},
465 {"filename=foo%cc%88",
466 HttpContentDisposition::HAS_FILENAME |
467 HttpContentDisposition::HAS_PERCENT_ENCODED_STRINGS},
468 {"filename==?utf-8?Q?foo?=",
469 HttpContentDisposition::HAS_FILENAME |
470 HttpContentDisposition::HAS_RFC2047_ENCODED_STRINGS},
471 {"filename=\"=?utf-8?Q?foo?=\"",
472 HttpContentDisposition::HAS_FILENAME |
473 HttpContentDisposition::HAS_RFC2047_ENCODED_STRINGS},
474 {"filename==?utf-8?Q?foo?", HttpContentDisposition::INVALID},
475
476 // Test 'name' isn't a synonym for 'filename'.
477 {"name=foo\xcc\x88", HttpContentDisposition::INVALID},
478
479 // Shouldn't set |has_non_ascii_strings| based on 'name' attribute.
480 {"filename=x; name=foo\xcc\x88", HttpContentDisposition::HAS_FILENAME},
481 {"filename=foo\xcc\x88 foo%cc%88 =?utf-8?Q?foo?=",
482 HttpContentDisposition::HAS_FILENAME |
483 HttpContentDisposition::HAS_NON_ASCII_STRINGS |
484 HttpContentDisposition::HAS_PERCENT_ENCODED_STRINGS |
485 HttpContentDisposition::HAS_RFC2047_ENCODED_STRINGS},
486
487 // If 'filename' attribute is invalid, should set any flags based on it.
488 {"filename=foo\xcc\x88 foo%cc%88 =?utf-8?Q?foo?",
489 HttpContentDisposition::INVALID},
490 {"filename=foo\xcc\x88 foo%cc%88 =?utf-8?Q?foo?; name=x",
491 HttpContentDisposition::INVALID},
492 };
493
494 for (size_t i = 0; i < std::size(kTestCases); ++i) {
495 const ParseResultTestCase& test_case = kTestCases[i];
496 HttpContentDisposition content_disposition(test_case.header, "utf-8");
497 int result = content_disposition.parse_result_flags();
498
499 SCOPED_TRACE(testing::Message() << "Test case " << i
500 << " with header " << test_case.header);
501 EXPECT_EQ(test_case.expected_flags, result);
502 }
503 }
504
TEST(HttpContentDispositionTest,ContainsNul)505 TEST(HttpContentDispositionTest, ContainsNul) {
506 const char kHeader[] = "filename=ab\0c";
507 const char kExpectedFilename[] = "ab\0c";
508 // Note: both header and expected_filename include the trailing NUL.
509 std::string header{kHeader, sizeof(kHeader)};
510 std::string expected_filename{kExpectedFilename, sizeof(kExpectedFilename)};
511
512 HttpContentDisposition content_disposition(header, "utf-8");
513 EXPECT_EQ(expected_filename, content_disposition.filename());
514 }
515
516 } // namespace net
517