/// <summary> /// Logs a success event either through the health monitoring framework or directly to the event log /// </summary> /// <param name="msg"></param> /// <param name="data"></param> /// <param name="eventCode"></param> public static void LogSucessEvent(string msg, UploadData data, int eventCode) { if (HealthMonitoringManager.Enabled) { UploadRequestSuccessEvent objUploadRequestSuccessEvent = new UploadRequestSuccessEvent(msg, data, eventCode); objUploadRequestSuccessEvent.Raise(); } else { try { if (!EventLog.SourceExists(HealthMonitoringManager.Source)) { System.Diagnostics.EventLog.CreateEventSource(HealthMonitoringManager.Source, HealthMonitoringManager.Log); } System.Diagnostics.EventLog.WriteEntry( HealthMonitoringManager.Source, String.Format("{0}\r\n\r\n{1}", msg, data), EventLogEntryType.Information, eventCode); } catch { } } }
/// <summary> /// Gets upload data from context cache /// </summary> /// <param name="context"></param> /// <param name="uploadId"></param> /// <returns></returns> public static UploadData GetUploadData(HttpContext context, string uploadId) { #if (TRACE) ThreadLog.WriteLine("Getting upload data for " + uploadId); #endif #if (DEBUG) //DEBUG mode should support unit tests if ((context == null) || (String.IsNullOrEmpty(uploadId))) { return(new UploadData(String.Empty, String.Empty, false)); } #endif if (context == null) { throw new ArgumentNullException("context", Resources.ExceptionNullContext); } if (String.IsNullOrEmpty(uploadId)) { throw new ArgumentNullException("uploadId", Resources.ExceptionNullOrEmptyUploadId); } //Try first to retrieve upload context data from context cache UploadData objUploadDataRet = HttpRuntime.Cache.Get(UPLOAD_DATA_PREFIX + uploadId) as UploadData; //If upload context data is available in application state/context cache, check user provided download has started if (objUploadDataRet != null) { #if (TRACE) ThreadLog.WriteLine("Upload status is " + objUploadDataRet.ProgressStatus.ToString()); #endif //We need to test for Flash applets, because the Flash applet works in anonymous mode //but passes cookies which may contain the authentication from previous sessions on the web site //implying a failure of the test below if (!context.Request.UrlReferrer.AbsolutePath.ToLowerInvariant().EndsWith(".swf")) { if ((objUploadDataRet.IsAuthenticated != context.User.Identity.IsAuthenticated) || (objUploadDataRet.UserName != context.User.Identity.Name)) { throw new System.Security.SecurityException(Resources.ExceptionUnauthorizedAccessToUploadData); } } } else { //Upload context data could be null, for example when the uploadId requested does not exist #if (TRACE) ThreadLog.WriteLine("Upload data is null"); #endif } return(objUploadDataRet); }
/// <summary> /// Constructor /// </summary> /// <remarks>The request stream is passed as a Stream instead of a RequestStream to allow unit testing from dump files.</remarks> /// <param name="requestStream">A Stream representing the request stream</param> /// <param name="hashAlgorithm">The hash algorithm used to hash file upload data</param> /// <param name="contentLength"></param> /// <param name="encoding"></param> /// <param name="multipartBoundary"></param> /// <param name="uploadData"></param> public RequestFilter(Stream requestStream, HashAlgorithm hashAlgorithm, long contentLength, Encoding encoding, string multipartBoundary, UploadData uploadData) { if (requestStream == null) { throw new ArgumentNullException("requestStream"); } if (hashAlgorithm == null) { throw new ArgumentNullException("hashAlgorithm"); } if (contentLength < 0) { throw new ArgumentOutOfRangeException("contentLength"); } if (encoding == null) { throw new ArgumentNullException("encoding"); } if (multipartBoundary == null) { throw new ArgumentNullException("multipartBoundary"); } //Note: Even for unit testing we should have an uploadData object if (uploadData == null) { throw new ArgumentNullException("uploadData"); } _RequestStream = requestStream; //_CryptoStream = null; //CA1805 _HashAlgorithm = hashAlgorithm; _Encoding = encoding; _MultiPartBoundary = _Encoding.GetBytes(multipartBoundary); _MultiPartBoundary2 = _Encoding.GetBytes(Constants.LineSeparator + multipartBoundary); _MultiPartRegex = new Regex(@".*name=\""(?<name>.*)\"".*filename=\""(?<filename>.*)\"".*\r\nContent-Type:\s(?<contenttype>.*)", RegexOptions.IgnoreCase | RegexOptions.ExplicitCapture | RegexOptions.Compiled); _FilteredRequestStringBuilder = new StringBuilder(Constants.StringBuilderCapacity); _FilteredRequestStringBuilder.Append(multipartBoundary); //_IsProcessingUploadedFile = false; //CA1805 _UploadData = uploadData; _UploadData.ReportStart(contentLength); }
/// <summary> /// Called by user from page to cancel an upload /// </summary> public static void CancelUpload(HttpContext context, string uploadId) { if (context == null) { throw new ArgumentNullException("context", Resources.ExceptionNullContext); } if (String.IsNullOrEmpty(uploadId)) { throw new ArgumentNullException("uploadId", Resources.ExceptionNullOrEmptyUploadId); } UploadData objUploadData = GetUploadData(context, uploadId); if ((objUploadData != null) && (objUploadData.ProgressStatus != UploadProgressStatus.Canceled) && (objUploadData.ProgressStatus != UploadProgressStatus.Completed)) { objUploadData.ReportCanceled(); } }
/// <summary> /// Outputs a string representation of a upload data, which is used in health monitoring and event log entries /// </summary> /// <returns></returns> public override string ToString() { StringBuilder objStringBuilderRet = new StringBuilder(); objStringBuilderRet.AppendLine(Properties.Resources.UploadData_Title); if (HttpContext.Current != null) { objStringBuilderRet.AppendLine(String.Format(Properties.Resources.UploadData_Request, HttpContext.Current.Request.RawUrl)); } objStringBuilderRet.AppendLine(String.Format(Properties.Resources.UploadData_UploadId, this.UploadId)); objStringBuilderRet.AppendLine(String.Format(Properties.Resources.UploadData_UserName, this.UserName)); objStringBuilderRet.AppendLine(String.Format(Properties.Resources.UploadData_UploadProgressStatus, UploadData.Format(this.ProgressStatus), this.ProgressRatio)); objStringBuilderRet.AppendLine(String.Format(Properties.Resources.UploadData_BytesProgress, this.ContentPosition, this.ContentLength, this.BytesPerSecond)); objStringBuilderRet.AppendLine(String.Format(Properties.Resources.UploadData_TimeProgress, this.TimeElapsed, this.TimeLeftEstimated, this.TimeTotalEstimated)); objStringBuilderRet.AppendLine(Properties.Resources.UploadData_Files); foreach (UploadFile objUploadFile in this.UploadFiles) { objStringBuilderRet.AppendLine(String.Format( Properties.Resources.UploadData_UploadFile, objUploadFile.OriginalPath, objUploadFile.ContentType, objUploadFile.ContentLength, objUploadFile.ProviderFileKey)); } if (this.Exception != null) { objStringBuilderRet.AppendLine(String.Format(Properties.Resources.UploadData_Exception, this.Exception)); } return(objStringBuilderRet.ToString()); }
private void OnPreRequestHandlerExecute(object sender, EventArgs e) { HttpApplication objHttpApplication = sender as HttpApplication; System.Diagnostics.Debug.Assert(objHttpApplication != null); HttpRequest objHttpRequest = objHttpApplication.Request; System.Diagnostics.Debug.Assert(objHttpRequest != null); if (objHttpRequest != null && UploadHttpModule.IsUploadRequest(objHttpRequest)) { #if (TRACE) ThreadLog.WriteLine("Entered OnPreRequestHandlerExecute for an upload request"); #endif HttpContext objHttpContext = objHttpApplication.Context; System.Diagnostics.Debug.Assert(objHttpContext != null); HttpWorkerRequest objHttpWorkerRequest = UploadHttpModule.GetWorkerRequest(objHttpContext); System.Diagnostics.Debug.Assert(objHttpWorkerRequest != null); if (objHttpWorkerRequest != null) { //long lContentLength = objHttpRequest.ContentLength; long lContentLength = long.Parse(objHttpWorkerRequest.GetKnownRequestHeader(HttpWorkerRequest.HeaderContentLength), CultureInfo.InvariantCulture); if (lContentLength <= 0) //This is for Flash 8 FileReference which tests an empty post before sending a large upload { #if (TRACE) ThreadLog.WriteLine("No content, maybe Flash"); #endif HealthMonitoringManager.LogSucessEvent( Resources.ExceptionZeroContentLength, null, //<- no upload data yet UploadRequestSuccessEvent.RuntimeUploadSuccessZeroContentLength); objHttpApplication.Response.StatusCode = 200; //OK objHttpApplication.CompleteRequest(); //See: http://support.microsoft.com/kb/312629 //objHttpApplication.Response.End(); return; } //Initialize an upload monitor string sUploadID = UploadHttpModule.GetUploadID(objHttpRequest); if (String.IsNullOrEmpty(sUploadID)) { #if (TRACE) ThreadLog.WriteLine("No upload ID"); #endif HttpException objHttpException = new HttpException(400, Resources.ExceptionNullOrEmptyUploadId); HealthMonitoringManager.LogErrorEvent( Resources.ExceptionNullOrEmptyUploadId, null, //<- no upload data yet UploadRequestErrorEvent.RuntimeErrorMissingUploadID, objHttpException); UploadHttpModule.CloseConnectionAfterError(objHttpApplication.Response); throw objHttpException; //See comment in relation with MaxRequestLength here below //objHttpApplication.Response.StatusCode = 400; //Bad request //objHttpApplication.Response.StatusDescription = Resources.ExceptionNullOrEmptyUploadId; //objHttpApplication.Response.Write(String.Format(Resources.Culture, Resources.ErrorPage, 400, Resources.ExceptionNullOrEmptyUploadId)); //objHttpApplication.CompleteRequest(); //See: http://support.microsoft.com/kb/312629 //objHttpApplication.Response.End(); } UploadData objUploadData = UploadMonitor.SetUploadData(objHttpContext, sUploadID); System.Diagnostics.Debug.Assert(objUploadData != null); //Check whether we should read MaxRequestLength from httpRuntime section of web.config (true) bool bMaximizeRequestLength = UploadHttpModule.GetForceHttpMaxRequestLength(objHttpRequest); //Check the upload size and end request if file is too big long lMaxRequestLength = UploadHttpModule.GetMaxRequestLengthBytes(objHttpContext, bMaximizeRequestLength); if ((lMaxRequestLength >= 0) && (lContentLength > lMaxRequestLength)) { #if (TRACE) ThreadLog.WriteLine("Post request is too large"); #endif HttpException objHttpException = new HttpException(413, Resources.ExceptionPostTooLarge); HealthMonitoringManager.LogErrorEvent( Resources.ExceptionPostTooLarge, objUploadData, UploadRequestErrorEvent.RuntimeErrorPostTooLarge, objHttpException); objUploadData.ReportFailed(objHttpException); UploadHttpModule.CloseConnectionAfterError(objHttpApplication.Response); throw objHttpException; //There are 3 possible options //1) Do nothing and let httpRuntime/maxRequestlength do its job //2) Do something like // objHttpApplication.Response.StatusCode = 413; // objHttpApplication.Response.StatusDescription = Resources.ExceptionPostTooLarge; // objHttpApplication.Response.Write(String.Format(Resources.Culture, Resources.ErrorPage, 413, Resources.ExceptionPostTooLarge)); // objHttpApplication.CompleteRequest(); // See: http://support.microsoft.com/kb/312629 // //objHttpApplication.Response.End(); // return; //3) Raise an HttpException //Option 1 is no more an option since we have implemented uploadRuntime //Option 2 sometimes aborts and closes the connection with an IE error page, //sometimes displays a blank page. When the IE page appears, we get //an ERROR_INTERNET_CONNECTION_ABORTED, when the blank page is displayed //the post returns a 413 status code. To get some content we would need //to write to the response _Input using something like objHttpApplication.Response.Write //HttpRequest.GetEntireRawContent implements option 3). Actually it triggers //throw new HttpException(SR.GetString("Max_request_length_exceeded"), null, 0xbbc); //after calling HttpResponse.CloseConnectionAfterError(). In this case an unhdandled //exception is thrown abd we can rely on Application_Error and Custom Errors which //is the best option. } #if (TRACE) ThreadLog.WriteLine("Start parsing upload _Input"); #endif Encoding objEncoding = objHttpRequest.ContentEncoding; string sContentType = objHttpRequest.ContentType; int iPos = sContentType.ToLowerInvariant().IndexOf(Constants.MultiPartBoundary); if (iPos < 0) { #if (TRACE) ThreadLog.WriteLine("Bad request"); #endif HttpException objHttpException = new HttpException(400, Resources.ExceptionMalformedContentType); HealthMonitoringManager.LogErrorEvent( Resources.ExceptionMalformedContentType, objUploadData, UploadRequestErrorEvent.RuntimeErrorMalformedContentType, objHttpException ); objUploadData.ReportFailed(objHttpException); UploadHttpModule.CloseConnectionAfterError(objHttpApplication.Response); throw objHttpException; //See comment in relation with MaxRequestLength here above //objHttpApplication.Response.StatusCode = 400; //Bad request //objHttpApplication.Response.StatusDescription = Resources.ExceptionMultipartBoundaryNotFound; //objHttpApplication.Response.Write(String.Format(Resources.Culture, Resources.ErrorPage, 400, Resources.ExceptionMultipartBoundaryNotFound)); //objHttpApplication.CompleteRequest(); //See: http://support.microsoft.com/kb/312629 //objHttpApplication.Response.End(); //return; } string sMultiPartBoundary = Constants.BoundaryPrefix + sContentType.Substring(iPos + Constants.MultiPartBoundary.Length); #if (TRACE) ThreadLog.WriteLine("Identified boundary = " + sMultiPartBoundary); #endif RequestStream objRequestStream = null; RequestFilter objRequestFilter = null; try { HashAlgorithm objHashAlgorithm = CryptoConfig.CreateFromName(Constants.HashAlgorithmName) as HashAlgorithm; //objHashAlgorithm.Initialize(); Done in RequestFilter objRequestStream = new RequestStream(objHttpWorkerRequest); objRequestFilter = new RequestFilter(objRequestStream, objHashAlgorithm, lContentLength, objEncoding, sMultiPartBoundary, objUploadData); #if (TRACE) ThreadLog.WriteLine("Started parsing"); #endif //Parse the request to filter input files MimeParser objMimeParser = new MimeParser(objRequestFilter); objMimeParser.Parse(); //Get the filtered request byte[] arrFilteredRequest = objRequestFilter.Encoding.GetBytes(objRequestFilter.FilteredRequest); //Redirect the filtered request RedirectFilteredRequest(objHttpApplication, objHttpWorkerRequest, arrFilteredRequest); #if (TRACE) ThreadLog.WriteLine("Filtered request redirected"); #endif HealthMonitoringManager.LogSucessEvent( Resources.MessageUploadCompleted, objUploadData, UploadRequestSuccessEvent.RuntimeUploadSuccessCompleted ); } catch (Exception Ex) { #if (TRACE) ThreadLog.WriteLine("Parsing error"); #endif HealthMonitoringManager.LogErrorEvent( Resources.ExceptionUnhandled + "\r\n" + objHttpWorkerRequest.GetKnownRequestHeader(HttpWorkerRequest.HeaderUserAgent), objUploadData, UploadRequestErrorEvent.RuntimeErrorExceptionUnhandled, Ex); objUploadData.ReportFailed(Ex); UploadHttpModule.CloseConnectionAfterError(objHttpApplication.Response); if ((Ex is HttpException) || (Ex is System.Net.WebException)) { throw; } else { throw new HttpException(500, Resources.ExceptionUnhandled, Ex); } //objHttpApplication.Response.StatusCode = 500; //Error //objHttpApplication.Response.StatusDescription = Resources.ExceptionUnhandled; //objHttpApplication.Response.Write(String.Format(Resources.Culture, Resources.ErrorPage, 500, Ex.Message)); //objHttpApplication.CompleteRequest(); //See: http://support.microsoft.com/kb/312629 //objHttpApplication.Response.End(); } finally { #if (TRACE) ThreadLog.WriteLine("Disposing of resources"); #endif if (objRequestFilter != null) { objRequestFilter.Dispose(); } if (objRequestStream != null) { objRequestStream.Dispose(); } if (objUploadData != null) { if (objUploadData.ProgressStatus != UploadProgressStatus.Completed) { this.DeleteUploadFiles(objUploadData.UploadFiles); } //Too soon to release here: let sliding expiration work for us //UploadMonitor.Release(objHttpContext, objUploadData.UploadId); } } } #if (TRACE) ThreadLog.WriteLine("Exit OnPreRequestHandlerExecute"); #endif } }
private const int CACHE_SLIDINGEXPIRATION = 1; //in Days #region Other Members #region SetUploadData /// <summary> /// Sets upload data in context cache /// </summary> /// <param name="context"></param> /// <param name="uploadId"></param> /// <returns></returns> public static UploadData SetUploadData(HttpContext context, string uploadId) { #if (TRACE) ThreadLog.WriteLine("Setting upload data with " + uploadId); #endif //Note: //Adding UploadData to HttpContext.Items is not a possible option considering HttpContext.Items are only available during the lifetime and scope of a single request //Adding UploadData to HttpSessionState is a reasonable choice to share data between requests and prevent other users to access uploaded files but the server hangs the connection //So we are left with two options: // 1) storing UploadData in HttpApplicationState. // 2) storing UploadData in HttpRuntime.Cache //Note that HttpApplicationState is a legacy of ASP, and the use of HttpRuntime.Cache is recommended in ASP.NET //Also note: //Because HttpRuntime.Cache is global, we need to prevent unauthorized access and check the context user each time UploadData is requested. #if (DEBUG) //DEBUG mode should support unit tests if ((context == null) || (String.IsNullOrEmpty(uploadId))) { return(new UploadData((String.IsNullOrEmpty(uploadId) ? String.Empty : uploadId), String.Empty, false)); } #endif if (context == null) { throw new ArgumentNullException("context", Resources.ExceptionNullContext); } if (String.IsNullOrEmpty(uploadId)) { throw new ArgumentNullException("uploadId", Resources.ExceptionNullOrEmptyUploadId); } //Needs to be removed before it can be added if (context.Items.Contains(UploadMonitor.UploadIdParam)) { context.Items.Remove(UploadMonitor.UploadIdParam); } context.Items.Add(UploadMonitor.UploadIdParam, uploadId); UploadData objUploadDataRet = new UploadData(uploadId, context.User.Identity.Name, context.User.Identity.IsAuthenticated); if (HttpRuntime.Cache.Get(UPLOAD_DATA_PREFIX + uploadId) != null) { HttpRuntime.Cache.Remove(UPLOAD_DATA_PREFIX + uploadId); } //TODO: Not sure we could create an event callback because we need teh data after the module has completed its part of the request //See: http://blogs.msdn.com/tess/archive/2006/08/11/asp-net-quiz-answers-does-page-cache-leak-memory.aspx HttpRuntime.Cache.Add( UPLOAD_DATA_PREFIX + uploadId, objUploadDataRet, null, Cache.NoAbsoluteExpiration, new TimeSpan(CACHE_SLIDINGEXPIRATION, 0, 0, 0), CacheItemPriority.AboveNormal, //Make it above normal just in case scavenging is triggered because some caching space is needed null ); return(objUploadDataRet); }