aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--dep/gsoap/httpget.c277
-rw-r--r--dep/gsoap/httpget.h84
-rw-r--r--dep/gsoap/httppost.c368
-rw-r--r--dep/gsoap/httppost.h101
-rw-r--r--src/common/Cryptography/Authentication/SRP6.cpp7
-rw-r--r--src/common/Utilities/Concepts.h33
-rw-r--r--src/server/bnetserver/Main.cpp20
-rw-r--r--src/server/bnetserver/REST/LoginHttpSession.cpp118
-rw-r--r--src/server/bnetserver/REST/LoginHttpSession.h43
-rw-r--r--src/server/bnetserver/REST/LoginRESTService.cpp491
-rw-r--r--src/server/bnetserver/REST/LoginRESTService.h91
-rw-r--r--src/server/bnetserver/Server/Session.cpp4
-rw-r--r--src/server/bnetserver/Server/Session.h9
-rw-r--r--src/server/database/Database/DatabaseWorkerPool.cpp1
-rw-r--r--src/server/shared/CMakeLists.txt3
-rw-r--r--src/server/shared/Networking/Http/BaseHttpSocket.cpp115
-rw-r--r--src/server/shared/Networking/Http/BaseHttpSocket.h191
-rw-r--r--src/server/shared/Networking/Http/HttpCommon.h55
-rw-r--r--src/server/shared/Networking/Http/HttpService.cpp258
-rw-r--r--src/server/shared/Networking/Http/HttpService.h188
-rw-r--r--src/server/shared/Networking/Http/HttpSessionState.h35
-rw-r--r--src/server/shared/Networking/Http/HttpSocket.h75
-rw-r--r--src/server/shared/Networking/Http/HttpSslSocket.h97
-rw-r--r--src/server/shared/Networking/NetworkThread.h2
-rw-r--r--src/server/shared/Networking/Socket.h18
-rw-r--r--src/server/shared/Networking/SslSocket.h4
-rw-r--r--src/server/worldserver/CMakeLists.txt3
27 files changed, 1434 insertions, 1257 deletions
diff --git a/dep/gsoap/httpget.c b/dep/gsoap/httpget.c
deleted file mode 100644
index 34eb3960e64..00000000000
--- a/dep/gsoap/httpget.c
+++ /dev/null
@@ -1,277 +0,0 @@
-/*
- httpget.c
-
- gSOAP HTTP GET plugin.
-
- See instructions below.
-
-gSOAP XML Web services tools
-Copyright (C) 2000-2008, Robert van Engelen, Genivia Inc., All Rights Reserved.
-This part of the software is released under ONE of the following licenses:
-GPL or the gSOAP public license.
---------------------------------------------------------------------------------
-gSOAP public license.
-
-The contents of this file are subject to the gSOAP Public License Version 1.3
-(the "License"); you may not use this file except in compliance with the
-License. You may obtain a copy of the License at
-http://www.cs.fsu.edu/~engelen/soaplicense.html
-Software distributed under the License is distributed on an "AS IS" basis,
-WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
-for the specific language governing rights and limitations under the License.
-
-The Initial Developer of the Original Code is Robert A. van Engelen.
-Copyright (C) 2000-2008 Robert A. van Engelen, Genivia inc. All Rights Reserved.
---------------------------------------------------------------------------------
-GPL license.
-
-This program is free software; you can redistribute it and/or modify it under
-the terms of the GNU General Public License as published by the Free Software
-Foundation; either version 2 of the License, or (at your option) any later
-version.
-
-This program is distributed in the hope that it will be useful, but WITHOUT ANY
-WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
-PARTICULAR PURPOSE. See the GNU General Public License for more details.
-
-You should have received a copy of the GNU General Public License along with
-this program; if not, write to the Free Software Foundation, Inc., 59 Temple
-Place, Suite 330, Boston, MA 02111-1307 USA
-
-Author contact information:
-engelen@genivia.com / engelen@acm.org
-
-This program is released under the GPL with the additional exemption that
-compiling, linking, and/or using OpenSSL is allowed.
---------------------------------------------------------------------------------
-
- Compile & link with stand-alone gSOAP server for HTTP GET support.
- Compile & link with gSOAP clients for client-side HTTP GET requests.
-
- Usage (server side):
-
- struct soap soap;
- soap_init(&soap);
- soap_register_plugin_arg(&soap, http_get, http_get_handler);
- ...
- ... = soap_copy(&soap); // copies plugin too but not its data: plugin data is shared since fcopy is not set
- ...
- soap_done(&soap); // detach plugin (calls plugin->fdelete)
-
- You need to define a HTTP GET handling function at the server-side:
-
- int http_get_handler(struct soap*)
-
- which will be called from the plugin upon an HTTP GET request.
- The function should return an error code or SOAP_OK;
- This function should produce a valid HTTP response, for example:
-
- soap_response(soap, SOAP_HTML); // use this to return HTML ...
- soap_response(soap, SOAP_OK); // ... or use this to return a SOAP message
- ...
- soap_send(soap, "<HTML>...</HTML>"); // example HTML
- ...
- soap_end_send(soap);
-
- To get query string key-value pairs within a service routine, use:
-
- char *s;
- s = soap_query(soap);
- while (s)
- {
- char *key = soap_query_key(soap, &s);
- char *val = soap_query_val(soap, &s);
- ...
- }
-
- This will garble soap->path, which contains the HTTP path of the form:
- /path?query
- where path and/or ?query may be absent. The path info is obtained from
- the HTTP request URL: http://domain/path?query
- The URL should not exceed the length of SOAP_TAGLEN. Adjust SOAP_TAGLEN
- in stdsoap2.h if necessary.
-
- Usage (client side):
-
- For SOAP GET method calls, declare the service method with protocol GET:
-
- //gsoap ns service method-protocol: someMethod GET
- int ns__someMethod(... in-params ..., struct ns__someMethodResponse { ... out-params ... } *);
-
- and to make the call in your code:
-
- struct ns__someMethodResponse res;
- soap_call_ns__someMethod(soap, "endpoint", "action", ... in-params ...., &res)
-
- Client code for non-SOAP REST GET:
-
- To use general HTTP GET, for example to retrieve an HTML document, use:
-
- struct soap soap;
- char *buf = NULL;
- size_t len = 0;
- soap_init(&soap);
- if (soap_GET(&soap, endpoint, action)
- || soap_begin_recv(&soap))
- ... connect/recv error ...
- else
- buf = soap_http_get_body(&soap, &len);
- soap_end_recv(&soap);
- ... process data in buf[0..len-1]
- soap_destroy(&soap);
- soap_end(&soap);
- soap_done(&soap);
-
- The soap_http_get_body() function above returns the HTTP body content
- as a string.
-
-*/
-
-#include "httpget.h"
-
-#ifdef __cplusplus
-extern "C" {
-#endif
-
-const char http_get_id[] = HTTP_GET_ID;
-
-static int http_get_init(struct soap *soap, struct http_get_data *data, int (*handler)(struct soap*));
-static void http_get_delete(struct soap *soap, struct soap_plugin *p);
-static int http_get_parse(struct soap *soap);
-static int http_get_handler(struct soap *soap);
-
-int http_get(struct soap *soap, struct soap_plugin *p, void *arg)
-{
- p->id = http_get_id;
- p->data = (void*)SOAP_MALLOC(soap, sizeof(struct http_get_data));
- /* p->fcopy = http_get_copy; obsolete, see note with http_get_copy() */
- p->fdelete = http_get_delete;
- if (!p->data)
- return SOAP_EOM;
- if (http_get_init(soap, (struct http_get_data*)p->data, (int (*)(struct soap*))arg))
- {
- SOAP_FREE(soap, p->data); /* error: could not init */
- return SOAP_EOM; /* return error */
- }
- return SOAP_OK;
-}
-
-static int http_get_init(struct soap *soap, struct http_get_data *data, int (*handler)(struct soap*))
-{
- data->fparse = soap->fparse; /* save old HTTP header parser callback */
- data->fget = handler;
- data->stat_get = 0;
- data->stat_post = 0;
- data->stat_fail = 0;
- memset((void*)data->hist_min, 0, sizeof(data->hist_min));
- memset((void*)data->hist_hour, 0, sizeof(data->hist_hour));
- memset((void*)data->hist_day, 0, sizeof(data->hist_day));
- soap->fparse = http_get_parse; /* replace HTTP header parser callback with ours */
- soap->fget = http_get_handler; /* replace HTTP GET callback with ours */
- return SOAP_OK;
-}
-
-/* We will share the plugin data among all soap copies created with soap_copy(), so we don't have to define this
-static int http_get_copy(struct soap *soap, struct soap_plugin *dst, struct soap_plugin *src)
-{
- *dst = *src;
- return SOAP_OK;
-}
-*/
-
-static void http_get_delete(struct soap *soap, struct soap_plugin *p)
-{
- (void)soap;
- SOAP_FREE(soap, p->data); /* free allocated plugin data (this function is not called for shared plugin data, but only when the final soap_done() is invoked on the original soap struct) */
-}
-
-static int http_get_parse(struct soap *soap)
-{
-#ifndef WITH_LEAN
- time_t t;
-#ifdef HAVE_LOCALTIME_R
- struct tm T;
-#endif
- struct tm *pT;
-#endif
- struct http_get_data *data = (struct http_get_data*)soap_lookup_plugin(soap, http_get_id);
- if (!data)
- return SOAP_PLUGIN_ERROR;
-#ifndef WITH_LEAN
- time(&t);
-#ifdef HAVE_LOCALTIME_R
- pT = localtime_r(&t, &T);
-#else
- pT = localtime(&t);
-#endif
- /* updates should be in mutex, but we don't mind some inaccuracy in the count to preserve performance */
- data->hist_day[pT->tm_yday]++;
- data->hist_day[(pT->tm_yday + 1) % 365] = 0;
- data->hist_hour[pT->tm_hour]++;
- data->hist_hour[(pT->tm_hour + 1) % 24] = 0;
- data->hist_min[pT->tm_min]++;
- data->hist_min[(pT->tm_min + 1) % 60] = 0;
-#endif
- soap->error = data->fparse(soap); /* parse HTTP header */
- if (!soap->error)
- {
- if (soap->status == SOAP_POST)
- data->stat_post++; /* update should be in mutex, but we don't mind some inaccuracy in the count */
- }
- else
- {
- data->stat_fail++; /* update should be in mutex, but we don't mind some inaccuracy in the count */
- }
- return soap->error;
-}
-
-/******************************************************************************/
-
-static int http_get_handler(struct soap *soap)
-{
- struct http_get_data *data = (struct http_get_data*)soap_lookup_plugin(soap, http_get_id);
- if (!data)
- return SOAP_PLUGIN_ERROR;
- soap->error = data->fget(soap); /* call user-defined HTTP GET handler */
- if (soap->error)
- {
- /* update should be in mutex, but we don't mind some inaccuracy in the count */
- data->stat_fail++;
- return soap->error;
- }
- /* update should be in mutex, but we don't mind some inaccuracy in the count */
- data->stat_get++;
- return SOAP_OK;
-}
-
-/******************************************************************************/
-
-void soap_http_get_stats(struct soap *soap, size_t *stat_get, size_t *stat_post, size_t *stat_fail, size_t **hist_min, size_t **hist_hour, size_t **hist_day)
-{
- struct http_get_data *data = (struct http_get_data*)soap_lookup_plugin(soap, http_get_id);
- if (!data)
- return;
- if (stat_get)
- *stat_get = data->stat_get;
- if (stat_post)
- *stat_post = data->stat_post;
- if (stat_fail)
- *stat_fail = data->stat_fail;
- if (hist_min)
- *hist_min = data->hist_min;
- if (hist_hour)
- *hist_hour = data->hist_hour;
- if (hist_day)
- *hist_day = data->hist_day;
-}
-
-/* deprecated: use soap_GET instead */
-int soap_http_get_connect(struct soap *soap, const char *endpoint, const char *action)
-{
- return soap_GET(soap, endpoint, action);
-}
-
-#ifdef __cplusplus
-}
-#endif
-
diff --git a/dep/gsoap/httpget.h b/dep/gsoap/httpget.h
deleted file mode 100644
index 02b8bf24408..00000000000
--- a/dep/gsoap/httpget.h
+++ /dev/null
@@ -1,84 +0,0 @@
-/*
- httpget.h
-
- gSOAP HTTP GET plugin.
-
- See httpget.c for usage instructions.
-
-gSOAP XML Web services tools
-Copyright (C) 2000-2008, Robert van Engelen, Genivia Inc., All Rights Reserved.
-This part of the software is released under ONE of the following licenses:
-GPL or the gSOAP public license.
---------------------------------------------------------------------------------
-gSOAP public license.
-
-The contents of this file are subject to the gSOAP Public License Version 1.3
-(the "License"); you may not use this file except in compliance with the
-License. You may obtain a copy of the License at
-http://www.cs.fsu.edu/~engelen/soaplicense.html
-Software distributed under the License is distributed on an "AS IS" basis,
-WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
-for the specific language governing rights and limitations under the License.
-
-The Initial Developer of the Original Code is Robert A. van Engelen.
-Copyright (C) 2000-2008 Robert A. van Engelen, Genivia inc. All Rights Reserved.
---------------------------------------------------------------------------------
-GPL license.
-
-This program is free software; you can redistribute it and/or modify it under
-the terms of the GNU General Public License as published by the Free Software
-Foundation; either version 2 of the License, or (at your option) any later
-version.
-
-This program is distributed in the hope that it will be useful, but WITHOUT ANY
-WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
-PARTICULAR PURPOSE. See the GNU General Public License for more details.
-
-You should have received a copy of the GNU General Public License along with
-this program; if not, write to the Free Software Foundation, Inc., 59 Temple
-Place, Suite 330, Boston, MA 02111-1307 USA
-
-Author contact information:
-engelen@genivia.com / engelen@acm.org
-
-This program is released under the GPL with the additional exemption that
-compiling, linking, and/or using OpenSSL is allowed.
---------------------------------------------------------------------------------
-*/
-
-#ifndef HTTPGET_H
-#define HTTPGET_H
-
-#include "stdsoap2.h"
-
-#ifdef __cplusplus
-extern "C" {
-#endif
-
-#define HTTP_GET_ID "SOAP-HTTP-GET/2.1" /* plugin identification */
-
-extern const char http_get_id[];
-
-/* This is the local plugin data shared among all copies of the soap struct: */
-struct http_get_data
-{
- int (*fparse)(struct soap*); /* to save and call the internal HTTP header parser */
- int (*fget)(struct soap*); /* user-defined server-side HTTP GET handler */
- size_t stat_get; /* HTTP GET usage statistics */
- size_t stat_post; /* HTTP POST usage statistics */
- size_t stat_fail; /* HTTP failure statistics */
- size_t hist_min[60]; /* Hits by the minute */
- size_t hist_hour[24]; /* Hits by the hour */
- size_t hist_day[366]; /* Hits by day */
-};
-
-int http_get(struct soap*, struct soap_plugin*, void*);
-int soap_http_get_connect(struct soap*, const char*, const char*);
-
-void soap_http_get_stats(struct soap *soap, size_t *stat_get, size_t *stat_post, size_t *stat_fail, size_t **hist_min, size_t **hist_hour, size_t **hist_day);
-
-#ifdef __cplusplus
-}
-#endif
-
-#endif
diff --git a/dep/gsoap/httppost.c b/dep/gsoap/httppost.c
deleted file mode 100644
index 6219ecbf33b..00000000000
--- a/dep/gsoap/httppost.c
+++ /dev/null
@@ -1,368 +0,0 @@
-/*
- httppost.c
-
- gSOAP HTTP POST/PUT/DELETE plugin for SOAP and non-SOAP payloads.
-
- See instructions below.
-
- Revisions:
- register multiple POST content handlers, each for a content type
-
- Note: multipart/related and multipart/form-data are already handled in gSOAP.
-
-gSOAP XML Web services tools
-Copyright (C) 2000-2018, Robert van Engelen, Genivia, Inc. All Rights Reserved.
-
---------------------------------------------------------------------------------
-gSOAP public license.
-
-The contents of this file are subject to the gSOAP Public License Version 1.3
-(the "License"); you may not use this file except in compliance with the
-License. You may obtain a copy of the License at
-http://www.cs.fsu.edu/~engelen/soaplicense.html
-Software distributed under the License is distributed on an "AS IS" basis,
-WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
-for the specific language governing rights and limitations under the License.
-
-The Initial Developer of the Original Code is Robert A. van Engelen.
-Copyright (C) 2000-2018 Robert A. van Engelen, Genivia inc. All Rights Reserved.
---------------------------------------------------------------------------------
-GPL license.
-
-This program is free software; you can redistribute it and/or modify it under
-the terms of the GNU General Public License as published by the Free Software
-Foundation; either version 2 of the License, or (at your option) any later
-version.
-
-This program is distributed in the hope that it will be useful, but WITHOUT ANY
-WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
-PARTICULAR PURPOSE. See the GNU General Public License for more details.
-
-You should have received a copy of the GNU General Public License along with
-this program; if not, write to the Free Software Foundation, Inc., 59 Temple
-Place, Suite 330, Boston, MA 02111-1307 USA
-
-Author contact information:
-engelen@genivia.com / engelen@acm.org
-
-This program is released under the GPL with the additional exemption that
-compiling, linking, and/or using OpenSSL is allowed.
---------------------------------------------------------------------------------
-
- Compile & link with stand-alone gSOAP server.
-
- Usage (server side):
-
- Define a NULL-terminated array of type-handler pairs, each with a media
- type and a the handler function:
-
- struct http_post_handlers my_handlers[] = {
- { "application/json", json_post_handler },
- { "image/jpg", jpeg_post_handler },
- { "text/html", html_post_handler },
- { "POST", generic_POST_handler },
- { "PUT", generic_PUT_handler },
- { "PATCH", generic_PATCH_handler },
- { "DELETE", generic_DELETE_handler },
- { NULL }
- };
-
- A `generic_POST_handler`, when specified with a `"POST"` key entry in
- the table, takes priority over `soap_serve()` if no `SOAPAction` HTTP
- header is included in the message. This means that SOAP/XML messages
- without a `SOAPAction` header will not be processed by `soap_serve()`!
-
- Note that `*` and `-` can be used as wildcards to match any text and
- any character, respectively:
-
- { "image*", image_post_handler },
- { "text*", text_post_handler },
- { "text/---", three_char_type_text_handler },
-
- In the above, to be more accurate, we should use a slash / between
- image and the wildcard * (which is not shown in the table above due to
- compilers throwing a fit at the / and * combo in this comment block).
-
- It is possible to specify other paramters that must match:
-
- { "text/html; charset=utf-8", html_post_handler },
-
- Media types may have optional parameters after `;` such as `charset`
- and `boundary`. These parameters can be matched by the media type
- patterns in the table. Patterns that are more specific must precede
- patterns that are less specific in the table. For example,
- `"text/xml;*charset=utf-8*"` must precede `"text/xml"` which must
- precede `"text*"`. Note that `"text/xml"` also matches any parameters
- of the media type of the message reveived, such as `"text/xml;
- charset=utf-8"` (only since gSOAP version 2.8.75).
-
- Each handler is a function that takes the soap context as a parameter
- and returns SOAP_OK or an error code.
-
- Register the plugin and the handlers:
-
- struct soap soap;
- soap_init(&soap);
- soap_register_plugin_arg(&soap, http_post, my_handlers);
- ...
- ... = soap_copy(&soap); // copies plugin too but not its data: plugin data is shared since fcopy is not set
- ...
- soap_done(&soap); // delete plugin (calls plugin->fdelete)
-
- A POST handler function is triggered by the media type in the
- http_post_handlers table. Use http_http_get_body() as below to retrieve
- HTTP POST body data:
-
- int image_post_handler(struct soap *soap)
- {
- const char *buf;
- size_t len;
- // if necessary, check type in soap->http_content
- if (soap->http_content && soap_tag_cmp(soap->http_content, "image/gif"))
- return 404;
- buf = soap_http_get_body(soap, &len);
- if (!buf)
- return soap->error;
- soap_end_recv(soap);
- ... // process image in buf[0..len-1]
- // reply with empty HTTP OK response:
- return soap_send_empty_response(soap, SOAP_OK);
- }
-
- This function should also produce a valid HTTP response, for example:
-
- if (we want to return HTML)
- {
- if (soap_response(soap, SOAP_HTML) // use this to return HTML...
- || soap_send(soap, "<HTML>...</HTML>"); // example HTML
- || ...
- || soap_end_send(soap))
- return soap_closesock(soap);
- return SOAP_OK;
- }
- else
- {
- soap->http_content = "image/jpeg"; // a jpeg image
- if (soap_response(soap, SOAP_FILE) // SOAP_FILE sets custom http content
- || soap_send_raw(soap, ..., ...);
- || ...
- || soap_end_send(soap))
- return soap_closesock(soap);
- return SOAP_OK;
- }
-
- The soap_closesock() call only closes the connection when it shoud not
- be kept alive. This function is called automatically after the handler
- returns. There is no harm in calling this function more than once.
-
- The soap_send(soap, char*) and soap_send_raw(soap, char*, size_t) can
- be used to return content from server.
-
- The soap_response(soap, SOAP_FILE) call returns a HTTP 200 OK response
- with the HTTP content-type header set to soap->http_content. To return
- a http error status code use soap_response(soap, SOAP_FILE + status)
- with status between 200 and 599 or 0.
-
- Usage (client side):
-
- char *buf; // to hold the HTTP response body data
- size_t len;
- ...
- if (soap_post_connect(soap, "URL", "SOAP action or NULL", "media type")
- || soap_send(soap, ...)
- || soap_end_send(soap))
- ... // error
- if (soap_begin_recv(&soap)
- || soap_http_body(&soap, &buf, &len)
- || soap_end_recv(&soap))
- ... // error
- ... // use buf[0..len-1]
- soap_closesock(soap); // close, but keep open only with HTTP keep-alive
- soap_end(soap); // also deletes buf content
-
- The soap_send(soap, char*) and soap_send_raw(soap, char*, size_t) can
- be used to send content to the server as shown above.
-
-*/
-
-#include "httppost.h"
-
-#ifdef __cplusplus
-extern "C" {
-#endif
-
-const char http_post_id[] = HTTP_POST_ID;
-
-static int http_post_init(struct soap *soap, struct http_post_data *data, struct http_post_handlers *handlers);
-static void http_post_delete(struct soap *soap, struct soap_plugin *p);
-static int http_post_parse_header(struct soap *soap, const char*, const char*);
-static http_handler_t http_lookup_handler(struct soap *soap, const char *type, struct http_post_data *data);
-
-static int http_fput(struct soap *soap);
-static int http_fpatch(struct soap *soap);
-static int http_fdel(struct soap *soap);
-
-int http_post(struct soap *soap, struct soap_plugin *p, void *arg)
-{
- p->id = http_post_id;
- p->data = (void*)SOAP_MALLOC(soap, sizeof(struct http_post_data));
- p->fdelete = http_post_delete;
- if (!p->data)
- return SOAP_EOM;
- if (http_post_init(soap, (struct http_post_data*)p->data, (struct http_post_handlers *)arg))
- {
- SOAP_FREE(soap, p->data); /* error: could not init */
- return SOAP_EOM; /* return error */
- }
- return SOAP_OK;
-}
-
-static int http_post_init(struct soap *soap, struct http_post_data *data, struct http_post_handlers *handlers)
-{
- data->fparsehdr = soap->fparsehdr; /* save old HTTP header parser callback */
- soap->fparsehdr = http_post_parse_header; /* replace HTTP header parser callback with ours */
- data->fput = soap->fput;
- soap->fput = http_fput;
- data->fpatch = soap->fpatch;
- soap->fpatch = http_fpatch;
- data->fdel = soap->fdel;
- soap->fdel = http_fdel;
- data->handlers = handlers;
- return SOAP_OK;
-}
-
-static void http_post_delete(struct soap *soap, struct soap_plugin *p)
-{
- soap->fparsehdr = ((struct http_post_data*)p->data)->fparsehdr;
- soap->fput = ((struct http_post_data*)p->data)->fput;
- soap->fpatch = ((struct http_post_data*)p->data)->fpatch;
- soap->fdel = ((struct http_post_data*)p->data)->fdel;
- SOAP_FREE(soap, p->data); /* free allocated plugin data (this function is not called for shared plugin data, but only when the final soap_done() is invoked on the original soap struct) */
-}
-
-static int http_post_parse_header(struct soap *soap, const char *key, const char *val)
-{
- struct http_post_data *data = (struct http_post_data*)soap_lookup_plugin(soap, http_post_id);
- if (!data)
- return SOAP_PLUGIN_ERROR;
- if (data->fparsehdr(soap, key, val)) /* parse HTTP header */
- return soap->error;
- if (!soap_tag_cmp(key, "Content-Type")) /* check content type */
- soap->fform = http_lookup_handler(soap, val, data);
- if (!soap->fform)
- soap->fform = http_lookup_handler(soap, "POST", data);
- return soap->error;
-}
-
-static http_handler_t http_lookup_handler(struct soap *soap, const char *type, struct http_post_data *data)
-{
- struct http_post_handlers *p;
- const char *params = strchr(type, ';');
- char temp[SOAP_HDRLEN];
- (void)soap;
- if (params)
- {
- size_t n = params - type;
- soap_strncpy(temp, sizeof(temp), type, n);
- temp[n] = '\0';
- }
- for (p = data->handlers; p && p->type; p++)
- {
- if (params)
- {
- if (strchr(p->type, ';'))
- {
- if (!soap_tag_cmp(type, p->type))
- break;
- }
- else if (!soap_tag_cmp(temp, p->type))
- {
- break;
- }
- }
- else if (!soap_tag_cmp(type, p->type))
- {
- break;
- }
- }
- if (p && p->type)
- {
- DBGLOG(TEST,SOAP_MESSAGE(fdebug, "Found HTTP POST plugin handler for '%s'\n", type));
- return p->handler;
- }
- return NULL;
-}
-
-static int http_fput(struct soap *soap)
-{
- int (*fform)(struct soap*);
- struct http_post_data *data = (struct http_post_data*)soap_lookup_plugin(soap, http_post_id);
- if (!data)
- return SOAP_PLUGIN_ERROR;
- fform = http_lookup_handler(soap, "PUT", data);
- if (fform)
- return fform(soap);
- return data->fput(soap);
-}
-
-static int http_fpatch(struct soap *soap)
-{
- int (*fform)(struct soap*);
- struct http_post_data *data = (struct http_post_data*)soap_lookup_plugin(soap, http_post_id);
- if (!data)
- return SOAP_PLUGIN_ERROR;
- fform = http_lookup_handler(soap, "PATCH", data);
- if (fform)
- return fform(soap);
- return data->fpatch(soap);
-}
-
-static int http_fdel(struct soap *soap)
-{
- int (*fform)(struct soap*);
- struct http_post_data *data = (struct http_post_data*)soap_lookup_plugin(soap, http_post_id);
- if (!data)
- return SOAP_PLUGIN_ERROR;
- fform = http_lookup_handler(soap, "DELETE", data);
- if (fform)
- return fform(soap);
- return data->fdel(soap);
-}
-
-/******************************************************************************/
-
-/* call soap_http_get_body to receive HTTP body and set buf and len */
-int soap_http_body(struct soap *soap, char **buf, size_t *len)
-{
- *buf = soap_http_get_body(soap, len);
- if (*buf)
- return SOAP_OK;
- return soap->error;
-}
-
-/******************************************************************************/
-
-/* deprecated: use soap_POST instead */
-int soap_post_connect(struct soap *soap, const char *endpoint, const char *action, const char *type)
-{
- return soap_POST(soap, endpoint, action, type);
-}
-
-/* deprecated: use soap_PUT instead */
-int soap_put_connect(struct soap *soap, const char *endpoint, const char *action, const char *type)
-{
- return soap_PUT(soap, endpoint, action, type);
-}
-
-/* deprecated: use soap_DELETE instead */
-int soap_delete_connect(struct soap *soap, const char *endpoint)
-{
- return soap_DELETE(soap, endpoint);
-}
-
-/******************************************************************************/
-
-#ifdef __cplusplus
-}
-#endif
-
diff --git a/dep/gsoap/httppost.h b/dep/gsoap/httppost.h
deleted file mode 100644
index 9747ff0a923..00000000000
--- a/dep/gsoap/httppost.h
+++ /dev/null
@@ -1,101 +0,0 @@
-/*
- httppost.h
-
- gSOAP HTTP POST plugin for non-SOAP payloads.
-
- See httppost.c for instructions.
-
- Revisions:
- register multiple POST content handlers, each for a content type
-
-gSOAP XML Web services tools
-Copyright (C) 2000-2018, Robert van Engelen, Genivia, Inc. All Rights Reserved.
-
---------------------------------------------------------------------------------
-gSOAP public license.
-
-The contents of this file are subject to the gSOAP Public License Version 1.3
-(the "License"); you may not use this file except in compliance with the
-License. You may obtain a copy of the License at
-http://www.cs.fsu.edu/~engelen/soaplicense.html
-Software distributed under the License is distributed on an "AS IS" basis,
-WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
-for the specific language governing rights and limitations under the License.
-
-The Initial Developer of the Original Code is Robert A. van Engelen.
-Copyright (C) 2000-2004 Robert A. van Engelen, Genivia inc. All Rights Reserved.
---------------------------------------------------------------------------------
-GPL license.
-
-This program is free software; you can redistribute it and/or modify it under
-the terms of the GNU General Public License as published by the Free Software
-Foundation; either version 2 of the License, or (at your option) any later
-version.
-
-This program is distributed in the hope that it will be useful, but WITHOUT ANY
-WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
-PARTICULAR PURPOSE. See the GNU General Public License for more details.
-
-You should have received a copy of the GNU General Public License along with
-this program; if not, write to the Free Software Foundation, Inc., 59 Temple
-Place, Suite 330, Boston, MA 02111-1307 USA
-
-Author contact information:
-engelen@genivia.com / engelen@acm.org
-
-This program is released under the GPL with the additional exemption that
-compiling, linking, and/or using OpenSSL is allowed.
---------------------------------------------------------------------------------
-*/
-
-#ifndef HTTPPOST_H
-#define HTTPPOST_H
-
-#include "stdsoap2.h"
-
-#ifdef __cplusplus
-extern "C" {
-#endif
-
-#define HTTP_POST_ID "SOAP-HTTP-POST/2.2" /* plugin identification */
-
-extern const char http_post_id[];
-
-typedef int (*http_handler_t)(struct soap*);
-
-struct http_post_handlers
-{
- const char *type;
- http_handler_t handler;
-};
-
-/* This is the local plugin data shared among all copies of the soap struct: */
-struct http_post_data
-{
- int (*fparsehdr)(struct soap*, const char*, const char*); /* to save and call the internal HTTP header parser */
- int (*fput)(struct soap*); /* to save */
- int (*fpatch)(struct soap*); /* to save */
- int (*fdel)(struct soap*); /* to save */
- struct http_post_handlers *handlers; /* the server-side POST content type handlers */
-};
-
-/* the http post plugin, note: argument should be a table of type-handler pairs */
-int http_post(struct soap*, struct soap_plugin*, void*);
-
-/* deprecated: use soap_POST instead */
-int soap_post_connect(struct soap*, const char *endpoint, const char *action, const char *type);
-
-/* deprecated: use soap_PUT instead */
-int soap_put_connect(struct soap*, const char *endpoint, const char *action, const char *type);
-
-/* deprecated: use soap_DELETE instead */
-int soap_delete_connect(struct soap*, const char *endpoint);
-
-/* deprecated: use soap_get_http_body instead */
-int soap_http_body(struct soap*, char **buf, size_t *len);
-
-#ifdef __cplusplus
-}
-#endif
-
-#endif
diff --git a/src/common/Cryptography/Authentication/SRP6.cpp b/src/common/Cryptography/Authentication/SRP6.cpp
index a2266f35f71..c172bbc1d3d 100644
--- a/src/common/Cryptography/Authentication/SRP6.cpp
+++ b/src/common/Cryptography/Authentication/SRP6.cpp
@@ -24,12 +24,7 @@
using SHA1 = Trinity::Crypto::SHA1;
using SRP6 = Trinity::Crypto::SRP6;
-/*static*/ std::array<uint8, 1> const SRP6::g = []()
-{
- std::array<uint8, 1> g_temp;
- g_temp[0] = 7;
- return g_temp;
-}();
+/*static*/ std::array<uint8, 1> const SRP6::g = { 7 };
/*static*/ std::array<uint8, 32> const SRP6::N = HexStrToByteArray<32>("894B645E89E1535BBDAD5B8B290650530801B18EBFBF5E8FAB3C82872A3E9BB7", true);
/*static*/ BigNumber const SRP6::_g(SRP6::g);
/*static*/ BigNumber const SRP6::_N(N);
diff --git a/src/common/Utilities/Concepts.h b/src/common/Utilities/Concepts.h
new file mode 100644
index 00000000000..4cfa52c029c
--- /dev/null
+++ b/src/common/Utilities/Concepts.h
@@ -0,0 +1,33 @@
+/*
+ * This file is part of the TrinityCore Project. See AUTHORS file for Copyright information
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef TRINITYCORE_CONCEPTS_H
+#define TRINITYCORE_CONCEPTS_H
+
+#include <concepts>
+#include <functional> // std::invoke
+
+namespace Trinity
+{
+template <typename Callable, typename R, typename... Args>
+concept invocable_r = requires(Callable && callable, Args&&... args)
+{
+ { std::invoke(static_cast<Callable&&>(callable), static_cast<Args&&>(args)...) } -> std::convertible_to<R>;
+};
+}
+
+#endif // TRINITYCORE_CONCEPTS_H
diff --git a/src/server/bnetserver/Main.cpp b/src/server/bnetserver/Main.cpp
index e1168a1a90b..6d667cd48b1 100644
--- a/src/server/bnetserver/Main.cpp
+++ b/src/server/bnetserver/Main.cpp
@@ -200,21 +200,29 @@ int main(int argc, char** argv)
Trinity::Net::ScanLocalNetworks();
- // Start the listening port (acceptor) for auth connections
- int32 bnport = sConfigMgr->GetIntDefault("BattlenetPort", 1119);
- if (bnport < 0 || bnport > 0xFFFF)
+ std::string httpBindIp = sConfigMgr->GetStringDefault("BindIP", "0.0.0.0");
+ int32 httpPort = sConfigMgr->GetIntDefault("LoginREST.Port", 8081);
+ if (httpPort <= 0 || httpPort > 0xFFFF)
{
- TC_LOG_ERROR("server.bnetserver", "Specified battle.net port ({}) out of allowed range (1-65535)", bnport);
+ TC_LOG_ERROR("server.bnetserver", "Specified login service port ({}) out of allowed range (1-65535)", httpPort);
return 1;
}
- if (!sLoginService.Start(ioContext.get()))
+ if (!sLoginService.StartNetwork(*ioContext, httpBindIp, httpPort))
{
TC_LOG_ERROR("server.bnetserver", "Failed to initialize login service");
return 1;
}
- std::shared_ptr<void> sLoginServiceHandle(nullptr, [](void*) { sLoginService.Stop(); });
+ std::shared_ptr<void> sLoginServiceHandle(nullptr, [](void*) { sLoginService.StopNetwork(); });
+
+ // Start the listening port (acceptor) for auth connections
+ int32 bnport = sConfigMgr->GetIntDefault("BattlenetPort", 1119);
+ if (bnport <= 0 || bnport > 0xFFFF)
+ {
+ TC_LOG_ERROR("server.bnetserver", "Specified battle.net port ({}) out of allowed range (1-65535)", bnport);
+ return 1;
+ }
// Get the list of realms for the server
sRealmList->Initialize(*ioContext, sConfigMgr->GetIntDefault("RealmsStateUpdateDelay", 10));
diff --git a/src/server/bnetserver/REST/LoginHttpSession.cpp b/src/server/bnetserver/REST/LoginHttpSession.cpp
new file mode 100644
index 00000000000..95112cb8836
--- /dev/null
+++ b/src/server/bnetserver/REST/LoginHttpSession.cpp
@@ -0,0 +1,118 @@
+/*
+ * This file is part of the TrinityCore Project. See AUTHORS file for Copyright information
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "LoginHttpSession.h"
+#include "DatabaseEnv.h"
+#include "LoginRESTService.h"
+#include "SslContext.h"
+#include "Util.h"
+
+namespace Battlenet
+{
+LoginHttpSession::LoginHttpSession(boost::asio::ip::tcp::socket&& socket)
+ : SslSocket(std::move(socket), SslContext::instance())
+{
+}
+
+LoginHttpSession::~LoginHttpSession() = default;
+
+void LoginHttpSession::Start()
+{
+ std::string ip_address = GetRemoteIpAddress().to_string();
+ TC_LOG_TRACE("server.http.session", "{} Accepted connection", GetClientInfo());
+
+ // Verify that this IP is not in the ip_banned table
+ LoginDatabase.Execute(LoginDatabase.GetPreparedStatement(LOGIN_DEL_EXPIRED_IP_BANS));
+
+ LoginDatabasePreparedStatement* stmt = LoginDatabase.GetPreparedStatement(LOGIN_SEL_IP_INFO);
+ stmt->setString(0, ip_address);
+
+ _queryProcessor.AddCallback(LoginDatabase.AsyncQuery(stmt)
+ .WithPreparedCallback([sess = shared_from_this()](PreparedQueryResult result) { sess->CheckIpCallback(std::move(result)); }));
+}
+
+void LoginHttpSession::CheckIpCallback(PreparedQueryResult result)
+{
+ if (result)
+ {
+ bool banned = false;
+ do
+ {
+ Field* fields = result->Fetch();
+ if (fields[0].GetUInt64() != 0)
+ banned = true;
+
+ } while (result->NextRow());
+
+ if (banned)
+ {
+ TC_LOG_DEBUG("server.http.session", "{} tries to log in using banned IP!", GetClientInfo());
+ CloseSocket();
+ return;
+ }
+ }
+
+ AsyncHandshake();
+}
+
+Trinity::Net::Http::RequestHandlerResult LoginHttpSession::RequestHandler(Trinity::Net::Http::RequestContext& context)
+{
+ return sLoginService.HandleRequest(shared_from_this(), context);
+}
+
+std::shared_ptr<Trinity::Net::Http::SessionState> LoginHttpSession::ObtainSessionState(Trinity::Net::Http::RequestContext& context) const
+{
+ using namespace std::string_literals;
+
+ std::shared_ptr<Trinity::Net::Http::SessionState> state;
+
+ auto cookieItr = context.request.find(boost::beast::http::field::cookie);
+ if (cookieItr != context.request.end())
+ {
+ std::vector<std::string_view> cookies = Trinity::Tokenize(Trinity::Net::Http::ToStdStringView(cookieItr->value()), ';', false);
+ std::size_t eq = 0;
+ auto sessionIdItr = std::find_if(cookies.begin(), cookies.end(), [&](std::string_view cookie)
+ {
+ std::string_view name = cookie;
+ eq = cookie.find('=');
+ if (eq != std::string_view::npos)
+ name = cookie.substr(0, eq);
+
+ return name == SESSION_ID_COOKIE;
+ });
+ if (sessionIdItr != cookies.end())
+ {
+ std::string_view value = sessionIdItr->substr(eq + 1);
+ state = sLoginService.FindAndRefreshSessionState(value, GetRemoteIpAddress());
+ }
+ }
+
+ if (!state)
+ {
+ state = sLoginService.CreateNewSessionState(GetRemoteIpAddress());
+
+ std::string_view host = Trinity::Net::Http::ToStdStringView(context.request[boost::beast::http::field::host]);
+ if (std::size_t port = host.find(':'); port != std::string_view::npos)
+ host.remove_suffix(host.length() - port);
+
+ context.response.insert(boost::beast::http::field::set_cookie, Trinity::StringFormat("{}={}; Path=/bnetserver; Domain={}; Secure; HttpOnly; SameSite=None",
+ SESSION_ID_COOKIE, boost::uuids::to_string(state->Id), host));
+ }
+
+ return state;
+}
+}
diff --git a/src/server/bnetserver/REST/LoginHttpSession.h b/src/server/bnetserver/REST/LoginHttpSession.h
new file mode 100644
index 00000000000..17c94b55bda
--- /dev/null
+++ b/src/server/bnetserver/REST/LoginHttpSession.h
@@ -0,0 +1,43 @@
+/*
+ * This file is part of the TrinityCore Project. See AUTHORS file for Copyright information
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef TRINITYCORE_LOGIN_HTTP_SESSION_H
+#define TRINITYCORE_LOGIN_HTTP_SESSION_H
+
+#include "HttpSslSocket.h"
+
+namespace Battlenet
+{
+class LoginHttpSession : public Trinity::Net::Http::SslSocket<LoginHttpSession>
+{
+public:
+ static constexpr std::string_view SESSION_ID_COOKIE = "JSESSIONID";
+
+ explicit LoginHttpSession(boost::asio::ip::tcp::socket&& socket);
+ ~LoginHttpSession();
+
+ void Start() override;
+
+ void CheckIpCallback(PreparedQueryResult result);
+
+ Trinity::Net::Http::RequestHandlerResult RequestHandler(Trinity::Net::Http::RequestContext& context) override;
+
+protected:
+ std::shared_ptr<Trinity::Net::Http::SessionState> ObtainSessionState(Trinity::Net::Http::RequestContext& context) const override;
+};
+}
+#endif // TRINITYCORE_LOGIN_HTTP_SESSION_H
diff --git a/src/server/bnetserver/REST/LoginRESTService.cpp b/src/server/bnetserver/REST/LoginRESTService.cpp
index 6141dc702f9..602a38dceed 100644
--- a/src/server/bnetserver/REST/LoginRESTService.cpp
+++ b/src/server/bnetserver/REST/LoginRESTService.cpp
@@ -16,71 +16,63 @@
*/
#include "LoginRESTService.h"
+#include "Base64.h"
#include "Configuration/Config.h"
#include "CryptoHash.h"
#include "CryptoRandom.h"
#include "DatabaseEnv.h"
-#include "Errors.h"
#include "IpNetwork.h"
+#include "IteratorPair.h"
#include "ProtobufJSON.h"
#include "Resolver.h"
-#include "SslContext.h"
#include "Util.h"
-#include "httpget.h"
-#include "httppost.h"
-#include "soapH.h"
+#include <boost/uuid/string_generator.hpp>
+#include <fmt/chrono.h>
-int ns1__executeCommand(soap*, char*, char**) { return SOAP_OK; }
+namespace Battlenet
+{
+LoginRESTService& LoginRESTService::Instance()
+{
+ static LoginRESTService instance;
+ return instance;
+}
-class AsyncRequest
+bool LoginRESTService::StartNetwork(Trinity::Asio::IoContext& ioContext, std::string const& bindIp, uint16 port, int32 threadCount)
{
-public:
- AsyncRequest(soap const& server) : _client(server), _responseStatus(0) { }
+ if (!HttpService::StartNetwork(ioContext, bindIp, port, threadCount))
+ return false;
- AsyncRequest(AsyncRequest const&) = delete;
- AsyncRequest& operator=(AsyncRequest const&) = delete;
- AsyncRequest(AsyncRequest&&) = default;
- AsyncRequest& operator=(AsyncRequest&&) = default;
+ using Trinity::Net::Http::RequestHandlerFlag;
- bool InvokeIfReady()
+ RegisterHandler(boost::beast::http::verb::get, "/bnetserver/login/", [this](std::shared_ptr<LoginHttpSession> session, HttpRequestContext& context)
{
- ASSERT(_callback);
- return _callback->InvokeIfReady();
- }
-
- soap* GetClient() { return &_client; }
- void SetCallback(std::unique_ptr<QueryCallback> callback) { _callback = std::move(callback); }
- int32 GetResponseStatus() const { return _responseStatus; }
- void SetResponseStatus(int32 responseStatus) { _responseStatus = responseStatus; }
+ return HandleGetForm(std::move(session), context);
+ });
-private:
- soap _client;
- std::unique_ptr<QueryCallback> _callback;
- int32 _responseStatus;
-};
+ RegisterHandler(boost::beast::http::verb::get, "/bnetserver/gameAccounts/", [this](std::shared_ptr<LoginHttpSession> session, HttpRequestContext& context)
+ {
+ return HandleGetGameAccounts(std::move(session), context);
+ });
-int32 handle_get_plugin(soap* soapClient)
-{
- return sLoginService.HandleHttpRequest(soapClient, "GET", sLoginService._getHandlers);
-}
+ RegisterHandler(boost::beast::http::verb::get, "/bnetserver/portal/", [this](std::shared_ptr<LoginHttpSession> session, HttpRequestContext& context)
+ {
+ return HandleGetPortal(std::move(session), context);
+ });
-int32 handle_post_plugin(soap* soapClient)
-{
- return sLoginService.HandleHttpRequest(soapClient, "POST", sLoginService._postHandlers);
-}
+ RegisterHandler(boost::beast::http::verb::post, "/bnetserver/login/", [this](std::shared_ptr<LoginHttpSession> session, HttpRequestContext& context)
+ {
+ return HandlePostLogin(std::move(session), context);
+ }, RequestHandlerFlag::DoNotLogRequestContent);
-bool LoginRESTService::Start(Trinity::Asio::IoContext* ioContext)
-{
- _ioContext = ioContext;
- _bindIP = sConfigMgr->GetStringDefault("BindIP", "0.0.0.0");
- _port = sConfigMgr->GetIntDefault("LoginREST.Port", 8081);
- if (_port < 0 || _port > 0xFFFF)
+ RegisterHandler(boost::beast::http::verb::post, "/bnetserver/refreshLoginTicket/", [this](std::shared_ptr<LoginHttpSession> session, HttpRequestContext& context)
{
- TC_LOG_ERROR("server.rest", "Specified login service port ({}) out of allowed range (1-65535), defaulting to 8081", _port);
- _port = 8081;
- }
+ return HandlePostRefreshLoginTicket(std::move(session), context);
+ });
- Trinity::Asio::Resolver resolver(*ioContext);
+ _bindIP = bindIp;
+ _port = port;
+
+ Trinity::Asio::Resolver resolver(ioContext);
_hostnames[0] = sConfigMgr->GetStringDefault("LoginREST.ExternalAddress", "127.0.0.1");
Optional<boost::asio::ip::tcp::endpoint> externalAddress = resolver.Resolve(boost::asio::ip::tcp::v4(), _hostnames[0], std::to_string(_port));
@@ -103,8 +95,8 @@ bool LoginRESTService::Start(Trinity::Asio::IoContext* ioContext)
_addresses[1] = localAddress->address();
// set up form inputs
- Battlenet::JSON::Login::FormInput* input;
- _formInputs.set_type(Battlenet::JSON::Login::LOGIN_FORM);
+ JSON::Login::FormInput* input;
+ _formInputs.set_type(JSON::Login::LOGIN_FORM);
input = _formInputs.add_inputs();
input->set_input_id("account_name");
input->set_type("text");
@@ -124,16 +116,10 @@ bool LoginRESTService::Start(Trinity::Asio::IoContext* ioContext)
_loginTicketDuration = sConfigMgr->GetIntDefault("LoginREST.TicketDuration", 3600);
- _thread = std::thread(&LoginRESTService::Run, this);
+ _acceptor->AsyncAcceptWithCallback<&LoginRESTService::OnSocketAccept>();
return true;
}
-void LoginRESTService::Stop()
-{
- _stopped = true;
- _thread.join();
-}
-
std::string const& LoginRESTService::GetHostnameForClient(boost::asio::ip::address const& address) const
{
if (auto addressIndex = Trinity::Net::SelectAddressForClient(address, _addresses))
@@ -145,117 +131,53 @@ std::string const& LoginRESTService::GetHostnameForClient(boost::asio::ip::addre
return _hostnames[0];
}
-void LoginRESTService::Run()
+std::string LoginRESTService::ExtractAuthorization(HttpRequest const& request)
{
- soap soapServer(SOAP_C_UTFSTRING, SOAP_C_UTFSTRING);
-
- // check every 3 seconds if world ended
- soapServer.accept_timeout = 3;
- soapServer.recv_timeout = 5;
- soapServer.send_timeout = 5;
- if (!soap_valid_socket(soap_bind(&soapServer, _bindIP.c_str(), _port, 100)))
- {
- TC_LOG_ERROR("server.rest", "Couldn't bind to {}:{}", _bindIP, _port);
- return;
- }
-
- TC_LOG_INFO("server.rest", "Login service bound to http://{}:{}", _bindIP, _port);
-
- http_post_handlers handlers[] =
- {
- { "application/json;charset=utf-8", handle_post_plugin },
- { "application/json", handle_post_plugin },
- { nullptr, nullptr }
- };
+ using namespace std::string_view_literals;
- _getHandlers["/bnetserver/login/"] = &LoginRESTService::HandleGetForm;
- _getHandlers["/bnetserver/gameAccounts/"] = &LoginRESTService::HandleGetGameAccounts;
- _getHandlers["/bnetserver/portal/"] = &LoginRESTService::HandleGetPortal;
+ std::string ticket;
+ auto itr = request.find(boost::beast::http::field::authorization);
+ if (itr == request.end())
+ return ticket;
- _postHandlers["/bnetserver/login/"] = &LoginRESTService::HandlePostLogin;
- _postHandlers["/bnetserver/refreshLoginTicket/"] = &LoginRESTService::HandlePostRefreshLoginTicket;
+ std::string_view authorization = Trinity::Net::Http::ToStdStringView(itr->value());
+ constexpr std::string_view BASIC_PREFIX = "Basic "sv;
- soap_register_plugin_arg(&soapServer, &http_get, (void*)&handle_get_plugin);
- soap_register_plugin_arg(&soapServer, &http_post, handlers);
- soap_register_plugin_arg(&soapServer, &ContentTypePlugin::Init, (void*)"application/json;charset=utf-8");
- soap_register_plugin_arg(&soapServer, &ResponseCodePlugin::Init, nullptr);
+ if (authorization.starts_with(BASIC_PREFIX))
+ authorization.remove_prefix(BASIC_PREFIX.length());
- // Use our already ready ssl context
- soapServer.ctx = Battlenet::SslContext::instance().native_handle();
- soapServer.ssl_flags = SOAP_SSL_RSA;
+ Optional<std::vector<uint8>> decoded = Trinity::Encoding::Base64::Decode(authorization);
+ if (!decoded)
+ return ticket;
- while (!_stopped)
- {
- if (!soap_valid_socket(soap_accept(&soapServer)))
- continue; // ran into an accept timeout
-
- std::shared_ptr<AsyncRequest> soapClient = std::make_shared<AsyncRequest>(soapServer);
- if (soap_ssl_accept(soapClient->GetClient()) != SOAP_OK)
- {
- TC_LOG_DEBUG("server.rest", "Failed SSL handshake from IP={}", boost::asio::ip::address_v4(soapClient->GetClient()->ip).to_string());
- continue;
- }
-
- TC_LOG_DEBUG("server.rest", "Accepted connection from IP={}", boost::asio::ip::address_v4(soapClient->GetClient()->ip).to_string());
-
- Trinity::Asio::post(*_ioContext, [soapClient]()
- {
- soapClient->GetClient()->user = (void*)&soapClient; // this allows us to make a copy of pointer inside GET/POST handlers to increment reference count
- soap_begin(soapClient->GetClient());
- soap_begin_recv(soapClient->GetClient());
- });
- }
-
- // and release the context handle here - soap does not own it so it should not free it on exit
- soapServer.ctx = nullptr;
-
- TC_LOG_INFO("server.rest", "Login service exiting...");
-}
-
-int32 LoginRESTService::HandleHttpRequest(soap* soapClient, char const* method, HttpMethodHandlerMap const& handlers)
-{
- TC_LOG_DEBUG("server.rest", "[{}:{}] Handling {} request path=\"{}\"",
- boost::asio::ip::address_v4(soapClient->ip).to_string(), soapClient->port, method, soapClient->path);
-
- size_t pathLength = strlen(soapClient->path);
- if (char const* queryPart = strchr(soapClient->path, '?'))
- pathLength = queryPart - soapClient->path;
+ std::string_view decodedHeader(reinterpret_cast<char const*>(decoded->data()), decoded->size());
- auto handler = handlers.find(std::string{ soapClient->path, pathLength });
- if (handler != handlers.end())
- {
- int32 status = (this->*handler->second)(*reinterpret_cast<std::shared_ptr<AsyncRequest>*>(soapClient->user));
- if (status != SOAP_OK)
- {
- ResponseCodePlugin::GetForClient(soapClient)->ErrorCode = status;
- return SendResponse(soapClient, Battlenet::JSON::Login::ErrorResponse());
- }
+ if (std::size_t ticketEnd = decodedHeader.find(':'); ticketEnd != std::string_view::npos)
+ decodedHeader.remove_suffix(decodedHeader.length() - ticketEnd);
- return SOAP_OK;
- }
-
- ResponseCodePlugin::GetForClient(soapClient)->ErrorCode = 404;
- return SendResponse(soapClient, Battlenet::JSON::Login::ErrorResponse());
+ ticket = decodedHeader;
+ return ticket;
}
-int32 LoginRESTService::HandleGetForm(std::shared_ptr<AsyncRequest> request)
+LoginRESTService::RequestHandlerResult LoginRESTService::HandleGetForm(std::shared_ptr<LoginHttpSession> /*session*/, HttpRequestContext& context)
{
- return SendResponse(request->GetClient(), _formInputs);
+ context.response.set(boost::beast::http::field::content_type, "application/json;charset=utf-8");
+ context.response.body() = ::JSON::Serialize(_formInputs);
+ return RequestHandlerResult::Handled;
}
-int32 LoginRESTService::HandleGetGameAccounts(std::shared_ptr<AsyncRequest> request)
+LoginRESTService::RequestHandlerResult LoginRESTService::HandleGetGameAccounts(std::shared_ptr<LoginHttpSession> session, HttpRequestContext& context)
{
- if (!request->GetClient()->userid)
- return 401;
-
- request->SetCallback(std::make_unique<QueryCallback>(LoginDatabase.AsyncQuery([&] {
- LoginDatabasePreparedStatement* stmt = LoginDatabase.GetPreparedStatement(LOGIN_SEL_BNET_GAME_ACCOUNT_LIST);
- stmt->setString(0, request->GetClient()->userid);
- return stmt;
- }())
- .WithPreparedCallback([this, request](PreparedQueryResult result)
+ std::string ticket = ExtractAuthorization(context.request);
+ if (ticket.empty())
+ return HandleUnauthorized(std::move(session), context);
+
+ LoginDatabasePreparedStatement* stmt = LoginDatabase.GetPreparedStatement(LOGIN_SEL_BNET_GAME_ACCOUNT_LIST);
+ stmt->setString(0, ticket);
+ session->QueueQuery(LoginDatabase.AsyncQuery(stmt)
+ .WithPreparedCallback([session, context = std::move(context)](PreparedQueryResult result) mutable
{
- Battlenet::JSON::Login::GameAccountList response;
+ JSON::Login::GameAccountList gameAccounts;
if (result)
{
auto formatDisplayName = [](char const* name) -> std::string
@@ -270,7 +192,7 @@ int32 LoginRESTService::HandleGetGameAccounts(std::shared_ptr<AsyncRequest> requ
do
{
Field* fields = result->Fetch();
- Battlenet::JSON::Login::GameAccountInfo* gameAccount = response.add_game_accounts();
+ JSON::Login::GameAccountInfo* gameAccount = gameAccounts.add_game_accounts();
gameAccount->set_display_name(formatDisplayName(fields[0].GetCString()));
gameAccount->set_expansion(fields[1].GetUInt8());
if (!fields[2].IsNull())
@@ -285,40 +207,37 @@ int32 LoginRESTService::HandleGetGameAccounts(std::shared_ptr<AsyncRequest> requ
} while (result->NextRow());
}
- SendResponse(request->GetClient(), response);
- })));
-
- Trinity::Asio::post(*_ioContext, [this, request]() { HandleAsyncRequest(request); });
+ context.response.set(boost::beast::http::field::content_type, "application/json;charset=utf-8");
+ context.response.body() = ::JSON::Serialize(gameAccounts);
+ session->SendResponse(context);
+ }));
- return SOAP_OK;
+ return RequestHandlerResult::Async;
}
-int32 LoginRESTService::HandleGetPortal(std::shared_ptr<AsyncRequest> request)
+LoginRESTService::RequestHandlerResult LoginRESTService::HandleGetPortal(std::shared_ptr<LoginHttpSession> session, HttpRequestContext& context)
{
- std::string const& hostname = GetHostnameForClient(boost::asio::ip::address_v4(request->GetClient()->ip));
- std::string response = Trinity::StringFormat("{}:{}", hostname, sConfigMgr->GetIntDefault("BattlenetPort", 1119));
-
- soap_response(request->GetClient(), SOAP_FILE);
- soap_send_raw(request->GetClient(), response.c_str(), response.length());
- return soap_end_send(request->GetClient());
+ context.response.set(boost::beast::http::field::content_type, "text/plain");
+ context.response.body() = Trinity::StringFormat("{}:{}", GetHostnameForClient(session->GetRemoteIpAddress()), sConfigMgr->GetIntDefault("BattlenetPort", 1119));
+ return RequestHandlerResult::Handled;
}
-int32 LoginRESTService::HandlePostLogin(std::shared_ptr<AsyncRequest> request)
+LoginRESTService::RequestHandlerResult LoginRESTService::HandlePostLogin(std::shared_ptr<LoginHttpSession> session, HttpRequestContext& context)
{
- char* buf = nullptr;
- size_t len = 0;
- soap_http_body(request->GetClient(), &buf, &len);
-
- Battlenet::JSON::Login::LoginForm loginForm;
- if (!buf || !JSON::Deserialize(buf, &loginForm))
+ JSON::Login::LoginForm loginForm;
+ if (!::JSON::Deserialize(context.request.body(), &loginForm))
{
- ResponseCodePlugin::GetForClient(request->GetClient())->ErrorCode = 400;
-
- Battlenet::JSON::Login::LoginResult loginResult;
- loginResult.set_authentication_state(Battlenet::JSON::Login::LOGIN);
+ JSON::Login::LoginResult loginResult;
+ loginResult.set_authentication_state(JSON::Login::LOGIN);
loginResult.set_error_code("UNABLE_TO_DECODE");
loginResult.set_error_message("There was an internal error while connecting to Battle.net. Please try again later.");
- return SendResponse(request->GetClient(), loginResult);
+
+ context.response.result(boost::beast::http::status::bad_request);
+ context.response.set(boost::beast::http::field::content_type, "application/json;charset=utf-8");
+ context.response.body() = ::JSON::Serialize(loginResult);
+ session->SendResponse(context);
+
+ return RequestHandlerResult::Handled;
}
std::string login;
@@ -340,44 +259,32 @@ int32 LoginRESTService::HandlePostLogin(std::shared_ptr<AsyncRequest> request)
std::string sentPasswordHash = CalculateShaPassHash(login, password);
- request->SetCallback(std::make_unique<QueryCallback>(LoginDatabase.AsyncQuery(stmt)
- .WithChainingPreparedCallback([request, login, sentPasswordHash, this](QueryCallback& callback, PreparedQueryResult result)
+ session->QueueQuery(LoginDatabase.AsyncQuery(stmt)
+ .WithChainingPreparedCallback([this, session, context = std::move(context), login = std::move(login), sentPasswordHash = std::move(sentPasswordHash)](QueryCallback& callback, PreparedQueryResult result) mutable
{
- if (result)
+ if (!result)
{
- Field* fields = result->Fetch();
- uint32 accountId = fields[0].GetUInt32();
- std::string pass_hash = fields[1].GetString();
- uint32 failedLogins = fields[2].GetUInt32();
- std::string loginTicket = fields[3].GetString();
- uint32 loginTicketExpiry = fields[4].GetUInt32();
- bool isBanned = fields[5].GetUInt64() != 0;
-
- if (sentPasswordHash == pass_hash)
- {
- if (loginTicket.empty() || loginTicketExpiry < time(nullptr))
- {
- std::array<uint8, 20> ticket = Trinity::Crypto::GetRandomBytes<20>();
+ JSON::Login::LoginResult loginResult;
+ loginResult.set_authentication_state(JSON::Login::DONE);
+ context.response.set(boost::beast::http::field::content_type, "application/json;charset=utf-8");
+ context.response.body() = ::JSON::Serialize(loginResult);
+ session->SendResponse(context);
+ return;
+ }
- loginTicket = "TC-" + ByteArrayToHexStr(ticket);
- }
+ Field* fields = result->Fetch();
+ uint32 accountId = fields[0].GetUInt32();
+ std::string pass_hash = fields[1].GetString();
+ uint32 failedLogins = fields[2].GetUInt32();
+ std::string loginTicket = fields[3].GetString();
+ uint32 loginTicketExpiry = fields[4].GetUInt32();
+ bool isBanned = fields[5].GetUInt64() != 0;
- LoginDatabasePreparedStatement* stmt = LoginDatabase.GetPreparedStatement(LOGIN_UPD_BNET_AUTHENTICATION);
- stmt->setString(0, loginTicket);
- stmt->setUInt32(1, time(nullptr) + _loginTicketDuration);
- stmt->setUInt32(2, accountId);
- callback.WithPreparedCallback([request, loginTicket](PreparedQueryResult)
- {
- Battlenet::JSON::Login::LoginResult loginResult;
- loginResult.set_authentication_state(Battlenet::JSON::Login::DONE);
- loginResult.set_login_ticket(loginTicket);
- sLoginService.SendResponse(request->GetClient(), loginResult);
- }).SetNextQuery(LoginDatabase.AsyncQuery(stmt));
- return;
- }
- else if (!isBanned)
+ if (sentPasswordHash != pass_hash)
+ {
+ if (!isBanned)
{
- std::string ip_address = boost::asio::ip::address_v4(request->GetClient()->ip).to_string();
+ std::string ip_address = session->GetRemoteIpAddress().to_string();
uint32 maxWrongPassword = uint32(sConfigMgr->GetIntDefault("WrongPass.MaxCount", 0));
if (sConfigMgr->GetBoolDefault("WrongPass.Logging", false))
@@ -421,31 +328,50 @@ int32 LoginRESTService::HandlePostLogin(std::shared_ptr<AsyncRequest> request)
LoginDatabase.CommitTransaction(trans);
}
}
+
+ JSON::Login::LoginResult loginResult;
+ loginResult.set_authentication_state(JSON::Login::DONE);
+
+ context.response.set(boost::beast::http::field::content_type, "application/json;charset=utf-8");
+ context.response.body() = ::JSON::Serialize(loginResult);
+ session->SendResponse(context);
+ return;
}
- Battlenet::JSON::Login::LoginResult loginResult;
- loginResult.set_authentication_state(Battlenet::JSON::Login::DONE);
- sLoginService.SendResponse(request->GetClient(), loginResult);
- })));
+ if (loginTicket.empty() || loginTicketExpiry < time(nullptr))
+ loginTicket = "TC-" + ByteArrayToHexStr(Trinity::Crypto::GetRandomBytes<20>());
- Trinity::Asio::post(*_ioContext, [this, request]() { HandleAsyncRequest(request); });
+ LoginDatabasePreparedStatement* stmt = LoginDatabase.GetPreparedStatement(LOGIN_UPD_BNET_AUTHENTICATION);
+ stmt->setString(0, loginTicket);
+ stmt->setUInt32(1, time(nullptr) + _loginTicketDuration);
+ stmt->setUInt32(2, accountId);
+ callback.WithPreparedCallback([session, context = std::move(context), loginTicket = std::move(loginTicket)](PreparedQueryResult) mutable
+ {
+ JSON::Login::LoginResult loginResult;
+ loginResult.set_authentication_state(JSON::Login::DONE);
+ loginResult.set_login_ticket(loginTicket);
+
+ context.response.set(boost::beast::http::field::content_type, "application/json;charset=utf-8");
+ context.response.body() = ::JSON::Serialize(loginResult);
+ session->SendResponse(context);
+ }).SetNextQuery(LoginDatabase.AsyncQuery(stmt));
+ }));
- return SOAP_OK;
+ return RequestHandlerResult::Async;
}
-int32 LoginRESTService::HandlePostRefreshLoginTicket(std::shared_ptr<AsyncRequest> request)
+LoginRESTService::RequestHandlerResult LoginRESTService::HandlePostRefreshLoginTicket(std::shared_ptr<LoginHttpSession> session, HttpRequestContext& context)
{
- if (!request->GetClient()->userid)
- return 401;
-
- request->SetCallback(std::make_unique<QueryCallback>(LoginDatabase.AsyncQuery([&] {
- LoginDatabasePreparedStatement* stmt = LoginDatabase.GetPreparedStatement(LOGIN_SEL_BNET_EXISTING_AUTHENTICATION);
- stmt->setString(0, request->GetClient()->userid);
- return stmt;
- }())
- .WithPreparedCallback([this, request](PreparedQueryResult result)
+ std::string ticket = ExtractAuthorization(context.request);
+ if (ticket.empty())
+ return HandleUnauthorized(std::move(session), context);
+
+ LoginDatabasePreparedStatement* stmt = LoginDatabase.GetPreparedStatement(LOGIN_SEL_BNET_EXISTING_AUTHENTICATION);
+ stmt->setString(0, ticket);
+ session->QueueQuery(LoginDatabase.AsyncQuery(stmt)
+ .WithPreparedCallback([this, session, context = std::move(context), ticket = std::move(ticket)](PreparedQueryResult result) mutable
{
- Battlenet::JSON::Login::LoginRefreshResult loginRefreshResult;
+ JSON::Login::LoginRefreshResult loginRefreshResult;
if (result)
{
uint32 loginTicketExpiry = (*result)[0].GetUInt32();
@@ -456,7 +382,7 @@ int32 LoginRESTService::HandlePostRefreshLoginTicket(std::shared_ptr<AsyncReques
LoginDatabasePreparedStatement* stmt = LoginDatabase.GetPreparedStatement(LOGIN_UPD_BNET_EXISTING_AUTHENTICATION);
stmt->setUInt32(0, uint32(now + _loginTicketDuration));
- stmt->setString(1, request->GetClient()->userid);
+ stmt->setString(1, ticket);
LoginDatabase.Execute(stmt);
}
else
@@ -465,34 +391,12 @@ int32 LoginRESTService::HandlePostRefreshLoginTicket(std::shared_ptr<AsyncReques
else
loginRefreshResult.set_is_expired(true);
- SendResponse(request->GetClient(), loginRefreshResult);
- })));
-
- Trinity::Asio::post(*_ioContext, [this, request]() { HandleAsyncRequest(request); });
-
- return SOAP_OK;
-}
-
-int32 LoginRESTService::SendResponse(soap* soapClient, google::protobuf::Message const& response)
-{
- std::string jsonResponse = JSON::Serialize(response);
-
- soap_response(soapClient, SOAP_FILE);
- soap_send_raw(soapClient, jsonResponse.c_str(), jsonResponse.length());
- return soap_end_send(soapClient);
-}
+ context.response.set(boost::beast::http::field::content_type, "application/json;charset=utf-8");
+ context.response.body() = ::JSON::Serialize(loginRefreshResult);
+ session->SendResponse(context);
+ }));
-void LoginRESTService::HandleAsyncRequest(std::shared_ptr<AsyncRequest> request)
-{
- if (!request->InvokeIfReady())
- {
- Trinity::Asio::post(*_ioContext, [this, request]() { HandleAsyncRequest(request); });
- }
- else if (request->GetResponseStatus())
- {
- ResponseCodePlugin::GetForClient(request->GetClient())->ErrorCode = request->GetResponseStatus();
- SendResponse(request->GetClient(), Battlenet::JSON::Login::ErrorResponse());
- }
+ return RequestHandlerResult::Async;
}
std::string LoginRESTService::CalculateShaPassHash(std::string const& name, std::string const& password)
@@ -510,85 +414,8 @@ std::string LoginRESTService::CalculateShaPassHash(std::string const& name, std:
return ByteArrayToHexStr(sha.GetDigest(), true);
}
-Namespace namespaces[] =
-{
- { nullptr, nullptr, nullptr, nullptr }
-};
-
-LoginRESTService& LoginRESTService::Instance()
-{
- static LoginRESTService instance;
- return instance;
-}
-
-char const* const LoginRESTService::ResponseCodePlugin::PluginId = "bnet-error-code";
-
-int32 LoginRESTService::ResponseCodePlugin::Init(soap* s, soap_plugin* p, void* /*arg*/)
-{
- ResponseCodePlugin* data = new ResponseCodePlugin();
- data->fresponse = s->fresponse;
-
- p->id = PluginId;
- p->fcopy = &Copy;
- p->fdelete = &Destroy;
- p->data = data;
-
- s->fresponse = &ChangeResponse;
- return SOAP_OK;
-}
-
-int32 LoginRESTService::ResponseCodePlugin::Copy(soap* /*s*/, soap_plugin* dst, soap_plugin* src)
-{
- dst->data = new ResponseCodePlugin(*reinterpret_cast<ResponseCodePlugin*>(src->data));
- return SOAP_OK;
-}
-
-void LoginRESTService::ResponseCodePlugin::Destroy(soap* s, soap_plugin* p)
-{
- ResponseCodePlugin* data = reinterpret_cast<ResponseCodePlugin*>(p->data);
- s->fresponse = data->fresponse;
- delete data;
-}
-
-int32 LoginRESTService::ResponseCodePlugin::ChangeResponse(soap* s, int32 originalResponse, uint64 contentLength)
-{
- ResponseCodePlugin* self = reinterpret_cast<ResponseCodePlugin*>(soap_lookup_plugin(s, PluginId));
- return self->fresponse(s, self->ErrorCode && originalResponse == SOAP_FILE ? self->ErrorCode : originalResponse, contentLength);
-}
-
-LoginRESTService::ResponseCodePlugin* LoginRESTService::ResponseCodePlugin::GetForClient(soap* s)
-{
- return ASSERT_NOTNULL(reinterpret_cast<ResponseCodePlugin*>(soap_lookup_plugin(s, PluginId)));
-}
-
-char const* const LoginRESTService::ContentTypePlugin::PluginId = "bnet-content-type";
-
-int32 LoginRESTService::ContentTypePlugin::Init(soap* s, soap_plugin* p, void* arg)
-{
- ContentTypePlugin* data = new ContentTypePlugin();
- data->fposthdr = s->fposthdr;
- data->ContentType = reinterpret_cast<char const*>(arg);
-
- p->id = PluginId;
- p->fdelete = &Destroy;
- p->data = data;
-
- s->fposthdr = &OnSetHeader;
- return SOAP_OK;
-}
-
-void LoginRESTService::ContentTypePlugin::Destroy(soap* s, soap_plugin* p)
+void LoginRESTService::OnSocketAccept(boost::asio::ip::tcp::socket&& sock, uint32 threadIndex)
{
- ContentTypePlugin* data = reinterpret_cast<ContentTypePlugin*>(p->data);
- s->fposthdr = data->fposthdr;
- delete data;
+ sLoginService.OnSocketOpen(std::move(sock), threadIndex);
}
-
-int32 LoginRESTService::ContentTypePlugin::OnSetHeader(soap* s, char const* key, char const* value)
-{
- ContentTypePlugin* self = reinterpret_cast<ContentTypePlugin*>(soap_lookup_plugin(s, PluginId));
- if (key && !strcmp("Content-Type", key))
- value = self->ContentType;
-
- return self->fposthdr(s, key, value);
}
diff --git a/src/server/bnetserver/REST/LoginRESTService.h b/src/server/bnetserver/REST/LoginRESTService.h
index f783fea243a..1313493e023 100644
--- a/src/server/bnetserver/REST/LoginRESTService.h
+++ b/src/server/bnetserver/REST/LoginRESTService.h
@@ -18,98 +18,59 @@
#ifndef LoginRESTService_h__
#define LoginRESTService_h__
-#include "Define.h"
-#include "IoContext.h"
+#include "HttpService.h"
#include "Login.pb.h"
-#include "Session.h"
-#include <boost/asio/ip/tcp.hpp>
-#include <atomic>
-#include <thread>
-
-class AsyncRequest;
-struct soap;
-struct soap_plugin;
+#include "LoginHttpSession.h"
+namespace Battlenet
+{
enum class BanMode
{
BAN_IP = 0,
BAN_ACCOUNT = 1
};
-class LoginRESTService
+class LoginRESTService : public Trinity::Net::Http::HttpService<LoginHttpSession>
{
public:
- LoginRESTService() : _ioContext(nullptr), _stopped(false), _port(0), _loginTicketDuration(0) { }
+ using RequestHandlerResult = Trinity::Net::Http::RequestHandlerResult;
+ using HttpRequest = Trinity::Net::Http::Request;
+ using HttpResponse = Trinity::Net::Http::Response;
+ using HttpRequestContext = Trinity::Net::Http::RequestContext;
+ using HttpSessionState = Trinity::Net::Http::SessionState;
+
+ LoginRESTService() : HttpService("login"), _port(0), _loginTicketDuration(0) { }
static LoginRESTService& Instance();
- bool Start(Trinity::Asio::IoContext* ioContext);
- void Stop();
+ bool StartNetwork(Trinity::Asio::IoContext& ioContext, std::string const& bindIp, uint16 port, int32 threadCount = 1) override;
std::string const& GetHostnameForClient(boost::asio::ip::address const& address) const;
- int32 GetPort() const { return _port; }
+ uint16 GetPort() const { return _port; }
private:
- void Run();
-
- friend int32 handle_get_plugin(soap* soapClient);
- friend int32 handle_post_plugin(soap* soapClient);
-
- using HttpMethodHandlerMap = std::unordered_map<std::string, int32(LoginRESTService::*)(std::shared_ptr<AsyncRequest>)>;
- int32 HandleHttpRequest(soap* soapClient, char const* method, HttpMethodHandlerMap const& handlers);
-
- int32 HandleGetForm(std::shared_ptr<AsyncRequest> request);
- int32 HandleGetGameAccounts(std::shared_ptr<AsyncRequest> request);
- int32 HandleGetPortal(std::shared_ptr<AsyncRequest> request);
+ static void OnSocketAccept(boost::asio::ip::tcp::socket&& sock, uint32 threadIndex);
- int32 HandlePostLogin(std::shared_ptr<AsyncRequest> request);
- int32 HandlePostRefreshLoginTicket(std::shared_ptr<AsyncRequest> request);
+ static std::string ExtractAuthorization(HttpRequest const& request);
- int32 SendResponse(soap* soapClient, google::protobuf::Message const& response);
+ RequestHandlerResult HandleGetForm(std::shared_ptr<LoginHttpSession> session, HttpRequestContext& context);
+ RequestHandlerResult HandleGetGameAccounts(std::shared_ptr<LoginHttpSession> session, HttpRequestContext& context);
+ RequestHandlerResult HandleGetPortal(std::shared_ptr<LoginHttpSession> session, HttpRequestContext& context);
- void HandleAsyncRequest(std::shared_ptr<AsyncRequest> request);
+ RequestHandlerResult HandlePostLogin(std::shared_ptr<LoginHttpSession> session, HttpRequestContext& context);
+ RequestHandlerResult HandlePostRefreshLoginTicket(std::shared_ptr<LoginHttpSession> session, HttpRequestContext& context);
- std::string CalculateShaPassHash(std::string const& name, std::string const& password);
+ static std::string CalculateShaPassHash(std::string const& name, std::string const& password);
- struct ResponseCodePlugin
- {
- static char const* const PluginId;
- static int32 Init(soap* s, soap_plugin*, void*);
- static int32 Copy(soap* s, soap_plugin* dst, soap_plugin* src);
- static void Destroy(soap* s, soap_plugin* p);
- static int32 ChangeResponse(soap* s, int32 originalResponse, uint64 contentLength);
-
- static ResponseCodePlugin* GetForClient(soap* s);
-
- int32(*fresponse)(soap* s, int32 status, uint64 length);
- int32 ErrorCode;
- };
-
- struct ContentTypePlugin
- {
- static char const* const PluginId;
- static int32 Init(soap* s, soap_plugin* p, void*);
- static void Destroy(soap* s, soap_plugin* p);
- static int32 OnSetHeader(soap* s, char const* key, char const* value);
-
- int32(*fposthdr)(soap* s, char const* key, char const* value);
- char const* ContentType;
- };
-
- Trinity::Asio::IoContext* _ioContext;
- std::thread _thread;
- std::atomic<bool> _stopped;
- Battlenet::JSON::Login::FormInputs _formInputs;
+ JSON::Login::FormInputs _formInputs;
std::string _bindIP;
- int32 _port;
+ uint16 _port;
std::array<std::string, 2> _hostnames;
std::array<boost::asio::ip::address, 2> _addresses;
uint32 _loginTicketDuration;
-
- HttpMethodHandlerMap _getHandlers;
- HttpMethodHandlerMap _postHandlers;
};
+}
-#define sLoginService LoginRESTService::Instance()
+#define sLoginService Battlenet::LoginRESTService::Instance()
#endif // LoginRESTService_h__
diff --git a/src/server/bnetserver/Server/Session.cpp b/src/server/bnetserver/Server/Session.cpp
index ea5256ed635..b450df14e1d 100644
--- a/src/server/bnetserver/Server/Session.cpp
+++ b/src/server/bnetserver/Server/Session.cpp
@@ -30,6 +30,7 @@
#include "RealmList.h"
#include "RealmList.pb.h"
#include "ServiceDispatcher.h"
+#include "SslContext.h"
#include "Timezone.h"
#include <rapidjson/document.h>
#include <zlib.h>
@@ -73,7 +74,8 @@ void Battlenet::Session::GameAccountInfo::LoadResult(Field const* fields)
DisplayName = Name;
}
-Battlenet::Session::Session(boost::asio::ip::tcp::socket&& socket) : BattlenetSocket(std::move(socket)), _accountInfo(new AccountInfo()), _gameAccountInfo(nullptr), _locale(),
+Battlenet::Session::Session(boost::asio::ip::tcp::socket&& socket) : BattlenetSocket(std::move(socket), SslContext::instance()),
+ _accountInfo(new AccountInfo()), _gameAccountInfo(nullptr), _locale(),
_os(), _build(0), _timezoneOffset(0min), _ipCountry(), _clientSecret(), _authed(false), _requestToken(0)
{
_headerLengthBuffer.Resize(2);
diff --git a/src/server/bnetserver/Server/Session.h b/src/server/bnetserver/Server/Session.h
index 6d22c202615..33a058f940c 100644
--- a/src/server/bnetserver/Server/Session.h
+++ b/src/server/bnetserver/Server/Session.h
@@ -20,11 +20,10 @@
#include "AsyncCallbackProcessor.h"
#include "Duration.h"
+#include "QueryResult.h"
#include "Realm.h"
-#include "SslContext.h"
-#include "SslSocket.h"
#include "Socket.h"
-#include "QueryResult.h"
+#include "SslSocket.h"
#include <boost/asio/ip/tcp.hpp>
#include <google/protobuf/message.h>
#include <memory>
@@ -65,9 +64,9 @@ using namespace bgs::protocol;
namespace Battlenet
{
- class Session : public Socket<Session, SslSocket<SslContext>>
+ class Session : public Socket<Session, SslSocket<>>
{
- typedef Socket<Session, SslSocket<SslContext>> BattlenetSocket;
+ typedef Socket<Session, SslSocket<>> BattlenetSocket;
public:
struct LastPlayedCharacterInfo
diff --git a/src/server/database/Database/DatabaseWorkerPool.cpp b/src/server/database/Database/DatabaseWorkerPool.cpp
index 6e9f8a8df1a..9cc4f755d9d 100644
--- a/src/server/database/Database/DatabaseWorkerPool.cpp
+++ b/src/server/database/Database/DatabaseWorkerPool.cpp
@@ -35,6 +35,7 @@
#include "MySQLWorkaround.h"
#include <boost/asio/use_future.hpp>
#include <mysqld_error.h>
+#include <utility>
#ifdef TRINITY_DEBUG
#include <sstream>
#include <boost/stacktrace.hpp>
diff --git a/src/server/shared/CMakeLists.txt b/src/server/shared/CMakeLists.txt
index 66f4ff56fd2..040381b946f 100644
--- a/src/server/shared/CMakeLists.txt
+++ b/src/server/shared/CMakeLists.txt
@@ -45,8 +45,7 @@ target_link_libraries(shared
database
rapidjson
proto
- zlib
- gsoap)
+ zlib)
set_target_properties(shared
PROPERTIES
diff --git a/src/server/shared/Networking/Http/BaseHttpSocket.cpp b/src/server/shared/Networking/Http/BaseHttpSocket.cpp
new file mode 100644
index 00000000000..ca92c442aa2
--- /dev/null
+++ b/src/server/shared/Networking/Http/BaseHttpSocket.cpp
@@ -0,0 +1,115 @@
+/*
+ * This file is part of the TrinityCore Project. See AUTHORS file for Copyright information
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "BaseHttpSocket.h"
+#include <boost/beast/http/serializer.hpp>
+
+namespace Trinity::Net::Http
+{
+using RequestSerializer = boost::beast::http::request_serializer<ResponseBody>;
+using ResponseSerializer = boost::beast::http::response_serializer<ResponseBody>;
+
+bool AbstractSocket::ParseRequest(MessageBuffer& packet, RequestParser& parser)
+{
+ if (!parser.is_done())
+ {
+ // need more data in the payload
+ boost::system::error_code ec = {};
+ std::size_t readDataSize = parser.put(boost::asio::const_buffer(packet.GetReadPointer(), packet.GetActiveSize()), ec);
+ packet.ReadCompleted(readDataSize);
+ }
+
+ return parser.is_done();
+}
+
+std::string AbstractSocket::SerializeRequest(Request const& request)
+{
+ RequestSerializer serializer(request);
+
+ std::string buffer;
+ while (!serializer.is_done())
+ {
+ size_t totalBytes = 0;
+ boost::system::error_code ec = {};
+ serializer.next(ec, [&]<typename ConstBufferSequence>(boost::system::error_code const&, ConstBufferSequence const& buffers)
+ {
+ size_t totalBytesInBuffers = boost::asio::buffer_size(buffers);
+
+ buffer.reserve(buffer.size() + totalBytes);
+
+ auto begin = boost::asio::buffers_begin(buffers);
+ auto end = boost::asio::buffers_end(buffers);
+
+ std::copy(begin, end, std::back_inserter(buffer));
+ totalBytes += totalBytesInBuffers;
+ });
+
+ serializer.consume(totalBytes);
+ }
+
+ return buffer;
+}
+
+MessageBuffer AbstractSocket::SerializeResponse(Request const& request, Response& response)
+{
+ response.prepare_payload();
+
+ ResponseSerializer serializer(response);
+ bool (*serializerIsDone)(ResponseSerializer&);
+ if (request.method() != boost::beast::http::verb::head)
+ {
+ serializerIsDone = [](ResponseSerializer& s) { return s.is_done(); };
+ }
+ else
+ {
+ serializerIsDone = [](ResponseSerializer& s) { return s.is_header_done(); };
+ serializer.split(true);
+ }
+
+ MessageBuffer buffer;
+ while (!serializerIsDone(serializer))
+ {
+ serializer.limit(buffer.GetRemainingSpace());
+
+ size_t totalBytes = 0;
+ boost::system::error_code ec = {};
+ serializer.next(ec, [&]<typename ConstBufferSequence>(boost::system::error_code& currentError, ConstBufferSequence const& buffers)
+ {
+ size_t totalBytesInBuffers = boost::asio::buffer_size(buffers);
+ if (totalBytesInBuffers > buffer.GetRemainingSpace())
+ {
+ currentError = boost::beast::http::error::need_more;
+ return;
+ }
+
+ auto begin = boost::asio::buffers_begin(buffers);
+ auto end = boost::asio::buffers_end(buffers);
+
+ std::copy(begin, end, buffer.GetWritePointer());
+ buffer.WriteCompleted(totalBytesInBuffers);
+ totalBytes += totalBytesInBuffers;
+ });
+
+ serializer.consume(totalBytes);
+
+ if (ec == boost::beast::http::error::need_more)
+ buffer.Resize(buffer.GetBufferSize() + 4096);
+ }
+
+ return buffer;
+}
+}
diff --git a/src/server/shared/Networking/Http/BaseHttpSocket.h b/src/server/shared/Networking/Http/BaseHttpSocket.h
new file mode 100644
index 00000000000..330287252d2
--- /dev/null
+++ b/src/server/shared/Networking/Http/BaseHttpSocket.h
@@ -0,0 +1,191 @@
+/*
+ * This file is part of the TrinityCore Project. See AUTHORS file for Copyright information
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef TRINITYCORE_BASE_HTTP_SOCKET_H
+#define TRINITYCORE_BASE_HTTP_SOCKET_H
+
+#include "AsyncCallbackProcessor.h"
+#include "DatabaseEnvFwd.h"
+#include "HttpCommon.h"
+#include "HttpSessionState.h"
+#include "Optional.h"
+#include "QueryCallback.h"
+#include "Socket.h"
+#include <boost/asio/buffers_iterator.hpp>
+#include <boost/beast/http/parser.hpp>
+#include <boost/beast/http/string_body.hpp>
+#include <boost/uuid/uuid_io.hpp>
+
+namespace Trinity::Net::Http
+{
+using RequestParser = boost::beast::http::request_parser<RequestBody>;
+
+class TC_SHARED_API AbstractSocket
+{
+public:
+ AbstractSocket() = default;
+ AbstractSocket(AbstractSocket const& other) = default;
+ AbstractSocket(AbstractSocket&& other) = default;
+ AbstractSocket& operator=(AbstractSocket const& other) = default;
+ AbstractSocket& operator=(AbstractSocket&& other) = default;
+ virtual ~AbstractSocket() = default;
+
+ static bool ParseRequest(MessageBuffer& packet, RequestParser& parser);
+
+ static std::string SerializeRequest(Request const& request);
+ static MessageBuffer SerializeResponse(Request const& request, Response& response);
+
+ virtual void SendResponse(RequestContext& context) = 0;
+
+ virtual void QueueQuery(QueryCallback&& queryCallback) = 0;
+
+ virtual std::string GetClientInfo() const = 0;
+
+ virtual Optional<boost::uuids::uuid> GetSessionId() const = 0;
+};
+
+template<typename Derived, typename Stream>
+class BaseSocket : public ::Socket<Derived, Stream>, public AbstractSocket
+{
+ using Base = ::Socket<Derived, Stream>;
+
+public:
+ template<typename... Args>
+ explicit BaseSocket(boost::asio::ip::tcp::socket&& socket, Args&&... args)
+ : Base(std::move(socket), std::forward<Args>(args)...) { }
+
+ BaseSocket(BaseSocket const& other) = delete;
+ BaseSocket(BaseSocket&& other) = delete;
+ BaseSocket& operator=(BaseSocket const& other) = delete;
+ BaseSocket& operator=(BaseSocket&& other) = delete;
+
+ ~BaseSocket() = default;
+
+ void ReadHandler() override
+ {
+ if (!this->IsOpen())
+ return;
+
+ MessageBuffer& packet = this->GetReadBuffer();
+ while (packet.GetActiveSize() > 0)
+ {
+ if (!ParseRequest(packet, *_httpParser))
+ {
+ // Couldn't receive the whole data this time.
+ break;
+ }
+
+ if (!HandleMessage(_httpParser->get()))
+ {
+ this->CloseSocket();
+ break;
+ }
+
+ this->ResetHttpParser();
+ }
+
+ this->AsyncRead();
+ }
+
+ bool HandleMessage(Request& request)
+ {
+ RequestContext context { .request = std::move(request) };
+
+ if (!_state)
+ _state = this->ObtainSessionState(context);
+
+ RequestHandlerResult status = this->RequestHandler(context);
+
+ if (status != RequestHandlerResult::Async)
+ this->SendResponse(context);
+
+ return status != RequestHandlerResult::Error;
+ }
+
+ virtual RequestHandlerResult RequestHandler(RequestContext& context) = 0;
+
+ void SendResponse(RequestContext& context) override
+ {
+ MessageBuffer buffer = SerializeResponse(context.request, context.response);
+
+ TC_LOG_DEBUG("server.http", "{} Request {} {} done, status {}", this->GetClientInfo(), ToStdStringView(context.request.method_string()),
+ ToStdStringView(context.request.target()), context.response.result_int());
+ if (sLog->ShouldLog("server.http", LOG_LEVEL_TRACE))
+ {
+ sLog->OutMessage("server.http", LOG_LEVEL_TRACE, "{} Request: ", this->GetClientInfo(),
+ CanLogRequestContent(context) ? SerializeRequest(context.request) : "<REDACTED>");
+ sLog->OutMessage("server.http", LOG_LEVEL_TRACE, "{} Response: ", this->GetClientInfo(),
+ CanLogResponseContent(context) ? std::string_view(reinterpret_cast<char const*>(buffer.GetBasePointer()), buffer.GetActiveSize()) : "<REDACTED>");
+ }
+
+ this->QueuePacket(std::move(buffer));
+
+ if (!context.response.keep_alive())
+ this->DelayedCloseSocket();
+ }
+
+ void QueueQuery(QueryCallback&& queryCallback) override
+ {
+ this->_queryProcessor.AddCallback(std::move(queryCallback));
+ }
+
+ bool Update() override
+ {
+ if (!this->Base::Update())
+ return false;
+
+ this->_queryProcessor.ProcessReadyCallbacks();
+ return true;
+ }
+
+ std::string GetClientInfo() const override
+ {
+ std::string info;
+ info.reserve(500);
+ auto itr = StringFormatTo(std::back_inserter(info), "[{}:{}", this->GetRemoteIpAddress().to_string(), this->GetRemotePort());
+ if (_state)
+ itr = StringFormatTo(itr, ", Session Id: {}", boost::uuids::to_string(_state->Id));
+
+ StringFormatTo(itr, "]");
+ return info;
+ }
+
+ Optional<boost::uuids::uuid> GetSessionId() const final
+ {
+ if (this->_state)
+ return this->_state->Id;
+
+ return {};
+ }
+
+protected:
+ void ResetHttpParser()
+ {
+ this->_httpParser.reset();
+ this->_httpParser.emplace();
+ this->_httpParser->eager(true);
+ }
+
+ virtual std::shared_ptr<SessionState> ObtainSessionState(RequestContext& context) const = 0;
+
+ QueryCallbackProcessor _queryProcessor;
+ Optional<RequestParser> _httpParser;
+ std::shared_ptr<SessionState> _state;
+};
+}
+
+#endif // TRINITYCORE_BASE_HTTP_SOCKET_H
diff --git a/src/server/shared/Networking/Http/HttpCommon.h b/src/server/shared/Networking/Http/HttpCommon.h
new file mode 100644
index 00000000000..5f6ecb6c147
--- /dev/null
+++ b/src/server/shared/Networking/Http/HttpCommon.h
@@ -0,0 +1,55 @@
+/*
+ * This file is part of the TrinityCore Project. See AUTHORS file for Copyright information
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef TRINITYCORE_HTTP_COMMON_H
+#define TRINITYCORE_HTTP_COMMON_H
+
+#include "Define.h"
+#include <boost/beast/http/message.hpp>
+#include <boost/beast/http/string_body.hpp>
+
+namespace Trinity::Net::Http
+{
+using RequestBody = boost::beast::http::string_body;
+using ResponseBody = boost::beast::http::string_body;
+
+using Request = boost::beast::http::request<RequestBody>;
+using Response = boost::beast::http::response<ResponseBody>;
+
+struct RequestContext
+{
+ Request request;
+ Response response;
+ struct RequestHandler const* handler = nullptr;
+};
+
+TC_SHARED_API bool CanLogRequestContent(RequestContext const& context);
+TC_SHARED_API bool CanLogResponseContent(RequestContext const& context);
+
+inline std::string_view ToStdStringView(boost::beast::string_view bsw)
+{
+ return { bsw.data(), bsw.size() };
+}
+
+enum class RequestHandlerResult
+{
+ Handled,
+ Error,
+ Async,
+};
+}
+#endif // TRINITYCORE_HTTP_COMMON_H
diff --git a/src/server/shared/Networking/Http/HttpService.cpp b/src/server/shared/Networking/Http/HttpService.cpp
new file mode 100644
index 00000000000..8995b65612a
--- /dev/null
+++ b/src/server/shared/Networking/Http/HttpService.cpp
@@ -0,0 +1,258 @@
+/*
+ * This file is part of the TrinityCore Project. See AUTHORS file for Copyright information
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "HttpService.h"
+#include "BaseHttpSocket.h"
+#include "CryptoRandom.h"
+#include "Timezone.h"
+#include <boost/beast/version.hpp>
+#include <boost/uuid/string_generator.hpp>
+#include <fmt/chrono.h>
+
+namespace Trinity::Net::Http
+{
+bool CanLogRequestContent(RequestContext const& context)
+{
+ return !context.handler || !context.handler->Flags.HasFlag(RequestHandlerFlag::DoNotLogRequestContent);
+}
+
+bool CanLogResponseContent(RequestContext const& context)
+{
+ return !context.handler || !context.handler->Flags.HasFlag(RequestHandlerFlag::DoNotLogResponseContent);
+}
+
+RequestHandlerResult DispatcherService::HandleRequest(std::shared_ptr<AbstractSocket> session, RequestContext& context)
+{
+ TC_LOG_DEBUG(_logger, "{} Starting request {} {}", session->GetClientInfo(),
+ ToStdStringView(context.request.method_string()), ToStdStringView(context.request.target()));
+
+ std::string_view path = [&]
+ {
+ std::string_view path = ToStdStringView(context.request.target());
+ size_t queryIndex = path.find('?');
+ if (queryIndex != std::string_view::npos)
+ path = path.substr(0, queryIndex);
+ return path;
+ }();
+
+ context.handler = [&]() -> HttpMethodHandlerMap::mapped_type const*
+ {
+ switch (context.request.method())
+ {
+ case boost::beast::http::verb::get:
+ case boost::beast::http::verb::head:
+ {
+ auto itr = _getHandlers.find(path);
+ return itr != _getHandlers.end() ? &itr->second : nullptr;
+ }
+ case boost::beast::http::verb::post:
+ {
+ auto itr = _postHandlers.find(path);
+ return itr != _postHandlers.end() ? &itr->second : nullptr;
+ }
+ default:
+ break;
+ }
+ return nullptr;
+ }();
+
+ SystemTimePoint responseDate = SystemTimePoint::clock::now();
+ context.response.set(boost::beast::http::field::date, StringFormat("{:%a, %d %b %Y %T GMT}", responseDate - Timezone::GetSystemZoneOffsetAt(responseDate)));
+ context.response.set(boost::beast::http::field::server, BOOST_BEAST_VERSION_STRING);
+ context.response.keep_alive(context.response.keep_alive());
+
+ if (!context.handler)
+ return HandlePathNotFound(std::move(session), context);
+
+ return context.handler->Func(std::move(session), context);
+}
+
+RequestHandlerResult DispatcherService::HandlePathNotFound(std::shared_ptr<AbstractSocket> /*session*/, RequestContext& context)
+{
+ context.response.result(boost::beast::http::status::not_found);
+ return RequestHandlerResult::Handled;
+}
+
+RequestHandlerResult DispatcherService::HandleUnauthorized(std::shared_ptr<AbstractSocket> /*session*/, RequestContext& context)
+{
+ context.response.result(boost::beast::http::status::unauthorized);
+ return RequestHandlerResult::Handled;
+}
+
+void DispatcherService::RegisterHandler(boost::beast::http::verb method, std::string_view path,
+ std::function<RequestHandlerResult(std::shared_ptr<AbstractSocket> session, RequestContext& context)> handler,
+ RequestHandlerFlag flags)
+{
+ HttpMethodHandlerMap& handlerMap = [&]() -> HttpMethodHandlerMap&
+ {
+ switch (method)
+ {
+ case boost::beast::http::verb::get:
+ return _getHandlers;
+ case boost::beast::http::verb::post:
+ return _postHandlers;
+ default:
+ {
+ std::string_view methodString = ToStdStringView(boost::beast::http::to_string(method));
+ ABORT_MSG("Tried to register a handler for unsupported HTTP method " STRING_VIEW_FMT, STRING_VIEW_FMT_ARG(methodString));
+ }
+ }
+ }();
+
+ handlerMap[std::string(path)] = { .Func = std::move(handler), .Flags = flags };
+ TC_LOG_INFO(_logger, "Registered new handler for {} {}", ToStdStringView(boost::beast::http::to_string(method)), path);
+}
+
+void SessionService::InitAndStoreSessionState(std::shared_ptr<SessionState> state, boost::asio::ip::address const& address)
+{
+ state->RemoteAddress = address;
+
+ // Generate session id
+ {
+ std::unique_lock lock{ _sessionsMutex };
+
+ while (state->Id.is_nil() || _sessions.contains(state->Id))
+ std::copy_n(Trinity::Crypto::GetRandomBytes<16>().begin(), 16, state->Id.begin());
+
+ TC_LOG_DEBUG(_logger, "Client at {} created new session {}", address.to_string(), boost::uuids::to_string(state->Id));
+ _sessions[state->Id] = std::move(state);
+ }
+}
+
+void SessionService::Start(Asio::IoContext& ioContext)
+{
+ _inactiveSessionsKillTimer = std::make_unique<Asio::DeadlineTimer>(ioContext);
+ _inactiveSessionsKillTimer->expires_from_now(boost::posix_time::minutes(1));
+ _inactiveSessionsKillTimer->async_wait([this](boost::system::error_code const& err)
+ {
+ if (err)
+ return;
+
+ KillInactiveSessions();
+ });
+}
+
+void SessionService::Stop()
+{
+ _inactiveSessionsKillTimer = nullptr;
+ {
+ std::unique_lock lock{ _sessionsMutex };
+ _sessions.clear();
+ }
+ {
+ std::unique_lock lock{ _inactiveSessionsMutex };
+ _inactiveSessions.clear();
+ }
+}
+
+std::shared_ptr<SessionState> SessionService::FindAndRefreshSessionState(std::string_view id, boost::asio::ip::address const& address)
+{
+ std::shared_ptr<SessionState> state;
+
+ {
+ std::shared_lock lock{ _sessionsMutex };
+ auto itr = _sessions.find(boost::uuids::string_generator()(id.begin(), id.end()));
+ if (itr == _sessions.end())
+ {
+ TC_LOG_DEBUG(_logger, "Client at {} attempted to use a session {} that was expired", address.to_string(), id);
+ return nullptr; // no session
+ }
+
+ state = itr->second;
+ }
+
+ if (state->RemoteAddress != address)
+ {
+ TC_LOG_ERROR(_logger, "Client at {} attempted to use a session {} that was last accessed from {}, denied access",
+ address.to_string(), id, state->RemoteAddress.to_string());
+ return nullptr;
+ }
+
+ {
+ std::unique_lock inactiveSessionsLock{ _inactiveSessionsMutex };
+ _inactiveSessions.erase(state->Id);
+ }
+
+ return state;
+}
+
+void SessionService::MarkSessionInactive(boost::uuids::uuid const& id)
+{
+ {
+ std::unique_lock inactiveSessionsLock{ _inactiveSessionsMutex };
+ _inactiveSessions.insert(id);
+ }
+
+ {
+ auto itr = _sessions.find(id);
+ if (itr != _sessions.end())
+ {
+ itr->second->InactiveTimestamp = TimePoint::clock::now() + Minutes(5);
+ TC_LOG_TRACE(_logger, "Session {} marked as inactive", boost::uuids::to_string(id));
+ }
+ }
+}
+
+void SessionService::KillInactiveSessions()
+{
+ std::set<boost::uuids::uuid> inactiveSessions;
+
+ {
+ std::unique_lock lock{ _inactiveSessionsMutex };
+ std::swap(_inactiveSessions, inactiveSessions);
+ }
+
+ {
+ TimePoint now = TimePoint::clock::now();
+ std::size_t inactiveSessionsCount = inactiveSessions.size();
+
+ std::unique_lock lock{ _sessionsMutex };
+ for (auto itr = inactiveSessions.begin(); itr != inactiveSessions.end(); )
+ {
+ auto sessionItr = _sessions.find(*itr);
+ if (sessionItr == _sessions.end() || sessionItr->second->InactiveTimestamp < now)
+ {
+ _sessions.erase(sessionItr);
+ itr = inactiveSessions.erase(itr);
+ }
+ else
+ ++itr;
+ }
+
+ TC_LOG_DEBUG(_logger, "Killed {} inactive sessions", inactiveSessionsCount - inactiveSessions.size());
+ }
+
+ {
+ // restore sessions not killed to inactive queue
+ std::unique_lock lock{ _inactiveSessionsMutex };
+ for (auto itr = inactiveSessions.begin(); itr != inactiveSessions.end(); )
+ {
+ auto node = inactiveSessions.extract(itr++);
+ _inactiveSessions.insert(std::move(node));
+ }
+ }
+
+ _inactiveSessionsKillTimer->expires_from_now(boost::posix_time::minutes(1));
+ _inactiveSessionsKillTimer->async_wait([this](boost::system::error_code const& err)
+ {
+ if (err)
+ return;
+
+ KillInactiveSessions();
+ });
+}
+}
diff --git a/src/server/shared/Networking/Http/HttpService.h b/src/server/shared/Networking/Http/HttpService.h
new file mode 100644
index 00000000000..01c66146ae3
--- /dev/null
+++ b/src/server/shared/Networking/Http/HttpService.h
@@ -0,0 +1,188 @@
+/*
+ * This file is part of the TrinityCore Project. See AUTHORS file for Copyright information
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef TRINITYCORE_HTTP_SERVICE_H
+#define TRINITYCORE_HTTP_SERVICE_H
+
+#include "AsioHacksFwd.h"
+#include "Concepts.h"
+#include "Define.h"
+#include "EnumFlag.h"
+#include "HttpCommon.h"
+#include "HttpSessionState.h"
+#include "Optional.h"
+#include "SocketMgr.h"
+#include <boost/uuid/uuid.hpp>
+#include <functional>
+#include <map>
+#include <set>
+#include <shared_mutex>
+
+namespace Trinity::Net::Http
+{
+class AbstractSocket;
+
+enum class RequestHandlerFlag
+{
+ None = 0x0,
+ DoNotLogRequestContent = 0x1,
+ DoNotLogResponseContent = 0x2,
+};
+
+DEFINE_ENUM_FLAG(RequestHandlerFlag);
+
+struct RequestHandler
+{
+ std::function<RequestHandlerResult(std::shared_ptr<AbstractSocket> session, RequestContext& context)> Func;
+ EnumFlag<RequestHandlerFlag> Flags = RequestHandlerFlag::None;
+};
+
+class TC_SHARED_API DispatcherService
+{
+public:
+ explicit DispatcherService(std::string_view loggerSuffix) : _logger("server.http.dispatcher.")
+ {
+ _logger.append(loggerSuffix);
+ }
+
+ RequestHandlerResult HandleRequest(std::shared_ptr<AbstractSocket> session, RequestContext& context);
+
+ RequestHandlerResult HandlePathNotFound(std::shared_ptr<AbstractSocket> session, RequestContext& context);
+ RequestHandlerResult HandleUnauthorized(std::shared_ptr<AbstractSocket> session, RequestContext& context);
+
+protected:
+ void RegisterHandler(boost::beast::http::verb method, std::string_view path,
+ std::function<RequestHandlerResult(std::shared_ptr<AbstractSocket> session, RequestContext& context)> handler,
+ RequestHandlerFlag flags = RequestHandlerFlag::None);
+
+private:
+ using HttpMethodHandlerMap = std::map<std::string, RequestHandler, std::less<>>;
+
+ HttpMethodHandlerMap _getHandlers;
+ HttpMethodHandlerMap _postHandlers;
+
+ std::string _logger;
+};
+
+class TC_SHARED_API SessionService
+{
+public:
+ explicit SessionService(std::string_view loggerSuffix) : _logger("server.http.session.")
+ {
+ _logger.append(loggerSuffix);
+ }
+
+ void Start(Asio::IoContext& ioContext);
+ void Stop();
+
+ std::shared_ptr<SessionState> FindAndRefreshSessionState(std::string_view id, boost::asio::ip::address const& address);
+ void MarkSessionInactive(boost::uuids::uuid const& id);
+
+protected:
+ void InitAndStoreSessionState(std::shared_ptr<SessionState> state, boost::asio::ip::address const& address);
+
+ void KillInactiveSessions();
+
+private:
+ std::shared_mutex _sessionsMutex;
+ std::map<boost::uuids::uuid, std::shared_ptr<SessionState>> _sessions;
+
+ std::mutex _inactiveSessionsMutex;
+ std::set<boost::uuids::uuid> _inactiveSessions;
+ std::unique_ptr<Asio::DeadlineTimer> _inactiveSessionsKillTimer;
+
+ std::string _logger;
+};
+
+template<typename Callable, typename SessionImpl>
+concept HttpRequestHandler = invocable_r<Callable, RequestHandlerResult, std::shared_ptr<SessionImpl>, RequestContext&>;
+
+template<typename SessionImpl>
+class HttpService : public SocketMgr<SessionImpl>, public DispatcherService, public SessionService
+{
+public:
+ HttpService(std::string_view loggerSuffix) : DispatcherService(loggerSuffix), SessionService(loggerSuffix), _ioContext(nullptr), _logger("server.http.")
+ {
+ _logger.append(loggerSuffix);
+ }
+
+ bool StartNetwork(Asio::IoContext& ioContext, std::string const& bindIp, uint16 port, int32 threadCount = 1) override
+ {
+ if (!SocketMgr<SessionImpl>::StartNetwork(ioContext, bindIp, port, threadCount))
+ return false;
+
+ SessionService::Start(ioContext);
+ return true;
+ }
+
+ void StopNetwork() override
+ {
+ SessionService::Stop();
+ SocketMgr<SessionImpl>::StopNetwork();
+ }
+
+ // http handling
+ using DispatcherService::RegisterHandler;
+
+ template<HttpRequestHandler<SessionImpl> Callable>
+ void RegisterHandler(boost::beast::http::verb method, std::string_view path, Callable handler, RequestHandlerFlag flags = RequestHandlerFlag::None)
+ {
+ this->DispatcherService::RegisterHandler(method, path, [handler = std::move(handler)](std::shared_ptr<AbstractSocket> session, RequestContext& context) -> RequestHandlerResult
+ {
+ return handler(std::static_pointer_cast<SessionImpl>(std::move(session)), context);
+ }, flags);
+ }
+
+ // session tracking
+ virtual std::shared_ptr<SessionState> CreateNewSessionState(boost::asio::ip::address const& address)
+ {
+ std::shared_ptr<SessionState> state = std::make_shared<SessionState>();
+ InitAndStoreSessionState(state, address);
+ return state;
+ }
+
+protected:
+ class Thread : public NetworkThread<SessionImpl>
+ {
+ protected:
+ void SocketRemoved(std::shared_ptr<SessionImpl> session) override
+ {
+ if (Optional<boost::uuids::uuid> id = session->GetSessionId())
+ _service->MarkSessionInactive(*id);
+ }
+
+ private:
+ friend HttpService;
+
+ SessionService* _service;
+ };
+
+ NetworkThread<SessionImpl>* CreateThreads() const override
+ {
+ Thread* threads = new Thread[this->GetNetworkThreadCount()];
+ for (int32 i = 0; i < this->GetNetworkThreadCount(); ++i)
+ threads[i]._service = const_cast<HttpService*>(this);
+ return threads;
+ }
+
+private:
+ Asio::IoContext* _ioContext;
+ std::string _logger;
+};
+}
+
+#endif // TRINITYCORE_HTTP_SERVICE_H
diff --git a/src/server/shared/Networking/Http/HttpSessionState.h b/src/server/shared/Networking/Http/HttpSessionState.h
new file mode 100644
index 00000000000..3012a2efc65
--- /dev/null
+++ b/src/server/shared/Networking/Http/HttpSessionState.h
@@ -0,0 +1,35 @@
+/*
+ * This file is part of the TrinityCore Project. See AUTHORS file for Copyright information
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef TRINITYCORE_HTTP_SESSION_STATE_H
+#define TRINITYCORE_HTTP_SESSION_STATE_H
+
+#include "Duration.h"
+#include <boost/asio/ip/address.hpp>
+#include <boost/uuid/uuid.hpp>
+
+namespace Trinity::Net::Http
+{
+struct SessionState
+{
+ boost::uuids::uuid Id = { };
+ boost::asio::ip::address RemoteAddress;
+ TimePoint InactiveTimestamp = TimePoint::max();
+};
+}
+
+#endif // TRINITYCORE_HTTP_SESSION_STATE_H
diff --git a/src/server/shared/Networking/Http/HttpSocket.h b/src/server/shared/Networking/Http/HttpSocket.h
new file mode 100644
index 00000000000..2bd18efd565
--- /dev/null
+++ b/src/server/shared/Networking/Http/HttpSocket.h
@@ -0,0 +1,75 @@
+/*
+ * This file is part of the TrinityCore Project. See AUTHORS file for Copyright information
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef TRINITYCORE_HTTP_SOCKET_H
+#define TRINITYCORE_HTTP_SOCKET_H
+
+#include "BaseHttpSocket.h"
+#include <boost/beast/core/tcp_stream.hpp>
+
+namespace Trinity::Net::Http
+{
+namespace Impl
+{
+class BoostBeastSocketWrapper : public boost::beast::tcp_stream
+{
+public:
+ using boost::beast::tcp_stream::tcp_stream;
+
+ void shutdown(boost::asio::socket_base::shutdown_type what, boost::system::error_code& shutdownError)
+ {
+ socket().shutdown(what, shutdownError);
+ }
+
+ void close(boost::system::error_code& /*error*/)
+ {
+ boost::beast::tcp_stream::close();
+ }
+
+ boost::asio::ip::tcp::socket::endpoint_type remote_endpoint() const
+ {
+ return socket().remote_endpoint();
+ }
+};
+}
+
+template <typename Derived>
+class Socket : public BaseSocket<Derived, Impl::BoostBeastSocketWrapper>
+{
+ using SocketBase = BaseSocket<Derived, Impl::BoostBeastSocketWrapper>;
+
+public:
+ explicit Socket(boost::asio::ip::tcp::socket&& socket)
+ : SocketBase(std::move(socket)) { }
+
+ Socket(Socket const& other) = delete;
+ Socket(Socket&& other) = delete;
+ Socket& operator=(Socket const& other) = delete;
+ Socket& operator=(Socket&& other) = delete;
+
+ ~Socket() = default;
+
+ void Start() override
+ {
+ this->ResetHttpParser();
+
+ this->AsyncRead();
+ }
+};
+}
+
+#endif // TRINITYCORE_HTTP_SOCKET_H
diff --git a/src/server/shared/Networking/Http/HttpSslSocket.h b/src/server/shared/Networking/Http/HttpSslSocket.h
new file mode 100644
index 00000000000..cdb70645e05
--- /dev/null
+++ b/src/server/shared/Networking/Http/HttpSslSocket.h
@@ -0,0 +1,97 @@
+/*
+ * This file is part of the TrinityCore Project. See AUTHORS file for Copyright information
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef TRINITYCORE_HTTP_SSL_SOCKET_H
+#define TRINITYCORE_HTTP_SSL_SOCKET_H
+
+#include "BaseHttpSocket.h"
+#include "SslSocket.h"
+#include <boost/beast/core/stream_traits.hpp>
+#include <boost/beast/core/tcp_stream.hpp>
+#include <boost/beast/ssl/ssl_stream.hpp>
+
+namespace Trinity::Net::Http
+{
+namespace Impl
+{
+class BoostBeastSslSocketWrapper : public ::SslSocket<boost::beast::ssl_stream<boost::beast::tcp_stream>>
+{
+public:
+ using SslSocket::SslSocket;
+
+ void shutdown(boost::asio::socket_base::shutdown_type what, boost::system::error_code& shutdownError)
+ {
+ _sslSocket.shutdown(shutdownError);
+ boost::beast::get_lowest_layer(_sslSocket).socket().shutdown(what, shutdownError);
+ }
+
+ void close(boost::system::error_code& /*error*/)
+ {
+ boost::beast::get_lowest_layer(_sslSocket).close();
+ }
+
+ boost::asio::ip::tcp::socket::endpoint_type remote_endpoint() const
+ {
+ return boost::beast::get_lowest_layer(_sslSocket).socket().remote_endpoint();
+ }
+};
+}
+
+template <typename Derived>
+class SslSocket : public BaseSocket<Derived, Impl::BoostBeastSslSocketWrapper>
+{
+ using SocketBase = BaseSocket<Derived, Impl::BoostBeastSslSocketWrapper>;
+
+public:
+ explicit SslSocket(boost::asio::ip::tcp::socket&& socket, boost::asio::ssl::context& sslContext)
+ : SocketBase(std::move(socket), sslContext) { }
+
+ SslSocket(SslSocket const& other) = delete;
+ SslSocket(SslSocket&& other) = delete;
+ SslSocket& operator=(SslSocket const& other) = delete;
+ SslSocket& operator=(SslSocket&& other) = delete;
+
+ ~SslSocket() = default;
+
+ void Start() override
+ {
+ this->AsyncHandshake();
+ }
+
+ void AsyncHandshake()
+ {
+ this->underlying_stream().async_handshake(boost::asio::ssl::stream_base::server,
+ [self = this->shared_from_this()](boost::system::error_code const& error) { self->HandshakeHandler(error); });
+ }
+
+ void HandshakeHandler(boost::system::error_code const& error)
+ {
+ if (error)
+ {
+ TC_LOG_ERROR("server.http.session.ssl", "{} SSL Handshake failed {}", this->GetClientInfo(), error.message());
+ this->CloseSocket();
+ return;
+ }
+
+ this->ResetHttpParser();
+
+ this->AsyncRead();
+ }
+};
+}
+
+#endif // TRINITYCORE_HTTP_SSL_SOCKET_H
diff --git a/src/server/shared/Networking/NetworkThread.h b/src/server/shared/Networking/NetworkThread.h
index 69d62403249..0195c48b9fc 100644
--- a/src/server/shared/Networking/NetworkThread.h
+++ b/src/server/shared/Networking/NetworkThread.h
@@ -77,7 +77,7 @@ public:
return _connections;
}
- virtual void AddSocket(std::shared_ptr<SocketType> sock)
+ void AddSocket(std::shared_ptr<SocketType> sock)
{
std::lock_guard<std::mutex> lock(_newSocketsLock);
diff --git a/src/server/shared/Networking/Socket.h b/src/server/shared/Networking/Socket.h
index a996ecb2cbe..511f94ed366 100644
--- a/src/server/shared/Networking/Socket.h
+++ b/src/server/shared/Networking/Socket.h
@@ -18,14 +18,13 @@
#ifndef __SOCKET_H__
#define __SOCKET_H__
-#include "MessageBuffer.h"
#include "Log.h"
+#include "MessageBuffer.h"
+#include <boost/asio/ip/tcp.hpp>
#include <atomic>
-#include <queue>
#include <memory>
-#include <functional>
+#include <queue>
#include <type_traits>
-#include <boost/asio/ip/tcp.hpp>
#define READ_BLOCK_SIZE 4096
#ifdef BOOST_ASIO_HAS_IOCP
@@ -63,12 +62,19 @@ template<class T, class Stream = boost::asio::ip::tcp::socket>
class Socket : public std::enable_shared_from_this<T>
{
public:
- explicit Socket(boost::asio::ip::tcp::socket&& socket) : _socket(std::move(socket)), _remoteAddress(_socket.remote_endpoint().address()),
- _remotePort(_socket.remote_endpoint().port()), _readBuffer(), _closed(false), _closing(false), _isWritingAsync(false)
+ template<typename... Args>
+ explicit Socket(boost::asio::ip::tcp::socket&& socket, Args&&... args) : _socket(std::move(socket), std::forward<Args>(args)...),
+ _remoteAddress(_socket.remote_endpoint().address()), _remotePort(_socket.remote_endpoint().port()),
+ _closed(false), _closing(false), _isWritingAsync(false)
{
_readBuffer.Resize(READ_BLOCK_SIZE);
}
+ Socket(Socket const& other) = delete;
+ Socket(Socket&& other) = delete;
+ Socket& operator=(Socket const& other) = delete;
+ Socket& operator=(Socket&& other) = delete;
+
virtual ~Socket()
{
_closed = true;
diff --git a/src/server/shared/Networking/SslSocket.h b/src/server/shared/Networking/SslSocket.h
index e00b1b6b65e..c19c8612edf 100644
--- a/src/server/shared/Networking/SslSocket.h
+++ b/src/server/shared/Networking/SslSocket.h
@@ -24,11 +24,11 @@
namespace boostssl = boost::asio::ssl;
-template<class SslContext, class Stream = boostssl::stream<boost::asio::ip::tcp::socket>>
+template<class Stream = boostssl::stream<boost::asio::ip::tcp::socket>>
class SslSocket
{
public:
- explicit SslSocket(boost::asio::ip::tcp::socket&& socket) : _sslSocket(std::move(socket), SslContext::instance())
+ explicit SslSocket(boost::asio::ip::tcp::socket&& socket, boost::asio::ssl::context& sslContext) : _sslSocket(std::move(socket), sslContext)
{
_sslSocket.set_verify_mode(boostssl::verify_none);
}
diff --git a/src/server/worldserver/CMakeLists.txt b/src/server/worldserver/CMakeLists.txt
index da3bd43c2d5..f6df3e524de 100644
--- a/src/server/worldserver/CMakeLists.txt
+++ b/src/server/worldserver/CMakeLists.txt
@@ -51,7 +51,8 @@ target_link_libraries(worldserver
PUBLIC
scripts
game
- readline)
+ readline
+ gsoap)
CollectIncludeDirectories(
${CMAKE_CURRENT_SOURCE_DIR}