private ActionComponentSetup getExportAction(IEnumerable <object> headers, List <IEnumerable <object> > tableData) => new ButtonSetup( "Export", behavior: new PostBackBehavior( postBack: PostBack.CreateFull( id: PostBack.GetCompositeId(setup.PostBackIdBase, "export"), actionGetter: () => new PostBackAction( new PageReloadBehavior( secondaryResponse: new SecondaryResponse( () => EwfResponse.Create( ContentTypes.Csv, new EwfResponseBodyCreator( writer => { var csv = new CsvFileWriter(); csv.AddValuesToLine(headers.ToArray()); csv.WriteCurrentLineToFile(writer); foreach (var td in tableData) { csv.AddValuesToLine(td.ToArray()); csv.WriteCurrentLineToFile(writer); } }), () => "{0} {1}".FormatWith( setup.ExportFileName, AppRequestState.RequestTime.InZone(DateTimeZoneProviders.Tzdb.GetSystemDefault()).ToDateTimeUnspecified()) + FileExtensions.Csv)))))));
private Block getExportButton(IEnumerable <object> headers, List <IEnumerable <object> > tableData) { var block = new Block( new PostBackButton( PostBack.CreateFull( id: PostBack.GetCompositeId(setup.PostBackIdBase, "export"), actionGetter: () => new PostBackAction( new SecondaryResponse( () => EwfResponse.Create( ContentTypes.Csv, new EwfResponseBodyCreator( writer => { var csv = new CsvFileWriter(); csv.AddValuesToLine(headers.ToArray()); csv.WriteCurrentLineToFile(writer); foreach (var td in tableData) { csv.AddValuesToLine(td.ToArray()); csv.WriteCurrentLineToFile(writer); } }), () => "{0} {1}".FormatWith(setup.ExportFileName, DateTime.Now) + FileExtensions.Csv)))), new TextActionControlStyle("Export"), usesSubmitBehavior: false)); block.Style.Add("text-align", "right"); return(block); }
/// <summary> /// Creates a response writer with CSS text. /// </summary> /// <param name="cssGetter">A function that gets the CSS that will be preprocessed and used as the response. Do not pass or return null.</param> /// <param name="urlVersionString">The resource-version string from the request URL. Do not pass null or the empty string; this parameter is required /// because it greatly improves cacheability, which is important for CSS. You must change the URL when the CSS changes, and you must not vary the response /// based on non-URL elements of the request, such as the authenticated user.</param> /// <param name="memoryCachingSetupGetter">A function that gets the memory-caching setup object for the response. Do not pass or return null; memory caching /// is important for CSS and is therefore required.</param> public EwfSafeResponseWriter(Func <string> cssGetter, string urlVersionString, Func <ResponseMemoryCachingSetup> memoryCachingSetupGetter) { var memoryCachingSetup = new Lazy <ResponseMemoryCachingSetup>(memoryCachingSetupGetter); writer = createWriter( () => EwfResponse.Create(ContentTypes.Css, new EwfResponseBodyCreator(() => CssPreprocessor.TransformCssFile(cssGetter()))), urlVersionString, "", () => memoryCachingSetup.Value.LastModificationDateAndTime, () => memoryCachingSetup.Value.CacheKey); }
protected sealed override EwfSafeRequestHandler getOrHead() { var extensionIndex = relativeFilePath.LastIndexOf('.'); if (extensionIndex < 0) { throw new ResourceNotAvailableException("Failed to find the extension in the file path.", null); } var extension = relativeFilePath.Substring(extensionIndex); var mediaTypeOverride = EwfApp.Instance.GetMediaTypeOverrides().SingleOrDefault(i => i.FileExtension == extension); var contentType = mediaTypeOverride != null ? mediaTypeOverride.MediaType : MimeTypeMap.GetMimeType(extension); var urlVersionString = isVersioned ? "invariant" : ""; string getCacheKey() => "staticFile-{0}-{1}".FormatWith(isFrameworkFile, relativeFilePath); EwfSafeResponseWriter responseWriter; if (contentType == TewlContrib.ContentTypes.Css) { Func <string> cssGetter = () => File.ReadAllText(filePath); responseWriter = urlVersionString.Any() ? new EwfSafeResponseWriter( cssGetter, urlVersionString, () => new ResponseMemoryCachingSetup(getCacheKey(), GetResourceLastModificationDateAndTime())) : new EwfSafeResponseWriter( () => EwfResponse.Create( TewlContrib.ContentTypes.Css, new EwfResponseBodyCreator(() => CssPreprocessor.TransformCssFile(cssGetter()))), GetResourceLastModificationDateAndTime(), memoryCacheKeyGetter: getCacheKey); } else { Func <EwfResponse> responseCreator = () => EwfResponse.Create( contentType, new EwfResponseBodyCreator( responseStream => { using (var fileStream = File.OpenRead(filePath)) fileStream.CopyTo(responseStream); })); responseWriter = urlVersionString.Any() ? new EwfSafeResponseWriter( responseCreator, urlVersionString, memoryCachingSetupGetter: () => new ResponseMemoryCachingSetup(getCacheKey(), GetResourceLastModificationDateAndTime())) : new EwfSafeResponseWriter(responseCreator, GetResourceLastModificationDateAndTime(), memoryCacheKeyGetter: getCacheKey); } return(responseWriter); }
internal EwfResponse GetResponse() { return(EwfResponse.Create( file.ContentType, new EwfResponseBodyCreator( () => { var contents = BlobFileOps.SystemProvider.GetFileContents(file.FileId); if (forcedImageWidth.Value.HasValue) { contents = EwlStatics.ResizeImage(contents, forcedImageWidth.Value.Value); } return contents; }), fileNameCreator: () => processAsAttachment.Value ? file.FileName : "")); }
internal static void WriteRedirectResponse(HttpContext context, string url, bool permanent) { context.Response.StatusCode = permanent ? 308 : 307; if (context.Request.HttpMethod == "GET" || context.Request.HttpMethod == "HEAD") { EwfSafeResponseWriter.AddCacheControlHeader( context.Response, EwfApp.Instance.RequestIsSecure(context.Request), false, permanent ? (bool?)null : false); } EwfResponse.Create( ContentTypes.PlainText, new EwfResponseBodyCreator(writer => writer.Write("{0} Redirect: {1}".FormatWith(permanent ? "Permanent" : "Temporary", url))), additionalHeaderFieldGetter: () => ("Location", url).ToCollection()) .WriteToAspNetResponse(context.Response, omitBody: context.Request.HttpMethod == "HEAD"); }
private static Action <HttpRequest, HttpResponse> createWriter( Func <EwfResponse> responseCreator, string urlVersionString, string eTagBase, Func <DateTimeOffset> lastModificationDateAndTimeGetter, Func <string> memoryCacheKeyGetter) { return((aspNetRequest, aspNetResponse) => { // Disable ASP.NET output caching. aspNetResponse.Cache.SetNoServerCaching(); var response = new Lazy <EwfResponse>(responseCreator); // If we ever want to implement content negotiation, we can do so here. We can accept multiple response-creator functions instead of just one. Then, if // the request contains an Accept header, we can evaluate the response-creator functions here and then choose the response with the best content type. // If there is no match, we can send a 406 (Not Acceptable) status code and exit the method. Note that if we do add support for multiple // response-creator functions, we need to incorporate the chosen response's content type into the ETag if we want it to remain a strong ETag. We also // need to incorporate the content type into the memory-cache key. // Assume that all HTTPS responses are private. This isn't true for CSS, JavaScript, etc. requests that are only secure in order to match the security // of a page, but that's not a big deal since most shared caches can't open and cache HTTPS anyway. // // If we don't have caching information, the response is probably not shareable. // // Using ServerAndPrivate instead of just Private is a hack that is necessary because ASP.NET suppresses ETags with Private. But ASP.NET output caching // (i.e. server caching) will still be disabled because of our SetNoServerCaching call above. aspNetResponse.Cache.SetCacheability( !EwfApp.Instance.RequestIsSecure(aspNetRequest) && (urlVersionString.Any() || eTagBase.Any() || lastModificationDateAndTimeGetter != null) ? HttpCacheability.Public : HttpCacheability.ServerAndPrivate); aspNetResponse.Cache.SetMaxAge(urlVersionString.Any() ? TimeSpan.FromDays(365) : TimeSpan.Zero); var lastModificationDateAndTime = lastModificationDateAndTimeGetter != null ? new Lazy <DateTimeOffset>(lastModificationDateAndTimeGetter) : null; string eTag; if (urlVersionString.Any()) { eTag = urlVersionString; } else if (eTagBase.Any() || lastModificationDateAndTimeGetter != null) { eTag = eTagBase + (lastModificationDateAndTimeGetter != null ? GetUrlVersionString(lastModificationDateAndTime.Value) : ""); } else { // Buffer the response body. var responseWithBufferedBody = EwfResponse.Create( response.Value.ContentType, response.Value.BodyCreator.GetBufferedBodyCreator(), fileNameCreator: response.Value.FileNameCreator); response = new Lazy <EwfResponse>(() => responseWithBufferedBody); var bodyAsBinary = response.Value.BodyCreator.BodyIsText ? Encoding.UTF8.GetBytes(response.Value.BodyCreator.TextBodyCreator()) : response.Value.BodyCreator.BinaryBodyCreator(); eTag = Convert.ToBase64String(MD5.Create().ComputeHash(bodyAsBinary)); } // Strong ETags must vary by content coding. Since we don't know yet how this response will be encoded (gzip or otherwise), the best thing we can do is // use the Accept-Encoding header value in the ETag. var acceptEncoding = aspNetRequest.Headers["Accept-Encoding"]; // returns null if field missing if (acceptEncoding != null) { eTag += acceptEncoding.Replace(", ", ""); // On 4 Feb 2015 we saw the comma/space cause Chrome to incorrectly split the ETag apart. } aspNetResponse.Cache.SetETag(eTag); // Sending a Last-Modified header isn't a good enough reason to force evaluation of lastModificationDateAndTimeGetter, which could be expensive. if (lastModificationDateAndTimeGetter != null && lastModificationDateAndTime.IsValueCreated) { aspNetResponse.Cache.SetLastModified(lastModificationDateAndTime.Value.UtcDateTime); } // When we separate EWF from Web Forms, we may need to add a Vary header with "Accept-Encoding". Do that here. var ifNoneMatch = aspNetRequest.Headers.GetValues("If-None-Match"); // returns null if field missing if (ifNoneMatch != null && ifNoneMatch.Contains(eTag)) { aspNetResponse.StatusCode = 304; return; } var memoryCacheKey = memoryCacheKeyGetter(); if (memoryCacheKey.Any()) { var fullResponse = FullResponse.GetFromCache(memoryCacheKey, lastModificationDateAndTime.Value, () => response.Value.CreateFullResponse()); response = new Lazy <EwfResponse>(() => new EwfResponse(fullResponse)); } if (response.Value.ContentType.Length > 0) { aspNetResponse.ContentType = response.Value.ContentType; } var fileName = response.Value.FileNameCreator(); if (fileName.Any()) { aspNetResponse.AppendHeader("content-disposition", "attachment; filename=\"" + fileName + "\""); } if (aspNetRequest.HttpMethod != "HEAD") { response.Value.BodyCreator.WriteToResponse(aspNetResponse); } }); }
/// <summary> /// Creates a response writer with a generic response and no caching information. /// </summary> /// <param name="response">The response.</param> public EwfSafeResponseWriter(EwfResponse response) { writer = createWriter(() => response, "", "", null, () => ""); }
/// <summary> /// Creates a response writer with a generic response and no caching information. /// </summary> /// <param name="response">The response.</param> public EwfSafeResponseWriter( EwfResponse response ) { writer = createWriter( () => response, "", "", null, () => "" ); }
private static Action<HttpRequest, HttpResponse> createWriter( Func<EwfResponse> responseCreator, string urlVersionString, string eTagBase, Func<DateTimeOffset> lastModificationDateAndTimeGetter, Func<string> memoryCacheKeyGetter) { return ( aspNetRequest, aspNetResponse ) => { // Disable ASP.NET output caching. aspNetResponse.Cache.SetNoServerCaching(); var response = new Lazy<EwfResponse>( responseCreator ); // If we ever want to implement content negotiation, we can do so here. We can accept multiple response-creator functions instead of just one. Then, if // the request contains an Accept header, we can evaluate the response-creator functions here and then choose the response with the best content type. // If there is no match, we can send a 406 (Not Acceptable) status code and exit the method. Note that if we do add support for multiple // response-creator functions, we need to incorporate the chosen response's content type into the ETag if we want it to remain a strong ETag. We also // need to incorporate the content type into the memory-cache key. // Assume that all HTTPS responses are private. This isn't true for CSS, JavaScript, etc. requests that are only secure in order to match the security // of a page, but that's not a big deal since most shared caches can't open and cache HTTPS anyway. // // If we don't have caching information, the response is probably not shareable. // // Using ServerAndPrivate instead of just Private is a hack that is necessary because ASP.NET suppresses ETags with Private. But ASP.NET output caching // (i.e. server caching) will still be disabled because of our SetNoServerCaching call above. aspNetResponse.Cache.SetCacheability( !EwfApp.Instance.RequestIsSecure( aspNetRequest ) && ( urlVersionString.Any() || eTagBase.Any() || lastModificationDateAndTimeGetter != null ) ? HttpCacheability.Public : HttpCacheability.ServerAndPrivate ); aspNetResponse.Cache.SetMaxAge( urlVersionString.Any() ? TimeSpan.FromDays( 365 ) : TimeSpan.Zero ); var lastModificationDateAndTime = lastModificationDateAndTimeGetter != null ? new Lazy<DateTimeOffset>( lastModificationDateAndTimeGetter ) : null; string eTag; if( urlVersionString.Any() ) eTag = urlVersionString; else if( eTagBase.Any() || lastModificationDateAndTimeGetter != null ) eTag = eTagBase + ( lastModificationDateAndTimeGetter != null ? GetUrlVersionString( lastModificationDateAndTime.Value ) : "" ); else { // Buffer the response body. var responseWithBufferedBody = new EwfResponse( response.Value.ContentType, response.Value.BodyCreator.GetBufferedBodyCreator(), fileNameCreator: response.Value.FileNameCreator ); response = new Lazy<EwfResponse>( () => responseWithBufferedBody ); var bodyAsBinary = response.Value.BodyCreator.BodyIsText ? Encoding.UTF8.GetBytes( response.Value.BodyCreator.TextBodyCreator() ) : response.Value.BodyCreator.BinaryBodyCreator(); eTag = Convert.ToBase64String( MD5.Create().ComputeHash( bodyAsBinary ) ); } // Strong ETags must vary by content coding. Since we don't know yet how this response will be encoded (gzip or otherwise), the best thing we can do is // use the Accept-Encoding header value in the ETag. var acceptEncoding = aspNetRequest.Headers[ "Accept-Encoding" ]; // returns null if field missing if( acceptEncoding != null ) eTag += acceptEncoding.Replace( ", ", "" ); // On 4 Feb 2015 we saw the comma/space cause Chrome to incorrectly split the ETag apart. aspNetResponse.Cache.SetETag( eTag ); // Sending a Last-Modified header isn't a good enough reason to force evaluation of lastModificationDateAndTimeGetter, which could be expensive. if( lastModificationDateAndTimeGetter != null && lastModificationDateAndTime.IsValueCreated ) aspNetResponse.Cache.SetLastModified( lastModificationDateAndTime.Value.UtcDateTime ); // When we separate EWF from Web Forms, we may need to add a Vary header with "Accept-Encoding". Do that here. var ifNoneMatch = aspNetRequest.Headers.GetValues( "If-None-Match" ); // returns null if field missing if( ifNoneMatch != null && ifNoneMatch.Contains( eTag ) ) { aspNetResponse.StatusCode = 304; return; } var memoryCacheKey = memoryCacheKeyGetter(); if( memoryCacheKey.Any() ) { var fullResponse = FullResponse.GetFromCache( memoryCacheKey, lastModificationDateAndTime.Value, () => response.Value.CreateFullResponse() ); response = new Lazy<EwfResponse>( () => new EwfResponse( fullResponse ) ); } if( response.Value.ContentType.Length > 0 ) aspNetResponse.ContentType = response.Value.ContentType; var fileName = response.Value.FileNameCreator(); if( fileName.Any() ) aspNetResponse.AppendHeader( "content-disposition", "attachment; filename=\"" + fileName + "\"" ); if( aspNetRequest.HttpMethod != "HEAD" ) response.Value.BodyCreator.WriteToResponse( aspNetResponse ); }; }
void IHttpHandler.ProcessRequest(HttpContext context) { var url = EwfApp.GetRequestAppRelativeUrl(context.Request); var queryIndex = url.IndexOf("?", StringComparison.Ordinal); if (queryIndex >= 0) { url = url.Remove(queryIndex); } var extensionIndex = url.LastIndexOf(".", StringComparison.Ordinal); var versionStringOrFileExtensionIndex = extensionIndex; var urlVersionString = ""; if (url.StartsWith(VersionedFilesFolderName + "/") || url.StartsWith(EwfFolderName + "/" + VersionedFilesFolderName + "/")) { urlVersionString = "invariant"; } else { // We assume that all URL version strings will have the same length as the format string. var prefixedVersionStringIndex = extensionIndex - (urlVersionStringPrefix.Length + EwfSafeResponseWriter.UrlVersionStringFormat.Length); if (prefixedVersionStringIndex >= 0) { DateTimeOffset dateAndTime; var versionString = url.Substring(prefixedVersionStringIndex + urlVersionStringPrefix.Length, EwfSafeResponseWriter.UrlVersionStringFormat.Length); if (DateTimeOffset.TryParseExact( versionString, EwfSafeResponseWriter.UrlVersionStringFormat, DateTimeFormatInfo.InvariantInfo, DateTimeStyles.None, out dateAndTime)) { versionStringOrFileExtensionIndex = prefixedVersionStringIndex; urlVersionString = versionString; } } } if (versionStringOrFileExtensionIndex < 0) { throw new ResourceNotAvailableException("Failed to find the extension in the URL.", null); } var extension = url.Substring(extensionIndex); var staticFileInfo = EwfApp.GlobalType.Assembly.CreateInstance( CombineNamespacesAndProcessEwfIfNecessary( EwfApp.GlobalType.Namespace, (url.Remove(versionStringOrFileExtensionIndex) + extension.CapitalizeString()).Separate("/", false) .Select(i => EwlStatics.GetCSharpIdentifier(i.CapitalizeString())) .Aggregate((a, b) => a + "." + b) + "+Info")) as StaticFileInfo; if (staticFileInfo == null) { throw new ResourceNotAvailableException("Failed to create an Info object for the request.", null); } var mediaTypeOverride = EwfApp.Instance.GetMediaTypeOverrides().SingleOrDefault(i => i.FileExtension == extension); var contentType = mediaTypeOverride != null ? mediaTypeOverride.MediaType : MimeTypeMap.GetMimeType(extension); Func <string> cacheKeyGetter = () => staticFileInfo.GetUrl(false, false, false); EwfSafeResponseWriter responseWriter; if (contentType == ContentTypes.Css) { Func <string> cssGetter = () => File.ReadAllText(staticFileInfo.FilePath); responseWriter = urlVersionString.Any() ? new EwfSafeResponseWriter( cssGetter, urlVersionString, () => new ResponseMemoryCachingSetup(cacheKeyGetter(), staticFileInfo.GetResourceLastModificationDateAndTime())) : new EwfSafeResponseWriter( () => EwfResponse.Create(ContentTypes.Css, new EwfResponseBodyCreator(() => CssPreprocessor.TransformCssFile(cssGetter()))), staticFileInfo.GetResourceLastModificationDateAndTime(), memoryCacheKeyGetter: cacheKeyGetter); } else { Func <EwfResponse> responseCreator = () => EwfResponse.Create( contentType, new EwfResponseBodyCreator( responseStream => { using (var fileStream = File.OpenRead(staticFileInfo.FilePath)) fileStream.CopyTo(responseStream); })); responseWriter = urlVersionString.Any() ? new EwfSafeResponseWriter( responseCreator, urlVersionString, memoryCachingSetupGetter: () => new ResponseMemoryCachingSetup(cacheKeyGetter(), staticFileInfo.GetResourceLastModificationDateAndTime())) : new EwfSafeResponseWriter( responseCreator, staticFileInfo.GetResourceLastModificationDateAndTime(), memoryCacheKeyGetter: cacheKeyGetter); } responseWriter.WriteResponse(); }
private static Action <HttpResponse, HttpRequest, bool> createWriter( Func <EwfResponse> responseCreator, string urlVersionString, string eTagBase, Func <DateTimeOffset> lastModificationDateAndTimeGetter, Func <string> memoryCacheKeyGetter) { return((aspNetResponse, aspNetRequest, forceImmediateResponseExpiration) => { var response = new Lazy <EwfResponse>(responseCreator); // If we ever want to implement content negotiation, we can do so here. We can accept multiple response-creator functions instead of just one. Then, if // the request contains an Accept header, we can evaluate the response-creator functions here and then choose the response with the best content type. // If there is no match, we can send a 406 (Not Acceptable) status code and exit the method. Note that if we do add support for multiple // response-creator functions, we need to incorporate the chosen response's content type into the ETag if we want it to remain a strong ETag. We also // need to incorporate the content type into the memory-cache key. AddCacheControlHeader( aspNetResponse, EwfApp.Instance.RequestIsSecure(aspNetRequest), urlVersionString.Any() || eTagBase.Any() || lastModificationDateAndTimeGetter != null, urlVersionString.Any() && !forceImmediateResponseExpiration); var lastModificationDateAndTime = lastModificationDateAndTimeGetter != null ? new Lazy <DateTimeOffset>(lastModificationDateAndTimeGetter) : null; string eTag; if (urlVersionString.Any()) { eTag = urlVersionString; } else if (eTagBase.Any() || lastModificationDateAndTimeGetter != null) { eTag = eTagBase + (lastModificationDateAndTimeGetter != null ? GetUrlVersionString(lastModificationDateAndTime.Value) : ""); } else { // Buffer the response body. var responseWithBufferedBody = EwfResponse.Create( response.Value.ContentType, response.Value.BodyCreator.GetBufferedBodyCreator(), fileNameCreator: response.Value.FileNameCreator, additionalHeaderFieldGetter: response.Value.AdditionalHeaderFieldGetter); response = new Lazy <EwfResponse>(() => responseWithBufferedBody); var bodyAsBinary = response.Value.BodyCreator.BodyIsText ? Encoding.UTF8.GetBytes(response.Value.BodyCreator.TextBodyCreator()) : response.Value.BodyCreator.BinaryBodyCreator(); eTag = Convert.ToBase64String(MD5.Create().ComputeHash(bodyAsBinary)); } // Strong ETags must vary by content coding. Since we don't know yet how this response will be encoded (gzip or otherwise), the best thing we can do is // use the Accept-Encoding header value in the ETag. var acceptEncoding = aspNetRequest.Headers["Accept-Encoding"]; // returns null if field missing if (acceptEncoding != null) { eTag += acceptEncoding.Replace(", ", ""); // On 4 Feb 2015 we saw the comma/space cause Chrome to incorrectly split the ETag apart. } aspNetResponse.Cache.SetETag(eTag); // Sending a Last-Modified header isn't a good enough reason to force evaluation of lastModificationDateAndTimeGetter, which could be expensive. if (lastModificationDateAndTimeGetter != null && lastModificationDateAndTime.IsValueCreated) { aspNetResponse.Cache.SetLastModified(lastModificationDateAndTime.Value.UtcDateTime); } // When we separate EWF from Web Forms, we may need to add a Vary header with "Accept-Encoding". Do that here. var ifNoneMatch = aspNetRequest.Headers.GetValues("If-None-Match"); // returns null if field missing if (ifNoneMatch != null && ifNoneMatch.Contains(eTag)) { aspNetResponse.StatusCode = 304; return; } var memoryCacheKey = memoryCacheKeyGetter(); if (memoryCacheKey.Any()) { var fullResponse = FullResponse.GetFromCache(memoryCacheKey, lastModificationDateAndTime.Value, () => response.Value.CreateFullResponse()); response = new Lazy <EwfResponse>(() => new EwfResponse(fullResponse)); } response.Value.WriteToAspNetResponse(aspNetResponse, omitBody: aspNetRequest.HttpMethod == "HEAD"); }); }
protected override EwfSafeRequestHandler getOrHead() => new EwfSafeResponseWriter( EwfResponse.Create(ContentTypes.PlainText, new EwfResponseBodyCreator(() => new JavaScriptSerializer().Serialize(getItems()))));