예제 #1
0
        private static void ReadHeaderLines(AbstractHttpStream s, IDictionary <string, string> headers)
        {
            headers.Clear();
            string lastHeader = string.Empty;
            string hdrline;

            while ((hdrline = s.ReadHeaderLine()).Length != 0)
            {
                if (hdrline.StartsWith(" "))
                {
                    if (lastHeader.Length != 0)
                    {
                        headers[lastHeader] += hdrline.Trim();
                    }
                }
                else
                {
                    string[] splits = hdrline.Split(new char[] { ':' }, 2);
                    if (splits.Length < 2)
                    {
                        throw new BadHttpResponseException();
                    }
                    lastHeader = splits[0].Trim().ToLowerInvariant();
                    if (headers.ContainsKey(lastHeader))
                    {
                        headers[lastHeader] += "\0" + splits[1].Trim();
                    }
                    else
                    {
                        headers[lastHeader] = splits[1].Trim();
                    }
                }
            }
        }
 public BodyResponseChunkedStream(AbstractHttpStream input, string scheme, string host, int port)
     : base(input)
 {
     m_Scheme = scheme;
     m_Host   = host;
     m_Port   = port;
 }
예제 #3
0
 internal ResponseBodyStream(AbstractHttpStream input, long contentLength, string scheme, string host, int port)
 {
     m_RemainingLength = contentLength;
     m_Input           = input;
     Length            = contentLength;
     m_Scheme          = scheme;
     m_Host            = host;
     m_Port            = port;
 }
예제 #4
0
            public override int Read(byte[] buffer, int offset, int count)
            {
                int rescount = 0;
                int result;

                if (m_RemainingLength == 0 || m_Input == null)
                {
                    return(0);
                }

                while (count > 0 && m_RemainingLength != 0)
                {
                    int remcount = count;
                    if (remcount > m_RemainingLength)
                    {
                        remcount = (int)m_RemainingLength;
                    }

                    result = m_Input.Read(buffer, offset, remcount > 10240 ? 10240 : remcount);

                    if (result > 0)
                    {
                        m_RemainingLength -= result;
                        offset            += result;
                        count             -= result;
                        rescount          += result;
                    }
                }

                if (m_RemainingLength == 0)
                {
                    AddStreamForNextRequest(m_Input, m_Scheme, m_Host, m_Port);
                    m_Input = null;
                }
                return(rescount);
            }
예제 #5
0
        private static Stream GetResponseBodyStream(IDictionary <string, string> headers, Uri uri, string[] splits, string method, AbstractHttpStream s)
        {
            string value;
            string compressedresult = string.Empty;

            if (headers.TryGetValue("content-encoding", out value) || headers.TryGetValue("x-content-encoding", out value))
            {
                /* Content-Encoding */
                /* x-gzip is deprecated but better feel safe about having that */
                if (value == "gzip" || value == "x-gzip")
                {
                    compressedresult = "gzip";
                }
                else if (value == "deflate")
                {
                    compressedresult = "deflate";
                }
                else
                {
                    throw new NotSupportedException("Unsupport content-encoding");
                }
            }

            if (splits[0] == "HTTP/1.0")
            {
                s.IsReusable = false;
            }

            if (headers.TryGetValue("connection", out value))
            {
                value = value.Trim().ToLower();
                if (value == "keep-alive")
                {
                    s.IsReusable = true;
                }
                else if (value == "close")
                {
                    s.IsReusable = false;
                }
            }

            if (method == "HEAD")
            {
                /* HEAD does not have any response data */
                return(new ResponseBodyStream(s, 0, uri.Scheme, uri.Host, uri.Port));
            }
            else if (headers.TryGetValue("content-length", out value))
            {
                long contentLength;
                if (!long.TryParse(value, out contentLength))
                {
                    throw new BadHttpResponseException("Unparseable length in response");
                }
                Stream bs = new ResponseBodyStream(s, contentLength, uri.Scheme, uri.Host, uri.Port);
                if (headers.TryGetValue("transfer-encoding", out value) && value == "chunked")
                {
                    bs = new HttpReadChunkedBodyStream(bs);
                }
                if (compressedresult.Length != 0)
                {
                    if (compressedresult == "gzip")
                    {
                        bs = new GZipStream(bs, CompressionMode.Decompress);
                    }
                    else if (compressedresult == "deflate")
                    {
                        bs = new DeflateStream(bs, CompressionMode.Decompress);
                    }
                }
                return(bs);
            }
            else
            {
                Stream bs = s;
                if (headers.TryGetValue("transfer-encoding", out value) && value == "chunked")
                {
                    bs = new BodyResponseChunkedStream(s, uri.Scheme, uri.Host, uri.Port);
                }
                else
                {
                    s.IsReusable = false;
                }
                if (compressedresult.Length != 0)
                {
                    if (compressedresult == "gzip")
                    {
                        bs = new GZipStream(bs, CompressionMode.Decompress);
                    }
                    else if (compressedresult == "deflate")
                    {
                        bs = new DeflateStream(bs, CompressionMode.Decompress);
                    }
                }
                return(bs);
            }
        }
예제 #6
0
        public static Stream ExecuteStreamRequest(
            this Request request)
        {
            byte[] postdata = request.RequestBody;
            if (postdata != null)
            {
                request.RequestContentLength = 0;

                if (request.IsCompressed || request.RequestContentType == "application/x-gzip")
                {
                    using (var ms = new MemoryStream())
                    {
                        using (var comp = new GZipStream(ms, CompressionMode.Compress))
                        {
                            comp.Write(postdata, 0, postdata.Length);
                            /* The GZIP stream has a CRC-32 and a EOF marker, so we close it first to have it completed */
                        }
                        postdata = ms.ToArray();
                    }
                }

                request.RequestBodyDelegate = (Stream poststream) => poststream.Write(postdata, 0, postdata.Length);

                /* append request POST data */
                request.RequestContentLength = postdata.Length;
            }

            string url = request.Url;

            if (request.GetValues != null)
            {
                url += "?" + BuildQueryString(request.GetValues);
            }

            var uri = new Uri(url);

            if (request.ConnectionMode == ConnectionModeEnum.Http2PriorKnowledge)
            {
                return(DoStreamRequestHttp2(request, uri));
            }

            if (request.ConnectionMode == ConnectionModeEnum.UpgradeHttp2)
            {
                Http2Connection.Http2Stream h2stream = TryReuseStream(uri.Scheme, uri.Host, uri.Port);
                if (h2stream != null)
                {
                    return(DoStreamRequestHttp2(request, uri, h2stream));
                }
            }

            byte[] outdata;

            string method = request.Method;

redoafter401:
            var reqdata = new StringBuilder(uri.IsDefaultPort ?
                                            $"{method} {uri.PathAndQuery} HTTP/1.1\r\nHost: {uri.Host}\r\n" :
                                            $"{method} {uri.PathAndQuery} HTTP/1.1\r\nHost: {uri.Host}:{uri.Port}\r\n");

            bool doPost         = false;
            bool doChunked      = false;
            bool compressed     = request.IsCompressed;
            int  content_length = request.RequestContentLength;

            IDictionary <string, string> headers = request.Headers;
            bool haveAccept = false;

            if (headers != null)
            {
                var removal = new List <string>();
                foreach (string k in headers.Keys)
                {
                    if (string.Compare(k, "content-length", true) == 0 ||
                        string.Compare(k, "content-type", true) == 0 ||
                        string.Compare(k, "connection", true) == 0 ||
                        string.Compare(k, "expect", true) == 0 ||
                        string.Compare(k, "transfer-encoding", true) == 0)
                    {
                        removal.Add(k);
                    }
                    if (string.Compare(k, "accept", true) == 0)
                    {
                        haveAccept = true;
                    }
                }
                if (removal.Count != 0)
                {
                    foreach (string k in removal)
                    {
                        headers.Remove(k);
                    }
                }
            }

            if (request.Authorization != null)
            {
                if (headers == null)
                {
                    headers = new Dictionary <string, string>();
                }
                if (request.Authorization.IsSchemeAllowed(uri.Scheme))
                {
                    request.Authorization.GetRequestHeaders(headers, request.Method, uri.PathAndQuery);
                }
            }

            if (headers != null)
            {
                foreach (KeyValuePair <string, string> kvp in headers)
                {
                    reqdata.Append($"{kvp.Key}: {kvp.Value}\r\n");
                }
            }

            if (request.UseChunkedEncoding)
            {
                reqdata.Append("Transfer-Encoding: chunked\r\n");
            }

            string content_type      = request.RequestContentType;
            bool   expect100Continue = request.Expect100Continue;

            if (request.RequestBodyDelegate != null)
            {
                if (content_type != null)
                {
                    reqdata.Append($"Content-Type: {content_type}\r\n");
                }

                if (request.UseChunkedEncoding)
                {
                    doPost    = true;
                    doChunked = true;
                    reqdata.Append("Transfer-Encoding: chunked\r\n");
                    if (compressed && content_type != "application/x-gzip")
                    {
                        reqdata.Append("X-Content-Encoding: gzip\r\n");
                    }

                    if (expect100Continue)
                    {
                        reqdata.Append("Expect: 100-continue\r\n");
                    }
                }
                else
                {
                    doPost = true;
                    if (content_length > request.Expect100ContinueMinSize)
                    {
                        expect100Continue         = true;
                        request.Expect100Continue = true;
                    }
                    reqdata.Append($"Content-Length: {content_length}\r\n");
                    if (compressed && content_type != "application/x-gzip")
                    {
                        reqdata.Append("X-Content-Encoding: gzip\r\n");
                    }

                    if (expect100Continue)
                    {
                        reqdata.Append("Expect: 100-continue\r\n");
                    }
                }
            }

            if (method != "HEAD")
            {
                reqdata.Append("Accept-Encoding: gzip, deflate\r\n");
            }

            if (!haveAccept)
            {
                reqdata.Append("Accept: */*\r\n");
            }

            int retrycnt = 1;

retry:
            AbstractHttpStream s = OpenStream(uri.Scheme, uri.Host, uri.Port, request.ClientCertificates, request.EnabledSslProtocols, request.CheckCertificateRevocation, request.ConnectionMode, request.RemoteCertificateValidationCallback, request.EnableIPv6 && OSSupportsIPv6);
            string finalreqdata = reqdata.ToString();

            if (!s.IsReusable)
            {
                finalreqdata += "Connection: close\r\n";
            }
            bool h2cUpgrade = request.ConnectionMode == ConnectionModeEnum.UpgradeHttp2;

            if (h2cUpgrade)
            {
                finalreqdata += "Upgrade: h2c\r\nHTTP2-Settings:\r\n";
            }
            finalreqdata += "\r\n";
            outdata       = Encoding.ASCII.GetBytes(finalreqdata);

            try
            {
                s.Write(outdata, 0, outdata.Length);
                s.Flush();
            }
            catch (ObjectDisposedException)
            {
                if (retrycnt-- > 0)
                {
                    goto retry;
                }
                throw;
            }
            catch (SocketException)
            {
                if (retrycnt-- > 0)
                {
                    goto retry;
                }
                throw;
            }
            catch (IOException)
            {
                if (retrycnt-- > 0)
                {
                    goto retry;
                }
                throw;
            }
            s.ReadTimeout = 10000;
            string resline;

            string[] splits;

            if (doPost)
            {
                if (expect100Continue)
                {
                    try
                    {
                        resline = s.ReadHeaderLine();
                        splits  = resline.Split(new char[] { ' ' }, 3);
                        if (splits.Length < 3)
                        {
                            throw new BadHttpResponseException("Not a HTTP response");
                        }

                        if (!splits[0].StartsWith("HTTP/"))
                        {
                            throw new BadHttpResponseException("Missing HTTP version info");
                        }

                        if (splits[1] == "101")
                        {
                            ReadHeaderLines(s, headers);
                            headers.Clear();
                            var conn = new Http2Connection(s, false);
                            Http2Connection.Http2Stream h2stream = conn.UpgradeClientStream();
                            AddH2Connection(conn, uri.Scheme, uri.Host, uri.Port);

                            return(DoStreamRequestHttp2Response(h2stream, request, uri, doPost));
                        }

                        if (splits[1] != "100")
                        {
                            int statusCode;
                            headers.Clear();
                            ReadHeaderLines(s, headers);
                            if (!int.TryParse(splits[1], out statusCode))
                            {
                                statusCode = 500;
                            }
                            request.StatusCode = (HttpStatusCode)statusCode;

                            if (statusCode == 401 && request.Authorization != null && request.Authorization.CanHandleUnauthorized(headers))
                            {
                                using (GetResponseBodyStream(headers, uri, splits, method, s))
                                {
                                    /* just consume it */
                                }
                                goto redoafter401;
                            }
                            if (statusCode == 401)
                            {
                                string data;
                                if (headers.TryGetValue("www-authenticate", out data))
                                {
                                    string authtype;
                                    Dictionary <string, string> authpara = ParseWWWAuthenticate(data, out authtype);
                                    if (authpara == null)
                                    {
                                        using (GetResponseBodyStream(headers, uri, splits, method, s))
                                        {
                                            /* just consume it */
                                        }
                                        throw new BadHttpResponseException("Invalid WWW-Authenticate");
                                    }
                                    else if (request.IsExceptionDisabled())
                                    {
                                        return(GetResponseBodyStream(headers, uri, splits, method, s));
                                    }
                                    else
                                    {
                                        string realm;
                                        if (!authpara.TryGetValue("realm", out realm))
                                        {
                                            realm = string.Empty;
                                        }
                                        using (GetResponseBodyStream(headers, uri, splits, method, s))
                                        {
                                            /* just consume it */
                                        }
                                        throw new HttpUnauthorizedException(authtype, realm, authpara);
                                    }
                                }
                            }

                            if (request.IsExceptionDisabled())
                            {
                                return(GetResponseBodyStream(headers, uri, splits, method, s));
                            }
                            else
                            {
                                using (GetResponseBodyStream(headers, uri, splits, method, s))
                                {
                                    /* just consume it */
                                }
                                throw new HttpException(statusCode, statusCode == 404 ? splits[2] + " (" + url + ")" : splits[2]);
                            }
                        }

                        while (s.ReadHeaderLine().Length != 0)
                        {
                            /* ReadHeaderLine() is all we have to do */
                        }
                    }
                    catch (HttpStream.TimeoutException)
                    {
                        /* keep caller from being exceptioned */
                    }
                    catch (IOException)
                    {
                        /* keep caller from being exceptioned */
                    }
                    catch (SocketException)
                    {
                        /* keep caller from being exceptioned */
                    }
                }

                if (doChunked)
                {
                    /* append request POST data */
                    using (var reqbody = new HttpWriteChunkedBodyStream(s))
                    {
                        request.RequestBodyDelegate(reqbody);
                    }
                }
                else
                {
                    /* append request POST data */
                    using (var reqbody = new RequestBodyStream(s, content_length))
                    {
                        request.RequestBodyDelegate(reqbody);
                    }
                }
                s.Flush();
            }

            s.ReadTimeout = request.TimeoutMs;
            resline       = s.ReadHeaderLine();
            splits        = resline.Split(new char[] { ' ' }, 3);
            if (splits.Length < 3)
            {
                throw new BadHttpResponseException("Not a HTTP response");
            }

            if (!splits[0].StartsWith("HTTP/"))
            {
                throw new BadHttpResponseException("Missing HTTP version info");
            }

            if (headers == null)
            {
                headers = new Dictionary <string, string>();
            }
            else
            {
                headers.Clear();
            }

            if (splits[1] == "101")
            {
                ReadHeaderLines(s, headers);
                headers.Clear();
                var conn = new Http2Connection(s, false);
                Http2Connection.Http2Stream h2stream = conn.UpgradeClientStream();
                AddH2Connection(conn, uri.Scheme, uri.Host, uri.Port);

                return(DoStreamRequestHttp2Response(h2stream, request, uri, doPost));
            }

            if (!splits[1].StartsWith("2"))
            {
                ReadHeaderLines(s, headers);
                int statusCode;
                if (!int.TryParse(splits[1], out statusCode))
                {
                    statusCode = 500;
                }
                request.StatusCode = (HttpStatusCode)statusCode;

                if (statusCode == 401 && request.Authorization != null && request.Authorization.CanHandleUnauthorized(headers))
                {
                    using (GetResponseBodyStream(headers, uri, splits, method, s))
                    {
                        /* just consume it */
                    }
                    goto redoafter401;
                }
                if (statusCode == 401)
                {
                    string data;
                    if (headers.TryGetValue("www-authenticate", out data))
                    {
                        string authtype;
                        Dictionary <string, string> authpara = ParseWWWAuthenticate(data, out authtype);
                        if (authpara == null)
                        {
                            using (GetResponseBodyStream(headers, uri, splits, method, s))
                            {
                                /* just consume it */
                            }
                            throw new BadHttpResponseException("Invalid WWW-Authenticate");
                        }
                        else if (request.IsExceptionDisabled())
                        {
                            return(GetResponseBodyStream(headers, uri, splits, method, s));
                        }
                        else
                        {
                            string realm;
                            if (!authpara.TryGetValue("realm", out realm))
                            {
                                realm = string.Empty;
                            }
                            using (GetResponseBodyStream(headers, uri, splits, method, s))
                            {
                                /* just consume it */
                            }
                            throw new HttpUnauthorizedException(authtype, realm, authpara);
                        }
                    }
                }
                else
                {
                    request.Authorization?.ProcessResponseHeaders(headers);
                }

                if (request.IsExceptionDisabled())
                {
                    return(GetResponseBodyStream(headers, uri, splits, method, s));
                }
                else
                {
                    using (GetResponseBodyStream(headers, uri, splits, method, s))
                    {
                        /* just consume it */
                    }
                    throw new HttpException(statusCode, statusCode == 404 ? splits[2] + " (" + url + ")" : splits[2]);
                }
            }
            else
            {
                request.StatusCode = (HttpStatusCode)int.Parse(splits[1]);
            }

            /* needs a little passthrough for not changing the API, this actually comes from HTTP/2 */
            headers.Add(":status", splits[1]);

            ReadHeaderLines(s, headers);
            request.Authorization?.ProcessResponseHeaders(headers);

            return(GetResponseBodyStream(headers, uri, splits, method, s));
        }