| @@ -0,0 +1,160 @@ | ||
| <?xml version="1.0" encoding="utf-8"?> | ||
| <Project DefaultTargets="Build" ToolsVersion="14.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> | ||
| <ItemGroup Label="ProjectConfigurations"> | ||
| <ProjectConfiguration Include="Debug|Win32"> | ||
| <Configuration>Debug</Configuration> | ||
| <Platform>Win32</Platform> | ||
| </ProjectConfiguration> | ||
| <ProjectConfiguration Include="Release|Win32"> | ||
| <Configuration>Release</Configuration> | ||
| <Platform>Win32</Platform> | ||
| </ProjectConfiguration> | ||
| <ProjectConfiguration Include="Debug|x64"> | ||
| <Configuration>Debug</Configuration> | ||
| <Platform>x64</Platform> | ||
| </ProjectConfiguration> | ||
| <ProjectConfiguration Include="Release|x64"> | ||
| <Configuration>Release</Configuration> | ||
| <Platform>x64</Platform> | ||
| </ProjectConfiguration> | ||
| </ItemGroup> | ||
| <ItemGroup> | ||
| <ClInclude Include="httplib.h" /> | ||
| </ItemGroup> | ||
| <ItemGroup> | ||
| <ClCompile Include="httplib.cc" /> | ||
| </ItemGroup> | ||
| <PropertyGroup Label="Globals"> | ||
| <Keyword>Win32Proj</Keyword> | ||
| <RootNamespace>libconfig</RootNamespace> | ||
| <ProjectGuid>{7A1A25BC-2CF7-44B2-8CAF-B4273B510FC6}</ProjectGuid> | ||
| </PropertyGroup> | ||
| <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" /> | ||
| <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration"> | ||
| <ConfigurationType>StaticLibrary</ConfigurationType> | ||
| <UseDebugLibraries>true</UseDebugLibraries> | ||
| <PlatformToolset>$(DefaultPlatformToolset)</PlatformToolset> | ||
| <CharacterSet>MultiByte</CharacterSet> | ||
| </PropertyGroup> | ||
| <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration"> | ||
| <ConfigurationType>StaticLibrary</ConfigurationType> | ||
| <UseDebugLibraries>false</UseDebugLibraries> | ||
| <PlatformToolset>$(DefaultPlatformToolset)</PlatformToolset> | ||
| <WholeProgramOptimization>true</WholeProgramOptimization> | ||
| <CharacterSet>MultiByte</CharacterSet> | ||
| </PropertyGroup> | ||
| <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration"> | ||
| <ConfigurationType>StaticLibrary</ConfigurationType> | ||
| <UseDebugLibraries>true</UseDebugLibraries> | ||
| <PlatformToolset>$(DefaultPlatformToolset)</PlatformToolset> | ||
| <CharacterSet>MultiByte</CharacterSet> | ||
| </PropertyGroup> | ||
| <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration"> | ||
| <ConfigurationType>StaticLibrary</ConfigurationType> | ||
| <UseDebugLibraries>false</UseDebugLibraries> | ||
| <PlatformToolset>$(DefaultPlatformToolset)</PlatformToolset> | ||
| <WholeProgramOptimization>true</WholeProgramOptimization> | ||
| <CharacterSet>MultiByte</CharacterSet> | ||
| </PropertyGroup> | ||
| <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" /> | ||
| <ImportGroup Label="ExtensionSettings"> | ||
| </ImportGroup> | ||
| <ImportGroup Label="Shared"> | ||
| </ImportGroup> | ||
| <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'"> | ||
| <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" /> | ||
| </ImportGroup> | ||
| <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'"> | ||
| <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" /> | ||
| </ImportGroup> | ||
| <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'"> | ||
| <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" /> | ||
| </ImportGroup> | ||
| <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|x64'"> | ||
| <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" /> | ||
| </ImportGroup> | ||
| <PropertyGroup Label="UserMacros" /> | ||
| <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'"> | ||
| <OutDir>$(SolutionDir).vs\build\</OutDir> | ||
| <IntDir>$(SolutionDir).vs\build\$(ProjectName)\$(Platform)\$(Configuration)\</IntDir> | ||
| </PropertyGroup> | ||
| <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'"> | ||
| <OutDir>$(SolutionDir).vs\build\</OutDir> | ||
| <IntDir>$(SolutionDir).vs\build\$(ProjectName)\$(Platform)\$(Configuration)\</IntDir> | ||
| </PropertyGroup> | ||
| <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'"> | ||
| <OutDir>$(SolutionDir).vs\build\</OutDir> | ||
| <IntDir>$(SolutionDir).vs\build\$(ProjectName)\$(Platform)\$(Configuration)\</IntDir> | ||
| </PropertyGroup> | ||
| <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'"> | ||
| <OutDir>$(SolutionDir).vs\build\</OutDir> | ||
| <IntDir>$(SolutionDir).vs\build\$(ProjectName)\$(Platform)\$(Configuration)\</IntDir> | ||
| </PropertyGroup> | ||
| <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'"> | ||
| <ClCompile> | ||
| <PrecompiledHeader> | ||
| </PrecompiledHeader> | ||
| <WarningLevel>Level3</WarningLevel> | ||
| <Optimization>Disabled</Optimization> | ||
| <PreprocessorDefinitions>_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_DEPRECATE;WIN32;_DEBUG;_LIB;_ITERATOR_DEBUG_LEVEL=0;%(PreprocessorDefinitions)</PreprocessorDefinitions> | ||
| <RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary> | ||
| </ClCompile> | ||
| <Link> | ||
| <SubSystem>Windows</SubSystem> | ||
| <GenerateDebugInformation>true</GenerateDebugInformation> | ||
| </Link> | ||
| </ItemDefinitionGroup> | ||
| <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'"> | ||
| <ClCompile> | ||
| <PrecompiledHeader> | ||
| </PrecompiledHeader> | ||
| <WarningLevel>Level3</WarningLevel> | ||
| <Optimization>Disabled</Optimization> | ||
| <PreprocessorDefinitions>_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_DEPRECATE;_DEBUG;_LIB;_ITERATOR_DEBUG_LEVEL=0;%(PreprocessorDefinitions)</PreprocessorDefinitions> | ||
| <RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary> | ||
| </ClCompile> | ||
| <Link> | ||
| <SubSystem>Windows</SubSystem> | ||
| <GenerateDebugInformation>true</GenerateDebugInformation> | ||
| </Link> | ||
| </ItemDefinitionGroup> | ||
| <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'"> | ||
| <ClCompile> | ||
| <WarningLevel>Level3</WarningLevel> | ||
| <PrecompiledHeader> | ||
| </PrecompiledHeader> | ||
| <Optimization>MaxSpeed</Optimization> | ||
| <FunctionLevelLinking>true</FunctionLevelLinking> | ||
| <IntrinsicFunctions>true</IntrinsicFunctions> | ||
| <PreprocessorDefinitions>_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_DEPRECATE;WIN32;NDEBUG;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions> | ||
| <RuntimeLibrary>MultiThreaded</RuntimeLibrary> | ||
| </ClCompile> | ||
| <Link> | ||
| <SubSystem>Windows</SubSystem> | ||
| <GenerateDebugInformation>true</GenerateDebugInformation> | ||
| <EnableCOMDATFolding>true</EnableCOMDATFolding> | ||
| <OptimizeReferences>true</OptimizeReferences> | ||
| </Link> | ||
| </ItemDefinitionGroup> | ||
| <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'"> | ||
| <ClCompile> | ||
| <WarningLevel>Level3</WarningLevel> | ||
| <PrecompiledHeader> | ||
| </PrecompiledHeader> | ||
| <Optimization>MaxSpeed</Optimization> | ||
| <FunctionLevelLinking>true</FunctionLevelLinking> | ||
| <IntrinsicFunctions>true</IntrinsicFunctions> | ||
| <PreprocessorDefinitions>_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_DEPRECATE;NDEBUG;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions> | ||
| <RuntimeLibrary>MultiThreaded</RuntimeLibrary> | ||
| </ClCompile> | ||
| <Link> | ||
| <SubSystem>Windows</SubSystem> | ||
| <GenerateDebugInformation>true</GenerateDebugInformation> | ||
| <EnableCOMDATFolding>true</EnableCOMDATFolding> | ||
| <OptimizeReferences>true</OptimizeReferences> | ||
| </Link> | ||
| </ItemDefinitionGroup> | ||
| <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" /> | ||
| <ImportGroup Label="ExtensionTargets"> | ||
| </ImportGroup> | ||
| </Project> |
| @@ -0,0 +1,21 @@ | ||
| <?xml version="1.0" encoding="utf-8"?> | ||
| <Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> | ||
| <ItemGroup> | ||
| <Filter Include="Source Files"> | ||
| <Extensions>cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx</Extensions> | ||
| </Filter> | ||
| <Filter Include="Header Files"> | ||
| <Extensions>h;hh;hpp;hxx;hm;inl;inc;xsd</Extensions> | ||
| </Filter> | ||
| </ItemGroup> | ||
| <ItemGroup> | ||
| <ClInclude Include="httplib.h"> | ||
| <Filter>Header Files</Filter> | ||
| </ClInclude> | ||
| </ItemGroup> | ||
| <ItemGroup> | ||
| <ClCompile Include="httplib.cc"> | ||
| <Filter>Source Files</Filter> | ||
| </ClCompile> | ||
| </ItemGroup> | ||
| </Project> |
| @@ -0,0 +1,3 @@ | ||
| // rAthena msg_athena.conf | ||
| // Message Configuration of web-server | ||
| // ----------------------- |
| @@ -0,0 +1,56 @@ | ||
| // rAthena Web Server configuration file. | ||
|
|
||
| // Note: "Comments" are all text on the right side of a double slash "//" | ||
| // Whatever text is commented will not be parsed by the servers, and serves | ||
| // only as information/reference. | ||
|
|
||
| // The web server listens on the interface with this IP address. | ||
| // NOTE: This allows you to run multiple servers on multiple interfaces | ||
| // while using the same ports for each server. | ||
| //bind_ip: 127.0.0.1 | ||
|
|
||
| // Web Server Port | ||
| web_port: 8888 | ||
|
|
||
| //Time-stamp format which will be printed before all messages. | ||
| //Can at most be 20 characters long. | ||
| //Common formats: | ||
| // %I:%M:%S %p (hour:minute:second 12 hour, AM/PM format) | ||
| // %H:%M:%S (hour:minute:second, 24 hour format) | ||
| // %d/%b/%Y (day/Month/year) | ||
| //For full format information, consult the strftime() manual. | ||
| //timestamp_format: [%d/%b %H:%M] | ||
|
|
||
| //If redirected output contains escape sequences (color codes) | ||
| stdout_with_ansisequence: no | ||
|
|
||
| //Makes server log selected message types to a file in the /log/ folder | ||
| //1: Log Warning Messages | ||
| //2: Log Error and SQL Error messages. | ||
| //4: Log Debug Messages | ||
| //Example: "console_msg_log: 7" logs all 3 kinds | ||
| //Messages logged by this overrides console_silent setting | ||
| console_msg_log: 0 | ||
|
|
||
| // File path to store the console messages above | ||
| console_log_filepath: ./log/web-msg_log.log | ||
|
|
||
| //Makes server output more silent by omitting certain types of messages: | ||
| //1: Hide Information messages | ||
| //2: Hide Status messages | ||
| //4: Hide Notice Messages | ||
| //8: Hide Warning Messages | ||
| //16: Hide Error and SQL Error messages. | ||
| //32: Hide Debug Messages | ||
| //Example: "console_silent: 7" Hides information, status and notice messages (1+2+4) | ||
| console_silent: 0 | ||
|
|
||
| // Print requests and responses? | ||
| // This is useful for debugging purposes, it will print the entire | ||
| // request and response for each transaction. | ||
| print_req_res: off | ||
|
|
||
| // Allow GIF images to be uploaded as guild emblem? | ||
| allow_gifs: yes | ||
|
|
||
| import: conf/import/web_conf.txt | ||
Lemongrass3110 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| @@ -0,0 +1,50 @@ | ||
|
|
||
|
There was a problem hiding this comment. Maybe a quick doc in the SQL Readme? |
||
| -- | ||
| -- Table structure for table `guild_emblems` | ||
| -- | ||
|
|
||
| CREATE TABLE IF NOT EXISTS `guild_emblems` ( | ||
| `guild_id` int(11) unsigned NOT NULL, | ||
| `world_name` varchar(32) NOT NULL, | ||
| `file_type` varchar(255) NOT NULL, | ||
| `file_data` blob, | ||
| `version` int(11) unsigned NOT NULL default '0', | ||
| PRIMARY KEY (`guild_id`, `world_name`) | ||
| ) ENGINE=MyISAM; | ||
|
|
||
| -- | ||
| -- Table structure for table `user_configs` | ||
| -- | ||
|
|
||
| CREATE TABLE IF NOT EXISTS `user_configs` ( | ||
| `account_id` int(11) unsigned NOT NULL, | ||
| `world_name` varchar(32) NOT NULL, | ||
| `data` longtext NOT NULL, | ||
| PRIMARY KEY (`account_id`, `world_name`) | ||
| ) ENGINE=MyISAM; | ||
|
|
||
|
|
||
| -- | ||
| -- Table structure for table `char_configs` | ||
| -- | ||
|
|
||
| CREATE TABLE IF NOT EXISTS `char_configs` ( | ||
| `account_id` int(11) unsigned NOT NULL, | ||
| `char_id` int(11) unsigned NOT NULL, | ||
| `world_name` varchar(32) NOT NULL, | ||
| `data` longtext NOT NULL, | ||
| PRIMARY KEY (`account_id`, `char_id`, `world_name`) | ||
| ) ENGINE=MyISAM; | ||
|
|
||
| -- | ||
| -- Table structure for table `merchant_configs` | ||
| -- | ||
|
|
||
| CREATE TABLE IF NOT EXISTS `merchant_configs` ( | ||
| `account_id` int(11) unsigned NOT NULL, | ||
| `char_id` INT(11) UNSIGNED NOT NULL, | ||
| `world_name` varchar(32) NOT NULL, | ||
| `store_type` tinyint(3) UNSIGNED NOT NULL DEFAULT 0, | ||
| `data` longtext NOT NULL, | ||
| PRIMARY KEY (`account_id`, `char_id`, `world_name`) | ||
| ) ENGINE=MyISAM; | ||
| @@ -16,5 +16,6 @@ endif() | ||
| add_subdirectory( login ) | ||
| add_subdirectory( char ) | ||
| add_subdirectory( map ) | ||
| add_subdirectory( web ) | ||
| add_subdirectory( tool ) | ||
|
|
||
| @@ -557,8 +557,6 @@ struct Battle_Config | ||
| int vip_exp_penalty_job; | ||
| int vip_disp_rate; | ||
| int mon_trans_disable_in_gvg; | ||
| int discount_item_point_shop; | ||
| int update_enemy_position; | ||
| int devotion_rdamage; | ||
| @@ -0,0 +1,41 @@ | ||
| # | ||
| # setup | ||
| # | ||
| set( WEB_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR} CACHE INTERNAL "" ) | ||
|
|
||
|
|
||
| # | ||
| # web server | ||
| # | ||
| if ( NOT ENABLE_WEB_SERVER ) | ||
| return() | ||
| endif ( NOT ENABLE_WEB_SERVER ) | ||
|
|
||
| if( BUILD_SERVERS AND ENABLE_WEB_SERVER ) | ||
| message( STATUS "Creating target web-server" ) | ||
| file(GLOB WEB_HEADERS ${WEB_SOURCE_DIR}/*.hpp) | ||
| file(GLOB WEB_SOURCES ${WEB_SOURCE_DIR}/*.cpp) | ||
| set( DEPENDENCIES common yaml-cpp httplib) | ||
| set( LIBRARIES ${GLOBAL_LIBRARIES}) | ||
| set( INCLUDE_DIRS ${GLOBAL_INCLUDE_DIRS} ${COMMON_BASE_INCLUDE_DIRS} ${HTTPLIB_INCLUDE_DIRS}) | ||
| set( DEFINITIONS "${GLOBAL_DEFINITIONS} ${COMMON_BASE_DEFINITIONS}" ) | ||
| set( SOURCE_FILES ${COMMON_BASE_HEADERS} ${COMMON_HEADERS} ${WEB_HEADERS} ${WEB_SOURCES} ) | ||
| source_group( common FILES ${COMMON_BASE_HEADERS} ${COMMON_HEADERS} ) | ||
| source_group( web FILES ${WEB_HEADERS} ${WEB_SOURCES} ) | ||
| include_directories( ${INCLUDE_DIRS} ) | ||
|
|
||
| #message( STATUS "web-server SOURCE_FILES=${SOURCE_FILES}") | ||
| add_executable( web-server ${SOURCE_FILES} ) | ||
| #message( STATUS "web-server LIBRARIES=${LIBRARIES}, DEPENDENCIES=${DEPENDENCIES} DEFINITIONS=${DEFINITIONS}") | ||
| add_dependencies( web-server ${DEPENDENCIES} ) | ||
| target_link_libraries( web-server ${LIBRARIES} ${DEPENDENCIES} ) | ||
| set_target_properties( web-server PROPERTIES COMPILE_FLAGS "${DEFINITIONS}" ) | ||
| if( INSTALL_COMPONENT_RUNTIME ) | ||
| cpack_add_component( Runtime_webserver DESCRIPTION "web-server" DISPLAY_NAME "web-server" GROUP Runtime ) | ||
| install( TARGETS web-server | ||
| DESTINATION "." | ||
| COMPONENT Runtime_webserver ) | ||
| endif( INSTALL_COMPONENT_RUNTIME ) | ||
| set( TARGET_LIST ${TARGET_LIST} web-server CACHE INTERNAL "" ) | ||
| message( STATUS "Creating target web-server - done" ) | ||
| endif( BUILD_SERVERS AND ENABLE_WEB_SERVER) |
| @@ -0,0 +1,93 @@ | ||||||||
|
|
||||||||
| COMMON_H = $(shell ls ../common/*.hpp) | ||||||||
| COMMON_AR = ../common/obj/common.a | ||||||||
| COMMON_INCLUDE = -I../common | ||||||||
|
|
||||||||
| LIBCONFIG_H = $(shell ls ../../3rdparty/libconfig/*.h) | ||||||||
| LIBCONFIG_AR = ../../3rdparty/libconfig/obj/libconfig.a | ||||||||
| LIBCONFIG_INCLUDE = -I../../3rdparty/libconfig | ||||||||
|
|
||||||||
| RAPIDYAML_OBJ = $(shell find ../../3rdparty/rapidyaml/ -type f -name "*.cpp" | sed -e "s/\.cpp/\.o/g" ) | ||||||||
| RAPIDYAML_DIR_OBJ = $(RAPIDYAML_OBJ:%=obj/%) | ||||||||
| RAPIDYAML_AR = ../../3rdparty/rapidyaml/obj/ryml.a | ||||||||
| RAPIDYAML_H = $(shell find ../../3rdparty/rapidyaml/ -type f -name "*.h*") | ||||||||
| RAPIDYAML_INCLUDE = -I../../3rdparty/rapidyaml/src -I../../3rdparty/rapidyaml/ext/c4core/src | ||||||||
|
|
||||||||
| HTTPLIB_OBJ = $(shell find ../../3rdparty/httplib/ -type f -name "*.cc" | sed -e "s/\.cc/\.o/g" ) | ||||||||
| HTTPLIB_DIR_OBJ = $(HTTPLIB_OBJ:%=obj/%) | ||||||||
| HTTPLIB_AR = ../../3rdparty/httplib/obj/httplib.a | ||||||||
| HTTPLIB_H = $(shell find ../../3rdparty/httplib/ -type f -name "*.h") | ||||||||
| HTTPLIB_INCLUDE = -I../../3rdparty/httplib | ||||||||
|
|
||||||||
| WEB_OBJ = $(shell ls *.cpp | sed -e "s/\.cpp/\.o/g") | ||||||||
| WEB_DIR_OBJ = $(WEB_OBJ:%=obj/%) | ||||||||
| WEB_H = $(shell ls *.hpp) | ||||||||
|
|
||||||||
|
|
||||||||
| SERVER_DEPENDS=web-server | ||||||||
|
|
||||||||
| HAVE_MYSQL=@HAVE_MYSQL@ | ||||||||
| ifeq ($(HAVE_MYSQL),yes) | ||||||||
| SERVER_DEPENDS=web-server | ||||||||
| else | ||||||||
| SERVER_DEPENDS=needs_mysql | ||||||||
| endif | ||||||||
|
|
||||||||
| ALL_DEPENDS=server | ||||||||
|
|
||||||||
| @SET_MAKE@ | ||||||||
|
|
||||||||
| ##################################################################### | ||||||||
| .PHONY :all server clean help | ||||||||
|
|
||||||||
| all: $(ALL_DEPENDS) | ||||||||
|
|
||||||||
| server: $(SERVER_DEPENDS) | ||||||||
|
|
||||||||
| clean: | ||||||||
| @echo " CLEAN web" | ||||||||
| @rm -rf *.o obj ../../@OWEB@@EXEEXT@ | ||||||||
|
|
||||||||
| help: | ||||||||
| @echo "possible targets are 'server' 'all' 'clean' 'help'" | ||||||||
| @echo "'server' - web server" | ||||||||
| @echo "'all' - builds all above targets" | ||||||||
| @echo "'clean' - cleans builds and objects" | ||||||||
| @echo "'help' - outputs this message" | ||||||||
|
|
||||||||
| ##################################################################### | ||||||||
|
|
||||||||
| needs_mysql: | ||||||||
| @echo "MySQL not found or disabled by the configure script" | ||||||||
| @exit 1 | ||||||||
|
|
||||||||
| # object directories | ||||||||
|
|
||||||||
| obj: | ||||||||
| @echo " MKDIR obj" | ||||||||
| @-mkdir obj | ||||||||
|
|
||||||||
| #executables | ||||||||
|
|
||||||||
| web-server: obj $(WEB_DIR_OBJ) $(COMMON_AR) $(LIBCONFIG_AR) $(RAPIDYAML_AR) $(HTTPLIB_AR) | ||||||||
| @echo " LD @OWEB@@EXEEXT@" | ||||||||
| @@CXX@ @LDFLAGS@ -o ../../@OWEB@@EXEEXT@ $(WEB_DIR_OBJ) $(COMMON_AR) $(LIBCONFIG_AR) $(RAPIDYAML_AR) $(HTTPLIB_AR) @LIBS@ @MYSQL_LIBS@ | ||||||||
|
|
||||||||
| # web object files | ||||||||
|
|
||||||||
| obj/%.o: %.cpp $(WEB_H) $(COMMON_H) $(LIBCONFIG_H) $(HTTPLIB_H) $(RAPIDYAML_H) | ||||||||
| @echo " CXX $<" | ||||||||
| @@CXX@ @CXXFLAGS@ $(COMMON_INCLUDE) $(LIBCONFIG_INCLUDE) $(HTTPLIB_INCLUDE) $(RAPIDYAML_INCLUDE) @MYSQL_CFLAGS@ @CPPFLAGS@ -c $(OUTPUT_OPTION) $< | ||||||||
|
|
||||||||
| # missing object files | ||||||||
| $(COMMON_AR): | ||||||||
| @$(MAKE) -C ../common server | ||||||||
|
|
||||||||
| $(LIBCONFIG_AR): | ||||||||
| @$(MAKE) -C ../../3rdparty/libconfig | ||||||||
|
|
||||||||
| $(RAPIDYAML_AR): | ||||||||
| @$(MAKE) -C ../../3rdparty/rapidyaml | ||||||||
|
|
||||||||
| $(HTTPLIB_AR): | ||||||||
| @$(MAKE) -C ../../3rdparty/httplib | ||||||||
|
There was a problem hiding this comment.
Suggested change
Missing end of file space. |
||||||||
| @@ -0,0 +1,91 @@ | ||
| // Copyright (c) rAthena Dev Teams - Licensed under GNU GPL | ||
| // For more information, see LICENCE in the main folder | ||
|
|
||
| #include "auth.hpp" | ||
|
|
||
| #include <string.h> | ||
|
|
||
| #include "../common/showmsg.hpp" | ||
| #include "../common/sql.hpp" | ||
|
|
||
| #include "http.hpp" | ||
| #include "sqllock.hpp" | ||
| #include "web.hpp" | ||
|
|
||
|
|
||
| bool isAuthorized(const Request &request, bool checkGuildLeader) { | ||
| if (!request.has_file("AuthToken") || !request.has_file("AID")) | ||
| return false; | ||
|
|
||
| if (checkGuildLeader && !request.has_file("GDID")) | ||
| return false; | ||
|
|
||
| auto token_str = request.get_file_value("AuthToken").content; | ||
| auto token = token_str.c_str(); | ||
| auto account_id = std::stoi(request.get_file_value("AID").content); | ||
|
|
||
| SQLLock loginlock(LOGIN_SQL_LOCK); | ||
|
|
||
| loginlock.lock(); | ||
|
|
||
| auto handle = loginlock.getHandle(); | ||
|
|
||
| SqlStmt * stmt = SqlStmt_Malloc(handle); | ||
|
|
||
| if (SQL_SUCCESS != SqlStmt_Prepare(stmt, | ||
| "SELECT `account_id` FROM `%s` WHERE (`account_id` = ? AND `web_auth_token` = ? AND `web_auth_token_enabled` = '1')", | ||
| login_table) | ||
| || SQL_SUCCESS != SqlStmt_BindParam(stmt, 0, SQLDT_INT, &account_id, sizeof(account_id)) | ||
| || SQL_SUCCESS != SqlStmt_BindParam(stmt, 1, SQLDT_STRING, (void *)token, strlen(token)) | ||
| || SQL_SUCCESS != SqlStmt_Execute(stmt) | ||
| ) { | ||
| SqlStmt_ShowDebug(stmt); | ||
| SqlStmt_Free(stmt); | ||
| loginlock.unlock(); | ||
| return false; | ||
| } | ||
|
|
||
| if (SqlStmt_NumRows(stmt) <= 0) { | ||
| ShowWarning("Request with AID %d and token %s unverified\n", account_id, token); | ||
| SqlStmt_Free(stmt); | ||
| loginlock.unlock(); | ||
| return false; | ||
| } | ||
|
|
||
| SqlStmt_Free(stmt); | ||
| loginlock.unlock(); | ||
| if (!checkGuildLeader) { | ||
| // we're done, auth ok | ||
| return true; | ||
| } | ||
|
|
||
| auto guild_id = std::stoi(request.get_file_value("GDID").content); | ||
|
|
||
| SQLLock charlock(CHAR_SQL_LOCK); | ||
| charlock.lock(); | ||
| handle = charlock.getHandle(); | ||
| stmt = SqlStmt_Malloc(handle); | ||
|
|
||
| if (SQL_SUCCESS != SqlStmt_Prepare(stmt, | ||
| "SELECT `account_id` FROM `%s` LEFT JOIN `%s` using (`char_id`) WHERE (`%s`.`account_id` = ? AND `%s`.`guild_id` = ?) LIMIT 1", | ||
| guild_db_table, char_db_table, char_db_table, guild_db_table) | ||
| || SQL_SUCCESS != SqlStmt_BindParam(stmt, 0, SQLDT_INT, &account_id, sizeof(account_id)) | ||
| || SQL_SUCCESS != SqlStmt_BindParam(stmt, 1, SQLDT_INT, &guild_id, sizeof(guild_id)) | ||
| || SQL_SUCCESS != SqlStmt_Execute(stmt) | ||
| ) { | ||
| SqlStmt_ShowDebug(stmt); | ||
| SqlStmt_Free(stmt); | ||
| charlock.unlock(); | ||
| return false; | ||
| } | ||
|
|
||
| if (SqlStmt_NumRows(stmt) <= 0) { | ||
| ShowDebug("Request with AID %d GDID %d and token %s unverified\n", account_id, guild_id, token); | ||
| SqlStmt_Free(stmt); | ||
| charlock.unlock(); | ||
| return false; | ||
| } | ||
| SqlStmt_Free(stmt); | ||
| charlock.unlock(); | ||
| return true; | ||
| } |
| @@ -0,0 +1,11 @@ | ||
| // Copyright (c) rAthena Dev Teams - Licensed under GNU GPL | ||
| // For more information, see LICENCE in the main folder | ||
|
|
||
| #ifndef AUTH_HPP | ||
| #define AUTH_HPP | ||
|
|
||
| #include "http.hpp" | ||
|
|
||
| bool isAuthorized(const Request &request, bool checkGuildLeader=false); | ||
|
|
||
| #endif |
| @@ -0,0 +1,162 @@ | ||
| // Copyright (c) rAthena Dev Teams - Licensed under GNU GPL | ||
| // For more information, see LICENCE in the main folder | ||
|
|
||
| #include "charconfig_controller.hpp" | ||
|
|
||
| #include <string> | ||
|
|
||
| #include "../common/showmsg.hpp" | ||
| #include "../common/sql.hpp" | ||
|
|
||
| #include "auth.hpp" | ||
| #include "http.hpp" | ||
| #include "sqllock.hpp" | ||
| #include "webutils.hpp" | ||
| #include "web.hpp" | ||
|
|
||
| HANDLER_FUNC(charconfig_save) { | ||
| if (!isAuthorized(req, false)) { | ||
| res.status = 400; | ||
| res.set_content("Error", "text/plain"); | ||
| return; | ||
| } | ||
|
|
||
| auto account_id = std::stoi(req.get_file_value("AID").content); | ||
| auto char_id = std::stoi(req.get_file_value("GID").content); | ||
| auto world_name_str = req.get_file_value("WorldName").content; | ||
| auto world_name = world_name_str.c_str(); | ||
| std::string data; | ||
|
|
||
| if (req.has_file("data")) { | ||
| data = req.get_file_value("data").content; | ||
| addToJsonObject(data, "\"Type\": 1"); | ||
| } else { | ||
| data = "{\"Type\": 1}"; | ||
| } | ||
| SQLLock sl(WEB_SQL_LOCK); | ||
| sl.lock(); | ||
| auto handle = sl.getHandle(); | ||
| SqlStmt * stmt = SqlStmt_Malloc(handle); | ||
| if (SQL_SUCCESS != SqlStmt_Prepare(stmt, | ||
| "SELECT `account_id` FROM `%s` WHERE (`account_id` = ? AND `char_id` = ? AND `world_name` = ?) LIMIT 1", | ||
| char_configs_table) | ||
| || SQL_SUCCESS != SqlStmt_BindParam(stmt, 0, SQLDT_INT, &account_id, sizeof(account_id)) | ||
| || SQL_SUCCESS != SqlStmt_BindParam(stmt, 1, SQLDT_INT, &char_id, sizeof(char_id)) | ||
| || SQL_SUCCESS != SqlStmt_BindParam(stmt, 2, SQLDT_STRING, (void *)world_name, strlen(world_name)) | ||
| || SQL_SUCCESS != SqlStmt_Execute(stmt) | ||
| ) { | ||
| SqlStmt_ShowDebug(stmt); | ||
| SqlStmt_Free(stmt); | ||
| sl.unlock(); | ||
| res.status = 400; | ||
| res.set_content("Error", "text/plain"); | ||
| return; | ||
| } | ||
|
|
||
| if (SqlStmt_NumRows(stmt) <= 0) { | ||
| if (SQL_SUCCESS != SqlStmt_Prepare(stmt, | ||
| "INSERT INTO `%s` (`account_id`, `char_id`, `world_name`, `data`) VALUES (?, ?, ?, ?)", | ||
| char_configs_table) | ||
| || SQL_SUCCESS != SqlStmt_BindParam(stmt, 0, SQLDT_INT, &account_id, sizeof(account_id)) | ||
| || SQL_SUCCESS != SqlStmt_BindParam(stmt, 1, SQLDT_INT, &char_id, sizeof(char_id)) | ||
| || SQL_SUCCESS != SqlStmt_BindParam(stmt, 2, SQLDT_STRING, (void *)world_name, strlen(world_name)) | ||
| || SQL_SUCCESS != SqlStmt_BindParam(stmt, 3, SQLDT_STRING, (void *)data.c_str(), strlen(data.c_str())) | ||
| || SQL_SUCCESS != SqlStmt_Execute(stmt) | ||
| ) { | ||
| SqlStmt_ShowDebug(stmt); | ||
| SqlStmt_Free(stmt); | ||
| sl.unlock(); | ||
| res.status = 400; | ||
| res.set_content("Error", "text/plain"); | ||
| return; | ||
| } | ||
| } else { | ||
| if (SQL_SUCCESS != SqlStmt_Prepare(stmt, | ||
| "UPDATE `%s` SET `data` = ? WHERE (`account_id` = ? AND `char_id` = ? AND `world_name` = ?)", | ||
| char_configs_table) | ||
| || SQL_SUCCESS != SqlStmt_BindParam(stmt, 0, SQLDT_STRING, (void *)data.c_str(), strlen(data.c_str())) | ||
| || SQL_SUCCESS != SqlStmt_BindParam(stmt, 1, SQLDT_INT, &account_id, sizeof(account_id)) | ||
| || SQL_SUCCESS != SqlStmt_BindParam(stmt, 2, SQLDT_INT, &char_id, sizeof(char_id)) | ||
| || SQL_SUCCESS != SqlStmt_BindParam(stmt, 3, SQLDT_STRING, (void *)world_name, strlen(world_name)) | ||
| || SQL_SUCCESS != SqlStmt_Execute(stmt) | ||
| ) { | ||
| SqlStmt_ShowDebug(stmt); | ||
| SqlStmt_Free(stmt); | ||
| sl.unlock(); | ||
| res.status = 400; | ||
| res.set_content("Error", "text/plain"); | ||
| return; | ||
| } | ||
| } | ||
|
There was a problem hiding this comment. Just a stupid thought: REPLACE INTO? There was a problem hiding this comment. I think |
||
|
|
||
| SqlStmt_Free(stmt); | ||
| sl.unlock(); | ||
| res.set_content(data, "application/json"); | ||
| } | ||
|
|
||
| HANDLER_FUNC(charconfig_load) { | ||
| if (!req.has_file("AID") || !req.has_file("WorldName")) { | ||
| res.status = 400; | ||
| res.set_content("Error", "text/plain"); | ||
| return; | ||
| } | ||
|
|
||
| // TODO: Figure out when client sends AuthToken for this path, then add packetver check | ||
| // if (!isAuthorized(req)) { | ||
| // ShowError("Not authorized!\n"); | ||
| // message.reply(web::http::status_codes::Forbidden); | ||
| // return; | ||
| // } | ||
|
|
||
| auto account_id = std::stoi(req.get_file_value("AID").content); | ||
| auto char_id = std::stoi(req.get_file_value("GID").content); | ||
| auto world_name_str = req.get_file_value("WorldName").content; | ||
| auto world_name = world_name_str.c_str(); | ||
|
|
||
| SQLLock sl(WEB_SQL_LOCK); | ||
| sl.lock(); | ||
| auto handle = sl.getHandle(); | ||
| SqlStmt * stmt = SqlStmt_Malloc(handle); | ||
| if (SQL_SUCCESS != SqlStmt_Prepare(stmt, | ||
| "SELECT `data` FROM `%s` WHERE (`account_id` = ? AND `char_id` = ? AND `world_name` = ?) LIMIT 1", | ||
| char_configs_table) | ||
| || SQL_SUCCESS != SqlStmt_BindParam(stmt, 0, SQLDT_INT, &account_id, sizeof(account_id)) | ||
| || SQL_SUCCESS != SqlStmt_BindParam(stmt, 1, SQLDT_INT, &char_id, sizeof(char_id)) | ||
| || SQL_SUCCESS != SqlStmt_BindParam(stmt, 2, SQLDT_STRING, (void *)world_name, strlen(world_name)) | ||
| || SQL_SUCCESS != SqlStmt_Execute(stmt) | ||
| ) { | ||
| SqlStmt_ShowDebug(stmt); | ||
| SqlStmt_Free(stmt); | ||
| sl.unlock(); | ||
| res.status = 400; | ||
| res.set_content("Error", "text/plain"); | ||
| return; | ||
| } | ||
|
|
||
| if (SqlStmt_NumRows(stmt) <= 0) { | ||
| SqlStmt_Free(stmt); | ||
| ShowDebug("[AccountID: %d, CharID: %d, World: \"%s\"] Not found in table, sending new info.\n", account_id, char_id, world_name); | ||
| sl.unlock(); | ||
| res.set_content("{\"Type\": 1}", "application/json"); | ||
| return; | ||
| } | ||
|
|
||
| char databuf[SQL_BUFFER_SIZE]; | ||
|
|
||
| if (SQL_SUCCESS != SqlStmt_BindColumn(stmt, 0, SQLDT_STRING, &databuf, sizeof(databuf), NULL, NULL) | ||
| || SQL_SUCCESS != SqlStmt_NextRow(stmt) | ||
| ) { | ||
| SqlStmt_ShowDebug(stmt); | ||
| SqlStmt_Free(stmt); | ||
| sl.unlock(); | ||
| res.status = 400; | ||
| res.set_content("Error", "text/plain"); | ||
| return; | ||
| } | ||
|
|
||
| SqlStmt_Free(stmt); | ||
| sl.unlock(); | ||
|
|
||
| databuf[sizeof(databuf) - 1] = 0; | ||
| res.set_content(databuf, "application/json"); | ||
| } | ||
| @@ -0,0 +1,12 @@ | ||
| // Copyright (c) rAthena Dev Teams - Licensed under GNU GPL | ||
| // For more information, see LICENCE in the main folder | ||
|
|
||
| #ifndef CHARCONFIG_CONTROLLER_HPP | ||
| #define CHARCONFIG_CONTROLLER_HPP | ||
|
|
||
| #include "http.hpp" | ||
|
|
||
| HANDLER_FUNC(charconfig_save); | ||
| HANDLER_FUNC(charconfig_load); | ||
|
|
||
| #endif |
| @@ -0,0 +1,287 @@ | ||||||||||||||||||
| // Copyright (c) rAthena Dev Teams - Licensed under GNU GPL | ||||||||||||||||||
| // For more information, see LICENCE in the main folder | ||||||||||||||||||
|
|
||||||||||||||||||
| #include "emblem_controller.hpp" | ||||||||||||||||||
|
|
||||||||||||||||||
| #include <fstream> | ||||||||||||||||||
| #include <iostream> | ||||||||||||||||||
| #include <ostream> | ||||||||||||||||||
|
|
||||||||||||||||||
| #include "../common/showmsg.hpp" | ||||||||||||||||||
| #include "../common/socket.hpp" | ||||||||||||||||||
|
|
||||||||||||||||||
| #include "auth.hpp" | ||||||||||||||||||
| #include "http.hpp" | ||||||||||||||||||
| #include "sqllock.hpp" | ||||||||||||||||||
| #include "web.hpp" | ||||||||||||||||||
|
|
||||||||||||||||||
| // Max size is 50kb for gif | ||||||||||||||||||
| #define MAX_EMBLEM_SIZE 50000 | ||||||||||||||||||
|
There was a problem hiding this comment. Should be moved somewhere else i guess. |
||||||||||||||||||
| #define START_VERSION 1 | ||||||||||||||||||
|
|
||||||||||||||||||
| HANDLER_FUNC(emblem_download) { | ||||||||||||||||||
| if (!isAuthorized(req, false)) { | ||||||||||||||||||
| res.status = 400; | ||||||||||||||||||
| res.set_content("Error", "text/plain"); | ||||||||||||||||||
| return; | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| bool fail = false; | ||||||||||||||||||
| if (!req.has_file("GDID")) { | ||||||||||||||||||
| ShowDebug("Missing GuildID field for emblem download.\n"); | ||||||||||||||||||
| fail = true; | ||||||||||||||||||
| } | ||||||||||||||||||
| if (!req.has_file("WorldName")) { | ||||||||||||||||||
| ShowDebug("Missing WorldName field for emblem download.\n"); | ||||||||||||||||||
| fail = true; | ||||||||||||||||||
| } | ||||||||||||||||||
| if (fail) { | ||||||||||||||||||
| res.status = 400; | ||||||||||||||||||
| res.set_content("Error", "text/plain"); | ||||||||||||||||||
| return; | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| auto world_name_str = req.get_file_value("WorldName").content; | ||||||||||||||||||
| auto world_name = world_name_str.c_str(); | ||||||||||||||||||
| auto guild_id = std::stoi(req.get_file_value("GDID").content); | ||||||||||||||||||
|
|
||||||||||||||||||
| SQLLock sl(WEB_SQL_LOCK); | ||||||||||||||||||
| sl.lock(); | ||||||||||||||||||
| auto handle = sl.getHandle(); | ||||||||||||||||||
| SqlStmt * stmt = SqlStmt_Malloc(handle); | ||||||||||||||||||
| if (SQL_SUCCESS != SqlStmt_Prepare(stmt, | ||||||||||||||||||
| "SELECT `version`, `file_type`, `file_data` FROM `%s` WHERE (`guild_id` = ? AND `world_name` = ?)", | ||||||||||||||||||
| guild_emblems_table) | ||||||||||||||||||
| || SQL_SUCCESS != SqlStmt_BindParam(stmt, 0, SQLDT_INT, &guild_id, sizeof(guild_id)) | ||||||||||||||||||
| || SQL_SUCCESS != SqlStmt_BindParam(stmt, 1, SQLDT_STRING, (void *)world_name, strlen(world_name)) | ||||||||||||||||||
| || SQL_SUCCESS != SqlStmt_Execute(stmt) | ||||||||||||||||||
| ) { | ||||||||||||||||||
| SqlStmt_ShowDebug(stmt); | ||||||||||||||||||
| SqlStmt_Free(stmt); | ||||||||||||||||||
| sl.unlock(); | ||||||||||||||||||
| res.status = 400; | ||||||||||||||||||
| res.set_content("Error", "text/plain"); | ||||||||||||||||||
| return; | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| uint32 version = 0; | ||||||||||||||||||
| char filetype[256]; | ||||||||||||||||||
| char blob[MAX_EMBLEM_SIZE]; // yikes | ||||||||||||||||||
| uint32 emblem_size; | ||||||||||||||||||
|
|
||||||||||||||||||
| if (SqlStmt_NumRows(stmt) <= 0) { | ||||||||||||||||||
| SqlStmt_Free(stmt); | ||||||||||||||||||
| ShowError("[GuildID: %d / World: \"%s\"] Not found in table\n", guild_id, world_name); | ||||||||||||||||||
| sl.unlock(); | ||||||||||||||||||
| res.status = 404; | ||||||||||||||||||
| res.set_content("Error", "text/plain"); | ||||||||||||||||||
| return; | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
|
|
||||||||||||||||||
| if (SQL_SUCCESS != SqlStmt_BindColumn(stmt, 0, SQLDT_UINT32, &version, sizeof(version), nullptr, nullptr) | ||||||||||||||||||
| || SQL_SUCCESS != SqlStmt_BindColumn(stmt, 1, SQLDT_STRING, &filetype, sizeof(filetype), nullptr, nullptr) | ||||||||||||||||||
| || SQL_SUCCESS != SqlStmt_BindColumn(stmt, 2, SQLDT_BLOB, &blob, MAX_EMBLEM_SIZE, &emblem_size, nullptr) | ||||||||||||||||||
| || SQL_SUCCESS != SqlStmt_NextRow(stmt) | ||||||||||||||||||
| ) { | ||||||||||||||||||
| SqlStmt_ShowDebug(stmt); | ||||||||||||||||||
| SqlStmt_Free(stmt); | ||||||||||||||||||
| sl.unlock(); | ||||||||||||||||||
| res.status = 400; | ||||||||||||||||||
| res.set_content("Error", "text/plain"); | ||||||||||||||||||
| return; | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| SqlStmt_Free(stmt); | ||||||||||||||||||
| sl.unlock(); | ||||||||||||||||||
|
|
||||||||||||||||||
| if (emblem_size > MAX_EMBLEM_SIZE) { | ||||||||||||||||||
| ShowDebug("Emblem is too big, current size is %d and the max length is %d.\n", emblem_size, MAX_EMBLEM_SIZE); | ||||||||||||||||||
| res.status = 400; | ||||||||||||||||||
| res.set_content("Error", "text/plain"); | ||||||||||||||||||
| return; | ||||||||||||||||||
| } | ||||||||||||||||||
| const char * content_type; | ||||||||||||||||||
| if (!strcmp(filetype, "BMP")) | ||||||||||||||||||
| content_type = "image/bmp"; | ||||||||||||||||||
| else if (!strcmp(filetype, "GIF")) | ||||||||||||||||||
| content_type = "image/gif"; | ||||||||||||||||||
|
There was a problem hiding this comment.
Suggested change
|
||||||||||||||||||
| else { | ||||||||||||||||||
| ShowError("Invalid image type %s, rejecting!\n", filetype); | ||||||||||||||||||
| res.status = 404; | ||||||||||||||||||
| res.set_content("Error", "text/plain"); | ||||||||||||||||||
| return; | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| res.body.assign(blob, emblem_size); | ||||||||||||||||||
| res.set_header("Content-Type", content_type); | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
|
|
||||||||||||||||||
| HANDLER_FUNC(emblem_upload) { | ||||||||||||||||||
| if (!isAuthorized(req, true)) { | ||||||||||||||||||
| res.status = 400; | ||||||||||||||||||
| res.set_content("Error", "text/plain"); | ||||||||||||||||||
| return; | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| bool fail = false; | ||||||||||||||||||
| if (!req.has_file("GDID")) { | ||||||||||||||||||
| ShowDebug("Missing GuildID field for emblem upload.\n"); | ||||||||||||||||||
| fail = true; | ||||||||||||||||||
| } | ||||||||||||||||||
| if (!req.has_file("WorldName")) { | ||||||||||||||||||
| ShowDebug("Missing WorldName field for emblem upload.\n"); | ||||||||||||||||||
| fail = true; | ||||||||||||||||||
| } | ||||||||||||||||||
| if (!req.has_file("Img")) { | ||||||||||||||||||
| ShowDebug("Missing Img field for emblem upload.\n"); | ||||||||||||||||||
| fail = true; | ||||||||||||||||||
| } | ||||||||||||||||||
| if (!req.has_file("ImgType")) { | ||||||||||||||||||
| ShowDebug("Missing ImgType for emblem upload.\n"); | ||||||||||||||||||
| fail = true; | ||||||||||||||||||
| } | ||||||||||||||||||
| if (fail) { | ||||||||||||||||||
| res.status = 400; | ||||||||||||||||||
| res.set_content("Error", "text/plain"); | ||||||||||||||||||
| return; | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| auto world_name_str = req.get_file_value("WorldName").content; | ||||||||||||||||||
| auto world_name = world_name_str.c_str(); | ||||||||||||||||||
| auto guild_id = std::stoi(req.get_file_value("GDID").content); | ||||||||||||||||||
| auto imgtype_str = req.get_file_value("ImgType").content; | ||||||||||||||||||
| auto imgtype = imgtype_str.c_str(); | ||||||||||||||||||
| auto img = req.get_file_value("Img").content; | ||||||||||||||||||
| auto img_cstr = img.c_str(); | ||||||||||||||||||
|
|
||||||||||||||||||
| if (imgtype_str != "BMP" && imgtype_str != "GIF") { | ||||||||||||||||||
| ShowError("Invalid image type %s, rejecting!\n", imgtype); | ||||||||||||||||||
| res.status = 400; | ||||||||||||||||||
| res.set_content("Error", "text/plain"); | ||||||||||||||||||
| return; | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| auto length = img.length(); | ||||||||||||||||||
| if (length > MAX_EMBLEM_SIZE) { | ||||||||||||||||||
| ShowDebug("Emblem is too big, current size is %lu and the max length is %d.\n", length, MAX_EMBLEM_SIZE); | ||||||||||||||||||
| res.status = 400; | ||||||||||||||||||
| res.set_content("Error", "text/plain"); | ||||||||||||||||||
| return; | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| if (imgtype_str == "GIF") { | ||||||||||||||||||
| if (!web_config.allow_gifs) { | ||||||||||||||||||
| ShowDebug("Client sent GIF image but GIF image support is disabled.\n"); | ||||||||||||||||||
| res.status = 400; | ||||||||||||||||||
| res.set_content("Error", "text/plain"); | ||||||||||||||||||
| return; | ||||||||||||||||||
| } | ||||||||||||||||||
| if (img.substr(0, 3) != "GIF") { | ||||||||||||||||||
| ShowDebug("Server received ImgType GIF but received file does not start with \"GIF\" magic header.\n"); | ||||||||||||||||||
| res.status = 400; | ||||||||||||||||||
| res.set_content("Error", "text/plain"); | ||||||||||||||||||
| return; | ||||||||||||||||||
| } | ||||||||||||||||||
|
There was a problem hiding this comment. Please add a TODO mark here for the transparency check. |
||||||||||||||||||
| // TODO: transparency check for GIF emblems | ||||||||||||||||||
| } | ||||||||||||||||||
| else if (imgtype_str == "BMP") { | ||||||||||||||||||
| if (length < 14) { | ||||||||||||||||||
| ShowDebug("File size is too short\n"); | ||||||||||||||||||
| res.status = 400; | ||||||||||||||||||
| res.set_content("Error", "text/plain"); | ||||||||||||||||||
| return; | ||||||||||||||||||
| } | ||||||||||||||||||
| if (img.substr(0, 2) != "BM") { | ||||||||||||||||||
| ShowDebug("Server received ImgType BMP but received file does not start with \"BM\" magic header.\n"); | ||||||||||||||||||
| res.status = 400; | ||||||||||||||||||
| res.set_content("Error", "text/plain"); | ||||||||||||||||||
| return; | ||||||||||||||||||
| } | ||||||||||||||||||
| if (RBUFL(img_cstr, 2) != length) { | ||||||||||||||||||
| ShowDebug("Bitmap size doesn't match size in file header.\n"); | ||||||||||||||||||
| res.status = 400; | ||||||||||||||||||
| res.set_content("Error", "text/plain"); | ||||||||||||||||||
| return; | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| if (inter_config.emblem_transparency_limit < 100) { | ||||||||||||||||||
| uint32 offset = RBUFL(img_cstr, 0x0A); | ||||||||||||||||||
| int i, transcount = 1, tmp[3]; | ||||||||||||||||||
| for (i = offset; i < length - 1; i++) { | ||||||||||||||||||
| int j = i % 3; | ||||||||||||||||||
| tmp[j] = RBUFL(img_cstr, i); | ||||||||||||||||||
| if (j == 2 && (tmp[0] == 0xFFFF00FF) && (tmp[1] == 0xFFFF00) && (tmp[2] == 0xFF00FFFF)) //if pixel is transparent | ||||||||||||||||||
| transcount++; | ||||||||||||||||||
| } | ||||||||||||||||||
| if (((transcount * 300) / (length - offset)) > inter_config.emblem_transparency_limit) { | ||||||||||||||||||
| ShowDebug("Bitmap transparency check failed.\n"); | ||||||||||||||||||
| res.status = 400; | ||||||||||||||||||
| res.set_content("Error", "text/plain"); | ||||||||||||||||||
| return; | ||||||||||||||||||
| } | ||||||||||||||||||
| } | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| SQLLock sl(WEB_SQL_LOCK); | ||||||||||||||||||
| sl.lock(); | ||||||||||||||||||
| auto handle = sl.getHandle(); | ||||||||||||||||||
| SqlStmt * stmt = SqlStmt_Malloc(handle); | ||||||||||||||||||
| if (SQL_SUCCESS != SqlStmt_Prepare(stmt, | ||||||||||||||||||
| "SELECT `version` FROM `%s` WHERE (`guild_id` = ? AND `world_name` = ?)", | ||||||||||||||||||
| guild_emblems_table) | ||||||||||||||||||
| || SQL_SUCCESS != SqlStmt_BindParam(stmt, 0, SQLDT_INT, &guild_id, sizeof(guild_id)) | ||||||||||||||||||
| || SQL_SUCCESS != SqlStmt_BindParam(stmt, 1, SQLDT_STRING, (void *)world_name, strlen(world_name)) | ||||||||||||||||||
| || SQL_SUCCESS != SqlStmt_Execute(stmt) | ||||||||||||||||||
| ) { | ||||||||||||||||||
| SqlStmt_ShowDebug(stmt); | ||||||||||||||||||
| SqlStmt_Free(stmt); | ||||||||||||||||||
| sl.unlock(); | ||||||||||||||||||
| res.status = 400; | ||||||||||||||||||
| res.set_content("Error", "text/plain"); | ||||||||||||||||||
| return; | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| uint32 version = START_VERSION; | ||||||||||||||||||
|
|
||||||||||||||||||
| if (SqlStmt_NumRows(stmt) > 0) { | ||||||||||||||||||
| if (SQL_SUCCESS != SqlStmt_BindColumn(stmt, 0, SQLDT_UINT32, &version, sizeof(version), NULL, NULL) | ||||||||||||||||||
| || SQL_SUCCESS != SqlStmt_NextRow(stmt) | ||||||||||||||||||
| ) { | ||||||||||||||||||
| SqlStmt_ShowDebug(stmt); | ||||||||||||||||||
| SqlStmt_Free(stmt); | ||||||||||||||||||
| sl.unlock(); | ||||||||||||||||||
| res.status = 400; | ||||||||||||||||||
| res.set_content("Error", "text/plain"); | ||||||||||||||||||
| return; | ||||||||||||||||||
| } | ||||||||||||||||||
| version += 1; | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| // insert new | ||||||||||||||||||
| if (SQL_SUCCESS != SqlStmt_Prepare(stmt, | ||||||||||||||||||
|
There was a problem hiding this comment. Is it ok to "reuse" the stmt here without freeing the old one? There was a problem hiding this comment. Yes, SqlStmt_PrepareV frees the statement at the beginning. |
||||||||||||||||||
| "REPLACE INTO `%s` (`version`, `file_type`, `guild_id`, `world_name`, `file_data`) VALUES (?, ?, ?, ?, ?)", | ||||||||||||||||||
| guild_emblems_table) | ||||||||||||||||||
| || SQL_SUCCESS != SqlStmt_BindParam(stmt, 0, SQLDT_UINT32, &version, sizeof(version)) | ||||||||||||||||||
| || SQL_SUCCESS != SqlStmt_BindParam(stmt, 1, SQLDT_STRING, (void *)imgtype, strlen(imgtype)) | ||||||||||||||||||
| || SQL_SUCCESS != SqlStmt_BindParam(stmt, 2, SQLDT_INT, &guild_id, sizeof(guild_id)) | ||||||||||||||||||
| || SQL_SUCCESS != SqlStmt_BindParam(stmt, 3, SQLDT_STRING, (void *)world_name, strlen(world_name)) | ||||||||||||||||||
| || SQL_SUCCESS != SqlStmt_BindParam(stmt, 4, SQLDT_BLOB, (void *)img.c_str(), length) | ||||||||||||||||||
| || SQL_SUCCESS != SqlStmt_Execute(stmt) | ||||||||||||||||||
| ) { | ||||||||||||||||||
| SqlStmt_ShowDebug(stmt); | ||||||||||||||||||
| SqlStmt_Free(stmt); | ||||||||||||||||||
| sl.unlock(); | ||||||||||||||||||
| res.status = 400; | ||||||||||||||||||
| res.set_content("Error", "text/plain"); | ||||||||||||||||||
| return; | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| SqlStmt_Free(stmt); | ||||||||||||||||||
| sl.unlock(); | ||||||||||||||||||
|
|
||||||||||||||||||
| std::ostringstream stream; | ||||||||||||||||||
| stream << "{\"Type\":1,\"version\":" << version << "}"; | ||||||||||||||||||
| res.set_content(stream.str(), "application/json"); | ||||||||||||||||||
| } | ||||||||||||||||||
| @@ -0,0 +1,12 @@ | ||
| // Copyright (c) rAthena Dev Teams - Licensed under GNU GPL | ||
| // For more information, see LICENCE in the main folder | ||
|
|
||
| #ifndef EMBLEM_CONTROLLER_HPP | ||
| #define EMBLEM_CONTROLLER_HPP | ||
|
|
||
| #include "http.hpp" | ||
|
|
||
| HANDLER_FUNC(emblem_download); | ||
| HANDLER_FUNC(emblem_upload); | ||
|
|
||
| #endif |
| @@ -0,0 +1,21 @@ | ||
| // Copyright (c) rAthena Dev Teams - Licensed under GNU GPL | ||
| // For more information, see LICENCE in the main folder | ||
|
|
||
| #ifndef HTTP_HPP | ||
| #define HTTP_HPP | ||
|
|
||
| #ifdef WIN32 | ||
| #include "../common/winapi.hpp" | ||
| #endif | ||
|
|
||
| #include <httplib.h> | ||
|
|
||
|
|
||
| typedef httplib::Request Request; | ||
| typedef httplib::Response Response; | ||
|
|
||
| #define HANDLER_FUNC(x) void x (const Request &req, Response &res) | ||
| typedef HANDLER_FUNC((*handler_func)); | ||
|
There was a problem hiding this comment. I would have loved to see this making use of C++, but we can change that later on. There was a problem hiding this comment. Maybe with classes and static methods, probably a better implementation! |
||
|
|
||
|
|
||
| #endif | ||
| @@ -0,0 +1,169 @@ | ||
| // Copyright (c) rAthena Dev Teams - Licensed under GNU GPL | ||
| // For more information, see LICENCE in the main folder | ||
|
|
||
| #include "merchantstore_controller.hpp" | ||
|
|
||
| #include <string> | ||
|
|
||
| #include "../common/showmsg.hpp" | ||
| #include "../common/sql.hpp" | ||
|
|
||
| #include "auth.hpp" | ||
| #include "http.hpp" | ||
| #include "sqllock.hpp" | ||
| #include "webutils.hpp" | ||
| #include "web.hpp" | ||
|
|
||
| HANDLER_FUNC(merchantstore_save) { | ||
| if (!isAuthorized(req, false)) { | ||
| res.status = 400; | ||
| res.set_content("Error", "text/plain"); | ||
| return; | ||
| } | ||
|
|
||
| auto account_id = std::stoi(req.get_file_value("AID").content); | ||
| auto char_id = std::stoi(req.get_file_value("GID").content); | ||
| auto world_name_str = req.get_file_value("WorldName").content; | ||
| auto world_name = world_name_str.c_str(); | ||
| auto store_type = std::stoi(req.get_file_value("Type").content); | ||
| std::string data; | ||
|
|
||
| if (req.has_file("data")) { | ||
| data = req.get_file_value("data").content; | ||
| addToJsonObject(data, "\"Type\": 1"); | ||
| } else { | ||
| data = "{\"Type\": 1}"; | ||
| } | ||
| SQLLock sl(WEB_SQL_LOCK); | ||
| sl.lock(); | ||
| auto handle = sl.getHandle(); | ||
| SqlStmt* stmt = SqlStmt_Malloc(handle); | ||
| if (SQL_SUCCESS != SqlStmt_Prepare(stmt, | ||
| "SELECT `account_id` FROM `%s` WHERE (`account_id` = ? AND `char_id` = ? AND `world_name` = ? AND `store_type` = ?) LIMIT 1", | ||
| merchant_configs_table) | ||
| || SQL_SUCCESS != SqlStmt_BindParam(stmt, 0, SQLDT_INT, &account_id, sizeof(account_id)) | ||
| || SQL_SUCCESS != SqlStmt_BindParam(stmt, 1, SQLDT_INT, &char_id, sizeof(char_id)) | ||
| || SQL_SUCCESS != SqlStmt_BindParam(stmt, 2, SQLDT_STRING, (void *)world_name, strlen(world_name)) | ||
| || SQL_SUCCESS != SqlStmt_BindParam(stmt, 3, SQLDT_INT, &store_type, sizeof(store_type)) | ||
| || SQL_SUCCESS != SqlStmt_Execute(stmt) | ||
| ) { | ||
| SqlStmt_ShowDebug(stmt); | ||
| SqlStmt_Free(stmt); | ||
| sl.unlock(); | ||
| res.status = 400; | ||
| res.set_content("Error", "text/plain"); | ||
| return; | ||
| } | ||
|
|
||
| if (SqlStmt_NumRows(stmt) <= 0) { | ||
| if (SQL_SUCCESS != SqlStmt_Prepare(stmt, | ||
| "INSERT INTO `%s` (`account_id`, `char_id`, `world_name`, `store_type`, `data`) VALUES (?, ?, ?, ?, ?)", | ||
| merchant_configs_table) | ||
| || SQL_SUCCESS != SqlStmt_BindParam(stmt, 0, SQLDT_INT, &account_id, sizeof(account_id)) | ||
| || SQL_SUCCESS != SqlStmt_BindParam(stmt, 1, SQLDT_INT, &char_id, sizeof(char_id)) | ||
| || SQL_SUCCESS != SqlStmt_BindParam(stmt, 2, SQLDT_STRING, (void *)world_name, strlen(world_name)) | ||
| || SQL_SUCCESS != SqlStmt_BindParam(stmt, 3, SQLDT_INT, &store_type, sizeof(store_type)) | ||
| || SQL_SUCCESS != SqlStmt_BindParam(stmt, 4, SQLDT_STRING, (void *)data.c_str(), strlen(data.c_str())) | ||
| || SQL_SUCCESS != SqlStmt_Execute(stmt) | ||
| ) { | ||
| SqlStmt_ShowDebug(stmt); | ||
| SqlStmt_Free(stmt); | ||
| sl.unlock(); | ||
| res.status = 400; | ||
| res.set_content("Error", "text/plain"); | ||
| return; | ||
| } | ||
| } | ||
| else { | ||
| if (SQL_SUCCESS != SqlStmt_Prepare(stmt, | ||
| "UPDATE `%s` SET `data` = ? WHERE (`account_id` = ? AND `char_id` = ? AND `world_name` = ? AND `store_type` = ?)", | ||
| merchant_configs_table) | ||
| || SQL_SUCCESS != SqlStmt_BindParam(stmt, 0, SQLDT_STRING, (void *)data.c_str(), strlen(data.c_str())) | ||
| || SQL_SUCCESS != SqlStmt_BindParam(stmt, 1, SQLDT_INT, &account_id, sizeof(account_id)) | ||
| || SQL_SUCCESS != SqlStmt_BindParam(stmt, 2, SQLDT_INT, &char_id, sizeof(char_id)) | ||
| || SQL_SUCCESS != SqlStmt_BindParam(stmt, 3, SQLDT_STRING, (void *)world_name, strlen(world_name)) | ||
| || SQL_SUCCESS != SqlStmt_BindParam(stmt, 4, SQLDT_INT, &store_type, sizeof(store_type)) | ||
| || SQL_SUCCESS != SqlStmt_Execute(stmt) | ||
| ) { | ||
| SqlStmt_ShowDebug(stmt); | ||
| SqlStmt_Free(stmt); | ||
| sl.unlock(); | ||
| res.status = 400; | ||
| res.set_content("Error", "text/plain"); | ||
| return; | ||
| } | ||
| } | ||
|
|
||
|
|
||
| SqlStmt_Free(stmt); | ||
| sl.unlock(); | ||
| res.set_content(data, "application/json"); | ||
| } | ||
|
|
||
| HANDLER_FUNC(merchantstore_load) { | ||
| if (!req.has_file("AID") || !req.has_file("WorldName")) { | ||
| res.status = 400; | ||
| res.set_content("Error", "text/plain"); | ||
| return; | ||
| } | ||
|
|
||
| // TODO: Figure out when client sends AuthToken for this path, then add packetver check | ||
| // if (!isAuthorized(req)) { | ||
| // ShowError("Not authorized!\n"); | ||
| // message.reply(web::http::status_codes::Forbidden); | ||
| // return; | ||
| // } | ||
|
|
||
| auto account_id = std::stoi(req.get_file_value("AID").content); | ||
| auto char_id = std::stoi(req.get_file_value("GID").content); | ||
| auto world_name_str = req.get_file_value("WorldName").content; | ||
| auto world_name = world_name_str.c_str(); | ||
| auto store_type = std::stoi(req.get_file_value("Type").content); | ||
|
|
||
| SQLLock sl(WEB_SQL_LOCK); | ||
| sl.lock(); | ||
| auto handle = sl.getHandle(); | ||
| SqlStmt* stmt = SqlStmt_Malloc(handle); | ||
| if (SQL_SUCCESS != SqlStmt_Prepare(stmt, | ||
| "SELECT `data` FROM `%s` WHERE (`account_id` = ? AND `char_id` = ? AND `world_name` = ? AND `store_type` = ?) LIMIT 1", | ||
| merchant_configs_table) | ||
| || SQL_SUCCESS != SqlStmt_BindParam(stmt, 0, SQLDT_INT, &account_id, sizeof(account_id)) | ||
| || SQL_SUCCESS != SqlStmt_BindParam(stmt, 1, SQLDT_INT, &char_id, sizeof(char_id)) | ||
| || SQL_SUCCESS != SqlStmt_BindParam(stmt, 2, SQLDT_STRING, (void *)world_name, strlen(world_name)) | ||
| || SQL_SUCCESS != SqlStmt_BindParam(stmt, 3, SQLDT_INT, &store_type, sizeof(store_type)) | ||
| || SQL_SUCCESS != SqlStmt_Execute(stmt) | ||
| ) { | ||
| SqlStmt_ShowDebug(stmt); | ||
| SqlStmt_Free(stmt); | ||
| sl.unlock(); | ||
| res.status = 400; | ||
| res.set_content("Error", "text/plain"); | ||
| return; | ||
| } | ||
|
|
||
| if (SqlStmt_NumRows(stmt) <= 0) { | ||
| SqlStmt_Free(stmt); | ||
| ShowDebug("[AccountID: %d, World: \"%s\"] Not found in table, sending new info.\n", account_id, world_name); | ||
| sl.unlock(); | ||
| res.set_content("{\"Type\": 1}", "application/json"); | ||
| return; | ||
| } | ||
|
|
||
| char databuf[SQL_BUFFER_SIZE] = { 0 }; | ||
|
|
||
| if (SQL_SUCCESS != SqlStmt_BindColumn(stmt, 0, SQLDT_STRING, &databuf, sizeof(databuf), NULL, NULL) | ||
| || SQL_SUCCESS != SqlStmt_NextRow(stmt) | ||
| ) { | ||
| SqlStmt_ShowDebug(stmt); | ||
| SqlStmt_Free(stmt); | ||
| sl.unlock(); | ||
| res.status = 400; | ||
| res.set_content("Error", "text/plain"); | ||
| return; | ||
| } | ||
|
|
||
| SqlStmt_Free(stmt); | ||
| sl.unlock(); | ||
|
|
||
| databuf[sizeof(databuf) - 1] = 0; | ||
| res.set_content(databuf, "application/json"); | ||
| } | ||
| @@ -0,0 +1,12 @@ | ||
| // Copyright (c) rAthena Dev Teams - Licensed under GNU GPL | ||
| // For more information, see LICENCE in the main folder | ||
|
|
||
| #ifndef MERCHANTSTORE_CONTROLLER_HPP | ||
| #define MERCHANTSTORE_CONTROLLER_HPP | ||
|
|
||
| #include "http.hpp" | ||
|
|
||
| HANDLER_FUNC(merchantstore_save); | ||
| HANDLER_FUNC(merchantstore_load); | ||
|
|
||
| #endif |
| @@ -0,0 +1,71 @@ | ||
| // Copyright (c) rAthena Dev Teams - Licensed under GNU GPL | ||
| // For more information, see LICENCE in the main folder | ||
|
|
||
| #include "sqllock.hpp" | ||
|
|
||
| #include <mutex> | ||
|
|
||
| #include "../common/showmsg.hpp" | ||
|
|
||
| std::mutex dbmutex; | ||
|
|
||
| extern Sql * login_handle; | ||
| extern Sql * char_handle; | ||
| extern Sql * web_handle; | ||
|
|
||
|
|
||
| SQLLock::SQLLock(locktype lt) : ulock(dbmutex, std::defer_lock), lt(lt) { | ||
| switch(lt) { | ||
| case LOGIN_SQL_LOCK: | ||
| handle = login_handle; | ||
| break; | ||
| case CHAR_SQL_LOCK: | ||
| handle = char_handle; | ||
| break; | ||
| case WEB_SQL_LOCK: | ||
| handle = web_handle; | ||
| break; | ||
| } | ||
| } | ||
|
|
||
| void SQLLock::lock() { | ||
| // switch(lt) { | ||
| // case LOGIN_SQL_LOCK: | ||
| // ShowDebug("Locking login sql\n"); | ||
| // break; | ||
| // case CHAR_SQL_LOCK: | ||
| // ShowDebug("Locking char sql\n"); | ||
| // break; | ||
| // case WEB_SQL_LOCK: | ||
| // ShowDebug("Locking web sql\n"); | ||
| // break; | ||
| // } | ||
| ulock.lock(); | ||
| } | ||
|
|
||
| void SQLLock::unlock() { | ||
| ulock.unlock(); | ||
| // switch(lt) { | ||
| // case LOGIN_SQL_LOCK: | ||
| // ShowDebug("Unlocked login sql\n"); | ||
| // break; | ||
| // case CHAR_SQL_LOCK: | ||
| // ShowDebug("Unlocked char sql\n"); | ||
| // break; | ||
| // case WEB_SQL_LOCK: | ||
| // ShowDebug("Unlocked web sql\n"); | ||
| // break; | ||
| // } | ||
|
|
||
| } | ||
|
|
||
|
|
||
| // can only get handle if locked | ||
| Sql * SQLLock::getHandle() { | ||
| if (!ulock.owns_lock()) | ||
| return nullptr; | ||
| return handle; | ||
| } | ||
|
|
||
| SQLLock::~SQLLock() { | ||
| } |
| @@ -0,0 +1,32 @@ | ||
| // Copyright (c) rAthena Dev Teams - Licensed under GNU GPL | ||
| // For more information, see LICENCE in the main folder | ||
|
|
||
| #ifndef SQLLOCK_HPP | ||
| #define SQLLOCK_HPP | ||
|
|
||
| #include <mutex> | ||
|
|
||
| #include "../common/sql.hpp" | ||
|
|
||
| enum locktype { | ||
| LOGIN_SQL_LOCK, | ||
| CHAR_SQL_LOCK, | ||
| WEB_SQL_LOCK | ||
| }; | ||
|
|
||
| class SQLLock { | ||
| private: | ||
| std::unique_lock<std::mutex> ulock; | ||
| Sql * handle; | ||
| locktype lt; | ||
|
|
||
| public: | ||
| SQLLock(locktype); | ||
| ~SQLLock(); | ||
| void lock(); | ||
| void unlock(); | ||
| Sql * getHandle(); | ||
| }; | ||
|
|
||
|
|
||
| #endif |
| @@ -0,0 +1,156 @@ | ||
| // Copyright (c) rAthena Dev Teams - Licensed under GNU GPL | ||
| // For more information, see LICENCE in the main folder | ||
|
|
||
| #include "userconfig_controller.hpp" | ||
|
|
||
| #include <string> | ||
|
|
||
| #include "../common/showmsg.hpp" | ||
| #include "../common/sql.hpp" | ||
|
|
||
| #include "auth.hpp" | ||
| #include "http.hpp" | ||
| #include "sqllock.hpp" | ||
| #include "webutils.hpp" | ||
| #include "web.hpp" | ||
|
|
||
| HANDLER_FUNC(userconfig_save) { | ||
| if (!isAuthorized(req, false)) { | ||
| res.status = 400; | ||
| res.set_content("Error", "text/plain"); | ||
| return; | ||
| } | ||
|
|
||
| auto account_id = std::stoi(req.get_file_value("AID").content); | ||
| auto world_name_str = req.get_file_value("WorldName").content; | ||
| auto world_name = world_name_str.c_str(); | ||
| std::string data; | ||
|
|
||
| if (req.has_file("data")) { | ||
| data = req.get_file_value("data").content; | ||
| addToJsonObject(data, "\"Type\": 1"); | ||
| } else { | ||
| data = "{\"Type\": 1}"; | ||
| } | ||
| SQLLock sl(WEB_SQL_LOCK); | ||
| sl.lock(); | ||
| auto handle = sl.getHandle(); | ||
| SqlStmt * stmt = SqlStmt_Malloc(handle); | ||
| if (SQL_SUCCESS != SqlStmt_Prepare(stmt, | ||
| "SELECT `account_id` FROM `%s` WHERE (`account_id` = ? AND `world_name` = ?) LIMIT 1", | ||
| user_configs_table) | ||
| || SQL_SUCCESS != SqlStmt_BindParam(stmt, 0, SQLDT_INT, &account_id, sizeof(account_id)) | ||
| || SQL_SUCCESS != SqlStmt_BindParam(stmt, 1, SQLDT_STRING, (void *)world_name, strlen(world_name)) | ||
| || SQL_SUCCESS != SqlStmt_Execute(stmt) | ||
| ) { | ||
| SqlStmt_ShowDebug(stmt); | ||
| SqlStmt_Free(stmt); | ||
| sl.unlock(); | ||
| res.status = 400; | ||
| res.set_content("Error", "text/plain"); | ||
| return; | ||
| } | ||
|
|
||
| if (SqlStmt_NumRows(stmt) <= 0) { | ||
| if (SQL_SUCCESS != SqlStmt_Prepare(stmt, | ||
| "INSERT INTO `%s` (`account_id`, `world_name`, `data`) VALUES (?, ?, ?)", | ||
| user_configs_table) | ||
| || SQL_SUCCESS != SqlStmt_BindParam(stmt, 0, SQLDT_INT, &account_id, sizeof(account_id)) | ||
| || SQL_SUCCESS != SqlStmt_BindParam(stmt, 1, SQLDT_STRING, (void *)world_name, strlen(world_name)) | ||
| || SQL_SUCCESS != SqlStmt_BindParam(stmt, 2, SQLDT_STRING, (void *)data.c_str(), strlen(data.c_str())) | ||
| || SQL_SUCCESS != SqlStmt_Execute(stmt) | ||
| ) { | ||
| SqlStmt_ShowDebug(stmt); | ||
| SqlStmt_Free(stmt); | ||
| sl.unlock(); | ||
| res.status = 400; | ||
| res.set_content("Error", "text/plain"); | ||
| return; | ||
| } | ||
| } else { | ||
| if (SQL_SUCCESS != SqlStmt_Prepare(stmt, | ||
| "UPDATE `%s` SET `data` = ? WHERE (`account_id` = ? AND `world_name` = ?)", | ||
| user_configs_table) | ||
| || SQL_SUCCESS != SqlStmt_BindParam(stmt, 0, SQLDT_STRING, (void *)data.c_str(), strlen(data.c_str())) | ||
| || SQL_SUCCESS != SqlStmt_BindParam(stmt, 1, SQLDT_INT, &account_id, sizeof(account_id)) | ||
| || SQL_SUCCESS != SqlStmt_BindParam(stmt, 2, SQLDT_STRING, (void *)world_name, strlen(world_name)) | ||
| || SQL_SUCCESS != SqlStmt_Execute(stmt) | ||
| ) { | ||
| SqlStmt_ShowDebug(stmt); | ||
| SqlStmt_Free(stmt); | ||
| sl.unlock(); | ||
| res.status = 400; | ||
| res.set_content("Error", "text/plain"); | ||
| return; | ||
| } | ||
| } | ||
|
|
||
|
|
||
| SqlStmt_Free(stmt); | ||
| sl.unlock(); | ||
| res.set_content(data, "application/json"); | ||
| } | ||
|
|
||
| HANDLER_FUNC(userconfig_load) { | ||
| if (!req.has_file("AID") || !req.has_file("WorldName")) { | ||
| res.status = 400; | ||
| res.set_content("Error", "text/plain"); | ||
| return; | ||
| } | ||
|
|
||
| // TODO: Figure out when client sends AuthToken for this path, then add packetver check | ||
| // if (!isAuthorized(req)) { | ||
| // ShowError("Not authorized!\n"); | ||
| // message.reply(web::http::status_codes::Forbidden); | ||
| // return; | ||
| // } | ||
|
|
||
| auto account_id = std::stoi(req.get_file_value("AID").content); | ||
| auto world_name_str = req.get_file_value("WorldName").content; | ||
| auto world_name = world_name_str.c_str(); | ||
|
|
||
| SQLLock sl(WEB_SQL_LOCK); | ||
| sl.lock(); | ||
| auto handle = sl.getHandle(); | ||
| SqlStmt * stmt = SqlStmt_Malloc(handle); | ||
| if (SQL_SUCCESS != SqlStmt_Prepare(stmt, | ||
| "SELECT `data` FROM `%s` WHERE (`account_id` = ? AND `world_name` = ?) LIMIT 1", | ||
| user_configs_table) | ||
| || SQL_SUCCESS != SqlStmt_BindParam(stmt, 0, SQLDT_INT, &account_id, sizeof(account_id)) | ||
| || SQL_SUCCESS != SqlStmt_BindParam(stmt, 1, SQLDT_STRING, (void *)world_name, strlen(world_name)) | ||
| || SQL_SUCCESS != SqlStmt_Execute(stmt) | ||
| ) { | ||
| SqlStmt_ShowDebug(stmt); | ||
| SqlStmt_Free(stmt); | ||
| sl.unlock(); | ||
| res.status = 400; | ||
| res.set_content("Error", "text/plain"); | ||
| return; | ||
| } | ||
|
|
||
| if (SqlStmt_NumRows(stmt) <= 0) { | ||
| SqlStmt_Free(stmt); | ||
| ShowDebug("[AccountID: %d, World: \"%s\"] Not found in table, sending new info.\n", account_id, world_name); | ||
| sl.unlock(); | ||
| res.set_content("{\"Type\": 1}", "application/json"); | ||
| return; | ||
| } | ||
|
|
||
| char databuf[SQL_BUFFER_SIZE]; | ||
|
|
||
| if (SQL_SUCCESS != SqlStmt_BindColumn(stmt, 0, SQLDT_STRING, &databuf, sizeof(databuf), NULL, NULL) | ||
| || SQL_SUCCESS != SqlStmt_NextRow(stmt) | ||
| ) { | ||
| SqlStmt_ShowDebug(stmt); | ||
| SqlStmt_Free(stmt); | ||
| sl.unlock(); | ||
| res.status = 400; | ||
| res.set_content("Error", "text/plain"); | ||
| return; | ||
| } | ||
|
|
||
| SqlStmt_Free(stmt); | ||
| sl.unlock(); | ||
|
|
||
| databuf[sizeof(databuf) - 1] = 0; | ||
| res.set_content(databuf, "application/json"); | ||
| } | ||
| @@ -0,0 +1,12 @@ | ||
| // Copyright (c) rAthena Dev Teams - Licensed under GNU GPL | ||
| // For more information, see LICENCE in the main folder | ||
|
|
||
| #ifndef USERCONFIG_CONTROLLER_HPP | ||
| #define USERCONFIG_CONTROLLER_HPP | ||
|
|
||
| #include "http.hpp" | ||
|
|
||
| HANDLER_FUNC(userconfig_save); | ||
| HANDLER_FUNC(userconfig_load); | ||
|
|
||
| #endif |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe put a small comment here that the web server also uses the character server's guild table?
And also the login server's login table (token)?
Otherwise redefine them here with a new name, but then again putting it into inter is useless in general.