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