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