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); }
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)); }