1 /******************************************************************************
3 * Copyright (C) 2012-2018 Icinga Development Team (https://www.icinga.com/) *
5 * This program is free software; you can redistribute it and/or *
6 * modify it under the terms of the GNU General Public License *
7 * as published by the Free Software Foundation; either version 2 *
8 * of the License, or (at your option) any later version. *
10 * This program is distributed in the hope that it will be useful, *
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
13 * GNU General Public License for more details. *
15 * You should have received a copy of the GNU General Public License *
16 * along with this program; if not, write to the Free Software Foundation *
17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. *
18 ******************************************************************************/
20 #include "remote/httprequest.hpp"
21 #include "base/logger.hpp"
22 #include "base/application.hpp"
23 #include "base/convert.hpp"
25 using namespace icinga;
27 HttpRequest::HttpRequest(Stream::Ptr stream)
29 ProtocolVersion(HttpVersion11),
30 Headers(new Dictionary()),
31 m_Stream(std::move(stream)),
32 m_State(HttpRequestStart)
35 bool HttpRequest::Parse(StreamReadContext& src, bool may_wait)
40 if (m_State != HttpRequestBody) {
42 StreamReadStatus srs = m_Stream->ReadLine(&line, src, may_wait);
44 if (srs != StatusNewItem)
47 if (m_State == HttpRequestStart) {
48 /* ignore trailing new-lines */
52 std::vector<String> tokens = line.Split(" ");
53 Log(LogDebug, "HttpRequest")
54 << "line: " << line << ", tokens: " << tokens.size();
55 if (tokens.size() != 3)
56 BOOST_THROW_EXCEPTION(std::invalid_argument("Invalid HTTP request"));
58 RequestMethod = tokens[0];
59 RequestUrl = new class Url(tokens[1]);
61 if (tokens[2] == "HTTP/1.0")
62 ProtocolVersion = HttpVersion10;
63 else if (tokens[2] == "HTTP/1.1") {
64 ProtocolVersion = HttpVersion11;
66 BOOST_THROW_EXCEPTION(std::invalid_argument("Unsupported HTTP version"));
68 m_State = HttpRequestHeaders;
69 } else if (m_State == HttpRequestHeaders) {
71 m_State = HttpRequestBody;
73 /* we're done if the request doesn't contain a message body */
74 if (!Headers->Contains("content-length") && !Headers->Contains("transfer-encoding"))
82 String::SizeType pos = line.FindFirstOf(":");
83 if (pos == String::NPos)
84 BOOST_THROW_EXCEPTION(std::invalid_argument("Invalid HTTP request"));
86 String key = line.SubStr(0, pos).ToLower().Trim();
87 String value = line.SubStr(pos + 1).Trim();
88 Headers->Set(key, value);
90 if (key == "x-http-method-override")
91 RequestMethod = value;
94 VERIFY(!"Invalid HTTP request state.");
96 } else if (m_State == HttpRequestBody) {
97 if (Headers->Get("transfer-encoding") == "chunked") {
99 m_ChunkContext = std::make_shared<ChunkReadContext>(std::ref(src));
103 StreamReadStatus srs = HttpChunkedEncoding::ReadChunkFromStream(m_Stream, &data, &size, *m_ChunkContext.get(), may_wait);
105 if (srs != StatusNewItem)
108 m_Body->Write(data, size);
118 BOOST_THROW_EXCEPTION(std::invalid_argument("Unexpected EOF in HTTP body"));
121 if (!src.FillFromStream(m_Stream, false)) {
123 BOOST_THROW_EXCEPTION(std::invalid_argument("Unexpected EOF in HTTP body"));
126 src.MustRead = false;
129 long length_indicator_signed = Convert::ToLong(Headers->Get("content-length"));
131 if (length_indicator_signed < 0)
132 BOOST_THROW_EXCEPTION(std::invalid_argument("Content-Length must not be negative."));
134 size_t length_indicator = length_indicator_signed;
136 if (src.Size < length_indicator) {
141 m_Body->Write(src.Buffer, length_indicator);
142 src.DropData(length_indicator);
151 size_t HttpRequest::ReadBody(char *data, size_t count)
156 return m_Body->Read(data, count, true);
159 void HttpRequest::AddHeader(const String& key, const String& value)
161 ASSERT(m_State == HttpRequestStart || m_State == HttpRequestHeaders);
162 Headers->Set(key.ToLower(), value);
165 void HttpRequest::FinishHeaders()
167 if (m_State == HttpRequestStart) {
168 String rqline = RequestMethod + " " + RequestUrl->Format(true) + " HTTP/1." + (ProtocolVersion == HttpVersion10 ? "0" : "1") + "\n";
169 m_Stream->Write(rqline.CStr(), rqline.GetLength());
170 m_State = HttpRequestHeaders;
173 if (m_State == HttpRequestHeaders) {
174 AddHeader("User-Agent", "Icinga/" + Application::GetAppVersion());
176 if (ProtocolVersion == HttpVersion11) {
177 AddHeader("Transfer-Encoding", "chunked");
178 if (!Headers->Contains("Host"))
179 AddHeader("Host", RequestUrl->GetHost() + ":" + RequestUrl->GetPort());
182 ObjectLock olock(Headers);
183 for (const Dictionary::Pair& kv : Headers)
185 String header = kv.first + ": " + kv.second + "\n";
186 m_Stream->Write(header.CStr(), header.GetLength());
189 m_Stream->Write("\n", 1);
191 m_State = HttpRequestBody;
195 void HttpRequest::WriteBody(const char *data, size_t count)
197 ASSERT(m_State == HttpRequestStart || m_State == HttpRequestHeaders || m_State == HttpRequestBody);
199 if (ProtocolVersion == HttpVersion10) {
203 m_Body->Write(data, count);
207 HttpChunkedEncoding::WriteChunkToStream(m_Stream, data, count);
211 void HttpRequest::Finish()
213 ASSERT(m_State != HttpRequestEnd);
215 if (ProtocolVersion == HttpVersion10) {
217 AddHeader("Content-Length", Convert::ToString(m_Body->GetAvailableBytes()));
221 while (m_Body && m_Body->IsDataAvailable()) {
223 size_t rc = m_Body->Read(buffer, sizeof(buffer), true);
224 m_Stream->Write(buffer, rc);
227 if (m_State == HttpRequestStart || m_State == HttpRequestHeaders)
230 WriteBody(nullptr, 0);
231 m_Stream->Write("\r\n", 2);
234 m_State = HttpRequestEnd;