private void FindResponses(TcpConnection conn, int direction) { Int64 totalReplyLength = conn.Stream(direction).Data.Length; RpyStateType state = RpyStateType.RpyStateStartHttp; Byte[] data = conn.Stream(direction).Data.ToArray(); int cur = 0; int reqIdx = 0; HttpResponse rpy = null; while (cur < data.Length) { switch (state) { // Start state: Find "HTTP/" that begins a response case RpyStateType.RpyStateStartHttp: if (strncasecmp(data, cur, 5, "HTTP/")) { // Found start of a response state = RpyStateType.RpyStateFinishHttp; cur += 5; rpy = new HttpResponse(); } else { cur++; } break; // Finish off HTTP string (version number) by looking for whitespace case RpyStateType.RpyStateFinishHttp: if (data[cur] == 0x20) // data[cur] == <space> { state = RpyStateType.RpyStateFindResponse; } else { cur++; } break; // Look for response code by finding non-whitespace. case RpyStateType.RpyStateFindResponse: if (data[cur] != 0x20) // data[cur] == <space> { cur += rpy.ParseResponseCode(data, cur); state = RpyStateType.RpyStateFindContentLength; } else { cur++; } break; // this state is now misnamed since we pull out other // headers than just content-length now. case RpyStateType.RpyStateFindContentLength: if (strncasecmp(data, cur, 17, "\r\nContent-Length:")) { cur += 17; // skip leading spaces while (data[cur] == 0x20) // data[cur] == '<space>' { cur++; } cur += rpy.ParseContentLength(data, cur); } else if (strncasecmp(data, cur, 15, "\r\nContent-Type:")) { cur += 15; // skip leading spaces while (data[cur] == 0x20) // data[cur] == '<space>' { cur++; } cur += rpy.ParseContentType(data, cur); } else if (strncasecmp(data, cur, 20, "\r\nTransfer-Encoding:")) { cur += 20; // skip leading spaces while (data[cur] == 0x20) // data[cur] == '<space>' { cur++; } cur += rpy.ParseTransferEncoding(data, cur); } else if (strncasecmp(data, cur, 4, "\r\n\r\n")) { // No increment for cur here, effectively fall through state = RpyStateType.RpyStateFinishHeader; } else if (strncasecmp(data, cur, 2, "\r\n")) { cur += 2; // Try to fatch other possible headers int pos = cur; while (data[pos] != 0x0D && // data[pos] != '\n' data[pos] != 0x0A) // data[pos] != '\r' { pos++; } string str = Encoding.ASCII.GetString(data, cur, pos - cur); int idx = str.IndexOf(":"); if (idx != -1) { string field = str.Substring(0, idx).Trim(); string value = str.Substring(idx + 1).Trim(); rpy.OtherHeaders[field] = value; } else { // This should not happen during a specific compatate connection. // But if it happened, only we can do is to ignore it. } cur = pos; } else { cur++; } break; // Skip over the rest of the header case RpyStateType.RpyStateFinishHeader: if (strncasecmp(data, cur, 4, "\r\n\r\n")) { // Found end of header cur += 4; // We are sure to have found a response now, // so match it to a request. // Should always match in a complete connection, // however, there are rare circumstences when responses // are more than requests. if (reqIdx < RequestList.Count) { rpy.Request = RequestList[reqIdx]; RequestList[reqIdx++].Response = rpy; } // At this point, we need to find the end of the // response body. There's a variety of ways to // do this, but in any case, we need to make sure // that ryp.ContentLength, cur are all set appropriately. // See if we can ignore the body. // We can do this // for the reply to HEAD // for a 1xx // for a 204 (no content), 205 (reset content), or 304 (not modified). if ((rpy.Request != null && rpy.Request.Method == HttpMethod.Head) || rpy.ResponseCode < 200 || rpy.ResponseCode == 204 || rpy.ResponseCode == 205 || rpy.ResponseCode == 304) { rpy.ContentLength = 0; rpy.Body = null; } else if (rpy.TransferEncoding != "") { // According to RFC 2616, when both TransferEncoding and ContentLength // present, latter is ignored. if (rpy.TransferEncoding.Contains("chunked")) { int pos = cur; while (pos < data.Length) { int t; int w; if ((w = Parses.ParseHex(data, pos, out t)) > 0) { if (t == 0) { pos += 2; break; } // skip to next chunk pos += w + 2 + t + 2; } else { pos++; } } rpy.ContentLength = pos - cur + 1; rpy.Body = new Byte[rpy.ContentLength]; // We should check in case we didn't captured complete stream. if (rpy.ContentLength + cur > data.Length) { Array.Copy(data, cur, rpy.Body, 0, data.Length - cur); } else { Array.Copy(data, cur, rpy.Body, 0, rpy.ContentLength); } cur += rpy.ContentLength; // TODO: There may also tailors to deal with } else { // Use content-length header if one was present. rpy.Body = new Byte[rpy.ContentLength]; if (rpy.ContentLength + cur > data.Length) { Array.Copy(data, cur, rpy.Body, 0, data.Length - cur); } else { Array.Copy(data, cur, rpy.Body, 0, rpy.ContentLength); } cur += rpy.ContentLength; } } else if (rpy.ContentLength != -1) { // Use content-length header if one was present. rpy.Body = new Byte[rpy.ContentLength]; if (rpy.ContentLength + cur > data.Length) { Array.Copy(data, cur, rpy.Body, 0, data.Length - cur); } else { Array.Copy(data, cur, rpy.Body, 0, rpy.ContentLength); } cur += rpy.ContentLength; } else { // No content-length header found, // so delimit response by end of stream. // But make sure we do not have a "\r\n\r\n" string // in the response, which may indicate the begining // of a following response. int start = cur; while (cur < data.Length) { if (strncasecmp(data, cur, 4, "\r\n\r\n")) { cur += 4; state = RpyStateType.RpyStateStartHttp; break; } else { cur++; } } if (state == RpyStateType.RpyStateStartHttp) { rpy.ContentLength = cur - start; } else { rpy.ContentLength = data.Length - start; cur = data.Length; } rpy.Body = new Byte[rpy.ContentLength]; Array.Copy(data, start, rpy.Body, 0, rpy.ContentLength); } // Set next state state = RpyStateType.RpyStateStartHttp; // Fill in other infos rpy.Source = conn.Pair.EndPoint(direction); rpy.Destination = conn.Pair.EndPoint(1 - direction); rpy.ConnectionID = conn.ConnectionID; // Add reply to list ResponseList.Add(rpy); } else { cur++; } break; } } }
private void FormatBody(HttpResponse rpy) { ContentType type = new ContentType(rpy.ContentType); // First we must deal chunked message if (rpy.TransferEncoding.Contains("chunked")) { using (MemoryStream ss = new MemoryStream()) { int pos = 0; while (pos < rpy.Body.Length) { int t; int w; if ((w = Parses.ParseHex(rpy.Body, pos, out t)) > 0) { if (t == 0) { pos += 2; break; } // skip length pos += w + 2; // save content ss.Write(rpy.Body, pos, t); // skip to next chunk pos += t + 2; } else { pos++; } } rpy.Body = ss.ToArray(); rpy.ContentLength = rpy.Body.Length; } } // TODO: deal with multipart content type // Decompress if needed if (rpy.OtherHeaders.ContainsKey("Content-Encoding") && rpy.OtherHeaders["Content-Encoding"].Contains("gzip")) { using (MemoryStream mstream = new MemoryStream(rpy.Body)) { using (MemoryStream tstream = new MemoryStream()) { using (GZipStream gstream = new GZipStream(mstream, CompressionMode.Decompress)) { using (BufferedStream buf = new BufferedStream(gstream)) { buf.CopyTo(tstream); rpy.Body = tstream.ToArray(); } } } } } else { } }