OW_HTTPClient.cpp

Go to the documentation of this file.
00001 /*******************************************************************************
00002 * Copyright (C) 2001-2004 Vintela, Inc. All rights reserved.
00003 *
00004 * Redistribution and use in source and binary forms, with or without
00005 * modification, are permitted provided that the following conditions are met:
00006 *
00007 *  - Redistributions of source code must retain the above copyright notice,
00008 *    this list of conditions and the following disclaimer.
00009 *
00010 *  - Redistributions in binary form must reproduce the above copyright notice,
00011 *    this list of conditions and the following disclaimer in the documentation
00012 *    and/or other materials provided with the distribution.
00013 *
00014 *  - Neither the name of Vintela, Inc. nor the names of its
00015 *    contributors may be used to endorse or promote products derived from this
00016 *    software without specific prior written permission.
00017 *
00018 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ``AS IS''
00019 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
00020 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
00021 * ARE DISCLAIMED. IN NO EVENT SHALL Vintela, Inc. OR THE CONTRIBUTORS
00022 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
00023 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
00024 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
00025 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
00026 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
00027 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
00028 * POSSIBILITY OF SUCH DAMAGE.
00029 *******************************************************************************/
00030 
00036 #include "OW_config.h"
00037 #include "OW_HTTPClient.hpp"
00038 #include "OW_HTTPUtils.hpp"
00039 #include "OW_HTTPChunkedIStream.hpp"
00040 #include "OW_HTTPChunkedOStream.hpp"
00041 #include "OW_HTTPDeflateOStream.hpp"
00042 #include "OW_HTTPDeflateIStream.hpp"
00043 #include "OW_HTTPLenLimitIStream.hpp"
00044 #include "OW_Format.hpp"
00045 #include "OW_TempFileStream.hpp"
00046 #include "OW_Assertion.hpp"
00047 #include "OW_CryptographicRandomNumber.hpp"
00048 #include "OW_HTTPException.hpp"
00049 #include "OW_UserUtils.hpp"
00050 #include "OW_Select.hpp"
00051 #include "OW_SSLCtxMgr.hpp"
00052 
00053 #include <fstream>
00054 #include <cerrno>
00055 
00056 
00057 using namespace std;
00058 
00059 namespace OW_NAMESPACE
00060 {
00061 
00062 using std::flush;
00063 using std::istream;
00065 HTTPClient::HTTPClient( const String &sURL, const SSLClientCtxRef& sslCtx)
00066 #ifndef OW_DISABLE_DIGEST
00067    : m_iDigestNonceCount(1) ,
00068 #else
00069    :
00070 #endif
00071     m_url(sURL)
00072    , m_pIstrReturn(0)
00073    , m_sslCtx(sslCtx)
00074    , m_socket(
00075       m_url.scheme.endsWith('s') ?
00076       (
00077          m_sslCtx ?
00078          (
00079             m_sslCtx
00080          )
00081          :
00082          (
00083             m_sslCtx = SSLClientCtxRef(new SSLClientCtx()), m_sslCtx
00084          )
00085       )
00086       :
00087       (
00088          SSLClientCtxRef(0)
00089       )
00090    )
00091    , m_requestMethod("M-POST"), m_authRequired(false)
00092    , m_istr(m_socket.getInputStream()), m_ostr(m_socket.getOutputStream())
00093    , m_doDeflateOut(false)
00094    , m_retryCount(0)
00095    , m_httpPath("/cimom")
00096    , m_uselocalAuthentication(false)
00097 {
00098    // turn off exceptions, since we're not coded to handle them.
00099    m_istr.exceptions(std::ios::goodbit);
00100    m_ostr.exceptions(std::ios::goodbit);
00101 
00102 #ifndef OW_WIN32
00103    // TODO: figure out a better way to do this.
00104    signal(SIGPIPE, SIG_IGN);
00105 #endif
00106 
00107    setUrl();
00108    addHeaderPersistent("Host", m_url.host);
00109    addHeaderPersistent("User-Agent", OW_PACKAGE"/"OW_VERSION);
00110 
00111    m_socket.setConnectTimeout(60);
00112    m_socket.setReceiveTimeout(600);
00113    m_socket.setSendTimeout(600);
00114 }
00116 HTTPClient::~HTTPClient()
00117 {
00118    try
00119    {
00120       cleanUpIStreams();
00121    }
00122    catch (...)
00123    {
00124       // don't let exceptions escape
00125    }
00126 }
00128 void
00129 HTTPClient::cleanUpIStreams()
00130 {
00131    // if there remains bytes from last response, eat them.
00132    if (m_pIstrReturn)
00133    {
00134       HTTPUtils::eatEntity(*m_pIstrReturn);
00135       m_pIstrReturn = 0;
00136    }
00137 }
00139 namespace
00140 {
00141    inline bool isUInt16(const String& s)
00142    {
00143       try
00144       {
00145          s.toUInt16();
00146          return true;
00147       }
00148       catch (StringConversionException& e)
00149       {
00150          return false;
00151       }
00152    }
00153 
00154    String getAuthParam(const String& paramName, const String& authInfo)
00155    {
00156       String rval;
00157       size_t pos = authInfo.indexOf(paramName);
00158       if (pos == String::npos)
00159       {
00160          return rval;
00161       }
00162 
00163       pos += paramName.length();
00164       if ((pos = authInfo.indexOf('=', pos)) == String::npos)
00165       {
00166          return rval;
00167       }
00168 
00169       if (pos + 1 >= authInfo.length())
00170       {
00171          return rval;
00172       }
00173       ++pos; // move past =
00174 
00175       if (authInfo[pos] == '"')
00176       {
00177          size_t endPos = authInfo.indexOf('"', pos + 1);
00178          if (endPos != String::npos)
00179          {
00180             rval = authInfo.substring(pos + 1, endPos - pos - 1); // don't get the quotes.
00181          }
00182       }
00183       else
00184       {
00185          size_t endPos = authInfo.indexOf(',', pos);
00186          if (endPos != String::npos)
00187          {
00188             rval = authInfo.substring(pos, endPos - pos);
00189          }
00190          else
00191          {
00192             rval = authInfo.substring(pos);
00193          }
00194       }
00195       return rval;
00196    }
00197 }
00199 void HTTPClient::setUrl()
00200 {
00201    if (m_url.scheme.empty())
00202    {
00203       m_url.scheme = "http";
00204    }
00205    if (m_url.port.empty())
00206    {
00207       if ( m_url.scheme.endsWith('s') ) // https, cimxml.wbems, owbinary.wbems
00208       {
00209          m_url.port = "5989";
00210       }
00211       else // http, cimxml.wbem, owbinary.wbem
00212       {
00213          m_url.port = "5988";
00214       }
00215    }
00216    if (m_url.scheme.endsWith('s'))
00217    {
00218 #ifdef OW_NO_SSL
00219       OW_THROW(SocketException, "SSL not available");
00220 #endif // #ifdef OW_NO_SSL
00221    }
00222    if (m_url.port.equalsIgnoreCase(URL::OWIPC)
00223       || m_url.scheme.equals("ipc")) // the ipc:// scheme is deprecated in 3.0.0 and will be removed!
00224    {
00225 #ifdef OW_WIN32
00226       OW_THROW(SocketException, "IPC Method not currently available on Win32");
00227 #else
00228       m_serverAddress = SocketAddress::getUDS(OW_DOMAIN_SOCKET_NAME);
00229 #endif
00230    }
00231    else if (isUInt16(m_url.port))
00232    {
00233       m_serverAddress = SocketAddress::getByName(HTTPUtils::unescapeForURL(m_url.host),
00234          m_url.port.toUInt16());
00235    }
00236    else // port may be a path to the UDS
00237    {
00238 #ifdef OW_WIN32
00239       OW_THROW(SocketException, "IPC Method not currently available on Win32");
00240 #else
00241       m_serverAddress = SocketAddress::getUDS(HTTPUtils::unescapeForURL(m_url.port));
00242 #endif
00243    }
00244 
00245    if ((m_url.host == "localhost" || m_url.host == "127.0.0.1") && m_url.principal.empty() && m_url.credential.empty())
00246    {
00247       m_uselocalAuthentication = true;
00248    }
00249 }
00251 void
00252 HTTPClient::receiveAuthentication()
00253 {
00254    String authInfo = getHeaderValue("www-authenticate");
00255    String scheme; 
00256     if (!authInfo.empty())
00257    {
00258       scheme = authInfo.tokenize()[0]; 
00259       scheme.toLowerCase(); 
00260    }
00261    m_sRealm = getAuthParam("realm", authInfo);
00262 
00263 #ifndef OW_DISABLE_DIGEST
00264    CryptographicRandomNumber rn(0, 0x7FFFFFFF);
00265    m_sDigestCNonce.format( "%08x", rn.getNextNumber() );
00266    // do this 4 more times, so we get > 128 bits of randomness. Each round only yields 31.
00267    for (size_t i = 0; i < 4; ++i)
00268    {
00269       String randomData;
00270       randomData.format("%08x", rn.getNextNumber());
00271       m_sDigestCNonce += randomData;
00272    }
00273    
00274    if (headerHasKey("authentication-info") && m_sAuthorization=="Digest" )
00275    {
00276       String authInfo = getHeaderValue("authentication-info");
00277       m_sDigestNonce = getAuthParam("nextnonce", authInfo);
00278       getCredentialsIfNecessary();
00279       HTTPUtils::DigestCalcHA1( "md5", m_url.principal, m_sRealm,
00280          m_url.credential, m_sDigestNonce, m_sDigestCNonce, m_sDigestSessionKey );
00281       m_iDigestNonceCount = 1;
00282    }
00283    else if ( scheme.equals("digest"))
00284    {
00285       m_sAuthorization = "Digest";
00286       m_uselocalAuthentication = false;
00287       m_sDigestNonce = getAuthParam("nonce", authInfo);
00288       getCredentialsIfNecessary();
00289       HTTPUtils::DigestCalcHA1( "md5", m_url.principal, m_sRealm,
00290          m_url.credential, m_sDigestNonce, m_sDigestCNonce, m_sDigestSessionKey );
00291    }
00292    else
00293 #endif
00294    if ( scheme.equals("basic"))
00295    {
00296       m_sAuthorization = "Basic";
00297       m_uselocalAuthentication = false;
00298    }
00299    else if ( scheme.equals( "owlocal" ) || m_uselocalAuthentication)
00300    {
00301       m_sAuthorization = "OWLocal";
00302       m_uselocalAuthentication = true;
00303 
00304       m_localNonce = getAuthParam("nonce", authInfo);
00305       m_localCookieFile = getAuthParam("cookiefile", authInfo);
00306 
00307    }
00308 
00309     if (m_sAuthorization.empty())
00310    {
00311       OW_THROW(HTTPException, "No known authentication schemes");
00312    }
00313 
00314 
00315 }
00316 
00318 void HTTPClient::getCredentialsIfNecessary()
00319 {
00320    if (m_url.principal.empty())
00321    {
00322       if (!m_loginCB)
00323       {
00324          OW_THROW(HTTPException, "No login/password to send");
00325       }
00326       else
00327       {
00328          String realm;
00329          if (m_sRealm.empty())
00330          {
00331             realm = m_url.toString();
00332          }
00333          else
00334          {
00335             realm = m_sRealm;
00336          }
00337          String name, passwd;
00338          if (m_loginCB->getCredentials(realm, name, passwd, "") && !name.empty())
00339          {
00340             m_url.principal = name;
00341             m_url.credential = passwd;
00342          }
00343          else
00344          {
00345             OW_THROW(HTTPException, "No login/password to send");
00346          }
00347       }
00348    }
00349 
00350 }
00351 
00353 void HTTPClient::addCustomHeader(const String& name, const String& value)
00354 {
00355    this->addHeaderPersistent(name, value);
00356 }
00357 
00359 bool HTTPClient::getResponseHeader(const String& hdrName,
00360    String& valueOut) const
00361 {
00362    bool cc = false;
00363    if (HTTPUtils::headerHasKey(m_responseHeaders, hdrName))
00364    {
00365       cc = true;
00366       valueOut = HTTPUtils::getHeaderValue(m_responseHeaders, hdrName);
00367    }
00368    return cc;
00369 }
00370 
00372 void HTTPClient::sendAuthorization()
00373 {
00374    if ( !m_sAuthorization.empty())
00375    {
00376       OStringStream ostr;
00377       ostr << m_sAuthorization << " ";
00378       if ( m_sAuthorization == "Basic" )
00379       {
00380          getCredentialsIfNecessary();
00381          ostr << HTTPUtils::base64Encode( m_url.principal + ":" +
00382             m_url.credential );
00383       }
00384 #ifndef OW_DISABLE_DIGEST
00385       else if ( m_sAuthorization == "Digest" )
00386       {
00387          String sNonceCount;
00388          sNonceCount.format( "%08x", m_iDigestNonceCount );
00389          HTTPUtils::DigestCalcResponse( m_sDigestSessionKey, m_sDigestNonce, sNonceCount,
00390             m_sDigestCNonce, "auth", m_requestMethod, m_httpPath, "", m_sDigestResponse );
00391          ostr << "username=\"" << m_url.principal << "\", ";
00392          ostr << "realm=\"" << m_sRealm << "\", ";
00393          ostr << "nonce=\"" << m_sDigestNonce << "\", ";
00394          ostr << "uri=\"" + m_httpPath + ", ";
00395          ostr << "qop=\"auth\", ";  
00396          ostr << "nc=" << sNonceCount << ", ";
00397          ostr << "cnonce=\"" << m_sDigestCNonce << "\", ";
00398          ostr << "response=\"" << m_sDigestResponse << "\"";
00399          m_iDigestNonceCount++;
00400       }
00401 #endif
00402       else if (m_sAuthorization == "OWLocal")
00403       {
00404          if (m_localNonce.empty())
00405          {
00406             // first round - we just send our euid
00407 
00408             ostr << "uid=\"" << UserUtils::getEffectiveUserId() << "\"";
00409          }
00410          else
00411          {
00412             // second round - send the nonce and the cookie
00413 
00414             // first try to read the cookie
00415             std::ifstream cookieFile(m_localCookieFile.c_str());
00416             if (!cookieFile)
00417             {
00418                OW_THROW_ERRNO_MSG(HTTPException, "Unable to open local authentication file");
00419             }
00420             String cookie = String::getLine(cookieFile);
00421             ostr << "nonce=\"" << m_localNonce << "\", ";
00422             ostr << "cookie=\"" << cookie << "\"";
00423          }
00424       }
00425       addHeaderNew("Authorization", ostr.toString());
00426    }
00427 }
00428 
00430 HTTPClient::EStatusLineSummary
00431 HTTPClient::checkAndExamineStatusLine()
00432 {
00433    // If there's some input, we need to read the header. If we got anything
00434    // besides a 100 or 2xx, we need to stop sending!
00435    // It's a pretty safe assumption that if the server sent us anything not
00436    // in response, but just out of the blue, it's terminating the connection
00437    // for some reason.
00438    if (m_socket.isConnected() && !m_socket.waitForInput(0))
00439    {
00440       getStatusLine();
00441       StringArray statusLine(m_statusLine.tokenize(" "));
00442       if (statusLine.size() < 2)
00443       {
00444          return E_STATUS_ERROR;
00445       }
00446       int statusCode = 500;
00447       try
00448       {
00449          statusCode = statusLine[1].toInt32();
00450       }
00451       catch (const StringConversionException&)
00452       {
00453          return E_STATUS_ERROR;
00454       }
00455       if (statusCode >= 300)
00456       {
00457          return E_STATUS_ERROR;
00458       }
00459       return E_STATUS_GOOD;
00460    }
00461    return E_STATUS_GOOD;
00462 }
00463 
00465 void HTTPClient::copyStreams(std::ostream& ostr, std::istream& istr)
00466 {
00467    std::streambuf* outbuf(ostr.rdbuf());
00468    std::streambuf* inbuf(istr.rdbuf());
00469    std::streamsize rv = 0;
00470    std::streamsize curbufsize = inbuf->in_avail();
00471    std::streamsize bytesWritten;
00472 
00473    std::vector<char> buffer(curbufsize);
00474    while (curbufsize != -1)
00475    {
00476       if (checkAndExamineStatusLine() == E_STATUS_ERROR)
00477       {
00478          break;
00479       }
00480 
00481       streamsize bytesToRead(curbufsize > 0 ? curbufsize : 1);
00482 
00483       // reserve() is guaranteed to allocate the appropriate number of bytes.
00484       buffer.reserve(bytesToRead);
00485       buffer.push_back(0); // gcc-4.0.2 won't let us access buffer[0] below if the vector is empty
00486 
00487       streamsize charsRead = inbuf->sgetn(&buffer[0], bytesToRead);
00488       bytesWritten = outbuf->sputn(&buffer[0], charsRead);
00489       
00490       rv += bytesWritten;
00491       if (bytesWritten != charsRead)
00492          break;
00493 
00494       if (std::istream::traits_type::eq_int_type(inbuf->sgetc(), std::istream::traits_type::eof()))
00495          break;
00496 
00497       curbufsize = inbuf->in_avail();
00498    }
00499 
00500 }
00501 
00503 void HTTPClient::sendDataToServer( const Reference<TempFileStream>& tfs,
00504    const String& methodName, const String& cimObject, ERequestType requestType )
00505 {
00506    // Make sure our connection is good
00507    checkConnection();
00508    handleAuth();
00509    String hp;
00510    if (m_requestMethod.equals("M-POST"))
00511    {
00512       hp = HTTPUtils::getCounterStr();
00513       addHeaderNew("Man", "http://www.dmtf.org/cim/mapping/http/v1.0; ns="
00514          + hp);
00515       hp += "-";
00516    }
00517    else
00518    {
00519       hp.erase();
00520    }
00521 
00522    if (requestType == E_CIM_OPERATION_REQUEST || requestType == E_CIM_BATCH_OPERATION_REQUEST)
00523    {
00524       addHeaderNew(hp + "CIMOperation", "MethodCall");
00525 
00526       if (requestType == E_CIM_BATCH_OPERATION_REQUEST || methodName.equals("CIMBatch"))
00527       {
00528          addHeaderNew(hp + "CIMBatch", "");
00529       }
00530       else // normal operations
00531       {
00532          addHeaderNew(hp + "CIMMethod", HTTPUtils::escapeForURL(methodName));
00533          addHeaderNew(hp + "CIMObject", HTTPUtils::escapeForURL(cimObject));
00534       }
00535    }
00536 
00537    if (requestType == E_CIM_EXPORT_REQUEST || requestType == E_CIM_BATCH_EXPORT_REQUEST)
00538    {
00539       addHeaderNew(hp + "CIMExport", "MethodRequest");
00540 
00541       if (requestType == E_CIM_BATCH_EXPORT_REQUEST || methodName.equals("CIMBatch"))
00542       {
00543          addHeaderNew(hp + "CIMExportBatch", "");
00544       }
00545       else // normal operations
00546       {
00547          addHeaderNew(hp + "CIMExportMethod", HTTPUtils::escapeForURL(methodName));
00548       }
00549    }
00550 
00551    if (m_doDeflateOut)
00552    {
00553       // deflate test items
00554       addHeaderNew("Transfer-Encoding", "chunked");
00555       addHeaderNew("Content-Encoding", "deflate");
00556    }
00557    
00558    // clear out previous status, this may be set if the server sends us something while we're sending.
00559    m_statusLine.erase();
00560 
00561    // send headers
00562    sendHeaders(m_requestMethod, "HTTP/1.1");
00563 
00564    // send entity
00565    tfs->rewind();          
00566    if (m_doDeflateOut)
00567    {
00568 #ifdef OW_HAVE_ZLIB_H
00569       // test deflate stuff
00570       HTTPChunkedOStream chunkostr(m_ostr);
00571       HTTPDeflateOStream deflateostr(chunkostr);
00572 //    deflateostr << tfs->rdbuf();
00573       copyStreams(deflateostr, *tfs);
00574       deflateostr.termOutput();
00575       chunkostr.termOutput();
00576       // end deflate test stuff
00577 #else
00578       OW_THROW(HTTPException, "Attempted to deflate output but not "
00579          "compiled with zlib!");
00580 #endif
00581    }
00582    else
00583    {
00584 //    m_ostr << tfs->rdbuf() << flush;
00585       copyStreams(m_ostr, *tfs);
00586       m_ostr.flush();
00587    }
00588    m_requestHeadersNew.clear();
00589    m_responseHeaders.clear();
00590 }
00592 Reference<std::iostream>
00593 HTTPClient::beginRequest(const String& methodName,
00594    const String& cimObject)
00595 {
00596    return Reference<std::iostream>(new TempFileStream());
00597 }
00599 CIMProtocolIStreamIFCRef
00600 HTTPClient::endRequest(const Reference<std::iostream>& request, const String& methodName,
00601          const String& cimObject, ERequestType requestType, const String& cimProtocolVersion)
00602 {
00603    Reference<TempFileStream> tfs = request.cast_to<TempFileStream>();
00604    OW_ASSERT(tfs);
00605    if (!tfs->good())
00606    {
00607       OW_THROW(HTTPException, "HTTPClient: TempFileStream is bad. Temp file creation failed.");
00608    }
00609 
00610    int len = tfs->getSize();
00611    // add common headers
00612    prepareHeaders();
00613    OW_ASSERT(!m_contentType.empty());
00614    addHeaderCommon("Content-Type", m_contentType + "; charset=\"utf-8\"");
00615    if (!m_doDeflateOut)
00616    {
00617       addHeaderCommon("Content-Length", String(len));
00618    }
00619    // else we're chunked and don't need the Content-Length header
00620    addHeaderCommon("TE", "trailers");
00621 #ifdef OW_HAVE_ZLIB_H
00622    addHeaderCommon("Accept-Encoding", "deflate");
00623 #endif
00624 
00625    // default of 1.0 if we leave it out, so don't bother sending it if that's the case.
00626    if (!cimProtocolVersion.empty() && cimProtocolVersion != "1.0")
00627    {
00628       addHeaderCommon("CIMProtocolVersion", cimProtocolVersion);
00629    }
00630 
00631    // if there remains bytes from last response, eat them.
00632    cleanUpIStreams();
00633 
00634 
00635    String reasonPhrase;
00636    Resp_t rt = RETRY;
00637    do
00638    {
00639       if (checkAndExamineStatusLine() == E_STATUS_GOOD)
00640       {
00641          sendDataToServer(tfs, methodName, cimObject, requestType);
00642       }
00643       reasonPhrase = checkResponse(rt);
00644    } while (rt == RETRY);
00645    if (rt == FATAL)
00646    {
00647       String CIMError = getHeaderValue("CIMError");
00648       if (CIMError.empty())
00649       {
00650          OW_THROW(HTTPException, Format("Unable to process request: %1",
00651             reasonPhrase).c_str());
00652       }
00653       else
00654       {
00655          OW_THROW(HTTPException, Format("Unable to process request: %1:%2",
00656             reasonPhrase, CIMError).c_str());
00657       }
00658    }
00659    m_pIstrReturn = convertToFiniteStream();
00660    if (!m_pIstrReturn)
00661    {
00662       OW_THROW(HTTPException, "HTTPClient: unable to understand server response. There may be no content in the reply.");
00663    }
00664    return m_pIstrReturn;
00665 }
00667 CIMFeatures
00668 HTTPClient::getFeatures()
00669 {
00670    String methodOrig = m_requestMethod;
00671    m_requestMethod = "OPTIONS";
00672    prepareHeaders();
00673    String reasonPhrase;
00674    Resp_t rt = RETRY;
00675    do
00676    {
00677       checkConnection();
00678       handleAuth();
00679       sendHeaders(m_requestMethod,  "HTTP/1.1");
00680       m_ostr.flush();
00681       m_requestHeadersNew.clear();
00682       m_responseHeaders.clear();
00683       m_statusLine.erase();
00684       reasonPhrase = checkResponse(rt);
00685    } while (rt == RETRY);
00686    m_requestMethod = methodOrig;
00687    if (rt == FATAL)
00688    {
00689       OW_THROW(HTTPException, Format("Unable to process request: %1",
00690          reasonPhrase).c_str());
00691    }
00692    if (getHeaderValue("allow").indexOf("M-POST") == String::npos)
00693    {
00694       m_requestMethod = "POST";
00695    }
00696    if (getHeaderValue("Accept-Encoding").indexOf("deflate") != String::npos)
00697    {
00698       m_doDeflateOut = true;
00699    }
00700    String extURL = getHeaderValue("Opt");
00701    size_t idx = extURL.indexOf(';');
00702    if (idx < 1 || idx == String::npos)
00703    {
00704       OW_THROW(HTTPException, "No \"Opt\" header in OPTIONS response");
00705    }
00706    CIMFeatures rval;
00707    rval.extURL = extURL.substring(0, idx);
00708    rval.extURL.trim();
00709    String hp = extURL.substring(idx + 1);
00710    idx = hp.indexOf("=");
00711    hp = hp.substring(idx + 1);
00712    hp.trim();
00713    if (hp.length() != 2)
00714    {
00715       OW_THROW(HTTPException, "HTTP Ext header prefix is not a two digit "
00716          "number");
00717    }
00718    hp += "-";
00719    rval.protocolVersion = getHeaderValue(hp + "CIMProtocolVersion");
00720    String supportedGroups;
00721    rval.supportsBatch = false;
00722    if (headerHasKey(hp + "CIMSupportedFunctionalGroups"))
00723    {
00724       rval.cimProduct = CIMFeatures::SERVER;
00725       supportedGroups = getHeaderValue(hp + "CIMSupportedFunctionalGroups");
00726       if (headerHasKey(hp + "CIMSupportsMultipleOperations"))
00727       {
00728          rval.supportsBatch = true;
00729       }
00730    }
00731    else if (headerHasKey(hp + "CIMSupportedExportGroups"))
00732    {
00733       rval.cimProduct = CIMFeatures::LISTENER;
00734       supportedGroups = getHeaderValue(hp + "CIMSupportedExportGroups");
00735       if (headerHasKey(hp + "CIMSupportsMultipleExports"))
00736       {
00737          rval.supportsBatch = true;
00738       }
00739    }
00740    else
00741    {
00742       OW_THROW(HTTPException, "No CIMSupportedFunctionalGroups or "
00743          "CIMSupportedExportGroups header");
00744    }
00745    rval.supportedGroups = supportedGroups.tokenize(",");
00746    for (size_t i = 0; i < rval.supportedGroups.size(); i++)
00747    {
00748       rval.supportedGroups[i].trim();
00749    }
00750    rval.supportedQueryLanguages =
00751       getHeaderValue(hp + "CIMSupportedQueryLanguages").tokenize(",");
00752    for (size_t i = 0; i < rval.supportedQueryLanguages.size(); i++)
00753    {
00754       rval.supportedQueryLanguages[i].trim();
00755    }
00756    rval.cimom = getHeaderValue(hp + "CIMOM");
00757    rval.validation = getHeaderValue(hp + "CIMValidation");
00758    return rval;
00759 }
00761 void
00762 HTTPClient::prepareForRetry()
00763 {
00764    CIMProtocolIStreamIFCRef tmpIstr = convertToFiniteStream();
00765    if (tmpIstr)
00766    {
00767       HTTPUtils::eatEntity(*tmpIstr);
00768    }
00769 }
00771 void
00772 HTTPClient::sendHeaders(const String& method,
00773    const String& prot)
00774 {
00775    m_ostr << method << ' ' << m_httpPath << ' ' << prot << "\r\n";
00776    for (size_t i = 0; i < m_requestHeadersPersistent.size(); i++)
00777    {
00778       m_ostr << m_requestHeadersPersistent[i] << "\r\n";
00779    }
00780    for (size_t i = 0; i < m_requestHeadersCommon.size(); i++)
00781    {
00782       m_ostr << m_requestHeadersCommon[i] << "\r\n";
00783    }
00784    for (size_t i = 0; i < m_requestHeadersNew.size(); i++)
00785    {
00786       m_ostr << m_requestHeadersNew[i] << "\r\n";
00787    }
00788    m_ostr << "\r\n";
00789 }
00791 HTTPClient::Resp_t
00792 HTTPClient::processHeaders(String& reasonPhrase)
00793 {
00794    if (getHeaderValue("Connection").equalsIgnoreCase("close"))
00795    {
00796       close();
00797    }
00798 
00799    Resp_t rt = RETRY;
00800    String statusLine(m_statusLine);
00801    size_t idx = statusLine.indexOf(' ');
00802    String sc; // http status code
00803    int isc = 500; // status code (int)
00804    if (idx > 0 && idx != String::npos)
00805    {
00806       statusLine = statusLine.substring(idx + 1);
00807    }
00808    idx = statusLine.indexOf(' ');
00809    if (idx > 0 && idx != String::npos)
00810    {
00811       sc = statusLine.substring(0,idx);
00812       reasonPhrase = statusLine.substring(idx + 1);
00813       try
00814       {
00815          isc = sc.toInt32();
00816       }
00817       catch (const StringConversionException&)
00818       {
00819          return RETRY;
00820       }
00821    }
00822    if (sc.length() != 3)
00823    {
00824       return RETRY;
00825    }
00826    switch (sc[0])
00827    {
00828       case '1':
00829          if (isc == 100)
00830          {
00831             rt = CONTINUE;
00832          }
00833          else
00834          {
00835             rt = FATAL; // support protocol upgrades?  nope.
00836          }
00837          break;
00838       case '2':
00839          rt = GOOD;
00840          m_authRequired = false;
00841          break;
00842       case '3':
00843          rt = FATAL; // support redirects?  I think not...
00844          break;
00845       case '4':
00846          close();
00847          switch (isc)
00848          {
00849             case SC_REQUEST_TIMEOUT:
00850                rt = RETRY;
00851                break;
00852             case SC_UNAUTHORIZED:
00853                // add authentication info, if available
00854                if (!m_authRequired)
00855                {
00856                   m_authRequired = true;
00857                   m_retryCount = 0;
00858                   rt = RETRY;
00859                }
00860                else
00861                {
00862                   rt = FATAL; // already tried authorization once.
00863                }
00864                break;
00865             case SC_METHOD_NOT_ALLOWED:
00866                // switch from M-POST to POST
00867                if (m_requestMethod.equals("M-POST"))
00868                {
00869                   m_requestMethod = "POST";
00870                   rt = RETRY;
00871                }
00872                else
00873                {
00874                   rt = FATAL;
00875                }
00876                break;
00877          default:
00878                close();
00879                rt = FATAL;
00880                break;
00881          } // switch (isc)
00882          break;
00883       case '5':
00884          switch (isc)
00885          {
00886             case SC_NOT_IMPLEMENTED:
00887             case SC_NOT_EXTENDED:
00888                // switch from M-POST to POST
00889                if (m_requestMethod.equals("M-POST"))
00890                {
00891                   m_requestMethod = "POST";
00892                   rt = RETRY;
00893                   // only do this for JWS, since it doesn't eat the entity.
00894                   close();
00895                }
00896                else
00897                {
00898                   rt = FATAL;
00899                }
00900                break;
00901             default:
00902                rt = FATAL;
00903                break;
00904          } // switch (isc)
00905          break;
00906       default:
00907          rt = RETRY; // shouln't happen
00908          break;
00909    } // switch (sc[0])
00910 
00911    // now check for headers which indicate an error
00912    String CIMError = getHeaderValue("CIMError");
00913    if (!CIMError.empty())
00914    {
00915       rt = FATAL;
00916    }
00917 
00918    return rt;
00919 }
00921 CIMProtocolIStreamIFCRef
00922 HTTPClient::convertToFiniteStream()
00923 {
00924    CIMProtocolIStreamIFCRef rval(0);
00925    if (getHeaderValue("Transfer-Encoding").equalsIgnoreCase("chunked"))
00926    {
00927       rval = new HTTPChunkedIStream(m_istr);
00928    }
00929    else if (headerHasKey("Content-Length"))
00930    {
00931       UInt64 clen = getHeaderValue("Content-Length").toUInt64(); 
00932       rval = new HTTPLenLimitIStream(m_istr,clen); 
00933    }
00934    if (getHeaderValue("Content-Encoding").equalsIgnoreCase("deflate"))
00935    {
00936 #ifdef OW_HAVE_ZLIB_H
00937       rval = new HTTPDeflateIStream(rval);
00938 #else
00939       OW_THROW(HTTPException, "Response is deflated but we're not "
00940          "compiled with zlib!");
00941 #endif // #ifdef OW_HAVE_ZLIB_H
00942    }
00943    return rval;
00944 }
00946 void
00947 HTTPClient::handleAuth()
00948 {
00949    // add new headers.
00950    if (m_authRequired || m_uselocalAuthentication)
00951    {
00952       receiveAuthentication();
00953       sendAuthorization();
00954    }
00955 }
00957 void
00958 HTTPClient::checkConnection()
00959 {
00960    if (!m_istr || !m_ostr || !m_socket.isConnected())
00961    {
00962       m_socket.disconnect();
00963       m_socket.connect(m_serverAddress);
00964    }
00965 }
00967 void
00968 HTTPClient::getStatusLine()
00969 {
00970    // RFC 2616 says to skip leading blank lines...
00971    while (m_statusLine.trim().empty() && m_istr)
00972    {
00973       m_statusLine = String::getLine(m_istr);
00974    }
00975 }
00977 String
00978 HTTPClient::checkResponse(Resp_t& rt)
00979 {
00980    String reasonPhrase;
00981    do
00982    {
00983       getStatusLine();
00984       
00985       if (!m_istr)
00986       {
00987          if (m_socket.receiveTimeOutExpired())
00988          {
00989             reasonPhrase = Format("Client receive timeout (%1 seconds) expired.", m_socket.getReceiveTimeout());
00990          }
00991          rt = FATAL;
00992          close();
00993          break;
00994       }
00995       if (!HTTPUtils::parseHeader(m_responseHeaders, m_istr))
00996       {
00997          OW_THROW(HTTPException, Format("Received junk from server statusline = %1", m_statusLine).c_str());
00998       }
00999       rt = processHeaders(reasonPhrase);
01000       if (rt == CONTINUE)
01001       {
01002          prepareForRetry();
01003       }
01004    } while (rt == CONTINUE);
01005    if (rt == RETRY)
01006    {
01007       ++m_retryCount;
01008       if (m_retryCount > 3)
01009       {
01010          rt = FATAL;
01011       }
01012       else
01013       {
01014          prepareForRetry();
01015       }
01016    }
01017    else if (rt == GOOD)
01018    {
01019       m_retryCount = 0;
01020    }
01021    return reasonPhrase;
01022 }
01024 void
01025 HTTPClient::prepareHeaders()
01026 {
01027    m_requestHeadersCommon.clear();
01028    m_responseHeaders.clear();
01029 }
01031 SocketAddress
01032 HTTPClient::getLocalAddress() const
01033 {
01034    if (!m_socket.isConnected())
01035    {
01036       m_socket.connect(m_serverAddress);
01037    }
01038    return m_socket.getLocalAddress();
01039 }
01041 SocketAddress
01042 HTTPClient::getPeerAddress() const
01043 {
01044    if (!m_socket.isConnected())
01045    {
01046       m_socket.connect(m_serverAddress);
01047    }
01048    return m_socket.getPeerAddress();
01049 }
01050 
01052 void
01053 HTTPClient::setHTTPPath(const String& newPath)
01054 {
01055    m_httpPath = newPath;
01056 }
01057 
01059 void HTTPClient::assumeBasicAuth()
01060 {
01061    close();
01062    m_authRequired = true;
01063    m_sAuthorization = "Basic";
01064    m_uselocalAuthentication = false;
01065 }
01066 
01068 void HTTPClient::close()
01069 {
01070    m_socket.disconnect();
01071 }
01072 
01073 
01075 void
01076 HTTPClient::setReceiveTimeout(int seconds)
01077 {
01078    m_socket.setReceiveTimeout(seconds);
01079 }
01080 
01082 int
01083 HTTPClient::getReceiveTimeout() const
01084 {
01085    return m_socket.getReceiveTimeout();
01086 }
01087 
01089 void
01090 HTTPClient::setSendTimeout(int seconds)
01091 {
01092    m_socket.setSendTimeout(seconds);
01093 }
01094 
01096 int
01097 HTTPClient::getSendTimeout() const
01098 {
01099    return m_socket.getSendTimeout();
01100 }
01101 
01103 void
01104 HTTPClient::setConnectTimeout(int seconds)
01105 {
01106    m_socket.setConnectTimeout(seconds);
01107 }
01108 
01110 int
01111 HTTPClient::getConnectTimeout() const
01112 {
01113    return m_socket.getConnectTimeout();
01114 }
01115 
01117 void
01118 HTTPClient::setTimeouts(int seconds)
01119 {
01120    m_socket.setTimeouts(seconds);
01121 }
01122 
01123 
01124 } // end namespace OW_NAMESPACE
01125 

Generated on Thu Feb 9 08:47:59 2006 for openwbem by  doxygen 1.4.6