aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authortoni <toni@pdp7.tq-net.de>2017-11-16 15:07:12 +0100
committerToni Uhlig <Toni.Uhlig@tq-group.com>2017-11-27 15:36:43 +0100
commitef0f08a3d4d8eeb118592bc96480109d7b78e37b (patch)
tree8e57959487f08dd743b2728ab6fc0269b17cf4bd /src
parent91f979252aa3ce910b2a7a577f807a941b24a555 (diff)
initial commit
Diffstat (limited to 'src')
-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
12 files changed, 4648 insertions, 0 deletions
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