1 /* ****************************************************************************
\r
3 * Copyright (c) Microsoft Corporation. All rights reserved.
\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
9 * You must not remove this notice, or any other, from this software.
\r
11 * ***************************************************************************/
\r
13 namespace System.Web.Mvc {
\r
15 using System.Net.Mime;
\r
18 using System.Web.Mvc.Resources;
\r
20 public abstract class FileResult : ActionResult {
\r
22 protected FileResult(string contentType) {
\r
23 if (String.IsNullOrEmpty(contentType)) {
\r
24 throw new ArgumentException(MvcResources.Common_NullOrEmpty, "contentType");
\r
27 ContentType = contentType;
\r
30 private string _fileDownloadName;
\r
32 public string ContentType {
\r
37 public string FileDownloadName {
\r
39 return _fileDownloadName ?? String.Empty;
\r
42 _fileDownloadName = value;
\r
46 public override void ExecuteResult(ControllerContext context) {
\r
47 if (context == null) {
\r
48 throw new ArgumentNullException("context");
\r
51 HttpResponseBase response = context.HttpContext.Response;
\r
52 response.ContentType = ContentType;
\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
64 WriteFile(response);
\r
67 protected abstract void WriteFile(HttpResponseBase response);
\r
69 private static class ContentDispositionUtil {
\r
70 private const string _hexDigits = "0123456789ABCDEF";
\r
72 private static void AddByteToStringBuilder(byte b, StringBuilder builder) {
\r
73 builder.Append('%');
\r
76 AddHexDigitToStringBuilder(i >> 4, builder);
\r
77 AddHexDigitToStringBuilder(i % 16, builder);
\r
80 private static void AddHexDigitToStringBuilder(int digit, StringBuilder builder) {
\r
81 builder.Append(_hexDigits[digit]);
\r
84 private static string CreateRfc2231HeaderValue(string filename) {
\r
85 StringBuilder builder = new StringBuilder("attachment; filename*=UTF-8''");
\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
93 AddByteToStringBuilder(b, builder);
\r
97 return builder.ToString();
\r
100 public static string GetHeaderValue(string fileName) {
\r
102 // first, try using the .NET built-in generator
\r
103 ContentDisposition disposition = new ContentDisposition() { FileName = fileName };
\r
104 return disposition.ToString();
\r
106 catch (FormatException) {
\r
107 // otherwise, fall back to RFC 2231 extensions generator
\r
108 return CreateRfc2231HeaderValue(fileName);
\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
118 if ((byte)'a' <= b && b <= (byte)'z') {
\r
119 return true; // lowercase letter
\r
121 if ((byte)'A' <= b && b <= (byte)'Z') {
\r
122 return true; // uppercase letter
\r