StreamReadStatus Stream::ReadLine(String *line, StreamReadContext& context, bool may_wait) { if (context.Eof) return StatusEof; if (context.MustRead) { if (!context.FillFromStream(this, may_wait)) { context.Eof = true; *line = String(context.Buffer, &(context.Buffer[context.Size])); boost::algorithm::trim_right(*line); return StatusNewItem; } } for (size_t i = 0; i < context.Size; i++) { if (context.Buffer[i] == '\n') { *line = String(context.Buffer, context.Buffer + i); boost::algorithm::trim_right(*line); context.DropData(i + 1u); context.MustRead = !context.Size; return StatusNewItem; } } context.MustRead = true; return StatusNeedData; }
bool HttpRequest::Parse(StreamReadContext& src, bool may_wait) { if (m_State != HttpRequestBody) { String line; StreamReadStatus srs = m_Stream->ReadLine(&line, src, may_wait); if (srs != StatusNewItem) return false; if (m_State == HttpRequestStart) { /* ignore trailing new-lines */ if (line == "") return true; std::vector<String> tokens; boost::algorithm::split(tokens, line, boost::is_any_of(" ")); Log(LogDebug, "HttpRequest") << "line: " << line << ", tokens: " << tokens.size(); if (tokens.size() != 3) BOOST_THROW_EXCEPTION(std::invalid_argument("Invalid HTTP request")); RequestMethod = tokens[0]; RequestUrl = new class Url(tokens[1]); if (tokens[2] == "HTTP/1.0") ProtocolVersion = HttpVersion10; else if (tokens[2] == "HTTP/1.1") { ProtocolVersion = HttpVersion11; } else BOOST_THROW_EXCEPTION(std::invalid_argument("Unsupported HTTP version")); m_State = HttpRequestHeaders; } else if (m_State == HttpRequestHeaders) { if (line == "") { m_State = HttpRequestBody; /* we're done if the request doesn't contain a message body */ if (!Headers->Contains("content-length") && !Headers->Contains("transfer-encoding")) Complete = true; else m_Body = new FIFO(); return true; } else { String::SizeType pos = line.FindFirstOf(":"); if (pos == String::NPos) BOOST_THROW_EXCEPTION(std::invalid_argument("Invalid HTTP request")); String key = line.SubStr(0, pos).ToLower().Trim(); String value = line.SubStr(pos + 1).Trim(); Headers->Set(key, value); if (key == "x-http-method-override") RequestMethod = value; } } else { VERIFY(!"Invalid HTTP request state."); } } else if (m_State == HttpRequestBody) { if (Headers->Get("transfer-encoding") == "chunked") { if (!m_ChunkContext) m_ChunkContext = boost::make_shared<ChunkReadContext>(src); char *data; size_t size; StreamReadStatus srs = HttpChunkedEncoding::ReadChunkFromStream(m_Stream, &data, &size, *m_ChunkContext.get(), may_wait); if (srs != StatusNewItem) return false; Log(LogInformation, "HttpRequest") << "Read " << size << " bytes"; m_Body->Write(data, size); delete [] data; if (size == 0) { Complete = true; return true; } } else { if (src.Eof) BOOST_THROW_EXCEPTION(std::invalid_argument("Unexpected EOF in HTTP body")); if (src.MustRead) { if (!src.FillFromStream(m_Stream, false)) { src.Eof = true; BOOST_THROW_EXCEPTION(std::invalid_argument("Unexpected EOF in HTTP body")); } src.MustRead = false; } size_t length_indicator = Convert::ToLong(Headers->Get("content-length")); if (src.Size < length_indicator) { src.MustRead = true; return false; } m_Body->Write(src.Buffer, length_indicator); src.DropData(length_indicator); Complete = true; return true; } } return true; }
bool HttpRequest::ParseBody(StreamReadContext& src, bool may_wait) { if (!m_Stream) return false; if (m_State != HttpRequestBody) BOOST_THROW_EXCEPTION(std::runtime_error("Invalid HTTP state")); /* we're done if the request doesn't contain a message body */ if (!Headers->Contains("content-length") && !Headers->Contains("transfer-encoding")) { CompleteBody = true; return true; } else if (!m_Body) m_Body = new FIFO(); if (Headers->Get("transfer-encoding") == "chunked") { if (!m_ChunkContext) m_ChunkContext = std::make_shared<ChunkReadContext>(std::ref(src)); char *data; size_t size; StreamReadStatus srs = HttpChunkedEncoding::ReadChunkFromStream(m_Stream, &data, &size, *m_ChunkContext.get(), may_wait); if (srs != StatusNewItem) return false; m_Body->Write(data, size); delete [] data; if (size == 0) { CompleteBody = true; } return true; } if (src.Eof) BOOST_THROW_EXCEPTION(std::invalid_argument("Unexpected EOF in HTTP body")); if (src.MustRead) { if (!src.FillFromStream(m_Stream, false)) { src.Eof = true; BOOST_THROW_EXCEPTION(std::invalid_argument("Unexpected EOF in HTTP body")); } src.MustRead = false; } long length_indicator_signed = Convert::ToLong(Headers->Get("content-length")); if (length_indicator_signed < 0) BOOST_THROW_EXCEPTION(std::invalid_argument("Content-Length must not be negative.")); size_t length_indicator = length_indicator_signed; if (src.Size < length_indicator) { src.MustRead = true; return false; } m_Body->Write(src.Buffer, length_indicator); src.DropData(length_indicator); CompleteBody = true; return true; }
bool HttpResponse::Parse(StreamReadContext& src, bool may_wait) { if (m_State != HttpResponseBody) { String line; StreamReadStatus srs = m_Stream->ReadLine(&line, src, may_wait); if (srs != StatusNewItem) return false; if (m_State == HttpResponseStart) { /* ignore trailing new-lines */ if (line == "") return true; std::vector<String> tokens = line.Split(" "); Log(LogDebug, "HttpRequest") << "line: " << line << ", tokens: " << tokens.size(); if (tokens.size() < 2) BOOST_THROW_EXCEPTION(std::invalid_argument("Invalid HTTP response (Status line)")); if (tokens[0] == "HTTP/1.0") ProtocolVersion = HttpVersion10; else if (tokens[0] == "HTTP/1.1") { ProtocolVersion = HttpVersion11; } else BOOST_THROW_EXCEPTION(std::invalid_argument("Unsupported HTTP version")); StatusCode = Convert::ToLong(tokens[1]); if (tokens.size() >= 3) StatusMessage = tokens[2]; // TODO: Join tokens[2..end] m_State = HttpResponseHeaders; } else if (m_State == HttpResponseHeaders) { if (!Headers) Headers = new Dictionary(); if (line == "") { m_State = HttpResponseBody; m_Body = new FIFO(); return true; } else { String::SizeType pos = line.FindFirstOf(":"); if (pos == String::NPos) BOOST_THROW_EXCEPTION(std::invalid_argument("Invalid HTTP request")); String key = line.SubStr(0, pos).ToLower().Trim(); String value = line.SubStr(pos + 1).Trim(); Headers->Set(key, value); } } else { VERIFY(!"Invalid HTTP request state."); } } else if (m_State == HttpResponseBody) { if (Headers->Get("transfer-encoding") == "chunked") { if (!m_ChunkContext) m_ChunkContext = std::make_shared<ChunkReadContext>(std::ref(src)); char *data; size_t size; StreamReadStatus srs = HttpChunkedEncoding::ReadChunkFromStream(m_Stream, &data, &size, *m_ChunkContext.get(), may_wait); if (srs != StatusNewItem) return false; Log(LogNotice, "HttpResponse") << "Read " << size << " bytes"; m_Body->Write(data, size); delete[] data; if (size == 0) { Complete = true; return true; } } else { bool hasLengthIndicator = false; size_t lengthIndicator = 0; Value contentLengthHeader; if (Headers->Get("content-length", &contentLengthHeader)) { hasLengthIndicator = true; lengthIndicator = Convert::ToLong(contentLengthHeader); } if (!hasLengthIndicator && ProtocolVersion != HttpVersion10 && !Headers->Contains("transfer-encoding")) { Complete = true; return true; } if (hasLengthIndicator && src.Eof) BOOST_THROW_EXCEPTION(std::invalid_argument("Unexpected EOF in HTTP body")); if (src.MustRead) { if (!src.FillFromStream(m_Stream, may_wait)) src.Eof = true; src.MustRead = false; } if (!hasLengthIndicator) lengthIndicator = src.Size; if (src.Size < lengthIndicator) { src.MustRead = true; return may_wait; } m_Body->Write(src.Buffer, lengthIndicator); src.DropData(lengthIndicator); if (!hasLengthIndicator && !src.Eof) { src.MustRead = true; return may_wait; } Complete = true; return true; } } return true; }
/** * Reads data from a stream in netstring format. * * @param stream The stream to read from. * @param[out] str The String that has been read from the IOQueue. * @returns true if a complete String was read from the IOQueue, false otherwise. * @exception invalid_argument The input stream is invalid. * @see https://github.com/PeterScott/netstring-c/blob/master/netstring.c */ StreamReadStatus NetString::ReadStringFromStream(const Stream::Ptr& stream, String *str, StreamReadContext& context, bool may_wait) { if (context.Eof) return StatusEof; if (context.MustRead) { if (!context.FillFromStream(stream, may_wait)) { context.Eof = true; return StatusEof; } context.MustRead = false; } size_t header_length = 0; for (size_t i = 0; i < context.Size; i++) { if (context.Buffer[i] == ':') { header_length = i; /* make sure there's a header */ if (header_length == 0) BOOST_THROW_EXCEPTION(std::invalid_argument("Invalid NetString (no length specifier)")); break; } else if (i > 16) BOOST_THROW_EXCEPTION(std::invalid_argument("Invalid NetString (missing :)")); } if (header_length == 0) { context.MustRead = true; return StatusNeedData; } /* no leading zeros allowed */ if (context.Buffer[0] == '0' && isdigit(context.Buffer[1])) BOOST_THROW_EXCEPTION(std::invalid_argument("Invalid NetString (leading zero)")); size_t len, i; len = 0; for (i = 0; i < header_length && isdigit(context.Buffer[i]); i++) { /* length specifier must have at most 9 characters */ if (i >= 9) BOOST_THROW_EXCEPTION(std::invalid_argument("Length specifier must not exceed 9 characters")); len = len * 10 + (context.Buffer[i] - '0'); } /* read the whole message */ size_t data_length = len + 1; char *data = context.Buffer + header_length + 1; if (context.Size < header_length + 1 + data_length) { context.MustRead = true; return StatusNeedData; } if (data[len] != ',') BOOST_THROW_EXCEPTION(std::invalid_argument("Invalid NetString (missing ,)")); *str = String(&data[0], &data[len]); context.DropData(header_length + 1 + len + 1); return StatusNewItem; }