]> granicus.if.org Git - icinga2/blob - lib/remote/httprequest.cpp
Add validation for HTTP connection sizes
[icinga2] / lib / remote / httprequest.cpp
1 /******************************************************************************
2  * Icinga 2                                                                   *
3  * Copyright (C) 2012-2018 Icinga Development Team (https://www.icinga.com/)  *
4  *                                                                            *
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.                     *
9  *                                                                            *
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.                               *
14  *                                                                            *
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  ******************************************************************************/
19
20 #include "remote/httprequest.hpp"
21 #include "base/logger.hpp"
22 #include "base/application.hpp"
23 #include "base/convert.hpp"
24
25 using namespace icinga;
26
27 HttpRequest::HttpRequest(Stream::Ptr stream)
28         : Complete(false),
29         ProtocolVersion(HttpVersion11),
30         Headers(new Dictionary()),
31         m_Stream(std::move(stream)),
32         m_State(HttpRequestStart)
33 { }
34
35 bool HttpRequest::Parse(StreamReadContext& src, bool may_wait)
36 {
37         if (!m_Stream)
38                 return false;
39
40         if (m_State != HttpRequestBody) {
41                 String line;
42                 StreamReadStatus srs = m_Stream->ReadLine(&line, src, may_wait);
43
44                 if (srs != StatusNewItem)
45                         return false;
46
47                 if (m_State == HttpRequestStart) {
48                         /* ignore trailing new-lines */
49                         if (line == "")
50                                 return true;
51
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"));
57
58                         RequestMethod = tokens[0];
59                         RequestUrl = new class Url(tokens[1]);
60
61                         if (tokens[2] == "HTTP/1.0")
62                                 ProtocolVersion = HttpVersion10;
63                         else if (tokens[2] == "HTTP/1.1") {
64                                 ProtocolVersion = HttpVersion11;
65                         } else
66                                 BOOST_THROW_EXCEPTION(std::invalid_argument("Unsupported HTTP version"));
67
68                         m_State = HttpRequestHeaders;
69                 } else if (m_State == HttpRequestHeaders) {
70                         if (line == "") {
71                                 m_State = HttpRequestBody;
72
73                                 /* we're done if the request doesn't contain a message body */
74                                 if (!Headers->Contains("content-length") && !Headers->Contains("transfer-encoding"))
75                                         Complete = true;
76                                 else
77                                         m_Body = new FIFO();
78
79                                 return true;
80
81                         } else {
82                                 String::SizeType pos = line.FindFirstOf(":");
83                                 if (pos == String::NPos)
84                                         BOOST_THROW_EXCEPTION(std::invalid_argument("Invalid HTTP request"));
85
86                                 String key = line.SubStr(0, pos).ToLower().Trim();
87                                 String value = line.SubStr(pos + 1).Trim();
88                                 Headers->Set(key, value);
89
90                                 if (key == "x-http-method-override")
91                                         RequestMethod = value;
92                         }
93                 } else {
94                         VERIFY(!"Invalid HTTP request state.");
95                 }
96         } else if (m_State == HttpRequestBody) {
97                 if (Headers->Get("transfer-encoding") == "chunked") {
98                         if (!m_ChunkContext)
99                                 m_ChunkContext = std::make_shared<ChunkReadContext>(std::ref(src));
100
101                         char *data;
102                         size_t size;
103                         StreamReadStatus srs = HttpChunkedEncoding::ReadChunkFromStream(m_Stream, &data, &size, *m_ChunkContext.get(), may_wait);
104
105                         if (srs != StatusNewItem)
106                                 return false;
107
108                         m_Body->Write(data, size);
109
110                         delete [] data;
111
112                         if (size == 0) {
113                                 Complete = true;
114                                 return true;
115                         }
116                 } else {
117                         if (src.Eof)
118                                 BOOST_THROW_EXCEPTION(std::invalid_argument("Unexpected EOF in HTTP body"));
119
120                         if (src.MustRead) {
121                                 if (!src.FillFromStream(m_Stream, false)) {
122                                         src.Eof = true;
123                                         BOOST_THROW_EXCEPTION(std::invalid_argument("Unexpected EOF in HTTP body"));
124                                 }
125
126                                 src.MustRead = false;
127                         }
128
129                         long length_indicator_signed = Convert::ToLong(Headers->Get("content-length"));
130
131                         if (length_indicator_signed < 0)
132                                 BOOST_THROW_EXCEPTION(std::invalid_argument("Content-Length must not be negative."));
133
134                         size_t length_indicator = length_indicator_signed;
135
136                         if (src.Size < length_indicator) {
137                                 src.MustRead = true;
138                                 return false;
139                         }
140
141                         m_Body->Write(src.Buffer, length_indicator);
142                         src.DropData(length_indicator);
143                         Complete = true;
144                         return true;
145                 }
146         }
147
148         return true;
149 }
150
151 size_t HttpRequest::ReadBody(char *data, size_t count)
152 {
153         if (!m_Body)
154                 return 0;
155         else
156                 return m_Body->Read(data, count, true);
157 }
158
159 void HttpRequest::AddHeader(const String& key, const String& value)
160 {
161         ASSERT(m_State == HttpRequestStart || m_State == HttpRequestHeaders);
162         Headers->Set(key.ToLower(), value);
163 }
164
165 void HttpRequest::FinishHeaders()
166 {
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;
171         }
172
173         if (m_State == HttpRequestHeaders) {
174                 AddHeader("User-Agent", "Icinga/" + Application::GetAppVersion());
175
176                 if (ProtocolVersion == HttpVersion11) {
177                         AddHeader("Transfer-Encoding", "chunked");
178                         if (!Headers->Contains("Host"))
179                                 AddHeader("Host", RequestUrl->GetHost() + ":" + RequestUrl->GetPort());
180                 }
181
182                 ObjectLock olock(Headers);
183                 for (const Dictionary::Pair& kv : Headers)
184                 {
185                         String header = kv.first + ": " + kv.second + "\n";
186                         m_Stream->Write(header.CStr(), header.GetLength());
187                 }
188
189                 m_Stream->Write("\n", 1);
190
191                 m_State = HttpRequestBody;
192         }
193 }
194
195 void HttpRequest::WriteBody(const char *data, size_t count)
196 {
197         ASSERT(m_State == HttpRequestStart || m_State == HttpRequestHeaders || m_State == HttpRequestBody);
198
199         if (ProtocolVersion == HttpVersion10) {
200                 if (!m_Body)
201                         m_Body = new FIFO();
202
203                 m_Body->Write(data, count);
204         } else {
205                 FinishHeaders();
206
207                 HttpChunkedEncoding::WriteChunkToStream(m_Stream, data, count);
208         }
209 }
210
211 void HttpRequest::Finish()
212 {
213         ASSERT(m_State != HttpRequestEnd);
214
215         if (ProtocolVersion == HttpVersion10) {
216                 if (m_Body)
217                         AddHeader("Content-Length", Convert::ToString(m_Body->GetAvailableBytes()));
218
219                 FinishHeaders();
220
221                 while (m_Body && m_Body->IsDataAvailable()) {
222                         char buffer[1024];
223                         size_t rc = m_Body->Read(buffer, sizeof(buffer), true);
224                         m_Stream->Write(buffer, rc);
225                 }
226         } else {
227                 if (m_State == HttpRequestStart || m_State == HttpRequestHeaders)
228                         FinishHeaders();
229
230                 WriteBody(nullptr, 0);
231                 m_Stream->Write("\r\n", 2);
232         }
233
234         m_State = HttpRequestEnd;
235 }
236