commit
e3ba3b8f97
@ -145,7 +145,6 @@ endif
|
||||
INCLUDE = -I.\
|
||||
-Isource\
|
||||
-Isource/md5\
|
||||
-IWebServer\
|
||||
-Isource/items\
|
||||
-Isource/blocks\
|
||||
-Itolua++-1.0.93/src/lib\
|
||||
@ -167,7 +166,7 @@ INCLUDE = -I.\
|
||||
|
||||
# 2012_11_08 _X: Removed: squirrel_3_0_1_stable
|
||||
|
||||
SOURCES := $(shell find CryptoPP lua-5.1.4 jsoncpp-src-0.5.0 zlib-1.2.7 source tolua++-1.0.93 iniFile WebServer expat '(' -name '*.cpp' -o -name '*.c' ')')
|
||||
SOURCES := $(shell find CryptoPP lua-5.1.4 jsoncpp-src-0.5.0 zlib-1.2.7 source tolua++-1.0.93 iniFile expat '(' -name '*.cpp' -o -name '*.c' ')')
|
||||
SOURCES := $(filter-out %minigzip.c %lua.c %tolua.c %toluabind.c %LeakFinder.cpp %StackWalker.cpp %example.c,$(SOURCES))
|
||||
OBJECTS := $(patsubst %.c,$(BUILDDIR)%.o,$(SOURCES))
|
||||
OBJECTS := $(patsubst %.cpp,$(BUILDDIR)%.o,$(OBJECTS))
|
||||
|
@ -2,7 +2,6 @@ Microsoft Visual Studio Solution File, Format Version 10.00
|
||||
# Visual C++ Express 2008
|
||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "MCServer", "MCServer.vcproj", "{32012054-0C96-4C43-AB27-174FF8E72D66}"
|
||||
ProjectSection(ProjectDependencies) = postProject
|
||||
{9A476537-42C0-4848-AB40-15CFE83D17A8} = {9A476537-42C0-4848-AB40-15CFE83D17A8}
|
||||
{082E8185-7B3A-4945-8C82-9132341A329D} = {082E8185-7B3A-4945-8C82-9132341A329D}
|
||||
{5FCFAF8D-FF2C-456D-A72C-1D76F913AD96} = {5FCFAF8D-FF2C-456D-A72C-1D76F913AD96}
|
||||
{3423EC9A-52E4-4A4D-9753-EDEBC38785EF} = {3423EC9A-52E4-4A4D-9753-EDEBC38785EF}
|
||||
@ -19,8 +18,6 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Lua", "Lua.vcproj", "{082E8
|
||||
EndProject
|
||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ToLua", "ToLua.vcproj", "{EEAB54AD-114C-4AB8-8482-0A52D502BD35}"
|
||||
EndProject
|
||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "WebServer", "WebServer.vcproj", "{9A476537-42C0-4848-AB40-15CFE83D17A8}"
|
||||
EndProject
|
||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "CryptoPP", "CryptoPP.vcproj", "{3423EC9A-52E4-4A4D-9753-EDEBC38785EF}"
|
||||
EndProject
|
||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "expat", "expat.vcproj", "{5FCFAF8D-FF2C-456D-A72C-1D76F913AD96}"
|
||||
@ -73,14 +70,6 @@ Global
|
||||
{EEAB54AD-114C-4AB8-8482-0A52D502BD35}.Release profiled|Win32.Build.0 = Release profiled|Win32
|
||||
{EEAB54AD-114C-4AB8-8482-0A52D502BD35}.Release|Win32.ActiveCfg = Release|Win32
|
||||
{EEAB54AD-114C-4AB8-8482-0A52D502BD35}.Release|Win32.Build.0 = Release|Win32
|
||||
{9A476537-42C0-4848-AB40-15CFE83D17A8}.Debug profiled|Win32.ActiveCfg = Debug profiled|Win32
|
||||
{9A476537-42C0-4848-AB40-15CFE83D17A8}.Debug profiled|Win32.Build.0 = Debug profiled|Win32
|
||||
{9A476537-42C0-4848-AB40-15CFE83D17A8}.Debug|Win32.ActiveCfg = Debug|Win32
|
||||
{9A476537-42C0-4848-AB40-15CFE83D17A8}.Debug|Win32.Build.0 = Debug|Win32
|
||||
{9A476537-42C0-4848-AB40-15CFE83D17A8}.Release profiled|Win32.ActiveCfg = Release profiled|Win32
|
||||
{9A476537-42C0-4848-AB40-15CFE83D17A8}.Release profiled|Win32.Build.0 = Release profiled|Win32
|
||||
{9A476537-42C0-4848-AB40-15CFE83D17A8}.Release|Win32.ActiveCfg = Release|Win32
|
||||
{9A476537-42C0-4848-AB40-15CFE83D17A8}.Release|Win32.Build.0 = Release|Win32
|
||||
{3423EC9A-52E4-4A4D-9753-EDEBC38785EF}.Debug profiled|Win32.ActiveCfg = Debug|Win32
|
||||
{3423EC9A-52E4-4A4D-9753-EDEBC38785EF}.Debug profiled|Win32.Build.0 = Debug|Win32
|
||||
{3423EC9A-52E4-4A4D-9753-EDEBC38785EF}.Debug|Win32.ActiveCfg = Debug|Win32
|
||||
|
@ -2716,6 +2716,66 @@
|
||||
>
|
||||
</File>
|
||||
</Filter>
|
||||
<Filter
|
||||
Name="HTTPServer"
|
||||
>
|
||||
<File
|
||||
RelativePath="..\source\HTTPServer\EnvelopeParser.cpp"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath="..\source\HTTPServer\EnvelopeParser.h"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath="..\source\HTTPServer\HTTPConnection.cpp"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath="..\source\HTTPServer\HTTPConnection.h"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath="..\source\HTTPServer\HTTPFormParser.cpp"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath="..\source\HTTPServer\HTTPFormParser.h"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath="..\source\HTTPServer\HTTPMessage.cpp"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath="..\source\HTTPServer\HTTPMessage.h"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath="..\source\HTTPServer\HTTPServer.cpp"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath="..\source\HTTPServer\HTTPServer.h"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath="..\source\HTTPServer\MultipartParser.cpp"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath="..\source\HTTPServer\MultipartParser.h"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath="..\source\HTTPServer\NameValueParser.cpp"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath="..\source\HTTPServer\NameValueParser.h"
|
||||
>
|
||||
</File>
|
||||
</Filter>
|
||||
</Filter>
|
||||
<Filter
|
||||
Name="Config files"
|
||||
|
@ -1,77 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
|
||||
<CodeBlocks_project_file>
|
||||
<FileVersion major="1" minor="6" />
|
||||
<Project>
|
||||
<Option title="WebServer" />
|
||||
<Option pch_mode="2" />
|
||||
<Option compiler="gcc" />
|
||||
<Build>
|
||||
<Target title="Debug Win32">
|
||||
<Option output="lib" prefix_auto="1" extension_auto="1" />
|
||||
<Option working_dir="" />
|
||||
<Option object_output="Debug Win32/webserver" />
|
||||
<Option type="2" />
|
||||
<Option compiler="gcc" />
|
||||
<Option createDefFile="1" />
|
||||
<Compiler>
|
||||
<Add option="-DWIN32" />
|
||||
<Add option="-D_DEBUG" />
|
||||
<Add option="-D_LIB" />
|
||||
<Add option="-W" />
|
||||
<Add option="-g" />
|
||||
<Add option="-O0" />
|
||||
</Compiler>
|
||||
</Target>
|
||||
<Target title="Release Win32">
|
||||
<Option output="lib" prefix_auto="1" extension_auto="1" />
|
||||
<Option working_dir="" />
|
||||
<Option object_output="Release Win32/webserver" />
|
||||
<Option type="2" />
|
||||
<Option compiler="gcc" />
|
||||
<Option createDefFile="1" />
|
||||
<Compiler>
|
||||
<Add option="-DWIN32" />
|
||||
<Add option="-DNDEBUG" />
|
||||
<Add option="-D_LIB" />
|
||||
<Add option="-W" />
|
||||
<Add option="-O2" />
|
||||
</Compiler>
|
||||
</Target>
|
||||
<Target title="Release profiled Win32">
|
||||
<Option output="lib" prefix_auto="1" extension_auto="1" />
|
||||
<Option working_dir="" />
|
||||
<Option object_output="Release profiled Win32/webserver" />
|
||||
<Option type="2" />
|
||||
<Option compiler="gcc" />
|
||||
<Option createDefFile="1" />
|
||||
<Compiler>
|
||||
<Add option="-DWIN32" />
|
||||
<Add option="-DNDEBUG" />
|
||||
<Add option="-D_LIB" />
|
||||
<Add option="-W" />
|
||||
<Add option="-O2" />
|
||||
</Compiler>
|
||||
</Target>
|
||||
</Build>
|
||||
<Unit filename="../WebServer/Events.cpp" />
|
||||
<Unit filename="../WebServer/Events.h" />
|
||||
<Unit filename="../WebServer/Globals.cpp" />
|
||||
<Unit filename="../WebServer/Globals.h" />
|
||||
<Unit filename="../WebServer/Socket.cpp" />
|
||||
<Unit filename="../WebServer/Socket.h" />
|
||||
<Unit filename="../WebServer/StdHelpers.cpp" />
|
||||
<Unit filename="../WebServer/StdHelpers.h" />
|
||||
<Unit filename="../WebServer/Tracer.h" />
|
||||
<Unit filename="../WebServer/UrlHelper.cpp" />
|
||||
<Unit filename="../WebServer/UrlHelper.h" />
|
||||
<Unit filename="../WebServer/WebServer.cpp" />
|
||||
<Unit filename="../WebServer/WebServer.h" />
|
||||
<Unit filename="../WebServer/base64.cpp" />
|
||||
<Unit filename="../WebServer/base64.h" />
|
||||
<Extensions>
|
||||
<code_completion />
|
||||
<envvars />
|
||||
<debugger />
|
||||
</Extensions>
|
||||
</Project>
|
||||
</CodeBlocks_project_file>
|
@ -1,374 +0,0 @@
|
||||
<?xml version="1.0" encoding="windows-1250"?>
|
||||
<VisualStudioProject
|
||||
ProjectType="Visual C++"
|
||||
Version="9,00"
|
||||
Name="WebServer"
|
||||
ProjectGUID="{9A476537-42C0-4848-AB40-15CFE83D17A8}"
|
||||
RootNamespace="WebServer"
|
||||
Keyword="Win32Proj"
|
||||
TargetFrameworkVersion="196613"
|
||||
>
|
||||
<Platforms>
|
||||
<Platform
|
||||
Name="Win32"
|
||||
/>
|
||||
</Platforms>
|
||||
<ToolFiles>
|
||||
</ToolFiles>
|
||||
<Configurations>
|
||||
<Configuration
|
||||
Name="Debug|Win32"
|
||||
OutputDirectory="$(SolutionDir)$(ConfigurationName)\WebServer"
|
||||
IntermediateDirectory="$(ConfigurationName)\webserver"
|
||||
ConfigurationType="4"
|
||||
CharacterSet="1"
|
||||
>
|
||||
<Tool
|
||||
Name="VCPreBuildEventTool"
|
||||
/>
|
||||
<Tool
|
||||
Name="VCCustomBuildTool"
|
||||
/>
|
||||
<Tool
|
||||
Name="VCXMLDataGeneratorTool"
|
||||
/>
|
||||
<Tool
|
||||
Name="VCWebServiceProxyGeneratorTool"
|
||||
/>
|
||||
<Tool
|
||||
Name="VCMIDLTool"
|
||||
/>
|
||||
<Tool
|
||||
Name="VCCLCompilerTool"
|
||||
Optimization="0"
|
||||
PreprocessorDefinitions="WIN32;_DEBUG;_LIB"
|
||||
MinimalRebuild="true"
|
||||
BasicRuntimeChecks="3"
|
||||
RuntimeLibrary="1"
|
||||
UsePrecompiledHeader="2"
|
||||
PrecompiledHeaderThrough="Globals.h"
|
||||
WarningLevel="3"
|
||||
DebugInformationFormat="4"
|
||||
/>
|
||||
<Tool
|
||||
Name="VCManagedResourceCompilerTool"
|
||||
/>
|
||||
<Tool
|
||||
Name="VCResourceCompilerTool"
|
||||
/>
|
||||
<Tool
|
||||
Name="VCPreLinkEventTool"
|
||||
/>
|
||||
<Tool
|
||||
Name="VCLibrarianTool"
|
||||
/>
|
||||
<Tool
|
||||
Name="VCALinkTool"
|
||||
/>
|
||||
<Tool
|
||||
Name="VCXDCMakeTool"
|
||||
/>
|
||||
<Tool
|
||||
Name="VCBscMakeTool"
|
||||
/>
|
||||
<Tool
|
||||
Name="VCFxCopTool"
|
||||
/>
|
||||
<Tool
|
||||
Name="VCPostBuildEventTool"
|
||||
/>
|
||||
</Configuration>
|
||||
<Configuration
|
||||
Name="Release|Win32"
|
||||
OutputDirectory="$(SolutionDir)$(ConfigurationName)\WebServer"
|
||||
IntermediateDirectory="$(ConfigurationName)\webserver"
|
||||
ConfigurationType="4"
|
||||
CharacterSet="1"
|
||||
WholeProgramOptimization="1"
|
||||
>
|
||||
<Tool
|
||||
Name="VCPreBuildEventTool"
|
||||
/>
|
||||
<Tool
|
||||
Name="VCCustomBuildTool"
|
||||
/>
|
||||
<Tool
|
||||
Name="VCXMLDataGeneratorTool"
|
||||
/>
|
||||
<Tool
|
||||
Name="VCWebServiceProxyGeneratorTool"
|
||||
/>
|
||||
<Tool
|
||||
Name="VCMIDLTool"
|
||||
/>
|
||||
<Tool
|
||||
Name="VCCLCompilerTool"
|
||||
Optimization="2"
|
||||
EnableIntrinsicFunctions="true"
|
||||
PreprocessorDefinitions="WIN32;NDEBUG;_LIB"
|
||||
RuntimeLibrary="0"
|
||||
EnableFunctionLevelLinking="true"
|
||||
UsePrecompiledHeader="2"
|
||||
PrecompiledHeaderThrough="Globals.h"
|
||||
WarningLevel="3"
|
||||
DebugInformationFormat="3"
|
||||
/>
|
||||
<Tool
|
||||
Name="VCManagedResourceCompilerTool"
|
||||
/>
|
||||
<Tool
|
||||
Name="VCResourceCompilerTool"
|
||||
/>
|
||||
<Tool
|
||||
Name="VCPreLinkEventTool"
|
||||
/>
|
||||
<Tool
|
||||
Name="VCLibrarianTool"
|
||||
/>
|
||||
<Tool
|
||||
Name="VCALinkTool"
|
||||
/>
|
||||
<Tool
|
||||
Name="VCXDCMakeTool"
|
||||
/>
|
||||
<Tool
|
||||
Name="VCBscMakeTool"
|
||||
/>
|
||||
<Tool
|
||||
Name="VCFxCopTool"
|
||||
/>
|
||||
<Tool
|
||||
Name="VCPostBuildEventTool"
|
||||
/>
|
||||
</Configuration>
|
||||
<Configuration
|
||||
Name="Release profiled|Win32"
|
||||
OutputDirectory="$(SolutionDir)$(ConfigurationName)\webserver"
|
||||
IntermediateDirectory="$(ConfigurationName)\webserver"
|
||||
ConfigurationType="4"
|
||||
CharacterSet="1"
|
||||
WholeProgramOptimization="1"
|
||||
>
|
||||
<Tool
|
||||
Name="VCPreBuildEventTool"
|
||||
/>
|
||||
<Tool
|
||||
Name="VCCustomBuildTool"
|
||||
/>
|
||||
<Tool
|
||||
Name="VCXMLDataGeneratorTool"
|
||||
/>
|
||||
<Tool
|
||||
Name="VCWebServiceProxyGeneratorTool"
|
||||
/>
|
||||
<Tool
|
||||
Name="VCMIDLTool"
|
||||
/>
|
||||
<Tool
|
||||
Name="VCCLCompilerTool"
|
||||
Optimization="2"
|
||||
EnableIntrinsicFunctions="true"
|
||||
PreprocessorDefinitions="WIN32;NDEBUG;_LIB"
|
||||
RuntimeLibrary="0"
|
||||
EnableFunctionLevelLinking="true"
|
||||
UsePrecompiledHeader="2"
|
||||
PrecompiledHeaderThrough="Globals.h"
|
||||
WarningLevel="3"
|
||||
DebugInformationFormat="3"
|
||||
/>
|
||||
<Tool
|
||||
Name="VCManagedResourceCompilerTool"
|
||||
/>
|
||||
<Tool
|
||||
Name="VCResourceCompilerTool"
|
||||
/>
|
||||
<Tool
|
||||
Name="VCPreLinkEventTool"
|
||||
/>
|
||||
<Tool
|
||||
Name="VCLibrarianTool"
|
||||
/>
|
||||
<Tool
|
||||
Name="VCALinkTool"
|
||||
/>
|
||||
<Tool
|
||||
Name="VCXDCMakeTool"
|
||||
/>
|
||||
<Tool
|
||||
Name="VCBscMakeTool"
|
||||
/>
|
||||
<Tool
|
||||
Name="VCFxCopTool"
|
||||
/>
|
||||
<Tool
|
||||
Name="VCPostBuildEventTool"
|
||||
/>
|
||||
</Configuration>
|
||||
<Configuration
|
||||
Name="Debug profiled|Win32"
|
||||
OutputDirectory="$(SolutionDir)$(ConfigurationName)\WebServer"
|
||||
IntermediateDirectory="$(ConfigurationName)\webserver"
|
||||
ConfigurationType="4"
|
||||
CharacterSet="1"
|
||||
>
|
||||
<Tool
|
||||
Name="VCPreBuildEventTool"
|
||||
/>
|
||||
<Tool
|
||||
Name="VCCustomBuildTool"
|
||||
/>
|
||||
<Tool
|
||||
Name="VCXMLDataGeneratorTool"
|
||||
/>
|
||||
<Tool
|
||||
Name="VCWebServiceProxyGeneratorTool"
|
||||
/>
|
||||
<Tool
|
||||
Name="VCMIDLTool"
|
||||
/>
|
||||
<Tool
|
||||
Name="VCCLCompilerTool"
|
||||
Optimization="0"
|
||||
PreprocessorDefinitions="WIN32;_DEBUG;_LIB"
|
||||
MinimalRebuild="true"
|
||||
BasicRuntimeChecks="3"
|
||||
RuntimeLibrary="1"
|
||||
UsePrecompiledHeader="2"
|
||||
PrecompiledHeaderThrough="Globals.h"
|
||||
WarningLevel="3"
|
||||
DebugInformationFormat="4"
|
||||
/>
|
||||
<Tool
|
||||
Name="VCManagedResourceCompilerTool"
|
||||
/>
|
||||
<Tool
|
||||
Name="VCResourceCompilerTool"
|
||||
/>
|
||||
<Tool
|
||||
Name="VCPreLinkEventTool"
|
||||
/>
|
||||
<Tool
|
||||
Name="VCLibrarianTool"
|
||||
/>
|
||||
<Tool
|
||||
Name="VCALinkTool"
|
||||
/>
|
||||
<Tool
|
||||
Name="VCXDCMakeTool"
|
||||
/>
|
||||
<Tool
|
||||
Name="VCBscMakeTool"
|
||||
/>
|
||||
<Tool
|
||||
Name="VCFxCopTool"
|
||||
/>
|
||||
<Tool
|
||||
Name="VCPostBuildEventTool"
|
||||
/>
|
||||
</Configuration>
|
||||
</Configurations>
|
||||
<References>
|
||||
</References>
|
||||
<Files>
|
||||
<Filter
|
||||
Name="Source Files"
|
||||
Filter="cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx"
|
||||
UniqueIdentifier="{4FC737F1-C7A5-4376-A066-2A32D752A2FF}"
|
||||
>
|
||||
<File
|
||||
RelativePath="..\WebServer\base64.cpp"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath="..\WebServer\base64.h"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath="..\WebServer\Events.cpp"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath="..\WebServer\Events.h"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath="..\WebServer\Globals.cpp"
|
||||
>
|
||||
<FileConfiguration
|
||||
Name="Debug|Win32"
|
||||
>
|
||||
<Tool
|
||||
Name="VCCLCompilerTool"
|
||||
UsePrecompiledHeader="1"
|
||||
/>
|
||||
</FileConfiguration>
|
||||
<FileConfiguration
|
||||
Name="Release|Win32"
|
||||
>
|
||||
<Tool
|
||||
Name="VCCLCompilerTool"
|
||||
UsePrecompiledHeader="1"
|
||||
/>
|
||||
</FileConfiguration>
|
||||
<FileConfiguration
|
||||
Name="Release profiled|Win32"
|
||||
>
|
||||
<Tool
|
||||
Name="VCCLCompilerTool"
|
||||
UsePrecompiledHeader="1"
|
||||
/>
|
||||
</FileConfiguration>
|
||||
<FileConfiguration
|
||||
Name="Debug profiled|Win32"
|
||||
>
|
||||
<Tool
|
||||
Name="VCCLCompilerTool"
|
||||
UsePrecompiledHeader="1"
|
||||
/>
|
||||
</FileConfiguration>
|
||||
</File>
|
||||
<File
|
||||
RelativePath="..\WebServer\Globals.h"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath="..\WebServer\Socket.cpp"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath="..\WebServer\Socket.h"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath="..\WebServer\StdHelpers.cpp"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath="..\WebServer\StdHelpers.h"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath="..\WebServer\Tracer.h"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath="..\WebServer\UrlHelper.cpp"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath="..\WebServer\UrlHelper.h"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath="..\WebServer\WebServer.cpp"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath="..\WebServer\WebServer.h"
|
||||
>
|
||||
</File>
|
||||
</Filter>
|
||||
</Files>
|
||||
<Globals>
|
||||
</Globals>
|
||||
</VisualStudioProject>
|
@ -1,125 +0,0 @@
|
||||
|
||||
#include "Globals.h" // NOTE: MSVC stupidness requires this to be the same across all modules
|
||||
|
||||
#include "Events.h"
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
cEvents::cEvents( unsigned int a_NumEvents /* = 1 */ )
|
||||
: m_NumEvents( a_NumEvents )
|
||||
#ifndef _WIN32
|
||||
, m_bNamed( false )
|
||||
#endif
|
||||
{
|
||||
if( m_NumEvents < 1 ) m_NumEvents = 1;
|
||||
|
||||
#ifdef _WIN32
|
||||
m_Handle = new HANDLE[ m_NumEvents ];
|
||||
for( unsigned int i = 0; i < m_NumEvents; i++)
|
||||
{
|
||||
((HANDLE*)m_Handle)[i] = CreateEvent( 0, FALSE, FALSE, 0 );
|
||||
}
|
||||
#else
|
||||
m_Handle = new sem_t*[ m_NumEvents ];
|
||||
for( unsigned int i = 0; i < m_NumEvents; i++)
|
||||
{
|
||||
|
||||
sem_t* & HandlePtr = ((sem_t**)m_Handle)[i];
|
||||
HandlePtr = new sem_t;
|
||||
|
||||
if( sem_init( HandlePtr, 0, 0 ) )
|
||||
{
|
||||
LOG("WARNING cEvents: Could not create unnamed semaphore, fallback to named.");
|
||||
m_bNamed = true;
|
||||
delete HandlePtr; // named semaphores return their own address
|
||||
|
||||
char c_Str[32];
|
||||
sprintf( c_Str, "cEvents%p", &HandlePtr );
|
||||
HandlePtr = sem_open( c_Str, O_CREAT, 777, 0 );
|
||||
if( HandlePtr == SEM_FAILED )
|
||||
LOG("ERROR: Could not create Event. (%i)", errno);
|
||||
else
|
||||
if( sem_unlink( c_Str ) != 0 )
|
||||
LOG("ERROR: Could not unlink cEvents. (%i)", errno);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
cEvents::~cEvents()
|
||||
{
|
||||
#ifdef _WIN32
|
||||
for( unsigned int i = 0; i < m_NumEvents; i++ )
|
||||
{
|
||||
CloseHandle( ((HANDLE*)m_Handle)[i] );
|
||||
}
|
||||
delete [] (HANDLE*)m_Handle;
|
||||
#else
|
||||
for( unsigned int i = 0; i < m_NumEvents; i++ )
|
||||
{
|
||||
if( m_bNamed )
|
||||
{
|
||||
sem_t* & HandlePtr = ((sem_t**)m_Handle)[i];
|
||||
char c_Str[32];
|
||||
sprintf( c_Str, "cEvents%p", &HandlePtr );
|
||||
// LOG("Closing event: %s", c_Str );
|
||||
// LOG("Sem ptr: %p", HandlePtr );
|
||||
if( sem_close( HandlePtr ) != 0 )
|
||||
{
|
||||
LOG("ERROR: Could not close cEvents. (%i)", errno);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
sem_destroy( ((sem_t**)m_Handle)[i] );
|
||||
delete ((sem_t**)m_Handle)[i];
|
||||
}
|
||||
}
|
||||
delete [] (sem_t**)m_Handle; m_Handle = 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
void cEvents::Wait()
|
||||
{
|
||||
#ifdef _WIN32
|
||||
WaitForMultipleObjects( m_NumEvents, (HANDLE*)m_Handle, true, INFINITE );
|
||||
#else
|
||||
for(unsigned int i = 0; i < m_NumEvents; i++)
|
||||
{
|
||||
if( sem_wait( ((sem_t**)m_Handle)[i] ) != 0 )
|
||||
{
|
||||
LOG("ERROR: Could not wait for cEvents. (%i)", errno);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
void cEvents::Set(unsigned int a_EventNum /* = 0 */)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
SetEvent( ((HANDLE*)m_Handle)[a_EventNum] );
|
||||
#else
|
||||
if( sem_post( ((sem_t**)m_Handle)[a_EventNum] ) != 0 )
|
||||
{
|
||||
LOG("ERROR: Could not set cEvents. (%i)", errno);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
@ -1,18 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
class cEvents
|
||||
{
|
||||
public:
|
||||
cEvents( unsigned int a_NumEvents = 1 );
|
||||
~cEvents();
|
||||
|
||||
void Wait();
|
||||
void Set(unsigned int a_EventNum = 0);
|
||||
private:
|
||||
unsigned int m_NumEvents;
|
||||
void* m_Handle; // HANDLE[] pointer
|
||||
|
||||
#ifndef _WIN32
|
||||
bool m_bNamed;
|
||||
#endif
|
||||
};
|
@ -1,10 +0,0 @@
|
||||
|
||||
// Globals.cpp
|
||||
|
||||
// This file is used for precompiled header generation in MSVC environments
|
||||
|
||||
#include "Globals.h"
|
||||
|
||||
|
||||
|
||||
|
@ -1,15 +0,0 @@
|
||||
|
||||
// Globals.h
|
||||
|
||||
// This file gets included from every module in the project, so that global symbols may be introduced easily
|
||||
// Also used for precompiled header generation in MSVC environments
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
#include "../source/Globals.h"
|
||||
|
||||
|
||||
|
||||
|
@ -1,376 +0,0 @@
|
||||
/*
|
||||
Socket.cpp
|
||||
|
||||
Copyright (C) 2002-2004 René Nyffenegger
|
||||
|
||||
This source code is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the author be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this source code must not be misrepresented; you must not
|
||||
claim that you wrote the original source code. If you use this source code
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original source code.
|
||||
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
|
||||
René Nyffenegger rene.nyffenegger@adp-gmbh.ch
|
||||
*/
|
||||
|
||||
/*
|
||||
Note on point 2:
|
||||
THIS IS NOT THE ORIGINAL SOURCE1!!1!!!~!!~`1ONE!!`1
|
||||
*/
|
||||
|
||||
#include "Globals.h" // NOTE: MSVC stupidness requires this to be the same across all modules
|
||||
|
||||
#include "Socket.h"
|
||||
#include <iostream>
|
||||
|
||||
#ifndef _WIN32
|
||||
#include <cstring>
|
||||
#include <sys/time.h>
|
||||
#include <unistd.h>
|
||||
#define SD_SEND 0x01
|
||||
#else
|
||||
#define MSG_NOSIGNAL (0)
|
||||
#endif
|
||||
|
||||
#ifdef __MAC_NA
|
||||
#define MSG_NOSIGNAL (0)
|
||||
#endif
|
||||
|
||||
#ifndef MSG_NOSIGNAL
|
||||
#define MSG_NOSIGNAL 0
|
||||
#endif // MSG_NOSIGNAL
|
||||
|
||||
using namespace std;
|
||||
|
||||
int Socket::nofSockets_= 0;
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
void Socket::Start()
|
||||
{
|
||||
#ifdef _WIN32
|
||||
if (!nofSockets_)
|
||||
{
|
||||
WSADATA info;
|
||||
if (WSAStartup(MAKEWORD(2,0), &info))
|
||||
{
|
||||
throw "Could not start WSA";
|
||||
}
|
||||
}
|
||||
#endif
|
||||
++nofSockets_;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
void Socket::End()
|
||||
{
|
||||
#ifdef _WIN32
|
||||
WSACleanup();
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Socket::Socket() :
|
||||
s_(INVALID_SOCKET)
|
||||
{
|
||||
Start();
|
||||
// UDP: use SOCK_DGRAM instead of SOCK_STREAM
|
||||
s_ = socket(AF_INET,SOCK_STREAM,0);
|
||||
|
||||
if (!IsValid())
|
||||
{
|
||||
#if !defined(ANDROID_NDK)
|
||||
throw "INVALID_SOCKET";
|
||||
#endif
|
||||
}
|
||||
|
||||
refCounter_ = new int(1);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Socket::Socket(SOCKET s) : s_(s)
|
||||
{
|
||||
Start();
|
||||
refCounter_ = new int(1);
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Socket::~Socket()
|
||||
{
|
||||
if (! --(*refCounter_))
|
||||
{
|
||||
Close();
|
||||
delete refCounter_;
|
||||
}
|
||||
|
||||
--nofSockets_;
|
||||
if (!nofSockets_)
|
||||
{
|
||||
End();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Socket::Socket(const Socket& o)
|
||||
{
|
||||
refCounter_=o.refCounter_;
|
||||
(*refCounter_)++;
|
||||
s_ =o.s_;
|
||||
|
||||
nofSockets_++;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Socket& Socket::operator=(Socket& o)
|
||||
{
|
||||
(*o.refCounter_)++;
|
||||
|
||||
refCounter_ = o.refCounter_;
|
||||
s_ = o.s_;
|
||||
|
||||
nofSockets_++;
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
bool Socket::IsValid(void) const
|
||||
{
|
||||
#ifdef _WIN32
|
||||
return (s_ != INVALID_SOCKET);
|
||||
#else
|
||||
return (s_ >= 0);
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
void Socket::Close( bool a_WaitSend /* = false */ )
|
||||
{
|
||||
if (IsValid())
|
||||
{
|
||||
if (a_WaitSend)
|
||||
{
|
||||
assert( shutdown(s_, SD_SEND ) == 0 );
|
||||
char c;
|
||||
while( recv(s_, &c, 1, 0 ) > 0 )
|
||||
{}
|
||||
}
|
||||
|
||||
#ifndef _WIN32
|
||||
// Linux needs to shut the socket down first, otherwise the socket keeps blocking accept() and select() calls!
|
||||
// Ref.: http://forum.mc-server.org/showthread.php?tid=344
|
||||
if (shutdown(s_, SHUT_RDWR) != 0)
|
||||
{
|
||||
LOGWARN("Error on shutting down socket %d", s_);
|
||||
}
|
||||
#endif // _WIN32
|
||||
|
||||
closesocket(s_);
|
||||
|
||||
s_ = INVALID_SOCKET;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
std::string Socket::ReceiveLine()
|
||||
{
|
||||
std::string ret;
|
||||
while (1)
|
||||
{
|
||||
char r;
|
||||
|
||||
if (recv(s_, &r, 1, 0) <= 0 )
|
||||
{
|
||||
return "";
|
||||
}
|
||||
ret += r;
|
||||
if (r == '\n') return ret;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
std::string Socket::ReceiveBytes( unsigned int a_Length )
|
||||
{
|
||||
std::string ret;
|
||||
while( ret.size() < a_Length )
|
||||
{
|
||||
char r;
|
||||
|
||||
if (recv(s_, &r, 1, 0) <= 0 )
|
||||
{
|
||||
return "";
|
||||
}
|
||||
ret += r;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
void Socket::SendLine(std::string s)
|
||||
{
|
||||
s += '\n';
|
||||
if( send(s_,s.c_str(),s.length(),MSG_NOSIGNAL) <= 0 )
|
||||
{
|
||||
//printf("SendLine Socket error!! \n" );
|
||||
Close();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
void Socket::SendBytes(const std::string& s)
|
||||
{
|
||||
if( send(s_,s.c_str(),s.length(), MSG_NOSIGNAL) <= 0 )
|
||||
{
|
||||
//printf("SendBytes Socket error!! \n" );
|
||||
Close();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
SocketServer::SocketServer(int port, int connections, TypeSocket type)
|
||||
{
|
||||
sockaddr_in sa;
|
||||
|
||||
memset(&sa, 0, sizeof(sa));
|
||||
|
||||
sa.sin_family = PF_INET;
|
||||
sa.sin_port = htons(port);
|
||||
s_ = socket(AF_INET, SOCK_STREAM, 0);
|
||||
|
||||
if (!IsValid())
|
||||
{
|
||||
LOG("WebServer: INVALID_SOCKET");
|
||||
}
|
||||
|
||||
if(type==NonBlockingSocket)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
char yes = 1;
|
||||
#else
|
||||
int yes = 1;
|
||||
#endif
|
||||
|
||||
if (setsockopt(s_,SOL_SOCKET,SO_REUSEADDR,&yes,sizeof(int)) == -1)
|
||||
{
|
||||
LOG("WebServer: setsockopt == -1");
|
||||
return;
|
||||
}
|
||||
|
||||
/* bind the socket to the internet address */
|
||||
if (bind(s_, (sockaddr *)&sa, sizeof(sockaddr_in)) == SOCKET_ERROR)
|
||||
{
|
||||
closesocket(s_);
|
||||
LOG("WebServer: INVALID_SOCKET");
|
||||
}
|
||||
|
||||
listen(s_, connections);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Socket* SocketServer::Accept()
|
||||
{
|
||||
Socket * r = new Socket(accept(s_, 0, 0));
|
||||
if (!r->IsValid())
|
||||
{
|
||||
delete r;
|
||||
r = NULL;
|
||||
}
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
SocketSelect::SocketSelect(Socket const * const s1, Socket const * const s2, TypeSocket type)
|
||||
{
|
||||
FD_ZERO(&fds_);
|
||||
SOCKET Highest = s1->s_;
|
||||
FD_SET(const_cast<Socket*>(s1)->s_,&fds_);
|
||||
if(s2)
|
||||
{
|
||||
FD_SET(const_cast<Socket*>(s2)->s_,&fds_);
|
||||
if (s2->s_ > Highest)
|
||||
{
|
||||
Highest = s2->s_;
|
||||
}
|
||||
}
|
||||
if (select(Highest + 1, &fds_, NULL, NULL, NULL) == SOCKET_ERROR)
|
||||
{
|
||||
#if !defined(ANDROID_NDK)
|
||||
throw "Error in select";
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
bool SocketSelect::Readable(Socket const* const s)
|
||||
{
|
||||
return (FD_ISSET(s->s_,&fds_) != 0);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
@ -1,127 +0,0 @@
|
||||
/*
|
||||
Socket.h
|
||||
|
||||
Copyright (C) 2002-2004 René Nyffenegger
|
||||
|
||||
This source code is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the author be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this source code must not be misrepresented; you must not
|
||||
claim that you wrote the original source code. If you use this source code
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original source code.
|
||||
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
|
||||
René Nyffenegger rene.nyffenegger@adp-gmbh.ch
|
||||
*/
|
||||
|
||||
/*
|
||||
Note on point 2:
|
||||
THIS IS NOT THE ORIGINAL SOURCE1!!1!!!~!!~`1ONE!!`1
|
||||
*/
|
||||
|
||||
#ifndef SOCKET_H
|
||||
#define SOCKET_H
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
#ifndef _WIN32
|
||||
typedef int SOCKET;
|
||||
#define SOCKET_ERROR (-1)
|
||||
#define INVALID_SOCKET (-1)
|
||||
#define closesocket close
|
||||
#endif // !_WIN32
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
enum TypeSocket {BlockingSocket, NonBlockingSocket};
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
class Socket
|
||||
{
|
||||
public:
|
||||
|
||||
virtual ~Socket();
|
||||
Socket(const Socket&);
|
||||
Socket& operator=(Socket&);
|
||||
|
||||
std::string ReceiveLine();
|
||||
std::string ReceiveBytes( unsigned int a_Length );
|
||||
|
||||
bool IsValid(void) const;
|
||||
|
||||
void Close( bool a_WaitSend = false );
|
||||
|
||||
// The parameter of SendLine is not a const reference
|
||||
// because SendLine modifes the std::string passed.
|
||||
void SendLine (std::string);
|
||||
|
||||
// The parameter of SendBytes is a const reference
|
||||
// because SendBytes does not modify the std::string passed
|
||||
// (in contrast to SendLine).
|
||||
void SendBytes(const std::string&);
|
||||
|
||||
protected:
|
||||
friend class SocketServer;
|
||||
friend class SocketSelect;
|
||||
|
||||
Socket(SOCKET s);
|
||||
Socket();
|
||||
|
||||
|
||||
SOCKET s_;
|
||||
|
||||
int* refCounter_;
|
||||
|
||||
private:
|
||||
static void Start();
|
||||
static void End();
|
||||
static int nofSockets_;
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
class SocketServer :
|
||||
public Socket
|
||||
{
|
||||
public:
|
||||
SocketServer(int port, int connections, TypeSocket type=BlockingSocket);
|
||||
|
||||
Socket* Accept();
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// http://msdn.microsoft.com/library/default.asp?url=/library/en-us/winsock/wsapiref_2tiq.asp
|
||||
class SocketSelect
|
||||
{
|
||||
public:
|
||||
SocketSelect(Socket const * const s1, Socket const * const s2=NULL, TypeSocket type=BlockingSocket);
|
||||
|
||||
bool Readable(Socket const * const s);
|
||||
|
||||
private:
|
||||
fd_set fds_;
|
||||
};
|
||||
|
||||
#endif
|
@ -1,62 +0,0 @@
|
||||
/*
|
||||
stdHelpers.cpp
|
||||
|
||||
Copyright (C) 2002-2004 René Nyffenegger
|
||||
|
||||
This source code is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the author be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this source code must not be misrepresented; you must not
|
||||
claim that you wrote the original source code. If you use this source code
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original source code.
|
||||
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
|
||||
René Nyffenegger rene.nyffenegger@adp-gmbh.ch
|
||||
*/
|
||||
|
||||
/*
|
||||
Note on point 2:
|
||||
THIS IS NOT THE ORIGINAL SOURCE1!!1!!!~!!~`1ONE!!`1
|
||||
*/
|
||||
|
||||
#include "Globals.h" // NOTE: MSVC stupidness requires this to be the same across all modules
|
||||
|
||||
#include "StdHelpers.h"
|
||||
#include <cctype>
|
||||
|
||||
std::string ReplaceInStr(const std::string& in, const std::string& search_for, const std::string& replace_with) {
|
||||
std::string ret = in;
|
||||
|
||||
std::string::size_type pos = ret.find(search_for);
|
||||
|
||||
while (pos != std::string::npos) {
|
||||
ret = ret.replace(pos, search_for.size(), replace_with);
|
||||
pos = pos - search_for.size() + replace_with.size() + 1;
|
||||
pos = ret.find(search_for, pos);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
// std:toupper and std::tolower are overloaded. Well...
|
||||
// http://gcc.gnu.org/ml/libstdc++/2002-11/msg00180.html
|
||||
char toLower_ (char c) { return std::tolower(c); }
|
||||
char toUpper_ (char c) { return std::toupper(c); }
|
||||
|
||||
void ToUpper(std::string& s) {
|
||||
std::transform(s.begin(), s.end(), s.begin(),toUpper_);
|
||||
}
|
||||
|
||||
void ToLower(std::string& s) {
|
||||
std::transform(s.begin(), s.end(), s.begin(),toLower_);
|
||||
}
|
@ -1,65 +0,0 @@
|
||||
/*
|
||||
stdHelpers.h
|
||||
|
||||
Copyright (C) 2002-2005 René Nyffenegger
|
||||
|
||||
This source code is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the author be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this source code must not be misrepresented; you must not
|
||||
claim that you wrote the original source code. If you use this source code
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original source code.
|
||||
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
|
||||
René Nyffenegger rene.nyffenegger@adp-gmbh.ch
|
||||
*/
|
||||
|
||||
/*
|
||||
Note on point 2:
|
||||
THIS IS NOT THE ORIGINAL SOURCE1!!1!!!~!!~`1ONE!!`1
|
||||
*/
|
||||
|
||||
#ifndef STDHELPERS_H__
|
||||
#define STDHELPERS_H__
|
||||
|
||||
#include <string>
|
||||
#include <sstream>
|
||||
|
||||
std::string ReplaceInStr(const std::string& in, const std::string& search_for, const std::string& replace_with);
|
||||
|
||||
void ToUpper(std::string& s);
|
||||
void ToLower(std::string& s);
|
||||
|
||||
template <class T>
|
||||
T To(std::string const& s) {
|
||||
T ret;
|
||||
|
||||
std::stringstream stream;
|
||||
stream << s;
|
||||
stream >> ret;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
template<class T>
|
||||
std::string StringFrom(T const& t) {
|
||||
std::string ret;
|
||||
|
||||
std::stringstream stream;
|
||||
stream << t;
|
||||
stream >> ret;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
#endif
|
@ -1,113 +0,0 @@
|
||||
/*
|
||||
Tracer.h
|
||||
|
||||
Copyright (C) 2002-2004 René Nyffenegger
|
||||
|
||||
This source code is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the author be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this source code must not be misrepresented; you must not
|
||||
claim that you wrote the original source code. If you use this source code
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original source code.
|
||||
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
|
||||
The most current version of Tracer.h can be found at
|
||||
http://www.adp-gmbh.ch/cpp/common/Tracer.html
|
||||
|
||||
René Nyffenegger rene.nyffenegger@adp-gmbh.ch
|
||||
*/
|
||||
|
||||
/*
|
||||
Note on point 2:
|
||||
THIS IS NOT THE ORIGINAL SOURCE1!!1!!!~!!~`1ONE!!`1
|
||||
*/
|
||||
|
||||
#ifndef TRACER_H__
|
||||
#define TRACER_H__
|
||||
|
||||
#ifdef DO_TRACE
|
||||
|
||||
#include <string>
|
||||
#include <sstream>
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
#define StartTrace(x) TraceFunc_::StartTrace_(x)
|
||||
#define Trace(x) dummy_____for_trace_func______.Trace_(x)
|
||||
#define Trace2(x,y) dummy_____for_trace_func______.Trace_(x,y)
|
||||
#define TraceFunc(x) TraceFunc_ dummy_____for_trace_func______(x)
|
||||
#define TraceFunc2(x,y) TraceFunc_ dummy_____for_trace_func______(x,y)
|
||||
|
||||
class TraceFunc_ {
|
||||
std::string func_name_;
|
||||
public:
|
||||
/*
|
||||
Calling TraceFunc_ indents the output until the enclosing scope ( {...} )
|
||||
is left.
|
||||
*/
|
||||
TraceFunc_(std::string const&);
|
||||
TraceFunc_(std::string const&, std::string const& something);
|
||||
~TraceFunc_();
|
||||
|
||||
/*
|
||||
Creates the trace output file named by filename.
|
||||
Must be called before any other tracing function.
|
||||
Use the StartTrace macro for it.
|
||||
*/
|
||||
static void StartTrace_(std::string const& filename);
|
||||
|
||||
template <class T>
|
||||
void Trace_(T const& t) {
|
||||
DWORD d;
|
||||
std::string indent_s;
|
||||
std::stringstream s;
|
||||
|
||||
s << t;
|
||||
|
||||
for (int i=0; i< indent; i++) indent_s += " ";
|
||||
|
||||
::WriteFile(trace_file_,indent_s.c_str(), indent_s.size(), &d, 0);
|
||||
::WriteFile(trace_file_, s.str().c_str(), s.str().size() ,&d, 0);
|
||||
::WriteFile(trace_file_,"\x0a",1,&d,0);
|
||||
}
|
||||
|
||||
template <class T, class U>
|
||||
void Trace_(T const& t, U const& u) {
|
||||
std::string indent_s;
|
||||
std::stringstream s;
|
||||
|
||||
s << t;
|
||||
s << u;
|
||||
Trace_(s.str());
|
||||
}
|
||||
|
||||
static int indent;
|
||||
/* trace_file_ is a HANDLE for the file in which the traced output goes.
|
||||
The file is opened (that is, created) by calling StartTrace_.
|
||||
Better yet, use the StartTrace macro
|
||||
to create the file.
|
||||
*/
|
||||
static HANDLE trace_file_;
|
||||
};
|
||||
|
||||
#else
|
||||
|
||||
#define StartTrace(x)
|
||||
#define Trace(x)
|
||||
#define Trace2(x,y)
|
||||
#define TraceFunc(x)
|
||||
#define TraceFunc2(x,y)
|
||||
|
||||
#endif
|
||||
|
||||
#endif // TRACER_H__
|
@ -1,169 +0,0 @@
|
||||
/*
|
||||
UrlHelper.cpp
|
||||
|
||||
Copyright (C) 2002-2004 René Nyffenegger
|
||||
|
||||
This source code is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the author be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this source code must not be misrepresented; you must not
|
||||
claim that you wrote the original source code. If you use this source code
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original source code.
|
||||
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
|
||||
René Nyffenegger rene.nyffenegger@adp-gmbh.ch
|
||||
*/
|
||||
|
||||
/*
|
||||
Note on point 2:
|
||||
THIS IS NOT THE ORIGINAL SOURCE1!!1!!!~!!~`1ONE!!`1
|
||||
*/
|
||||
|
||||
#include "Globals.h" // NOTE: MSVC stupidness requires this to be the same across all modules
|
||||
|
||||
#include "UrlHelper.h"
|
||||
#include "Tracer.h"
|
||||
#include "StdHelpers.h"
|
||||
|
||||
#include <sstream>
|
||||
#include <iostream>
|
||||
|
||||
bool RemoveProtocolFromUrl(std::string const& url, std::string& protocol, std::string& rest) {
|
||||
TraceFunc("RemoveProtocolFromUrl");
|
||||
Trace(std::string("url='")+url+"'");
|
||||
std::string::size_type pos_colon = url.find(":");
|
||||
|
||||
if (pos_colon == std::string::npos) {
|
||||
rest = url;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (url.size() < pos_colon + 2) {
|
||||
rest = url;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (url[pos_colon+1] != '/' ||
|
||||
url[pos_colon+2] != '/') {
|
||||
rest = url;
|
||||
return false;
|
||||
}
|
||||
|
||||
protocol = url.substr(0,pos_colon);
|
||||
rest = url.substr(3+pos_colon); // Skipping three characters ( '://' )
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void SplitGetReq(std::string get_req, std::string& path, std::map<std::string, std::string>& params) {
|
||||
TraceFunc("SplitGetReq");
|
||||
|
||||
// Remove trailing newlines
|
||||
if (get_req[get_req.size()-1] == '\x0d' ||
|
||||
get_req[get_req.size()-1] == '\x0a')
|
||||
get_req=get_req.substr(0, get_req.size()-1);
|
||||
|
||||
if (get_req[get_req.size()-1] == '\x0d' ||
|
||||
get_req[get_req.size()-1] == '\x0a')
|
||||
get_req=get_req.substr(0, get_req.size()-1);
|
||||
|
||||
// Remove potential Trailing HTTP/1.x
|
||||
if (get_req.size() > 7) {
|
||||
if (get_req.substr(get_req.size()-8, 7) == "HTTP/1.") {
|
||||
get_req=get_req.substr(0, get_req.size()-9);
|
||||
}
|
||||
}
|
||||
|
||||
std::string::size_type qm = get_req.find("?");
|
||||
if (qm != std::string::npos) {
|
||||
std::string url_params = get_req.substr(qm+1);
|
||||
|
||||
path = get_req.substr(0, qm);
|
||||
|
||||
// Appending a '&' so that there are as many '&' as name-value pairs.
|
||||
// It makes it easier to split the url for name value pairs, he he he
|
||||
url_params += "&";
|
||||
|
||||
std::string::size_type next_amp = url_params.find("&");
|
||||
|
||||
while (next_amp != std::string::npos) {
|
||||
std::string name_value = url_params.substr(0,next_amp);
|
||||
url_params = url_params.substr(next_amp+1);
|
||||
next_amp = url_params.find("&");
|
||||
|
||||
std::string::size_type pos_equal = name_value.find("=");
|
||||
|
||||
std::string nam = name_value.substr(0,pos_equal);
|
||||
std::string val = name_value.substr(pos_equal+1);
|
||||
|
||||
std::string::size_type pos_plus;
|
||||
while ( (pos_plus = val.find("+")) != std::string::npos ) {
|
||||
val.replace(pos_plus, 1, " ");
|
||||
}
|
||||
|
||||
while ( (pos_plus = val.find("%20")) != std::string::npos ) {
|
||||
val.replace(pos_plus, 3, " ");
|
||||
}
|
||||
|
||||
// Replacing %xy notation
|
||||
std::string::size_type pos_hex = 0;
|
||||
while ( (pos_hex = val.find("%", pos_hex)) != std::string::npos ) {
|
||||
std::stringstream h;
|
||||
h << val.substr(pos_hex+1, 2);
|
||||
h << std::hex;
|
||||
|
||||
int i;
|
||||
h>>i;
|
||||
|
||||
std::stringstream f;
|
||||
f << static_cast<char>(i);
|
||||
std::string s;
|
||||
f >> s;
|
||||
|
||||
val.replace(pos_hex, 3, s);
|
||||
pos_hex ++;
|
||||
}
|
||||
|
||||
params.insert(std::map<std::string,std::string>::value_type(nam, val));
|
||||
}
|
||||
}
|
||||
else {
|
||||
path = get_req;
|
||||
}
|
||||
}
|
||||
|
||||
void SplitUrl(std::string const& url, std::string& protocol, std::string& server, std::string& path) {
|
||||
TraceFunc("SplitUrl");
|
||||
RemoveProtocolFromUrl(url, protocol, server);
|
||||
|
||||
if (protocol == "http") {
|
||||
std::string::size_type pos_slash = server.find("/");
|
||||
|
||||
if (pos_slash != std::string::npos) {
|
||||
Trace("slash found");
|
||||
path = server.substr(pos_slash);
|
||||
server = server.substr(0, pos_slash);
|
||||
}
|
||||
else {
|
||||
Trace("slash not found");
|
||||
path = "/";
|
||||
}
|
||||
}
|
||||
else if (protocol == "file") {
|
||||
path = ReplaceInStr(server, "\\", "/");
|
||||
server = "";
|
||||
}
|
||||
else {
|
||||
std::cerr << "unknown protocol in SplitUrl: '" << protocol << "'" << std::endl;
|
||||
}
|
||||
}
|
@ -1,42 +0,0 @@
|
||||
/*
|
||||
UrlHelper.h
|
||||
|
||||
Copyright (C) 2002-2004 René Nyffenegger
|
||||
|
||||
This source code is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the author be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this source code must not be misrepresented; you must not
|
||||
claim that you wrote the original source code. If you use this source code
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original source code.
|
||||
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
|
||||
René Nyffenegger rene.nyffenegger@adp-gmbh.ch
|
||||
*/
|
||||
|
||||
/*
|
||||
Note on point 2:
|
||||
THIS IS NOT THE ORIGINAL SOURCE1!!1!!!~!!~`1ONE!!`1
|
||||
*/
|
||||
#ifndef __URL_HELPER_H__
|
||||
#define __URL_HELPER_H__
|
||||
|
||||
#include <string>
|
||||
#include <map>
|
||||
|
||||
void SplitUrl (std::string const& url, std::string& protocol, std::string& server, std::string& path);
|
||||
bool RemoveProtocolFromUrl(std::string const& url, std::string& protocol, std::string& rest);
|
||||
|
||||
void SplitGetReq (std::string et_req, std::string& path, std::map<std::string, std::string>& params);
|
||||
|
||||
#endif
|
@ -1,498 +0,0 @@
|
||||
/*
|
||||
WebServer.cpp
|
||||
|
||||
Copyright (C) 2003-2007 René Nyffenegger
|
||||
|
||||
This source code is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the author be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this source code must not be misrepresented; you must not
|
||||
claim that you wrote the original source code. If you use this source code
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original source code.
|
||||
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
|
||||
René Nyffenegger rene.nyffenegger@adp-gmbh.ch
|
||||
|
||||
Thanks to Tom Lynn who pointed out an error in this source code.
|
||||
*/
|
||||
|
||||
/*
|
||||
Note on point 2:
|
||||
THIS IS NOT THE ORIGINAL SOURCE1!!1!!!~!!~`1ONE!!`1
|
||||
*/
|
||||
|
||||
#include "Globals.h" // NOTE: MSVC stupidness requires this to be the same across all modules
|
||||
|
||||
#include <ctime>
|
||||
#ifdef _WIN32
|
||||
#include <process.h>
|
||||
#endif
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
|
||||
|
||||
#include "WebServer.h"
|
||||
#include "Events.h"
|
||||
#include "Socket.h"
|
||||
#include "UrlHelper.h"
|
||||
#include "base64.h"
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
webserver::request_func webserver::request_func_ = NULL;
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
static std::string EatLine( std::string& a_String )
|
||||
{
|
||||
std::string RetVal = "";
|
||||
unsigned int StringSize = a_String.size();
|
||||
const char* c = a_String.c_str();
|
||||
|
||||
for( unsigned int i = 0; i < StringSize; ++i, ++c)
|
||||
{
|
||||
if( *c == '\n' )
|
||||
{
|
||||
RetVal += *c;
|
||||
// ++i; ++c;
|
||||
// if( i < StringSize )
|
||||
// {
|
||||
// if( *c == '\r' )
|
||||
// {
|
||||
// RetVal += *c;
|
||||
// }
|
||||
// }
|
||||
break;
|
||||
}
|
||||
RetVal += *c;
|
||||
}
|
||||
a_String = a_String.substr( RetVal.size() );
|
||||
return RetVal;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// Turns
|
||||
// "blabla my string with \"quotes\"!"
|
||||
// into
|
||||
// blabla my string with "quotes"!
|
||||
static std::string GetQuotedString( const std::string& a_String )
|
||||
{
|
||||
std::string RetVal;
|
||||
|
||||
bool bGotFirstQuote = false;
|
||||
bool bIgnoreNext = false;
|
||||
unsigned int StrLen = a_String.size();
|
||||
for( unsigned int i = 0; i < StrLen; ++i )
|
||||
{
|
||||
if( bIgnoreNext == false )
|
||||
{
|
||||
if( a_String[i] == '\"' )
|
||||
{
|
||||
if( bGotFirstQuote == false )
|
||||
{
|
||||
bGotFirstQuote = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
else if( a_String[i] == '\\' ) // Escape character
|
||||
{
|
||||
bIgnoreNext = true;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
RetVal.push_back( a_String[i] );
|
||||
bIgnoreNext = false;
|
||||
}
|
||||
|
||||
return RetVal;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
void ParseMultipartFormData( webserver::http_request& req, Socket* s)
|
||||
{
|
||||
static const std::string multipart_form_data = "multipart/form-data";
|
||||
if(req.content_type_.substr(0, multipart_form_data.size()) == multipart_form_data) // Difficult data... :(
|
||||
{
|
||||
AStringVector ContentTypeData = StringSplit( req.content_type_, "; " );
|
||||
|
||||
std::string boundary;
|
||||
// Find boundary
|
||||
for( unsigned int i = 0; i < ContentTypeData.size(); ++i )
|
||||
{
|
||||
static const std::string boundary_ = "boundary=";
|
||||
if( ContentTypeData[i].substr(0, boundary_.size()) == boundary_ ) // Found boundary
|
||||
{
|
||||
boundary = ContentTypeData[i].substr( boundary_.size() );
|
||||
}
|
||||
}
|
||||
|
||||
//LOGINFO("Boundary: %s", boundary.c_str() );
|
||||
std::string boundary_start = "--" + boundary;
|
||||
std::string boundary_end = boundary_start + "--";
|
||||
|
||||
std::string Content = s->ReceiveBytes( req.content_length_ );
|
||||
|
||||
//printf("Total content: \n%s\n", Content.c_str() );
|
||||
|
||||
// Should start with boundary!
|
||||
std::string line = EatLine( Content );
|
||||
if( line.substr(0, boundary_start.size() ) != boundary_start )
|
||||
{
|
||||
// Something was wrong! :(
|
||||
Content.clear();
|
||||
}
|
||||
|
||||
while( !Content.empty() )
|
||||
{
|
||||
webserver::formdata FormData;
|
||||
|
||||
static const std::string content_disposition = "Content-Disposition: ";
|
||||
static const std::string content_type = "Content-Type: ";
|
||||
|
||||
std::string f_disposition;
|
||||
|
||||
while( 1 )
|
||||
{
|
||||
std::string line = EatLine( Content );
|
||||
if( line.empty() )
|
||||
break;
|
||||
|
||||
unsigned int pos_cr_lf = line.find_first_of("\x0a\x0d");
|
||||
if (pos_cr_lf == 0) break; // Empty line, indicates end of mime thingy
|
||||
|
||||
if( line.substr(0, content_disposition.size() ) == content_disposition )
|
||||
{
|
||||
f_disposition = line.substr(content_disposition.size());
|
||||
LOGINFO("Disposition: %s", f_disposition.c_str() );
|
||||
}
|
||||
else if( line.substr(0, content_type.size() ) == content_type )
|
||||
{
|
||||
FormData.content_type_ = line.substr(content_type.size());
|
||||
}
|
||||
|
||||
//LOGINFO("Got line: '%s'", line.c_str() );
|
||||
}
|
||||
|
||||
// Check if we got the proper headers
|
||||
if( !f_disposition.empty() )
|
||||
{
|
||||
static const std::string disp_name = "name=";
|
||||
static const std::string disp_filename = "filename=";
|
||||
|
||||
// Parse the disposition
|
||||
AStringVector DispositionData = StringSplit( f_disposition, "; " );
|
||||
for( unsigned int i = 0; i < DispositionData.size(); ++i )
|
||||
{
|
||||
if( DispositionData[i].substr(0, disp_name.size()) == disp_name )
|
||||
{
|
||||
FormData.name_ = GetQuotedString( DispositionData[i].substr(disp_name.size()) );
|
||||
}
|
||||
else if( DispositionData[i].substr(0, disp_filename.size()) == disp_filename )
|
||||
{
|
||||
FormData.filename_ = GetQuotedString( DispositionData[i].substr(disp_filename.size()) );
|
||||
}
|
||||
}
|
||||
|
||||
std::string ContentValue;
|
||||
// Parse until boundary_end is found
|
||||
while( 1 )
|
||||
{
|
||||
std::string line = EatLine( Content );
|
||||
if( line.empty() )
|
||||
break;
|
||||
|
||||
if( line.substr(0, boundary_end.size() ) == boundary_end )
|
||||
{
|
||||
break;
|
||||
}
|
||||
else if( line.substr(0, boundary_start.size() ) == boundary_start )
|
||||
{
|
||||
break;
|
||||
}
|
||||
ContentValue.append( line.c_str(), line.size() );
|
||||
}
|
||||
|
||||
|
||||
FormData.value_ = ContentValue;
|
||||
}
|
||||
|
||||
req.multipart_formdata_.push_back( FormData );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
#ifdef _WIN32
|
||||
unsigned webserver::Request(void* ptr_s)
|
||||
#else
|
||||
void* webserver::Request(void* ptr_s)
|
||||
#endif
|
||||
{
|
||||
Socket* s = (reinterpret_cast<Socket*>(ptr_s));
|
||||
|
||||
std::string line = s->ReceiveLine();
|
||||
if (line.empty())
|
||||
{
|
||||
s->Close();
|
||||
delete s;
|
||||
return 0;
|
||||
}
|
||||
|
||||
http_request req;
|
||||
std::string path;
|
||||
std::map<std::string, std::string> params;
|
||||
size_t posStartPath = 0;
|
||||
|
||||
if (line.find("GET") == 0)
|
||||
{
|
||||
req.method_="GET";
|
||||
posStartPath = line.find_first_not_of(" ",3);
|
||||
}
|
||||
else if (line.find("POST") == 0)
|
||||
{
|
||||
req.method_="POST";
|
||||
posStartPath = line.find_first_not_of(" ",4);
|
||||
}
|
||||
|
||||
SplitGetReq(line.substr(posStartPath), path, params);
|
||||
|
||||
req.status_ = "202 OK";
|
||||
req.s_ = s;
|
||||
req.path_ = path;
|
||||
req.params_ = params;
|
||||
|
||||
static const char authorization[] = "Authorization: Basic ";
|
||||
static const char accept[] = "Accept: ";
|
||||
static const char accept_language[] = "Accept-Language: ";
|
||||
static const char accept_encoding[] = "Accept-Encoding: ";
|
||||
static const char user_agent[] = "User-Agent: ";
|
||||
static const char content_length[] = "Content-Length: ";
|
||||
static const char content_type[] = "Content-Type: ";
|
||||
|
||||
while(1)
|
||||
{
|
||||
line=s->ReceiveLine();
|
||||
if (line.empty())
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
unsigned int pos_cr_lf = line.find_first_of("\x0a\x0d");
|
||||
if (pos_cr_lf == 0) break;
|
||||
|
||||
line = line.substr(0,pos_cr_lf);
|
||||
|
||||
if (line.compare(0, sizeof(authorization) - 1, authorization) == 0)
|
||||
{
|
||||
req.authentication_given_ = true;
|
||||
std::string encoded = line.substr(sizeof(authorization) - 1);
|
||||
std::string decoded = base64_decode(encoded);
|
||||
|
||||
unsigned int pos_colon = decoded.find(":");
|
||||
|
||||
req.username_ = decoded.substr(0, pos_colon);
|
||||
req.password_ = decoded.substr(pos_colon + 1);
|
||||
}
|
||||
else if (line.compare(0, sizeof(accept) - 1, accept) == 0)
|
||||
{
|
||||
req.accept_ = line.substr(sizeof(accept) - 1);
|
||||
}
|
||||
else if (line.compare(0, sizeof(accept_language) - 1, accept_language) == 0)
|
||||
{
|
||||
req.accept_language_ = line.substr(sizeof(accept_language) - 1);
|
||||
}
|
||||
else if (line.compare(0, sizeof(accept_encoding) - 1, accept_encoding) == 0)
|
||||
{
|
||||
req.accept_encoding_ = line.substr(sizeof(accept_encoding) - 1);
|
||||
}
|
||||
else if (line.compare(0, sizeof(user_agent) - 1, user_agent) == 0)
|
||||
{
|
||||
req.user_agent_ = line.substr(sizeof(user_agent) - 1);
|
||||
}
|
||||
else if (line.compare(0, sizeof(content_length) - 1, content_length) == 0)
|
||||
{
|
||||
req.content_length_ = atoi(line.substr(sizeof(content_length) - 1).c_str() );
|
||||
}
|
||||
else if (line.compare(0, sizeof(content_type) - 1, content_type) == 0)
|
||||
{
|
||||
req.content_type_ = line.substr(sizeof(content_type) - 1);
|
||||
}
|
||||
}
|
||||
|
||||
if( (req.method_.compare("POST") == 0) && (req.content_length_ > 0) )
|
||||
{
|
||||
const char FormUrlEncoded[] = "application/x-www-form-urlencoded";
|
||||
// The only content type we can parse at the moment, the default HTML post data
|
||||
if( req.content_type_.substr(0, strlen(FormUrlEncoded)).compare( FormUrlEncoded ) == 0 )
|
||||
{
|
||||
std::string Content = s->ReceiveBytes( req.content_length_ );
|
||||
Content.insert( 0, "/ ?" ); // Little hack, inserts dummy URL so that SplitGetReq() can work with this content
|
||||
|
||||
std::string dummy;
|
||||
std::map<std::string, std::string> post_params;
|
||||
SplitGetReq(Content, dummy, post_params);
|
||||
|
||||
req.params_post_ = post_params;
|
||||
}
|
||||
else
|
||||
{
|
||||
ParseMultipartFormData( req, s );
|
||||
}
|
||||
}
|
||||
|
||||
request_func_(&req);
|
||||
|
||||
std::stringstream str_str;
|
||||
str_str << req.answer_.size();
|
||||
|
||||
time_t ltime;
|
||||
time(<ime);
|
||||
tm* gmt= gmtime(<ime);
|
||||
|
||||
#ifdef _WIN32
|
||||
static const char serverName[] = "MCServerWebAdmin (Windows)";
|
||||
#elif __APPLE__
|
||||
static const char serverName[] = "MCServerWebAdmin (MacOSX)";
|
||||
#else
|
||||
static const char serverName[] = "MCServerWebAdmin (Linux)";
|
||||
#endif
|
||||
|
||||
|
||||
char* asctime_remove_nl = std::asctime(gmt);
|
||||
asctime_remove_nl[24] = 0;
|
||||
|
||||
s->SendBytes("HTTP/1.1 ");
|
||||
|
||||
if (! req.auth_realm_.empty() )
|
||||
{
|
||||
s->SendLine("401 Unauthorized");
|
||||
s->SendBytes("WWW-Authenticate: Basic Realm=\"");
|
||||
s->SendBytes(req.auth_realm_);
|
||||
s->SendLine("\"");
|
||||
}
|
||||
else
|
||||
{
|
||||
s->SendLine(req.status_);
|
||||
}
|
||||
s->SendLine(std::string("Date: ") + asctime_remove_nl + " GMT");
|
||||
s->SendLine(std::string("Server: ") + serverName);
|
||||
s->SendLine("Connection: close");
|
||||
s->SendLine("Content-Type: text/html; charset=ISO-8859-1");
|
||||
s->SendLine("Content-Length: " + str_str.str());
|
||||
s->SendLine("");
|
||||
s->SendLine(req.answer_);
|
||||
|
||||
s->Close( true ); // true = wait for all data to be sent before closing
|
||||
delete s;
|
||||
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
void webserver::Stop()
|
||||
{
|
||||
m_bStop = true;
|
||||
m_Socket->Close();
|
||||
m_Events->Wait();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
bool webserver::Begin()
|
||||
{
|
||||
if (!m_Socket->IsValid())
|
||||
{
|
||||
LOGINFO("WebAdmin: The server socket is invalid. Terminating WebAdmin.");
|
||||
return false;
|
||||
}
|
||||
m_bStop = false;
|
||||
while ( !m_bStop )
|
||||
{
|
||||
Socket * ptr_s = m_Socket->Accept();
|
||||
if (m_bStop)
|
||||
{
|
||||
if (ptr_s != 0)
|
||||
{
|
||||
ptr_s->Close();
|
||||
delete ptr_s;
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (ptr_s == NULL)
|
||||
{
|
||||
LOGINFO("WebAdmin: Accepted socket is NULL. Terminating WebAdmin to avoid busywait.");
|
||||
return false;
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
unsigned ret;
|
||||
HANDLE hHandle = reinterpret_cast<HANDLE>(_beginthreadex(NULL, 0, Request, (void *)ptr_s, 0, &ret));
|
||||
CloseHandle(hHandle);
|
||||
#else
|
||||
// Mattes: TODO: this handle probably leaks!
|
||||
pthread_t * hHandle = new pthread_t;
|
||||
pthread_create( hHandle, NULL, Request, ptr_s);
|
||||
#endif
|
||||
}
|
||||
m_Events->Set();
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
webserver::webserver(unsigned int port_to_listen, request_func r)
|
||||
{
|
||||
m_Socket = new SocketServer(port_to_listen, 1);
|
||||
|
||||
request_func_ = r;
|
||||
m_Events = new cEvents();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
webserver::~webserver()
|
||||
{
|
||||
delete m_Socket;
|
||||
delete m_Events;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
@ -1,108 +0,0 @@
|
||||
/*
|
||||
WebServer.h
|
||||
|
||||
Copyright (C) 2003-2004 René Nyffenegger
|
||||
|
||||
This source code is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the author be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this source code must not be misrepresented; you must not
|
||||
claim that you wrote the original source code. If you use this source code
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original source code.
|
||||
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
|
||||
René Nyffenegger rene.nyffenegger@adp-gmbh.ch
|
||||
|
||||
*/
|
||||
|
||||
/*
|
||||
Note on point 2:
|
||||
THIS IS NOT THE ORIGINAL SOURCE1!!1!!!~!!~`1ONE!!`1
|
||||
*/
|
||||
|
||||
|
||||
class cEvents;
|
||||
class Socket;
|
||||
class SocketServer;
|
||||
class webserver {
|
||||
public:
|
||||
struct formdata
|
||||
{
|
||||
std::string name_;
|
||||
std::string filename_;
|
||||
std::string content_type_;
|
||||
std::string value_;
|
||||
};
|
||||
|
||||
struct http_request {
|
||||
|
||||
http_request()
|
||||
: s_( 0 )
|
||||
, content_length_( 0 )
|
||||
, authentication_given_(false)
|
||||
{}
|
||||
|
||||
Socket* s_;
|
||||
std::string method_;
|
||||
std::string path_;
|
||||
std::map<std::string, std::string> params_;
|
||||
std::map<std::string, std::string> params_post_;
|
||||
|
||||
std::string accept_;
|
||||
std::string accept_language_;
|
||||
std::string accept_encoding_;
|
||||
std::string user_agent_;
|
||||
int content_length_;
|
||||
std::string content_type_;
|
||||
std::vector< formdata > multipart_formdata_;
|
||||
|
||||
/* status_: used to transmit server's error status, such as
|
||||
o 202 OK
|
||||
o 404 Not Found
|
||||
and so on */
|
||||
std::string status_;
|
||||
|
||||
/* auth_realm_: allows to set the basic realm for an authentication,
|
||||
no need to additionally set status_ if set */
|
||||
std::string auth_realm_;
|
||||
|
||||
std::string answer_;
|
||||
|
||||
/* authentication_given_ is true when the user has entered a username and password.
|
||||
These can then be read from username_ and password_ */
|
||||
bool authentication_given_;
|
||||
std::string username_;
|
||||
std::string password_;
|
||||
};
|
||||
|
||||
typedef void (*request_func) (http_request*);
|
||||
webserver(unsigned int port_to_listen, request_func);
|
||||
~webserver();
|
||||
|
||||
bool Begin();
|
||||
void Stop();
|
||||
|
||||
private:
|
||||
bool m_bStop;
|
||||
|
||||
#ifdef _WIN32
|
||||
static unsigned __stdcall Request(void*);
|
||||
#else
|
||||
static void* Request(void*);
|
||||
#endif
|
||||
|
||||
static request_func request_func_;
|
||||
|
||||
cEvents * m_Events;
|
||||
SocketServer* m_Socket;
|
||||
};
|
@ -1,102 +0,0 @@
|
||||
|
||||
#include "Globals.h" // NOTE: MSVC stupidness requires this to be the same across all modules
|
||||
|
||||
#include "base64.h"
|
||||
#include <iostream>
|
||||
#if defined(ANDROID_NDK)
|
||||
#include <ctype.h>
|
||||
#endif
|
||||
|
||||
static const std::string base64_chars =
|
||||
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||||
"abcdefghijklmnopqrstuvwxyz"
|
||||
"0123456789+/";
|
||||
|
||||
|
||||
static inline bool is_base64(unsigned char c) {
|
||||
return (isalnum(c) || (c == '+') || (c == '/'));
|
||||
}
|
||||
|
||||
std::string base64_encode(unsigned char const* bytes_to_encode, unsigned int in_len) {
|
||||
std::string ret;
|
||||
int i = 0;
|
||||
int j = 0;
|
||||
unsigned char char_array_3[3];
|
||||
unsigned char char_array_4[4];
|
||||
|
||||
while (in_len--) {
|
||||
char_array_3[i++] = *(bytes_to_encode++);
|
||||
if (i == 3) {
|
||||
char_array_4[0] = (char_array_3[0] & 0xfc) >> 2;
|
||||
char_array_4[1] = ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4);
|
||||
char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6);
|
||||
char_array_4[3] = char_array_3[2] & 0x3f;
|
||||
|
||||
for(i = 0; (i <4) ; i++)
|
||||
ret += base64_chars[char_array_4[i]];
|
||||
i = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (i)
|
||||
{
|
||||
for(j = i; j < 3; j++)
|
||||
char_array_3[j] = '\0';
|
||||
|
||||
char_array_4[0] = (char_array_3[0] & 0xfc) >> 2;
|
||||
char_array_4[1] = ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4);
|
||||
char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6);
|
||||
char_array_4[3] = char_array_3[2] & 0x3f;
|
||||
|
||||
for (j = 0; (j < i + 1); j++)
|
||||
ret += base64_chars[char_array_4[j]];
|
||||
|
||||
while((i++ < 3))
|
||||
ret += '=';
|
||||
|
||||
}
|
||||
|
||||
return ret;
|
||||
|
||||
}
|
||||
|
||||
std::string base64_decode(std::string const& encoded_string) {
|
||||
int in_len = encoded_string.size();
|
||||
int i = 0;
|
||||
int j = 0;
|
||||
int in_ = 0;
|
||||
unsigned char char_array_4[4], char_array_3[3];
|
||||
std::string ret;
|
||||
|
||||
while (in_len-- && ( encoded_string[in_] != '=') && is_base64(encoded_string[in_])) {
|
||||
char_array_4[i++] = encoded_string[in_]; in_++;
|
||||
if (i ==4) {
|
||||
for (i = 0; i <4; i++)
|
||||
char_array_4[i] = base64_chars.find(char_array_4[i]);
|
||||
|
||||
char_array_3[0] = (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4);
|
||||
char_array_3[1] = ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2);
|
||||
char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3];
|
||||
|
||||
for (i = 0; (i < 3); i++)
|
||||
ret += char_array_3[i];
|
||||
i = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (i) {
|
||||
for (j = i; j <4; j++)
|
||||
char_array_4[j] = 0;
|
||||
|
||||
for (j = 0; j <4; j++)
|
||||
char_array_4[j] = base64_chars.find(char_array_4[j]);
|
||||
|
||||
char_array_3[0] = (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4);
|
||||
char_array_3[1] = ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2);
|
||||
char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3];
|
||||
|
||||
for (j = 0; (j < i - 1); j++) ret += char_array_3[j];
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
@ -1,4 +0,0 @@
|
||||
#include <string>
|
||||
|
||||
std::string base64_encode(unsigned char const* , unsigned int len);
|
||||
std::string base64_decode(std::string const& s);
|
@ -1,6 +1,6 @@
|
||||
/*
|
||||
** Lua binding: AllToLua
|
||||
** Generated automatically by tolua++-1.0.92 on 10/04/13 08:36:37.
|
||||
** Generated automatically by tolua++-1.0.92 on 10/06/13 15:12:14.
|
||||
*/
|
||||
|
||||
#ifndef __cplusplus
|
||||
@ -240,18 +240,19 @@ static void tolua_reg_types (lua_State* tolua_S)
|
||||
tolua_usertype(tolua_S,"cCraftingRecipe");
|
||||
tolua_usertype(tolua_S,"cPlugin");
|
||||
tolua_usertype(tolua_S,"cItemGrid");
|
||||
tolua_usertype(tolua_S,"cBlockArea");
|
||||
tolua_usertype(tolua_S,"cHTTPServer::cCallbacks");
|
||||
tolua_usertype(tolua_S,"cLuaWindow");
|
||||
tolua_usertype(tolua_S,"cInventory");
|
||||
tolua_usertype(tolua_S,"cBoundingBox");
|
||||
tolua_usertype(tolua_S,"cBlockEntityWithItems");
|
||||
tolua_usertype(tolua_S,"HTTPFormData");
|
||||
tolua_usertype(tolua_S,"cTracer");
|
||||
tolua_usertype(tolua_S,"HTTPFormData");
|
||||
tolua_usertype(tolua_S,"cWindow");
|
||||
tolua_usertype(tolua_S,"cArrowEntity");
|
||||
tolua_usertype(tolua_S,"cDropSpenserEntity");
|
||||
tolua_usertype(tolua_S,"cWindow");
|
||||
tolua_usertype(tolua_S,"Vector3i");
|
||||
tolua_usertype(tolua_S,"cBlockArea");
|
||||
tolua_usertype(tolua_S,"cCraftingGrid");
|
||||
tolua_usertype(tolua_S,"Vector3i");
|
||||
tolua_usertype(tolua_S,"cGroup");
|
||||
tolua_usertype(tolua_S,"cStringMap");
|
||||
tolua_usertype(tolua_S,"cBlockEntity");
|
||||
@ -18794,38 +18795,6 @@ static int tolua_AllToLua_cWebAdmin_GetMemoryUsage00(lua_State* tolua_S)
|
||||
}
|
||||
#endif //#ifndef TOLUA_DISABLE
|
||||
|
||||
/* method: GetPort of class cWebAdmin */
|
||||
#ifndef TOLUA_DISABLE_tolua_AllToLua_cWebAdmin_GetPort00
|
||||
static int tolua_AllToLua_cWebAdmin_GetPort00(lua_State* tolua_S)
|
||||
{
|
||||
#ifndef TOLUA_RELEASE
|
||||
tolua_Error tolua_err;
|
||||
if (
|
||||
!tolua_isusertype(tolua_S,1,"cWebAdmin",0,&tolua_err) ||
|
||||
!tolua_isnoobj(tolua_S,2,&tolua_err)
|
||||
)
|
||||
goto tolua_lerror;
|
||||
else
|
||||
#endif
|
||||
{
|
||||
cWebAdmin* self = (cWebAdmin*) tolua_tousertype(tolua_S,1,0);
|
||||
#ifndef TOLUA_RELEASE
|
||||
if (!self) tolua_error(tolua_S,"invalid 'self' in function 'GetPort'", NULL);
|
||||
#endif
|
||||
{
|
||||
int tolua_ret = (int) self->GetPort();
|
||||
tolua_pushnumber(tolua_S,(lua_Number)tolua_ret);
|
||||
}
|
||||
}
|
||||
return 1;
|
||||
#ifndef TOLUA_RELEASE
|
||||
tolua_lerror:
|
||||
tolua_error(tolua_S,"#ferror in function 'GetPort'.",&tolua_err);
|
||||
return 0;
|
||||
#endif
|
||||
}
|
||||
#endif //#ifndef TOLUA_DISABLE
|
||||
|
||||
/* method: GetPage of class cWebAdmin */
|
||||
#ifndef TOLUA_DISABLE_tolua_AllToLua_cWebAdmin_GetPage00
|
||||
static int tolua_AllToLua_cWebAdmin_GetPage00(lua_State* tolua_S)
|
||||
@ -18870,6 +18839,38 @@ static int tolua_AllToLua_cWebAdmin_GetPage00(lua_State* tolua_S)
|
||||
}
|
||||
#endif //#ifndef TOLUA_DISABLE
|
||||
|
||||
/* method: GetDefaultPage of class cWebAdmin */
|
||||
#ifndef TOLUA_DISABLE_tolua_AllToLua_cWebAdmin_GetDefaultPage00
|
||||
static int tolua_AllToLua_cWebAdmin_GetDefaultPage00(lua_State* tolua_S)
|
||||
{
|
||||
#ifndef TOLUA_RELEASE
|
||||
tolua_Error tolua_err;
|
||||
if (
|
||||
!tolua_isusertype(tolua_S,1,"cWebAdmin",0,&tolua_err) ||
|
||||
!tolua_isnoobj(tolua_S,2,&tolua_err)
|
||||
)
|
||||
goto tolua_lerror;
|
||||
else
|
||||
#endif
|
||||
{
|
||||
cWebAdmin* self = (cWebAdmin*) tolua_tousertype(tolua_S,1,0);
|
||||
#ifndef TOLUA_RELEASE
|
||||
if (!self) tolua_error(tolua_S,"invalid 'self' in function 'GetDefaultPage'", NULL);
|
||||
#endif
|
||||
{
|
||||
AString tolua_ret = (AString) self->GetDefaultPage();
|
||||
tolua_pushcppstring(tolua_S,(const char*)tolua_ret);
|
||||
}
|
||||
}
|
||||
return 1;
|
||||
#ifndef TOLUA_RELEASE
|
||||
tolua_lerror:
|
||||
tolua_error(tolua_S,"#ferror in function 'GetDefaultPage'.",&tolua_err);
|
||||
return 0;
|
||||
#endif
|
||||
}
|
||||
#endif //#ifndef TOLUA_DISABLE
|
||||
|
||||
/* method: GetBaseURL of class cWebAdmin */
|
||||
#ifndef TOLUA_DISABLE_tolua_AllToLua_cWebAdmin_GetBaseURL00
|
||||
static int tolua_AllToLua_cWebAdmin_GetBaseURL00(lua_State* tolua_S)
|
||||
@ -30330,11 +30331,11 @@ TOLUA_API int tolua_AllToLua_open (lua_State* tolua_S)
|
||||
tolua_variable(tolua_S,"PluginName",tolua_get_sWebAdminPage_PluginName,tolua_set_sWebAdminPage_PluginName);
|
||||
tolua_variable(tolua_S,"TabName",tolua_get_sWebAdminPage_TabName,tolua_set_sWebAdminPage_TabName);
|
||||
tolua_endmodule(tolua_S);
|
||||
tolua_cclass(tolua_S,"cWebAdmin","cWebAdmin","",NULL);
|
||||
tolua_cclass(tolua_S,"cWebAdmin","cWebAdmin","cHTTPServer::cCallbacks",NULL);
|
||||
tolua_beginmodule(tolua_S,"cWebAdmin");
|
||||
tolua_function(tolua_S,"GetMemoryUsage",tolua_AllToLua_cWebAdmin_GetMemoryUsage00);
|
||||
tolua_function(tolua_S,"GetPort",tolua_AllToLua_cWebAdmin_GetPort00);
|
||||
tolua_function(tolua_S,"GetPage",tolua_AllToLua_cWebAdmin_GetPage00);
|
||||
tolua_function(tolua_S,"GetDefaultPage",tolua_AllToLua_cWebAdmin_GetDefaultPage00);
|
||||
tolua_function(tolua_S,"GetBaseURL",tolua_AllToLua_cWebAdmin_GetBaseURL00);
|
||||
tolua_endmodule(tolua_S);
|
||||
tolua_cclass(tolua_S,"cWebPlugin","cWebPlugin","",NULL);
|
||||
|
@ -1,6 +1,6 @@
|
||||
/*
|
||||
** Lua binding: AllToLua
|
||||
** Generated automatically by tolua++-1.0.92 on 10/04/13 08:36:38.
|
||||
** Generated automatically by tolua++-1.0.92 on 10/06/13 15:12:15.
|
||||
*/
|
||||
|
||||
/* Exported function */
|
||||
|
132
source/HTTPServer/EnvelopeParser.cpp
Normal file
132
source/HTTPServer/EnvelopeParser.cpp
Normal file
@ -0,0 +1,132 @@
|
||||
|
||||
// EnvelopeParser.cpp
|
||||
|
||||
// Implements the cEnvelopeParser class representing a parser for RFC-822 envelope headers, used both in HTTP and in MIME
|
||||
|
||||
#include "Globals.h"
|
||||
#include "EnvelopeParser.h"
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
cEnvelopeParser::cEnvelopeParser(cCallbacks & a_Callbacks) :
|
||||
m_Callbacks(a_Callbacks),
|
||||
m_IsInHeaders(true)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
int cEnvelopeParser::Parse(const char * a_Data, int a_Size)
|
||||
{
|
||||
if (!m_IsInHeaders)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Start searching 1 char from the end of the already received data, if available:
|
||||
size_t SearchStart = m_IncomingData.size();
|
||||
SearchStart = (SearchStart > 1) ? SearchStart - 1 : 0;
|
||||
|
||||
m_IncomingData.append(a_Data, a_Size);
|
||||
|
||||
size_t idxCRLF = m_IncomingData.find("\r\n", SearchStart);
|
||||
if (idxCRLF == AString::npos)
|
||||
{
|
||||
// Not a complete line yet, all input consumed:
|
||||
return a_Size;
|
||||
}
|
||||
|
||||
// Parse as many lines as found:
|
||||
size_t Last = 0;
|
||||
do
|
||||
{
|
||||
if (idxCRLF == Last)
|
||||
{
|
||||
// This was the last line of the data. Finish whatever value has been cached and return:
|
||||
NotifyLast();
|
||||
m_IsInHeaders = false;
|
||||
return a_Size - (m_IncomingData.size() - idxCRLF) + 2;
|
||||
}
|
||||
if (!ParseLine(m_IncomingData.c_str() + Last, idxCRLF - Last))
|
||||
{
|
||||
// An error has occurred
|
||||
m_IsInHeaders = false;
|
||||
return -1;
|
||||
}
|
||||
Last = idxCRLF + 2;
|
||||
idxCRLF = m_IncomingData.find("\r\n", idxCRLF + 2);
|
||||
} while (idxCRLF != AString::npos);
|
||||
m_IncomingData.erase(0, Last);
|
||||
|
||||
// Parsed all lines and still expecting more
|
||||
return a_Size;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
void cEnvelopeParser::Reset(void)
|
||||
{
|
||||
m_IsInHeaders = true;
|
||||
m_IncomingData.clear();
|
||||
m_LastKey.clear();
|
||||
m_LastValue.clear();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
void cEnvelopeParser::NotifyLast(void)
|
||||
{
|
||||
if (!m_LastKey.empty())
|
||||
{
|
||||
m_Callbacks.OnHeaderLine(m_LastKey, m_LastValue);
|
||||
m_LastKey.clear();
|
||||
}
|
||||
m_LastValue.clear();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
bool cEnvelopeParser::ParseLine(const char * a_Data, size_t a_Size)
|
||||
{
|
||||
ASSERT(a_Size > 0);
|
||||
if (a_Data[0] <= ' ')
|
||||
{
|
||||
// This line is a continuation for the previous line
|
||||
if (m_LastKey.empty())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
// Append, including the whitespace in a_Data[0]
|
||||
m_LastValue.append(a_Data, a_Size);
|
||||
return true;
|
||||
}
|
||||
|
||||
// This is a line with a new key:
|
||||
NotifyLast();
|
||||
for (size_t i = 0; i < a_Size; i++)
|
||||
{
|
||||
if (a_Data[i] == ':')
|
||||
{
|
||||
m_LastKey.assign(a_Data, i);
|
||||
m_LastValue.assign(a_Data + i + 2, a_Size - i - 2);
|
||||
return true;
|
||||
}
|
||||
} // for i - a_Data[]
|
||||
|
||||
// No colon was found, key-less header??
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
69
source/HTTPServer/EnvelopeParser.h
Normal file
69
source/HTTPServer/EnvelopeParser.h
Normal file
@ -0,0 +1,69 @@
|
||||
|
||||
// EnvelopeParser.h
|
||||
|
||||
// Declares the cEnvelopeParser class representing a parser for RFC-822 envelope headers, used both in HTTP and in MIME
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
#pragma once
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
class cEnvelopeParser
|
||||
{
|
||||
public:
|
||||
class cCallbacks
|
||||
{
|
||||
public:
|
||||
/// Called when a full header line is parsed
|
||||
virtual void OnHeaderLine(const AString & a_Key, const AString & a_Value) = 0;
|
||||
} ;
|
||||
|
||||
|
||||
cEnvelopeParser(cCallbacks & a_Callbacks);
|
||||
|
||||
/** Parses the incoming data.
|
||||
Returns the number of bytes consumed from the input. The bytes not consumed are not part of the envelope header
|
||||
*/
|
||||
int Parse(const char * a_Data, int a_Size);
|
||||
|
||||
/// Makes the parser forget everything parsed so far, so that it can be reused for parsing another datastream
|
||||
void Reset(void);
|
||||
|
||||
/// Returns true if more input is expected for the envelope header
|
||||
bool IsInHeaders(void) const { return m_IsInHeaders; }
|
||||
|
||||
/// Sets the IsInHeaders flag; used by cMultipartParser to simplify the parser initial conditions
|
||||
void SetIsInHeaders(bool a_IsInHeaders) { m_IsInHeaders = a_IsInHeaders; }
|
||||
|
||||
public:
|
||||
/// Callbacks to call for the various events
|
||||
cCallbacks & m_Callbacks;
|
||||
|
||||
/// Set to true while the parser is still parsing the envelope headers. Once set to true, the parser will not consume any more data.
|
||||
bool m_IsInHeaders;
|
||||
|
||||
/// Buffer for the incoming data until it is parsed
|
||||
AString m_IncomingData;
|
||||
|
||||
/// Holds the last parsed key; used for line-wrapped values
|
||||
AString m_LastKey;
|
||||
|
||||
/// Holds the last parsed value; used for line-wrapped values
|
||||
AString m_LastValue;
|
||||
|
||||
|
||||
/// Notifies the callback of the key/value stored in m_LastKey/m_LastValue, then erases them
|
||||
void NotifyLast(void);
|
||||
|
||||
/// Parses one line of header data. Returns true if successful
|
||||
bool ParseLine(const char * a_Data, size_t a_Size);
|
||||
} ;
|
||||
|
||||
|
||||
|
||||
|
247
source/HTTPServer/HTTPConnection.cpp
Normal file
247
source/HTTPServer/HTTPConnection.cpp
Normal file
@ -0,0 +1,247 @@
|
||||
|
||||
// HTTPConnection.cpp
|
||||
|
||||
// Implements the cHTTPConnection class representing a single persistent connection in the HTTP server.
|
||||
|
||||
#include "Globals.h"
|
||||
#include "HTTPConnection.h"
|
||||
#include "HTTPMessage.h"
|
||||
#include "HTTPServer.h"
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
cHTTPConnection::cHTTPConnection(cHTTPServer & a_HTTPServer) :
|
||||
m_HTTPServer(a_HTTPServer),
|
||||
m_State(wcsRecvHeaders),
|
||||
m_CurrentRequest(NULL)
|
||||
{
|
||||
// LOGD("HTTP: New connection at %p", this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
cHTTPConnection::~cHTTPConnection()
|
||||
{
|
||||
// LOGD("HTTP: Del connection at %p", this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
void cHTTPConnection::SendStatusAndReason(int a_StatusCode, const AString & a_Response)
|
||||
{
|
||||
AppendPrintf(m_OutgoingData, "%d %s\r\nContent-Length: 0\r\n\r\n", a_StatusCode, a_Response.c_str());
|
||||
m_HTTPServer.NotifyConnectionWrite(*this);
|
||||
m_State = wcsRecvHeaders;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
void cHTTPConnection::SendNeedAuth(const AString & a_Realm)
|
||||
{
|
||||
AppendPrintf(m_OutgoingData, "HTTP/1.1 401 Unauthorized\r\nWWW-Authenticate: Basic realm=\"%s\"\r\nContent-Length: 0\r\n\r\n", a_Realm.c_str());
|
||||
m_HTTPServer.NotifyConnectionWrite(*this);
|
||||
m_State = wcsRecvHeaders;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
void cHTTPConnection::Send(const cHTTPResponse & a_Response)
|
||||
{
|
||||
ASSERT(m_State = wcsRecvIdle);
|
||||
a_Response.AppendToData(m_OutgoingData);
|
||||
m_State = wcsSendingResp;
|
||||
m_HTTPServer.NotifyConnectionWrite(*this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
void cHTTPConnection::Send(const void * a_Data, int a_Size)
|
||||
{
|
||||
ASSERT(m_State == wcsSendingResp);
|
||||
AppendPrintf(m_OutgoingData, "%x\r\n", a_Size);
|
||||
m_OutgoingData.append((const char *)a_Data, a_Size);
|
||||
m_OutgoingData.append("\r\n");
|
||||
m_HTTPServer.NotifyConnectionWrite(*this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
void cHTTPConnection::FinishResponse(void)
|
||||
{
|
||||
ASSERT(m_State == wcsSendingResp);
|
||||
m_OutgoingData.append("0\r\n\r\n");
|
||||
m_State = wcsRecvHeaders;
|
||||
m_HTTPServer.NotifyConnectionWrite(*this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
void cHTTPConnection::AwaitNextRequest(void)
|
||||
{
|
||||
switch (m_State)
|
||||
{
|
||||
case wcsRecvHeaders:
|
||||
{
|
||||
// Nothing has been received yet, or a special response was given (SendStatusAndReason() or SendNeedAuth() )
|
||||
break;
|
||||
}
|
||||
|
||||
case wcsRecvIdle:
|
||||
{
|
||||
// The client is waiting for a response, send an "Internal server error":
|
||||
m_OutgoingData.append("HTTP/1.1 500 Internal Server Error\r\n\r\n");
|
||||
m_HTTPServer.NotifyConnectionWrite(*this);
|
||||
m_State = wcsRecvHeaders;
|
||||
break;
|
||||
}
|
||||
|
||||
case wcsSendingResp:
|
||||
{
|
||||
// The response headers have been sent, we need to terminate the response body:
|
||||
m_OutgoingData.append("0\r\n\r\n");
|
||||
m_State = wcsRecvHeaders;
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
{
|
||||
ASSERT(!"Unhandled state recovery");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
void cHTTPConnection::Terminate(void)
|
||||
{
|
||||
if (m_CurrentRequest != NULL)
|
||||
{
|
||||
m_HTTPServer.RequestFinished(*this, *m_CurrentRequest);
|
||||
}
|
||||
m_HTTPServer.CloseConnection(*this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
void cHTTPConnection::DataReceived(const char * a_Data, int a_Size)
|
||||
{
|
||||
switch (m_State)
|
||||
{
|
||||
case wcsRecvHeaders:
|
||||
{
|
||||
if (m_CurrentRequest == NULL)
|
||||
{
|
||||
m_CurrentRequest = new cHTTPRequest;
|
||||
}
|
||||
|
||||
int BytesConsumed = m_CurrentRequest->ParseHeaders(a_Data, a_Size);
|
||||
if (BytesConsumed < 0)
|
||||
{
|
||||
delete m_CurrentRequest;
|
||||
m_CurrentRequest = NULL;
|
||||
m_State = wcsInvalid;
|
||||
m_HTTPServer.CloseConnection(*this);
|
||||
return;
|
||||
}
|
||||
if (m_CurrentRequest->IsInHeaders())
|
||||
{
|
||||
// The request headers are not yet complete
|
||||
return;
|
||||
}
|
||||
|
||||
// The request has finished parsing its headers successfully, notify of it:
|
||||
m_State = wcsRecvBody;
|
||||
m_HTTPServer.NewRequest(*this, *m_CurrentRequest);
|
||||
m_CurrentRequestBodyRemaining = m_CurrentRequest->GetContentLength();
|
||||
if (m_CurrentRequestBodyRemaining < 0)
|
||||
{
|
||||
// The body length was not specified in the request, assume zero
|
||||
m_CurrentRequestBodyRemaining = 0;
|
||||
}
|
||||
|
||||
// Process the rest of the incoming data into the request body:
|
||||
if (a_Size > BytesConsumed)
|
||||
{
|
||||
DataReceived(a_Data + BytesConsumed, a_Size - BytesConsumed);
|
||||
}
|
||||
else
|
||||
{
|
||||
DataReceived("", 0); // If the request has zero body length, let it be processed right-away
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case wcsRecvBody:
|
||||
{
|
||||
ASSERT(m_CurrentRequest != NULL);
|
||||
if (m_CurrentRequestBodyRemaining > 0)
|
||||
{
|
||||
int BytesToConsume = std::min(m_CurrentRequestBodyRemaining, a_Size);
|
||||
m_HTTPServer.RequestBody(*this, *m_CurrentRequest, a_Data, BytesToConsume);
|
||||
m_CurrentRequestBodyRemaining -= BytesToConsume;
|
||||
}
|
||||
if (m_CurrentRequestBodyRemaining == 0)
|
||||
{
|
||||
m_State = wcsRecvIdle;
|
||||
m_HTTPServer.RequestFinished(*this, *m_CurrentRequest);
|
||||
delete m_CurrentRequest;
|
||||
m_CurrentRequest = NULL;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
{
|
||||
// TODO: Should we be receiving data in this state?
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
void cHTTPConnection::GetOutgoingData(AString & a_Data)
|
||||
{
|
||||
std::swap(a_Data, m_OutgoingData);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
void cHTTPConnection::SocketClosed(void)
|
||||
{
|
||||
if (m_CurrentRequest != NULL)
|
||||
{
|
||||
m_HTTPServer.RequestFinished(*this, *m_CurrentRequest);
|
||||
}
|
||||
m_HTTPServer.CloseConnection(*this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
101
source/HTTPServer/HTTPConnection.h
Normal file
101
source/HTTPServer/HTTPConnection.h
Normal file
@ -0,0 +1,101 @@
|
||||
|
||||
// HTTPConnection.h
|
||||
|
||||
// Declares the cHTTPConnection class representing a single persistent connection in the HTTP server.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "../OSSupport/SocketThreads.h"
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// fwd:
|
||||
class cHTTPServer;
|
||||
class cHTTPResponse;
|
||||
class cHTTPRequest;
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
class cHTTPConnection :
|
||||
public cSocketThreads::cCallback
|
||||
{
|
||||
public:
|
||||
|
||||
enum eState
|
||||
{
|
||||
wcsRecvHeaders, ///< Receiving request headers (m_CurrentRequest is created if NULL)
|
||||
wcsRecvBody, ///< Receiving request body (m_CurrentRequest is valid)
|
||||
wcsRecvIdle, ///< Has received the entire body, waiting to send the response (m_CurrentRequest == NULL)
|
||||
wcsSendingResp, ///< Sending response body (m_CurrentRequest == NULL)
|
||||
wcsInvalid, ///< The request was malformed, the connection is closing
|
||||
} ;
|
||||
|
||||
cHTTPConnection(cHTTPServer & a_HTTPServer);
|
||||
~cHTTPConnection();
|
||||
|
||||
/// Sends HTTP status code together with a_Reason (used for HTTP errors)
|
||||
void SendStatusAndReason(int a_StatusCode, const AString & a_Reason);
|
||||
|
||||
/// Sends the "401 unauthorized" reply together with instructions on authorizing, using the specified realm
|
||||
void SendNeedAuth(const AString & a_Realm);
|
||||
|
||||
/// Sends the headers contained in a_Response
|
||||
void Send(const cHTTPResponse & a_Response);
|
||||
|
||||
/// Sends the data as the response (may be called multiple times)
|
||||
void Send(const void * a_Data, int a_Size);
|
||||
|
||||
/// Sends the data as the response (may be called multiple times)
|
||||
void Send(const AString & a_Data) { Send(a_Data.data(), a_Data.size()); }
|
||||
|
||||
/// Indicates that the current response is finished, gets ready for receiving another request (HTTP 1.1 keepalive)
|
||||
void FinishResponse(void);
|
||||
|
||||
/// Resets the connection for a new request. Depending on the state, this will send an "InternalServerError" status or a "ResponseEnd"
|
||||
void AwaitNextRequest(void);
|
||||
|
||||
/// Terminates the connection; finishes any request being currently processed
|
||||
void Terminate(void);
|
||||
|
||||
protected:
|
||||
typedef std::map<AString, AString> cNameValueMap;
|
||||
|
||||
/// The parent webserver that is to be notified of events on this connection
|
||||
cHTTPServer & m_HTTPServer;
|
||||
|
||||
/// All the incoming data until the entire request header is parsed
|
||||
AString m_IncomingHeaderData;
|
||||
|
||||
/// Status in which the request currently is
|
||||
eState m_State;
|
||||
|
||||
/// Data that is queued for sending, once the socket becomes writable
|
||||
AString m_OutgoingData;
|
||||
|
||||
/// The request being currently received (valid only between having parsed the headers and finishing receiving the body)
|
||||
cHTTPRequest * m_CurrentRequest;
|
||||
|
||||
/// Number of bytes that remain to read for the complete body of the message to be received. Valid only in wcsRecvBody
|
||||
int m_CurrentRequestBodyRemaining;
|
||||
|
||||
|
||||
// cSocketThreads::cCallback overrides:
|
||||
virtual void DataReceived (const char * a_Data, int a_Size) override; // Data is received from the client
|
||||
virtual void GetOutgoingData(AString & a_Data) override; // Data can be sent to client
|
||||
virtual void SocketClosed (void) override; // The socket has been closed for any reason
|
||||
} ;
|
||||
|
||||
typedef std::vector<cHTTPConnection *> cHTTPConnections;
|
||||
|
||||
|
||||
|
||||
|
||||
|
278
source/HTTPServer/HTTPFormParser.cpp
Normal file
278
source/HTTPServer/HTTPFormParser.cpp
Normal file
@ -0,0 +1,278 @@
|
||||
|
||||
// HTTPFormParser.cpp
|
||||
|
||||
// Implements the cHTTPFormParser class representing a parser for forms sent over HTTP
|
||||
|
||||
#include "Globals.h"
|
||||
#include "HTTPFormParser.h"
|
||||
#include "HTTPMessage.h"
|
||||
#include "MultipartParser.h"
|
||||
#include "NameValueParser.h"
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
cHTTPFormParser::cHTTPFormParser(cHTTPRequest & a_Request, cCallbacks & a_Callbacks) :
|
||||
m_Callbacks(a_Callbacks),
|
||||
m_IsValid(true)
|
||||
{
|
||||
if (a_Request.GetMethod() == "GET")
|
||||
{
|
||||
m_Kind = fpkURL;
|
||||
|
||||
// Directly parse the URL in the request:
|
||||
const AString & URL = a_Request.GetURL();
|
||||
size_t idxQM = URL.find('?');
|
||||
if (idxQM != AString::npos)
|
||||
{
|
||||
Parse(URL.c_str() + idxQM + 1, URL.size() - idxQM - 1);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if ((a_Request.GetMethod() == "POST") || (a_Request.GetMethod() == "PUT"))
|
||||
{
|
||||
if (strncmp(a_Request.GetContentType().c_str(), "application/x-www-form-urlencoded", 33) == 0)
|
||||
{
|
||||
m_Kind = fpkFormUrlEncoded;
|
||||
return;
|
||||
}
|
||||
if (strncmp(a_Request.GetContentType().c_str(), "multipart/form-data", 19) == 0)
|
||||
{
|
||||
m_Kind = fpkMultipart;
|
||||
BeginMultipart(a_Request);
|
||||
return;
|
||||
}
|
||||
}
|
||||
// Invalid method / content type combination, this is not a HTTP form
|
||||
m_IsValid = false;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
void cHTTPFormParser::Parse(const char * a_Data, int a_Size)
|
||||
{
|
||||
if (!m_IsValid)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
switch (m_Kind)
|
||||
{
|
||||
case fpkURL:
|
||||
case fpkFormUrlEncoded:
|
||||
{
|
||||
// This format is used for smaller forms (not file uploads), so we can delay parsing it until Finish()
|
||||
m_IncomingData.append(a_Data, a_Size);
|
||||
break;
|
||||
}
|
||||
case fpkMultipart:
|
||||
{
|
||||
ASSERT(m_MultipartParser.get() != NULL);
|
||||
m_MultipartParser->Parse(a_Data, a_Size);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
{
|
||||
ASSERT(!"Unhandled form kind");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
bool cHTTPFormParser::Finish(void)
|
||||
{
|
||||
switch (m_Kind)
|
||||
{
|
||||
case fpkURL:
|
||||
case fpkFormUrlEncoded:
|
||||
{
|
||||
// m_IncomingData has all the form data, parse it now:
|
||||
ParseFormUrlEncoded();
|
||||
break;
|
||||
}
|
||||
}
|
||||
return (m_IsValid && m_IncomingData.empty());
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
bool cHTTPFormParser::HasFormData(const cHTTPRequest & a_Request)
|
||||
{
|
||||
const AString & ContentType = a_Request.GetContentType();
|
||||
return (
|
||||
(ContentType == "application/x-www-form-urlencoded") ||
|
||||
(strncmp(ContentType.c_str(), "multipart/form-data", 19) == 0) ||
|
||||
(
|
||||
(a_Request.GetMethod() == "GET") &&
|
||||
(a_Request.GetURL().find('?') != AString::npos)
|
||||
)
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
void cHTTPFormParser::BeginMultipart(const cHTTPRequest & a_Request)
|
||||
{
|
||||
ASSERT(m_MultipartParser.get() == NULL);
|
||||
m_MultipartParser.reset(new cMultipartParser(a_Request.GetContentType(), *this));
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
void cHTTPFormParser::ParseFormUrlEncoded(void)
|
||||
{
|
||||
// Parse m_IncomingData for all the variables; no more data is incoming, since this is called from Finish()
|
||||
// This may not be the most performant version, but we don't care, the form data is small enough and we're not a full-fledged web server anyway
|
||||
AStringVector Lines = StringSplit(m_IncomingData, "&");
|
||||
for (AStringVector::iterator itr = Lines.begin(), end = Lines.end(); itr != end; ++itr)
|
||||
{
|
||||
AStringVector Components = StringSplit(*itr, "=");
|
||||
switch (Components.size())
|
||||
{
|
||||
default:
|
||||
{
|
||||
// Neither name nor value, or too many "="s, mark this as invalid form:
|
||||
m_IsValid = false;
|
||||
return;
|
||||
}
|
||||
case 1:
|
||||
{
|
||||
// Only name present
|
||||
(*this)[URLDecode(ReplaceAllCharOccurrences(Components[0], '+', ' '))] = "";
|
||||
break;
|
||||
}
|
||||
case 2:
|
||||
{
|
||||
// name=value format:
|
||||
(*this)[URLDecode(ReplaceAllCharOccurrences(Components[0], '+', ' '))] = URLDecode(ReplaceAllCharOccurrences(Components[1], '+', ' '));
|
||||
break;
|
||||
}
|
||||
}
|
||||
} // for itr - Lines[]
|
||||
m_IncomingData.clear();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
void cHTTPFormParser::OnPartStart(void)
|
||||
{
|
||||
m_CurrentPartFileName.clear();
|
||||
m_CurrentPartName.clear();
|
||||
m_IsCurrentPartFile = false;
|
||||
m_FileHasBeenAnnounced = false;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
void cHTTPFormParser::OnPartHeader(const AString & a_Key, const AString & a_Value)
|
||||
{
|
||||
if (NoCaseCompare(a_Key, "Content-Disposition") == 0)
|
||||
{
|
||||
size_t len = a_Value.size();
|
||||
size_t ParamsStart = AString::npos;
|
||||
for (size_t i = 0; i < len; ++i)
|
||||
{
|
||||
if (a_Value[i] > ' ')
|
||||
{
|
||||
if (strncmp(a_Value.c_str() + i, "form-data", 9) != 0)
|
||||
{
|
||||
// Content disposition is not "form-data", mark the whole form invalid
|
||||
m_IsValid = false;
|
||||
return;
|
||||
}
|
||||
ParamsStart = a_Value.find(';', i + 9);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (ParamsStart == AString::npos)
|
||||
{
|
||||
// There is data missing in the Content-Disposition field, mark the whole form invalid:
|
||||
m_IsValid = false;
|
||||
return;
|
||||
}
|
||||
|
||||
// Parse the field name and optional filename from this header:
|
||||
cNameValueParser Parser(a_Value.data() + ParamsStart, a_Value.size() - ParamsStart);
|
||||
Parser.Finish();
|
||||
m_CurrentPartName = Parser["name"];
|
||||
if (!Parser.IsValid() || m_CurrentPartName.empty())
|
||||
{
|
||||
// The required parameter "name" is missing, mark the whole form invalid:
|
||||
m_IsValid = false;
|
||||
return;
|
||||
}
|
||||
m_CurrentPartFileName = Parser["filename"];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
void cHTTPFormParser::OnPartData(const char * a_Data, int a_Size)
|
||||
{
|
||||
if (m_CurrentPartName.empty())
|
||||
{
|
||||
// Prologue, epilogue or invalid part
|
||||
return;
|
||||
}
|
||||
if (m_CurrentPartFileName.empty())
|
||||
{
|
||||
// This is a variable, store it in the map
|
||||
iterator itr = find(m_CurrentPartName);
|
||||
if (itr == end())
|
||||
{
|
||||
(*this)[m_CurrentPartName] = AString(a_Data, a_Size);
|
||||
}
|
||||
else
|
||||
{
|
||||
itr->second.append(a_Data, a_Size);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// This is a file, pass it on through the callbacks
|
||||
if (!m_FileHasBeenAnnounced)
|
||||
{
|
||||
m_Callbacks.OnFileStart(*this, m_CurrentPartFileName);
|
||||
m_FileHasBeenAnnounced = true;
|
||||
}
|
||||
m_Callbacks.OnFileData(*this, a_Data, a_Size);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
void cHTTPFormParser::OnPartEnd(void)
|
||||
{
|
||||
if (m_FileHasBeenAnnounced)
|
||||
{
|
||||
m_Callbacks.OnFileEnd(*this);
|
||||
}
|
||||
m_CurrentPartName.clear();
|
||||
m_CurrentPartFileName.clear();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
107
source/HTTPServer/HTTPFormParser.h
Normal file
107
source/HTTPServer/HTTPFormParser.h
Normal file
@ -0,0 +1,107 @@
|
||||
|
||||
// HTTPFormParser.h
|
||||
|
||||
// Declares the cHTTPFormParser class representing a parser for forms sent over HTTP
|
||||
|
||||
|
||||
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "MultipartParser.h"
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// fwd:
|
||||
class cHTTPRequest;
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
class cHTTPFormParser :
|
||||
public std::map<AString, AString>,
|
||||
public cMultipartParser::cCallbacks
|
||||
{
|
||||
public:
|
||||
class cCallbacks
|
||||
{
|
||||
public:
|
||||
/// Called when a new file part is encountered in the form data
|
||||
virtual void OnFileStart(cHTTPFormParser & a_Parser, const AString & a_FileName) = 0;
|
||||
|
||||
/// Called when more file data has come for the current file in the form data
|
||||
virtual void OnFileData(cHTTPFormParser & a_Parser, const char * a_Data, int a_Size) = 0;
|
||||
|
||||
/// Called when the current file part has ended in the form data
|
||||
virtual void OnFileEnd(cHTTPFormParser & a_Parser) = 0;
|
||||
} ;
|
||||
|
||||
|
||||
cHTTPFormParser(cHTTPRequest & a_Request, cCallbacks & a_Callbacks);
|
||||
|
||||
/// Adds more data into the parser, as the request body is received
|
||||
void Parse(const char * a_Data, int a_Size);
|
||||
|
||||
/** Notifies that there's no more data incoming and the parser should finish its parsing.
|
||||
Returns true if parsing successful
|
||||
*/
|
||||
bool Finish(void);
|
||||
|
||||
/// Returns true if the headers suggest the request has form data parseable by this class
|
||||
static bool HasFormData(const cHTTPRequest & a_Request);
|
||||
|
||||
protected:
|
||||
enum eKind
|
||||
{
|
||||
fpkURL, ///< The form has been transmitted as parameters to a GET request
|
||||
fpkFormUrlEncoded, ///< The form has been POSTed or PUT, with Content-Type of "application/x-www-form-urlencoded"
|
||||
fpkMultipart, ///< The form has been POSTed or PUT, with Content-Type of "multipart/form-data"
|
||||
};
|
||||
|
||||
/// The callbacks to call for incoming file data
|
||||
cCallbacks & m_Callbacks;
|
||||
|
||||
/// The kind of the parser (decided in the constructor, used in Parse()
|
||||
eKind m_Kind;
|
||||
|
||||
/// Buffer for the incoming data until it's parsed
|
||||
AString m_IncomingData;
|
||||
|
||||
/// True if the information received so far is a valid form; set to false on first problem. Further parsing is skipped when false.
|
||||
bool m_IsValid;
|
||||
|
||||
/// The parser for the multipart data, if used
|
||||
std::auto_ptr<cMultipartParser> m_MultipartParser;
|
||||
|
||||
/// Name of the currently parsed part in multipart data
|
||||
AString m_CurrentPartName;
|
||||
|
||||
/// True if the currently parsed part in multipart data is a file
|
||||
bool m_IsCurrentPartFile;
|
||||
|
||||
/// Filename of the current parsed part in multipart data (for file uploads)
|
||||
AString m_CurrentPartFileName;
|
||||
|
||||
/// Set to true after m_Callbacks.OnFileStart() has been called, reset to false on PartEnd
|
||||
bool m_FileHasBeenAnnounced;
|
||||
|
||||
|
||||
/// Sets up the object for parsing a fpkMultipart request
|
||||
void BeginMultipart(const cHTTPRequest & a_Request);
|
||||
|
||||
/// Parses m_IncomingData as form-urlencoded data (fpkURL or fpkFormUrlEncoded kinds)
|
||||
void ParseFormUrlEncoded(void);
|
||||
|
||||
// cMultipartParser::cCallbacks overrides:
|
||||
virtual void OnPartStart (void) override;
|
||||
virtual void OnPartHeader(const AString & a_Key, const AString & a_Value) override;
|
||||
virtual void OnPartData (const char * a_Data, int a_Size) override;
|
||||
virtual void OnPartEnd (void) override;
|
||||
} ;
|
||||
|
||||
|
||||
|
||||
|
279
source/HTTPServer/HTTPMessage.cpp
Normal file
279
source/HTTPServer/HTTPMessage.cpp
Normal file
@ -0,0 +1,279 @@
|
||||
|
||||
// HTTPMessage.cpp
|
||||
|
||||
// Declares the cHTTPMessage class representing the common ancestor for HTTP request and response classes
|
||||
|
||||
#include "Globals.h"
|
||||
#include "HTTPMessage.h"
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// Disable MSVC warnings:
|
||||
#if defined(_MSC_VER)
|
||||
#pragma warning(push)
|
||||
#pragma warning(disable:4355) // 'this' : used in base member initializer list
|
||||
#endif
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// cHTTPMessage:
|
||||
|
||||
cHTTPMessage::cHTTPMessage(eKind a_Kind) :
|
||||
m_Kind(a_Kind),
|
||||
m_ContentLength(-1)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
void cHTTPMessage::AddHeader(const AString & a_Key, const AString & a_Value)
|
||||
{
|
||||
AString Key = a_Key;
|
||||
StrToLower(Key);
|
||||
cNameValueMap::iterator itr = m_Headers.find(Key);
|
||||
if (itr == m_Headers.end())
|
||||
{
|
||||
m_Headers[Key] = a_Value;
|
||||
}
|
||||
else
|
||||
{
|
||||
// The header-field key is specified multiple times, combine into comma-separated list (RFC 2616 @ 4.2)
|
||||
itr->second.append(", ");
|
||||
itr->second.append(a_Value);
|
||||
}
|
||||
|
||||
// Special processing for well-known headers:
|
||||
if (Key == "content-type")
|
||||
{
|
||||
m_ContentType = m_Headers[Key];
|
||||
}
|
||||
else if (Key == "content-length")
|
||||
{
|
||||
m_ContentLength = atoi(m_Headers[Key].c_str());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// cHTTPRequest:
|
||||
|
||||
cHTTPRequest::cHTTPRequest(void) :
|
||||
super(mkRequest),
|
||||
m_EnvelopeParser(*this),
|
||||
m_IsValid(true),
|
||||
m_UserData(NULL),
|
||||
m_HasAuth(false)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
int cHTTPRequest::ParseHeaders(const char * a_Data, int a_Size)
|
||||
{
|
||||
if (!m_IsValid)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (m_Method.empty())
|
||||
{
|
||||
// The first line hasn't been processed yet
|
||||
int res = ParseRequestLine(a_Data, a_Size);
|
||||
if ((res < 0) || (res == a_Size))
|
||||
{
|
||||
return res;
|
||||
}
|
||||
int res2 = m_EnvelopeParser.Parse(a_Data + res, a_Size - res);
|
||||
if (res2 < 0)
|
||||
{
|
||||
m_IsValid = false;
|
||||
return res2;
|
||||
}
|
||||
return res2 + res;
|
||||
}
|
||||
|
||||
if (m_EnvelopeParser.IsInHeaders())
|
||||
{
|
||||
int res = m_EnvelopeParser.Parse(a_Data, a_Size);
|
||||
if (res < 0)
|
||||
{
|
||||
m_IsValid = false;
|
||||
}
|
||||
return res;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
AString cHTTPRequest::GetBareURL(void) const
|
||||
{
|
||||
size_t idxQM = m_URL.find('?');
|
||||
if (idxQM != AString::npos)
|
||||
{
|
||||
return m_URL.substr(0, idxQM);
|
||||
}
|
||||
else
|
||||
{
|
||||
return m_URL;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
int cHTTPRequest::ParseRequestLine(const char * a_Data, int a_Size)
|
||||
{
|
||||
m_IncomingHeaderData.append(a_Data, a_Size);
|
||||
size_t IdxEnd = m_IncomingHeaderData.size();
|
||||
|
||||
// Ignore the initial CRLFs (HTTP spec's "should")
|
||||
size_t LineStart = 0;
|
||||
while (
|
||||
(LineStart < IdxEnd) &&
|
||||
(
|
||||
(m_IncomingHeaderData[LineStart] == '\r') ||
|
||||
(m_IncomingHeaderData[LineStart] == '\n')
|
||||
)
|
||||
)
|
||||
{
|
||||
LineStart++;
|
||||
}
|
||||
if (LineStart >= IdxEnd)
|
||||
{
|
||||
m_IsValid = false;
|
||||
return -1;
|
||||
}
|
||||
|
||||
int NumSpaces = 0;
|
||||
size_t MethodEnd = 0;
|
||||
size_t URLEnd = 0;
|
||||
for (size_t i = LineStart; i < IdxEnd; i++)
|
||||
{
|
||||
switch (m_IncomingHeaderData[i])
|
||||
{
|
||||
case ' ':
|
||||
{
|
||||
switch (NumSpaces)
|
||||
{
|
||||
case 0:
|
||||
{
|
||||
MethodEnd = i;
|
||||
break;
|
||||
}
|
||||
case 1:
|
||||
{
|
||||
URLEnd = i;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
{
|
||||
// Too many spaces in the request
|
||||
m_IsValid = false;
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
NumSpaces += 1;
|
||||
break;
|
||||
}
|
||||
case '\n':
|
||||
{
|
||||
if ((i == 0) || (m_IncomingHeaderData[i - 1] != '\r') || (NumSpaces != 2) || (i < URLEnd + 7))
|
||||
{
|
||||
// LF too early, without a CR, without two preceeding spaces or too soon after the second space
|
||||
m_IsValid = false;
|
||||
return -1;
|
||||
}
|
||||
// Check that there's HTTP/version at the end
|
||||
if (strncmp(a_Data + URLEnd + 1, "HTTP/1.", 7) != 0)
|
||||
{
|
||||
m_IsValid = false;
|
||||
return -1;
|
||||
}
|
||||
m_Method = m_IncomingHeaderData.substr(LineStart, MethodEnd - LineStart);
|
||||
m_URL = m_IncomingHeaderData.substr(MethodEnd + 1, URLEnd - MethodEnd - 1);
|
||||
return i + 1;
|
||||
}
|
||||
} // switch (m_IncomingHeaderData[i])
|
||||
} // for i - m_IncomingHeaderData[]
|
||||
|
||||
// CRLF hasn't been encountered yet, consider all data consumed
|
||||
return a_Size;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
void cHTTPRequest::OnHeaderLine(const AString & a_Key, const AString & a_Value)
|
||||
{
|
||||
if (
|
||||
(NoCaseCompare(a_Key, "Authorization") == 0) &&
|
||||
(strncmp(a_Value.c_str(), "Basic ", 6) == 0)
|
||||
)
|
||||
{
|
||||
AString UserPass = Base64Decode(a_Value.substr(6));
|
||||
size_t idxCol = UserPass.find(':');
|
||||
if (idxCol != AString::npos)
|
||||
{
|
||||
m_AuthUsername = UserPass.substr(0, idxCol);
|
||||
m_AuthPassword = UserPass.substr(idxCol + 1);
|
||||
m_HasAuth = true;
|
||||
}
|
||||
}
|
||||
AddHeader(a_Key, a_Value);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// cHTTPResponse:
|
||||
|
||||
cHTTPResponse::cHTTPResponse(void) :
|
||||
super(mkResponse)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
void cHTTPResponse::AppendToData(AString & a_DataStream) const
|
||||
{
|
||||
a_DataStream.append("HTTP/1.1 200 OK\r\nTransfer-Encoding: chunked\r\nContent-Type: ");
|
||||
a_DataStream.append(m_ContentType);
|
||||
a_DataStream.append("\r\n");
|
||||
for (cNameValueMap::const_iterator itr = m_Headers.begin(), end = m_Headers.end(); itr != end; ++itr)
|
||||
{
|
||||
if ((itr->first == "Content-Type") || (itr->first == "Content-Length"))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
a_DataStream.append(itr->first);
|
||||
a_DataStream.append(": ");
|
||||
a_DataStream.append(itr->second);
|
||||
a_DataStream.append("\r\n");
|
||||
} // for itr - m_Headers[]
|
||||
a_DataStream.append("\r\n");
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
164
source/HTTPServer/HTTPMessage.h
Normal file
164
source/HTTPServer/HTTPMessage.h
Normal file
@ -0,0 +1,164 @@
|
||||
|
||||
// HTTPMessage.h
|
||||
|
||||
// Declares the cHTTPMessage class representing the common ancestor for HTTP request and response classes
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "EnvelopeParser.h"
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
class cHTTPMessage
|
||||
{
|
||||
public:
|
||||
enum
|
||||
{
|
||||
HTTP_OK = 200,
|
||||
HTTP_BAD_REQUEST = 400,
|
||||
} ;
|
||||
|
||||
enum eKind
|
||||
{
|
||||
mkRequest,
|
||||
mkResponse,
|
||||
} ;
|
||||
|
||||
cHTTPMessage(eKind a_Kind);
|
||||
|
||||
/// Adds a header into the internal map of headers. Recognizes special headers: Content-Type and Content-Length
|
||||
void AddHeader(const AString & a_Key, const AString & a_Value);
|
||||
|
||||
void SetContentType (const AString & a_ContentType) { m_ContentType = a_ContentType; }
|
||||
void SetContentLength(int a_ContentLength) { m_ContentLength = a_ContentLength; }
|
||||
|
||||
const AString & GetContentType (void) const { return m_ContentType; }
|
||||
int GetContentLength(void) const { return m_ContentLength; }
|
||||
|
||||
protected:
|
||||
typedef std::map<AString, AString> cNameValueMap;
|
||||
|
||||
eKind m_Kind;
|
||||
|
||||
cNameValueMap m_Headers;
|
||||
|
||||
/// Type of the content; parsed by AddHeader(), set directly by SetContentLength()
|
||||
AString m_ContentType;
|
||||
|
||||
/// Length of the content that is to be received. -1 when the object is created, parsed by AddHeader() or set directly by SetContentLength()
|
||||
int m_ContentLength;
|
||||
} ;
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
class cHTTPRequest :
|
||||
public cHTTPMessage,
|
||||
protected cEnvelopeParser::cCallbacks
|
||||
{
|
||||
typedef cHTTPMessage super;
|
||||
|
||||
public:
|
||||
cHTTPRequest(void);
|
||||
|
||||
/** Parses the request line and then headers from the received data.
|
||||
Returns the number of bytes consumed or a negative number for error
|
||||
*/
|
||||
int ParseHeaders(const char * a_Data, int a_Size);
|
||||
|
||||
/// Returns true if the request did contain a Content-Length header
|
||||
bool HasReceivedContentLength(void) const { return (m_ContentLength >= 0); }
|
||||
|
||||
/// Returns the method used in the request
|
||||
const AString & GetMethod(void) const { return m_Method; }
|
||||
|
||||
/// Returns the URL used in the request
|
||||
const AString & GetURL(void) const { return m_URL; }
|
||||
|
||||
/// Returns the URL used in the request, without any parameters
|
||||
AString GetBareURL(void) const;
|
||||
|
||||
/// Sets the UserData pointer that is stored within this request. The request doesn't touch this data (doesn't delete it)!
|
||||
void SetUserData(void * a_UserData) { m_UserData = a_UserData; }
|
||||
|
||||
/// Retrieves the UserData pointer that has been stored within this request.
|
||||
void * GetUserData(void) const { return m_UserData; }
|
||||
|
||||
/// Returns true if more data is expected for the request headers
|
||||
bool IsInHeaders(void) const { return m_EnvelopeParser.IsInHeaders(); }
|
||||
|
||||
/// Returns true if the request did present auth data that was understood by the parser
|
||||
bool HasAuth(void) const { return m_HasAuth; }
|
||||
|
||||
/// Returns the username that the request presented. Only valid if HasAuth() is true
|
||||
const AString & GetAuthUsername(void) const { return m_AuthUsername; }
|
||||
|
||||
/// Returns the password that the request presented. Only valid if HasAuth() is true
|
||||
const AString & GetAuthPassword(void) const { return m_AuthPassword; }
|
||||
|
||||
protected:
|
||||
/// Parser for the envelope data
|
||||
cEnvelopeParser m_EnvelopeParser;
|
||||
|
||||
/// True if the data received so far is parsed successfully. When false, all further parsing is skipped
|
||||
bool m_IsValid;
|
||||
|
||||
/// Bufferred incoming data, while parsing for the request line
|
||||
AString m_IncomingHeaderData;
|
||||
|
||||
/// Method of the request (GET / PUT / POST / ...)
|
||||
AString m_Method;
|
||||
|
||||
/// Full URL of the request
|
||||
AString m_URL;
|
||||
|
||||
/// Data that the HTTPServer callbacks are allowed to store.
|
||||
void * m_UserData;
|
||||
|
||||
/// Set to true if the request contains auth data that was understood by the parser
|
||||
bool m_HasAuth;
|
||||
|
||||
/// The username used for auth
|
||||
AString m_AuthUsername;
|
||||
|
||||
/// The password used for auth
|
||||
AString m_AuthPassword;
|
||||
|
||||
|
||||
/** Parses the incoming data for the first line (RequestLine)
|
||||
Returns the number of bytes consumed, or -1 for an error
|
||||
*/
|
||||
int ParseRequestLine(const char * a_Data, int a_Size);
|
||||
|
||||
// cEnvelopeParser::cCallbacks overrides:
|
||||
virtual void OnHeaderLine(const AString & a_Key, const AString & a_Value) override;
|
||||
} ;
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
class cHTTPResponse :
|
||||
public cHTTPMessage
|
||||
{
|
||||
typedef cHTTPMessage super;
|
||||
|
||||
public:
|
||||
cHTTPResponse(void);
|
||||
|
||||
/** Appends the response to the specified datastream - response line and headers.
|
||||
The body will be sent later directly through cConnection::Send()
|
||||
*/
|
||||
void AppendToData(AString & a_DataStream) const;
|
||||
} ;
|
||||
|
||||
|
||||
|
||||
|
258
source/HTTPServer/HTTPServer.cpp
Normal file
258
source/HTTPServer/HTTPServer.cpp
Normal file
@ -0,0 +1,258 @@
|
||||
|
||||
// HTTPServer.cpp
|
||||
|
||||
// Implements the cHTTPServer class representing a HTTP webserver that uses cListenThread and cSocketThreads for processing
|
||||
|
||||
#include "Globals.h"
|
||||
#include "HTTPServer.h"
|
||||
#include "HTTPMessage.h"
|
||||
#include "HTTPConnection.h"
|
||||
#include "HTTPFormParser.h"
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// Disable MSVC warnings:
|
||||
#if defined(_MSC_VER)
|
||||
#pragma warning(push)
|
||||
#pragma warning(disable:4355) // 'this' : used in base member initializer list
|
||||
#endif
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
class cDebugCallbacks :
|
||||
public cHTTPServer::cCallbacks,
|
||||
protected cHTTPFormParser::cCallbacks
|
||||
{
|
||||
virtual void OnRequestBegun(cHTTPConnection & a_Connection, cHTTPRequest & a_Request) override
|
||||
{
|
||||
if (cHTTPFormParser::HasFormData(a_Request))
|
||||
{
|
||||
a_Request.SetUserData(new cHTTPFormParser(a_Request, *this));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
virtual void OnRequestBody(cHTTPConnection & a_Connection, cHTTPRequest & a_Request, const char * a_Data, int a_Size) override
|
||||
{
|
||||
cHTTPFormParser * FormParser = (cHTTPFormParser *)(a_Request.GetUserData());
|
||||
if (FormParser != NULL)
|
||||
{
|
||||
FormParser->Parse(a_Data, a_Size);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
virtual void OnRequestFinished(cHTTPConnection & a_Connection, cHTTPRequest & a_Request) override
|
||||
{
|
||||
cHTTPFormParser * FormParser = (cHTTPFormParser *)(a_Request.GetUserData());
|
||||
if (FormParser != NULL)
|
||||
{
|
||||
if (FormParser->Finish())
|
||||
{
|
||||
cHTTPResponse Resp;
|
||||
Resp.SetContentType("text/html");
|
||||
a_Connection.Send(Resp);
|
||||
a_Connection.Send("<html><body><table border=1 cellspacing=0><tr><th>Name</th><th>Value</th></tr>\r\n");
|
||||
for (cHTTPFormParser::iterator itr = FormParser->begin(), end = FormParser->end(); itr != end; ++itr)
|
||||
{
|
||||
a_Connection.Send(Printf("<tr><td valign=\"top\"><pre>%s</pre></td><td valign=\"top\"><pre>%s</pre></td></tr>\r\n", itr->first.c_str(), itr->second.c_str()));
|
||||
} // for itr - FormParser[]
|
||||
a_Connection.Send("</table></body></html>");
|
||||
return;
|
||||
}
|
||||
|
||||
// Parsing failed:
|
||||
cHTTPResponse Resp;
|
||||
Resp.SetContentType("text/plain");
|
||||
a_Connection.Send(Resp);
|
||||
a_Connection.Send("Form parsing failed");
|
||||
return;
|
||||
}
|
||||
|
||||
// Test the auth failure and success:
|
||||
if (a_Request.GetURL() == "/auth")
|
||||
{
|
||||
if (!a_Request.HasAuth() || (a_Request.GetAuthUsername() != "a") || (a_Request.GetAuthPassword() != "b"))
|
||||
{
|
||||
a_Connection.SendNeedAuth("MCServer WebAdmin");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
cHTTPResponse Resp;
|
||||
Resp.SetContentType("text/plain");
|
||||
a_Connection.Send(Resp);
|
||||
a_Connection.Send("Hello, world");
|
||||
}
|
||||
|
||||
|
||||
virtual void OnFileStart(cHTTPFormParser & a_Parser, const AString & a_FileName) override
|
||||
{
|
||||
// TODO
|
||||
}
|
||||
|
||||
|
||||
virtual void OnFileData(cHTTPFormParser & a_Parser, const char * a_Data, int a_Size) override
|
||||
{
|
||||
// TODO
|
||||
}
|
||||
|
||||
|
||||
virtual void OnFileEnd(cHTTPFormParser & a_Parser) override
|
||||
{
|
||||
// TODO
|
||||
}
|
||||
|
||||
} g_DebugCallbacks;
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// cHTTPServer:
|
||||
|
||||
cHTTPServer::cHTTPServer(void) :
|
||||
m_ListenThreadIPv4(*this, cSocket::IPv4, "WebServer IPv4"),
|
||||
m_ListenThreadIPv6(*this, cSocket::IPv6, "WebServer IPv6"),
|
||||
m_Callbacks(NULL)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
cHTTPServer::~cHTTPServer()
|
||||
{
|
||||
Stop();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
bool cHTTPServer::Initialize(const AString & a_PortsIPv4, const AString & a_PortsIPv6)
|
||||
{
|
||||
bool HasAnyPort;
|
||||
HasAnyPort = m_ListenThreadIPv4.Initialize(a_PortsIPv4);
|
||||
HasAnyPort = m_ListenThreadIPv6.Initialize(a_PortsIPv6) || HasAnyPort;
|
||||
if (!HasAnyPort)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
bool cHTTPServer::Start(cCallbacks & a_Callbacks)
|
||||
{
|
||||
m_Callbacks = &a_Callbacks;
|
||||
if (!m_ListenThreadIPv4.Start())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (!m_ListenThreadIPv6.Start())
|
||||
{
|
||||
m_ListenThreadIPv4.Stop();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
void cHTTPServer::Stop(void)
|
||||
{
|
||||
m_ListenThreadIPv4.Stop();
|
||||
m_ListenThreadIPv6.Stop();
|
||||
|
||||
// Drop all current connections:
|
||||
cCSLock Lock(m_CSConnections);
|
||||
while (!m_Connections.empty())
|
||||
{
|
||||
m_Connections.front()->Terminate();
|
||||
} // for itr - m_Connections[]
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
void cHTTPServer::OnConnectionAccepted(cSocket & a_Socket)
|
||||
{
|
||||
cHTTPConnection * Connection = new cHTTPConnection(*this);
|
||||
m_SocketThreads.AddClient(a_Socket, Connection);
|
||||
cCSLock Lock(m_CSConnections);
|
||||
m_Connections.push_back(Connection);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
void cHTTPServer::CloseConnection(cHTTPConnection & a_Connection)
|
||||
{
|
||||
m_SocketThreads.RemoveClient(&a_Connection);
|
||||
cCSLock Lock(m_CSConnections);
|
||||
for (cHTTPConnections::iterator itr = m_Connections.begin(), end = m_Connections.end(); itr != end; ++itr)
|
||||
{
|
||||
if (*itr == &a_Connection)
|
||||
{
|
||||
m_Connections.erase(itr);
|
||||
break;
|
||||
}
|
||||
}
|
||||
delete &a_Connection;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
void cHTTPServer::NotifyConnectionWrite(cHTTPConnection & a_Connection)
|
||||
{
|
||||
m_SocketThreads.NotifyWrite(&a_Connection);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
void cHTTPServer::NewRequest(cHTTPConnection & a_Connection, cHTTPRequest & a_Request)
|
||||
{
|
||||
m_Callbacks->OnRequestBegun(a_Connection, a_Request);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
void cHTTPServer::RequestBody(cHTTPConnection & a_Connection, cHTTPRequest & a_Request, const char * a_Data, int a_Size)
|
||||
{
|
||||
m_Callbacks->OnRequestBody(a_Connection, a_Request, a_Data, a_Size);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
void cHTTPServer::RequestFinished(cHTTPConnection & a_Connection, cHTTPRequest & a_Request)
|
||||
{
|
||||
m_Callbacks->OnRequestFinished(a_Connection, a_Request);
|
||||
a_Connection.AwaitNextRequest();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
101
source/HTTPServer/HTTPServer.h
Normal file
101
source/HTTPServer/HTTPServer.h
Normal file
@ -0,0 +1,101 @@
|
||||
|
||||
// HTTPServer.h
|
||||
|
||||
// Declares the cHTTPServer class representing a HTTP webserver that uses cListenThread and cSocketThreads for processing
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "../OSSupport/ListenThread.h"
|
||||
#include "../OSSupport/SocketThreads.h"
|
||||
#include "../../iniFile/iniFile.h"
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// fwd:
|
||||
class cHTTPMessage;
|
||||
class cHTTPRequest;
|
||||
class cHTTPResponse;
|
||||
class cHTTPConnection;
|
||||
|
||||
typedef std::vector<cHTTPConnection *> cHTTPConnections;
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
class cHTTPServer :
|
||||
public cListenThread::cCallback
|
||||
{
|
||||
public:
|
||||
class cCallbacks
|
||||
{
|
||||
public:
|
||||
/** Called when a new request arrives over a connection and its headers have been parsed.
|
||||
The request body needn't have arrived yet.
|
||||
*/
|
||||
virtual void OnRequestBegun(cHTTPConnection & a_Connection, cHTTPRequest & a_Request) = 0;
|
||||
|
||||
/// Called when another part of request body has arrived.
|
||||
virtual void OnRequestBody(cHTTPConnection & a_Connection, cHTTPRequest & a_Request, const char * a_Data, int a_Size) = 0;
|
||||
|
||||
/// Called when the request body has been fully received in previous calls to OnRequestBody()
|
||||
virtual void OnRequestFinished(cHTTPConnection & a_Connection, cHTTPRequest & a_Request) = 0;
|
||||
} ;
|
||||
|
||||
cHTTPServer(void);
|
||||
~cHTTPServer();
|
||||
|
||||
/// Initializes the server on the specified ports
|
||||
bool Initialize(const AString & a_PortsIPv4, const AString & a_PortsIPv6);
|
||||
|
||||
/// Starts the server and assigns the callbacks to use for incoming requests
|
||||
bool Start(cCallbacks & a_Callbacks);
|
||||
|
||||
/// Stops the server, drops all current connections
|
||||
void Stop(void);
|
||||
|
||||
protected:
|
||||
friend class cHTTPConnection;
|
||||
|
||||
cListenThread m_ListenThreadIPv4;
|
||||
cListenThread m_ListenThreadIPv6;
|
||||
|
||||
cSocketThreads m_SocketThreads;
|
||||
|
||||
cCriticalSection m_CSConnections;
|
||||
cHTTPConnections m_Connections; ///< All the connections that are currently being serviced
|
||||
|
||||
/// The callbacks to call for various events
|
||||
cCallbacks * m_Callbacks;
|
||||
|
||||
|
||||
// cListenThread::cCallback overrides:
|
||||
virtual void OnConnectionAccepted(cSocket & a_Socket) override;
|
||||
|
||||
/// Called by cHTTPConnection to close the connection (presumably due to an error)
|
||||
void CloseConnection(cHTTPConnection & a_Connection);
|
||||
|
||||
/// Called by cHTTPConnection to notify SocketThreads that there's data to be sent for the connection
|
||||
void NotifyConnectionWrite(cHTTPConnection & a_Connection);
|
||||
|
||||
/// Called by cHTTPConnection when it finishes parsing the request header
|
||||
void NewRequest(cHTTPConnection & a_Connection, cHTTPRequest & a_Request);
|
||||
|
||||
/// Called by cHTTPConenction when it receives more data for the request body
|
||||
void RequestBody(cHTTPConnection & a_Connection, cHTTPRequest & a_Request, const char * a_Data, int a_Size);
|
||||
|
||||
/// Called by cHTTPConnection when it detects that the request has finished (all of its body has been received)
|
||||
void RequestFinished(cHTTPConnection & a_Connection, cHTTPRequest & a_Request);
|
||||
} ;
|
||||
|
||||
|
||||
|
||||
|
||||
|
256
source/HTTPServer/MultipartParser.cpp
Normal file
256
source/HTTPServer/MultipartParser.cpp
Normal file
@ -0,0 +1,256 @@
|
||||
|
||||
// MultipartParser.cpp
|
||||
|
||||
// Implements the cMultipartParser class that parses messages in "multipart/*" encoding into the separate parts
|
||||
|
||||
#include "Globals.h"
|
||||
#include "MultipartParser.h"
|
||||
#include "NameValueParser.h"
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// Disable MSVC warnings:
|
||||
#if defined(_MSC_VER)
|
||||
#pragma warning(push)
|
||||
#pragma warning(disable:4355) // 'this' : used in base member initializer list
|
||||
#endif
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// self-test:
|
||||
|
||||
#if 0
|
||||
|
||||
class cMultipartParserTest :
|
||||
public cMultipartParser::cCallbacks
|
||||
{
|
||||
public:
|
||||
cMultipartParserTest(void)
|
||||
{
|
||||
cMultipartParser Parser("multipart/mixed; boundary=\"MyBoundaryString\"; foo=bar", *this);
|
||||
const char Data[] =
|
||||
"ThisIsIgnoredPrologue\r\n\
|
||||
--MyBoundaryString\r\n\
|
||||
\r\n\
|
||||
Body with confusing strings\r\n\
|
||||
--NotABoundary\r\n\
|
||||
--MyBoundaryStringWithPostfix\r\n\
|
||||
--\r\n\
|
||||
--MyBoundaryString\r\n\
|
||||
content-disposition: inline\r\n\
|
||||
\r\n\
|
||||
This is body\r\n\
|
||||
--MyBoundaryString\r\n\
|
||||
\r\n\
|
||||
Headerless body with trailing CRLF\r\n\
|
||||
\r\n\
|
||||
--MyBoundaryString--\r\n\
|
||||
ThisIsIgnoredEpilogue";
|
||||
printf("Multipart parsing test commencing.\n");
|
||||
Parser.Parse(Data, sizeof(Data) - 1);
|
||||
// DEBUG: Check if the onscreen output corresponds with the data above
|
||||
printf("Multipart parsing test finished\n");
|
||||
}
|
||||
|
||||
virtual void OnPartStart(void) override
|
||||
{
|
||||
printf("Starting a new part\n");
|
||||
}
|
||||
|
||||
|
||||
virtual void OnPartHeader(const AString & a_Key, const AString & a_Value) override
|
||||
{
|
||||
printf(" Hdr: \"%s\"=\"%s\"\n", a_Key.c_str(), a_Value.c_str());
|
||||
}
|
||||
|
||||
|
||||
virtual void OnPartData(const char * a_Data, int a_Size) override
|
||||
{
|
||||
printf(" Data: %d bytes, \"%.*s\"\n", a_Size, a_Size, a_Data);
|
||||
}
|
||||
|
||||
|
||||
virtual void OnPartEnd(void) override
|
||||
{
|
||||
printf("Part end\n");
|
||||
}
|
||||
} g_Test;
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// cMultipartParser:
|
||||
|
||||
|
||||
cMultipartParser::cMultipartParser(const AString & a_ContentType, cCallbacks & a_Callbacks) :
|
||||
m_Callbacks(a_Callbacks),
|
||||
m_IsValid(true),
|
||||
m_EnvelopeParser(*this),
|
||||
m_HasHadData(false)
|
||||
{
|
||||
static AString s_Multipart = "multipart/";
|
||||
|
||||
// Check that the content type is multipart:
|
||||
AString ContentType(a_ContentType);
|
||||
if (strncmp(ContentType.c_str(), "multipart/", 10) != 0)
|
||||
{
|
||||
m_IsValid = false;
|
||||
return;
|
||||
}
|
||||
size_t idxSC = ContentType.find(';', 10);
|
||||
if (idxSC == AString::npos)
|
||||
{
|
||||
m_IsValid = false;
|
||||
return;
|
||||
}
|
||||
|
||||
// Find the multipart boundary:
|
||||
ContentType.erase(0, idxSC + 1);
|
||||
cNameValueParser CTParser(ContentType.c_str(), ContentType.size());
|
||||
CTParser.Finish();
|
||||
if (!CTParser.IsValid())
|
||||
{
|
||||
m_IsValid = false;
|
||||
return;
|
||||
}
|
||||
m_Boundary = CTParser["boundary"];
|
||||
m_IsValid = !m_Boundary.empty();
|
||||
if (!m_IsValid)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Set the envelope parser for parsing the body, so that our Parse() function parses the ignored prefix data as a body
|
||||
m_EnvelopeParser.SetIsInHeaders(false);
|
||||
|
||||
// Append an initial CRLF to the incoming data, so that a body starting with the boundary line will get caught
|
||||
m_IncomingData.assign("\r\n");
|
||||
|
||||
/*
|
||||
m_Boundary = AString("\r\n--") + m_Boundary
|
||||
m_BoundaryEnd = m_Boundary + "--\r\n";
|
||||
m_Boundary = m_Boundary + "\r\n";
|
||||
*/
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
void cMultipartParser::Parse(const char * a_Data, int a_Size)
|
||||
{
|
||||
// Skip parsing if invalid
|
||||
if (!m_IsValid)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Append to buffer, then parse it:
|
||||
m_IncomingData.append(a_Data, a_Size);
|
||||
while (true)
|
||||
{
|
||||
if (m_EnvelopeParser.IsInHeaders())
|
||||
{
|
||||
int BytesConsumed = m_EnvelopeParser.Parse(m_IncomingData.data(), m_IncomingData.size());
|
||||
if (BytesConsumed < 0)
|
||||
{
|
||||
m_IsValid = false;
|
||||
return;
|
||||
}
|
||||
if ((BytesConsumed == a_Size) && m_EnvelopeParser.IsInHeaders())
|
||||
{
|
||||
// All the incoming data has been consumed and still waiting for more
|
||||
return;
|
||||
}
|
||||
m_IncomingData.erase(0, BytesConsumed);
|
||||
}
|
||||
|
||||
// Search for boundary / boundary end:
|
||||
size_t idxBoundary = m_IncomingData.find("\r\n--");
|
||||
if (idxBoundary == AString::npos)
|
||||
{
|
||||
// Boundary string start not present, present as much data to the part callback as possible
|
||||
if (m_IncomingData.size() > m_Boundary.size() + 8)
|
||||
{
|
||||
size_t BytesToReport = m_IncomingData.size() - m_Boundary.size() - 8;
|
||||
m_Callbacks.OnPartData(m_IncomingData.data(), BytesToReport);
|
||||
m_IncomingData.erase(0, BytesToReport);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (idxBoundary > 0)
|
||||
{
|
||||
m_Callbacks.OnPartData(m_IncomingData.data(), idxBoundary);
|
||||
m_IncomingData.erase(0, idxBoundary);
|
||||
}
|
||||
idxBoundary = 4;
|
||||
size_t LineEnd = m_IncomingData.find("\r\n", idxBoundary);
|
||||
if (LineEnd == AString::npos)
|
||||
{
|
||||
// Not a complete line yet, present as much data to the part callback as possible
|
||||
if (m_IncomingData.size() > m_Boundary.size() + 8)
|
||||
{
|
||||
size_t BytesToReport = m_IncomingData.size() - m_Boundary.size() - 8;
|
||||
m_Callbacks.OnPartData(m_IncomingData.data(), BytesToReport);
|
||||
m_IncomingData.erase(0, BytesToReport);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (
|
||||
(LineEnd - idxBoundary != m_Boundary.size()) && // Line length not equal to boundary
|
||||
(LineEnd - idxBoundary != m_Boundary.size() + 2) // Line length not equal to boundary end
|
||||
)
|
||||
{
|
||||
// Got a line, but it's not a boundary, report it as data:
|
||||
m_Callbacks.OnPartData(m_IncomingData.data(), LineEnd);
|
||||
m_IncomingData.erase(0, LineEnd);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (strncmp(m_IncomingData.c_str() + idxBoundary, m_Boundary.c_str(), m_Boundary.size()) == 0)
|
||||
{
|
||||
// Boundary or BoundaryEnd found:
|
||||
m_Callbacks.OnPartEnd();
|
||||
size_t idxSlash = idxBoundary + m_Boundary.size();
|
||||
if ((m_IncomingData[idxSlash] == '-') && (m_IncomingData[idxSlash + 1] == '-'))
|
||||
{
|
||||
// This was the last part
|
||||
m_Callbacks.OnPartData(m_IncomingData.data() + idxSlash + 4, m_IncomingData.size() - idxSlash - 4);
|
||||
m_IncomingData.clear();
|
||||
return;
|
||||
}
|
||||
m_Callbacks.OnPartStart();
|
||||
m_IncomingData.erase(0, LineEnd + 2);
|
||||
|
||||
// Keep parsing for the headers that may have come with this data:
|
||||
m_EnvelopeParser.Reset();
|
||||
continue;
|
||||
}
|
||||
|
||||
// It's a line, but not a boundary. It can be fully sent to the data receiver, since a boundary cannot cross lines
|
||||
m_Callbacks.OnPartData(m_IncomingData.c_str(), LineEnd);
|
||||
m_IncomingData.erase(0, LineEnd);
|
||||
} // while (true)
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
void cMultipartParser::OnHeaderLine(const AString & a_Key, const AString & a_Value)
|
||||
{
|
||||
m_Callbacks.OnPartHeader(a_Key, a_Value);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
76
source/HTTPServer/MultipartParser.h
Normal file
76
source/HTTPServer/MultipartParser.h
Normal file
@ -0,0 +1,76 @@
|
||||
|
||||
// MultipartParser.h
|
||||
|
||||
// Declares the cMultipartParser class that parses messages in "multipart/*" encoding into the separate parts
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "EnvelopeParser.h"
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
class cMultipartParser :
|
||||
protected cEnvelopeParser::cCallbacks
|
||||
{
|
||||
public:
|
||||
class cCallbacks
|
||||
{
|
||||
public:
|
||||
/// Called when a new part starts
|
||||
virtual void OnPartStart(void) = 0;
|
||||
|
||||
/// Called when a complete header line is received for a part
|
||||
virtual void OnPartHeader(const AString & a_Key, const AString & a_Value) = 0;
|
||||
|
||||
/// Called when body for a part is received
|
||||
virtual void OnPartData(const char * a_Data, int a_Size) = 0;
|
||||
|
||||
/// Called when the current part ends
|
||||
virtual void OnPartEnd(void) = 0;
|
||||
} ;
|
||||
|
||||
/// Creates the parser, expects to find the boundary in a_ContentType
|
||||
cMultipartParser(const AString & a_ContentType, cCallbacks & a_Callbacks);
|
||||
|
||||
/// Parses more incoming data
|
||||
void Parse(const char * a_Data, int a_Size);
|
||||
|
||||
protected:
|
||||
/// The callbacks to call for various parsing events
|
||||
cCallbacks & m_Callbacks;
|
||||
|
||||
/// True if the data parsed so far is valid; if false, further parsing is skipped
|
||||
bool m_IsValid;
|
||||
|
||||
/// Parser for each part's envelope
|
||||
cEnvelopeParser m_EnvelopeParser;
|
||||
|
||||
/// Buffer for the incoming data until it is parsed
|
||||
AString m_IncomingData;
|
||||
|
||||
/// The boundary, excluding both the initial "--" and the terminating CRLF
|
||||
AString m_Boundary;
|
||||
|
||||
/// Set to true if some data for the current part has already been signalized to m_Callbacks. Used for proper CRLF inserting.
|
||||
bool m_HasHadData;
|
||||
|
||||
|
||||
/// Parse one line of incoming data. The CRLF has already been stripped from a_Data / a_Size
|
||||
void ParseLine(const char * a_Data, int a_Size);
|
||||
|
||||
/// Parse one line of incoming data in the headers section of a part. The CRLF has already been stripped from a_Data / a_Size
|
||||
void ParseHeaderLine(const char * a_Data, int a_Size);
|
||||
|
||||
// cEnvelopeParser overrides:
|
||||
virtual void OnHeaderLine(const AString & a_Key, const AString & a_Value) override;
|
||||
} ;
|
||||
|
||||
|
||||
|
||||
|
412
source/HTTPServer/NameValueParser.cpp
Normal file
412
source/HTTPServer/NameValueParser.cpp
Normal file
@ -0,0 +1,412 @@
|
||||
|
||||
// NameValueParser.cpp
|
||||
|
||||
// Implements the cNameValueParser class that parses strings in the "name=value;name2=value2" format into a stringmap
|
||||
|
||||
#include "Globals.h"
|
||||
#include "NameValueParser.h"
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// DEBUG: Self-test
|
||||
|
||||
#if 0
|
||||
|
||||
class cNameValueParserTest
|
||||
{
|
||||
public:
|
||||
cNameValueParserTest(void)
|
||||
{
|
||||
const char Data[] = " Name1=Value1;Name2 = Value 2; Name3 =\"Value 3\"; Name4 =\'Value 4\'; Name5=\"Confusing; isn\'t it?\"";
|
||||
|
||||
// Now try parsing char-by-char, to debug transitions across datachunk boundaries:
|
||||
cNameValueParser Parser2;
|
||||
for (int i = 0; i < sizeof(Data) - 1; i++)
|
||||
{
|
||||
Parser2.Parse(Data + i, 1);
|
||||
}
|
||||
Parser2.Finish();
|
||||
|
||||
// Parse as a single chunk of data:
|
||||
cNameValueParser Parser(Data, sizeof(Data) - 1);
|
||||
|
||||
// Use the debugger to inspect the Parser variable
|
||||
|
||||
// Check that the two parsers have the same content:
|
||||
for (cNameValueParser::const_iterator itr = Parser.begin(), end = Parser.end(); itr != end; ++itr)
|
||||
{
|
||||
ASSERT(Parser2[itr->first] == itr->second);
|
||||
} // for itr - Parser[]
|
||||
|
||||
// Try parsing in 2-char chunks:
|
||||
cNameValueParser Parser3;
|
||||
for (int i = 0; i < sizeof(Data) - 2; i += 2)
|
||||
{
|
||||
Parser3.Parse(Data + i, 2);
|
||||
}
|
||||
if ((sizeof(Data) % 2) == 0) // There are even number of chars, including the NUL, so the data has an odd length. Parse one more char
|
||||
{
|
||||
Parser3.Parse(Data + sizeof(Data) - 2, 1);
|
||||
}
|
||||
Parser3.Finish();
|
||||
|
||||
// Check that the third parser has the same content:
|
||||
for (cNameValueParser::const_iterator itr = Parser.begin(), end = Parser.end(); itr != end; ++itr)
|
||||
{
|
||||
ASSERT(Parser3[itr->first] == itr->second);
|
||||
} // for itr - Parser[]
|
||||
|
||||
printf("cNameValueParserTest done");
|
||||
}
|
||||
} g_Test;
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// cNameValueParser:
|
||||
|
||||
cNameValueParser::cNameValueParser(bool a_AllowsKeyOnly) :
|
||||
m_State(psKeySpace),
|
||||
m_AllowsKeyOnly(a_AllowsKeyOnly)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
cNameValueParser::cNameValueParser(const char * a_Data, int a_Size, bool a_AllowsKeyOnly) :
|
||||
m_State(psKeySpace),
|
||||
m_AllowsKeyOnly(a_AllowsKeyOnly)
|
||||
{
|
||||
Parse(a_Data, a_Size);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
void cNameValueParser::Parse(const char * a_Data, int a_Size)
|
||||
{
|
||||
ASSERT(m_State != psFinished); // Calling Parse() after Finish() is wrong!
|
||||
|
||||
if ((m_State == psInvalid) || (m_State == psFinished))
|
||||
{
|
||||
return;
|
||||
}
|
||||
int Last = 0;
|
||||
for (int i = 0; i < a_Size;)
|
||||
{
|
||||
switch (m_State)
|
||||
{
|
||||
case psKeySpace:
|
||||
{
|
||||
// Skip whitespace until a non-whitespace is found, then start the key:
|
||||
while ((i < a_Size) && (a_Data[i] <= ' '))
|
||||
{
|
||||
i++;
|
||||
}
|
||||
if ((i < a_Size) && (a_Data[i] > ' '))
|
||||
{
|
||||
m_State = psKey;
|
||||
Last = i;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case psKey:
|
||||
{
|
||||
// Read the key until whitespace or an equal sign:
|
||||
while (i < a_Size)
|
||||
{
|
||||
if (a_Data[i] == '=')
|
||||
{
|
||||
m_CurrentKey.append(a_Data + Last, i - Last);
|
||||
i++;
|
||||
Last = i;
|
||||
m_State = psEqual;
|
||||
break;
|
||||
}
|
||||
else if (a_Data[i] <= ' ')
|
||||
{
|
||||
m_CurrentKey.append(a_Data + Last, i - Last);
|
||||
i++;
|
||||
Last = i;
|
||||
m_State = psEqualSpace;
|
||||
break;
|
||||
}
|
||||
else if (a_Data[i] == ';')
|
||||
{
|
||||
if (!m_AllowsKeyOnly)
|
||||
{
|
||||
m_State = psInvalid;
|
||||
return;
|
||||
}
|
||||
m_CurrentKey.append(a_Data + Last, i - Last);
|
||||
i++;
|
||||
Last = i;
|
||||
(*this)[m_CurrentKey] = "";
|
||||
m_CurrentKey.clear();
|
||||
m_State = psKeySpace;
|
||||
break;
|
||||
}
|
||||
else if ((a_Data[i] == '\"') || (a_Data[i] == '\''))
|
||||
{
|
||||
m_State = psInvalid;
|
||||
return;
|
||||
}
|
||||
i++;
|
||||
} // while (i < a_Size)
|
||||
if (i == a_Size)
|
||||
{
|
||||
// Still the key, ran out of data to parse, store the part of the key parsed so far:
|
||||
m_CurrentKey.append(a_Data + Last, a_Size - Last);
|
||||
return;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case psEqualSpace:
|
||||
{
|
||||
// The space before the expected equal sign; the current key is already assigned
|
||||
while (i < a_Size)
|
||||
{
|
||||
if (a_Data[i] == '=')
|
||||
{
|
||||
m_State = psEqual;
|
||||
i++;
|
||||
Last = i;
|
||||
break;
|
||||
}
|
||||
else if (a_Data[i] == ';')
|
||||
{
|
||||
// Key-only
|
||||
if (!m_AllowsKeyOnly)
|
||||
{
|
||||
m_State = psInvalid;
|
||||
return;
|
||||
}
|
||||
i++;
|
||||
Last = i;
|
||||
(*this)[m_CurrentKey] = "";
|
||||
m_CurrentKey.clear();
|
||||
m_State = psKeySpace;
|
||||
break;
|
||||
}
|
||||
else if (a_Data[i] > ' ')
|
||||
{
|
||||
m_State = psInvalid;
|
||||
return;
|
||||
}
|
||||
i++;
|
||||
} // while (i < a_Size)
|
||||
break;
|
||||
} // case psEqualSpace
|
||||
|
||||
case psEqual:
|
||||
{
|
||||
// just parsed the equal-sign
|
||||
while (i < a_Size)
|
||||
{
|
||||
if (a_Data[i] == ';')
|
||||
{
|
||||
if (!m_AllowsKeyOnly)
|
||||
{
|
||||
m_State = psInvalid;
|
||||
return;
|
||||
}
|
||||
i++;
|
||||
Last = i;
|
||||
(*this)[m_CurrentKey] = "";
|
||||
m_CurrentKey.clear();
|
||||
m_State = psKeySpace;
|
||||
break;
|
||||
}
|
||||
else if (a_Data[i] == '\"')
|
||||
{
|
||||
i++;
|
||||
Last = i;
|
||||
m_State = psValueInDQuotes;
|
||||
break;
|
||||
}
|
||||
else if (a_Data[i] == '\'')
|
||||
{
|
||||
i++;
|
||||
Last = i;
|
||||
m_State = psValueInSQuotes;
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_CurrentValue.push_back(a_Data[i]);
|
||||
i++;
|
||||
Last = i;
|
||||
m_State = psValueRaw;
|
||||
break;
|
||||
}
|
||||
i++;
|
||||
} // while (i < a_Size)
|
||||
break;
|
||||
} // case psEqual
|
||||
|
||||
case psValueInDQuotes:
|
||||
{
|
||||
while (i < a_Size)
|
||||
{
|
||||
if (a_Data[i] == '\"')
|
||||
{
|
||||
m_CurrentValue.append(a_Data + Last, i - Last);
|
||||
(*this)[m_CurrentKey] = m_CurrentValue;
|
||||
m_CurrentKey.clear();
|
||||
m_CurrentValue.clear();
|
||||
m_State = psAfterValue;
|
||||
i++;
|
||||
Last = i;
|
||||
break;
|
||||
}
|
||||
i++;
|
||||
} // while (i < a_Size)
|
||||
if (i == a_Size)
|
||||
{
|
||||
m_CurrentValue.append(a_Data + Last, a_Size - Last);
|
||||
}
|
||||
break;
|
||||
} // case psValueInDQuotes
|
||||
|
||||
case psValueInSQuotes:
|
||||
{
|
||||
while (i < a_Size)
|
||||
{
|
||||
if (a_Data[i] == '\'')
|
||||
{
|
||||
m_CurrentValue.append(a_Data + Last, i - Last);
|
||||
(*this)[m_CurrentKey] = m_CurrentValue;
|
||||
m_CurrentKey.clear();
|
||||
m_CurrentValue.clear();
|
||||
m_State = psAfterValue;
|
||||
i++;
|
||||
Last = i;
|
||||
break;
|
||||
}
|
||||
i++;
|
||||
} // while (i < a_Size)
|
||||
if (i == a_Size)
|
||||
{
|
||||
m_CurrentValue.append(a_Data + Last, a_Size - Last);
|
||||
}
|
||||
break;
|
||||
} // case psValueInSQuotes
|
||||
|
||||
case psValueRaw:
|
||||
{
|
||||
while (i < a_Size)
|
||||
{
|
||||
if (a_Data[i] == ';')
|
||||
{
|
||||
m_CurrentValue.append(a_Data + Last, i - Last);
|
||||
(*this)[m_CurrentKey] = m_CurrentValue;
|
||||
m_CurrentKey.clear();
|
||||
m_CurrentValue.clear();
|
||||
m_State = psKeySpace;
|
||||
i++;
|
||||
Last = i;
|
||||
break;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
if (i == a_Size)
|
||||
{
|
||||
m_CurrentValue.append(a_Data + Last, a_Size - Last);
|
||||
}
|
||||
break;
|
||||
} // case psValueRaw
|
||||
|
||||
case psAfterValue:
|
||||
{
|
||||
// Between the closing DQuote or SQuote and the terminating semicolon
|
||||
while (i < a_Size)
|
||||
{
|
||||
if (a_Data[i] == ';')
|
||||
{
|
||||
m_State = psKeySpace;
|
||||
i++;
|
||||
Last = i;
|
||||
break;
|
||||
}
|
||||
else if (a_Data[i] < ' ')
|
||||
{
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
m_State = psInvalid;
|
||||
return;
|
||||
} // while (i < a_Size)
|
||||
break;
|
||||
}
|
||||
} // switch (m_State)
|
||||
} // for i - a_Data[]
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
bool cNameValueParser::Finish(void)
|
||||
{
|
||||
switch (m_State)
|
||||
{
|
||||
case psInvalid:
|
||||
{
|
||||
return false;
|
||||
}
|
||||
case psFinished:
|
||||
{
|
||||
return true;
|
||||
}
|
||||
case psKey:
|
||||
case psEqualSpace:
|
||||
case psEqual:
|
||||
{
|
||||
if ((m_AllowsKeyOnly) && !m_CurrentKey.empty())
|
||||
{
|
||||
(*this)[m_CurrentKey] = "";
|
||||
m_State = psFinished;
|
||||
return true;
|
||||
}
|
||||
m_State = psInvalid;
|
||||
return false;
|
||||
}
|
||||
case psValueRaw:
|
||||
{
|
||||
(*this)[m_CurrentKey] = m_CurrentValue;
|
||||
m_State = psFinished;
|
||||
return true;
|
||||
}
|
||||
case psValueInDQuotes:
|
||||
case psValueInSQuotes:
|
||||
{
|
||||
// Missing the terminating quotes, this is an error
|
||||
m_State = psInvalid;
|
||||
return false;
|
||||
}
|
||||
case psKeySpace:
|
||||
case psAfterValue:
|
||||
{
|
||||
m_State = psFinished;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
ASSERT(!"Unhandled parser state!");
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
70
source/HTTPServer/NameValueParser.h
Normal file
70
source/HTTPServer/NameValueParser.h
Normal file
@ -0,0 +1,70 @@
|
||||
|
||||
// NameValueParser.h
|
||||
|
||||
// Declares the cNameValueParser class that parses strings in the "name=value;name2=value2" format into a stringmap
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
#pragma once
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
class cNameValueParser :
|
||||
public std::map<AString, AString>
|
||||
{
|
||||
public:
|
||||
/// Creates an empty parser
|
||||
cNameValueParser(bool a_AllowsKeyOnly = true);
|
||||
|
||||
/// Creates an empty parser, then parses the data given. Doesn't call Finish(), so more data can be parsed later
|
||||
cNameValueParser(const char * a_Data, int a_Size, bool a_AllowsKeyOnly = true);
|
||||
|
||||
/// Parses the data given
|
||||
void Parse(const char * a_Data, int a_Size);
|
||||
|
||||
/// Notifies the parser that no more data will be coming. Returns true if the parser state is valid
|
||||
bool Finish(void);
|
||||
|
||||
/// Returns true if the data parsed so far was valid
|
||||
bool IsValid(void) const { return (m_State != psInvalid); }
|
||||
|
||||
/// Returns true if the parser expects no more data
|
||||
bool IsFinished(void) const { return ((m_State == psInvalid) || (m_State == psFinished)); }
|
||||
|
||||
protected:
|
||||
enum eState
|
||||
{
|
||||
psKeySpace, ///< Parsing the space in front of the next key
|
||||
psKey, ///< Currently adding more chars to the key in m_CurrentKey
|
||||
psEqualSpace, ///< Space after m_CurrentKey
|
||||
psEqual, ///< Just parsed the = sign after a name
|
||||
psValueInSQuotes, ///< Just parsed a Single-quote sign after the Equal sign
|
||||
psValueInDQuotes, ///< Just parsed a Double-quote sign after the Equal sign
|
||||
psValueRaw, ///< Just parsed a raw value without a quote
|
||||
psAfterValue, ///< Just finished parsing the value, waiting for semicolon or data end
|
||||
psInvalid, ///< The parser has encountered an invalid input; further parsing is skipped
|
||||
psFinished, ///< The parser has already been instructed to finish and doesn't expect any more data
|
||||
} ;
|
||||
|
||||
/// The current state of the parser
|
||||
eState m_State;
|
||||
|
||||
/// If true, the parser will accept keys without an equal sign and the value
|
||||
bool m_AllowsKeyOnly;
|
||||
|
||||
/// Buffer for the current Key
|
||||
AString m_CurrentKey;
|
||||
|
||||
/// Buffer for the current Value;
|
||||
AString m_CurrentValue;
|
||||
|
||||
|
||||
} ;
|
||||
|
||||
|
||||
|
||||
|
@ -224,7 +224,10 @@ void cListenThread::Execute(void)
|
||||
if (itr->IsValid() && FD_ISSET(itr->GetSocket(), &fdRead))
|
||||
{
|
||||
cSocket Client = (m_Family == cSocket::IPv4) ? itr->AcceptIPv4() : itr->AcceptIPv6();
|
||||
m_Callback.OnConnectionAccepted(Client);
|
||||
if (Client.IsValid())
|
||||
{
|
||||
m_Callback.OnConnectionAccepted(Client);
|
||||
}
|
||||
}
|
||||
} // for itr - m_Sockets[]
|
||||
} // while (!m_ShouldTerminate)
|
||||
|
@ -134,17 +134,9 @@ void cRoot::Start(void)
|
||||
}
|
||||
IniFile.WriteFile();
|
||||
|
||||
cIniFile WebIniFile("webadmin.ini");
|
||||
if (!WebIniFile.ReadFile())
|
||||
{
|
||||
LOGWARNING("webadmin.ini inaccessible, wabadmin is disabled");
|
||||
}
|
||||
|
||||
if (WebIniFile.GetValueB("WebAdmin", "Enabled", false))
|
||||
{
|
||||
LOGD("Creating WebAdmin...");
|
||||
m_WebAdmin = new cWebAdmin(8080);
|
||||
}
|
||||
LOG("Initialising WebAdmin...");
|
||||
m_WebAdmin = new cWebAdmin();
|
||||
m_WebAdmin->Init();
|
||||
|
||||
LOGD("Loading settings...");
|
||||
m_GroupManager = new cGroupManager();
|
||||
@ -173,6 +165,9 @@ void cRoot::Start(void)
|
||||
|
||||
LOGD("Finalising startup...");
|
||||
m_Server->Start();
|
||||
|
||||
LOG("Starting WebAdmin...");
|
||||
m_WebAdmin->Start();
|
||||
|
||||
#if !defined(ANDROID_NDK)
|
||||
LOGD("Starting InputThread...");
|
||||
|
@ -2,6 +2,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "Authenticator.h"
|
||||
#include "HTTPServer/HTTPServer.h"
|
||||
|
||||
|
||||
|
||||
@ -141,6 +142,7 @@ private:
|
||||
cWebAdmin * m_WebAdmin;
|
||||
cPluginManager * m_PluginManager;
|
||||
cAuthenticator m_Authenticator;
|
||||
cHTTPServer m_HTTPServer;
|
||||
|
||||
cMCLogger * m_Log;
|
||||
|
||||
|
@ -196,6 +196,23 @@ AString & StrToUpper(AString & s)
|
||||
|
||||
|
||||
|
||||
AString & StrToLower(AString & s)
|
||||
{
|
||||
AString::iterator i = s.begin();
|
||||
AString::iterator end = s.end();
|
||||
|
||||
while (i != end)
|
||||
{
|
||||
*i = (char)tolower(*i);
|
||||
++i;
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
int NoCaseCompare(const AString & s1, const AString & s2)
|
||||
{
|
||||
#ifdef _MSC_VER
|
||||
@ -658,3 +675,141 @@ AString StripColorCodes(const AString & a_Message)
|
||||
|
||||
|
||||
|
||||
|
||||
AString URLDecode(const AString & a_String)
|
||||
{
|
||||
AString res;
|
||||
size_t len = a_String.length();
|
||||
res.reserve(len);
|
||||
for (size_t i = 0; i < len; i++)
|
||||
{
|
||||
char ch = a_String[i];
|
||||
if ((ch != '%') || (i > len - 3))
|
||||
{
|
||||
res.push_back(ch);
|
||||
continue;
|
||||
}
|
||||
// Decode the hex value:
|
||||
char hi = a_String[i + 1], lo = a_String[i + 2];
|
||||
if ((hi >= '0') && (hi <= '9'))
|
||||
{
|
||||
hi = hi - '0';
|
||||
}
|
||||
else if ((hi >= 'a') && (hi <= 'f'))
|
||||
{
|
||||
hi = hi - 'a' + 10;
|
||||
}
|
||||
else if ((hi >= 'A') && (hi <= 'F'))
|
||||
{
|
||||
hi = hi - 'F' + 10;
|
||||
}
|
||||
else
|
||||
{
|
||||
res.push_back(ch);
|
||||
continue;
|
||||
}
|
||||
if ((lo >= '0') && (lo <= '9'))
|
||||
{
|
||||
lo = lo - '0';
|
||||
}
|
||||
else if ((lo >= 'a') && (lo <= 'f'))
|
||||
{
|
||||
lo = lo - 'a' + 10;
|
||||
}
|
||||
else if ((lo >= 'A') && (lo <= 'F'))
|
||||
{
|
||||
lo = lo - 'A' + 10;
|
||||
}
|
||||
else
|
||||
{
|
||||
res.push_back(ch);
|
||||
continue;
|
||||
}
|
||||
res.push_back((hi << 4) | lo);
|
||||
i += 2;
|
||||
} // for i - a_String[]
|
||||
return res;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
AString ReplaceAllCharOccurrences(const AString & a_String, char a_From, char a_To)
|
||||
{
|
||||
AString res(a_String);
|
||||
std::replace(res.begin(), res.end(), a_From, a_To);
|
||||
return res;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/// Converts one Hex character in a Base64 encoding into the data value
|
||||
static inline int UnBase64(char c)
|
||||
{
|
||||
if (c >='A' && c <= 'Z')
|
||||
{
|
||||
return c - 'A';
|
||||
}
|
||||
if (c >='a' && c <= 'z')
|
||||
{
|
||||
return c - 'a' + 26;
|
||||
}
|
||||
if (c >= '0' && c <= '9')
|
||||
{
|
||||
return c - '0' + 52;
|
||||
}
|
||||
if (c == '+')
|
||||
{
|
||||
return 62;
|
||||
}
|
||||
if (c == '/')
|
||||
{
|
||||
return 63;
|
||||
}
|
||||
if (c == '=')
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
return -2;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
AString Base64Decode(const AString & a_Base64String)
|
||||
{
|
||||
AString res;
|
||||
size_t i, len = a_Base64String.size();
|
||||
int o, c;
|
||||
res.resize((len * 4) / 3 + 5, 0); // Approximate the upper bound on the result length
|
||||
for (o = 0, i = 0; i < len; i++)
|
||||
{
|
||||
c = UnBase64(a_Base64String[i]);
|
||||
if (c >= 0)
|
||||
{
|
||||
switch (o & 7)
|
||||
{
|
||||
case 0: res[o >> 3] |= (c << 2); break;
|
||||
case 6: res[o >> 3] |= (c >> 4); res[(o >> 3) + 1] |= (c << 4); break;
|
||||
case 4: res[o >> 3] |= (c >> 2); res[(o >> 3) + 1] |= (c << 6); break;
|
||||
case 2: res[o >> 3] |= c; break;
|
||||
}
|
||||
o += 6;
|
||||
}
|
||||
if (c == -1)
|
||||
{
|
||||
// Error while decoding, invalid input. Return as much as we've decoded:
|
||||
res.resize(o >> 3);
|
||||
return res;
|
||||
}
|
||||
}
|
||||
res.resize(o >> 3);
|
||||
return res;}
|
||||
|
||||
|
||||
|
||||
|
||||
|
@ -45,6 +45,9 @@ extern AString TrimString(const AString & str); // tolua_export
|
||||
/// In-place string conversion to uppercase; returns the same string
|
||||
extern AString & StrToUpper(AString & s);
|
||||
|
||||
/// In-place string conversion to lowercase; returns the same string
|
||||
extern AString & StrToLower(AString & s);
|
||||
|
||||
/// Case-insensitive string comparison; returns 0 if the strings are the same
|
||||
extern int NoCaseCompare(const AString & s1, const AString & s2); // tolua_export
|
||||
|
||||
@ -72,6 +75,15 @@ extern AString EscapeString(const AString & a_Message); // tolua_export
|
||||
/// Removes all control codes used by MC for colors and styles
|
||||
extern AString StripColorCodes(const AString & a_Message); // tolua_export
|
||||
|
||||
/// URL-Decodes the given string, replacing all "%HH" into the correct characters. Invalid % sequences are left intact
|
||||
extern AString URLDecode(const AString & a_String); // Cannot export to Lua automatically - would generated an extra return value
|
||||
|
||||
/// Replaces all occurrences of char a_From inside a_String with char a_To.
|
||||
extern AString ReplaceAllCharOccurrences(const AString & a_String, char a_From, char a_To); // Needn't export to Lua, since Lua doesn't have chars anyway
|
||||
|
||||
/// Decodes a Base64-encoded string into the raw data
|
||||
extern AString Base64Decode(const AString & a_Base64String);
|
||||
|
||||
// If you have any other string helper functions, declare them here
|
||||
|
||||
|
||||
|
@ -14,7 +14,8 @@
|
||||
#include "Server.h"
|
||||
#include "Root.h"
|
||||
|
||||
#include "../iniFile/iniFile.h"
|
||||
#include "HTTPServer/HTTPMessage.h"
|
||||
#include "HTTPServer/HTTPConnection.h"
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <psapi.h>
|
||||
@ -49,37 +50,11 @@ public:
|
||||
|
||||
|
||||
|
||||
cWebAdmin * WebAdmin = NULL;
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
cWebAdmin::cWebAdmin( int a_Port /* = 8080 */ ) :
|
||||
m_Port(a_Port),
|
||||
m_bConnected(false),
|
||||
m_TemplateScript("<webadmin_template>")
|
||||
cWebAdmin::cWebAdmin(void) :
|
||||
m_IsInitialized(false),
|
||||
m_TemplateScript("<webadmin_template>"),
|
||||
m_IniFile("webadmin.ini")
|
||||
{
|
||||
WebAdmin = this;
|
||||
m_Event = new cEvent();
|
||||
Init( m_Port );
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
cWebAdmin::~cWebAdmin()
|
||||
{
|
||||
|
||||
WebAdmin = 0;
|
||||
m_WebServer->Stop();
|
||||
|
||||
delete m_WebServer;
|
||||
delete m_IniFile;
|
||||
|
||||
m_Event->Wait();
|
||||
delete m_Event;
|
||||
}
|
||||
|
||||
|
||||
@ -105,189 +80,36 @@ void cWebAdmin::RemovePlugin( cWebPlugin * a_Plugin )
|
||||
|
||||
|
||||
|
||||
void cWebAdmin::Request_Handler(webserver::http_request* r)
|
||||
bool cWebAdmin::Init(void)
|
||||
{
|
||||
if( WebAdmin == 0 ) return;
|
||||
LOG("Path: %s", r->path_.c_str() );
|
||||
|
||||
if (r->path_ == "/")
|
||||
if (!m_IniFile.ReadFile())
|
||||
{
|
||||
r->answer_ += "<h1>MCServer WebAdmin</h1>";
|
||||
r->answer_ += "<center>";
|
||||
r->answer_ += "<form method='get' action='webadmin/'>";
|
||||
r->answer_ += "<input type='submit' value='Log in'>";
|
||||
r->answer_ += "</form>";
|
||||
r->answer_ += "</center>";
|
||||
return;
|
||||
}
|
||||
|
||||
if (r->path_.empty() || r->path_[0] != '/')
|
||||
{
|
||||
r->answer_ += "<h1>Bad request</h1>";
|
||||
r->answer_ += "<p>";
|
||||
r->answer_ = r->path_; // TODO: Shouldn't we sanitize this? Possible security issue.
|
||||
r->answer_ += "</p>";
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
AStringVector Split = StringSplit(r->path_.substr(1), "/");
|
||||
|
||||
if (Split.empty() || (Split[0] != "webadmin" && Split[0] != "~webadmin"))
|
||||
{
|
||||
r->answer_ += "<h1>Bad request</h1>";
|
||||
return;
|
||||
}
|
||||
AString PortsIPv4 = m_IniFile.GetValue("WebAdmin", "Port", "8080");
|
||||
AString PortsIPv6 = m_IniFile.GetValue("WebAdmin", "PortsIPv6", "");
|
||||
|
||||
if (!r->authentication_given_)
|
||||
if (!m_HTTPServer.Initialize(PortsIPv4, PortsIPv6))
|
||||
{
|
||||
r->answer_ += "no auth";
|
||||
r->auth_realm_ = "MCServer WebAdmin";
|
||||
}
|
||||
|
||||
bool bDontShowTemplate = false;
|
||||
if (Split[0] == "~webadmin")
|
||||
{
|
||||
bDontShowTemplate = true;
|
||||
}
|
||||
|
||||
AString UserPassword = WebAdmin->m_IniFile->GetValue( "User:"+r->username_, "Password", "");
|
||||
if ((UserPassword != "") && (r->password_ == UserPassword))
|
||||
{
|
||||
AString Template;
|
||||
|
||||
HTTPTemplateRequest TemplateRequest;
|
||||
TemplateRequest.Request.Username = r->username_;
|
||||
TemplateRequest.Request.Method = r->method_;
|
||||
TemplateRequest.Request.Params = r->params_;
|
||||
TemplateRequest.Request.PostParams = r->params_post_;
|
||||
TemplateRequest.Request.Path = r->path_.substr(1);
|
||||
|
||||
for( unsigned int i = 0; i < r->multipart_formdata_.size(); ++i )
|
||||
{
|
||||
webserver::formdata& fd = r->multipart_formdata_[i];
|
||||
|
||||
HTTPFormData HTTPfd;//( fd.value_ );
|
||||
HTTPfd.Value = fd.value_;
|
||||
HTTPfd.Type = fd.content_type_;
|
||||
HTTPfd.Name = fd.name_;
|
||||
LOGINFO("Form data name: %s", fd.name_.c_str() );
|
||||
TemplateRequest.Request.FormData[ fd.name_ ] = HTTPfd;
|
||||
}
|
||||
|
||||
// Try to get the template from the Lua template script
|
||||
bool bLuaTemplateSuccessful = false;
|
||||
if (!bDontShowTemplate)
|
||||
{
|
||||
bLuaTemplateSuccessful = WebAdmin->m_TemplateScript.Call("ShowPage", WebAdmin, &TemplateRequest, cLuaState::Return, Template);
|
||||
}
|
||||
|
||||
if (!bLuaTemplateSuccessful)
|
||||
{
|
||||
AString BaseURL = WebAdmin->GetBaseURL(Split);
|
||||
AString Menu;
|
||||
Template = bDontShowTemplate ? "{CONTENT}" : WebAdmin->GetTemplate();
|
||||
AString FoundPlugin;
|
||||
|
||||
for (PluginList::iterator itr = WebAdmin->m_Plugins.begin(); itr != WebAdmin->m_Plugins.end(); ++itr)
|
||||
{
|
||||
cWebPlugin* WebPlugin = *itr;
|
||||
std::list< std::pair<AString, AString> > NameList = WebPlugin->GetTabNames();
|
||||
for( std::list< std::pair<AString, AString> >::iterator Names = NameList.begin(); Names != NameList.end(); ++Names )
|
||||
{
|
||||
Menu += "<li><a href='" + BaseURL + WebPlugin->GetWebTitle().c_str() + "/" + (*Names).second + "'>" + (*Names).first + "</a></li>";
|
||||
}
|
||||
}
|
||||
|
||||
sWebAdminPage Page = WebAdmin->GetPage(TemplateRequest.Request);
|
||||
AString Content = Page.Content;
|
||||
FoundPlugin = Page.PluginName;
|
||||
if (!Page.TabName.empty())
|
||||
FoundPlugin += " - " + Page.TabName;
|
||||
|
||||
if( FoundPlugin.empty() ) // Default page
|
||||
{
|
||||
Content.clear();
|
||||
FoundPlugin = "Current Game";
|
||||
Content += "<h4>Server Name:</h4>";
|
||||
Content += "<p>" + AString( cRoot::Get()->GetServer()->GetServerID() ) + "</p>";
|
||||
|
||||
Content += "<h4>Plugins:</h4><ul>";
|
||||
cPluginManager* PM = cRoot::Get()->GetPluginManager();
|
||||
if( PM )
|
||||
{
|
||||
const cPluginManager::PluginMap & List = PM->GetAllPlugins();
|
||||
for( cPluginManager::PluginMap::const_iterator itr = List.begin(); itr != List.end(); ++itr )
|
||||
{
|
||||
if( itr->second == NULL ) continue;
|
||||
AString VersionNum;
|
||||
AppendPrintf(Content, "<li>%s V.%i</li>", itr->second->GetName().c_str(), itr->second->GetVersion());
|
||||
}
|
||||
}
|
||||
Content += "</ul>";
|
||||
Content += "<h4>Players:</h4><ul>";
|
||||
|
||||
cPlayerAccum PlayerAccum;
|
||||
cWorld * World = cRoot::Get()->GetDefaultWorld(); // TODO - Create a list of worlds and players
|
||||
if( World != NULL )
|
||||
{
|
||||
World->ForEachPlayer(PlayerAccum);
|
||||
Content.append(PlayerAccum.m_Contents);
|
||||
}
|
||||
Content += "</ul><br>";
|
||||
}
|
||||
|
||||
|
||||
|
||||
if (!bDontShowTemplate && (Split.size() > 1))
|
||||
{
|
||||
Content += "\n<p><a href='" + BaseURL + "'>Go back</a></p>";
|
||||
}
|
||||
|
||||
int MemUsageKiB = GetMemoryUsage();
|
||||
if (MemUsageKiB > 0)
|
||||
{
|
||||
ReplaceString(Template, "{MEM}", Printf("%.02f", (double)MemUsageKiB / 1024));
|
||||
ReplaceString(Template, "{MEMKIB}", Printf("%d", MemUsageKiB));
|
||||
}
|
||||
else
|
||||
{
|
||||
ReplaceString(Template, "{MEM}", "unknown");
|
||||
ReplaceString(Template, "{MEMKIB}", "unknown");
|
||||
}
|
||||
ReplaceString(Template, "{USERNAME}", r->username_);
|
||||
ReplaceString(Template, "{MENU}", Menu);
|
||||
ReplaceString(Template, "{PLUGIN_NAME}", FoundPlugin);
|
||||
ReplaceString(Template, "{CONTENT}", Content);
|
||||
ReplaceString(Template, "{TITLE}", "MCServer");
|
||||
|
||||
AString NumChunks;
|
||||
Printf(NumChunks, "%d", cRoot::Get()->GetTotalChunkCount());
|
||||
ReplaceString(Template, "{NUMCHUNKS}", NumChunks);
|
||||
}
|
||||
|
||||
r->answer_ = Template;
|
||||
}
|
||||
else
|
||||
{
|
||||
r->answer_ += "Wrong username/password";
|
||||
r->auth_realm_ = "MCServer WebAdmin";
|
||||
return false;
|
||||
}
|
||||
m_IsInitialized = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
bool cWebAdmin::Init(int a_Port)
|
||||
bool cWebAdmin::Start(void)
|
||||
{
|
||||
m_Port = a_Port;
|
||||
|
||||
m_IniFile = new cIniFile("webadmin.ini");
|
||||
if (m_IniFile->ReadFile())
|
||||
if (!m_IsInitialized)
|
||||
{
|
||||
m_Port = m_IniFile->GetValueI("WebAdmin", "Port", 8080);
|
||||
// Not initialized
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
// Initialize the WebAdmin template script and load the file
|
||||
m_TemplateScript.Create();
|
||||
if (!m_TemplateScript.LoadFile(FILE_IO_PREFIX "webadmin/template.lua"))
|
||||
@ -296,46 +118,7 @@ bool cWebAdmin::Init(int a_Port)
|
||||
m_TemplateScript.Close();
|
||||
}
|
||||
|
||||
|
||||
LOGINFO("Starting WebAdmin on port %i", m_Port);
|
||||
|
||||
#ifdef _WIN32
|
||||
HANDLE hThread = CreateThread(
|
||||
NULL, // default security
|
||||
0, // default stack size
|
||||
ListenThread, // name of the thread function
|
||||
this, // thread parameters
|
||||
0, // default startup flags
|
||||
NULL);
|
||||
CloseHandle( hThread ); // Just close the handle immediately
|
||||
#else
|
||||
pthread_t LstnThread;
|
||||
pthread_create( &LstnThread, 0, ListenThread, this);
|
||||
#endif
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
#ifdef _WIN32
|
||||
DWORD WINAPI cWebAdmin::ListenThread(LPVOID lpParam)
|
||||
#else
|
||||
void *cWebAdmin::ListenThread( void *lpParam )
|
||||
#endif
|
||||
{
|
||||
cWebAdmin* self = (cWebAdmin*)lpParam;
|
||||
|
||||
self->m_WebServer = new webserver(self->m_Port, Request_Handler );
|
||||
if (!self->m_WebServer->Begin())
|
||||
{
|
||||
LOGWARN("WebServer failed to start! WebAdmin is disabled");
|
||||
}
|
||||
|
||||
self->m_Event->Set();
|
||||
return 0;
|
||||
return m_HTTPServer.Start(*this);
|
||||
}
|
||||
|
||||
|
||||
@ -364,7 +147,156 @@ AString cWebAdmin::GetTemplate()
|
||||
|
||||
|
||||
|
||||
sWebAdminPage cWebAdmin::GetPage(const HTTPRequest& a_Request)
|
||||
void cWebAdmin::HandleWebadminRequest(cHTTPConnection & a_Connection, cHTTPRequest & a_Request)
|
||||
{
|
||||
if (!a_Request.HasAuth())
|
||||
{
|
||||
a_Connection.SendNeedAuth("MCServer WebAdmin");
|
||||
return;
|
||||
}
|
||||
|
||||
// Check auth:
|
||||
AString UserPassword = m_IniFile.GetValue("User:" + a_Request.GetAuthUsername(), "Password", "");
|
||||
if ((UserPassword == "") || (a_Request.GetAuthPassword() != UserPassword))
|
||||
{
|
||||
a_Connection.SendNeedAuth("MCServer WebAdmin - bad username or password");
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if the contents should be wrapped in the template:
|
||||
AString URL = a_Request.GetBareURL();
|
||||
ASSERT(URL.length() > 0);
|
||||
bool ShouldWrapInTemplate = ((URL.length() > 1) && (URL[1] != '~'));
|
||||
|
||||
// Retrieve the request data:
|
||||
cWebadminRequestData * Data = (cWebadminRequestData *)(a_Request.GetUserData());
|
||||
if (Data == NULL)
|
||||
{
|
||||
a_Connection.SendStatusAndReason(500, "Bad UserData");
|
||||
return;
|
||||
}
|
||||
|
||||
// Wrap it all up for the Lua call:
|
||||
AString Template;
|
||||
HTTPTemplateRequest TemplateRequest;
|
||||
TemplateRequest.Request.Username = a_Request.GetAuthUsername();
|
||||
TemplateRequest.Request.Method = a_Request.GetMethod();
|
||||
TemplateRequest.Request.Path = URL.substr(1);
|
||||
|
||||
if (Data->m_Form.Finish())
|
||||
{
|
||||
for (cHTTPFormParser::const_iterator itr = Data->m_Form.begin(), end = Data->m_Form.end(); itr != end; ++itr)
|
||||
{
|
||||
HTTPFormData HTTPfd;
|
||||
HTTPfd.Value = itr->second;
|
||||
HTTPfd.Type = "";
|
||||
HTTPfd.Name = itr->first;
|
||||
TemplateRequest.Request.FormData[itr->first] = HTTPfd;
|
||||
TemplateRequest.Request.PostParams[itr->first] = itr->second;
|
||||
TemplateRequest.Request.Params[itr->first] = itr->second;
|
||||
} // for itr - Data->m_Form[]
|
||||
}
|
||||
|
||||
// Try to get the template from the Lua template script
|
||||
if (ShouldWrapInTemplate)
|
||||
{
|
||||
if (m_TemplateScript.Call("ShowPage", this, &TemplateRequest, cLuaState::Return, Template))
|
||||
{
|
||||
cHTTPResponse Resp;
|
||||
Resp.SetContentType("text/html");
|
||||
a_Connection.Send(Resp);
|
||||
a_Connection.Send(Template.c_str(), Template.length());
|
||||
return;
|
||||
}
|
||||
a_Connection.SendStatusAndReason(500, "m_TemplateScript failed");
|
||||
return;
|
||||
}
|
||||
|
||||
AString BaseURL = GetBaseURL(URL);
|
||||
AString Menu;
|
||||
Template = "{CONTENT}";
|
||||
AString FoundPlugin;
|
||||
|
||||
for (PluginList::iterator itr = m_Plugins.begin(); itr != m_Plugins.end(); ++itr)
|
||||
{
|
||||
cWebPlugin * WebPlugin = *itr;
|
||||
std::list< std::pair<AString, AString> > NameList = WebPlugin->GetTabNames();
|
||||
for (std::list< std::pair<AString, AString> >::iterator Names = NameList.begin(); Names != NameList.end(); ++Names)
|
||||
{
|
||||
Menu += "<li><a href='" + BaseURL + WebPlugin->GetWebTitle().c_str() + "/" + (*Names).second + "'>" + (*Names).first + "</a></li>";
|
||||
}
|
||||
}
|
||||
|
||||
sWebAdminPage Page = GetPage(TemplateRequest.Request);
|
||||
AString Content = Page.Content;
|
||||
FoundPlugin = Page.PluginName;
|
||||
if (!Page.TabName.empty())
|
||||
{
|
||||
FoundPlugin += " - " + Page.TabName;
|
||||
}
|
||||
|
||||
if (FoundPlugin.empty()) // Default page
|
||||
{
|
||||
Content = GetDefaultPage();
|
||||
}
|
||||
|
||||
if (ShouldWrapInTemplate && (URL.size() > 1))
|
||||
{
|
||||
Content += "\n<p><a href='" + BaseURL + "'>Go back</a></p>";
|
||||
}
|
||||
|
||||
int MemUsageKiB = GetMemoryUsage();
|
||||
if (MemUsageKiB > 0)
|
||||
{
|
||||
ReplaceString(Template, "{MEM}", Printf("%.02f", (double)MemUsageKiB / 1024));
|
||||
ReplaceString(Template, "{MEMKIB}", Printf("%d", MemUsageKiB));
|
||||
}
|
||||
else
|
||||
{
|
||||
ReplaceString(Template, "{MEM}", "unknown");
|
||||
ReplaceString(Template, "{MEMKIB}", "unknown");
|
||||
}
|
||||
ReplaceString(Template, "{USERNAME}", a_Request.GetAuthUsername());
|
||||
ReplaceString(Template, "{MENU}", Menu);
|
||||
ReplaceString(Template, "{PLUGIN_NAME}", FoundPlugin);
|
||||
ReplaceString(Template, "{CONTENT}", Content);
|
||||
ReplaceString(Template, "{TITLE}", "MCServer");
|
||||
|
||||
AString NumChunks;
|
||||
Printf(NumChunks, "%d", cRoot::Get()->GetTotalChunkCount());
|
||||
ReplaceString(Template, "{NUMCHUNKS}", NumChunks);
|
||||
|
||||
cHTTPResponse Resp;
|
||||
Resp.SetContentType("text/html");
|
||||
a_Connection.Send(Resp);
|
||||
a_Connection.Send(Template.c_str(), Template.length());
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
void cWebAdmin::HandleRootRequest(cHTTPConnection & a_Connection, cHTTPRequest & a_Request)
|
||||
{
|
||||
static const char LoginForm[] = \
|
||||
"<h1>MCServer WebAdmin</h1>" \
|
||||
"<center>" \
|
||||
"<form method='get' action='webadmin/'>" \
|
||||
"<input type='submit' value='Log in'>" \
|
||||
"</form>" \
|
||||
"</center>";
|
||||
cHTTPResponse Resp;
|
||||
Resp.SetContentType("text/html");
|
||||
a_Connection.Send(Resp);
|
||||
a_Connection.Send(LoginForm, sizeof(LoginForm) - 1);
|
||||
a_Connection.FinishResponse();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
sWebAdminPage cWebAdmin::GetPage(const HTTPRequest & a_Request)
|
||||
{
|
||||
sWebAdminPage Page;
|
||||
AStringVector Split = StringSplit(a_Request.Path, "/");
|
||||
@ -373,7 +305,7 @@ sWebAdminPage cWebAdmin::GetPage(const HTTPRequest& a_Request)
|
||||
AString FoundPlugin;
|
||||
if (Split.size() > 1)
|
||||
{
|
||||
for (PluginList::iterator itr = WebAdmin->m_Plugins.begin(); itr != WebAdmin->m_Plugins.end(); ++itr)
|
||||
for (PluginList::iterator itr = m_Plugins.begin(); itr != m_Plugins.end(); ++itr)
|
||||
{
|
||||
if ((*itr)->GetWebTitle() == Split[1])
|
||||
{
|
||||
@ -396,6 +328,41 @@ sWebAdminPage cWebAdmin::GetPage(const HTTPRequest& a_Request)
|
||||
|
||||
|
||||
|
||||
AString cWebAdmin::GetDefaultPage(void)
|
||||
{
|
||||
AString Content;
|
||||
Content += "<h4>Server Name:</h4>";
|
||||
Content += "<p>" + AString( cRoot::Get()->GetServer()->GetServerID() ) + "</p>";
|
||||
|
||||
Content += "<h4>Plugins:</h4><ul>";
|
||||
cPluginManager * PM = cPluginManager::Get();
|
||||
const cPluginManager::PluginMap & List = PM->GetAllPlugins();
|
||||
for (cPluginManager::PluginMap::const_iterator itr = List.begin(); itr != List.end(); ++itr)
|
||||
{
|
||||
if (itr->second == NULL)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
AString VersionNum;
|
||||
AppendPrintf(Content, "<li>%s V.%i</li>", itr->second->GetName().c_str(), itr->second->GetVersion());
|
||||
}
|
||||
Content += "</ul>";
|
||||
Content += "<h4>Players:</h4><ul>";
|
||||
|
||||
cPlayerAccum PlayerAccum;
|
||||
cWorld * World = cRoot::Get()->GetDefaultWorld(); // TODO - Create a list of worlds and players
|
||||
if( World != NULL )
|
||||
{
|
||||
World->ForEachPlayer(PlayerAccum);
|
||||
Content.append(PlayerAccum.m_Contents);
|
||||
}
|
||||
Content += "</ul><br>";
|
||||
return Content;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
AString cWebAdmin::GetBaseURL( const AString& a_URL )
|
||||
{
|
||||
return GetBaseURL(StringSplit(a_URL, "/"));
|
||||
@ -474,3 +441,81 @@ int cWebAdmin::GetMemoryUsage(void)
|
||||
|
||||
|
||||
|
||||
|
||||
void cWebAdmin::OnRequestBegun(cHTTPConnection & a_Connection, cHTTPRequest & a_Request)
|
||||
{
|
||||
const AString & URL = a_Request.GetURL();
|
||||
if (
|
||||
(strncmp(URL.c_str(), "/webadmin", 9) == 0) ||
|
||||
(strncmp(URL.c_str(), "/~webadmin", 10) == 0)
|
||||
)
|
||||
{
|
||||
a_Request.SetUserData(new cWebadminRequestData(a_Request));
|
||||
return;
|
||||
}
|
||||
if (URL == "/")
|
||||
{
|
||||
// The root needs no body handler and is fully handled in the OnRequestFinished() call
|
||||
return;
|
||||
}
|
||||
// TODO: Handle other requests
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
void cWebAdmin::OnRequestBody(cHTTPConnection & a_Connection, cHTTPRequest & a_Request, const char * a_Data, int a_Size)
|
||||
{
|
||||
cRequestData * Data = (cRequestData *)(a_Request.GetUserData());
|
||||
if (Data == NULL)
|
||||
{
|
||||
return;
|
||||
}
|
||||
Data->OnBody(a_Data, a_Size);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
void cWebAdmin::OnRequestFinished(cHTTPConnection & a_Connection, cHTTPRequest & a_Request)
|
||||
{
|
||||
const AString & URL = a_Request.GetURL();
|
||||
if (
|
||||
(strncmp(URL.c_str(), "/webadmin", 9) == 0) ||
|
||||
(strncmp(URL.c_str(), "/~webadmin", 10) == 0)
|
||||
)
|
||||
{
|
||||
HandleWebadminRequest(a_Connection, a_Request);
|
||||
}
|
||||
else if (URL == "/")
|
||||
{
|
||||
// The root needs no body handler and is fully handled in the OnRequestFinished() call
|
||||
HandleRootRequest(a_Connection, a_Request);
|
||||
}
|
||||
else
|
||||
{
|
||||
// TODO: Handle other requests
|
||||
}
|
||||
|
||||
// Delete any request data assigned to the request:
|
||||
cRequestData * Data = (cRequestData *)(a_Request.GetUserData());
|
||||
delete Data;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// cWebAdmin::cWebadminRequestData
|
||||
|
||||
void cWebAdmin::cWebadminRequestData::OnBody(const char * a_Data, int a_Size)
|
||||
{
|
||||
m_Form.Parse(a_Data, a_Size);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
@ -1,8 +1,26 @@
|
||||
|
||||
// WebAdmin.h
|
||||
|
||||
// Declares the cWebAdmin class representing the admin interface over http protocol, and related services (API)
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "../WebServer/WebServer.h"
|
||||
#include "OSSupport/Socket.h"
|
||||
#include "LuaState.h"
|
||||
#include "../iniFile/iniFile.h"
|
||||
#include "HTTPServer/HTTPServer.h"
|
||||
#include "HTTPServer/HTTPFormParser.h"
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// Disable MSVC warnings:
|
||||
#if defined(_MSC_VER)
|
||||
#pragma warning(push)
|
||||
#pragma warning(disable:4355) // 'this' : used in base member initializer list
|
||||
#endif
|
||||
|
||||
|
||||
|
||||
|
||||
@ -10,7 +28,6 @@
|
||||
// fwd:
|
||||
class cStringMap;
|
||||
class cEvent;
|
||||
class cIniFile;
|
||||
class cWebPlugin;
|
||||
|
||||
|
||||
@ -73,7 +90,8 @@ struct sWebAdminPage
|
||||
|
||||
|
||||
// tolua_begin
|
||||
class cWebAdmin
|
||||
class cWebAdmin :
|
||||
public cHTTPServer::cCallbacks
|
||||
{
|
||||
public:
|
||||
// tolua_end
|
||||
@ -81,60 +99,109 @@ public:
|
||||
typedef std::list< cWebPlugin* > PluginList;
|
||||
|
||||
|
||||
cWebAdmin( int a_Port = 8080 );
|
||||
~cWebAdmin();
|
||||
cWebAdmin(void);
|
||||
|
||||
bool Init( int a_Port );
|
||||
/// Initializes the object. Returns true if successfully initialized and ready to start
|
||||
bool Init(void);
|
||||
|
||||
/// Starts the HTTP server taking care of the admin. Returns true if successful
|
||||
bool Start(void);
|
||||
|
||||
void AddPlugin( cWebPlugin* a_Plugin );
|
||||
void RemovePlugin( cWebPlugin* a_Plugin );
|
||||
void AddPlugin( cWebPlugin* a_Plugin );
|
||||
void RemovePlugin( cWebPlugin* a_Plugin );
|
||||
|
||||
// TODO: Convert this to the auto-locking callback mechanism used for looping players in worlds and such
|
||||
PluginList GetPlugins() const { return m_Plugins; } // >> EXPORTED IN MANUALBINDINGS <<
|
||||
|
||||
static void Request_Handler(webserver::http_request* r);
|
||||
|
||||
// tolua_begin
|
||||
|
||||
/// Returns the amount of currently used memory, in KiB, or -1 if it cannot be queried
|
||||
static int GetMemoryUsage(void);
|
||||
|
||||
int GetPort() { return m_Port; }
|
||||
|
||||
sWebAdminPage GetPage(const HTTPRequest& a_Request);
|
||||
|
||||
/// Returns the contents of the default page - the list of plugins and players
|
||||
AString GetDefaultPage(void);
|
||||
|
||||
AString GetBaseURL(const AString& a_URL);
|
||||
|
||||
// tolua_end
|
||||
|
||||
AString GetBaseURL(const AStringVector& a_URLSplit);
|
||||
|
||||
|
||||
private:
|
||||
int m_Port;
|
||||
protected:
|
||||
/// Common base class for request body data handlers
|
||||
class cRequestData
|
||||
{
|
||||
public:
|
||||
virtual ~cRequestData() {} // Force a virtual destructor in all descendants
|
||||
|
||||
/// Called when a new chunk of body data is received
|
||||
virtual void OnBody(const char * a_Data, int a_Size) = 0;
|
||||
} ;
|
||||
|
||||
/// The body handler for requests in the "/webadmin" and "/~webadmin" paths
|
||||
class cWebadminRequestData :
|
||||
public cRequestData,
|
||||
public cHTTPFormParser::cCallbacks
|
||||
{
|
||||
public:
|
||||
cHTTPFormParser m_Form;
|
||||
|
||||
|
||||
cWebadminRequestData(cHTTPRequest & a_Request) :
|
||||
m_Form(a_Request, *this)
|
||||
{
|
||||
}
|
||||
|
||||
// cRequestData overrides:
|
||||
virtual void OnBody(const char * a_Data, int a_Size) override;
|
||||
|
||||
bool m_bConnected;
|
||||
cSocket m_ListenSocket;
|
||||
// cHTTPFormParser::cCallbacks overrides. Files are ignored:
|
||||
virtual void OnFileStart(cHTTPFormParser & a_Parser, const AString & a_FileName) override {}
|
||||
virtual void OnFileData(cHTTPFormParser & a_Parser, const char * a_Data, int a_Size) override {}
|
||||
virtual void OnFileEnd(cHTTPFormParser & a_Parser) override {}
|
||||
} ;
|
||||
|
||||
|
||||
/// Set to true if Init() succeeds and the webadmin isn't to be disabled
|
||||
bool m_IsInitialized;
|
||||
|
||||
cIniFile * m_IniFile;
|
||||
/// The webadmin.ini file, used for the settings and allowed logins
|
||||
cIniFile m_IniFile;
|
||||
|
||||
PluginList m_Plugins;
|
||||
|
||||
cEvent * m_Event;
|
||||
|
||||
webserver * m_WebServer;
|
||||
|
||||
/// The Lua template script to provide templates:
|
||||
cLuaState m_TemplateScript;
|
||||
|
||||
/// The HTTP server which provides the underlying HTTP parsing, serialization and events
|
||||
cHTTPServer m_HTTPServer;
|
||||
|
||||
|
||||
#ifdef _WIN32
|
||||
static DWORD WINAPI ListenThread(LPVOID lpParam);
|
||||
#else
|
||||
static void * ListenThread(void * lpParam);
|
||||
#endif
|
||||
|
||||
AString GetTemplate();
|
||||
AString GetTemplate(void);
|
||||
|
||||
/// Handles requests coming to the "/webadmin" or "/~webadmin" URLs
|
||||
void HandleWebadminRequest(cHTTPConnection & a_Connection, cHTTPRequest & a_Request);
|
||||
|
||||
/// Handles requests for the root page
|
||||
void HandleRootRequest(cHTTPConnection & a_Connection, cHTTPRequest & a_Request);
|
||||
|
||||
// cHTTPServer::cCallbacks overrides:
|
||||
virtual void OnRequestBegun (cHTTPConnection & a_Connection, cHTTPRequest & a_Request) override;
|
||||
virtual void OnRequestBody (cHTTPConnection & a_Connection, cHTTPRequest & a_Request, const char * a_Data, int a_Size) override;
|
||||
virtual void OnRequestFinished(cHTTPConnection & a_Connection, cHTTPRequest & a_Request) override;
|
||||
} ; // tolua_export
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// Revert MSVC warnings back to orignal state:
|
||||
#if defined(_MSC_VER)
|
||||
#pragma warning(pop)
|
||||
#endif
|
||||
|
||||
|
||||
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user