EvEmu  0.8.4
11 September 2021
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Modules Pages
APIServerConnection.cpp
Go to the documentation of this file.
1 /*
2  ------------------------------------------------------------------------------------
3  LICENSE:
4  ------------------------------------------------------------------------------------
5  This file is part of EVEmu: EVE Online Server Emulator
6  Copyright 2006 - 2021 The EVEmu Team
7  For the latest information visit https://evemu.dev
8  ------------------------------------------------------------------------------------
9  This program is free software; you can redistribute it and/or modify it under
10  the terms of the GNU Lesser General Public License as published by the Free Software
11  Foundation; either version 2 of the License, or (at your option) any later
12  version.
13 
14  This program is distributed in the hope that it will be useful, but WITHOUT
15  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
16  FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
17 
18  You should have received a copy of the GNU Lesser General Public License along with
19  this program; if not, write to the Free Software Foundation, Inc., 59 Temple
20  Place - Suite 330, Boston, MA 02111-1307, USA, or go to
21  http://www.gnu.org/copyleft/lesser.txt.
22  ------------------------------------------------------------------------------------
23  Author: Aknor Jaden, adapted from ImageServer.h authored by caytchen
24 */
25 
26 #include "eve-server.h"
27 
28 #include "apiserver/APIServer.h"
30 
31 boost::asio::const_buffers_1 APIServerConnection::_responseOK = boost::asio::buffer("HTTP/1.0 200 OK\r\nContent-Type: text/xml\r\n\r\n", 43);
32  // The last parameter must be exactly the # of chars in the string, otherwise
33  // a browser will not recognize the xml structure and think the document is empty
34  // if the size is larger than this string.
35 //boost::asio::const_buffers_1 APIServerConnection::_responseNotFound = boost::asio::buffer("HTTP/1.0 404 - File or directory not found.\r\n\r\n", 26);
36 boost::asio::const_buffers_1 APIServerConnection::_responseRedirectBegin = boost::asio::buffer("HTTP/1.0 301 Moved Permanently\r\nLocation: ", 42);
37 boost::asio::const_buffers_1 APIServerConnection::_responseRedirectEnd = boost::asio::buffer("\r\n\r\n", 4);
38 boost::asio::const_buffers_1 APIServerConnection::_responseNoContent = boost::asio::buffer("HTTP/1.0 204 No Content", 23);
39 boost::asio::const_buffers_1 APIServerConnection::_responseNotFound = boost::asio::buffer(
40 "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">"
41 "<html xmlns=\"http://www.w3.org/1999/xhtml\">"
42 "<head>"
43 "<meta http-equiv=\"Content-Type\" content=\"text/html; charset=iso-8859-1\"/>"
44 "<title>404 - File or directory not found.</title>"
45 "<style type=\"text/css\">"
46 "<!--"
47 "body{margin:0;font-size:.7em;font-family:Verdana, Arial, Helvetica, sans-serif;background:#EEEEEE;}"
48 "fieldset{padding:0 15px 10px 15px;} "
49 "h1{font-size:2.4em;margin:0;color:#FFF;}"
50 "h2{font-size:1.7em;margin:0;color:#CC0000;} "
51 "h3{font-size:1.2em;margin:10px 0 0 0;color:#000000;} "
52 "#header{width:96%;margin:0 0 0 0;padding:6px 2% 6px 2%;font-family:\"trebuchet MS\", Verdana, sans-serif;color:#FFF;"
53 "background-color:#555555;}"
54 "#content{margin:0 0 0 2%;position:relative;}"
55 ".content-container{background:#FFF;width:96%;margin-top:8px;padding:10px;position:relative;}"
56 "-->"
57 "</style>"
58 "</head>"
59 "<body>"
60 "<div id=\"header\"><h1>Server Error</h1></div>"
61 "<div id=\"content\">"
62 " <div class=\"content-container\"><fieldset>"
63 " <h2>404 - File or directory not found.</h2>"
64 " <h3>The resource you are looking for might have been removed, had its name changed, or is temporarily unavailable.</h3>"
65 " </fieldset></div>"
66 "</div>"
67 "</body>"
68 "</html>"
69 , 1187);
70 
71 APIServerConnection::APIServerConnection(boost::asio::io_context& io)
72  : _socket(io)
73 {
74 }
75 
76 boost::asio::ip::tcp::socket& APIServerConnection::socket()
77 {
78  return _socket;
79 }
80 
82 {
83  // receive all HTTP headers from the client
84  boost::asio::async_read_until(_socket, _buffer, "\r\n\r\n", std::bind(&APIServerConnection::ProcessHeaders, shared_from_this()));
85 }
86 
88 {
89  std::istream stream(&_buffer);
90  std::string query;
91  std::string request;
92  std::string get_chk_str;
93  std::string post_chk_str;
94  int pos;
95  int parameterCount;
96  std::string param;
97  std::string value;
98 
99  // Clear API Command Call container:
100  m_apiCommandCall.clear();
101 
102  // Get the first header line, every request line ends with "\r\n"
103  // GET /service/ServiceHandler.xml.aspx?param1=value&param2=value&param3=value HTTP/1.0\r\n
104  // POST /service/ServiceHandler.xml.aspx HTTP/1.0\r\n
105  std::getline(stream, request, '\r');
106  query = request;
107 
108  get_chk_str = request.substr(0,3);
109  post_chk_str = request.substr(0,4);
110 
111  if (get_chk_str.compare("GET") == 0)
112  {
113  sLog.Debug( "APIServerConnection::ProcessHeaders()", "RECEIVED new HTTP GET request..." );
114 
115  // Format of an HTTP GET query:
116  // 0 5 10 20
117  // GET /service/ServiceHandler.xml.aspx?param1=value&param2=value&param3=value HTTP/1.0\r\n
118  _http_cmd_str = get_chk_str;
119 
120  request = request.substr(4); // Strip off the "GET " prefix
121 
122  // Find first space at end of header, if there is one, and strip off the rest of the line, ie the " HTTP/1.0\r\n" string
123  int del = request.find_first_of(' ');
124  if (del == std::string::npos)
125  {
126  NotFound();
127  return;
128  }
129  request = request.substr(0,del);
130 
131  if (!starts_with(request, "/"))
132  {
133  NotFound();
134  return;
135  }
136  request = request.substr(1);
137 
138  pos = request.find_first_of('/');
139  _service = request.substr(0,pos);
140  m_apiCommandCall.insert( std::pair<std::string, std::string>( "service", _service ) );
141  request = request.substr(pos+1);
142  pos = request.find_first_of('?');
143  _service_handler = request.substr(0,pos);
144  m_apiCommandCall.insert( std::pair<std::string, std::string>( "servicehandler", _service_handler ) );
145  request = request.substr(pos+1);
146 
148  // Parse the query portion of the GET to a series of string pairs ("param", "value") from the URI
149  while( (pos = request.find_first_of('=')) >= 0 )
150  {
151  param = request.substr(0,pos);
152  std::transform(param.begin(), param.end(), param.begin(), tolower);
153  request = request.substr(pos+1);
154  pos = request.find_first_of('&');
155  if( pos < 0 )
156  value = request;
157  else
158  {
159  value = request.substr(0,pos);
160  request = request.substr(pos);
161  if( request.substr(0,5) == "&amp;" ) // Strip out "&amp;" encoded for "&"
162  request = request.substr(5);
163  else
164  request = request.substr(1);
165  }
166  m_apiCommandCall.insert( std::pair<std::string, std::string>( param, value ) );
167  }
168 
170  if (!_xmlData)
171  {
172  sLog.Error("APIServerConnection::ProcessHeaders()", "Unknown or malformed EVEmu API HTTP CMD Received:\r\n%s\r\n", query.c_str());
173  NotFound();
174  return;
175  }
176 
177  // Print out to the Log with basic info on the API call and all parameters and their values parsed out
178  sLog.Debug("APIServerConnection::ProcessHeaders()", "HTTP %s CMD Received: Service: %s, Handler: %s", _http_cmd_str.c_str(), _service.c_str(), _service_handler.c_str());
179  APICommandCall::const_iterator cur, end;
180  cur = m_apiCommandCall.begin();
181  end = m_apiCommandCall.end();
182  for (int i=1; cur != end; cur++, i++)
183  sLog.Debug(" ", "%d: param = %s, value = %s", i, cur->first.c_str(), cur->second.c_str() );
184 
185  // first we have to send the responseOK, then our actual result
186  boost::asio::async_write(_socket, _responseOK, boost::asio::transfer_all(), std::bind(&APIServerConnection::SendXML, shared_from_this()));
188  }
189  else if (post_chk_str.compare("POST") == 0)
190  {
191  sLog.Debug( "APIServerConnection::ProcessHeaders()", "RECEIVED new HTTP POST request..." );
192 
193  // Format of an HTTP GET query:
194  //
195  // POST /service/ServiceHandler.xml.aspx HTTP/1.0\r\n
196  // Content-Type: application/x-www-form-urlencoded\r\n
197  // Host: api.eve-online.com\r\n
198  // Content-Length: 86\r\n
199  // \r\n
200  // param1=value&param2=value&param3=value\r\n
201  // \r\n
202 
203  _http_cmd_str = post_chk_str;
204 
205  request = request.substr(5); // Strip off the "POST " prefix
206 
207  // Find first space at end of header, if there is one, and strip off the rest of the line, ie the " HTTP/1.0\r\n" string
208  int del = request.find_first_of(' ');
209  if (del == std::string::npos)
210  {
211  NotFound();
212  return;
213  }
214  request = request.substr(0,del);
215 
216  if (!starts_with(request, "/"))
217  {
218  NotFound();
219  return;
220  }
221  request = request.substr(1);
222 
223  pos = request.find_first_of('/');
224  _service = request.substr(0,pos);
225  m_apiCommandCall.insert( std::pair<std::string, std::string>( "service", _service ) );
226  request = request.substr(pos+1);
227  _service_handler = request;
228  m_apiCommandCall.insert( std::pair<std::string, std::string>( "servicehandler", _service_handler ) );
229 
230  // Read the integer after 'Content-Length:' and use that as the # of bytes in the next step
231  while( (request.substr( 0,15 ).compare( "Content-Length:" )) != 0 )
232  {
233  std::getline(stream, request, '\r');
234  request = request.substr(1);
235  query += request;
236  }
237  pos = request.find_first_of(' ');
238  request = request.substr( pos+1 );
239  uint32 postDataBytes = atoi( request.c_str() );
240  std::getline(stream, request, '\n');
241 
242  sLog.Debug( "APIServerConnection::ProcessHeaders()", " POST Content-Length = %u bytes", postDataBytes );
243 
244  // Keep reading lines until we get past the next "\r\n" line (blank line):
245  while( request.compare( "\r" ) != 0 )
246  {
247  std::getline(stream, request, '\n');
248  }
249 
250  std::getline(stream, request, '\n');
251  pos = request.find_first_of('\r');
252  request = request.substr( 0,pos );
253 
254  if( request.compare( "" ) != 0 )
255  {
256  // Decode the arguments of the POST data block here since asio did NOT stop reading past the first "\r\n\r\n"
258  // Parse the query portion of the GET to a series of string pairs ("param", "value") from the URI
259  sLog.Debug( "APIServerConnection::ProcessHeaders()", "POST data found in ProcessHeaders() ! Parsing..." );
260  parameterCount = 0;
261  while( (pos = request.find_first_of('=')) >= 0 )
262  {
263  parameterCount++;
264  param = request.substr(0,pos);
265  std::transform(param.begin(), param.end(), param.begin(), tolower);
266  request = request.substr(pos+1);
267  pos = request.find_first_of('&');
268  if( pos < 0 )
269  value = request;
270  else
271  {
272  value = request.substr(0,pos);
273  request = request.substr(pos);
274  if( request.substr(0,5) == "&amp;" ) // Strip out "&amp;" encoded for "&"
275  request = request.substr(5);
276  else
277  request = request.substr(1);
278  }
279  m_apiCommandCall.insert( std::pair<std::string, std::string>( param, value ) );
280  }
281 
282  // Did we somehow not detect a lack of POST data? If so, and NO parameters were recovered, queue up the trigger for PostProcessHeaders():
283  if( parameterCount == 0 )
284  {
285  // Call boost::asio::async_read() and feed it the # of bytes from step 1) to get the POST data
286  // The 'CompleteCondition' for THIS boost::asio::async_read, a parameter that specifies when to stop reading,
287  // is transfer_exactly(contentLength), where contentLength is the # of bytes we just recovered from the "Content-Length" header
288  boost::asio::async_read(_socket, _postBuffer, boost::asio::transfer_exactly(postDataBytes), std::bind(&APIServerConnection::ProcessPostData, shared_from_this()));
289  }
290 
292  if (!_xmlData)
293  {
294  sLog.Error("APIServerConnection::ProcessHeaders()", "Unknown or malformed EVEmu API HTTP CMD Received:\r\n%s\r\n", query.c_str());
295  NotFound();
296  return;
297  }
298 
299  // Print out to the Log with basic info on the API call and all parameters and their values parsed out
300  sLog.Debug("APIServerConnection::ProcessHeaders()", "HTTP %s CMD Received: Service: %s, Handler: %s", _http_cmd_str.c_str(), _service.c_str(), _service_handler.c_str());
301  APICommandCall::const_iterator cur, end;
302  cur = m_apiCommandCall.begin();
303  end = m_apiCommandCall.end();
304  for (int i=1; cur != end; cur++, i++)
305  sLog.Debug(" ", "%d: param = %s, value = %s", i, cur->first.c_str(), cur->second.c_str() );
306 
307  // first we have to send the responseOK, then our actual result
308  boost::asio::async_write(_socket, _responseOK, boost::asio::transfer_all(), std::bind(&APIServerConnection::SendXML, shared_from_this()));
310  }
311  else
312  {
313  // Call boost::asio::async_read() and feed it the # of bytes from step 1) to get the POST data
314  // The 'CompleteCondition' for THIS boost::asio::async_read, a parameter that specifies when to stop reading,
315  // is transfer_exactly(contentLength), where contentLength is the # of bytes we just recovered from the "Content-Length" header
316  boost::asio::async_read(_socket, _postBuffer, boost::asio::transfer_exactly(postDataBytes), std::bind(&APIServerConnection::ProcessPostData, shared_from_this()));
317  }
318  }
319  else
320  {
321  NotFound();
322  return;
323  }
324 }
325 
327 {
328  std::istream stream(&_postBuffer);
329  std::string query;
330  std::string request;
331  int pos;
332  std::string param;
333  std::string value;
334 
335  std::getline(stream, request, '\r');
336 
337  // Check for empty POST data block, and if empty, return without sending anything back:
338  if( request.compare( "" ) == 0 )
339  {
340  sLog.Error("APIServerConnection::ProcessPostData()", "POST data block is COMPLETELY EMPTY!!" );
341  boost::asio::async_write(_socket, _responseNoContent, boost::asio::transfer_all(), std::bind(&APIServerConnection::Close, shared_from_this()));
342  //NotFound();
343  return;
344  }
345 
346  _http_cmd_str += request;
347 
348  // Parse the data portion of the POST to a series of string pairs ("param", "value") from the URI
349  while( (pos = request.find_first_of('=')) >= 0 )
350  {
351  param = request.substr(0,pos);
352  std::transform(param.begin(), param.end(), param.begin(), tolower);
353  request = request.substr(pos+1);
354  pos = request.find_first_of('&');
355  if( pos < 0 )
356  value = request;
357  else
358  {
359  value = request.substr(0,pos);
360  request = request.substr(pos);
361  if( request.substr(0,5) == "&amp;" ) // Strip out "&amp;" encoded for "&"
362  request = request.substr(5);
363  else
364  request = request.substr(1);
365  }
366  m_apiCommandCall.insert( std::pair<std::string, std::string>( param, value ) );
367  }
368 
370  if (!_xmlData)
371  {
372  sLog.Error("APIServerConnection::ProcessPostData()", "Unknown or malformed EVEmu API HTTP CMD Received:\r\n%s\r\n", query.c_str());
373  NotFound();
374  return;
375  }
376 
377  // Print out to the Log with basic info on the API call and all parameters and their values parsed out
378  sLog.Debug("APIServerConnection::ProcessPostData()", "HTTP %s CMD Received: Service: %s, Handler: %s", _http_cmd_str.c_str(), _service.c_str(), _service_handler.c_str());
379  APICommandCall::const_iterator cur, end;
380  cur = m_apiCommandCall.begin();
381  end = m_apiCommandCall.end();
382  for (int i=1; cur != end; cur++, i++)
383  sLog.Debug(" ", "%d: param = %s, value = %s", i, cur->first.c_str(), cur->second.c_str() );
384 
385  // first we have to send the responseOK, then our actual result
386  boost::asio::async_write(_socket, _responseOK, boost::asio::transfer_all(), std::bind(&APIServerConnection::SendXML, shared_from_this()));
387 }
388 
390 {
391  boost::asio::async_write(_socket, boost::asio::buffer(*_xmlData, _xmlData->size()), boost::asio::transfer_all(), std::bind(&APIServerConnection::Close, shared_from_this()));
392 }
393 
395 {
396  boost::asio::async_write(_socket, _responseNotFound, boost::asio::transfer_all(), std::bind(&APIServerConnection::Close, shared_from_this()));
397 }
398 
400 {
401  boost::asio::async_write(_socket, _responseRedirectBegin, boost::asio::transfer_all(), std::bind(&APIServerConnection::RedirectLocation, shared_from_this()));
402 }
403 
405 {
406  //std::string extension = _service == "Character" ? "jpg" : "png";
407  std::stringstream url;
408  url << APIServer::FallbackURL << _service;// << "/" << _id;
409  _redirectUrl = url.str();
410  boost::asio::async_write(_socket, boost::asio::buffer(_redirectUrl), boost::asio::transfer_all(), std::bind(&APIServerConnection::RedirectFinalize, shared_from_this()));
411 }
412 
414 {
415  boost::asio::async_write(_socket, _responseRedirectEnd, boost::asio::transfer_all(), std::bind(&APIServerConnection::Close, shared_from_this()));
416 }
417 
419 {
420  _socket.close();
421 }
422 
423 bool APIServerConnection::starts_with(std::string& haystack, const char *const needle)
424 {
425  return haystack.substr(0, strlen(needle)).compare(needle) == 0;
426 }
427 
428 std::tr1::shared_ptr<APIServerConnection> APIServerConnection::create(boost::asio::io_context& io)
429 {
430  return std::tr1::shared_ptr<APIServerConnection>(new APIServerConnection(io));
431 }
static const char *const FallbackURL
Definition: APIServer.h:58
boost::asio::streambuf _postBuffer
#define sAPIServer
Definition: APIServer.h:85
static std::tr1::shared_ptr< APIServerConnection > create(boost::asio::io_context &io)
static boost::asio::const_buffers_1 _responseRedirectBegin
static boost::asio::const_buffers_1 _responseRedirectEnd
boost::asio::ip::tcp::socket & socket()
static boost::asio::const_buffers_1 _responseOK
static boost::asio::const_buffers_1 _responseNotFound
APICommandCall m_apiCommandCall
std::tr1::shared_ptr< std::vector< char > > _xmlData
#define sLog
Evaluates to a NewLog instance.
Definition: LogNew.h:250
static boost::asio::const_buffers_1 _responseNoContent
APIServerConnection(boost::asio::io_context &io)
unsigned __int32 uint32
Definition: eve-compat.h:50
boost::asio::streambuf _buffer
static bool starts_with(std::string &haystack, const char *const needle)
boost::asio::ip::tcp::socket _socket