FS2_Open
Open source remastering of the Freespace 2 engine
stand_gui-unix.cpp
Go to the documentation of this file.
1 #ifndef _WIN32
2 
3 #include <cstdlib>
4 #include <iostream>
5 #include <string>
6 #include <sstream>
7 #include <fstream>
8 #include <streambuf>
9 #include <algorithm>
10 #include <iterator>
11 #include <cstddef>
12 
13 // Copy-Paste from http://www.cplusplus.com/faq/sequences/strings/split/#c-tokenizer
14 struct split_struct {
15  enum empties_t {
17  };
18 };
19 
20 template<typename Container>
21 Container& split(Container& result, const typename Container::value_type& s,
22  const typename Container::value_type& delimiters, split_struct::empties_t empties = split_struct::empties_ok) {
23  result.clear();
24  size_t current;
25  size_t next = -1;
26  do {
27  if (empties == split_struct::no_empties) {
28  next = s.find_first_not_of(delimiters, next + 1);
29  if (next == Container::value_type::npos)
30  break;
31  next -= 1;
32  }
33  current = next + 1;
34  next = s.find_first_of(delimiters, current);
35  result.push_back(s.substr(current, next - current));
36  } while (next != Container::value_type::npos);
37  return result;
38 }
39 // Copy-Paste end
40 
41 // Copy-Paste from http://www.adp-gmbh.ch/cpp/common/base64.html
42 /*
43  base64.cpp and base64.h
44 
45  Copyright (C) 2004-2008 RenĂ© Nyffenegger
46 
47  This source code is provided 'as-is', without any express or implied
48  warranty. In no event will the author be held liable for any damages
49  arising from the use of this software.
50 
51  Permission is granted to anyone to use this software for any purpose,
52  including commercial applications, and to alter it and redistribute it
53  freely, subject to the following restrictions:
54 
55  1. The origin of this source code must not be misrepresented; you must not
56  claim that you wrote the original source code. If you use this source code
57  in a product, an acknowledgment in the product documentation would be
58  appreciated but is not required.
59 
60  2. Altered source versions must be plainly marked as such, and must not be
61  misrepresented as being the original source code.
62 
63  3. This notice may not be removed or altered from any source distribution.
64 
65  RenĂ© Nyffenegger rene.nyffenegger@adp-gmbh.ch
66 
67  */
68 
69 static const std::string base64_chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
70  "abcdefghijklmnopqrstuvwxyz"
71  "0123456789+/";
72 
73 static inline bool is_base64(unsigned char c) {
74  return (isalnum(c) || (c == '+') || (c == '/'));
75 }
76 
77 std::string base64_encode(unsigned char const* bytes_to_encode, unsigned int in_len) {
78  std::string ret;
79  int i = 0;
80  int j = 0;
81  unsigned char char_array_3[3];
82  unsigned char char_array_4[4];
83 
84  while (in_len--) {
85  char_array_3[i++] = *(bytes_to_encode++);
86  if (i == 3) {
87  char_array_4[0] = (char_array_3[0] & 0xfc) >> 2;
88  char_array_4[1] = ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4);
89  char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6);
90  char_array_4[3] = char_array_3[2] & 0x3f;
91 
92  for (i = 0; (i < 4); i++)
93  ret += base64_chars[char_array_4[i]];
94  i = 0;
95  }
96  }
97 
98  if (i) {
99  for (j = i; j < 3; j++)
100  char_array_3[j] = '\0';
101 
102  char_array_4[0] = (char_array_3[0] & 0xfc) >> 2;
103  char_array_4[1] = ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4);
104  char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6);
105  char_array_4[3] = char_array_3[2] & 0x3f;
106 
107  for (j = 0; (j < i + 1); j++)
108  ret += base64_chars[char_array_4[j]];
109 
110  while ((i++ < 3))
111  ret += '=';
112 
113  }
114 
115  return ret;
116 
117 }
118 
119 std::string base64_decode(std::string const& encoded_string) {
120  int in_len = encoded_string.size();
121  int i = 0;
122  int j = 0;
123  int in_ = 0;
124  unsigned char char_array_4[4], char_array_3[3];
125  std::string ret;
126 
127  while (in_len-- && (encoded_string[in_] != '=') && is_base64(encoded_string[in_])) {
128  char_array_4[i++] = encoded_string[in_];
129  in_++;
130  if (i == 4) {
131  for (i = 0; i < 4; i++)
132  char_array_4[i] = base64_chars.find(char_array_4[i]);
133 
134  char_array_3[0] = (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4);
135  char_array_3[1] = ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2);
136  char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3];
137 
138  for (i = 0; (i < 3); i++)
139  ret += char_array_3[i];
140  i = 0;
141  }
142  }
143 
144  if (i) {
145  for (j = i; j < 4; j++)
146  char_array_4[j] = 0;
147 
148  for (j = 0; j < 4; j++)
149  char_array_4[j] = base64_chars.find(char_array_4[j]);
150 
151  char_array_3[0] = (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4);
152  char_array_3[1] = ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2);
153  char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3];
154 
155  for (j = 0; (j < i - 1); j++)
156  ret += char_array_3[j];
157  }
158 
159  return ret;
160 }
161 // Copy-Paste end
162 
163 
164 /*
165  Copyright (c) 2013 Eli2
166  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:
167  The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
168  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.
169  */
170 
171 #include "network/stand_gui.h"
172 
173 #include "globalincs/pstypes.h"
175 #include "playerman/player.h"
176 #include "mission/missiongoals.h"
177 #include "ship/ship.h"
178 
179 #include "network/multi.h"
180 #include "network/multiutil.h"
181 #include "network/multimsgs.h"
182 #include "network/multi_pmsg.h"
183 #include "network/multi_kick.h"
184 #include "network/multi_endgame.h"
185 
186 #include "fs2netd/fs2netd_client.h"
187 
188 #include "mongoose.h"
189 #include "jansson.h"
190 
191 #define ARRAY_SIZE(array) (sizeof(array) / sizeof(array[0]))
192 
193 
194 
196 public:
197  virtual ~WebapiCommand() {
198  }
199  ;
200  virtual void execute() = 0;
201 };
202 
204 {
206  bool hasName;
207 
208  SCP_string passwd;
209  bool hasPasswd;
210 
211  int framecap;
212 
213 public:
214  ChangeServerInformationCommand() : hasName(false), hasPasswd(false), framecap(0) {}
215 
216  void setFrameCap(int cap) { framecap = cap; }
217 
218  void setName(const char* newName)
219  {
220  hasName = true;
221  name.assign(newName);
222  }
223 
224  void setPasswd(const char* newPasswd)
225  {
226  hasPasswd = true;
227  passwd.assign(newPasswd);
228  }
229 
230  virtual void execute()
231  {
232  if (hasName) {
233  strcpy_s(Netgame.name, name.c_str());
234  strcpy_s(Multi_options_g.std_pname, name.c_str());
235  // update fs2netd with the info
236  if (MULTI_IS_TRACKER_GAME) {
238  Sleep(50);
240  }
241  }
242 
243  if (hasPasswd) {
244  strcpy_s(Multi_options_g.std_passwd, passwd.c_str());
245  }
246 
247  if (framecap)
248  {
249  Multi_options_g.std_framecap = framecap;
250  }
251  }
252 };
253 
255 public:
256  KickPlayerCommand(int playerId)
257  : mPlayerId(playerId) {
258  }
259 
260  virtual void execute() {
261  size_t foundPlayerIndex = MAX_PLAYERS;
262  for (size_t idx = 0; idx < MAX_PLAYERS; idx++) {
264  if (Net_players[idx].player_id == mPlayerId) {
265  foundPlayerIndex = idx;
266  }
267  }
268  }
269 
270  if (foundPlayerIndex < MAX_PLAYERS) {
271  multi_kick_player(foundPlayerIndex, 0);
272  }
273  }
274 private:
275  int mPlayerId;
276 };
277 
279 public:
280  virtual void execute() {
282  }
283 };
284 
286 public:
287  virtual void execute() {
288  if (MULTI_IS_TRACKER_GAME) {
289  // delete mvalid.cfg if it exists
291 
292  // refresh missions
294  }
295  }
296 };
297 
299 public:
300  virtual void execute() {
302  }
303 };
304 
306 public:
307  virtual void execute() {
309  }
310 };
311 
312 SDL_mutex* webapiCommandQueueMutex = SDL_CreateMutex();
314 
316  SDL_mutexP(webapiCommandQueueMutex);
317 
318  webapiCommandQueue.push_back(command);
319 
320  SDL_mutexV(webapiCommandQueueMutex);
321 }
322 
324  SDL_mutexP(webapiCommandQueueMutex);
325 
326  for (SCP_vector<WebapiCommand*>::iterator iter = webapiCommandQueue.begin(); iter != webapiCommandQueue.end();
327  ++iter) {
328  (*iter)->execute();
329  delete *iter;
330  }
331 
332  webapiCommandQueue.clear();
333 
334  SDL_mutexV(webapiCommandQueueMutex);
335 }
336 
337 struct LogResource {
339  long timestamp;
340  json_t *entity;
341  };
342 
345 
346  void addEntity(json_t* entity) {
347  LogResourceEntry entry;
348  entry.timestamp = currentTimestamp;
349  entry.entity = entity;
350 
351  entries.push_back(entry);
352  currentTimestamp++;
353 
354  if (entries.size() > 500) {
355  json_delete(entries.front().entity);
356  entries.pop_front();
357  }
358  }
359 
360  json_t* getEntriesAfter(long after) {
361  json_t *msgs = json_array();
362 
363  for (std::list<LogResourceEntry>::iterator iter = entries.begin(); iter != entries.end(); ++iter) {
364  if (iter->timestamp >= after) {
365  json_t *msg = json_copy(iter->entity);
366  json_object_set_new(msg, "timestamp", json_integer(iter->timestamp));
367  json_array_append(msgs, msg);
368  }
369  }
370 
371  return msgs;
372  }
373 };
374 
375 
376 SDL_mutex *webapi_dataMutex = SDL_CreateMutex();
378 std::map<short, net_player> webapiNetPlayers;
379 float webui_fps;
381 std::list<mission_goal> webuiMissionGoals;
384 
387 };
388 
389 static void sendResponse(mg_connection *conn, std::string const& data, HttpStatuscode status) {
390  std::stringstream headerStream;
391 
392  headerStream << "HTTP/1.0 ";
393  switch (status) {
394  case HTTP_200_OK:
395  headerStream << "200 OK";
396  break;
398  headerStream << "401 Unauthorized";
399  break;
400  case HTTP_404_NOT_FOUND:
401  headerStream << "404 Not Found";
402  break;
404  headerStream << "500 Internal Server Error";
405  break;
406  }
407  headerStream << "\r\n";
408 
409  if (data.length() > 0) {
410  headerStream << "Content-Length: " << data.length() << "\r\n";
411  headerStream << "Content-Type: application/json\r\n\r\n";
412  }
413 
414  std::string resultString;
415  resultString += headerStream.str();
416  resultString += data;
417 
418  mg_write(conn, resultString.c_str(), (int) resultString.length());
419 }
420 
421 static void sendJsonResponse(mg_connection *connection, json_t *responseEntity) {
422  char* result = json_dumps(responseEntity, 0);
423  std::string resultString(result);
424  free(result);
425  json_delete(responseEntity);
426 
427  sendResponse(connection, resultString, HTTP_200_OK);
428 }
429 
430 // =============================================================================
431 
433  json_t *requestEntity;
435 };
436 
437 typedef json_t* (*resourceHandler)(ResourceContext *);
438 
439 struct Resource {
442 
444 };
445 
446 json_t* emptyResource(ResourceContext *context) {
447  return json_object();
448 }
449 
450 json_t* serverGet(ResourceContext *context) {
451  json_t *result = json_object();
452 
453  json_object_set_new(result, "name", json_string(Multi_options_g.std_pname));
454  json_object_set_new(result, "password", json_string(Multi_options_g.std_passwd));
455  json_object_set_new(result, "framecap", json_integer(Multi_options_g.std_framecap));
456 
457  return result;
458 }
459 
460 json_t* serverPut(ResourceContext *context) {
462 
463  const char* name = json_string_value(json_object_get(context->requestEntity, "name"));
464  if (name) {
465  changeCommand->setName(name);
466  }
467  const char* passwd = json_string_value(json_object_get(context->requestEntity, "password"));
468  if (passwd) {
469  changeCommand->setPasswd(passwd);
470  }
471  int framecap = atoi(json_string_value(json_object_get(context->requestEntity, "framecap")));
472  if (framecap)
473  {
474  changeCommand->setFrameCap(framecap);
475  }
476 
477  webapiAddCommand(changeCommand);
478 
479  return json_object();
480 }
481 
482 json_t* serverDelete(ResourceContext *context) {
484  return json_object();
485 }
486 
487 json_t* refreshMissions(ResourceContext *context) {
489  return json_object();
490 }
491 
492 json_t* serverResetGame(ResourceContext *context) {
494  return json_object();
495 }
496 
497 json_t* fs2netReset(ResourceContext *context) {
499  return json_object();
500 }
501 
502 json_t* netgameInfoGet(ResourceContext *context) {
503  json_t *obj = json_object();
504 
505  json_object_set_new(obj, "name", json_string(webapi_netgameInfo.name));
506  json_object_set_new(obj, "mission", json_string(webapi_netgameInfo.mission_name));
507  json_object_set_new(obj, "campaign", json_string(webapi_netgameInfo.campaign_name));
508 
509  json_object_set_new(obj, "maxPlayers", json_integer(webapi_netgameInfo.max_players));
510  json_object_set_new(obj, "maxObservers", json_integer(webapi_netgameInfo.options.max_observers));
511  json_object_set_new(obj, "respawn", json_integer(webapi_netgameInfo.respawn));
512 
513  json_object_set_new(obj, "gameState", json_integer(webapi_netgameInfo.game_state));
514 
515  json_object_set_new(obj, "security", json_integer(webapi_netgameInfo.security));
516  return obj;
517 }
518 
519 json_t* missionGet(ResourceContext *context) {
520  json_t *fpsEntity = json_object();
521 
522  json_object_set_new(fpsEntity, "fps", json_real(webui_fps));
523  json_object_set_new(fpsEntity, "time", json_real(webui_missiontime));
524 
525  return fpsEntity;
526 }
527 
528 json_t* missionGoalsGet(ResourceContext *context) {
529  json_t *goals = json_array();
530 
531  for (std::list<mission_goal>::iterator iter = webuiMissionGoals.begin(); iter != webuiMissionGoals.end(); ++iter) {
532  mission_goal goal = *iter;
533 
534  json_t *goalEntity = json_object();
535 
536  json_object_set_new(goalEntity, "name", json_string(goal.name));
537  json_object_set_new(goalEntity, "message", json_string(goal.message));
538  json_object_set_new(goalEntity, "score", json_integer(goal.score));
539  json_object_set_new(goalEntity, "team", json_integer(goal.team));
540 
541  char *typeString;
542  switch (goal.type) {
543  case PRIMARY_GOAL:
544  typeString = "primary";
545  break;
546  case SECONDARY_GOAL:
547  typeString = "secondary";
548  break;
549  case BONUS_GOAL:
550  typeString = "bonus";
551  break;
552  default:
553  typeString = "error";
554  break;
555  };
556  json_object_set_new(goalEntity, "type", json_string(typeString));
557 
558  char *statusString;
559  switch (goal.satisfied) {
560  case GOAL_FAILED:
561  statusString = "failed";
562  break;
563  case GOAL_COMPLETE:
564  statusString = "complete";
565  break;
566  case GOAL_INCOMPLETE:
567  statusString = "incomplete";
568  break;
569  default:
570  statusString = "error";
571  break;
572  };
573  json_object_set_new(goalEntity, "status", json_string(statusString));
574 
575  json_array_append(goals, goalEntity);
576  }
577 
578  return goals;
579 }
580 
581 json_t* playerGet(ResourceContext *context) {
582  json_t *playerList = json_array();
583 
584  for (std::map<short, net_player>::iterator iter = webapiNetPlayers.begin(); iter != webapiNetPlayers.end();
585  ++iter) {
586  net_player p = iter->second;
587 
588  char address[256];
589  sprintf(address, "%u.%u.%u.%u:%u", p.p_info.addr.addr[0], p.p_info.addr.addr[1], p.p_info.addr.addr[2],
590  p.p_info.addr.addr[3], p.p_info.addr.port);
591 
592  json_t *obj = json_object();
593 
594  json_object_set(obj, "id", json_integer(p.player_id));
595  json_object_set(obj, "address", json_string(address));
596  json_object_set(obj, "ping", json_integer(p.s_info.ping.ping_avg));
597  json_object_set(obj, "host", (MULTI_HOST(p)) ? json_true() : json_false());
598  json_object_set(obj, "observer", (MULTI_OBSERVER(p)) ? json_true() : json_false());
599  json_object_set(obj, "callsign", json_string(p.m_player->callsign));
600  json_object_set(obj, "ship", json_string(Ship_info[p.p_info.ship_class].name));
601 
602  json_array_append(playerList, obj);
603  }
604 
605  return playerList;
606 }
607 
608 json_t* playerDelete(ResourceContext *context) {
609  int playerId = atoi(context->parameters["playerId"].c_str());
610  webapiAddCommand(new KickPlayerCommand(playerId));
611  return json_object();
612 }
613 
614 net_player *playerForId(int playerId) {
615  net_player *foundPlayer = NULL;
616  for (size_t idx = 0; idx < MAX_PLAYERS; idx++) {
618  if (Net_players[idx].player_id == playerId) {
619  foundPlayer = &Net_players[idx];
620  }
621  }
622  }
623  return foundPlayer;
624 }
625 
627  net_player *p = playerForId(atoi(context->parameters["playerId"].c_str()));
628 
629  json_t *obj2 = json_object();
630  if (p == NULL || p->m_player == NULL)
631  return obj2;
632 
633  scoring_struct *scores = &(p->m_player->stats);
634 
635  json_object_set_new(obj2, "score", json_integer(scores->m_score));
636  json_object_set_new(obj2, "kills", json_integer(scores->m_kill_count));
637  json_object_set_new(obj2, "kills-enemy", json_integer(scores->m_kill_count_ok));
638  json_object_set_new(obj2, "kills-friendly", json_integer(scores->m_bonehead_kills));
639  json_object_set_new(obj2, "assists", json_integer(scores->m_assists));
640  json_object_set_new(obj2, "shots-primary", json_integer(scores->mp_shots_fired));
641  json_object_set_new(obj2, "shots-secondary", json_integer(scores->ms_shots_fired));
642  json_object_set_new(obj2, "hits-primary", json_integer(scores->mp_shots_hit));
643  json_object_set_new(obj2, "hits-secondary", json_integer(scores->ms_shots_hit));
644  json_object_set_new(obj2, "hits-friendly-primary", json_integer(scores->mp_bonehead_hits));
645  json_object_set_new(obj2, "hits-friendly-secondary", json_integer(scores->ms_bonehead_hits));
646 
647  return obj2;
648 }
649 
651  net_player *p = playerForId(atoi(context->parameters["playerId"].c_str()));
652 
653  json_t *obj = json_object();
654  if (p == NULL || p->m_player == NULL)
655  return obj;
656 
657  scoring_struct *scores = &(p->m_player->stats);
658 
659  json_object_set_new(obj, "score", json_integer(scores->score));
660  json_object_set_new(obj, "kills", json_integer(scores->kill_count));
661  json_object_set_new(obj, "kills-enemy", json_integer(scores->kill_count_ok));
662  json_object_set_new(obj, "kills-friendly", json_integer(scores->bonehead_kills));
663  json_object_set_new(obj, "assists", json_integer(scores->assists));
664  json_object_set_new(obj, "shots-primary", json_integer(scores->p_shots_fired));
665  json_object_set_new(obj, "shots-secondary", json_integer(scores->s_shots_fired));
666  json_object_set_new(obj, "hits-primary", json_integer(scores->p_shots_hit));
667  json_object_set_new(obj, "hits-secondary", json_integer(scores->s_shots_hit));
668  json_object_set_new(obj, "hits-friendly-primary", json_integer(scores->p_bonehead_hits));
669  json_object_set_new(obj, "hits-friendly-secondary", json_integer(scores->s_bonehead_hits));
670 
671  return obj;
672 }
673 
675  SCP_map<SCP_string, SCP_string>::iterator iter = context->parameters.find("after");
676  if (iter != context->parameters.end()) {
677  return atoi(iter->second.c_str());
678  }
679 
680  return 0;
681 }
682 
683 json_t* chatGet(ResourceContext *context) {
684  int after = afterTimestamp(context);
685  return webapi_chatLog.getEntriesAfter(after);
686 }
687 
688 json_t* chatPost(ResourceContext *context) {
689  const char* message = json_string_value(json_object_get(context->requestEntity, "message"));
690  if (message) {
691  send_game_chat_packet(Net_player, const_cast<char*>(message), MULTI_MSG_ALL, NULL);
692  std_add_chat_text(const_cast<char*>(message), 0 /*MY_NET_PLAYER_NUM*/, 1);
693  }
694 
695  return emptyResource(context);
696 }
697 
698 json_t* debugGet(ResourceContext *context) {
699  int after = afterTimestamp(context);
700  return webapi_debugLog.getEntriesAfter(after);
701 }
702 
703 struct Resource resources[] = {
704  { "api/1/auth", "GET", &emptyResource },
705  { "api/1/server", "GET", &serverGet },
706  { "api/1/server", "PUT", &serverPut },
707  { "api/1/server", "DELETE", &serverDelete },
708  { "api/1/server/refreshMissions", "GET", &refreshMissions },
709  { "api/1/server/resetGame", "GET", &serverResetGame },
710  { "api/1/server/fs2net/reset", "GET", &fs2netReset },
711  { "api/1/netgameInfo", "GET", &netgameInfoGet },
712  { "api/1/mission", "GET", &missionGet },
713  { "api/1/mission/goals", "GET", &missionGoalsGet },
714  { "api/1/player", "GET", &playerGet },
715  { "api/1/player/*", "DELETE", &playerDelete },
716  { "api/1/player/*/score/mission", "GET", &playerMissionScoreMissionGet },
717  { "api/1/player/*/score/alltime", "GET", &playerMissionScoreAlltimeGet },
718  { "api/1/chat", "GET", &chatGet },
719  { "api/1/chat", "POST", &chatPost },
720  { "api/1/debug", "GET", &debugGet } };
721 
722 static bool webserverApiRequest(mg_connection *conn, const mg_request_info *ri) {
723  SCP_string resourcePath(ri->uri);
724 
725  resourcePath.erase(0, 1);
726  SCP_vector<SCP_string> pathParts;
727  split(pathParts, resourcePath, "/", split_struct::no_empties);
728 
729  json_t *result = NULL;
730 
731  SCP_string method(ri->request_method);
732 
733  for (size_t i = 0; i < ARRAY_SIZE(resources); i++) {
734  Resource* r = &resources[i];
735  SCP_vector<SCP_string> resourcePathParts;
736  split(resourcePathParts, r->path, "/", split_struct::no_empties);
737 
738  if (resourcePathParts.size() == pathParts.size()) {
739  ResourceContext context;
740 
741  bool pathMatch = true;
742  for (size_t u = 0; u < resourcePathParts.size(); u++) {
743  //TODO this is kind of a hack
744  if (resourcePathParts.at(u) == "*") {
745  context.parameters["playerId"] = pathParts.at(u);
746  } else if (resourcePathParts.at(u) != pathParts.at(u)) {
747  pathMatch = false;
748  break;
749  }
750  }
751 
752  if (pathMatch && r->method == method) {
753 
754  std::string userNameAndPassword;
755 
756  userNameAndPassword += Multi_options_g.webapiUsername.c_str();
757  userNameAndPassword += ":";
758  userNameAndPassword += Multi_options_g.webapiPassword.c_str();
759 
760  std::string basicAuthValue = "Basic ";
761 
762  basicAuthValue += base64_encode(reinterpret_cast<const unsigned char*>(userNameAndPassword.c_str()), userNameAndPassword.length());
763 
764  const char* authValue = mg_get_header(conn, "Authorization");
765  if (authValue == NULL || strcmp(authValue, basicAuthValue.c_str()) != 0) {
766  sendResponse(conn, std::string(), HTTP_401_UNAUTHORIZED);
767  return true;
768  }
769 
770  if (ri->query_string) {
771  SCP_string query(ri->query_string);
772  SCP_vector<SCP_string> queryPairs;
773  split(queryPairs, query, "&", split_struct::no_empties);
774 
775  for (SCP_vector<SCP_string>::const_iterator iter = queryPairs.begin(); iter != queryPairs.end();
776  ++iter) {
778 
779  split(temp, *iter, "=", split_struct::no_empties);
780 
781  if (temp.size() == 2) {
782  context.parameters[temp[0]] = temp[1];
783  }
784  }
785  }
786 
787  char entityBuffer[1024];
788  memset(entityBuffer, 0, sizeof(entityBuffer));
789 
790  /*int readBytes = */mg_read(conn, &entityBuffer, sizeof(entityBuffer));
791 
792  json_error_t parseError;
793  context.requestEntity = json_loads((const char*) &entityBuffer, JSON_DISABLE_EOF_CHECK, &parseError);
794 
795 
796  SDL_mutexP(webapi_dataMutex);
797  result = r->handler(&context);
798  SDL_mutexV(webapi_dataMutex);
799 
800  break;
801  }
802  }
803  }
804 
805  if (result) {
806  sendJsonResponse(conn, result);
807  return true;
808  }
809 
810  return false;
811 }
812 
813 static void *webserverCallback(enum mg_event event, struct mg_connection *conn) {
814  const struct mg_request_info *ri = mg_get_request_info(conn);
815 
816  switch (event) {
817  case MG_EVENT_LOG: {
818  const char *msg = (const char *) ri->ev_data;
819  mprintf(("Webapi error: %s\n", msg));
820  break;
821  }
822  case MG_NEW_REQUEST: {
823  bool requestHandled = webserverApiRequest(conn, ri);
824  return requestHandled ? const_cast<char*>("") : NULL;
825  }
826  default:
827  break;
828  };
829  return NULL;
830 }
831 
832 struct mg_context *webserverContext = NULL;
833 
835  if (webserverContext) {
836  mprintf(("Webapi shutting down\n"));
837  mg_stop(webserverContext);
838  }
839 }
840 
842  atexit(webapi_shutdown);
843 }
844 
846 
847  webapi_shutdown();
848 
849  char buffer[16];
850  sprintf(buffer, "%d", options->webapiPort);
851 
852  const char *mgOptions[] = {
853  "listening_ports", buffer,
854  "document_root", options->webuiRootDirectory.c_str(),
855  "num_threads", "4",
856  NULL };
857 
858  mprintf(("Webapi starting on port: %d with document root at: %s\n", options->webapiPort, options->webuiRootDirectory.c_str()));
859 
860  webserverContext = mg_start(&webserverCallback, NULL, mgOptions);
861 }
862 
863 void std_add_chat_text(const char *text, int player_index, int add_id) {
864 
865  json_t *msg = json_object();
866  //json_object_set_new(msg, "source", json_string(Net_players[player_index].m_player->callsign));
867  json_object_set_new(msg, "message", json_string(text));
868 
869  webapi_chatLog.addEntity(msg);
870 }
871 
872 void std_debug_multilog_add_line(const char *str) {
873 
874  SCP_vector<SCP_string> debugMessages;
875  split(debugMessages, SCP_string(str), "\n", split_struct::no_empties);
876 
877  for (SCP_vector<SCP_string>::const_iterator iter = debugMessages.begin(); iter != debugMessages.end(); ++iter) {
878  json_t *msg = json_object();
879  json_object_set_new(msg, "message", json_string(str));
880  webapi_debugLog.addEntity(msg);
881  }
882 }
883 
884 // =============================================================================
885 
886 std::vector<std::string> bannedPlayers;
887 
888 void std_add_ban(const char *name) {
889  bannedPlayers.push_back(std::string(name));
890 }
891 
892 int std_player_is_banned(const char *name) {
893  return std::find(bannedPlayers.begin(), bannedPlayers.end(), std::string(name)) != bannedPlayers.end() ? 1 : 0;
894 }
895 
900  return (Multi_options_g.std_passwd[0] != '\0') ? 1 : 0;
901 }
902 
903 void std_set_standalone_fps(float fps) {
904  webui_fps = fps;
905 }
906 
908  SDL_mutexP(webapi_dataMutex);
909 
910  webapi_netgameInfo = Netgame;
911 
912  // Update player data
913  webapiNetPlayers.clear();
914 
915  for (size_t idx = 0; idx < MAX_PLAYERS; idx++) {
916  if (MULTI_CONNECTED(Net_players[idx]) && (Net_player != &Net_players[idx])) {
918 
919  webapiNetPlayers[p->player_id] = *p;
920  }
921  }
922 
923  // Update mission data
924  webui_missiontime = f2fl(Missiontime);
925 
926  webuiMissionGoals.clear();
927  for (int idx = 0; idx < Num_goals; idx++) {
928  webuiMissionGoals.push_back(Mission_goals[idx]);
929  }
930 
931  SDL_mutexV(webapi_dataMutex);
932 
934 }
935 
936 // set the game name for the standalone. passing NULL uses the default
938 {
939  // use the default name for now
940  if(name == NULL){
941  // if a permanent name exists, use that instead of the default
942  if(strlen(Multi_options_g.std_pname)){
944  } else {
945  strcpy_s(Netgame.name,XSTR("Standalone Server",916));
946  }
947  } else {
948  strcpy_s(Netgame.name,name);
949 
950  // update fs2netd
951  if (MULTI_IS_TRACKER_GAME) {
953  Sleep(50);
955  }
956  }
957 }
958 
964 /* The return value does nothing, except cause the two functions below to be called*/
965 int std_remove_player(net_player *p) {return 0;}
973 void std_multi_set_standalone_mission_name(char *mission_name) {}
974 void std_gen_set_text(char *str, int field_num) {}
975 void std_create_gen_dialog(char *title) {}
977 int std_gen_is_active() {return 0;}
981 void std_multi_set_standalone_missiontime(float mission_time) {}
982 
983 // stub - not required for *nix standalone
984 void std_init_os() {}
985 
986 #endif
GLuint64EXT * result
Definition: Glext.h:10775
uint respawn
Definition: multi.h:507
int std_gen_is_active()
void multi_update_valid_missions()
Definition: multiutil.cpp:3041
int i
Definition: multi_pxo.cpp:466
fix Missiontime
Definition: systemvars.cpp:19
LogResource webapi_debugLog
int kill_count_ok
Definition: scoring.h:90
void fs2netd_gameserver_start()
int cf_delete(const char *filename, int path_type)
Delete the specified file.
Definition: cfile.cpp:483
#define BONUS_GOAL
Definition: missiongoals.h:30
int ship_class
Definition: multi.h:451
void std_debug_multilog_add_line(const char *str)
int std_is_host_passwd()
json_t * debugGet(ResourceContext *context)
json_t * serverGet(ResourceContext *context)
virtual void execute()
net_player * Net_player
Definition: multi.cpp:94
json_t * chatGet(ResourceContext *context)
player * m_player
Definition: multi.h:459
int security
Definition: multi.h:499
json_t * chatPost(ResourceContext *context)
void setName(const char *newName)
void std_multi_update_netgame_info_controls()
void std_destroy_gen_dialog()
std::string base64_decode(std::string const &encoded_string)
#define mprintf(args)
Definition: pstypes.h:238
HttpStatuscode
#define MULTI_VALID_MISSION_FILE
Definition: multi.h:40
struct Resource resources[]
unsigned int mp_shots_fired
Definition: scoring.h:117
unsigned int mp_bonehead_hits
Definition: scoring.h:121
int m_bonehead_kills
Definition: scoring.h:123
std::map< short, net_player > webapiNetPlayers
#define MULTI_HOST(np)
Definition: multi.h:137
#define PRIMARY_GOAL
Definition: missiongoals.h:28
int kill_count
Definition: scoring.h:89
#define f2fl(fx)
Definition: floating.h:37
virtual void execute()=0
enum_h * u
Definition: lua.cpp:12649
json_t * serverPut(ResourceContext *context)
void fs2netd_gameserver_disconnect()
unsigned int ms_bonehead_hits
Definition: scoring.h:122
GLenum GLuint GLenum GLsizei const GLchar * message
Definition: Glext.h:5156
std::basic_string< char, std::char_traits< char >, std::allocator< char > > SCP_string
Definition: vmallocator.h:21
ubyte addr[6]
Definition: psnet2.h:41
unsigned int s_shots_fired
Definition: scoring.h:92
void webapi_shutdown()
mission_goal Mission_goals[MAX_GOALS]
char callsign[CALLSIGN_LEN+1]
Definition: player.h:91
int game_state
Definition: multi.h:498
unsigned int mp_shots_hit
Definition: scoring.h:119
Container & split(Container &result, const typename Container::value_type &s, const typename Container::value_type &delimiters, split_struct::empties_t empties=split_struct::empties_ok)
json_t * entity
json_t * serverResetGame(ResourceContext *context)
void std_init_os()
SCP_string path
void std_update_player_ping(net_player *p)
SCP_string method
void std_multi_set_standalone_mission_name(char *mission_name)
json_t * playerGet(ResourceContext *context)
void std_connect_set_host_connect_status()
unsigned int ms_shots_fired
Definition: scoring.h:118
int m_kill_count_ok
Definition: scoring.h:115
virtual void execute()
typedef int(SCP_EXT_CALLCONV *SCPDLL_PFVERSION)(SCPDLL_Version *)
void std_init_standalone()
void multi_kick_player(int player_index, int ban, int reason)
Definition: multi_kick.cpp:82
net_player_info p_info
Definition: multi.h:473
GLdouble GLdouble GLdouble r
Definition: Glext.h:5337
void addEntity(json_t *entity)
SCP_string webapiPassword
Definition: multi_options.h:59
int ping_avg
Definition: multi_ping.h:32
multi_server_options options
Definition: multi.h:514
SCP_vector< WebapiCommand * > webapiCommandQueue
char name[MAX_GAMENAME_LEN+1]
Definition: multi.h:487
void send_game_chat_packet(net_player *from, const char *msg, int msg_mode, net_player *to, const char *expr, int server_msg)
Definition: multimsgs.cpp:615
void fs2netd_reset_connection()
unsigned int p_shots_fired
Definition: scoring.h:91
netgame_info Netgame
Definition: multi.cpp:97
json_t * fs2netReset(ResourceContext *context)
sprintf(buf,"(%f,%f,%f)", v3->xyz.x, v3->xyz.y, v3->xyz.z)
void std_gen_set_text(char *str, int field_num)
#define ARRAY_SIZE(array)
#define MULTI_OBSERVER(np)
Definition: multi.h:140
void std_configLoaded(multi_global_options *options)
void std_add_chat_text(const char *text, int player_index, int add_id)
struct mg_context * webserverContext
GLuint buffer
Definition: Glext.h:5492
GLdouble s
Definition: Glext.h:5321
net_player_server_info s_info
Definition: multi.h:472
GLsizei const GLchar ** string
Definition: Glext.h:5636
SCP_string webapiUsername
Definition: multi_options.h:58
#define MAX_PLAYERS
Definition: pstypes.h:32
json_t * missionGoalsGet(ResourceContext *context)
short player_id
Definition: multi.h:460
char mission_name[NAME_LENGTH+1]
Definition: multi.h:488
virtual void execute()
int idx
Definition: multiui.cpp:761
char campaign_name[NAME_LENGTH+1]
Definition: multi.h:490
#define MULTI_MSG_ALL
Definition: multi_pmsg.h:27
net_addr addr
Definition: multi.h:453
void std_reset_timestamps()
const char * XSTR(const char *str, int index)
Definition: localize.cpp:851
#define SECONDARY_GOAL
Definition: missiongoals.h:29
void setPasswd(const char *newPasswd)
GLenum cap
Definition: Glext.h:9110
void webapiAddCommand(WebapiCommand *command)
void std_debug_set_standalone_state_string(char *str)
void std_add_ban(const char *name)
float webui_missiontime
#define GOAL_INCOMPLETE
Definition: missiongoals.h:40
std::string base64_encode(unsigned char const *bytes_to_encode, unsigned int in_len)
GLuint const GLchar * name
Definition: Glext.h:5608
#define PROMPT_NONE
Definition: multi_endgame.h:26
json_t * playerMissionScoreAlltimeGet(ResourceContext *context)
int multi_quit_game(int prompt, int notify_code, int err_code, int wsa_error)
#define CF_TYPE_DATA
Definition: cfile.h:46
void std_multi_set_standalone_missiontime(float mission_time)
ping_struct ping
Definition: multi.h:395
json_t * refreshMissions(ResourceContext *context)
KickPlayerCommand(int playerId)
SDL_mutex * webapiCommandQueueMutex
json_t * playerDelete(ResourceContext *context)
void std_do_gui_frame()
int afterTimestamp(ResourceContext *context)
#define MULTI_CONNECTED(np)
Definition: multi.h:136
json_t * missionGet(ResourceContext *context)
int std_remove_player(net_player *p)
json_t * netgameInfoGet(ResourceContext *context)
int std_player_is_banned(const char *name)
unsigned int p_shots_hit
Definition: scoring.h:94
json_t * playerMissionScoreMissionGet(ResourceContext *context)
std::list< mission_goal > webuiMissionGoals
GLfloat GLfloat p
Definition: Glext.h:8373
void std_connect_set_gamename(char *name)
unsigned int p_bonehead_hits
Definition: scoring.h:97
void Sleep(int mili)
long timestamp
LogResource webapi_chatLog
void std_multi_update_goals()
struct _cl_event * event
Definition: Glext.h:7296
SCP_vector< ship_info > Ship_info
Definition: ship.cpp:164
char std_pname[STD_NAME_LEN+1]
Definition: multi_options.h:52
GLenum GLsizei GLenum GLenum const GLvoid * data
Definition: Gl.h:1509
SCP_map< SCP_string, SCP_string > parameters
GLsizei GLsizei GLuint * obj
Definition: Glext.h:5619
char name[NAME_LENGTH]
Definition: missiongoals.h:55
json_t * getEntriesAfter(long after)
char message[MAX_GOAL_TEXT]
Definition: missiongoals.h:58
float webui_fps
void std_create_gen_dialog(char *title)
GLenum query
Definition: Glext.h:7338
void std_add_player(net_player *p)
int temp
Definition: lua.cpp:4996
net_player * playerForId(int playerId)
void webapiExecuteCommands()
json_t *(* resourceHandler)(ResourceContext *)
false
Definition: lua.cpp:6789
int std_connect_set_connect_count()
SDL_mutex * webapi_dataMutex
int bonehead_kills
Definition: scoring.h:99
SCP_list< LogResourceEntry > entries
#define GOAL_COMPLETE
Definition: missiongoals.h:39
int Num_goals
virtual ~WebapiCommand()
SCP_string webuiRootDirectory
Definition: multi_options.h:60
void std_multi_setup_goal_tree()
#define MULTI_IS_TRACKER_GAME
Definition: multi.h:146
int m_kill_count
Definition: scoring.h:114
net_player Net_players[MAX_PLAYERS]
Definition: multi.cpp:93
short port
Definition: psnet2.h:42
int max_players
Definition: multi.h:497
void std_reset_standalone_gui()
unsigned int ms_shots_hit
Definition: scoring.h:120
void gameseq_post_event(int event)
resourceHandler handler
unsigned int s_shots_hit
Definition: scoring.h:95
multi_global_options Multi_options_g
std::vector< std::string > bannedPlayers
json_t * emptyResource(ResourceContext *context)
json_t * serverDelete(ResourceContext *context)
unsigned int s_bonehead_hits
Definition: scoring.h:98
GLuint address
Definition: Glext.h:8864
const GLubyte * c
Definition: Glext.h:8376
netgame_info webapi_netgameInfo
void std_set_standalone_fps(float fps)
void std_multi_add_goals()
scoring_struct stats
Definition: player.h:127
#define strcpy_s(...)
Definition: safe_strings.h:67
#define GOAL_FAILED
Definition: missiongoals.h:38
char std_passwd[STD_PASSWD_LEN+1]
Definition: multi_options.h:51