/// <summary> /// Adds the correct response headers for a file download request. /// </summary> /// <param name="context">HttpContext</param> /// <param name="fileHeadObject">The file object representing the file to stream.</param> public static void AddDownloadHeaders(HttpContext context, FileHeadObject fileHeadObject) { HttpContext httpContext = context as HttpContext; string requestLanguage = string.Empty; if (httpContext.Request.UserLanguages.Length > 0) requestLanguage = httpContext.Request.UserLanguages[0]; httpContext.Response.AppendHeader("Content-Length", fileHeadObject.BytesCount.ToString(CultureInfo.InvariantCulture)); httpContext.Response.ContentType = GetMimeType(fileHeadObject.FileName); string disposition = BuildContentDispositionHeader(fileHeadObject, requestLanguage, httpContext.Request.Browser.Type, httpContext.Request.Browser.MajorVersion, httpContext.Request.Browser.Platform); httpContext.Response.AppendHeader("Content-Disposition", disposition); // gives save-as dialog and filename }
private static string TruncateHeaderLine(FileHeadObject fileHeadObject, string text, int firstLineLen) { int maxLen = firstLineLen; int pos = 0; if (pos + maxLen < text.Length) { int lastByteCount = 0; while (pos < maxLen) { if (text[pos] == '%') // we don't want a filename to end with '%'. lastByteCount = 3; else lastByteCount = 1; pos += lastByteCount; } pos = pos - lastByteCount; string ext = fileHeadObject.FileExtensionName; text = text.Substring(0, pos); int dotPos = text.LastIndexOf(".", StringComparison.Ordinal); if (dotPos > 0) text = text.Substring(0, dotPos); text += ext; } return text; }
/// <summary> /// If the filename contains high ASCII and client is non-English, the filename to use (w/o extension). /// </summary> /// <param name="fileHeadObject"></param> private static string GetPlaceholderFileName(FileHeadObject fileHeadObject) { return string.Format(CultureInfo.InvariantCulture, "Download_{0}", fileHeadObject.Id.ToString()); }
private static string GetDownloadFilename(FileHeadObject fileHeadObject, string userLanguage, bool requestIsFromXPIE6Japanese) { bool isUsePlaceholder = false; string origFileName = fileHeadObject.FileName; string fileName; // if the request from WINXP IE6 and Japanese if (requestIsFromXPIE6Japanese) { fileName = BuildDownloadNameForXPIE6Japanese(fileHeadObject); } else { if (StringContainsHighAscii(origFileName)) { isUsePlaceholder = true; if (userLanguage.StartsWith("en", StringComparison.OrdinalIgnoreCase)) isUsePlaceholder = false; } string nameToUse = origFileName; if (isUsePlaceholder) { string extension = fileHeadObject.FileExtensionName; nameToUse = GetPlaceholderFileName(fileHeadObject) + extension; } fileName = HttpUtility.UrlPathEncode(nameToUse); fileName = fileName.Replace("#", "%23"); } return fileName; }
/// <summary> /// Builds the optimum file name that can be displayed without exceeeding /// header limit. /// </summary> /// <param name="fileHeadObject">original file head</param> /// <returns></returns> private static string BuildDownloadNameForXPIE6Japanese(FileHeadObject fileHeadObject) { //To DO Krishna: We can change this logic to use ability of UTF8 encoded byte to determine the truncation point. string nameToUse = fileHeadObject.FileName; string ext = fileHeadObject.FileExtensionName; if (ext.Length > 0) nameToUse = nameToUse.Substring(0, nameToUse.Length - ext.Length); char[] charArr = nameToUse.ToCharArray(); string holderStr; StringBuilder fileName = new StringBuilder(); int nCurrentLength = 0; foreach (char c in charArr) { holderStr = HttpUtility.UrlEncode(c.ToString()); if ((holderStr.Length + nCurrentLength) > 155) break; nCurrentLength += holderStr.Length; fileName.Append(holderStr); holderStr = null; } if (ext.Length > 0) fileName.Append(ext); return fileName.ToString(); }
/// <summary> /// Note that the httpContext param can legally be null. See callers. /// </summary> /// <remarks> /// The Content-Disposition HTTP response header gives us some control over how the client will react /// to the file download. In particular, whether or not the client will launch a "Save As" dialog when a file /// is requested for dowload. See RFC 1806 for detials.</remarks> /// <remarks>We tried excluding "attachment" from the contentDisposition header for IE 5.5 to avoid /// getting a double download prompt. Except, this causes a problem for the case /// where Office2K is installed on IE5.5 -- for that case we don't get any download prompt /// unless we have the header. So on IE5.5 we get a double download prompt. /// </remarks> /// <param name="fileHeadObject"></param> /// <param name="userLanguage"></param> /// <param name="capabilitiesType"></param> /// <param name="majorVersion"></param> /// <param name="platform"></param> /// <returns></returns> private static string BuildContentDispositionHeader(FileHeadObject fileHeadObject, string userLanguage, string capabilitiesType, int majorVersion, string platform) { // To build and hold the Content-Disposition header. StringBuilder dispositionBuilder = new StringBuilder(); dispositionBuilder.Append("attachment;"); // Note that we cannot determine browser details from a WebOperationContext, // so we simply ignore this check in that case. See callers. bool requestIsFromXPIE6Japanese = false; if ((!string.IsNullOrEmpty(capabilitiesType)) && majorVersion > -1 && (!string.IsNullOrEmpty(platform))) requestIsFromXPIE6Japanese = IfRequestIsFromXPIE6Japanese(userLanguage, capabilitiesType, majorVersion, platform); dispositionBuilder.AppendFormat("filename=\"{0}\"", GetDownloadFilename(fileHeadObject, userLanguage, requestIsFromXPIE6Japanese)); string disposition = dispositionBuilder.ToString(); // Max line length is 78 characters, including header name; we'll limit the first line to less characters // to make sure we have space for the header and the line terminators if (!requestIsFromXPIE6Japanese) disposition = TruncateHeaderLine(fileHeadObject, disposition, 100); return disposition; }