diff --git a/Engine/source/app/net/httpObject.cpp b/Engine/source/app/net/httpObject.cpp deleted file mode 100644 index 986cb5471..000000000 --- a/Engine/source/app/net/httpObject.cpp +++ /dev/null @@ -1,552 +0,0 @@ -//----------------------------------------------------------------------------- -// Copyright (c) 2012 GarageGames, LLC -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to -// deal in the Software without restriction, including without limitation the -// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or -// sell copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS -// IN THE SOFTWARE. -//----------------------------------------------------------------------------- - -//----------------------------------------------------------------------------- -// Copyright (c) 2017 The Platinum Team -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to -// deal in the Software without restriction, including without limitation the -// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or -// sell copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -// DEALINGS IN THE SOFTWARE. -//----------------------------------------------------------------------------- - -#include "app/net/httpObject.h" - -#include "platform/platform.h" -#include "core/stream/fileStream.h" -#include "console/simBase.h" -#include "console/consoleInternal.h" -#include "console/engineAPI.h" -#include - -IMPLEMENT_CONOBJECT(HTTPObject); - -ConsoleDocClass(HTTPObject, - "@brief Allows communications between the game and a server using HTTP.\n\n" - - "HTTPObject is derrived from TCPObject and makes use of the same callbacks for dealing with " - "connections and received data. However, the way in which you use HTTPObject to connect " - "with a server is different than TCPObject. Rather than opening a connection, sending data, " - "waiting to receive data, and then closing the connection, you issue a get() or post() and " - "handle the response. The connection is automatically created and destroyed for you.\n\n" - - "@tsexample\n" - "// In this example we'll retrieve the weather in Las Vegas using\n" - "// Google's API. The response is in XML which could be processed\n" - "// and used by the game using SimXMLDocument, but we'll just output\n" - "// the results to the console in this example.\n\n" - - "// Define callbacks for our specific HTTPObject using our instance's\n" - "// name (WeatherFeed) as the namespace.\n\n" - - "// Handle an issue with resolving the server's name\n" - "function WeatherFeed::onDNSFailed(%this)\n" - "{\n" - " // Store this state\n" - " %this.lastState = \"DNSFailed\";\n\n" - - " // Handle DNS failure\n" - "}\n\n" - - "function WeatherFeed::onConnectFailed(%this)\n" - "{\n" - " // Store this state\n" - " %this.lastState = \"ConnectFailed\";\n\n" - " // Handle connection failure\n" - "}\n\n" - - "function WeatherFeed::onDNSResolved(%this)\n" - "{\n" - " // Store this state\n" - " %this.lastState = \"DNSResolved\";\n\n" - "}\n\n" - - "function WeatherFeed::onConnected(%this)\n" - "{\n" - " // Store this state\n" - " %this.lastState = \"Connected\";\n\n" - - " // Clear our buffer\n" - " %this.buffer = \"\";\n" - "}\n\n" - - "function WeatherFeed::onDisconnect(%this)\n" - "{\n" - " // Store this state\n" - " %this.lastState = \"Disconnected\";\n\n" - - " // Output the buffer to the console\n" - " echo(\"Google Weather Results:\");\n" - " echo(%this.buffer);\n" - "}\n\n" - - "// Handle a line from the server\n" - "function WeatherFeed::onLine(%this, %line)\n" - "{\n" - " // Store this line in out buffer\n" - " %this.buffer = %this.buffer @ %line;\n" - "}\n\n" - - "// Create the HTTPObject\n" - "%feed = new HTTPObject(WeatherFeed);\n\n" - - "// Define a dynamic field to store the last connection state\n" - "%feed.lastState = \"None\";\n\n" - - "// Send the GET command\n" - "%feed.get(\"www.google.com:80\", \"/ig/api\", \"weather=Las-Vegas,US\");\n" - "@endtsexample\n\n" - - "@see TCPObject\n" - - "@ingroup Networking\n" -); - -CURLM *HTTPObject::gCurlMulti = nullptr; -int HTTPObject::gCurlMultiTotal = 0; -std::unordered_map HTTPObject::gCurlMap; - -size_t HTTPObject::writeCallback(char *buffer, size_t size, size_t nitems, HTTPObject *object) { - return object->processData(buffer, size, nitems); -} - - -size_t HTTPObject::headerCallback(char *buffer, size_t size, size_t nitems, HTTPObject *object) { - return object->processHeader(buffer, size, nitems); -} - -//-------------------------------------- - -HTTPObject::HTTPObject() - : mCurl(nullptr), - mBuffer(nullptr), - mBufferSize(0), - mBufferUsed(0), - mDownload(false), - mHeaders(nullptr) -{ - CURL *request = curl_easy_init(); - curl_easy_setopt(request, CURLOPT_VERBOSE, false); - curl_easy_setopt(request, CURLOPT_FOLLOWLOCATION, true); - curl_easy_setopt(request, CURLOPT_TRANSFERTEXT, true); - curl_easy_setopt(request, CURLOPT_USERAGENT, "Torque 1.0"); - curl_easy_setopt(request, CURLOPT_ENCODING, "ISO 8859-1"); - - mCurl = request; - gCurlMap[request] = this; - - curl_easy_setopt(request, CURLOPT_WRITEDATA, this); - curl_easy_setopt(request, CURLOPT_WRITEFUNCTION, writeCallback); - curl_easy_setopt(request, CURLOPT_HEADERDATA, this); - curl_easy_setopt(request, CURLOPT_HEADERFUNCTION, headerCallback); -} - -HTTPObject::~HTTPObject() -{ -} - -//-------------------------------------- - -bool HTTPObject::ensureBuffer(U32 length) -{ - if (mBufferSize < length) { - //CURL_MAX_WRITE_SIZE is the maximum packet size we'll be given. So round - // off to that and we should not have to allocate too often. - length = ((length / CURL_MAX_WRITE_SIZE) + 1) * CURL_MAX_WRITE_SIZE; - void *alloced = dRealloc(mBuffer, length * sizeof(char)); - - //Out of memory - if (!alloced) { - return false; - } - - mBuffer = (U8 *)alloced; - mBufferSize = length; - } - return true; -} - -size_t HTTPObject::processData(char *buffer, size_t size, size_t nitems) -{ - size_t writeSize = size * nitems + 1; - - if (!ensureBuffer(mBufferUsed + writeSize)) { - //Error - return 0; - } - - memcpy(mBuffer + mBufferUsed, buffer, size * nitems); - mBufferUsed += size * nitems; - mBuffer[mBufferUsed] = 0; - - return size * nitems; -} - -size_t HTTPObject::processHeader(char *buffer, size_t size, size_t nitems) -{ - char *colon = strchr(buffer, ':'); - if (colon != NULL) { - std::string key(buffer, colon - buffer); - std::string value(colon + 2); - - if (value[value.length() - 1] == '\n') - value.erase(value.length() - 1, 1); - if (value[value.length() - 1] == '\r') - value.erase(value.length() - 1, 1); - - mRecieveHeaders[key] = value; - } - - return size * nitems; -} - -void HTTPObject::start() -{ - CURLMcode result = curl_multi_add_handle(gCurlMulti, mCurl); - if (result != CURLM_OK) { - Con::errorf("curl_easy_perform failed (%d): %s", result, curl_multi_strerror(result)); - return; - } - ++gCurlMultiTotal; -} - -void HTTPObject::processLines() -{ - if (mDownload) { - const std::string &dlPath = mDownloadPath; - int lastSlash = dlPath.find_last_of('/'); - - const char *path; - const char *file; - - if (lastSlash == std::string::npos) { - //No - return; - } else { - path = StringTable->insert(dlPath.c_str(), false); - file = StringTable->insert(dlPath.substr(lastSlash + 1).c_str(), false); - } - - //Don't download unless we get an OK - long responseCode; - curl_easy_getinfo(mCurl, CURLINFO_RESPONSE_CODE, &responseCode); - if (responseCode != 200) { - onDownloadFailed(path); - return; - } - - //Write to the output file - FileStream *stream = new FileStream(); - - if (!stream->open(path, Torque::FS::File::Write)) { - Con::errorf("Could not download %s: error opening stream."); - onDownloadFailed(path); - return; - } - - stream->write(mBufferUsed, mBuffer); - stream->close(); - - onDownload(path); - - delete stream; - } else { - - //Pull all the lines out of mBuffer - char *str = (char *)mBuffer; - char *nextLine = str; - while (str && nextLine) { - nextLine = strchr(str, '\n'); - - //Get how long the current line for allocating - U32 lineSize = 0; - if (nextLine == NULL) { - lineSize = strlen(str); - if (lineSize == 0) { - break; - } - } else { - lineSize = nextLine - str; - } - - //Copy into a return buffer for the script - char *line = Con::getReturnBuffer(lineSize + 1); - memcpy(line, str, lineSize); - line[lineSize] = 0; - - //Strip the \r from \r\n - if (lineSize > 0 && line[lineSize - 1] == '\r') { - line[lineSize - 1] = 0; - } - - onLine(line); - - if (nextLine) { - //Strip the \n - str = nextLine + 1; - } - } - } -} - -void HTTPObject::finish(CURLcode errorCode) -{ - bool status = (errorCode == CURLE_OK); - Con::printf("Request %d finished with %s", getId(), (status ? "success" : "failure")); - - //Get HTTP response code - long responseCode; - curl_easy_getinfo(mCurl, CURLINFO_RESPONSE_CODE, &responseCode); - Con::printf("HTTP Response code: %d", responseCode); - - if (status) { - //We're done - processLines(); - } else { - Con::errorf("Error info: Code %d: %s", errorCode, curl_easy_strerror(errorCode)); - } - - //Clean up - if (mBuffer) { - dFree(mBuffer); - } - - //Then delete the request - curl_multi_remove_handle(gCurlMulti, mCurl); - --gCurlMultiTotal; - curl_easy_cleanup(mCurl); - - //Send a disconnect - onDisconnect(); -} - -//-------------------------------------- - -void HTTPObject::init() -{ - gCurlMulti = curl_multi_init(); -} - -void HTTPObject::process() -{ - int runningHandles = 0; - CURLMcode code = curl_multi_perform(gCurlMulti, &runningHandles); - if (code != CURLM_OK) { - Con::errorf("curl_multi_perform failed (%d): %s", code, curl_multi_strerror(code)); - return; - } - if (runningHandles >= gCurlMultiTotal) { - return; - } - while (true) { - int queueSize = 0; - CURLMsg *msg = curl_multi_info_read(gCurlMulti, &queueSize); - if (!msg) { - break; - } - if (msg->msg != CURLMSG_DONE) { - continue; - } - auto it = gCurlMap.find(msg->easy_handle); - if (it == gCurlMap.end()) { - continue; - } - it->second->finish(msg->data.result); - gCurlMap.erase(it); - } - -} - -void HTTPObject::shutdown() -{ - curl_multi_cleanup(gCurlMulti); - gCurlMulti = nullptr; -} - -//-------------------------------------- - -void HTTPObject::setOption(const std::string &option, const std::string &value) -{ - if (option == "verbose") { /* opt = new curlpp::options::Verbose(StringMath::scan(value)); */ } - else if (option == "user-agent") { curl_easy_setopt(mCurl, CURLOPT_USERAGENT, value.c_str()); } - else if (option == "cookie") { curl_easy_setopt(mCurl, CURLOPT_COOKIE, value.c_str()); } - else if (option == "verify-peer") { curl_easy_setopt(mCurl, CURLOPT_SSL_VERIFYPEER, value == "true"); } - else { - Con::errorf("HTTPObject::setOption unknown option %s", option.c_str()); - } -} - -void HTTPObject::setDownloadPath(const std::string &path) -{ - char expanded[0x100]; - Con::expandScriptFilename(expanded, 0x100, path.c_str()); - mDownloadPath = std::string(expanded); - mDownload = true; -} - -void HTTPObject::addHeader(const std::string &name, const std::string &value) -{ - std::string header = name + ": " + value; - - //Formatting: Replace spaces with hyphens - size_t nameLen = name.size(); - for (U32 i = 0; i < nameLen; i ++) { - if (header[i] == ' ') - header[i] = '-'; - } - - mHeaders = curl_slist_append(mHeaders, header.c_str()); -} - -void HTTPObject::get(const std::string &address, const std::string &uri, const std::string &query) -{ - mUrl = address + uri + (query.empty() ? std::string("") : std::string("?") + query); - curl_easy_setopt(mCurl, CURLOPT_URL, mUrl.c_str()); - - start(); -} - -void HTTPObject::post(const std::string &address, const std::string &uri, const std::string &query, const std::string &data) -{ - mUrl = address + uri + (query.empty() ? std::string("") : std::string("?") + query); - curl_easy_setopt(mCurl, CURLOPT_URL, mUrl.c_str()); - mValues = data; - - curl_easy_setopt(mCurl, CURLOPT_POST, true); - curl_easy_setopt(mCurl, CURLOPT_POSTFIELDS, mValues.c_str()); - - start(); -} - -//-------------------------------------- - -void HTTPObject::onConnected() -{ - Con::executef(this, "onConnected"); -} - -void HTTPObject::onConnectFailed() -{ - Con::executef(this, "onConnectFailed"); -} - -void HTTPObject::onLine(const std::string& line) -{ - Con::executef(this, "onLine", line.c_str()); -} - -void HTTPObject::onDownload(const std::string& path) -{ - Con::executef(this, "onDownload", path.c_str()); -} - -void HTTPObject::onDownloadFailed(const std::string& path) -{ - Con::executef(this, "onDownloadFailed", path.c_str()); -} - -void HTTPObject::onDisconnect() -{ - Con::executef(this, "onDisconncted"); -} - -//-------------------------------------- -DefineEngineMethod(HTTPObject, get, void, (const char* Address, const char* requirstURI, const char* query), (""), - "@brief Send a GET command to a server to send or retrieve data.\n\n" - - "@param Address HTTP web address to send this get call to. Be sure to include the port at the end (IE: \"www.garagegames.com:80\").\n" - "@param requirstURI Specific location on the server to access (IE: \"index.php\".)\n" - "@param query Optional. Actual data to transmit to the server. Can be anything required providing it sticks with limitations of the HTTP protocol. " - "If you were building the URL manually, this is the text that follows the question mark. For example: http://www.google.com/ig/api?weather=Las-Vegas,US\n" - - "@tsexample\n" - "// Create an HTTP object for communications\n" - "%httpObj = new HTTPObject();\n\n" - "// Specify a URL to transmit to\n" - "%url = \"www.garagegames.com:80\";\n\n" - "// Specify a URI to communicate with\n" - "%URI = \"/index.php\";\n\n" - "// Specify a query to send.\n" - "%query = \"\";\n\n" - "// Send the GET command to the server\n" - "%httpObj.get(%url,%URI,%query);\n" - "@endtsexample\n\n") -{ - if(!query || !query[ 0 ]) - object->get(Address, requirstURI, ""); - else - object->get(Address, requirstURI, query); -} - -DefineEngineMethod(HTTPObject, post, void, (const char* Address, const char* requirstURI, const char* query, const char* post),, - "@brief Send POST command to a server to send or retrieve data.\n\n" - - "@param Address HTTP web address to send this get call to. Be sure to include the port at the end (IE: \"www.garagegames.com:80\").\n" - "@param requirstURI Specific location on the server to access (IE: \"index.php\".)\n" - "@param query Actual data to transmit to the server. Can be anything required providing it sticks with limitations of the HTTP protocol. \n" - "@param post Submission data to be processed.\n" - - "@tsexample\n" - "// Create an HTTP object for communications\n" - "%httpObj = new HTTPObject();\n\n" - "// Specify a URL to transmit to\n" - "%url = \"www.garagegames.com:80\";\n\n" - "// Specify a URI to communicate with\n" - "%URI = \"/index.php\";\n\n" - "// Specify a query to send.\n" - "%query = \"\";\n\n" - "// Specify the submission data.\n" - "%post = \"\";\n\n" - "// Send the POST command to the server\n" - "%httpObj.POST(%url,%URI,%query,%post);\n" - "@endtsexample\n\n") -{ - object->post(Address, requirstURI, query, post); -} - -DefineEngineMethod(HTTPObject, setOption, void, (const char* option, const char* value),, "HTTPObject.setOption(option, value)") -{ - object->setOption(option, value); -} - -DefineEngineMethod(HTTPObject, setDownloadPath, void, (const char* path),, "HTTPObject.setDownloadPath(path)") -{ - object->setDownloadPath(path); -} - -DefineEngineMethod(HTTPObject, addHeader, void, (const char* name, const char* value),, "HTTPObject.addHeader(name, value)") -{ - object->addHeader(name, value); -} diff --git a/Engine/source/app/net/httpObject.h b/Engine/source/app/net/httpObject.h deleted file mode 100644 index 2061de476..000000000 --- a/Engine/source/app/net/httpObject.h +++ /dev/null @@ -1,119 +0,0 @@ -//----------------------------------------------------------------------------- -// Copyright (c) 2012 GarageGames, LLC -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to -// deal in the Software without restriction, including without limitation the -// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or -// sell copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS -// IN THE SOFTWARE. -//----------------------------------------------------------------------------- - -//----------------------------------------------------------------------------- -// Copyright (c) 2017 The Platinum Team -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to -// deal in the Software without restriction, including without limitation the -// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or -// sell copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -// DEALINGS IN THE SOFTWARE. -//----------------------------------------------------------------------------- - -#ifndef _HTTPOBJECT_H_ -#define _HTTPOBJECT_H_ - -#ifndef _TVECTOR_H_ -#include "core/util/tVector.h" -#endif -#ifndef _TCPOBJECT_H_ -#include "app/net/tcpObject.h" -#endif - -#include -#include -#include - -class HTTPObject : public SimObject -{ -private: - typedef SimObject Parent; -protected: - CURL *mCurl; - U8 *mBuffer; - U32 mBufferSize; - U32 mBufferUsed; - - std::string mUrl; - std::string mValues; - - bool mDownload; - std::string mDownloadPath; - - curl_slist *mHeaders; - std::unordered_map mRecieveHeaders; - - static CURLM *gCurlMulti; - static int gCurlMultiTotal; - static std::unordered_map gCurlMap; - - static size_t writeCallback(char *buffer, size_t size, size_t nitems, HTTPObject *object); - static size_t headerCallback(char *buffer, size_t size, size_t nitems, HTTPObject *object); - - bool ensureBuffer(U32 length); - size_t processData(char *buffer, size_t size, size_t nitems); - size_t processHeader(char *buffer, size_t size, size_t nitems); - - void start(); - void processLines(); - void finish(CURLcode errorCode); - - void onConnected(); - void onConnectFailed(); - void onLine(const std::string& line); - void onDownload(const std::string& path); - void onDownloadFailed(const std::string& path); - void onDisconnect(); - -public: - HTTPObject(); - ~HTTPObject() override; - - void setOption(const std::string &option, const std::string &value); - void setDownloadPath(const std::string &path); - void addHeader(const std::string &name, const std::string &value); - - void get(const std::string &address, const std::string &uri, const std::string &query); - void post(const std::string &address, const std::string &uri, const std::string &query, const std::string &data); - - static void init(); - static void process(); - static void shutdown(); - - DECLARE_CONOBJECT(HTTPObject); -}; - - -#endif // _H_HTTPOBJECT_