private void SendGZipResponse(HeyHttpResponse response) { using (MemoryStream compressedStream = new MemoryStream()) { using (GZipStream gzipStream = new GZipStream(compressedStream, CompressionMode.Compress)) { response.ContentStream.CopyTo(gzipStream); // Not sure why, but gzip stream must be close before getting the data. gzipStream.Close(); byte[] bytes = compressedStream.ToArray(); // Send response headers. response.Headers.Add("Content-Encoding: gzip"); response.Headers.Add("Content-Length: " + bytes.Length); response.Headers.Add("Content-Type: " + GetMediaType(request.Path)); response.CopyHeadersTo(networkStream); //compressedStream.CopyTo(networkStream); //networkStream.Flush(); // For debugging. networkStream.Write(bytes, 0, bytes.Length); logger.WriteBodyLine(BitConverter.ToString(bytes)); } } }
private void ForkSendResponse(HeyHttpResponse response) { // Fork gzip response. if (request.QueryStringHasTrueValue("gzip")) { SendGZipResponse(response); return; } // Fork slow response. int delayInMilliseconds; if (request.QueryStringHas("slow", out delayInMilliseconds)) { SendSlowResponse(response, delayInMilliseconds, HttpResponseOption.Slow); return; } // Fork slow response with ReadLine pauses. if (request.QueryStringHasTrueValue("pause")) { SendSlowResponse(response, 0, HttpResponseOption.Pause); return; } // Fork chunked response. if (request.QueryStringHasTrueValue("chunked")) { SendChunkedResponse(response); return; } // Send normal response. SendSlowResponse(response, 0, HttpResponseOption.None); }
private static void Reply407AndClose(HeyLogger logger, NetworkStream clientStream) { HeyHttpResponse response = new HeyHttpResponse(logger); response.Version = "HTTP/1.0"; response.Status = "407 Proxy Authentication Required"; response.Headers.Add("Proxy-agent: Netscape-Proxy/1.1"); response.Headers.Add("Proxy-Authenticate: Basic realm=\"WallyWorld\""); response.Headers.Add("Content-Length: 0"); response.Headers.Add("Connection: close"); response.CopyHeadersTo(clientStream); clientStream.Close(); }
private void AcceptCore() { requestsCount = 0; keepAlive = true; while (keepAlive) { using (logFile = new HttpLogFile(clientSocket.RemoteEndPoint, requestsCount + 1)) { ReadRequestHeaders(); ReadRequestContent(); keepAlive = request.IsKeepAlive; HeyHttpResponse response = new HeyHttpResponse(logger) { Status = "200 OK", IsKeepAlive = request.IsKeepAlive }; SendResponse(response); requestsCount++; } } }
private static void OnAccept(IAsyncResult asyncResult) { HeyLogger logger = new HeyLogger(); Socket socketListener = asyncResult.AsyncState as Socket; Socket client = socketListener.EndAccept(asyncResult); socketListener.BeginAccept(OnAccept, asyncResult.AsyncState); try { HeyHttpRequest request = new HeyHttpRequest(logger); // Client got connected. logger.WriteLine(String.Format("Connected: {0}", client.RemoteEndPoint)); // Read HTTP headers. NetworkStream clientStream = new NetworkStream(client); MemoryStream tempStream = new MemoryStream(); request.ReadHeaders(clientStream, tempStream); // Authentication. if (settings.AuthenticationRequired && !IsAuthenticated(request)) { Reply407AndClose(logger, clientStream); return; } // Find server host name. logger.WriteLine(String.Format("Trying to connect to {0}", request.Host)); // Get IP address for the given server hostname. IPHostEntry hostEntry = Dns.GetHostEntry(request.Host); if (hostEntry.AddressList.Length == 0) { throw new Exception("Unknow server hostname."); } IPAddress address = hostEntry.AddressList[0]; // Connect to server. Socket server = new Socket(address.AddressFamily, SocketType.Stream, ProtocolType.Tcp); server.Connect(new IPEndPoint(address, request.Port)); logger.WriteLine(String.Format("Connected to {0}", request.Host)); // Send cached data to server. NetworkStream serverStream = new NetworkStream(server); // When using the CONNECT method, proxy must send a HTTP response to client. // See "Tunneling TCP based protocols through Web proxy servers" internet-draft. if (request.Method == "CONNECT") { HeyHttpResponse response = new HeyHttpResponse(logger); response.Status = "200 Connection established"; response.Headers.Add("Proxy-agent: Happy-Sockets-Proxy/1.0"); response.CopyHeadersTo(clientStream); } else { // Only forward headers when it is not a CONNECT request. tempStream.Seek(0, SeekOrigin.Begin); tempStream.CopyTo(serverStream); } // Forward data. ParameterizedThreadStart serverToClientStart = new ParameterizedThreadStart(ForwardData); Thread serverToClientThread = new Thread(serverToClientStart); serverToClientThread.Start( new StreamsPair { StreamA = serverStream, StreamB = clientStream, Label = "server to client", Logger = logger }); ParameterizedThreadStart clientToServerStart = new ParameterizedThreadStart(ForwardData); Thread clientToServerThread = new Thread(clientToServerStart); clientToServerThread.Start( new StreamsPair { StreamA = clientStream, StreamB = serverStream, Label = "client to server", Logger = logger }); //serverToClientThread.Join(); //clientToServerThread.Join(); // TODO: make sure streams do not go out of scope. // TODO: wait until threads end and ensure connections are close. } catch (SocketException ex) { WriteLine(ConsoleColor.Red, ex.Message); logger.WriteLine(ex.ToString()); // We couldn't connect to the server, terminate connection with the client. client.Close(); } catch (IOException ex) { WriteLine(ConsoleColor.Red, ex.Message); logger.WriteLine(ex.ToString()); // The client closed the connection, terminate the connection with the server. client.Close(); } catch (Exception ex) { WriteLine(ConsoleColor.Red, ex.Message); logger.WriteLine(ex.ToString()); } }
private void SendChunkedResponse(HeyHttpResponse response) { Stream stream = response.ContentStream; // Send headers. response.Headers.Add("Transfer-Encoding: chunked"); response.Headers.Add("Trailer: \"Foo\""); response.Headers.Add("Content-Type: " + GetMediaType(request.Path)); response.CopyHeadersTo(networkStream); // Send a random amount of bytes. Random random = new Random(); int bytesRead = 0; while (bytesRead < stream.Length) { byte[] buffer; int chunkSize = random.Next(1, MaxChunckSize); if (stream.Length - bytesRead < chunkSize) { chunkSize = (int)stream.Length - bytesRead; } // Send chunck size and CRLF. string chunkSizeString = String.Format("{0:X}\r\n", chunkSize); buffer = Encoding.ASCII.GetBytes(chunkSizeString); networkStream.Write(buffer, 0, buffer.Length); // Send chunck. buffer = new byte[chunkSize]; int localBytesRead = stream.Read(buffer, 0, buffer.Length); networkStream.Write(buffer, 0, localBytesRead); // Send CRLF. buffer = Encoding.ASCII.GetBytes("\r\n"); networkStream.Write(buffer, 0, buffer.Length); networkStream.Flush(); bytesRead += localBytesRead; logger.WriteBodyLine( String.Format("Chunk of {0,13:N0} bytes, {1,13:N0} of {2,13:N0} bytes sent.", chunkSize, bytesRead, stream.Length)); Thread.Sleep(100); } StringBuilder theEnd = new StringBuilder(); // Add last chunk. theEnd.Append("0\r\n"); // Add trailer. theEnd.Append("Foo: Bar\r\n"); // Add last CRLF. theEnd.Append("\r\n"); byte[] lastChunkBuffer = Encoding.ASCII.GetBytes(theEnd.ToString()); networkStream.Write(lastChunkBuffer, 0, lastChunkBuffer.Length); }
private void SendSlowResponse( HeyHttpResponse response, int delayInMilliseconds, HttpResponseOption option) { Stream stream = response.ContentStream; long idleLength; if (!request.QueryStringHas("idleLength", out idleLength)) { idleLength = -1; } // Calculate positions. long totalBytesRead = 0; long firstPosition = 0; if (response.FirstPosition > 0) { firstPosition = response.FirstPosition; stream.Position = firstPosition; } long length = response.ContentStream.Length; if (response.LastPosition > 0) { length = response.LastPosition - firstPosition + 1; } // Send headers. response.Headers.Add("Content-Length: " + length); response.Headers.Add("Content-Type: " + GetMediaType(request.Path)); response.CopyHeadersTo(networkStream); // Server must not send a message body when using HEAD method (RFC 7231 4.3.2 HEAD). if (request.IsHeadMethod) { return; } // Calulate buffer size. int bufferLength; if (!request.QueryStringHas("bufferLength", out bufferLength)) { bufferLength = 1000000; // 1 MB } byte[] buffer = new byte[bufferLength]; while (totalBytesRead < length) { long remainingBytes = length - totalBytesRead; int loopLength = (int)Math.Min(bufferLength, remainingBytes); int localBytesRead = stream.Read(buffer, 0, loopLength); if (option == HttpResponseOption.Pause) { BlockThreadUntilEnterIsPressed(); } if (option == HttpResponseOption.Slow) { Thread.Sleep(delayInMilliseconds); } networkStream.Write(buffer, 0, localBytesRead); networkStream.Flush(); totalBytesRead += localBytesRead; logger.WriteBodyLine(String.Format( "{0,13:N0} of {1,13:N0} bytes sent.", totalBytesRead, length)); if (idleLength >= 0 && totalBytesRead >= idleLength) { BlockThreadUntilClientIsDisconnected(); } } logger.WriteBodyLine("Response completed!\r\n"); }
private void SendResponse(HeyHttpResponse response) { try { // Set default content. response.ContentStream = CreateDefaultResponseStream(); // Do authentication. HeyHttpAuthentication.AddAuthenticateHeaderIfNeeded(request, response); // Custom headers. if (request.QueryStringHasTrueValue("custom")) { response.Headers.Add("X-Header: Value1"); response.Headers.Add("X-Header: Value2"); response.Headers.Add("X-Header: Value3"); } // Show the 'Save As' dialog. string filename; if (request.QueryStringHas("filename", out filename)) { response.Headers.Add(String.Format("Content-Disposition: Attachment; filename={0}", filename)); } // 201 Created for AtomPub. if (request.QueryStringHasTrueValue("create")) { response.Status = "201 Created"; response.Headers.Add("Location: http://heyhttp.org/Data/first-post.atom"); response.Headers.Add("Content-Location: http://heyhttp.org/Data/first-post.atom"); response.Headers.Add("ETag: \"e180ee84f0671b1\""); } // 301 Moved Permanently. string redirect; if (request.QueryStringHas("redirect", out redirect)) { response.Status = "301 Moved Permanently"; response.Headers.Add("Location: " + redirect); // Do not send any data back. request.Path = ""; } // 503 Service Unavailable, retry after N seconds. if (request.QueryStringHasTrueValue("retry")) { if (!retryReceivedBefore) { response.Status = "503 Service Unavailable"; response.Headers.Add("Retry-After: 5"); response.ContentStream = GetStringStream(String.Empty); } retryReceivedBefore = !retryReceivedBefore; } // Set cache headers. if (request.QueryStringHasTrueValue("cache")) { response.Headers.Add("Cache-Control: max-age=3600"); // One hour (60 minutes * 60 seconds) //additionalHeaders.Add("Expires: " + DateTime.UtcNow.AddHours(24).ToString("R")); } else if (request.QueryStringHasTrueValue("nocache")) { response.Headers.Add("Cache-Control: no-cache"); // HTTP 1.1 } // Set cookie header. if (request.QueryStringHasTrueValue("setcookie")) { response.Headers.Add("Set-Cookie: sessionTestCookie=X"); response.Headers.Add("Set-Cookie: persistentTestCookie=Y; expires=Wednesday, 09-Nov-2020 23:12:40 GMT"); response.Headers.Add("Set-Cookie: httpOnlyTestCookie=X; expires=Wednesday, 09-Nov-2020 23:12:40 GMT; HttpOnly"); response.Headers.Add("Set-Cookie: subdomainTestCookie=ghi; expires=Wednesday, 09-Nov-2020 23:12:40 GMT; domain=foo.heyhttp.org"); response.Headers.Add("Set-Cookie: slashEndingCookie=b; expires=Wednesday, 09-Nov-2020 23:12:40 GMT; domain=heyhttp.org; path=/foo/"); response.Headers.Add("Set-Cookie: nonSlashEndingCookie=a; expires=Wednesday, 09-Nov-2020 23:12:40 GMT; domain=heyhttp.org; path=/foo"); } // Return an specific HTTP status. int requestedStatus; if (request.QueryStringHas("status", out requestedStatus)) { response.Status = String.Format("{0} Foo Foo Bar", requestedStatus); } // Set arbitrary header. string headerName; string headerValue; if (request.QueryStringHas("name", out headerName) && request.QueryStringHas("value", out headerValue)) { response.Headers.Add(String.Format("{0}: {1}", headerName, headerValue)); } // Introduce a long delay. int delay; if (request.QueryStringHas("delay", out delay)) { Thread.Sleep(delay); } // Get trace content stream. if (request.QueryStringHasTrueValue("trace")) { response.ContentStream = GetTraceStream(); // Do not send any data back. request.Path = ""; } // Get file. if (!String.IsNullOrEmpty(request.Path) && !String.IsNullOrEmpty(response.Status) && request.Path != "/" && !response.Status.StartsWith("404")) { try { response.ContentStream = GetFileStream(); } catch (FileNotFoundException ex) { response.Status = "404 Not Found"; response.ContentStream = GetStringStream(ex.Message); } catch (DirectoryNotFoundException ex) { response.Status = "404 Not Found"; response.ContentStream = GetStringStream(ex.Message); } } // Create a response of the length given. long sizeInBytes; if (request.QueryStringHas("length", out sizeInBytes)) { response.ContentStream = CreateBigStream(sizeInBytes); } // This check must be done after the response coontent has been selected. string eTag; bool useETag = request.QueryStringHas("etag", out eTag); if (useETag) { //// 304 Not Modified with ETag. //if (request.GetHeader("If-None-Match") == eTag) //{ // response.Status = "304 Not Modified"; // response.ContentStream = GetStringStream(String.Empty); //} //else //{ //} // This 'if' is to manually change the flow when debugging. if (DateTime.Now.Second == -1) { eTag = String.Empty; response.Status = "404 NOT FOUND"; response.ContentStream = GetStringStream("Oops!"); } else { response.Headers.Add(String.Format("ETag: \"{0}\"", eTag)); response.Headers.Add("Accept-Ranges: bytes"); } } bool useLastModified = request.QueryStringHasTrueValue("lastModified"); if (useLastModified) { response.Headers.Add("Last-Modified: Thu, 21 Aug 2014 21:34:57 GMT"); response.Headers.Add("Accept-Ranges: bytes"); } HttpRangeHeader rangeHeader = request.GetRange(); if (rangeHeader != null && (useETag || useLastModified)) { response.Status = "206 Partial Content"; response.Headers.Add(rangeHeader.GetContentRange(response.ContentStream.Length)); response.FirstPosition = rangeHeader.FirstPosition; response.LastPosition = rangeHeader.LastPosition; } ForkSendResponse(response); } finally { // 'finally' block is executed even if a 'try' or 'catch' block contains a 'return'. if (response.ContentStream != null) { response.ContentStream.Dispose(); } } }
public static void AddAuthenticateHeaderIfNeeded(HeyHttpRequest request, HeyHttpResponse response) { // Basic Server: // GET ---------> // // <--------- 401 Unauthorized // WWW-Authenticate: Basic realm="xyz" // // GET ---------> // Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ== // // <--------- 200 OK // Basic Proxy: // // GET ---------> // <-------- 407 Proxy Authentication Required // Proxy-Authenticate: Basic realm="xyz" // // GET ---------> // Proxy-Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ== // // <--------- 200 OK // NTLM: // // GET ---------> // <--------- 401 Unauthorized // negotiate message ---------> // <--------- challenge message // authenticate message ---------> // <--------- 200 OK if (IsNtlmMessage(request, NtlmMessageType.NegotiatieMessage)) { response.Status = "401 Unauthorized"; response.Headers.Add("WWW-Authenticate: NTLM TlRMTVNTUAACAAAADgAOADgAAAAFgomiBTEwAGt4s6QAAAAAAAAAAPwA/ABGAAAABgLwIwAAAA9SAEUARABNAE8ATgBEAAIADgBSAEUARABNAE8ATgBEAAEAHgBXAEkATgAtAEQARgBHADEAMABFADIAOABLADEANgAEADQAcgBlAGQAbQBvAG4AZAAuAGMAbwByAHAALgBtAGkAYwByAG8AcwBvAGYAdAAuAGMAbwBtAAMAVABXAEkATgAtAEQARgBHADEAMABFADIAOABLADEANgAuAHIAZQBkAG0AbwBuAGQALgBjAG8AcgBwAC4AbQBpAGMAcgBvAHMAbwBmAHQALgBjAG8AbQAFACQAYwBvAHIAcAAuAG0AaQBjAHIAbwBzAG8AZgB0AC4AYwBvAG0ABwAIADJX/d1Cjc0BAAAAAA=="); } // Require basic access authentication. if (request.QueryStringHasTrueValue("basic") && !IsAuthorizationValid(request)) { response.Status = "401 Unauthorized"; response.Headers.Add("WWW-Authenticate: Basic realm=\"Secure Area\""); request.Path = ""; } // Require digest access authentication. if (request.QueryStringHasTrueValue("digest") && !IsAuthorizationValid(request)) { response.Status = "401 Unauthorized"; response.Headers.Add(String.Format( "WWW-Authenticate: Digest realm=\"{0}\", qop=\"{1}\", nonce=\"{2}\", opaque=\"{3}\"", digestRealm, digestQop, digestNonce, digestOpaque)); request.Path = ""; } // Require NTLM credentials. if (request.QueryStringHasTrueValue("negotiate") && !IsAuthorizationValid(request)) { response.Status = "401 Unauthorized"; response.Headers.Add("WWW-Authenticate: Negotiate"); request.Path = ""; } // NTLM authentication. if (request.QueryStringHasTrueValue("ntlm") && !IsAuthorizationValid(request)) { response.Status = "401 Unauthorized"; response.Headers.Add("WWW-Authenticate: NTLM"); request.Path = ""; } }