/// <summary> /// Add a crash passed in the payload as Xml to the database. /// </summary> /// <param name="id">Unused.</param> /// <returns>The row id of the newly added crash.</returns> public ActionResult AddCrash( int id ) { using( FAutoScopedLogTimer LogTimer = new FAutoScopedLogTimer( this.GetType().ToString() + "(NewCrashId=" + id + ")" ) ) { CrashRepository Crashes = new CrashRepository(); CrashReporterResult NewCrashResult = new CrashReporterResult(); NewCrashResult.ID = -1; try { using( StreamReader Reader = new StreamReader( Request.InputStream, Request.ContentEncoding ) ) { string Result = Reader.ReadToEnd(); CrashDescription NewCrash = XmlHandler.FromXmlString<CrashDescription>( Result ); NewCrashResult.ID = Crashes.AddNewCrash( NewCrash ); NewCrashResult.bSuccess = true; } } catch( Exception Ex ) { NewCrashResult.Message = Ex.ToString(); NewCrashResult.bSuccess = false; } string ReturnResult = XmlHandler.ToXmlString<CrashReporterResult>( NewCrashResult ); return Content( ReturnResult, "text/xml" ); } }
/// <summary> /// Add a crash passed in the payload as Xml to the database. /// </summary> /// <param name="id">Unused.</param> /// <returns>The row id of the newly added crash.</returns> public ActionResult AddCrash( int id ) { CrashReporterResult NewCrashResult = new CrashReporterResult(); NewCrashResult.ID = -1; try { using( StreamReader Reader = new StreamReader( Request.InputStream, Request.ContentEncoding ) ) { string Result = Reader.ReadToEnd(); CrashDescription NewCrash = XmlHandler.FromXmlString<CrashDescription>( Result ); NewCrashResult.ID = LocalCrashRepository.AddNewCrash( NewCrash ); NewCrashResult.bSuccess = true; } } catch( Exception Ex ) { NewCrashResult.Message = Ex.ToString(); NewCrashResult.bSuccess = false; } string ReturnResult = XmlHandler.ToXmlString<CrashReporterResult>( NewCrashResult ); return Content( ReturnResult, "text/xml" ); }
/// <summary> /// The main listener callback to handle client requests. /// </summary> /// <param name="ClientRequest">The request from the client.</param> private void AsyncHandleHttpRequest( IAsyncResult ClientRequest ) { try { HttpListenerContext Context = ServiceHttpListener.EndGetContext( ClientRequest ); ServiceHttpListener.BeginGetContext( AsyncHandleHttpRequest, null ); HttpListenerRequest Request = Context.Request; bool bIgnorePerfData = false; using( HttpListenerResponse Response = Context.Response ) { // Extract the URL parameters string[] UrlElements = Request.RawUrl.Split( "/".ToCharArray(), StringSplitOptions.RemoveEmptyEntries ); // http://*:57005/CrashReporter/CheckReport // http://*:57005/CrashReporter/CheckReportDetail CrashReporterResult ReportResult = new CrashReporterResult(); if( UrlElements[0].ToLower() == "crashreporter" ) { switch( UrlElements[1].ToLower() ) { case "ping": ReportResult.bSuccess = true; break; case "checkreport": ReportResult = CheckReport( Context.Request ); break; case "checkreportdetail": ReportResult = CheckReportDetail( Context.Request ); break; case "uploadreportfile": ReportResult = ReceiveFile( Context.Request ); bIgnorePerfData = true; break; case "uploadcomplete": ReportResult = UploadComplete( Context.Request ); break; default: ReportResult.bSuccess = false; ReportResult.Message = "Invalid command: " + UrlElements[1]; break; } } else { ReportResult.bSuccess = false; ReportResult.Message = "Invalid application: " + UrlElements[0] + " (expecting CrashReporter)"; } string ResponseString = XmlHandler.ToXmlString<CrashReporterResult>( ReportResult ); Response.SendChunked = true; Response.ContentType = "text/xml"; byte[] Buffer = Encoding.UTF8.GetBytes( ResponseString ); Response.ContentLength64 = Buffer.Length; Response.OutputStream.Write( Buffer, 0, Buffer.Length ); Response.StatusCode = ( int )HttpStatusCode.OK; if( !bIgnorePerfData ) { // Update the overhead data. Int64 ContentLenght = Response.ContentLength64 + Request.ContentLength64; Interlocked.Add( ref UploadsInProgress.CurrentReceivedData, ContentLenght ); } } } catch( Exception Ex ) { CrashReporterReceiverServicer.WriteEvent( "Error during async listen: " + Ex.Message ); } }
/// <summary> /// Rename to the temporary landing zone directory to the final location. /// </summary> /// <param name="Request">A request containing either the Report Id as a string or an XML representation of a CheckReportRequest class instance.</param> /// <returns>true if everything is renamed correctly.</returns> private CrashReporterResult UploadComplete(HttpListenerRequest Request) { var ReportResult = new CrashReporterResult(); var RequestClass = new CheckReportRequest(); RequestClass.ReportId = GetReportIdFromPostData(GetContentStreamString(Request)); string IntermediatePathName = Path.Combine(FileReceiptPath, RequestClass.ReportId); if (!UploadsInProgress.TrySetReportComplete(RequestClass.ReportId)) { ReportResult.Message = string.Format("Report \"{0}\" has already been completed", RequestClass.ReportId); ReportResult.bSuccess = false; return ReportResult; } DirectoryInfo DirInfo = new DirectoryInfo(IntermediatePathName); if (!DirInfo.Exists) { return ReportResult; } LandingZone.ReceiveReport(DirInfo, RequestClass.ReportId); ReportResult.bSuccess = true; int CurrentDay = DateTime.UtcNow.Day; if( CurrentDay > LastDay ) { // Check the log and create a new one for a new day. CrashReporterReceiverServicer.Log.CreateNewLogFile(); LastDay = CurrentDay; } return ReportResult; }
/// <summary> /// Receive a file and write it to a temporary folder. /// </summary> /// <param name="Request">A request containing the file details in the headers (DirectoryName/FileName/FileLength).</param> /// <returns>true if the file is received successfully.</returns> /// <remarks>There is an arbitrary file size limit of CrashReporterConstants.MaxFileSizeToUpload as a simple exploit prevention method.</remarks> private CrashReporterResult ReceiveFile( HttpListenerRequest Request ) { CrashReporterResult ReportResult = new CrashReporterResult(); if( !Request.HasEntityBody ) { return ReportResult; } // Take this opportunity to clean out folders for reports that were never completed CheckForAbandonedReports(); // Make sure we have a sensible file size long BytesToReceive = 0; if (long.TryParse(Request.Headers["FileLength"], out BytesToReceive)) { if (BytesToReceive >= CrashReporterConstants.MaxFileSizeToUpload) { return ReportResult; } } string DirectoryName = Request.Headers["DirectoryName"]; string FileName = Request.Headers["FileName"]; var T = Request.ContentLength64; bool bIsOverloaded = false; if( !UploadsInProgress.TryReceiveFile( DirectoryName, FileName, BytesToReceive, ref ReportResult.Message, ref bIsOverloaded ) ) { CrashReporterReceiverServicer.WriteEvent(ReportResult.Message); ReportResult.bSuccess = false; return ReportResult; } string PathName = Path.Combine( FileReceiptPath, DirectoryName, FileName ); // Recreate the file receipt directory, just in case. Directory.CreateDirectory( FileReceiptPath ); // Create the folder to save files to DirectoryInfo DirInfo = new DirectoryInfo( Path.GetDirectoryName( PathName ) ); DirInfo.Create(); // Make sure the file doesn't already exist. If it does, delete it. if (File.Exists(PathName)) { File.Delete(PathName); } FileInfo Info = new FileInfo( PathName ); FileStream FileWriter = Info.OpenWrite(); // Read in the input stream from the request, and write to a file long OriginalBytesToReceive = BytesToReceive; try { using (BinaryReader Reader = new BinaryReader(Request.InputStream)) { byte[] Buffer = new byte[CrashReporterConstants.StreamChunkSize]; while (BytesToReceive > 0) { int BytesToRead = Math.Min((int)BytesToReceive, CrashReporterConstants.StreamChunkSize); Interlocked.Add( ref UploadsInProgress.CurrentReceivedData, BytesToRead ); int ReceivedChunkSize = Reader.Read(Buffer, 0, BytesToRead); if (ReceivedChunkSize == 0) { ReportResult.Message = string.Format("Partial file \"{0}\" received", FileName); ReportResult.bSuccess = false; CrashReporterReceiverServicer.WriteEvent(ReportResult.Message); return ReportResult; } BytesToReceive -= ReceivedChunkSize; FileWriter.Write(Buffer, 0, ReceivedChunkSize); } } } finally { FileWriter.Close(); Request.InputStream.Close(); UploadsInProgress.FileUploadAttemptDone( OriginalBytesToReceive, bIsOverloaded ); bool bWriteMetadata = Path.GetExtension( FileName ) == ".ue4crash"; if( bWriteMetadata ) { string CompressedSize = Request.Headers["CompressedSize"]; string UncompressedSize = Request.Headers["UncompressedSize"]; string NumberOfFiles = Request.Headers["NumberOfFiles"]; string MetadataPath = Path.Combine( FileReceiptPath, DirectoryName, Path.GetFileNameWithoutExtension( FileName ) + ".xml" ); XmlHandler.WriteXml<FCompressedCrashInformation>( new FCompressedCrashInformation( CompressedSize, UncompressedSize, NumberOfFiles ), MetadataPath ); } } ReportResult.bSuccess = true; return ReportResult; }
/// <summary> /// Check to see if we wish to reject a report based on the WER meta data. /// </summary> /// <param name="Request">A request containing the XML representation of a WERReportMetadata class instance.</param> /// <returns>true if we do not reject.</returns> private CrashReporterResult CheckReportDetail( HttpListenerRequest Request ) { CrashReporterResult ReportResult = new CrashReporterResult(); string WERReportMetadataString = GetContentStreamString( Request ); WERReportMetadata WERData = null; if( WERReportMetadataString.Length > 0 ) { try { WERData = XmlHandler.FromXmlString<WERReportMetadata>( WERReportMetadataString ); } catch( System.Exception Ex ) { CrashReporterReceiverServicer.WriteEvent( "Error during XmlHandler.FromXmlString, probably incorrect encoding, trying to fix: " + Ex.Message ); byte[] StringBytes = System.Text.Encoding.Unicode.GetBytes( WERReportMetadataString ); string ConvertedXML = System.Text.Encoding.UTF8.GetString( StringBytes ); WERData = XmlHandler.FromXmlString<WERReportMetadata>( ConvertedXML ); } } if( WERData != null ) { // Ignore crashes in the minidump parser itself ReportResult.bSuccess = true; if( WERData.ProblemSignatures.Parameter0.ToLower() == "MinidumpDiagnostics".ToLower() ) { ReportResult.bSuccess = false; ReportResult.Message = "Rejecting MinidumpDiagnostics crash"; } // Ignore Debug and DebugGame crashes string CrashingModule = WERData.ProblemSignatures.Parameter3.ToLower(); if( CrashingModule.Contains( "-debug" ) ) { ReportResult.bSuccess = false; ReportResult.Message = "Rejecting Debug or DebugGame crash"; } } return ReportResult; }
/// <summary> /// Check to see if a report has already been uploaded. /// </summary> /// <param name="Request">A request containing either the Report Id as a string or an XML representation of a CheckReportRequest class instance.</param> /// <returns>Result object, indicating whether the report has already been uploaded.</returns> private CrashReporterResult CheckReport(HttpListenerRequest Request) { var ReportResult = new CrashReporterResult(); var RequestClass = new CheckReportRequest(); RequestClass.ReportId = GetReportIdFromPostData(GetContentStreamString(Request)); ReportResult.bSuccess = !LandingZone.HasReportAlreadyBeenReceived(RequestClass.ReportId); if( !ReportResult.bSuccess ) { CrashReporterReceiverServicer.WriteEvent( string.Format( "Report \"{0}\" has already been received", RequestClass.ReportId ) ); } return ReportResult; }
/// <summary> /// Add a crash passed in the payload as Xml to the database. /// </summary> /// <param name="id">Unused.</param> /// <returns>The row id of the newly added crash.</returns> public ActionResult AddCrash( int id ) { var newCrashResult = new CrashReporterResult(); CrashDescription newCrash; newCrashResult.ID = -1; string payloadString; //Read the request payload try { using (var reader = new StreamReader(Request.InputStream, Request.ContentEncoding)) { payloadString = reader.ReadToEnd(); if (string.IsNullOrEmpty(payloadString)) { FLogger.Global.WriteEvent(string.Format("Add Crash Failed : Payload string empty")); } } } catch (Exception ex) { var messageBuilder = new StringBuilder(); messageBuilder.AppendLine("Error Reading Crash Payload"); messageBuilder.AppendLine("Exception was:"); messageBuilder.AppendLine(ex.ToString()); FLogger.Global.WriteException(messageBuilder.ToString()); newCrashResult.Message = messageBuilder.ToString(); newCrashResult.bSuccess = false; return Content(XmlHandler.ToXmlString<CrashReporterResult>(newCrashResult), "text/xml"); } // De-serialise the payload string try { newCrash = XmlHandler.FromXmlString<CrashDescription>(payloadString); } catch (Exception ex) { var messageBuilder = new StringBuilder(); messageBuilder.AppendLine("Error Reading CrashDescription XML"); messageBuilder.AppendLine("Exception was: "); messageBuilder.AppendLine(ex.ToString()); FLogger.Global.WriteException(messageBuilder.ToString()); newCrashResult.Message = messageBuilder.ToString(); newCrashResult.bSuccess = false; return Content(XmlHandler.ToXmlString<CrashReporterResult>(newCrashResult), "text/xml"); } //Add crash to database try { var crash = CreateCrash(newCrash); newCrashResult.ID = crash.Id; newCrashResult.bSuccess = true; } catch (DbEntityValidationException dbentEx) { var messageBuilder = new StringBuilder(); messageBuilder.AppendLine("Exception was:"); messageBuilder.AppendLine(dbentEx.ToString()); var innerEx = dbentEx.InnerException; while (innerEx != null) { messageBuilder.AppendLine("Inner Exception : " + innerEx.Message); innerEx = innerEx.InnerException; } if (dbentEx.EntityValidationErrors != null) { messageBuilder.AppendLine("Validation Errors : "); foreach (var valErr in dbentEx.EntityValidationErrors) { messageBuilder.AppendLine(valErr.ValidationErrors.Select(data => data.ErrorMessage).Aggregate((current, next) => current + "; /n" + next)); } } messageBuilder.AppendLine("Received payload was:"); messageBuilder.AppendLine(payloadString); FLogger.Global.WriteException(messageBuilder.ToString()); newCrashResult.Message = messageBuilder.ToString(); newCrashResult.bSuccess = false; } catch (SqlException sqlExc) { if (sqlExc.Number == -2)//If this is an sql timeout log the timeout and try again. { FLogger.Global.WriteEvent( string.Format( "AddCrash: Timeout" ) ); } else { var messageBuilder = new StringBuilder(); messageBuilder.AppendLine("Exception was:"); messageBuilder.AppendLine(sqlExc.ToString()); messageBuilder.AppendLine("Received payload was:"); messageBuilder.AppendLine(payloadString); FLogger.Global.WriteException(messageBuilder.ToString()); newCrashResult.Message = messageBuilder.ToString(); newCrashResult.bSuccess = false; } } catch (Exception ex) { var messageBuilder = new StringBuilder(); messageBuilder.AppendLine("Exception was:"); messageBuilder.AppendLine(ex.ToString()); messageBuilder.AppendLine("Received payload was:"); messageBuilder.AppendLine(payloadString); FLogger.Global.WriteException(messageBuilder.ToString()); newCrashResult.Message = messageBuilder.ToString(); newCrashResult.bSuccess = false; } string returnResult = XmlHandler.ToXmlString<CrashReporterResult>( newCrashResult ); return Content( returnResult, "text/xml" ); }