예제 #1
0
        /// <summary>
        /// Gets or creates a new stream for the dictionary.
        /// </summary>
        /// <param name="streamId">The stream id.</param>
        /// <returns>The current stream.</returns>
        internal ContextStream GetStream(int streamId)
        {
            ContextStream stream;

            if (!_contextStreamDictionary.TryGetValue(streamId, out stream))
            {
                // Get the max stream concurrent streams
                SettingsPair max = _settings[2];

                // If more streams can be created.
                if (_contextStreamDictionary.Count < max.Value)
                {
                    // Try to create the stream.
                    stream        = new ContextStream(streamId, this);
                    stream.Opened = true;

                    // Add the stream to the connection.
                    _contextStreamDictionary.Add(streamId, stream);
                    return(stream);
                }
                else
                {
                    return(null);
                }
            }
            return(stream);
        }
예제 #2
0
        /// <summary>
        /// Validate all headers.
        /// </summary>
        /// <param name="stream">The current stream.</param>
        internal void ValidateHeaders(ContextStream stream)
        {
            /* 12 -> 8.1.3
             * Header field names MUST be converted to lowercase prior to their
             * encoding in HTTP/2.0. A request or response containing uppercase
             * header field names MUST be treated as malformed. */
            foreach (NameValue header in stream.HttpRequest.OriginalHeaders)
            {
                string key = header.Name;
                if (!_matcher.IsMatch(key) || key == ":")
                {
                    // Clear all headers found from the list.
                    stream.HttpRequest.HeadersFound = false;
                    stream.HttpRequest.OriginalHeaders.Clear();

                    // Write to the client indicating that the
                    // headers are incorrect.
                    Utility.ProcessCloseRstStreamFrame(this, ErrorCodeRegistry.Refused_Stream, stream.StreamId);
                    Utility.ProcessClose(this, ErrorCodeRegistry.Refused_Stream, stream);
                    break;
                }
            }
        }
예제 #3
0
        /// <summary>
        /// Process a priority frame request.
        /// </summary>
        /// <param name="httpContext">The current http context.</param>
        /// <param name="priorityFrame">The priority frame.</param>
        /// <param name="stream">The selected stream context.</param>
        private static void ProcessPriorityFrameRequest(Nequeo.Net.Http2.HttpContext httpContext, PriorityFrame priorityFrame, out ContextStream stream)
        {
            /*  The PRIORITY frame is associated with an existing stream. If a
             *  PRIORITY frame is received with a stream identifier of 0x0, the
             *  recipient MUST respond with a connection error of type PROTOCOL_ERROR. */
            if (priorityFrame.StreamId == 0)
            {
                throw new ProtocolError(ErrorCodeRegistry.Protocol_Error, "Incoming priority frame with stream id equal to 0.");
            }

            // Attempt to get the sepcific stream.
            stream = httpContext.GetStream(priorityFrame.StreamId);
            if (stream == null)
            {
                throw new MaxConcurrentStreamsLimitException();
            }

            /*  The PRIORITY frame can be sent on a stream in any of the "reserved
             *  (remote)", "open", "half-closed (local)", or "half closed (remote)"
             *  states, though it cannot be sent between consecutive frames that
             *  comprise a single header block. */

            if (stream.Closed)
            {
                throw new StreamNotFoundException(priorityFrame.StreamId);
            }

            if (!(stream.Opened || stream.ReservedRemote || stream.HalfClosedLocal))
            {
                throw new ProtocolError(ErrorCodeRegistry.Protocol_Error, "Priority for non opened or reserved stream.");
            }

            // If using priorities.
            if (httpContext.UsePriorities)
            {
                // Set the priority weight.
                stream.Priority = priorityFrame.Weight;
            }
        }
예제 #4
0
        /// <summary>
        /// Process a continuation frame request.
        /// </summary>
        /// <param name="httpContext">The current http context.</param>
        /// <param name="continuationFrame">The continuation frame.</param>
        /// <param name="stream">The selected stream context.</param>
        /// <param name="canPassContext">Can the data be sent to the client context.</param>
        private static void ProcessContinuationFrameRequest(Nequeo.Net.Http2.HttpContext httpContext,
                                                            ContinuationFrame continuationFrame, out ContextStream stream, out bool canPassContext)
        {
            // The data can be sent to the client
            // through the http context session.
            canPassContext = false;

            /*  CONTINUATION frames MUST be associated with a stream. If a CONTINUATION
             *  frame is received whose stream identifier field is 0x0, the recipient MUST
             *  respond with a connection error of type PROTOCOL_ERROR. */
            if (continuationFrame.StreamId == 0)
            {
                throw new ProtocolError(ErrorCodeRegistry.Protocol_Error, "Incoming continuation frame with stream id is equal to 0.");
            }

            // Attempt to get the sepcific stream.
            stream = httpContext.GetStream(continuationFrame.StreamId);
            if (stream == null)
            {
                throw new MaxConcurrentStreamsLimitException();
            }

            // Get the number of compressed headers.
            var serializedHeaders = new byte[continuationFrame.CompressedHeaders.Count];

            // Copy the compressed frame.
            Buffer.BlockCopy(continuationFrame.CompressedHeaders.Array, continuationFrame.CompressedHeaders.Offset, serializedHeaders, 0, serializedHeaders.Length);

            // Decompress the compressed headers.
            HeadersList decompressedHeaders = new HeaderCompression().Decompress(serializedHeaders);
            HeadersList headers             = new HeadersList(decompressedHeaders);

            // Add the list of headers.
            foreach (KeyValuePair <string, string> header in headers)
            {
                stream.HttpRequest.OriginalHeaders.Add(new NameValue()
                {
                    Name = header.Key, Value = header.Value
                });
            }

            // Determine if all headers have been found.
            if (continuationFrame.IsEndHeaders)
            {
                // The end of the headers has been found.
                stream.HttpRequest.HeadersFound = true;
            }

            bool wasValidated = false;

            // Check the current stream state.
            if (stream.Idle || stream.ReservedRemote)
            {
                // Validate all the headers.
                httpContext.ValidateHeaders(stream);
                wasValidated = true;
            }
            else if (stream.Opened || stream.HalfClosedLocal)
            {
                // Validate all the headers.
                httpContext.ValidateHeaders(stream);
                wasValidated = true;
            }
            else if (stream.HalfClosedRemote)
            {
                // Half closed remote stream.
                throw new ProtocolError(ErrorCodeRegistry.Protocol_Error, "Continuation for half closed remote stream.");
            }
            else
            {
                // Stream has no state error.
                throw new StreamNotFoundException(continuationFrame.StreamId);
            }

            // If the headers where validated.
            if (wasValidated)
            {
                // If headers have been found.
                if (stream.HttpRequest.HeadersFound)
                {
                    // Set the current stream id
                    httpContext.StreamId           = stream.StreamId;
                    stream.HttpRequest.IsEndOfData = continuationFrame.IsEndStream;

                    // Get the request resources.
                    RequestResource resource = Nequeo.Net.Http2.Utility.GetRequestResource(stream.HttpRequest.OriginalHeaders);

                    // Assign the http request content.
                    stream.HttpRequest.ReadRequestHeaders(stream.HttpRequest.OriginalHeaders, resource);

                    // If this is the last bit of data
                    // that has been sent then sent
                    // the data to the client context.
                    if (continuationFrame.IsEndStream)
                    {
                        canPassContext = true;
                    }
                }
            }
        }
예제 #5
0
        /// <summary>
        /// Process a data frame request.
        /// </summary>
        /// <param name="httpContext">The current http context.</param>
        /// <param name="dataFrame">The data frame.</param>
        /// <param name="stream">The selected stream context.</param>
        /// <param name="canPassContext">Can the data be sent to the client context.</param>
        private static void ProcessDataFrameRequest(Nequeo.Net.Http2.HttpContext httpContext, DataFrame dataFrame, out ContextStream stream, out bool canPassContext)
        {
            // The data can be sent to the client
            // through the http context session.
            canPassContext = false;

            /*  DATA frames MUST be associated with a stream. If a DATA frame is
             *  received whose stream identifier field is 0x0, the recipient MUST
             *  respond with a connection error of type PROTOCOL_ERROR. */
            if (dataFrame.StreamId == 0)
            {
                throw new ProtocolError(ErrorCodeRegistry.Protocol_Error, "Incoming continuation frame with stream id is equal to 0.");
            }

            // Attempt to get the sepcific stream.
            stream = httpContext.GetStream(dataFrame.StreamId);
            if (stream == null)
            {
                throw new MaxConcurrentStreamsLimitException();
            }

            // Set the current stream id.
            httpContext.StreamId = stream.StreamId;

            // Is the data compressed.
            stream.HttpRequest.IsCompressed = dataFrame.IsCompressed;
            stream.HttpRequest.IsEndOfData  = dataFrame.IsEndStream;

            // Write the data to the buffer.
            stream.HttpRequest.Input.Write(dataFrame.Data.ToArray(), 0, dataFrame.Data.Count);
            canPassContext = true;
        }
예제 #6
0
 /// <summary>
 /// Process a window update frame request.
 /// </summary>
 /// <param name="httpContext">The current http context.</param>
 /// <param name="windowUpdateFrame">The window update frame.</param>
 /// <param name="stream">The selected stream context.</param>
 private static void ProcessWindowUpdateFrameRequest(Nequeo.Net.Http2.HttpContext httpContext, WindowUpdateFrame windowUpdateFrame, out ContextStream stream)
 {
     // Attempt to get the sepcific stream.
     stream = httpContext.GetStream(windowUpdateFrame.StreamId);
     if (stream == null)
     {
         throw new MaxConcurrentStreamsLimitException();
     }
 }
예제 #7
0
        /// <summary>
        /// Process close connection frame.
        /// </summary>
        /// <param name="httpContext">The current http context.</param>
        /// <param name="errorCode">Error code registry.</param>
        /// <param name="stream">The current stream.</param>
        internal static void ProcessClose(Nequeo.Net.Http2.HttpContext httpContext, ErrorCodeRegistry errorCode, ContextStream stream)
        {
            // If the stream has been cancelled of an internal error.
            if (errorCode == ErrorCodeRegistry.Cancel || errorCode == ErrorCodeRegistry.Internal_Error)
            {
                ProcessCloseRstStreamFrame(httpContext, errorCode, stream.StreamId);
            }

            // Close flow control manager.
            stream.FlowControlManager.StreamClosedHandler(stream);

            // Indicate the stream is closed.
            stream.Closed = true;
            stream.Opened = false;

            // Remove the stream from this list.
            httpContext.RemoveStream(stream.StreamId);
        }
예제 #8
0
        /// <summary>
        /// Process frame requests from the input stream within the current http context.
        /// </summary>
        /// <param name="httpContext">The current http context.</param>
        /// <param name="canPassContext">True if the current frame processed is a data frame and can be send to the client context.</param>
        /// <param name="timeout">The maximum time in milliseconds to wait for the end of the header data; -1 wait Indefinitely.</param>
        /// <param name="maxReadLength">The maximun number of bytes to read before cancelling (must be greater then zero).</param>
        /// <param name="requestBufferStore">The request buffer store stream.</param>
        /// <exception cref="System.Exception"></exception>
        /// <returns>True if the processing of frames was successful; else false.</returns>
        public static bool ProcessFrameRequest(Nequeo.Net.Http2.HttpContext httpContext, out bool canPassContext,
                                               long timeout = -1, int maxReadLength = 0, System.IO.Stream requestBufferStore = null)
        {
            bool ret = true;

            canPassContext = false;
            ContextStream stream = null;

            // Http context is null.
            if (httpContext == null)
            {
                return(false);
            }

            // Http request context stream is null.
            if (httpContext.Input == null)
            {
                return(false);
            }

            // Read the current frame.
            FrameReader frameReader = null;

            // If not using the buffer store.
            if (requestBufferStore == null)
            {
                // We need to wait until we get all the header
                // data then send the context to the server.
                frameReader = new FrameReader(httpContext.Input);
            }
            else
            {
                // We need to wait until we get all the header
                // data then send the context to the server.
                frameReader = new FrameReader(httpContext.RequestBufferStore);
            }

            try
            {
                // Attempt to read the current frame.
                Frame http2Frame = frameReader.ReadFrame(timeout);

                // If ma frame payload size.
                if (http2Frame.PayloadLength > Constants.MaxFramePayloadSize)
                {
                    // Throw exception.
                    throw new ProtocolError(ErrorCodeRegistry.Frame_Size_Error,
                                            String.Format("Frame too large: Type: {0} {1}", http2Frame.FrameType, http2Frame.PayloadLength));
                }

                // Select the frame type.
                switch (http2Frame.FrameType)
                {
                case OpCodeFrame.Continuation:
                    ProcessContinuationFrameRequest(httpContext, http2Frame as ContinuationFrame, out stream, out canPassContext);
                    break;

                case OpCodeFrame.Data:
                    ProcessDataFrameRequest(httpContext, http2Frame as DataFrame, out stream, out canPassContext);
                    break;

                case OpCodeFrame.Go_Away:
                    ProcessGoAwayFrameRequest(httpContext, http2Frame as GoAwayFrame);
                    break;

                case OpCodeFrame.Headers:
                    ProcessHeaderFrameRequest(httpContext, http2Frame as HeadersFrame, out stream, out canPassContext);
                    break;

                case OpCodeFrame.Ping:
                    ProcessPingFrameRequest(httpContext, http2Frame as PingFrame);
                    break;

                case OpCodeFrame.Priority:
                    ProcessPriorityFrameRequest(httpContext, http2Frame as PriorityFrame, out stream);
                    break;

                case OpCodeFrame.Push_Promise:
                    ProcessPushPromiseFrameRequest(httpContext, http2Frame as PushPromiseFrame, out stream);
                    break;

                case OpCodeFrame.Reset_Stream:
                    ProcessResetStreamFrameRequest(httpContext, http2Frame as RstStreamFrame, out stream);
                    break;

                case OpCodeFrame.Settings:
                    ProcessSettingFrameRequest(httpContext, http2Frame as SettingsFrame);
                    break;

                case OpCodeFrame.Window_Update:
                    ProcessWindowUpdateFrameRequest(httpContext, http2Frame as WindowUpdateFrame, out stream);
                    break;

                default:
                    /*  Implementations MUST treat the receipt of an unknown frame type
                     *  (any frame types not defined in this document) as a connection
                     *  error of type PROTOCOL_ERROR. */
                    throw new ProtocolError(ErrorCodeRegistry.Protocol_Error, "Unknown frame type detected.");
                }

                // If the frame is type end stream frame and end of stream is true.
                if (http2Frame is IEndStreamFrame && ((IEndStreamFrame)http2Frame).IsEndStream)
                {
                    if (stream != null)
                    {
                        // Half closed remote state.
                        stream.HalfClosedRemote = true;
                    }

                    // Promised resource has been pushed.
                    if (httpContext.PromisedResources.ContainsKey(stream.StreamId))
                    {
                        httpContext.PromisedResources.Remove(stream.StreamId);
                    }
                }

                if (stream != null)
                {
                    // Increment the frame received count.
                    stream.FramesReceived++;
                }

                // Set the return value to true
                // att this point no errors have
                // been head.
                ret = true;
            }
            catch (StreamNotFoundException snfex)
            {
                /*  5.1.  Stream States
                 *  An endpoint MUST NOT send frames on a closed stream.  An endpoint
                 *  that receives a frame after receiving a RST_STREAM [RST_STREAM] or
                 *  a frame containing a END_STREAM flag on that stream MUST treat
                 *  that as a stream error (Section 5.4.2) of type STREAM_CLOSED [STREAM_CLOSED].*/
                ProcessStreamNotFoundFrame(httpContext, snfex.StreamId);
                if (stream != null)
                {
                    stream.WasResetSent = true;
                }
                ret = true;
            }
            catch (ProtocolError pex)
            {
                // Close the connection.
                ProcessCloseFrame(httpContext, pex.Code);
                ret = true;
            }
            catch (MaxConcurrentStreamsLimitException)
            {
                // Close the connection.
                ProcessCloseFrame(httpContext, ErrorCodeRegistry.No_Error);
                ret = true;
            }
            catch (Exception)
            {
                // Close the connection.
                ProcessCloseFrame(httpContext, ErrorCodeRegistry.Internal_Error);
                ret = false;
            }

            // Return the result.
            return(ret);
        }
예제 #9
0
        /// <summary>
        /// Process a reset stream frame request.
        /// </summary>
        /// <param name="httpContext">The current http context.</param>
        /// <param name="resetFrame">The reset stream frame.</param>
        /// <param name="stream">The selected stream context.</param>
        private static void ProcessResetStreamFrameRequest(Nequeo.Net.Http2.HttpContext httpContext, RstStreamFrame resetFrame, out ContextStream stream)
        {
            /*  RST_STREAM frames MUST be associated with a stream.  If a RST_STREAM
             *  frame is received with a stream identifier of 0x0, the recipient MUST
             *  treat this as a connection error of type PROTOCOL_ERROR. */
            if (resetFrame.StreamId == 0)
            {
                throw new ProtocolError(ErrorCodeRegistry.Protocol_Error, "Reset stream frame with stream id is equal to 0.");
            }

            // Attempt to get the sepcific stream.
            stream = httpContext.GetStream(resetFrame.StreamId);
            if (stream == null)
            {
                throw new MaxConcurrentStreamsLimitException();
            }

            // If the stream is closed.
            if (stream.Closed)
            {
                /* 12 -> 5.4.2
                 * An endpoint MUST NOT send a RST_STREAM in response to an RST_STREAM
                 * frame, to avoid looping. */
                if (!stream.WasResetSent)
                {
                    throw new StreamNotFoundException(resetFrame.StreamId);
                }
                return;
            }

            if (!(stream.ReservedRemote || stream.Opened || stream.HalfClosedLocal))
            {
                throw new ProtocolError(ErrorCodeRegistry.Protocol_Error, "Reset stream for non opened or reserved stream.");
            }

            // Close the current stream.
            ProcessClose(httpContext, ErrorCodeRegistry.No_Error, stream);
        }