1 /******************************************************************************
3 * Copyright (C) 2012-2015 Icinga Development Team (http://www.icinga.org) *
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/httpresponse.hpp"
21 #include "remote/httpchunkedencoding.hpp"
22 #include "base/logger.hpp"
23 #include <boost/algorithm/string/split.hpp>
24 #include <boost/algorithm/string/classification.hpp>
25 #include "base/application.hpp"
26 #include "base/convert.hpp"
27 #include <boost/smart_ptr/make_shared.hpp>
29 using namespace icinga;
31 HttpResponse::HttpResponse(const Stream::Ptr& stream, const HttpRequest& request)
32 : Complete(false), m_State(HttpResponseStart), m_Request(request), m_Stream(stream)
35 void HttpResponse::SetStatus(int code, const String& message)
37 ASSERT(m_State == HttpResponseStart);
38 ASSERT(code >= 100 && code <= 599);
39 ASSERT(!message.IsEmpty());
41 String status = "HTTP/";
43 if (m_Request.ProtocolVersion == HttpVersion10)
48 status += " " + Convert::ToString(code) + " " + message + "\r\n";
50 m_Stream->Write(status.CStr(), status.GetLength());
52 m_State = HttpResponseHeaders;
55 void HttpResponse::AddHeader(const String& key, const String& value)
57 ASSERT(m_State = HttpResponseHeaders);
58 String header = key + ": " + value + "\r\n";
59 m_Stream->Write(header.CStr(), header.GetLength());
62 void HttpResponse::FinishHeaders(void)
64 if (m_State == HttpResponseHeaders) {
65 if (m_Request.ProtocolVersion == HttpVersion11)
66 AddHeader("Transfer-Encoding", "chunked");
68 AddHeader("Server", "Icinga/" + Application::GetAppVersion());
69 m_Stream->Write("\r\n", 2);
70 m_State = HttpResponseBody;
74 void HttpResponse::WriteBody(const char *data, size_t count)
76 ASSERT(m_State == HttpResponseHeaders || m_State == HttpResponseBody);
78 if (m_Request.ProtocolVersion == HttpVersion10) {
82 m_Body->Write(data, count);
86 HttpChunkedEncoding::WriteChunkToStream(m_Stream, data, count);
90 void HttpResponse::Finish(void)
92 ASSERT(m_State != HttpResponseEnd);
94 if (m_Request.ProtocolVersion == HttpVersion10) {
96 AddHeader("Content-Length", Convert::ToString(m_Body->GetAvailableBytes()));
100 while (m_Body && m_Body->IsDataAvailable()) {
102 size_t rc = m_Body->Read(buffer, sizeof(buffer), true);
103 m_Stream->Write(buffer, rc);
107 m_Stream->Write("\r\n", 2);
110 m_State = HttpResponseEnd;
112 if (m_Request.ProtocolVersion == HttpVersion10 || m_Request.Headers->Get("connection") == "close")
113 m_Stream->Shutdown();
116 bool HttpResponse::Parse(StreamReadContext& src, bool may_wait)
118 if (m_State != HttpResponseBody) {
120 StreamReadStatus srs = m_Stream->ReadLine(&line, src, may_wait);
122 if (srs != StatusNewItem)
125 if (m_State == HttpResponseStart) {
126 /* ignore trailing new-lines */
130 std::vector<String> tokens;
131 boost::algorithm::split(tokens, line, boost::is_any_of(" "));
132 Log(LogDebug, "HttpRequest")
133 << "line: " << line << ", tokens: " << tokens.size();
134 if (tokens.size() < 3)
135 BOOST_THROW_EXCEPTION(std::invalid_argument("Invalid HTTP request"));
137 if (tokens[0] == "HTTP/1.0")
138 ProtocolVersion = HttpVersion10;
139 else if (tokens[0] == "HTTP/1.1") {
140 ProtocolVersion = HttpVersion11;
142 BOOST_THROW_EXCEPTION(std::invalid_argument("Unsupported HTTP version"));
144 StatusCode = Convert::ToLong(tokens[1]);
145 StatusMessage = tokens[2]; // TODO: Join tokens[2..end]
147 m_State = HttpResponseHeaders;
148 } else if (m_State == HttpResponseHeaders) {
150 Headers = new Dictionary();
153 m_State = HttpResponseBody;
155 /* we're done if the request doesn't contain a message body */
156 if (!Headers->Contains("content-length") && !Headers->Contains("transfer-encoding"))
164 String::SizeType pos = line.FindFirstOf(":");
165 if (pos == String::NPos)
166 BOOST_THROW_EXCEPTION(std::invalid_argument("Invalid HTTP request"));
167 String key = line.SubStr(0, pos).ToLower().Trim();
169 String value = line.SubStr(pos + 1).Trim();
170 Headers->Set(key, value);
173 VERIFY(!"Invalid HTTP request state.");
175 } else if (m_State == HttpResponseBody) {
176 if (Headers->Get("transfer-encoding") == "chunked") {
178 m_ChunkContext = boost::make_shared<ChunkReadContext>(src);
182 StreamReadStatus srs = HttpChunkedEncoding::ReadChunkFromStream(m_Stream, &data, &size, *m_ChunkContext.get(), may_wait);
184 if (srs != StatusNewItem)
187 Log(LogInformation, "HttpResponse")
188 << "Read " << size << " bytes";
190 m_Body->Write(data, size);
200 BOOST_THROW_EXCEPTION(std::invalid_argument("Unexpected EOF in HTTP body"));
203 if (!src.FillFromStream(m_Stream, false)) {
205 BOOST_THROW_EXCEPTION(std::invalid_argument("Unexpected EOF in HTTP body"));
208 src.MustRead = false;
211 size_t length_indicator = Convert::ToLong(Headers->Get("content-length"));
213 if (src.Size < length_indicator) {
218 m_Body->Write(src.Buffer, length_indicator);
219 src.DropData(length_indicator);
228 size_t HttpResponse::ReadBody(char *data, size_t count)
233 return m_Body->Read(data, count, true);