aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore1
-rw-r--r--.gitmodules3
-rw-r--r--AUTHORS2
l---------COPYING1
-rw-r--r--ChangeLog0
l---------INSTALL1
-rw-r--r--Makefile15
-rw-r--r--Makefile.am1
-rw-r--r--Makefile.debug41
-rw-r--r--NEWS0
-rw-r--r--README0
-rw-r--r--TODO5
-rw-r--r--UpdateTool.cpp6
-rw-r--r--UpdateTool.hpp28
-rwxr-xr-xautogen.sh9
-rwxr-xr-xbuild_wxwidgets.sh45
-rw-r--r--configure.ac137
-rw-r--r--contrib/example.csv128
-rw-r--r--contrib/example2.csv6
-rwxr-xr-xcontrib/mock.py71
-rw-r--r--src/Csv.hpp1252
-rw-r--r--src/Http.hpp1308
-rw-r--r--src/JobQueue.cpp98
-rw-r--r--src/JobQueue.hpp103
-rw-r--r--src/Json.cpp788
-rw-r--r--src/Json.hpp232
-rw-r--r--src/Makefile.am17
-rw-r--r--src/UpdateFactory.cpp346
-rw-r--r--src/UpdateFactory.hpp77
-rw-r--r--src/UpdateGUI.cpp277
-rw-r--r--src/UpdateGUI.hpp61
-rw-r--r--src/UpdateTool.cpp89
m---------wxWidgets0
33 files changed, 5099 insertions, 49 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..33cd5b5
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1 @@
+/wxWidgets-*
diff --git a/.gitmodules b/.gitmodules
new file mode 100644
index 0000000..bf1e32d
--- /dev/null
+++ b/.gitmodules
@@ -0,0 +1,3 @@
+[submodule "wxWidgets"]
+ path = wxWidgets
+ url = https://github.com/wxWidgets/wxWidgets.git
diff --git a/AUTHORS b/AUTHORS
new file mode 100644
index 0000000..e52b986
--- /dev/null
+++ b/AUTHORS
@@ -0,0 +1,2 @@
+Toni Uhlig
+Valeri Budjko
diff --git a/COPYING b/COPYING
new file mode 120000
index 0000000..88798ab
--- /dev/null
+++ b/COPYING
@@ -0,0 +1 @@
+/usr/share/automake-1.15/COPYING \ No newline at end of file
diff --git a/ChangeLog b/ChangeLog
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/ChangeLog
diff --git a/INSTALL b/INSTALL
new file mode 120000
index 0000000..ddcdb76
--- /dev/null
+++ b/INSTALL
@@ -0,0 +1 @@
+/usr/share/automake-1.15/INSTALL \ No newline at end of file
diff --git a/Makefile b/Makefile
deleted file mode 100644
index c0c4c43..0000000
--- a/Makefile
+++ /dev/null
@@ -1,15 +0,0 @@
-WXDIR=../wxWidgets
-WXLIBDIR=$(WXDIR)/lib
-
-CC=i686-w64-mingw32-g++
-CFLAGS=-static -I. -I$(WXDIR)/include -D_FILE_OFFSET_BITS=64 -D__WXMSW__ -mthreads
-LDFLAGS_WIN_EXTRA=$(WXLIBDIR)/libwxpng-3.1-i686-w64-mingw32.a $(WXLIBDIR)/libwxzlib-3.1-i686-w64-mingw32.a
-LDFLAGS_WIN=-Wl,--subsystem,windows -mwindows $(WXLIBDIR)/libwx_mswu_xrc-3.1-i686-w64-mingw32.a $(WXLIBDIR)/libwx_mswu_qa-3.1-i686-w64-mingw32.a $(WXLIBDIR)/libwx_baseu_net-3.1-i686-w64-mingw32.a $(WXLIBDIR)/libwx_mswu_html-3.1-i686-w64-mingw32.a $(WXLIBDIR)/libwx_mswu_adv-3.1-i686-w64-mingw32.a $(WXLIBDIR)/libwx_mswu_core-3.1-i686-w64-mingw32.a $(WXLIBDIR)/libwx_baseu_xml-3.1-i686-w64-mingw32.a $(WXLIBDIR)/libwx_baseu-3.1-i686-w64-mingw32.a -lrpcrt4 -loleaut32 -lole32 -luuid -lwinspool -lwinmm -lshell32 -lshlwapi -lcomctl32 -lcomdlg32 -ladvapi32 -lversion -lwsock32 -lgdi32 $(LDFLAGS_WIN_EXTRA)
-DEPS=UpdateTool.cpp
-OBJ=UpdateTool.o
-
-%.o: $(DEPS)
- $(CC) -Wall -c -o $@ $< $(CFLAGS)
-
-utool.exe: $(OBJ)
- $(CC) -Wall -o $@ $^ $(CFLAGS) $(LDFLAGS) $(LDFLAGS_WIN)
diff --git a/Makefile.am b/Makefile.am
new file mode 100644
index 0000000..af437a6
--- /dev/null
+++ b/Makefile.am
@@ -0,0 +1 @@
+SUBDIRS = src
diff --git a/Makefile.debug b/Makefile.debug
new file mode 100644
index 0000000..29c55b2
--- /dev/null
+++ b/Makefile.debug
@@ -0,0 +1,41 @@
+CFLAGS = -std=c++11 -I. -ffunction-sections -fdata-sections -Wl,--gc-sections -Os -DCSV_IO_NO_THREAD=1
+LDFLAGS =
+ifneq ($(BUILD_LINUX),)
+WXDIR = wxWidgets-host
+CC = g++
+CFLAGS += -g
+else
+WXDIR = wxWidgets-i686-w64-mingw32
+CC = i686-w64-mingw32-g++
+CFLAGS += -static-libstdc++ -static-libgcc -D_UNICODE=1 -municode -mthreads -mwindows
+LDFLAGS += -static -lws2_32 -dynamic
+SUFFIX = .exe
+OBJSUFFIX = _win
+endif
+
+BIN = utool$(SUFFIX)
+SRC_FILES = Json.cpp UpdateFactory.cpp UpdateTool.cpp
+ifneq ($(BUILD_GUI),)
+SRC_FILES += UpdateGUI.cpp JobQueue.cpp
+CFLAGS += -DUSE_GUI=1 -Wno-deprecated-declarations $(shell $(WXDIR)/wx-config --cflags)
+LDFLAGS += $(shell $(WXDIR)/wx-config --libs std)
+endif
+
+SRC = $(patsubst %.cpp,src/%.cpp,$(SRC_FILES))
+OBJ = $(SRC:.cpp=$(OBJSUFFIX).o)
+
+
+%$(OBJSUFFIX).o: %.cpp
+ @echo 'Building $@ from $<'
+ $(CC) -Wall -c -o $@ $< $(CFLAGS)
+
+$(BIN): $(OBJ)
+ @echo 'Linking $^ to $@'
+ $(CC) -Wall -o $@ $^ $(CFLAGS) $(LDFLAGS)
+
+all: $(BIN)
+
+clean:
+ $(RM) $(BIN) $(OBJ)
+
+.PHONY: all clean
diff --git a/NEWS b/NEWS
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/NEWS
diff --git a/README b/README
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/README
diff --git a/TODO b/TODO
new file mode 100644
index 0000000..e404772
--- /dev/null
+++ b/TODO
@@ -0,0 +1,5 @@
+- switch to https://github.com/mrtazz/restclient-cpp if its "cleaner" and more usable?
+
+- shrink Http.hpp (server is not needed afaik)
+- port Http.hpp to HTTP/1.1 (chunked transport)
+- Http.hpp uses Maps and Multimaps (What happens with multiple values per key e.g. multiple Set-Cookie's in one response?)
diff --git a/UpdateTool.cpp b/UpdateTool.cpp
deleted file mode 100644
index 5c6ad89..0000000
--- a/UpdateTool.cpp
+++ /dev/null
@@ -1,6 +0,0 @@
-#include "UpdateTool.hpp"
-
-
-int main(int argc, char **argv) {
- return 0;
-}
diff --git a/UpdateTool.hpp b/UpdateTool.hpp
deleted file mode 100644
index 24ebede..0000000
--- a/UpdateTool.hpp
+++ /dev/null
@@ -1,28 +0,0 @@
-#include <wx/wxprec.h>
-
-#ifndef WX_PRECOMP
-#include <wx/wx.h>
-#endif
-
-
-class UpdateTool: public wxApp
-{
-public:
- virtual bool OnInit();
-};
-
-class UpdateToolFrame: public wxFrame
-{
-public:
- UpdateToolFrame(const wxString& title, const wxPoint& pos, const wxSize& size);
-private:
- void OnHello(wxCommandEvent& event);
- void OnExit(wxCommandEvent& event);
- void OnAbout(wxCommandEvent& event);
- wxDECLARE_EVENT_TABLE();
-};
-
-enum
-{
- ID_Hello = 1
-};
diff --git a/autogen.sh b/autogen.sh
new file mode 100755
index 0000000..575c258
--- /dev/null
+++ b/autogen.sh
@@ -0,0 +1,9 @@
+#!/bin/sh
+
+libtoolize --force
+aclocal
+autoheader
+automake --force-missing --add-missing
+autoconf
+
+./configure $@
diff --git a/build_wxwidgets.sh b/build_wxwidgets.sh
new file mode 100755
index 0000000..6622ecf
--- /dev/null
+++ b/build_wxwidgets.sh
@@ -0,0 +1,45 @@
+#!/bin/bash
+
+set -e
+set -x
+
+HOSTT="${1}"
+if [ "x${HOSTT}" = "x" -o "x${HOSTT}" = "xhost" ]; then
+ HOSTARG=""
+else
+ HOSTARG="--host=${HOSTT}"
+fi
+
+WDIR="wxWidgets"
+git submodule init
+git submodule update
+cd ${WDIR}
+git checkout .
+
+cd ..
+mkdir -p "${WDIR}-${HOSTT:-host}"
+cd "${WDIR}-${HOSTT:-host}"
+CXXFLAGS="-ffunction-sections -fdata-sections -Os -Wno-deprecated-declarations -Wno-misleading-indentation -Wno-undef"
+../${WDIR}/configure --without-expat --disable-compat28 --disable-compat30 \
+ --disable-richtooltip --disable-richmsgdlg --disable-richtext \
+ --without-libpng --without-libjpeg --without-regex \
+ --disable-ole --disable-mediactrl --disable-dataobj --disable-dataviewctrl \
+ --disable-treebook --disable-treelist --disable-stc \
+ --disable-webkit --disable-webview --disable-webviewwebkit --disable-webviewie \
+ --disable-svg --without-libtiff --without-zlib --without-opengl \
+ --without-gtkprint --disable-printfposparam --disable-printarch --disable-ps-in-msw \
+ --enable-cxx11 \
+ --disable-mshtmlhelp --disable-html --disable-htmlhelp \
+ --disable-ribbon --disable-propgrid --disable-aui \
+ --disable-sockets --disable-dialupman --disable-fs_inet \
+ --disable-shared ${HOSTARG} \
+ --disable-sys-libs \
+ --disable-debug --disable-debug_flag \
+ CXXFLAGS="${CXXFLAGS}"
+make -j${BUILDJOBS:-4} BUILD=release
+
+# fix static lib path for cross compile targets
+for lib in lib/*-${HOSTT:-host}.a; do
+ NEWNAME="$(echo -n "${lib}" | sed -n "s/-${HOSTT}\.a$//gp").a"
+ ln -sr "${lib}" "${NEWNAME}" 2>/dev/null || true
+done
diff --git a/configure.ac b/configure.ac
new file mode 100644
index 0000000..66f9aa3
--- /dev/null
+++ b/configure.ac
@@ -0,0 +1,137 @@
+AC_PREREQ(2.58)
+AC_INIT([UpdateTool], [1.0.0], [], [], [https://dl-website.tld/utool.exe])
+AC_CONFIG_SRCDIR([src/config.h.in])
+AC_CONFIG_FILES([Makefile src/Makefile])
+
+AC_CANONICAL_BUILD
+AC_CANONICAL_HOST
+if test x"$cross_compiling" != x"no"; then
+ HOST_PREFIX="${host_alias}-"
+ HOST_SUFFIX="-$host_alias"
+ if test x"${build_alias}" = x; then
+ AC_MSG_ERROR([Cross compile enabled but no --build *explicitly* specified. For example: --build=$(gcc -dumpmachine)])
+ fi
+else
+ HOST_PREFIX=
+ HOST_SUFFIX=
+fi
+
+AM_SILENT_RULES([yes])
+AM_INIT_AUTOMAKE
+AC_PROG_CXX
+AC_PROG_INSTALL
+AC_C_CONST
+AC_C_INLINE
+AC_TYPE_SIZE_T
+AC_CHECK_HEADER_STDBOOL
+AC_FUNC_VPRINTF
+AC_FUNC_STAT
+AC_CHECK_FUNCS([malloc calloc realloc free memcmp memchr memcpy memmove memset putc putwc raise rand signal sprintf strchr strcmp strcoll strcpy strerror strftime strlen strncmp strncpy strpbrk strspn strstr strtol strtoul tolower toupper towlower towupper ungetc ungetwc vfprintf wcschr wcscmp wcscoll wcscpy wcspbrk wcsspn wcsstr wcstol wcstoul fopen fgets fprintf fputs fread fwrite getc fputc isalnum isalpha isupper time mktime gmtime close fclose feof exit],,
+ [AC_MSG_ERROR([Missing essential std functions.])])
+
+utool_major_version_number=1
+utool_minor_version_number=0
+utool_release_number=0
+UTOOL_RELEASE=${utool_major_version_number}.${utool_minor_version_number}
+UTOOL_VERSION=${UTOOL_RELEASE}.${utool_release_number}
+
+AC_ARG_ENABLE([gui],
+ [AS_HELP_STRING([--enable-gui], [Build the UpdateTool with a wxWidgets based GUI instead of a simple CLI (default enabled).])],,
+ [ut_buildgui=yes])
+ut_buildgui=$(echo ${ut_buildgui})
+case ${ut_buildgui} in
+ ''|1|y|yes) ut_buildgui=yes ;;
+ no) ut_buildgui= ;;
+ *) AC_MSG_ERROR([unknown option '${ut_buildgui}' for --enable-gui]) ;;
+esac
+
+AC_ARG_ENABLE([host-wxwidgets],
+ [AS_HELP_STRING([--enable-host-wxwidgets], [Use the system wxWidgets library instead of our own shipped in ./wxWidgets (not recommended).])],,
+ [enable_host_wxwidgets=no])
+enable_host_wxwidgets=$(echo ${enable_host_wxwidgets})
+case ${enable_host_wxwidgets} in
+ ''|1|y|yes) enable_host_wxwidgets=yes ;;
+ no) enable_host_wxwidgets= ;;
+ *) AC_MSG_ERROR([unknown option '${enable_host_wxwidgets}' for --enable-host-wxwidgets]) ;;
+esac
+if test x"${ut_buildgui}" != x"yes"; then
+ enable_host_wxwidgets=
+fi
+AC_MSG_CHECKING([for host wxWidgets])
+if test x"${enable_host_wxwidgets}" != x; then
+ AC_MSG_RESULT([${enable_host_wxwidgets}])
+else
+ AC_MSG_RESULT([disabled])
+fi
+
+if test x"${enable_host_wxwidgets}" != x"yes"; then
+ if test "${cross_compiling}" != "no"; then
+ utool_wxdir="wxWidgets-${host_alias}/"
+ else
+ utool_wxdir="wxWidgets-host/"
+ fi
+ AC_MSG_CHECKING([for wxWidgets])
+ if ! test -d "${utool_wxdir}lib"; then
+ AC_MSG_ERROR([Missing wxWidgets builddir ${utool_wxdir}: Please run \`./build_wxwidgets.sh ${host_alias:-host}\` first.])
+ fi
+ AC_MSG_RESULT([yes])
+else
+ utool_wxdir=""
+fi
+
+ut_cflags=""
+ut_libs=""
+PROGRAM_EXT=
+case "${host}" in
+ *-*-cygwin* | *-*-mingw32* )
+ PROGRAM_EXT=".exe"
+ ut_use_msw=yes
+ ut_cflags="${ut_cflags} -D_UNICODE=1 -municode"
+ ut_libs="${ut_libs} -static -lws2_32 -dynamic"
+ ;;
+esac
+
+if test x"${ut_buildgui}" = x"yes"; then
+ if test x"${ut_use_msw}" = x"yes" -a x"${enable_host_wxwidgets}" = x"yes"; then
+ AC_MSG_ERROR([Building UpdateTool for Windows based platforms is not supported with --enable-host-wxwidgets])
+ fi
+ AC_MSG_CHECKING([for wxWidgets version])
+ wx_version=$(${utool_wxdir}wx-config --version-full)
+ AC_MSG_RESULT([${wx_version}])
+ AC_MSG_CHECKING([for wxWidgets CFLAGS])
+ wx_cflags=$(${utool_wxdir}wx-config --cflags)
+ AC_MSG_RESULT([yes])
+ AC_MSG_CHECKING([for wxWidgets LIBS])
+ wx_libs=$(${utool_wxdir}wx-config --libs std)
+ AC_MSG_RESULT([yes])
+fi
+
+ut_cflags="${ut_cflags} -Wall -Wno-deprecated-declarations"
+if `echo $CXXFLAGS $CFLAGS | grep " -O" >/dev/null`; then
+ AC_MSG_WARN([CXXFLAGS/CFLAGS already contains -O flag; ignoring -Os])
+else
+ ut_cflags="${ut_cflags} -Os"
+fi
+
+AM_CONDITIONAL([UT_BUILDGUI], [test x"${ut_buildgui}" = xyes])
+AC_SUBST([UT_CFLAGS], [${ut_cflags}])
+AC_SUBST([UT_LIBS], [${ut_libs}])
+AC_SUBST([WX_CFLAGS], [${wx_cflags}])
+AC_SUBST([WX_LIBS], [${wx_libs}])
+
+AC_CONFIG_HEADERS([src/config.h:src/config.h.in])
+AC_OUTPUT
+
+cat << EOF
+
+UpdateTool was configured with:
+
+ CFLAGS/CXXFLAGS..........: ${ut_cflags}
+ Build GUI................: ${ut_buildgui:-disabled}
+ Build for Windows........: ${ut_use_msw:-disabled}
+ Host wxWidgets...........: ${enable_host_wxwidgets:-disabled}
+ wxWidgets Version........: ${wx_version:-unknown}
+ wxWidgets CFLAGS/CXXFLAGS: ${wx_cflags}
+ wxWidgets LDFLAGS........: ${wx_libs}
+
+EOF
diff --git a/contrib/example.csv b/contrib/example.csv
new file mode 100644
index 0000000..e86238a
--- /dev/null
+++ b/contrib/example.csv
@@ -0,0 +1,128 @@
+hostname,port,password
+127.0.0.1,80,1234
+127.0.0.1,80,1234
+127.0.0.1,80,1234
+127.0.0.1,80,1234
+127.0.0.1,80,1234
+127.0.0.1,80,1234
+127.0.0.1,80,1234
+127.0.0.1,80,1234
+127.0.0.1,80,1234
+127.0.0.1,80,1234
+127.0.0.1,80,1234
+127.0.0.1,80,1234
+127.0.0.1,80,1234
+127.0.0.1,80,1234
+127.0.0.1,80,1234
+127.0.0.1,80,1234
+127.0.0.1,80,1234
+127.0.0.1,80,1234
+127.0.0.1,80,1234
+127.0.0.1,80,1234
+127.0.0.1,80,1234
+127.0.0.1,80,1234
+127.0.0.1,80,1234
+127.0.0.1,80,1234
+127.0.0.1,80,1234
+127.0.0.1,80,1234
+127.0.0.1,80,1234
+127.0.0.1,80,1234
+127.0.0.1,80,1234
+127.0.0.1,80,1234
+127.0.0.1,80,1234
+127.0.0.1,80,1234
+127.0.0.1,80,1234
+127.0.0.1,80,1234
+127.0.0.1,80,1234
+127.0.0.1,80,1234
+127.0.0.1,80,1234
+127.0.0.1,80,1234
+127.0.0.1,80,1234
+127.0.0.1,80,1234
+127.0.0.1,80,1234
+127.0.0.1,80,1234
+127.0.0.1,80,1234
+127.0.0.1,80,1234
+127.0.0.1,80,1234
+127.0.0.1,80,1234
+127.0.0.1,80,1234
+127.0.0.1,80,1234
+127.0.0.1,80,1234
+127.0.0.1,80,1234
+127.0.0.1,80,""
+127.0.0.1,80,""
+127.0.0.1,80,""
+127.0.0.1,80,""
+127.0.0.1,80,""
+127.0.0.1,80,""
+127.0.0.1,80,""
+127.0.0.1,80,""
+127.0.0.1,80,""
+127.0.0.1,80,""
+127.0.0.1,80,""
+127.0.0.1,80,""
+127.0.0.1,80,""
+127.0.0.1,80,""
+127.0.0.1,80,""
+127.0.0.1,80,""
+127.0.0.1,80,""
+127.0.0.1,80,""
+127.0.0.1,80,""
+127.0.0.1,80,""
+127.0.0.1,80,""
+127.0.0.1,80,""
+127.0.0.1,80,""
+127.0.0.1,80,""
+127.0.0.1,80,""
+127.0.0.1,80,""
+127.0.0.1,80,""
+127.0.0.1,80,""
+127.0.0.1,80,""
+127.0.0.1,80,""
+127.0.0.1,80,""
+127.0.0.1,80,""
+127.0.0.1,80,""
+127.0.0.1,80,""
+127.0.0.1,80,""
+127.0.0.1,80,""
+127.0.0.1,80,""
+127.0.0.1,80,""
+127.0.0.1,80,""
+127.0.0.1,80,""
+127.0.0.1,80,""
+127.0.0.1,80,""
+127.0.0.1,80,""
+127.0.0.1,80,""
+127.0.0.1,80,""
+127.0.0.1,80,""
+127.0.0.1,80,""
+127.0.0.1,80,""
+127.0.0.1,80,""
+127.0.0.1,80,""
+127.0.0.1,80,""
+127.0.0.1,80,""
+127.0.0.1,80,""
+127.0.0.1,80,""
+127.0.0.1,80,""
+127.0.0.1,80,""
+127.0.0.1,80,""
+127.0.0.1,80,""
+127.0.0.1,80,""
+127.0.0.1,80,""
+127.0.0.1,80,""
+127.0.0.1,80,""
+127.0.0.1,80,""
+127.0.0.1,80,""
+127.0.0.1,80,""
+127.0.0.1,80,""
+127.0.0.1,80,""
+0.0.0.0,80,1234
+0.0.0.0,80,1234
+0.0.0.0,80,1234
+0.0.0.0,80,1234
+0.0.0.0,80,1234
+0.0.0.0,80,1234
+nonexistant.tld,80,""
+nonexistant.tld,80,""
+nonexistant.tld,80,""
+nonexistant.tld,80,""
diff --git a/contrib/example2.csv b/contrib/example2.csv
new file mode 100644
index 0000000..dc2cb6c
--- /dev/null
+++ b/contrib/example2.csv
@@ -0,0 +1,6 @@
+hostname,port,password,unused_column1,unused_column2,unused_columnN
+127.0.0.1,80,1234,unused1,unused2,unusedN
+127.0.0.1,80,,unused1,unused2,unusedN
+0.0.0.0,80,1234,unused1,unused2,unusedN
+nonexistant.tld,80,,unused1,unused2,unusedN
+localhost,80,1234,unused,unused,unused
diff --git a/contrib/mock.py b/contrib/mock.py
new file mode 100755
index 0000000..fe75814
--- /dev/null
+++ b/contrib/mock.py
@@ -0,0 +1,71 @@
+#!/usr/bin/env python
+
+from sys import argv
+import uuid, json
+from bottle import route, run, template, request, response, abort, post
+
+DEFAULT_TEXT = '<html><body><b>default page</b></body></html>'
+EMC_VERSION = '1.50'
+EMC_SERIAL = '0123456789'
+PASSWORD = None
+EMC_OK = json.dumps({'app_version': EMC_VERSION , 'serial': EMC_SERIAL , 'authentication': True}, sort_keys=True, indent=4, separators=(',', ': '))
+EMC_AUTH = json.dumps({'app_version': EMC_VERSION , 'serial': EMC_SERIAL , 'authentication': False}, sort_keys=True, indent=4, separators=(',', ': '))
+
+
+@route('/start.php', method='GET')
+def get_start_php():
+ sid = uuid.uuid1()
+ response.set_header('Set-Cookie', 'PHPSESSID='+str(sid))
+ print 'PHPSESSID:', str(sid)
+ return EMC_AUTH
+
+@route('/start.php', method='POST')
+def post_start_php():
+ lines = request.body.readlines()
+ print 'Request BODY:', '"'+str(lines[0])+'"'
+
+ passwd = ''
+ try:
+ passwd = request.forms.get('password')
+ except KeyError:
+ pass
+ if PASSWORD is not None:
+ if passwd != PASSWORD:
+ print 'Auth failed: "%s" != "%s"' % (PASSWORD, passwd)
+ return EMC_AUTH
+
+ return EMC_OK
+
+@route('/setup.php', method='GET')
+def get_setup_php():
+ cleanup = None
+ try:
+ cleanup = request.query['update_cleanup']
+ except KeyError:
+ pass
+
+ if cleanup is None:
+ abort(404)
+
+ return EMC_OK
+
+@route('/mum-webservice/0/update.php', method='POST')
+def post_setup_php():
+ upload = request.files.get('update_file')
+ upload_len = len(upload.file.read())
+ print 'Update ... Length:', str(upload_len), 'bytes'
+ return DEFAULT_TEXT
+
+
+if __name__ == '__main__':
+ try:
+ PASSWORD = argv[1]
+ except IndexError:
+ pass
+ print 'Device Password:', PASSWORD
+ try:
+ listen_adr = argv[2]
+ except IndexError:
+ listen_adr = '127.0.0.1'
+ print 'Listen Address:', listen_adr
+ run(host=listen_adr, port=80)
diff --git a/src/Csv.hpp b/src/Csv.hpp
new file mode 100644
index 0000000..09cd053
--- /dev/null
+++ b/src/Csv.hpp
@@ -0,0 +1,1252 @@
+// Copyright: (2012-2015) Ben Strasser <code@ben-strasser.net>
+// License: BSD-3
+//
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice,
+// this list of conditions and the following disclaimer.
+//
+//2. Redistributions in binary form must reproduce the above copyright notice,
+// this list of conditions and the following disclaimer in the documentation
+// and/or other materials provided with the distribution.
+//
+//3. Neither the name of the copyright holder nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+// POSSIBILITY OF SUCH DAMAGE.
+
+#ifndef CSV_H
+#define CSV_H
+
+#include <vector>
+#include <string>
+#include <cstring>
+#include <algorithm>
+#include <utility>
+#include <cstdio>
+#include <exception>
+#ifndef CSV_IO_NO_THREAD
+#include <mutex>
+#include <thread>
+#include <condition_variable>
+#endif
+#include <memory>
+#include <cassert>
+#include <cerrno>
+#include <istream>
+
+namespace io{
+ ////////////////////////////////////////////////////////////////////////////
+ // LineReader //
+ ////////////////////////////////////////////////////////////////////////////
+
+ namespace error{
+ struct base : std::exception{
+ virtual void format_error_message()const = 0;
+
+ const char*what()const throw(){
+ format_error_message();
+ return error_message_buffer;
+ }
+
+ mutable char error_message_buffer[512];
+ };
+
+ const int max_file_name_length = 255;
+
+ struct with_file_name{
+ with_file_name(){
+ std::memset(file_name, 0, max_file_name_length+1);
+ }
+
+ void set_file_name(const char*file_name){
+ std::strncpy(this->file_name, file_name, max_file_name_length);
+ this->file_name[max_file_name_length] = '\0';
+ }
+
+ char file_name[max_file_name_length+1];
+ };
+
+ struct with_file_line{
+ with_file_line(){
+ file_line = -1;
+ }
+
+ void set_file_line(int file_line){
+ this->file_line = file_line;
+ }
+
+ int file_line;
+ };
+
+ struct with_errno{
+ with_errno(){
+ errno_value = 0;
+ }
+
+ void set_errno(int errno_value){
+ this->errno_value = errno_value;
+ }
+
+ int errno_value;
+ };
+
+ struct can_not_open_file :
+ base,
+ with_file_name,
+ with_errno{
+ void format_error_message()const{
+ if(errno_value != 0)
+ std::snprintf(error_message_buffer, sizeof(error_message_buffer),
+ "Can not open file \"%s\" because \"%s\"."
+ , file_name, std::strerror(errno_value));
+ else
+ std::snprintf(error_message_buffer, sizeof(error_message_buffer),
+ "Can not open file \"%s\"."
+ , file_name);
+ }
+ };
+
+ struct line_length_limit_exceeded :
+ base,
+ with_file_name,
+ with_file_line{
+ void format_error_message()const{
+ std::snprintf(error_message_buffer, sizeof(error_message_buffer),
+ "Line number %d in file \"%s\" exceeds the maximum length of 2^24-1."
+ , file_line, file_name);
+ }
+ };
+ }
+
+ class ByteSourceBase{
+ public:
+ virtual int read(char*buffer, int size)=0;
+ virtual ~ByteSourceBase(){}
+ };
+
+ namespace detail{
+
+ class OwningStdIOByteSourceBase : public ByteSourceBase{
+ public:
+ explicit OwningStdIOByteSourceBase(FILE*file):file(file){
+ // Tell the std library that we want to do the buffering ourself.
+ std::setvbuf(file, 0, _IONBF, 0);
+ }
+
+ int read(char*buffer, int size){
+ return std::fread(buffer, 1, size, file);
+ }
+
+ ~OwningStdIOByteSourceBase(){
+ std::fclose(file);
+ }
+
+ private:
+ FILE*file;
+ };
+
+ class NonOwningIStreamByteSource : public ByteSourceBase{
+ public:
+ explicit NonOwningIStreamByteSource(std::istream&in):in(in){}
+
+ int read(char*buffer, int size){
+ in.read(buffer, size);
+ return in.gcount();
+ }
+
+ ~NonOwningIStreamByteSource(){}
+
+ private:
+ std::istream&in;
+ };
+
+ class NonOwningStringByteSource : public ByteSourceBase{
+ public:
+ NonOwningStringByteSource(const char*str, long long size):str(str), remaining_byte_count(size){}
+
+ int read(char*buffer, int desired_byte_count){
+ int to_copy_byte_count = desired_byte_count;
+ if(remaining_byte_count < to_copy_byte_count)
+ to_copy_byte_count = remaining_byte_count;
+ std::memcpy(buffer, str, to_copy_byte_count);
+ remaining_byte_count -= to_copy_byte_count;
+ str += to_copy_byte_count;
+ return to_copy_byte_count;
+ }
+
+ ~NonOwningStringByteSource(){}
+
+ private:
+ const char*str;
+ long long remaining_byte_count;
+ };
+
+ #ifndef CSV_IO_NO_THREAD
+ class AsynchronousReader{
+ public:
+ void init(std::unique_ptr<ByteSourceBase>arg_byte_source){
+ std::unique_lock<std::mutex>guard(lock);
+ byte_source = std::move(arg_byte_source);
+ desired_byte_count = -1;
+ termination_requested = false;
+ worker = std::thread(
+ [&]{
+ std::unique_lock<std::mutex>guard(lock);
+ try{
+ for(;;){
+ read_requested_condition.wait(
+ guard,
+ [&]{
+ return desired_byte_count != -1 || termination_requested;
+ }
+ );
+ if(termination_requested)
+ return;
+
+ read_byte_count = byte_source->read(buffer, desired_byte_count);
+ desired_byte_count = -1;
+ if(read_byte_count == 0)
+ break;
+ read_finished_condition.notify_one();
+ }
+ }catch(...){
+ read_error = std::current_exception();
+ }
+ read_finished_condition.notify_one();
+ }
+ );
+ }
+
+ bool is_valid()const{
+ return byte_source != nullptr;
+ }
+
+ void start_read(char*arg_buffer, int arg_desired_byte_count){
+ std::unique_lock<std::mutex>guard(lock);
+ buffer = arg_buffer;
+ desired_byte_count = arg_desired_byte_count;
+ read_byte_count = -1;
+ read_requested_condition.notify_one();
+ }
+
+ int finish_read(){
+ std::unique_lock<std::mutex>guard(lock);
+ read_finished_condition.wait(
+ guard,
+ [&]{
+ return read_byte_count != -1 || read_error;
+ }
+ );
+ if(read_error)
+ std::rethrow_exception(read_error);
+ else
+ return read_byte_count;
+ }
+
+ ~AsynchronousReader(){
+ if(byte_source != nullptr){
+ {
+ std::unique_lock<std::mutex>guard(lock);
+ termination_requested = true;
+ }
+ read_requested_condition.notify_one();
+ worker.join();
+ }
+ }
+
+ private:
+ std::unique_ptr<ByteSourceBase>byte_source;
+
+ std::thread worker;
+
+ bool termination_requested;
+ std::exception_ptr read_error;
+ char*buffer;
+ int desired_byte_count;
+ int read_byte_count;
+
+ std::mutex lock;
+ std::condition_variable read_finished_condition;
+ std::condition_variable read_requested_condition;
+ };
+ #endif
+
+ class SynchronousReader{
+ public:
+ void init(std::unique_ptr<ByteSourceBase>arg_byte_source){
+ byte_source = std::move(arg_byte_source);
+ }
+
+ bool is_valid()const{
+ return byte_source != nullptr;
+ }
+
+ void start_read(char*arg_buffer, int arg_desired_byte_count){
+ buffer = arg_buffer;
+ desired_byte_count = arg_desired_byte_count;
+ }
+
+ int finish_read(){
+ return byte_source->read(buffer, desired_byte_count);
+ }
+ private:
+ std::unique_ptr<ByteSourceBase>byte_source;
+ char*buffer;
+ int desired_byte_count;
+ };
+ }
+
+ class LineReader{
+ private:
+ static const int block_len = 1<<24;
+ std::unique_ptr<char[]>buffer; // must be constructed before (and thus destructed after) the reader!
+ #ifdef CSV_IO_NO_THREAD
+ detail::SynchronousReader reader;
+ #else
+ detail::AsynchronousReader reader;
+ #endif
+ int data_begin;
+ int data_end;
+
+ char file_name[error::max_file_name_length+1];
+ unsigned file_line;
+
+ static std::unique_ptr<ByteSourceBase> open_file(const char*file_name){
+ // We open the file in binary mode as it makes no difference under *nix
+ // and under Windows we handle \r\n newlines ourself.
+ FILE*file = std::fopen(file_name, "rb");
+ if(file == 0){
+ int x = errno; // store errno as soon as possible, doing it after constructor call can fail.
+ error::can_not_open_file err;
+ err.set_errno(x);
+ err.set_file_name(file_name);
+ throw err;
+ }
+ return std::unique_ptr<ByteSourceBase>(new detail::OwningStdIOByteSourceBase(file));
+ }
+
+ void init(std::unique_ptr<ByteSourceBase>byte_source){
+ file_line = 0;
+
+ buffer = std::unique_ptr<char[]>(new char[3*block_len]);
+ data_begin = 0;
+ data_end = byte_source->read(buffer.get(), 2*block_len);
+
+ // Ignore UTF-8 BOM
+ if(data_end >= 3 && buffer[0] == '\xEF' && buffer[1] == '\xBB' && buffer[2] == '\xBF')
+ data_begin = 3;
+
+ if(data_end == 2*block_len){
+ reader.init(std::move(byte_source));
+ reader.start_read(buffer.get() + 2*block_len, block_len);
+ }
+ }
+
+ public:
+ LineReader() = delete;
+ LineReader(const LineReader&) = delete;
+ LineReader&operator=(const LineReader&) = delete;
+
+ explicit LineReader(const char*file_name){
+ set_file_name(file_name);
+ init(open_file(file_name));
+ }
+
+ explicit LineReader(const std::string&file_name){
+ set_file_name(file_name.c_str());
+ init(open_file(file_name.c_str()));
+ }
+
+ LineReader(const char*file_name, std::unique_ptr<ByteSourceBase>byte_source){
+ set_file_name(file_name);
+ init(std::move(byte_source));
+ }
+
+ LineReader(const std::string&file_name, std::unique_ptr<ByteSourceBase>byte_source){
+ set_file_name(file_name.c_str());
+ init(std::move(byte_source));
+ }
+
+ LineReader(const char*file_name, const char*data_begin, const char*data_end){
+ set_file_name(file_name);
+ init(std::unique_ptr<ByteSourceBase>(new detail::NonOwningStringByteSource(data_begin, data_end-data_begin)));
+ }
+
+ LineReader(const std::string&file_name, const char*data_begin, const char*data_end){
+ set_file_name(file_name.c_str());
+ init(std::unique_ptr<ByteSourceBase>(new detail::NonOwningStringByteSource(data_begin, data_end-data_begin)));
+ }
+
+ LineReader(const char*file_name, FILE*file){
+ set_file_name(file_name);
+ init(std::unique_ptr<ByteSourceBase>(new detail::OwningStdIOByteSourceBase(file)));
+ }
+
+ LineReader(const std::string&file_name, FILE*file){
+ set_file_name(file_name.c_str());
+ init(std::unique_ptr<ByteSourceBase>(new detail::OwningStdIOByteSourceBase(file)));
+ }
+
+ LineReader(const char*file_name, std::istream&in){
+ set_file_name(file_name);
+ init(std::unique_ptr<ByteSourceBase>(new detail::NonOwningIStreamByteSource(in)));
+ }
+
+ LineReader(const std::string&file_name, std::istream&in){
+ set_file_name(file_name.c_str());
+ init(std::unique_ptr<ByteSourceBase>(new detail::NonOwningIStreamByteSource(in)));
+ }
+
+ void set_file_name(const std::string&file_name){
+ set_file_name(file_name.c_str());
+ }
+
+ void set_file_name(const char*file_name){
+ strncpy(this->file_name, file_name, error::max_file_name_length);
+ this->file_name[error::max_file_name_length] = '\0';
+ }
+
+ const char*get_truncated_file_name()const{
+ return file_name;
+ }
+
+ void set_file_line(unsigned file_line){
+ this->file_line = file_line;
+ }
+
+ unsigned get_file_line()const{
+ return file_line;
+ }
+
+ char*next_line(){
+ if(data_begin == data_end)
+ return 0;
+
+ ++file_line;
+
+ assert(data_begin < data_end);
+ assert(data_end <= block_len*2);
+
+ if(data_begin >= block_len){
+ std::memcpy(buffer.get(), buffer.get()+block_len, block_len);
+ data_begin -= block_len;
+ data_end -= block_len;
+ if(reader.is_valid())
+ {
+ data_end += reader.finish_read();
+ std::memcpy(buffer.get()+block_len, buffer.get()+2*block_len, block_len);
+ reader.start_read(buffer.get() + 2*block_len, block_len);
+ }
+ }
+
+ int line_end = data_begin;
+ while(buffer[line_end] != '\n' && line_end != data_end){
+ ++line_end;
+ }
+
+ if(line_end - data_begin + 1 > block_len){
+ error::line_length_limit_exceeded err;
+ err.set_file_name(file_name);
+ err.set_file_line(file_line);
+ throw err;
+ }
+
+ if(buffer[line_end] == '\n'){
+ buffer[line_end] = '\0';
+ }else{
+ // some files are missing the newline at the end of the
+ // last line
+ ++data_end;
+ buffer[line_end] = '\0';
+ }
+
+ // handle windows \r\n-line breaks
+ if(line_end != data_begin && buffer[line_end-1] == '\r')
+ buffer[line_end-1] = '\0';
+
+ char*ret = buffer.get() + data_begin;
+ data_begin = line_end+1;
+ return ret;
+ }
+ };
+
+
+ ////////////////////////////////////////////////////////////////////////////
+ // CSV //
+ ////////////////////////////////////////////////////////////////////////////
+
+ namespace error{
+ const int max_column_name_length = 63;
+ struct with_column_name{
+ with_column_name(){
+ std::memset(column_name, 0, max_column_name_length+1);
+ }
+
+ void set_column_name(const char*column_name){
+ std::strncpy(this->column_name, column_name, max_column_name_length);
+ this->column_name[max_column_name_length] = '\0';
+ }
+
+ char column_name[max_column_name_length+1];
+ };
+
+
+ const int max_column_content_length = 63;
+
+ struct with_column_content{
+ with_column_content(){
+ std::memset(column_content, 0, max_column_content_length+1);
+ }
+
+ void set_column_content(const char*column_content){
+ std::strncpy(this->column_content, column_content, max_column_content_length);
+ this->column_content[max_column_content_length] = '\0';
+ }
+
+ char column_content[max_column_content_length+1];
+ };
+
+
+ struct extra_column_in_header :
+ base,
+ with_file_name,
+ with_column_name{
+ void format_error_message()const{
+ std::snprintf(error_message_buffer, sizeof(error_message_buffer),
+ "Extra column \"%s\" in header of file \"%s\"."
+ , column_name, file_name);
+ }
+ };
+
+ struct missing_column_in_header :
+ base,
+ with_file_name,
+ with_column_name{
+ void format_error_message()const{
+ std::snprintf(error_message_buffer, sizeof(error_message_buffer),
+ "Missing column \"%s\" in header of file \"%s\"."
+ , column_name, file_name);
+ }
+ };
+
+ struct duplicated_column_in_header :
+ base,
+ with_file_name,
+ with_column_name{
+ void format_error_message()const{
+ std::snprintf(error_message_buffer, sizeof(error_message_buffer),
+ "Duplicated column \"%s\" in header of file \"%s\"."
+ , column_name, file_name);
+ }
+ };
+
+ struct header_missing :
+ base,
+ with_file_name{
+ void format_error_message()const{
+ std::snprintf(error_message_buffer, sizeof(error_message_buffer),
+ "Header missing in file \"%s\"."
+ , file_name);
+ }
+ };
+
+ struct too_few_columns :
+ base,
+ with_file_name,
+ with_file_line{
+ void format_error_message()const{
+ std::snprintf(error_message_buffer, sizeof(error_message_buffer),
+ "Too few columns in line %d in file \"%s\"."
+ , file_line, file_name);
+ }
+ };
+
+ struct too_many_columns :
+ base,
+ with_file_name,
+ with_file_line{
+ void format_error_message()const{
+ std::snprintf(error_message_buffer, sizeof(error_message_buffer),
+ "Too many columns in line %d in file \"%s\"."
+ , file_line, file_name);
+ }
+ };
+
+ struct escaped_string_not_closed :
+ base,
+ with_file_name,
+ with_file_line{
+ void format_error_message()const{
+ std::snprintf(error_message_buffer, sizeof(error_message_buffer),
+ "Escaped string was not closed in line %d in file \"%s\"."
+ , file_line, file_name);
+ }
+ };
+
+ struct integer_must_be_positive :
+ base,
+ with_file_name,
+ with_file_line,
+ with_column_name,
+ with_column_content{
+ void format_error_message()const{
+ std::snprintf(error_message_buffer, sizeof(error_message_buffer),
+ "The integer \"%s\" must be positive or 0 in column \"%s\" in file \"%s\" in line \"%d\"."
+ , column_content, column_name, file_name, file_line);
+ }
+ };
+
+ struct no_digit :
+ base,
+ with_file_name,
+ with_file_line,
+ with_column_name,
+ with_column_content{
+ void format_error_message()const{
+ std::snprintf(error_message_buffer, sizeof(error_message_buffer),
+ "The integer \"%s\" contains an invalid digit in column \"%s\" in file \"%s\" in line \"%d\"."
+ , column_content, column_name, file_name, file_line);
+ }
+ };
+
+ struct integer_overflow :
+ base,
+ with_file_name,
+ with_file_line,
+ with_column_name,
+ with_column_content{
+ void format_error_message()const{
+ std::snprintf(error_message_buffer, sizeof(error_message_buffer),
+ "The integer \"%s\" overflows in column \"%s\" in file \"%s\" in line \"%d\"."
+ , column_content, column_name, file_name, file_line);
+ }
+ };
+
+ struct integer_underflow :
+ base,
+ with_file_name,
+ with_file_line,
+ with_column_name,
+ with_column_content{
+ void format_error_message()const{
+ std::snprintf(error_message_buffer, sizeof(error_message_buffer),
+ "The integer \"%s\" underflows in column \"%s\" in file \"%s\" in line \"%d\"."
+ , column_content, column_name, file_name, file_line);
+ }
+ };
+
+ struct invalid_single_character :
+ base,
+ with_file_name,
+ with_file_line,
+ with_column_name,
+ with_column_content{
+ void format_error_message()const{
+ std::snprintf(error_message_buffer, sizeof(error_message_buffer),
+ "The content \"%s\" of column \"%s\" in file \"%s\" in line \"%d\" is not a single character."
+ , column_content, column_name, file_name, file_line);
+ }
+ };
+ }
+
+ typedef unsigned ignore_column;
+ static const ignore_column ignore_no_column = 0;
+ static const ignore_column ignore_extra_column = 1;
+ static const ignore_column ignore_missing_column = 2;
+
+ template<char ... trim_char_list>
+ struct trim_chars{
+ private:
+ constexpr static bool is_trim_char(char){
+ return false;
+ }
+
+ template<class ...OtherTrimChars>
+ constexpr static bool is_trim_char(char c, char trim_char, OtherTrimChars...other_trim_chars){
+ return c == trim_char || is_trim_char(c, other_trim_chars...);
+ }
+
+ public:
+ static void trim(char*&str_begin, char*&str_end){
+ while(is_trim_char(*str_begin, trim_char_list...) && str_begin != str_end)
+ ++str_begin;
+ while(is_trim_char(*(str_end-1), trim_char_list...) && str_begin != str_end)
+ --str_end;
+ *str_end = '\0';
+ }
+ };
+
+
+ struct no_comment{
+ static bool is_comment(const char*){
+ return false;
+ }
+ };
+
+ template<char ... comment_start_char_list>
+ struct single_line_comment{
+ private:
+ constexpr static bool is_comment_start_char(char){
+ return false;
+ }
+
+ template<class ...OtherCommentStartChars>
+ constexpr static bool is_comment_start_char(char c, char comment_start_char, OtherCommentStartChars...other_comment_start_chars){
+ return c == comment_start_char || is_comment_start_char(c, other_comment_start_chars...);
+ }
+
+ public:
+
+ static bool is_comment(const char*line){
+ return is_comment_start_char(*line, comment_start_char_list...);
+ }
+ };
+
+ struct empty_line_comment{
+ static bool is_comment(const char*line){
+ if(*line == '\0')
+ return true;
+ while(*line == ' ' || *line == '\t'){
+ ++line;
+ if(*line == 0)
+ return true;
+ }
+ return false;
+ }
+ };
+
+ template<char ... comment_start_char_list>
+ struct single_and_empty_line_comment{
+ static bool is_comment(const char*line){
+ return single_line_comment<comment_start_char_list...>::is_comment(line) || empty_line_comment::is_comment(line);
+ }
+ };
+
+ template<char sep>
+ struct no_quote_escape{
+ static const char*find_next_column_end(const char*col_begin){
+ while(*col_begin != sep && *col_begin != '\0')
+ ++col_begin;
+ return col_begin;
+ }
+
+ static void unescape(char*&, char*&){
+
+ }
+ };
+
+ template<char sep, char quote>
+ struct double_quote_escape{
+ static const char*find_next_column_end(const char*col_begin){
+ while(*col_begin != sep && *col_begin != '\0')
+ if(*col_begin != quote)
+ ++col_begin;
+ else{
+ do{
+ ++col_begin;
+ while(*col_begin != quote){
+ if(*col_begin == '\0')
+ throw error::escaped_string_not_closed();
+ ++col_begin;
+ }
+ ++col_begin;
+ }while(*col_begin == quote);
+ }
+ return col_begin;
+ }
+
+ static void unescape(char*&col_begin, char*&col_end){
+ if(col_end - col_begin >= 2){
+ if(*col_begin == quote && *(col_end-1) == quote){
+ ++col_begin;
+ --col_end;
+ char*out = col_begin;
+ for(char*in = col_begin; in!=col_end; ++in){
+ if(*in == quote && (in+1) != col_end && *(in+1) == quote){
+ ++in;
+ }
+ *out = *in;
+ ++out;
+ }
+ col_end = out;
+ *col_end = '\0';
+ }
+ }
+
+ }
+ };
+
+ struct throw_on_overflow{
+ template<class T>
+ static void on_overflow(T&){
+ throw error::integer_overflow();
+ }
+
+ template<class T>
+ static void on_underflow(T&){
+ throw error::integer_underflow();
+ }
+ };
+
+ struct ignore_overflow{
+ template<class T>
+ static void on_overflow(T&){}
+
+ template<class T>
+ static void on_underflow(T&){}
+ };
+
+ struct set_to_max_on_overflow{
+ template<class T>
+ static void on_overflow(T&x){
+ x = std::numeric_limits<T>::max();
+ }
+
+ template<class T>
+ static void on_underflow(T&x){
+ x = std::numeric_limits<T>::min();
+ }
+ };
+
+
+ namespace detail{
+ template<class quote_policy>
+ void chop_next_column(
+ char*&line, char*&col_begin, char*&col_end
+ ){
+ assert(line != nullptr);
+
+ col_begin = line;
+ // the col_begin + (... - col_begin) removes the constness
+ col_end = col_begin + (quote_policy::find_next_column_end(col_begin) - col_begin);
+
+ if(*col_end == '\0'){
+ line = nullptr;
+ }else{
+ *col_end = '\0';
+ line = col_end + 1;
+ }
+ }
+
+ template<class trim_policy, class quote_policy>
+ void parse_line(
+ char*line,
+ char**sorted_col,
+ const std::vector<int>&col_order
+ ){
+ for(std::size_t i=0; i<col_order.size(); ++i){
+ if(line == nullptr)
+ throw ::io::error::too_few_columns();
+ char*col_begin, *col_end;
+ chop_next_column<quote_policy>(line, col_begin, col_end);
+
+ if(col_order[i] != -1){
+ trim_policy::trim(col_begin, col_end);
+ quote_policy::unescape(col_begin, col_end);
+
+ sorted_col[col_order[i]] = col_begin;
+ }
+ }
+ if(line != nullptr)
+ throw ::io::error::too_many_columns();
+ }
+
+ template<unsigned column_count, class trim_policy, class quote_policy>
+ void parse_header_line(
+ char*line,
+ std::vector<int>&col_order,
+ const std::string*col_name,
+ ignore_column ignore_policy
+ ){
+ col_order.clear();
+
+ bool found[column_count];
+ std::fill(found, found + column_count, false);
+ while(line){
+ char*col_begin,*col_end;
+ chop_next_column<quote_policy>(line, col_begin, col_end);
+
+ trim_policy::trim(col_begin, col_end);
+ quote_policy::unescape(col_begin, col_end);
+
+ for(unsigned i=0; i<column_count; ++i)
+ if(col_begin == col_name[i]){
+ if(found[i]){
+ error::duplicated_column_in_header err;
+ err.set_column_name(col_begin);
+ throw err;
+ }
+ found[i] = true;
+ col_order.push_back(i);
+ col_begin = 0;
+ break;
+ }
+ if(col_begin){
+ if(ignore_policy & ::io::ignore_extra_column)
+ col_order.push_back(-1);
+ else{
+ error::extra_column_in_header err;
+ err.set_column_name(col_begin);
+ throw err;
+ }
+ }
+ }
+ if(!(ignore_policy & ::io::ignore_missing_column)){
+ for(unsigned i=0; i<column_count; ++i){
+ if(!found[i]){
+ error::missing_column_in_header err;
+ err.set_column_name(col_name[i].c_str());
+ throw err;
+ }
+ }
+ }
+ }
+
+ template<class overflow_policy>
+ void parse(char*col, char &x){
+ if(!*col)
+ throw error::invalid_single_character();
+ x = *col;
+ ++col;
+ if(*col)
+ throw error::invalid_single_character();
+ }
+
+ template<class overflow_policy>
+ void parse(char*col, std::string&x){
+ x = col;
+ }
+
+ template<class overflow_policy>
+ void parse(char*col, const char*&x){
+ x = col;
+ }
+
+ template<class overflow_policy>
+ void parse(char*col, char*&x){
+ x = col;
+ }
+
+ template<class overflow_policy, class T>
+ void parse_unsigned_integer(const char*col, T&x){
+ x = 0;
+ while(*col != '\0'){
+ if('0' <= *col && *col <= '9'){
+ T y = *col - '0';
+ if(x > (std::numeric_limits<T>::max()-y)/10){
+ overflow_policy::on_overflow(x);
+ return;
+ }
+ x = 10*x+y;
+ }else
+ throw error::no_digit();
+ ++col;
+ }
+ }
+
+ template<class overflow_policy>void parse(char*col, unsigned char &x)
+ {parse_unsigned_integer<overflow_policy>(col, x);}
+ template<class overflow_policy>void parse(char*col, unsigned short &x)
+ {parse_unsigned_integer<overflow_policy>(col, x);}
+ template<class overflow_policy>void parse(char*col, unsigned int &x)
+ {parse_unsigned_integer<overflow_policy>(col, x);}
+ template<class overflow_policy>void parse(char*col, unsigned long &x)
+ {parse_unsigned_integer<overflow_policy>(col, x);}
+ template<class overflow_policy>void parse(char*col, unsigned long long &x)
+ {parse_unsigned_integer<overflow_policy>(col, x);}
+
+ template<class overflow_policy, class T>
+ void parse_signed_integer(const char*col, T&x){
+ if(*col == '-'){
+ ++col;
+
+ x = 0;
+ while(*col != '\0'){
+ if('0' <= *col && *col <= '9'){
+ T y = *col - '0';
+ if(x < (std::numeric_limits<T>::min()+y)/10){
+ overflow_policy::on_underflow(x);
+ return;
+ }
+ x = 10*x-y;
+ }else
+ throw error::no_digit();
+ ++col;
+ }
+ return;
+ }else if(*col == '+')
+ ++col;
+ parse_unsigned_integer<overflow_policy>(col, x);
+ }
+
+ template<class overflow_policy>void parse(char*col, signed char &x)
+ {parse_signed_integer<overflow_policy>(col, x);}
+ template<class overflow_policy>void parse(char*col, signed short &x)
+ {parse_signed_integer<overflow_policy>(col, x);}
+ template<class overflow_policy>void parse(char*col, signed int &x)
+ {parse_signed_integer<overflow_policy>(col, x);}
+ template<class overflow_policy>void parse(char*col, signed long &x)
+ {parse_signed_integer<overflow_policy>(col, x);}
+ template<class overflow_policy>void parse(char*col, signed long long &x)
+ {parse_signed_integer<overflow_policy>(col, x);}
+
+ template<class T>
+ void parse_float(const char*col, T&x){
+ bool is_neg = false;
+ if(*col == '-'){
+ is_neg = true;
+ ++col;
+ }else if(*col == '+')
+ ++col;
+
+ x = 0;
+ while('0' <= *col && *col <= '9'){
+ int y = *col - '0';
+ x *= 10;
+ x += y;
+ ++col;
+ }
+
+ if(*col == '.'|| *col == ','){
+ ++col;
+ T pos = 1;
+ while('0' <= *col && *col <= '9'){
+ pos /= 10;
+ int y = *col - '0';
+ ++col;
+ x += y*pos;
+ }
+ }
+
+ if(*col == 'e' || *col == 'E'){
+ ++col;
+ int e;
+
+ parse_signed_integer<set_to_max_on_overflow>(col, e);
+
+ if(e != 0){
+ T base;
+ if(e < 0){
+ base = 0.1;
+ e = -e;
+ }else{
+ base = 10;
+ }
+
+ while(e != 1){
+ if((e & 1) == 0){
+ base = base*base;
+ e >>= 1;
+ }else{
+ x *= base;
+ --e;
+ }
+ }
+ x *= base;
+ }
+ }else{
+ if(*col != '\0')
+ throw error::no_digit();
+ }
+
+ if(is_neg)
+ x = -x;
+ }
+
+ template<class overflow_policy> void parse(char*col, float&x) { parse_float(col, x); }
+ template<class overflow_policy> void parse(char*col, double&x) { parse_float(col, x); }
+ template<class overflow_policy> void parse(char*col, long double&x) { parse_float(col, x); }
+
+ template<class overflow_policy, class T>
+ void parse(char*col, T&x){
+ // Mute unused variable compiler warning
+ (void)col;
+ (void)x;
+ // GCC evalutes "false" when reading the template and
+ // "sizeof(T)!=sizeof(T)" only when instantiating it. This is why
+ // this strange construct is used.
+ static_assert(sizeof(T)!=sizeof(T),
+ "Can not parse this type. Only buildin integrals, floats, char, char*, const char* and std::string are supported");
+ }
+
+ }
+
+ template<unsigned column_count,
+ class trim_policy = trim_chars<' ', '\t'>,
+ class quote_policy = no_quote_escape<','>,
+ class overflow_policy = throw_on_overflow,
+ class comment_policy = no_comment
+ >
+ class CSVReader{
+ private:
+ LineReader in;
+
+ char*(row[column_count]);
+ std::string column_names[column_count];
+
+ std::vector<int>col_order;
+
+ template<class ...ColNames>
+ void set_column_names(std::string s, ColNames...cols){
+ column_names[column_count-sizeof...(ColNames)-1] = std::move(s);
+ set_column_names(std::forward<ColNames>(cols)...);
+ }
+
+ void set_column_names(){}
+
+
+ public:
+ CSVReader() = delete;
+ CSVReader(const CSVReader&) = delete;
+ CSVReader&operator=(const CSVReader&);
+
+ template<class ...Args>
+ explicit CSVReader(Args&&...args):in(std::forward<Args>(args)...){
+ std::fill(row, row+column_count, nullptr);
+ col_order.resize(column_count);
+ for(unsigned i=0; i<column_count; ++i)
+ col_order[i] = i;
+ for(unsigned i=1; i<=column_count; ++i)
+ column_names[i-1] = "col"+std::to_string(i);
+ }
+
+ char*next_line(){
+ return in.next_line();
+ }
+
+ template<class ...ColNames>
+ void read_header(ignore_column ignore_policy, ColNames...cols){
+ static_assert(sizeof...(ColNames)>=column_count, "not enough column names specified");
+ static_assert(sizeof...(ColNames)<=column_count, "too many column names specified");
+ try{
+ set_column_names(std::forward<ColNames>(cols)...);
+
+ char*line;
+ do{
+ line = in.next_line();
+ if(!line)
+ throw error::header_missing();
+ }while(comment_policy::is_comment(line));
+
+ detail::parse_header_line
+ <column_count, trim_policy, quote_policy>
+ (line, col_order, column_names, ignore_policy);
+ }catch(error::with_file_name&err){
+ err.set_file_name(in.get_truncated_file_name());
+ throw;
+ }
+ }
+
+ template<class ...ColNames>
+ void set_header(ColNames...cols){
+ static_assert(sizeof...(ColNames)>=column_count,
+ "not enough column names specified");
+ static_assert(sizeof...(ColNames)<=column_count,
+ "too many column names specified");
+ set_column_names(std::forward<ColNames>(cols)...);
+ std::fill(row, row+column_count, nullptr);
+ col_order.resize(column_count);
+ for(unsigned i=0; i<column_count; ++i)
+ col_order[i] = i;
+ }
+
+ bool has_column(const std::string&name) const {
+ return col_order.end() != std::find(
+ col_order.begin(), col_order.end(),
+ std::find(std::begin(column_names), std::end(column_names), name)
+ - std::begin(column_names));
+ }
+
+ void set_file_name(const std::string&file_name){
+ in.set_file_name(file_name);
+ }
+
+ void set_file_name(const char*file_name){
+ in.set_file_name(file_name);
+ }
+
+ const char*get_truncated_file_name()const{
+ return in.get_truncated_file_name();
+ }
+
+ void set_file_line(unsigned file_line){
+ in.set_file_line(file_line);
+ }
+
+ unsigned get_file_line()const{
+ return in.get_file_line();
+ }
+
+ private:
+ void parse_helper(std::size_t){}
+
+ template<class T, class ...ColType>
+ void parse_helper(std::size_t r, T&t, ColType&...cols){
+ if(row[r]){
+ try{
+ try{
+ ::io::detail::parse<overflow_policy>(row[r], t);
+ }catch(error::with_column_content&err){
+ err.set_column_content(row[r]);
+ throw;
+ }
+ }catch(error::with_column_name&err){
+ err.set_column_name(column_names[r].c_str());
+ throw;
+ }
+ }
+ parse_helper(r+1, cols...);
+ }
+
+
+ public:
+ template<class ...ColType>
+ bool read_row(ColType& ...cols){
+ static_assert(sizeof...(ColType)>=column_count,
+ "not enough columns specified");
+ static_assert(sizeof...(ColType)<=column_count,
+ "too many columns specified");
+ try{
+ try{
+
+ char*line;
+ do{
+ line = in.next_line();
+ if(!line)
+ return false;
+ }while(comment_policy::is_comment(line));
+
+ detail::parse_line<trim_policy, quote_policy>
+ (line, row, col_order);
+
+ parse_helper(0, cols...);
+ }catch(error::with_file_name&err){
+ err.set_file_name(in.get_truncated_file_name());
+ throw;
+ }
+ }catch(error::with_file_line&err){
+ err.set_file_line(in.get_file_line());
+ throw;
+ }
+
+ return true;
+ }
+ };
+}
+#endif
+
diff --git a/src/Http.hpp b/src/Http.hpp
new file mode 100644
index 0000000..1058564
--- /dev/null
+++ b/src/Http.hpp
@@ -0,0 +1,1308 @@
+//
+// httplib.h
+//
+// Copyright (c) 2017 Yuji Hirose. All rights reserved.
+// The Boost Software License 1.0
+//
+
+#ifndef _CPPHTTPLIB_HTTPLIB_H_
+#define _CPPHTTPLIB_HTTPLIB_H_
+
+#ifdef _MSC_VER
+#define _CRT_SECURE_NO_WARNINGS
+#define _CRT_NONSTDC_NO_DEPRECATE
+
+#if (_MSC_VER < 1900)
+#define snprintf _snprintf_s
+#endif
+
+#define S_ISREG(m) (((m)&S_IFREG)==S_IFREG)
+#define S_ISDIR(m) (((m)&S_IFDIR)==S_IFDIR)
+
+#include <fcntl.h>
+#include <io.h>
+#include <winsock2.h>
+#include <ws2tcpip.h>
+
+#undef min
+#undef max
+
+typedef SOCKET socket_t;
+#else
+#ifndef __MINGW32__
+#include <netdb.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <sys/socket.h>
+#else
+#undef _WINSOCKAPI_
+#include <winsock2.h>
+#include <windows.h>
+#include <ws2tcpip.h>
+#endif
+#include <pthread.h>
+#include <unistd.h>
+#include <cstring>
+#include <signal.h>
+
+typedef int socket_t;
+#endif
+
+#include <fstream>
+#include <functional>
+#include <map>
+#include <memory>
+#include <regex>
+#include <string>
+#include <sys/stat.h>
+#include <assert.h>
+
+#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
+#include <openssl/ssl.h>
+#endif
+
+namespace httplib
+{
+
+typedef std::map<std::string, std::string> Map;
+typedef std::multimap<std::string, std::string> MultiMap;
+typedef std::smatch Match;
+
+struct Request {
+ std::string method;
+ std::string path;
+ MultiMap headers;
+ std::string body;
+ Map params;
+ Match matches;
+
+ bool has_header(const char* key) const;
+ std::string get_header_value(const char* key) const;
+ void set_header(const char* key, const char* val);
+
+ bool has_param(const char* key) const;
+};
+
+struct Response {
+ int status;
+ MultiMap headers;
+ std::string body;
+
+ bool has_header(const char* key) const;
+ std::string get_header_value(const char* key) const;
+ void set_header(const char* key, const char* val);
+
+ void set_redirect(const char* url);
+ void set_content(const char* s, size_t n, const char* content_type);
+ void set_content(const std::string& s, const char* content_type);
+
+ Response() : status(-1) {}
+};
+
+class Stream {
+public:
+ virtual ~Stream() {}
+ virtual int read(char* ptr, size_t size) = 0;
+ virtual int write(const char* ptr, size_t size1) = 0;
+ virtual int write(const char* ptr) = 0;
+};
+
+class SocketStream : public Stream {
+public:
+ SocketStream(socket_t sock);
+ virtual ~SocketStream();
+
+ virtual int read(char* ptr, size_t size);
+ virtual int write(const char* ptr, size_t size);
+ virtual int write(const char* ptr);
+
+private:
+ socket_t sock_;
+};
+
+class Server {
+public:
+ typedef std::function<void (const Request&, Response&)> Handler;
+ typedef std::function<void (const Request&, const Response&)> Logger;
+
+ Server();
+ virtual ~Server();
+
+ void get(const char* pattern, Handler handler);
+ void post(const char* pattern, Handler handler);
+
+ bool set_base_dir(const char* path);
+
+ void set_error_handler(Handler handler);
+ void set_logger(Logger logger);
+
+ bool listen(const char* host, int port, int socket_flags = 0);
+ void stop();
+
+protected:
+ void process_request(Stream& strm);
+
+private:
+ typedef std::vector<std::pair<std::regex, Handler>> Handlers;
+
+ bool routing(Request& req, Response& res);
+ bool handle_file_request(Request& req, Response& res);
+ bool dispatch_request(Request& req, Response& res, Handlers& handlers);
+
+ bool read_request_line(Stream& strm, Request& req);
+
+ virtual bool read_and_close_socket(socket_t sock);
+
+ socket_t svr_sock_;
+ std::string base_dir_;
+ Handlers get_handlers_;
+ Handlers post_handlers_;
+ Handler error_handler_;
+ Logger logger_;
+};
+
+class Client {
+public:
+ Client(const char* host, int port);
+ virtual ~Client();
+
+ std::shared_ptr<Response> get(const char* path);
+ std::shared_ptr<Response> head(const char* path);
+ std::shared_ptr<Response> post(const char* path, const std::string& body, const char* content_type);
+ std::shared_ptr<Response> post(const char* path, const Map& params);
+
+ bool send(const Request& req, Response& res);
+
+protected:
+ bool process_request(Stream& strm, const Request& req, Response& res);
+
+ const std::string host_;
+ const int port_;
+ const std::string host_and_port_;
+
+private:
+ bool read_response_line(Stream& strm, Response& res);
+ void add_default_headers(Request& req);
+
+ virtual bool read_and_close_socket(socket_t sock, const Request& req, Response& res);
+};
+
+#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
+class SSLSocketStream : public Stream {
+public:
+ SSLSocketStream(SSL* ssl);
+ virtual ~SSLSocketStream();
+
+ virtual int read(char* ptr, size_t size);
+ virtual int write(const char* ptr, size_t size);
+ virtual int write(const char* ptr);
+
+private:
+ SSL* ssl_;
+};
+
+class SSLServer : public Server {
+public:
+ SSLServer(const char* cert_path, const char* private_key_path);
+ virtual ~SSLServer();
+
+private:
+ virtual bool read_and_close_socket(socket_t sock);
+
+ SSL_CTX* ctx_;
+};
+
+class SSLClient : public Client {
+public:
+ SSLClient(const char* host, int port);
+ virtual ~SSLClient();
+
+private:
+ virtual bool read_and_close_socket(socket_t sock, const Request& req, Response& res);
+
+ SSL_CTX* ctx_;
+};
+#endif
+
+/*
+ * Implementation
+ */
+namespace detail {
+
+template <class Fn>
+void split(const char* b, const char* e, char d, Fn fn)
+{
+ int i = 0;
+ int beg = 0;
+
+ while (e ? (b + i != e) : (b[i] != '\0')) {
+ if (b[i] == d) {
+ fn(&b[beg], &b[i]);
+ beg = i + 1;
+ }
+ i++;
+ }
+
+ if (i) {
+ fn(&b[beg], &b[i]);
+ }
+}
+
+inline bool socket_gets(Stream& strm, char* buf, int bufsiz)
+{
+ // TODO: buffering for better performance
+ size_t i = 0;
+
+ for (;;) {
+ char byte;
+ auto n = strm.read(&byte, 1);
+
+ if (n < 1) {
+ if (i == 0) {
+ return false;
+ } else {
+ break;
+ }
+ }
+
+ buf[i++] = byte;
+
+ if (byte == '\n') {
+ break;
+ }
+ }
+
+ buf[i] = '\0';
+ return true;
+}
+
+template <typename ...Args>
+inline void socket_printf(Stream& strm, const char* fmt, const Args& ...args)
+{
+ char buf[BUFSIZ];
+ auto n = snprintf(buf, BUFSIZ, fmt, args...);
+ if (n > 0) {
+ if (n >= BUFSIZ) {
+ // TODO: buffer size is not large enough...
+ } else {
+ strm.write(buf, n);
+ }
+ }
+}
+
+inline int close_socket(socket_t sock)
+{
+#if defined(_MSC_VER) || defined(__MINGW32__)
+ return closesocket(sock);
+#else
+ return close(sock);
+#endif
+}
+
+template <typename T>
+inline bool read_and_close_socket(socket_t sock, T callback)
+{
+ SocketStream strm(sock);
+ auto ret = callback(strm);
+ close_socket(sock);
+ return ret;
+}
+
+inline int shutdown_socket(socket_t sock)
+{
+#if defined(_MSC_VER) || defined(__MINGW32__)
+ return shutdown(sock, SD_BOTH);
+#else
+ return shutdown(sock, SHUT_RDWR);
+#endif
+}
+
+template <typename Fn>
+socket_t create_socket(const char* host, int port, Fn fn, int socket_flags = 0)
+{
+#if defined(_MSC_VER) || defined(__MINGW32__)
+#ifndef SO_OPENTYPE
+#define SO_OPENTYPE 0x7008
+#endif
+#ifndef SO_SYNCHRONOUS_NONALERT
+#define SO_SYNCHRONOUS_NONALERT 0x20
+#endif
+ int opt = SO_SYNCHRONOUS_NONALERT;
+ setsockopt(INVALID_SOCKET, SOL_SOCKET, SO_OPENTYPE, (char*)&opt, sizeof(opt));
+#endif
+
+ // Get address info
+ struct addrinfo hints;
+ struct addrinfo *result;
+
+ memset(&hints, 0, sizeof(struct addrinfo));
+ hints.ai_family = AF_UNSPEC;
+ hints.ai_socktype = SOCK_STREAM;
+ hints.ai_flags = socket_flags;
+ hints.ai_protocol = 0;
+
+ auto service = std::to_string(port);
+
+ if (getaddrinfo(host, service.c_str(), &hints, &result)) {
+ return -1;
+ }
+
+ for (auto rp = result; rp; rp = rp->ai_next) {
+ // Create a socket
+ auto sock = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
+ if ((int)sock == -1) {
+ continue;
+ }
+
+ // Make 'reuse address' option available
+ int yes = 1;
+ setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (char*)&yes, sizeof(yes));
+
+ // bind or connect
+ if (fn(sock, *rp)) {
+ freeaddrinfo(result);
+ return sock;
+ }
+
+ close_socket(sock);
+ }
+
+ freeaddrinfo(result);
+ return -1;
+}
+
+inline socket_t create_server_socket(const char* host, int port, int socket_flags)
+{
+ return create_socket(host, port, [](socket_t sock, struct addrinfo& ai) -> socket_t {
+ if (::bind(sock, ai.ai_addr, ai.ai_addrlen)) {
+ return false;
+ }
+ if (listen(sock, 5)) { // Listen through 5 channels
+ return false;
+ }
+ return true;
+ }, socket_flags);
+}
+
+inline socket_t create_client_socket(const char* host, int port)
+{
+ return create_socket(host, port, [](socket_t sock, struct addrinfo& ai) -> socket_t {
+ if (connect(sock, ai.ai_addr, ai.ai_addrlen)) {
+ return false;
+ }
+ return true;
+ });
+}
+
+inline bool is_file(const std::string& s)
+{
+ struct stat st;
+ return stat(s.c_str(), &st) >= 0 && S_ISREG(st.st_mode);
+}
+
+inline bool is_dir(const std::string& s)
+{
+ struct stat st;
+ return stat(s.c_str(), &st) >= 0 && S_ISDIR(st.st_mode);
+}
+
+inline void read_file(const std::string& path, std::string& out)
+{
+ std::ifstream fs(path, std::ios_base::binary);
+ fs.seekg(0, std::ios_base::end);
+ auto size = fs.tellg();
+ fs.seekg(0);
+ out.resize(static_cast<size_t>(size));
+ fs.read(&out[0], size);
+}
+
+inline std::string file_extension(const std::string& path)
+{
+ std::smatch m;
+ auto pat = std::regex("\\.([a-zA-Z0-9]+)$");
+ if (std::regex_search(path, m, pat)) {
+ return m[1].str();
+ }
+ return std::string();
+}
+
+inline const char* content_type(const std::string& path)
+{
+ auto ext = detail::file_extension(path);
+ if (ext == "txt") {
+ return "text/plain";
+ } else if (ext == "html") {
+ return "text/html";
+ } else if (ext == "js") {
+ return "text/javascript";
+ } else if (ext == "css") {
+ return "text/css";
+ } else if (ext == "xml") {
+ return "text/xml";
+ } else if (ext == "jpeg" || ext == "jpg") {
+ return "image/jpg";
+ } else if (ext == "png") {
+ return "image/png";
+ } else if (ext == "gif") {
+ return "image/gif";
+ } else if (ext == "svg") {
+ return "image/svg+xml";
+ } else if (ext == "ico") {
+ return "image/x-icon";
+ } else if (ext == "json") {
+ return "application/json";
+ } else if (ext == "pdf") {
+ return "application/pdf";
+ } else if (ext == "xhtml") {
+ return "application/xhtml+xml";
+ }
+ return nullptr;
+}
+
+inline const char* status_message(int status)
+{
+ switch (status) {
+ case 200: return "OK";
+ case 400: return "Bad Request";
+ case 404: return "Not Found";
+ default:
+ case 500: return "Internal Server Error";
+ }
+}
+
+inline const char* get_header_value(const MultiMap& map, const char* key, const char* def)
+{
+ auto it = map.find(key);
+ if (it != map.end()) {
+ return it->second.c_str();
+ }
+ return def;
+}
+
+inline int get_header_value_int(const MultiMap& map, const char* key, int def)
+{
+ auto it = map.find(key);
+ if (it != map.end()) {
+ return std::stoi(it->second);
+ }
+ return def;
+}
+
+inline bool read_headers(Stream& strm, MultiMap& headers)
+{
+ static std::regex re("(.+?): (.+?)\r\n");
+
+ const auto BUFSIZ_HEADER = 2048;
+ char buf[BUFSIZ_HEADER];
+
+ for (;;) {
+ if (!socket_gets(strm, buf, BUFSIZ_HEADER)) {
+ return false;
+ }
+ if (!strcmp(buf, "\r\n")) {
+ break;
+ }
+ std::cmatch m;
+ if (std::regex_match(buf, m, re)) {
+ auto key = std::string(m[1]);
+ auto val = std::string(m[2]);
+ headers.insert(std::make_pair(key, val));
+ }
+ }
+
+ return true;
+}
+
+template <typename T>
+bool read_content(Stream& strm, T& x, bool allow_no_content_length)
+{
+ auto len = get_header_value_int(x.headers, "Content-Length", 0);
+ if (len) {
+ x.body.assign(len, 0);
+ auto r = 0;
+ while (r < len){
+ auto r_incr = strm.read(&x.body[r], len - r);
+ if (r_incr <= 0) {
+ return false;
+ }
+ r += r_incr;
+ }
+ } else if (allow_no_content_length) {
+ for (;;) {
+ char byte;
+ auto n = strm.read(&byte, 1);
+ if (n < 1) {
+ if (x.body.size() == 0) {
+ return true; // no body
+ } else {
+ break;
+ }
+ }
+ x.body += byte;
+ }
+ }
+ return true;
+}
+
+template <typename T>
+inline void write_headers(Stream& strm, const T& res)
+{
+ strm.write("Connection: close\r\n");
+
+ for (const auto& x: res.headers) {
+ if (x.first != "Content-Type" && x.first != "Content-Length") {
+ socket_printf(strm, "%s: %s\r\n", x.first.c_str(), x.second.c_str());
+ }
+ }
+
+ auto t = get_header_value(res.headers, "Content-Type", "text/plain");
+ socket_printf(strm, "Content-Type: %s\r\n", t);
+ socket_printf(strm, "Content-Length: %ld\r\n", res.body.size());
+ strm.write("\r\n");
+}
+
+inline void write_response(Stream& strm, const Request& req, const Response& res)
+{
+ socket_printf(strm, "HTTP/1.0 %d %s\r\n", res.status, status_message(res.status));
+
+ write_headers(strm, res);
+
+ if (!res.body.empty() && req.method != "HEAD") {
+ strm.write(res.body.c_str(), res.body.size());
+ }
+}
+
+inline std::string encode_url(const std::string& s)
+{
+ std::string result;
+
+ for (auto i = 0; s[i]; i++) {
+ switch (s[i]) {
+ case ' ': result += "+"; break;
+ case '\'': result += "%27"; break;
+ case ',': result += "%2C"; break;
+ case ':': result += "%3A"; break;
+ case ';': result += "%3B"; break;
+ default:
+ if (s[i] < 0) {
+ result += '%';
+ char hex[4];
+ size_t len = snprintf(hex, sizeof(hex), "%02X", (unsigned char)s[i]);
+ assert(len == 2);
+ result.append(hex, len);
+ } else {
+ result += s[i];
+ }
+ break;
+ }
+ }
+
+ return result;
+}
+
+inline bool is_hex(char c, int& v)
+{
+ if (0x20 <= c && isdigit(c)) {
+ v = c - '0';
+ return true;
+ } else if ('A' <= c && c <= 'F') {
+ v = c - 'A' + 10;
+ return true;
+ } else if ('a' <= c && c <= 'f') {
+ v = c - 'a' + 10;
+ return true;
+ }
+ return false;
+}
+
+inline int from_hex_to_i(const std::string& s, int i, int cnt, int& val)
+{
+ val = 0;
+ for (; s[i] && cnt; i++, cnt--) {
+ int v = 0;
+ if (is_hex(s[i], v)) {
+ val = val * 16 + v;
+ } else {
+ break;
+ }
+ }
+ return --i;
+}
+
+inline size_t to_utf8(int code, char* buff)
+{
+ if (code < 0x0080) {
+ buff[0] = (code & 0x7F);
+ return 1;
+ } else if (code < 0x0800) {
+ buff[0] = (0xC0 | ((code >> 6) & 0x1F));
+ buff[1] = (0x80 | (code & 0x3F));
+ return 2;
+ } else if (code < 0xD800) {
+ buff[0] = (0xE0 | ((code >> 12) & 0xF));
+ buff[1] = (0x80 | ((code >> 6) & 0x3F));
+ buff[2] = (0x80 | (code & 0x3F));
+ return 3;
+ } else if (code < 0xE000) { // D800 - DFFF is invalid...
+ return 0;
+ } else if (code < 0x10000) {
+ buff[0] = (0xE0 | ((code >> 12) & 0xF));
+ buff[1] = (0x80 | ((code >> 6) & 0x3F));
+ buff[2] = (0x80 | (code & 0x3F));
+ return 3;
+ } else if (code < 0x110000) {
+ buff[0] = (0xF0 | ((code >> 18) & 0x7));
+ buff[1] = (0x80 | ((code >> 12) & 0x3F));
+ buff[2] = (0x80 | ((code >> 6) & 0x3F));
+ buff[3] = (0x80 | (code & 0x3F));
+ return 4;
+ }
+
+ // NOTREACHED
+ return 0;
+}
+
+inline std::string decode_url(const std::string& s)
+{
+ std::string result;
+
+ for (int i = 0; s[i]; i++) {
+ if (s[i] == '%') {
+ i++;
+ assert(s[i]);
+
+ if (s[i] == '%') {
+ result += s[i];
+ } else if (s[i] == 'u') {
+ // Unicode
+ i++;
+ assert(s[i]);
+
+ int val = 0;
+ i = from_hex_to_i(s, i, 4, val);
+
+ char buff[4];
+ size_t len = to_utf8(val, buff);
+
+ if (len > 0) {
+ result.append(buff, len);
+ }
+ } else {
+ // HEX
+ int val = 0;
+ i = from_hex_to_i(s, i, 2, val);
+ result += val;
+ }
+ } else if (s[i] == '+') {
+ result += ' ';
+ } else {
+ result += s[i];
+ }
+ }
+
+ return result;
+}
+
+inline void write_request(Stream& strm, const Request& req)
+{
+ auto path = encode_url(req.path);
+ socket_printf(strm, "%s %s HTTP/1.0\r\n", req.method.c_str(), path.c_str());
+
+ write_headers(strm, req);
+
+ if (!req.body.empty()) {
+ if (req.has_header("application/x-www-form-urlencoded")) {
+ auto str = encode_url(req.body);
+ strm.write(str.c_str(), str.size());
+ } else {
+ strm.write(req.body.c_str(), req.body.size());
+ }
+ }
+}
+
+inline void parse_query_text(const std::string& s, Map& params)
+{
+ split(&s[0], &s[s.size()], '&', [&](const char* b, const char* e) {
+ std::string key;
+ std::string val;
+ split(b, e, '=', [&](const char* b, const char* e) {
+ if (key.empty()) {
+ key.assign(b, e);
+ } else {
+ val.assign(b, e);
+ }
+ });
+ params[key] = detail::decode_url(val);
+ });
+}
+
+#if defined(_MSC_VER) || defined(__MINGW32__)
+class WSInit {
+public:
+ WSInit() {
+ WSADATA wsaData;
+ WSAStartup(0x0002, &wsaData);
+ }
+
+ ~WSInit() {
+ WSACleanup();
+ }
+};
+
+static WSInit wsinit_;
+#endif
+
+} // namespace detail
+
+// Request implementation
+inline bool Request::has_header(const char* key) const
+{
+ return headers.find(key) != headers.end();
+}
+
+inline std::string Request::get_header_value(const char* key) const
+{
+ return detail::get_header_value(headers, key, "");
+}
+
+inline void Request::set_header(const char* key, const char* val)
+{
+ headers.insert(std::make_pair(key, val));
+}
+
+inline bool Request::has_param(const char* key) const
+{
+ return params.find(key) != params.end();
+}
+
+// Response implementation
+inline bool Response::has_header(const char* key) const
+{
+ return headers.find(key) != headers.end();
+}
+
+inline std::string Response::get_header_value(const char* key) const
+{
+ return detail::get_header_value(headers, key, "");
+}
+
+inline void Response::set_header(const char* key, const char* val)
+{
+ headers.insert(std::make_pair(key, val));
+}
+
+inline void Response::set_redirect(const char* url)
+{
+ set_header("Location", url);
+ status = 302;
+}
+
+inline void Response::set_content(const char* s, size_t n, const char* content_type)
+{
+ body.assign(s, n);
+ set_header("Content-Type", content_type);
+}
+
+inline void Response::set_content(const std::string& s, const char* content_type)
+{
+ body = s;
+ set_header("Content-Type", content_type);
+}
+
+// Socket stream implementation
+inline SocketStream::SocketStream(socket_t sock): sock_(sock)
+{
+}
+
+inline SocketStream::~SocketStream()
+{
+}
+
+inline int SocketStream::read(char* ptr, size_t size)
+{
+ return recv(sock_, ptr, size, 0);
+}
+
+inline int SocketStream::write(const char* ptr, size_t size)
+{
+ return send(sock_, ptr, size, 0);
+}
+
+inline int SocketStream::write(const char* ptr)
+{
+ return write(ptr, strlen(ptr));
+}
+
+// HTTP server implementation
+inline Server::Server()
+ : svr_sock_(-1)
+{
+#if !defined(_MSC_VER) && !defined(__MINGW32__)
+ signal(SIGPIPE, SIG_IGN);
+#endif
+}
+
+inline Server::~Server()
+{
+}
+
+inline void Server::get(const char* pattern, Handler handler)
+{
+ get_handlers_.push_back(std::make_pair(std::regex(pattern), handler));
+}
+
+inline void Server::post(const char* pattern, Handler handler)
+{
+ post_handlers_.push_back(std::make_pair(std::regex(pattern), handler));
+}
+
+inline bool Server::set_base_dir(const char* path)
+{
+ if (detail::is_dir(path)) {
+ base_dir_ = path;
+ return true;
+ }
+ return false;
+}
+
+inline void Server::set_error_handler(Handler handler)
+{
+ error_handler_ = handler;
+}
+
+inline void Server::set_logger(Logger logger)
+{
+ logger_ = logger;
+}
+
+inline bool Server::listen(const char* host, int port, int socket_flags)
+{
+ svr_sock_ = detail::create_server_socket(host, port, socket_flags);
+ if (svr_sock_ == -1) {
+ return false;
+ }
+
+ auto ret = true;
+
+ for (;;) {
+ socket_t sock = accept(svr_sock_, NULL, NULL);
+
+ if (sock == -1) {
+ if (svr_sock_ != -1) {
+ detail::close_socket(svr_sock_);
+ ret = false;
+ } else {
+ ; // The server socket was closed by user.
+ }
+ break;
+ }
+
+ // TODO: should be async
+ read_and_close_socket(sock);
+ }
+
+ return ret;
+}
+
+inline void Server::stop()
+{
+ detail::shutdown_socket(svr_sock_);
+ detail::close_socket(svr_sock_);
+ svr_sock_ = -1;
+}
+
+inline bool Server::read_request_line(Stream& strm, Request& req)
+{
+ const auto BUFSIZ_REQUESTLINE = 2048;
+ char buf[BUFSIZ_REQUESTLINE];
+ if (!detail::socket_gets(strm, buf, BUFSIZ_REQUESTLINE)) {
+ return false;
+ }
+
+ static std::regex re("(GET|HEAD|POST) ([^?]+)(?:\\?(.+?))? HTTP/1\\.[01]\r\n");
+
+ std::cmatch m;
+ if (std::regex_match(buf, m, re)) {
+ req.method = std::string(m[1]);
+ req.path = detail::decode_url(m[2]);
+
+ // Parse query text
+ auto len = std::distance(m[3].first, m[3].second);
+ if (len > 0) {
+ detail::parse_query_text(m[3], req.params);
+ }
+
+ return true;
+ }
+
+ return false;
+}
+
+inline bool Server::handle_file_request(Request& req, Response& res)
+{
+ if (!base_dir_.empty()) {
+ std::string path = base_dir_ + req.path;
+
+ if (!path.empty() && path.back() == '/') {
+ path += "index.html";
+ }
+
+ if (detail::is_file(path)) {
+ detail::read_file(path, res.body);
+ auto type = detail::content_type(path);
+ if (type) {
+ res.set_header("Content-Type", type);
+ }
+ res.status = 200;
+ return true;
+ }
+ }
+
+ return false;
+}
+
+inline bool Server::routing(Request& req, Response& res)
+{
+ if (req.method == "GET" && handle_file_request(req, res)) {
+ return true;
+ }
+
+ if (req.method == "GET" || req.method == "HEAD") {
+ return dispatch_request(req, res, get_handlers_);
+ } else if (req.method == "POST") {
+ return dispatch_request(req, res, post_handlers_);
+ }
+ return false;
+}
+
+inline bool Server::dispatch_request(Request& req, Response& res, Handlers& handlers)
+{
+ for (const auto& x: handlers) {
+ const auto& pattern = x.first;
+ const auto& handler = x.second;
+
+ if (std::regex_match(req.path, req.matches, pattern)) {
+ handler(req, res);
+ return true;
+ }
+ }
+ return false;
+}
+
+inline void Server::process_request(Stream& strm)
+{
+ Request req;
+ Response res;
+
+ if (!read_request_line(strm, req) ||
+ !detail::read_headers(strm, req.headers)) {
+ // TODO:
+ return;
+ }
+
+ if (req.method == "POST") {
+ if (!detail::read_content(strm, req, false)) {
+ // TODO:
+ return;
+ }
+ static std::string type = "application/x-www-form-urlencoded";
+ if (!req.get_header_value("Content-Type").compare(0, type.size(), type)) {
+ detail::parse_query_text(req.body, req.params);
+ }
+ }
+
+ if (routing(req, res)) {
+ if (res.status == -1) {
+ res.status = 200;
+ }
+ } else {
+ res.status = 404;
+ }
+ assert(res.status != -1);
+
+ if (400 <= res.status && error_handler_) {
+ error_handler_(req, res);
+ }
+
+ detail::write_response(strm, req, res);
+
+ if (logger_) {
+ logger_(req, res);
+ }
+}
+
+inline bool Server::read_and_close_socket(socket_t sock)
+{
+ return detail::read_and_close_socket(sock, [this](Stream& strm) {
+ process_request(strm);
+ return true;
+ });
+}
+
+// HTTP client implementation
+inline Client::Client(const char* host, int port)
+ : host_(host)
+ , port_(port)
+ , host_and_port_(host_ + ":" + std::to_string(port_))
+{
+}
+
+inline Client::~Client()
+{
+}
+
+inline bool Client::read_response_line(Stream& strm, Response& res)
+{
+ const auto BUFSIZ_RESPONSELINE = 2048;
+ char buf[BUFSIZ_RESPONSELINE];
+ if (!detail::socket_gets(strm, buf, BUFSIZ_RESPONSELINE)) {
+ return false;
+ }
+
+ const static std::regex re("HTTP/1\\.[01] (\\d+?) .+\r\n");
+
+ std::cmatch m;
+ if (std::regex_match(buf, m, re)) {
+ res.status = std::stoi(std::string(m[1]));
+ }
+
+ return true;
+}
+
+inline bool Client::send(const Request& req, Response& res)
+{
+ auto sock = detail::create_client_socket(host_.c_str(), port_);
+ if (sock == -1) {
+ return false;
+ }
+
+ return read_and_close_socket(sock, req, res);
+}
+
+inline bool Client::process_request(Stream& strm, const Request& req, Response& res)
+{
+ // Send request
+ detail::write_request(strm, req);
+
+ // Receive response
+ if (!read_response_line(strm, res) ||
+ !detail::read_headers(strm, res.headers)) {
+ return false;
+ }
+ if (req.method != "HEAD") {
+ if (!detail::read_content(strm, res, true)) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+inline bool Client::read_and_close_socket(socket_t sock, const Request& req, Response& res)
+{
+ return detail::read_and_close_socket(sock, [&](Stream& strm) {
+ return process_request(strm, req, res);
+ });
+}
+
+inline void Client::add_default_headers(Request& req)
+{
+ req.set_header("Host", host_and_port_.c_str());
+ req.set_header("Accept", "*/*");
+ req.set_header("User-Agent", "cpp-httplib/0.1");
+}
+
+inline std::shared_ptr<Response> Client::get(const char* path)
+{
+ Request req;
+ req.method = "GET";
+ req.path = path;
+ add_default_headers(req);
+
+ auto res = std::make_shared<Response>();
+
+ return send(req, *res) ? res : nullptr;
+}
+
+inline std::shared_ptr<Response> Client::head(const char* path)
+{
+ Request req;
+ req.method = "HEAD";
+ req.path = path;
+ add_default_headers(req);
+
+ auto res = std::make_shared<Response>();
+
+ return send(req, *res) ? res : nullptr;
+}
+
+inline std::shared_ptr<Response> Client::post(
+ const char* path, const std::string& body, const char* content_type)
+{
+ Request req;
+ req.method = "POST";
+ req.path = path;
+ add_default_headers(req);
+
+ req.set_header("Content-Type", content_type);
+ req.body = body;
+
+ auto res = std::make_shared<Response>();
+
+ return send(req, *res) ? res : nullptr;
+}
+
+inline std::shared_ptr<Response> Client::post(
+ const char* path, const Map& params)
+{
+ std::string query;
+ for (auto it = params.begin(); it != params.end(); ++it) {
+ if (it != params.begin()) {
+ query += "&";
+ }
+ query += it->first;
+ query += "=";
+ query += it->second;
+ }
+
+ return post(path, query, "application/x-www-form-urlencoded");
+}
+
+/*
+ * SSL Implementation
+ */
+#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
+namespace detail {
+
+template <typename U, typename V, typename T>
+inline bool read_and_close_socket_ssl(socket_t sock, SSL_CTX* ctx, U SSL_connect_or_accept, V setup, T callback)
+{
+ auto ssl = SSL_new(ctx);
+
+ auto bio = BIO_new_socket(sock, BIO_NOCLOSE);
+ SSL_set_bio(ssl, bio, bio);
+
+ setup(ssl);
+
+ SSL_connect_or_accept(ssl);
+
+ SSLSocketStream strm(ssl);
+ auto ret = callback(strm);
+
+ SSL_shutdown(ssl);
+ SSL_free(ssl);
+ close_socket(sock);
+ return ret;
+}
+
+class SSLInit {
+public:
+ SSLInit() {
+ SSL_load_error_strings();
+ SSL_library_init();
+ }
+};
+
+static SSLInit sslinit_;
+
+} // namespace detail
+
+// SSL socket stream implementation
+inline SSLSocketStream::SSLSocketStream(SSL* ssl): ssl_(ssl)
+{
+}
+
+inline SSLSocketStream::~SSLSocketStream()
+{
+}
+
+inline int SSLSocketStream::read(char* ptr, size_t size)
+{
+ return SSL_read(ssl_, ptr, size);
+}
+
+inline int SSLSocketStream::write(const char* ptr, size_t size)
+{
+ return SSL_write(ssl_, ptr, size);
+}
+
+inline int SSLSocketStream::write(const char* ptr)
+{
+ return write(ptr, strlen(ptr));
+}
+
+// SSL HTTP server implementation
+inline SSLServer::SSLServer(const char* cert_path, const char* private_key_path)
+{
+ ctx_ = SSL_CTX_new(SSLv23_server_method());
+
+ if (ctx_) {
+ SSL_CTX_set_options(ctx_,
+ SSL_OP_ALL | SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 |
+ SSL_OP_NO_COMPRESSION |
+ SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION);
+
+ // auto ecdh = EC_KEY_new_by_curve_name(NID_X9_62_prime256v1);
+ // SSL_CTX_set_tmp_ecdh(ctx_, ecdh);
+ // EC_KEY_free(ecdh);
+
+ if (SSL_CTX_use_certificate_file(ctx_, cert_path, SSL_FILETYPE_PEM) != 1 ||
+ SSL_CTX_use_PrivateKey_file(ctx_, private_key_path, SSL_FILETYPE_PEM) != 1) {
+ SSL_CTX_free(ctx_);
+ ctx_ = nullptr;
+ }
+ }
+}
+
+inline SSLServer::~SSLServer()
+{
+ if (ctx_) {
+ SSL_CTX_free(ctx_);
+ }
+}
+
+inline bool SSLServer::read_and_close_socket(socket_t sock)
+{
+ return detail::read_and_close_socket_ssl(
+ sock, ctx_,
+ SSL_accept,
+ [](SSL* ssl) {},
+ [this](Stream& strm) {
+ process_request(strm);
+ return true;
+ });
+}
+
+// SSL HTTP client implementation
+inline SSLClient::SSLClient(const char* host, int port)
+ : Client(host, port)
+{
+ ctx_ = SSL_CTX_new(SSLv23_client_method());
+}
+
+inline SSLClient::~SSLClient()
+{
+ if (ctx_) {
+ SSL_CTX_free(ctx_);
+ }
+}
+
+inline bool SSLClient::read_and_close_socket(socket_t sock, const Request& req, Response& res)
+{
+ return detail::read_and_close_socket_ssl(
+ sock, ctx_,
+ SSL_connect,
+ [&](SSL* ssl) {
+ SSL_set_tlsext_host_name(ssl, host_.c_str());
+ },
+ [&](Stream& strm) {
+ return process_request(strm, req, res);
+ });
+}
+#endif
+
+} // namespace httplib
+
+#endif
+
+// vim: et ts=4 sw=4 cin cino={1s ff=unix
diff --git a/src/JobQueue.cpp b/src/JobQueue.cpp
new file mode 100644
index 0000000..77e9603
--- /dev/null
+++ b/src/JobQueue.cpp
@@ -0,0 +1,98 @@
+#include "JobQueue.hpp"
+#include "UpdateFactory.hpp"
+
+
+void Queue::AddJob(const Job& job, const JobPriority& priority)
+{
+ wxMutexLocker lock(m_MutexQueue);
+ m_Jobs.insert(std::make_pair(priority, job));
+ m_QueueCount.Post();
+}
+
+Job Queue::Pop()
+{
+ Job element;
+ m_QueueCount.Wait();
+ m_MutexQueue.Lock();
+ element = (m_Jobs.begin())->second;
+ m_Jobs.erase(m_Jobs.begin());
+ m_MutexQueue.Unlock();
+ return element;
+}
+
+void Queue::Report(const Job::JobEvents& cmd, const wxString& sArg, int iArg)
+{
+ wxCommandEvent evt(wxEVT_THREAD, cmd);
+ evt.SetString(sArg);
+ evt.SetInt(iArg);
+ m_pParent->AddPendingEvent(evt);
+}
+
+wxThread::ExitCode WorkerThread::doWork()
+{
+ Sleep(1000);
+ Job::JobEvents iErr;
+ m_pQueue->Report(Job::eID_THREAD_STARTED, wxEmptyString, m_ID);
+ try { while(true) OnJob(); }
+ catch (Job::JobEvents& i) { m_pQueue->Report(iErr=i, wxEmptyString, m_ID); }
+ return (wxThread::ExitCode) iErr;
+}
+
+void WorkerThread::doJob()
+{
+ int rv;
+ std::string err;
+ UpdateFactory uf;
+
+ Job job = m_pQueue->Pop();
+ switch(job.m_cmd)
+ {
+ case Job::eID_THREAD_EXIT:
+ throw Job::eID_THREAD_EXIT;
+ case Job::eID_THREAD_JOB:
+ m_pQueue->Report(Job::eID_THREAD_MSG,
+ wxString::Format(wxT("Job #%d: Connecting to %s:%i"),
+ job.m_Arg.jobid, job.m_Arg.hostname, job.m_Arg.port), m_ID);
+ uf.setDest(job.m_Arg.hostname, job.m_Arg.port);
+ uf.setPass(job.m_Arg.password);
+
+ rv = uf.doAuth();
+ if (rv != UPDATE_OK) {
+ mapEmcError(rv, err);
+ m_pQueue->Report(Job::eID_THREAD_MSGERR,
+ wxString::Format(wxT("Job #%d: %s."),
+ job.m_Arg.jobid, err.c_str()), m_ID);
+ break;
+ }
+
+ m_pQueue->Report(Job::eID_THREAD_MSG,
+ wxString::Format(wxT("Job #%d: Uploading file \"%s\""),
+ job.m_Arg.jobid, job.m_Arg.update_file), m_ID);
+ uf.setUpdateFile(job.m_Arg.update_file.c_str());
+ rv = uf.loadUpdateFile();
+ if (rv != UPDATE_OK) {
+ mapEmcError(rv, err);
+ m_pQueue->Report(Job::eID_THREAD_MSGERR,
+ wxString::Format(wxT("Job #%d: %s."),
+ job.m_Arg.jobid, err.c_str()), m_ID);
+ break;
+ }
+
+ rv = uf.doUpdate();
+ mapEmcError(rv, err);
+ if (rv != UPDATE_OK) {
+ m_pQueue->Report(Job::eID_THREAD_MSGERR,
+ wxString::Format(wxT("Job #%d: %s."),
+ job.m_Arg.jobid, err.c_str()), m_ID);
+ break;
+ }
+
+ m_pQueue->Report(Job::eID_THREAD_MSGOK,
+ wxString::Format(wxT("Job #%d: %s."),
+ job.m_Arg.jobid, err), m_ID);
+ break;
+ case Job::eID_THREAD_NULL:
+ default:
+ break;
+ }
+}
diff --git a/src/JobQueue.hpp b/src/JobQueue.hpp
new file mode 100644
index 0000000..9b356f3
--- /dev/null
+++ b/src/JobQueue.hpp
@@ -0,0 +1,103 @@
+#ifndef JOBQUEUE_H
+#define JOBQUEUE_H 1
+
+#include <string>
+#include <map>
+#include <list>
+#include <wx/frame.h>
+#include <wx/thread.h>
+#include <wx/menu.h>
+#include <wx/app.h>
+
+#include "UpdateFactory.hpp"
+
+
+class JobArgs
+{
+public:
+ JobArgs()
+ : jobid(-1), hostname(""), port(0),
+ update_file(""), password("") {}
+ JobArgs(int jobid, UpdateFactory& uf)
+ : jobid(jobid), hostname(uf.getHostname()),
+ port(uf.getPort()), update_file(uf.getUpdateFile()), password(uf.getPassword()) {}
+ JobArgs(int jobid, const char *hostname, int port,
+ const char *update_file, const char *password)
+ : jobid(jobid), hostname(hostname), port(port),
+ update_file(update_file), password(password) {}
+ JobArgs(int jobid, std::string& hostname, int port,
+ std::string& update_file, std::string& password)
+ : jobid(jobid), hostname(hostname), port(port),
+ update_file(update_file), password(password) {}
+
+ int jobid;
+ std::string hostname;
+ int port;
+ std::string update_file;
+ std::string password;
+};
+
+class Job
+{
+public:
+ enum JobEvents
+ {
+ /* thread should exit or wants to exit */
+ eID_THREAD_EXIT = wxID_HIGHEST + 1000,
+ /* dummy command */
+ eID_THREAD_NULL,
+ /* worker thread has started OK */
+ eID_THREAD_STARTED,
+ /* process normal job */
+ eID_THREAD_JOB,
+ /* process different messages in the frontend */
+ eID_THREAD_MSG,
+ eID_THREAD_MSGOK,
+ eID_THREAD_MSGERR
+ };
+
+ Job() : m_cmd(eID_THREAD_NULL) {}
+ Job(JobEvents cmd, JobArgs arg) : m_cmd(cmd), m_Arg(arg) {}
+ Job(JobEvents cmd, int jobid, UpdateFactory& uf) : m_cmd(cmd), m_Arg(jobid, uf) {}
+ JobEvents m_cmd;
+ JobArgs m_Arg;
+};
+
+class Queue
+{
+public:
+ enum JobPriority { eHIGHEST, eHIGHER, eNORMAL, eBELOW_NORMAL, eLOW, eIDLE };
+ Queue(wxEvtHandler *pParent) : m_pParent(pParent) {}
+ /* push a job with given priority class onto the FIFO */
+ void AddJob(const Job& job, const JobPriority& priority = eNORMAL);
+ Job Pop();
+ /* report back to parent */
+ void Report(const Job::JobEvents& cmd, const wxString& sArg = wxEmptyString, int iArg = 0);
+ size_t Stacksize()
+ {
+ wxMutexLocker lock(m_MutexQueue);
+ return m_Jobs.size();
+ }
+private:
+ wxEvtHandler *m_pParent;
+ /* a priority Queue using std::multimap */
+ std::multimap<JobPriority, Job> m_Jobs;
+ wxMutex m_MutexQueue;
+ wxSemaphore m_QueueCount;
+};
+
+class WorkerThread : public wxThread
+{
+public:
+ WorkerThread(Queue *pQueue, int id = 0) : m_pQueue(pQueue), m_ID(id) { assert(pQueue); wxThread::Create(); }
+private:
+ Queue *m_pQueue;
+ int m_ID;
+
+ wxThread::ExitCode doWork();
+ virtual wxThread::ExitCode Entry() { return this->doWork(); }
+ void doJob();
+ virtual void OnJob() { return this->doJob(); }
+};
+
+#endif
diff --git a/src/Json.cpp b/src/Json.cpp
new file mode 100644
index 0000000..2cc6113
--- /dev/null
+++ b/src/Json.cpp
@@ -0,0 +1,788 @@
+/* Copyright (c) 2013 Dropbox, Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include "Json.hpp"
+#include <cassert>
+#include <cmath>
+#include <cstdlib>
+#include <cstdio>
+#include <limits>
+
+namespace json11 {
+
+static const int max_depth = 200;
+
+using std::string;
+using std::vector;
+using std::map;
+using std::make_shared;
+using std::initializer_list;
+using std::move;
+
+/* Helper for representing null - just a do-nothing struct, plus comparison
+ * operators so the helpers in JsonValue work. We can't use nullptr_t because
+ * it may not be orderable.
+ */
+struct NullStruct {
+ bool operator==(NullStruct) const { return true; }
+ bool operator<(NullStruct) const { return false; }
+};
+
+/* * * * * * * * * * * * * * * * * * * *
+ * Serialization
+ */
+
+static void dump(NullStruct, string &out) {
+ out += "null";
+}
+
+static void dump(double value, string &out) {
+ if (std::isfinite(value)) {
+ char buf[32];
+ snprintf(buf, sizeof buf, "%.17g", value);
+ out += buf;
+ } else {
+ out += "null";
+ }
+}
+
+static void dump(int value, string &out) {
+ char buf[32];
+ snprintf(buf, sizeof buf, "%d", value);
+ out += buf;
+}
+
+static void dump(bool value, string &out) {
+ out += value ? "true" : "false";
+}
+
+static void dump(const string &value, string &out) {
+ out += '"';
+ for (size_t i = 0; i < value.length(); i++) {
+ const char ch = value[i];
+ if (ch == '\\') {
+ out += "\\\\";
+ } else if (ch == '"') {
+ out += "\\\"";
+ } else if (ch == '\b') {
+ out += "\\b";
+ } else if (ch == '\f') {
+ out += "\\f";
+ } else if (ch == '\n') {
+ out += "\\n";
+ } else if (ch == '\r') {
+ out += "\\r";
+ } else if (ch == '\t') {
+ out += "\\t";
+ } else if (static_cast<uint8_t>(ch) <= 0x1f) {
+ char buf[8];
+ snprintf(buf, sizeof buf, "\\u%04x", ch);
+ out += buf;
+ } else if (static_cast<uint8_t>(ch) == 0xe2 && static_cast<uint8_t>(value[i+1]) == 0x80
+ && static_cast<uint8_t>(value[i+2]) == 0xa8) {
+ out += "\\u2028";
+ i += 2;
+ } else if (static_cast<uint8_t>(ch) == 0xe2 && static_cast<uint8_t>(value[i+1]) == 0x80
+ && static_cast<uint8_t>(value[i+2]) == 0xa9) {
+ out += "\\u2029";
+ i += 2;
+ } else {
+ out += ch;
+ }
+ }
+ out += '"';
+}
+
+static void dump(const Json::array &values, string &out) {
+ bool first = true;
+ out += "[";
+ for (const auto &value : values) {
+ if (!first)
+ out += ", ";
+ value.dump(out);
+ first = false;
+ }
+ out += "]";
+}
+
+static void dump(const Json::object &values, string &out) {
+ bool first = true;
+ out += "{";
+ for (const auto &kv : values) {
+ if (!first)
+ out += ", ";
+ dump(kv.first, out);
+ out += ": ";
+ kv.second.dump(out);
+ first = false;
+ }
+ out += "}";
+}
+
+void Json::dump(string &out) const {
+ m_ptr->dump(out);
+}
+
+/* * * * * * * * * * * * * * * * * * * *
+ * Value wrappers
+ */
+
+template <Json::Type tag, typename T>
+class Value : public JsonValue {
+protected:
+
+ // Constructors
+ explicit Value(const T &value) : m_value(value) {}
+ explicit Value(T &&value) : m_value(move(value)) {}
+
+ // Get type tag
+ Json::Type type() const override {
+ return tag;
+ }
+
+ // Comparisons
+ bool equals(const JsonValue * other) const override {
+ return m_value == static_cast<const Value<tag, T> *>(other)->m_value;
+ }
+ bool less(const JsonValue * other) const override {
+ return m_value < static_cast<const Value<tag, T> *>(other)->m_value;
+ }
+
+ const T m_value;
+ void dump(string &out) const override { json11::dump(m_value, out); }
+};
+
+class JsonDouble final : public Value<Json::NUMBER, double> {
+ double number_value() const override { return m_value; }
+ int int_value() const override { return static_cast<int>(m_value); }
+ bool equals(const JsonValue * other) const override { return m_value == other->number_value(); }
+ bool less(const JsonValue * other) const override { return m_value < other->number_value(); }
+public:
+ explicit JsonDouble(double value) : Value(value) {}
+};
+
+class JsonInt final : public Value<Json::NUMBER, int> {
+ double number_value() const override { return m_value; }
+ int int_value() const override { return m_value; }
+ bool equals(const JsonValue * other) const override { return m_value == other->number_value(); }
+ bool less(const JsonValue * other) const override { return m_value < other->number_value(); }
+public:
+ explicit JsonInt(int value) : Value(value) {}
+};
+
+class JsonBoolean final : public Value<Json::BOOL, bool> {
+ bool bool_value() const override { return m_value; }
+public:
+ explicit JsonBoolean(bool value) : Value(value) {}
+};
+
+class JsonString final : public Value<Json::STRING, string> {
+ const string &string_value() const override { return m_value; }
+public:
+ explicit JsonString(const string &value) : Value(value) {}
+ explicit JsonString(string &&value) : Value(move(value)) {}
+};
+
+class JsonArray final : public Value<Json::ARRAY, Json::array> {
+ const Json::array &array_items() const override { return m_value; }
+ const Json & operator[](size_t i) const override;
+public:
+ explicit JsonArray(const Json::array &value) : Value(value) {}
+ explicit JsonArray(Json::array &&value) : Value(move(value)) {}
+};
+
+class JsonObject final : public Value<Json::OBJECT, Json::object> {
+ const Json::object &object_items() const override { return m_value; }
+ const Json & operator[](const string &key) const override;
+public:
+ explicit JsonObject(const Json::object &value) : Value(value) {}
+ explicit JsonObject(Json::object &&value) : Value(move(value)) {}
+};
+
+class JsonNull final : public Value<Json::NUL, NullStruct> {
+public:
+ JsonNull() : Value({}) {}
+};
+
+/* * * * * * * * * * * * * * * * * * * *
+ * Static globals - static-init-safe
+ */
+struct Statics {
+ const std::shared_ptr<JsonValue> null = make_shared<JsonNull>();
+ const std::shared_ptr<JsonValue> t = make_shared<JsonBoolean>(true);
+ const std::shared_ptr<JsonValue> f = make_shared<JsonBoolean>(false);
+ const string empty_string;
+ const vector<Json> empty_vector;
+ const map<string, Json> empty_map;
+ Statics() {}
+};
+
+static const Statics & statics() {
+ static const Statics s {};
+ return s;
+}
+
+static const Json & static_null() {
+ // This has to be separate, not in Statics, because Json() accesses statics().null.
+ static const Json json_null;
+ return json_null;
+}
+
+/* * * * * * * * * * * * * * * * * * * *
+ * Constructors
+ */
+
+Json::Json() noexcept : m_ptr(statics().null) {}
+Json::Json(std::nullptr_t) noexcept : m_ptr(statics().null) {}
+Json::Json(double value) : m_ptr(make_shared<JsonDouble>(value)) {}
+Json::Json(int value) : m_ptr(make_shared<JsonInt>(value)) {}
+Json::Json(bool value) : m_ptr(value ? statics().t : statics().f) {}
+Json::Json(const string &value) : m_ptr(make_shared<JsonString>(value)) {}
+Json::Json(string &&value) : m_ptr(make_shared<JsonString>(move(value))) {}
+Json::Json(const char * value) : m_ptr(make_shared<JsonString>(value)) {}
+Json::Json(const Json::array &values) : m_ptr(make_shared<JsonArray>(values)) {}
+Json::Json(Json::array &&values) : m_ptr(make_shared<JsonArray>(move(values))) {}
+Json::Json(const Json::object &values) : m_ptr(make_shared<JsonObject>(values)) {}
+Json::Json(Json::object &&values) : m_ptr(make_shared<JsonObject>(move(values))) {}
+
+/* * * * * * * * * * * * * * * * * * * *
+ * Accessors
+ */
+
+Json::Type Json::type() const { return m_ptr->type(); }
+double Json::number_value() const { return m_ptr->number_value(); }
+int Json::int_value() const { return m_ptr->int_value(); }
+bool Json::bool_value() const { return m_ptr->bool_value(); }
+const string & Json::string_value() const { return m_ptr->string_value(); }
+const vector<Json> & Json::array_items() const { return m_ptr->array_items(); }
+const map<string, Json> & Json::object_items() const { return m_ptr->object_items(); }
+const Json & Json::operator[] (size_t i) const { return (*m_ptr)[i]; }
+const Json & Json::operator[] (const string &key) const { return (*m_ptr)[key]; }
+
+double JsonValue::number_value() const { return 0; }
+int JsonValue::int_value() const { return 0; }
+bool JsonValue::bool_value() const { return false; }
+const string & JsonValue::string_value() const { return statics().empty_string; }
+const vector<Json> & JsonValue::array_items() const { return statics().empty_vector; }
+const map<string, Json> & JsonValue::object_items() const { return statics().empty_map; }
+const Json & JsonValue::operator[] (size_t) const { return static_null(); }
+const Json & JsonValue::operator[] (const string &) const { return static_null(); }
+
+const Json & JsonObject::operator[] (const string &key) const {
+ auto iter = m_value.find(key);
+ return (iter == m_value.end()) ? static_null() : iter->second;
+}
+const Json & JsonArray::operator[] (size_t i) const {
+ if (i >= m_value.size()) return static_null();
+ else return m_value[i];
+}
+
+/* * * * * * * * * * * * * * * * * * * *
+ * Comparison
+ */
+
+bool Json::operator== (const Json &other) const {
+ if (m_ptr == other.m_ptr)
+ return true;
+ if (m_ptr->type() != other.m_ptr->type())
+ return false;
+
+ return m_ptr->equals(other.m_ptr.get());
+}
+
+bool Json::operator< (const Json &other) const {
+ if (m_ptr == other.m_ptr)
+ return false;
+ if (m_ptr->type() != other.m_ptr->type())
+ return m_ptr->type() < other.m_ptr->type();
+
+ return m_ptr->less(other.m_ptr.get());
+}
+
+/* * * * * * * * * * * * * * * * * * * *
+ * Parsing
+ */
+
+/* esc(c)
+ *
+ * Format char c suitable for printing in an error message.
+ */
+static inline string esc(char c) {
+ char buf[12];
+ if (static_cast<uint8_t>(c) >= 0x20 && static_cast<uint8_t>(c) <= 0x7f) {
+ snprintf(buf, sizeof buf, "'%c' (%d)", c, c);
+ } else {
+ snprintf(buf, sizeof buf, "(%d)", c);
+ }
+ return string(buf);
+}
+
+static inline bool in_range(long x, long lower, long upper) {
+ return (x >= lower && x <= upper);
+}
+
+namespace {
+/* JsonParser
+ *
+ * Object that tracks all state of an in-progress parse.
+ */
+struct JsonParser final {
+
+ /* State
+ */
+ const string &str;
+ size_t i;
+ string &err;
+ bool failed;
+ const JsonParse strategy;
+
+ /* fail(msg, err_ret = Json())
+ *
+ * Mark this parse as failed.
+ */
+ Json fail(string &&msg) {
+ return fail(move(msg), Json());
+ }
+
+ template <typename T>
+ T fail(string &&msg, const T err_ret) {
+ if (!failed)
+ err = std::move(msg);
+ failed = true;
+ return err_ret;
+ }
+
+ /* consume_whitespace()
+ *
+ * Advance until the current character is non-whitespace.
+ */
+ void consume_whitespace() {
+ while (str[i] == ' ' || str[i] == '\r' || str[i] == '\n' || str[i] == '\t')
+ i++;
+ }
+
+ /* consume_comment()
+ *
+ * Advance comments (c-style inline and multiline).
+ */
+ bool consume_comment() {
+ bool comment_found = false;
+ if (str[i] == '/') {
+ i++;
+ if (i == str.size())
+ return fail("unexpected end of input after start of comment", false);
+ if (str[i] == '/') { // inline comment
+ i++;
+ // advance until next line, or end of input
+ while (i < str.size() && str[i] != '\n') {
+ i++;
+ }
+ comment_found = true;
+ }
+ else if (str[i] == '*') { // multiline comment
+ i++;
+ if (i > str.size()-2)
+ return fail("unexpected end of input inside multi-line comment", false);
+ // advance until closing tokens
+ while (!(str[i] == '*' && str[i+1] == '/')) {
+ i++;
+ if (i > str.size()-2)
+ return fail(
+ "unexpected end of input inside multi-line comment", false);
+ }
+ i += 2;
+ comment_found = true;
+ }
+ else
+ return fail("malformed comment", false);
+ }
+ return comment_found;
+ }
+
+ /* consume_garbage()
+ *
+ * Advance until the current character is non-whitespace and non-comment.
+ */
+ void consume_garbage() {
+ consume_whitespace();
+ if(strategy == JsonParse::COMMENTS) {
+ bool comment_found = false;
+ do {
+ comment_found = consume_comment();
+ if (failed) return;
+ consume_whitespace();
+ }
+ while(comment_found);
+ }
+ }
+
+ /* get_next_token()
+ *
+ * Return the next non-whitespace character. If the end of the input is reached,
+ * flag an error and return 0.
+ */
+ char get_next_token() {
+ consume_garbage();
+ if (failed) return (char)0;
+ if (i == str.size())
+ return fail("unexpected end of input", (char)0);
+
+ return str[i++];
+ }
+
+ /* encode_utf8(pt, out)
+ *
+ * Encode pt as UTF-8 and add it to out.
+ */
+ void encode_utf8(long pt, string & out) {
+ if (pt < 0)
+ return;
+
+ if (pt < 0x80) {
+ out += static_cast<char>(pt);
+ } else if (pt < 0x800) {
+ out += static_cast<char>((pt >> 6) | 0xC0);
+ out += static_cast<char>((pt & 0x3F) | 0x80);
+ } else if (pt < 0x10000) {
+ out += static_cast<char>((pt >> 12) | 0xE0);
+ out += static_cast<char>(((pt >> 6) & 0x3F) | 0x80);
+ out += static_cast<char>((pt & 0x3F) | 0x80);
+ } else {
+ out += static_cast<char>((pt >> 18) | 0xF0);
+ out += static_cast<char>(((pt >> 12) & 0x3F) | 0x80);
+ out += static_cast<char>(((pt >> 6) & 0x3F) | 0x80);
+ out += static_cast<char>((pt & 0x3F) | 0x80);
+ }
+ }
+
+ /* parse_string()
+ *
+ * Parse a string, starting at the current position.
+ */
+ string parse_string() {
+ string out;
+ long last_escaped_codepoint = -1;
+ while (true) {
+ if (i == str.size())
+ return fail("unexpected end of input in string", "");
+
+ char ch = str[i++];
+
+ if (ch == '"') {
+ encode_utf8(last_escaped_codepoint, out);
+ return out;
+ }
+
+ if (in_range(ch, 0, 0x1f))
+ return fail("unescaped " + esc(ch) + " in string", "");
+
+ // The usual case: non-escaped characters
+ if (ch != '\\') {
+ encode_utf8(last_escaped_codepoint, out);
+ last_escaped_codepoint = -1;
+ out += ch;
+ continue;
+ }
+
+ // Handle escapes
+ if (i == str.size())
+ return fail("unexpected end of input in string", "");
+
+ ch = str[i++];
+
+ if (ch == 'u') {
+ // Extract 4-byte escape sequence
+ string esc = str.substr(i, 4);
+ // Explicitly check length of the substring. The following loop
+ // relies on std::string returning the terminating NUL when
+ // accessing str[length]. Checking here reduces brittleness.
+ if (esc.length() < 4) {
+ return fail("bad \\u escape: " + esc, "");
+ }
+ for (size_t j = 0; j < 4; j++) {
+ if (!in_range(esc[j], 'a', 'f') && !in_range(esc[j], 'A', 'F')
+ && !in_range(esc[j], '0', '9'))
+ return fail("bad \\u escape: " + esc, "");
+ }
+
+ long codepoint = strtol(esc.data(), nullptr, 16);
+
+ // JSON specifies that characters outside the BMP shall be encoded as a pair
+ // of 4-hex-digit \u escapes encoding their surrogate pair components. Check
+ // whether we're in the middle of such a beast: the previous codepoint was an
+ // escaped lead (high) surrogate, and this is a trail (low) surrogate.
+ if (in_range(last_escaped_codepoint, 0xD800, 0xDBFF)
+ && in_range(codepoint, 0xDC00, 0xDFFF)) {
+ // Reassemble the two surrogate pairs into one astral-plane character, per
+ // the UTF-16 algorithm.
+ encode_utf8((((last_escaped_codepoint - 0xD800) << 10)
+ | (codepoint - 0xDC00)) + 0x10000, out);
+ last_escaped_codepoint = -1;
+ } else {
+ encode_utf8(last_escaped_codepoint, out);
+ last_escaped_codepoint = codepoint;
+ }
+
+ i += 4;
+ continue;
+ }
+
+ encode_utf8(last_escaped_codepoint, out);
+ last_escaped_codepoint = -1;
+
+ if (ch == 'b') {
+ out += '\b';
+ } else if (ch == 'f') {
+ out += '\f';
+ } else if (ch == 'n') {
+ out += '\n';
+ } else if (ch == 'r') {
+ out += '\r';
+ } else if (ch == 't') {
+ out += '\t';
+ } else if (ch == '"' || ch == '\\' || ch == '/') {
+ out += ch;
+ } else {
+ return fail("invalid escape character " + esc(ch), "");
+ }
+ }
+ }
+
+ /* parse_number()
+ *
+ * Parse a double.
+ */
+ Json parse_number() {
+ size_t start_pos = i;
+
+ if (str[i] == '-')
+ i++;
+
+ // Integer part
+ if (str[i] == '0') {
+ i++;
+ if (in_range(str[i], '0', '9'))
+ return fail("leading 0s not permitted in numbers");
+ } else if (in_range(str[i], '1', '9')) {
+ i++;
+ while (in_range(str[i], '0', '9'))
+ i++;
+ } else {
+ return fail("invalid " + esc(str[i]) + " in number");
+ }
+
+ if (str[i] != '.' && str[i] != 'e' && str[i] != 'E'
+ && (i - start_pos) <= static_cast<size_t>(std::numeric_limits<int>::digits10)) {
+ return std::atoi(str.c_str() + start_pos);
+ }
+
+ // Decimal part
+ if (str[i] == '.') {
+ i++;
+ if (!in_range(str[i], '0', '9'))
+ return fail("at least one digit required in fractional part");
+
+ while (in_range(str[i], '0', '9'))
+ i++;
+ }
+
+ // Exponent part
+ if (str[i] == 'e' || str[i] == 'E') {
+ i++;
+
+ if (str[i] == '+' || str[i] == '-')
+ i++;
+
+ if (!in_range(str[i], '0', '9'))
+ return fail("at least one digit required in exponent");
+
+ while (in_range(str[i], '0', '9'))
+ i++;
+ }
+
+ return std::strtod(str.c_str() + start_pos, nullptr);
+ }
+
+ /* expect(str, res)
+ *
+ * Expect that 'str' starts at the character that was just read. If it does, advance
+ * the input and return res. If not, flag an error.
+ */
+ Json expect(const string &expected, Json res) {
+ assert(i != 0);
+ i--;
+ if (str.compare(i, expected.length(), expected) == 0) {
+ i += expected.length();
+ return res;
+ } else {
+ return fail("parse error: expected " + expected + ", got " + str.substr(i, expected.length()));
+ }
+ }
+
+ /* parse_json()
+ *
+ * Parse a JSON object.
+ */
+ Json parse_json(int depth) {
+ if (depth > max_depth) {
+ return fail("exceeded maximum nesting depth");
+ }
+
+ char ch = get_next_token();
+ if (failed)
+ return Json();
+
+ if (ch == '-' || (ch >= '0' && ch <= '9')) {
+ i--;
+ return parse_number();
+ }
+
+ if (ch == 't')
+ return expect("true", true);
+
+ if (ch == 'f')
+ return expect("false", false);
+
+ if (ch == 'n')
+ return expect("null", Json());
+
+ if (ch == '"')
+ return parse_string();
+
+ if (ch == '{') {
+ map<string, Json> data;
+ ch = get_next_token();
+ if (ch == '}')
+ return data;
+
+ while (1) {
+ if (ch != '"')
+ return fail("expected '\"' in object, got " + esc(ch));
+
+ string key = parse_string();
+ if (failed)
+ return Json();
+
+ ch = get_next_token();
+ if (ch != ':')
+ return fail("expected ':' in object, got " + esc(ch));
+
+ data[std::move(key)] = parse_json(depth + 1);
+ if (failed)
+ return Json();
+
+ ch = get_next_token();
+ if (ch == '}')
+ break;
+ if (ch != ',')
+ return fail("expected ',' in object, got " + esc(ch));
+
+ ch = get_next_token();
+ }
+ return data;
+ }
+
+ if (ch == '[') {
+ vector<Json> data;
+ ch = get_next_token();
+ if (ch == ']')
+ return data;
+
+ while (1) {
+ i--;
+ data.push_back(parse_json(depth + 1));
+ if (failed)
+ return Json();
+
+ ch = get_next_token();
+ if (ch == ']')
+ break;
+ if (ch != ',')
+ return fail("expected ',' in list, got " + esc(ch));
+
+ ch = get_next_token();
+ (void)ch;
+ }
+ return data;
+ }
+
+ return fail("expected value, got " + esc(ch));
+ }
+};
+}//namespace {
+
+Json Json::parse(const string &in, string &err, JsonParse strategy) {
+ JsonParser parser { in, 0, err, false, strategy };
+ Json result = parser.parse_json(0);
+
+ // Check for any trailing garbage
+ parser.consume_garbage();
+ if (parser.failed)
+ return Json();
+ if (parser.i != in.size())
+ return parser.fail("unexpected trailing " + esc(in[parser.i]));
+
+ return result;
+}
+
+// Documented in json11.hpp
+vector<Json> Json::parse_multi(const string &in,
+ std::string::size_type &parser_stop_pos,
+ string &err,
+ JsonParse strategy) {
+ JsonParser parser { in, 0, err, false, strategy };
+ parser_stop_pos = 0;
+ vector<Json> json_vec;
+ while (parser.i != in.size() && !parser.failed) {
+ json_vec.push_back(parser.parse_json(0));
+ if (parser.failed)
+ break;
+
+ // Check for another object
+ parser.consume_garbage();
+ if (parser.failed)
+ break;
+ parser_stop_pos = parser.i;
+ }
+ return json_vec;
+}
+
+/* * * * * * * * * * * * * * * * * * * *
+ * Shape-checking
+ */
+
+bool Json::has_shape(const shape & types, string & err) const {
+ if (!is_object()) {
+ err = "expected JSON object, got " + dump();
+ return false;
+ }
+
+ for (auto & item : types) {
+ if ((*this)[item.first].type() != item.second) {
+ err = "bad type for " + item.first + " in " + dump();
+ return false;
+ }
+ }
+
+ return true;
+}
+
+} // namespace json11
diff --git a/src/Json.hpp b/src/Json.hpp
new file mode 100644
index 0000000..0c47d05
--- /dev/null
+++ b/src/Json.hpp
@@ -0,0 +1,232 @@
+/* json11
+ *
+ * json11 is a tiny JSON library for C++11, providing JSON parsing and serialization.
+ *
+ * The core object provided by the library is json11::Json. A Json object represents any JSON
+ * value: null, bool, number (int or double), string (std::string), array (std::vector), or
+ * object (std::map).
+ *
+ * Json objects act like values: they can be assigned, copied, moved, compared for equality or
+ * order, etc. There are also helper methods Json::dump, to serialize a Json to a string, and
+ * Json::parse (static) to parse a std::string as a Json object.
+ *
+ * Internally, the various types of Json object are represented by the JsonValue class
+ * hierarchy.
+ *
+ * A note on numbers - JSON specifies the syntax of number formatting but not its semantics,
+ * so some JSON implementations distinguish between integers and floating-point numbers, while
+ * some don't. In json11, we choose the latter. Because some JSON implementations (namely
+ * Javascript itself) treat all numbers as the same type, distinguishing the two leads
+ * to JSON that will be *silently* changed by a round-trip through those implementations.
+ * Dangerous! To avoid that risk, json11 stores all numbers as double internally, but also
+ * provides integer helpers.
+ *
+ * Fortunately, double-precision IEEE754 ('double') can precisely store any integer in the
+ * range +/-2^53, which includes every 'int' on most systems. (Timestamps often use int64
+ * or long long to avoid the Y2038K problem; a double storing microseconds since some epoch
+ * will be exact for +/- 275 years.)
+ */
+
+/* Copyright (c) 2013 Dropbox, Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#pragma once
+
+#include <string>
+#include <vector>
+#include <map>
+#include <memory>
+#include <initializer_list>
+
+#ifdef _MSC_VER
+ #if _MSC_VER <= 1800 // VS 2013
+ #ifndef noexcept
+ #define noexcept throw()
+ #endif
+
+ #ifndef snprintf
+ #define snprintf _snprintf_s
+ #endif
+ #endif
+#endif
+
+namespace json11 {
+
+enum JsonParse {
+ STANDARD, COMMENTS
+};
+
+class JsonValue;
+
+class Json final {
+public:
+ // Types
+ enum Type {
+ NUL, NUMBER, BOOL, STRING, ARRAY, OBJECT
+ };
+
+ // Array and object typedefs
+ typedef std::vector<Json> array;
+ typedef std::map<std::string, Json> object;
+
+ // Constructors for the various types of JSON value.
+ Json() noexcept; // NUL
+ Json(std::nullptr_t) noexcept; // NUL
+ Json(double value); // NUMBER
+ Json(int value); // NUMBER
+ Json(bool value); // BOOL
+ Json(const std::string &value); // STRING
+ Json(std::string &&value); // STRING
+ Json(const char * value); // STRING
+ Json(const array &values); // ARRAY
+ Json(array &&values); // ARRAY
+ Json(const object &values); // OBJECT
+ Json(object &&values); // OBJECT
+
+ // Implicit constructor: anything with a to_json() function.
+ template <class T, class = decltype(&T::to_json)>
+ Json(const T & t) : Json(t.to_json()) {}
+
+ // Implicit constructor: map-like objects (std::map, std::unordered_map, etc)
+ template <class M, typename std::enable_if<
+ std::is_constructible<std::string, decltype(std::declval<M>().begin()->first)>::value
+ && std::is_constructible<Json, decltype(std::declval<M>().begin()->second)>::value,
+ int>::type = 0>
+ Json(const M & m) : Json(object(m.begin(), m.end())) {}
+
+ // Implicit constructor: vector-like objects (std::list, std::vector, std::set, etc)
+ template <class V, typename std::enable_if<
+ std::is_constructible<Json, decltype(*std::declval<V>().begin())>::value,
+ int>::type = 0>
+ Json(const V & v) : Json(array(v.begin(), v.end())) {}
+
+ // This prevents Json(some_pointer) from accidentally producing a bool. Use
+ // Json(bool(some_pointer)) if that behavior is desired.
+ Json(void *) = delete;
+
+ // Accessors
+ Type type() const;
+
+ bool is_null() const { return type() == NUL; }
+ bool is_number() const { return type() == NUMBER; }
+ bool is_bool() const { return type() == BOOL; }
+ bool is_string() const { return type() == STRING; }
+ bool is_array() const { return type() == ARRAY; }
+ bool is_object() const { return type() == OBJECT; }
+
+ // Return the enclosed value if this is a number, 0 otherwise. Note that json11 does not
+ // distinguish between integer and non-integer numbers - number_value() and int_value()
+ // can both be applied to a NUMBER-typed object.
+ double number_value() const;
+ int int_value() const;
+
+ // Return the enclosed value if this is a boolean, false otherwise.
+ bool bool_value() const;
+ // Return the enclosed string if this is a string, "" otherwise.
+ const std::string &string_value() const;
+ // Return the enclosed std::vector if this is an array, or an empty vector otherwise.
+ const array &array_items() const;
+ // Return the enclosed std::map if this is an object, or an empty map otherwise.
+ const object &object_items() const;
+
+ // Return a reference to arr[i] if this is an array, Json() otherwise.
+ const Json & operator[](size_t i) const;
+ // Return a reference to obj[key] if this is an object, Json() otherwise.
+ const Json & operator[](const std::string &key) const;
+
+ // Serialize.
+ void dump(std::string &out) const;
+ std::string dump() const {
+ std::string out;
+ dump(out);
+ return out;
+ }
+
+ // Parse. If parse fails, return Json() and assign an error message to err.
+ static Json parse(const std::string & in,
+ std::string & err,
+ JsonParse strategy = JsonParse::STANDARD);
+ static Json parse(const char * in,
+ std::string & err,
+ JsonParse strategy = JsonParse::STANDARD) {
+ if (in) {
+ return parse(std::string(in), err, strategy);
+ } else {
+ err = "null input";
+ return nullptr;
+ }
+ }
+ // Parse multiple objects, concatenated or separated by whitespace
+ static std::vector<Json> parse_multi(
+ const std::string & in,
+ std::string::size_type & parser_stop_pos,
+ std::string & err,
+ JsonParse strategy = JsonParse::STANDARD);
+
+ static inline std::vector<Json> parse_multi(
+ const std::string & in,
+ std::string & err,
+ JsonParse strategy = JsonParse::STANDARD) {
+ std::string::size_type parser_stop_pos;
+ return parse_multi(in, parser_stop_pos, err, strategy);
+ }
+
+ bool operator== (const Json &rhs) const;
+ bool operator< (const Json &rhs) const;
+ bool operator!= (const Json &rhs) const { return !(*this == rhs); }
+ bool operator<= (const Json &rhs) const { return !(rhs < *this); }
+ bool operator> (const Json &rhs) const { return (rhs < *this); }
+ bool operator>= (const Json &rhs) const { return !(*this < rhs); }
+
+ /* has_shape(types, err)
+ *
+ * Return true if this is a JSON object and, for each item in types, has a field of
+ * the given type. If not, return false and set err to a descriptive message.
+ */
+ typedef std::initializer_list<std::pair<std::string, Type>> shape;
+ bool has_shape(const shape & types, std::string & err) const;
+
+private:
+ std::shared_ptr<JsonValue> m_ptr;
+};
+
+// Internal class hierarchy - JsonValue objects are not exposed to users of this API.
+class JsonValue {
+protected:
+ friend class Json;
+ friend class JsonInt;
+ friend class JsonDouble;
+ virtual Json::Type type() const = 0;
+ virtual bool equals(const JsonValue * other) const = 0;
+ virtual bool less(const JsonValue * other) const = 0;
+ virtual void dump(std::string &out) const = 0;
+ virtual double number_value() const;
+ virtual int int_value() const;
+ virtual bool bool_value() const;
+ virtual const std::string &string_value() const;
+ virtual const Json::array &array_items() const;
+ virtual const Json &operator[](size_t i) const;
+ virtual const Json::object &object_items() const;
+ virtual const Json &operator[](const std::string &key) const;
+ virtual ~JsonValue() {}
+};
+
+} // namespace json11
diff --git a/src/Makefile.am b/src/Makefile.am
new file mode 100644
index 0000000..b5b9b8e
--- /dev/null
+++ b/src/Makefile.am
@@ -0,0 +1,17 @@
+bin_PROGRAMS = utool
+
+utool_CXXFLAGS = -std=c++11 -ffunction-sections -fdata-sections -Wl,--gc-sections $(UT_CFLAGS) $(WX_CFLAGS) -DCSV_IO_NO_THREAD=1
+if UT_BUILDGUI
+utool_CXXFLAGS += -DUSE_GUI=1
+endif
+utool_LDADD = $(UT_LIBS) $(WX_LIBS)
+
+utool_SOURCES = \
+ Json.cpp \
+ UpdateFactory.cpp \
+ UpdateTool.cpp
+if UT_BUILDGUI
+utool_SOURCES += \
+ UpdateGUI.cpp \
+ JobQueue.cpp
+endif
diff --git a/src/UpdateFactory.cpp b/src/UpdateFactory.cpp
new file mode 100644
index 0000000..0a102cf
--- /dev/null
+++ b/src/UpdateFactory.cpp
@@ -0,0 +1,346 @@
+#include <iostream>
+#include <string>
+#include <sstream>
+#include <map>
+#include <iomanip>
+
+#include "UpdateFactory.hpp"
+#include "Csv.hpp"
+
+
+/* debug only */
+#if 0
+static std::string toHex(const std::string& s, bool upper_case)
+{
+ std::ostringstream ret;
+
+ for (std::string::size_type i = 0; i < s.length(); ++i)
+ ret << std::hex << std::setfill('0') << std::setw(2) << (upper_case ? std::uppercase : std::nouppercase) << (int)s[i];
+
+ return ret.str();
+}
+#endif
+
+/* Do not use `const char *` as key type. */
+static const std::map<const std::string, const enum EMCVersion> version_map = {
+ { "1.50", EMC_150 }, { "2.04", EMC_204RC6 },
+ { "2.04~rc5", EMC_150 }, { "2.04~rc6", EMC_204RC6 }
+};
+
+enum EMCVersion mapEmcVersion(std::string& emc_version)
+{
+ try {
+ return version_map.at(emc_version);
+ } catch (std::out_of_range) {
+ return EMC_UNKNOWN;
+ }
+}
+
+static const std::map<const int, const std::string> error_map = {
+ { UPDATE_OK, "Update succeeded" },
+ { UPDATE_HTTP_ERROR, "HTTP connection failed" },
+ { UPDATE_HTTP_NOT200,"HTTP Error (not an EnergyManager)" },
+ { UPDATE_HTTP_SID, "No Session ID found (not an EnergyManager)" },
+ { UPDATE_JSON_ERROR, "Invalid JSON HTTP response (not an EnergyManager)" },
+ { UPDATE_AUTH_ERROR, "Authentication failed" },
+ { UPDATE_VERSION, "Invalid EnergyManager version" },
+ { UPDATE_FILE, "Could not open update file" }
+};
+
+void mapEmcError(int error, std::string& out)
+{
+ try {
+ out = error_map.at(error);
+ } catch (std::out_of_range) {
+ out = "Unknown";
+ }
+}
+
+static inline void dump_request(httplib::Request& req)
+{
+ std::cerr << "http-cli: " << req.method << " "
+ << req.path
+ << " with MIME " << req.get_header_value("Content-Type")
+ << " and SIZE " << req.body.length()
+ << std::endl;
+}
+
+static inline void dump_json(json11::Json& json)
+{
+ std::string str;
+ json.dump(str);
+ std::cerr << "json: " << (!str.empty() ? str : "empty") << std::endl;
+}
+
+inline void dump_class(UpdateFactory *uf)
+{
+ if (!uf->phpsessid.empty())
+ std::cerr << "Session: " << uf->phpsessid << std::endl;
+ if (!uf->hostname.empty())
+ std::cerr << "Host...: " << uf->hostname << ":" << uf->port << std::endl;
+ if (!uf->emc_serial.empty())
+ std::cerr << "Serial.: " << uf->emc_serial << std::endl;
+ if (!uf->emc_version.empty())
+ std::cerr << "Version: " << uf->emc_version << std::endl;
+}
+
+void UpdateFactory::setDest(const char *hostname, int port)
+{
+ cleanup();
+ this->hostname = hostname;
+ this->port = port;
+ http_client = new httplib::Client(hostname, port);
+ phpsessid = "";
+ emc_serial = "";
+ emc_version = "";
+ authenticated = false;
+ mapped_emc_version = EMC_UNKNOWN;
+}
+
+void UpdateFactory::setDest(std::string& hostname, int port)
+{
+ this->setDest(hostname.c_str(), port);
+}
+
+void UpdateFactory::setDest(std::string& hostname, std::string& port)
+{
+ this->setDest(hostname.c_str(), std::stoi(port));
+}
+
+void UpdateFactory::setUpdateFile(const char *update_file)
+{
+ this->update_file = std::string(update_file);
+}
+
+void UpdateFactory::setPass(const char *passwd)
+{
+ this->passwd = std::string(passwd);
+}
+
+void UpdateFactory::setPass(std::string& passwd)
+{
+ this->setPass(passwd.c_str());
+}
+
+static bool grepCookie(const std::string& setcookie, const char *name, std::string& out)
+{
+ std::string::size_type index;
+ std::string prefix = "=";
+
+ if (name) {
+ prefix = name;
+ }
+ index = setcookie.find(prefix, 0);
+ if (index == std::string::npos)
+ return false;
+ index = setcookie.find(';', 0);
+ if (index == std::string::npos)
+ index = setcookie.length()-1;
+
+ out = setcookie.substr(0, index);
+ return true;
+}
+
+int UpdateFactory::doAuth()
+{
+ httplib::Request req;
+ httplib::Response res1, res2;
+ json11::Json json;
+ std::string errmsg;
+
+ if (!http_client)
+ return UPDATE_HTTP_ERROR;
+
+ genRequest(req, "/start.php", nullptr);
+ if (!doGet(req, res1))
+ return UPDATE_HTTP_ERROR;
+ if (res1.status != 200)
+ return UPDATE_HTTP_NOT200;
+ if (!grepCookie(res1.get_header_value("Set-Cookie"), "PHPSESSID", phpsessid))
+ return UPDATE_HTTP_SID;
+ if (!parseJsonResult(res1, json, errmsg)) {
+ return UPDATE_JSON_ERROR;
+ }
+
+ dump_json(json);
+ emc_serial = json["serial"].string_value();
+ emc_version = json["app_version"].string_value();
+ mapped_emc_version = mapEmcVersion(emc_version);
+ if (mapped_emc_version == EMC_UNKNOWN)
+ return UPDATE_VERSION;
+ authenticated = json["authentication"].bool_value();
+
+ if (!authenticated) {
+ std::ostringstream ostr;
+ ostr << "login=" << emc_serial << "&password=" << (passwd.c_str() ? passwd.c_str() : "");
+
+ genRequest(req, "/start.php", ostr.str().c_str());
+ if (!doPost(req, res2))
+ return UPDATE_HTTP_ERROR;
+ if (res2.status != 200)
+ return UPDATE_HTTP_NOT200;
+ if (!parseJsonResult(res2, json, errmsg))
+ return UPDATE_JSON_ERROR;
+ dump_json(json);
+ authenticated = json["authentication"].bool_value();
+ }
+
+ dump_class(this);
+ return UPDATE_OK;
+}
+
+int UpdateFactory::loadUpdateFile()
+{
+ std::ifstream input(update_file, std::ios::binary);
+ if (!input)
+ return UPDATE_FILE;
+ std::vector<unsigned char> buffer(
+ (std::istreambuf_iterator<char>(input)),
+ (std::istreambuf_iterator<char>())
+ );
+ update_buffer = buffer;
+
+ return UPDATE_OK;
+}
+
+int UpdateFactory::doUpdate()
+{
+ httplib::Request req;
+ httplib::Response res1, res2;
+ json11::Json json;
+ std::string errmsg;
+
+ if (!http_client)
+ return UPDATE_HTTP_ERROR;
+ if (mapped_emc_version == EMC_UNKNOWN)
+ return UPDATE_VERSION;
+ if (!authenticated)
+ return UPDATE_AUTH_ERROR;
+
+ /* Verify: Is this required before update? */
+ genRequest(req, "/setup.php?update_cleanup=1", nullptr);
+ if (!doGet(req, res1))
+ return UPDATE_HTTP_ERROR;
+ if (res1.status != 200)
+ return UPDATE_HTTP_NOT200;
+ if (!parseJsonResult(res1, json, errmsg)) {
+ return UPDATE_JSON_ERROR;
+ }
+ //dump_json(json);
+
+ /* The update process itself. */
+ std::ostringstream ostr;
+ std::string out;
+
+ ostr << "------WebKitFormBoundaryUPDATETOOL\r\n"
+ << "Content-Disposition: form-data; name=\"update_file\"; filename=\""
+ << update_file << "\"\r\n"
+ << "Content-Type: application/octet-stream\r\n\r\n";
+ ostr.write((const char*) &update_buffer[0], update_buffer.size());
+ ostr << "\r\n------WebKitFormBoundaryUPDATETOOL\r\n"
+ << "Content-Disposition: form-data; name=\"update_install\"\r\n\r\n\r\n"
+ << "------WebKitFormBoundaryUPDATETOOL--\r\n";
+ out = ostr.str();
+ genRequest(req, "/mum-webservice/0/update.php", out);
+ req.headers.erase("Content-Type");
+ req.set_header("Content-Type", "multipart/form-data; boundary=----WebKitFormBoundaryUPDATETOOL");
+ if (!doPost(req, res2))
+ return UPDATE_HTTP_ERROR;
+ if (res2.status != 200)
+ return UPDATE_HTTP_NOT200;
+
+ return UPDATE_OK;
+}
+
+void UpdateFactory::cleanup()
+{
+ if (http_client) {
+ delete http_client;
+ http_client = nullptr;
+ }
+ authenticated = false;
+ emc_serial = "";
+ emc_version = "";
+}
+
+void UpdateFactory::genRequest(httplib::Request& req, const char *path,
+ const char *body)
+{
+ std::string b( (body ? body : "") );
+ this->genRequest(req, path, b);
+}
+
+void UpdateFactory::genRequest(httplib::Request& req, const char *path,
+ std::string& body)
+{
+ if (!http_client)
+ return;
+
+ std::ostringstream ostr;
+ ostr << hostname;
+
+ req.headers.clear();
+ req.path = path;
+ req.set_header("Content-Type", "application/x-www-form-urlencoded");
+ req.set_header("Host", ostr.str().c_str());
+ if (!phpsessid.empty())
+ req.set_header("Cookie", phpsessid.c_str());
+ if (!body.empty()) {
+ req.body = body;
+ req.set_header("Content-Length", std::to_string(req.body.length()).c_str());
+ }
+}
+
+bool UpdateFactory::doGet(httplib::Request& req, httplib::Response& res)
+{
+ req.method = "GET";
+ dump_request(req);
+
+ return http_client->send(req, res);
+}
+
+bool UpdateFactory::doPost(httplib::Request& req, httplib::Response& res)
+{
+ req.method = "POST";
+ dump_request(req);
+
+ if (!http_client)
+ return false;
+ return http_client->send(req, res);
+}
+
+bool UpdateFactory::parseJsonResult(httplib::Response& res, json11::Json& result, std::string& errmsg)
+{
+ if (res.status != 200) {
+ std::ostringstream os;
+ os << "HTTP Response Code " << res.status;
+ errmsg = os.str();
+ return false;
+ }
+
+ result = json11::Json::parse(res.body, errmsg);
+ return !result.is_null();
+}
+
+int loadUpdateFactoriesFromCSV(const char *csv_file, const char *update_file, std::vector<UpdateFactory*>& update_list)
+{
+ std::vector<int> err_line;
+ io::CSVReader<3> in(csv_file);
+ in.read_header(io::ignore_extra_column, "hostname", "port", "password");
+ std::string hostname, port, passwd;
+
+ try {
+ while (in.read_row(hostname, port, passwd)) {
+ UpdateFactory *uf = new UpdateFactory();
+ uf->setDest(hostname, port);
+ uf->setPass(passwd);
+ uf->setUpdateFile(update_file);
+ update_list.push_back(uf);
+ }
+ } catch (io::error::with_file_line& err) {
+ err_line.push_back(err.file_line);
+ } catch (io::error::with_file_name& err) {
+ }
+
+ return UPDATE_OK;
+}
diff --git a/src/UpdateFactory.hpp b/src/UpdateFactory.hpp
new file mode 100644
index 0000000..e8f0a07
--- /dev/null
+++ b/src/UpdateFactory.hpp
@@ -0,0 +1,77 @@
+#ifndef UPDATE_FACTORY_H
+#define UPDATE_FACTORY_H 1
+
+#include <string>
+#include <vector>
+
+#include "Http.hpp"
+#include "Json.hpp"
+
+#define UPDATE_OK 0
+#define UPDATE_HTTP_ERROR 1
+#define UPDATE_HTTP_NOT200 2
+#define UPDATE_HTTP_SID 3
+#define UPDATE_JSON_ERROR 4
+#define UPDATE_AUTH_ERROR 5
+#define UPDATE_VERSION 6
+#define UPDATE_FILE 7
+
+
+enum EMCVersion {
+ EMC_150, EMC_204,
+ EMC_204RC5, EMC_204RC6 /* only for testing */,
+ EMC_UNKNOWN
+};
+
+enum EMCVersion mapEmcVersion(std::string& emc_version);
+
+void mapEmcError(int error, std::string& out);
+
+class UpdateFactory
+{
+public:
+ explicit UpdateFactory() {}
+ UpdateFactory(const UpdateFactory&) = delete;
+ ~UpdateFactory() { cleanup(); }
+
+ void setDest(const char *hostname, int port);
+ void setDest(std::string& hostname, int port);
+ void setDest(std::string& hostname, std::string& port);
+ void setUpdateFile(const char *update_file);
+ void setPass(const char *passwd);
+ void setPass(std::string& passwd);
+ const char *getUpdateFile() const { return this->update_file.c_str(); }
+ const char *getHostname() const { return this->hostname.c_str(); }
+ const char *getPassword() const { return this->passwd.c_str(); }
+ int getPort() const { return this->port; }
+ int doAuth();
+ int loadUpdateFile();
+ int doUpdate();
+ friend void dump_class(UpdateFactory *uf);
+protected:
+ std::string phpsessid;
+ std::string emc_serial;
+ std::string emc_version;
+ bool authenticated;
+ enum EMCVersion mapped_emc_version;
+ std::string update_file;
+ std::string passwd;
+ std::vector<unsigned char> update_buffer;
+private:
+ void cleanup();
+ void genRequest(httplib::Request& req, const char *path,
+ const char *body);
+ void genRequest(httplib::Request& req, const char *path,
+ std::string& body);
+ bool doGet(httplib::Request& req, httplib::Response& res);
+ bool doPost(httplib::Request& req, httplib::Response& res);
+ bool parseJsonResult(httplib::Response& res, json11::Json& result, std::string& errmsg);
+
+ httplib::Client *http_client = nullptr;
+ std::string hostname;
+ int port;
+};
+
+int loadUpdateFactoriesFromCSV(const char *csv_file, const char *update_file, std::vector<UpdateFactory*>& update_list);
+
+#endif
diff --git a/src/UpdateGUI.cpp b/src/UpdateGUI.cpp
new file mode 100644
index 0000000..e08870d
--- /dev/null
+++ b/src/UpdateGUI.cpp
@@ -0,0 +1,277 @@
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+#include "UpdateGUI.hpp"
+#include "JobQueue.hpp"
+
+#include <iostream>
+#include <iomanip>
+#include <chrono>
+#include <ctime>
+#include <wx/aboutdlg.h>
+
+
+wxBEGIN_EVENT_TABLE(UpdateGUIFrame, wxFrame)
+ EVT_CLOSE(UpdateGUIFrame::OnClose)
+
+ EVT_MENU(wxID_EXIT, UpdateGUIFrame::OnExit)
+ EVT_MENU(wxID_ABOUT, UpdateGUIFrame::OnAbout)
+ EVT_MENU(wxID_EDITOR, UpdateGUIFrame::OnEditor)
+ EVT_MENU(wxID_UPDATEFILE, UpdateGUIFrame::OnUpdateFile)
+ EVT_MENU(wxID_DOUPDATE, UpdateGUIFrame::OnUpdate)
+
+ EVT_BUTTON(wxID_UPDATEFILE, UpdateGUIFrame::OnUpdateFile)
+ EVT_BUTTON(wxID_IMPORTCSV, UpdateGUIFrame::OnImportCSV)
+ EVT_BUTTON(wxID_DOUPDATE, UpdateGUIFrame::OnUpdate)
+
+ EVT_COMMAND(wxID_ANY, wxEVT_THREAD, UpdateGUIFrame::OnThread)
+wxEND_EVENT_TABLE()
+
+
+bool UpdateGUI::OnInit()
+{
+ UpdateGUIFrame *frame = new UpdateGUIFrame("UpdateTool",
+ wxPoint(50, 50), wxSize(450, 340));
+ frame->Show(true);
+ return true;
+}
+
+UpdateGUIFrame::UpdateGUIFrame(const wxString& title, const wxPoint& pos, const wxSize& size)
+ : wxFrame(NULL, wxID_ANY, title, pos, size)
+{
+ wxMenu *menuFile = new wxMenu;
+ menuFile->Append(wxID_UPDATEFILE,
+ "&Select Image ...\tCtrl-F",
+ "Select a firmware image.");
+ menuFile->Append(wxID_DOUPDATE,
+ "&Submit\tCtrl-U",
+ "Start the firmware update process.");
+ menuFile->AppendSeparator();
+ menuFile->Append(wxID_EXIT);
+
+ wxMenu *menuHelp = new wxMenu;
+ menuHelp->Append(wxID_ABOUT);
+
+ wxMenuBar *menuBar = new wxMenuBar;
+ menuBar->Append(menuFile, "&File");
+ menuBar->Append(menuHelp, "&Help");
+
+ SetMenuBar(menuBar);
+ CreateStatusBar();
+ SetStatusText("Initialised");
+
+ mainVSizer = new wxBoxSizer(wxVERTICAL);
+ ipBox = new wxStaticBoxSizer(wxHORIZONTAL, this, "IP address (or FQDN)");
+ pwBox = new wxStaticBoxSizer(wxHORIZONTAL, this, "Device Password");
+ imgBox = new wxStaticBoxSizer(wxHORIZONTAL, this, "Firmware Image");
+ subBox = new wxStaticBoxSizer(wxHORIZONTAL, this);
+ logBox = new wxStaticBoxSizer(wxHORIZONTAL, this, "Status Log");
+
+ imgButton = new wxButton(this, wxID_UPDATEFILE, wxT("Select ..."));
+ imgBox->Add(imgButton, 0, wxALIGN_LEFT|wxALL, 5);
+ subButton = new wxButton(this, wxID_DOUPDATE, wxT("Submit"));
+ csvButton = new wxButton(this, wxID_IMPORTCSV, wxT("Import CSV ..."));
+ subBox->AddStretchSpacer();
+ subBox->Add(csvButton, 0, wxALL, 5);
+ subBox->Add(subButton, 0, wxALL, 5);
+ subBox->AddStretchSpacer();
+ ipEntry = new wxTextCtrl(this, wxID_IP);
+ ipBox->Add(ipEntry, 1, wxEXPAND|wxALL, 5);
+ pwEntry = new wxTextCtrl(this, wxID_PW, wxEmptyString, wxDefaultPosition,
+ wxDefaultSize, wxTE_PASSWORD);
+ pwBox->Add(pwEntry, 1, wxEXPAND|wxALL, 5);
+ imgEntry = new wxTextCtrl(this, wxID_IMG, wxEmptyString, wxDefaultPosition,
+ wxDefaultSize, wxTE_READONLY
+ );
+ imgBox->Add(imgEntry, 1, wxALIGN_CENTER|wxALL, 5);
+ logText = new wxTextCtrl(
+ this, wxID_EDITOR, wxEmptyString, wxDefaultPosition,
+ wxDefaultSize, wxTE_MULTILINE | wxTE_READONLY | wxTE_RICH2
+ );
+ logBox->Add(logText, 1, wxEXPAND|wxALL, 5);
+
+ mainVSizer->Add(ipBox, 0, wxEXPAND|wxALL|wxTOP, 5);
+ mainVSizer->Add(pwBox, 0, wxEXPAND|wxALL, 5);
+ mainVSizer->Add(imgBox, 0, wxEXPAND|wxALL, 5);
+ mainVSizer->Add(subBox, 0, wxEXPAND|wxALL, 5);
+ mainVSizer->Add(logBox, 1, wxEXPAND|wxALL|wxBOTTOM, 5);
+
+ SetSizerAndFit(mainVSizer);
+ jobs = new Queue(this);
+ {
+ for (int tid = 1; tid <= 2; ++tid) {
+ threads.push_back(tid);
+ WorkerThread* thread = new WorkerThread(jobs, tid);
+ if (thread) thread->Run();
+ }
+ }
+ tLog(RTL_DEFAULT, "UpdateTool started (wxGUI).");
+}
+
+void UpdateGUIFrame::tLog(enum LogType type, const char *text, const char *ident)
+{
+ switch (type) {
+ case RTL_DEFAULT:
+ logText->SetDefaultStyle(wxTextAttr(*wxBLACK));
+ break;
+ case RTL_GREEN:
+ logText->SetDefaultStyle(wxTextAttr(*wxGREEN));
+ break;
+ case RTL_RED:
+ logText->SetDefaultStyle(wxTextAttr(*wxRED));
+ break;
+ }
+ std::ostringstream out;
+ auto timestamp = std::time(nullptr);
+ out << "[" << std::put_time(std::localtime(&timestamp), "%H:%M:%S") << "] ";
+ if (ident)
+ out << "[" << ident << "] ";
+ out << text << std::endl;
+ logText->AppendText(out.str());
+}
+
+void UpdateGUIFrame::tLog(enum LogType type, std::string& text, const char *ident)
+{
+ this->tLog(type, text.c_str(), ident);
+}
+
+void UpdateGUIFrame::OnClose(wxCloseEvent& event)
+{
+ for (unsigned i = 0; i < threads.size(); ++i) {
+ jobs->AddJob(Job(Job::eID_THREAD_EXIT, JobArgs()), Queue::eHIGHEST);
+ }
+ if (!threads.empty()) return;
+
+ for (auto *p : {ipEntry,pwEntry,imgEntry,logText}) { p->Destroy(); }
+ imgButton->Destroy();
+ subButton->Destroy();
+ for (auto *p : {ipBox,pwBox,imgBox,subBox,logBox}) { p->Clear(); }
+ mainVSizer->Clear();
+ Destroy();
+}
+
+void UpdateGUIFrame::OnExit(wxCommandEvent& event)
+{
+ Close(true);
+}
+
+#ifndef PACKAGE_NAME
+#define PACKAGE_NAME "UpdateTool"
+#endif
+#ifndef PACKAGE_VERSION
+#define PACKAGE_VERSION "unknown"
+#endif
+#ifndef PACKAGE_URL
+#define PACKAGE_URL "https://some_download_website.tld"
+#endif
+
+void UpdateGUIFrame::OnAbout(wxCommandEvent& event)
+{
+ wxAboutDialogInfo aboutInfo;
+ aboutInfo.SetName(PACKAGE_NAME);
+ aboutInfo.SetVersion(PACKAGE_VERSION);
+ aboutInfo.SetDescription("A simple firmware update tool.");
+ aboutInfo.SetCopyright("(C) 2017");
+ aboutInfo.SetWebSite(PACKAGE_URL);
+ aboutInfo.AddDeveloper("Toni Uhlig");
+ aboutInfo.AddDeveloper("Valeri Budjko");
+ wxAboutBox(aboutInfo, this);
+}
+
+void UpdateGUIFrame::OnEditor(wxCommandEvent& event)
+{
+}
+
+void UpdateGUIFrame::OnUpdateFile(wxCommandEvent& event)
+{
+ wxString log;
+ wxFileDialog openFileDialog(this, _("Select Update File"), "", "",
+ "image files (*.image)|*.image", wxFD_OPEN|wxFD_FILE_MUST_EXIST);
+
+ if (openFileDialog.ShowModal() == wxID_CANCEL) {
+ openFileDialog.Destroy();
+ return;
+ }
+
+ imgEntry->Clear();
+ imgEntry->AppendText(openFileDialog.GetPath());
+ log = wxString::Format(wxT("Update File: \"%s\""), openFileDialog.GetPath());
+ tLog(RTL_DEFAULT, log);
+
+ openFileDialog.Destroy();
+}
+
+void UpdateGUIFrame::OnImportCSV(wxCommandEvent& event)
+{
+ int rv;
+ std::vector<UpdateFactory*> uf;
+ std::string err;
+ wxString log;
+ wxFileDialog openFileDialog(this, _("Select Update CSV"), "", "",
+ "image files (*.csv)|*.csv", wxFD_OPEN|wxFD_FILE_MUST_EXIST);
+
+ if (openFileDialog.ShowModal() == wxID_CANCEL) {
+ openFileDialog.Destroy();
+ return;
+ }
+
+ log = wxString::Format(wxT("CSV File: \"%s\""), openFileDialog.GetPath());
+ tLog(RTL_DEFAULT, log);
+ rv = loadUpdateFactoriesFromCSV(openFileDialog.GetPath(), imgEntry->GetValue(), uf);
+ if (rv != UPDATE_OK) {
+ mapEmcError(rv, err);
+ tLog(RTL_RED, wxString::Format(wxT("CSV parse failed: \"%s\""), err));
+ }
+ for (auto *u : uf) {
+ int jobid = rand();
+ jobs->AddJob(Job(Job::eID_THREAD_JOB, JobArgs(jobid, *u)));
+ }
+ SetStatusText(wxString::Format(wxT("CSV Import %s"), openFileDialog.GetPath()));
+ openFileDialog.Destroy();
+}
+
+void UpdateGUIFrame::OnUpdate(wxCommandEvent& event)
+{
+ int jobid = rand();
+ std::ostringstream log;
+ std::string str;
+
+ log << "Update started ... (Job: #" << jobid << ")";
+ tLog(RTL_DEFAULT, log.str().c_str());
+ jobs->AddJob(Job(Job::eID_THREAD_JOB,
+ JobArgs(jobid, ipEntry->GetValue(), 80,
+ imgEntry->GetValue(), pwEntry->GetValue())
+ ));
+ SetStatusText(wxString::Format(wxT("Job #%i started."), jobid));
+}
+
+void UpdateGUIFrame::OnThread(wxCommandEvent& event)
+{
+ wxString wxs;
+ LogType tp = RTL_DEFAULT;
+
+ switch (event.GetId()) {
+ case Job::eID_THREAD_JOB:
+ case Job::eID_THREAD_MSG:
+ case Job::eID_THREAD_MSGOK:
+ case Job::eID_THREAD_MSGERR:
+ wxs = wxString::Format(wxT("Thread [%i]: \"%s\""), event.GetInt(), event.GetString().c_str());
+
+ switch (event.GetId()) {
+ case Job::eID_THREAD_JOB: SetStatusText(wxs); break;
+ case Job::eID_THREAD_MSGOK: tp = RTL_GREEN; break;
+ case Job::eID_THREAD_MSGERR: tp = RTL_RED; break;
+ }
+ tLog(tp, wxs);
+ break;
+ case Job::eID_THREAD_EXIT:
+ SetStatusText(wxString::Format(wxT("Thread [%i]: Stopped."), event.GetInt()));
+ threads.remove(event.GetInt());
+ if (threads.empty()) { this->OnExit(event); }
+ break;
+ case Job::eID_THREAD_STARTED:
+ SetStatusText(wxString::Format(wxT("Thread [%i]: Ready."), event.GetInt()));
+ break;
+ default: event.Skip();
+ }
+}
diff --git a/src/UpdateGUI.hpp b/src/UpdateGUI.hpp
new file mode 100644
index 0000000..bba0799
--- /dev/null
+++ b/src/UpdateGUI.hpp
@@ -0,0 +1,61 @@
+#ifndef UPDATEGUI_H
+#define UPDATEGUI_H 1
+
+#include <string>
+#include <wx/wxprec.h>
+
+#ifndef WX_PRECOMP
+#include <wx/wx.h>
+#endif
+
+#include <wx/richtext/richtextctrl.h>
+
+#include "UpdateFactory.hpp"
+#include "JobQueue.hpp"
+
+
+enum LogType { RTL_DEFAULT, RTL_GREEN, RTL_RED };
+enum {
+ wxID_EDITOR = wxID_HIGHEST + 1,
+ wxID_IP, wxID_PW, wxID_IMG,
+ wxID_UPDATEFILE, wxID_IMPORTCSV, wxID_DOUPDATE
+};
+
+class UpdateGUI : public wxApp
+{
+public:
+ virtual bool OnInit();
+};
+
+class UpdateGUIFrame: public wxFrame
+{
+public:
+ UpdateGUIFrame(const wxString& title, const wxPoint& pos, const wxSize& size);
+ ~UpdateGUIFrame() {}
+protected:
+ UpdateFactory uf;
+private:
+ void tLog(enum LogType type, const char *text, const char *ident=nullptr);
+ void tLog(enum LogType type, std::string& text, const char *ident=nullptr);
+
+ void OnClose(wxCloseEvent& event);
+ void OnExit(wxCommandEvent& event);
+ void OnAbout(wxCommandEvent& event);
+ void OnEditor(wxCommandEvent& event);
+ void OnUpdateFile(wxCommandEvent& event);
+ void OnImportCSV(wxCommandEvent& event);
+ void OnUpdate(wxCommandEvent& event);
+ void OnThread(wxCommandEvent& event);
+
+ wxDECLARE_EVENT_TABLE();
+
+ wxBoxSizer *mainVSizer;
+ wxStaticBoxSizer *ipBox, *pwBox, *imgBox, *subBox, *logBox;
+ wxButton *imgButton, *subButton, *csvButton;
+ wxTextCtrl *ipEntry, *pwEntry, *imgEntry, *logText;
+
+ Queue *jobs;
+ std::list<int> threads;
+};
+
+#endif
diff --git a/src/UpdateTool.cpp b/src/UpdateTool.cpp
new file mode 100644
index 0000000..55da6d4
--- /dev/null
+++ b/src/UpdateTool.cpp
@@ -0,0 +1,89 @@
+#include <cstdio>
+#include <iostream>
+
+#include "UpdateFactory.hpp"
+
+#ifdef USE_GUI
+#include "UpdateGUI.hpp"
+
+wxIMPLEMENT_APP(UpdateGUI);
+
+#if defined(_UNICODE) && defined(WIN32)
+int wmain(int argc, wchar_t* wargv[])
+{
+ wxEntryStart(argc, wargv);
+ wxTheApp->CallOnInit();
+ wxTheApp->OnRun();
+ return 0;
+}
+#endif
+
+#else
+
+int main(int argc, char **argv)
+{
+ int rv;
+ std::vector<UpdateFactory*> uf;
+ std::string errstr;
+
+ if (argc == 0)
+ return 1;
+ if (argc == 3) {
+ uf.clear();
+ rv = loadUpdateFactoriesFromCSV(argv[1], argv[2], uf);
+ if (rv != UPDATE_OK) {
+ std::cerr << "CSV file read \"" << argv[1] << "\" failed with: " << rv << std::endl;
+ return 1;
+ }
+ } else
+ if (argc == 4) {
+ uf.push_back(new UpdateFactory());
+ uf[0]->setDest(argv[1], 80);
+ uf[0]->setPass(argv[2]);
+ uf[0]->setUpdateFile(argv[3]);
+ } else {
+ std::cerr << "Missing CLI arguments, using defaults .." << std::endl
+ << "usage: " << argv[0]
+ << " [update-csv-file]|[[hostname] [password]] [update-file]"
+ << std::endl << std::endl;
+ return 1;
+ }
+
+ for (auto *u : uf) {
+ rv = u->doAuth();
+ mapEmcError(rv, errstr);
+ std::cerr << "doAuth returned " << rv << ": " << errstr << std::endl;
+ if (rv == UPDATE_OK) {
+ std::cerr << "uploading file " << u->getUpdateFile() << std::endl;
+ rv = u->loadUpdateFile();
+ mapEmcError(rv, errstr);
+ std::cerr << "load file returned " << rv << ": " << errstr << std::endl;
+ rv = u->doUpdate();
+ mapEmcError(rv, errstr);
+ std::cerr << "doUpdate returned " << rv << ": " << errstr << std::endl;
+ }
+ }
+
+ for (auto *u : uf) {
+ delete u;
+ }
+ return 0;
+}
+
+#if defined(_UNICODE) && defined(WIN32)
+int wmain(int argc, wchar_t* wargv[])
+{
+ size_t len;
+ static char **argv = new char*[argc];
+
+ /* convert wide character argvector to ASCII */
+ for (int i = 0; i < argc; ++i) {
+ len = wcslen(wargv[i]) * sizeof(wchar_t);
+ argv[i] = (char *) calloc(len+1, sizeof(char));
+ wcstombs(argv[i], wargv[i], len);
+ fprintf(stderr, "arg[%d]: %s\n", i, argv[i]);
+ }
+ return main(argc, argv);
+}
+#endif
+#endif
diff --git a/wxWidgets b/wxWidgets
new file mode 160000
+Subproject cbb799b1ae3f309c99beb0d287e9bb3b62ea405