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