//%2004//////////////////////////////////////////////////////////////////////// // // Copyright (c) 2000, 2001, 2002 BMC Software; Hewlett-Packard Development // Company, L.P.; IBM Corp.; The Open Group; Tivoli Systems. // Copyright (c) 2003 BMC Software; Hewlett-Packard Development Company, L.P.; // IBM Corp.; EMC Corporation, The Open Group. // Copyright (c) 2004 BMC Software; Hewlett-Packard Development Company, L.P.; // IBM Corp.; EMC Corporation; VERITAS Software Corporation; The Open Group. // // 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. // //============================================================================== // // Author: Mike Brasher (mbrasher@bmc.com) // // Modified By: // Nag Boranna, Hewlett-Packard Company(nagaraja_boranna@hp.com) // Jenny Yu, Hewlett-Packard Company (jenny_yu@hp.com) // Dave Rosckes (rosckes@us.ibm.com) // Amit Arora, IBM (amita@in.ibm.com) // Heather Sterling, IBM (hsterl@us.ibm.com) // Brian G. Campbell, EMC (campbell_brian@emc.com) - PEP140/phase1 // Alagaraja Ramasubramanian (alags_raj@in.ibm.com) for Bug#1090 // Amit K Arora, IBM (amita@in.ibm.com) for Bug#1097 // Sushma Fernandes, IBM (sushma@hp.com) for Bug#2057 // //%///////////////////////////////////////////////////////////////////////////// #include #include #include #include #include #include "Socket.h" #include "TLS.h" #include "HTTPConnection.h" #include "MessageQueue.h" #include "Monitor.h" #include "HTTPMessage.h" #include "Signal.h" #include "Tracer.h" #ifdef PEGASUS_KERBEROS_AUTHENTICATION #include #endif #include PEGASUS_USING_STD; PEGASUS_NAMESPACE_BEGIN // initialize the request count AtomicInt HTTPConnection::_requestCount = 0; //////////////////////////////////////////////////////////////////////////////// // // Local routines: // //////////////////////////////////////////////////////////////////////////////// /* * string and number constants for HTTP sending/receiving */ // buffer size for sending receiving static const Uint32 httpTcpBufferSize = 8192; // string constants for HTTP header. "Name" represents strings on the left // side of headerNameTerminator and "Value" represents strings on the right // side of headerNameTerminator #define headerNameTrailer "Trailer" #undef CRLF #define CRLF "\r\n" static const char headerNameTransferTE[] = "TE"; static const char headerNameTransferEncoding[] = "transfer-encoding"; static const char headerNameContentLength[] = "content-length"; static const char headerValueTransferEncodingChunked[] = "chunked"; static const char headerValueTransferEncodingIdentity[] = "identity"; static const char headerValueTEchunked[] = "chunked"; static const char headerValueTEtrailers[] = "trailers"; static const char headerNameError[] = "CIMError"; static const char headerNameCode[] = "CIMStatusCode"; static const char headerNameDescription[] = "CIMStatusCodeDescription"; static const char headerNameOperation[] = "CIMOperation"; // the names comes from the HTTP specification on chunked transfer encoding static const char headerNameTerminator[] = ": "; static const char headerValueSeparator[] = ", "; static const char headerLineTerminator[] = CRLF; static const char headerTerminator[] = CRLF CRLF; static const Sint8 chunkLineTerminator[] = CRLF; static const Sint8 chunkTerminator[] = CRLF; static const Sint8 chunkBodyTerminator[] = CRLF; static const Sint8 trailerTerminator[] = CRLF; static const Sint8 chunkExtensionTerminator[] = ";"; // string sizes static const Uint32 headerNameContentLengthLength = sizeof(headerNameContentLength)-1; static const Uint32 headerValueTransferEncodingChunkedLength = sizeof(headerValueTransferEncodingChunked)-1; static const Uint32 headerNameTransferEncodingLength = sizeof(headerNameTransferEncoding)-1; static const Uint32 headerNameTerminatorLength =sizeof(headerNameTerminator)-1; static const Uint32 headerLineTerminatorLength =sizeof(headerLineTerminator)-1; static const Uint32 chunkLineTerminatorLength = sizeof(chunkLineTerminator)-1; static const Uint32 chunkTerminatorLength = sizeof(chunkTerminator)-1; static const Uint32 chunkBodyTerminatorLength = sizeof(chunkBodyTerminator)-1; static const Uint32 trailerTerminatorLength = sizeof(trailerTerminator)-1; static const Uint32 chunkExtensionTerminatorLength = sizeof(chunkExtensionTerminator)-1; // the number of bytes it takes to place a Uint32 into a string (minus null) static const Uint32 numberAsStringLength = 10; /* * given an HTTP status code, return the description. not all codes are listed * here. Unmapped codes result in the internal error string. * Add any required future codes here. */ static const String httpDetailDelimiter = headerValueSeparator; static const String httpStatusInternal = HTTP_STATUS_INTERNALSERVERERROR; /* * throw given http code with detail, file, line * This is shared client/server code. The caller will decide what to do * with the thrown message */ static void _throwEventFailure(const String &status, const String &detail, const char *func, const char *file , Uint32 line) { String message = status + httpDetailDelimiter + detail; Tracer::trace(file, line, TRC_HTTP, Tracer::LEVEL2, message); if (status == httpStatusInternal) throw AssertionFailureException(file, line, message); else throw Exception(message); } // throw a http exception. This is used for both client and server common code. // The macro allows is used for file, line inclusion for debugging #define _throwEventFailure(status, detail) \ _throwEventFailure(status, String(detail), func, __FILE__, __LINE__) static inline Uint32 _Min(Uint32 x, Uint32 y) { return x < y ? x : y; } static char* _FindSeparator(const char* data, Uint32 size) { const char* p = data; const char* end = p + size; while (p != end) { if (*p == '\r') { Uint32 n = end - p; if (n >= 2 && p[1] == '\n') return (char*)p; } else if (*p == '\n') return (char*)p; p++; } return 0; } // Used to test signal handling void * sigabrt_generator(void * parm) { abort(); return 0; } //////////////////////////////////////////////////////////////////////////////// // // HTTPConnection // //////////////////////////////////////////////////////////////////////////////// HTTPConnection::HTTPConnection( Monitor* monitor, AutoPtr& socket, MessageQueue* ownerMessageQueue, MessageQueue* outputMessageQueue, Boolean exportConnection) : Base(PEGASUS_QUEUENAME_HTTPCONNECTION), _monitor(monitor), _socket(socket.get()), _ownerMessageQueue(ownerMessageQueue), _outputMessageQueue(outputMessageQueue), _contentOffset(-1), _contentLength(-1), _connectionClosePending(false) { PEG_METHOD_ENTER(TRC_HTTP, "HTTPConnection::HTTPConnection"); //Socket::disableBlocking(_socket); _socket->disableBlocking(); _authInfo.reset(new AuthenticationInfo(true)); // Add SSL verification information to the authentication information if (_socket->isSecure()) { // // Set the flag to indicate that the request was received on // export Connection // if (exportConnection) { _authInfo->setExportConnection(exportConnection); } if (_socket->isPeerVerificationEnabled() && _socket->isCertificateVerified()) { _authInfo->setAuthStatus(AuthenticationInfoRep::AUTHENTICATED); _authInfo->setAuthType(AuthenticationInfoRep::AUTH_TYPE_SSL); } } _responsePending = false; _connectionRequestCount = 0; _transferEncodingChunkOffset = 0; PEG_METHOD_EXIT(); } HTTPConnection::~HTTPConnection() { PEG_METHOD_ENTER(TRC_HTTP, "HTTPConnection::~HTTPConnection"); _socket->close(); PEG_METHOD_EXIT(); } void HTTPConnection::handleEnqueue(Message *message) { PEG_METHOD_ENTER(TRC_HTTP, "HTTPConnection::handleEnqueue"); if( ! message ) { PEG_METHOD_EXIT(); return; } Boolean LockAcquired = false; if (pegasus_thread_self() != _connection_mut.get_owner()) { _connection_mut.lock(pegasus_thread_self()); // Use lock_connection() ? LockAcquired = true; } switch (message->getType()) { case SOCKET_MESSAGE: { Tracer::trace(TRC_HTTP, Tracer::LEVEL4, "HTTPConnection::handleEnqueue - SOCKET_MESSAGE"); SocketMessage* socketMessage = (SocketMessage*)message; if (socketMessage->events & SocketMessage::READ) _handleReadEvent(); break; } case HTTP_MESSAGE: { Tracer::trace(TRC_HTTP, Tracer::LEVEL4, "HTTPConnection::handleEnqueue - HTTP_MESSAGE"); _handleWriteEvent(*message); break; } default: // ATTN: need unexpected message error! break; } // switch delete message; if (LockAcquired) { _connection_mut.unlock(); // Use unlock_connection() ? } PEG_METHOD_EXIT(); } /* * handle the message coming down from the above. This is shared client and * server code. If the message is coming in chunks, then validate the chunk * sequence number. If the message is being processed on the server side, * make sure the client has requested transfer encodings and/or trailers before * sending them. If not, the message must be queued up until a complete flag * has arrived. */ Boolean HTTPConnection::_handleWriteEvent(Message &message) { static const char func[] = "HTTPConnection::_handleWriteEvent"; String httpStatus; HTTPMessage& httpMessage = *(HTTPMessage*)&message; Array& buffer = httpMessage.message; Boolean isFirst = message.isFirst(); Boolean isLast = message.isComplete(); Sint32 totalBytesWritten = 0; Uint32 messageLength = buffer.size(); try { #ifdef PEGASUS_KERBEROS_AUTHENTICATION // The following is processing to wrap (encrypt) the response from the // server when using kerberos authentications. // If the security association does not exist then kerberos authentication // is not being used. CIMKerberosSecurityAssociation *sa = _authInfo->getSecurityAssociation(); if (sa) { // The message needs to be parsed in order to distinguish between the // headers and content. When parsing, the code breaks out // of the loop as soon as it finds the double separator that terminates // the headers so the headers and content can be easily separated. // Parse the HTTP message: String startLine; Array headers; Uint32 contentLength = 0; httpMessage.parse(startLine, headers, contentLength); Boolean authrecExists = false; String authorization = String::EMPTY; if (HTTPMessage::lookupHeader(headers, "WWW-Authenticate", authorization, false)) { authrecExists = true; } // The following is processing to wrap (encrypt) the response from the // server when using kerberos authentications. sa->wrapResponseMessage(buffer, contentLength, authrecExists); messageLength = buffer.size(); } #endif // ATTN: convert over to asynchronous write scheme: // Send response message to the client (use synchronous I/O for now: _socket->enableBlocking(); SignalHandler::ignore(PEGASUS_SIGPIPE); // use the next four lines to test the SIGABRT handler //getSigHandle()->registerHandler(PEGASUS_SIGABRT, sig_act); //getSigHandle()->activate(PEGASUS_SIGABRT); //Thread t(sigabrt_generator, NULL, false); //t.run(); // delivery behavior: // 1 handler.processing() : header + optional body? // 2 handler.deliver() : 1+ fully XML encoded object(s) // 3 handler.complete() : deliver() + isLast = true Uint32 bytesRemaining = messageLength; Sint8 *messageStart = (Sint8 *)buffer.getData(); Uint32 bytesToWrite = httpTcpBufferSize; Uint32 messageIndex = message.getIndex(); Boolean isChunkResponse = false; Boolean isChunkRequest = false; if (_isClient() == false) { // for null termination buffer.reserveCapacity(messageLength + 1); messageStart = (Sint8 *)buffer.getData(); messageStart[messageLength] = 0; if (isFirst == true) { _incomingBuffer.clear(); // tracks the message coming from above _transferEncodingChunkOffset = 0; _mpostPrefix.clear(); } else { // this is coming from our own internal code, therefore it is an // internal error. somehow the chunks came out of order. if (_transferEncodingChunkOffset+1 != messageIndex) _throwEventFailure(httpStatusInternal, "chunk sequence mismatch"); _transferEncodingChunkOffset++; } // check to see if the client requested chunking OR trailers. trailers // are tightly integrated with chunking, so it can also be used. if (_transferEncodingTEValues.size() > 0 && (Contains(_transferEncodingTEValues, String(headerValueTEchunked)) || Contains(_transferEncodingTEValues, String(headerValueTEtrailers)))) { isChunkRequest = true; } // We now need to adjust the contentLength line. // If chunking was requested and this is the first chunk, then we need // to enter this block so we can adjust the header and send to the client // the first set of bytes right away. // If chunking was NOT requested, we have to wait for the last chunk of // the message to get (and set) the size of the content because we are // going to send it the traditional (i.e non-chunked) way if (isChunkRequest == true && isFirst == true || isChunkRequest == false && isLast == true) { // need to find the end of the header String startLine; Array headers; Uint32 contentLength = 0; // Note: this gets the content length from subtracting the header // length from the messageLength, not by parsing the content length // header field httpMessage.parse(startLine, headers, contentLength); Uint32 httpStatusCode = 0; String httpVersion; String reasonPhrase; Boolean isValid = httpMessage. parseStatusLine(startLine, httpVersion, httpStatusCode,reasonPhrase); Uint32 headerLength = messageLength - contentLength; Sint8 save = messageStart[headerLength]; messageStart[headerLength] = 0; Sint8 *contentLengthStart = strstr(messageStart, headerNameContentLength); Sint8 *contentLengthEnd = contentLengthStart ? strstr(contentLengthStart, headerLineTerminator) : 0; messageStart[headerLength] = save; // the message may or may not have the content length specified // depending on the type of request it is if (contentLengthStart) { // the message has the content length specified. // If we are NOT sending a chunked response, then we need to overlay // the contentLength number to reflect the actual byte count of the // content (i.e message body). If we ARE sending a chunked response, // then we will overlay the transferEncoding keyword name and value // on top of the contentLength keyword and value. // Important note: // for performance reasons, the contentLength and/or transferEncoding // strings are being overlayed DIRECTLY inside the message buffer // WITHOUT changing the actual length in bytes of the message. // The XmlWriter has been modified to pad out the maximum number in // zeros to accomodate any number. The maximum contentLength name and // value is identical to the transferEncoding name and value and can // be easily interchanged. By doing this, we do not have to piece // together the header (and more importantly, the lengthy body) // all over again! // This is why the http line lengths are validated below Uint32 transferEncodingLineLengthExpected = headerNameTransferEncodingLength + headerNameTerminatorLength + headerValueTransferEncodingChunkedLength; Uint32 contentLengthLineLengthExpected = headerNameContentLengthLength + headerNameTerminatorLength + numberAsStringLength; Uint32 contentLengthLineLengthFound = contentLengthEnd - contentLengthStart; if (isValid == false || ! contentLengthEnd || contentLengthLineLengthFound != transferEncodingLineLengthExpected || transferEncodingLineLengthExpected != contentLengthLineLengthExpected) { // these should match up since this is coming directly from our // code in XmlWriter! If not,some code changes have got out of sync _throwEventFailure(httpStatusInternal, "content length was incorrectly formatted"); } // we will be sending a chunk response if: // 1. chunking has been requested AND // 2. contentLength has been set // (meaning entire message has come in) OR // 3. this is not the last message // (meaning the data is coming in pieces and we should send chunked) if (isChunkRequest == true && (contentLength > 0 || isLast == false)) isChunkResponse = true; save = contentLengthStart[contentLengthLineLengthExpected]; contentLengthStart[contentLengthLineLengthExpected] = 0; // overlay the contentLength value if (isChunkResponse == false) { // overwrite the content length number with the actual byte count Sint8 *contentLengthNumberStart = contentLengthStart + headerNameContentLengthLength + headerNameTerminatorLength; char format[6]; sprintf (format, "%%.%uu", numberAsStringLength); // overwrite the bytes in buffer with the content encoding length sprintf(contentLengthNumberStart, format, contentLength); contentLengthStart[contentLengthLineLengthExpected] = save; } else { // overlay the contentLength name and value with the // transferEncoding name and value sprintf(contentLengthStart, "%s%s%s",headerNameTransferEncoding, headerNameTerminator,headerValueTransferEncodingChunked); bytesToWrite = messageLength - contentLength; contentLengthStart[contentLengthLineLengthExpected] = save; String operationName = headerNameOperation; // look for 2-digit prefix (if mpost was use) HTTPMessage::lookupHeaderPrefix(headers, operationName, _mpostPrefix); } // else chunk response is true } // if content length was found } // if this is the first chunk containing the header else { // if chunking was requested, then subsequent messages that are // received need to turn response chunking on if (isChunkRequest == true && messageIndex > 0) { isChunkResponse = true; bytesToWrite = messageLength; } } if (isChunkResponse == false) { // we are not sending chunks in the response because either // the client did not request it or there is no body to send // because of bodyless message or an error if (isLast == false) { // this tells the send loop (below) to NOT send any of the // data collected yet bytesRemaining = 0; Uint32 capacity = _incomingBuffer.size() + messageLength + 1; _incomingBuffer.reserveCapacity(capacity); _incomingBuffer.appendArray(buffer); if (_incomingBuffer.size() > 0) { Sint8 *data = (Sint8 *) _incomingBuffer.getData(); data[_incomingBuffer.size()] = 0; } messageLength = 0; messageStart = 0; } else { // performance: if this was the last of at least 2 chunks, get it // from the incoming buffer which has been collecting them if (isFirst == false) { messageLength = _incomingBuffer.size(); messageStart = (Sint8 *)_incomingBuffer.getData(); } // ... else get it from the single buffer else { messageLength = buffer.size(); messageStart = (Sint8 *)buffer.getData(); } bytesRemaining = messageLength; } } // if not sending chunks } // if not a client static const char errorSocket[] = "socket write error"; Sint8 *sendStart = messageStart; Sint32 bytesWritten = 0; if (isFirst == true && isChunkResponse == true) { // send the header first for chunked reponses. // dont include header terminator yet Uint32 headerLength = bytesToWrite; bytesToWrite -= headerLineTerminatorLength; bytesWritten = _socket->write(sendStart, bytesToWrite); if (bytesWritten < 0) _throwEventFailure(httpStatusInternal, errorSocket); totalBytesWritten += bytesWritten; bytesRemaining -= bytesWritten; // put in trailer header. Array trailer; trailer << headerNameTrailer << headerNameTerminator << _mpostPrefix << headerNameCode << headerValueSeparator << _mpostPrefix << headerNameDescription << headerLineTerminator; sendStart = (Sint8 *)trailer.getData(); bytesToWrite = trailer.size(); bytesWritten = _socket->write(sendStart, bytesToWrite); if (bytesWritten < 0) _throwEventFailure(httpStatusInternal, errorSocket); totalBytesWritten += bytesWritten; // the trailer is outside the header buffer, so dont include in // tracking variables // now send header terminator bytesToWrite = headerLineTerminatorLength; sendStart = messageStart + headerLength - bytesToWrite; bytesWritten = _socket->write(sendStart, bytesToWrite); if (bytesWritten < 0) _throwEventFailure(httpStatusInternal, errorSocket); totalBytesWritten += bytesWritten; bytesRemaining -= bytesWritten; messageStart += headerLength; messageLength -= headerLength; sendStart = messageStart; bytesWritten = 0; bytesToWrite = bytesRemaining; } // if first chunk of chunked response // room enough for hex string representing chunk length and terminator char chunkLine[sizeof(Uint32)*2 + chunkLineTerminatorLength+1]; for (; bytesRemaining > 0; ) { if (isChunkResponse == true) { // send chunk line containing hex string and chunk line terminator sprintf(chunkLine, "%x%s", bytesToWrite, chunkLineTerminator); sendStart = chunkLine; Sint32 chunkBytesToWrite = strlen(sendStart); bytesWritten = _socket->write(sendStart, chunkBytesToWrite); if (bytesWritten < 0) _throwEventFailure(httpStatusInternal, errorSocket); totalBytesWritten += bytesWritten; } // for chunking, we will send the entire chunk data in one send, but // for non-chunking, we will send incrementally else bytesToWrite = _Min(bytesRemaining, bytesToWrite); // send non-chunked data sendStart = messageStart + messageLength - bytesRemaining; bytesWritten = _socket->write(sendStart, bytesToWrite); if (bytesWritten < 0) _throwEventFailure(httpStatusInternal, errorSocket); totalBytesWritten += bytesWritten; bytesRemaining -= bytesWritten; if (isChunkResponse == true) { // send chunk terminator, on the last chunk, it is the chunk body // terminator Array trailer; trailer << chunkLineTerminator; // on the last chunk, attach the last chunk termination sequence: // 0 + last chunk terminator + optional trailer + chunkBodyTerminator if (isLast == true) { if (bytesRemaining > 0) _throwEventFailure(httpStatusInternal, "more bytes after indicated last chunk"); trailer << "0" << chunkLineTerminator; Uint32 httpStatus = httpMessage.cimException.getCode(); if (httpStatus != 0) { char httpStatusP[11]; sprintf(httpStatusP, "%u",httpStatus); trailer << _mpostPrefix << headerNameCode << headerNameTerminator << httpStatusP << headerLineTerminator; const String& httpDescription = httpMessage.cimException.getMessage(); if (httpDescription.size() != 0) trailer << _mpostPrefix << headerNameDescription << headerNameTerminator << httpDescription << headerLineTerminator; // terminate the header trailer << headerLineTerminator; } // now add chunkBodyTerminator trailer << chunkBodyTerminator; } // if isLast sendStart = (Sint8 *)trailer.getData(); Sint32 chunkBytesToWrite = (Sint32) trailer.size(); bytesWritten = _socket->write(sendStart, chunkBytesToWrite); if (bytesWritten < 0) _throwEventFailure(httpStatusInternal, errorSocket); totalBytesWritten += bytesWritten; } // isChunkResponse == true } // for all bytes in message } // try catch (Exception &e) { httpStatus = e.getMessage(); _connectionClosePending = true; } catch (...) { httpStatus = HTTP_STATUS_INTERNALSERVERERROR; _connectionClosePending = true; String message("Unknown internal error"); Tracer::trace(__FILE__, __LINE__, TRC_HTTP, Tracer::LEVEL2, message); } if (httpStatus.size() > 0) isLast = true; if (isLast == true) { _incomingBuffer.clear(); _transferEncodingTEValues.clear(); // // handle automatic truststore update, if enabled // if (_socket->isSecure() && _socket->isPeerVerificationEnabled()) { Tracer::trace(TRC_HTTP, Tracer::LEVEL3, "Authenticated = %d; Username = %s", _authInfo->isAuthenticated(), (const char*)_authInfo->getAuthenticatedUser().getCString()); // If the client sent an untrusted certificate along with valid credentials // for the SSL truststore, add the certificate to the server's truststore. // This will fail in the addTrustedClient function if enableSSLTrustStoreAutoUpdate is // not enabled. if (_authInfo->isAuthenticated() && _socket->getPeerCertificate() && _socket->getPeerCertificate()->getErrorCode() != (Uint32) SSLCertificateInfo::V_OK) { _socket->addTrustedClient(_authInfo->getAuthenticatedUser().getCString()); } } // // decrement request count // _requestCount--; _socket->disableBlocking(); _responsePending = false; if (httpStatus.size() == 0) { static const char msg[] = "A response has been sent (%d of %d bytes have been written).\n" "There are %d requests pending within the CIM Server.\n" "A total of %d requests have been processed on this connection."; Tracer::trace(TRC_HTTP, Tracer::LEVEL4, msg, totalBytesWritten, messageLength, _requestCount.value(), _connectionRequestCount); } // // Since we are done writing, update the status of entry to IDLE // and notify the Monitor. // if (_isClient() == false && !_connectionClosePending) { Tracer::trace (TRC_HTTP, Tracer::LEVEL2, "Now setting state to %d", _MonitorEntry::IDLE); _monitor->setState (_entry_index, _MonitorEntry::IDLE); _monitor->tickle(); } } // // Check if there was an error writing, if so close the connection. // if (_isClient() == false && _connectionClosePending) { Tracer::trace(TRC_DISCARDED_DATA, Tracer::LEVEL2, "Error in writing, closing connection"); _responsePending = true; _closeConnection(); } return httpStatus.size() == 0 ? false : true; } void HTTPConnection::handleEnqueue() { Message* message = dequeue(); if (!message) return; handleEnqueue(message); } Boolean _IsBodylessMessage(const char* line) { //ATTN: Make sure this is the right place to check for HTTP/1.1 and // HTTP/1.0 that is part of the authentication challenge header. // ATTN-RK-P2-20020305: How do we make sure we have the complete list? // List of request methods which do not have message bodies const char* METHOD_NAMES[] = { "GET", "HEAD" }; // List of response codes which the client accepts and which should not (normally) have // message bodies. The RFC is vague regarding which response codes support or require bodies. // These are being reported by class (4xx, 5xx, etc) because the CIM client should be able to handle // any status code, including those not explicitly defined in RFC 2616. Therefore, listing codes individually // will not work because the client socket will hang on a code not in this list if no content length is specified. // See bugzilla 1586 const char* RESPONSE_CODES[] = { "HTTP/1.1 3XX", "HTTP/1.0 3XX", "HTTP/1.1 4XX", "HTTP/1.0 4XX", "HTTP/1.1 5XX", "HTTP/1.0 5XX" }; // Check for bodyless HTTP request method const Uint32 METHOD_NAMES_SIZE = sizeof(METHOD_NAMES) / sizeof(char*); for (Uint32 i = 0; i < METHOD_NAMES_SIZE; i++) { Uint32 n = strlen(METHOD_NAMES[i]); if (strncmp(line, METHOD_NAMES[i], n) == 0 && isspace(line[n])) return true; } // Check for bodyless HTTP status code const Uint32 RESPONSE_CODES_SIZE = sizeof(RESPONSE_CODES) / sizeof(char*); for (Uint32 i = 0; i < RESPONSE_CODES_SIZE; i++) { Uint32 n = strlen(RESPONSE_CODES[i]); if (strncmp(line, RESPONSE_CODES[i], n - 2) == 0 && isspace(line[n])) return true; } return false; } /* Boolean _IsBodylessMessage(const char* line) { //ATTN: Make sure this is the right place to check for HTTP/1.1 and // HTTP/1.0 that is part of the authentication challenge header. // ATTN-RK-P2-20020305: How do we make sure we have the complete list? const char* METHOD_NAMES[] = { "GET", "HTTP/1.1 400", "HTTP/1.0 400", "HTTP/1.1 401", "HTTP/1.0 401", "HTTP/1.1 413", "HTTP/1.0 413", "HTTP/1.1 500", "HTTP/1.0 500", "HTTP/1.1 501", "HTTP/1.0 501", "HTTP/1.1 503", "HTTP/1.0 503" }; const Uint32 METHOD_NAMES_SIZE = sizeof(METHOD_NAMES) / sizeof(char*); for (Uint32 i = 0; i < METHOD_NAMES_SIZE; i++) { Uint32 n = strlen(METHOD_NAMES[i]); if (strncmp(line, METHOD_NAMES[i], n) == 0 && isspace(line[n])) return true; } return false; }*/ void HTTPConnection::_getContentLengthAndContentOffset() throw (Exception) { static const char func[] = "HTTPConnection::_getContentLengthAndContentOffset"; Uint32 size = _incomingBuffer.size(); if (size == 0) return; char* data = (char*)_incomingBuffer.getData(); char* line = (char*)data; char* sep; Uint32 lineNum = 0; Boolean bodylessMessage = false; while ((sep = _FindSeparator(line, size - (line - data)))) { char save = *sep; *sep = '\0'; // Did we find the double separator which terminates the headers? if (line == sep) { *sep = save; line = sep + ((save == '\r') ? 2 : 1); _contentOffset = line - _incomingBuffer.getData(); // reserve space for entire non-chunked message if (_contentLength > 0) { Uint32 capacity = (Uint32)(_contentLength + _contentOffset + 1); _incomingBuffer.reserveCapacity(capacity); data = (char *)_incomingBuffer.getData(); data[capacity-1] = 0; } break; } // If this is one of the bodyless methods, then we can assume the // message is complete when the "\r\n\r\n" is encountered. if (lineNum == 0 && _IsBodylessMessage(line)) bodylessMessage = true; // Look for the content-length if not already found: char* colon = strchr(line, ':'); if (colon) { *colon = '\0'; // remove whitespace after colon before value char *valueStart = colon + 1; while(*valueStart == ' ' || *valueStart == '\t') valueStart++; // we found some non-whitespace token if (valueStart != sep) { char *valueEnd = sep - 1; // now remove whitespace from end of line back to last byte of value while(*valueEnd == ' ' || *valueEnd == '\t') valueEnd--; char valueSave = *(valueEnd+1); if (System::strcasecmp(line, headerNameContentLength) == 0) { if (_transferEncodingValues.size() == 0) _contentLength = atoi(valueStart); else _contentLength = -1; } else if (System::strcasecmp(line, headerNameTransferEncoding) == 0) { _transferEncodingValues.clear(); if (strcmp(valueStart,headerValueTransferEncodingChunked) == 0) _transferEncodingValues.append(headerValueTransferEncodingChunked); else if (strcmp(valueStart,headerValueTransferEncodingIdentity) == 0) ; // do nothing else _throwEventFailure(HTTP_STATUS_NOTIMPLEMENTED, "unimplemented transfer-encoding value"); _contentLength = -1; } else if (System::strcasecmp(line, headerNameTransferTE) == 0) { _transferEncodingTEValues.clear(); static const char valueDelimiter = ','; char *valuesStart = valueStart; // now tokenize the values while (*valuesStart) { // strip off whitepsace from the front while(*valuesStart == ' ' || *valuesStart == '\t') valuesStart++; if (valuesStart == valueEnd) break; char *v = strchr(valuesStart, valueDelimiter); if (v) { if (v == valuesStart) { valuesStart++; continue; } v--; // strip off whitespace from the end while(*v == ' ' || *v == '\t') v--; v++; *v = 0; } _transferEncodingTEValues.append(valuesStart); if (v) { *v = valueDelimiter; valuesStart = v+1; } else break; } } *(valueEnd+1) = valueSave; } // if some value tokens *colon = ':'; } *sep = save; line = sep + ((save == '\r') ? 2 : 1); lineNum++; } if (_contentOffset != -1 && bodylessMessage) _contentLength = 0; } void HTTPConnection::_clearIncoming() { _contentOffset = -1; _contentLength = -1; _incomingBuffer.clear(); _mpostPrefix.clear(); } void HTTPConnection::_closeConnection() { // return - don't send the close connection message. // let the monitor dispatch function do the cleanup. PEG_METHOD_ENTER(TRC_HTTP, "HTTPConnection::_closeConnection"); _connectionClosePending = true; if (_responsePending) { Tracer::trace(TRC_DISCARDED_DATA, Tracer::LEVEL2, "HTTPConnection::_closeConnection - Connection being closed with response still pending."); } if (_connectionRequestCount == 0) { Tracer::trace(TRC_HTTP, Tracer::LEVEL4, "HTTPConnection::_closeConnection - Connection being closed without receiving any requests."); } if (_isClient()==false && _connectionClosePending) { Tracer::trace (TRC_HTTP, Tracer::LEVEL2, "Now setting state to %d", _MonitorEntry::DYING); _monitor->setState (_entry_index, _MonitorEntry::DYING); _monitor->tickle(); } PEG_METHOD_EXIT(); // Message* message= new CloseConnectionMessage(_socket->getSocket)); // message->dest = _ownerMessageQueue->getQueueId(); // SendForget(message); // _ownerMessageQueue->enqueue(message); } // determine if the current code being executed is on the client side Boolean HTTPConnection::_isClient() { return strcmp(get_owner().getQueueName(), PEGASUS_QUEUENAME_HTTPCONNECTOR) == 0 ? true : false; } /* * determine if the data read in should be treated as transfer encoded data. * If so, proceed to strip out the transfer encoded meta data within the body * but the headers relating to transfer encoding will remain unchanged. * One should refer to the transfer encoding section of the HTTP protocol * specification to understand the parsing semantics below. * NOTE: this function is coded as a syncronous read! The entire message will * be read in before the message leaves this class and is passed up to the * client application. */ void HTTPConnection::_handleReadEventTransferEncoding() throw (Exception) { static const char func[] = "HTTPConnection::_handleReadEventTransferEncoding"; PEG_METHOD_ENTER(TRC_HTTP, func); Uint32 messageLength = _incomingBuffer.size(); Uint32 headerLength = (Uint32) _contentOffset; // return immediately under these conditions: // - Header terminator has not been reached yet (_contentOffset < 0) // - This is a non-transfer encoded message because the content length // has been set from the given header value (_contentLength > 0) // (_contentLength == 0 means bodyless, so return too - section 4.3) // - The message read in so far is <= to the header length // - No transfer encoding has been declared in the header. if (_contentOffset < 0 || _contentLength >= 0 || messageLength <= headerLength || _transferEncodingValues.size() == 0) { PEG_METHOD_EXIT(); return; } // on the first chunk in the message, set the encoding offset to the content // offset if (_transferEncodingChunkOffset == 0) _transferEncodingChunkOffset = (Uint32) _contentOffset; Sint8 *headerStart = (Sint8 *) _incomingBuffer.getData(); Sint8 *messageStart = headerStart; // loop thru the received data (so far) and strip out all chunked meta data. // this logic assumes that the data read in may be only partial at any point // during the parsing. the variable _transferEncodingChunkOffset represents // the byte offset (from the start of the message) of the last NON completed // chunk parsed within the message. Remember that the tcp reader has padded // the buffer with a terminating null for easy string parsing. for (;;) { // we have parsed the length, but not all bytes of chunk have been read // in yet if (_transferEncodingChunkOffset >= messageLength) break; // this is the length from _transferEncodingChunkOffset to the end // of the message (so far). It represents the bytes that have not been // processed yet Uint32 remainderLength = messageLength - _transferEncodingChunkOffset; // the start of the first fully non-parsed chunk of this interation Sint8 *chunkLineStart = messageStart + _transferEncodingChunkOffset; Sint8 *chunkLineEnd = chunkLineStart; // Find the end of the hex string representing the data portion length of // the current chunk. Note that we must hit at least one non-hexdigit // (except null) to know we have read in the complete number while (isxdigit(*chunkLineEnd)) chunkLineEnd++; if (! *chunkLineEnd) break; // This is the parsed chunk length in hex. From here on, this many bytes // plus the chunk terminator (AFTER this chunk line is done) must be // read in to constitute a complete chunk in which // _transferEncodingChunkOffset can be incremented to the next chunk Uint32 chunkLengthParsed = (Uint32) strtoul((const char *)chunkLineStart, 0, 16); // this also covers strings stated even larger if (chunkLengthParsed == PEG_NOT_FOUND) _throwEventFailure(HTTP_STATUS_REQUEST_TOO_LARGE, "stated chunk length too large"); Sint8 *chunkExtensionStart = chunkLineEnd; chunkLineEnd = strstr(chunkLineEnd, chunkLineTerminator); // If we have not received the chunk line terminator yet, then return and // wait for the next iteration. This is done because the hex length given // only represents the non-meta data, not the chunk line itself. if (!chunkLineEnd) break; // the token after the hex digit must be either the chunk line terminator // or the chunk extension terminator. If not, the sender has sent an // illegal chunked encoding syntax. if (strncmp(chunkExtensionStart, chunkExtensionTerminator, chunkExtensionTerminatorLength) != 0 && strncmp(chunkExtensionStart, chunkLineTerminator, chunkLineTerminatorLength) != 0) _throwEventFailure(HTTP_STATUS_BADREQUEST, "missing chunk extension"); chunkLineEnd += chunkLineTerminatorLength; Uint32 chunkLineLength = chunkLineEnd - chunkLineStart; Uint32 chunkMetaLength = chunkLineLength; if (chunkLengthParsed > 0) chunkMetaLength += chunkTerminatorLength; Uint32 chunkTerminatorOffset = _transferEncodingChunkOffset + chunkLineLength + chunkLengthParsed; // The parsed length represents the non-meta data bytes which starts // after the chunk line terminator has been received. // If we dont have enough remainder bytes to process from the length parsed // then return and wait for the next iteration. if (chunkLengthParsed + chunkMetaLength > remainderLength) break; // at this point we have a complete chunk. proceed and strip out meta-data // NOTE: any time "remove" is called on the buffer, many variables must be // recomputed to reflect the data removed. // remove the chunk length line _incomingBuffer.remove(_transferEncodingChunkOffset, chunkLineLength); messageLength = _incomingBuffer.size(); // always keep the byte after the last data byte null for easy string // processing. messageStart[messageLength] = 0; // recalculate since we just removed the chunk length line chunkTerminatorOffset -= chunkLineLength; // is this the last chunk ? if (chunkLengthParsed == 0) { // We are at the last chunk. The only remaining data should be: // 1. optional trailer first // 2. message terminator (will remain on incoming buffer and passed up) remainderLength -= chunkLineLength; CIMStatusCode cimStatusCode = CIM_ERR_SUCCESS; Uint32 httpStatusCode = HTTP_STATUSCODE_OK; String httpStatus; String cimErrorValue; // is there an optional trailer ? if (remainderLength > chunkBodyTerminatorLength) { Uint32 trailerLength = remainderLength - chunkBodyTerminatorLength; Uint32 trailerOffset = _transferEncodingChunkOffset; Sint8 *trailerStart = messageStart + trailerOffset; Sint8 *trailerTerminatorStart = trailerStart + trailerLength - trailerTerminatorLength; // no trailer terminator before end of chunk body ? if (strncmp(trailerTerminatorStart, trailerTerminator, trailerTerminatorLength) != 0) _throwEventFailure(HTTP_STATUS_BADREQUEST, "No chunk trailer terminator received"); Array trailer; // add a dummy startLine so that the parser works trailer << " " << headerLineTerminator; char save = trailerStart[trailerLength]; trailerStart[trailerLength] = 0; trailer << trailerStart; trailerStart[trailerLength] = save; _incomingBuffer.remove(trailerOffset, trailerLength); messageLength = _incomingBuffer.size(); messageStart[messageLength] = 0; remainderLength -= trailerLength; // parse the trailer looking for the code and description String startLine; Array headers; Uint32 contentLength = 0; HTTPMessage httpTrailer(trailer); httpTrailer.parse(startLine, headers, contentLength); String cimErrorName = headerNameError; // first look for cim error. this is an http level error Boolean found = false; found = httpTrailer.lookupHeader(headers, cimErrorName, cimErrorValue, true); if (found == true) { // we have a cim error. parse the header to get the original http // level error if any, otherwise, we have to make one up. Array header(messageStart, headerLength); String startLine; Array headers; Uint32 contentLength = 0; HTTPMessage httpHeader(header); httpHeader.parse(startLine, headers, contentLength); String httpVersion; Boolean isValid = httpHeader. parseStatusLine(startLine, httpVersion, httpStatusCode,httpStatus); if (isValid == false || httpStatusCode == 0 || httpStatusCode == HTTP_STATUSCODE_OK) { // ??? ATTN: make up our own http code if not given ? httpStatusCode = (Uint32) HTTP_STATUSCODE_BADREQUEST; httpStatus = HTTP_STATUS_BADREQUEST; } } else { String codeName = headerNameCode; String codeValue; found = httpTrailer.lookupHeader(headers, codeName, codeValue, true); if (found == true && codeValue.size() > 0 && (cimStatusCode = (CIMStatusCode)atoi(codeValue.getCString()))>0) { HTTPMessage::lookupHeaderPrefix(headers, codeName, _mpostPrefix); httpStatus = _mpostPrefix + codeName + headerNameTerminator + codeValue + headerLineTerminator; // look for cim status description String descriptionName = headerNameDescription; String descriptionValue; found = httpTrailer.lookupHeader(headers, descriptionName, descriptionValue, true); if (descriptionValue.size() == 0) descriptionValue = cimStatusCodeToString(cimStatusCode); httpStatus = httpStatus + _mpostPrefix + descriptionName + headerNameTerminator + descriptionValue + headerLineTerminator; } // if found a cim status code } // else not a cim error } // if optional trailer present Sint8 *chunkBodyTerminatorStart = messageStart + _transferEncodingChunkOffset; // look for chunk body terminator if (remainderLength != chunkBodyTerminatorLength || strncmp(chunkBodyTerminatorStart, chunkBodyTerminator, chunkBodyTerminatorLength) != 0) _throwEventFailure(HTTP_STATUS_BADREQUEST, "No chunk body terminator received"); // else the remainder is just the terminator, which we will leave // on the incoming buffer and pass up // (as if a non-transfer message arrived) _transferEncodingChunkOffset = 0; _contentLength = messageLength - headerLength; if (httpStatusCode != HTTP_STATUSCODE_OK) { _handleReadEventFailure(httpStatus, cimErrorValue); } else if (cimStatusCode != CIM_ERR_SUCCESS) { // discard the XML payload data (body) according to cim operations spec // and add code and description to the header so the next layer can // interpret the error correctly _incomingBuffer.remove(headerLength, _contentLength); // remove the header line terminator _incomingBuffer.remove(headerLength - headerLineTerminatorLength, headerLineTerminatorLength); // append new status _incomingBuffer.append(httpStatus.getCString(), httpStatus.size()); _incomingBuffer.append(headerLineTerminator, headerLineTerminatorLength); // null terminate - the buffer is at least as long after removing Sint8 *data = (Sint8 *)_incomingBuffer.getData(); data[_incomingBuffer.size()] = 0; _contentLength = 0; _contentOffset = 0; } break; } // if last chunk // we are NOT on the last chunk! validate that the offset where the chunk // terminator was found matches what the parsed chunk length claimed. if (strncmp(messageStart + chunkTerminatorOffset, chunkTerminator, chunkTerminatorLength) != 0) _throwEventFailure(HTTP_STATUS_BADREQUEST, "Bad chunk terminator"); // now remove the chunk terminator _incomingBuffer.remove(chunkTerminatorOffset, chunkTerminatorLength); messageLength = _incomingBuffer.size(); messageStart[messageLength] = 0; // jump to the start of the next chunk (which may not have been read yet) _transferEncodingChunkOffset = chunkTerminatorOffset; } // for all remaining bytes containing chunks PEG_METHOD_EXIT(); } /* * Handle a failure on the read or an HTTP error. This is NOT meant for * errors found in the cim response or the trailer. * The http status MAY have the detailed message attached to it using the * detail delimiter. */ void HTTPConnection::_handleReadEventFailure(String &httpStatusWithDetail, String cimError) { Uint32 delimiterFound = httpStatusWithDetail.find(httpDetailDelimiter); String httpDetail; String httpStatus; if (delimiterFound != PEG_NOT_FOUND) { httpDetail = httpStatus.subString(delimiterFound+1); httpStatus = httpStatus.subString(0, delimiterFound); } String combined = httpStatus + httpDetailDelimiter + httpDetail + httpDetailDelimiter + cimError; Tracer::trace(__FILE__, __LINE__, TRC_HTTP, Tracer::LEVEL2, combined); _requestCount++; Array message; message = XmlWriter::formatHttpErrorRspMessage(httpStatus, cimError, httpDetail); HTTPMessage* httpMessage = new HTTPMessage(message); Tracer::traceBuffer(TRC_XML_IO, Tracer::LEVEL2, httpMessage->message.getData(), httpMessage->message.size()); // this is common error code. If we are the server side, we want to send // back the error to the client, but if we are the client side, then we // simply want to queue up this error locally so the client app can receive // the error. The client side's own message queue name will be the same // as the connector name (the server would be acceptor) if (_isClient() == true) { httpMessage->dest = _outputMessageQueue->getQueueId(); #ifndef LOCK_CONNECTION_ENABLED _outputMessageQueue->enqueue(httpMessage); #endif _clearIncoming(); #ifdef LOCK_CONNECTION_ENABLED unlock_connection(); _outputMessageQueue->enqueue(httpMessage); _clearIncoming(); #endif } else { // else server side processing error - send back to client handleEnqueue(httpMessage); #ifdef LOCK_CONNECTION_ENABLED unlock_connection(); #endif } _closeConnection(); } void HTTPConnection::_handleReadEvent() { static const char func[] = "HTTPConnection::_handleReadEvent()"; PEG_METHOD_ENTER(TRC_HTTP, func); // -- Append all data waiting on socket to incoming buffer: #ifdef LOCK_CONNECTION_ENABLED lock_connection(); #endif String httpStatus; Sint32 bytesRead = 0; Boolean incompleteSecureReadOccurred = false; for (;;) { // save one for null char buffer[httpTcpBufferSize+1]; buffer[sizeof(buffer)-1] = 0; Sint32 n = _socket->read(buffer, sizeof(buffer)-1); if (n <= 0) { if (_socket->isSecure() && bytesRead == 0) { // It is possible that SSL_read was not able to // read the entire SSL record. This could happen // if the record was send in multiple packets // over the network and only some of the packets // are available. Since SSL requires the entire // record to successfully decrypt, the SSL_read // operation will return "0 bytes" read. // Once all the bytes of the SSL record have been read, // SSL_read will return the entire record. // The following test was added to allow // handleReadEvent to distinguish between a // disconnect and partial read of an SSL record. // incompleteSecureReadOccurred = !_socket->incompleteReadOccurred(n); } break; } try { buffer[n] = 0; // important: always keep message buffer null terminated for easy // string parsing! Uint32 size = _incomingBuffer.size() + n; _incomingBuffer.reserveCapacity(size + 1); _incomingBuffer.append(buffer, n); // put a null on it. This is safe sice we have reserved an extra byte Sint8 *data = (Sint8 *)_incomingBuffer.getData(); data[size] = 0; } catch(...) { static const char detailP[] = "Unable to append the request to the input buffer"; httpStatus = HTTP_STATUS_REQUEST_TOO_LARGE + httpDetailDelimiter + detailP; _handleReadEventFailure(httpStatus); PEG_METHOD_EXIT(); return; } bytesRead += n; } Tracer::trace(TRC_HTTP, Tracer::LEVEL4, "Total bytesRead = %d; Bytes read this iteration = %d", _incomingBuffer.size(), bytesRead); try { if (_contentOffset == -1) _getContentLengthAndContentOffset(); _handleReadEventTransferEncoding(); } catch(Exception &e) { httpStatus = e.getMessage(); } if (httpStatus.size() > 0) { _handleReadEventFailure(httpStatus); PEG_METHOD_EXIT(); return; } // -- See if the end of the message was reached (some peers signal end of // -- the message by closing the connection; others use the content length // -- HTTP header and then there are those messages which have no bodies // -- at all). if ((bytesRead == 0 && !incompleteSecureReadOccurred) || _contentLength != -1 && (Sint32(_incomingBuffer.size()) >= _contentLength + _contentOffset)) { HTTPMessage* message = new HTTPMessage(_incomingBuffer, getQueueId()); message->authInfo = _authInfo.get(); // // increment request count // if (bytesRead > 0) { _requestCount++; _connectionRequestCount++; _responsePending = true; } Tracer::trace(TRC_HTTP, Tracer::LEVEL4, "_requestCount = %d", _requestCount.value()); message->dest = _outputMessageQueue->getQueueId(); // SendForget(message); #ifndef LOCK_CONNECTION_ENABLED // // Set the entry status to BUSY. // if (_isClient() == false && !_connectionClosePending) { Tracer::trace (TRC_HTTP, Tracer::LEVEL2, "Now setting state to %d", _MonitorEntry::BUSY); _monitor->setState (_entry_index, _MonitorEntry::BUSY); _monitor->tickle(); } _outputMessageQueue->enqueue(message); #endif _clearIncoming(); #ifdef LOCK_CONNECTION_ENABLED unlock_connection(); if (bytesRead > 0) { // // Set the entry status to BUSY. // if (_isClient() == false && !_connectionClosePending) { Tracer::trace (TRC_HTTP, Tracer::LEVEL2, "Now setting state to %d", _MonitorEntry::BUSY); _monitor->setState (_entry_index, _MonitorEntry::BUSY); _monitor->tickle(); } _outputMessageQueue->enqueue(message); } else #else if (bytesRead == 0) #endif { Tracer::trace(TRC_HTTP, Tracer::LEVEL3, "HTTPConnection::_handleReadEvent - bytesRead == 0 - Connection being closed."); _closeConnection(); // // decrement request count // Tracer::trace(TRC_HTTP, Tracer::LEVEL4, "_requestCount = %d", _requestCount.value()); PEG_METHOD_EXIT(); return; } } PEG_METHOD_EXIT(); } Uint32 HTTPConnection::getRequestCount() { return(_requestCount.value()); } Boolean HTTPConnection::run(Uint32 milliseconds) { Boolean handled_events = false; int events = 0; fd_set fdread; // , fdwrite; struct timeval tv = { 0, 1 }; FD_ZERO(&fdread); FD_SET(getSocket(), &fdread); events = select(FD_SETSIZE, &fdread, NULL, NULL, &tv); #ifdef PEGASUS_OS_TYPE_WINDOWS if(events == SOCKET_ERROR) #else if(events == -1) #endif { return false; } if (events) { events = 0; if( FD_ISSET(getSocket(), &fdread)) { events |= SocketMessage::READ; Message *msg = new SocketMessage(getSocket(), events); try { handleEnqueue(msg); } catch(...) { Tracer::trace(TRC_DISCARDED_DATA, Tracer::LEVEL2, "HTTPConnection::run handleEnqueue(msg) failure"); return true; } handled_events = true; } } return handled_events; } AtomicInt HTTPConnection2::_requestCount(0); HTTPConnection2::HTTPConnection2(pegasus_socket socket, MessageQueue* outputMessageQueue) : Base(PEGASUS_QUEUENAME_HTTPCONNECTION), _socket(socket), _outputMessageQueue(outputMessageQueue), _contentOffset(-1), _contentLength(-1), _closed(0) { PEG_METHOD_ENTER(TRC_HTTP, "HTTPConnection2::HTTPConnection2"); _authInfo.reset(new AuthenticationInfo(true)); // add SSL verification information to the authentication information if (_socket.is_secure() && _socket.isPeerVerificationEnabled() && _socket.isCertificateVerified()) { _authInfo->setAuthStatus(AuthenticationInfoRep::AUTHENTICATED); _authInfo->setAuthType(AuthenticationInfoRep::AUTH_TYPE_SSL); } PEG_METHOD_EXIT(); } HTTPConnection2::~HTTPConnection2() { PEG_METHOD_ENTER(TRC_HTTP, "HTTPConnection2::~HTTPConnection2"); try { _close_connection(); } catch(...) { } PEG_METHOD_EXIT(); } void HTTPConnection2::handleEnqueue(Message *message) { PEG_METHOD_ENTER(TRC_HTTP, "HTTPConnection2::handleEnqueue"); switch (message->getType()) { case HTTP_MESSAGE: { Tracer::trace(TRC_HTTP, Tracer::LEVEL4, "HTTPConnection2::handleEnqueue - HTTP_MESSAGE"); HTTPMessage* httpMessage = (HTTPMessage*)message; #ifdef PEGASUS_KERBEROS_AUTHENTICATION // The following is processing to wrap (encrypt) the response from the // server when using kerberos authentication. // If the security association does not exist then kerberos authentication // is not being used. CIMKerberosSecurityAssociation *sa = _authInfo->getSecurityAssociation(); if (sa) { // The message needs to be parsed in order to distinguish between the // headers and content. When parsing, the code breaks out // of the loop as soon as it finds the double separator that terminates // the headers so the headers and content can be easily separated. // Parse the HTTP message: String startLine; Array headers; Uint32 contentLength = 0; httpMessage->parse(startLine, headers, contentLength); Boolean authrecExists = false; String authorization = String::EMPTY; if (HTTPMessage::lookupHeader(headers, "WWW-Authenticate", authorization, false)) { authrecExists = true; } // The following is processing to wrap (encrypt) the response from the // server when using kerberos authentications. sa->wrapResponseMessage(httpMessage->message, contentLength, authrecExists); } #endif //------------------------------------------------------------ // There is no need to convert the write calls to be asynchronous. // The write is happening on a dedicated thread (not the monitor thread) // so it is just fine if it blocks. << Tue Oct 7 09:48:06 2003 mdd >> //------------------------------------------------------------ const Array& buffer = httpMessage->message; const Uint32 CHUNK_SIZE = 16 * 1024; SignalHandler::ignore(PEGASUS_SIGPIPE); Uint32 totalBytesWritten = 0; for (Uint32 bytesRemaining = buffer.size(); bytesRemaining > 0; ) { Uint32 bytesToWrite = _Min(bytesRemaining, CHUNK_SIZE); Sint32 bytesWritten = _socket.write( buffer.getData() + buffer.size() - bytesRemaining, bytesToWrite); if (bytesWritten < 0) break; totalBytesWritten += bytesWritten; bytesRemaining -= bytesWritten; } if (_socket.is_secure() && _socket.isPeerVerificationEnabled()) { Tracer::trace(TRC_HTTP, Tracer::LEVEL3, "Authenticated = %d; Username = %s", _authInfo->isAuthenticated(), (const char*)_authInfo->getAuthenticatedUser().getCString()); // If the client sent an untrusted certificate along with valid credentials // for the SSL truststore, add the certificate to the server's truststore. // This will fail in the addTrustedClient function if enableSSLTrustStoreAutoUpdate is // not enabled. if (_authInfo->isAuthenticated() && _socket.getPeerCertificate() && _socket.getPeerCertificate()->getErrorCode() != (Uint32) SSLCertificateInfo::V_OK) { _socket.addTrustedClient(_authInfo->getAuthenticatedUser().getCString()); } } // // decrement request count // _requestCount--; Tracer::trace(TRC_HTTP, Tracer::LEVEL4, "Total bytes written = %d; Buffer Size = %d; _requestCount = %d", totalBytesWritten, buffer.size(), _requestCount.value()); break; } default: // ATTN: need unexpected message error! break; } delete message; PEG_METHOD_EXIT(); } void HTTPConnection2::enqueue(Message* message) throw(IPCException) { AutoMutex autoMut(_reentry); if(_closed.value()) { delete message; } else { handleEnqueue(message); } } void HTTPConnection2::handleEnqueue() { Message* message = dequeue(); if (!message) return; handleEnqueue(message); } Sint32 HTTPConnection2::getSocket(void) { return (Sint32)_socket; } void HTTPConnection2::_getContentLengthAndContentOffset() { char* data = (char*)_incomingBuffer.getData(); Uint32 size = _incomingBuffer.size(); char* line = (char*)data; char* sep; Uint32 lineNum = 0; Boolean bodylessMessage = false; while ((sep = _FindSeparator(line, size - (line - data)))) { char save = *sep; *sep = '\0'; // Did we find the double separator which terminates the headers? if (line == sep) { *sep = save; line = sep + ((save == '\r') ? 2 : 1); _contentOffset = line - _incomingBuffer.getData(); break; } // If this is one of the bodyless methods, then we can assume the // message is complete when the "\r\n\r\n" is encountered. if (lineNum == 0 && _IsBodylessMessage(line)) bodylessMessage = true; // Look for the content-length if not already found: char* colon = strchr(line, ':'); if (colon) { *colon = '\0'; if (System::strcasecmp(line, "content-length") == 0) _contentLength = atoi(colon + 1); *colon = ':'; } *sep = save; line = sep + ((save == '\r') ? 2 : 1); lineNum++; } if (_contentOffset != -1 && bodylessMessage) _contentLength = 0; } void HTTPConnection2::_clearIncoming() { _contentOffset = -1; _contentLength = -1; _incomingBuffer.clear(); } void HTTPConnection2::_handleReadEvent(monitor_2_entry* entry) { PEG_METHOD_ENTER(TRC_HTTP, "HTTPConnection2::_handleReadEvent"); // -- Append all data waiting on socket to incoming buffer: _socket.disableBlocking(); Sint32 bytesRead = 0; Boolean incompleteSecureReadOccurred = false; Boolean would_block = false; Tracer::trace(TRC_HTTP, Tracer::LEVEL4, "Doing a read on %d.", (Sint32)entry->get_sock()); for (;;) { char buffer[4096]; Sint32 n = _socket.read(buffer, sizeof(buffer)); //------------------------------------------------------------ // Note on reading non-blocking sockets: // If there is no data to read from a non-blocking socket, the return // code from _socket.read will be a -1 and errno will be EWOULDBLOCK. // The correct thing to do in this case is nothing at all. The client on // the other end of the connection has not closed the socket, and the // connection will have data to read very soon. The connection needs to // be present in the monitor's select loop so it can be read as soon as the // data arrives. << Tue Oct 7 09:45:37 2003 mdd >> //------------------------------------------------------------ #ifdef PEGASUS_OS_TYPE_WINDOWS if( n == SOCKET_ERROR && WSAGetLastError() == WSAEWOULDBLOCK ) #else if( n == -1 && errno == EWOULDBLOCK ) #endif { would_block = true; } if (n <= 0) { if (_socket.is_secure() && bytesRead == 0) { // It is possible that SSL_read was not able to // read the entire SSL record. This could happen // if the record was send in multiple packets // over the network and only some of the packets // are available. Since SSL requires the entire // record to successfully decrypt, the SSL_read // operation will return "0 bytes" read. // Once all the bytes of the SSL record have been read, // SSL_read will return the entire record. // The following test was added to allow // handleReadEvent to distinguish between a // disconnect and partial read of an SSL record. // incompleteSecureReadOccurred = !_socket.incompleteReadOccurred(n); } break; } _incomingBuffer.append(buffer, n); bytesRead += n; } _socket.enableBlocking(); Tracer::trace(TRC_HTTP, Tracer::LEVEL4, "_socket.read bytesRead = %d", bytesRead); // -- If still waiting for beginning of content! if (_contentOffset == -1) _getContentLengthAndContentOffset(); // -- See if the end of the message was reached (some peers signal end of // -- the message by closing the connection; others use the content length // -- HTTP header and then there are those messages which have no bodies // -- at all). if ((bytesRead == 0 && !incompleteSecureReadOccurred) || _contentLength != -1 && (Sint32(_incomingBuffer.size()) >= _contentLength + _contentOffset)) { if (bytesRead > 0) { // We are setting the state of entry as IDLE so that monitor_2::run // does a select on this FD. And only when next time _handleReadEvent // gets called, bytesRead would be 0, and the state of the entry // would be changed to CLOSED. entry->set_state(IDLE); delete entry; HTTPMessage* message = new HTTPMessage(_incomingBuffer, getQueueId()); message->authInfo = _authInfo.get(); // // increment request count // _requestCount++; Tracer::trace(TRC_HTTP, Tracer::LEVEL4, "_requestCount = %d", _requestCount.value()); message->dest = _outputMessageQueue->getQueueId(); _clearIncoming(); _outputMessageQueue->enqueue(message); } else if (bytesRead == 0) { if(would_block == false) { Tracer::trace(TRC_HTTP, Tracer::LEVEL3, "HTTPConnection2::_handleReadEvent - bytesRead == 0 - Conection being closed."); // Commenting out below line, since the decrement is happening // twice, and increment only once for every connection. One // decrement was happening here and another by handleEnqueue() // after response has been written on the socket. // _requestCount--; Tracer::trace(TRC_HTTP, Tracer::LEVEL4, "_requestCount = %d", _requestCount.value()); _close_connection(); if(entry->get_type() != CLIENTSESSION) entry->set_state(CLOSED); delete entry; } } } PEG_METHOD_EXIT(); } void HTTPConnection2::_close_connection(void) { AutoMutex autoMut(_reentry); _closed = 1; remove_myself(_queueId); } Uint32 HTTPConnection2::getRequestCount() { return(_requestCount.value()); } Boolean HTTPConnection2::operator ==(const HTTPConnection2& h2) { if(this == &h2) return true; return false; } Boolean HTTPConnection2::operator ==(void* h2) { if((void *)this == h2) return true; return false; } void HTTPConnection2::connection_dispatch(monitor_2_entry* entry) { HTTPConnection2* myself = (HTTPConnection2*) entry->get_dispatch(); myself->_socket = entry->get_sock(); myself->_handleReadEvent(entry); } PEGASUS_NAMESPACE_END