src/transport/axis3/HTTPTransport.cpp (1,401 lines of code) (raw):

/* * Copyright 2003-2004 The Apache Software Foundation. // (c) Copyright IBM Corp. 2004, 2005 All Rights Reserved * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ // !!! This include file must be first thing in file !!! #include "../../platforms/PlatformAutoSense.hpp" #include "HTTPTransport.hpp" #include "../../platforms/PlatformLanguage.hpp" // for the basic auth encryption #include "../../soap/apr_base64.h" #include <stdio.h> #include "../../common/AxisTrace.h" static int axtoi( char *pcHexString); /* * HTTPTransport constuctor */ HTTPTransport:: HTTPTransport (): m_bReopenConnection (false), m_strHTTPProtocol ("HTTP/1.1"), m_strHTTPMethod ("POST"), m_strProxyHost (""), m_uiProxyPort (0), m_bUseProxy (false), m_bMaintainSession (false) { logEntryTransport("HTTPTransport::HTTPTransport") m_pcEndpointUri = NULL; m_pReleaseBufferCallback = 0; m_eProtocolType = APTHTTP1_1; m_strBytesToSend = ""; m_strBytesToSendRedirect = ""; m_strHeaderBytesToSend = ""; m_bChannelSecure = false; m_pNormalChannel = 0; m_pSecureChannel = 0; m_pActiveChannel = 0; m_pChannelFactory = new ChannelFactory(); m_viCurrentHeader = m_vHTTPHeaders.begin(); m_viCurrentResponseHeader = m_vResponseHTTPHeaders.begin(); m_pszRxBuffer = new char [BUF_SIZE]; m_pcUsername=NULL; m_pcPassword=NULL; #ifdef WIN32 m_lChannelTimeout = 10; m_strChannelTimeout = "10"; #else m_lChannelTimeout = 0; m_strChannelTimeout = "0"; #endif m_lChannelConnectTimeout = 0; m_strChannelConnectTimeout = "0"; m_strResponseLocationURI = ""; m_bPerformAutoRedirect = false; m_iMaximumAutoRedirects = 1; m_strMaximumAutoRedirects = "1"; m_iNbrOfRedirectAttempts = 0; m_bHasReadBeenDone = false; m_pNormalChannel = m_pChannelFactory->createChannel(UnsecureChannel); m_pSecureChannel = m_pChannelFactory->createChannel(SecureChannel); resetInputStateMachine(); logExit() } /* * HTTPTransport destructor */ HTTPTransport:: ~HTTPTransport() { logEntryTransport("HTTPTransport::~HTTPTransport") delete [] m_pcEndpointUri; delete m_pChannelFactory; // should also destroy channels delete [] m_pszRxBuffer; logExit() } void HTTPTransport:: resetOutputStateMachine() { logEntryTransport("HTTPTransport::resetOutputStateMachine") // Empty the bytes to send string. Note that the payload is saved in the case // we need to handle redirect. if (m_bPerformAutoRedirect && m_iMaximumAutoRedirects > 0 && m_strBytesToSend.length() > 0) m_strBytesToSendRedirect = m_strBytesToSend; m_strBytesToSend = ""; m_strHeaderBytesToSend = ""; // Also empty the response headers as there aren't any yet until the response comes back ! m_vResponseHTTPHeaders.clear(); // TODO: Possible memory leak here - does the clear op clean out the memory too? // Empty other variables set from response received m_strResponseHTTPStatusMessage = ""; m_strResponseContentType = ""; m_strResponseLocationURI = ""; logExit() } void HTTPTransport:: resetInputStateMachine() { logEntryTransport("HTTPTransport::resetInputStateMachine") m_GetBytesState = eWaitingForHTTPHeader; m_strReceived.erase(); m_iBytesLeft = 0; m_iChunkedDataLeftToConsume = 0; m_iNextChunkedDataSize = 0; m_iContentLength = 0; m_bMimeTrue = false; logExit() } /* * HTTPTransport::setEndpointUri( EndpointURI) sets the URI for the message. * Every time the endpoint changes then currently connected channel is closed * and a new channel connection is opened. * * @param EndpointURI - char * to a null terminated string that holds the * new URI. */ void HTTPTransport:: setEndpointUri( const char * pcEndpointUri) throw (HTTPTransportException) { logEntryTransport("HTTPTransport::setEndpointUri") logDebugArg1("Endpoint is %s", pcEndpointUri ? pcEndpointUri : "NULL") // if URI not valid, return if (!pcEndpointUri || strlen(pcEndpointUri) < strlen("http://") ) { logExit() return; } // Does the new URI equal the existing channel URI? // If there is a new URI, then connection will be closed and a secure or unsecure channel // will be set. If the required channel is not available, an exception will be thrown. if (m_pActiveChannel == NULL || m_pActiveChannel->getURL() == NULL || strcmp(m_pActiveChannel->getURL(), pcEndpointUri) != 0) { if( m_pActiveChannel == NULL) m_pActiveChannel = m_pNormalChannel; m_pActiveChannel->setURL( pcEndpointUri); m_bReopenConnection = true; // Check if the new URI requires SSL (denoted by the https prefix). if( (m_pActiveChannel->getURLObject()).getProtocol() == URL::https) { if( m_pSecureChannel != NULL) { m_pNormalChannel->close(); m_pActiveChannel = m_pSecureChannel; m_pActiveChannel->setURL( pcEndpointUri); m_bChannelSecure = true; } if( !m_bChannelSecure) { logThrowException("HTTPTransportException - CLIENT_TRANSPORT_HAS_NO_SECURE_TRANSPORT_LAYER") throw HTTPTransportException( CLIENT_TRANSPORT_HAS_NO_SECURE_TRANSPORT_LAYER); } } else if (m_bChannelSecure) { if( m_pNormalChannel != NULL) { m_pSecureChannel->close(); m_pActiveChannel = m_pNormalChannel; m_pActiveChannel->setURL( pcEndpointUri); m_bChannelSecure = false; } if( m_bChannelSecure) { logThrowException("HTTPTransportException - CLIENT_TRANSPORT_HAS_NO_UNSECURE_TRANSPORT_LAYER") throw HTTPTransportException( CLIENT_TRANSPORT_HAS_NO_UNSECURE_TRANSPORT_LAYER); } } } // Set the channel timeout. If the timeout was changed before the // channel was created, then it may not have the correct timeout. By setting it here, // the channel is sure to have the correct timeout value next time the channel is read. if( m_pActiveChannel != NULL) { m_pActiveChannel->setTimeout( m_lChannelTimeout); m_pActiveChannel->setConnectTimeout( m_lChannelConnectTimeout); } logExit() } /* * HTTPTransport::openConnection(). */ int HTTPTransport:: openConnection() { logEntryTransport("HTTPTransport::openConnection") // If connection not valid or reopen required, open a connection to server. if (m_pActiveChannel->reopenRequired() || m_bReopenConnection) { closeConnection(true); if( m_pActiveChannel->open() != AXIS_SUCCESS) { logThrowExceptionWithData("HTTPTransportException - CLIENT_TRANSPORT_OPEN_CONNECTION_FAILED", m_pActiveChannel->GetLastErrorMsg().c_str()) throw HTTPTransportException( CLIENT_TRANSPORT_OPEN_CONNECTION_FAILED, m_pActiveChannel->GetLastErrorMsg().c_str()); } } logExitWithReturnCode(AXIS_SUCCESS) return AXIS_SUCCESS; } /* * HTTPTransport::closeConnection(). */ void HTTPTransport:: closeConnection(bool forceClose) { logEntryTransport("HTTPTransport::closeConnection") resetInputStateMachine(); // We will close the connection if forced close, or if "Connection: close" // header was detected. if (forceClose || m_bReopenConnection) { m_bReopenConnection = false; m_pActiveChannel->close(); } logExit() } /* * HTTPTransport::flushOutput() Is called when the message construction is * complete. The message is ready to be 'flushed out' onto the network. * Check if the URI has changed. If it has, then need to open a new Channel * instance before transmitting the message. * * @return AXIS_TRANSPORT_STATUS If the method completes successfully, then * this will be set to TRANSPORT_FINISHED. Otherwise, an exception will have * been thrown. */ AXIS_TRANSPORT_STATUS HTTPTransport:: flushOutput() throw (AxisException, HTTPTransportException) { logEntryTransport("HTTPTransport::flushOutput") // since we are writing...we need to reset flag indicating a read has been done. m_bHasReadBeenDone = false; char *utf8BufHeader = NULL; // buffer for HTTP header when converting to utf8. char *utf8BufPayload = NULL; // buffer for HTTP payload when converting to utf8. char buff[24]; // Handle re-sending payload in case of redirect. int payLoadLength = m_strBytesToSend.length(); const char *payLoad = m_strBytesToSend.c_str(); if (payLoadLength == 0 && m_bPerformAutoRedirect && m_iMaximumAutoRedirects > 0) { payLoadLength = m_strBytesToSendRedirect.length(); payLoad = m_strBytesToSendRedirect.c_str(); } // Send HTTP headers and body try { #ifndef __OS400__ // Generate HTTP header string - need to set content-length before generating headers. sprintf( buff, "%d", payLoadLength); this->setTransportProperty ("Content-Length", buff); generateHTTPHeaders (); m_pActiveChannel->writeBytes(m_strHeaderBytesToSend.c_str(), m_strHeaderBytesToSend.length()); m_pActiveChannel->writeBytes(payLoad, payLoadLength); #else // Generate HTTP header string - need to set content-length before generating headers. // We need to convert payload to UTF-8 first to get accurate length of payload. utf8BufPayload = PlatformLanguage::toUTF8(payLoad, payLoadLength+1); payLoadLength = strlen(utf8BufPayload); sprintf( buff, "%d", payLoadLength); this->setTransportProperty ("Content-Length", buff); generateHTTPHeaders (); // Convert HTTP header to UTF-8. (Really should be US-ASCII, but it is compatible with UTF-8.) utf8BufHeader = PlatformLanguage::toUTF8((const char *)m_strHeaderBytesToSend.c_str(), m_strHeaderBytesToSend.length()+1); // Write out the HTTP header followed by the HTTP payload. m_pActiveChannel->writeBytes(utf8BufHeader, strlen(utf8BufHeader)); delete utf8BufHeader; utf8BufHeader = NULL; m_pActiveChannel->writeBytes(utf8BufPayload, payLoadLength); delete utf8BufPayload; utf8BufPayload = NULL; #endif } catch(...) { delete utf8BufHeader; delete utf8BufPayload; resetOutputStateMachine(); logRethrowException() throw; } resetOutputStateMachine(); logExit() return TRANSPORT_FINISHED; } /* HTTPTransport::getHTTPHeaders() Called to retrieve the current HTTP header * information block that will preceed the SOAP message. * * @return const char* Pointer to a NULL terminated character string containing * the HTTP header block of information. */ const char * HTTPTransport:: generateHTTPHeaders() { logEntryTransport("HTTPTransport::generateHTTPHeaders") URL & url = m_pActiveChannel->getURLObject(); unsigned short uiPort; char buff[32]; m_strHeaderBytesToSend = m_strHTTPMethod + " "; if (m_bUseProxy) m_strHeaderBytesToSend += url.getURL (); else m_strHeaderBytesToSend += url.getResource (); m_strHeaderBytesToSend += " "; m_strHeaderBytesToSend += m_strHTTPProtocol; // The "Host:" HTTP request header should specify the Internet host and port number of // the resource being requested, as obtained from the original URI given by the user or // referring resource. m_strHeaderBytesToSend += "\r\nHost: "; m_strHeaderBytesToSend += url.getHostName (); uiPort = url.getPort(); sprintf(buff, ":%u\r\n", uiPort); m_strHeaderBytesToSend += buff; // The Content-Type must be set, but it may already be set. bool foundCT = false; for (unsigned int j = 0; j < m_vHTTPHeaders.size (); j++) if (0==PLATFORM_STRCASECMP(AXIS_CONTENT_TYPE,m_vHTTPHeaders[j].first.c_str())) foundCT = true; if (!foundCT) m_strHeaderBytesToSend += AXIS_CONTENT_TYPE ": text/xml; charset=UTF-8\r\n"; // set basic auth if the username and password are both set if(getUsername()!=NULL && getPassword() !=NULL) { char *cpUsernamePassword = new char[strlen (getUsername()) + strlen (getPassword()) + 2]; strcpy (cpUsernamePassword, getUsername()); strcat (cpUsernamePassword, ":"); strcat (cpUsernamePassword, getPassword()); // We use apr_base64_encode and NOT apr_base64_encode_binary since this is // textual data that needs to be converted to ascii on ebcdic platforms. // On ascii platforms the data will just get passed to apr_base64_encode_binary(). int len = apr_base64_encode_len (strlen (cpUsernamePassword)); AxisChar *base64Value = new AxisChar[len + 1]; len = apr_base64_encode(base64Value,(const char *) cpUsernamePassword); std::string strValue = "Basic "; strValue += base64Value; setTransportProperty ("Authorization", strValue.c_str ()); delete[]cpUsernamePassword; delete[]base64Value; } // Set other HTTP headers but not cookies as they are put in afterwards. for (unsigned int i = 0; i < m_vHTTPHeaders.size (); i++) { if( PLATFORM_STRCASECMP(m_vHTTPHeaders[i].first.c_str(), "Cookie")!=0) { m_strHeaderBytesToSend += m_vHTTPHeaders[i].first; m_strHeaderBytesToSend += ": "; m_strHeaderBytesToSend += m_vHTTPHeaders[i].second; m_strHeaderBytesToSend += "\r\n"; if (0==PLATFORM_STRCASECMP("Connection",m_vHTTPHeaders[i].first.c_str()) && 0==PLATFORM_STRCASECMP("close", m_vHTTPHeaders[i].second.c_str())) m_bReopenConnection = true; } } // Set cookies if (m_bMaintainSession && (m_vCookies.size () > 0)) { string cookieHeader=""; // Add in all the cookies ar the last one because that shouldn't have a ';' on it for (unsigned int var = 0; var < m_vCookies.size()-1; var++) { cookieHeader += m_vCookies[var].first; cookieHeader += "="; cookieHeader += m_vCookies[var].second; cookieHeader += ";"; } // add on the last cookie cookieHeader += m_vCookies[m_vCookies.size()-1].first; cookieHeader += "="; cookieHeader += m_vCookies[m_vCookies.size()-1].second; m_strHeaderBytesToSend += "Cookie: "; m_strHeaderBytesToSend += cookieHeader; m_strHeaderBytesToSend += "\r\n"; // Now add this header in to the list of sent headers if it's not already been set ! If it has been then // override it bool b_keyFound=false; for (unsigned int i = 0; i < m_vHTTPHeaders.size(); i++) { if (PLATFORM_STRCASECMP(m_vHTTPHeaders[i].first.c_str(), "Cookie") == 0) { m_vHTTPHeaders[i].second = (string) cookieHeader; b_keyFound = true; break; } } if(!b_keyFound) { m_vHTTPHeaders.push_back( std::make_pair( (string) "Cookie", (string) cookieHeader)); } } m_strHeaderBytesToSend += "\r\n"; logExit() return m_strHeaderBytesToSend.c_str (); } /* HTTPTransport::getHTTPMethod() Is a public method that gets the HTTP method * (i.e. GET or POST) that will be part of the HTTP header block. * * @return const char* Pointer to a NULL terminated character string containing * the HTTP method. */ const char * HTTPTransport:: getHTTPMethod() { return m_strHTTPMethod.c_str (); } /* HTTPTransport::setHTTPMethod( Method) Is a public method that sets the HTTP * method (i.e. POST or GET) that will be part of the HTTP header block. * * @param const char* Pointer to a NULL terminated character string containing * the new HTTP method. */ void HTTPTransport:: setHTTPMethod( const char *cpMethod) { logEntryTransport("HTTPTransport::setHTTPMethod") logDebugArg1("HTTP method is %s", cpMethod ? cpMethod : "NULL") if (cpMethod) m_strHTTPMethod = std::string( cpMethod); logExit() } /* HTTPTransport::sendBytes( SendBuffer, BufferId) Is a public method that * concatinates the new send buffer to the bytes to send string. This message * will only be sent when a flush buffer is received. * * @param const char* SendBufer - Pointer to a NULL terminated character string * containing all or some of the transmission message. * @param const void* BufferId - Pointer. This parameter is ignored. * * @return AXIS_TRANSPORT_STATUS Value to a status value (currently it will * always be TRANSPORT_IN_PROGRESS). */ AXIS_TRANSPORT_STATUS HTTPTransport:: sendBytes( const char *pcSendBuffer, const void *pBufferId) { m_strBytesToSend += std::string (pcSendBuffer); // Since we are sending new data, ensure nothing in redirect buffer. if (m_strBytesToSendRedirect.length() > 0) { m_iNbrOfRedirectAttempts = 0; m_strBytesToSendRedirect = ""; } return TRANSPORT_IN_PROGRESS; } bool HTTPTransport:: isThereResponseData() { logEntryTransport("HTTPTransport::isThereResponseData") // We do not want to consume any SOAP data, just find out if there is any data. int bufLen = 0; if (!m_bHasReadBeenDone) getBytes(NULL, &bufLen); bool returnValue = (m_GetBytesState != eWaitingForHTTPHeader || m_iBytesLeft != 0); logExitWithBoolean(returnValue) return returnValue; } /* HTTPTransport::getBytes( ReceiveBuffer, Size) Is a public method that will * receive the synchronous reply to the sent message. * * @param const char* ReceiveBuffer - Pointer to a character string that on * return will containing all or part of the received message. * @param int* Size - Pointer to an integer value that on return will contain * the length of the received message. * * @return AXIS_TRANSPORT_STATUS Value to the status o message reception * (TRANSPORT_FINISHED or TRANSPORT_IN_PROGRESS). */ AXIS_TRANSPORT_STATUS HTTPTransport:: getBytes( char * pcBuffer, int * piSize) throw (AxisException, HTTPTransportException) { logEntryTransport("HTTPTransport::getBytes") // The method getBytes has three distinct states. These are defined as // follows:- // eWaitingForHTTPHeader - Waiting for HTTP response header. Initiate processing // that will read an HTTP header. One caveat. // We can be in this state but still have data // to be processed. So if there is data, it will // be returned and we will not attempt to read // an HTTP response. // eSOAPMessageIsChunked - Keep reading until a zero length chunk is // encountered, signifying end of response. // eSOAPMessageIsNotChunked - Keep reading until the connection is closed. // eSOAPMessageHasContentLength - Keep reading until content-length is zero. // Read in an HTTP header if we are waiting for one. Now this is a bit tricky // and I was fooled by this when I was fix problems in the code so here is // the tricky part. If we have data in the buffer then we need to return // the data - i.e. we will skip reading HTTP header until data has been // consumed! Not the most elegant of designs, but it works. The reason is // that we read in data later on and then change the state based on whether // there is more data to read or not. The caller of this routine will have // data copied into buffer, but the buffer may not be big enough to consume // the data, so that is why we have data in the buffer left and thus must // skip reading HTTP header until data has been consumed. if (m_GetBytesState == eWaitingForHTTPHeader) if (m_iBytesLeft == 0) readHTTPHeader(); // Process data based on current state. int iBytesToCopy = m_iBytesLeft; if (m_GetBytesState == eSOAPMessageHasContentLength) iBytesToCopy = getBytes_MessageHasContentLength(pcBuffer, piSize); else if (m_GetBytesState == eSOAPMessageIsNotChunked) iBytesToCopy = getBytes_MessageIsNotChunked(pcBuffer, piSize); else if( m_GetBytesState == eSOAPMessageIsChunked) iBytesToCopy = getBytes_MessageIsChunked(pcBuffer, piSize); // Copy as much of the message to the parser buffer as possible. if (iBytesToCopy > 0 && *piSize > 0) { int iToCopy = (*piSize < iBytesToCopy) ? *piSize : iBytesToCopy; m_strReceived.copy(pcBuffer, iToCopy); *piSize = iToCopy; m_iBytesLeft -= iToCopy; m_strReceived.erase(0, iToCopy); } else *piSize = 0; logExit() // Set transport status return (m_iBytesLeft > 0 || (m_GetBytesState != eWaitingForHTTPHeader)) ? TRANSPORT_IN_PROGRESS : TRANSPORT_FINISHED; } /** * getBytes_MessageHasContentLength() is used when the HTTP response * includes the Content-Length HTTP header. */ int HTTPTransport:: getBytes_MessageHasContentLength(char * pcBuffer, int * piSize) { logEntryTransport("HTTPTransport::getBytes_MessageHasContentLength") // If we do not have data to give back, read more. if (m_iContentLength > 0 && m_iBytesLeft == 0) { getNextDataPacket( "No data available for message."); // Check for Mime header if( m_bMimeTrue) { processRootMimeBody(); m_iBytesLeft = m_strReceived.length(); } } // Subtract message length (so far) from expected content length. if( m_iContentLength >= m_iBytesLeft) m_iContentLength -= m_iBytesLeft; else m_iContentLength = 0; // If all of the message has been received, then reset the process state if (0 == m_iContentLength) m_GetBytesState = eWaitingForHTTPHeader; logExitWithInteger(m_iBytesLeft) // Return number of bytes that can be copied return m_iBytesLeft; } /** * getBytes_MessageIsNotChunked() is used when the HTTP response * is not chunked and does not include the Content-Length HTTP header. * Thus, the only way we know that the data has been read is to read * from the input stream until a zero length read completes. */ int HTTPTransport:: getBytes_MessageIsNotChunked(char * pcBuffer, int * piSize) { logEntryTransport("HTTPTransport::getBytes_MessageIsNotChunked") // Keep reading until the connection is closed by the server. getNextDataPacket( "Expecting server connection to close."); // Check for Mime header if( m_bMimeTrue) { processRootMimeBody(); m_iBytesLeft = m_strReceived.length(); } logExitWithInteger(m_iBytesLeft) // Return number of bytes that can be copied return m_iBytesLeft; } /** * getBytes_MessageIsChunked() is used when the HTTP response * is chunked. The chunked encoding modifies the body of a message * in order to transfer it as a series of chunks, each with its own * size indicator, followed by an OPTIONAL trailer containing * entity-header fields. This allows dynamically produced content to * be transferred along with the information necessary for the recipient * to verify that it has received the full message. Format is as follows: * * Chunked-Body = *chunk * last-chunk * trailer * CRLF * * chunk = chunk-size [ chunk-extension ] CRLF * chunk-data CRLF * chunk-size = 1*HEX * last-chunk = 1*("0") [ chunk-extension ] CRLF * * chunk-extension= *( ";" chunk-ext-name [ "=" chunk-ext-val ] ) * chunk-ext-name = token * chunk-ext-val = token | quoted-string * chunk-data = chunk-size(OCTET) * trailer = *(entity-header CRLF) * * The chunk-size field is a string of hex digits indicating the size of the * chunk. The chunked encoding is ended by any chunk whose size is zero, * followed by the trailer, which is terminated by an empty line. The * trailer is ignored by this routine. */ int HTTPTransport:: getBytes_MessageIsChunked(char * pcBuffer, int * piSize) { logEntryTransport("HTTPTransport::getBytes_MessageIsChunked") // We only read the next chunk if the chunk we have has been consumed. if (m_iChunkedDataLeftToConsume == 0) { // First, ensure that we remove any leading CRLF. This would be the // ending CRLF for a chunk. A chunk has this form: // chunk-data CRLF if (m_iBytesLeft > 1 && m_strReceived[0] == ASCII_C_CR && m_strReceived[1] == ASCII_C_LF) { m_strReceived.erase(0, 2); m_iBytesLeft = m_strReceived.length(); } // Get chunk size. We may already have it. It is set to zero only if we have // not obtained it. It is also set to zero for end of chunk size of zero, but we should // never be coming down to read it. if (m_iNextChunkedDataSize > 0) { // Need to remove the chunk-size line since it has been already processed. // Should never be the case where we get npos, but we check just in case. string::size_type iEndOfChunkSize = m_strReceived.find(ASCII_S_CRLF); if (iEndOfChunkSize != std::string::npos) { m_strReceived.erase(0, iEndOfChunkSize + 2); m_iBytesLeft = m_strReceived.length(); } m_iChunkedDataLeftToConsume = m_iNextChunkedDataSize; m_iNextChunkedDataSize = 0; } else m_iChunkedDataLeftToConsume = getChunkSize(); // We should never be in a situation where caller invokes getBytes // and there is no data to read, but just in case... if (m_iChunkedDataLeftToConsume > 0) { // If the chunk size is larger than the available data, then read in more // data until all of the chunk has been read. We read in the CRLF that // ends a chunk and at least one more byte so that later we can read the // next chunk size. while ((m_iChunkedDataLeftToConsume + 2) >= m_iBytesLeft) getNextDataPacket( "No data available for next chunk."); // Determine next chunk size, mainly to determine if zero-length, // meaning end-of-chunk processing and thus a state reset. // "+ 2" used in if-check for CRLF since at end of each chunk there should be CRLF. if (0 == (m_iNextChunkedDataSize = getChunkSize(m_iChunkedDataLeftToConsume + 2))) m_GetBytesState = eWaitingForHTTPHeader; // The chunk may contain Mime data (this depends on information in the HTTP header). if( m_bMimeTrue) { processRootMimeBody(); m_iBytesLeft = m_strReceived.length(); } } else m_GetBytesState = eWaitingForHTTPHeader; } // OK, now we need to determine how much of the chunk will be left // over depending on the buffer length that is passed in. int iDataToCpyLen; if (*((unsigned int *)piSize) >= m_iChunkedDataLeftToConsume) { iDataToCpyLen = m_iChunkedDataLeftToConsume; m_iChunkedDataLeftToConsume = 0; } else { iDataToCpyLen = *piSize; m_iChunkedDataLeftToConsume -= *piSize; } logExitWithInteger(iDataToCpyLen) return iDataToCpyLen; } /** * getChunkSize() is method used to obtain the chunk size. * Once obtained, it may remove the chunksize line from * the data since it is not data to be returned. */ int HTTPTransport:: getChunkSize(string::size_type pos) { logEntryTransport("HTTPTransport::getChunkSize") string::size_type iEndOfChunkSize; string::size_type iEndOfChunkLine; // Here is what a chunk size line may look like: // chunk-size [ chunk-extension ] CRLF // where chunk-extension is defined as: // *( ";" chunk-ext-name [ "=" chunk-ext-val ] ) // Get complete line. while( (iEndOfChunkLine = m_strReceived.find(ASCII_S_CRLF, pos)) == std::string::npos ) getNextDataPacket("Could not find delimiter for end of chunk size."); string chunkSizeLine = m_strReceived.substr( pos, iEndOfChunkLine-pos); // Look to see if there are any extensions - these are put in brackets. if ((iEndOfChunkSize = chunkSizeLine.find(ASCII_S_LEFTPAREN)) != std::string::npos) chunkSizeLine.erase(iEndOfChunkSize); // Convert the hex string into the length of the chunk. int chunkSize = axtoi((char *)chunkSizeLine.c_str()); // A chunk size of -1 means either we received an invalid chunk size // or the code is not parsing chunks correctly. Whatever the case, // we are hosed and there is no use continuing. if (-1 == chunkSize) { m_bReopenConnection = true; // We will only dump out just 40 bytes...otherwise we may be // dumping out huge chunks of data if (chunkSizeLine.length() > 40) chunkSizeLine = chunkSizeLine.substr(0, 40); PLATFORM_ASCTOSTR(chunkSizeLine.c_str()); string errorMessage = string("Chunk size (") + chunkSizeLine + string(") not valid."); logThrowExceptionWithData("HTTPTransportException - SERVER_TRANSPORT_HTTP_EXCEPTION", errorMessage.c_str()) throw HTTPTransportException(SERVER_TRANSPORT_HTTP_EXCEPTION, errorMessage.c_str()); } // If chunksize is zero, remove the line including ending CRLFCRLF. This line // will never be consumed so it needs to be removed from the buffer. Otherwise, // if chunksize is not zero, we only remove the chunksize line only if offset is // zero, i.e. at the beginning of line. If offset is not zero, it means this // request came down to peek at the next chunksize. if (0 == chunkSize) { string::size_type iEndOfResponse; while ((iEndOfResponse = m_strReceived.find(ASCII_S_CRLFCRLF, pos)) == std::string::npos ) getNextDataPacket("Could not find delimiter for end of chunked response."); m_strReceived.erase(pos, iEndOfResponse + 4); // "+4" for CRLFCRLF } else if (pos == 0) m_strReceived.erase(0, iEndOfChunkLine + 2); // "+2" for CRLF m_iBytesLeft = m_strReceived.length(); logExitWithInteger(chunkSize) // return chunksize return chunkSize; } /* HTTPTransport::setTransportProperty( Type, Value) Is an overloaded public * method used to set a HTTP transport or SSL implementation property. * * @param AXIS_TRANSPORT_INFORMATION_TYPE Type is an enumerated type containing * the type of information to be stored in either the HTTP Header or SSL * implementation settings. * @param const char* Value is a NULL terminated character string containing * the value associated with the type. */ int HTTPTransport:: setTransportProperty( AXIS_TRANSPORT_INFORMATION_TYPE type, const char *value) throw (HTTPTransportException) { logEntryTransport("HTTPTransport::setTransportProperty") const char *key = NULL; int iSuccess = AXIS_SUCCESS; switch (type) { case SOAPACTION_HEADER: { key = "SOAPAction"; break; } case SERVICE_URI: // need to set ? { break; } case OPERATION_NAME: // need to set ? { break; } case SOAP_MESSAGE_LENGTH: { key = "Content-Length"; // this Axis transport handles only HTTP break; } case TRANSPORT_PROPERTIES: { if (value && (PLATFORM_STRCASECMP(value, "Connection: close") == 0)) setTransportProperty("Connection", "close"); else if( m_pActiveChannel != NULL) m_pActiveChannel->setTransportProperty( type, value); break; } case SECURE_PROPERTIES: { if( m_pActiveChannel != NULL) iSuccess = m_pActiveChannel->setSecureProperties( value); break; } case CHANNEL_HTTP_DLL_NAME: { if( m_pChannelFactory != NULL) m_pNormalChannel = m_pChannelFactory->LoadChannelLibrary( UnsecureChannel, value); break; } case CHANNEL_HTTP_SSL_DLL_NAME: { if( m_pChannelFactory != NULL) m_pSecureChannel = m_pChannelFactory->LoadChannelLibrary( SecureChannel, value); break; } case ENABLE_AUTOMATIC_REDIRECT: { if (value && (PLATFORM_STRCASECMP(value, "true") == 0)) m_bPerformAutoRedirect = true; else m_bPerformAutoRedirect = false; break; } case MAX_AUTOMATIC_REDIRECT: { if (value) m_iMaximumAutoRedirects = atoi(value); if (m_iMaximumAutoRedirects < 1) m_iMaximumAutoRedirects = 0; char buffer[100]; sprintf(buffer, "%d", m_iMaximumAutoRedirects); m_strMaximumAutoRedirects = (const char *)buffer; break; } case IO_TIMEOUT: { if (value) setTimeout((long)atoi(value)); break; } case CONNECT_TIMEOUT: { if (value) setConnectTimeout((long)atoi(value)); break; } default: { break; } } if( key) setTransportProperty( key, value); logExitWithReturnCode(iSuccess) return iSuccess; } /* HTTPTransport::setTransportProperty( Key, Value) Is an overloaded public * method used to set a HTTP transport or SSL implementation property. * * Any existing transport property will be replaced with the new value. * * @param const char* Key is a NULL terminated character string containing * the type of information to be stored in either the HTTP Header or SSL * implementation settings. * @param const char* Value is a NULL terminated character string containing * the value associated with the type. */ int HTTPTransport:: setTransportProperty( const char *pcKey, const char *pcValue) throw (HTTPTransportException) { logEntryTransport("HTTPTransport::setTransportProperty") int iSuccess = AXIS_SUCCESS; if( pcKey && pcValue) { logDebugArg2("Transport property to set: %s=%s", pcKey, pcValue) bool b_KeyFound = false; // Cookies are handled elsewhere. For other properties, we first check to // see if there is a matching (i.e. duplicate property) already set. If one // exists, we replace the value. if(PLATFORM_STRCASECMP(pcKey, "Cookie")==0) { iSuccess = addCookie(pcValue); b_KeyFound = true; } else { for (unsigned int i = 0; i < m_vHTTPHeaders.size(); i++) if (PLATFORM_STRCASECMP(m_vHTTPHeaders[i].first.c_str(), pcKey) == 0) { m_vHTTPHeaders[i].second = (string) pcValue; b_KeyFound = true; break; } } // If the property was not handled, then add it to the list of transport properties. if( !b_KeyFound) m_vHTTPHeaders.push_back( std::make_pair( (string) pcKey, (string) pcValue)); } logExitWithReturnCode(iSuccess) return iSuccess; } /* HTTPTransport::getTransportProperty( Type) Is a public method that will * return the HTTP Header/SSL implementation value associated with type. * * @param AXIS_TRANSPORT_INFORMATION_TYPE Type is an enumerated type containing * the type of information to be retrieved in either the HTTP Header or SSL * implementation settings. * * @return const char* Value is a NULL terminated character string containing * the value associated with the type. */ const char * HTTPTransport:: getTransportProperty( AXIS_TRANSPORT_INFORMATION_TYPE eType) throw (HTTPTransportException) { logEntryTransport("HTTPTransport::getTransportProperty") const char *pszPropValue = NULL; switch( eType) { case SOAPACTION_HEADER: { int iIndex = FindTransportPropertyIndex( "SOAPAction"); if (iIndex > -1) pszPropValue = m_vHTTPHeaders[iIndex].second.c_str(); break; } case SERVICE_URI: { break; } case OPERATION_NAME: { break; } case SOAP_MESSAGE_LENGTH: { int iIndex = FindTransportPropertyIndex( "Content-Length"); if (iIndex > -1) pszPropValue = m_vHTTPHeaders[iIndex].second.c_str(); break; } case TRANSPORT_PROPERTIES: case SECURE_PROPERTIES: { pszPropValue = m_pActiveChannel->getTransportProperty( eType); break; } case CHANNEL_HTTP_SSL_DLL_NAME: case CHANNEL_HTTP_DLL_NAME: case CONTENT_TYPE: { break; } case ENABLE_AUTOMATIC_REDIRECT: { pszPropValue = m_bPerformAutoRedirect ? "true" : "false"; break; } case MAX_AUTOMATIC_REDIRECT: { pszPropValue = m_strMaximumAutoRedirects.c_str(); break; } case IO_TIMEOUT: { pszPropValue = m_strChannelTimeout.c_str(); break; } case CONNECT_TIMEOUT: { pszPropValue = m_strChannelConnectTimeout.c_str(); break; } } logExitWithString(pszPropValue) return pszPropValue; } /* HTTPTransport::FindTransportPropertyIndex( Key) Is a private method that will * return the HTTP Header index associated with Key. * * @param AXIS_TRANSPORT_INFORMATION_TYPE Key is an enumerated type containing * the type of information to be retrieved in either the HTTP Header settings. * * @return int Index is an index to the key within the HTTP Header list. If * the return value is -1, then the key was not found. */ int HTTPTransport:: FindTransportPropertyIndex( string sKey) { bool bKeyFound = false; int iIndex = 0; while( (unsigned int) iIndex < m_vHTTPHeaders.size() && !bKeyFound) { if (!m_vHTTPHeaders[iIndex].first.compare( sKey)) bKeyFound = true; else iIndex++; } if( !bKeyFound) iIndex = -1; return iIndex; } /* HTTPTransport::getServiceName() Is a public method to return the HTTP * Header service name. * * @return const char* Value is a NULL terminated character string containing * the value associated with the service name. */ const char * HTTPTransport:: getServiceName() { //Assume SOAPAction header to contain service name int iIndex = FindTransportPropertyIndex( "SOAPAction"); if (iIndex > -1) return m_vHTTPHeaders[iIndex].second.c_str(); return NULL; } AXIS_PROTOCOL_TYPE HTTPTransport:: getProtocol() { return m_eProtocolType; } int HTTPTransport:: setProtocol( AXIS_PROTOCOL_TYPE eProtocol) { if (eProtocol == APTHTTP1_1 || eProtocol == APTHTTP1_0) { m_eProtocolType = eProtocol; m_strHTTPProtocol = (m_eProtocolType == APTHTTP1_1) ? "HTTP/1.1" : "HTTP/1.0"; return AXIS_SUCCESS; } return AXIS_FAIL; } /** * HTTPTransport::getSubProtocol() is a public method that is supposed to * return the sub protocol (currently this method always return POST). * This method is supposed to return whether it is http GET or POST */ int HTTPTransport:: getSubProtocol() { //TODO // for SimpleAxisServer assume POST return AXIS_HTTP_POST; //return 0; } /* HTTPTransport::setProxy( Host, Port) Is a public method for setting or * updating the proxy for the connection. * * @param const char* Host is a NULL terminated character string containing the new * proxy host. * @param unsigned int Port is the new proxy port number. */ void HTTPTransport:: setProxy( const char *pcProxyHost, unsigned int uiProxyPort) { logEntryTransport("HTTPTransport::setProxy") logDebugArg2("Proxy host is %s, proxy port is %d", pcProxyHost ? pcProxyHost : "NULL", uiProxyPort) m_pActiveChannel->setProxy(pcProxyHost,uiProxyPort); m_strProxyHost = pcProxyHost; m_uiProxyPort = uiProxyPort; m_bUseProxy = true; logExit() } /* HTTPTransport::setTimeout( Timeout) Is a public method for setting the * current maximum timeout period between that can elapse between receiving * message parts. * * @param long Timeout is a long value in seconds. */ void HTTPTransport:: setTimeout( long lSeconds) { logEntryTransport("HTTPTransport::setTimeout") logDebugArg1("I/O timeout: %d", lSeconds) if( m_pActiveChannel != NULL) m_pActiveChannel->setTimeout( lSeconds); m_lChannelTimeout = lSeconds; char buffer[100]; sprintf(buffer, "%d", m_lChannelTimeout); m_strChannelTimeout = (const char *)buffer; logExit() } /* HTTPTransport::setConnectTimeout( Timeout) Is a public method for setting the * current maximum connect timeout. * * @param long Timeout is a long value in seconds. */ void HTTPTransport:: setConnectTimeout( long lSeconds) { logEntryTransport("HTTPTransport::setConnectTimeout") logDebugArg1("Connect timeout: %d", lSeconds) if( m_pActiveChannel != NULL) m_pActiveChannel->setConnectTimeout( lSeconds); m_lChannelConnectTimeout = lSeconds; char buffer[100]; sprintf(buffer, "%d", m_lChannelConnectTimeout); m_strChannelConnectTimeout = (const char *)buffer; logExit() } /* HTTPTransport::getHTTPProtocol() Is a public method for retrieving the * current HTTP protocol settings. * * @return const char* HTTPProtocol is a NULL terminated character string * containing the HTTP protocol. */ const char * HTTPTransport:: getHTTPProtocol() { return m_strHTTPProtocol.c_str (); } /* axtoi( Hex) Is a private method to convert an ascii hex string to an integer. */ static int axtoi( char *pcHexString) { int iN = 0; // position in string int iM = 0; // position in digit[] to shift int iCount; // loop index int intValue = 0; // integer value of hex string int iDigit[32]; // hold values to convert bool badChunkSize = false; while( iN < 32 && pcHexString[iN] != '\0') { if( pcHexString[iN] >= ASCII_C_ZERO && pcHexString[iN] <= ASCII_C_NINE) { iDigit[iN] = pcHexString[iN] & 0x0f; //convert to int } else if ((pcHexString[iN] >= ASCII_C_LOWERCASEA && pcHexString[iN] <= ASCII_C_LOWERCASEF) || (pcHexString[iN] >= ASCII_C_UPPERCASEA && pcHexString[iN] <= ASCII_C_UPPERCASEF)) { iDigit[iN] = (pcHexString[iN] & 0x0f) + 9; //convert to int } else { badChunkSize = true; break; } iN++; } if (badChunkSize || pcHexString[0] == '\0') return -1; iCount = iN; iM = iN - 1; iN = 0; while( iN < iCount) { // digit[n] is value of hex digit at position n (m << 2) is the // number of positions to shift OR the bits into return value intValue = intValue | (iDigit[iN] << (iM << 2)); iM--; // adjust the position to set iN++; // next digit to process } return intValue; } /** * HTTPTransport::processHTTPHeader() Is a public method used to * parse the HTTP header of the response message. */ void HTTPTransport:: processHTTPHeader() { logEntryTransport("HTTPTransport::processHTTPHeader") // Prior to calling this method, m_strResponseHTTPHeaders should have been // set to the HTTP header in the response, which is in ascii. For EBCDIC // systems, we need to convert the data from ASCII to EBCDIC. // WARNING: Response header processing within here should not be moved // unless you search for the right string - ascii or ebcdic. // Since we convert everything to EBCDIC, we can use literal // strings such as "\r\n" instead of ascii equivalent "\x0d\x0a". PLATFORM_ASCTOSTR(m_strResponseHTTPHeaders.c_str()); // The first line of a Response message is the Status-Line: // // Status-Line = HTTP-Version SP Status-Code SP Reason-Phrase CRLF // // Parse it to get status code and reason-phrase. string::size_type iPosition = std::string::npos; string::size_type iStartPosition = iPosition; if( (iPosition = m_strResponseHTTPHeaders.find( "HTTP" )) == std::string::npos) { logThrowExceptionWithData("HTTPTransportException - SERVER_TRANSPORT_UNKNOWN_HTTP_RESPONSE", "Protocol is not HTTP.") throw HTTPTransportException( SERVER_TRANSPORT_UNKNOWN_HTTP_RESPONSE, "Protocol is not HTTP."); } m_strResponseHTTPProtocol = m_strResponseHTTPHeaders.substr( iPosition, strlen("HTTP/1.x")); iPosition += strlen("HTTP/1.x"); while( m_strResponseHTTPHeaders[iPosition] == ' ') iPosition++; iStartPosition = iPosition; while( m_strResponseHTTPHeaders[iPosition] != ' ' ) iPosition++; std::string strResponseHTTPStatusCode = m_strResponseHTTPHeaders.substr( iStartPosition,iPosition - iStartPosition); m_iResponseHTTPStatusCode = atoi(strResponseHTTPStatusCode.c_str()); iStartPosition = ++iPosition; iPosition = m_strResponseHTTPHeaders.find( "\n" ); m_strResponseHTTPStatusMessage = m_strResponseHTTPHeaders.substr( iStartPosition,iPosition - iStartPosition - 1); // reached the end of the first line iStartPosition = m_strResponseHTTPHeaders.find( "\n" ); iStartPosition++; // Now read the header fields and add to vector. do { // Remove consumed line from header string. m_strResponseHTTPHeaders = m_strResponseHTTPHeaders.substr( iStartPosition); // now find end of line...if we do not find one, break out. iPosition = m_strResponseHTTPHeaders.find( "\n" ); if( iPosition == std::string::npos) break; iStartPosition = iPosition + 1; // store line in work buffer and find seperator ":", if no seperator, // continue on with next line. std::string strHeaderLine = m_strResponseHTTPHeaders.substr(0, iPosition); string::size_type iSeperator = strHeaderLine.find( ":" ); if( iSeperator == std::string::npos) continue; // Store as key-value pairs string key = strHeaderLine.substr( 0, iSeperator); string value = strHeaderLine.substr( iSeperator + 1, strHeaderLine.length() - iSeperator - 2); trim(value); trim(key); m_vResponseHTTPHeaders.push_back( std::make_pair( key, value)); // Content length set? Chunked overrides Content-length. It should be noted // that on successful one-way requests Content-Length would be set to zero. if (PLATFORM_STRCASECMP(key.c_str(), "Content-Length") == 0) if (m_GetBytesState != eSOAPMessageIsChunked) { m_iContentLength = atoi(value.c_str()); m_GetBytesState = eSOAPMessageHasContentLength; } // Redirect? if (PLATFORM_STRCASECMP(key.c_str(), "Location") == 0) m_strResponseLocationURI = value; // Is chunked? if ((PLATFORM_STRCASECMP(key.c_str(), "Transfer-Encoding") == 0) && (PLATFORM_STRCASECMP(value.c_str(), "chunked") == 0)) m_GetBytesState = eSOAPMessageIsChunked; // Now handle whether we are going to close connection after processing // request. If HTTP/1.0 we have to always close the connection by default; otherwise, // we assume persistent connection by default. if( m_eProtocolType == APTHTTP1_0) m_bReopenConnection = true; // We need to close the connection and open a new one if we have 'Connection: close' if ((PLATFORM_STRCASECMP(key.c_str(), "Connection") == 0) && (PLATFORM_STRCASECMP(value.c_str(), "close") == 0)) { m_bReopenConnection = true; m_pActiveChannel->closeQuietly( true); } // We need to close the connection and open a new one if we have 'Proxy-Connection: close' if ((PLATFORM_STRCASECMP(key.c_str(), "Proxy-Connection") == 0) && (PLATFORM_STRCASECMP(value.c_str(), "close") == 0)) m_bReopenConnection = true; // For both HTTP/1.0 and HTTP/1.1, We need to keep the connection if we have 'Connection: Keep-Alive' if ((PLATFORM_STRCASECMP(key.c_str(), "Connection") == 0) && (PLATFORM_STRCASECMP(value.c_str(), "Keep-Alive") == 0)) m_bReopenConnection = false; // Look for cookies if ( m_bMaintainSession ) if (PLATFORM_STRCASECMP(key.c_str(), "Set-Cookie") == 0) addCookie(value); /* If Content-Type: Multipart/Related; boundary=<MIME_boundary>; type=text/xml; start="<content id>" */ if (PLATFORM_STRCASECMP(key.c_str(), "Content-Type") == 0) { m_strResponseContentType = value; string::size_type ulMimePos = m_strResponseContentType.find( ";"); std::string strTypePart; if( ulMimePos != std::string::npos) strTypePart = m_strResponseContentType.substr( 1, ulMimePos - 1); if ( "Multipart/Related" == strTypePart) { m_bMimeTrue = true; m_strResponseContentType = m_strResponseContentType.substr( ulMimePos + 1, m_strResponseContentType.length()); ulMimePos = m_strResponseContentType.find( "boundary="); m_strMimeBoundary = m_strResponseContentType.substr( ulMimePos); ulMimePos = m_strMimeBoundary.find( ";"); m_strMimeBoundary = m_strMimeBoundary.substr( 9, ulMimePos - 9); ulMimePos = m_strResponseContentType.find( "type="); m_strMimeType = m_strResponseContentType.substr( ulMimePos); ulMimePos = m_strMimeType.find( ";"); m_strMimeType = m_strMimeType.substr( 5, ulMimePos - 5); ulMimePos = m_strResponseContentType.find( "start="); m_strMimeStart = m_strResponseContentType.substr( ulMimePos); ulMimePos = m_strMimeStart.find( ";"); m_strMimeStart = m_strMimeStart.substr( 6, ulMimePos - 6); } } } while( iPosition != std::string::npos); // If 100 Continue is the response header, then we need to ensure we are waiting for // an HTTP header. Otherwise, HTTP protocol allows body with no Content-Length // and no transfer-encoding! This means that we read from the stream until we are unable // to read anymore. So ensure state is set correctly. In this case we set it to not-chunked. if (m_iResponseHTTPStatusCode == 100) m_GetBytesState = eWaitingForHTTPHeader; else if (m_GetBytesState == eWaitingForHTTPHeader) m_GetBytesState = eSOAPMessageIsNotChunked; logExit() } /* HTTPTransport::processRootMimeBody() Is a public method used to * parse the mime attachments. */ void HTTPTransport:: processRootMimeBody() { logEntryTransport("HTTPTransport::processRootMimeBody") int numberOfBytesRead = 0; if( false == m_bReadPastRootMimeHeader) { do { if( m_strReceived.find( ASCII_S_CRLFCRLF ) == std::string::npos) { numberOfBytesRead = m_pActiveChannel->readBytes(m_pszRxBuffer, BUF_SIZE); if (numberOfBytesRead > 0) m_strReceived += m_pszRxBuffer; } } while( m_strReceived.find( ASCII_S_CRLFCRLF ) == std::string::npos); //now we have found the end of root mime header m_bReadPastRootMimeHeader = true; //processMimeHeader(); For the time being we don't process this Done with root mime body headers, get rest of // the payload which contain the soap message m_strReceived = m_strReceived.substr( m_strReceived.find( ASCII_S_CRLFCRLF ) + 4); string::size_type intMimeTemp = m_strReceived.find( m_strMimeBoundary); if (intMimeTemp != std::string::npos) { m_strReceived = m_strReceived.substr( 0, intMimeTemp); m_strMimeReceived = m_strReceived.substr( intMimeTemp); PLATFORM_ASCTOSTR(m_strMimeReceived.c_str()); // Using m_strMimeReceived will be continued when getAttachment is called. m_bMimeTrue = false; } } else { string::size_type intMimeTemp = m_strReceived.find( m_strMimeBoundary); if( intMimeTemp != std::string::npos) { m_strReceived = m_strReceived.substr( 0, intMimeTemp); m_strMimeReceived = m_strReceived.substr( intMimeTemp); PLATFORM_ASCTOSTR(m_strMimeReceived.c_str()); // Using m_strMimeReceived will be continued when getAttachment is called. m_bMimeTrue = false; } } logExit() } /* HTTPTransport::processMimeHeaders() Is a public method used to * parse the Mime headers of the response message. */ void HTTPTransport:: processMimeHeader() { logEntryTransport("HTTPTransport::processMimeHeader") string::size_type pos = 0; string::size_type temppos = 0; // Look for content lenght if( (pos = m_strMimeReceived.find( "Content-Type: ")) != std::string::npos) { m_strMimeContentType = m_strMimeReceived.substr( pos + strlen( "Content-Type: "), m_strMimeReceived.find( "\n", pos)); pos = m_strMimeContentType.find( ";"); temppos = m_strMimeContentType.find( "\r\n"); if( pos < temppos) m_strMimeContentType = m_strMimeContentType.substr( 0, pos); else m_strMimeContentType = m_strMimeContentType.substr( 0, temppos); } // Look for mime root body's content transfer encoding if( (pos = m_strMimeReceived.find( "Content-Transfer-Encoding: ")) != std::string::npos) { m_strMimeContentTransferEncoding = m_strMimeReceived.substr( pos + strlen( "Content-Transfer-Encoding: "), m_strMimeReceived.find( "\n", pos)); temppos = m_strMimeContentTransferEncoding.find( "\r\n"); m_strMimeContentTransferEncoding = m_strMimeContentTransferEncoding.substr( 0, temppos); } // Look for mime root body's content id if( (pos = m_strMimeReceived.find( "Content-ID: ")) != std::string::npos) { m_strMimeContentID = m_strMimeReceived.substr( pos + strlen( "Content-ID: "), m_strMimeReceived.find( "\n", pos)); temppos = m_strMimeContentID.find( "\r\n"); m_strMimeContentID = m_strMimeContentID.substr( 0, temppos); } // Look for mime root body's content location if( (pos = m_strMimeReceived.find( "Content-Location: ")) != std::string::npos) { m_strMimeContentLocation = atoi( m_strMimeReceived.substr( pos + strlen( "Content-Location: "), m_strMimeReceived.find( "\n", pos)).c_str()); temppos = m_strMimeContentLocation.find( "\r\n"); m_strMimeContentLocation = m_strMimeContentLocation.substr( 0, temppos); } logExit() } void HTTPTransport:: processMimeBody () { } void HTTPTransport:: getAttachment( char * pStrAttachment, int * pIntSize, int intAttachmentId) { logEntryTransport("HTTPTransport::getAttachment") int numberOfBytesRead = 0; do { numberOfBytesRead = m_pActiveChannel->readBytes(m_pszRxBuffer, BUF_SIZE); if (numberOfBytesRead > 0) m_strMimeReceived += m_pszRxBuffer; } while( m_strMimeReceived.find( "\r\n\r\n") == std::string::npos ); //now we have found the end of next mime header processMimeHeader(); m_strMimeReceived = m_strMimeReceived.substr( m_strMimeReceived.find( "\r\n\r\n")); processMimeBody(); logExit() } void HTTPTransport:: setSocket( unsigned int uiNewSocket) { m_pActiveChannel->setSocket( uiNewSocket); } const char * HTTPTransport:: getTransportProperty( const char * pcKey, bool response) throw (HTTPTransportException) { logEntryTransport("HTTPTransport::getTransportProperty") const char *returnValue = NULL; if (pcKey == NULL) return NULL; std::vector < std::pair < std::string, std::string > > *hdrs=NULL; if (response) hdrs = &m_vResponseHTTPHeaders; else hdrs = &m_vHTTPHeaders; for( unsigned int i = 0; i < hdrs->size(); i++) if (PLATFORM_STRCASECMP((*hdrs)[i].first.c_str(), pcKey) == 0) { returnValue = (*hdrs)[i].second.c_str(); break; } logExitWithString(returnValue) return returnValue; } const char * HTTPTransport:: getFirstTransportPropertyKey(bool response) { if(response) { m_viCurrentResponseHeader = m_vResponseHTTPHeaders.begin (); if( m_viCurrentResponseHeader == m_vResponseHTTPHeaders.end()) return NULL; else return (*m_viCurrentResponseHeader).first.c_str(); } else { m_viCurrentHeader = m_vHTTPHeaders.begin (); if( m_viCurrentHeader == m_vHTTPHeaders.end()) return NULL; else return (*m_viCurrentHeader).first.c_str(); } } const char * HTTPTransport:: getNextTransportPropertyKey(bool response) { if(response) { //already at the end? if( m_viCurrentResponseHeader == m_vResponseHTTPHeaders.end()) return NULL; m_viCurrentResponseHeader++; if( m_viCurrentResponseHeader == m_vResponseHTTPHeaders.end()) return NULL; else return (*m_viCurrentResponseHeader).first.c_str(); } else { //already at the end? if( m_viCurrentHeader == m_vResponseHTTPHeaders.end()) return NULL; m_viCurrentHeader++; if( m_viCurrentHeader == m_vHTTPHeaders.end()) return NULL; else return (*m_viCurrentHeader).first.c_str(); } } const char * HTTPTransport:: getCurrentTransportPropertyKey(bool response) { if (response) { if( m_viCurrentResponseHeader == m_vResponseHTTPHeaders.end()) return NULL; else return (*m_viCurrentResponseHeader).first.c_str(); } else if( m_viCurrentHeader == m_vHTTPHeaders.end()) return NULL; else return (*m_viCurrentHeader).first.c_str(); } const char * HTTPTransport:: getCurrentTransportPropertyValue(bool response) { if(response) { if( m_viCurrentResponseHeader == m_vResponseHTTPHeaders.end()) return NULL; else return (*m_viCurrentResponseHeader).second.c_str(); } else if( m_viCurrentHeader == m_vHTTPHeaders.end()) return NULL; else return (*m_viCurrentHeader).second.c_str(); } void HTTPTransport:: deleteCurrentTransportProperty(bool response) { // response=true by default std::vector < std::pair < std::string, std::string > >* headers = &m_vResponseHTTPHeaders; vector <std::pair < std::string, std::string > >::iterator* currentHeader = &m_viCurrentResponseHeader; if(!response) { headers = &m_vHTTPHeaders; currentHeader = &m_viCurrentHeader; } if( *currentHeader != headers->end()) headers->erase( *currentHeader); } void HTTPTransport:: deleteTransportProperty (char *pcKey, unsigned int uiOccurance) { if (pcKey == NULL) return; vector < std::pair < std::string, std::string > >::iterator currentHeader = m_vHTTPHeaders.begin(); unsigned int uiCount = 1; bool found=false; while( currentHeader != m_vHTTPHeaders.end() && uiCount <= uiOccurance) { if (PLATFORM_STRCASECMP(pcKey, (*currentHeader).first.c_str()) == 0) { if( uiCount == uiOccurance) { m_vHTTPHeaders.erase( currentHeader); // if this is the special case of cookies then delete them all if (PLATFORM_STRCASECMP(pcKey, "Cookie")==0) removeAllCookies(); found=true; break; } uiCount++; } currentHeader++; } // if the property has not been found then it might be a cookie if(!found) removeCookie(pcKey); } void HTTPTransport:: setMaintainSession( bool bSession) { m_bMaintainSession = bSession; } void HTTPTransport:: setSessionId( const char * pcSessionId) { m_strSessionKey = std::string (pcSessionId); } const char * HTTPTransport:: getSessionId() { return m_strSessionKey.c_str(); } const char * HTTPTransport:: getLastChannelError() { if( m_pActiveChannel != NULL) return m_pActiveChannel->GetLastErrorMsg().c_str(); return NULL; } void HTTPTransport:: readHTTPHeader() { logEntryTransport("HTTPTransport::readHTTPHeader") m_pActiveChannel->closeQuietly( false); // The parser is expecting a SOAP message. Thus, the HTTP header must have // been read and processed before control is returned to the parser. It can // not be assumed that the HTTP header will be read in one block, thus there // must be processing that first identifies the beginning of the HTTP header // block (i.e. looks for 'HTTP') and then additional processing that identifies // the end of the HTTP header block (i.e. looks for CR LF CR LF). // Note that for HTTP 100 responses, we consume it and restart the process. int numberOfBytesRead; string::size_type iHTTPStart; string::size_type iHTTPEnd; resetInputStateMachine(); m_bHasReadBeenDone = true; do { while (m_strReceived.find( ASCII_S_HTTP) == std::string::npos || m_strReceived.find( ASCII_S_CRLFCRLF) == std::string::npos) { numberOfBytesRead = m_pActiveChannel->readBytes(m_pszRxBuffer, BUF_SIZE); if (numberOfBytesRead > 0) { m_strReceived += m_pszRxBuffer; m_iBytesLeft = m_strReceived.length(); } else { m_bReopenConnection = true; logThrowExceptionWithData("HTTPTransportException - SERVER_TRANSPORT_INPUT_STREAMING_ERROR", "Socket connection has been closed.") throw HTTPTransportException( SERVER_TRANSPORT_INPUT_STREAMING_ERROR, "Socket connection has been closed."); } } // At this point the HTTP header has been found. Seperate the response headers // from the payload (i.e. SOAP message). iHTTPStart = m_strReceived.find( ASCII_S_HTTP); iHTTPEnd = m_strReceived.find( ASCII_S_CRLFCRLF, iHTTPStart); m_strResponseHTTPHeaders = m_strReceived.substr( iHTTPStart, iHTTPEnd + 4 - iHTTPStart); // Process the HTTP header processHTTPHeader(); // Remove the HTTP header from the buffer since it has been processed. m_strReceived = m_strReceived.substr( iHTTPEnd + 4); m_iBytesLeft = m_strReceived.length(); } while( m_iResponseHTTPStatusCode == 100); // Now have a valid HTTP header that is not 100. Interrogate status code. const char *contentType = m_strResponseContentType.c_str(); if (m_iResponseHTTPStatusCode > 199 && m_iResponseHTTPStatusCode < 300) { // SOAP return is OK - so fall through } else if ((contentType != NULL) && (strncmp(contentType, "text/html", 9) != 0) && ((m_iResponseHTTPStatusCode > 499) && (m_iResponseHTTPStatusCode < 600))) { // SOAP Fault should be in here - so fall through } else if ((m_strResponseLocationURI.length() != 0) && ((m_iResponseHTTPStatusCode == 301) || (m_iResponseHTTPStatusCode == 302) || (m_iResponseHTTPStatusCode == 307))) { // Redirect (HTTP: 301/302/307) handleRedirect(); } else { m_iNbrOfRedirectAttempts = 0; m_strBytesToSendRedirect = ""; // Unknown return code - so wrap up the content into a SOAP fault TODO m_strResponseHTTPStatusMessage = std::string( "Server sent HTTP error: '") + m_strResponseHTTPStatusMessage + std::string("'\n"); m_GetBytesState = eWaitingForHTTPHeader; logThrowExceptionWithData("HTTPTransportException - SERVER_TRANSPORT_HTTP_EXCEPTION", m_strResponseHTTPStatusMessage.c_str()) throw HTTPTransportException( SERVER_TRANSPORT_HTTP_EXCEPTION, m_strResponseHTTPStatusMessage.c_str()); } m_iNbrOfRedirectAttempts = 0; m_strBytesToSendRedirect = ""; logExit() } int HTTPTransport:: getNextDataPacket( const char * pcszExceptionMessage, char *bufferToUse, int *bufferLen) { logEntryTransport("HTTPTransport::getNextDataPacket") int numberOfBytesRead; // Read whatever part of the response message that has arrived at the active channel socket. // The buffer used is dependent on whether one is passed in. There is an internal buffer // associated with the class, m_pszRxBuffer, that will be used if one is not passed in. // Currently we do not pass buffers into this routine, but in the future we will as an // optimization step...since if we are reading non-protocol data, we should just // directly plop the data into parser buffer. if (bufferToUse && bufferLen) numberOfBytesRead = m_pActiveChannel->readBytes(bufferToUse, *bufferLen); else numberOfBytesRead = m_pActiveChannel->readBytes(m_pszRxBuffer, BUF_SIZE); if( numberOfBytesRead > 0) { if (bufferToUse && bufferLen) *bufferLen = numberOfBytesRead; else { m_strReceived += m_pszRxBuffer; m_iBytesLeft = m_strReceived.length(); } } else if( m_strReceived.length() == 0) { m_bReopenConnection = true; if( pcszExceptionMessage != NULL && strlen( pcszExceptionMessage) > 0) { logThrowExceptionWithData("HTTPTransportException - SERVER_TRANSPORT_HTTP_EXCEPTION", pcszExceptionMessage) throw HTTPTransportException( SERVER_TRANSPORT_HTTP_EXCEPTION, pcszExceptionMessage); } } logExitWithInteger(numberOfBytesRead) return numberOfBytesRead; } int HTTPTransport:: addCookie(const string name, const string value) { logEntryTransport("HTTPTransport::addCookie") // trim the name string theName(name); trim(theName); // Make sure that the cookie is not duplicated. This cookie might be replacing one that's already there bool b_keyFound=false; for (unsigned int i = 0; i < m_vCookies.size(); i++) { if (m_vCookies[i].first == theName) { m_vCookies[i].second = (string) value; b_keyFound = true; break; } } // if cookie has not already been found add it if(!b_keyFound) m_vCookies.push_back( std::make_pair( theName, value)); logExit() return AXIS_SUCCESS; } int HTTPTransport:: addCookie(const string nameValuePair) { logEntryTransport("HTTPTransport::addCookie") // Spec syntax : Set-Cookie: NAME=VALUE; expires=DATE; path=PATH; domain=DOMAIN_NAME; secure // This code assumes it to be : Set-Cookie: NAME=VALUE; Anything_else // And discards stuff after first ';' // This is the same assumption used in Axis Java string nameValue = nameValuePair; string::size_type ulKeyEndsAt = nameValuePair.find( ";"); if( ulKeyEndsAt != std::string::npos) nameValue = nameValuePair.substr( 0, ulKeyEndsAt); // Now split the nameValue up string::size_type nameEndsAt = nameValue.find("="); string value = ""; if (nameEndsAt != std::string::npos) value = nameValue.substr(nameEndsAt+1); int rc = addCookie(nameValue.substr(0, nameEndsAt), value); logExit() return rc; } int HTTPTransport:: removeCookie(const string name) { vector < std::pair < std::string, std::string > >::iterator currentCookie = m_vCookies.begin(); while( currentCookie != m_vCookies.end()) { if( strcmp( name.c_str(), (*currentCookie).first.c_str()) == 0) { m_vCookies.erase( currentCookie); return AXIS_SUCCESS; } currentCookie++; } return AXIS_FAIL; } int HTTPTransport:: removeAllCookies() { m_vCookies.clear(); // we also need to remove it from the header properties that we send. // This is done from the deleteTransportMethod before this one is called. return AXIS_SUCCESS; } void HTTPTransport:: trim(string& str) { string::size_type pos = str.find_last_not_of(' '); if(pos != string::npos) { str.erase(pos + 1); pos = str.find_first_not_of(' '); if(pos != string::npos) str.erase(0, pos); } else str.erase(str.begin(), str.end()); } void HTTPTransport:: enableTrace(const char* logFilePath, const char *filters) { AxisTrace::setLogFilter(filters); AxisTrace::startTrace(logFilePath, false); if (m_pNormalChannel) m_pNormalChannel->enableTrace(logFilePath, filters); if (m_pSecureChannel) m_pSecureChannel->enableTrace(logFilePath, filters); } void HTTPTransport:: handleRedirect() { logEntryTransport("HTTPTransport::handleRedirect") // close old connection closeConnection(true); // Setup exception data with redirect location. m_strResponseHTTPStatusMessage = std::string( "Redirect: ") + m_strResponseLocationURI + std::string("'\n"); m_GetBytesState = eWaitingForHTTPHeader; // See if we can process redirect seamlessly. // We only do so if we are going from http -> http or https -> https. const char *location = m_strResponseLocationURI.c_str(); bool throwException = true; if (location && m_bPerformAutoRedirect && m_iNbrOfRedirectAttempts < m_iMaximumAutoRedirects && ((m_bChannelSecure && strncmp("https:", location, 6) == 0) || (!m_bChannelSecure && strncmp("http:", location, 5) == 0))) { throwException = false; m_iNbrOfRedirectAttempts++; setEndpointUri(location); openConnection(); flushOutput(); readHTTPHeader(); } if (throwException) { m_iNbrOfRedirectAttempts = 0; m_strBytesToSendRedirect = ""; logThrowExceptionWithData("HTTPTransportException - SERVER_TRANSPORT_REDIRECT_RECEIVED", m_strResponseHTTPStatusMessage.c_str()) throw HTTPTransportException( SERVER_TRANSPORT_REDIRECT_RECEIVED, m_strResponseHTTPStatusMessage.c_str()); } logExit() }