| /* |
| * 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() |
| } |