private static async void clientThread(HttpListener listener) { try { // Will wait for the next client request: var context = await listener.GetContextAsync(); // Filter-out any requests which are to huge: if (context.Request.ContentLength64 > NoIISServer.maxRequestSizeBytes) { Console.WriteLine("A request was to huge: {0} bytes.", context.Request.ContentLength64); context.Response.Abort(); return; } // Read the client's address: var clientAddress = context.Request.RemoteEndPoint.Address.ToString(); // Is this address known? if (!NoIISServer.clients.ContainsKey(clientAddress)) { // Unknown: NoIISServer.createAndStoreClientProfile(clientAddress); } // // In the meanwhile the maintenance thread could take action. Scenario: This client is known // but was not visiting for the allowed time span. One moment after the previous check, the // allowed time span expires and the maintenance thread deletes the entry. In such a case, // an exception will be throwed. Is this the case, we add a new client profile. This is // fine and a valid case. // Client clientProfile; try { // Read the client's profile: clientProfile = NoIISServer.clients[clientAddress]; } catch (KeyNotFoundException) { // Valide case. Create a new entry: NoIISServer.createAndStoreClientProfile(clientAddress); // Read it again. It is not possible that the case occurs again: clientProfile = NoIISServer.clients[clientAddress]; } // Is this client blocked? if (clientProfile.Blocked) { Console.WriteLine("A blocked client tried to access: {0}.", clientAddress); context.Response.Abort(); return; } // File this visit. It is intended to do so after the block check. // Otherwise, an attacker could use this fact to flood the memory // with visits: NoIISServer.clientVisits[clientAddress].Enqueue(DateTime.UtcNow); clientProfile.LastVisitUTC = DateTime.UtcNow; // Store the changed time. This could happens parallel with several // threads. Thus, it is not atomic. This behaviour is intended! Only // the roughly time is necessary. NoIISServer.clients[clientAddress] = clientProfile; try { // Create the NoIIS request: var request = new NoIISRequest(context, NoIISServer.tempFolder); // Create the NoIIS response: var response = new NoIISResponse(context); // Create the NoIIS context with request and response: var webContext = new NoIISContext(request, response); // Search for a handler inside all factories which matches the request: var foundHandler = false; foreach (var factory in NoIISServer.factories) { // Does this factory is able to deliver a handler for this request? var handler = factory.GetHandler(webContext, request.RequestType, request.Path, string.Empty); // Case: Yes, we have found the first handler: if (handler != null) { // Let the handler process the request: handler.ProcessRequest(webContext); foundHandler = true; // We only use the first matching handler: break; } } // Case: No handler was found if (!foundHandler) { Console.WriteLine("No handler found for the URL '{0}'.", request.RawUrl); response.StatusCode = 404; } try { response.Dispose(); } catch { } try { request.Dispose(); } catch { } } catch (Exception e) { Console.WriteLine("Exception while processing request: {0}", e.Message); Console.WriteLine(e.StackTrace); try { context.Response.Abort(); } catch { } } } catch (Exception e) { Console.WriteLine("Exception while accepting request: {0}", e.Message); Console.WriteLine(e.StackTrace); } finally { NoIISServer.semaphoreClients.Release(); } }
/// <summary> /// The constructor of this class. You dont have to use this by your own, NoIIS will handle this for you. /// </summary> /// <param name="request">The client's request.</param> /// <param name="response">Your response to the client's request.</param> public NoIISContext(NoIISRequest request, NoIISResponse response) { this.request = request; this.response = response; }
/// <summary> /// Processes the request to get all uploaded files. /// </summary> /// <param name="request">The request.</param> /// <param name="bodyStream">The stream for the request's body.</param> /// <param name="tmpFolderPATH">The temporary folder for this request.</param> /// <param name="niiRequest">The NoIIS request to register additional open streams.</param> /// <returns>Returns the collection with all uploaded files. The collection is possibily empty.</returns> public static HttpFileCollectionBase GetFiles(this HttpListenerRequest request, Stream bodyStream, string tmpFolderPATH, NoIISRequest niiRequest) { if (request == null || tmpFolderPATH == null || bodyStream == null) { return(new NoIISFileCollection()); } if (!request.HasEntityBody) { return(new NoIISFileCollection()); } // Case #1: Just a form // Content Type = application/x-www-form-urlencoded // Body = MAX_FILE_SIZE=100000&t%C3%A4stValue=das+ist+ein+test // // ==> Does not matter // // Case #2: Just a file // Content type = multipart/form-data; boundary=----WebKitFormBoundaryeghC9TqseFEkpCXg // Body = // ------WebKitFormBoundaryeghC9TqseFEkpCXg // Content-Disposition: form-data; name="meinFileö"; filename="Short Time Notes.txt" // Content-Type: text/plain // // DATA STREAM // ------WebKitFormBoundaryeghC9TqseFEkpCXg-- // // ==> Does not matter // // Case #3: Multiple files // Content type = multipart/form-data; boundary=----WebKitFormBoundaryeghC9TqseFEkpCXg // Body = // ------WebKitFormBoundaryeghC9TqseFEkpCXg // Content-Disposition: form-data; name="meinFileö"; filename="Short Time Notes.txt" // Content-Type: text/plain // // DATA STREAM // ------WebKitFormBoundaryeghC9TqseFEkpCXg // Content-Disposition: form-data; name="meinFile2"; filename="fsdfsdf.txt" // Content-Type: text/plain // // DATA STREAM // ------WebKitFormBoundaryeghC9TqseFEkpCXg-- // // ==> Does not matter // // Case #4: A file and form data // Content type = multipart/form-data; boundary=----WebKitFormBoundaryeghC9TqseFEkpCXg // Body = // ------WebKitFormBoundaryeghC9TqseFEkpCXg // Content-Disposition: form-data; name="meinFileö"; filename="Short Time Notes.txt" // Content-Type: text/plain // // DATA STREAM // ------WebKitFormBoundaryeghC9TqseFEkpCXg // Content-Disposition: form-data; name="einsÖÄÜ" // // 2354öäpü // ------WebKitFormBoundaryeghC9TqseFEkpCXg // Content-Disposition: form-data; name="zwei" // // [email protected] // ------WebKitFormBoundaryeghC9TqseFEkpCXg-- // // ==> Does not matter, use case #5. // // Case #5: Multiple files and form data // Content type = multipart/form-data; boundary=----WebKitFormBoundaryeghC9TqseFEkpCXg // Body = // ------WebKitFormBoundaryeghC9TqseFEkpCXg // Content-Disposition: form-data; name="meinFileö"; filename="Short Time Notes.txt" // Content-Type: text/plain // // DATA STREAM // ------WebKitFormBoundaryeghC9TqseFEkpCXg // Content-Disposition: form-data; name="einsÖÄÜ" // // 2354öäpü // ------WebKitFormBoundaryeghC9TqseFEkpCXg // Content-Disposition: form-data; name="zwei" // // [email protected] // ------WebKitFormBoundaryeghC9TqseFEkpCXg // Content-Disposition: form-data; name="meinFile3"; filename="Ssdfsdfd.fdf" // Content-Type: text/plain // // DATA STREAM // ------WebKitFormBoundaryeghC9TqseFEkpCXg // Content-Disposition: form-data; name="rterz" // // [email protected] // ------WebKitFormBoundaryeghC9TqseFEkpCXg-- // If the request's content type does not match the requirements, // return here: if (!request.ContentType.Contains("multipart/form-data; boundary=")) { return(new NoIISFileCollection()); } // The cache for all tmp. filenames for files in the request: var cacheFilenames = new ConcurrentDictionary <string, NoIISPostedFile>(); using (bodyStream) { // Dummy to consume the name of each parameter: var dummy = string.Empty; // Create the parser for the body: var smftp = new StreamingMultipartFormDataParser(bodyStream); // The parser requires both, the parameter and the file handler: smftp.ParameterHandler += parameter => dummy = parameter.Name; // The file handler gets called for every chunk of each file inside: smftp.FileHandler += (name, fileName, type, disposition, buffer, bytes) => { // Generate a key for this file: var key = name + fileName + type + disposition; // The destination tmp. file name: var destination = string.Empty; if (!cacheFilenames.ContainsKey(key)) { // Case: A new file. cacheFilenames[key] = new NoIISPostedFile(); // Generate the tmp. file name with path: cacheFilenames[key].TMPFilenamePATH = tmpFolderPATH + Guid.NewGuid().ToString(); cacheFilenames[key].FormName = name; cacheFilenames[key].setContentType(type); cacheFilenames[key].setFileName(fileName); } // Read the current destination: destination = cacheFilenames[key].TMPFilenamePATH; try { // Append the bytes to the tmp. file: using (var fileStream = File.Open(destination, FileMode.Append, FileAccess.Write, FileShare.None)) { fileStream.Write(buffer, 0, bytes); } } catch (Exception e) { Console.WriteLine("Exception while processing the file upload '{1}' (name='{2}'): {0}", e.Message, fileName, name); Console.WriteLine(e.StackTrace); } }; try { // Start the processing. This call will block until end of processing: smftp.Run(); } catch (Exception e) { Console.WriteLine("Exception while processing the file upload: {0}", e.Message); Console.WriteLine(e.StackTrace); } } // // The files should now be available as tmp. data. // // The result's collection: var result = new List <NoIISPostedFile>(cacheFilenames.Count); // Loop over all files: foreach (var key in cacheFilenames.Keys) { // Get a file: var file = cacheFilenames[key]; // Get the tmp. file's name: var filename = file.TMPFilenamePATH; // Get the stream to the file's content: var stream = File.OpenRead(filename); // Register this open stream: niiRequest.AddProbablyOpenStream(stream); // Store the file's length: file.setContentLength((int)stream.Length); // Store the stream: file.setInputStream(stream); // Store this file to the result: result.Add(file); } return(new NoIISFileCollection(result)); }