1 /*
2 * Copyright (C) 2008 Emweb bv, Herent, Belgium.
3 *
4 * All rights reserved.
5 */
6 //
7 // reply.cpp
8 // ~~~~~~~~~
9 //
10 // Copyright (c) 2003-2006 Christopher M. Kohlhoff (chris at kohlhoff dot com)
11 //
12 // Distributed under the Boost Software License, Version 1.0. (See accompanying
13 // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
14 //
15
16 #include "Configuration.h"
17 #include "Connection.h"
18 #include "Reply.h"
19 #include "Request.h"
20 #include "Server.h"
21
22 #include <time.h>
23 #include <cassert>
24 #include <string>
25
26 namespace {
27
my_gmtime_r(const time_t * t,struct tm * r)28 inline struct tm* my_gmtime_r(const time_t* t, struct tm* r)
29 {
30 #ifdef WT_WIN32
31 return gmtime_s(r, t) ? 0 : r;
32 #else // !WT_WIN32
33 return gmtime_r(t, r);
34 #endif // WT_WIN32
35 }
36
37 }
38
39 namespace Wt {
40 LOGGER("wthttp");
41 }
42
43 namespace {
pad2(Wt::WStringStream & buf,int value)44 inline void pad2(Wt::WStringStream& buf, int value) {
45 if (value < 10)
46 buf << '0';
47 buf << value;
48 }
49 }
50
51 namespace http {
52 namespace server {
53
54 template <typename T>
55 inline asio::const_buffer asio_cstring_buf(T str);
56
57 template <std::size_t N>
asio_cstring_buf(const char (& s)[N])58 inline asio::const_buffer asio_cstring_buf(const char (&s) [N])
59 {
60 return asio::const_buffer(s, N-1);
61 }
62
httpDateBuf(time_t t,Wt::WStringStream & buf)63 void httpDateBuf(time_t t, Wt::WStringStream& buf)
64 {
65 struct tm td;
66 my_gmtime_r(&t, &td);
67
68 static const char dayOfWeekStr[7][4]
69 = { "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun" };
70 static const char monthStr[12][4]
71 = { "Jan", "Feb", "Mar", "Apr", "May", "Jun",
72 "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" };
73
74 // Wed, 15 Jan 2014 21:20:01 GMT
75 buf << dayOfWeekStr[td.tm_wday] << ", "
76 << td.tm_mday << ' '
77 << monthStr[td.tm_mon] << ' '
78 << (td.tm_year + 1900) << ' ';
79
80 pad2(buf, td.tm_hour);
81 buf << ':';
82 pad2(buf, td.tm_min);
83 buf << ':';
84 pad2(buf, td.tm_sec);
85 buf << " GMT";
86 }
87
88 namespace status_strings {
89
90 template<class S>
toText(S & stream,Reply::status_type status)91 void toText(S& stream, Reply::status_type status)
92 {
93 switch (status)
94 {
95 case Reply::switching_protocols:
96 stream << "101 Switching Protocol\r\n";
97 break;
98 case Reply::ok:
99 stream << "200 OK\r\n";
100 break;
101 case Reply::created:
102 stream << "201 Created\r\n";
103 break;
104 case Reply::accepted:
105 stream << "202 Accepted\r\n";
106 break;
107 case Reply::no_content:
108 stream << "204 No Content\r\n";
109 break;
110 case Reply::partial_content:
111 stream << "206 Partial Content\r\n";
112 break;
113 case Reply::multiple_choices:
114 stream << "300 Multiple Choices\r\n";
115 break;
116 case Reply::moved_permanently:
117 stream << "301 Moved Permanently\r\n";
118 break;
119 case Reply::found:
120 stream << "302 Found\r\n";
121 break;
122 case Reply::see_other:
123 stream << "303 See Other\r\n";
124 break;
125 case Reply::not_modified:
126 stream << "304 Not Modified\r\n";
127 break;
128 case Reply::moved_temporarily:
129 stream << "307 Moved Temporarily\r\n";
130 break;
131 case Reply::bad_request:
132 stream << "400 Bad Request\r\n";
133 break;
134 case Reply::unauthorized:
135 stream << "401 Unauthorized\r\n";
136 break;
137 case Reply::forbidden:
138 stream << "403 Forbidden\r\n";
139 break;
140 case Reply::not_found:
141 stream << "404 Not Found\r\n";
142 break;
143 case Reply::request_entity_too_large:
144 stream << "413 Request Entity too Large\r\n";
145 break;
146 case Reply::requested_range_not_satisfiable:
147 stream << "416 Requested Range Not Satisfiable\r\n";
148 break;
149 case Reply::not_implemented:
150 stream << "501 Not Implemented\r\n";
151 break;
152 case Reply::bad_gateway:
153 stream << "502 Bad Gateway\r\n";
154 break;
155 case Reply::service_unavailable:
156 stream << "503 Service Unavailable\r\n";
157 break;
158 case Reply::version_not_supported:
159 stream << "505 HTTP Version Not Supported\r\n";
160 break;
161 case Reply::no_status:
162 case Reply::internal_server_error:
163 stream << "500 Internal Server Error\r\n";
164 break;
165 default:
166 stream << (int) status << " Unknown\r\n";
167 }
168 }
169
170 } // namespace status_strings
171
Reply(Request & request,const Configuration & config)172 Reply::Reply(Request& request, const Configuration& config)
173 : request_(request),
174 configuration_(config),
175 status_(no_status),
176 transmitting_(false),
177 closeConnection_(false),
178 chunkedEncoding_(false),
179 gzipEncoding_(false),
180 contentSent_(0),
181 contentOriginalSize_(0)
182 #ifdef WTHTTP_WITH_ZLIB
183 , gzipBusy_(false)
184 #endif // WTHTTP_WITH_ZLIB
185 { }
186
~Reply()187 Reply::~Reply()
188 {
189 LOG_DEBUG("~Reply");
190 #ifdef WTHTTP_WITH_ZLIB
191 if (gzipBusy_)
192 deflateEnd(&gzipStrm_);
193 #endif // WTHTTP_WITH_ZLIB
194 }
195
writeDone(bool success)196 void Reply::writeDone(bool success)
197 { }
198
reset(const Wt::EntryPoint * ep)199 void Reply::reset(const Wt::EntryPoint *ep)
200 {
201 #ifdef WTHTTP_WITH_ZLIB
202 if (gzipBusy_) {
203 deflateEnd(&gzipStrm_);
204 gzipBusy_ = false;
205 }
206 #endif // WTHTTP_WITH_ZLIB
207
208 headers_.clear();
209 status_ = no_status;
210 transmitting_ = false;
211 closeConnection_ = false;
212 chunkedEncoding_ = false;
213 gzipEncoding_ = false;
214 contentSent_ = 0;
215 contentOriginalSize_ = 0;
216
217 relay_.reset();
218 }
219
setStatus(status_type status)220 void Reply::setStatus(status_type status)
221 {
222 status_ = status;
223 }
224
consumeWebSocketMessage(ws_opcode opcode,const char * begin,const char * end,Request::State state)225 bool Reply::consumeWebSocketMessage(ws_opcode opcode,
226 const char* begin,
227 const char* end,
228 Request::State state)
229 {
230 LOG_ERROR("Reply::consumeWebSocketMessage() is pure virtual");
231 assert(false);
232 return false;
233 }
234
location()235 std::string Reply::location()
236 {
237 return std::string();
238 }
239
addHeader(const std::string name,const std::string value)240 void Reply::addHeader(const std::string name, const std::string value)
241 {
242 headers_.push_back(std::make_pair(name, value));
243 }
244
245 namespace {
246
hexLookup(int n)247 inline char hexLookup(int n) {
248 return "0123456789abcdef"[(n & 0xF)];
249 }
250
251 /*
252 * Encode an integer as a hex std::string
253 *
254 * NOTE: Only works for *positive* integers
255 */
hexEncode(int n)256 inline std::string hexEncode(int n) {
257 assert(n >= 0);
258 if (n == 0)
259 return "0";
260 char buffer[sizeof(int) * 2];
261 int i = sizeof(int) * 2;
262 while (n != 0) {
263 --i;
264 buffer[i] = hexLookup(n);
265 n >>= 4;
266 }
267 return std::string(buffer + i, sizeof(int) * 2 - i);
268 }
269
270 }
271
nextWrappedContentBuffers(std::vector<asio::const_buffer> & result)272 bool Reply::nextWrappedContentBuffers(std::vector<asio::const_buffer>& result) {
273 std::vector<asio::const_buffer> contentBuffers;
274 int originalSize;
275 int encodedSize;
276
277 bool lastData = encodeNextContentBuffer(contentBuffers, originalSize,
278 encodedSize);
279
280 contentSent_ += encodedSize;
281 contentOriginalSize_ += originalSize;
282
283 if (chunkedEncoding_) {
284 if (encodedSize || lastData) {
285 buf_ << hexEncode(encodedSize);
286 buf_ << "\r\n";
287
288 buf_.asioBuffers(result);
289
290 if (encodedSize) {
291 result.insert(result.end(),
292 contentBuffers.begin(), contentBuffers.end());
293 postBuf_ << "\r\n";
294
295 if (lastData) {
296 postBuf_ << "0\r\n\r\n";
297 }
298 } else {
299 postBuf_ << "\r\n";
300 }
301
302 postBuf_.asioBuffers(result);
303 } else
304 buf_.asioBuffers(result);
305
306 return lastData;
307 } else {
308 buf_.asioBuffers(result);
309 result.insert(result.end(), contentBuffers.begin(), contentBuffers.end());
310 return lastData;
311 }
312 }
313
nextBuffers(std::vector<asio::const_buffer> & result)314 bool Reply::nextBuffers(std::vector<asio::const_buffer>& result)
315 {
316 bufs_.clear();
317 buf_.clear();
318 postBuf_.clear();
319
320 if (relay_.get())
321 return relay_->nextBuffers(result);
322 else {
323 if (!transmitting_) {
324 transmitting_ = true;
325 bool http10 = (request_.http_version_major == 1)
326 && (request_.http_version_minor == 0);
327
328 closeConnection_ = closeConnection_ || request_.closeConnection();
329
330 /*
331 * Status line.
332 */
333
334 if (http10) {
335 buf_ << "HTTP/1.0 ";
336 } else {
337 buf_ << "HTTP/1.1 ";
338 }
339
340 status_strings::toText(buf_, status_);
341
342 if (!http10 && status_ != switching_protocols) {
343 /*
344 * Date header (current time)
345 */
346 buf_ << "Date: ";
347 httpDateBuf(time(0), buf_);
348 buf_ << "\r\n";
349 }
350
351 /*
352 * Content type or location
353 */
354
355 std::string ct;
356 if (status_ >= 300 && status_ < 400) {
357 if (!location().empty()) {
358 buf_ << "Location: " << location() << "\r\n";
359 }
360 } else if (status_ != not_modified && status_ != switching_protocols) {
361 ct = contentType();
362 buf_ << "Content-Type: " << ct << "\r\n";
363 }
364
365 /*
366 * Other provided headers
367 */
368 bool haveContentEncoding = false;
369 for (unsigned i = 0; i < headers_.size(); ++i) {
370 if (headers_[i].first == "Content-Encoding")
371 haveContentEncoding = true;
372 buf_ << headers_[i].first << ": " << headers_[i].second << "\r\n";
373 }
374
375 ::int64_t cl = -1;
376
377 if (status_ != not_modified)
378 cl = contentLength();
379 else
380 cl = 0;
381
382 /*
383 * We would need to figure out the content length based on the
384 * response data, but this doesn't work: WtReply reuses the
385 * same buffers over and over, expecting them to be sent
386 * inbetween each call to nextContentBuffer()
387 */
388 if ((cl == -1) && http10)
389 closeConnection_ = true;
390
391 /*
392 * Connection
393 */
394 if (closeConnection_ && request_.type == Request::HTTP) {
395 buf_ << "Connection: close\r\n";
396 } else {
397 if (http10) {
398 buf_ << "Connection: keep-alive\r\n";
399 }
400 }
401
402 if (status_ != not_modified) {
403 #ifdef WTHTTP_WITH_ZLIB
404 /*
405 * Content-Encoding: gzip ?
406 */
407 gzipEncoding_ =
408 !haveContentEncoding
409 && configuration_.compression()
410 && request_.acceptGzipEncoding()
411 && (cl == -1)
412 && (ct.find("text/html") != std::string::npos
413 || ct.find("text/plain") != std::string::npos
414 || ct.find("text/javascript") != std::string::npos
415 || ct.find("text/css") != std::string::npos
416 || ct.find("application/xhtml+xml")!= std::string::npos
417 || ct.find("image/svg+xml")!= std::string::npos
418 || ct.find("application/octet")!= std::string::npos
419 || ct.find("text/x-json") != std::string::npos);
420
421 if (gzipEncoding_) {
422 buf_ << "Content-Encoding: gzip\r\n";
423
424 initGzip();
425 }
426 #endif
427
428 /*
429 * We do not need to determine the length of the response...
430 * Transmit only header first.
431 */
432 if (cl != -1) {
433 buf_ << "Content-Length: " << (long long)cl << "\r\n";
434 chunkedEncoding_ = false;
435 } else
436 if (closeConnection_)
437 chunkedEncoding_ = false; // should be false
438 else
439 if (!http10 && status_ != switching_protocols)
440 chunkedEncoding_ = true;
441
442 if (chunkedEncoding_) {
443 buf_ << "Transfer-Encoding: chunked\r\n";
444 }
445
446 buf_ << "\r\n";
447
448 return nextWrappedContentBuffers(result);
449 } else { // status_ == not-modified
450 buf_ << "\r\n";
451
452 buf_.asioBuffers(result);
453 return true;
454 }
455 } else { // transmitting (data)
456 return nextWrappedContentBuffers(result);
457 }
458 }
459
460 assert(false);
461
462 return true;
463 }
464
closeConnection()465 bool Reply::closeConnection() const
466 {
467 if (closeConnection_)
468 return true;
469
470 if (relay_.get())
471 return relay_->closeConnection();
472 else
473 return false;
474 }
475
setConnection(ConnectionPtr connection)476 void Reply::setConnection(ConnectionPtr connection)
477 {
478 connection_ = connection;
479
480 if (relay_.get())
481 relay_->setConnection(connection);
482 }
483
receive()484 void Reply::receive()
485 {
486 connection_->strand().post
487 (std::bind(&Connection::readMore, connection_,
488 shared_from_this(), 120));
489 }
490
send()491 void Reply::send()
492 {
493 if (connection_->waitingResponse())
494 connection_->setHaveResponse();
495 else {
496 LOG_DEBUG("Reply: send(): scheduling write response.");
497
498 // We post this since we want to avoid growing the stack indefinitely
499 connection_->server()->service().post
500 (connection_->strand().wrap
501 (std::bind(&Connection::startWriteResponse, connection_,
502 shared_from_this())));
503 }
504 }
505
detectDisconnect(const std::function<void ()> & callback)506 void Reply::detectDisconnect(const std::function<void()>& callback)
507 {
508 connection_->detectDisconnect(shared_from_this(), callback);
509 }
510
setRelay(ReplyPtr reply)511 void Reply::setRelay(ReplyPtr reply)
512 {
513 if (!transmitting_) {
514 relay_ = reply;
515 relay_->connection_ = connection_;
516 }
517 }
518
logReply(Wt::WLogger & logger)519 void Reply::logReply(Wt::WLogger& logger)
520 {
521 if (relay_.get())
522 return relay_->logReply(logger);
523
524 if (logger.logging("")) {
525 Wt::WLogEntry e = logger.entry("");
526
527 e << request_.remoteIP << Wt::WLogger::sep
528 << /* rfc931 << */ Wt::WLogger::sep
529 << /* authuser << */ Wt::WLogger::sep
530 << Wt::WLogger::timestamp << Wt::WLogger::sep
531 << request_.method.str() << ' '
532 << request_.uri.str() << " HTTP/"
533 << request_.http_version_major << '.'
534 << request_.http_version_minor << Wt::WLogger::sep
535 << status_ << Wt::WLogger::sep
536 << contentSent_;
537
538 /*
539 if (gzipEncoding_)
540 std::cerr << " <" << contentOriginalSize_ << ">";
541 */
542 }
543 }
544
buf(const std::string & s)545 asio::const_buffer Reply::buf(const std::string &s)
546 {
547 bufs_.push_back(s);
548 return asio::buffer(bufs_.back());
549 }
550
httpDate(time_t t)551 std::string Reply::httpDate(time_t t)
552 {
553 Wt::WStringStream s;
554 httpDateBuf(t, s);
555 return s.str();
556 }
557
558 #ifdef WTHTTP_WITH_ZLIB
initGzip()559 void Reply::initGzip()
560 {
561 gzipStrm_.zalloc = Z_NULL;
562 gzipStrm_.zfree = Z_NULL;
563 gzipStrm_.opaque = Z_NULL;
564 gzipStrm_.next_in = Z_NULL;
565 int r = 0;
566 r = deflateInit2(&gzipStrm_, Z_DEFAULT_COMPRESSION,
567 Z_DEFLATED, 15+16, 8, Z_DEFAULT_STRATEGY);
568 gzipBusy_ = true;
569 assert(r == Z_OK);
570 }
571 #endif
572
encodeNextContentBuffer(std::vector<asio::const_buffer> & result,int & originalSize,int & encodedSize)573 bool Reply::encodeNextContentBuffer(
574 std::vector<asio::const_buffer>& result, int& originalSize,
575 int& encodedSize)
576 {
577 std::vector<asio::const_buffer> buffers;
578 bool lastData = nextContentBuffers(buffers);
579
580 originalSize = 0;
581
582 #ifdef WTHTTP_WITH_ZLIB
583 if (gzipEncoding_) {
584 encodedSize = 0;
585
586 if (lastData && buffers.empty())
587 buffers.push_back(asio::buffer((void *)(&encodedSize), 0));
588
589 for (unsigned i = 0; i < buffers.size(); ++i) {
590 const asio::const_buffer& b = buffers[i];
591 int bs = buffer_size(b); // std::size_t ?
592 originalSize += bs;
593
594 gzipStrm_.avail_in = bs;
595 gzipStrm_.next_in = const_cast<unsigned char*>(
596 asio::buffer_cast<const unsigned char*>(b));
597
598 unsigned char out[16*1024];
599 do {
600 gzipStrm_.next_out = out;
601 gzipStrm_.avail_out = sizeof(out);
602
603 int r = 0;
604 r = deflate(&gzipStrm_,
605 lastData && (i == buffers.size() - 1) ?
606 Z_FINISH : Z_NO_FLUSH);
607
608 assert(r != Z_STREAM_ERROR);
609
610 unsigned have = sizeof(out) - gzipStrm_.avail_out;
611
612 if (have) {
613 encodedSize += have;
614 result.push_back(buf(std::string((char *)out, have)));
615 }
616 } while (gzipStrm_.avail_out == 0);
617 }
618
619 if (lastData) {
620 deflateEnd(&gzipStrm_);
621 gzipBusy_ = false;
622 }
623 } else {
624 #endif
625 for (unsigned i = 0; i < buffers.size(); ++i) {
626 const asio::const_buffer& b = buffers[i];
627 int bs = buffer_size(b); // std::size_t ?
628 originalSize += bs;
629
630 if (bs)
631 result.push_back(b);
632 }
633
634 encodedSize = originalSize;
635 #ifdef WTHTTP_WITH_ZLIB
636 return lastData;
637 }
638 #endif
639
640 return lastData;
641 }
642
643 } // namespace server
644 } // namespace http
645