#ifndef HTTP_CLIENT_H #define HTTP_CLIENT_H #include "DEFINES.H" #include #include #include #include #include #define DYNALLOC_HTTPCLIENT #ifdef DYNALLOC_HTTPCLIENT #include #endif // Compile time assert, from NeL #define ctassert(cond) sizeof(unsigned int[(cond) ? 1 : 0]); #ifndef _min #define _min(a, b) ((a < b) ? a : b) #endif void logStr(const char *format, ...); std::string printfToStr(const char *format, ...); /** * Simple fixed-size string */ template class SimpleString { public: mutable char Buf [MaxSize+1]; // mutable for SimpleBuffer::c_str() public: enum { SizeLimit = MaxSize }; /// Constructor SimpleString() { ctassert(MaxSize != 0); Buf[0] = '\0'; } /// Test if the string is empty bool empty() const { return Buf[0] == '\0'; } /// Reset the string void clear() { Buf[0] = '\0'; } /// Access the string for reading const char *c_str() const { return Buf; } /// Append str to this string (no bound check on this string!) SimpleString& appendStrCat(const char *str, size_t maxChars) { strncat(Buf, str, maxChars); return *this; } /// Append str to this string, with bound check on this string SimpleString& appendStr(const char *str, size_t maxChars) { size_t size = strlen(Buf); strncat(Buf, str, _min(maxChars, MaxSize-size)); return *this; } /// Set the string using sprintf, but with bound check (MaxSize) SimpleString& sprintf(const char *format, ...); }; template SimpleString& SimpleString::sprintf(const char *format, ...) { va_list args; va_start(args, format); int res = _vsnprintf(Buf, MaxSize-1, format, args); if ((res == -1) || (res == MaxSize-1)) { Buf[MaxSize-1] = '\0'; } va_end(args); return *this; } /** * Simple fixed-size buffer. * Support fast & safe string appending with appendStr(). * Supports null chars with appendBinary(). */ template class SimpleBuffer : protected SimpleString { protected: size_t _Size; public: enum { SizeLimit = MaxSize }; /// Constructor SimpleBuffer() : SimpleString(), _Size(0) {} // Test if the buffer is empty bool empty() const { return (_Size == 0); } // Clear the buffer (set to an empty string) void clear() { SimpleString::clear(); _Size = 0; } // Return the current size of the buffer size_t size() const { return _Size; } // Access the buffer for reading. WARNING: it might be a binary buffer with no terminal null char! const char *buffer() const { return Buf; } // Access the buffer for reading. WARNING: it might be a binary buffer with no terminal null char! const char *data() const { return Buf; } // Access the buffer as a string for reading. This ensures the buffer is null-terminated. const char *c_str() const { Buf[_Size] = '\0'; return Buf; } // Append str to this buffer, with bound check on this buffer (faster than SimpleString::appendStrCat() for large buffers) SimpleString& appendStr(const char *str, size_t maxChars) { for (size_t i=0; (str[i]!='\0') && (i!=maxChars) && (_Size!=MaxSize); ++i) { Buf[_Size] = str[i]; ++_Size; } return *this; } // Append buf to this buffer, with bound check on this buffer SimpleString& appendBinary(const char *buf, size_t bufsize) { for (size_t i=0; (i!=bufsize) && (_Size!=MaxSize); ++i) { Buf[_Size] = buf[i]; ++_Size; } return *this; } }; #ifdef DYNALLOC_HTTPCLIENT typedef std::string HttpPage; #else typedef SimpleBuffer HttpPage; #endif /** * Background Task base class, build your asynchronous tasks from it. */ class CBackgroundTask { public: CBackgroundTask() : _IsRunning(false) {} virtual ~CBackgroundTask() {} /// Is the run() function in progress? bool isRunning() { return _IsRunning; } /// Initialize virtual bool init() { return true; } /// Start the background task. init() must have been called virtual bool start(); /// Wait until the background thread exits (aka "join"), must be called by main thread if interrupting the task in order to quit void waitForExit(); protected: // The main routine to extend. Should set _IsRunning to false when complete. virtual void run() = 0; friend void BackgroundTaskRun(void *instance); volatile bool _IsRunning; }; // Mutexes for OpenSSL, adapted from http://www.koders.com/c/fid808B682F691FA3A88DCBFFC71D571425BDAD764B.aspx # define MUTEX_TYPE HANDLE # define MUTEX_SETUP(x) (x) = CreateMutex(NULL, FALSE, NULL) # define GET_MUTEX_SETUP CreateMutex(NULL, FALSE, NULL) # define MUTEX_CLEANUP(x) CloseHandle(x) # define MUTEX_LOCK(x) WaitForSingleObject((x), INFINITE) # define MUTEX_UNLOCK(x) ReleaseMutex(x) # define THREAD_ID GetCurrentThreadId() class CHttpClient; /** * URL-Encoded/Decoded string */ class UEString { private: const char *_Str; public: UEString(const char *src, bool encode, CHttpClient *client); ~UEString(); const char *c_str() const { return _Str; } // the URL-Encoded/Decoded string corresponding to the src passed to the constructor }; /** * HTTP client with SSL capabilities, using cURL. * See size limits in enum below * Author: Olivier Cado */ class CHttpClient { public: /// Constructor CHttpClient() : _MaxRedirection(10), _WantRedirection(true), _Initialized(false), _CurlStruct(NULL), _FirstParamStruct(NULL), _LastParamStruct(NULL), _RequestCode(0), _IsAborting(false), _DLTotal(0), _DLNow(0), _ULTotal(0), _ULNow(0), _CurrentPacm(0) {} ~CHttpClient(); /// Initialize bool init(); /// Is the object initialized? bool initialized() const { return _Initialized; } enum { MaxPasswordChars = 31, MaxUserChars = 32, MaxCookieNameChars = 63, MaxUserAgentChars = 64, MaxCookieValueChars = 191, MaxAuthChars = MaxUserChars + 1 + MaxPasswordChars, MaxCookieChars = MaxCookieNameChars + 1 + MaxCookieValueChars, MaxPageURLChars = 511, MaxURLParamChars = 512, MaxURLChars = MaxPageURLChars + 1 + MaxURLParamChars, PageBufferSize = 1024*1024*10 // up to 10 MB! }; /// Set the user agent string. Limit: MaxUserAgentChars. void setUserAgent(const char *userAgent); /// Set the proxy address (ip:port) or a blank string to force direct connection (otherwise the HTTP_PROXY environment var is used) void setProxy(const char *ip_port); /** Try setting the proxy address by WPAD DNS auto-detection / IE proxy hostname / IE WPAD url. * Each time you call this method, it will try the next method, unless you call resetCurrentPacMethod(). * Important: The address sometimes depends on the targetted url (cf. proxy-bypass urls), so you might not * want to share the same CHttpClient for servers having different proxy configs. * hSession can remain null when called externally. * This method can take a long time to complete, you might want to call it only if a direct request failed. * Requires Windows XP SP1 or newer. * Returns false if no proxy could be deduced, after all methods have been tried. */ bool nextTryAutoConfigureProxy(const char *url, void* hSession=NULL); /// Reset the current proxy auto-config method void resetCurrentPacMethod() { _CurrentPacm = 0; } enum ProxyAutoConfigMethods { PacmDirectConnection, PacmHostname, PacmWPADURL, PacmDNS, PacmFullAuto, PacmBeforeFailed=PacmFullAuto, PacmFailed, PacmManual }; /// Return the current proxy auto-config method int getCurrentPacMethod() { return _CurrentPacm; } /// Force the current pac method (use it to skip a method or to set to PacManual) void setCurrentPacMethod(int pacm) { _CurrentPacm = pacm; } /// Try setting the proxy address using a WPAD script at specified URL, or try using DNS auto-detection with L"http://wpad/wpad.dat", or try full auto-detection (NULL url). Windows: hSession must be a valid WinHttp handler; requires Windows XP SP1 or newer. bool setProxyFromWPAD(void* hSession, const char *url, const wchar_t *wpadwURL=NULL); /// Return the proxy address (ip:port): "" for direct, NULL for default (environment). Useful to know the proxy after auto-config. const char *getProxy() const; /// Specify user and password for authenticated requests on restricted websites. Set user to NULL or empty to reset. Limits: MaxUserChars, MaxPasswordChars. bool setCredentials(const char *user, const char *password); /// (Alternative) Specify user:password for authenticated requests on restricted websites. Set user to NULL or empty to reset. Limits: MaxUserChars, MaxPasswordChars. bool setCredentials(const char *userPassword); /// Reset the cookies (to be call before add a new one) void resetCookie(); /// Set Number of redirection void setMaxRedirection(int redirection) { _MaxRedirection = redirection; } /// Specify cookie (does not support multiple cookies so far). Set user to NULL or empty to reset. Limits: MaxCookieNameChars, MaxCookieValueChars. bool setCookie(const char *cookieName, const char *cookieValue); /** Call this with true to check for both server certificate validity and host matching (HTTPS server only). * If true, you need to specify the filename (e.g. cacert.pem) of a file holding certificates able to authentify the server certificate. * See also http://curl.haxx.se/docs/caextract.html for commonly knwon root certificates. */ bool setVerifyServer(bool verify, const char *certicatesFilename=""); /// Send GET or POST request. pageURL like "http://www.website.com/dir/page.php", paramStr like "param1=value1¶m2=my%20value2". Can be interrupted from another thread using abortRequest(). bool sendRequest(const char *pageURL, const char *paramStr, bool isPostRequest=false, bool wantHeader=false, bool verbose=false); // See MultipartParam enum MultipartParamSource { ParamCString, ParamLoadFile }; // Value of multipart parameter, either a string or a filename that will be loaded struct MultipartParam { MultipartParam() : Source(ParamCString) {} MultipartParam(const char *str) : Source(ParamCString), Str(str) {} MultipartParam(const char *str, MultipartParamSource src) : Source(src), Str(str) {} MultipartParamSource Source; const char * Str; }; /// Add an argument for the next sendMultipartRequest(). Strings are stored (you don't need to maintain the pointers). Arguments can be specified as binary buffers or files. bool addMultipartParam(const char *field, const MultipartParam& value); /// Send multipart/form-data POST (RFC1867). Uses and then resets any data set by addMultipartParam(). Can be interrupted from another thread using abortRequest(). bool sendMultipartRequest(const char *pageURL, bool wantHeader=false, bool verbose=false); /// After a sendRequest() call, use this to access the resulting page (or binary buffer) const HttpPage& getResponsePage(bool verbose=false) const; /// After a sendRequest() call, use this to access the resulting page (or binary buffer) with modification rights HttpPage& getAndEditResponsePage(bool verbose=false); // Most frequent cURL error codes, from curl.h enum RequestErrorCode { ErrorCouldNotResolve = 6 /*CURLE_COULDNT_RESOLVE_HOST*/, ErrorCouldNotConnect = 7 /*CURLE_COULDNT_CONNECT*/, ErrorInvalidFile = 26 /*CURLE_READ_ERROR*/ }; /// Return the response or error code for the latest sendRequest() call. If it returned true, return the HTTP code. If false, return the cURL error code (use getErrorStr() for an English explanation). long getRequestCode() const { return _RequestCode; } /// Return true if the last request failed to connect, and proxy auto config is enabled and has remaining methods to try bool mayRetryAfterProxyAutoConfig() const { return (_RequestCode == ErrorCouldNotConnect) && (_CurrentPacm < PacmFailed); } // assumes PacmManual > PacmFailed /// Return an header value, after sendRequest() was successful const std::string& getResponseHeader(const char *field) { return _Headers[std::string(field)]; } // will create an empty entry if not found /// Return the error string, filled if sendRequest() returned false const char * getErrorStr() const { return _CurlErrorString.c_str(); } void setWantRedirection(bool redirection) { _WantRedirection = redirection; } /// Get Memory usage #ifndef USER_MODE unsigned int getMemoryUsage() const; #endif /// Return true if the receive buffer is full #ifdef DYNALLOC_HTTPCLIENT bool isResponsePageTruncated() const { return false; } #else bool isResponsePageTruncated() const { return _ReceiveBuffer.size() == _ReceiveBuffer.SizeLimit; } #endif // Stop a running request (to call from another thread) void abortRequest() { _IsAborting = true; } void unabortRequest() { _IsAborting = false; } // reset abort flag, to start a new request bool isAborting() const { return _IsAborting; } volatile const bool& isAbortingRef() const { return _IsAborting; } // for thread monitoring // Query progress (to call from another thread) volatile const double& dlTotal() const { return _DLTotal; } volatile const double& dlNow() const { return _DLNow; } volatile const double& ulTotal() const { return _ULTotal; } volatile const double& ulNow() const { return _ULNow; } /// Counterpart of init() void release(); protected: /// Helpers void onDataReceived(char * buffer, size_t size); void onHeaderReceived(char * buffer, size_t size); int onProgress(double dltotal, double dlnow, double ultotal, double ulnow); friend size_t onDataReceivedCallback(void *buffer, size_t size, size_t nmemb, void *pHttpClient); friend size_t onHeaderReceivedCallback(void *buffer, size_t size, size_t nmemb, void *pHttpClient); friend int onProgressCallback(void *pHttpClient, double dltotal, double dlnow, double ultotal, double ulnow); private: friend class UEString; void* getCurlStruct() { return _CurlStruct; } void* _CurlStruct; // void* to avoid including curl.h in a header file void *_FirstParamStruct, *_LastParamStruct; // same HttpPage _ReceiveBuffer; // can contain text or binary data SimpleString _UserAgentStr; SimpleString _AuthStr ; // must be stored because cUrl only stores the char pointer SimpleString _CookieStr; typedef std::map CookiesList; CookiesList _CookiesList; long _RequestCode; volatile double _DLTotal, _DLNow, _ULTotal, _ULNow; int _CurrentPacm; bool _WantRedirection; int _MaxRedirection; typedef std::map HeaderMap; HeaderMap _Headers; enum { MY_CURL_ERROR_SIZE=256 }; // to avoid including curl.h in a header file SimpleString _CurlErrorString; volatile bool _IsAborting; bool _Initialized; static int _InstanceCount; static MUTEX_TYPE _MutexInstanceCount; }; #endif // HTTP_CLIENT_H