make #includes consistent
[hiphop-php.git] / hphp / runtime / base / server / transport.cpp
blobffde036483f92a4e591ef2861370400427ae9fb8
1 /*
2 +----------------------------------------------------------------------+
3 | HipHop for PHP |
4 +----------------------------------------------------------------------+
5 | Copyright (c) 2010- Facebook, Inc. (http://www.facebook.com) |
6 +----------------------------------------------------------------------+
7 | This source file is subject to version 3.01 of the PHP license, |
8 | that is bundled with this package in the file LICENSE, and is |
9 | available through the world-wide-web at the following url: |
10 | http://www.php.net/license/3_01.txt |
11 | If you did not receive a copy of the PHP license and are unable to |
12 | obtain it through the world-wide-web, please send a note to |
13 | license@php.net so we can mail you a copy immediately. |
14 +----------------------------------------------------------------------+
17 #include "hphp/runtime/base/server/transport.h"
18 #include "hphp/runtime/base/server/server.h"
19 #include "hphp/runtime/base/server/upload.h"
20 #include "hphp/runtime/base/server/server_stats.h"
21 #include "hphp/runtime/base/file/file.h"
22 #include "hphp/runtime/base/string_util.h"
23 #include "hphp/runtime/base/time/datetime.h"
24 #include "hphp/runtime/base/zend/zend_url.h"
25 #include "hphp/runtime/base/runtime_option.h"
26 #include "hphp/runtime/base/server/access_log.h"
27 #include "hphp/runtime/ext/ext_openssl.h"
28 #include "hphp/util/compression.h"
29 #include "hphp/util/util.h"
30 #include "hphp/util/logger.h"
31 #include "hphp/util/compatibility.h"
32 #include "hphp/runtime/base/hardware_counter.h"
34 namespace HPHP {
35 ///////////////////////////////////////////////////////////////////////////////
37 Transport::Transport()
38 : m_instructions(0), m_url(nullptr), m_postData(nullptr), m_postDataParsed(false),
39 m_chunkedEncoding(false), m_headerSent(false),
40 m_headerCallback(uninit_null()), m_headerCallbackDone(false),
41 m_responseCode(-1), m_firstHeaderSet(false), m_firstHeaderLine(0),
42 m_responseSize(0), m_responseTotalSize(0), m_responseSentSize(0),
43 m_flushTimeUs(0), m_sendContentType(true),
44 m_compression(true), m_compressor(nullptr), m_isSSL(false),
45 m_compressionDecision(NotDecidedYet), m_threadType(RequestThread) {
46 memset(&m_queueTime, 0, sizeof(m_queueTime));
47 memset(&m_wallTime, 0, sizeof(m_wallTime));
48 memset(&m_cpuTime, 0, sizeof(m_cpuTime));
49 m_chunksSentSizes.clear();
52 Transport::~Transport() {
53 if (m_url) {
54 free(m_url);
56 if (m_postData) {
57 free(m_postData);
59 if (m_compressor) {
60 delete m_compressor;
62 m_chunksSentSizes.clear();
65 void Transport::onRequestStart(const timespec &queueTime) {
66 m_queueTime = queueTime;
67 gettime(CLOCK_MONOTONIC, &m_wallTime);
68 gettime(CLOCK_THREAD_CPUTIME_ID, &m_cpuTime);
70 * The hardware counter is only 48 bits, so reset this at the beginning
71 * of every request to make sure we don't overflow.
73 HardwareCounter::Reset();
74 m_instructions = HardwareCounter::GetInstructionCount();
77 const char *Transport::getMethodName() {
78 switch (getMethod()) {
79 case GET: return "GET";
80 case HEAD: return "HEAD";
81 case POST: {
82 const char *m = getExtendedMethod();
83 return m ? m : "POST";
85 default:
86 break;
88 return "";
91 ///////////////////////////////////////////////////////////////////////////////
92 // url
94 const char *Transport::getServerObject() {
95 const char *url = getUrl();
96 int strip = 0;
97 if (strncmp(url, "http://", 7) == 0) {
98 strip = 7;
99 } else if (strncmp(url, "https://", 8) == 0) {
100 strip = 8;
102 const char *p = strchr(url + strip, '/');
103 if (p) {
104 while (*(p + 1) == '/') p++;
105 return p;
107 if (strip == 0) return url;
108 return "";
111 string Transport::getCommand() {
112 const char *url = getServerObject();
113 assert(url);
114 if (!*url) {
115 return "";
118 while (*url == '/') {
119 ++url;
121 const char *v = strchr(url, '?');
122 if (v) {
123 return string(url, v - url);
125 return url;
128 ///////////////////////////////////////////////////////////////////////////////
129 // parameters
131 // copied and re-factored from clearsilver-0.10.5/cgi/cgi.c
132 void Transport::urlUnescape(char *value) {
133 assert(value && *value); // check before calling this function
135 int i = 0, o = 0;
136 unsigned char *s = (unsigned char *)value;
138 while (s[i]) {
139 if (s[i] == '+') {
140 s[o++] = ' ';
141 i++;
142 } else if (s[i] == '%' && isxdigit(s[i+1]) && isxdigit(s[i+2])) {
143 char num;
144 num = (s[i+1] >= 'A') ? ((s[i+1] & 0xdf) - 'A') + 10 : (s[i+1] - '0');
145 num *= 16;
146 num += (s[i+2] >= 'A') ? ((s[i+2] & 0xdf) - 'A') + 10 : (s[i+2] - '0');
147 s[o++] = num;
148 i+=3;
149 } else {
150 s[o++] = s[i++];
153 if (i && o) s[o] = '\0';
156 void Transport::parseQuery(char *query, ParamMap &params) {
157 if (!query || !*query) return;
159 char *l;
160 char *k = strtok_r(query, "&", &l);
161 while (k && *k) {
162 char *v = strchr(k, '=');
163 const char *fv;
164 if (v == nullptr) {
165 fv = "";
166 } else {
167 *v = '\0';
168 v++;
169 if (*v) urlUnescape(v);
170 fv = v;
172 if (*k) urlUnescape(k);
173 params[k].push_back(fv);
174 k = strtok_r(nullptr, "&", &l);
178 void Transport::parseGetParams() {
179 if (m_url == nullptr) {
180 const char *url = getServerObject();
181 assert(url);
183 const char *p = strchr(url, '?');
184 if (p) {
185 m_url = strdup(p + 1);
186 } else {
187 m_url = strdup("");
190 parseQuery(m_url, m_getParams);
194 void Transport::parsePostParams() {
195 if (!m_postDataParsed) {
196 assert(m_postData == nullptr);
197 int size;
198 const char *data = (const char *)getPostData(size);
199 if (data && *data && size) {
200 // Post data may be binary, but if parsePostParams() is called, it is
201 // correct to handle it as a null-terminated string
202 m_postData = strdup(data);
203 parseQuery(m_postData, m_postParams);
205 m_postDataParsed = true;
209 bool Transport::paramExists(const char *name, Method method /* = GET */) {
210 assert(name && *name);
211 if (method == GET || method == AUTO) {
212 if (m_url == nullptr) {
213 parseGetParams();
215 if (m_getParams.find(name) != m_getParams.end()) {
216 return true;
220 if (method == POST || method == AUTO) {
221 if (!m_postDataParsed) {
222 parsePostParams();
224 if (m_postParams.find(name) != m_postParams.end()) {
225 return true;
229 return false;
232 std::string Transport::getParam(const char *name, Method method /* = GET */) {
233 assert(name && *name);
235 if (method == GET || method == AUTO) {
236 if (m_url == nullptr) {
237 parseGetParams();
239 ParamMap::const_iterator iter = m_getParams.find(name);
240 if (iter != m_getParams.end()) {
241 return iter->second[0];
245 if (method == POST || method == AUTO) {
246 if (!m_postDataParsed) {
247 parsePostParams();
249 ParamMap::const_iterator iter = m_postParams.find(name);
250 if (iter != m_postParams.end()) {
251 return iter->second[0];
255 return "";
258 int Transport::getIntParam(const char *name, Method method /* = GET */) {
259 std::string param = getParam(name, method);
260 if (param.empty()) {
261 return 0;
263 return atoi(param.c_str());
266 long long Transport::getInt64Param(const char *name,
267 Method method /* = GET */) {
268 std::string param = getParam(name, method);
269 if (param.empty()) {
270 return 0;
272 return atoll(param.c_str());
275 void Transport::getArrayParam(const char *name,
276 std::vector<std::string> &values,
277 Method method /* = GET */) {
278 if (method == GET || method == AUTO) {
279 if (m_url == nullptr) {
280 parseGetParams();
282 ParamMap::const_iterator iter = m_getParams.find(name);
283 if (iter != m_getParams.end()) {
284 const vector<const char *> &params = iter->second;
285 values.insert(values.end(), params.begin(), params.end());
289 if (method == POST || method == AUTO) {
290 if (!m_postDataParsed) {
291 parsePostParams();
293 ParamMap::const_iterator iter = m_postParams.find(name);
294 if (iter != m_postParams.end()) {
295 const vector<const char *> &params = iter->second;
296 values.insert(values.end(), params.begin(), params.end());
301 void Transport::getSplitParam(const char *name,
302 std::vector<std::string> &values,
303 char delimiter, Method method /* = GET */) {
304 std::string param = getParam(name, method);
305 if (!param.empty()) {
306 Util::split(delimiter, param.c_str(), values);
310 ///////////////////////////////////////////////////////////////////////////////
311 // headers
313 bool Transport::splitHeader(CStrRef header, String &name, const char *&value) {
314 int pos = header.find(':');
316 if (pos != String::npos) {
317 name = header.substr(0, pos);
318 value = header.data() + pos;
320 do {
321 value++;
322 } while (*value == ' ');
324 return true;
327 // header("HTTP/1.0 404 Not Found");
328 // header("HTTP/1.0 404");
329 if (strncasecmp(header.data(), "http/", 5) == 0) {
330 int pos1 = header.find(' ');
331 if (pos1 != String::npos) {
332 int pos2 = header.find(' ', pos1 + 1);
333 if (pos2 == String::npos) pos2 = header.size();
334 if (pos2 - pos1 > 1) {
335 setResponse(atoi(header.data() + pos1),
336 getResponseInfo().empty() ? "splitHeader"
337 : getResponseInfo().c_str()
339 return false;
344 throw InvalidArgumentException("header", header.c_str());
347 void Transport::addHeaderNoLock(const char *name, const char *value) {
348 assert(name && *name);
349 assert(value);
351 if (!m_firstHeaderSet) {
352 m_firstHeaderSet = true;
353 m_firstHeaderFile = g_vmContext->getContainingFileName().data();
354 m_firstHeaderLine = g_vmContext->getLine();
357 string svalue = value;
358 Util::replaceAll(svalue, "\n", "");
359 m_responseHeaders[name].push_back(svalue);
361 if (strcasecmp(name, "Location") == 0 && m_responseCode != 201 &&
362 !(m_responseCode >= 300 && m_responseCode <=307)) {
363 /* Zend seems to set 303 on a post with HTTP version > 1.0 in the code but
364 * in our testing we can only get it to give 302.
365 Method m = getMethod();
366 if (m != GET && m != HEAD) {
367 setResponse(303);
368 } else {
369 setResponse(302);
372 setResponse(302, "forced.302");
376 void Transport::addHeader(const char *name, const char *value) {
377 assert(name && *name);
378 assert(value);
379 addHeaderNoLock(name, value);
382 void Transport::addHeader(CStrRef header) {
383 String name;
384 const char *value;
385 if (splitHeader(header, name, value)) {
386 addHeader(name.data(), value);
390 void Transport::replaceHeader(const char *name, const char *value) {
391 assert(name && *name);
392 assert(value);
393 m_responseHeaders[name].clear();
394 addHeaderNoLock(name, value);
397 void Transport::replaceHeader(CStrRef header) {
398 String name;
399 const char *value;
400 if (splitHeader(header, name, value)) {
401 replaceHeader(name.data(), value);
405 void Transport::removeHeader(const char *name) {
406 if (name && *name) {
407 m_responseHeaders.erase(name);
408 if (strcasecmp(name, "Set-Cookie") == 0) {
409 m_responseCookies.clear();
414 void Transport::removeAllHeaders() {
415 m_responseHeaders.clear();
416 m_responseCookies.clear();
419 void Transport::getResponseHeaders(HeaderMap &headers) {
420 headers = m_responseHeaders;
422 vector<string> &cookies = headers["Set-Cookie"];
423 for (CookieMap::const_iterator iter = m_responseCookies.begin();
424 iter != m_responseCookies.end(); ++iter) {
425 cookies.push_back(iter->second);
429 bool Transport::acceptEncoding(const char *encoding) {
430 assert(encoding && *encoding);
431 string header = getHeader("Accept-Encoding");
433 // This is testing a substring than a word match, but in practice, this
434 // always works.
435 return header.find(encoding) != string::npos;
438 bool Transport::cookieExists(const char *name) {
439 assert(name && *name);
440 string header = getHeader("Cookie");
441 int len = strlen(name);
442 bool hasValue = (strchr(name, '=') != nullptr);
443 for (size_t pos = header.find(name); pos != string::npos;
444 pos = header.find(name, pos + 1)) {
445 if (pos == 0 || isspace(header[pos-1]) || header[pos-1] == ';') {
446 pos += len;
447 if (hasValue) {
448 if (pos == header.size() || header[pos] == ';') return true;
449 } else {
450 if (pos < header.size() && header[pos] == '=') return true;
454 return false;
457 string Transport::getCookie(const string &name) {
458 assert(!name.empty());
459 string header = getHeader("Cookie");
460 for (size_t pos = header.find(name); pos != string::npos;
461 pos = header.find(name, pos + 1)) {
462 if (pos == 0 || isspace(header[pos-1]) || header[pos-1] == ';') {
463 pos += name.size();
464 if (pos < header.size() && header[pos] == '=') {
465 size_t end = header.find(';', pos + 1);
466 if (end != string::npos) end -= pos + 1;
467 return header.substr(pos + 1, end);
471 return "";
474 bool Transport::decideCompression() {
475 assert(m_compressionDecision == NotDecidedYet);
477 if (!RuntimeOption::ForceCompressionURL.empty() &&
478 getCommand() == RuntimeOption::ForceCompressionURL) {
479 m_compressionDecision = HasToCompress;
480 return true;
483 if (acceptEncoding("gzip") ||
484 (!RuntimeOption::ForceCompressionCookie.empty() &&
485 cookieExists(RuntimeOption::ForceCompressionCookie.c_str())) ||
486 (!RuntimeOption::ForceCompressionParam.empty() &&
487 paramExists(RuntimeOption::ForceCompressionParam.c_str()))) {
488 m_compressionDecision = ShouldCompress;
489 return true;
492 m_compressionDecision = ShouldNotCompress;
493 return false;
496 std::string Transport::getHTTPVersion() const {
497 return "1.1";
500 int Transport::getRequestSize() const {
501 return 0;
504 void Transport::setMimeType(CStrRef mimeType) {
505 m_mimeType = mimeType.data();
508 String Transport::getMimeType() {
509 return String(m_mimeType);
512 ///////////////////////////////////////////////////////////////////////////////
513 // cookies
515 bool Transport::setCookie(CStrRef name, CStrRef value, int64_t expire /* = 0 */,
516 CStrRef path /* = "" */, CStrRef domain /* = "" */,
517 bool secure /* = false */,
518 bool httponly /* = false */,
519 bool encode_url /* = true */) {
520 if (!name.empty() && strpbrk(name.data(), "=,; \t\r\n\013\014")) {
521 Logger::Warning("Cookie names can not contain any of the following "
522 "'=,; \\t\\r\\n\\013\\014'");
523 return false;
526 if (!encode_url &&
527 !value.empty() && strpbrk(value.data(), ",; \t\r\n\013\014")) {
528 Logger::Warning("Cookie values can not contain any of the following "
529 "',; \\t\\r\\n\\013\\014'");
530 return false;
533 char *encoded_value = nullptr;
534 int len = 0;
535 if (!value.empty() && encode_url) {
536 int encoded_value_len = value.size();
537 encoded_value = url_encode(value.data(), encoded_value_len);
538 len += encoded_value_len;
539 } else if (!value.empty()) {
540 encoded_value = strdup(value.data());
541 len += value.size();
543 len += path.size();
544 len += domain.size();
546 std::string cookie;
547 cookie.reserve(len + 100);
548 if (value.empty()) {
550 * MSIE doesn't delete a cookie when you set it to a null value
551 * so in order to force cookies to be deleted, even on MSIE, we
552 * pick an expiry date in the past
554 String sdt = DateTime(1, true)
555 .toString(DateTime::Cookie);
556 cookie += name.data();
557 cookie += "=deleted; expires=";
558 cookie += sdt.data();
559 } else {
560 cookie += name.data();
561 cookie += "=";
562 cookie += encoded_value ? encoded_value : "";
563 if (expire > 0) {
564 if (expire > 253402300799LL) {
565 raise_warning("Expiry date cannot have a year greater then 9999");
566 return false;
568 cookie += "; expires=";
569 String sdt = DateTime(expire, true).toString(DateTime::Cookie);
570 cookie += sdt.data();
574 if (encoded_value) {
575 free(encoded_value);
578 if (!path.empty()) {
579 cookie += "; path=";
580 cookie += path.data();
582 if (!domain.empty()) {
583 cookie += "; domain=";
584 cookie += domain.data();
586 if (secure) {
587 cookie += "; secure";
589 if (httponly) {
590 cookie += "; httponly";
593 m_responseCookies[name.data()] = cookie;
594 return true;
597 ///////////////////////////////////////////////////////////////////////////////
599 void Transport::prepareHeaders(bool compressed, const void *data, int size) {
600 for (HeaderMap::const_iterator iter = m_responseHeaders.begin();
601 iter != m_responseHeaders.end(); ++iter) {
602 const vector<string> &values = iter->second;
603 for (unsigned int i = 0; i < values.size(); i++) {
604 addHeaderImpl(iter->first.c_str(), values[i].c_str());
608 for (CookieMap::const_iterator iter = m_responseCookies.begin();
609 iter != m_responseCookies.end(); ++iter) {
610 addHeaderImpl("Set-Cookie", iter->second.c_str());
613 if (compressed) {
614 addHeaderImpl("Content-Encoding", "gzip");
615 removeHeaderImpl("Content-Length");
616 if (m_responseHeaders.find("Content-MD5") != m_responseHeaders.end()) {
617 String response((const char *)data, size, AttachLiteral);
618 removeHeaderImpl("Content-MD5");
619 addHeaderImpl("Content-MD5", StringUtil::Base64Encode(
620 StringUtil::MD5(response, true)).c_str());
624 if (m_responseHeaders.find("Content-Type") == m_responseHeaders.end() &&
625 m_responseCode != 304) {
626 string contentType = "text/html; charset="
627 + RuntimeOption::DefaultCharsetName;
628 addHeaderImpl("Content-Type", contentType.c_str());
631 if (RuntimeOption::ExposeHPHP) {
632 addHeaderImpl("X-Powered-By", "HPHP");
635 if ((RuntimeOption::ExposeXFBServer || RuntimeOption::ExposeXFBDebug) &&
636 !RuntimeOption::XFBDebugSSLKey.empty() &&
637 m_responseHeaders.find("X-FB-Debug") == m_responseHeaders.end()) {
638 String ip = RuntimeOption::ServerPrimaryIP;
639 String key = RuntimeOption::XFBDebugSSLKey;
640 String cipher("AES-256-CBC");
641 int iv_len = f_openssl_cipher_iv_length(cipher);
642 String iv = f_openssl_random_pseudo_bytes(iv_len);
643 String encrypted =
644 f_openssl_encrypt(ip, cipher, key, k_OPENSSL_RAW_DATA, iv);
645 String output = StringUtil::Base64Encode(iv + encrypted);
646 if (debug) {
647 String decrypted =
648 f_openssl_decrypt(encrypted, cipher, key, k_OPENSSL_RAW_DATA, iv);
649 assert(decrypted->same(ip.get()));
651 addHeaderImpl("X-FB-Debug", output.c_str());
654 // shutting down servers, so need to terminate all Keep-Alive connections
655 if (!RuntimeOption::EnableKeepAlive || isServerStopping()) {
656 addHeaderImpl("Connection", "close");
657 removeHeaderImpl("Keep-Alive");
659 // so lower level transports can ignore incoming "Connection: keep-alive"
660 removeRequestHeaderImpl("Connection");
664 String Transport::prepareResponse(const void *data, int size, bool &compressed,
665 bool last) {
666 String response((const char *)data, size, AttachLiteral);
668 // we don't use chunk encoding to send anything pre-compressed
669 assert(!compressed || !m_chunkedEncoding);
671 if (m_compressionDecision == NotDecidedYet) {
672 decideCompression();
674 if (compressed || !isCompressionEnabled() ||
675 m_compressionDecision == ShouldNotCompress) {
676 return response;
679 // There isn't that much need to gzip response, when it can fit into one
680 // Ethernet packet (1500 bytes), unless we are doing chunked encoding,
681 // where we don't really know if next chunk will benefit from compresseion.
682 if (m_chunkedEncoding || size > 1000 ||
683 m_compressionDecision == HasToCompress) {
684 if (m_compressor == nullptr) {
685 m_compressor = new StreamCompressor(RuntimeOption::GzipCompressionLevel,
686 CODING_GZIP, true);
688 int len = size;
689 char *compressedData =
690 m_compressor->compress((const char*)data, len, last);
691 if (compressedData) {
692 String deleter(compressedData, len, AttachString);
693 if (m_chunkedEncoding || len < size ||
694 m_compressionDecision == HasToCompress) {
695 response = deleter;
696 compressed = true;
698 } else {
699 Logger::Error("Unable to compress response: level=%d len=%d",
700 RuntimeOption::GzipCompressionLevel, len);
704 return response;
707 bool Transport::setHeaderCallback(CVarRef callback) {
708 if (m_headerCallback) {
709 // return false if a callback has already been set.
710 return false;
712 m_headerCallback = callback;
713 return true;
716 void Transport::sendRawLocked(void *data, int size, int code /* = 200 */,
717 bool compressed /* = false */,
718 bool chunked /* = false */,
719 const char *codeInfo /* = "" */
721 if (!compressed && RuntimeOption::ForceChunkedEncoding) {
722 chunked = true;
724 if (m_chunkedEncoding) {
725 chunked = true;
726 assert(!compressed);
727 } else if (chunked) {
728 m_chunkedEncoding = true;
729 assert(!compressed);
732 // I don't think there is any need to send an empty chunk, other than sending
733 // out headers earlier, which seems to be a useless feature.
734 if (chunked && size == 0) {
735 return;
738 if (!m_headerCallbackDone && !m_headerCallback.isNull()) {
739 // We could use m_headerSent here, however it seems we can still
740 // end up in an infinite loop when:
741 // m_headerCallback calls flush()
742 // flush() triggers php's recursion guard
743 // the recursion guard calls back into m_headerCallback
744 m_headerCallbackDone = true;
745 vm_call_user_func(m_headerCallback, null_array);
748 // compression handling
749 ServerStatsHelper ssh("send");
750 String response = prepareResponse(data, size, compressed, !chunked);
752 if (m_responseCode < 0) {
753 m_responseCode = code;
754 m_responseCodeInfo = codeInfo ? codeInfo: "";
757 // HTTP header handling
758 if (!m_headerSent) {
759 prepareHeaders(compressed, data, size);
760 m_headerSent = true;
763 m_responseSize += response.size();
764 ServerStats::SetThreadMode(ServerStats::Writing);
765 sendImpl(response.data(), response.size(), m_responseCode, chunked);
766 ServerStats::SetThreadMode(ServerStats::Processing);
768 ServerStats::LogBytes(size);
769 if (RuntimeOption::EnableStats && RuntimeOption::EnableWebStats) {
770 ServerStats::Log("network.uncompressed", size);
771 ServerStats::Log("network.compressed", response.size());
775 void Transport::sendRaw(void *data, int size, int code /* = 200 */,
776 bool compressed /* = false */,
777 bool chunked /* = false */,
778 const char *codeInfo /* = "" */
780 sendRawLocked(data, size, code, compressed, chunked, codeInfo);
783 void Transport::onSendEnd() {
784 if (m_compressor && m_chunkedEncoding) {
785 bool compressed = false;
786 String response = prepareResponse("", 0, compressed, true);
787 sendImpl(response.data(), response.size(), m_responseCode, true);
789 onSendEndImpl();
792 void Transport::redirect(const char *location, int code /* = 302 */,
793 const char *info) {
794 addHeaderImpl("Location", location);
795 setResponse(code, info);
796 sendStringLocked("Moved", code);
799 void Transport::onFlushProgress(int writtenSize, int64_t delayUs) {
800 m_responseSentSize += writtenSize;
801 m_flushTimeUs += delayUs;
802 m_chunksSentSizes.push_back(writtenSize);
805 void Transport::onChunkedProgress(int writtenSize) {
806 m_responseSentSize += writtenSize;
807 m_chunksSentSizes.push_back(writtenSize);
810 void Transport::getChunkSentSizes(Array &ret) {
811 for (unsigned int i = 0; i < m_chunksSentSizes.size(); i++) {
812 ret.append(m_chunksSentSizes[i]);
816 int Transport::getLastChunkSentSize() {
817 size_t size = m_chunksSentSizes.size();
818 return size == 0 ? 0 : m_chunksSentSizes.back();
821 ///////////////////////////////////////////////////////////////////////////////
822 // support rfc1867
824 bool Transport::isUploadedFile(CStrRef filename) {
825 return is_uploaded_file(filename.c_str());
828 // Move a file if and only if it was created by an upload
829 bool Transport::moveUploadedFileHelper(CStrRef filename, CStrRef destination) {
830 // Do access check.
831 String dest = File::TranslatePath(destination);
832 if (Util::rename(filename.c_str(), dest.c_str()) < 0) {
833 Logger::Error("Unable to move uploaded file %s to %s: %s.",
834 filename.c_str(), dest.c_str(),
835 Util::safe_strerror(errno).c_str());
836 return false;
838 Logger::Verbose("Successfully moved uploaded file %s to %s.",
839 filename.c_str(), dest.c_str());
840 return true;
843 bool Transport::moveUploadedFile(CStrRef filename, CStrRef destination) {
844 if (!is_uploaded_file(filename.c_str())) {
845 Logger::Error("%s is not an uploaded file.", filename.c_str());
846 return false;
848 return moveUploadedFileHelper(filename, destination);
851 ///////////////////////////////////////////////////////////////////////////////
852 // IDebuggable
854 const char *Transport::getThreadTypeName() const {
855 switch (m_threadType) {
856 case RequestThread: return "Web Request";
857 case PageletThread: return "Pagelet Thread";
858 case XboxThread: return "Xbox Thread";
859 case RpcThread: return "RPC Thread";
861 return "(unknown)";
864 void Transport::debuggerInfo(InfoVec &info) {
865 Add(info, "Thread Type", getThreadTypeName());
866 Add(info, "URL", getCommand());
867 Add(info, "HTTP", getHTTPVersion());
868 Add(info, "Method", getMethodName());
869 if (getMethod() == Transport::POST) {
870 int size; getPostData(size);
871 Add(info, "Post Data", FormatSize(size));
875 ///////////////////////////////////////////////////////////////////////////////