llurlrequest.cpp

Go to the documentation of this file.
00001 
00034 #include "linden_common.h"
00035 #include "llurlrequest.h"
00036 
00037 #include <algorithm>
00038 
00039 #include "llcurl.h"
00040 #include "llioutil.h"
00041 #include "llmemtype.h"
00042 #include "llpumpio.h"
00043 #include "llsd.h"
00044 #include "llstring.h"
00045 #include "apr-1/apr_env.h"
00046 
00047 static const U32 HTTP_STATUS_PIPE_ERROR = 499;
00048 
00052 const std::string CONTEXT_DEST_URI_SD_LABEL("dest_uri");
00053 
00054 
00055 static size_t headerCallback(void* data, size_t size, size_t nmemb, void* user);
00056 
00060 class LLURLRequestDetail
00061 {
00062 public:
00063         LLURLRequestDetail();
00064         ~LLURLRequestDetail();
00065         std::string mURL;
00066         LLCurlEasyRequest* mCurlRequest;
00067         LLBufferArray* mResponseBuffer;
00068         LLChannelDescriptors mChannels;
00069         U8* mLastRead;
00070         U32 mBodyLimit;
00071         bool mIsBodyLimitSet;
00072 };
00073 
00074 LLURLRequestDetail::LLURLRequestDetail() :
00075         mCurlRequest(NULL),
00076         mResponseBuffer(NULL),
00077         mLastRead(NULL),
00078         mBodyLimit(0),
00079         mIsBodyLimitSet(false)
00080         
00081 {
00082         LLMemType m1(LLMemType::MTYPE_IO_URL_REQUEST);
00083         mCurlRequest = new LLCurlEasyRequest();
00084 }
00085 
00086 LLURLRequestDetail::~LLURLRequestDetail()
00087 {
00088         LLMemType m1(LLMemType::MTYPE_IO_URL_REQUEST);
00089         delete mCurlRequest;
00090         mResponseBuffer = NULL;
00091         mLastRead = NULL;
00092 }
00093 
00094 
00099 LLURLRequest::LLURLRequest(LLURLRequest::ERequestAction action) :
00100         mAction(action)
00101 {
00102         LLMemType m1(LLMemType::MTYPE_IO_URL_REQUEST);
00103         initialize();
00104 }
00105 
00106 LLURLRequest::LLURLRequest(
00107         LLURLRequest::ERequestAction action,
00108         const std::string& url) :
00109         mAction(action)
00110 {
00111         LLMemType m1(LLMemType::MTYPE_IO_URL_REQUEST);
00112         initialize();
00113         setURL(url);
00114 }
00115 
00116 LLURLRequest::~LLURLRequest()
00117 {
00118         LLMemType m1(LLMemType::MTYPE_IO_URL_REQUEST);
00119         delete mDetail;
00120 }
00121 
00122 void LLURLRequest::setURL(const std::string& url)
00123 {
00124         LLMemType m1(LLMemType::MTYPE_IO_URL_REQUEST);
00125         mDetail->mURL = url;
00126 }
00127 
00128 void LLURLRequest::addHeader(const char* header)
00129 {
00130         LLMemType m1(LLMemType::MTYPE_IO_URL_REQUEST);
00131         mDetail->mCurlRequest->slist_append(header);
00132 }
00133 
00134 void LLURLRequest::setBodyLimit(U32 size)
00135 {
00136         mDetail->mBodyLimit = size;
00137         mDetail->mIsBodyLimitSet = true;
00138 }
00139 
00140 void LLURLRequest::checkRootCertificate(bool check)
00141 {
00142         mDetail->mCurlRequest->setopt(CURLOPT_SSL_VERIFYPEER, (check? TRUE : FALSE));
00143         mDetail->mCurlRequest->setoptString(CURLOPT_ENCODING, "");
00144 }
00145 
00146 void LLURLRequest::setCallback(LLURLRequestComplete* callback)
00147 {
00148         LLMemType m1(LLMemType::MTYPE_IO_URL_REQUEST);
00149         mCompletionCallback = callback;
00150         mDetail->mCurlRequest->setHeaderCallback(&headerCallback, (void*)callback);
00151 }
00152 
00153 // Added to mitigate the effect of libcurl looking
00154 // for the ALL_PROXY and http_proxy env variables
00155 // and deciding to insert a Pragma: no-cache
00156 // header! The only usage of this method at the
00157 // time of this writing is in llhttpclient.cpp
00158 // in the request() method, where this method
00159 // is called with use_proxy = FALSE
00160 void LLURLRequest::useProxy(bool use_proxy)
00161 {
00162     static char *env_proxy;
00163 
00164     if (use_proxy && (env_proxy == NULL))
00165     {
00166         apr_status_t status;
00167         apr_pool_t* pool;
00168         apr_pool_create(&pool, NULL);
00169         status = apr_env_get(&env_proxy, "ALL_PROXY", pool);
00170         if (status != APR_SUCCESS)
00171         {
00172             status = apr_env_get(&env_proxy, "http_proxy", pool);
00173         }
00174         if (status != APR_SUCCESS)
00175         {
00176            use_proxy = FALSE;
00177         }
00178         apr_pool_destroy(pool);
00179     }
00180 
00181 
00182     lldebugs << "use_proxy = " << (use_proxy?'Y':'N') << ", env_proxy = " << (env_proxy ? env_proxy : "(null)") << llendl;
00183 
00184     if (env_proxy && use_proxy)
00185     {
00186                 mDetail->mCurlRequest->setoptString(CURLOPT_PROXY, env_proxy);
00187     }
00188     else
00189     {
00190         mDetail->mCurlRequest->setoptString(CURLOPT_PROXY, "");
00191     }
00192 }
00193 
00194 void LLURLRequest::useProxy(const std::string &proxy)
00195 {
00196     mDetail->mCurlRequest->setoptString(CURLOPT_PROXY, proxy);
00197 }
00198 
00199 // virtual
00200 LLIOPipe::EStatus LLURLRequest::handleError(
00201         LLIOPipe::EStatus status,
00202         LLPumpIO* pump)
00203 {
00204         LLMemType m1(LLMemType::MTYPE_IO_URL_REQUEST);
00205         if(mCompletionCallback && pump)
00206         {
00207                 LLURLRequestComplete* complete = NULL;
00208                 complete = (LLURLRequestComplete*)mCompletionCallback.get();
00209                 complete->httpStatus(
00210                         HTTP_STATUS_PIPE_ERROR,
00211                         LLIOPipe::lookupStatusString(status));
00212                 complete->responseStatus(status);
00213                 pump->respond(complete);
00214                 mCompletionCallback = NULL;
00215         }
00216         return status;
00217 }
00218 
00219 // virtual
00220 LLIOPipe::EStatus LLURLRequest::process_impl(
00221         const LLChannelDescriptors& channels,
00222         buffer_ptr_t& buffer,
00223         bool& eos,
00224         LLSD& context,
00225         LLPumpIO* pump)
00226 {
00227         PUMP_DEBUG;
00228         LLMemType m1(LLMemType::MTYPE_IO_URL_REQUEST);
00229         //llinfos << "LLURLRequest::process_impl()" << llendl;
00230         if(!buffer) return STATUS_ERROR;
00231         switch(mState)
00232         {
00233         case STATE_INITIALIZED:
00234         {
00235                 PUMP_DEBUG;
00236                 // We only need to wait for input if we are uploading
00237                 // something.
00238                 if(((HTTP_PUT == mAction) || (HTTP_POST == mAction)) && !eos)
00239                 {
00240                         // we're waiting to get all of the information
00241                         return STATUS_BREAK;
00242                 }
00243 
00244                 // *FIX: bit of a hack, but it should work. The configure and
00245                 // callback method expect this information to be ready.
00246                 mDetail->mResponseBuffer = buffer.get();
00247                 mDetail->mChannels = channels;
00248                 if(!configure())
00249                 {
00250                         return STATUS_ERROR;
00251                 }
00252                 mState = STATE_WAITING_FOR_RESPONSE;
00253 
00254                 // *FIX: Maybe we should just go to the next state now...
00255                 return STATUS_BREAK;
00256         }
00257         case STATE_WAITING_FOR_RESPONSE:
00258         case STATE_PROCESSING_RESPONSE:
00259         {
00260                 PUMP_DEBUG;
00261                 LLIOPipe::EStatus status = STATUS_BREAK;
00262                 mDetail->mCurlRequest->perform();
00263                 while(1)
00264                 {
00265                         CURLcode result;
00266                         bool newmsg = mDetail->mCurlRequest->getResult(&result);
00267                         if (!newmsg)
00268                         {
00269                                 break;
00270                         }
00271 
00272                         mState = STATE_HAVE_RESPONSE;
00273                         switch(result)
00274                         {
00275                                 case CURLE_OK:
00276                                 case CURLE_WRITE_ERROR:
00277                                         // NB: The error indication means that we stopped the
00278                                         // writing due the body limit being reached
00279                                         if(mCompletionCallback && pump)
00280                                         {
00281                                                 LLURLRequestComplete* complete = NULL;
00282                                                 complete = (LLURLRequestComplete*)
00283                                                         mCompletionCallback.get();
00284                                                 complete->responseStatus(
00285                                                                 result == CURLE_OK
00286                                                                         ? STATUS_OK : STATUS_STOP);
00287                                                 LLPumpIO::links_t chain;
00288                                                 LLPumpIO::LLLinkInfo link;
00289                                                 link.mPipe = mCompletionCallback;
00290                                                 link.mChannels = LLBufferArray::makeChannelConsumer(
00291                                                         channels);
00292                                                 chain.push_back(link);
00293                                                 pump->respond(chain, buffer, context);
00294                                                 mCompletionCallback = NULL;
00295                                         }
00296                                         break;
00297                                 case CURLE_FAILED_INIT:
00298                                 case CURLE_COULDNT_CONNECT:
00299                                         status = STATUS_NO_CONNECTION;
00300                                         break;
00301                                 default:
00302                                         llwarns << "URLRequest Error: " << result
00303                                                         << ", "
00304                                                         << LLCurl::strerror(result)
00305                                                         << ", "
00306                                                         << (mDetail->mURL.empty() ? "<EMPTY URL>" : mDetail->mURL)
00307                                                         << llendl;
00308                                         status = STATUS_ERROR;
00309                                         break;
00310                         }
00311                 }
00312                 return status;
00313         }
00314         case STATE_HAVE_RESPONSE:
00315                 PUMP_DEBUG;
00316                 // we already stuffed everything into channel in in the curl
00317                 // callback, so we are done.
00318                 eos = true;
00319                 return STATUS_DONE;
00320 
00321         default:
00322                 PUMP_DEBUG;
00323                 return STATUS_ERROR;
00324         }
00325 }
00326 
00327 void LLURLRequest::initialize()
00328 {
00329         LLMemType m1(LLMemType::MTYPE_IO_URL_REQUEST);
00330         mState = STATE_INITIALIZED;
00331         mDetail = new LLURLRequestDetail;
00332         mDetail->mCurlRequest->setopt(CURLOPT_NOSIGNAL, 1);
00333         mDetail->mCurlRequest->setWriteCallback(&downCallback, (void*)this);
00334         mDetail->mCurlRequest->setReadCallback(&upCallback, (void*)this);
00335 }
00336 
00337 bool LLURLRequest::configure()
00338 {
00339         LLMemType m1(LLMemType::MTYPE_IO_URL_REQUEST);
00340         bool rv = false;
00341         S32 bytes = mDetail->mResponseBuffer->countAfter(
00342                 mDetail->mChannels.in(),
00343                 NULL);
00344         switch(mAction)
00345         {
00346         case HTTP_HEAD:
00347                 mDetail->mCurlRequest->setopt(CURLOPT_HEADER, 1);
00348                 mDetail->mCurlRequest->setopt(CURLOPT_NOBODY, 1);
00349                 mDetail->mCurlRequest->setopt(CURLOPT_FOLLOWLOCATION, 1);
00350                 rv = true;
00351                 break;
00352         case HTTP_GET:
00353                 mDetail->mCurlRequest->setopt(CURLOPT_HTTPGET, 1);
00354                 mDetail->mCurlRequest->setopt(CURLOPT_FOLLOWLOCATION, 1);
00355                 rv = true;
00356                 break;
00357 
00358         case HTTP_PUT:
00359                 // Disable the expect http 1.1 extension. POST and PUT default
00360                 // to turning this on, and I am not too sure what it means.
00361                 addHeader("Expect:");
00362 
00363                 mDetail->mCurlRequest->setopt(CURLOPT_UPLOAD, 1);
00364                 mDetail->mCurlRequest->setopt(CURLOPT_INFILESIZE, bytes);
00365                 rv = true;
00366                 break;
00367 
00368         case HTTP_POST:
00369                 // Disable the expect http 1.1 extension. POST and PUT default
00370                 // to turning this on, and I am not too sure what it means.
00371                 addHeader("Expect:");
00372 
00373                 // Disable the content type http header.
00374                 // *FIX: what should it be?
00375                 addHeader("Content-Type:");
00376 
00377                 // Set the handle for an http post
00378                 mDetail->mCurlRequest->setPost(NULL, bytes);
00379                 rv = true;
00380                 break;
00381 
00382         case HTTP_DELETE:
00383                 // Set the handle for an http post
00384                 mDetail->mCurlRequest->setoptString(CURLOPT_CUSTOMREQUEST, "DELETE");
00385                 rv = true;
00386                 break;
00387 
00388         case HTTP_MOVE:
00389                 // Set the handle for an http post
00390                 mDetail->mCurlRequest->setoptString(CURLOPT_CUSTOMREQUEST, "MOVE");
00391                 // *NOTE: should we check for the Destination header?
00392                 rv = true;
00393                 break;
00394 
00395         default:
00396                 llwarns << "Unhandled URLRequest action: " << mAction << llendl;
00397                 break;
00398         }
00399         if(rv)
00400         {
00401                 mDetail->mCurlRequest->sendRequest(mDetail->mURL);
00402         }
00403         return rv;
00404 }
00405 
00406 // static
00407 size_t LLURLRequest::downCallback(
00408         char* data,
00409         size_t size,
00410         size_t nmemb,
00411         void* user)
00412 {
00413         LLMemType m1(LLMemType::MTYPE_IO_URL_REQUEST);
00414         LLURLRequest* req = (LLURLRequest*)user;
00415         if(STATE_WAITING_FOR_RESPONSE == req->mState)
00416         {
00417                 req->mState = STATE_PROCESSING_RESPONSE;
00418         }
00419         U32 bytes = size * nmemb;
00420         if (req->mDetail->mIsBodyLimitSet)
00421         {
00422                 if (bytes > req->mDetail->mBodyLimit)
00423                 {
00424                         bytes = req->mDetail->mBodyLimit;
00425                         req->mDetail->mBodyLimit = 0;
00426                 }
00427                 else
00428                 {
00429                         req->mDetail->mBodyLimit -= bytes;
00430                 }
00431         }
00432 
00433         req->mDetail->mResponseBuffer->append(
00434                 req->mDetail->mChannels.out(),
00435                 (U8*)data,
00436                 bytes);
00437         return bytes;
00438 }
00439 
00440 // static
00441 size_t LLURLRequest::upCallback(
00442         char* data,
00443         size_t size,
00444         size_t nmemb,
00445         void* user)
00446 {
00447         LLMemType m1(LLMemType::MTYPE_IO_URL_REQUEST);
00448         LLURLRequest* req = (LLURLRequest*)user;
00449         S32 bytes = llmin(
00450                 (S32)(size * nmemb),
00451                 req->mDetail->mResponseBuffer->countAfter(
00452                         req->mDetail->mChannels.in(),
00453                         req->mDetail->mLastRead));
00454         req->mDetail->mLastRead =  req->mDetail->mResponseBuffer->readAfter(
00455                 req->mDetail->mChannels.in(),
00456                 req->mDetail->mLastRead,
00457                 (U8*)data,
00458                 bytes);
00459         return bytes;
00460 }
00461 
00462 static size_t headerCallback(void* data, size_t size, size_t nmemb, void* user)
00463 {
00464         const char* headerLine = (const char*)data;
00465         size_t headerLen = size * nmemb;
00466         LLURLRequestComplete* complete = (LLURLRequestComplete*)user;
00467 
00468         // *TODO: This should be a utility in llstring.h: isascii()
00469         for (size_t i = 0; i < headerLen; ++i)
00470         {
00471                 if (headerLine[i] < 0)
00472                 {
00473                         return headerLen;
00474                 }
00475         }
00476 
00477         size_t sep;
00478         for (sep = 0; sep < headerLen  &&  headerLine[sep] != ':'; ++sep) { }
00479 
00480         if (sep < headerLen && complete)
00481         {
00482                 std::string key(headerLine, sep);
00483                 std::string value(headerLine + sep + 1, headerLen - sep - 1);
00484 
00485                 key = utf8str_tolower(utf8str_trim(key));
00486                 value = utf8str_trim(value);
00487 
00488                 complete->header(key, value);
00489         }
00490         else
00491         {
00492                 std::string s(headerLine, headerLen);
00493 
00494                 std::string::iterator end = s.end();
00495                 std::string::iterator pos1 = std::find(s.begin(), end, ' ');
00496                 if (pos1 != end) ++pos1;
00497                 std::string::iterator pos2 = std::find(pos1, end, ' ');
00498                 if (pos2 != end) ++pos2;
00499                 std::string::iterator pos3 = std::find(pos2, end, '\r');
00500 
00501                 std::string version(s.begin(), pos1);
00502                 std::string status(pos1, pos2);
00503                 std::string reason(pos2, pos3);
00504 
00505                 int statusCode = atoi(status.c_str());
00506                 if (statusCode > 0)
00507                 {
00508                         if (complete)
00509                         {
00510                                 complete->httpStatus((U32)statusCode, reason);
00511                         }
00512                 }
00513         }
00514 
00515         return headerLen;
00516 }
00517 
00521 // virtual
00522 LLIOPipe::EStatus LLContextURLExtractor::process_impl(
00523         const LLChannelDescriptors& channels,
00524         buffer_ptr_t& buffer,
00525         bool& eos,
00526         LLSD& context,
00527         LLPumpIO* pump)
00528 {
00529         PUMP_DEBUG;
00530         LLMemType m1(LLMemType::MTYPE_IO_URL_REQUEST);
00531         // The destination host is in the context.
00532         if(context.isUndefined() || !mRequest)
00533         {
00534                 return STATUS_PRECONDITION_NOT_MET;
00535         }
00536 
00537         // copy in to out, since this just extract the URL and does not
00538         // actually change the data.
00539         LLChangeChannel change(channels.in(), channels.out());
00540         std::for_each(buffer->beginSegment(), buffer->endSegment(), change);
00541 
00542         // find the context url
00543         if(context.has(CONTEXT_DEST_URI_SD_LABEL))
00544         {
00545                 mRequest->setURL(context[CONTEXT_DEST_URI_SD_LABEL]);
00546                 return STATUS_DONE;
00547         }
00548         return STATUS_ERROR;
00549 }
00550 
00551 
00555 LLURLRequestComplete::LLURLRequestComplete() :
00556         mRequestStatus(LLIOPipe::STATUS_ERROR)
00557 {
00558         LLMemType m1(LLMemType::MTYPE_IO_URL_REQUEST);
00559 }
00560 
00561 // virtual
00562 LLURLRequestComplete::~LLURLRequestComplete()
00563 {
00564         LLMemType m1(LLMemType::MTYPE_IO_URL_REQUEST);
00565 }
00566 
00567 //virtual 
00568 void LLURLRequestComplete::header(const std::string& header, const std::string& value)
00569 {
00570 }
00571 
00572 //virtual 
00573 void LLURLRequestComplete::httpStatus(U32 status, const std::string& reason)
00574 {
00575 }
00576 
00577 //virtual 
00578 void LLURLRequestComplete::complete(const LLChannelDescriptors& channels,
00579                 const buffer_ptr_t& buffer)
00580 {
00581         if(STATUS_OK == mRequestStatus)
00582         {
00583                 response(channels, buffer);
00584         }
00585         else
00586         {
00587                 noResponse();
00588         }
00589 }
00590 
00591 //virtual 
00592 void LLURLRequestComplete::response(const LLChannelDescriptors& channels,
00593                 const buffer_ptr_t& buffer)
00594 {
00595         llwarns << "LLURLRequestComplete::response default implementation called"
00596                 << llendl;
00597 }
00598 
00599 //virtual 
00600 void LLURLRequestComplete::noResponse()
00601 {
00602         llwarns << "LLURLRequestComplete::noResponse default implementation called"
00603                 << llendl;
00604 }
00605 
00606 void LLURLRequestComplete::responseStatus(LLIOPipe::EStatus status)
00607 {
00608         LLMemType m1(LLMemType::MTYPE_IO_URL_REQUEST);
00609         mRequestStatus = status;
00610 }
00611 
00612 // virtual
00613 LLIOPipe::EStatus LLURLRequestComplete::process_impl(
00614         const LLChannelDescriptors& channels,
00615         buffer_ptr_t& buffer,
00616         bool& eos,
00617         LLSD& context,
00618         LLPumpIO* pump)
00619 {
00620         PUMP_DEBUG;
00621         complete(channels, buffer);
00622         return STATUS_OK;
00623 }

Generated on Fri May 16 08:32:31 2008 for SecondLife by  doxygen 1.5.5