1 //
2 // reply.cpp
3 // ~~~~~~~~~
4 //
5 // Copyright (c) 2003-2008 Christopher M. Kohlhoff (chris at kohlhoff dot com)
6 //
7 // Distributed under the Boost Software License, Version 1.0. (See accompanying
8 // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
9 //
10 #include "stdafx.h"
11 #include "reply.hpp"
12 #include "mime_types.hpp"
13 #include "utf.hpp"
14 #include <string>
15 #include <fstream>
16 #include <boost/algorithm/string.hpp>
17
18 namespace http {
19 namespace server {
20
21 namespace status_strings {
22
23 const std::string switching_protocols =
24 "HTTP/1.1 101 Switching Protocols\r\n";
25 const std::string download_file =
26 "HTTP/1.1 102 Download File\r\n";
27 const std::string ok =
28 "HTTP/1.1 200 OK\r\n";
29 const std::string created =
30 "HTTP/1.1 201 Created\r\n";
31 const std::string accepted =
32 "HTTP/1.1 202 Accepted\r\n";
33 const std::string no_content =
34 "HTTP/1.1 204 No Content\r\n";
35 const std::string multiple_choices =
36 "HTTP/1.1 300 Multiple Choices\r\n";
37 const std::string moved_permanently =
38 "HTTP/1.1 301 Moved Permanently\r\n";
39 const std::string moved_temporarily =
40 "HTTP/1.1 302 Moved Temporarily\r\n";
41 const std::string not_modified =
42 "HTTP/1.1 304 Not Modified\r\n";
43 const std::string bad_request =
44 "HTTP/1.1 400 Bad Request\r\n";
45 const std::string unauthorized =
46 "HTTP/1.1 401 Unauthorized\r\n";
47 const std::string forbidden =
48 "HTTP/1.1 403 Forbidden\r\n";
49 const std::string not_found =
50 "HTTP/1.1 404 Not Found\r\n";
51 const std::string internal_server_error =
52 "HTTP/1.1 500 Internal Server Error\r\n";
53 const std::string not_implemented =
54 "HTTP/1.1 501 Not Implemented\r\n";
55 const std::string bad_gateway =
56 "HTTP/1.1 502 Bad Gateway\r\n";
57 const std::string service_unavailable =
58 "HTTP/1.1 503 Service Unavailable\r\n";
59
to_string(reply::status_type status)60 std::string to_string(reply::status_type status)
61 {
62 switch (status)
63 {
64 case reply::switching_protocols:
65 return switching_protocols;
66 case reply::download_file:
67 return download_file;
68
69 case reply::ok:
70 return ok;
71 case reply::created:
72 return created;
73 case reply::accepted:
74 return accepted;
75 case reply::no_content:
76 return no_content;
77 case reply::multiple_choices:
78 return multiple_choices;
79 case reply::moved_permanently:
80 return moved_permanently;
81 case reply::moved_temporarily:
82 return moved_temporarily;
83 case reply::not_modified:
84 return not_modified;
85 case reply::bad_request:
86 return bad_request;
87 case reply::unauthorized:
88 return unauthorized;
89 case reply::forbidden:
90 return forbidden;
91 case reply::not_found:
92 return not_found;
93 case reply::internal_server_error:
94 return internal_server_error;
95 case reply::not_implemented:
96 return not_implemented;
97 case reply::bad_gateway:
98 return bad_gateway;
99 case reply::service_unavailable:
100 return service_unavailable;
101 default:
102 return internal_server_error;
103 }
104 }
105
106 } // namespace status_strings
107
108 namespace misc_strings {
109
110 const char name_value_separator[] = { ':', ' ', 0 };
111 const char crlf[] = { '\r', '\n', 0 };
112
113 } // namespace misc_strings
114
header_to_string()115 std::string reply::header_to_string()
116 {
117 std::string buffers = status_strings::to_string(status);
118 for (std::size_t i = 0; i < headers.size(); ++i)
119 {
120 header& h = headers[i];
121 buffers += h.name + misc_strings::name_value_separator + h.value + misc_strings::crlf;
122 }
123 buffers += misc_strings::crlf;
124 return buffers;
125 }
126
to_string(const std::string & method)127 std::string reply::to_string(const std::string &method)
128 {
129 std::string buffers = header_to_string();
130 if (method != "HEAD") {
131 buffers += content;
132 }
133 return buffers;
134 }
135
reset()136 void reply::reset()
137 {
138 headers.clear();
139 content = "";
140 bIsGZIP = false;
141 }
142
143 namespace stock_replies {
144
145 const char switching_protocols[] = "";
146 const char download_file[] = "";
147 const char ok[] = "";
148 const char created[] =
149 "<html>"
150 "<head><title>Created</title></head>"
151 "<body><h1>201 Created</h1></body>"
152 "</html>";
153 const char accepted[] =
154 "<html>"
155 "<head><title>Accepted</title></head>"
156 "<body><h1>202 Accepted</h1></body>"
157 "</html>";
158 const char no_content[] =
159 ""; // The 204 response MUST NOT contain a message-body
160 const char multiple_choices[] =
161 "<html>"
162 "<head><title>Multiple Choices</title></head>"
163 "<body><h1>300 Multiple Choices</h1></body>"
164 "</html>";
165 const char moved_permanently[] =
166 "<html>"
167 "<head><title>Moved Permanently</title></head>"
168 "<body><h1>301 Moved Permanently</h1></body>"
169 "</html>";
170 const char moved_temporarily[] =
171 "<html>"
172 "<head><title>Moved Temporarily</title></head>"
173 "<body><h1>302 Moved Temporarily</h1></body>"
174 "</html>";
175 const char not_modified[] =
176 ""; // The 304 response MUST NOT contain a message-body
177 const char bad_request[] =
178 "<html>"
179 "<head><title>Bad Request</title></head>"
180 "<body><h1>400 Bad Request</h1></body>"
181 "</html>";
182 const char unauthorized[] =
183 "<html>"
184 "<head><title>Unauthorized</title></head>"
185 "<body><h1>401 Unauthorized</h1></body>"
186 "</html>";
187 const char forbidden[] =
188 "<html>"
189 "<head><title>Forbidden</title></head>"
190 "<body><h1>403 Forbidden</h1></body>"
191 "</html>";
192 const char not_found[] =
193 "<html>"
194 "<head><title>Not Found</title></head>"
195 "<body><h1>404 Not Found</h1></body>"
196 "</html>";
197 const char internal_server_error[] =
198 "<html>"
199 "<head><title>Internal Server Error</title></head>"
200 "<body><h1>500 Internal Server Error</h1></body>"
201 "</html>";
202 const char not_implemented[] =
203 "<html>"
204 "<head><title>Not Implemented</title></head>"
205 "<body><h1>501 Not Implemented</h1></body>"
206 "</html>";
207 const char bad_gateway[] =
208 "<html>"
209 "<head><title>Bad Gateway</title></head>"
210 "<body><h1>502 Bad Gateway</h1></body>"
211 "</html>";
212 const char service_unavailable[] =
213 "<html>"
214 "<head><title>Service Unavailable</title></head>"
215 "<body><h1>503 Service Unavailable</h1></body>"
216 "</html>";
217
to_string(reply::status_type status)218 std::string to_string(reply::status_type status)
219 {
220 switch (status)
221 {
222 case reply::switching_protocols:
223 return switching_protocols;
224 case reply::download_file:
225 return download_file;
226
227 case reply::ok:
228 return ok;
229 case reply::created:
230 return created;
231 case reply::accepted:
232 return accepted;
233 case reply::no_content:
234 return no_content;
235 case reply::multiple_choices:
236 return multiple_choices;
237 case reply::moved_permanently:
238 return moved_permanently;
239 case reply::moved_temporarily:
240 return moved_temporarily;
241 case reply::not_modified:
242 return not_modified;
243 case reply::bad_request:
244 return bad_request;
245 case reply::unauthorized:
246 return unauthorized;
247 case reply::forbidden:
248 return forbidden;
249 case reply::not_found:
250 return not_found;
251 case reply::internal_server_error:
252 return internal_server_error;
253 case reply::not_implemented:
254 return not_implemented;
255 case reply::bad_gateway:
256 return bad_gateway;
257 case reply::service_unavailable:
258 return service_unavailable;
259 default:
260 return internal_server_error;
261 }
262 }
263
264 } // namespace stock_replies
265
stock_reply(reply::status_type status)266 reply reply::stock_reply(reply::status_type status)
267 {
268 reply rep;
269 rep.status = status;
270 rep.content = stock_replies::to_string(status);
271 if (!rep.content.empty()) { // response can be empty (eg. HTTP 304)
272 rep.headers.resize(2);
273 rep.headers[0].name = "Content-Length";
274 rep.headers[0].value = std::to_string(rep.content.size());
275 rep.headers[1].name = "Content-Type";
276 rep.headers[1].value = "text/html";
277 }
278 return rep;
279 }
280
add_header(reply * rep,const std::string & name,const std::string & value,bool replace)281 void reply::add_header(reply *rep, const std::string &name, const std::string &value, bool replace)
282 {
283 size_t num = rep->headers.size();
284 if (replace) {
285 for (size_t h = 0; h < num; h++) {
286 if (boost::iequals(rep->headers[h].name, name)) {
287 rep->headers[h].value = value;
288 return;
289 }
290 }
291 }
292 rep->headers.resize(num + 1);
293 rep->headers[num].name = name;
294 rep->headers[num].value = value;
295 }
296
add_header_if_absent(reply * rep,const std::string & name,const std::string & value)297 void reply::add_header_if_absent(reply *rep, const std::string &name, const std::string &value) {
298 size_t num = rep->headers.size();
299 for (size_t h = 0; h < num; h++) {
300 if (boost::iequals(rep->headers[h].name, name)) {
301 // is present
302 return;
303 }
304 }
305 add_header(rep, name, value, false);
306 }
307
set_content(reply * rep,const std::string & content)308 void reply::set_content(reply *rep, const std::string & content) {
309 rep->content.assign(content);
310 }
311
set_content(reply * rep,const std::wstring & content_w)312 void reply::set_content(reply *rep, const std::wstring & content_w) {
313 cUTF utf( content_w.c_str() );
314 rep->content.assign(utf.get8(), strlen(utf.get8()));
315 }
316
set_content_from_file(reply * rep,const std::string & file_path)317 bool reply::set_content_from_file(reply *rep, const std::string & file_path) {
318 std::ifstream file(file_path.c_str(), std::ios::in | std::ios::binary);
319 if (!file.is_open())
320 return false;
321 file.seekg(0, std::ios::end);
322 size_t fileSize = (size_t)file.tellg();
323 if (fileSize > 0) {
324 rep->content.resize(fileSize);
325 file.seekg(0, std::ios::beg);
326 file.read(&rep->content[0], rep->content.size());
327 }
328 file.close();
329 return true;
330 }
331
set_content_from_file(reply * rep,const std::string & file_path,const std::string & attachment,bool set_content_type)332 bool reply::set_content_from_file(reply *rep, const std::string & file_path, const std::string & attachment, bool set_content_type) {
333 if (!reply::set_content_from_file(rep, file_path))
334 return false;
335 reply::add_header_attachment(rep, attachment);
336 if (set_content_type == true) {
337 std::size_t last_dot_pos = attachment.find_last_of(".");
338 if (last_dot_pos != std::string::npos) {
339 std::string file_extension = attachment.substr(last_dot_pos + 1);
340 std::string mime_type = mime_types::extension_to_type(file_extension);
341 if ((mime_type.find("text/") != std::string::npos) ||
342 (mime_type.find("/xml") != std::string::npos) ||
343 (mime_type.find("/javascript") != std::string::npos) ||
344 (mime_type.find("/json") != std::string::npos)) {
345 // Add charset on text content
346 mime_type += ";charset=UTF-8";
347 }
348 reply::add_header_content_type(rep, mime_type);
349 }
350 }
351 return true;
352 }
353
set_download_file(reply * rep,const std::string & file_path,const std::string & attachment)354 bool reply::set_download_file(reply* rep, const std::string& file_path, const std::string& attachment)
355 {
356 if (file_path.empty() || attachment.empty())
357 return false;
358 rep->reset();
359 rep->status = reply::status_type::download_file;
360 rep->content = file_path + "\r\n" + attachment;
361 return true;
362 }
363
add_header_attachment(reply * rep,const std::string & attachment)364 void reply::add_header_attachment(reply *rep, const std::string & attachment) {
365 reply::add_header(rep, "Content-Disposition", "attachment; filename=" + attachment);
366 }
367
368 /*
369 RFC-7231
370 Hypertext Transfer Protocol (HTTP/1.1): Semantics and Content
371 3.1.1.5. Content-Type
372
373 A sender that generates a message containing a payload body SHOULD
374 generate a Content-Type header field in that message unless the
375 intended media type of the enclosed representation is unknown to the
376 sender.
377 */
add_header_content_type(reply * rep,const std::string & content_type)378 void reply::add_header_content_type(reply *rep, const std::string & content_type) {
379 if (!content_type.empty())
380 reply::add_header(rep, "Content-Type", content_type);
381 }
382
383 } // namespace server
384 } // namespace http
385