]> git.mxchange.org Git - simgear.git/commitdiff
Optional use libCurl as the HTTP client.
authorJames Turner <zakalawe@mac.com>
Thu, 1 Oct 2015 03:12:35 +0000 (22:12 -0500)
committerJames Turner <zakalawe@mac.com>
Sun, 22 Nov 2015 23:53:46 +0000 (23:53 +0000)
Will permit HTTPS for packages in the future, disabled by default
for the moment.

CMakeLists.txt
simgear/CMakeLists.txt
simgear/io/CMakeLists.txt
simgear/io/HTTPClient.cxx
simgear/io/HTTPClient.hxx
simgear/io/HTTPRequest.cxx
simgear/io/HTTPRequest.hxx
simgear/io/SVNRepository.cxx
simgear/io/test_HTTP.cxx
simgear/package/Install.cxx
simgear/simgear_config_cmake.h.in

index 9ae509e2c512d614b4f8c467dff29606378e53df..e8d493ea6476c51f66f1d03e414448df450c9252 100644 (file)
@@ -116,6 +116,7 @@ option(ENABLE_RTI       "Set to ON to build SimGear with RTI support" OFF)
 option(ENABLE_TESTS     "Set to OFF to disable building SimGear's test applications" ON)
 option(ENABLE_SOUND     "Set to OFF to disable building SimGear's sound support" ON)
 option(ENABLE_PKGUTIL   "Set to ON to build the sg_pkgutil application (default)" ON)
+option(ENABLE_CURL      "Set to ON to use libCurl as the HTTP client backend" OFF)
 
 if (MSVC)
   GET_FILENAME_COMPONENT(PARENT_DIR ${PROJECT_BINARY_DIR} PATH)
@@ -203,6 +204,11 @@ endif(SIMGEAR_HEADLESS)
 
 find_package(ZLIB REQUIRED)
 
+if (ENABLE_CURL)
+  find_package(Curl REQUIRED)
+  message(STATUS "Curl HTTP client: ENABLED")
+endif()
+
 if (SYSTEM_EXPAT)
     message(STATUS "Requested to use system Expat library, forcing SIMGEAR_SHARED to true")
     set(SIMGEAR_SHARED ON)
@@ -368,6 +374,7 @@ include_directories(${OPENSCENEGRAPH_INCLUDE_DIRS}
     ${Boost_INCLUDE_DIRS}
     ${ZLIB_INCLUDE_DIR}
     ${OPENAL_INCLUDE_DIR}
+    ${CURL_INCLUDE_DIRS}
 )
 
 add_definitions(-DHAVE_CONFIG_H)
@@ -396,7 +403,8 @@ set(TEST_LIBS_INTERNAL_CORE
     ${WINSOCK_LIBRARY}
     ${RT_LIBRARY}
     ${DL_LIBRARY}
-    ${COCOA_LIBRARY})
+    ${COCOA_LIBRARY}
+    ${CURL_LIBRARIES})
 set(TEST_LIBS SimGearCore ${TEST_LIBS_INTERNAL_CORE})
 
 if(NOT SIMGEAR_HEADLESS)
index abb4b65a8c4e78b5df5b2f6a5d87b72920252487..37ef4fdc6c3291a45012725edf600f4a20cfb0d3 100644 (file)
@@ -1,7 +1,7 @@
 
 file(WRITE ${PROJECT_BINARY_DIR}/simgear/version.h "#define SIMGEAR_VERSION ${SIMGEAR_VERSION}")
 
-foreach( mylibfolder 
+foreach( mylibfolder
         bucket
         bvh
         debug
@@ -122,7 +122,8 @@ target_link_libraries(SimGearCore
     ${DL_LIBRARY}
     ${EXPAT_LIBRARIES}
     ${CMAKE_THREAD_LIBS_INIT}
-    ${COCOA_LIBRARY})
+    ${COCOA_LIBRARY}
+    ${CURL_LIBRARIES})
 
 if(NOT SIMGEAR_HEADLESS)
     target_link_libraries(SimGearScene
index b72a0040a6a76e33e19510c1b74d166b3c350780..25e79180ea6df5b0c947b24b8717d92933ec7225 100644 (file)
@@ -18,7 +18,6 @@ set(HEADERS
     HTTPFileRequest.hxx
     HTTPMemoryRequest.hxx
     HTTPRequest.hxx
-    HTTPContentDecode.hxx
     DAVMultiStatus.hxx
     SVNRepository.hxx
     SVNDirectory.hxx
@@ -41,13 +40,17 @@ set(SOURCES
     HTTPFileRequest.cxx
     HTTPMemoryRequest.cxx
     HTTPRequest.cxx
-    HTTPContentDecode.cxx
     DAVMultiStatus.cxx
     SVNRepository.cxx
     SVNDirectory.cxx
     SVNReportParser.cxx
     )
 
+if (NOT ENABLE_CURL)
+  list(APPEND SOURCES HTTPContentDecode.cxx)
+  list(APPEND HEADERS HTTPContentDecode.hxx)
+endif()
+
 simgear_component(io io "${SOURCES}" "${HEADERS}")
 
 if(ENABLE_TESTS)
@@ -70,8 +73,8 @@ add_executable(decode_binobj decode_binobj.cxx)
 target_link_libraries(decode_binobj ${TEST_LIBS})
 
 add_executable(test_binobj test_binobj.cxx)
-target_link_libraries(test_binobj ${TEST_LIBS}) 
-    
+target_link_libraries(test_binobj ${TEST_LIBS})
+
 add_test(binobj ${EXECUTABLE_OUTPUT_PATH}/test_binobj)
 
 endif(ENABLE_TESTS)
index a51fb2f43e4e89ea6daf5a5acf97ee0a63c90efe..2a6d41e2feb92d511553107ce44bbdbe7306fc0c 100644 (file)
@@ -21,6 +21,7 @@
 // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 //
 
+
 #include "HTTPClient.hxx"
 #include "HTTPFileRequest.hxx"
 
 #include <boost/foreach.hpp>
 #include <boost/algorithm/string/case_conv.hpp>
 
+#include <simgear/simgear_config.h>
+
+#if defined(ENABLE_CURL)
+  #include <curl/multi.h>
+#else
+    #include <simgear/io/HTTPContentDecode.hxx>
+#endif
+
 #include <simgear/io/sg_netChat.hxx>
-#include <simgear/io/HTTPContentDecode.hxx>
+
 #include <simgear/misc/strutils.hxx>
 #include <simgear/compiler.h>
 #include <simgear/debug/logstream.hxx>
@@ -68,24 +77,32 @@ typedef std::list<Request_ptr> RequestList;
 class Client::ClientPrivate
 {
 public:
+#if defined(ENABLE_CURL)
+    CURLM* curlMulti;
+    bool haveActiveRequests;
+#else
+    NetChannelPoller poller;
+// connections by host (potentially more than one)
+    ConnectionDict connections;
+#endif
+
     std::string userAgent;
     std::string proxy;
     int proxyPort;
     std::string proxyAuth;
-    NetChannelPoller poller;
     unsigned int maxConnections;
-    
+
     RequestList pendingRequests;
-    
-// connections by host (potentially more than one)
-    ConnectionDict connections;
-    
+
+
+
     SGTimeStamp timeTransferSample;
     unsigned int bytesTransferred;
     unsigned int lastTransferRate;
     uint64_t totalBytesDownloaded;
 };
-  
+
+#if !defined(ENABLE_CURL)
 class Connection : public NetChat
 {
 public:
@@ -623,6 +640,7 @@ private:
 
     ContentDecoder _contentDecoder;
 };
+#endif // of !ENABLE_CURL
 
 Client::Client() :
     d(new ClientPrivate)
@@ -633,12 +651,24 @@ Client::Client() :
     d->lastTransferRate = 0;
     d->timeTransferSample.stamp();
     d->totalBytesDownloaded = 0;
-    
+
     setUserAgent("SimGear-" SG_STRINGIZE(SIMGEAR_VERSION));
+#if defined(ENABLE_CURL)
+    static bool didInitCurlGlobal = false;
+    if (!didInitCurlGlobal) {
+      curl_global_init(CURL_GLOBAL_ALL);
+      didInitCurlGlobal = true;
+    }
+
+    d->curlMulti = curl_multi_init();
+#endif
 }
 
 Client::~Client()
 {
+#if defined(ENABLE_CURL)
+  curl_multi_cleanup(d->curlMulti);
+#endif
 }
 
 void Client::setMaxConnections(unsigned int maxCon)
@@ -646,12 +676,49 @@ void Client::setMaxConnections(unsigned int maxCon)
     if (maxCon < 1) {
         throw sg_range_exception("illegal HTTP::Client::setMaxConnections value");
     }
-    
+
     d->maxConnections = maxCon;
+#if defined(ENABLE_CURL)
+    curl_multi_setopt(d->curlMulti, CURLMOPT_MAXCONNECTS, (long) maxCon);
+#endif
 }
 
 void Client::update(int waitTimeout)
 {
+#if defined(ENABLE_CURL)
+    int remainingActive, messagesInQueue;
+    curl_multi_perform(d->curlMulti, &remainingActive);
+    d->haveActiveRequests = (remainingActive > 0);
+
+    CURLMsg* msg;
+    while ((msg = curl_multi_info_read(d->curlMulti, &messagesInQueue))) {
+      if (msg->msg == CURLMSG_DONE) {
+        Request* req;
+        CURL *e = msg->easy_handle;
+        curl_easy_getinfo(e, CURLINFO_PRIVATE, &req);
+
+        long responseCode;
+        curl_easy_getinfo(e, CURLINFO_RESPONSE_CODE, &responseCode);
+
+        if (msg->data.result == 0) {
+          req->responseComplete();
+        } else {
+          fprintf(stderr, "Result: %d - %s\n",
+                msg->data.result, curl_easy_strerror(msg->data.result));
+          req->setFailure(msg->data.result, curl_easy_strerror(msg->data.result));
+        }
+
+        curl_multi_remove_handle(d->curlMulti, e);
+
+        // balance the reference we take in makeRequest
+        SGReferenced::put(req);
+        curl_easy_cleanup(e);
+      }
+      else {
+        SG_LOG(SG_IO, SG_ALERT, "CurlMSG:" << msg->msg);
+      }
+    } // of curl message processing loop
+#else
     if (!d->poller.hasChannels() && (waitTimeout > 0)) {
         SGTimeStamp::sleepForMSec(waitTimeout);
     } else {
@@ -697,6 +764,7 @@ void Client::update(int waitTimeout)
             makeRequest(req);
         }
     }
+#endif
 }
 
 void Client::makeRequest(const Request_ptr& r)
@@ -709,18 +777,94 @@ void Client::makeRequest(const Request_ptr& r)
         return;
     }
 
+#if defined(ENABLE_CURL)
+    CURL* curlRequest = curl_easy_init();
+    curl_easy_setopt(curlRequest, CURLOPT_URL, r->url().c_str());
+
+    // manually increase the ref count of the request
+    SGReferenced::get(r.get());
+    curl_easy_setopt(curlRequest, CURLOPT_PRIVATE, r.get());
+    // disable built-in libCurl progress feedback
+    curl_easy_setopt(curlRequest, CURLOPT_NOPROGRESS, 1);
+
+    curl_easy_setopt(curlRequest, CURLOPT_WRITEFUNCTION, requestWriteCallback);
+    curl_easy_setopt(curlRequest, CURLOPT_WRITEDATA, r.get());
+    curl_easy_setopt(curlRequest, CURLOPT_HEADERFUNCTION, requestHeaderCallback);
+    curl_easy_setopt(curlRequest, CURLOPT_HEADERDATA, r.get());
+
+    curl_easy_setopt(curlRequest, CURLOPT_USERAGENT, d->userAgent.c_str());
+
+    if (!d->proxy.empty()) {
+      curl_easy_setopt(curlRequest, CURLOPT_PROXY, d->proxy.c_str());
+      curl_easy_setopt(curlRequest, CURLOPT_PROXYPORT, d->proxyPort);
+
+      if (!d->proxyAuth.empty()) {
+        curl_easy_setopt(curlRequest, CURLOPT_PROXYAUTH, CURLAUTH_BASIC);
+        curl_easy_setopt(curlRequest, CURLOPT_PROXYUSERPWD, d->proxyAuth.c_str());
+      }
+    }
+
+    std::string method = boost::to_lower_copy(r->method());
+    if (method == "get") {
+      curl_easy_setopt(curlRequest, CURLOPT_HTTPGET, 1);
+    } else if (method == "put") {
+      curl_easy_setopt(curlRequest, CURLOPT_PUT, 1);
+      curl_easy_setopt(curlRequest, CURLOPT_UPLOAD, 1);
+    } else if (method == "post") {
+      // see http://curl.haxx.se/libcurl/c/CURLOPT_POST.html
+      curl_easy_setopt(curlRequest, CURLOPT_HTTPPOST, 1);
+
+      std::string q = r->query().substr(1);
+      curl_easy_setopt(curlRequest, CURLOPT_COPYPOSTFIELDS, q.c_str());
+
+      // reset URL to exclude query pieces
+      std::string urlWithoutQuery = r->url();
+      std::string::size_type queryPos = urlWithoutQuery.find('?');
+      urlWithoutQuery.resize(queryPos);
+      curl_easy_setopt(curlRequest, CURLOPT_URL, urlWithoutQuery.c_str());
+    } else {
+      curl_easy_setopt(curlRequest, CURLOPT_CUSTOMREQUEST, r->method().c_str());
+    }
+
+    struct curl_slist* headerList = NULL;
+    if (r->hasBodyData() && (method != "post")) {
+      curl_easy_setopt(curlRequest, CURLOPT_UPLOAD, 1);
+      curl_easy_setopt(curlRequest, CURLOPT_INFILESIZE, r->bodyLength());
+      curl_easy_setopt(curlRequest, CURLOPT_READFUNCTION, requestReadCallback);
+      curl_easy_setopt(curlRequest, CURLOPT_READDATA, r.get());
+      std::string h = "Content-Type:" + r->bodyType();
+      headerList = curl_slist_append(headerList, h.c_str());
+    }
+
+    StringMap::const_iterator it;
+    for (it = r->requestHeaders().begin(); it != r->requestHeaders().end(); ++it) {
+      std::string h = it->first + ": " + it->second;
+      headerList = curl_slist_append(headerList, h.c_str());
+    }
+
+    if (headerList != NULL) {
+      curl_easy_setopt(curlRequest, CURLOPT_HTTPHEADER, headerList);
+    }
+
+    curl_multi_add_handle(d->curlMulti, curlRequest);
+    d->haveActiveRequests = true;
+
+// FIXME - premature?
+    r->requestStart();
+
+#else
     if( r->url().find("http://") != 0 ) {
         r->setFailure(EINVAL, "only HTTP protocol is supported");
         return;
     }
-    
+
     std::string host = r->host();
     int port = r->port();
     if (!d->proxy.empty()) {
         host = d->proxy;
         port = d->proxyPort;
     }
-    
+
     Connection* con = NULL;
     std::stringstream ss;
     ss << host << "-" << port;
@@ -774,6 +918,7 @@ void Client::makeRequest(const Request_ptr& r)
     }
 
     con->queueRequest(r);
+#endif
 }
 
 //------------------------------------------------------------------------------
@@ -829,12 +974,16 @@ void Client::setProxy( const std::string& proxy,
 
 bool Client::hasActiveRequests() const
 {
+  #if defined(ENABLE_CURL)
+    return d->haveActiveRequests;
+  #else
     ConnectionDict::const_iterator it = d->connections.begin();
     for (; it != d->connections.end(); ++it) {
         if (it->second->isActive()) return true;
     }
 
     return false;
+#endif
 }
 
 void Client::receivedBytes(unsigned int count)
@@ -874,6 +1023,63 @@ uint64_t Client::totalBytesDownloaded() const
     return d->totalBytesDownloaded;
 }
 
+size_t Client::requestWriteCallback(char *ptr, size_t size, size_t nmemb, void *userdata)
+{
+  size_t byteSize = size * nmemb;
+
+  Request* req = static_cast<Request*>(userdata);
+  req->processBodyBytes(ptr, byteSize);
+  return byteSize;
+}
+
+size_t Client::requestReadCallback(char *ptr, size_t size, size_t nmemb, void *userdata)
+{
+  size_t maxBytes = size * nmemb;
+  Request* req = static_cast<Request*>(userdata);
+  size_t actualBytes = req->getBodyData(ptr, 0, maxBytes);
+  return actualBytes;
+}
+
+size_t Client::requestHeaderCallback(char *rawBuffer, size_t size, size_t nitems, void *userdata)
+{
+  size_t byteSize = size * nitems;
+  Request* req = static_cast<Request*>(userdata);
+  std::string h = strutils::simplify(std::string(rawBuffer, byteSize));
+
+  if (req->readyState() == HTTP::Request::OPENED) {
+    req->responseStart(h);
+    return byteSize;
+  }
+
+  if (h.empty()) {
+      // got a 100-continue reponse; restart
+      if (req->responseCode() == 100) {
+          req->setReadyState(HTTP::Request::OPENED);
+          return byteSize;
+      }
+
+    req->responseHeadersComplete();
+    return byteSize;
+  }
+
+  if (req->responseCode() == 100) {
+      return byteSize; // skip headers associated with 100-continue status
+  }
+
+  int colonPos = h.find(':');
+  if (colonPos == std::string::npos) {
+      SG_LOG(SG_IO, SG_WARN, "malformed HTTP response header:" << h);
+      return byteSize;
+  }
+
+  std::string key = strutils::simplify(h.substr(0, colonPos));
+  std::string lkey = boost::to_lower_copy(key);
+  std::string value = strutils::strip(h.substr(colonPos + 1));
+
+  req->responseHeader(lkey, value);
+  return byteSize;
+}
+
 } // of namespace HTTP
 
 } // of namespace simgear
index 89800159e19a4383f2ccda72ed18a022450be0a0..a9b2eee62e220c2a282a3dbeb8e64f600a67aa21 100644 (file)
@@ -99,6 +99,11 @@ public:
      */
     uint64_t totalBytesDownloaded() const;
 private:
+    // libCurl callbacks
+    static size_t requestWriteCallback(char *ptr, size_t size, size_t nmemb, void *userdata);
+    static size_t requestReadCallback(char *ptr, size_t size, size_t nmemb, void *userdata);
+    static size_t requestHeaderCallback(char *buffer, size_t size, size_t nitems, void *userdata);
+
     void requestFinished(Connection* con);
 
     void receivedBytes(unsigned int count);
index 90b30719667a405e32ddabeaaf7409b527409cd0..4ed18d6eb87d2277e4a69149594443ee265a3e79 100644 (file)
@@ -133,19 +133,25 @@ void Request::responseStart(const std::string& r)
     const int maxSplit = 2; // HTTP/1.1 nnn reason-string
     string_list parts = strutils::split(r, NULL, maxSplit);
     if (parts.size() != 3) {
-        throw sg_io_exception("bad HTTP response");
+        throw sg_io_exception("bad HTTP response:" + r);
     }
-    
+
     _responseVersion = decodeHTTPVersion(parts[0]);
     _responseStatus = strutils::to_int(parts[1]);
     _responseReason = parts[2];
+
+    setReadyState(STATUS_RECEIVED);
 }
 
 //------------------------------------------------------------------------------
 void Request::responseHeader(const std::string& key, const std::string& value)
 {
-  if( key == "connection" )
+  if( key == "connection" ) {
     _willClose = (value.find("close") != std::string::npos);
+  } else if (key == "content-length") {
+    int sz = strutils::to_int(value);
+    setResponseLength(sz);
+  }
 
   _responseHeaders[key] = value;
 }
index 1897a5b0ce92b7f99caedd061fe62c4e252ac07f..9182ed3703c6988fbc68bc358085ee47dde3acf1 100644 (file)
@@ -50,6 +50,7 @@ public:
     {
       UNSENT = 0,
       OPENED,
+      STATUS_RECEIVED,
       HEADERS_RECEIVED,
       LOADING,
       DONE,
index 39114b1f172d93de878cfa39251bbd0569f583a6..c67f6007299dce2d1813ea5c6cef73160f4df425 100644 (file)
@@ -120,6 +120,7 @@ namespace { // anonmouse
         Request(repo->baseUrl, "PROPFIND"),
         _repo(repo)
       {
+        assert(repo);
         requestHeader("Depth") = "0";
         setBodyData( PROPFIND_REQUEST_BODY,
                      "application/xml; charset=\"utf-8\"" );
@@ -138,6 +139,8 @@ namespace { // anonmouse
             _repo->propFindFailed(this, SVNRepository::SVN_ERROR_SOCKET);
             _repo = NULL;
         }
+
+        Request::responseHeadersComplete();
       }
   
       virtual void onDone()
index 700eace47488d4c90e6f231aed5a8aa2f57a68ba..d7bd1280b6d8ff5d06d0bc5216c1b0c918d1f87f 100644 (file)
@@ -12,6 +12,7 @@
 #include <simgear/io/sg_netChat.hxx>
 #include <simgear/misc/strutils.hxx>
 #include <simgear/timing/timestamp.hxx>
+#include <simgear/debug/logstream.hxx>
 
 using std::cout;
 using std::cerr;
@@ -54,41 +55,42 @@ public:
     bool failed;
     string bodyData;
 
-    TestRequest(const std::string& url, const std::string method = "GET") : 
+    TestRequest(const std::string& url, const std::string method = "GET") :
         HTTP::Request(url, method),
         complete(false)
     {
 
     }
-    
+
     std::map<string, string> headers;
 protected:
-    
+
     virtual void onDone()
     {
         complete = true;
-    }  
-    
+    }
+
     virtual void onFail()
     {
         failed = true;
     }
-    
+
     virtual void gotBodyData(const char* s, int n)
     {
       //std::cout << "got body data:'" << string(s, n) << "'" <<std::endl;
         bodyData += string(s, n);
     }
-    
+
     virtual void responseHeader(const string& header, const string& value)
     {
+        Request::responseHeader(header, value);
         headers[header] =  value;
     }
 };
 
 class TestServerChannel : public NetChat
 {
-public:  
+public:
     enum State
     {
         STATE_IDLE = 0,
@@ -96,19 +98,19 @@ public:
         STATE_CLOSING,
         STATE_REQUEST_BODY
     };
-    
+
     TestServerChannel()
     {
         state = STATE_IDLE;
         setTerminator("\r\n");
-        
+
     }
-    
+
     virtual void collectIncomingData(const char* s, int n)
     {
         buffer += string(s, n);
     }
-    
+
     virtual void foundTerminator(void)
     {
         if (state == STATE_IDLE) {
@@ -118,16 +120,16 @@ public:
                 cerr << "malformed request:" << buffer << endl;
                 exit(-1);
             }
-            
+
             method = line[0];
             path = line[1];
-            
+
             string::size_type queryPos = path.find('?');
             if (queryPos != string::npos) {
                 parseArgs(path.substr(queryPos + 1));
                 path = path.substr(0, queryPos);
             }
-            
+
             httpVersion = line[2];
             requestHeaders.clear();
             buffer.clear();
@@ -138,10 +140,10 @@ public:
                 receivedRequestHeaders();
                 return;
             }
-            
+
             string::size_type colonPos = buffer.find(':');
             if (colonPos == string::npos) {
-                cerr << "malformed HTTP response header:" << buffer << endl;
+                cerr << "test malformed HTTP response header:" << buffer << endl;
                 buffer.clear();
                 return;
             }
@@ -156,8 +158,8 @@ public:
         } else if (state == STATE_CLOSING) {
           // ignore!
         }
-    }  
-    
+    }
+
     void parseArgs(const string& argData)
     {
         string_list argv = strutils::split(argData, "&");
@@ -173,11 +175,10 @@ public:
             args[key] = value;
         }
     }
-    
+
     void receivedRequestHeaders()
     {
         state = STATE_IDLE;
-        
         if (path == "/test1") {
             string contentStr(BODY1);
             stringstream d;
@@ -205,7 +206,7 @@ public:
         } else if (path == "/test_headers") {
             COMPARE(requestHeaders["X-Foo"], string("Bar"));
             COMPARE(requestHeaders["X-AnotherHeader"], string("A longer value"));
-            
+
             string contentStr(BODY1);
             stringstream d;
             d << "HTTP/1.1 " << 200 << " " << reasonForCode(200) << "\r\n";
@@ -235,11 +236,11 @@ public:
             if (requestHeaders["Host"] != "www.google.com") {
                 sendErrorResponse(400, true, "bad destination");
             }
-            
+
             if (requestHeaders["Proxy-Authorization"] != string()) {
-                sendErrorResponse(401, false, "bad auth"); // shouldn't supply auth
+                sendErrorResponse(401, false, "bad auth, not empty"); // shouldn't supply auth
             }
-            
+
             sendBody2();
         } else if (path == "http://www.google.com/test3") {
             // proxy test
@@ -247,8 +248,28 @@ public:
                 sendErrorResponse(400, true, "bad destination");
             }
 
-            if (requestHeaders["Proxy-Authorization"] != "ABCDEF") {
-                sendErrorResponse(401, false, "bad auth"); // forbidden
+            string credentials = requestHeaders["Proxy-Authorization"];
+            if (credentials.substr(0, 5) != "Basic") {
+              // request basic auth
+              stringstream d;
+              d << "HTTP/1.1 " << 407 << " " << reasonForCode(407) << "\r\n";
+              d << "WWW-Authenticate: Basic real=\"simgear\"\r\n";
+              d << "\r\n"; // final CRLF to terminate the headers
+              push(d.str().c_str());
+              return;
+            }
+
+            std::vector<unsigned char> userAndPass;
+            strutils::decodeBase64(credentials.substr(6), userAndPass);
+            std::string decodedUserPass((char*) userAndPass.data(), userAndPass.size());
+
+            if (decodedUserPass != "johndoe:swordfish") {
+                std::map<string, string>::const_iterator it;
+                for (it = requestHeaders.begin(); it != requestHeaders.end(); ++it) {
+                  cerr << "header:" << it->first << " = " << it->second << endl;
+                }
+
+                sendErrorResponse(401, false, "bad auth, not as set"); // forbidden
             }
 
             sendBody2();
@@ -291,43 +312,78 @@ public:
                  sendErrorResponse(400, true, "bad content type");
                  return;
             }
-            
+
             requestContentLength = strutils::to_int(requestHeaders["Content-Length"]);
             setByteCount(requestContentLength);
             state = STATE_REQUEST_BODY;
+        } else if ((path == "/test_put") || (path == "/test_create")) {
+              if (requestHeaders["Content-Type"] != "x-application/foobar") {
+                  cerr << "bad content type: '" << requestHeaders["Content-Type"] << "'" << endl;
+                   sendErrorResponse(400, true, "bad content type");
+                   return;
+              }
+
+              requestContentLength = strutils::to_int(requestHeaders["Content-Length"]);
+              setByteCount(requestContentLength);
+              state = STATE_REQUEST_BODY;
         } else {
             sendErrorResponse(404, false, "");
         }
     }
-    
+
     void closeAfterSending()
     {
       state = STATE_CLOSING;
       closeWhenDone();
     }
-  
+
     void receivedBody()
     {
         state = STATE_IDLE;
         if (method == "POST") {
             parseArgs(buffer);
         }
-        
+
         if (path == "/test_post") {
             if ((args["foo"] != "abc") || (args["bar"] != "1234") || (args["username"] != "johndoe")) {
                 sendErrorResponse(400, true, "bad arguments");
                 return;
             }
-            
+
             stringstream d;
             d << "HTTP/1.1 " << 204 << " " << reasonForCode(204) << "\r\n";
             d << "\r\n"; // final CRLF to terminate the headers
             push(d.str().c_str());
-            
-            cerr << "sent 204 response ok" << endl;
+        } else if (path == "/test_put") {
+          std::cerr << "sending PUT response" << std::endl;
+
+          COMPARE(buffer, BODY3);
+          stringstream d;
+          d << "HTTP/1.1 " << 204 << " " << reasonForCode(204) << "\r\n";
+          d << "\r\n"; // final CRLF to terminate the headers
+          push(d.str().c_str());
+        } else if (path == "/test_create") {
+          std::cerr << "sending create response" << std::endl;
+
+          std::string entityStr = "http://localhost:2000/something.txt";
+
+          COMPARE(buffer, BODY3);
+          stringstream d;
+          d << "HTTP/1.1 " << 201 << " " << reasonForCode(201) << "\r\n";
+          d << "Location:" << entityStr << "\r\n";
+          d << "Content-Length:" << entityStr.size() << "\r\n";
+          d << "\r\n"; // final CRLF to terminate the headers
+          d << entityStr;
+
+          push(d.str().c_str());
+        } else {
+          std::cerr << "weird URL " << path << std::endl;
+          sendErrorResponse(400, true, "bad URL:" + path);
         }
+
+        buffer.clear();
     }
-    
+
     void sendBody2()
     {
         stringstream d;
@@ -337,32 +393,36 @@ public:
         push(d.str().c_str());
         bufferSend(body2, body2Size);
     }
-    
+
     void sendErrorResponse(int code, bool close, string content)
     {
         cerr << "sending error " << code << " for " << path << endl;
+        cerr << "\tcontent:" << content << endl;
+
         stringstream headerData;
         headerData << "HTTP/1.1 " << code << " " << reasonForCode(code) << "\r\n";
         headerData << "Content-Length:" << content.size() << "\r\n";
         headerData << "\r\n"; // final CRLF to terminate the headers
         push(headerData.str().c_str());
         push(content.c_str());
-        
+
         if (close) {
             closeWhenDone();
         }
     }
-    
-    string reasonForCode(int code) 
+
+    string reasonForCode(int code)
     {
         switch (code) {
             case 200: return "OK";
+            case 201: return "Created";
             case 204: return "no content";
             case 404: return "not found";
+            case 407: return "proxy authentication required";
             default: return "unknown code";
         }
     }
-    
+
     State state;
     string buffer;
     string method;
@@ -376,7 +436,7 @@ public:
 class TestServer : public NetChannel
 {
     simgear::NetChannelPoller _poller;
-public:   
+public:
     TestServer()
     {
         Socket::initSockets();
@@ -384,14 +444,14 @@ public:
         open();
         bind(NULL, 2000); // localhost, any port
         listen(5);
-        
+
         _poller.addChannel(this);
     }
-    
+
     virtual ~TestServer()
-    {    
+    {
     }
-    
+
     virtual bool writable (void) { return false ; }
 
     virtual void handleAccept (void)
@@ -401,10 +461,10 @@ public:
         //cout << "did accept from " << addr.getHost() << ":" << addr.getPort() << endl;
         TestServerChannel* chan = new TestServerChannel();
         chan->setHandle(handle);
-        
+
         _poller.addChannel(chan);
     }
-    
+
     void poll()
     {
         _poller.poll();
@@ -419,13 +479,13 @@ void waitForComplete(HTTP::Client* cl, TestRequest* tr)
     while (start.elapsedMSec() <  10000) {
         cl->update();
         testServer.poll();
-        
+
         if (tr->complete) {
             return;
         }
         SGTimeStamp::sleepForMSec(15);
     }
-    
+
     cerr << "timed out" << endl;
 }
 
@@ -435,23 +495,24 @@ void waitForFailed(HTTP::Client* cl, TestRequest* tr)
     while (start.elapsedMSec() <  10000) {
         cl->update();
         testServer.poll();
-        
+
         if (tr->failed) {
             return;
         }
         SGTimeStamp::sleepForMSec(15);
     }
-    
+
     cerr << "timed out waiting for failure" << endl;
 }
 
 int main(int argc, char* argv[])
 {
-    
+    sglog().setLogLevels( SG_ALL, SG_INFO );
+
     HTTP::Client cl;
     // force all requests to use the same connection for this test
-    cl.setMaxConnections(1); 
-    
+    cl.setMaxConnections(1);
+
 // test URL parsing
     TestRequest* tr1 = new TestRequest("http://localhost.woo.zar:2000/test1?foo=bar");
     COMPARE(tr1->scheme(), "http");
@@ -459,14 +520,14 @@ int main(int argc, char* argv[])
     COMPARE(tr1->host(), "localhost.woo.zar");
     COMPARE(tr1->port(), 2000);
     COMPARE(tr1->path(), "/test1");
-    
+
     TestRequest* tr2 = new TestRequest("http://192.168.1.1/test1/dir/thing/file.png");
     COMPARE(tr2->scheme(), "http");
     COMPARE(tr2->hostAndPort(), "192.168.1.1");
     COMPARE(tr2->host(), "192.168.1.1");
     COMPARE(tr2->port(), 80);
     COMPARE(tr2->path(), "/test1/dir/thing/file.png");
-    
+
 // basic get request
     {
         TestRequest* tr = new TestRequest("http://localhost:2000/test1");
@@ -480,12 +541,12 @@ int main(int argc, char* argv[])
         COMPARE(tr->responseBytesReceived(), strlen(BODY1));
         COMPARE(tr->bodyData, string(BODY1));
     }
-    
+
     {
       TestRequest* tr = new TestRequest("http://localhost:2000/testLorem");
       HTTP::Request_ptr own(tr);
       cl.makeRequest(tr);
-      
+
       waitForComplete(&cl, tr);
       COMPARE(tr->responseCode(), 200);
       COMPARE(tr->responseReason(), string("OK"));
@@ -493,7 +554,7 @@ int main(int argc, char* argv[])
       COMPARE(tr->responseBytesReceived(), strlen(BODY3));
       COMPARE(tr->bodyData, string(BODY3));
     }
-  
+
     {
         TestRequest* tr = new TestRequest("http://localhost:2000/test_args?foo=abc&bar=1234&username=johndoe");
         HTTP::Request_ptr own(tr);
@@ -501,9 +562,7 @@ int main(int argc, char* argv[])
         waitForComplete(&cl, tr);
         COMPARE(tr->responseCode(), 200);
     }
-    
-    cerr << "done args" << endl;
-    
+
     {
         TestRequest* tr = new TestRequest("http://localhost:2000/test_headers");
         HTTP::Request_ptr own(tr);
@@ -518,12 +577,12 @@ int main(int argc, char* argv[])
         COMPARE(tr->responseBytesReceived(), strlen(BODY1));
         COMPARE(tr->bodyData, string(BODY1));
     }
-    
+
 // larger get request
     for (unsigned int i=0; i<body2Size; ++i) {
         body2[i] = (i << 4) | (i >> 2);
     }
-    
+
     {
         TestRequest* tr = new TestRequest("http://localhost:2000/test2");
         HTTP::Request_ptr own(tr);
@@ -533,8 +592,8 @@ int main(int argc, char* argv[])
         COMPARE(tr->responseBytesReceived(), body2Size);
         COMPARE(tr->bodyData, string(body2, body2Size));
     }
-    
-    cerr << "testing chunked" << endl;
+
+    cerr << "testing chunked transfer encoding" << endl;
     {
         TestRequest* tr = new TestRequest("http://localhost:2000/testchunked");
         HTTP::Request_ptr own(tr);
@@ -548,7 +607,7 @@ int main(int argc, char* argv[])
     // check trailers made it too
         COMPARE(tr->headers["x-foobar"], string("wibble"));
     }
-    
+
 // test 404
     {
         TestRequest* tr = new TestRequest("http://localhost:2000/not-found");
@@ -615,9 +674,10 @@ int main(int argc, char* argv[])
         COMPARE(tr->responseLength(), body2Size);
         COMPARE(tr->bodyData, string(body2, body2Size));
     }
-    
+
+#if defined(ENABLE_CURL)
     {
-        cl.setProxy("localhost", 2000, "ABCDEF");
+        cl.setProxy("localhost", 2000, "johndoe:swordfish");
         TestRequest* tr = new TestRequest("http://www.google.com/test3");
         HTTP::Request_ptr own(tr);
         cl.makeRequest(tr);
@@ -626,81 +686,105 @@ int main(int argc, char* argv[])
         COMPARE(tr->responseBytesReceived(), body2Size);
         COMPARE(tr->bodyData, string(body2, body2Size));
     }
-    
+#endif
+
 // pipelining
-    cout << "testing HTTP 1.1 pipelineing" << endl;
-  
+    cout << "testing HTTP 1.1 pipelining" << endl;
+
     {
-                                
+
         cl.setProxy("", 80);
         TestRequest* tr = new TestRequest("http://localhost:2000/test1");
         HTTP::Request_ptr own(tr);
         cl.makeRequest(tr);
-        
-        
+
+
         TestRequest* tr2 = new TestRequest("http://localhost:2000/testLorem");
         HTTP::Request_ptr own2(tr2);
         cl.makeRequest(tr2);
-        
+
         TestRequest* tr3 = new TestRequest("http://localhost:2000/test1");
         HTTP::Request_ptr own3(tr3);
         cl.makeRequest(tr3);
-        
+
         waitForComplete(&cl, tr3);
         VERIFY(tr->complete);
         VERIFY(tr2->complete);
         COMPARE(tr->bodyData, string(BODY1));
-      
+
         COMPARE(tr2->responseLength(), strlen(BODY3));
         COMPARE(tr2->responseBytesReceived(), strlen(BODY3));
         COMPARE(tr2->bodyData, string(BODY3));
-      
+
         COMPARE(tr3->bodyData, string(BODY1));
     }
-    
+
 // multiple requests with an HTTP 1.0 server
     {
         cout << "http 1.0 multiple requests" << endl;
-        
+
         cl.setProxy("", 80);
         TestRequest* tr = new TestRequest("http://localhost:2000/test_1_0/A");
         HTTP::Request_ptr own(tr);
         cl.makeRequest(tr);
-        
+
         TestRequest* tr2 = new TestRequest("http://localhost:2000/test_1_0/B");
         HTTP::Request_ptr own2(tr2);
         cl.makeRequest(tr2);
-        
+
         TestRequest* tr3 = new TestRequest("http://localhost:2000/test_1_0/C");
         HTTP::Request_ptr own3(tr3);
         cl.makeRequest(tr3);
-        
+
         waitForComplete(&cl, tr3);
         VERIFY(tr->complete);
         VERIFY(tr2->complete);
-        
+
         COMPARE(tr->responseLength(), strlen(BODY1));
         COMPARE(tr->responseBytesReceived(), strlen(BODY1));
         COMPARE(tr->bodyData, string(BODY1));
-      
+
         COMPARE(tr2->responseLength(), strlen(BODY3));
         COMPARE(tr2->responseBytesReceived(), strlen(BODY3));
         COMPARE(tr2->bodyData, string(BODY3));
         COMPARE(tr3->bodyData, string(BODY1));
     }
-    
+
 // POST
     {
-        cout << "POST" << endl;
+        cout << "testing POST" << endl;
         TestRequest* tr = new TestRequest("http://localhost:2000/test_post?foo=abc&bar=1234&username=johndoe", "POST");
         tr->setBodyData("", "application/x-www-form-urlencoded");
-        
+
         HTTP::Request_ptr own(tr);
         cl.makeRequest(tr);
         waitForComplete(&cl, tr);
         COMPARE(tr->responseCode(), 204);
     }
-    
+
+    // PUT
+        {
+            cout << "testing PUT" << endl;
+            TestRequest* tr = new TestRequest("http://localhost:2000/test_put", "PUT");
+            tr->setBodyData(BODY3, "x-application/foobar");
+
+            HTTP::Request_ptr own(tr);
+            cl.makeRequest(tr);
+            waitForComplete(&cl, tr);
+            COMPARE(tr->responseCode(), 204);
+        }
+
+        {
+            cout << "testing PUT create" << endl;
+            TestRequest* tr = new TestRequest("http://localhost:2000/test_create", "PUT");
+            tr->setBodyData(BODY3, "x-application/foobar");
+
+            HTTP::Request_ptr own(tr);
+            cl.makeRequest(tr);
+            waitForComplete(&cl, tr);
+            COMPARE(tr->responseCode(), 201);
+        }
+
     // test_zero_length_content
     {
         cout << "zero-length-content-response" << endl;
@@ -712,8 +796,8 @@ int main(int argc, char* argv[])
         COMPARE(tr->bodyData, string());
         COMPARE(tr->responseBytesReceived(), 0);
     }
-    
-    
+
+
     cout << "all tests passed ok" << endl;
     return EXIT_SUCCESS;
 }
index d8896d290a302aee937d2af4abb29970d249f209..a240be17b1d7a62a239bfcbbdcf4fd91a5b98c65 100644 (file)
@@ -98,6 +98,8 @@ protected:
 
     virtual void responseHeadersComplete()
     {
+        Request::responseHeadersComplete();
+
         Dir d(m_extractPath);
         d.create(0755);
 
index 0cce4b316c83ee8ed7a40bbd833309fd33a63444..4e68e4ce47cff74d1554e677e769b53e5a2154bb 100644 (file)
@@ -18,3 +18,4 @@
 
 #cmakedefine SYSTEM_EXPAT
 #cmakedefine ENABLE_SOUND
+#cmakedefine ENABLE_CURL