//////////////////////////////////////////////////////////// // // SFML - Simple and Fast Multimedia Library // Copyright (C) 2007-2009 Laurent Gomila (laurent.gom@gmail.com) // // This software is provided 'as-is', without any express or implied warranty. // In no event will the authors be held liable for any damages arising from the use of this software. // // Permission is granted to anyone to use this software for any purpose, // including commercial applications, and to alter it and redistribute it freely, // subject to the following restrictions: // // 1. The origin of this software must not be misrepresented; // you must not claim that you wrote the original software. // If you use this software in a product, an acknowledgment // in the product documentation would be appreciated but is not required. // // 2. Altered source versions must be plainly marked as such, // and must not be misrepresented as being the original software. // // 3. This notice may not be removed or altered from any source distribution. // //////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////// // Headers //////////////////////////////////////////////////////////// #include #include #include #include #include //////////////////////////////////////////////////////////// // Private data //////////////////////////////////////////////////////////// namespace { // Convert a string to lower case std::string ToLower(std::string str) { for (std::string::iterator i = str.begin(); i != str.end(); ++i) *i = static_cast(std::tolower(*i)); return str; } } namespace sf { //////////////////////////////////////////////////////////// Http::Request::Request(const std::string& uri, Method method, const std::string& body) { SetMethod(method); SetUri(uri); SetHttpVersion(1, 0); SetBody(body); } //////////////////////////////////////////////////////////// void Http::Request::SetField(const std::string& field, const std::string& value) { myFields[ToLower(field)] = value; } //////////////////////////////////////////////////////////// void Http::Request::SetMethod(Http::Request::Method method) { myMethod = method; } //////////////////////////////////////////////////////////// void Http::Request::SetUri(const std::string& uri) { myURI = uri; // Make sure it starts with a '/' if (myURI.empty() || (myURI[0] != '/')) myURI.insert(0, "/"); } //////////////////////////////////////////////////////////// void Http::Request::SetHttpVersion(unsigned int major, unsigned int minor) { myMajorVersion = major; myMinorVersion = minor; } //////////////////////////////////////////////////////////// void Http::Request::SetBody(const std::string& body) { myBody = body; } //////////////////////////////////////////////////////////// std::string Http::Request::Prepare() const { std::ostringstream out; // Convert the method to its string representation std::string method; switch (myMethod) { default : case Get : method = "GET"; break; case Post : method = "POST"; break; case Head : method = "HEAD"; break; } // Write the first line containing the request type out << method << " " << myURI << " "; out << "HTTP/" << myMajorVersion << "." << myMinorVersion << "\r\n"; // Write fields for (FieldTable::const_iterator i = myFields.begin(); i != myFields.end(); ++i) { out << i->first << ": " << i->second << "\r\n"; } // Use an extra \r\n to separate the header from the body out << "\r\n"; // Add the body out << myBody; return out.str(); } //////////////////////////////////////////////////////////// bool Http::Request::HasField(const std::string& field) const { return myFields.find(ToLower(field)) != myFields.end(); } //////////////////////////////////////////////////////////// Http::Response::Response() : myStatus (ConnectionFailed), myMajorVersion(0), myMinorVersion(0) { } //////////////////////////////////////////////////////////// const std::string& Http::Response::GetField(const std::string& field) const { FieldTable::const_iterator it = myFields.find(ToLower(field)); if (it != myFields.end()) { return it->second; } else { static const std::string empty = ""; return empty; } } //////////////////////////////////////////////////////////// Http::Response::Status Http::Response::GetStatus() const { return myStatus; } //////////////////////////////////////////////////////////// unsigned int Http::Response::GetMajorHttpVersion() const { return myMajorVersion; } //////////////////////////////////////////////////////////// unsigned int Http::Response::GetMinorHttpVersion() const { return myMinorVersion; } //////////////////////////////////////////////////////////// const std::string& Http::Response::GetBody() const { return myBody; } //////////////////////////////////////////////////////////// void Http::Response::Parse(const std::string& data) { std::istringstream in(data); // Extract the HTTP version from the first line std::string version; if (in >> version) { if ((version.size() >= 8) && (version[6] == '.') && (ToLower(version.substr(0, 5)) == "http/") && isdigit(version[5]) && isdigit(version[7])) { myMajorVersion = version[5] - '0'; myMinorVersion = version[7] - '0'; } else { // Invalid HTTP version myStatus = InvalidResponse; return; } } // Extract the status code from the first line int status; if (in >> status) { myStatus = static_cast(status); } else { // Invalid status code myStatus = InvalidResponse; return; } // Ignore the end of the first line in.ignore(10000, '\n'); // Parse the other lines, which contain fields, one by one std::string line; while (std::getline(in, line) && (line.size() > 2)) { std::string::size_type pos = line.find(": "); if (pos != std::string::npos) { // Extract the field name and its value std::string field = line.substr(0, pos); std::string value = line.substr(pos + 2); // Remove any trailing \r if (!value.empty() && (*value.rbegin() == '\r')) value.erase(value.size() - 1); // Add the field myFields[ToLower(field)] = value; } } // Finally extract the body myBody.clear(); std::copy(std::istreambuf_iterator(in), std::istreambuf_iterator(), std::back_inserter(myBody)); } //////////////////////////////////////////////////////////// Http::Http() : myHost(), myPort(0) { } //////////////////////////////////////////////////////////// Http::Http(const std::string& host, unsigned short port) { SetHost(host, port); } //////////////////////////////////////////////////////////// void Http::SetHost(const std::string& host, unsigned short port) { // Detect the protocol used std::string protocol = ToLower(host.substr(0, 8)); if (protocol.substr(0, 7) == "http://") { // HTTP protocol myHostName = host.substr(7); myPort = (port != 0 ? port : 80); } else if (protocol == "https://") { // HTTPS protocol myHostName = host.substr(8); myPort = (port != 0 ? port : 443); } else { // Undefined protocol - use HTTP myHostName = host; myPort = (port != 0 ? port : 80); } // Remove any trailing '/' from the host name if (!myHostName.empty() && (*myHostName.rbegin() == '/')) myHostName.erase(myHostName.size() - 1); myHost = IpAddress(myHostName); } //////////////////////////////////////////////////////////// Http::Response Http::SendRequest(const Http::Request& request, float timeout) { // First make sure that the request is valid -- add missing mandatory fields Request toSend(request); if (!toSend.HasField("From")) { toSend.SetField("From", "user@sfml-dev.org"); } if (!toSend.HasField("User-Agent")) { toSend.SetField("User-Agent", "libsfml-network/2.x"); } if (!toSend.HasField("Host")) { toSend.SetField("Host", myHostName); } if (!toSend.HasField("Content-Length")) { std::ostringstream out; out << toSend.myBody.size(); toSend.SetField("Content-Length", out.str()); } if ((toSend.myMethod == Request::Post) && !toSend.HasField("Content-Type")) { toSend.SetField("Content-Type", "application/x-www-form-urlencoded"); } if ((toSend.myMajorVersion * 10 + toSend.myMinorVersion >= 11) && !toSend.HasField("Connection")) { toSend.SetField("Connection", "close"); } // Prepare the response Response received; // Connect the socket to the host if (myConnection.Connect(myHost, myPort, timeout) == Socket::Done) { // Convert the request to string and send it through the connected socket std::string requestStr = toSend.Prepare(); if (!requestStr.empty()) { // Send it through the socket if (myConnection.Send(requestStr.c_str(), requestStr.size()) == Socket::Done) { // Wait for the server's response std::string receivedStr; std::size_t size = 0; char buffer[1024]; while (myConnection.Receive(buffer, sizeof(buffer), size) == Socket::Done) { receivedStr.append(buffer, buffer + size); } // Build the Response object from the received data received.Parse(receivedStr); } } // Close the connection myConnection.Disconnect(); } return received; } } // namespace sf