New tests.
[mono.git] / mcs / class / System.Web.Mvc2 / System.Web.Mvc / FileResult.cs
1 /* ****************************************************************************\r
2  *\r
3  * Copyright (c) Microsoft Corporation. All rights reserved.\r
4  *\r
5  * This software is subject to the Microsoft Public License (Ms-PL). \r
6  * A copy of the license can be found in the license.htm file included \r
7  * in this distribution.\r
8  *\r
9  * You must not remove this notice, or any other, from this software.\r
10  *\r
11  * ***************************************************************************/\r
12 \r
13 namespace System.Web.Mvc {\r
14     using System;\r
15     using System.Net.Mime;\r
16     using System.Text;\r
17     using System.Web;\r
18     using System.Web.Mvc.Resources;\r
19 \r
20     public abstract class FileResult : ActionResult {\r
21 \r
22         protected FileResult(string contentType) {\r
23             if (String.IsNullOrEmpty(contentType)) {\r
24                 throw new ArgumentException(MvcResources.Common_NullOrEmpty, "contentType");\r
25             }\r
26 \r
27             ContentType = contentType;\r
28         }\r
29 \r
30         private string _fileDownloadName;\r
31 \r
32         public string ContentType {\r
33             get;\r
34             private set;\r
35         }\r
36 \r
37         public string FileDownloadName {\r
38             get {\r
39                 return _fileDownloadName ?? String.Empty;\r
40             }\r
41             set {\r
42                 _fileDownloadName = value;\r
43             }\r
44         }\r
45 \r
46         public override void ExecuteResult(ControllerContext context) {\r
47             if (context == null) {\r
48                 throw new ArgumentNullException("context");\r
49             }\r
50 \r
51             HttpResponseBase response = context.HttpContext.Response;\r
52             response.ContentType = ContentType;\r
53 \r
54             if (!String.IsNullOrEmpty(FileDownloadName)) {\r
55                 // From RFC 2183, Sec. 2.3:\r
56                 // The sender may want to suggest a filename to be used if the entity is\r
57                 // detached and stored in a separate file. If the receiving MUA writes\r
58                 // the entity to a file, the suggested filename should be used as a\r
59                 // basis for the actual filename, where possible.\r
60                 string headerValue = ContentDispositionUtil.GetHeaderValue(FileDownloadName);\r
61                 context.HttpContext.Response.AddHeader("Content-Disposition", headerValue);\r
62             }\r
63 \r
64             WriteFile(response);\r
65         }\r
66 \r
67         protected abstract void WriteFile(HttpResponseBase response);\r
68 \r
69         private static class ContentDispositionUtil {\r
70             private const string _hexDigits = "0123456789ABCDEF";\r
71 \r
72             private static void AddByteToStringBuilder(byte b, StringBuilder builder) {\r
73                 builder.Append('%');\r
74 \r
75                 int i = b;\r
76                 AddHexDigitToStringBuilder(i >> 4, builder);\r
77                 AddHexDigitToStringBuilder(i % 16, builder);\r
78             }\r
79 \r
80             private static void AddHexDigitToStringBuilder(int digit, StringBuilder builder) {\r
81                 builder.Append(_hexDigits[digit]);\r
82             }\r
83 \r
84             private static string CreateRfc2231HeaderValue(string filename) {\r
85                 StringBuilder builder = new StringBuilder("attachment; filename*=UTF-8''");\r
86 \r
87                 byte[] filenameBytes = Encoding.UTF8.GetBytes(filename);\r
88                 foreach (byte b in filenameBytes) {\r
89                     if (IsByteValidHeaderValueCharacter(b)) {\r
90                         builder.Append((char)b);\r
91                     }\r
92                     else {\r
93                         AddByteToStringBuilder(b, builder);\r
94                     }\r
95                 }\r
96 \r
97                 return builder.ToString();\r
98             }\r
99 \r
100             public static string GetHeaderValue(string fileName) {\r
101                 try {\r
102                     // first, try using the .NET built-in generator\r
103                     ContentDisposition disposition = new ContentDisposition() { FileName = fileName };\r
104                     return disposition.ToString();\r
105                 }\r
106                 catch (FormatException) {\r
107                     // otherwise, fall back to RFC 2231 extensions generator\r
108                     return CreateRfc2231HeaderValue(fileName);\r
109                 }\r
110             }\r
111 \r
112             // Application of RFC 2231 Encoding to Hypertext Transfer Protocol (HTTP) Header Fields, sec. 3.2\r
113             // http://greenbytes.de/tech/webdav/draft-reschke-rfc2231-in-http-latest.html\r
114             private static bool IsByteValidHeaderValueCharacter(byte b) {\r
115                 if ((byte)'0' <= b && b <= (byte)'9') {\r
116                     return true; // is digit\r
117                 }\r
118                 if ((byte)'a' <= b && b <= (byte)'z') {\r
119                     return true; // lowercase letter\r
120                 }\r
121                 if ((byte)'A' <= b && b <= (byte)'Z') {\r
122                     return true; // uppercase letter\r
123                 }\r
124 \r
125                 switch (b) {\r
126                     case (byte)'-':\r
127                     case (byte)'.':\r
128                     case (byte)'_':\r
129                     case (byte)'~':\r
130                     case (byte)':':\r
131                     case (byte)'!':\r
132                     case (byte)'$':\r
133                     case (byte)'&':\r
134                     case (byte)'+':\r
135                         return true;\r
136                 }\r
137 \r
138                 return false;\r
139             }\r
140         }\r
141 \r
142     }\r
143 }\r