public void Methods_Deny_Unrestricted()
 {
     writer.Write(Char.MinValue);
     writer.Write(this);
     writer.Write(String.Empty);
     writer.Write(new char [1], 0, 1);
     writer.WriteLine();
     writer.WriteString("mono", 0, 4);
     writer.WriteBytes(new byte[1], 0, 1);
     writer.Flush();
     writer.Close();
 }
Exemplo n.º 2
0
        /// <summary>
        /// Private method that handles an incoming request.
        /// It sets up a RequestHandlerContext instance with the data from
        /// the incoming HTTP request, finds a suitable request handler to
        /// produce the response, and then sends the response as an HTTP
        /// response back to the client.
        /// Preconditions
        ///     "connection is open"
        ///     serviceRoot != null
        ///     serviceRoot.Length > 8
        ///     "serviceRoot starts with 'http://' and ends with '/'"
        ///     requestRouting != null
        /// </summary>
        /// <param name="connection">Open TCP/IP connection</param>
        /// <param name="serviceRoot">The absolute URI that is a prefix of
        /// all request URIs that this web service supports. It must start
        /// with "http://" and must end with "/".</param>
        /// <param name="relayDomain">Host name or Internet address of the
        /// relay to be used, or null if no relay is used</param>
        /// <param name="requestRouting">Collection of
        ///   { request pattern, request handler}
        /// pairs</param>
        /// <param name="connectionClose">Return parameter that indicates
        /// that the connection should be closed after this call. This may
        /// be because the incoming request has a "Connection: close"
        /// header, because the request handler has set the
        /// ConnectionClose property, or because some error occurred.
        /// </param>
        internal static void ConsumeRequest(Stream connection,
            string serviceRoot, string relayDomain,
            RequestRouting requestRouting,
            ref bool connectionClose)
        {
            Contract.Requires(connection != null);
            Contract.Requires(serviceRoot != null);
            Contract.Requires(serviceRoot.Length > 8);
            Contract.Requires(serviceRoot.Substring(0, 7) == "http://");
            Contract.Requires(serviceRoot[serviceRoot.Length - 1] != '/');
            Contract.Requires(requestRouting != null);

            // initialization --------------------------------------------
            HttpReader reader = new HttpReader();
            HttpWriter writer = new HttpWriter();
            var context = new RequestHandlerContext(serviceRoot,
                relayDomain);
            // Both client and server may request closing the connection.
            // Initially, we assume that neither one wants to close the
            // connection.
            context.ConnectionClose = false;

            // receive request -------------------------------------------
            reader.Attach(connection);

            // request line
            string httpMethod;
            string requestUri;
            string httpVersion;

            reader.ReadStringToBlank(out httpMethod);
            reader.ReadStringToBlank(out requestUri);
            reader.ReadFieldValue(out httpVersion);
            if (reader.Status != HttpStatus.BeforeContent)  // error
            {
                reader.Detach();
                connectionClose = true;
                return;
            }

            context.RequestMethod = httpMethod;
            context.RequestUri = requestUri;
            // ignore version

            // headers
            string fieldName;
            string fieldValue;
            int requestContentLength = -1;
            reader.ReadFieldName(out fieldName);
            while (reader.Status == HttpStatus.BeforeContent)
            {
                reader.ReadFieldValue(out fieldValue);
                if (fieldValue != null)
                {
                    Contract.Assert(reader.Status ==
                                    HttpStatus.BeforeContent);
                    if (fieldName == "Connection")
                    {
                        context.ConnectionClose =
                            ((fieldValue == "close") ||
                             (fieldValue == "Close"));
                    }
                    else if (fieldName == "Content-Type")
                    {
                        context.RequestContentType = fieldValue;
                    }
                    else if (fieldName == "Content-Length")
                    {
                        if (Utilities.TryParseUInt32(fieldValue,
                            out requestContentLength))
                        {
                            // content length is now known
                        }
                        else
                        {
                            reader.Status = HttpStatus.SyntaxError;
                            reader.Detach();
                            connectionClose = true;
                            return;
                        }
                    }
                }
                else
                {
                    // it's ok to skip header whose value is too long
                }
                Contract.Assert(reader.Status == HttpStatus.BeforeContent);
                reader.ReadFieldName(out fieldName);
            }
            if (reader.Status != HttpStatus.InContent)
            {
                reader.Detach();
                connectionClose = true;
                return;
            }

            // content
            if (requestContentLength > 0)
            {
                // receive content
                var requestContent = new byte[requestContentLength];
                int toRead = requestContentLength;
                var read = 0;
                while ((toRead > 0) && (read >= 0))
                {
                    // already read: requestContentLength - toRead
                    read = reader.ReadContent(requestContent,
                        requestContentLength - toRead, toRead);
                    if (read < 0) { break; }    // timeout or shutdown
                    toRead = toRead - read;
                }
                try
                {
                    char[] chars = Encoding.UTF8.GetChars(requestContent);
                    context.RequestContent = new string(chars);
                }
                catch (Exception)
                {
                    context.RequestContentType = "text/plain";
                    context.RequestContent = "request content is not in UTF8 format";
                    Debug.Print(context.RequestContent);
                    // TODO signal wrong content format through HTTP status code 400 (Bad Request)?
                }
            }

            reader.Detach();
            if (reader.Status != HttpStatus.InContent)
            {
                connectionClose = true;
                return;
            }

            // delegate request processing to a request handler ----------
            var match = false;
            foreach (Element e in requestRouting)
            {
                if (context.RequestMatch(e))
                {
                    bool connClose = context.ConnectionClose;
                    context.ResponseStatusCode = -1;                                    // undefined
                    Contract.Requires(context.ResponseContentType != null);
                    Contract.Requires(context.ResponseContentType == "text/plain");     // default
                    try
                    {
                        e.Handler(context);
                    }
                    catch (Exception h)
                    {
                        Debug.Print("exception in request handler: " + h);    // TODO how to better handle handler exceptions?
                        throw;                                  // rethrow, to avoid masking of errors
                    }
                    Contract.Ensures(context.ResponseStatusCode >= 100);
                    Contract.Ensures(context.ResponseStatusCode < 600);
                    Contract.Ensures(context.ResponseContentType != null);
                    Contract.Ensures(context.ResponseContentType.Length > 0);
                    // make sure that handler has not reset connectionClose flag:
                    Contract.Ensures((!connClose) || (context.ConnectionClose));
                    match = true;
                    break;
                }
            }
            if (!match)
            {
                context.ResponseStatusCode = 404;    // Not Found
                context.ResponseContentType = "text/plain";
                context.ResponseContent = null;
            }

            uint availableMemory = Debug.GC(true);
            Debug.Print(context.RequestMethod + " " +
                        context.RequestUri + " -> " +
                        context.ResponseStatusCode + " [" + availableMemory + "]");

            // send response ---------------------------------------------
            writer.Attach(connection);

            // status line
            writer.WriteString("HTTP/1.1 ");
            writer.WriteString(context.ResponseStatusCode.ToString());
            writer.WriteLine(" ");  // omit optional reason phrase

            // headers
            if (connectionClose)   // TODO (context.ConnectionClose)
            {
                writer.WriteLine("Connection: close");
            }
            byte[] responseBuffer = null;
            var responseLength = 0;
            if (context.ResponseContent != null)
            {
                responseBuffer =
                    Encoding.UTF8.GetBytes(context.ResponseContent);
                responseLength = responseBuffer.Length;
                writer.WriteString("Content-Type: ");
                writer.WriteLine(context.ResponseContentType);
            }
            else
            {
                responseLength = 0;
            }
            writer.WriteString("Content-Length: ");
            writer.WriteLine(responseLength.ToString());
            if (context.ResponseMaxAge > 0)
            {
                writer.WriteLine("Cache-Control: max-age=" + context.ResponseMaxAge);
            }
            else if (context.ResponseMaxAge == 0)
            {
                writer.WriteLine("Cache-Control: no-cache");
            }

            // content
            writer.WriteBeginOfContent();
            if (context.ResponseContent != null)    // send content
            {
                writer.WriteContent(responseBuffer, 0, responseLength);
            }

            writer.Detach();
            connectionClose = context.ConnectionClose;
        }