관리-도구
편집 파일: SendRequest.cpp
/* * Phusion Passenger - https://www.phusionpassenger.com/ * Copyright (c) 2011-2018 Phusion Holding B.V. * * "Passenger", "Phusion Passenger" and "Union Station" are registered * trademarks of Phusion Holding B.V. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ #include <Core/Controller.h> #include <SystemTools/SystemTime.h> /************************************************************************* * * Implements Core::Controller methods pertaining sending request data * to a selected application process. This happens in parallel to forwarding * application response data to the client. * *************************************************************************/ namespace Passenger { namespace Core { using namespace std; using namespace boost; /**************************** * * Private methods * ****************************/ struct Controller::SessionProtocolWorkingState { StaticString path; StaticString queryString; StaticString methodStr; StaticString serverName; StaticString serverPort; const LString *remoteAddr; const LString *remotePort; const LString *remoteUser; const LString *contentType; const LString *contentLength; char *environmentVariablesData; size_t environmentVariablesSize; bool hasBaseURI; SessionProtocolWorkingState() : environmentVariablesData(NULL) { } ~SessionProtocolWorkingState() { free(environmentVariablesData); } }; struct Controller::HttpHeaderConstructionCache { StaticString methodStr; const LString *remoteAddr; const LString *setCookie; bool cached; }; void Controller::sendHeaderToApp(Client *client, Request *req) { TRACE_POINT(); SKC_TRACE(client, 2, "Sending headers to application with " << req->session->getProtocol() << " protocol"); req->state = Request::SENDING_HEADER_TO_APP; P_ASSERT_EQ(req->halfClosePolicy, Request::HALF_CLOSE_POLICY_UNINITIALIZED); if (req->session->getProtocol() == "session") { UPDATE_TRACE_POINT(); if (req->bodyType == Request::RBT_NO_BODY) { // When there is no request body we will try to keep-alive the // application connection, so half-close the application // connection upon encountering the next request's early error // in order not to break the keep-alive. req->halfClosePolicy = Request::HALF_CLOSE_UPON_NEXT_REQUEST_EARLY_READ_ERROR; } else { // When there is a request body we won't try to keep-alive // the application connection, so it's safe to half-close immediately // upon reaching the end of the request body. req->halfClosePolicy = Request::HALF_CLOSE_UPON_REACHING_REQUEST_BODY_END; } sendHeaderToAppWithSessionProtocol(client, req); } else { UPDATE_TRACE_POINT(); if (req->bodyType == Request::RBT_UPGRADE) { req->halfClosePolicy = Request::HALF_CLOSE_UPON_REACHING_REQUEST_BODY_END; } else { // HTTP does not formally support half-closing. Some apps support // HTTP with half-closing, others (such as Node.js http.Server with // default settings) treat a half-close as a full close. Furthermore, // we always try to keep-alive the application connection. // // So we can't half-close immediately upon reaching the end of the // request body. The app might not have yet sent a response by then. // We only half-close upon the next request's early error. req->halfClosePolicy = Request::HALF_CLOSE_UPON_NEXT_REQUEST_EARLY_READ_ERROR; } sendHeaderToAppWithHttpProtocol(client, req); } UPDATE_TRACE_POINT(); if (!req->ended()) { if (req->appSink.acceptingInput()) { UPDATE_TRACE_POINT(); sendBodyToApp(client, req); if (!req->ended()) { req->appSource.startReading(); } } else if (req->appSink.mayAcceptInputLater()) { UPDATE_TRACE_POINT(); SKC_TRACE(client, 3, "Waiting for appSink channel to become " "idle before sending body to application"); req->appSink.setConsumedCallback(sendBodyToAppWhenAppSinkIdle); req->appSource.startReading(); } else { // Either we're done feeding to req->appSink, or req->appSink.feed() // encountered an error while writing to the application socket. // But we don't care about either scenarios; we just care that // ForwardResponse.cpp will now forward the response data and end the // request when it's done. UPDATE_TRACE_POINT(); assert(req->appSink.ended() || req->appSink.hasError()); logAppSocketWriteError(client, req->appSink.getErrcode()); req->state = Request::WAITING_FOR_APP_OUTPUT; req->appSource.startReading(); } } } void Controller::sendHeaderToAppWithSessionProtocol(Client *client, Request *req) { TRACE_POINT(); SessionProtocolWorkingState state; // Workaround for Ruby < 2.1 support. std::string deltaMonotonic; unsigned long long now = SystemTime::getUsec(); MonotonicTimeUsec monotonicNow = SystemTime::getMonotonicUsec(); if (now > monotonicNow) { deltaMonotonic = boost::to_string(now - monotonicNow); } else { long long diff = monotonicNow - now; deltaMonotonic = boost::to_string(-diff); } unsigned int bufferSize = determineMaxHeaderSizeForSessionProtocol(req, state, deltaMonotonic); MemoryKit::mbuf_pool &mbuf_pool = getContext()->mbuf_pool; const unsigned int MBUF_MAX_SIZE = mbuf_pool_data_size(&mbuf_pool); bool ok; if (bufferSize <= MBUF_MAX_SIZE) { MemoryKit::mbuf buffer(MemoryKit::mbuf_get(&mbuf_pool)); bufferSize = MBUF_MAX_SIZE; ok = constructHeaderForSessionProtocol(req, buffer.start, bufferSize, state, deltaMonotonic); assert(ok); buffer = MemoryKit::mbuf(buffer, 0, bufferSize); SKC_TRACE(client, 3, "Header data: \"" << cEscapeString( StaticString(buffer.start, bufferSize)) << "\""); req->appSink.feedWithoutRefGuard(boost::move(buffer)); } else { char *buffer = (char *) psg_pnalloc(req->pool, bufferSize); ok = constructHeaderForSessionProtocol(req, buffer, bufferSize, state, deltaMonotonic); assert(ok); SKC_TRACE(client, 3, "Header data: \"" << cEscapeString( StaticString(buffer, bufferSize)) << "\""); req->appSink.feedWithoutRefGuard(MemoryKit::mbuf( buffer, bufferSize)); } (void) ok; // Shut up compiler warning } void Controller::sendBodyToAppWhenAppSinkIdle(Channel *_channel, unsigned int size) { FdSinkChannel *channel = reinterpret_cast<FdSinkChannel *>(_channel); Request *req = static_cast<Request *>(static_cast< ServerKit::BaseHttpRequest *>(channel->getHooks()->userData)); Client *client = static_cast<Client *>(req->client); Controller *self = static_cast<Controller *>( getServerFromClient(client)); SKC_LOG_EVENT_FROM_STATIC(self, Controller, client, "sendBodyToAppWhenAppSinkIdle"); channel->setConsumedCallback(NULL); if (channel->acceptingInput()) { self->sendBodyToApp(client, req); if (!req->ended()) { req->appSource.startReading(); } } else { // req->appSink.feed() encountered an error while writing to the // application socket. But we don't care about that; we just care that // ForwardResponse.cpp will now forward the response data and end the // request when it's done. UPDATE_TRACE_POINT(); assert(!req->appSink.ended()); assert(req->appSink.hasError()); self->logAppSocketWriteError(client, req->appSink.getErrcode()); req->state = Request::WAITING_FOR_APP_OUTPUT; req->appSource.startReading(); } } static bool isAlphaNum(char ch) { return (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z'); } /** * For CGI, alphanum headers with optional dashes are mapped to UPP3R_CAS3. This * function can be used to reject non-alphanum/dash headers that would end up with * the same mapping (e.g. upp3r_cas3 and upp3r-cas3 would end up the same, and * potentially collide each other in the receiving application). This is * used to fix CVE-2015-7519. */ static bool containsNonAlphaNumDash(const LString &s) { const LString::Part *part = s.start; while (part != NULL) { for (unsigned int i = 0; i < part->size; i++) { const char start = part->data[i]; if (start != '-' && !isAlphaNum(start)) { return true; } } part = part->next; } return false; } static void httpHeaderToScgiUpperCase(unsigned char *data, unsigned int size) { static const boost::uint8_t toUpperMap[256] = { '\0', 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, '\t', '\n', 0x0b, 0x0c, '\r', 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, ' ', '!', '"', '#', '$', '%', '&', '\'', '(', ')', '*', '+', ',', '_', '.', '/', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', ':', ';', '<', '=', '>', '?', '@', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '[', '\\', ']', '^', '_', '`', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '{', '|', '}', '~', 0x7f, 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f, 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f, 0xa0, 0xa1, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, 0xa8, 0xa9, 0xaa, 0xab, 0xac, 0xad, 0xae, 0xaf, 0xb0, 0xb1, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, 0xb8, 0xb9, 0xba, 0xbb, 0xbc, 0xbd, 0xbe, 0xbf, 0xc0, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9, 0xca, 0xcb, 0xcc, 0xcd, 0xce, 0xcf, 0xd0, 0xd1, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda, 0xdb, 0xdc, 0xdd, 0xde, 0xdf, 0xe0, 0xe1, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, 0xeb, 0xec, 0xed, 0xee, 0xef, 0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff }; const unsigned char *buf = data; const size_t imax = size / 8; const size_t leftover = size % 8; size_t i; for (i = 0; i < imax; i++, data += 8) { data[0] = (unsigned char) toUpperMap[data[0]]; data[1] = (unsigned char) toUpperMap[data[1]]; data[2] = (unsigned char) toUpperMap[data[2]]; data[3] = (unsigned char) toUpperMap[data[3]]; data[4] = (unsigned char) toUpperMap[data[4]]; data[5] = (unsigned char) toUpperMap[data[5]]; data[6] = (unsigned char) toUpperMap[data[6]]; data[7] = (unsigned char) toUpperMap[data[7]]; } i = imax * 8; switch (leftover) { case 7: *data++ = (unsigned char) toUpperMap[buf[i++]]; /* Falls through. */ case 6: *data++ = (unsigned char) toUpperMap[buf[i++]]; /* Falls through. */ case 5: *data++ = (unsigned char) toUpperMap[buf[i++]]; /* Falls through. */ case 4: *data++ = (unsigned char) toUpperMap[buf[i++]]; /* Falls through. */ case 3: *data++ = (unsigned char) toUpperMap[buf[i++]]; /* Falls through. */ case 2: *data++ = (unsigned char) toUpperMap[buf[i++]]; /* Falls through. */ case 1: *data++ = (unsigned char) toUpperMap[buf[i]]; /* Falls through. */ case 0: break; } } unsigned int Controller::determineMaxHeaderSizeForSessionProtocol(Request *req, SessionProtocolWorkingState &state, string delta_monotonic) { unsigned int dataSize = sizeof(boost::uint32_t); state.path = req->getPathWithoutQueryString(); state.hasBaseURI = req->options.baseURI != P_STATIC_STRING("/") && startsWith(state.path, req->options.baseURI); if (state.hasBaseURI) { state.path = state.path.substr(req->options.baseURI.size()); if (state.path.empty()) { state.path = P_STATIC_STRING("/"); } } state.queryString = req->getQueryString(); state.methodStr = StaticString(llhttp_method_name(req->method)); state.remoteAddr = req->secureHeaders.lookup(REMOTE_ADDR); state.remotePort = req->secureHeaders.lookup(REMOTE_PORT); state.remoteUser = req->secureHeaders.lookup(REMOTE_USER); state.contentType = req->headers.lookup(HTTP_CONTENT_TYPE); if (req->hasBody()) { state.contentLength = req->headers.lookup(HTTP_CONTENT_LENGTH); } else { state.contentLength = NULL; } if (req->envvars != NULL) { size_t len = modp_b64_decode_len(req->envvars->size); state.environmentVariablesData = (char *) malloc(len); if (state.environmentVariablesData == NULL) { throw RuntimeException("Unable to allocate memory for base64 " "decoding of environment variables"); } len = modp_b64_decode(state.environmentVariablesData, req->envvars->start->data, req->envvars->size); if (len == (size_t) -1) { throw RuntimeException("Unable to base64 decode environment variables"); } state.environmentVariablesSize = len; } dataSize += sizeof("REQUEST_URI"); dataSize += req->path.size + 1; dataSize += sizeof("PATH_INFO"); dataSize += state.path.size() + 1; dataSize += sizeof("SCRIPT_NAME"); if (state.hasBaseURI) { dataSize += req->options.baseURI.size(); } else { dataSize += sizeof(""); } dataSize += sizeof("QUERY_STRING"); dataSize += state.queryString.size() + 1; dataSize += sizeof("REQUEST_METHOD"); dataSize += state.methodStr.size() + 1; if (req->host != NULL && req->host->size > 0) { const LString *host = psg_lstr_make_contiguous(req->host, req->pool); const char *sep = (const char *) memchr(host->start->data, ':', host->size); if (sep != NULL) { state.serverName = StaticString(host->start->data, sep - host->start->data); state.serverPort = StaticString(sep + 1, host->start->data + host->size - sep - 1); } else { state.serverName = StaticString(host->start->data, host->size); if (req->https) { state.serverPort = P_STATIC_STRING("443"); } else { state.serverPort = P_STATIC_STRING("80"); } } } else { state.serverName = req->config->defaultServerName; state.serverPort = req->config->defaultServerPort; } dataSize += sizeof("SERVER_NAME"); dataSize += state.serverName.size() + 1; dataSize += sizeof("SERVER_PORT"); dataSize += state.serverPort.size() + 1; dataSize += sizeof("SERVER_SOFTWARE"); dataSize += req->config->serverSoftware.size() + 1; dataSize += sizeof("SERVER_PROTOCOL"); dataSize += sizeof("HTTP/1.1"); dataSize += sizeof("REMOTE_ADDR"); if (state.remoteAddr != NULL) { dataSize += state.remoteAddr->size + 1; } else { dataSize += sizeof("127.0.0.1"); } dataSize += sizeof("REMOTE_PORT"); if (state.remotePort != NULL) { dataSize += state.remotePort->size + 1; } else { dataSize += sizeof("0"); } if (state.remoteUser != NULL) { dataSize += sizeof("REMOTE_USER"); dataSize += state.remoteUser->size + 1; } if (state.contentType != NULL) { dataSize += sizeof("CONTENT_TYPE"); dataSize += state.contentType->size + 1; } if (state.contentLength != NULL) { dataSize += sizeof("CONTENT_LENGTH"); dataSize += state.contentLength->size + 1; } dataSize += sizeof("PASSENGER_CONNECT_PASSWORD"); dataSize += ApplicationPool2::ApiKey::SIZE + 1; if (req->https) { dataSize += sizeof("HTTPS"); dataSize += sizeof("on"); } if (req->upgraded()) { dataSize += sizeof("HTTP_CONNECTION"); dataSize += sizeof("upgrade"); } ServerKit::HeaderTable::Iterator it(req->headers); while (*it != NULL) { dataSize += sizeof("HTTP_") - 1 + it->header->key.size + 1; dataSize += it->header->val.size + 1; it.next(); } if (state.environmentVariablesData != NULL) { dataSize += state.environmentVariablesSize; } return dataSize + 1; } bool Controller::constructHeaderForSessionProtocol(Request *req, char * restrict buffer, unsigned int &size, const SessionProtocolWorkingState &state, string delta_monotonic) { char *pos = buffer; const char *end = buffer + size; pos += sizeof(boost::uint32_t); pos = appendData(pos, end, P_STATIC_STRING_WITH_NULL("REQUEST_URI")); pos = appendData(pos, end, req->path.start->data, req->path.size); pos = appendData(pos, end, "", 1); pos = appendData(pos, end, P_STATIC_STRING_WITH_NULL("PATH_INFO")); pos = appendData(pos, end, state.path.data(), state.path.size()); pos = appendData(pos, end, "", 1); pos = appendData(pos, end, P_STATIC_STRING_WITH_NULL("SCRIPT_NAME")); if (state.hasBaseURI) { pos = appendData(pos, end, req->options.baseURI); pos = appendData(pos, end, "", 1); } else { pos = appendData(pos, end, P_STATIC_STRING_WITH_NULL("")); } pos = appendData(pos, end, P_STATIC_STRING_WITH_NULL("QUERY_STRING")); pos = appendData(pos, end, state.queryString.data(), state.queryString.size()); pos = appendData(pos, end, "", 1); pos = appendData(pos, end, P_STATIC_STRING_WITH_NULL("REQUEST_METHOD")); pos = appendData(pos, end, state.methodStr); pos = appendData(pos, end, "", 1); pos = appendData(pos, end, P_STATIC_STRING_WITH_NULL("SERVER_NAME")); pos = appendData(pos, end, state.serverName); pos = appendData(pos, end, "", 1); pos = appendData(pos, end, P_STATIC_STRING_WITH_NULL("SERVER_PORT")); pos = appendData(pos, end, state.serverPort); pos = appendData(pos, end, "", 1); pos = appendData(pos, end, P_STATIC_STRING_WITH_NULL("SERVER_SOFTWARE")); pos = appendData(pos, end, req->config->serverSoftware); pos = appendData(pos, end, "", 1); pos = appendData(pos, end, P_STATIC_STRING_WITH_NULL("SERVER_PROTOCOL")); pos = appendData(pos, end, P_STATIC_STRING_WITH_NULL("HTTP/1.1")); pos = appendData(pos, end, P_STATIC_STRING_WITH_NULL("REMOTE_ADDR")); if (state.remoteAddr != NULL) { pos = appendData(pos, end, state.remoteAddr); pos = appendData(pos, end, "", 1); } else { pos = appendData(pos, end, P_STATIC_STRING_WITH_NULL("127.0.0.1")); } pos = appendData(pos, end, P_STATIC_STRING_WITH_NULL("REMOTE_PORT")); if (state.remotePort != NULL) { pos = appendData(pos, end, state.remotePort); pos = appendData(pos, end, "", 1); } else { pos = appendData(pos, end, P_STATIC_STRING_WITH_NULL("0")); } if (state.remoteUser != NULL) { pos = appendData(pos, end, P_STATIC_STRING_WITH_NULL("REMOTE_USER")); pos = appendData(pos, end, state.remoteUser); pos = appendData(pos, end, "", 1); } if (state.contentType != NULL) { pos = appendData(pos, end, P_STATIC_STRING_WITH_NULL("CONTENT_TYPE")); pos = appendData(pos, end, state.contentType); pos = appendData(pos, end, "", 1); } if (state.contentLength != NULL) { pos = appendData(pos, end, P_STATIC_STRING_WITH_NULL("CONTENT_LENGTH")); pos = appendData(pos, end, state.contentLength); pos = appendData(pos, end, "", 1); } pos = appendData(pos, end, P_STATIC_STRING_WITH_NULL("PASSENGER_CONNECT_PASSWORD")); pos = appendData(pos, end, req->session->getApiKey().toStaticString()); pos = appendData(pos, end, "", 1); if (req->https) { pos = appendData(pos, end, P_STATIC_STRING_WITH_NULL("HTTPS")); pos = appendData(pos, end, P_STATIC_STRING_WITH_NULL("on")); } if (req->upgraded()) { pos = appendData(pos, end, P_STATIC_STRING_WITH_NULL("HTTP_CONNECTION")); pos = appendData(pos, end, P_STATIC_STRING_WITH_NULL("upgrade")); } ServerKit::HeaderTable::Iterator it(req->headers); while (*it != NULL) { // This header-skipping is not accounted for in determineMaxHeaderSizeForSessionProtocol(), but // since we are only reducing the size it just wastes some mem bytes. if (( (it->header->hash == HTTP_CONTENT_LENGTH.hash() || it->header->hash == HTTP_CONTENT_TYPE.hash() || it->header->hash == HTTP_CONNECTION.hash() ) && (psg_lstr_cmp(&it->header->key, HTTP_CONTENT_TYPE) || psg_lstr_cmp(&it->header->key, HTTP_CONTENT_LENGTH) || psg_lstr_cmp(&it->header->key, HTTP_CONNECTION) ) ) || containsNonAlphaNumDash(it->header->key) ) { it.next(); continue; } pos = appendData(pos, end, P_STATIC_STRING("HTTP_")); const LString::Part *part = it->header->key.start; while (part != NULL) { char *start = pos; pos = appendData(pos, end, part->data, part->size); httpHeaderToScgiUpperCase((unsigned char *) start, pos - start); part = part->next; } pos = appendData(pos, end, "", 1); part = it->header->val.start; while (part != NULL) { pos = appendData(pos, end, part->data, part->size); part = part->next; } pos = appendData(pos, end, "", 1); it.next(); } if (state.environmentVariablesData != NULL) { pos = appendData(pos, end, state.environmentVariablesData, state.environmentVariablesSize); } Uint32Message::generate(buffer, pos - buffer - sizeof(boost::uint32_t)); size = pos - buffer; return pos < end; } void Controller::sendHeaderToAppWithHttpProtocol(Client *client, Request *req) { ssize_t bytesWritten; HttpHeaderConstructionCache cache; cache.cached = false; if (OXT_UNLIKELY(LoggingKit::getLevel() >= LoggingKit::DEBUG3)) { struct iovec *buffers; unsigned int nbuffers, dataSize; bool ok; ok = constructHeaderBuffersForHttpProtocol(req, NULL, 0, nbuffers, dataSize, cache); assert(ok); buffers = (struct iovec *) psg_palloc(req->pool, sizeof(struct iovec) * nbuffers); ok = constructHeaderBuffersForHttpProtocol(req, buffers, nbuffers, nbuffers, dataSize, cache); assert(ok); (void) ok; // Shut up compiler warning char *buffer = (char *) psg_pnalloc(req->pool, dataSize); gatherBuffers(buffer, dataSize, buffers, nbuffers); SKC_TRACE(client, 3, "Header data: \"" << cEscapeString(StaticString(buffer, dataSize)) << "\""); } if (!sendHeaderToAppWithHttpProtocolAndWritev(req, bytesWritten, cache)) { if (bytesWritten >= 0 || errno == EAGAIN || errno == EWOULDBLOCK) { sendHeaderToAppWithHttpProtocolWithBuffering(req, bytesWritten, cache); } else { int e = errno; P_ASSERT_EQ(bytesWritten, -1); disconnectWithAppSocketWriteError(&client, e); } } } /** * Construct an array of buffers, which together contain the 'http' protocol header * data that should be sent to the application. This method does not copy any data: * it just constructs buffers that point to the data stored inside `req->pool`, * `req->headers`, etc. * * The buffers will be stored in the array pointed to by `buffer`. This array must * have space for at least `maxbuffers` items. The actual number of buffers constructed * is stored in `nbuffers`, and the total data size of the buffers is stored in `dataSize`. * Upon success, returns true. If the actual number of buffers necessary exceeds * `maxbuffers`, then false is returned. * * You can also set `buffers` to NULL, in which case this method will not construct any * buffers, but only count the number of buffers necessary, as well as the total data size. * In this case, this method always returns true. */ bool Controller::constructHeaderBuffersForHttpProtocol(Request *req, struct iovec *buffers, unsigned int maxbuffers, unsigned int & restrict_ref nbuffers, unsigned int & restrict_ref dataSize, HttpHeaderConstructionCache &cache) { #define BEGIN_PUSH_NEXT_BUFFER() \ do { \ if (buffers != NULL && i >= maxbuffers) { \ return false; \ } \ } while (false) #define INC_BUFFER_ITER(i) \ do { \ i++; \ } while (false) #define PUSH_STATIC_BUFFER(buf) \ do { \ BEGIN_PUSH_NEXT_BUFFER(); \ if (buffers != NULL) { \ buffers[i].iov_base = (void *) buf; \ buffers[i].iov_len = sizeof(buf) - 1; \ } \ INC_BUFFER_ITER(i); \ dataSize += sizeof(buf) - 1; \ } while (false) ServerKit::HeaderTable::Iterator it(req->headers); const LString::Part *part; unsigned int i = 0; nbuffers = 0; dataSize = 0; if (!cache.cached) { cache.methodStr = llhttp_method_name(req->method); cache.remoteAddr = req->secureHeaders.lookup(REMOTE_ADDR); cache.setCookie = req->headers.lookup(ServerKit::HTTP_SET_COOKIE); cache.cached = true; } if (buffers != NULL) { BEGIN_PUSH_NEXT_BUFFER(); buffers[i].iov_base = (void *) cache.methodStr.data(); buffers[i].iov_len = cache.methodStr.size(); } INC_BUFFER_ITER(i); dataSize += cache.methodStr.size(); PUSH_STATIC_BUFFER(" "); if (buffers != NULL) { BEGIN_PUSH_NEXT_BUFFER(); buffers[i].iov_base = (void *) req->path.start->data; buffers[i].iov_len = req->path.size; } INC_BUFFER_ITER(i); dataSize += req->path.size; if (req->upgraded()) { PUSH_STATIC_BUFFER(" HTTP/1.1\r\nConnection: upgrade\r\n"); } else { PUSH_STATIC_BUFFER(" HTTP/1.1\r\nConnection: close\r\n"); } if (cache.setCookie != NULL) { LString::Part *part; PUSH_STATIC_BUFFER("Set-Cookie: "); part = cache.setCookie->start; while (part != NULL) { if (part->size == 1 && part->data[0] == '\n') { // HeaderTable joins multiple Set-Cookie headers together using \n. PUSH_STATIC_BUFFER("\r\nSet-Cookie: "); } else { if (buffers != NULL) { BEGIN_PUSH_NEXT_BUFFER(); buffers[i].iov_base = (void *) part->data; buffers[i].iov_len = part->size; } INC_BUFFER_ITER(i); dataSize += part->size; } part = part->next; } PUSH_STATIC_BUFFER("\r\n"); } while (*it != NULL) { if ((it->header->hash == HTTP_CONNECTION.hash() || it->header->hash == ServerKit::HTTP_SET_COOKIE.hash()) && (psg_lstr_cmp(&it->header->key, HTTP_CONNECTION) || psg_lstr_cmp(&it->header->key, ServerKit::HTTP_SET_COOKIE))) { it.next(); continue; } part = it->header->key.start; while (part != NULL) { if (buffers != NULL) { BEGIN_PUSH_NEXT_BUFFER(); buffers[i].iov_base = (void *) part->data; buffers[i].iov_len = part->size; } INC_BUFFER_ITER(i); part = part->next; } dataSize += it->header->key.size; PUSH_STATIC_BUFFER(": "); part = it->header->val.start; while (part != NULL) { if (buffers != NULL) { BEGIN_PUSH_NEXT_BUFFER(); buffers[i].iov_base = (void *) part->data; buffers[i].iov_len = part->size; } INC_BUFFER_ITER(i); part = part->next; } dataSize += it->header->val.size; PUSH_STATIC_BUFFER("\r\n"); it.next(); } if (req->https) { PUSH_STATIC_BUFFER("X-Forwarded-Proto: https\r\n"); PUSH_STATIC_BUFFER("!~Passenger-Proto: https\r\n"); } if (cache.remoteAddr != NULL && cache.remoteAddr->size > 0) { PUSH_STATIC_BUFFER("X-Forwarded-For: "); part = cache.remoteAddr->start; while (part != NULL) { if (buffers != NULL) { BEGIN_PUSH_NEXT_BUFFER(); buffers[i].iov_base = (void *) part->data; buffers[i].iov_len = part->size; } INC_BUFFER_ITER(i); part = part->next; } dataSize += cache.remoteAddr->size; PUSH_STATIC_BUFFER("\r\n"); PUSH_STATIC_BUFFER("!~Passenger-Client-Address: "); part = cache.remoteAddr->start; while (part != NULL) { if (buffers != NULL) { BEGIN_PUSH_NEXT_BUFFER(); buffers[i].iov_base = (void *) part->data; buffers[i].iov_len = part->size; } INC_BUFFER_ITER(i); part = part->next; } dataSize += cache.remoteAddr->size; PUSH_STATIC_BUFFER("\r\n"); } if (req->envvars != NULL) { PUSH_STATIC_BUFFER("!~Passenger-Envvars: "); if (buffers != NULL) { BEGIN_PUSH_NEXT_BUFFER(); buffers[i].iov_base = (void *) req->envvars->start->data; buffers[i].iov_len = req->envvars->size; } INC_BUFFER_ITER(i); dataSize += req->envvars->size; PUSH_STATIC_BUFFER("\r\n"); } PUSH_STATIC_BUFFER("\r\n"); nbuffers = i; return true; #undef BEGIN_PUSH_NEXT_BUFFER #undef INC_BUFFER_ITER #undef PUSH_STATIC_BUFFER } bool Controller::sendHeaderToAppWithHttpProtocolAndWritev(Request *req, ssize_t &bytesWritten, HttpHeaderConstructionCache &cache) { unsigned int maxbuffers = std::min<unsigned int>( 5 + req->headers.size() * 4 + 4, IOV_MAX); struct iovec *buffers = (struct iovec *) psg_palloc(req->pool, sizeof(struct iovec) * maxbuffers); unsigned int nbuffers, dataSize; if (constructHeaderBuffersForHttpProtocol(req, buffers, maxbuffers, nbuffers, dataSize, cache)) { ssize_t ret; do { ret = writev(req->session->fd(), buffers, nbuffers); } while (ret == -1 && errno == EINTR); bytesWritten = ret; return ret == (ssize_t) dataSize; } else { bytesWritten = 0; return false; } } void Controller::sendHeaderToAppWithHttpProtocolWithBuffering(Request *req, unsigned int offset, HttpHeaderConstructionCache &cache) { struct iovec *buffers; unsigned int nbuffers, dataSize; bool ok; ok = constructHeaderBuffersForHttpProtocol(req, NULL, 0, nbuffers, dataSize, cache); assert(ok); buffers = (struct iovec *) psg_palloc(req->pool, sizeof(struct iovec) * nbuffers); ok = constructHeaderBuffersForHttpProtocol(req, buffers, nbuffers, nbuffers, dataSize, cache); assert(ok); (void) ok; // Shut up compiler warning MemoryKit::mbuf_pool &mbuf_pool = getContext()->mbuf_pool; const unsigned int MBUF_MAX_SIZE = mbuf_pool_data_size(&mbuf_pool); if (dataSize <= MBUF_MAX_SIZE) { MemoryKit::mbuf buffer(MemoryKit::mbuf_get(&mbuf_pool)); gatherBuffers(buffer.start, MBUF_MAX_SIZE, buffers, nbuffers); buffer = MemoryKit::mbuf(buffer, offset, dataSize - offset); req->appSink.feedWithoutRefGuard(boost::move(buffer)); } else { char *buffer = (char *) psg_pnalloc(req->pool, dataSize); gatherBuffers(buffer, dataSize, buffers, nbuffers); req->appSink.feedWithoutRefGuard(MemoryKit::mbuf( buffer + offset, dataSize - offset)); } } void Controller::sendBodyToApp(Client *client, Request *req) { TRACE_POINT(); assert(req->appSink.acceptingInput()); #ifdef DEBUG_CC_EVENT_LOOP_BLOCKING req->timeOnRequestHeaderSent = ev_now(getLoop()); reportLargeTimeDiff(client, "ApplicationPool get until headers sent", req->timeBeforeAccessingApplicationPool, req->timeOnRequestHeaderSent); #endif if (req->hasBody() || req->upgraded()) { // onRequestBody() will take care of forwarding // the request body to the app. SKC_TRACE(client, 2, "Sending body to application"); req->state = Request::FORWARDING_BODY_TO_APP; startBodyChannel(client, req); } else { // Our task is done. ForwardResponse.cpp will take // care of ending the request, once all response // data is forwarded. SKC_TRACE(client, 2, "No body to send to application"); req->state = Request::WAITING_FOR_APP_OUTPUT; maybeHalfCloseAppSinkBecauseRequestBodyEndReached(client, req); } } void Controller::maybeHalfCloseAppSinkBecauseRequestBodyEndReached(Client *client, Request *req) { P_ASSERT_EQ(req->state, Request::WAITING_FOR_APP_OUTPUT); if (req->halfClosePolicy == Request::HALF_CLOSE_UPON_REACHING_REQUEST_BODY_END) { SKC_TRACE(client, 3, "Half-closing application socket with SHUT_WR" " because end of request body reached"); req->halfClosePolicy = Request::HALF_CLOSE_PERFORMED; ::shutdown(req->session->fd(), SHUT_WR); } } ServerKit::Channel::Result Controller::whenSendingRequest_onRequestBody(Client *client, Request *req, const MemoryKit::mbuf &buffer, int errcode) { TRACE_POINT(); if (buffer.size() > 0) { // Data if (req->bodyType == Request::RBT_CONTENT_LENGTH) { SKC_TRACE(client, 3, "Forwarding " << buffer.size() << " bytes of client request body (" << req->bodyAlreadyRead << " of " << req->aux.bodyInfo.contentLength << " bytes forwarded in total): \"" << cEscapeString(StaticString(buffer.start, buffer.size())) << "\""); } else { SKC_TRACE(client, 3, "Forwarding " << buffer.size() << " bytes of client request body (" << req->bodyAlreadyRead << " bytes forwarded in total): \"" << cEscapeString(StaticString(buffer.start, buffer.size())) << "\""); } req->appSink.feed(buffer); if (!req->appSink.acceptingInput()) { if (req->appSink.mayAcceptInputLater()) { SKC_TRACE(client, 3, "Waiting for appSink channel to become " "idle before continuing sending body to application"); req->appSink.setConsumedCallback(resumeRequestBodyChannelWhenAppSinkIdle); stopBodyChannel(client, req); return Channel::Result(buffer.size(), false); } else { // Either we're done feeding to req->appSink, or req->appSink.feed() // encountered an error while writing to the application socket. // But we don't care about either scenarios; we just care that // ForwardResponse.cpp will now forward the response data and end the // request when it's done. assert(!req->ended()); assert(req->appSink.hasError()); logAppSocketWriteError(client, req->appSink.getErrcode()); req->state = Request::WAITING_FOR_APP_OUTPUT; stopBodyChannel(client, req); } } return Channel::Result(buffer.size(), false); } else if (errcode == 0 || errcode == ECONNRESET) { // EOF SKC_TRACE(client, 2, "End of request body encountered"); // Our task is done. ForwardResponse.cpp will take // care of ending the request, once all response // data is forwarded. req->state = Request::WAITING_FOR_APP_OUTPUT; maybeHalfCloseAppSinkBecauseRequestBodyEndReached(client, req); return Channel::Result(0, true); } else { const unsigned int BUFSIZE = 1024; char *message = (char *) psg_pnalloc(req->pool, BUFSIZE); int size = snprintf(message, BUFSIZE, "error reading request body: %s (errno=%d)", ServerKit::getErrorDesc(errcode), errcode); disconnectWithError(&client, StaticString(message, size)); return Channel::Result(0, true); } } void Controller::resumeRequestBodyChannelWhenAppSinkIdle(Channel *_channel, unsigned int size) { FdSinkChannel *channel = reinterpret_cast<FdSinkChannel *>(_channel); Request *req = static_cast<Request *>(static_cast< ServerKit::BaseHttpRequest *>(channel->getHooks()->userData)); Client *client = static_cast<Client *>(req->client); Controller *self = static_cast<Controller *>(getServerFromClient(client)); SKC_LOG_EVENT_FROM_STATIC(self, Controller, client, "resumeRequestBodyChannelWhenAppSinkIdle"); P_ASSERT_EQ(req->state, Request::FORWARDING_BODY_TO_APP); req->appSink.setConsumedCallback(NULL); if (req->appSink.acceptingInput()) { self->startBodyChannel(client, req); } else { // Either we're done feeding to req->appSink, or req->appSink.feed() // encountered an error while writing to the application socket. // But we don't care about either scenarios; we just care that // ForwardResponse.cpp will now forward the response data and end the // request when it's done. assert(!req->ended()); assert(req->appSink.hasError()); self->logAppSocketWriteError(client, req->appSink.getErrcode()); req->state = Request::WAITING_FOR_APP_OUTPUT; } } void Controller::startBodyChannel(Client *client, Request *req) { if (req->requestBodyBuffering) { req->bodyBuffer.start(); } else { req->bodyChannel.start(); } } void Controller::stopBodyChannel(Client *client, Request *req) { if (req->requestBodyBuffering) { req->bodyBuffer.stop(); } else { req->bodyChannel.stop(); } } void Controller::logAppSocketWriteError(Client *client, int errcode) { if (errcode == EPIPE) { SKC_INFO(client, "App socket write error: the application closed the socket prematurely" " (Broken pipe; errno=" << errcode << ")"); } else { SKC_INFO(client, "App socket write error: " << ServerKit::getErrorDesc(errcode) << " (errno=" << errcode << ")"); } } } // namespace Core } // namespace Passenger