2 +----------------------------------------------------------------------+
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"
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() {
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";
82 const char *m
= getExtendedMethod();
83 return m
? m
: "POST";
91 ///////////////////////////////////////////////////////////////////////////////
94 const char *Transport::getServerObject() {
95 const char *url
= getUrl();
97 if (strncmp(url
, "http://", 7) == 0) {
99 } else if (strncmp(url
, "https://", 8) == 0) {
102 const char *p
= strchr(url
+ strip
, '/');
104 while (*(p
+ 1) == '/') p
++;
107 if (strip
== 0) return url
;
111 string
Transport::getCommand() {
112 const char *url
= getServerObject();
118 while (*url
== '/') {
121 const char *v
= strchr(url
, '?');
123 return string(url
, v
- url
);
128 ///////////////////////////////////////////////////////////////////////////////
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
136 unsigned char *s
= (unsigned char *)value
;
142 } else if (s
[i
] == '%' && isxdigit(s
[i
+1]) && isxdigit(s
[i
+2])) {
144 num
= (s
[i
+1] >= 'A') ? ((s
[i
+1] & 0xdf) - 'A') + 10 : (s
[i
+1] - '0');
146 num
+= (s
[i
+2] >= 'A') ? ((s
[i
+2] & 0xdf) - 'A') + 10 : (s
[i
+2] - '0');
153 if (i
&& o
) s
[o
] = '\0';
156 void Transport::parseQuery(char *query
, ParamMap
¶ms
) {
157 if (!query
|| !*query
) return;
160 char *k
= strtok_r(query
, "&", &l
);
162 char *v
= strchr(k
, '=');
169 if (*v
) urlUnescape(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();
183 const char *p
= strchr(url
, '?');
185 m_url
= strdup(p
+ 1);
190 parseQuery(m_url
, m_getParams
);
194 void Transport::parsePostParams() {
195 if (!m_postDataParsed
) {
196 assert(m_postData
== nullptr);
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) {
215 if (m_getParams
.find(name
) != m_getParams
.end()) {
220 if (method
== POST
|| method
== AUTO
) {
221 if (!m_postDataParsed
) {
224 if (m_postParams
.find(name
) != m_postParams
.end()) {
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) {
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
) {
249 ParamMap::const_iterator iter
= m_postParams
.find(name
);
250 if (iter
!= m_postParams
.end()) {
251 return iter
->second
[0];
258 int Transport::getIntParam(const char *name
, Method method
/* = GET */) {
259 std::string param
= getParam(name
, method
);
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
);
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) {
282 ParamMap::const_iterator iter
= m_getParams
.find(name
);
283 if (iter
!= m_getParams
.end()) {
284 const vector
<const char *> ¶ms
= iter
->second
;
285 values
.insert(values
.end(), params
.begin(), params
.end());
289 if (method
== POST
|| method
== AUTO
) {
290 if (!m_postDataParsed
) {
293 ParamMap::const_iterator iter
= m_postParams
.find(name
);
294 if (iter
!= m_postParams
.end()) {
295 const vector
<const char *> ¶ms
= 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 ///////////////////////////////////////////////////////////////////////////////
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
;
322 } while (*value
== ' ');
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()
344 throw InvalidArgumentException("header", header
.c_str());
347 void Transport::addHeaderNoLock(const char *name
, const char *value
) {
348 assert(name
&& *name
);
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) {
372 setResponse(302, "forced.302");
376 void Transport::addHeader(const char *name
, const char *value
) {
377 assert(name
&& *name
);
379 addHeaderNoLock(name
, value
);
382 void Transport::addHeader(CStrRef header
) {
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
);
393 m_responseHeaders
[name
].clear();
394 addHeaderNoLock(name
, value
);
397 void Transport::replaceHeader(CStrRef header
) {
400 if (splitHeader(header
, name
, value
)) {
401 replaceHeader(name
.data(), value
);
405 void Transport::removeHeader(const char *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
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] == ';') {
448 if (pos
== header
.size() || header
[pos
] == ';') return true;
450 if (pos
< header
.size() && header
[pos
] == '=') return true;
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] == ';') {
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
);
474 bool Transport::decideCompression() {
475 assert(m_compressionDecision
== NotDecidedYet
);
477 if (!RuntimeOption::ForceCompressionURL
.empty() &&
478 getCommand() == RuntimeOption::ForceCompressionURL
) {
479 m_compressionDecision
= HasToCompress
;
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
;
492 m_compressionDecision
= ShouldNotCompress
;
496 std::string
Transport::getHTTPVersion() const {
500 int Transport::getRequestSize() const {
504 void Transport::setMimeType(CStrRef mimeType
) {
505 m_mimeType
= mimeType
.data();
508 String
Transport::getMimeType() {
509 return String(m_mimeType
);
512 ///////////////////////////////////////////////////////////////////////////////
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'");
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'");
533 char *encoded_value
= nullptr;
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());
544 len
+= domain
.size();
547 cookie
.reserve(len
+ 100);
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();
560 cookie
+= name
.data();
562 cookie
+= encoded_value
? encoded_value
: "";
564 if (expire
> 253402300799LL) {
565 raise_warning("Expiry date cannot have a year greater then 9999");
568 cookie
+= "; expires=";
569 String sdt
= DateTime(expire
, true).toString(DateTime::Cookie
);
570 cookie
+= sdt
.data();
580 cookie
+= path
.data();
582 if (!domain
.empty()) {
583 cookie
+= "; domain=";
584 cookie
+= domain
.data();
587 cookie
+= "; secure";
590 cookie
+= "; httponly";
593 m_responseCookies
[name
.data()] = cookie
;
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());
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
);
644 f_openssl_encrypt(ip
, cipher
, key
, k_OPENSSL_RAW_DATA
, iv
);
645 String output
= StringUtil::Base64Encode(iv
+ encrypted
);
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
,
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
) {
674 if (compressed
|| !isCompressionEnabled() ||
675 m_compressionDecision
== ShouldNotCompress
) {
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
,
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
) {
699 Logger::Error("Unable to compress response: level=%d len=%d",
700 RuntimeOption::GzipCompressionLevel
, len
);
707 bool Transport::setHeaderCallback(CVarRef callback
) {
708 if (m_headerCallback
) {
709 // return false if a callback has already been set.
712 m_headerCallback
= callback
;
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
) {
724 if (m_chunkedEncoding
) {
727 } else if (chunked
) {
728 m_chunkedEncoding
= true;
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) {
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
759 prepareHeaders(compressed
, data
, size
);
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);
792 void Transport::redirect(const char *location
, int code
/* = 302 */,
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 ///////////////////////////////////////////////////////////////////////////////
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
) {
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());
838 Logger::Verbose("Successfully moved uploaded file %s to %s.",
839 filename
.c_str(), dest
.c_str());
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());
848 return moveUploadedFileHelper(filename
, destination
);
851 ///////////////////////////////////////////////////////////////////////////////
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";
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 ///////////////////////////////////////////////////////////////////////////////