1 // Licensed to the .NET Foundation under one or more agreements.
2 // The .NET Foundation licenses this file to you under the MIT license.
3 // See the LICENSE file in the project root for more information.
4 
5 using System;
6 using System.Collections.Generic;
7 using System.Linq;
8 using System.Net.Http.Headers;
9 using System.Text;
10 
11 using Xunit;
12 
13 namespace System.Net.Http.Tests
14 {
15     public class ContentDispositionHeaderValueTest
16     {
17         [Fact]
Ctor_ContentDispositionNull_Throw()18         public void Ctor_ContentDispositionNull_Throw()
19         {
20             AssertExtensions.Throws<ArgumentException>("dispositionType", () => { new ContentDispositionHeaderValue(null); });
21         }
22 
23         [Fact]
Ctor_ContentDispositionEmpty_Throw()24         public void Ctor_ContentDispositionEmpty_Throw()
25         {
26             // null and empty should be treated the same. So we also throw for empty strings.
27             AssertExtensions.Throws<ArgumentException>("dispositionType", () => { new ContentDispositionHeaderValue(string.Empty); });
28         }
29 
30         [Fact]
Ctor_ContentDispositionInvalidFormat_ThrowFormatException()31         public void Ctor_ContentDispositionInvalidFormat_ThrowFormatException()
32         {
33             // When adding values using strongly typed objects, no leading/trailing LWS (whitespace) are allowed.
34             AssertFormatException(" inline ");
35             AssertFormatException(" inline");
36             AssertFormatException("inline ");
37             AssertFormatException("\"inline\"");
38             AssertFormatException("te xt");
39             AssertFormatException("te=xt");
40             AssertFormatException("te\u00E4xt");
41             AssertFormatException("text;");
42             AssertFormatException("te/xt;");
43             AssertFormatException("inline; name=someName; ");
44             AssertFormatException("text;name=someName"); // ctor takes only disposition-type name, no parameters
45         }
46 
47         [Fact]
Ctor_ContentDispositionValidFormat_SuccessfullyCreated()48         public void Ctor_ContentDispositionValidFormat_SuccessfullyCreated()
49         {
50             ContentDispositionHeaderValue contentDisposition = new ContentDispositionHeaderValue("inline");
51             Assert.Equal("inline", contentDisposition.DispositionType);
52             Assert.Equal(0, contentDisposition.Parameters.Count);
53             Assert.Null(contentDisposition.Name);
54             Assert.Null(contentDisposition.FileName);
55             Assert.Null(contentDisposition.CreationDate);
56             Assert.Null(contentDisposition.ModificationDate);
57             Assert.Null(contentDisposition.ReadDate);
58             Assert.Null(contentDisposition.Size);
59         }
60 
61         [Fact]
Parameters_AddNull_Throw()62         public void Parameters_AddNull_Throw()
63         {
64             ContentDispositionHeaderValue contentDisposition = new ContentDispositionHeaderValue("inline");
65             Assert.Throws<ArgumentNullException>(() => { contentDisposition.Parameters.Add(null); });
66         }
67 
68         [Fact]
ContentDisposition_SetAndGetContentDisposition_MatchExpectations()69         public void ContentDisposition_SetAndGetContentDisposition_MatchExpectations()
70         {
71             ContentDispositionHeaderValue contentDisposition = new ContentDispositionHeaderValue("inline");
72             Assert.Equal("inline", contentDisposition.DispositionType);
73 
74             contentDisposition.DispositionType = "attachment";
75             Assert.Equal("attachment", contentDisposition.DispositionType);
76         }
77 
78         [Fact]
Name_SetNameAndValidateObject_ParametersEntryForNameAdded()79         public void Name_SetNameAndValidateObject_ParametersEntryForNameAdded()
80         {
81             ContentDispositionHeaderValue contentDisposition = new ContentDispositionHeaderValue("inline");
82             contentDisposition.Name = "myname";
83             Assert.Equal("myname", contentDisposition.Name);
84             Assert.Equal(1, contentDisposition.Parameters.Count);
85             Assert.Equal("name", contentDisposition.Parameters.First().Name);
86 
87             contentDisposition.Name = null;
88             Assert.Null(contentDisposition.Name);
89             Assert.Equal(0, contentDisposition.Parameters.Count);
90             contentDisposition.Name = null; // It's OK to set it again to null; no exception.
91         }
92 
93         [Fact]
Name_AddNameParameterThenUseProperty_ParametersEntryIsOverwritten()94         public void Name_AddNameParameterThenUseProperty_ParametersEntryIsOverwritten()
95         {
96             ContentDispositionHeaderValue contentDisposition = new ContentDispositionHeaderValue("inline");
97 
98             // Note that uppercase letters are used. Comparison should happen case-insensitive.
99             NameValueHeaderValue name = new NameValueHeaderValue("NAME", "old_name");
100             contentDisposition.Parameters.Add(name);
101             Assert.Equal(1, contentDisposition.Parameters.Count);
102             Assert.Equal("NAME", contentDisposition.Parameters.First().Name);
103 
104             contentDisposition.Name = "new_name";
105             Assert.Equal("new_name", contentDisposition.Name);
106             Assert.Equal(1, contentDisposition.Parameters.Count);
107             Assert.Equal("NAME", contentDisposition.Parameters.First().Name);
108 
109             contentDisposition.Parameters.Remove(name);
110             Assert.Null(contentDisposition.Name);
111         }
112 
113         [Fact]
FileName_AddNameParameterThenUseProperty_ParametersEntryIsOverwritten()114         public void FileName_AddNameParameterThenUseProperty_ParametersEntryIsOverwritten()
115         {
116             ContentDispositionHeaderValue contentDisposition = new ContentDispositionHeaderValue("inline");
117 
118             // Note that uppercase letters are used. Comparison should happen case-insensitive.
119             NameValueHeaderValue fileName = new NameValueHeaderValue("FILENAME", "old_name");
120             contentDisposition.Parameters.Add(fileName);
121             Assert.Equal(1, contentDisposition.Parameters.Count);
122             Assert.Equal("FILENAME", contentDisposition.Parameters.First().Name);
123 
124             contentDisposition.FileName = "new_name";
125             Assert.Equal("new_name", contentDisposition.FileName);
126             Assert.Equal(1, contentDisposition.Parameters.Count);
127             Assert.Equal("FILENAME", contentDisposition.Parameters.First().Name);
128 
129             contentDisposition.Parameters.Remove(fileName);
130             Assert.Null(contentDisposition.FileName);
131         }
132 
133         [Fact]
FileName_NeedsEncoding_EncodedAndDecodedCorrectly()134         public void FileName_NeedsEncoding_EncodedAndDecodedCorrectly()
135         {
136             ContentDispositionHeaderValue contentDisposition = new ContentDispositionHeaderValue("inline");
137 
138             contentDisposition.FileName = "File\u00C3Name.bat";
139             Assert.Equal("File\u00C3Name.bat", contentDisposition.FileName);
140             Assert.Equal(1, contentDisposition.Parameters.Count);
141             Assert.Equal("filename", contentDisposition.Parameters.First().Name);
142             Assert.Equal("\"=?utf-8?B?RmlsZcODTmFtZS5iYXQ=?=\"", contentDisposition.Parameters.First().Value);
143 
144             contentDisposition.Parameters.Remove(contentDisposition.Parameters.First());
145             Assert.Null(contentDisposition.FileName);
146         }
147 
148         [Fact]
FileName_UnknownOrBadEncoding_PropertyFails()149         public void FileName_UnknownOrBadEncoding_PropertyFails()
150         {
151             ContentDispositionHeaderValue contentDisposition = new ContentDispositionHeaderValue("inline");
152 
153             // Note that uppercase letters are used. Comparison should happen case-insensitive.
154             NameValueHeaderValue fileName = new NameValueHeaderValue("FILENAME", "\"=?utf-99?Q?R=mlsZcODTmFtZS5iYXQ=?=\"");
155             contentDisposition.Parameters.Add(fileName);
156             Assert.Equal(1, contentDisposition.Parameters.Count);
157             Assert.Equal("FILENAME", contentDisposition.Parameters.First().Name);
158             Assert.Equal("\"=?utf-99?Q?R=mlsZcODTmFtZS5iYXQ=?=\"", contentDisposition.Parameters.First().Value);
159             Assert.Equal("\"=?utf-99?Q?R=mlsZcODTmFtZS5iYXQ=?=\"", contentDisposition.FileName);
160 
161             contentDisposition.FileName = "new_name";
162             Assert.Equal("new_name", contentDisposition.FileName);
163             Assert.Equal(1, contentDisposition.Parameters.Count);
164             Assert.Equal("FILENAME", contentDisposition.Parameters.First().Name);
165 
166             contentDisposition.Parameters.Remove(fileName);
167             Assert.Null(contentDisposition.FileName);
168         }
169 
170         [Fact]
FileNameStar_AddNameParameterThenUseProperty_ParametersEntryIsOverwritten()171         public void FileNameStar_AddNameParameterThenUseProperty_ParametersEntryIsOverwritten()
172         {
173             ContentDispositionHeaderValue contentDisposition = new ContentDispositionHeaderValue("inline");
174 
175             // Note that uppercase letters are used. Comparison should happen case-insensitive.
176             NameValueHeaderValue fileNameStar = new NameValueHeaderValue("FILENAME*", "old_name");
177             contentDisposition.Parameters.Add(fileNameStar);
178             Assert.Equal(1, contentDisposition.Parameters.Count);
179             Assert.Equal("FILENAME*", contentDisposition.Parameters.First().Name);
180             Assert.Null(contentDisposition.FileNameStar); // Decode failure
181 
182             contentDisposition.FileNameStar = "new_name";
183             Assert.Equal("new_name", contentDisposition.FileNameStar);
184             Assert.Equal(1, contentDisposition.Parameters.Count);
185             Assert.Equal("FILENAME*", contentDisposition.Parameters.First().Name);
186             Assert.Equal("utf-8\'\'new_name", contentDisposition.Parameters.First().Value);
187 
188             contentDisposition.Parameters.Remove(fileNameStar);
189             Assert.Null(contentDisposition.FileNameStar);
190         }
191 
192         [Theory]
193         [InlineData("no_quotes")]
194         [InlineData("one'quote")]
195         [InlineData("'triple'quotes'")]
FileNameStar_NotTwoQuotes_IsNull(string value)196         public void FileNameStar_NotTwoQuotes_IsNull(string value)
197         {
198             ContentDispositionHeaderValue contentDisposition = new ContentDispositionHeaderValue("inline");
199 
200             // Note that uppercase letters are used. Comparison should happen case-insensitive.
201             NameValueHeaderValue fileNameStar = new NameValueHeaderValue("FILENAME*", value);
202             contentDisposition.Parameters.Add(fileNameStar);
203             Assert.Equal(1, contentDisposition.Parameters.Count);
204             Assert.Same(fileNameStar, contentDisposition.Parameters.First());
205             Assert.Null(contentDisposition.FileNameStar); // Decode failure
206         }
207 
208         [Fact]
FileNameStar_NeedsEncoding_EncodedAndDecodedCorrectly()209         public void FileNameStar_NeedsEncoding_EncodedAndDecodedCorrectly()
210         {
211             ContentDispositionHeaderValue contentDisposition = new ContentDispositionHeaderValue("inline");
212 
213             contentDisposition.FileNameStar = "File\u00C3Name.bat";
214             Assert.Equal("File\u00C3Name.bat", contentDisposition.FileNameStar);
215             Assert.Equal(1, contentDisposition.Parameters.Count);
216             Assert.Equal("filename*", contentDisposition.Parameters.First().Name);
217             Assert.Equal("utf-8\'\'File%C3%83Name.bat", contentDisposition.Parameters.First().Value);
218 
219             contentDisposition.Parameters.Remove(contentDisposition.Parameters.First());
220             Assert.Null(contentDisposition.FileNameStar);
221         }
222 
223         [Fact]
FileNameStar_UnknownOrBadEncoding_PropertyFails()224         public void FileNameStar_UnknownOrBadEncoding_PropertyFails()
225         {
226             ContentDispositionHeaderValue contentDisposition = new ContentDispositionHeaderValue("inline");
227 
228             // Note that uppercase letters are used. Comparison should happen case-insensitive.
229             NameValueHeaderValue fileNameStar = new NameValueHeaderValue("FILENAME*", "utf-99'lang'File%CZName.bat");
230             contentDisposition.Parameters.Add(fileNameStar);
231             Assert.Equal(1, contentDisposition.Parameters.Count);
232             Assert.Same(fileNameStar, contentDisposition.Parameters.First());
233             Assert.Null(contentDisposition.FileNameStar); // Decode failure
234 
235             contentDisposition.FileNameStar = "new_name";
236             Assert.Equal("new_name", contentDisposition.FileNameStar);
237             Assert.Equal(1, contentDisposition.Parameters.Count);
238             Assert.Equal("FILENAME*", contentDisposition.Parameters.First().Name);
239 
240             contentDisposition.Parameters.Remove(fileNameStar);
241             Assert.Null(contentDisposition.FileNameStar);
242         }
243 
244         [Fact]
Dates_AddDateParameterThenUseProperty_ParametersEntryIsOverwritten()245         public void Dates_AddDateParameterThenUseProperty_ParametersEntryIsOverwritten()
246         {
247             string validDateString = "\"Tue, 15 Nov 1994 08:12:31 GMT\"";
248             DateTimeOffset validDate = DateTimeOffset.Parse("Tue, 15 Nov 1994 08:12:31 GMT");
249 
250             ContentDispositionHeaderValue contentDisposition = new ContentDispositionHeaderValue("inline");
251 
252             // Note that uppercase letters are used. Comparison should happen case-insensitive.
253             NameValueHeaderValue dateParameter = new NameValueHeaderValue("Creation-DATE", validDateString);
254             contentDisposition.Parameters.Add(dateParameter);
255             Assert.Equal(1, contentDisposition.Parameters.Count);
256             Assert.Equal("Creation-DATE", contentDisposition.Parameters.First().Name);
257 
258             Assert.Equal(validDate, contentDisposition.CreationDate);
259 
260             DateTimeOffset newDate = validDate.AddSeconds(1);
261             contentDisposition.CreationDate = newDate;
262             Assert.Equal(newDate, contentDisposition.CreationDate);
263             Assert.Equal(1, contentDisposition.Parameters.Count);
264             Assert.Equal("Creation-DATE", contentDisposition.Parameters.First().Name);
265             Assert.Equal("\"Tue, 15 Nov 1994 08:12:32 GMT\"", contentDisposition.Parameters.First().Value);
266 
267             contentDisposition.Parameters.Remove(dateParameter);
268             Assert.Null(contentDisposition.CreationDate);
269         }
270 
271         [Fact]
Dates_InvalidDates_PropertyFails()272         public void Dates_InvalidDates_PropertyFails()
273         {
274             string invalidDateString = "\"Tue, 15 Nov 94 08:12 GMT\"";
275 
276             ContentDispositionHeaderValue contentDisposition = new ContentDispositionHeaderValue("inline");
277 
278             // Note that uppercase letters are used. Comparison should happen case-insensitive.
279             NameValueHeaderValue dateParameter = new NameValueHeaderValue("read-DATE", invalidDateString);
280             contentDisposition.Parameters.Add(dateParameter);
281             Assert.Equal(1, contentDisposition.Parameters.Count);
282             Assert.Equal("read-DATE", contentDisposition.Parameters.First().Name);
283 
284             Assert.Null(contentDisposition.ReadDate);
285 
286             contentDisposition.ReadDate = null;
287             Assert.Null(contentDisposition.ReadDate);
288             Assert.Equal(0, contentDisposition.Parameters.Count);
289         }
290 
291         [Fact]
Size_AddSizeParameterThenUseProperty_ParametersEntryIsOverwritten()292         public void Size_AddSizeParameterThenUseProperty_ParametersEntryIsOverwritten()
293         {
294             ContentDispositionHeaderValue contentDisposition = new ContentDispositionHeaderValue("inline");
295 
296             // Note that uppercase letters are used. Comparison should happen case-insensitive.
297             NameValueHeaderValue sizeParameter = new NameValueHeaderValue("SIZE", "279172874239");
298             contentDisposition.Parameters.Add(sizeParameter);
299             Assert.Equal(1, contentDisposition.Parameters.Count);
300             Assert.Equal("SIZE", contentDisposition.Parameters.First().Name);
301             Assert.Equal(279172874239, contentDisposition.Size);
302 
303             contentDisposition.Size = 279172874240;
304             Assert.Equal(279172874240, contentDisposition.Size);
305             Assert.Equal(1, contentDisposition.Parameters.Count);
306             Assert.Equal("SIZE", contentDisposition.Parameters.First().Name);
307 
308             contentDisposition.Parameters.Remove(sizeParameter);
309             Assert.Null(contentDisposition.Size);
310         }
311 
312         [Fact]
Size_InvalidSizes_PropertyFails()313         public void Size_InvalidSizes_PropertyFails()
314         {
315             ContentDispositionHeaderValue contentDisposition = new ContentDispositionHeaderValue("inline");
316 
317             // Note that uppercase letters are used. Comparison should happen case-insensitive.
318             NameValueHeaderValue sizeParameter = new NameValueHeaderValue("SIZE", "-279172874239");
319             contentDisposition.Parameters.Add(sizeParameter);
320             Assert.Equal(1, contentDisposition.Parameters.Count);
321             Assert.Equal("SIZE", contentDisposition.Parameters.First().Name);
322             Assert.Null(contentDisposition.Size);
323 
324             // Negatives not allowed
325             Assert.Throws<ArgumentOutOfRangeException>(() => { contentDisposition.Size = -279172874240; });
326 
327             Assert.Null(contentDisposition.Size);
328             Assert.Equal(1, contentDisposition.Parameters.Count);
329             Assert.Equal("SIZE", contentDisposition.Parameters.First().Name);
330 
331             contentDisposition.Parameters.Remove(sizeParameter);
332             Assert.Null(contentDisposition.Size);
333         }
334 
335         [Theory]
336         [InlineData(null)]
337         [InlineData(66)]
Size_ValueSetGet_RoundtripsSuccessfully(int? value)338         public void Size_ValueSetGet_RoundtripsSuccessfully(int? value)
339         {
340             var contentDisposition = new ContentDispositionHeaderValue("inline") { Size = value };
341             Assert.Equal(value, contentDisposition.Size);
342         }
343 
344         [Fact]
ToString_UseDifferentContentDispositions_AllSerializedCorrectly()345         public void ToString_UseDifferentContentDispositions_AllSerializedCorrectly()
346         {
347             ContentDispositionHeaderValue contentDisposition = new ContentDispositionHeaderValue("inline");
348             Assert.Equal("inline", contentDisposition.ToString());
349 
350             contentDisposition.Name = "myname";
351             Assert.Equal("inline; name=myname", contentDisposition.ToString());
352 
353             contentDisposition.FileName = "my File Name";
354             Assert.Equal("inline; name=myname; filename=\"my File Name\"", contentDisposition.ToString());
355 
356             contentDisposition.CreationDate = new DateTimeOffset(new DateTime(2011, 2, 15, 8, 0, 0, DateTimeKind.Utc));
357             Assert.Equal("inline; name=myname; filename=\"my File Name\"; creation-date="
358                 + "\"Tue, 15 Feb 2011 08:00:00 GMT\"", contentDisposition.ToString());
359 
360             contentDisposition.Parameters.Add(new NameValueHeaderValue("custom", "\"custom value\""));
361             Assert.Equal("inline; name=myname; filename=\"my File Name\"; creation-date="
362                 + "\"Tue, 15 Feb 2011 08:00:00 GMT\"; custom=\"custom value\"", contentDisposition.ToString());
363 
364             contentDisposition.Name = null;
365             Assert.Equal("inline; filename=\"my File Name\"; creation-date="
366                 + "\"Tue, 15 Feb 2011 08:00:00 GMT\"; custom=\"custom value\"", contentDisposition.ToString());
367 
368             contentDisposition.FileNameStar = "File%Name";
369             Assert.Equal("inline; filename=\"my File Name\"; creation-date="
370                 + "\"Tue, 15 Feb 2011 08:00:00 GMT\"; custom=\"custom value\"; filename*=utf-8\'\'File%25Name",
371                 contentDisposition.ToString());
372 
373             contentDisposition.FileName = null;
374             Assert.Equal("inline; creation-date=\"Tue, 15 Feb 2011 08:00:00 GMT\"; custom=\"custom value\";"
375                 + " filename*=utf-8\'\'File%25Name", contentDisposition.ToString());
376 
377             contentDisposition.CreationDate = null;
378             Assert.Equal("inline; custom=\"custom value\"; filename*=utf-8\'\'File%25Name",
379                 contentDisposition.ToString());
380         }
381 
382         [Fact]
GetHashCode_UseContentDispositionWithAndWithoutParameters_SameOrDifferentHashCodes()383         public void GetHashCode_UseContentDispositionWithAndWithoutParameters_SameOrDifferentHashCodes()
384         {
385             ContentDispositionHeaderValue contentDisposition1 = new ContentDispositionHeaderValue("inline");
386             ContentDispositionHeaderValue contentDisposition2 = new ContentDispositionHeaderValue("inline");
387             contentDisposition2.Name = "myname";
388             ContentDispositionHeaderValue contentDisposition3 = new ContentDispositionHeaderValue("inline");
389             contentDisposition3.Parameters.Add(new NameValueHeaderValue("name", "value"));
390             ContentDispositionHeaderValue contentDisposition4 = new ContentDispositionHeaderValue("INLINE");
391             ContentDispositionHeaderValue contentDisposition5 = new ContentDispositionHeaderValue("INLINE");
392             contentDisposition5.Parameters.Add(new NameValueHeaderValue("NAME", "MYNAME"));
393 
394             Assert.NotEqual(contentDisposition1.GetHashCode(), contentDisposition2.GetHashCode()); // "No params vs. name."
395             Assert.NotEqual(contentDisposition1.GetHashCode(), contentDisposition3.GetHashCode()); // "No params vs. custom param."
396             Assert.NotEqual(contentDisposition2.GetHashCode(), contentDisposition3.GetHashCode()); // "name vs. custom param."
397             Assert.Equal(contentDisposition1.GetHashCode(), contentDisposition4.GetHashCode()); // "Different casing."
398             Assert.Equal(contentDisposition2.GetHashCode(), contentDisposition5.GetHashCode()); // "Different casing in name."
399         }
400 
401         [Fact]
Equals_UseContentDispositionWithAndWithoutParameters_EqualOrNotEqualNoExceptions()402         public void Equals_UseContentDispositionWithAndWithoutParameters_EqualOrNotEqualNoExceptions()
403         {
404             ContentDispositionHeaderValue contentDisposition1 = new ContentDispositionHeaderValue("inline");
405             ContentDispositionHeaderValue contentDisposition2 = new ContentDispositionHeaderValue("inline");
406             contentDisposition2.Name = "myName";
407             ContentDispositionHeaderValue contentDisposition3 = new ContentDispositionHeaderValue("inline");
408             contentDisposition3.Parameters.Add(new NameValueHeaderValue("name", "value"));
409             ContentDispositionHeaderValue contentDisposition4 = new ContentDispositionHeaderValue("INLINE");
410             ContentDispositionHeaderValue contentDisposition5 = new ContentDispositionHeaderValue("INLINE");
411             contentDisposition5.Parameters.Add(new NameValueHeaderValue("NAME", "MYNAME"));
412             ContentDispositionHeaderValue contentDisposition6 = new ContentDispositionHeaderValue("INLINE");
413             contentDisposition6.Parameters.Add(new NameValueHeaderValue("NAME", "MYNAME"));
414             contentDisposition6.Parameters.Add(new NameValueHeaderValue("custom", "value"));
415             ContentDispositionHeaderValue contentDisposition7 = new ContentDispositionHeaderValue("attachment");
416 
417             Assert.False(contentDisposition1.Equals(contentDisposition2)); // "No params vs. name."
418             Assert.False(contentDisposition2.Equals(contentDisposition1)); // "name vs. no params."
419             Assert.False(contentDisposition1.Equals(null)); // "No params vs. <null>."
420             Assert.False(contentDisposition1.Equals(contentDisposition3)); // "No params vs. custom param."
421             Assert.False(contentDisposition2.Equals(contentDisposition3)); // "name vs. custom param."
422             Assert.True(contentDisposition1.Equals(contentDisposition4)); // "Different casing."
423             Assert.True(contentDisposition2.Equals(contentDisposition5)); // "Different casing in name."
424             Assert.False(contentDisposition5.Equals(contentDisposition6)); // "name vs. custom param."
425             Assert.False(contentDisposition1.Equals(contentDisposition7)); // "inline vs. text/other."
426         }
427 
428         [Fact]
Clone_Call_CloneFieldsMatchSourceFields()429         public void Clone_Call_CloneFieldsMatchSourceFields()
430         {
431             ContentDispositionHeaderValue source = new ContentDispositionHeaderValue("attachment");
432             ContentDispositionHeaderValue clone = (ContentDispositionHeaderValue)((ICloneable)source).Clone();
433             Assert.Equal(source.DispositionType, clone.DispositionType);
434             Assert.Equal(0, clone.Parameters.Count);
435 
436             source.Name = "myName";
437             clone = (ContentDispositionHeaderValue)((ICloneable)source).Clone();
438             Assert.Equal(source.DispositionType, clone.DispositionType);
439             Assert.Equal("myName", clone.Name);
440             Assert.Equal(1, clone.Parameters.Count);
441 
442             source.Parameters.Add(new NameValueHeaderValue("custom", "customValue"));
443             clone = (ContentDispositionHeaderValue)((ICloneable)source).Clone();
444             Assert.Equal(source.DispositionType, clone.DispositionType);
445             Assert.Equal("myName", clone.Name);
446             Assert.Equal(2, clone.Parameters.Count);
447             Assert.Equal("custom", clone.Parameters.ElementAt(1).Name);
448             Assert.Equal("customValue", clone.Parameters.ElementAt(1).Value);
449         }
450 
451         [Fact]
GetDispositionTypeLength_DifferentValidScenarios_AllReturnNonZero()452         public void GetDispositionTypeLength_DifferentValidScenarios_AllReturnNonZero()
453         {
454             object result = null;
455             ContentDispositionHeaderValue value = null;
456 
457             Assert.Equal(7, ContentDispositionHeaderValue.GetDispositionTypeLength("inline , other/name", 0,
458                 out result));
459             value = (ContentDispositionHeaderValue)result;
460             Assert.Equal("inline", value.DispositionType);
461             Assert.Equal(0, value.Parameters.Count);
462 
463             Assert.Equal(6, ContentDispositionHeaderValue.GetDispositionTypeLength("inline", 0, out result));
464             value = (ContentDispositionHeaderValue)result;
465             Assert.Equal("inline", value.DispositionType);
466             Assert.Equal(0, value.Parameters.Count);
467 
468             Assert.Equal(19, ContentDispositionHeaderValue.GetDispositionTypeLength("inline; name=MyName", 0,
469                 out result));
470             value = (ContentDispositionHeaderValue)result;
471             Assert.Equal("inline", value.DispositionType);
472             Assert.Equal("MyName", value.Name);
473             Assert.Equal(1, value.Parameters.Count);
474 
475             Assert.Equal(32, ContentDispositionHeaderValue.GetDispositionTypeLength(" inline; custom=value;name=myName",
476                 1, out result));
477             value = (ContentDispositionHeaderValue)result;
478             Assert.Equal("inline", value.DispositionType);
479             Assert.Equal("myName", value.Name);
480             Assert.Equal(2, value.Parameters.Count);
481 
482             Assert.Equal(14, ContentDispositionHeaderValue.GetDispositionTypeLength(" inline; custom, next",
483                 1, out result));
484             value = (ContentDispositionHeaderValue)result;
485             Assert.Equal("inline", value.DispositionType);
486             Assert.Null(value.Name);
487             Assert.Equal(1, value.Parameters.Count);
488             Assert.Equal("custom", value.Parameters.ElementAt(0).Name);
489             Assert.Null(value.Parameters.ElementAt(0).Value);
490 
491             Assert.Equal(40, ContentDispositionHeaderValue.GetDispositionTypeLength(
492                 "inline ; custom =\r\n \"x\" ; name = myName , next", 0, out result));
493             value = (ContentDispositionHeaderValue)result;
494             Assert.Equal("inline", value.DispositionType);
495             Assert.Equal("myName", value.Name);
496             Assert.Equal(2, value.Parameters.Count);
497             Assert.Equal("custom", value.Parameters.ElementAt(0).Name);
498             Assert.Equal("\"x\"", value.Parameters.ElementAt(0).Value);
499             Assert.Equal("name", value.Parameters.ElementAt(1).Name);
500             Assert.Equal("myName", value.Parameters.ElementAt(1).Value);
501 
502             Assert.Equal(29, ContentDispositionHeaderValue.GetDispositionTypeLength(
503                 "inline;custom=\"x\";name=myName,next", 0, out result));
504             value = (ContentDispositionHeaderValue)result;
505             Assert.Equal("inline", value.DispositionType);
506             Assert.Equal("myName", value.Name);
507             Assert.Equal(2, value.Parameters.Count);
508             Assert.Equal("custom", value.Parameters.ElementAt(0).Name);
509             Assert.Equal("\"x\"", value.Parameters.ElementAt(0).Value);
510             Assert.Equal("name", value.Parameters.ElementAt(1).Name);
511             Assert.Equal("myName", value.Parameters.ElementAt(1).Value);
512         }
513 
514         [Fact]
GetDispositionTypeLength_DifferentInvalidScenarios_AllReturnZero()515         public void GetDispositionTypeLength_DifferentInvalidScenarios_AllReturnZero()
516         {
517             object result = null;
518 
519             Assert.Equal(0, ContentDispositionHeaderValue.GetDispositionTypeLength(" inline", 0, out result));
520             Assert.Null(result);
521             Assert.Equal(0, ContentDispositionHeaderValue.GetDispositionTypeLength("inline;", 0, out result));
522             Assert.Null(result);
523             Assert.Equal(0, ContentDispositionHeaderValue.GetDispositionTypeLength("inline;name=", 0, out result));
524             Assert.Null(result);
525             Assert.Equal(0, ContentDispositionHeaderValue.GetDispositionTypeLength("inline;name=value;", 0, out result));
526             Assert.Null(result);
527             Assert.Equal(0, ContentDispositionHeaderValue.GetDispositionTypeLength("inline;", 0, out result));
528             Assert.Null(result);
529             Assert.Equal(0, ContentDispositionHeaderValue.GetDispositionTypeLength(null, 0, out result));
530             Assert.Null(result);
531             Assert.Equal(0, ContentDispositionHeaderValue.GetDispositionTypeLength(string.Empty, 0, out result));
532             Assert.Null(result);
533         }
534 
535         [Fact]
Parse_SetOfValidValueStrings_ParsedCorrectly()536         public void Parse_SetOfValidValueStrings_ParsedCorrectly()
537         {
538             ContentDispositionHeaderValue expected = new ContentDispositionHeaderValue("inline");
539             CheckValidParse("\r\n inline  ", expected);
540             CheckValidParse("inline", expected);
541 
542             // We don't have to test all possible input strings, since most of the pieces are handled by other parsers.
543             // The purpose of this test is to verify that these other parsers are combined correctly to build a
544             // Content-Disposition parser.
545             expected.Name = "myName";
546             CheckValidParse("\r\n inline  ;  name =   myName ", expected);
547             CheckValidParse("  inline;name=myName", expected);
548 
549             expected.Name = null;
550             expected.DispositionType = "attachment";
551             expected.FileName = "foo-ae.html";
552             expected.Parameters.Add(new NameValueHeaderValue("filename*", "UTF-8''foo-%c3%a4.html"));
553             CheckValidParse(@"attachment; filename*=UTF-8''foo-%c3%a4.html; filename=foo-ae.html", expected);
554         }
555 
556         [Fact]
Parse_SetOfInvalidValueStrings_Throws()557         public void Parse_SetOfInvalidValueStrings_Throws()
558         {
559             CheckInvalidParse("");
560             CheckInvalidParse("  ");
561             CheckInvalidParse(null);
562             CheckInvalidParse("inline\u4F1A");
563             CheckInvalidParse("inline ,");
564             CheckInvalidParse("inline,");
565             CheckInvalidParse("inline; name=myName ,");
566             CheckInvalidParse("inline; name=myName,");
567             CheckInvalidParse("inline; name=my\u4F1AName");
568             CheckInvalidParse("inline/");
569         }
570 
571         [Fact]
TryParse_SetOfValidValueStrings_ParsedCorrectly()572         public void TryParse_SetOfValidValueStrings_ParsedCorrectly()
573         {
574             ContentDispositionHeaderValue expected = new ContentDispositionHeaderValue("inline");
575             CheckValidTryParse("\r\n inline  ", expected);
576             CheckValidTryParse("inline", expected);
577 
578             // We don't have to test all possible input strings, since most of the pieces are handled by other parsers.
579             // The purpose of this test is to verify that these other parsers are combined correctly to build a
580             // Content-Disposition parser.
581             expected.Name = "myName";
582             CheckValidTryParse("\r\n inline  ;  name =   myName ", expected);
583             CheckValidTryParse("  inline;name=myName", expected);
584         }
585 
586         [Fact]
TryParse_SetOfInvalidValueStrings_ReturnsFalse()587         public void TryParse_SetOfInvalidValueStrings_ReturnsFalse()
588         {
589             CheckInvalidTryParse("");
590             CheckInvalidTryParse("  ");
591             CheckInvalidTryParse(null);
592             CheckInvalidTryParse("inline\u4F1A");
593             CheckInvalidTryParse("inline ,");
594             CheckInvalidTryParse("inline,");
595             CheckInvalidTryParse("inline; name=myName ,");
596             CheckInvalidTryParse("inline; name=myName,");
597             CheckInvalidTryParse("text/");
598         }
599 
600         #region Tests from HenrikN
601 
602 
603         private static Dictionary<string, ContentDispositionValue> ContentDispositionTestCases = new Dictionary<string, ContentDispositionValue>()
604         {
605             // Valid values
606             { "valid1", new ContentDispositionValue(@"inline", @"This should be equivalent to not including the header at all.", true) },
607             { "valid2", new ContentDispositionValue(@"inline; filename=""foo.html""", @"'inline', specifying a filename of foo.html", true) },
608             { "valid3", new ContentDispositionValue(@"inline; filename=""Not an attachment!""", @"'inline', specifying a filename of Not an attachment! - this checks for proper parsing for disposition types.", true) },
609             { "valid4", new ContentDispositionValue(@"inline; filename=""foo.pdf""", @"'inline', specifying a filename of foo.pdf", true) },
610             { "valid5", new ContentDispositionValue(@"attachment", @"'attachment' only", true) },
611             { "valid6", new ContentDispositionValue(@"ATTACHMENT", @"'ATTACHMENT' only", true) },
612             { "valid7", new ContentDispositionValue(@"attachment; filename=""foo.html""", @"'attachment', specifying a filename of foo.html", true) },
613             { "valid8", new ContentDispositionValue(@"attachment; filename=""f\oo.html""", @"'attachment', specifying a filename of f\oo.html (the first 'o' being escaped)", true) },
614             { "valid9", new ContentDispositionValue(@"attachment; filename=""\""quoting\"" tested.html""", @"'attachment', specifying a filename of \""quoting\"" tested.html (using double quotes around ""quoting"" to test... quoting)", true) },
615             { "valid10", new ContentDispositionValue(@"attachment; filename=""Here's a semicolon;.html""", @"'attachment', specifying a filename of Here's a semicolon;.html - this checks for proper parsing for parameters. ", true) },
616             { "valid11", new ContentDispositionValue(@"attachment; foo=""bar""; filename=""foo.html""", @"'attachment', specifying a filename of foo.html and an extension parameter ""foo"" which should be ignored (see <a href=""http://greenbytes.de/tech/webdav/rfc2183.html#rfc.section.2.8"">Section 2.8 of RFC 2183</a>.).", true) },
617             { "valid12", new ContentDispositionValue(@"attachment; foo=""\""\\"";filename=""foo.html""", @"'attachment', specifying a filename of foo.html and an extension parameter ""foo"" which should be ignored (see <a href=""http://greenbytes.de/tech/webdav/rfc2183.html#rfc.section.2.8"">Section 2.8 of RFC 2183</a>.). The extension parameter actually uses backslash-escapes. This tests whether the UA properly skips the parameter.", true) },
618             { "valid13", new ContentDispositionValue(@"attachment; FILENAME=""foo.html""", @"'attachment', specifying a filename of foo.html", true) },
619             { "valid14", new ContentDispositionValue(@"attachment; filename=foo.html", @"'attachment', specifying a filename of foo.html using a token instead of a quoted-string.", true) },
620             { "valid15", new ContentDispositionValue(@"attachment; filename='foo.bar'", @"'attachment', specifying a filename of 'foo.bar' using single quotes. ", true) },
621             { "valid16", new ContentDispositionValue(@"attachment; filename=""foo-\u00E4.html""", @"'attachment', specifying a filename of foo-\u00E4.html, using plain ISO-8859-1", true) },
622             { "valid17", new ContentDispositionValue(@"attachment; filename=""foo-&#xc3;&#xa4;.html""", @"'attachment', specifying a filename of foo-&#xc3;&#xa4;.html, which happens to be foo-\u00E4.html using UTF-8 encoding.", true) },
623             { "valid18", new ContentDispositionValue(@"attachment; filename=""foo-%41.html""", @"'attachment', specifying a filename of foo-%41.html", true) },
624             { "valid19", new ContentDispositionValue(@"attachment; filename=""50%.html""", @"'attachment', specifying a filename of 50%.html", true) },
625             { "valid20", new ContentDispositionValue(@"attachment; filename=""foo-%\41.html""", @"'attachment', specifying a filename of foo-%41.html, using an escape character (this tests whether adding an escape character inside a %xx sequence can be used to disable the non-conformant %xx-unescaping).", true) },
626             { "valid21", new ContentDispositionValue(@"attachment; name=""foo-%41.html""", @"'attachment', specifying a <i>name</i> parameter of foo-%41.html. (this test was added to observe the behavior of the (unspecified) treatment of ""name"" as synonym for ""filename""; see <a href=""http://www.imc.org/ietf-smtp/mail-archive/msg05023.html"">Ned Freed's summary</a> where this comes from in MIME messages)", true) },
627             { "valid22", new ContentDispositionValue(@"attachment; filename=""\u00E4-%41.html""", @"'attachment', specifying a filename parameter of \u00E4-%41.html. (this test was added to observe the behavior when non-ASCII characters and percent-hexdig sequences are combined)", true) },
628             { "valid23", new ContentDispositionValue(@"attachment; filename=""foo-%c3%a4-%e2%82%ac.html""", @"'attachment', specifying a filename of foo-%c3%a4-%e2%82%ac.html, using raw percent encoded UTF-8 to represent foo-\u00E4-&#x20ac;.html", true) },
629             { "valid24", new ContentDispositionValue(@"attachment; filename =""foo.html""", @"'attachment', specifying a filename of foo.html, with one blank space <em>before</em> the equals character.", true) },
630             { "valid25", new ContentDispositionValue(@"attachment; xfilename=foo.html", @"'attachment', specifying an ""xfilename"" parameter.", true) },
631             { "valid26", new ContentDispositionValue(@"attachment; filename=""/foo.html""", @"'attachment', specifying an absolute filename in the filesystem root.", true) },
632             { "valid27", new ContentDispositionValue(@"attachment; filename=""\\foo.html""", @"'attachment', specifying an absolute filename in the filesystem root.", true) },
633             { "valid28", new ContentDispositionValue(@"attachment; creation-date=""Wed, 12 Feb 1997 16:29:51 -0500""", @"'attachment', plus creation-date (see <a href=""http://greenbytes.de/tech/webdav/rfc2183.html#rfc.section.2.4"">Section 2.4 of RFC 2183</a>)", true) },
634             { "valid29", new ContentDispositionValue(@"attachment; modification-date=""Wed, 12 Feb 1997 16:29:51 -0500""", @"'attachment', plus modification-date (see <a href=""http://greenbytes.de/tech/webdav/rfc2183.html#rfc.section.2.5"">Section 2.5 of RFC 2183</a>)", true) },
635             { "valid30", new ContentDispositionValue(@"foobar", @"This should be equivalent to using ""attachment"".", true) },
636             { "valid31", new ContentDispositionValue(@"attachment; example=""filename=example.txt""", @"'attachment', with no filename parameter", true) },
637             { "valid32", new ContentDispositionValue(@"attachment; filename*=iso-8859-1''foo-%E4.html", @"'attachment', specifying a filename of foo-\u00E4.html, using RFC2231 encoded ISO-8859-1", true) },
638             { "valid33", new ContentDispositionValue(@"attachment; filename*=UTF-8''foo-%c3%a4-%e2%82%ac.html", @"'attachment', specifying a filename of foo-\u00E4-&#x20ac;.html, using RFC2231 encoded UTF-8", true) },
639             { "valid34", new ContentDispositionValue(@"attachment; filename*=''foo-%c3%a4-%e2%82%ac.html", @"Behavior is undefined in RFC 2231, the charset part is missing, although UTF-8 was used.", true) },
640             { "valid35", new ContentDispositionValue(@"attachment; filename*=UTF-8''foo-a%cc%88.html", @"'attachment', specifying a filename of foo-\u00E4.html, using RFC2231 encoded UTF-8, but choosing the decomposed form (lowercase a plus COMBINING DIAERESIS) -- on a Windows target system, this should be translated to the preferred Unicode normal form (composed).", true) },
641             { "valid36", new ContentDispositionValue(@"attachment; filename*= UTF-8''foo-%c3%a4.html", @"'attachment', specifying a filename of foo-\u00E4.html, using RFC2231 encoded UTF-8, with whitespace after ""*=""", true) },
642             { "valid37", new ContentDispositionValue(@"attachment; filename* =UTF-8''foo-%c3%a4.html", @"'attachment', specifying a filename of foo-\u00E4.html, using RFC2231 encoded UTF-8, with whitespace inside ""* =""", true) },
643             { "valid38", new ContentDispositionValue(@"attachment; filename*=UTF-8''A-%2541.html", @"'attachment', specifying a filename of A-%41.html, using RFC2231 encoded UTF-8.", true) },
644             { "valid39", new ContentDispositionValue(@"attachment; filename*=UTF-8''%5cfoo.html", @"'attachment', specifying a filename of /foo.html, using RFC2231 encoded UTF-8.", true) },
645             { "valid40", new ContentDispositionValue(@"attachment; filename*0=""foo.""; filename*1=""html""", @"'attachment', specifying a filename of foo.html, using RFC2231-style parameter continuations.", true) },
646             { "valid41", new ContentDispositionValue(@"attachment; filename*0*=UTF-8''foo-%c3%a4; filename*1="".html""", @"'attachment', specifying a filename of foo-\u00E4.html, using both RFC2231-style parameter continuations and UTF-8 encoding.", true) },
647             { "valid42", new ContentDispositionValue(@"attachment; filename*0=""foo""; filename*01=""bar""", @"'attachment', specifying a filename of foo (the parameter filename*01 should be ignored because of the leading zero)", true) },
648             { "valid43", new ContentDispositionValue(@"attachment; filename*0=""foo""; filename*2=""bar""", @"'attachment', specifying a filename of foo (the parameter filename*2 should be ignored because there's no filename*1 parameter)", true) },
649             { "valid44", new ContentDispositionValue(@"attachment; filename*1=""foo.""; filename*2=""html""", @"'attachment' (the filename* parameters should be ignored because filename*0 is missing)", true) },
650             { "valid45", new ContentDispositionValue(@"attachment; filename*1=""bar""; filename*0=""foo""", "'attachment', specifying a filename of foobar", true) },
651             { "valid46", new ContentDispositionValue(@"attachment; filename=""foo-ae.html""; filename*=UTF-8''foo-%c3%a4.html", @"'attachment', specifying a filename of foo-ae.html in the traditional format, and foo-\u00E4.html in RFC2231 format.", true) },
652             { "valid47", new ContentDispositionValue(@"attachment; filename*=UTF-8''foo-%c3%a4.html; filename=""foo-ae.html""", @"'attachment', specifying a filename of foo-ae.html in the traditional format, and foo-\u00E4.html in RFC2231 format.", true) },
653             { "valid48", new ContentDispositionValue(@"attachment; foobar=x; filename=""foo.html""", @"'attachment', specifying a new parameter ""foobar"", plus a filename of foo.html in the traditional format.", true) },
654             { "valid49", new ContentDispositionValue(@"attachment; filename=""=?ISO-8859-1?Q?foo-=E4.html?=""", @"attachment; filename=""=?ISO-8859-1?Q?foo-=E4.html?=""", true) },
655             { "valid50", new ContentDispositionValue(@"attachment; filename=""=?utf-8?B?Zm9vLeQuaHRtbA==?=""", @"attachment; filename=""=?utf-8?B?Zm9vLeQuaHRtbA==?=""", true) },
656 
657             // Invalid values
658             { "invalid1", new ContentDispositionValue(@"""inline""", @"'inline' only, using double quotes", false) },
659             { "invalid2", new ContentDispositionValue(@"""attachment""", @"'attachment' only, using double quotes", false) },
660             { "invalid3", new ContentDispositionValue(@"attachment; filename=foo.html ;", @"'attachment', specifying a filename of foo.html using a token instead of a quoted-string, and adding a trailing semicolon.", false) },
661             { "invalid4", new ContentDispositionValue(@"attachment; filename=foo bar.html", @"'attachment', specifying a filename of foo bar.html without using quoting.", false) },
662             { "invalid6", new ContentDispositionValue(@"attachment; filename=foo[1](2).html", @"'attachment', specifying a filename of foo[1](2).html, but missing the quotes. Also, ""["", ""]"", ""("" and "")"" are not allowed in the HTTP <a href=""http://greenbytes.de/tech/webdav/draft-ietf-httpbis-p1-messaging-latest.html#rfc.section.1.2.2"">token</a> production.", false) },
663             { "invalid7", new ContentDispositionValue(@"attachment; filename=foo-\u00E4.html", @"'attachment', specifying a filename of foo-\u00E4.html, but missing the quotes.", false) },
664             { "invalid9", new ContentDispositionValue(@"filename=foo.html", @"Disposition type missing, filename specified.", false) },
665             { "invalid10", new ContentDispositionValue(@"x=y; filename=foo.html", @"Disposition type missing, filename specified after extension parameter.", false) },
666             { "invalid11", new ContentDispositionValue(@"""foo; filename=bar;baz""; filename=qux", @"Disposition type missing, filename ""qux"". Can it be more broken? (Probably)", false) },
667             { "invalid12", new ContentDispositionValue(@"filename=foo.html, filename=bar.html", @"Disposition type missing, two filenames specified separated by a comma (this is syntactically equivalent to have two instances of the header with one filename parameter each).", false) },
668             { "invalid13", new ContentDispositionValue(@"; filename=foo.html", @"Disposition type missing (but delimiter present), filename specified.", false) },
669             { "invalid16", new ContentDispositionValue(@"attachment; filename=""foo.html"".txt", @"'attachment', specifying a filename parameter that is broken (quoted-string followed by more characters). This is invalid syntax. ", false) },
670             { "invalid17", new ContentDispositionValue(@"attachment; filename=""bar", @"'attachment', specifying a filename parameter that is broken (missing ending double quote). This is invalid syntax.", false) },
671             { "invalid18", new ContentDispositionValue(@"attachment; filename=foo""bar;baz""qux", @"'attachment', specifying a filename parameter that is broken (disallowed characters in token syntax). This is invalid syntax.", false) },
672             { "invalid19", new ContentDispositionValue(@"attachment; filename=foo.html, attachment; filename=bar.html", @"'attachment', two comma-separated instances of the header field. As Content-Disposition doesn't use a list-style syntax, this is invalid syntax and, according to <a href=""http://greenbytes.de/tech/webdav/rfc2616.html#rfc.section.4.2.p.5"">RFC 2616, Section 4.2</a>, roughly equivalent to having two separate header field instances.", false) },
673             { "invalid20", new ContentDispositionValue(@"filename=foo.html; attachment", @"filename parameter and disposition type reversed.", false) },
674             { "invalid24", new ContentDispositionValue(@"attachment; filename==?ISO-8859-1?Q?foo-=E4.html?=", @"Uses RFC 2047 style encoded word. ""="" is invalid inside the token production, so this is invalid.", false) },
675             { "invalid25", new ContentDispositionValue(@"attachment; filename==?utf-8?B?Zm9vLeQuaHRtbA==?=", @"Uses RFC 2047 style encoded word. ""="" is invalid inside the token production, so this is invalid.", false) },
676         };
677 
678         #region Parsing
679 
680         [Fact]
681         public void ContentDispositionHeaderValue_Parse_ExpectedResult()
682         {
683             foreach (var cd in ContentDispositionTestCases.Values)
684             {
685                 ContentDispositionHeaderValue header = Parse(cd);
686             }
687         }
688 
689         [Fact]
690         public void ContentDispositionHeaderValue_TryParse_ExpectedResult()
691         {
692             foreach (var cd in ContentDispositionTestCases.Values)
693             {
694                 ContentDispositionHeaderValue header = TryParse(cd);
695             }
696         }
697 
698         [Fact]
699         public void ContentDispositionHeader_Valid1_Success()
700         {
701             ContentDispositionValue cd = ContentDispositionTestCases["valid1"];
702             ContentDispositionHeaderValue header = TryParse(cd);
703             ValidateHeaderValues(header, "inline", null);
704         }
705 
706         [Fact]
707         public void ContentDispositionHeader_Valid2_Success()
708         {
709             ContentDispositionValue cd = ContentDispositionTestCases["valid2"];
710             ContentDispositionHeaderValue header = TryParse(cd);
711             ValidateHeaderValues(header, "inline", @"""foo.html""");
712         }
713 
714         [Fact]
715         public void ContentDispositionHeader_Valid3_Success()
716         {
717             ContentDispositionValue cd = ContentDispositionTestCases["valid3"];
718             ContentDispositionHeaderValue header = TryParse(cd);
719             ValidateHeaderValues(header, "inline", @"""Not an attachment!""");
720         }
721 
722         [Fact]
723         public void ContentDispositionHeader_Valid4_Success()
724         {
725             ContentDispositionValue cd = ContentDispositionTestCases["valid4"];
726             ContentDispositionHeaderValue header = TryParse(cd);
727             ValidateHeaderValues(header, "inline", @"""foo.pdf""");
728         }
729 
730         [Fact]
731         public void ContentDispositionHeader_Valid5_Success()
732         {
733             ContentDispositionValue cd = ContentDispositionTestCases["valid5"];
734             ContentDispositionHeaderValue header = TryParse(cd);
735             ValidateHeaderValues(header, "attachment", null);
736         }
737 
738         [Fact]
739         public void ContentDispositionHeader_Valid6_Success()
740         {
741             ContentDispositionValue cd = ContentDispositionTestCases["valid6"];
742             ContentDispositionHeaderValue header = TryParse(cd);
743             ValidateHeaderValues(header, "ATTACHMENT", null);
744         }
745 
746         [Fact]
747         public void ContentDispositionHeader_Valid7_Success()
748         {
749             ContentDispositionValue cd = ContentDispositionTestCases["valid7"];
750             ContentDispositionHeaderValue header = TryParse(cd);
751             ValidateHeaderValues(header, "attachment", @"""foo.html""");
752         }
753 
754         [Fact]
755         public void ContentDispositionHeader_Valid8_Success()
756         {
757             ContentDispositionValue cd = ContentDispositionTestCases["valid8"];
758             ContentDispositionHeaderValue header = TryParse(cd);
759             ValidateHeaderValues(header, "attachment", @"""f\oo.html""");
760         }
761 
762         [Fact]
763         public void ContentDispositionHeader_Valid9_Success()
764         {
765             ContentDispositionValue cd = ContentDispositionTestCases["valid9"];
766             ContentDispositionHeaderValue header = TryParse(cd);
767             ValidateHeaderValues(header, "attachment", @"""\""quoting\"" tested.html""");
768         }
769 
770         [Fact]
771         public void ContentDispositionHeader_Valid10_Success()
772         {
773             ContentDispositionValue cd = ContentDispositionTestCases["valid10"];
774             ContentDispositionHeaderValue header = TryParse(cd);
775             ValidateHeaderValues(header, "attachment", @"""Here's a semicolon;.html""");
776         }
777 
778         [Fact]
779         public void ContentDispositionHeader_Valid11_Success()
780         {
781             ContentDispositionValue cd = ContentDispositionTestCases["valid11"];
782             ContentDispositionHeaderValue header = TryParse(cd);
783             ValidateHeaderValues(header, "attachment", @"""foo.html""");
784             ValidateExtensionParameter(header, "foo", @"""bar""");
785         }
786 
787         [Fact]
788         public void ContentDispositionHeader_Valid12_Success()
789         {
790             ContentDispositionValue cd = ContentDispositionTestCases["valid12"];
791             ContentDispositionHeaderValue header = TryParse(cd);
792             ValidateHeaderValues(header, "attachment", @"""foo.html""");
793             ValidateExtensionParameter(header, "foo", @"""\""\\""");
794         }
795 
796         [Fact]
797         public void ContentDispositionHeader_Valid13_Success()
798         {
799             ContentDispositionValue cd = ContentDispositionTestCases["valid13"];
800             ContentDispositionHeaderValue header = TryParse(cd);
801             ValidateHeaderValues(header, "attachment", @"""foo.html""");
802         }
803 
804         [Fact]
805         public void ContentDispositionHeader_Valid14_Success()
806         {
807             ContentDispositionValue cd = ContentDispositionTestCases["valid14"];
808             ContentDispositionHeaderValue header = TryParse(cd);
809             ValidateHeaderValues(header, "attachment", @"foo.html");
810         }
811 
812         [Fact]
813         public void ContentDispositionHeader_Valid15_Success()
814         {
815             ContentDispositionValue cd = ContentDispositionTestCases["valid15"];
816             ContentDispositionHeaderValue header = TryParse(cd);
817             ValidateHeaderValues(header, "attachment", @"'foo.bar'");
818         }
819 
820         [Fact]
821         public void ContentDispositionHeader_Valid16_Success()
822         {
823             ContentDispositionValue cd = ContentDispositionTestCases["valid16"];
824             ContentDispositionHeaderValue header = TryParse(cd);
825             ValidateHeaderValues(header, "attachment", @"""foo-\u00E4.html""");
826         }
827 
828         [Fact]
829         public void ContentDispositionHeader_Valid17_Success()
830         {
831             ContentDispositionValue cd = ContentDispositionTestCases["valid17"];
832             ContentDispositionHeaderValue header = TryParse(cd);
833             ValidateHeaderValues(header, "attachment", @"""foo-&#xc3;&#xa4;.html""");
834         }
835 
836         [Fact]
837         public void ContentDispositionHeader_Valid18_Success()
838         {
839             ContentDispositionValue cd = ContentDispositionTestCases["valid18"];
840             ContentDispositionHeaderValue header = TryParse(cd);
841             ValidateHeaderValues(header, "attachment", @"""foo-%41.html""");
842         }
843 
844         [Fact]
845         public void ContentDispositionHeader_Valid19_Success()
846         {
847             ContentDispositionValue cd = ContentDispositionTestCases["valid19"];
848             ContentDispositionHeaderValue header = TryParse(cd);
849             ValidateHeaderValues(header, "attachment", @"""50%.html""");
850         }
851 
852         [Fact]
853         public void ContentDispositionHeader_Valid20_Success()
854         {
855             ContentDispositionValue cd = ContentDispositionTestCases["valid20"];
856             ContentDispositionHeaderValue header = TryParse(cd);
857             ValidateHeaderValues(header, "attachment", @"""foo-%\41.html""");
858         }
859 
860         [Fact]
861         public void ContentDispositionHeader_Valid21_Success()
862         {
863             ContentDispositionValue cd = ContentDispositionTestCases["valid21"];
864             ContentDispositionHeaderValue header = TryParse(cd);
865             ValidateHeaderValues(header, "attachment", null);
866             ValidateExtensionParameter(header, "name", @"""foo-%41.html""");
867         }
868 
869         [Fact]
870         public void ContentDispositionHeader_Valid22_Success()
871         {
872             ContentDispositionValue cd = ContentDispositionTestCases["valid22"];
873             ContentDispositionHeaderValue header = TryParse(cd);
874             ValidateHeaderValues(header, "attachment", @"""\u00E4-%41.html""");
875         }
876 
877         [Fact]
878         public void ContentDispositionHeader_Valid23_Success()
879         {
880             ContentDispositionValue cd = ContentDispositionTestCases["valid23"];
881             ContentDispositionHeaderValue header = TryParse(cd);
882             ValidateHeaderValues(header, "attachment", @"""foo-%c3%a4-%e2%82%ac.html""");
883         }
884 
885         [Fact]
886         public void ContentDispositionHeader_Valid24_Success()
887         {
888             ContentDispositionValue cd = ContentDispositionTestCases["valid24"];
889             ContentDispositionHeaderValue header = TryParse(cd);
890             ValidateHeaderValues(header, "attachment", @"""foo.html""");
891         }
892 
893         [Fact]
894         public void ContentDispositionHeader_Valid25_Success()
895         {
896             ContentDispositionValue cd = ContentDispositionTestCases["valid25"];
897             ContentDispositionHeaderValue header = TryParse(cd);
898             ValidateHeaderValues(header, "attachment", null);
899             ValidateExtensionParameter(header, "xfilename", @"foo.html");
900         }
901 
902         [Fact]
903         public void ContentDispositionHeader_Valid26_Success()
904         {
905             ContentDispositionValue cd = ContentDispositionTestCases["valid26"];
906             ContentDispositionHeaderValue header = TryParse(cd);
907             ValidateHeaderValues(header, "attachment", @"""/foo.html""");
908         }
909 
910         [Fact]
911         public void ContentDispositionHeader_Valid27_Success()
912         {
913             ContentDispositionValue cd = ContentDispositionTestCases["valid27"];
914             ContentDispositionHeaderValue header = TryParse(cd);
915             ValidateHeaderValues(header, "attachment", @"""\\foo.html""");
916         }
917 
918         [Fact]
919         public void ContentDispositionHeader_Valid28_Success()
920         {
921             ContentDispositionValue cd = ContentDispositionTestCases["valid28"];
922             ContentDispositionHeaderValue header = TryParse(cd);
923             ValidateHeaderValues(header, "attachment", null);
924             ValidateExtensionParameter(header, "creation-date", @"""Wed, 12 Feb 1997 16:29:51 -0500""");
925         }
926 
927         [Fact]
928         public void ContentDispositionHeader_Valid29_Success()
929         {
930             ContentDispositionValue cd = ContentDispositionTestCases["valid29"];
931             ContentDispositionHeaderValue header = TryParse(cd);
932             ValidateHeaderValues(header, "attachment", null);
933             ValidateExtensionParameter(header, "modification-date", @"""Wed, 12 Feb 1997 16:29:51 -0500""");
934         }
935 
936         [Fact]
937         public void ContentDispositionHeader_Valid30_Success()
938         {
939             ContentDispositionValue cd = ContentDispositionTestCases["valid30"];
940             ContentDispositionHeaderValue header = TryParse(cd);
941             ValidateHeaderValues(header, "foobar", null);
942         }
943 
944         [Fact]
945         public void ContentDispositionHeader_Valid31_Success()
946         {
947             ContentDispositionValue cd = ContentDispositionTestCases["valid31"];
948             ContentDispositionHeaderValue header = TryParse(cd);
949             ValidateHeaderValues(header, "attachment", null);
950             ValidateExtensionParameter(header, "example", @"""filename=example.txt""");
951         }
952 
953         [Fact]
954         public void ContentDispositionHeader_Valid32_Success()
955         {
956             ContentDispositionValue cd = ContentDispositionTestCases["valid32"];
957             ContentDispositionHeaderValue header = TryParse(cd);
958             ValidateHeaderValues(header, "attachment", null);
959             ValidateExtensionParameter(header, "filename*", @"iso-8859-1''foo-%E4.html");
960         }
961 
962         [Fact]
963         public void ContentDispositionHeader_Valid33_Success()
964         {
965             ContentDispositionValue cd = ContentDispositionTestCases["valid33"];
966             ContentDispositionHeaderValue header = TryParse(cd);
967             ValidateHeaderValues(header, "attachment", null);
968             ValidateExtensionParameter(header, "filename*", @"UTF-8''foo-%c3%a4-%e2%82%ac.html");
969         }
970 
971         [Fact]
972         public void ContentDispositionHeader_Valid34_Success()
973         {
974             ContentDispositionValue cd = ContentDispositionTestCases["valid34"];
975             ContentDispositionHeaderValue header = TryParse(cd);
976             ValidateHeaderValues(header, "attachment", null);
977             ValidateExtensionParameter(header, "filename*", @"''foo-%c3%a4-%e2%82%ac.html");
978         }
979 
980         [Fact]
981         public void ContentDispositionHeader_Valid35_Success()
982         {
983             ContentDispositionValue cd = ContentDispositionTestCases["valid35"];
984             ContentDispositionHeaderValue header = TryParse(cd);
985             ValidateHeaderValues(header, "attachment", null);
986             ValidateExtensionParameter(header, "filename*", @"UTF-8''foo-a%cc%88.html");
987         }
988 
989         [Fact]
990         public void ContentDispositionHeader_Valid36_Success()
991         {
992             ContentDispositionValue cd = ContentDispositionTestCases["valid36"];
993             ContentDispositionHeaderValue header = TryParse(cd);
994             ValidateHeaderValues(header, "attachment", null);
995             ValidateExtensionParameter(header, "filename*", @"UTF-8''foo-%c3%a4.html");
996         }
997 
998         [Fact]
999         public void ContentDispositionHeader_Valid37_Success()
1000         {
1001             ContentDispositionValue cd = ContentDispositionTestCases["valid37"];
1002             ContentDispositionHeaderValue header = TryParse(cd);
1003             ValidateHeaderValues(header, "attachment", null);
1004             ValidateExtensionParameter(header, "filename*", @"UTF-8''foo-%c3%a4.html");
1005         }
1006 
1007         [Fact]
1008         public void ContentDispositionHeader_Valid38_Success()
1009         {
1010             ContentDispositionValue cd = ContentDispositionTestCases["valid38"];
1011             ContentDispositionHeaderValue header = TryParse(cd);
1012             ValidateHeaderValues(header, "attachment", null);
1013             ValidateExtensionParameter(header, "filename*", @"UTF-8''A-%2541.html");
1014         }
1015 
1016         [Fact]
1017         public void ContentDispositionHeader_Valid39_Success()
1018         {
1019             ContentDispositionValue cd = ContentDispositionTestCases["valid39"];
1020             ContentDispositionHeaderValue header = TryParse(cd);
1021             ValidateHeaderValues(header, "attachment", null);
1022             ValidateExtensionParameter(header, "filename*", @"UTF-8''%5cfoo.html");
1023         }
1024 
1025         [Fact]
1026         public void ContentDispositionHeader_Valid40_Success()
1027         {
1028             ContentDispositionValue cd = ContentDispositionTestCases["valid40"];
1029             ContentDispositionHeaderValue header = TryParse(cd);
1030             ValidateHeaderValues(header, "attachment", null);
1031             ValidateExtensionParameter(header, "filename*0", @"""foo.""");
1032             ValidateExtensionParameter(header, "filename*1", @"""html""");
1033         }
1034 
1035         [Fact]
1036         public void ContentDispositionHeader_Valid41_Success()
1037         {
1038             ContentDispositionValue cd = ContentDispositionTestCases["valid41"];
1039             ContentDispositionHeaderValue header = TryParse(cd);
1040             ValidateHeaderValues(header, "attachment", null);
1041             ValidateExtensionParameter(header, "filename*0*", @"UTF-8''foo-%c3%a4");
1042             ValidateExtensionParameter(header, "filename*1", @""".html""");
1043         }
1044 
1045         [Fact]
1046         public void ContentDispositionHeader_Valid42_Success()
1047         {
1048             ContentDispositionValue cd = ContentDispositionTestCases["valid42"];
1049             ContentDispositionHeaderValue header = TryParse(cd);
1050             ValidateHeaderValues(header, "attachment", null);
1051             ValidateExtensionParameter(header, "filename*0", @"""foo""");
1052             ValidateExtensionParameter(header, "filename*01", @"""bar""");
1053         }
1054 
1055         [Fact]
1056         public void ContentDispositionHeader_Valid43_Success()
1057         {
1058             ContentDispositionValue cd = ContentDispositionTestCases["valid43"];
1059             ContentDispositionHeaderValue header = TryParse(cd);
1060             ValidateHeaderValues(header, "attachment", null);
1061             ValidateExtensionParameter(header, "filename*0", @"""foo""");
1062             ValidateExtensionParameter(header, "filename*2", @"""bar""");
1063         }
1064 
1065         [Fact]
1066         public void ContentDispositionHeader_Valid44_Success()
1067         {
1068             ContentDispositionValue cd = ContentDispositionTestCases["valid44"];
1069             ContentDispositionHeaderValue header = TryParse(cd);
1070             ValidateHeaderValues(header, "attachment", null);
1071             ValidateExtensionParameter(header, "filename*1", @"""foo.""");
1072             ValidateExtensionParameter(header, "filename*2", @"""html""");
1073         }
1074 
1075         [Fact]
1076         public void ContentDispositionHeader_Valid45_Success()
1077         {
1078             ContentDispositionValue cd = ContentDispositionTestCases["valid45"];
1079             ContentDispositionHeaderValue header = TryParse(cd);
1080             ValidateHeaderValues(header, "attachment", null);
1081             ValidateExtensionParameter(header, "filename*0", @"""foo""");
1082             ValidateExtensionParameter(header, "filename*1", @"""bar""");
1083         }
1084 
1085         [Fact]
1086         public void ContentDispositionHeader_Valid46_Success()
1087         {
1088             ContentDispositionValue cd = ContentDispositionTestCases["valid46"];
1089             ContentDispositionHeaderValue header = TryParse(cd);
1090             ValidateHeaderValues(header, "attachment", @"""foo-ae.html""");
1091             ValidateExtensionParameter(header, "filename*", @"UTF-8''foo-%c3%a4.html");
1092         }
1093 
1094         [Fact]
1095         public void ContentDispositionHeader_Valid47_Success()
1096         {
1097             ContentDispositionValue cd = ContentDispositionTestCases["valid47"];
1098             ContentDispositionHeaderValue header = TryParse(cd);
1099             ValidateHeaderValues(header, "attachment", @"""foo-ae.html""");
1100             ValidateExtensionParameter(header, "filename*", @"UTF-8''foo-%c3%a4.html");
1101         }
1102 
1103         [Fact]
1104         public void ContentDispositionHeader_Valid48_Success()
1105         {
1106             ContentDispositionValue cd = ContentDispositionTestCases["valid48"];
1107             ContentDispositionHeaderValue header = TryParse(cd);
1108             ValidateHeaderValues(header, "attachment", @"""foo.html""");
1109             ValidateExtensionParameter(header, "foobar", @"x");
1110         }
1111 
1112         [Fact]
1113         public void ContentDispositionHeader_Valid49_Success()
1114         {
1115             ContentDispositionValue cd = ContentDispositionTestCases["valid49"];
1116             ContentDispositionHeaderValue header = TryParse(cd);
1117             ValidateHeaderValues(header, "attachment", @"""=?ISO-8859-1?Q?foo-=E4.html?=""");
1118         }
1119 
1120         #endregion
1121 
1122         private static void ValidateHeaderValues(ContentDispositionHeaderValue header, string expectedDispositionType, string expectedFilename)
1123         {
1124             Assert.NotNull(header);
1125             Assert.Equal(expectedDispositionType, header.DispositionType);
1126             Assert.Equal(expectedFilename, header.FileName);
1127         }
1128 
1129         private static void ValidateExtensionParameter(ContentDispositionHeaderValue header, string name, string expectedValue)
1130         {
1131             Assert.NotNull(header);
1132             NameValueHeaderValue parameter = FindParameter(header.Parameters, name);
1133             Assert.NotNull(parameter);
1134             Assert.Equal(expectedValue, parameter.Value);
1135         }
1136 
1137         private static NameValueHeaderValue FindParameter(ICollection<NameValueHeaderValue> values, string name)
1138         {
1139             if ((values == null) || (values.Count == 0))
1140             {
1141                 return null;
1142             }
1143 
1144             foreach (var value in values)
1145             {
1146                 if (string.Equals(value.Name, name, StringComparison.OrdinalIgnoreCase))
1147                 {
1148                     return value;
1149                 }
1150             }
1151             return null;
1152         }
1153 
1154         private static ContentDispositionHeaderValue Parse(ContentDispositionValue cd)
1155         {
1156             Assert.NotNull(cd);
1157             ContentDispositionHeaderValue header = null;
1158             if (cd.Valid)
1159             {
1160                 header = ContentDispositionHeaderValue.Parse(cd.Value);
1161                 Assert.NotNull(header);
1162             }
1163             else
1164             {
1165                 Assert.Throws<FormatException>(() => { header = ContentDispositionHeaderValue.Parse(cd.Value); });
1166             }
1167 
1168             return header;
1169         }
1170 
1171         private static ContentDispositionHeaderValue TryParse(ContentDispositionValue cd)
1172         {
1173             Assert.NotNull(cd);
1174             ContentDispositionHeaderValue header;
1175             if (cd.Valid)
1176             {
1177                 Assert.True(ContentDispositionHeaderValue.TryParse(cd.Value, out header));
1178                 Assert.NotNull(header);
1179             }
1180             else
1181             {
1182                 Assert.False(ContentDispositionHeaderValue.TryParse(cd.Value, out header));
1183                 Assert.Null(header);
1184             }
1185 
1186             return header;
1187         }
1188 
1189         public class ContentDispositionValue
1190         {
1191             public ContentDispositionValue(string value, string description, bool valid)
1192             {
1193                 this.Value = value;
1194                 this.Description = description;
1195                 this.Valid = valid;
1196             }
1197 
1198             public string Value { get; private set; }
1199 
1200             public string Description { get; private set; }
1201 
1202             public bool Valid { get; private set; }
1203         }
1204 
1205         #endregion Tests from HenrikN
1206 
1207         #region Helper methods
1208 
1209         private void CheckValidParse(string input, ContentDispositionHeaderValue expectedResult)
1210         {
1211             ContentDispositionHeaderValue result = ContentDispositionHeaderValue.Parse(input);
1212             Assert.Equal(expectedResult, result);
1213         }
1214 
1215         private void CheckInvalidParse(string input)
1216         {
1217             Assert.Throws<FormatException>(() => { ContentDispositionHeaderValue.Parse(input); });
1218         }
1219 
1220         private void CheckValidTryParse(string input, ContentDispositionHeaderValue expectedResult)
1221         {
1222             ContentDispositionHeaderValue result = null;
1223             Assert.True(ContentDispositionHeaderValue.TryParse(input, out result), input);
1224             Assert.Equal(expectedResult, result);
1225         }
1226 
1227         private void CheckInvalidTryParse(string input)
1228         {
1229             ContentDispositionHeaderValue result = null;
1230             Assert.False(ContentDispositionHeaderValue.TryParse(input, out result), input);
1231             Assert.Null(result);
1232         }
1233 
1234         private static void AssertFormatException(string contentDisposition)
1235         {
1236             Assert.Throws<FormatException>(() => { new ContentDispositionHeaderValue(contentDisposition); });
1237         }
1238         #endregion
1239     }
1240 }
1241