public override Stream GetOutputStream(bool disableCompression = false)
        {
            bool gzipEnable = false;

            if (!m_IsHeaderSent)
            {
                if (!IsChunkedAccepted)
                {
                    IsCloseConnection = true;
                }
                Headers.Remove("Content-Length");
                if (!disableCompression && AcceptedEncodings != null && AcceptedEncodings.Contains("gzip"))
                {
                    gzipEnable = true;
                    Headers["Content-Encoding"] = "gzip";
                }
                if (IsChunkedAccepted)
                {
                    Headers["Transfer-Encoding"] = "chunked";
                }
                else
                {
                    Headers["Connection"] = "close";
                }
                SendHeaders();
            }
            else
            {
                throw new InvalidOperationException();
            }

            Stream stream;

            if (IsChunkedAccepted)
            {
                stream = new HttpWriteChunkedBodyStream(m_Output);
            }
            else
            {
                stream = new HttpResponseBodyStream(m_Output);
            }
            /* we never give out the original stream because Close is working recursively according to .NET specs */
            if (gzipEnable)
            {
                stream = new GZipStream(stream, CompressionMode.Compress);
            }
            return(stream);
        }
        bool RunTestData(byte[] data)
        {
            m_Log.InfoFormat("Testing {0} bytes of data", data.Length);
            byte[] encoded;
            var    decoded = new byte[data.Length];

            using (var ms = new MemoryStream())
            {
                using (var write = new HttpWriteChunkedBodyStream(ms))
                {
                    write.Write(data, 0, data.Length);
                }
                encoded = ms.ToArray();
            }

            using (var ms = new MemoryStream(encoded))
            {
                using (var read = new HttpReadChunkedBodyStream(ms))
                {
                    int rcvdbytes = read.Read(decoded, 0, data.Length);
                    if (data.Length != rcvdbytes)
                    {
                        m_Log.ErrorFormat("chunked stream does not contain all input bytes ({0}!={1})", data.Length, rcvdbytes);
                        return(false);
                    }
                    var t = new byte[1];
                    if (read.Read(t, 0, 1) != 0)
                    {
                        m_Log.Error("chunked stream does not end with last byte");
                        return(false);
                    }
                }
            }

            int i;

            for (i = 0; i < data.Length; ++i)
            {
                if (data[i] != decoded[i])
                {
                    m_Log.ErrorFormat("Compare error at byte position {0}", i);
                    return(false);
                }
            }
            return(true);
        }
Exemple #3
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));
        }