Http.cpp
1 //
3 // SFML - Simple and Fast Multimedia Library
4 // Copyright (C) 2007-2009 Laurent Gomila (laurent.gom@gmail.com)
5 //
6 // This software is provided 'as-is', without any express or implied warranty.
7 // In no event will the authors be held liable for any damages arising from the use of this software.
8 //
9 // Permission is granted to anyone to use this software for any purpose,
10 // including commercial applications, and to alter it and redistribute it freely,
11 // subject to the following restrictions:
12 //
13 // 1. The origin of this software must not be misrepresented;
14 // you must not claim that you wrote the original software.
15 // If you use this software in a product, an acknowledgment
16 // in the product documentation would be appreciated but is not required.
17 //
18 // 2. Altered source versions must be plainly marked as such,
19 // and must not be misrepresented as being the original software.
20 //
21 // 3. This notice may not be removed or altered from any source distribution.
22 //
24 
26 // Headers
28 #include <SFML/Network/Http.hpp>
29 #include <ctype.h>
30 #include <algorithm>
31 #include <iterator>
32 #include <sstream>
33 
34 
35 namespace
36 {
38  // Convenience function to convert a string to lower case
40  std::string ToLower(const std::string& Str)
41  {
42  std::string Ret = Str;
43  for (std::string::iterator i = Ret.begin(); i != Ret.end(); ++i)
44  *i = static_cast<char>(tolower(*i));
45 
46  return Ret;
47  }
48 }
49 
50 
51 namespace sf
52 {
56 Http::Request::Request(Method RequestMethod, const std::string& URI, const std::string& Body)
57 {
58  SetMethod(RequestMethod);
59  SetURI(URI);
60  SetHttpVersion(1, 0);
61  SetBody(Body);
62 }
63 
64 
68 void Http::Request::SetField(const std::string& Field, const std::string& Value)
69 {
70  myFields[ToLower(Field)] = Value;
71 }
72 
73 
79 {
80  myMethod = RequestMethod;
81 }
82 
83 
88 void Http::Request::SetURI(const std::string& URI)
89 {
90  myURI = URI;
91 
92  // Make sure it starts with a '/'
93  if (myURI.empty() || (myURI[0] != '/'))
94  myURI.insert(0, "/");
95 }
96 
97 
102 void Http::Request::SetHttpVersion(unsigned int Major, unsigned int Minor)
103 {
104  myMajorVersion = Major;
105  myMinorVersion = Minor;
106 }
107 
108 
114 void Http::Request::SetBody(const std::string& Body)
115 {
116  myBody = Body;
117 }
118 
119 
123 std::string Http::Request::ToString() const
124 {
125  std::ostringstream Out;
126 
127  // Convert the method to its string representation
128  std::string RequestMethod;
129  switch (myMethod)
130  {
131  default :
132  case Get : RequestMethod = "GET"; break;
133  case Post : RequestMethod = "POST"; break;
134  case Head : RequestMethod = "HEAD"; break;
135  }
136 
137  // Write the first line containing the request type
138  Out << RequestMethod << " " << myURI << " ";
139  Out << "HTTP/" << myMajorVersion << "." << myMinorVersion << "\r\n";
140 
141  // Write fields
142  for (FieldTable::const_iterator i = myFields.begin(); i != myFields.end(); ++i)
143  {
144  Out << i->first << ": " << i->second << "\r\n";
145  }
146 
147  // Use an extra \r\n to separate the header from the body
148  Out << "\r\n";
149 
150  // Add the body
151  Out << myBody;
152 
153  return Out.str();
154 }
155 
156 
160 bool Http::Request::HasField(const std::string& Field) const
161 {
162  return myFields.find(Field) != myFields.end();
163 }
164 
165 
170 myStatus (ConnectionFailed),
171 myMajorVersion(0),
172 myMinorVersion(0)
173 {
174 
175 }
176 
177 
181 const std::string& Http::Response::GetField(const std::string& Field) const
182 {
183  FieldTable::const_iterator It = myFields.find(ToLower(Field));
184  if (It != myFields.end())
185  {
186  return It->second;
187  }
188  else
189  {
190  static const std::string Empty = "";
191  return Empty;
192  }
193 }
194 
195 
200 {
201  return myStatus;
202 }
203 
204 
209 {
210  return myMajorVersion;
211 }
212 
213 
218 {
219  return myMinorVersion;
220 }
221 
222 
230 const std::string& Http::Response::GetBody() const
231 {
232  return myBody;
233 }
234 
235 
239 void Http::Response::FromString(const std::string& Data)
240 {
241  std::istringstream In(Data);
242 
243  // Extract the HTTP version from the first line
244  std::string Version;
245  if (In >> Version)
246  {
247  if ((Version.size() >= 8) && (Version[6] == '.') &&
248  (ToLower(Version.substr(0, 5)) == "http/") &&
249  isdigit(Version[5]) && isdigit(Version[7]))
250  {
251  myMajorVersion = Version[5] - '0';
252  myMinorVersion = Version[7] - '0';
253  }
254  else
255  {
256  // Invalid HTTP version
257  myStatus = InvalidResponse;
258  return;
259  }
260  }
261 
262  // Extract the status code from the first line
263  int StatusCode;
264  if (In >> StatusCode)
265  {
266  myStatus = static_cast<Status>(StatusCode);
267  }
268  else
269  {
270  // Invalid status code
271  myStatus = InvalidResponse;
272  return;
273  }
274 
275  // Ignore the end of the first line
276  In.ignore(10000, '\n');
277 
278  // Parse the other lines, which contain fields, one by one
279  std::string Line;
280  while (std::getline(In, Line) && (Line.size() > 2))
281  {
282  std::string::size_type Pos = Line.find(": ");
283  if (Pos != std::string::npos)
284  {
285  // Extract the field name and its value
286  std::string Field = Line.substr(0, Pos);
287  std::string Value = Line.substr(Pos + 2);
288 
289  // Remove any trailing \r
290  if (!Value.empty() && (*Value.rbegin() == '\r'))
291  Value.erase(Value.size() - 1);
292 
293  // Add the field
294  myFields[ToLower(Field)] = Value;
295  }
296  }
297 
298  // Finally extract the body
299  myBody.clear();
300  std::copy(std::istreambuf_iterator<char>(In), std::istreambuf_iterator<char>(), std::back_inserter(myBody));
301 }
302 
303 
308 myHost(),
309 myPort(0)
310 {
311 
312 }
313 
314 
318 Http::Http(const std::string& Host, unsigned short Port)
319 {
320  SetHost(Host, Port);
321 }
322 
323 
327 void Http::SetHost(const std::string& Host, unsigned short Port)
328 {
329  // Detect the protocol used
330  std::string Protocol = ToLower(Host.substr(0, 8));
331  if (Protocol.substr(0, 7) == "http://")
332  {
333  // HTTP protocol
334  myHostName = Host.substr(7);
335  myPort = (Port != 0 ? Port : 80);
336  }
337  else if (Protocol == "https://")
338  {
339  // HTTPS protocol
340  myHostName = Host.substr(8);
341  myPort = (Port != 0 ? Port : 443);
342  }
343  else
344  {
345  // Undefined protocol - use HTTP
346  myHostName = Host;
347  myPort = (Port != 0 ? Port : 80);
348  }
349 
350  // Remove any trailing '/' from the host name
351  if (!myHostName.empty() && (*myHostName.rbegin() == '/'))
352  myHostName.erase(myHostName.size() - 1);
353 
354  myHost = sf::IPAddress(myHostName);
355 }
356 
357 
367 {
368  // First make sure the request is valid -- add missing mandatory fields
369  Request ToSend(Req);
370  if (!ToSend.HasField("From"))
371  {
372  ToSend.SetField("From", "user@sfml-dev.org");
373  }
374  if (!ToSend.HasField("User-Agent"))
375  {
376  ToSend.SetField("User-Agent", "libsfml-network/1.x");
377  }
378  if (!ToSend.HasField("Host"))
379  {
380  ToSend.SetField("Host", myHostName);
381  }
382  if (!ToSend.HasField("Content-Length"))
383  {
384  std::ostringstream Out;
385  Out << ToSend.myBody.size();
386  ToSend.SetField("Content-Length", Out.str());
387  }
388  if ((ToSend.myMethod == Request::Post) && !ToSend.HasField("Content-Type"))
389  {
390  ToSend.SetField("Content-Type", "application/x-www-form-urlencoded");
391  }
392  if ((ToSend.myMajorVersion * 10 + ToSend.myMinorVersion >= 11) && !ToSend.HasField("Connection"))
393  {
394  ToSend.SetField("Connection", "close");
395  }
396 
397  // Prepare the response
398  Response Received;
399 
400  // Connect the socket to the host
401  if (myConnection.Connect(myPort, myHost, Timeout) == Socket::Done)
402  {
403  // Convert the request to string and send it through the connected socket
404  std::string RequestStr = ToSend.ToString();
405 
406  if (!RequestStr.empty())
407  {
408  // Send it through the socket
409  if (myConnection.Send(RequestStr.c_str(), RequestStr.size()) == sf::Socket::Done)
410  {
411  // Wait for the server's response
412  std::string ReceivedStr;
413  std::size_t Size = 0;
414  char Buffer[1024];
415  while (myConnection.Receive(Buffer, sizeof(Buffer), Size) == sf::Socket::Done)
416  {
417  ReceivedStr.append(Buffer, Buffer + Size);
418  }
419 
420  // Build the Response object from the received data
421  Received.FromString(ReceivedStr);
422  }
423  }
424 
425  // Close the connection
426  myConnection.Close();
427  }
428 
429  return Received;
430 }
431 
432 } // namespace sf
void SetHost(const std::string &Host, unsigned short Port=0)
Set the target host.
Definition: Http.cpp:327
void SetBody(const std::string &Body)
Set the body of the request.
Definition: Http.cpp:114
Request(Method RequestMethod=Get, const std::string &URI="/", const std::string &Body="")
Default constructor.
Definition: Http.cpp:56
This class wraps an HTTP response, which is basically :
Definition: Http.hpp:168
void SetField(const std::string &Field, const std::string &Value)
Set the value of a field; the field is added if it doesn&#39;t exist.
Definition: Http.cpp:68
void SetMethod(Method RequestMethod)
Set the request method.
Definition: Http.cpp:78
unsigned int GetMajorHttpVersion() const
Get the major HTTP version number of the response.
Definition: Http.cpp:208
Request in post mode, usually to send data to a page.
Definition: Http.hpp:64
Status GetStatus() const
Get the header&#39;s status code.
Definition: Http.cpp:199
Status
Enumerate all the valid status codes returned in a HTTP response.
Definition: Http.hpp:176
Method
Enumerate the available HTTP methods for a request.
Definition: Http.hpp:61
void SetURI(const std::string &URI)
Set the target URI of the request.
Definition: Http.cpp:88
Socket::Status Receive(char *Data, std::size_t MaxSize, std::size_t &SizeReceived)
Receive an array of bytes from the host (must be connected first).
Definition: SocketTCP.cpp:272
Socket::Status Send(const char *Data, std::size_t Size)
Send an array of bytes to the host (must be connected first)
Definition: SocketTCP.cpp:235
Socket::Status Connect(unsigned short Port, const IPAddress &HostAddress, float Timeout=0.f)
Connect to another computer on a specified port.
Definition: SocketTCP.cpp:70
Http()
Default constructor.
Definition: Http.cpp:307
IPAddress provides easy manipulation of IP v4 addresses.
Definition: IPAddress.hpp:42
const std::string & GetBody() const
Get the body of the response.
Definition: Http.cpp:230
const std::string & GetField(const std::string &Field) const
Get the value of a field.
Definition: Http.cpp:181
This class wraps an HTTP request, which is basically :
Definition: Http.hpp:54
bool Close()
Close the socket.
Definition: SocketTCP.cpp:406
Response()
Default constructor.
Definition: Http.cpp:169
Response SendRequest(const Request &Req, float Timeout=0.f)
Send a HTTP request and return the server&#39;s response.
Definition: Http.cpp:366
unsigned int GetMinorHttpVersion() const
Get the major HTTP version number of the response.
Definition: Http.cpp:217
void SetHttpVersion(unsigned int Major, unsigned int Minor)
Set the HTTP version of the request.
Definition: Http.cpp:102