From 86f49d72a7c99d62b9840789dfee9641bffac499 Mon Sep 17 00:00:00 2001 From: DL6ER Date: Tue, 22 Oct 2019 20:12:43 +0200 Subject: [PATCH 0001/1669] Add HTTP server implementation to FTL. Signed-off-by: DL6ER --- Makefile | 27 +- src/api/socket.c | 5 + src/civetweb/civetweb.c | 19881 +++++++++++++++++++++++++++++++++ src/civetweb/civetweb.h | 1541 +++ src/civetweb/handle_form.inl | 1060 ++ src/civetweb/http.h | 18 + src/civetweb/md5.inl | 471 + src/civetweb/sha1.inl | 323 + src/civetweb/timer.inl | 260 + src/config.c | 18 + src/config.h | 6 + src/main.c | 3 + 12 files changed, 23611 insertions(+), 2 deletions(-) create mode 100644 src/civetweb/civetweb.c create mode 100644 src/civetweb/civetweb.h create mode 100644 src/civetweb/handle_form.inl create mode 100644 src/civetweb/http.h create mode 100644 src/civetweb/md5.inl create mode 100644 src/civetweb/sha1.inl create mode 100644 src/civetweb/timer.inl diff --git a/Makefile b/Makefile index 2a65ea4f6..4ac685162 100644 --- a/Makefile +++ b/Makefile @@ -16,7 +16,7 @@ DNSMASQ_OPTS = -DHAVE_DNSSEC -DHAVE_DNSSEC_STATIC -DHAVE_IDN FTL_DEPS = *.h database/*.h api/*.h version.h FTL_DB_OBJ = database/common.o database/query-table.o database/network-table.o database/gravity-db.o database/database-thread.o \ - database/sqlite3-ext.o database/message-table.o + database/sqlite3-ext.o database/message-table.o api/http.o FTL_API_OBJ = api/socket.o api/request.o api/msgpack.o api/api.o FTL_OBJ = $(FTL_DB_OBJ) $(FTL_API_OBJ) main.o memory.o log.o daemon.o datastructure.o signals.o files.o setupVars.o args.o gc.o config.o \ dnsmasq_interface.o resolve.o regex.o shmem.o capabilities.o overTime.o timers.o vector.o @@ -26,6 +26,17 @@ DNSMASQ_OBJ = arp.o dbus.o domain.o lease.o outpacket.o rrfilter.o auth.o dhcp6. loop.o radv.o tables.o bpf.o dhcp-common.o helper.o netlink.o rfc1035.o tftp.o cache.o dnsmasq.o inotify.o network.o rfc2131.o \ util.o conntrack.o dnssec.o ipset.o option.o rfc3315.o crypto.o dump.o ubus.o metrics.o +# We can remove the NO_SSL later on. It adds additional constraints to the build system (availablity of libSSL-dev) +# -DNO_CGI = no CGI support (we don't need it) +# -DNO_SSL_DL -DNO_SSL = no SSL support (for now) +# -DUSE_SERVER_STATS = makes a few anonymous statistics available, such as +# - Number of connections (currently and total) +# - Amount of data read and written +# -DUSE_IPV6: add IPv6 support +CIVETWEB_OPTS = -DNO_CGI -DNO_SSL_DL -DNO_SSL -DUSE_SERVER_STATS -DUSE_IPV6 +CIVETWEB_DEPS = civetweb.h +CIVETWEB_OBJ = civetweb.o + # Get git commit version and date GIT_BRANCH := $(shell git branch | sed -n 's/^\* //p') GIT_HASH := $(shell git --no-pager describe --always --dirty) @@ -120,6 +131,7 @@ endif DB_OBJ_DIR = $(ODIR)/database API_OBJ_DIR = $(ODIR)/api DNSMASQ_OBJ_DIR = $(ODIR)/dnsmasq +CIVETWEB_OBJ_DIR = $(ODIR)/civetweb _FTL_DEPS = $(patsubst %,$(IDIR)/%,$(FTL_DEPS)) _FTL_OBJ = $(patsubst %,$(ODIR)/%,$(FTL_OBJ)) @@ -127,6 +139,9 @@ _FTL_OBJ = $(patsubst %,$(ODIR)/%,$(FTL_OBJ)) _DNSMASQ_DEPS = $(patsubst %,$(IDIR)/dnsmasq/%,$(DNSMASQ_DEPS)) _DNSMASQ_OBJ = $(patsubst %,$(DNSMASQ_OBJ_DIR)/%,$(DNSMASQ_OBJ)) +_CIVETWEB_DEPS = $(patsubst %,$(IDIR)/civetweb/%,$(CIVETWEB_DEPS)) +_CIVETWEB_OBJ = $(patsubst %,$(CIVETWEB_OBJ_DIR)/%,$(CIVETWEB_OBJ)) + all: pihole-FTL # Compile FTL source code files with virtually all possible warnings a modern gcc can generate @@ -138,6 +153,11 @@ $(_FTL_OBJ): $(ODIR)/%.o: $(IDIR)/%.c $(_FTL_DEPS) | $(ODIR) $(DB_OBJ_DIR) $(API $(_DNSMASQ_OBJ): $(DNSMASQ_OBJ_DIR)/%.o: $(IDIR)/dnsmasq/%.c $(_DNSMASQ_DEPS) | $(DNSMASQ_OBJ_DIR) $(CC) -c -o $@ $< -g3 $(CCFLAGS) -DVERSION=\"$(DNSMASQ_VERSION)\" $(DNSMASQ_OPTS) +# Compile the contained dnsmasq code with much less strict requirements as it would fail to comply +# when enforcing the standards we enforce for the rest of our FTL code base +$(_CIVETWEB_OBJ): $(CIVETWEB_OBJ_DIR)/%.o: $(IDIR)/civetweb/%.c $(_CIVETWEB_DEPS) | $(CIVETWEB_OBJ_DIR) + $(CC) -c -o $@ $< -g3 $(CCFLAGS) $(CIVETWEB_OPTS) + $(DB_OBJ_DIR)/sqlite3.o: $(IDIR)/database/sqlite3.c | $(DB_OBJ_DIR) $(CC) -c -o $@ $< -g3 $(CCFLAGS) @@ -153,7 +173,10 @@ $(API_OBJ_DIR): $(DNSMASQ_OBJ_DIR): mkdir -p $(DNSMASQ_OBJ_DIR) -pihole-FTL: $(_FTL_OBJ) $(_DNSMASQ_OBJ) $(DB_OBJ_DIR)/sqlite3.o +$(CIVETWEB_OBJ_DIR): + mkdir -p $(CIVETWEB_OBJ_DIR) + +pihole-FTL: $(_FTL_OBJ) $(_DNSMASQ_OBJ) $(_CIVETWEB_OBJ) $(DB_OBJ_DIR)/sqlite3.o $(CC) $(CCFLAGS) -o $@ $^ $(LIBS) .PHONY: clean force install diff --git a/src/api/socket.c b/src/api/socket.c index 34c452373..66cc9fdee 100644 --- a/src/api/socket.c +++ b/src/api/socket.c @@ -17,6 +17,8 @@ #include "memory.h" // global variable killed #include "signals.h" +// http_init() +#include "api/http.h" // The backlog argument defines the maximum length // to which the queue of pending connections for @@ -429,6 +431,9 @@ void bind_sockets(void) // Initialize Unix socket bind_to_unix_socket(&socketfd); + + // Initialize HTTP server + http_init(); } void *telnet_listening_thread_IPv4(void *args) diff --git a/src/civetweb/civetweb.c b/src/civetweb/civetweb.c new file mode 100644 index 000000000..20f24c118 --- /dev/null +++ b/src/civetweb/civetweb.c @@ -0,0 +1,19881 @@ +/* Copyright (c) 2013-2018 the Civetweb developers + * Copyright (c) 2004-2013 Sergey Lyubka + * + * 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. + */ + +#if defined(__GNUC__) || defined(__MINGW32__) +#define GCC_VERSION \ + (__GNUC__ * 10000 + __GNUC_MINOR__ * 100 + __GNUC_PATCHLEVEL__) +#if GCC_VERSION >= 40500 +/* gcc diagnostic pragmas available */ +#define GCC_DIAGNOSTIC +#endif +#endif + +#if defined(GCC_DIAGNOSTIC) +/* Disable unused macros warnings - not all defines are required + * for all systems and all compilers. */ +#pragma GCC diagnostic ignored "-Wunused-macros" +/* A padding warning is just plain useless */ +#pragma GCC diagnostic ignored "-Wpadded" +#endif + +#if defined(__clang__) /* GCC does not (yet) support this pragma */ +/* We must set some flags for the headers we include. These flags + * are reserved ids according to C99, so we need to disable a + * warning for that. */ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wreserved-id-macro" +#endif + +#if defined(_WIN32) +#if !defined(_CRT_SECURE_NO_WARNINGS) +#define _CRT_SECURE_NO_WARNINGS /* Disable deprecation warning in VS2005 */ +#endif +#if !defined(_WIN32_WINNT) /* defined for tdm-gcc so we can use getnameinfo */ +#define _WIN32_WINNT 0x0501 +#endif +#else +#if !defined(_GNU_SOURCE) +#define _GNU_SOURCE /* for setgroups(), pthread_setname_np() */ +#endif +#if defined(__linux__) && !defined(_XOPEN_SOURCE) +#define _XOPEN_SOURCE 600 /* For flockfile() on Linux */ +#endif +#if defined(__LSB_VERSION__) +#define NEED_TIMEGM +#define NO_THREAD_NAME +#endif +#if !defined(_LARGEFILE_SOURCE) +#define _LARGEFILE_SOURCE /* For fseeko(), ftello() */ +#endif +#if !defined(_FILE_OFFSET_BITS) +#define _FILE_OFFSET_BITS 64 /* Use 64-bit file offsets by default */ +#endif +#if !defined(__STDC_FORMAT_MACROS) +#define __STDC_FORMAT_MACROS /* wants this for C++ */ +#endif +#if !defined(__STDC_LIMIT_MACROS) +#define __STDC_LIMIT_MACROS /* C++ wants that for INT64_MAX */ +#endif +#if !defined(_DARWIN_UNLIMITED_SELECT) +#define _DARWIN_UNLIMITED_SELECT +#endif +#if defined(__sun) +#define __EXTENSIONS__ /* to expose flockfile and friends in stdio.h */ +#define __inline inline /* not recognized on older compiler versions */ +#endif +#endif + +#if defined(__clang__) +/* Enable reserved-id-macro warning again. */ +#pragma GCC diagnostic pop +#endif + + +#if defined(USE_LUA) +#define USE_TIMERS +#endif + +#if defined(_MSC_VER) +/* 'type cast' : conversion from 'int' to 'HANDLE' of greater size */ +#pragma warning(disable : 4306) +/* conditional expression is constant: introduced by FD_SET(..) */ +#pragma warning(disable : 4127) +/* non-constant aggregate initializer: issued due to missing C99 support */ +#pragma warning(disable : 4204) +/* padding added after data member */ +#pragma warning(disable : 4820) +/* not defined as a preprocessor macro, replacing with '0' for '#if/#elif' */ +#pragma warning(disable : 4668) +/* no function prototype given: converting '()' to '(void)' */ +#pragma warning(disable : 4255) +/* function has been selected for automatic inline expansion */ +#pragma warning(disable : 4711) +#endif + + +/* This code uses static_assert to check some conditions. + * Unfortunately some compilers still do not support it, so we have a + * replacement function here. */ +#if defined(__STDC_VERSION__) && __STDC_VERSION__ > 201100L +#define mg_static_assert _Static_assert +#elif defined(__cplusplus) && __cplusplus >= 201103L +#define mg_static_assert static_assert +#else +char static_assert_replacement[1]; +#define mg_static_assert(cond, txt) \ + extern char static_assert_replacement[(cond) ? 1 : -1] +#endif + +mg_static_assert(sizeof(int) == 4 || sizeof(int) == 8, + "int data type size check"); +mg_static_assert(sizeof(void *) == 4 || sizeof(void *) == 8, + "pointer data type size check"); +mg_static_assert(sizeof(void *) >= sizeof(int), "data type size check"); + + +/* Alternative queue is well tested and should be the new default */ +#if defined(NO_ALTERNATIVE_QUEUE) +#if defined(ALTERNATIVE_QUEUE) +#error "Define ALTERNATIVE_QUEUE or NO_ALTERNATIVE_QUEUE or none, but not both" +#endif +#else +#define ALTERNATIVE_QUEUE +#endif + +#if defined(NO_FILESYSTEMS) && !defined(NO_FILES) +#error "Inconsistent build flags, NO_FILESYSTEMS requires NO_FILES" +#endif + +/* DTL -- including winsock2.h works better if lean and mean */ +#if !defined(WIN32_LEAN_AND_MEAN) +#define WIN32_LEAN_AND_MEAN +#endif + +#if defined(__SYMBIAN32__) +/* According to https://en.wikipedia.org/wiki/Symbian#History, + * Symbian is no longer maintained since 2014-01-01. + * Recent versions of CivetWeb are no longer tested for Symbian. + * It makes no sense, to support an abandoned operating system. + */ +#error "Symbian is no longer maintained. CivetWeb no longer supports Symbian." +#define NO_SSL /* SSL is not supported */ +#define NO_CGI /* CGI is not supported */ +#define PATH_MAX FILENAME_MAX +#endif /* __SYMBIAN32__ */ + +#if defined(__ZEPHYR__) +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + +/* Max worker threads is the max of pthreads minus the main application thread + * and minus the main civetweb thread, thus -2 + */ +#define MAX_WORKER_THREADS (CONFIG_MAX_PTHREAD_COUNT - 2) + +#if defined(USE_STACK_SIZE) && (USE_STACK_SIZE > 1) +#define ZEPHYR_STACK_SIZE USE_STACK_SIZE +#else +#define ZEPHYR_STACK_SIZE 8096 +#endif + +K_THREAD_STACK_DEFINE(civetweb_main_stack, ZEPHYR_STACK_SIZE); +K_THREAD_STACK_ARRAY_DEFINE(civetweb_worker_stacks, + MAX_WORKER_THREADS, + ZEPHYR_STACK_SIZE); + +static int zephyr_worker_stack_index; + +#endif + +#if !defined(CIVETWEB_HEADER_INCLUDED) +/* Include the header file here, so the CivetWeb interface is defined for the + * entire implementation, including the following forward definitions. */ +#include "civetweb.h" +#endif + +#if !defined(DEBUG_TRACE) +#if defined(DEBUG) +static void DEBUG_TRACE_FUNC(const char *func, + unsigned line, + PRINTF_FORMAT_STRING(const char *fmt), + ...) PRINTF_ARGS(3, 4); + +#define DEBUG_TRACE(fmt, ...) \ + DEBUG_TRACE_FUNC(__func__, __LINE__, fmt, __VA_ARGS__) + +#define NEED_DEBUG_TRACE_FUNC + +#else +#define DEBUG_TRACE(fmt, ...) \ + do { \ + } while (0) +#endif /* DEBUG */ +#endif /* DEBUG_TRACE */ + + +#if !defined(DEBUG_ASSERT) +#if defined(DEBUG) +#define DEBUG_ASSERT(cond) \ + do { \ + if (!(cond)) { \ + DEBUG_TRACE("ASSERTION FAILED: %s", #cond); \ + exit(2); /* Exit with error */ \ + } \ + } while (0) +#else +#define DEBUG_ASSERT(cond) +#endif /* DEBUG */ +#endif + + +#if defined(__GNUC__) && defined(GCC_INSTRUMENTATION) +void __cyg_profile_func_enter(void *this_fn, void *call_site) + __attribute__((no_instrument_function)); + +void __cyg_profile_func_exit(void *this_fn, void *call_site) + __attribute__((no_instrument_function)); + +void +__cyg_profile_func_enter(void *this_fn, void *call_site) +{ + if ((void *)this_fn != (void *)printf) { + printf("E %p %p\n", this_fn, call_site); + } +} + +void +__cyg_profile_func_exit(void *this_fn, void *call_site) +{ + if ((void *)this_fn != (void *)printf) { + printf("X %p %p\n", this_fn, call_site); + } +} +#endif + + +#if !defined(IGNORE_UNUSED_RESULT) +#define IGNORE_UNUSED_RESULT(a) ((void)((a) && 1)) +#endif + + +#if defined(__GNUC__) || defined(__MINGW32__) + +/* GCC unused function attribute seems fundamentally broken. + * Several attempts to tell the compiler "THIS FUNCTION MAY BE USED + * OR UNUSED" for individual functions failed. + * Either the compiler creates an "unused-function" warning if a + * function is not marked with __attribute__((unused)). + * On the other hand, if the function is marked with this attribute, + * but is used, the compiler raises a completely idiotic + * "used-but-marked-unused" warning - and + * #pragma GCC diagnostic ignored "-Wused-but-marked-unused" + * raises error: unknown option after "#pragma GCC diagnostic". + * Disable this warning completely, until the GCC guys sober up + * again. + */ + +#pragma GCC diagnostic ignored "-Wunused-function" + +#define FUNCTION_MAY_BE_UNUSED /* __attribute__((unused)) */ + +#else +#define FUNCTION_MAY_BE_UNUSED +#endif + + +/* Some ANSI #includes are not available on Windows CE */ +#if !defined(_WIN32_WCE) && !defined(__ZEPHYR__) +#include +#include +#include +#include +#include +#include +#endif /* !_WIN32_WCE */ + + +#if defined(__clang__) +/* When using -Weverything, clang does not accept it's own headers + * in a release build configuration. Disable what is too much in + * -Weverything. */ +#pragma clang diagnostic ignored "-Wdisabled-macro-expansion" +#endif + +#if defined(__GNUC__) || defined(__MINGW32__) +/* Who on earth came to the conclusion, using __DATE__ should rise + * an "expansion of date or time macro is not reproducible" + * warning. That's exactly what was intended by using this macro. + * Just disable this nonsense warning. */ + +/* And disabling them does not work either: + * #pragma clang diagnostic ignored "-Wno-error=date-time" + * #pragma clang diagnostic ignored "-Wdate-time" + * So we just have to disable ALL warnings for some lines + * of code. + * This seems to be a known GCC bug, not resolved since 2012: + * https://gcc.gnu.org/bugzilla/show_bug.cgi?id=53431 + */ +#endif + + +#if defined(__MACH__) /* Apple OSX section */ + +#if defined(__clang__) +#if (__clang_major__ == 3) && ((__clang_minor__ == 7) || (__clang_minor__ == 8)) +/* Avoid warnings for Xcode 7. It seems it does no longer exist in Xcode 8 */ +#pragma clang diagnostic ignored "-Wno-reserved-id-macro" +#pragma clang diagnostic ignored "-Wno-keyword-macro" +#endif +#endif + +#define CLOCK_MONOTONIC (1) +#define CLOCK_REALTIME (2) + +#include +#include +#include +#include +#include + +/* clock_gettime is not implemented on OSX prior to 10.12 */ +static int +_civet_clock_gettime(int clk_id, struct timespec *t) +{ + memset(t, 0, sizeof(*t)); + if (clk_id == CLOCK_REALTIME) { + struct timeval now; + int rv = gettimeofday(&now, NULL); + if (rv) { + return rv; + } + t->tv_sec = now.tv_sec; + t->tv_nsec = now.tv_usec * 1000; + return 0; + + } else if (clk_id == CLOCK_MONOTONIC) { + static uint64_t clock_start_time = 0; + static mach_timebase_info_data_t timebase_ifo = {0, 0}; + + uint64_t now = mach_absolute_time(); + + if (clock_start_time == 0) { + kern_return_t mach_status = mach_timebase_info(&timebase_ifo); + DEBUG_ASSERT(mach_status == KERN_SUCCESS); + + /* appease "unused variable" warning for release builds */ + (void)mach_status; + + clock_start_time = now; + } + + now = (uint64_t)((double)(now - clock_start_time) + * (double)timebase_ifo.numer + / (double)timebase_ifo.denom); + + t->tv_sec = now / 1000000000; + t->tv_nsec = now % 1000000000; + return 0; + } + return -1; /* EINVAL - Clock ID is unknown */ +} + +/* if clock_gettime is declared, then __CLOCK_AVAILABILITY will be defined */ +#if defined(__CLOCK_AVAILABILITY) +/* If we compiled with Mac OSX 10.12 or later, then clock_gettime will be + * declared but it may be NULL at runtime. So we need to check before using + * it. */ +static int +_civet_safe_clock_gettime(int clk_id, struct timespec *t) +{ + if (clock_gettime) { + return clock_gettime(clk_id, t); + } + return _civet_clock_gettime(clk_id, t); +} +#define clock_gettime _civet_safe_clock_gettime +#else +#define clock_gettime _civet_clock_gettime +#endif + +#endif + + +/********************************************************************/ +/* CivetWeb configuration defines */ +/********************************************************************/ + +/* Maximum number of threads that can be configured. + * The number of threads actually created depends on the "num_threads" + * configuration parameter, but this is the upper limit. */ +#if !defined(MAX_WORKER_THREADS) +#define MAX_WORKER_THREADS (1024 * 64) /* in threads (count) */ +#endif + +/* Timeout interval for select/poll calls. + * The timeouts depend on "*_timeout_ms" configuration values, but long + * timeouts are split into timouts as small as SOCKET_TIMEOUT_QUANTUM. + * This reduces the time required to stop the server. */ +#if !defined(SOCKET_TIMEOUT_QUANTUM) +#define SOCKET_TIMEOUT_QUANTUM (2000) /* in ms */ +#endif + +/* Do not try to compress files smaller than this limit. */ +#if !defined(MG_FILE_COMPRESSION_SIZE_LIMIT) +#define MG_FILE_COMPRESSION_SIZE_LIMIT (1024) /* in bytes */ +#endif + +#if !defined(PASSWORDS_FILE_NAME) +#define PASSWORDS_FILE_NAME ".htpasswd" +#endif + +/* Initial buffer size for all CGI environment variables. In case there is + * not enough space, another block is allocated. */ +#if !defined(CGI_ENVIRONMENT_SIZE) +#define CGI_ENVIRONMENT_SIZE (4096) /* in bytes */ +#endif + +/* Maximum number of environment variables. */ +#if !defined(MAX_CGI_ENVIR_VARS) +#define MAX_CGI_ENVIR_VARS (256) /* in variables (count) */ +#endif + +/* General purpose buffer size. */ +#if !defined(MG_BUF_LEN) /* in bytes */ +#define MG_BUF_LEN (1024 * 8) +#endif + +/* Size of the accepted socket queue (in case the old queue implementation + * is used). */ +#if !defined(MGSQLEN) +#define MGSQLEN (20) /* count */ +#endif + + +/********************************************************************/ + +/* Helper makros */ +#if !defined(ARRAY_SIZE) +#define ARRAY_SIZE(array) (sizeof(array) / sizeof(array[0])) +#endif + +#include + +/* Standard defines */ +#if !defined(INT64_MAX) +#define INT64_MAX (9223372036854775807) +#endif + +#define SHUTDOWN_RD (0) +#define SHUTDOWN_WR (1) +#define SHUTDOWN_BOTH (2) + +mg_static_assert(MAX_WORKER_THREADS >= 1, + "worker threads must be a positive number"); + +mg_static_assert(sizeof(size_t) == 4 || sizeof(size_t) == 8, + "size_t data type size check"); + + +#if defined(_WIN32) /* WINDOWS include block */ +#include /* *alloc( */ +#include /* *alloc( */ +#include /* struct timespec */ +#include +#include /* DTL add for SO_EXCLUSIVE */ +#include + +typedef const char *SOCK_OPT_TYPE; + +#if !defined(PATH_MAX) +#define W_PATH_MAX (MAX_PATH) +/* at most three UTF-8 chars per wchar_t */ +#define PATH_MAX (W_PATH_MAX * 3) +#else +#define W_PATH_MAX ((PATH_MAX + 2) / 3) +#endif + +mg_static_assert(PATH_MAX >= 1, "path length must be a positive number"); + +#if !defined(_IN_PORT_T) +#if !defined(in_port_t) +#define in_port_t u_short +#endif +#endif + +#if !defined(_WIN32_WCE) +#include +#include +#include +#else /* _WIN32_WCE */ +#define NO_CGI /* WinCE has no pipes */ +#define NO_POPEN /* WinCE has no popen */ + +typedef long off_t; + +#define errno ((int)(GetLastError())) +#define strerror(x) (_ultoa(x, (char *)_alloca(sizeof(x) * 3), 10)) +#endif /* _WIN32_WCE */ + +#define MAKEUQUAD(lo, hi) \ + ((uint64_t)(((uint32_t)(lo)) | ((uint64_t)((uint32_t)(hi))) << 32)) +#define RATE_DIFF (10000000) /* 100 nsecs */ +#define EPOCH_DIFF (MAKEUQUAD(0xd53e8000, 0x019db1de)) +#define SYS2UNIX_TIME(lo, hi) \ + ((time_t)((MAKEUQUAD((lo), (hi)) - EPOCH_DIFF) / RATE_DIFF)) + +/* Visual Studio 6 does not know __func__ or __FUNCTION__ + * The rest of MS compilers use __FUNCTION__, not C99 __func__ + * Also use _strtoui64 on modern M$ compilers */ +#if defined(_MSC_VER) +#if (_MSC_VER < 1300) +#define STRX(x) #x +#define STR(x) STRX(x) +#define __func__ __FILE__ ":" STR(__LINE__) +#define strtoull(x, y, z) ((unsigned __int64)_atoi64(x)) +#define strtoll(x, y, z) (_atoi64(x)) +#else +#define __func__ __FUNCTION__ +#define strtoull(x, y, z) (_strtoui64(x, y, z)) +#define strtoll(x, y, z) (_strtoi64(x, y, z)) +#endif +#endif /* _MSC_VER */ + +#define ERRNO ((int)(GetLastError())) +#define NO_SOCKLEN_T + +#if defined(_WIN64) || defined(__MINGW64__) +#if !defined(SSL_LIB) +#define SSL_LIB "ssleay64.dll" +#endif +#if !defined(CRYPTO_LIB) +#define CRYPTO_LIB "libeay64.dll" +#endif +#else +#if !defined(SSL_LIB) +#define SSL_LIB "ssleay32.dll" +#endif +#if !defined(CRYPTO_LIB) +#define CRYPTO_LIB "libeay32.dll" +#endif +#endif + +#define O_NONBLOCK (0) +#if !defined(W_OK) +#define W_OK (2) /* http://msdn.microsoft.com/en-us/library/1w06ktdy.aspx */ +#endif +#define _POSIX_ +#define INT64_FMT "I64d" +#define UINT64_FMT "I64u" + +#define WINCDECL __cdecl +#define vsnprintf_impl _vsnprintf +#define access _access +#define mg_sleep(x) (Sleep(x)) + +#define pipe(x) _pipe(x, MG_BUF_LEN, _O_BINARY) +#if !defined(popen) +#define popen(x, y) (_popen(x, y)) +#endif +#if !defined(pclose) +#define pclose(x) (_pclose(x)) +#endif +#define close(x) (_close(x)) +#define dlsym(x, y) (GetProcAddress((HINSTANCE)(x), (y))) +#define RTLD_LAZY (0) +#define fseeko(x, y, z) ((_lseeki64(_fileno(x), (y), (z)) == -1) ? -1 : 0) +#define fdopen(x, y) (_fdopen((x), (y))) +#define write(x, y, z) (_write((x), (y), (unsigned)z)) +#define read(x, y, z) (_read((x), (y), (unsigned)z)) +#define flockfile(x) ((void)pthread_mutex_lock(&global_log_file_lock)) +#define funlockfile(x) ((void)pthread_mutex_unlock(&global_log_file_lock)) +#define sleep(x) (Sleep((x)*1000)) +#define rmdir(x) (_rmdir(x)) +#if defined(_WIN64) || !defined(__MINGW32__) +/* Only MinGW 32 bit is missing this function */ +#define timegm(x) (_mkgmtime(x)) +#else +time_t timegm(struct tm *tm); +#define NEED_TIMEGM +#endif + + +#if !defined(fileno) +#define fileno(x) (_fileno(x)) +#endif /* !fileno MINGW #defines fileno */ + +typedef struct { + CRITICAL_SECTION sec; /* Immovable */ +} pthread_mutex_t; +typedef DWORD pthread_key_t; +typedef HANDLE pthread_t; +typedef struct { + pthread_mutex_t threadIdSec; + struct mg_workerTLS *waiting_thread; /* The chain of threads */ +} pthread_cond_t; + +#if !defined(__clockid_t_defined) +typedef DWORD clockid_t; +#endif +#if !defined(CLOCK_MONOTONIC) +#define CLOCK_MONOTONIC (1) +#endif +#if !defined(CLOCK_REALTIME) +#define CLOCK_REALTIME (2) +#endif +#if !defined(CLOCK_THREAD) +#define CLOCK_THREAD (3) +#endif +#if !defined(CLOCK_PROCESS) +#define CLOCK_PROCESS (4) +#endif + + +#if defined(_MSC_VER) && (_MSC_VER >= 1900) +#define _TIMESPEC_DEFINED +#endif +#if !defined(_TIMESPEC_DEFINED) +struct timespec { + time_t tv_sec; /* seconds */ + long tv_nsec; /* nanoseconds */ +}; +#endif + +#if !defined(WIN_PTHREADS_TIME_H) +#define MUST_IMPLEMENT_CLOCK_GETTIME +#endif + +#if defined(MUST_IMPLEMENT_CLOCK_GETTIME) +#define clock_gettime mg_clock_gettime +static int +clock_gettime(clockid_t clk_id, struct timespec *tp) +{ + FILETIME ft; + ULARGE_INTEGER li, li2; + BOOL ok = FALSE; + double d; + static double perfcnt_per_sec = 0.0; + static BOOL initialized = FALSE; + + if (!initialized) { + QueryPerformanceFrequency((LARGE_INTEGER *)&li); + perfcnt_per_sec = 1.0 / li.QuadPart; + initialized = TRUE; + } + + if (tp) { + memset(tp, 0, sizeof(*tp)); + + if (clk_id == CLOCK_REALTIME) { + + /* BEGIN: CLOCK_REALTIME = wall clock (date and time) */ + GetSystemTimeAsFileTime(&ft); + li.LowPart = ft.dwLowDateTime; + li.HighPart = ft.dwHighDateTime; + li.QuadPart -= 116444736000000000; /* 1.1.1970 in filedate */ + tp->tv_sec = (time_t)(li.QuadPart / 10000000); + tp->tv_nsec = (long)(li.QuadPart % 10000000) * 100; + ok = TRUE; + /* END: CLOCK_REALTIME */ + + } else if (clk_id == CLOCK_MONOTONIC) { + + /* BEGIN: CLOCK_MONOTONIC = stopwatch (time differences) */ + QueryPerformanceCounter((LARGE_INTEGER *)&li); + d = li.QuadPart * perfcnt_per_sec; + tp->tv_sec = (time_t)d; + d -= (double)tp->tv_sec; + tp->tv_nsec = (long)(d * 1.0E9); + ok = TRUE; + /* END: CLOCK_MONOTONIC */ + + } else if (clk_id == CLOCK_THREAD) { + + /* BEGIN: CLOCK_THREAD = CPU usage of thread */ + FILETIME t_create, t_exit, t_kernel, t_user; + if (GetThreadTimes(GetCurrentThread(), + &t_create, + &t_exit, + &t_kernel, + &t_user)) { + li.LowPart = t_user.dwLowDateTime; + li.HighPart = t_user.dwHighDateTime; + li2.LowPart = t_kernel.dwLowDateTime; + li2.HighPart = t_kernel.dwHighDateTime; + li.QuadPart += li2.QuadPart; + tp->tv_sec = (time_t)(li.QuadPart / 10000000); + tp->tv_nsec = (long)(li.QuadPart % 10000000) * 100; + ok = TRUE; + } + /* END: CLOCK_THREAD */ + + } else if (clk_id == CLOCK_PROCESS) { + + /* BEGIN: CLOCK_PROCESS = CPU usage of process */ + FILETIME t_create, t_exit, t_kernel, t_user; + if (GetProcessTimes(GetCurrentProcess(), + &t_create, + &t_exit, + &t_kernel, + &t_user)) { + li.LowPart = t_user.dwLowDateTime; + li.HighPart = t_user.dwHighDateTime; + li2.LowPart = t_kernel.dwLowDateTime; + li2.HighPart = t_kernel.dwHighDateTime; + li.QuadPart += li2.QuadPart; + tp->tv_sec = (time_t)(li.QuadPart / 10000000); + tp->tv_nsec = (long)(li.QuadPart % 10000000) * 100; + ok = TRUE; + } + /* END: CLOCK_PROCESS */ + + } else { + + /* BEGIN: unknown clock */ + /* ok = FALSE; already set by init */ + /* END: unknown clock */ + } + } + + return ok ? 0 : -1; +} +#endif + + +#define pid_t HANDLE /* MINGW typedefs pid_t to int. Using #define here. */ + +static int pthread_mutex_lock(pthread_mutex_t *); +static int pthread_mutex_unlock(pthread_mutex_t *); +static void path_to_unicode(const struct mg_connection *conn, + const char *path, + wchar_t *wbuf, + size_t wbuf_len); + +/* All file operations need to be rewritten to solve #246. */ + +struct mg_file; + +static const char * +mg_fgets(char *buf, size_t size, struct mg_file *filep, char **p); + + +/* POSIX dirent interface */ +struct dirent { + char d_name[PATH_MAX]; +}; + +typedef struct DIR { + HANDLE handle; + WIN32_FIND_DATAW info; + struct dirent result; +} DIR; + +#if defined(HAVE_POLL) +#define mg_pollfd pollfd +#else +struct mg_pollfd { + SOCKET fd; + short events; + short revents; +}; +#endif + +/* Mark required libraries */ +#if defined(_MSC_VER) +#pragma comment(lib, "Ws2_32.lib") +#endif + +#else /* defined(_WIN32) - WINDOWS vs UNIX include block */ + +#include + +typedef const void *SOCK_OPT_TYPE; + +#if defined(ANDROID) +typedef unsigned short int in_port_t; +#endif + +#if !defined(__ZEPHYR__) +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#endif + +#define vsnprintf_impl vsnprintf + +#if !defined(NO_SSL_DL) && !defined(NO_SSL) +#include +#endif + +#if defined(__MACH__) +#define SSL_LIB "libssl.dylib" +#define CRYPTO_LIB "libcrypto.dylib" +#else +#if !defined(SSL_LIB) +#define SSL_LIB "libssl.so" +#endif +#if !defined(CRYPTO_LIB) +#define CRYPTO_LIB "libcrypto.so" +#endif +#endif +#if !defined(O_BINARY) +#define O_BINARY (0) +#endif /* O_BINARY */ +#define closesocket(a) (close(a)) +#define mg_mkdir(conn, path, mode) (mkdir(path, mode)) +#define mg_remove(conn, x) (remove(x)) +#define mg_sleep(x) (usleep((x)*1000)) +#define mg_opendir(conn, x) (opendir(x)) +#define mg_closedir(x) (closedir(x)) +#define mg_readdir(x) (readdir(x)) +#define ERRNO (errno) +#define INVALID_SOCKET (-1) +#define INT64_FMT PRId64 +#define UINT64_FMT PRIu64 +typedef int SOCKET; +#define WINCDECL + +#if defined(__hpux) +/* HPUX 11 does not have monotonic, fall back to realtime */ +#if !defined(CLOCK_MONOTONIC) +#define CLOCK_MONOTONIC CLOCK_REALTIME +#endif + +/* HPUX defines socklen_t incorrectly as size_t which is 64bit on + * Itanium. Without defining _XOPEN_SOURCE or _XOPEN_SOURCE_EXTENDED + * the prototypes use int* rather than socklen_t* which matches the + * actual library expectation. When called with the wrong size arg + * accept() returns a zero client inet addr and check_acl() always + * fails. Since socklen_t is widely used below, just force replace + * their typedef with int. - DTL + */ +#define socklen_t int +#endif /* hpux */ + +#define mg_pollfd pollfd + +#endif /* defined(_WIN32) - WINDOWS vs UNIX include block */ + +/* In case our C library is missing "timegm", provide an implementation */ +#if defined(NEED_TIMEGM) +static inline int +is_leap(int y) +{ + return (y % 4 == 0 && y % 100 != 0) || y % 400 == 0; +} + +static inline int +count_leap(int y) +{ + return (y - 1969) / 4 - (y - 1901) / 100 + (y - 1601) / 400; +} + +time_t +timegm(struct tm *tm) +{ + static const unsigned short ydays[] = { + 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365}; + int year = tm->tm_year + 1900; + int mon = tm->tm_mon; + int mday = tm->tm_mday - 1; + int hour = tm->tm_hour; + int min = tm->tm_min; + int sec = tm->tm_sec; + + if (year < 1970 || mon < 0 || mon > 11 || mday < 0 + || (mday >= ydays[mon + 1] - ydays[mon] + + (mon == 1 && is_leap(year) ? 1 : 0)) + || hour < 0 || hour > 23 || min < 0 || min > 59 || sec < 0 || sec > 60) + return -1; + + time_t res = year - 1970; + res *= 365; + res += mday; + res += ydays[mon] + (mon > 1 && is_leap(year) ? 1 : 0); + res += count_leap(year); + + res *= 24; + res += hour; + res *= 60; + res += min; + res *= 60; + res += sec; + return res; +} +#endif /* NEED_TIMEGM */ + + +/* va_copy should always be a macro, C99 and C++11 - DTL */ +#if !defined(va_copy) +#define va_copy(x, y) ((x) = (y)) +#endif + + +#if defined(_WIN32) +/* Create substitutes for POSIX functions in Win32. */ + +#if defined(GCC_DIAGNOSTIC) +/* Show no warning in case system functions are not used. */ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wunused-function" +#endif + + +static pthread_mutex_t global_log_file_lock; + +FUNCTION_MAY_BE_UNUSED +static DWORD +pthread_self(void) +{ + return GetCurrentThreadId(); +} + + +FUNCTION_MAY_BE_UNUSED +static int +pthread_key_create( + pthread_key_t *key, + void (*_ignored)(void *) /* destructor not supported for Windows */ +) +{ + (void)_ignored; + + if ((key != 0)) { + *key = TlsAlloc(); + return (*key != TLS_OUT_OF_INDEXES) ? 0 : -1; + } + return -2; +} + + +FUNCTION_MAY_BE_UNUSED +static int +pthread_key_delete(pthread_key_t key) +{ + return TlsFree(key) ? 0 : 1; +} + + +FUNCTION_MAY_BE_UNUSED +static int +pthread_setspecific(pthread_key_t key, void *value) +{ + return TlsSetValue(key, value) ? 0 : 1; +} + + +FUNCTION_MAY_BE_UNUSED +static void * +pthread_getspecific(pthread_key_t key) +{ + return TlsGetValue(key); +} + +#if defined(GCC_DIAGNOSTIC) +/* Enable unused function warning again */ +#pragma GCC diagnostic pop +#endif + +static struct pthread_mutex_undefined_struct *pthread_mutex_attr = NULL; +#else +static pthread_mutexattr_t pthread_mutex_attr; +#endif /* _WIN32 */ + + +#if defined(_WIN32_WCE) +/* Create substitutes for POSIX functions in Win32. */ + +#if defined(GCC_DIAGNOSTIC) +/* Show no warning in case system functions are not used. */ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wunused-function" +#endif + + +FUNCTION_MAY_BE_UNUSED +static time_t +time(time_t *ptime) +{ + time_t t; + SYSTEMTIME st; + FILETIME ft; + + GetSystemTime(&st); + SystemTimeToFileTime(&st, &ft); + t = SYS2UNIX_TIME(ft.dwLowDateTime, ft.dwHighDateTime); + + if (ptime != NULL) { + *ptime = t; + } + + return t; +} + + +FUNCTION_MAY_BE_UNUSED +static struct tm * +localtime_s(const time_t *ptime, struct tm *ptm) +{ + int64_t t = ((int64_t)*ptime) * RATE_DIFF + EPOCH_DIFF; + FILETIME ft, lft; + SYSTEMTIME st; + TIME_ZONE_INFORMATION tzinfo; + + if (ptm == NULL) { + return NULL; + } + + *(int64_t *)&ft = t; + FileTimeToLocalFileTime(&ft, &lft); + FileTimeToSystemTime(&lft, &st); + ptm->tm_year = st.wYear - 1900; + ptm->tm_mon = st.wMonth - 1; + ptm->tm_wday = st.wDayOfWeek; + ptm->tm_mday = st.wDay; + ptm->tm_hour = st.wHour; + ptm->tm_min = st.wMinute; + ptm->tm_sec = st.wSecond; + ptm->tm_yday = 0; /* hope nobody uses this */ + ptm->tm_isdst = + (GetTimeZoneInformation(&tzinfo) == TIME_ZONE_ID_DAYLIGHT) ? 1 : 0; + + return ptm; +} + + +FUNCTION_MAY_BE_UNUSED +static struct tm * +gmtime_s(const time_t *ptime, struct tm *ptm) +{ + /* FIXME(lsm): fix this. */ + return localtime_s(ptime, ptm); +} + + +static int mg_atomic_inc(volatile int *addr); +static struct tm tm_array[MAX_WORKER_THREADS]; +static int tm_index = 0; + + +FUNCTION_MAY_BE_UNUSED +static struct tm * +localtime(const time_t *ptime) +{ + int i = mg_atomic_inc(&tm_index) % (sizeof(tm_array) / sizeof(tm_array[0])); + return localtime_s(ptime, tm_array + i); +} + + +FUNCTION_MAY_BE_UNUSED +static struct tm * +gmtime(const time_t *ptime) +{ + int i = mg_atomic_inc(&tm_index) % ARRAY_SIZE(tm_array); + return gmtime_s(ptime, tm_array + i); +} + + +FUNCTION_MAY_BE_UNUSED +static size_t +strftime(char *dst, size_t dst_size, const char *fmt, const struct tm *tm) +{ + /* TODO: (void)mg_snprintf(NULL, dst, dst_size, "implement strftime() + * for WinCE"); */ + return 0; +} + +#define _beginthreadex(psec, stack, func, prm, flags, ptid) \ + (uintptr_t) CreateThread(psec, stack, func, prm, flags, ptid) + +#define remove(f) mg_remove(NULL, f) + + +FUNCTION_MAY_BE_UNUSED +static int +rename(const char *a, const char *b) +{ + wchar_t wa[W_PATH_MAX]; + wchar_t wb[W_PATH_MAX]; + path_to_unicode(NULL, a, wa, ARRAY_SIZE(wa)); + path_to_unicode(NULL, b, wb, ARRAY_SIZE(wb)); + + return MoveFileW(wa, wb) ? 0 : -1; +} + + +struct stat { + int64_t st_size; + time_t st_mtime; +}; + + +FUNCTION_MAY_BE_UNUSED +static int +stat(const char *name, struct stat *st) +{ + wchar_t wbuf[W_PATH_MAX]; + WIN32_FILE_ATTRIBUTE_DATA attr; + time_t creation_time, write_time; + + path_to_unicode(NULL, name, wbuf, ARRAY_SIZE(wbuf)); + memset(&attr, 0, sizeof(attr)); + + GetFileAttributesExW(wbuf, GetFileExInfoStandard, &attr); + st->st_size = + (((int64_t)attr.nFileSizeHigh) << 32) + (int64_t)attr.nFileSizeLow; + + write_time = SYS2UNIX_TIME(attr.ftLastWriteTime.dwLowDateTime, + attr.ftLastWriteTime.dwHighDateTime); + creation_time = SYS2UNIX_TIME(attr.ftCreationTime.dwLowDateTime, + attr.ftCreationTime.dwHighDateTime); + + if (creation_time > write_time) { + st->st_mtime = creation_time; + } else { + st->st_mtime = write_time; + } + return 0; +} + +#define access(x, a) 1 /* not required anyway */ + +/* WinCE-TODO: define stat, remove, rename, _rmdir, _lseeki64 */ +/* Values from errno.h in Windows SDK (Visual Studio). */ +#define EEXIST 17 +#define EACCES 13 +#define ENOENT 2 + +#if defined(GCC_DIAGNOSTIC) +/* Enable unused function warning again */ +#pragma GCC diagnostic pop +#endif + +#endif /* defined(_WIN32_WCE) */ + + +#if defined(GCC_DIAGNOSTIC) +/* Show no warning in case system functions are not used. */ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wunused-function" +#endif /* defined(GCC_DIAGNOSTIC) */ +#if defined(__clang__) +/* Show no warning in case system functions are not used. */ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunused-function" +#endif + +static pthread_mutex_t global_lock_mutex; + + +FUNCTION_MAY_BE_UNUSED +static void +mg_global_lock(void) +{ + (void)pthread_mutex_lock(&global_lock_mutex); +} + + +FUNCTION_MAY_BE_UNUSED +static void +mg_global_unlock(void) +{ + (void)pthread_mutex_unlock(&global_lock_mutex); +} + + +FUNCTION_MAY_BE_UNUSED +static int +mg_atomic_inc(volatile int *addr) +{ + int ret; +#if defined(_WIN32) && !defined(NO_ATOMICS) + /* Depending on the SDK, this function uses either + * (volatile unsigned int *) or (volatile LONG *), + * so whatever you use, the other SDK is likely to raise a warning. */ + ret = InterlockedIncrement((volatile long *)addr); +#elif defined(__GNUC__) \ + && ((__GNUC__ > 4) || ((__GNUC__ == 4) && (__GNUC_MINOR__ > 0))) \ + && !defined(NO_ATOMICS) + ret = __sync_add_and_fetch(addr, 1); +#else + mg_global_lock(); + ret = (++(*addr)); + mg_global_unlock(); +#endif + return ret; +} + + +FUNCTION_MAY_BE_UNUSED +static int +mg_atomic_dec(volatile int *addr) +{ + int ret; +#if defined(_WIN32) && !defined(NO_ATOMICS) + /* Depending on the SDK, this function uses either + * (volatile unsigned int *) or (volatile LONG *), + * so whatever you use, the other SDK is likely to raise a warning. */ + ret = InterlockedDecrement((volatile long *)addr); +#elif defined(__GNUC__) \ + && ((__GNUC__ > 4) || ((__GNUC__ == 4) && (__GNUC_MINOR__ > 0))) \ + && !defined(NO_ATOMICS) + ret = __sync_sub_and_fetch(addr, 1); +#else + mg_global_lock(); + ret = (--(*addr)); + mg_global_unlock(); +#endif + return ret; +} + + +#if defined(USE_SERVER_STATS) +static int64_t +mg_atomic_add(volatile int64_t *addr, int64_t value) +{ + int64_t ret; +#if defined(_WIN64) && !defined(NO_ATOMICS) + ret = InterlockedAdd64(addr, value); +#elif defined(__GNUC__) \ + && ((__GNUC__ > 4) || ((__GNUC__ == 4) && (__GNUC_MINOR__ > 0))) \ + && !defined(NO_ATOMICS) + ret = __sync_add_and_fetch(addr, value); +#else + mg_global_lock(); + *addr += value; + ret = (*addr); + mg_global_unlock(); +#endif + return ret; +} +#endif + + +#if defined(GCC_DIAGNOSTIC) +/* Show no warning in case system functions are not used. */ +#pragma GCC diagnostic pop +#endif /* defined(GCC_DIAGNOSTIC) */ +#if defined(__clang__) +/* Show no warning in case system functions are not used. */ +#pragma clang diagnostic pop +#endif + + +#if defined(USE_SERVER_STATS) + +struct mg_memory_stat { + volatile int64_t totalMemUsed; + volatile int64_t maxMemUsed; + volatile int blockCount; +}; + + +static struct mg_memory_stat *get_memory_stat(struct mg_context *ctx); + + +static void * +mg_malloc_ex(size_t size, + struct mg_context *ctx, + const char *file, + unsigned line) +{ + void *data = malloc(size + 2 * sizeof(uintptr_t)); + void *memory = 0; + struct mg_memory_stat *mstat = get_memory_stat(ctx); + +#if defined(MEMORY_DEBUGGING) + char mallocStr[256]; +#else + (void)file; + (void)line; +#endif + + if (data) { + int64_t mmem = mg_atomic_add(&mstat->totalMemUsed, (int64_t)size); + if (mmem > mstat->maxMemUsed) { + /* could use atomic compare exchange, but this + * seems overkill for statistics data */ + mstat->maxMemUsed = mmem; + } + + mg_atomic_inc(&mstat->blockCount); + ((uintptr_t *)data)[0] = size; + ((uintptr_t *)data)[1] = (uintptr_t)mstat; + memory = (void *)(((char *)data) + 2 * sizeof(uintptr_t)); + } + +#if defined(MEMORY_DEBUGGING) + sprintf(mallocStr, + "MEM: %p %5lu alloc %7lu %4lu --- %s:%u\n", + memory, + (unsigned long)size, + (unsigned long)mstat->totalMemUsed, + (unsigned long)mstat->blockCount, + file, + line); +#if defined(_WIN32) + OutputDebugStringA(mallocStr); +#else + DEBUG_TRACE("%s", mallocStr); +#endif +#endif + + return memory; +} + + +static void * +mg_calloc_ex(size_t count, + size_t size, + struct mg_context *ctx, + const char *file, + unsigned line) +{ + void *data = mg_malloc_ex(size * count, ctx, file, line); + + if (data) { + memset(data, 0, size * count); + } + return data; +} + + +static void +mg_free_ex(void *memory, const char *file, unsigned line) +{ + void *data = (void *)(((char *)memory) - 2 * sizeof(uintptr_t)); + + +#if defined(MEMORY_DEBUGGING) + char mallocStr[256]; +#else + (void)file; + (void)line; +#endif + + if (memory) { + uintptr_t size = ((uintptr_t *)data)[0]; + struct mg_memory_stat *mstat = + (struct mg_memory_stat *)(((uintptr_t *)data)[1]); + mg_atomic_add(&mstat->totalMemUsed, -(int64_t)size); + mg_atomic_dec(&mstat->blockCount); +#if defined(MEMORY_DEBUGGING) + sprintf(mallocStr, + "MEM: %p %5lu free %7lu %4lu --- %s:%u\n", + memory, + (unsigned long)size, + (unsigned long)mstat->totalMemUsed, + (unsigned long)mstat->blockCount, + file, + line); +#if defined(_WIN32) + OutputDebugStringA(mallocStr); +#else + DEBUG_TRACE("%s", mallocStr); +#endif +#endif + free(data); + } +} + + +static void * +mg_realloc_ex(void *memory, + size_t newsize, + struct mg_context *ctx, + const char *file, + unsigned line) +{ + void *data; + void *_realloc; + uintptr_t oldsize; + +#if defined(MEMORY_DEBUGGING) + char mallocStr[256]; +#else + (void)file; + (void)line; +#endif + + if (newsize) { + if (memory) { + /* Reallocate existing block */ + struct mg_memory_stat *mstat; + data = (void *)(((char *)memory) - 2 * sizeof(uintptr_t)); + oldsize = ((uintptr_t *)data)[0]; + mstat = (struct mg_memory_stat *)((uintptr_t *)data)[1]; + _realloc = realloc(data, newsize + 2 * sizeof(uintptr_t)); + if (_realloc) { + data = _realloc; + mg_atomic_add(&mstat->totalMemUsed, -(int64_t)oldsize); +#if defined(MEMORY_DEBUGGING) + sprintf(mallocStr, + "MEM: %p %5lu r-free %7lu %4lu --- %s:%u\n", + memory, + (unsigned long)oldsize, + (unsigned long)mstat->totalMemUsed, + (unsigned long)mstat->blockCount, + file, + line); +#if defined(_WIN32) + OutputDebugStringA(mallocStr); +#else + DEBUG_TRACE("%s", mallocStr); +#endif +#endif + mg_atomic_add(&mstat->totalMemUsed, (int64_t)newsize); +#if defined(MEMORY_DEBUGGING) + sprintf(mallocStr, + "MEM: %p %5lu r-alloc %7lu %4lu --- %s:%u\n", + memory, + (unsigned long)newsize, + (unsigned long)mstat->totalMemUsed, + (unsigned long)mstat->blockCount, + file, + line); +#if defined(_WIN32) + OutputDebugStringA(mallocStr); +#else + DEBUG_TRACE("%s", mallocStr); +#endif +#endif + *(uintptr_t *)data = newsize; + data = (void *)(((char *)data) + 2 * sizeof(uintptr_t)); + } else { +#if defined(MEMORY_DEBUGGING) +#if defined(_WIN32) + OutputDebugStringA("MEM: realloc failed\n"); +#else + DEBUG_TRACE("%s", "MEM: realloc failed\n"); +#endif +#endif + return _realloc; + } + } else { + /* Allocate new block */ + data = mg_malloc_ex(newsize, ctx, file, line); + } + } else { + /* Free existing block */ + data = 0; + mg_free_ex(memory, file, line); + } + + return data; +} + +#define mg_malloc(a) mg_malloc_ex(a, NULL, __FILE__, __LINE__) +#define mg_calloc(a, b) mg_calloc_ex(a, b, NULL, __FILE__, __LINE__) +#define mg_realloc(a, b) mg_realloc_ex(a, b, NULL, __FILE__, __LINE__) +#define mg_free(a) mg_free_ex(a, __FILE__, __LINE__) + +#define mg_malloc_ctx(a, c) mg_malloc_ex(a, c, __FILE__, __LINE__) +#define mg_calloc_ctx(a, b, c) mg_calloc_ex(a, b, c, __FILE__, __LINE__) +#define mg_realloc_ctx(a, b, c) mg_realloc_ex(a, b, c, __FILE__, __LINE__) + +#else /* USE_SERVER_STATS */ + +static __inline void * +mg_malloc(size_t a) +{ + return malloc(a); +} + +static __inline void * +mg_calloc(size_t a, size_t b) +{ + return calloc(a, b); +} + +static __inline void * +mg_realloc(void *a, size_t b) +{ + return realloc(a, b); +} + +static __inline void +mg_free(void *a) +{ + free(a); +} + +#define mg_malloc_ctx(a, c) mg_malloc(a) +#define mg_calloc_ctx(a, b, c) mg_calloc(a, b) +#define mg_realloc_ctx(a, b, c) mg_realloc(a, b) +#define mg_free_ctx(a, c) mg_free(a) + +#endif /* USE_SERVER_STATS */ + + +static void mg_vsnprintf(const struct mg_connection *conn, + int *truncated, + char *buf, + size_t buflen, + const char *fmt, + va_list ap); + +static void mg_snprintf(const struct mg_connection *conn, + int *truncated, + char *buf, + size_t buflen, + PRINTF_FORMAT_STRING(const char *fmt), + ...) PRINTF_ARGS(5, 6); + +/* This following lines are just meant as a reminder to use the mg-functions + * for memory management */ +#if defined(malloc) +#undef malloc +#endif +#if defined(calloc) +#undef calloc +#endif +#if defined(realloc) +#undef realloc +#endif +#if defined(free) +#undef free +#endif +#if defined(snprintf) +#undef snprintf +#endif +#if defined(vsnprintf) +#undef vsnprintf +#endif +#define malloc DO_NOT_USE_THIS_FUNCTION__USE_mg_malloc +#define calloc DO_NOT_USE_THIS_FUNCTION__USE_mg_calloc +#define realloc DO_NOT_USE_THIS_FUNCTION__USE_mg_realloc +#define free DO_NOT_USE_THIS_FUNCTION__USE_mg_free +#define snprintf DO_NOT_USE_THIS_FUNCTION__USE_mg_snprintf +#if defined(_WIN32) +/* vsnprintf must not be used in any system, + * but this define only works well for Windows. */ +#define vsnprintf DO_NOT_USE_THIS_FUNCTION__USE_mg_vsnprintf +#endif + + +/* mg_init_library counter */ +static int mg_init_library_called = 0; + +#if !defined(NO_SSL) +static int mg_ssl_initialized = 0; +#endif + +static pthread_key_t sTlsKey; /* Thread local storage index */ +static int thread_idx_max = 0; + +#if defined(MG_LEGACY_INTERFACE) +#define MG_ALLOW_USING_GET_REQUEST_INFO_FOR_RESPONSE +#endif + +struct mg_workerTLS { + int is_master; + unsigned long thread_idx; + void *user_ptr; +#if defined(_WIN32) + HANDLE pthread_cond_helper_mutex; + struct mg_workerTLS *next_waiting_thread; +#endif +#if defined(MG_ALLOW_USING_GET_REQUEST_INFO_FOR_RESPONSE) + char txtbuf[4]; +#endif +}; + + +#if defined(GCC_DIAGNOSTIC) +/* Show no warning in case system functions are not used. */ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wunused-function" +#endif /* defined(GCC_DIAGNOSTIC) */ +#if defined(__clang__) +/* Show no warning in case system functions are not used. */ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunused-function" +#endif + + +/* Get a unique thread ID as unsigned long, independent from the data type + * of thread IDs defined by the operating system API. + * If two calls to mg_current_thread_id return the same value, they calls + * are done from the same thread. If they return different values, they are + * done from different threads. (Provided this function is used in the same + * process context and threads are not repeatedly created and deleted, but + * CivetWeb does not do that). + * This function must match the signature required for SSL id callbacks: + * CRYPTO_set_id_callback + */ +FUNCTION_MAY_BE_UNUSED +static unsigned long +mg_current_thread_id(void) +{ +#if defined(_WIN32) + return GetCurrentThreadId(); +#else + +#if defined(__clang__) +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunreachable-code" +/* For every compiler, either "sizeof(pthread_t) > sizeof(unsigned long)" + * or not, so one of the two conditions will be unreachable by construction. + * Unfortunately the C standard does not define a way to check this at + * compile time, since the #if preprocessor conditions can not use the sizeof + * operator as an argument. */ +#endif + + if (sizeof(pthread_t) > sizeof(unsigned long)) { + /* This is the problematic case for CRYPTO_set_id_callback: + * The OS pthread_t can not be cast to unsigned long. */ + struct mg_workerTLS *tls = + (struct mg_workerTLS *)pthread_getspecific(sTlsKey); + if (tls == NULL) { + /* SSL called from an unknown thread: Create some thread index. + */ + tls = (struct mg_workerTLS *)mg_malloc(sizeof(struct mg_workerTLS)); + tls->is_master = -2; /* -2 means "3rd party thread" */ + tls->thread_idx = (unsigned)mg_atomic_inc(&thread_idx_max); + pthread_setspecific(sTlsKey, tls); + } + return tls->thread_idx; + } else { + /* pthread_t may be any data type, so a simple cast to unsigned long + * can rise a warning/error, depending on the platform. + * Here memcpy is used as an anything-to-anything cast. */ + unsigned long ret = 0; + pthread_t t = pthread_self(); + memcpy(&ret, &t, sizeof(pthread_t)); + return ret; + } + +#if defined(__clang__) +#pragma clang diagnostic pop +#endif + +#endif +} + + +FUNCTION_MAY_BE_UNUSED +static uint64_t +mg_get_current_time_ns(void) +{ + struct timespec tsnow; + clock_gettime(CLOCK_REALTIME, &tsnow); + return (((uint64_t)tsnow.tv_sec) * 1000000000) + (uint64_t)tsnow.tv_nsec; +} + + +#if defined(GCC_DIAGNOSTIC) +/* Show no warning in case system functions are not used. */ +#pragma GCC diagnostic pop +#endif /* defined(GCC_DIAGNOSTIC) */ +#if defined(__clang__) +/* Show no warning in case system functions are not used. */ +#pragma clang diagnostic pop +#endif + + +#if defined(NEED_DEBUG_TRACE_FUNC) +static void +DEBUG_TRACE_FUNC(const char *func, unsigned line, const char *fmt, ...) +{ + va_list args; + uint64_t nsnow; + static uint64_t nslast; + struct timespec tsnow; + + /* Get some operating system independent thread id */ + unsigned long thread_id = mg_current_thread_id(); + + clock_gettime(CLOCK_REALTIME, &tsnow); + nsnow = ((uint64_t)tsnow.tv_sec) * ((uint64_t)1000000000) + + ((uint64_t)tsnow.tv_nsec); + + if (!nslast) { + nslast = nsnow; + } + + flockfile(stdout); + printf("*** %lu.%09lu %12" INT64_FMT " %lu %s:%u: ", + (unsigned long)tsnow.tv_sec, + (unsigned long)tsnow.tv_nsec, + nsnow - nslast, + thread_id, + func, + line); + va_start(args, fmt); + vprintf(fmt, args); + va_end(args); + putchar('\n'); + fflush(stdout); + funlockfile(stdout); + nslast = nsnow; +} +#endif /* NEED_DEBUG_TRACE_FUNC */ + + +#define MD5_STATIC static +#include "md5.inl" + +/* Darwin prior to 7.0 and Win32 do not have socklen_t */ +#if defined(NO_SOCKLEN_T) +typedef int socklen_t; +#endif /* NO_SOCKLEN_T */ + +#define IP_ADDR_STR_LEN (50) /* IPv6 hex string is 46 chars */ + +#if !defined(MSG_NOSIGNAL) +#define MSG_NOSIGNAL (0) +#endif + + +#if defined(NO_SSL) +typedef struct SSL SSL; /* dummy for SSL argument to push/pull */ +typedef struct SSL_CTX SSL_CTX; +#else +#if defined(NO_SSL_DL) +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if defined(WOLFSSL_VERSION) +/* Additional defines for WolfSSL, see + * https://github.com/civetweb/civetweb/issues/583 */ +#include "wolfssl_extras.inl" +#endif + +#if (OPENSSL_VERSION_NUMBER >= 0x10100000L) +/* If OpenSSL headers are included, automatically select the API version */ +#if !defined(OPENSSL_API_1_1) +#define OPENSSL_API_1_1 +#endif +#define OPENSSL_REMOVE_THREAD_STATE() +#else +#define OPENSSL_REMOVE_THREAD_STATE() ERR_remove_thread_state(NULL) +#endif + +#else + +/* SSL loaded dynamically from DLL. + * I put the prototypes here to be independent from OpenSSL source + * installation. */ + +typedef struct ssl_st SSL; +typedef struct ssl_method_st SSL_METHOD; +typedef struct ssl_ctx_st SSL_CTX; +typedef struct x509_store_ctx_st X509_STORE_CTX; +typedef struct x509_name X509_NAME; +typedef struct asn1_integer ASN1_INTEGER; +typedef struct bignum BIGNUM; +typedef struct ossl_init_settings_st OPENSSL_INIT_SETTINGS; +typedef struct evp_md EVP_MD; +typedef struct x509 X509; + + +#define SSL_CTRL_OPTIONS (32) +#define SSL_CTRL_CLEAR_OPTIONS (77) +#define SSL_CTRL_SET_ECDH_AUTO (94) + +#define OPENSSL_INIT_NO_LOAD_SSL_STRINGS 0x00100000L +#define OPENSSL_INIT_LOAD_SSL_STRINGS 0x00200000L +#define OPENSSL_INIT_LOAD_CRYPTO_STRINGS 0x00000002L + +#define SSL_VERIFY_NONE (0) +#define SSL_VERIFY_PEER (1) +#define SSL_VERIFY_FAIL_IF_NO_PEER_CERT (2) +#define SSL_VERIFY_CLIENT_ONCE (4) + +#define SSL_OP_ALL (0x80000BFFul) + +#define SSL_OP_NO_SSLv2 (0x01000000ul) +#define SSL_OP_NO_SSLv3 (0x02000000ul) +#define SSL_OP_NO_TLSv1 (0x04000000ul) +#define SSL_OP_NO_TLSv1_2 (0x08000000ul) +#define SSL_OP_NO_TLSv1_1 (0x10000000ul) +#define SSL_OP_NO_TLSv1_3 (0x20000000ul) +#define SSL_OP_SINGLE_DH_USE (0x00100000ul) +#define SSL_OP_CIPHER_SERVER_PREFERENCE (0x00400000ul) +#define SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION (0x00010000ul) +#define SSL_OP_NO_COMPRESSION (0x00020000ul) +#define SSL_OP_NO_RENEGOTIATION (0x40000000ul) + +#define SSL_CB_HANDSHAKE_START (0x10) +#define SSL_CB_HANDSHAKE_DONE (0x20) + +#define SSL_ERROR_NONE (0) +#define SSL_ERROR_SSL (1) +#define SSL_ERROR_WANT_READ (2) +#define SSL_ERROR_WANT_WRITE (3) +#define SSL_ERROR_WANT_X509_LOOKUP (4) +#define SSL_ERROR_SYSCALL (5) /* see errno */ +#define SSL_ERROR_ZERO_RETURN (6) +#define SSL_ERROR_WANT_CONNECT (7) +#define SSL_ERROR_WANT_ACCEPT (8) + +#define TLSEXT_TYPE_server_name (0) +#define TLSEXT_NAMETYPE_host_name (0) +#define SSL_TLSEXT_ERR_OK (0) +#define SSL_TLSEXT_ERR_ALERT_WARNING (1) +#define SSL_TLSEXT_ERR_ALERT_FATAL (2) +#define SSL_TLSEXT_ERR_NOACK (3) + +struct ssl_func { + const char *name; /* SSL function name */ + void (*ptr)(void); /* Function pointer */ +}; + + +#if defined(OPENSSL_API_1_1) + +#define SSL_free (*(void (*)(SSL *))ssl_sw[0].ptr) +#define SSL_accept (*(int (*)(SSL *))ssl_sw[1].ptr) +#define SSL_connect (*(int (*)(SSL *))ssl_sw[2].ptr) +#define SSL_read (*(int (*)(SSL *, void *, int))ssl_sw[3].ptr) +#define SSL_write (*(int (*)(SSL *, const void *, int))ssl_sw[4].ptr) +#define SSL_get_error (*(int (*)(SSL *, int))ssl_sw[5].ptr) +#define SSL_set_fd (*(int (*)(SSL *, SOCKET))ssl_sw[6].ptr) +#define SSL_new (*(SSL * (*)(SSL_CTX *)) ssl_sw[7].ptr) +#define SSL_CTX_new (*(SSL_CTX * (*)(SSL_METHOD *)) ssl_sw[8].ptr) +#define TLS_server_method (*(SSL_METHOD * (*)(void)) ssl_sw[9].ptr) +#define OPENSSL_init_ssl \ + (*(int (*)(uint64_t opts, \ + const OPENSSL_INIT_SETTINGS *settings))ssl_sw[10] \ + .ptr) +#define SSL_CTX_use_PrivateKey_file \ + (*(int (*)(SSL_CTX *, const char *, int))ssl_sw[11].ptr) +#define SSL_CTX_use_certificate_file \ + (*(int (*)(SSL_CTX *, const char *, int))ssl_sw[12].ptr) +#define SSL_CTX_set_default_passwd_cb \ + (*(void (*)(SSL_CTX *, mg_callback_t))ssl_sw[13].ptr) +#define SSL_CTX_free (*(void (*)(SSL_CTX *))ssl_sw[14].ptr) +#define SSL_CTX_use_certificate_chain_file \ + (*(int (*)(SSL_CTX *, const char *))ssl_sw[15].ptr) +#define TLS_client_method (*(SSL_METHOD * (*)(void)) ssl_sw[16].ptr) +#define SSL_pending (*(int (*)(SSL *))ssl_sw[17].ptr) +#define SSL_CTX_set_verify \ + (*(void (*)(SSL_CTX *, \ + int, \ + int (*verify_callback)(int, X509_STORE_CTX *)))ssl_sw[18] \ + .ptr) +#define SSL_shutdown (*(int (*)(SSL *))ssl_sw[19].ptr) +#define SSL_CTX_load_verify_locations \ + (*(int (*)(SSL_CTX *, const char *, const char *))ssl_sw[20].ptr) +#define SSL_CTX_set_default_verify_paths (*(int (*)(SSL_CTX *))ssl_sw[21].ptr) +#define SSL_CTX_set_verify_depth (*(void (*)(SSL_CTX *, int))ssl_sw[22].ptr) +#define SSL_get_peer_certificate (*(X509 * (*)(SSL *)) ssl_sw[23].ptr) +#define SSL_get_version (*(const char *(*)(SSL *))ssl_sw[24].ptr) +#define SSL_get_current_cipher (*(SSL_CIPHER * (*)(SSL *)) ssl_sw[25].ptr) +#define SSL_CIPHER_get_name \ + (*(const char *(*)(const SSL_CIPHER *))ssl_sw[26].ptr) +#define SSL_CTX_check_private_key (*(int (*)(SSL_CTX *))ssl_sw[27].ptr) +#define SSL_CTX_set_session_id_context \ + (*(int (*)(SSL_CTX *, const unsigned char *, unsigned int))ssl_sw[28].ptr) +#define SSL_CTX_ctrl (*(long (*)(SSL_CTX *, int, long, void *))ssl_sw[29].ptr) +#define SSL_CTX_set_cipher_list \ + (*(int (*)(SSL_CTX *, const char *))ssl_sw[30].ptr) +#define SSL_CTX_set_options \ + (*(unsigned long (*)(SSL_CTX *, unsigned long))ssl_sw[31].ptr) +#define SSL_CTX_set_info_callback \ + (*(void (*)(SSL_CTX * ctx, void (*callback)(const SSL *, int, int))) \ + ssl_sw[32] \ + .ptr) +#define SSL_get_ex_data (*(char *(*)(const SSL *, int))ssl_sw[33].ptr) +#define SSL_set_ex_data (*(void (*)(SSL *, int, char *))ssl_sw[34].ptr) +#define SSL_CTX_callback_ctrl \ + (*(long (*)(SSL_CTX *, int, void (*)(void)))ssl_sw[35].ptr) +#define SSL_get_servername \ + (*(const char *(*)(const SSL *, int type))ssl_sw[36].ptr) +#define SSL_set_SSL_CTX (*(SSL_CTX * (*)(SSL *, SSL_CTX *)) ssl_sw[37].ptr) +#define SSL_ctrl (*(long (*)(SSL *, int, long, void *))ssl_sw[38].ptr) + +#define SSL_CTX_clear_options(ctx, op) \ + SSL_CTX_ctrl((ctx), SSL_CTRL_CLEAR_OPTIONS, (op), NULL) +#define SSL_CTX_set_ecdh_auto(ctx, onoff) \ + SSL_CTX_ctrl(ctx, SSL_CTRL_SET_ECDH_AUTO, onoff, NULL) + +#define SSL_CTRL_SET_TLSEXT_SERVERNAME_CB 53 +#define SSL_CTRL_SET_TLSEXT_SERVERNAME_ARG 54 +#define SSL_CTRL_SET_TLSEXT_HOSTNAME 55 +#define SSL_CTX_set_tlsext_servername_callback(ctx, cb) \ + SSL_CTX_callback_ctrl(ctx, \ + SSL_CTRL_SET_TLSEXT_SERVERNAME_CB, \ + (void (*)(void))cb) +#define SSL_CTX_set_tlsext_servername_arg(ctx, arg) \ + SSL_CTX_ctrl(ctx, SSL_CTRL_SET_TLSEXT_SERVERNAME_ARG, 0, (void *)arg) +#define SSL_set_tlsext_host_name(ctx, arg) \ + SSL_ctrl(ctx, SSL_CTRL_SET_TLSEXT_HOSTNAME, 0, (void *)arg) + +#define X509_get_notBefore(x) ((x)->cert_info->validity->notBefore) +#define X509_get_notAfter(x) ((x)->cert_info->validity->notAfter) + +#define SSL_set_app_data(s, arg) (SSL_set_ex_data(s, 0, (char *)arg)) +#define SSL_get_app_data(s) (SSL_get_ex_data(s, 0)) + +#define ERR_get_error (*(unsigned long (*)(void))crypto_sw[0].ptr) +#define ERR_error_string (*(char *(*)(unsigned long, char *))crypto_sw[1].ptr) +#define CONF_modules_unload (*(void (*)(int))crypto_sw[2].ptr) +#define X509_free (*(void (*)(X509 *))crypto_sw[3].ptr) +#define X509_get_subject_name (*(X509_NAME * (*)(X509 *)) crypto_sw[4].ptr) +#define X509_get_issuer_name (*(X509_NAME * (*)(X509 *)) crypto_sw[5].ptr) +#define X509_NAME_oneline \ + (*(char *(*)(X509_NAME *, char *, int))crypto_sw[6].ptr) +#define X509_get_serialNumber (*(ASN1_INTEGER * (*)(X509 *)) crypto_sw[7].ptr) +#define EVP_get_digestbyname \ + (*(const EVP_MD *(*)(const char *))crypto_sw[8].ptr) +#define EVP_Digest \ + (*(int (*)( \ + const void *, size_t, void *, unsigned int *, const EVP_MD *, void *)) \ + crypto_sw[9] \ + .ptr) +#define i2d_X509 (*(int (*)(X509 *, unsigned char **))crypto_sw[10].ptr) +#define BN_bn2hex (*(char *(*)(const BIGNUM *a))crypto_sw[11].ptr) +#define ASN1_INTEGER_to_BN \ + (*(BIGNUM * (*)(const ASN1_INTEGER *ai, BIGNUM *bn)) crypto_sw[12].ptr) +#define BN_free (*(void (*)(const BIGNUM *a))crypto_sw[13].ptr) +#define CRYPTO_free (*(void (*)(void *addr))crypto_sw[14].ptr) +#define ERR_clear_error (*(void (*)(void))crypto_sw[15].ptr) + +#define OPENSSL_free(a) CRYPTO_free(a) + +#define OPENSSL_REMOVE_THREAD_STATE() + +/* init_ssl_ctx() function updates this array. + * It loads SSL library dynamically and changes NULLs to the actual addresses + * of respective functions. The macros above (like SSL_connect()) are really + * just calling these functions indirectly via the pointer. */ +static struct ssl_func ssl_sw[] = {{"SSL_free", NULL}, + {"SSL_accept", NULL}, + {"SSL_connect", NULL}, + {"SSL_read", NULL}, + {"SSL_write", NULL}, + {"SSL_get_error", NULL}, + {"SSL_set_fd", NULL}, + {"SSL_new", NULL}, + {"SSL_CTX_new", NULL}, + {"TLS_server_method", NULL}, + {"OPENSSL_init_ssl", NULL}, + {"SSL_CTX_use_PrivateKey_file", NULL}, + {"SSL_CTX_use_certificate_file", NULL}, + {"SSL_CTX_set_default_passwd_cb", NULL}, + {"SSL_CTX_free", NULL}, + {"SSL_CTX_use_certificate_chain_file", NULL}, + {"TLS_client_method", NULL}, + {"SSL_pending", NULL}, + {"SSL_CTX_set_verify", NULL}, + {"SSL_shutdown", NULL}, + {"SSL_CTX_load_verify_locations", NULL}, + {"SSL_CTX_set_default_verify_paths", NULL}, + {"SSL_CTX_set_verify_depth", NULL}, + {"SSL_get_peer_certificate", NULL}, + {"SSL_get_version", NULL}, + {"SSL_get_current_cipher", NULL}, + {"SSL_CIPHER_get_name", NULL}, + {"SSL_CTX_check_private_key", NULL}, + {"SSL_CTX_set_session_id_context", NULL}, + {"SSL_CTX_ctrl", NULL}, + {"SSL_CTX_set_cipher_list", NULL}, + {"SSL_CTX_set_options", NULL}, + {"SSL_CTX_set_info_callback", NULL}, + {"SSL_get_ex_data", NULL}, + {"SSL_set_ex_data", NULL}, + {"SSL_CTX_callback_ctrl", NULL}, + {"SSL_get_servername", NULL}, + {"SSL_set_SSL_CTX", NULL}, + {"SSL_ctrl", NULL}, + {NULL, NULL}}; + + +/* Similar array as ssl_sw. These functions could be located in different + * lib. */ +static struct ssl_func crypto_sw[] = {{"ERR_get_error", NULL}, + {"ERR_error_string", NULL}, + {"CONF_modules_unload", NULL}, + {"X509_free", NULL}, + {"X509_get_subject_name", NULL}, + {"X509_get_issuer_name", NULL}, + {"X509_NAME_oneline", NULL}, + {"X509_get_serialNumber", NULL}, + {"EVP_get_digestbyname", NULL}, + {"EVP_Digest", NULL}, + {"i2d_X509", NULL}, + {"BN_bn2hex", NULL}, + {"ASN1_INTEGER_to_BN", NULL}, + {"BN_free", NULL}, + {"CRYPTO_free", NULL}, + {"ERR_clear_error", NULL}, + {NULL, NULL}}; +#else + +#define SSL_free (*(void (*)(SSL *))ssl_sw[0].ptr) +#define SSL_accept (*(int (*)(SSL *))ssl_sw[1].ptr) +#define SSL_connect (*(int (*)(SSL *))ssl_sw[2].ptr) +#define SSL_read (*(int (*)(SSL *, void *, int))ssl_sw[3].ptr) +#define SSL_write (*(int (*)(SSL *, const void *, int))ssl_sw[4].ptr) +#define SSL_get_error (*(int (*)(SSL *, int))ssl_sw[5].ptr) +#define SSL_set_fd (*(int (*)(SSL *, SOCKET))ssl_sw[6].ptr) +#define SSL_new (*(SSL * (*)(SSL_CTX *)) ssl_sw[7].ptr) +#define SSL_CTX_new (*(SSL_CTX * (*)(SSL_METHOD *)) ssl_sw[8].ptr) +#define SSLv23_server_method (*(SSL_METHOD * (*)(void)) ssl_sw[9].ptr) +#define SSL_library_init (*(int (*)(void))ssl_sw[10].ptr) +#define SSL_CTX_use_PrivateKey_file \ + (*(int (*)(SSL_CTX *, const char *, int))ssl_sw[11].ptr) +#define SSL_CTX_use_certificate_file \ + (*(int (*)(SSL_CTX *, const char *, int))ssl_sw[12].ptr) +#define SSL_CTX_set_default_passwd_cb \ + (*(void (*)(SSL_CTX *, mg_callback_t))ssl_sw[13].ptr) +#define SSL_CTX_free (*(void (*)(SSL_CTX *))ssl_sw[14].ptr) +#define SSL_load_error_strings (*(void (*)(void))ssl_sw[15].ptr) +#define SSL_CTX_use_certificate_chain_file \ + (*(int (*)(SSL_CTX *, const char *))ssl_sw[16].ptr) +#define SSLv23_client_method (*(SSL_METHOD * (*)(void)) ssl_sw[17].ptr) +#define SSL_pending (*(int (*)(SSL *))ssl_sw[18].ptr) +#define SSL_CTX_set_verify \ + (*(void (*)(SSL_CTX *, \ + int, \ + int (*verify_callback)(int, X509_STORE_CTX *)))ssl_sw[19] \ + .ptr) +#define SSL_shutdown (*(int (*)(SSL *))ssl_sw[20].ptr) +#define SSL_CTX_load_verify_locations \ + (*(int (*)(SSL_CTX *, const char *, const char *))ssl_sw[21].ptr) +#define SSL_CTX_set_default_verify_paths (*(int (*)(SSL_CTX *))ssl_sw[22].ptr) +#define SSL_CTX_set_verify_depth (*(void (*)(SSL_CTX *, int))ssl_sw[23].ptr) +#define SSL_get_peer_certificate (*(X509 * (*)(SSL *)) ssl_sw[24].ptr) +#define SSL_get_version (*(const char *(*)(SSL *))ssl_sw[25].ptr) +#define SSL_get_current_cipher (*(SSL_CIPHER * (*)(SSL *)) ssl_sw[26].ptr) +#define SSL_CIPHER_get_name \ + (*(const char *(*)(const SSL_CIPHER *))ssl_sw[27].ptr) +#define SSL_CTX_check_private_key (*(int (*)(SSL_CTX *))ssl_sw[28].ptr) +#define SSL_CTX_set_session_id_context \ + (*(int (*)(SSL_CTX *, const unsigned char *, unsigned int))ssl_sw[29].ptr) +#define SSL_CTX_ctrl (*(long (*)(SSL_CTX *, int, long, void *))ssl_sw[30].ptr) +#define SSL_CTX_set_cipher_list \ + (*(int (*)(SSL_CTX *, const char *))ssl_sw[31].ptr) +#define SSL_CTX_set_info_callback \ + (*(void (*)(SSL_CTX *, void (*callback)(const SSL *, int, int)))ssl_sw[32] \ + .ptr) +#define SSL_get_ex_data (*(char *(*)(const SSL *, int))ssl_sw[33].ptr) +#define SSL_set_ex_data (*(void (*)(SSL *, int, char *))ssl_sw[34].ptr) +#define SSL_CTX_callback_ctrl \ + (*(long (*)(SSL_CTX *, int, void (*)(void)))ssl_sw[35].ptr) +#define SSL_get_servername \ + (*(const char *(*)(const SSL *, int type))ssl_sw[36].ptr) +#define SSL_set_SSL_CTX (*(SSL_CTX * (*)(SSL *, SSL_CTX *)) ssl_sw[37].ptr) +#define SSL_ctrl (*(long (*)(SSL *, int, long, void *))ssl_sw[38].ptr) + +#define SSL_CTX_set_options(ctx, op) \ + SSL_CTX_ctrl((ctx), SSL_CTRL_OPTIONS, (op), NULL) +#define SSL_CTX_clear_options(ctx, op) \ + SSL_CTX_ctrl((ctx), SSL_CTRL_CLEAR_OPTIONS, (op), NULL) +#define SSL_CTX_set_ecdh_auto(ctx, onoff) \ + SSL_CTX_ctrl(ctx, SSL_CTRL_SET_ECDH_AUTO, onoff, NULL) + +#define SSL_CTRL_SET_TLSEXT_SERVERNAME_CB 53 +#define SSL_CTRL_SET_TLSEXT_SERVERNAME_ARG 54 +#define SSL_CTRL_SET_TLSEXT_HOSTNAME 55 +#define SSL_CTX_set_tlsext_servername_callback(ctx, cb) \ + SSL_CTX_callback_ctrl(ctx, \ + SSL_CTRL_SET_TLSEXT_SERVERNAME_CB, \ + (void (*)(void))cb) +#define SSL_CTX_set_tlsext_servername_arg(ctx, arg) \ + SSL_CTX_ctrl(ctx, SSL_CTRL_SET_TLSEXT_SERVERNAME_ARG, 0, (void *)arg) +#define SSL_set_tlsext_host_name(ctx, arg) \ + SSL_ctrl(ctx, SSL_CTRL_SET_TLSEXT_HOSTNAME, 0, (void *)arg) + +#define X509_get_notBefore(x) ((x)->cert_info->validity->notBefore) +#define X509_get_notAfter(x) ((x)->cert_info->validity->notAfter) + +#define SSL_set_app_data(s, arg) (SSL_set_ex_data(s, 0, (char *)arg)) +#define SSL_get_app_data(s) (SSL_get_ex_data(s, 0)) + +#define CRYPTO_num_locks (*(int (*)(void))crypto_sw[0].ptr) +#define CRYPTO_set_locking_callback \ + (*(void (*)(void (*)(int, int, const char *, int)))crypto_sw[1].ptr) +#define CRYPTO_set_id_callback \ + (*(void (*)(unsigned long (*)(void)))crypto_sw[2].ptr) +#define ERR_get_error (*(unsigned long (*)(void))crypto_sw[3].ptr) +#define ERR_error_string (*(char *(*)(unsigned long, char *))crypto_sw[4].ptr) +#define ERR_remove_state (*(void (*)(unsigned long))crypto_sw[5].ptr) +#define ERR_free_strings (*(void (*)(void))crypto_sw[6].ptr) +#define ENGINE_cleanup (*(void (*)(void))crypto_sw[7].ptr) +#define CONF_modules_unload (*(void (*)(int))crypto_sw[8].ptr) +#define CRYPTO_cleanup_all_ex_data (*(void (*)(void))crypto_sw[9].ptr) +#define EVP_cleanup (*(void (*)(void))crypto_sw[10].ptr) +#define X509_free (*(void (*)(X509 *))crypto_sw[11].ptr) +#define X509_get_subject_name (*(X509_NAME * (*)(X509 *)) crypto_sw[12].ptr) +#define X509_get_issuer_name (*(X509_NAME * (*)(X509 *)) crypto_sw[13].ptr) +#define X509_NAME_oneline \ + (*(char *(*)(X509_NAME *, char *, int))crypto_sw[14].ptr) +#define X509_get_serialNumber (*(ASN1_INTEGER * (*)(X509 *)) crypto_sw[15].ptr) +#define i2c_ASN1_INTEGER \ + (*(int (*)(ASN1_INTEGER *, unsigned char **))crypto_sw[16].ptr) +#define EVP_get_digestbyname \ + (*(const EVP_MD *(*)(const char *))crypto_sw[17].ptr) +#define EVP_Digest \ + (*(int (*)( \ + const void *, size_t, void *, unsigned int *, const EVP_MD *, void *)) \ + crypto_sw[18] \ + .ptr) +#define i2d_X509 (*(int (*)(X509 *, unsigned char **))crypto_sw[19].ptr) +#define BN_bn2hex (*(char *(*)(const BIGNUM *a))crypto_sw[20].ptr) +#define ASN1_INTEGER_to_BN \ + (*(BIGNUM * (*)(const ASN1_INTEGER *ai, BIGNUM *bn)) crypto_sw[21].ptr) +#define BN_free (*(void (*)(const BIGNUM *a))crypto_sw[22].ptr) +#define CRYPTO_free (*(void (*)(void *addr))crypto_sw[23].ptr) +#define ERR_clear_error (*(void (*)(void))crypto_sw[24].ptr) + +#define OPENSSL_free(a) CRYPTO_free(a) + +/* use here ERR_remove_state, + * while on some platforms function is not included into library due to + * deprication */ +#define OPENSSL_REMOVE_THREAD_STATE() ERR_remove_state(0) + +/* init_ssl_ctx() function updates this array. + * It loads SSL library dynamically and changes NULLs to the actual addresses + * of respective functions. The macros above (like SSL_connect()) are really + * just calling these functions indirectly via the pointer. */ +static struct ssl_func ssl_sw[] = {{"SSL_free", NULL}, + {"SSL_accept", NULL}, + {"SSL_connect", NULL}, + {"SSL_read", NULL}, + {"SSL_write", NULL}, + {"SSL_get_error", NULL}, + {"SSL_set_fd", NULL}, + {"SSL_new", NULL}, + {"SSL_CTX_new", NULL}, + {"SSLv23_server_method", NULL}, + {"SSL_library_init", NULL}, + {"SSL_CTX_use_PrivateKey_file", NULL}, + {"SSL_CTX_use_certificate_file", NULL}, + {"SSL_CTX_set_default_passwd_cb", NULL}, + {"SSL_CTX_free", NULL}, + {"SSL_load_error_strings", NULL}, + {"SSL_CTX_use_certificate_chain_file", NULL}, + {"SSLv23_client_method", NULL}, + {"SSL_pending", NULL}, + {"SSL_CTX_set_verify", NULL}, + {"SSL_shutdown", NULL}, + {"SSL_CTX_load_verify_locations", NULL}, + {"SSL_CTX_set_default_verify_paths", NULL}, + {"SSL_CTX_set_verify_depth", NULL}, + {"SSL_get_peer_certificate", NULL}, + {"SSL_get_version", NULL}, + {"SSL_get_current_cipher", NULL}, + {"SSL_CIPHER_get_name", NULL}, + {"SSL_CTX_check_private_key", NULL}, + {"SSL_CTX_set_session_id_context", NULL}, + {"SSL_CTX_ctrl", NULL}, + {"SSL_CTX_set_cipher_list", NULL}, + {"SSL_CTX_set_info_callback", NULL}, + {"SSL_get_ex_data", NULL}, + {"SSL_set_ex_data", NULL}, + {"SSL_CTX_callback_ctrl", NULL}, + {"SSL_get_servername", NULL}, + {"SSL_set_SSL_CTX", NULL}, + {"SSL_ctrl", NULL}, + {NULL, NULL}}; + + +/* Similar array as ssl_sw. These functions could be located in different + * lib. */ +static struct ssl_func crypto_sw[] = {{"CRYPTO_num_locks", NULL}, + {"CRYPTO_set_locking_callback", NULL}, + {"CRYPTO_set_id_callback", NULL}, + {"ERR_get_error", NULL}, + {"ERR_error_string", NULL}, + {"ERR_remove_state", NULL}, + {"ERR_free_strings", NULL}, + {"ENGINE_cleanup", NULL}, + {"CONF_modules_unload", NULL}, + {"CRYPTO_cleanup_all_ex_data", NULL}, + {"EVP_cleanup", NULL}, + {"X509_free", NULL}, + {"X509_get_subject_name", NULL}, + {"X509_get_issuer_name", NULL}, + {"X509_NAME_oneline", NULL}, + {"X509_get_serialNumber", NULL}, + {"i2c_ASN1_INTEGER", NULL}, + {"EVP_get_digestbyname", NULL}, + {"EVP_Digest", NULL}, + {"i2d_X509", NULL}, + {"BN_bn2hex", NULL}, + {"ASN1_INTEGER_to_BN", NULL}, + {"BN_free", NULL}, + {"CRYPTO_free", NULL}, + {"ERR_clear_error", NULL}, + {NULL, NULL}}; +#endif /* OPENSSL_API_1_1 */ +#endif /* NO_SSL_DL */ +#endif /* NO_SSL */ + + +#if !defined(NO_CACHING) +static const char month_names[][4] = {"Jan", + "Feb", + "Mar", + "Apr", + "May", + "Jun", + "Jul", + "Aug", + "Sep", + "Oct", + "Nov", + "Dec"}; +#endif /* !NO_CACHING */ + +/* Unified socket address. For IPv6 support, add IPv6 address structure in + * the + * union u. */ +union usa { + struct sockaddr sa; + struct sockaddr_in sin; +#if defined(USE_IPV6) + struct sockaddr_in6 sin6; +#endif +}; + +/* Describes a string (chunk of memory). */ +struct vec { + const char *ptr; + size_t len; +}; + +struct mg_file_stat { + /* File properties filled by mg_stat: */ + uint64_t size; + time_t last_modified; + int is_directory; /* Set to 1 if mg_stat is called for a directory */ + int is_gzipped; /* Set to 1 if the content is gzipped, in which + * case we need a "Content-Eencoding: gzip" header */ + int location; /* 0 = nowhere, 1 = on disk, 2 = in memory */ +}; + +struct mg_file_in_memory { + char *p; + uint32_t pos; + char mode; +}; + +struct mg_file_access { + /* File properties filled by mg_fopen: */ + FILE *fp; +#if defined(MG_USE_OPEN_FILE) + /* TODO (low): Remove obsolete "file in memory" implementation. + * In an "early 2017" discussion at Google groups + * https://groups.google.com/forum/#!topic/civetweb/h9HT4CmeYqI + * we decided to get rid of this feature (after some fade-out + * phase). */ + const char *membuf; +#endif +}; + +struct mg_file { + struct mg_file_stat stat; + struct mg_file_access access; +}; + +#if defined(MG_USE_OPEN_FILE) + +#define STRUCT_FILE_INITIALIZER \ + { \ + {(uint64_t)0, (time_t)0, 0, 0, 0}, \ + { \ + (FILE *)NULL, (const char *)NULL \ + } \ + } + +#else + +#define STRUCT_FILE_INITIALIZER \ + { \ + {(uint64_t)0, (time_t)0, 0, 0, 0}, \ + { \ + (FILE *)NULL \ + } \ + } + +#endif + + +/* Describes listening socket, or socket which was accept()-ed by the master + * thread and queued for future handling by the worker thread. */ +struct socket { + SOCKET sock; /* Listening socket */ + union usa lsa; /* Local socket address */ + union usa rsa; /* Remote socket address */ + unsigned char is_ssl; /* Is port SSL-ed */ + unsigned char ssl_redir; /* Is port supposed to redirect everything to SSL + * port */ + unsigned char in_use; /* 0: invalid, 1: valid, 2: free */ +}; + + +/* Enum const for all options must be in sync with + * static struct mg_option config_options[] + * This is tested in the unit test (test/private.c) + * "Private Config Options" + */ +enum { + /* Once for each server */ + LISTENING_PORTS, + NUM_THREADS, + RUN_AS_USER, + CONFIG_TCP_NODELAY, /* Prepended CONFIG_ to avoid conflict with the + * socket option typedef TCP_NODELAY. */ + MAX_REQUEST_SIZE, + LINGER_TIMEOUT, + MAX_CONNECTIONS, +#if defined(__linux__) + ALLOW_SENDFILE_CALL, +#endif +#if defined(_WIN32) + CASE_SENSITIVE_FILES, +#endif + THROTTLE, + ACCESS_LOG_FILE, + ERROR_LOG_FILE, + ENABLE_KEEP_ALIVE, + REQUEST_TIMEOUT, + KEEP_ALIVE_TIMEOUT, +#if defined(USE_WEBSOCKET) + WEBSOCKET_TIMEOUT, + ENABLE_WEBSOCKET_PING_PONG, +#endif + DECODE_URL, +#if defined(USE_LUA) + LUA_BACKGROUND_SCRIPT, + LUA_BACKGROUND_SCRIPT_PARAMS, +#endif +#if defined(USE_TIMERS) + CGI_TIMEOUT, +#endif + + /* Once for each domain */ + DOCUMENT_ROOT, + CGI_EXTENSIONS, + CGI_ENVIRONMENT, + PUT_DELETE_PASSWORDS_FILE, + CGI_INTERPRETER, + PROTECT_URI, + AUTHENTICATION_DOMAIN, + ENABLE_AUTH_DOMAIN_CHECK, + SSI_EXTENSIONS, + ENABLE_DIRECTORY_LISTING, + GLOBAL_PASSWORDS_FILE, + INDEX_FILES, + ACCESS_CONTROL_LIST, + EXTRA_MIME_TYPES, + SSL_CERTIFICATE, + SSL_CERTIFICATE_CHAIN, + URL_REWRITE_PATTERN, + HIDE_FILES, + SSL_DO_VERIFY_PEER, + SSL_CA_PATH, + SSL_CA_FILE, + SSL_VERIFY_DEPTH, + SSL_DEFAULT_VERIFY_PATHS, + SSL_CIPHER_LIST, + SSL_PROTOCOL_VERSION, + SSL_SHORT_TRUST, + +#if defined(USE_LUA) + LUA_PRELOAD_FILE, + LUA_SCRIPT_EXTENSIONS, + LUA_SERVER_PAGE_EXTENSIONS, +#if defined(MG_EXPERIMENTAL_INTERFACES) + LUA_DEBUG_PARAMS, +#endif +#endif +#if defined(USE_DUKTAPE) + DUKTAPE_SCRIPT_EXTENSIONS, +#endif + +#if defined(USE_WEBSOCKET) + WEBSOCKET_ROOT, +#endif +#if defined(USE_LUA) && defined(USE_WEBSOCKET) + LUA_WEBSOCKET_EXTENSIONS, +#endif + + ACCESS_CONTROL_ALLOW_ORIGIN, + ACCESS_CONTROL_ALLOW_METHODS, + ACCESS_CONTROL_ALLOW_HEADERS, + ERROR_PAGES, +#if !defined(NO_CACHING) + STATIC_FILE_MAX_AGE, +#endif +#if !defined(NO_SSL) + STRICT_HTTPS_MAX_AGE, +#endif + ADDITIONAL_HEADER, + ALLOW_INDEX_SCRIPT_SUB_RES, +#if defined(DAEMONIZE) + ENABLE_DAEMONIZE, +#endif + + NUM_OPTIONS +}; + + +/* Config option name, config types, default value. + * Must be in the same order as the enum const above. + */ +static const struct mg_option config_options[] = { + + /* Once for each server */ + {"listening_ports", MG_CONFIG_TYPE_STRING_LIST, "8080"}, + {"num_threads", MG_CONFIG_TYPE_NUMBER, "50"}, + {"run_as_user", MG_CONFIG_TYPE_STRING, NULL}, + {"tcp_nodelay", MG_CONFIG_TYPE_NUMBER, "0"}, + {"max_request_size", MG_CONFIG_TYPE_NUMBER, "16384"}, + {"linger_timeout_ms", MG_CONFIG_TYPE_NUMBER, NULL}, + {"max_connections", MG_CONFIG_TYPE_NUMBER, "100"}, +#if defined(__linux__) + {"allow_sendfile_call", MG_CONFIG_TYPE_BOOLEAN, "yes"}, +#endif +#if defined(_WIN32) + {"case_sensitive", MG_CONFIG_TYPE_BOOLEAN, "no"}, +#endif + {"throttle", MG_CONFIG_TYPE_STRING_LIST, NULL}, + {"access_log_file", MG_CONFIG_TYPE_FILE, NULL}, + {"error_log_file", MG_CONFIG_TYPE_FILE, NULL}, + {"enable_keep_alive", MG_CONFIG_TYPE_BOOLEAN, "no"}, + {"request_timeout_ms", MG_CONFIG_TYPE_NUMBER, "30000"}, + {"keep_alive_timeout_ms", MG_CONFIG_TYPE_NUMBER, "500"}, +#if defined(USE_WEBSOCKET) + {"websocket_timeout_ms", MG_CONFIG_TYPE_NUMBER, NULL}, + {"enable_websocket_ping_pong", MG_CONFIG_TYPE_BOOLEAN, "no"}, +#endif + {"decode_url", MG_CONFIG_TYPE_BOOLEAN, "yes"}, +#if defined(USE_LUA) + {"lua_background_script", MG_CONFIG_TYPE_FILE, NULL}, + {"lua_background_script_params", MG_CONFIG_TYPE_STRING_LIST, NULL}, +#endif +#if defined(USE_TIMERS) + {"cgi_timeout_ms", MG_CONFIG_TYPE_NUMBER, NULL}, +#endif + + /* Once for each domain */ + {"document_root", MG_CONFIG_TYPE_DIRECTORY, NULL}, + {"cgi_pattern", MG_CONFIG_TYPE_EXT_PATTERN, "**.cgi$|**.pl$|**.php$"}, + {"cgi_environment", MG_CONFIG_TYPE_STRING_LIST, NULL}, + {"put_delete_auth_file", MG_CONFIG_TYPE_FILE, NULL}, + {"cgi_interpreter", MG_CONFIG_TYPE_FILE, NULL}, + {"protect_uri", MG_CONFIG_TYPE_STRING_LIST, NULL}, + {"authentication_domain", MG_CONFIG_TYPE_STRING, "mydomain.com"}, + {"enable_auth_domain_check", MG_CONFIG_TYPE_BOOLEAN, "yes"}, + {"ssi_pattern", MG_CONFIG_TYPE_EXT_PATTERN, "**.shtml$|**.shtm$"}, + {"enable_directory_listing", MG_CONFIG_TYPE_BOOLEAN, "yes"}, + {"global_auth_file", MG_CONFIG_TYPE_FILE, NULL}, + {"index_files", + MG_CONFIG_TYPE_STRING_LIST, +#if defined(USE_LUA) + "index.xhtml,index.html,index.htm," + "index.lp,index.lsp,index.lua,index.cgi," + "index.shtml,index.php"}, +#else + "index.xhtml,index.html,index.htm,index.cgi,index.shtml,index.php"}, +#endif + {"access_control_list", MG_CONFIG_TYPE_STRING_LIST, NULL}, + {"extra_mime_types", MG_CONFIG_TYPE_STRING_LIST, NULL}, + {"ssl_certificate", MG_CONFIG_TYPE_FILE, NULL}, + {"ssl_certificate_chain", MG_CONFIG_TYPE_FILE, NULL}, + {"url_rewrite_patterns", MG_CONFIG_TYPE_STRING_LIST, NULL}, + {"hide_files_patterns", MG_CONFIG_TYPE_EXT_PATTERN, NULL}, + + {"ssl_verify_peer", MG_CONFIG_TYPE_YES_NO_OPTIONAL, "no"}, + + {"ssl_ca_path", MG_CONFIG_TYPE_DIRECTORY, NULL}, + {"ssl_ca_file", MG_CONFIG_TYPE_FILE, NULL}, + {"ssl_verify_depth", MG_CONFIG_TYPE_NUMBER, "9"}, + {"ssl_default_verify_paths", MG_CONFIG_TYPE_BOOLEAN, "yes"}, + {"ssl_cipher_list", MG_CONFIG_TYPE_STRING, NULL}, + {"ssl_protocol_version", MG_CONFIG_TYPE_NUMBER, "0"}, + {"ssl_short_trust", MG_CONFIG_TYPE_BOOLEAN, "no"}, + +#if defined(USE_LUA) + {"lua_preload_file", MG_CONFIG_TYPE_FILE, NULL}, + {"lua_script_pattern", MG_CONFIG_TYPE_EXT_PATTERN, "**.lua$"}, + {"lua_server_page_pattern", MG_CONFIG_TYPE_EXT_PATTERN, "**.lp$|**.lsp$"}, +#if defined(MG_EXPERIMENTAL_INTERFACES) + {"lua_debug", MG_CONFIG_TYPE_STRING, NULL}, +#endif +#endif +#if defined(USE_DUKTAPE) + /* The support for duktape is still in alpha version state. + * The name of this config option might change. */ + {"duktape_script_pattern", MG_CONFIG_TYPE_EXT_PATTERN, "**.ssjs$"}, +#endif + +#if defined(USE_WEBSOCKET) + {"websocket_root", MG_CONFIG_TYPE_DIRECTORY, NULL}, +#endif +#if defined(USE_LUA) && defined(USE_WEBSOCKET) + {"lua_websocket_pattern", MG_CONFIG_TYPE_EXT_PATTERN, "**.lua$"}, +#endif + {"access_control_allow_origin", MG_CONFIG_TYPE_STRING, "*"}, + {"access_control_allow_methods", MG_CONFIG_TYPE_STRING, "*"}, + {"access_control_allow_headers", MG_CONFIG_TYPE_STRING, "*"}, + {"error_pages", MG_CONFIG_TYPE_DIRECTORY, NULL}, +#if !defined(NO_CACHING) + {"static_file_max_age", MG_CONFIG_TYPE_NUMBER, "3600"}, +#endif +#if !defined(NO_SSL) + {"strict_transport_security_max_age", MG_CONFIG_TYPE_NUMBER, NULL}, +#endif + {"additional_header", MG_CONFIG_TYPE_STRING_MULTILINE, NULL}, + {"allow_index_script_resource", MG_CONFIG_TYPE_BOOLEAN, "no"}, +#if defined(DAEMONIZE) + {"daemonize", MG_CONFIG_TYPE_BOOLEAN, "no"}, +#endif + + {NULL, MG_CONFIG_TYPE_UNKNOWN, NULL}}; + + +/* Check if the config_options and the corresponding enum have compatible + * sizes. */ +mg_static_assert((sizeof(config_options) / sizeof(config_options[0])) + == (NUM_OPTIONS + 1), + "config_options and enum not sync"); + + +enum { REQUEST_HANDLER, WEBSOCKET_HANDLER, AUTH_HANDLER }; + + +struct mg_handler_info { + /* Name/Pattern of the URI. */ + char *uri; + size_t uri_len; + + /* handler type */ + int handler_type; + + /* Handler for http/https or authorization requests. */ + mg_request_handler handler; + unsigned int refcount; + pthread_mutex_t refcount_mutex; /* Protects refcount */ + pthread_cond_t + refcount_cond; /* Signaled when handler refcount is decremented */ + + /* Handler for ws/wss (websocket) requests. */ + mg_websocket_connect_handler connect_handler; + mg_websocket_ready_handler ready_handler; + mg_websocket_data_handler data_handler; + mg_websocket_close_handler close_handler; + + /* accepted subprotocols for ws/wss requests. */ + struct mg_websocket_subprotocols *subprotocols; + + /* Handler for authorization requests */ + mg_authorization_handler auth_handler; + + /* User supplied argument for the handler function. */ + void *cbdata; + + /* next handler in a linked list */ + struct mg_handler_info *next; +}; + + +enum { + CONTEXT_INVALID, + CONTEXT_SERVER, + CONTEXT_HTTP_CLIENT, + CONTEXT_WS_CLIENT +}; + + +struct mg_domain_context { + SSL_CTX *ssl_ctx; /* SSL context */ + char *config[NUM_OPTIONS]; /* Civetweb configuration parameters */ + struct mg_handler_info *handlers; /* linked list of uri handlers */ + + /* Server nonce */ + uint64_t auth_nonce_mask; /* Mask for all nonce values */ + unsigned long nonce_count; /* Used nonces, used for authentication */ + +#if defined(USE_LUA) && defined(USE_WEBSOCKET) + /* linked list of shared lua websockets */ + struct mg_shared_lua_websocket_list *shared_lua_websockets; +#endif + + /* Linked list of domains */ + struct mg_domain_context *next; +}; + + +struct mg_context { + + /* Part 1 - Physical context: + * This holds threads, ports, timeouts, ... + * set for the entire server, independent from the + * addressed hostname. + */ + + /* Connection related */ + int context_type; /* See CONTEXT_* above */ + + struct socket *listening_sockets; + struct mg_pollfd *listening_socket_fds; + unsigned int num_listening_sockets; + + struct mg_connection *worker_connections; /* The connection struct, pre- + * allocated for each worker */ + +#if defined(USE_SERVER_STATS) + int active_connections; + int max_connections; + int64_t total_connections; + int64_t total_requests; + int64_t total_data_read; + int64_t total_data_written; +#endif + + /* Thread related */ + volatile int stop_flag; /* Should we stop event loop */ + pthread_mutex_t thread_mutex; /* Protects (max|num)_threads */ + + pthread_t masterthreadid; /* The master thread ID */ + unsigned int + cfg_worker_threads; /* The number of configured worker threads. */ + pthread_t *worker_threadids; /* The worker thread IDs */ + +/* Connection to thread dispatching */ +#if defined(ALTERNATIVE_QUEUE) + struct socket *client_socks; + void **client_wait_events; +#else + struct socket queue[MGSQLEN]; /* Accepted sockets */ + volatile int sq_head; /* Head of the socket queue */ + volatile int sq_tail; /* Tail of the socket queue */ + pthread_cond_t sq_full; /* Signaled when socket is produced */ + pthread_cond_t sq_empty; /* Signaled when socket is consumed */ +#endif + + /* Memory related */ + unsigned int max_request_size; /* The max request size */ + +#if defined(USE_SERVER_STATS) + struct mg_memory_stat ctx_memory; +#endif + + /* Operating system related */ + char *systemName; /* What operating system is running */ + time_t start_time; /* Server start time, used for authentication + * and for diagnstics. */ + +#if defined(USE_TIMERS) + struct ttimers *timers; +#endif + +/* Lua specific: Background operations and shared websockets */ +#if defined(USE_LUA) + void *lua_background_state; +#endif + + /* Server nonce */ + pthread_mutex_t nonce_mutex; /* Protects nonce_count */ + + /* Server callbacks */ + struct mg_callbacks callbacks; /* User-defined callback function */ + void *user_data; /* User-defined data */ + + /* Part 2 - Logical domain: + * This holds hostname, TLS certificate, document root, ... + * set for a domain hosted at the server. + * There may be multiple domains hosted at one physical server. + * The default domain "dd" is the first element of a list of + * domains. + */ + struct mg_domain_context dd; /* default domain */ +}; + + +#if defined(USE_SERVER_STATS) +static struct mg_memory_stat mg_common_memory = {0, 0, 0}; + +static struct mg_memory_stat * +get_memory_stat(struct mg_context *ctx) +{ + if (ctx) { + return &(ctx->ctx_memory); + } + return &mg_common_memory; +} +#endif + +enum { + CONNECTION_TYPE_INVALID, + CONNECTION_TYPE_REQUEST, + CONNECTION_TYPE_RESPONSE +}; + +struct mg_connection { + int connection_type; /* see CONNECTION_TYPE_* above */ + + struct mg_request_info request_info; + struct mg_response_info response_info; + + struct mg_context *phys_ctx; + struct mg_domain_context *dom_ctx; + +#if defined(USE_SERVER_STATS) + int conn_state; /* 0 = undef, numerical value may change in different + * versions. For the current definition, see + * mg_get_connection_info_impl */ +#endif + + const char *host; /* Host (HTTP/1.1 header or SNI) */ + SSL *ssl; /* SSL descriptor */ + struct socket client; /* Connected client */ + time_t conn_birth_time; /* Time (wall clock) when connection was + * established */ + struct timespec req_time; /* Time (since system start) when the request + * was received */ + int64_t num_bytes_sent; /* Total bytes sent to client */ + int64_t content_len; /* How many bytes of content can be read + * !is_chunked: Content-Length header value + * or -1 (until connection closed, + * not allowed for a request) + * is_chunked: >= 0, appended gradually + */ + int64_t consumed_content; /* How many bytes of content have been read */ + int is_chunked; /* Transfer-Encoding is chunked: + * 0 = not chunked, + * 1 = chunked, not yet, or some data read, + * 2 = chunked, has error, + * 3 = chunked, all data read except trailer, + * 4 = chunked, all data read + */ + char *buf; /* Buffer for received data */ + char *path_info; /* PATH_INFO part of the URL */ + + int must_close; /* 1 if connection must be closed */ + int accept_gzip; /* 1 if gzip encoding is accepted */ + int in_error_handler; /* 1 if in handler for user defined error + * pages */ +#if defined(USE_WEBSOCKET) + int in_websocket_handling; /* 1 if in read_websocket */ +#endif + int handled_requests; /* Number of requests handled by this connection + */ + int buf_size; /* Buffer size */ + int request_len; /* Size of the request + headers in a buffer */ + int data_len; /* Total size of data in a buffer */ + int status_code; /* HTTP reply status code, e.g. 200 */ + int throttle; /* Throttling, bytes/sec. <= 0 means no + * throttle */ + + time_t last_throttle_time; /* Last time throttled data was sent */ + int last_throttle_bytes; /* Bytes sent this second */ + pthread_mutex_t mutex; /* Used by mg_(un)lock_connection to ensure + * atomic transmissions for websockets */ +#if defined(USE_LUA) && defined(USE_WEBSOCKET) + void *lua_websocket_state; /* Lua_State for a websocket connection */ +#endif + + void *tls_user_ptr; /* User defined pointer in thread local storage, + * for quick access */ +}; + + +/* Directory entry */ +struct de { + struct mg_connection *conn; + char *file_name; + struct mg_file_stat file; +}; + + +#if defined(USE_WEBSOCKET) +static int is_websocket_protocol(const struct mg_connection *conn); +#else +#define is_websocket_protocol(conn) (0) +#endif + + +#define mg_cry_internal(conn, fmt, ...) \ + mg_cry_internal_wrap(conn, NULL, __func__, __LINE__, fmt, __VA_ARGS__) + +#define mg_cry_ctx_internal(ctx, fmt, ...) \ + mg_cry_internal_wrap(NULL, ctx, __func__, __LINE__, fmt, __VA_ARGS__) + +static void mg_cry_internal_wrap(const struct mg_connection *conn, + struct mg_context *ctx, + const char *func, + unsigned line, + const char *fmt, + ...) PRINTF_ARGS(5, 6); + + +#if !defined(NO_THREAD_NAME) +#if defined(_WIN32) && defined(_MSC_VER) +/* Set the thread name for debugging purposes in Visual Studio + * http://msdn.microsoft.com/en-us/library/xcb2z8hs.aspx + */ +#pragma pack(push, 8) +typedef struct tagTHREADNAME_INFO { + DWORD dwType; /* Must be 0x1000. */ + LPCSTR szName; /* Pointer to name (in user addr space). */ + DWORD dwThreadID; /* Thread ID (-1=caller thread). */ + DWORD dwFlags; /* Reserved for future use, must be zero. */ +} THREADNAME_INFO; +#pragma pack(pop) + +#elif defined(__linux__) + +#include +#include +#if defined(ALTERNATIVE_QUEUE) +#include +#endif /* ALTERNATIVE_QUEUE */ + + +#if defined(ALTERNATIVE_QUEUE) + +static void * +event_create(void) +{ + int evhdl = eventfd(0, EFD_CLOEXEC); + int *ret; + + if (evhdl == -1) { + /* Linux uses -1 on error, Windows NULL. */ + /* However, Linux does not return 0 on success either. */ + return 0; + } + + ret = (int *)mg_malloc(sizeof(int)); + if (ret) { + *ret = evhdl; + } else { + (void)close(evhdl); + } + + return (void *)ret; +} + + +static int +event_wait(void *eventhdl) +{ + uint64_t u; + int evhdl, s; + + if (!eventhdl) { + /* error */ + return 0; + } + evhdl = *(int *)eventhdl; + + s = (int)read(evhdl, &u, sizeof(u)); + if (s != sizeof(u)) { + /* error */ + return 0; + } + (void)u; /* the value is not required */ + return 1; +} + + +static int +event_signal(void *eventhdl) +{ + uint64_t u = 1; + int evhdl, s; + + if (!eventhdl) { + /* error */ + return 0; + } + evhdl = *(int *)eventhdl; + + s = (int)write(evhdl, &u, sizeof(u)); + if (s != sizeof(u)) { + /* error */ + return 0; + } + return 1; +} + + +static void +event_destroy(void *eventhdl) +{ + int evhdl; + + if (!eventhdl) { + /* error */ + return; + } + evhdl = *(int *)eventhdl; + + close(evhdl); + mg_free(eventhdl); +} + + +#endif + +#endif + + +#if !defined(__linux__) && !defined(_WIN32) && defined(ALTERNATIVE_QUEUE) + +struct posix_event { + pthread_mutex_t mutex; + pthread_cond_t cond; + int signaled; +}; + + +static void * +event_create(void) +{ + struct posix_event *ret = mg_malloc(sizeof(struct posix_event)); + if (ret == 0) { + /* out of memory */ + return 0; + } + if (0 != pthread_mutex_init(&(ret->mutex), NULL)) { + /* pthread mutex not available */ + mg_free(ret); + return 0; + } + if (0 != pthread_cond_init(&(ret->cond), NULL)) { + /* pthread cond not available */ + pthread_mutex_destroy(&(ret->mutex)); + mg_free(ret); + return 0; + } + ret->signaled = 0; + return (void *)ret; +} + + +static int +event_wait(void *eventhdl) +{ + struct posix_event *ev = (struct posix_event *)eventhdl; + pthread_mutex_lock(&(ev->mutex)); + while (!ev->signaled) { + pthread_cond_wait(&(ev->cond), &(ev->mutex)); + } + ev->signaled = 0; + pthread_mutex_unlock(&(ev->mutex)); + return 1; +} + + +static int +event_signal(void *eventhdl) +{ + struct posix_event *ev = (struct posix_event *)eventhdl; + pthread_mutex_lock(&(ev->mutex)); + pthread_cond_signal(&(ev->cond)); + ev->signaled = 1; + pthread_mutex_unlock(&(ev->mutex)); + return 1; +} + + +static void +event_destroy(void *eventhdl) +{ + struct posix_event *ev = (struct posix_event *)eventhdl; + pthread_cond_destroy(&(ev->cond)); + pthread_mutex_destroy(&(ev->mutex)); + mg_free(ev); +} +#endif + + +static void +mg_set_thread_name(const char *name) +{ + char threadName[16 + 1]; /* 16 = Max. thread length in Linux/OSX/.. */ + + mg_snprintf( + NULL, NULL, threadName, sizeof(threadName), "civetweb-%s", name); + +#if defined(_WIN32) +#if defined(_MSC_VER) + /* Windows and Visual Studio Compiler */ + __try { + THREADNAME_INFO info; + info.dwType = 0x1000; + info.szName = threadName; + info.dwThreadID = ~0U; + info.dwFlags = 0; + + RaiseException(0x406D1388, + 0, + sizeof(info) / sizeof(ULONG_PTR), + (ULONG_PTR *)&info); + } __except (EXCEPTION_EXECUTE_HANDLER) { + } +#elif defined(__MINGW32__) +/* No option known to set thread name for MinGW */ +#endif +#elif defined(_GNU_SOURCE) && defined(__GLIBC__) \ + && ((__GLIBC__ > 2) || ((__GLIBC__ == 2) && (__GLIBC_MINOR__ >= 12))) +/* pthread_setname_np first appeared in glibc in version 2.12*/ +#if defined(__MACH__) + /* OS X only current thread name can be changed */ + (void)pthread_setname_np(threadName); +#else + (void)pthread_setname_np(pthread_self(), threadName); +#endif +#elif defined(__linux__) + /* on linux we can use the old prctl function */ + (void)prctl(PR_SET_NAME, threadName, 0, 0, 0); +#endif +} +#else /* !defined(NO_THREAD_NAME) */ +void +mg_set_thread_name(const char *threadName) +{ +} +#endif + + +#if defined(MG_LEGACY_INTERFACE) +const char ** +mg_get_valid_option_names(void) +{ + /* This function is deprecated. Use mg_get_valid_options instead. */ + static const char + *data[2 * sizeof(config_options) / sizeof(config_options[0])] = {0}; + int i; + + for (i = 0; config_options[i].name != NULL; i++) { + data[i * 2] = config_options[i].name; + data[i * 2 + 1] = config_options[i].default_value; + } + + return data; +} +#endif + + +const struct mg_option * +mg_get_valid_options(void) +{ + return config_options; +} + + +/* Do not open file (used in is_file_in_memory) */ +#define MG_FOPEN_MODE_NONE (0) + +/* Open file for read only access */ +#define MG_FOPEN_MODE_READ (1) + +/* Open file for writing, create and overwrite */ +#define MG_FOPEN_MODE_WRITE (2) + +/* Open file for writing, create and append */ +#define MG_FOPEN_MODE_APPEND (4) + + +/* If a file is in memory, set all "stat" members and the membuf pointer of + * output filep and return 1, otherwise return 0 and don't modify anything. + */ +static int +open_file_in_memory(const struct mg_connection *conn, + const char *path, + struct mg_file *filep, + int mode) +{ +#if defined(MG_USE_OPEN_FILE) + + size_t size = 0; + const char *buf = NULL; + if (!conn) { + return 0; + } + + if ((mode != MG_FOPEN_MODE_NONE) && (mode != MG_FOPEN_MODE_READ)) { + return 0; + } + + if (conn->phys_ctx->callbacks.open_file) { + buf = conn->phys_ctx->callbacks.open_file(conn, path, &size); + if (buf != NULL) { + if (filep == NULL) { + /* This is a file in memory, but we cannot store the + * properties + * now. + * Called from "is_file_in_memory" function. */ + return 1; + } + + /* NOTE: override filep->size only on success. Otherwise, it + * might + * break constructs like if (!mg_stat() || !mg_fopen()) ... */ + filep->access.membuf = buf; + filep->access.fp = NULL; + + /* Size was set by the callback */ + filep->stat.size = size; + + /* Assume the data may change during runtime by setting + * last_modified = now */ + filep->stat.last_modified = time(NULL); + + filep->stat.is_directory = 0; + filep->stat.is_gzipped = 0; + } + } + + return (buf != NULL); + +#else + (void)conn; + (void)path; + (void)filep; + (void)mode; + + return 0; + +#endif +} + + +static int +is_file_in_memory(const struct mg_connection *conn, const char *path) +{ + return open_file_in_memory(conn, path, NULL, MG_FOPEN_MODE_NONE); +} + + +static int +is_file_opened(const struct mg_file_access *fileacc) +{ + if (!fileacc) { + return 0; + } + +#if defined(MG_USE_OPEN_FILE) + return (fileacc->membuf != NULL) || (fileacc->fp != NULL); +#else + return (fileacc->fp != NULL); +#endif +} + + +#if !defined(NO_FILESYSTEMS) +static int mg_stat(const struct mg_connection *conn, + const char *path, + struct mg_file_stat *filep); + + +/* mg_fopen will open a file either in memory or on the disk. + * The input parameter path is a string in UTF-8 encoding. + * The input parameter mode is MG_FOPEN_MODE_* + * On success, either fp or membuf will be set in the output + * struct file. All status members will also be set. + * The function returns 1 on success, 0 on error. */ +static int +mg_fopen(const struct mg_connection *conn, + const char *path, + int mode, + struct mg_file *filep) +{ + int found; + + if (!filep) { + return 0; + } + filep->access.fp = NULL; +#if defined(MG_USE_OPEN_FILE) + filep->access.membuf = NULL; +#endif + + if (!is_file_in_memory(conn, path)) { + + /* filep is initialized in mg_stat: all fields with memset to, + * some fields like size and modification date with values */ + found = mg_stat(conn, path, &(filep->stat)); + + if ((mode == MG_FOPEN_MODE_READ) && (!found)) { + /* file does not exist and will not be created */ + return 0; + } + +#if defined(_WIN32) + { + wchar_t wbuf[W_PATH_MAX]; + path_to_unicode(conn, path, wbuf, ARRAY_SIZE(wbuf)); + switch (mode) { + case MG_FOPEN_MODE_READ: + filep->access.fp = _wfopen(wbuf, L"rb"); + break; + case MG_FOPEN_MODE_WRITE: + filep->access.fp = _wfopen(wbuf, L"wb"); + break; + case MG_FOPEN_MODE_APPEND: + filep->access.fp = _wfopen(wbuf, L"ab"); + break; + } + } +#else + /* Linux et al already use unicode. No need to convert. */ + switch (mode) { + case MG_FOPEN_MODE_READ: + filep->access.fp = fopen(path, "r"); + break; + case MG_FOPEN_MODE_WRITE: + filep->access.fp = fopen(path, "w"); + break; + case MG_FOPEN_MODE_APPEND: + filep->access.fp = fopen(path, "a"); + break; + } + +#endif + if (!found) { + /* File did not exist before fopen was called. + * Maybe it has been created now. Get stat info + * like creation time now. */ + found = mg_stat(conn, path, &(filep->stat)); + (void)found; + } + + /* file is on disk */ + return (filep->access.fp != NULL); + + } else { +#if defined(MG_USE_OPEN_FILE) + /* is_file_in_memory returned true */ + if (open_file_in_memory(conn, path, filep, mode)) { + /* file is in memory */ + return (filep->access.membuf != NULL); + } +#endif + } + + /* Open failed */ + return 0; +} + + +/* return 0 on success, just like fclose */ +static int +mg_fclose(struct mg_file_access *fileacc) +{ + int ret = -1; + if (fileacc != NULL) { + if (fileacc->fp != NULL) { + ret = fclose(fileacc->fp); +#if defined(MG_USE_OPEN_FILE) + } else if (fileacc->membuf != NULL) { + ret = 0; +#endif + } + /* reset all members of fileacc */ + memset(fileacc, 0, sizeof(*fileacc)); + } + return ret; +} +#endif /* NO_FILESYSTEMS */ + + +static void +mg_strlcpy(register char *dst, register const char *src, size_t n) +{ + for (; *src != '\0' && n > 1; n--) { + *dst++ = *src++; + } + *dst = '\0'; +} + + +static int +lowercase(const char *s) +{ + return tolower((unsigned char)*s); +} + + +int +mg_strncasecmp(const char *s1, const char *s2, size_t len) +{ + int diff = 0; + + if (len > 0) { + do { + diff = lowercase(s1++) - lowercase(s2++); + } while (diff == 0 && s1[-1] != '\0' && --len > 0); + } + + return diff; +} + + +int +mg_strcasecmp(const char *s1, const char *s2) +{ + int diff; + + do { + diff = lowercase(s1++) - lowercase(s2++); + } while (diff == 0 && s1[-1] != '\0'); + + return diff; +} + + +static char * +mg_strndup_ctx(const char *ptr, size_t len, struct mg_context *ctx) +{ + char *p; + (void)ctx; /* Avoid Visual Studio warning if USE_SERVER_STATS is not + * defined */ + + if ((p = (char *)mg_malloc_ctx(len + 1, ctx)) != NULL) { + mg_strlcpy(p, ptr, len + 1); + } + + return p; +} + + +static char * +mg_strdup_ctx(const char *str, struct mg_context *ctx) +{ + return mg_strndup_ctx(str, strlen(str), ctx); +} + +static char * +mg_strdup(const char *str) +{ + return mg_strndup_ctx(str, strlen(str), NULL); +} + + +static const char * +mg_strcasestr(const char *big_str, const char *small_str) +{ + size_t i, big_len = strlen(big_str), small_len = strlen(small_str); + + if (big_len >= small_len) { + for (i = 0; i <= (big_len - small_len); i++) { + if (mg_strncasecmp(big_str + i, small_str, small_len) == 0) { + return big_str + i; + } + } + } + + return NULL; +} + + +/* Return null terminated string of given maximum length. + * Report errors if length is exceeded. */ +static void +mg_vsnprintf(const struct mg_connection *conn, + int *truncated, + char *buf, + size_t buflen, + const char *fmt, + va_list ap) +{ + int n, ok; + + if (buflen == 0) { + if (truncated) { + *truncated = 1; + } + return; + } + +#if defined(__clang__) +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wformat-nonliteral" +/* Using fmt as a non-literal is intended here, since it is mostly called + * indirectly by mg_snprintf */ +#endif + + n = (int)vsnprintf_impl(buf, buflen, fmt, ap); + ok = (n >= 0) && ((size_t)n < buflen); + +#if defined(__clang__) +#pragma clang diagnostic pop +#endif + + if (ok) { + if (truncated) { + *truncated = 0; + } + } else { + if (truncated) { + *truncated = 1; + } + mg_cry_internal(conn, + "truncating vsnprintf buffer: [%.*s]", + (int)((buflen > 200) ? 200 : (buflen - 1)), + buf); + n = (int)buflen - 1; + } + buf[n] = '\0'; +} + + +static void +mg_snprintf(const struct mg_connection *conn, + int *truncated, + char *buf, + size_t buflen, + const char *fmt, + ...) +{ + va_list ap; + + va_start(ap, fmt); + mg_vsnprintf(conn, truncated, buf, buflen, fmt, ap); + va_end(ap); +} + + +static int +get_option_index(const char *name) +{ + int i; + + for (i = 0; config_options[i].name != NULL; i++) { + if (strcmp(config_options[i].name, name) == 0) { + return i; + } + } + return -1; +} + + +const char * +mg_get_option(const struct mg_context *ctx, const char *name) +{ + int i; + if ((i = get_option_index(name)) == -1) { + return NULL; + } else if (!ctx || ctx->dd.config[i] == NULL) { + return ""; + } else { + return ctx->dd.config[i]; + } +} + +#define mg_get_option DO_NOT_USE_THIS_FUNCTION_INTERNALLY__access_directly + +struct mg_context * +mg_get_context(const struct mg_connection *conn) +{ + return (conn == NULL) ? (struct mg_context *)NULL : (conn->phys_ctx); +} + + +void * +mg_get_user_data(const struct mg_context *ctx) +{ + return (ctx == NULL) ? NULL : ctx->user_data; +} + + +void * +mg_get_thread_pointer(const struct mg_connection *conn) +{ + /* both methods should return the same pointer */ + if (conn) { + /* quick access, in case conn is known */ + return conn->tls_user_ptr; + } else { + /* otherwise get pointer from thread local storage (TLS) */ + struct mg_workerTLS *tls = + (struct mg_workerTLS *)pthread_getspecific(sTlsKey); + return tls->user_ptr; + } +} + + +void +mg_set_user_connection_data(struct mg_connection *conn, void *data) +{ + if (conn != NULL) { + conn->request_info.conn_data = data; + } +} + + +void * +mg_get_user_connection_data(const struct mg_connection *conn) +{ + if (conn != NULL) { + return conn->request_info.conn_data; + } + return NULL; +} + + +#if defined(MG_LEGACY_INTERFACE) +/* Deprecated: Use mg_get_server_ports instead. */ +size_t +mg_get_ports(const struct mg_context *ctx, size_t size, int *ports, int *ssl) +{ + size_t i; + if (!ctx) { + return 0; + } + for (i = 0; i < size && i < ctx->num_listening_sockets; i++) { + ssl[i] = ctx->listening_sockets[i].is_ssl; + ports[i] = +#if defined(USE_IPV6) + (ctx->listening_sockets[i].lsa.sa.sa_family == AF_INET6) + ? ntohs(ctx->listening_sockets[i].lsa.sin6.sin6_port) + : +#endif + ntohs(ctx->listening_sockets[i].lsa.sin.sin_port); + } + return i; +} +#endif + + +int +mg_get_server_ports(const struct mg_context *ctx, + int size, + struct mg_server_port *ports) +{ + int i, cnt = 0; + + if (size <= 0) { + return -1; + } + memset(ports, 0, sizeof(*ports) * (size_t)size); + if (!ctx) { + return -1; + } + if (!ctx->listening_sockets) { + return -1; + } + + for (i = 0; (i < size) && (i < (int)ctx->num_listening_sockets); i++) { + + ports[cnt].port = +#if defined(USE_IPV6) + (ctx->listening_sockets[i].lsa.sa.sa_family == AF_INET6) + ? ntohs(ctx->listening_sockets[i].lsa.sin6.sin6_port) + : +#endif + ntohs(ctx->listening_sockets[i].lsa.sin.sin_port); + ports[cnt].is_ssl = ctx->listening_sockets[i].is_ssl; + ports[cnt].is_redirect = ctx->listening_sockets[i].ssl_redir; + + if (ctx->listening_sockets[i].lsa.sa.sa_family == AF_INET) { + /* IPv4 */ + ports[cnt].protocol = 1; + cnt++; + } else if (ctx->listening_sockets[i].lsa.sa.sa_family == AF_INET6) { + /* IPv6 */ + ports[cnt].protocol = 3; + cnt++; + } + } + + return cnt; +} + + +static void +sockaddr_to_string(char *buf, size_t len, const union usa *usa) +{ + buf[0] = '\0'; + + if (!usa) { + return; + } + + if (usa->sa.sa_family == AF_INET) { + getnameinfo(&usa->sa, + sizeof(usa->sin), + buf, + (unsigned)len, + NULL, + 0, + NI_NUMERICHOST); + } +#if defined(USE_IPV6) + else if (usa->sa.sa_family == AF_INET6) { + getnameinfo(&usa->sa, + sizeof(usa->sin6), + buf, + (unsigned)len, + NULL, + 0, + NI_NUMERICHOST); + } +#endif +} + + +/* Convert time_t to a string. According to RFC2616, Sec 14.18, this must be + * included in all responses other than 100, 101, 5xx. */ +static void +gmt_time_string(char *buf, size_t buf_len, time_t *t) +{ +#if !defined(REENTRANT_TIME) + struct tm *tm; + + tm = ((t != NULL) ? gmtime(t) : NULL); + if (tm != NULL) { +#else + struct tm _tm; + struct tm *tm = &_tm; + + if (t != NULL) { + gmtime_r(t, tm); +#endif + strftime(buf, buf_len, "%a, %d %b %Y %H:%M:%S GMT", tm); + } else { + mg_strlcpy(buf, "Thu, 01 Jan 1970 00:00:00 GMT", buf_len); + buf[buf_len - 1] = '\0'; + } +} + + +/* difftime for struct timespec. Return value is in seconds. */ +static double +mg_difftimespec(const struct timespec *ts_now, const struct timespec *ts_before) +{ + return (double)(ts_now->tv_nsec - ts_before->tv_nsec) * 1.0E-9 + + (double)(ts_now->tv_sec - ts_before->tv_sec); +} + + +#if defined(MG_EXTERNAL_FUNCTION_mg_cry_internal_impl) +static void mg_cry_internal_impl(const struct mg_connection *conn, + const char *func, + unsigned line, + const char *fmt, + va_list ap); +#include "external_mg_cry_internal_impl.inl" +#elif !defined(NO_FILESYSTEMS) + +/* Print error message to the opened error log stream. */ +static void +mg_cry_internal_impl(const struct mg_connection *conn, + const char *func, + unsigned line, + const char *fmt, + va_list ap) +{ + char buf[MG_BUF_LEN], src_addr[IP_ADDR_STR_LEN]; + struct mg_file fi; + time_t timestamp; + + /* Unused, in the RELEASE build */ + (void)func; + (void)line; + +#if defined(GCC_DIAGNOSTIC) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wformat-nonliteral" +#endif + + IGNORE_UNUSED_RESULT(vsnprintf_impl(buf, sizeof(buf), fmt, ap)); + +#if defined(GCC_DIAGNOSTIC) +#pragma GCC diagnostic pop +#endif + + buf[sizeof(buf) - 1] = 0; + + DEBUG_TRACE("mg_cry called from %s:%u: %s", func, line, buf); + + if (!conn) { + puts(buf); + return; + } + + /* Do not lock when getting the callback value, here and below. + * I suppose this is fine, since function cannot disappear in the + * same way string option can. */ + if ((conn->phys_ctx->callbacks.log_message == NULL) + || (conn->phys_ctx->callbacks.log_message(conn, buf) == 0)) { + + if (conn->dom_ctx->config[ERROR_LOG_FILE] != NULL) { + if (mg_fopen(conn, + conn->dom_ctx->config[ERROR_LOG_FILE], + MG_FOPEN_MODE_APPEND, + &fi) + == 0) { + fi.access.fp = NULL; + } + } else { + fi.access.fp = NULL; + } + + if (fi.access.fp != NULL) { + flockfile(fi.access.fp); + timestamp = time(NULL); + + sockaddr_to_string(src_addr, sizeof(src_addr), &conn->client.rsa); + fprintf(fi.access.fp, + "[%010lu] [error] [client %s] ", + (unsigned long)timestamp, + src_addr); + + if (conn->request_info.request_method != NULL) { + fprintf(fi.access.fp, + "%s %s: ", + conn->request_info.request_method, + conn->request_info.request_uri + ? conn->request_info.request_uri + : ""); + } + + fprintf(fi.access.fp, "%s", buf); + fputc('\n', fi.access.fp); + fflush(fi.access.fp); + funlockfile(fi.access.fp); + (void)mg_fclose(&fi.access); /* Ignore errors. We can't call + * mg_cry here anyway ;-) */ + } + } +} +#else +#error Must either enable filesystems or provide a custom mg_cry_internal_impl implementation +#endif /* Externally provided function */ + + +/* Construct fake connection structure. Used for logging, if connection + * is not applicable at the moment of logging. */ +static struct mg_connection * +fake_connection(struct mg_connection *fc, struct mg_context *ctx) +{ + static const struct mg_connection conn_zero = {0}; + *fc = conn_zero; + fc->phys_ctx = ctx; + fc->dom_ctx = &(ctx->dd); + return fc; +} + + +static void +mg_cry_internal_wrap(const struct mg_connection *conn, + struct mg_context *ctx, + const char *func, + unsigned line, + const char *fmt, + ...) +{ + va_list ap; + va_start(ap, fmt); + if (!conn && ctx) { + struct mg_connection fc; + mg_cry_internal_impl(fake_connection(&fc, ctx), func, line, fmt, ap); + } else { + mg_cry_internal_impl(conn, func, line, fmt, ap); + } + va_end(ap); +} + + +void +mg_cry(const struct mg_connection *conn, const char *fmt, ...) +{ + va_list ap; + va_start(ap, fmt); + mg_cry_internal_impl(conn, "user", 0, fmt, ap); + va_end(ap); +} + + +#define mg_cry DO_NOT_USE_THIS_FUNCTION__USE_mg_cry_internal + + +const char * +mg_version(void) +{ + return CIVETWEB_VERSION; +} + + +const struct mg_request_info * +mg_get_request_info(const struct mg_connection *conn) +{ + if (!conn) { + return NULL; + } +#if defined(MG_ALLOW_USING_GET_REQUEST_INFO_FOR_RESPONSE) + if (conn->connection_type == CONNECTION_TYPE_RESPONSE) { + char txt[16]; + struct mg_workerTLS *tls = + (struct mg_workerTLS *)pthread_getspecific(sTlsKey); + + sprintf(txt, "%03i", conn->response_info.status_code); + if (strlen(txt) == 3) { + memcpy(tls->txtbuf, txt, 4); + } else { + strcpy(tls->txtbuf, "ERR"); + } + + ((struct mg_connection *)conn)->request_info.local_uri = + ((struct mg_connection *)conn)->request_info.request_uri = + tls->txtbuf; /* use thread safe buffer */ + + ((struct mg_connection *)conn)->request_info.num_headers = + conn->response_info.num_headers; + memcpy(((struct mg_connection *)conn)->request_info.http_headers, + conn->response_info.http_headers, + sizeof(conn->response_info.http_headers)); + } else +#endif + if (conn->connection_type != CONNECTION_TYPE_REQUEST) { + return NULL; + } + return &conn->request_info; +} + + +const struct mg_response_info * +mg_get_response_info(const struct mg_connection *conn) +{ + if (!conn) { + return NULL; + } + if (conn->connection_type != CONNECTION_TYPE_RESPONSE) { + return NULL; + } + return &conn->response_info; +} + + +static const char * +get_proto_name(const struct mg_connection *conn) +{ +#if defined(__clang__) +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunreachable-code" +/* Depending on USE_WEBSOCKET and NO_SSL, some oft the protocols might be + * not supported. Clang raises an "unreachable code" warning for parts of ?: + * unreachable, but splitting into four different #ifdef clauses here is more + * complicated. + */ +#endif + + const struct mg_request_info *ri = &conn->request_info; + + const char *proto = + (is_websocket_protocol(conn) ? (ri->is_ssl ? "wss" : "ws") + : (ri->is_ssl ? "https" : "http")); + + return proto; + +#if defined(__clang__) +#pragma clang diagnostic pop +#endif +} + + +int +mg_get_request_link(const struct mg_connection *conn, char *buf, size_t buflen) +{ + if ((buflen < 1) || (buf == 0) || (conn == 0)) { + return -1; + } else { + + int truncated = 0; + const struct mg_request_info *ri = &conn->request_info; + + const char *proto = get_proto_name(conn); + + if (ri->local_uri == NULL) { + return -1; + } + + if ((ri->request_uri != NULL) + && (0 != strcmp(ri->local_uri, ri->request_uri))) { + /* The request uri is different from the local uri. + * This is usually if an absolute URI, including server + * name has been provided. */ + mg_snprintf(conn, + &truncated, + buf, + buflen, + "%s://%s", + proto, + ri->request_uri); + if (truncated) { + return -1; + } + return 0; + + } else { + + /* The common case is a relative URI, so we have to + * construct an absolute URI from server name and port */ + +#if defined(USE_IPV6) + int is_ipv6 = (conn->client.lsa.sa.sa_family == AF_INET6); + int port = is_ipv6 ? htons(conn->client.lsa.sin6.sin6_port) + : htons(conn->client.lsa.sin.sin_port); +#else + int port = htons(conn->client.lsa.sin.sin_port); +#endif + int def_port = ri->is_ssl ? 443 : 80; + int auth_domain_check_enabled = + conn->dom_ctx->config[ENABLE_AUTH_DOMAIN_CHECK] + && (!mg_strcasecmp( + conn->dom_ctx->config[ENABLE_AUTH_DOMAIN_CHECK], "yes")); + const char *server_domain = + conn->dom_ctx->config[AUTHENTICATION_DOMAIN]; + + char portstr[16]; + char server_ip[48]; + + if (port != def_port) { + sprintf(portstr, ":%u", (unsigned)port); + } else { + portstr[0] = 0; + } + + if (!auth_domain_check_enabled || !server_domain) { + + sockaddr_to_string(server_ip, + sizeof(server_ip), + &conn->client.lsa); + + server_domain = server_ip; + } + + mg_snprintf(conn, + &truncated, + buf, + buflen, + "%s://%s%s%s", + proto, + server_domain, + portstr, + ri->local_uri); + if (truncated) { + return -1; + } + return 0; + } + } +} + +/* Skip the characters until one of the delimiters characters found. + * 0-terminate resulting word. Skip the delimiter and following whitespaces. + * Advance pointer to buffer to the next word. Return found 0-terminated + * word. + * Delimiters can be quoted with quotechar. */ +static char * +skip_quoted(char **buf, + const char *delimiters, + const char *whitespace, + char quotechar) +{ + char *p, *begin_word, *end_word, *end_whitespace; + + begin_word = *buf; + end_word = begin_word + strcspn(begin_word, delimiters); + + /* Check for quotechar */ + if (end_word > begin_word) { + p = end_word - 1; + while (*p == quotechar) { + /* While the delimiter is quoted, look for the next delimiter. + */ + /* This happens, e.g., in calls from parse_auth_header, + * if the user name contains a " character. */ + + /* If there is anything beyond end_word, copy it. */ + if (*end_word != '\0') { + size_t end_off = strcspn(end_word + 1, delimiters); + memmove(p, end_word, end_off + 1); + p += end_off; /* p must correspond to end_word - 1 */ + end_word += end_off + 1; + } else { + *p = '\0'; + break; + } + } + for (p++; p < end_word; p++) { + *p = '\0'; + } + } + + if (*end_word == '\0') { + *buf = end_word; + } else { + +#if defined(GCC_DIAGNOSTIC) +/* Disable spurious conversion warning for GCC */ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wsign-conversion" +#endif /* defined(GCC_DIAGNOSTIC) */ + + end_whitespace = end_word + strspn(&end_word[1], whitespace) + 1; + +#if defined(GCC_DIAGNOSTIC) +#pragma GCC diagnostic pop +#endif /* defined(GCC_DIAGNOSTIC) */ + + for (p = end_word; p < end_whitespace; p++) { + *p = '\0'; + } + + *buf = end_whitespace; + } + + return begin_word; +} + + +/* Return HTTP header value, or NULL if not found. */ +static const char * +get_header(const struct mg_header *hdr, int num_hdr, const char *name) +{ + int i; + for (i = 0; i < num_hdr; i++) { + if (!mg_strcasecmp(name, hdr[i].name)) { + return hdr[i].value; + } + } + + return NULL; +} + + +#if defined(USE_WEBSOCKET) +/* Retrieve requested HTTP header multiple values, and return the number of + * found occurrences */ +static int +get_req_headers(const struct mg_request_info *ri, + const char *name, + const char **output, + int output_max_size) +{ + int i; + int cnt = 0; + if (ri) { + for (i = 0; i < ri->num_headers && cnt < output_max_size; i++) { + if (!mg_strcasecmp(name, ri->http_headers[i].name)) { + output[cnt++] = ri->http_headers[i].value; + } + } + } + return cnt; +} +#endif + + +const char * +mg_get_header(const struct mg_connection *conn, const char *name) +{ + if (!conn) { + return NULL; + } + + if (conn->connection_type == CONNECTION_TYPE_REQUEST) { + return get_header(conn->request_info.http_headers, + conn->request_info.num_headers, + name); + } + if (conn->connection_type == CONNECTION_TYPE_RESPONSE) { + return get_header(conn->response_info.http_headers, + conn->response_info.num_headers, + name); + } + return NULL; +} + + +static const char * +get_http_version(const struct mg_connection *conn) +{ + if (!conn) { + return NULL; + } + + if (conn->connection_type == CONNECTION_TYPE_REQUEST) { + return conn->request_info.http_version; + } + if (conn->connection_type == CONNECTION_TYPE_RESPONSE) { + return conn->response_info.http_version; + } + return NULL; +} + + +/* A helper function for traversing a comma separated list of values. + * It returns a list pointer shifted to the next value, or NULL if the end + * of the list found. + * Value is stored in val vector. If value has form "x=y", then eq_val + * vector is initialized to point to the "y" part, and val vector length + * is adjusted to point only to "x". */ +static const char * +next_option(const char *list, struct vec *val, struct vec *eq_val) +{ + int end; + +reparse: + if (val == NULL || list == NULL || *list == '\0') { + /* End of the list */ + return NULL; + } + + /* Skip over leading LWS */ + while (*list == ' ' || *list == '\t') + list++; + + val->ptr = list; + if ((list = strchr(val->ptr, ',')) != NULL) { + /* Comma found. Store length and shift the list ptr */ + val->len = ((size_t)(list - val->ptr)); + list++; + } else { + /* This value is the last one */ + list = val->ptr + strlen(val->ptr); + val->len = ((size_t)(list - val->ptr)); + } + + /* Adjust length for trailing LWS */ + end = (int)val->len - 1; + while (end >= 0 && ((val->ptr[end] == ' ') || (val->ptr[end] == '\t'))) + end--; + val->len = (size_t)(end) + (size_t)(1); + + if (val->len == 0) { + /* Ignore any empty entries. */ + goto reparse; + } + + if (eq_val != NULL) { + /* Value has form "x=y", adjust pointers and lengths + * so that val points to "x", and eq_val points to "y". */ + eq_val->len = 0; + eq_val->ptr = (const char *)memchr(val->ptr, '=', val->len); + if (eq_val->ptr != NULL) { + eq_val->ptr++; /* Skip over '=' character */ + eq_val->len = ((size_t)(val->ptr - eq_val->ptr)) + val->len; + val->len = ((size_t)(eq_val->ptr - val->ptr)) - 1; + } + } + + return list; +} + + +/* A helper function for checking if a comma separated list of values + * contains + * the given option (case insensitvely). + * 'header' can be NULL, in which case false is returned. */ +static int +header_has_option(const char *header, const char *option) +{ + struct vec opt_vec; + struct vec eq_vec; + + DEBUG_ASSERT(option != NULL); + DEBUG_ASSERT(option[0] != '\0'); + + while ((header = next_option(header, &opt_vec, &eq_vec)) != NULL) { + if (mg_strncasecmp(option, opt_vec.ptr, opt_vec.len) == 0) + return 1; + } + + return 0; +} + + +/* Perform case-insensitive match of string against pattern */ +static ptrdiff_t +match_prefix(const char *pattern, size_t pattern_len, const char *str) +{ + const char *or_str; + ptrdiff_t i, j, len, res; + + if ((or_str = (const char *)memchr(pattern, '|', pattern_len)) != NULL) { + res = match_prefix(pattern, (size_t)(or_str - pattern), str); + return (res > 0) ? res + : match_prefix(or_str + 1, + (size_t)((pattern + pattern_len) + - (or_str + 1)), + str); + } + + for (i = 0, j = 0; (i < (ptrdiff_t)pattern_len); i++, j++) { + if ((pattern[i] == '?') && (str[j] != '\0')) { + continue; + } else if (pattern[i] == '$') { + return (str[j] == '\0') ? j : -1; + } else if (pattern[i] == '*') { + i++; + if (pattern[i] == '*') { + i++; + len = strlen(str + j); + } else { + len = strcspn(str + j, "/"); + } + if (i == (ptrdiff_t)pattern_len) { + return j + len; + } + do { + res = match_prefix(pattern + i, pattern_len - i, str + j + len); + } while (res == -1 && len-- > 0); + return (res == -1) ? -1 : j + res + len; + } else if (lowercase(&pattern[i]) != lowercase(&str[j])) { + return -1; + } + } + return (ptrdiff_t)j; +} + + +/* HTTP 1.1 assumes keep alive if "Connection:" header is not set + * This function must tolerate situations when connection info is not + * set up, for example if request parsing failed. */ +static int +should_keep_alive(const struct mg_connection *conn) +{ + const char *http_version; + const char *header; + + /* First satisfy needs of the server */ + if ((conn == NULL) || conn->must_close) { + /* Close, if civetweb framework needs to close */ + return 0; + } + + if (mg_strcasecmp(conn->dom_ctx->config[ENABLE_KEEP_ALIVE], "yes") != 0) { + /* Close, if keep alive is not enabled */ + return 0; + } + + /* Check explicit wish of the client */ + header = mg_get_header(conn, "Connection"); + if (header) { + /* If there is a connection header from the client, obey */ + if (header_has_option(header, "keep-alive")) { + return 1; + } + return 0; + } + + /* Use default of the standard */ + http_version = get_http_version(conn); + if (http_version && (0 == strcmp(http_version, "1.1"))) { + /* HTTP 1.1 default is keep alive */ + return 1; + } + + /* HTTP 1.0 (and earlier) default is to close the connection */ + return 0; +} + + +static int +should_decode_url(const struct mg_connection *conn) +{ + if (!conn || !conn->dom_ctx) { + return 0; + } + + return (mg_strcasecmp(conn->dom_ctx->config[DECODE_URL], "yes") == 0); +} + + +static const char * +suggest_connection_header(const struct mg_connection *conn) +{ + return should_keep_alive(conn) ? "keep-alive" : "close"; +} + + +static int +send_no_cache_header(struct mg_connection *conn) +{ + /* Send all current and obsolete cache opt-out directives. */ + return mg_printf(conn, + "Cache-Control: no-cache, no-store, " + "must-revalidate, private, max-age=0\r\n" + "Pragma: no-cache\r\n" + "Expires: 0\r\n"); +} + + +static int +send_static_cache_header(struct mg_connection *conn) +{ +#if !defined(NO_CACHING) + /* Read the server config to check how long a file may be cached. + * The configuration is in seconds. */ + int max_age = atoi(conn->dom_ctx->config[STATIC_FILE_MAX_AGE]); + if (max_age <= 0) { + /* 0 means "do not cache". All values <0 are reserved + * and may be used differently in the future. */ + /* If a file should not be cached, do not only send + * max-age=0, but also pragmas and Expires headers. */ + return send_no_cache_header(conn); + } + + /* Use "Cache-Control: max-age" instead of "Expires" header. + * Reason: see https://www.mnot.net/blog/2007/05/15/expires_max-age */ + /* See also https://www.mnot.net/cache_docs/ */ + /* According to RFC 2616, Section 14.21, caching times should not exceed + * one year. A year with 365 days corresponds to 31536000 seconds, a + * leap + * year to 31622400 seconds. For the moment, we just send whatever has + * been configured, still the behavior for >1 year should be considered + * as undefined. */ + return mg_printf(conn, "Cache-Control: max-age=%u\r\n", (unsigned)max_age); +#else /* NO_CACHING */ + return send_no_cache_header(conn); +#endif /* !NO_CACHING */ +} + + +static int +send_additional_header(struct mg_connection *conn) +{ + int i = 0; + const char *header = conn->dom_ctx->config[ADDITIONAL_HEADER]; + +#if !defined(NO_SSL) + if (conn->dom_ctx->config[STRICT_HTTPS_MAX_AGE]) { + int max_age = atoi(conn->dom_ctx->config[STRICT_HTTPS_MAX_AGE]); + if (max_age >= 0) { + i += mg_printf(conn, + "Strict-Transport-Security: max-age=%u\r\n", + (unsigned)max_age); + } + } +#endif + + if (header && header[0]) { + i += mg_printf(conn, "%s\r\n", header); + } + + return i; +} + + +#if !defined(NO_FILESYSTEMS) +static void handle_file_based_request(struct mg_connection *conn, + const char *path, + struct mg_file *filep); +#endif /* NO_FILESYSTEMS */ + + +const char * +mg_get_response_code_text(const struct mg_connection *conn, int response_code) +{ + /* See IANA HTTP status code assignment: + * http://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml + */ + + switch (response_code) { + /* RFC2616 Section 10.1 - Informational 1xx */ + case 100: + return "Continue"; /* RFC2616 Section 10.1.1 */ + case 101: + return "Switching Protocols"; /* RFC2616 Section 10.1.2 */ + case 102: + return "Processing"; /* RFC2518 Section 10.1 */ + + /* RFC2616 Section 10.2 - Successful 2xx */ + case 200: + return "OK"; /* RFC2616 Section 10.2.1 */ + case 201: + return "Created"; /* RFC2616 Section 10.2.2 */ + case 202: + return "Accepted"; /* RFC2616 Section 10.2.3 */ + case 203: + return "Non-Authoritative Information"; /* RFC2616 Section 10.2.4 */ + case 204: + return "No Content"; /* RFC2616 Section 10.2.5 */ + case 205: + return "Reset Content"; /* RFC2616 Section 10.2.6 */ + case 206: + return "Partial Content"; /* RFC2616 Section 10.2.7 */ + case 207: + return "Multi-Status"; /* RFC2518 Section 10.2, RFC4918 Section 11.1 + */ + case 208: + return "Already Reported"; /* RFC5842 Section 7.1 */ + + case 226: + return "IM used"; /* RFC3229 Section 10.4.1 */ + + /* RFC2616 Section 10.3 - Redirection 3xx */ + case 300: + return "Multiple Choices"; /* RFC2616 Section 10.3.1 */ + case 301: + return "Moved Permanently"; /* RFC2616 Section 10.3.2 */ + case 302: + return "Found"; /* RFC2616 Section 10.3.3 */ + case 303: + return "See Other"; /* RFC2616 Section 10.3.4 */ + case 304: + return "Not Modified"; /* RFC2616 Section 10.3.5 */ + case 305: + return "Use Proxy"; /* RFC2616 Section 10.3.6 */ + case 307: + return "Temporary Redirect"; /* RFC2616 Section 10.3.8 */ + case 308: + return "Permanent Redirect"; /* RFC7238 Section 3 */ + + /* RFC2616 Section 10.4 - Client Error 4xx */ + case 400: + return "Bad Request"; /* RFC2616 Section 10.4.1 */ + case 401: + return "Unauthorized"; /* RFC2616 Section 10.4.2 */ + case 402: + return "Payment Required"; /* RFC2616 Section 10.4.3 */ + case 403: + return "Forbidden"; /* RFC2616 Section 10.4.4 */ + case 404: + return "Not Found"; /* RFC2616 Section 10.4.5 */ + case 405: + return "Method Not Allowed"; /* RFC2616 Section 10.4.6 */ + case 406: + return "Not Acceptable"; /* RFC2616 Section 10.4.7 */ + case 407: + return "Proxy Authentication Required"; /* RFC2616 Section 10.4.8 */ + case 408: + return "Request Time-out"; /* RFC2616 Section 10.4.9 */ + case 409: + return "Conflict"; /* RFC2616 Section 10.4.10 */ + case 410: + return "Gone"; /* RFC2616 Section 10.4.11 */ + case 411: + return "Length Required"; /* RFC2616 Section 10.4.12 */ + case 412: + return "Precondition Failed"; /* RFC2616 Section 10.4.13 */ + case 413: + return "Request Entity Too Large"; /* RFC2616 Section 10.4.14 */ + case 414: + return "Request-URI Too Large"; /* RFC2616 Section 10.4.15 */ + case 415: + return "Unsupported Media Type"; /* RFC2616 Section 10.4.16 */ + case 416: + return "Requested range not satisfiable"; /* RFC2616 Section 10.4.17 + */ + case 417: + return "Expectation Failed"; /* RFC2616 Section 10.4.18 */ + + case 421: + return "Misdirected Request"; /* RFC7540 Section 9.1.2 */ + case 422: + return "Unproccessable entity"; /* RFC2518 Section 10.3, RFC4918 + * Section 11.2 */ + case 423: + return "Locked"; /* RFC2518 Section 10.4, RFC4918 Section 11.3 */ + case 424: + return "Failed Dependency"; /* RFC2518 Section 10.5, RFC4918 + * Section 11.4 */ + + case 426: + return "Upgrade Required"; /* RFC 2817 Section 4 */ + + case 428: + return "Precondition Required"; /* RFC 6585, Section 3 */ + case 429: + return "Too Many Requests"; /* RFC 6585, Section 4 */ + + case 431: + return "Request Header Fields Too Large"; /* RFC 6585, Section 5 */ + + case 451: + return "Unavailable For Legal Reasons"; /* draft-tbray-http-legally-restricted-status-05, + * Section 3 */ + + /* RFC2616 Section 10.5 - Server Error 5xx */ + case 500: + return "Internal Server Error"; /* RFC2616 Section 10.5.1 */ + case 501: + return "Not Implemented"; /* RFC2616 Section 10.5.2 */ + case 502: + return "Bad Gateway"; /* RFC2616 Section 10.5.3 */ + case 503: + return "Service Unavailable"; /* RFC2616 Section 10.5.4 */ + case 504: + return "Gateway Time-out"; /* RFC2616 Section 10.5.5 */ + case 505: + return "HTTP Version not supported"; /* RFC2616 Section 10.5.6 */ + case 506: + return "Variant Also Negotiates"; /* RFC 2295, Section 8.1 */ + case 507: + return "Insufficient Storage"; /* RFC2518 Section 10.6, RFC4918 + * Section 11.5 */ + case 508: + return "Loop Detected"; /* RFC5842 Section 7.1 */ + + case 510: + return "Not Extended"; /* RFC 2774, Section 7 */ + case 511: + return "Network Authentication Required"; /* RFC 6585, Section 6 */ + + /* Other status codes, not shown in the IANA HTTP status code + * assignment. + * E.g., "de facto" standards due to common use, ... */ + case 418: + return "I am a teapot"; /* RFC2324 Section 2.3.2 */ + case 419: + return "Authentication Timeout"; /* common use */ + case 420: + return "Enhance Your Calm"; /* common use */ + case 440: + return "Login Timeout"; /* common use */ + case 509: + return "Bandwidth Limit Exceeded"; /* common use */ + + default: + /* This error code is unknown. This should not happen. */ + if (conn) { + mg_cry_internal(conn, + "Unknown HTTP response code: %u", + response_code); + } + + /* Return at least a category according to RFC 2616 Section 10. */ + if (response_code >= 100 && response_code < 200) { + /* Unknown informational status code */ + return "Information"; + } + if (response_code >= 200 && response_code < 300) { + /* Unknown success code */ + return "Success"; + } + if (response_code >= 300 && response_code < 400) { + /* Unknown redirection code */ + return "Redirection"; + } + if (response_code >= 400 && response_code < 500) { + /* Unknown request error code */ + return "Client Error"; + } + if (response_code >= 500 && response_code < 600) { + /* Unknown server error code */ + return "Server Error"; + } + + /* Response code not even within reasonable range */ + return ""; + } +} + + +static int +mg_send_http_error_impl(struct mg_connection *conn, + int status, + const char *fmt, + va_list args) +{ + char errmsg_buf[MG_BUF_LEN]; + va_list ap; + int has_body; + char date[64]; + time_t curtime = time(NULL); +#if !defined(NO_FILESYSTEMS) + char path_buf[PATH_MAX]; + int len, i, page_handler_found, scope, truncated; + const char *error_handler = NULL; + struct mg_file error_page_file = STRUCT_FILE_INITIALIZER; + const char *error_page_file_ext, *tstr; +#endif /* NO_FILESYSTEMS */ + int handled_by_callback = 0; + + const char *status_text = mg_get_response_code_text(conn, status); + + if ((conn == NULL) || (fmt == NULL)) { + return -2; + } + + /* Set status (for log) */ + conn->status_code = status; + + /* Errors 1xx, 204 and 304 MUST NOT send a body */ + has_body = ((status > 199) && (status != 204) && (status != 304)); + + /* Prepare message in buf, if required */ + if (has_body + || (!conn->in_error_handler + && (conn->phys_ctx->callbacks.http_error != NULL))) { + /* Store error message in errmsg_buf */ + va_copy(ap, args); + mg_vsnprintf(conn, NULL, errmsg_buf, sizeof(errmsg_buf), fmt, ap); + va_end(ap); + /* In a debug build, print all html errors */ + DEBUG_TRACE("Error %i - [%s]", status, errmsg_buf); + } + + /* If there is a http_error callback, call it. + * But don't do it recursively, if callback calls mg_send_http_error again. + */ + if (!conn->in_error_handler + && (conn->phys_ctx->callbacks.http_error != NULL)) { + /* Mark in_error_handler to avoid recursion and call user callback. */ + conn->in_error_handler = 1; + handled_by_callback = + (conn->phys_ctx->callbacks.http_error(conn, status, errmsg_buf) + == 0); + conn->in_error_handler = 0; + } + + if (!handled_by_callback) { + /* Check for recursion */ + if (conn->in_error_handler) { + DEBUG_TRACE( + "Recursion when handling error %u - fall back to default", + status); +#if !defined(NO_FILESYSTEMS) + } else { + /* Send user defined error pages, if defined */ + error_handler = conn->dom_ctx->config[ERROR_PAGES]; + error_page_file_ext = conn->dom_ctx->config[INDEX_FILES]; + page_handler_found = 0; + + if (error_handler != NULL) { + for (scope = 1; (scope <= 3) && !page_handler_found; scope++) { + switch (scope) { + case 1: /* Handler for specific error, e.g. 404 error */ + mg_snprintf(conn, + &truncated, + path_buf, + sizeof(path_buf) - 32, + "%serror%03u.", + error_handler, + status); + break; + case 2: /* Handler for error group, e.g., 5xx error + * handler + * for all server errors (500-599) */ + mg_snprintf(conn, + &truncated, + path_buf, + sizeof(path_buf) - 32, + "%serror%01uxx.", + error_handler, + status / 100); + break; + default: /* Handler for all errors */ + mg_snprintf(conn, + &truncated, + path_buf, + sizeof(path_buf) - 32, + "%serror.", + error_handler); + break; + } + + /* String truncation in buf may only occur if + * error_handler is too long. This string is + * from the config, not from a client. */ + (void)truncated; + + len = (int)strlen(path_buf); + + tstr = strchr(error_page_file_ext, '.'); + + while (tstr) { + for (i = 1; + (i < 32) && (tstr[i] != 0) && (tstr[i] != ','); + i++) { + /* buffer overrun is not possible here, since + * (i < 32) && (len < sizeof(path_buf) - 32) + * ==> (i + len) < sizeof(path_buf) */ + path_buf[len + i - 1] = tstr[i]; + } + /* buffer overrun is not possible here, since + * (i <= 32) && (len < sizeof(path_buf) - 32) + * ==> (i + len) <= sizeof(path_buf) */ + path_buf[len + i - 1] = 0; + + if (mg_stat(conn, path_buf, &error_page_file.stat)) { + DEBUG_TRACE("Check error page %s - found", + path_buf); + page_handler_found = 1; + break; + } + DEBUG_TRACE("Check error page %s - not found", + path_buf); + + tstr = strchr(tstr + i, '.'); + } + } + } + + if (page_handler_found) { + conn->in_error_handler = 1; + handle_file_based_request(conn, path_buf, &error_page_file); + conn->in_error_handler = 0; + return 0; + } +#endif /* NO_FILESYSTEMS */ + } + + /* No custom error page. Send default error page. */ + gmt_time_string(date, sizeof(date), &curtime); + + conn->must_close = 1; + mg_printf(conn, "HTTP/1.1 %d %s\r\n", status, status_text); + send_no_cache_header(conn); + send_additional_header(conn); + if (has_body) { + mg_printf(conn, + "%s", + "Content-Type: text/plain; charset=utf-8\r\n"); + } + mg_printf(conn, + "Date: %s\r\n" + "Connection: close\r\n\r\n", + date); + + /* HTTP responses 1xx, 204 and 304 MUST NOT send a body */ + if (has_body) { + /* For other errors, send a generic error message. */ + mg_printf(conn, "Error %d: %s\n", status, status_text); + mg_write(conn, errmsg_buf, strlen(errmsg_buf)); + + } else { + /* No body allowed. Close the connection. */ + DEBUG_TRACE("Error %i", status); + } + } + return 0; +} + + +int +mg_send_http_error(struct mg_connection *conn, int status, const char *fmt, ...) +{ + va_list ap; + int ret; + + va_start(ap, fmt); + ret = mg_send_http_error_impl(conn, status, fmt, ap); + va_end(ap); + + return ret; +} + + +int +mg_send_http_ok(struct mg_connection *conn, + const char *mime_type, + long long content_length) +{ + char date[64]; + time_t curtime = time(NULL); + + if ((mime_type == NULL) || (*mime_type == 0)) { + /* Parameter error */ + return -2; + } + + gmt_time_string(date, sizeof(date), &curtime); + + mg_printf(conn, + "HTTP/1.1 200 OK\r\n" + "Content-Type: %s\r\n" + "Date: %s\r\n" + "Connection: %s\r\n", + mime_type, + date, + suggest_connection_header(conn)); + + send_no_cache_header(conn); + send_additional_header(conn); + if (content_length < 0) { + mg_printf(conn, "Transfer-Encoding: chunked\r\n\r\n"); + } else { + mg_printf(conn, + "Content-Length: %" UINT64_FMT "\r\n\r\n", + (uint64_t)content_length); + } + + return 0; +} + + +int +mg_send_http_redirect(struct mg_connection *conn, + const char *target_url, + int redirect_code) +{ + /* Send a 30x redirect response. + * + * Redirect types (status codes): + * + * Status | Perm/Temp | Method | Version + * 301 | permanent | POST->GET undefined | HTTP/1.0 + * 302 | temporary | POST->GET undefined | HTTP/1.0 + * 303 | temporary | always use GET | HTTP/1.1 + * 307 | temporary | always keep method | HTTP/1.1 + * 308 | permanent | always keep method | HTTP/1.1 + */ + const char *redirect_text; + int ret; + size_t content_len = 0; + char reply[MG_BUF_LEN]; + + /* In case redirect_code=0, use 307. */ + if (redirect_code == 0) { + redirect_code = 307; + } + + /* In case redirect_code is none of the above, return error. */ + if ((redirect_code != 301) && (redirect_code != 302) + && (redirect_code != 303) && (redirect_code != 307) + && (redirect_code != 308)) { + /* Parameter error */ + return -2; + } + + /* Get proper text for response code */ + redirect_text = mg_get_response_code_text(conn, redirect_code); + + /* If target_url is not defined, redirect to "/". */ + if ((target_url == NULL) || (*target_url == 0)) { + target_url = "/"; + } + +#if defined(MG_SEND_REDIRECT_BODY) + /* TODO: condition name? */ + + /* Prepare a response body with a hyperlink. + * + * According to RFC2616 (and RFC1945 before): + * Unless the request method was HEAD, the entity of the + * response SHOULD contain a short hypertext note with a hyperlink to + * the new URI(s). + * + * However, this response body is not useful in M2M communication. + * Probably the original reason in the RFC was, clients not supporting + * a 30x HTTP redirect could still show the HTML page and let the user + * press the link. Since current browsers support 30x HTTP, the additional + * HTML body does not seem to make sense anymore. + * + * The new RFC7231 (Section 6.4) does no longer recommend it ("SHOULD"), + * but it only notes: + * The server's response payload usually contains a short + * hypertext note with a hyperlink to the new URI(s). + * + * Deactivated by default. If you need the 30x body, set the define. + */ + mg_snprintf( + conn, + NULL /* ignore truncation */, + reply, + sizeof(reply), + "%s%s", + redirect_text, + target_url, + target_url); + content_len = strlen(reply); +#else + reply[0] = 0; +#endif + + /* Do not send any additional header. For all other options, + * including caching, there are suitable defaults. */ + ret = mg_printf(conn, + "HTTP/1.1 %i %s\r\n" + "Location: %s\r\n" + "Content-Length: %u\r\n" + "Connection: %s\r\n\r\n", + redirect_code, + redirect_text, + target_url, + (unsigned int)content_len, + suggest_connection_header(conn)); + + /* Send response body */ + if (ret > 0) { + /* ... unless it is a HEAD request */ + if (0 != strcmp(conn->request_info.request_method, "HEAD")) { + ret = mg_write(conn, reply, content_len); + } + } + + return (ret > 0) ? ret : -1; +} + + +#if defined(_WIN32) +/* Create substitutes for POSIX functions in Win32. */ + +#if defined(GCC_DIAGNOSTIC) +/* Show no warning in case system functions are not used. */ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wunused-function" +#endif + + +static int +pthread_mutex_init(pthread_mutex_t *mutex, void *unused) +{ + (void)unused; + /* Always initialize as PTHREAD_MUTEX_RECURSIVE */ + InitializeCriticalSection(&mutex->sec); + return 0; +} + + +static int +pthread_mutex_destroy(pthread_mutex_t *mutex) +{ + DeleteCriticalSection(&mutex->sec); + return 0; +} + + +static int +pthread_mutex_lock(pthread_mutex_t *mutex) +{ + EnterCriticalSection(&mutex->sec); + return 0; +} + + +static int +pthread_mutex_unlock(pthread_mutex_t *mutex) +{ + LeaveCriticalSection(&mutex->sec); + return 0; +} + + +FUNCTION_MAY_BE_UNUSED +static int +pthread_cond_init(pthread_cond_t *cv, const void *unused) +{ + (void)unused; + (void)pthread_mutex_init(&cv->threadIdSec, &pthread_mutex_attr); + cv->waiting_thread = NULL; + return 0; +} + + +FUNCTION_MAY_BE_UNUSED +static int +pthread_cond_timedwait(pthread_cond_t *cv, + pthread_mutex_t *mutex, + FUNCTION_MAY_BE_UNUSED const struct timespec *abstime) +{ + struct mg_workerTLS **ptls, + *tls = (struct mg_workerTLS *)pthread_getspecific(sTlsKey); + int ok; + int64_t nsnow, nswaitabs, nswaitrel; + DWORD mswaitrel; + + pthread_mutex_lock(&cv->threadIdSec); + /* Add this thread to cv's waiting list */ + ptls = &cv->waiting_thread; + for (; *ptls != NULL; ptls = &(*ptls)->next_waiting_thread) + ; + tls->next_waiting_thread = NULL; + *ptls = tls; + pthread_mutex_unlock(&cv->threadIdSec); + + if (abstime) { + nsnow = mg_get_current_time_ns(); + nswaitabs = + (((int64_t)abstime->tv_sec) * 1000000000) + abstime->tv_nsec; + nswaitrel = nswaitabs - nsnow; + if (nswaitrel < 0) { + nswaitrel = 0; + } + mswaitrel = (DWORD)(nswaitrel / 1000000); + } else { + mswaitrel = (DWORD)INFINITE; + } + + pthread_mutex_unlock(mutex); + ok = (WAIT_OBJECT_0 + == WaitForSingleObject(tls->pthread_cond_helper_mutex, mswaitrel)); + if (!ok) { + ok = 1; + pthread_mutex_lock(&cv->threadIdSec); + ptls = &cv->waiting_thread; + for (; *ptls != NULL; ptls = &(*ptls)->next_waiting_thread) { + if (*ptls == tls) { + *ptls = tls->next_waiting_thread; + ok = 0; + break; + } + } + pthread_mutex_unlock(&cv->threadIdSec); + if (ok) { + WaitForSingleObject(tls->pthread_cond_helper_mutex, + (DWORD)INFINITE); + } + } + /* This thread has been removed from cv's waiting list */ + pthread_mutex_lock(mutex); + + return ok ? 0 : -1; +} + + +FUNCTION_MAY_BE_UNUSED +static int +pthread_cond_wait(pthread_cond_t *cv, pthread_mutex_t *mutex) +{ + return pthread_cond_timedwait(cv, mutex, NULL); +} + + +FUNCTION_MAY_BE_UNUSED +static int +pthread_cond_signal(pthread_cond_t *cv) +{ + HANDLE wkup = NULL; + BOOL ok = FALSE; + + pthread_mutex_lock(&cv->threadIdSec); + if (cv->waiting_thread) { + wkup = cv->waiting_thread->pthread_cond_helper_mutex; + cv->waiting_thread = cv->waiting_thread->next_waiting_thread; + + ok = SetEvent(wkup); + DEBUG_ASSERT(ok); + } + pthread_mutex_unlock(&cv->threadIdSec); + + return ok ? 0 : 1; +} + + +FUNCTION_MAY_BE_UNUSED +static int +pthread_cond_broadcast(pthread_cond_t *cv) +{ + pthread_mutex_lock(&cv->threadIdSec); + while (cv->waiting_thread) { + pthread_cond_signal(cv); + } + pthread_mutex_unlock(&cv->threadIdSec); + + return 0; +} + + +FUNCTION_MAY_BE_UNUSED +static int +pthread_cond_destroy(pthread_cond_t *cv) +{ + pthread_mutex_lock(&cv->threadIdSec); + DEBUG_ASSERT(cv->waiting_thread == NULL); + pthread_mutex_unlock(&cv->threadIdSec); + pthread_mutex_destroy(&cv->threadIdSec); + + return 0; +} + + +#if defined(ALTERNATIVE_QUEUE) +FUNCTION_MAY_BE_UNUSED +static void * +event_create(void) +{ + return (void *)CreateEvent(NULL, FALSE, FALSE, NULL); +} + + +FUNCTION_MAY_BE_UNUSED +static int +event_wait(void *eventhdl) +{ + int res = WaitForSingleObject((HANDLE)eventhdl, (DWORD)INFINITE); + return (res == WAIT_OBJECT_0); +} + + +FUNCTION_MAY_BE_UNUSED +static int +event_signal(void *eventhdl) +{ + return (int)SetEvent((HANDLE)eventhdl); +} + + +FUNCTION_MAY_BE_UNUSED +static void +event_destroy(void *eventhdl) +{ + CloseHandle((HANDLE)eventhdl); +} +#endif + + +#if defined(GCC_DIAGNOSTIC) +/* Enable unused function warning again */ +#pragma GCC diagnostic pop +#endif + + +/* For Windows, change all slashes to backslashes in path names. */ +static void +change_slashes_to_backslashes(char *path) +{ + int i; + + for (i = 0; path[i] != '\0'; i++) { + if (path[i] == '/') { + path[i] = '\\'; + } + + /* remove double backslash (check i > 0 to preserve UNC paths, + * like \\server\file.txt) */ + if ((path[i] == '\\') && (i > 0)) { + while ((path[i + 1] == '\\') || (path[i + 1] == '/')) { + (void)memmove(path + i + 1, path + i + 2, strlen(path + i + 1)); + } + } + } +} + + +static int +mg_wcscasecmp(const wchar_t *s1, const wchar_t *s2) +{ + int diff; + + do { + diff = ((*s1 >= L'A') && (*s1 <= L'Z') ? (*s1 - L'A' + L'a') : *s1) + - ((*s2 >= L'A') && (*s2 <= L'Z') ? (*s2 - L'A' + L'a') : *s2); + s1++; + s2++; + } while ((diff == 0) && (s1[-1] != L'\0')); + + return diff; +} + + +/* Encode 'path' which is assumed UTF-8 string, into UNICODE string. + * wbuf and wbuf_len is a target buffer and its length. */ +static void +path_to_unicode(const struct mg_connection *conn, + const char *path, + wchar_t *wbuf, + size_t wbuf_len) +{ + char buf[PATH_MAX], buf2[PATH_MAX]; + wchar_t wbuf2[W_PATH_MAX + 1]; + DWORD long_len, err; + int (*fcompare)(const wchar_t *, const wchar_t *) = mg_wcscasecmp; + + mg_strlcpy(buf, path, sizeof(buf)); + change_slashes_to_backslashes(buf); + + /* Convert to Unicode and back. If doubly-converted string does not + * match the original, something is fishy, reject. */ + memset(wbuf, 0, wbuf_len * sizeof(wchar_t)); + MultiByteToWideChar(CP_UTF8, 0, buf, -1, wbuf, (int)wbuf_len); + WideCharToMultiByte( + CP_UTF8, 0, wbuf, (int)wbuf_len, buf2, sizeof(buf2), NULL, NULL); + if (strcmp(buf, buf2) != 0) { + wbuf[0] = L'\0'; + } + + /* Windows file systems are not case sensitive, but you can still use + * uppercase and lowercase letters (on all modern file systems). + * The server can check if the URI uses the same upper/lowercase + * letters an the file system, effectively making Windows servers + * case sensitive (like Linux servers are). It is still not possible + * to use two files with the same name in different cases on Windows + * (like /a and /A) - this would be possible in Linux. + * As a default, Windows is not case sensitive, but the case sensitive + * file name check can be activated by an additional configuration. */ + if (conn) { + if (conn->dom_ctx->config[CASE_SENSITIVE_FILES] + && !mg_strcasecmp(conn->dom_ctx->config[CASE_SENSITIVE_FILES], + "yes")) { + /* Use case sensitive compare function */ + fcompare = wcscmp; + } + } + (void)conn; /* conn is currently unused */ + +#if !defined(_WIN32_WCE) + /* Only accept a full file path, not a Windows short (8.3) path. */ + memset(wbuf2, 0, ARRAY_SIZE(wbuf2) * sizeof(wchar_t)); + long_len = GetLongPathNameW(wbuf, wbuf2, ARRAY_SIZE(wbuf2) - 1); + if (long_len == 0) { + err = GetLastError(); + if (err == ERROR_FILE_NOT_FOUND) { + /* File does not exist. This is not always a problem here. */ + return; + } + } + if ((long_len >= ARRAY_SIZE(wbuf2)) || (fcompare(wbuf, wbuf2) != 0)) { + /* Short name is used. */ + wbuf[0] = L'\0'; + } +#else + (void)long_len; + (void)wbuf2; + (void)err; + + if (strchr(path, '~')) { + wbuf[0] = L'\0'; + } +#endif +} + + +#if !defined(NO_FILESYSTEMS) +static int +mg_stat(const struct mg_connection *conn, + const char *path, + struct mg_file_stat *filep) +{ + wchar_t wbuf[W_PATH_MAX]; + WIN32_FILE_ATTRIBUTE_DATA info; + time_t creation_time; + size_t len; + + if (!filep) { + return 0; + } + memset(filep, 0, sizeof(*filep)); + + if (conn && is_file_in_memory(conn, path)) { + /* filep->is_directory = 0; filep->gzipped = 0; .. already done by + * memset */ + + /* Quick fix (for 1.9.x): */ + /* mg_stat must fill all fields, also for files in memory */ + struct mg_file tmp_file = STRUCT_FILE_INITIALIZER; + open_file_in_memory(conn, path, &tmp_file, MG_FOPEN_MODE_NONE); + filep->size = tmp_file.stat.size; + filep->location = 2; + /* TODO: for 1.10: restructure how files in memory are handled */ + + /* The "file in memory" feature is a candidate for deletion. + * Please join the discussion at + * https://groups.google.com/forum/#!topic/civetweb/h9HT4CmeYqI + */ + + filep->last_modified = time(NULL); /* TODO */ + /* last_modified = now ... assumes the file may change during + * runtime, + * so every mg_fopen call may return different data */ + /* last_modified = conn->phys_ctx.start_time; + * May be used it the data does not change during runtime. This + * allows + * browser caching. Since we do not know, we have to assume the file + * in memory may change. */ + return 1; + } + + path_to_unicode(conn, path, wbuf, ARRAY_SIZE(wbuf)); + /* Windows happily opens files with some garbage at the end of file name. + * For example, fopen("a.cgi ", "r") on Windows successfully opens + * "a.cgi", despite one would expect an error back. */ + len = strlen(path); + if ((len > 0) && (path[len - 1] != ' ') && (path[len - 1] != '.') + && (GetFileAttributesExW(wbuf, GetFileExInfoStandard, &info) != 0)) { + filep->size = MAKEUQUAD(info.nFileSizeLow, info.nFileSizeHigh); + filep->last_modified = + SYS2UNIX_TIME(info.ftLastWriteTime.dwLowDateTime, + info.ftLastWriteTime.dwHighDateTime); + + /* On Windows, the file creation time can be higher than the + * modification time, e.g. when a file is copied. + * Since the Last-Modified timestamp is used for caching + * it should be based on the most recent timestamp. */ + creation_time = SYS2UNIX_TIME(info.ftCreationTime.dwLowDateTime, + info.ftCreationTime.dwHighDateTime); + if (creation_time > filep->last_modified) { + filep->last_modified = creation_time; + } + + filep->is_directory = info.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY; + return 1; + } + + return 0; +} +#endif + + +static int +mg_remove(const struct mg_connection *conn, const char *path) +{ + wchar_t wbuf[W_PATH_MAX]; + path_to_unicode(conn, path, wbuf, ARRAY_SIZE(wbuf)); + return DeleteFileW(wbuf) ? 0 : -1; +} + + +static int +mg_mkdir(const struct mg_connection *conn, const char *path, int mode) +{ + wchar_t wbuf[W_PATH_MAX]; + (void)mode; + path_to_unicode(conn, path, wbuf, ARRAY_SIZE(wbuf)); + return CreateDirectoryW(wbuf, NULL) ? 0 : -1; +} + + +/* Create substitutes for POSIX functions in Win32. */ + +#if defined(GCC_DIAGNOSTIC) +/* Show no warning in case system functions are not used. */ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wunused-function" +#endif + + +/* Implementation of POSIX opendir/closedir/readdir for Windows. */ +FUNCTION_MAY_BE_UNUSED +static DIR * +mg_opendir(const struct mg_connection *conn, const char *name) +{ + DIR *dir = NULL; + wchar_t wpath[W_PATH_MAX]; + DWORD attrs; + + if (name == NULL) { + SetLastError(ERROR_BAD_ARGUMENTS); + } else if ((dir = (DIR *)mg_malloc(sizeof(*dir))) == NULL) { + SetLastError(ERROR_NOT_ENOUGH_MEMORY); + } else { + path_to_unicode(conn, name, wpath, ARRAY_SIZE(wpath)); + attrs = GetFileAttributesW(wpath); + if ((wcslen(wpath) + 2 < ARRAY_SIZE(wpath)) && (attrs != 0xFFFFFFFF) + && ((attrs & FILE_ATTRIBUTE_DIRECTORY) != 0)) { + (void)wcscat(wpath, L"\\*"); + dir->handle = FindFirstFileW(wpath, &dir->info); + dir->result.d_name[0] = '\0'; + } else { + mg_free(dir); + dir = NULL; + } + } + + return dir; +} + + +FUNCTION_MAY_BE_UNUSED +static int +mg_closedir(DIR *dir) +{ + int result = 0; + + if (dir != NULL) { + if (dir->handle != INVALID_HANDLE_VALUE) + result = FindClose(dir->handle) ? 0 : -1; + + mg_free(dir); + } else { + result = -1; + SetLastError(ERROR_BAD_ARGUMENTS); + } + + return result; +} + + +FUNCTION_MAY_BE_UNUSED +static struct dirent * +mg_readdir(DIR *dir) +{ + struct dirent *result = 0; + + if (dir) { + if (dir->handle != INVALID_HANDLE_VALUE) { + result = &dir->result; + (void)WideCharToMultiByte(CP_UTF8, + 0, + dir->info.cFileName, + -1, + result->d_name, + sizeof(result->d_name), + NULL, + NULL); + + if (!FindNextFileW(dir->handle, &dir->info)) { + (void)FindClose(dir->handle); + dir->handle = INVALID_HANDLE_VALUE; + } + + } else { + SetLastError(ERROR_FILE_NOT_FOUND); + } + } else { + SetLastError(ERROR_BAD_ARGUMENTS); + } + + return result; +} + + +#if !defined(HAVE_POLL) +#undef POLLIN +#undef POLLPRI +#undef POLLOUT +#define POLLIN (1) /* Data ready - read will not block. */ +#define POLLPRI (2) /* Priority data ready. */ +#define POLLOUT (4) /* Send queue not full - write will not block. */ + +FUNCTION_MAY_BE_UNUSED +static int +poll(struct mg_pollfd *pfd, unsigned int n, int milliseconds) +{ + struct timeval tv; + fd_set rset; + fd_set wset; + unsigned int i; + int result; + SOCKET maxfd = 0; + + memset(&tv, 0, sizeof(tv)); + tv.tv_sec = milliseconds / 1000; + tv.tv_usec = (milliseconds % 1000) * 1000; + FD_ZERO(&rset); + FD_ZERO(&wset); + + for (i = 0; i < n; i++) { + if (pfd[i].events & POLLIN) { + FD_SET(pfd[i].fd, &rset); + } + if (pfd[i].events & POLLOUT) { + FD_SET(pfd[i].fd, &wset); + } + pfd[i].revents = 0; + + if (pfd[i].fd > maxfd) { + maxfd = pfd[i].fd; + } + } + + if ((result = select((int)maxfd + 1, &rset, &wset, NULL, &tv)) > 0) { + for (i = 0; i < n; i++) { + if (FD_ISSET(pfd[i].fd, &rset)) { + pfd[i].revents |= POLLIN; + } + if (FD_ISSET(pfd[i].fd, &wset)) { + pfd[i].revents |= POLLOUT; + } + } + } + + /* We should subtract the time used in select from remaining + * "milliseconds", in particular if called from mg_poll with a + * timeout quantum. + * Unfortunately, the remaining time is not stored in "tv" in all + * implementations, so the result in "tv" must be considered undefined. + * See http://man7.org/linux/man-pages/man2/select.2.html */ + + return result; +} +#endif /* HAVE_POLL */ + + +#if defined(GCC_DIAGNOSTIC) +/* Enable unused function warning again */ +#pragma GCC diagnostic pop +#endif + + +static void +set_close_on_exec(SOCKET sock, + const struct mg_connection *conn /* may be null */, + struct mg_context *ctx /* may be null */) +{ + (void)conn; /* Unused. */ + (void)ctx; +#if defined(_WIN32_WCE) + (void)sock; +#else + (void)SetHandleInformation((HANDLE)(intptr_t)sock, HANDLE_FLAG_INHERIT, 0); +#endif +} + + +int +mg_start_thread(mg_thread_func_t f, void *p) +{ +#if defined(USE_STACK_SIZE) && (USE_STACK_SIZE > 1) + /* Compile-time option to control stack size, e.g. + * -DUSE_STACK_SIZE=16384 + */ + return ((_beginthread((void(__cdecl *)(void *))f, USE_STACK_SIZE, p) + == ((uintptr_t)(-1L))) + ? -1 + : 0); +#else + return ( + (_beginthread((void(__cdecl *)(void *))f, 0, p) == ((uintptr_t)(-1L))) + ? -1 + : 0); +#endif /* defined(USE_STACK_SIZE) && (USE_STACK_SIZE > 1) */ +} + + +/* Start a thread storing the thread context. */ +static int +mg_start_thread_with_id(unsigned(__stdcall *f)(void *), + void *p, + pthread_t *threadidptr) +{ + uintptr_t uip; + HANDLE threadhandle; + int result = -1; + + uip = _beginthreadex(NULL, 0, f, p, 0, NULL); + threadhandle = (HANDLE)uip; + if ((uip != 0) && (threadidptr != NULL)) { + *threadidptr = threadhandle; + result = 0; + } + + return result; +} + + +/* Wait for a thread to finish. */ +static int +mg_join_thread(pthread_t threadid) +{ + int result; + DWORD dwevent; + + result = -1; + dwevent = WaitForSingleObject(threadid, (DWORD)INFINITE); + if (dwevent == WAIT_FAILED) { + DEBUG_TRACE("WaitForSingleObject() failed, error %d", ERRNO); + } else { + if (dwevent == WAIT_OBJECT_0) { + CloseHandle(threadid); + result = 0; + } + } + + return result; +} + +#if !defined(NO_SSL_DL) && !defined(NO_SSL) +/* If SSL is loaded dynamically, dlopen/dlclose is required. */ +/* Create substitutes for POSIX functions in Win32. */ + +#if defined(GCC_DIAGNOSTIC) +/* Show no warning in case system functions are not used. */ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wunused-function" +#endif + + +FUNCTION_MAY_BE_UNUSED +static HANDLE +dlopen(const char *dll_name, int flags) +{ + wchar_t wbuf[W_PATH_MAX]; + (void)flags; + path_to_unicode(NULL, dll_name, wbuf, ARRAY_SIZE(wbuf)); + return LoadLibraryW(wbuf); +} + + +FUNCTION_MAY_BE_UNUSED +static int +dlclose(void *handle) +{ + int result; + + if (FreeLibrary((HMODULE)handle) != 0) { + result = 0; + } else { + result = -1; + } + + return result; +} + + +#if defined(GCC_DIAGNOSTIC) +/* Enable unused function warning again */ +#pragma GCC diagnostic pop +#endif + +#endif + + +#if !defined(NO_CGI) +#define SIGKILL (0) + + +static int +kill(pid_t pid, int sig_num) +{ + (void)TerminateProcess((HANDLE)pid, (UINT)sig_num); + (void)CloseHandle((HANDLE)pid); + return 0; +} + + +#if !defined(WNOHANG) +#define WNOHANG (1) +#endif + + +static pid_t +waitpid(pid_t pid, int *status, int flags) +{ + DWORD timeout = INFINITE; + DWORD waitres; + + (void)status; /* Currently not used by any client here */ + + if ((flags | WNOHANG) == WNOHANG) { + timeout = 0; + } + + waitres = WaitForSingleObject((HANDLE)pid, timeout); + if (waitres == WAIT_OBJECT_0) { + return pid; + } + if (waitres == WAIT_TIMEOUT) { + return 0; + } + return (pid_t)-1; +} + + +static void +trim_trailing_whitespaces(char *s) +{ + char *e = s + strlen(s); + while ((e > s) && isspace((unsigned char)e[-1])) { + *(--e) = '\0'; + } +} + + +static pid_t +spawn_process(struct mg_connection *conn, + const char *prog, + char *envblk, + char *envp[], + int fdin[2], + int fdout[2], + int fderr[2], + const char *dir) +{ + HANDLE me; + char *p, *interp, full_interp[PATH_MAX], full_dir[PATH_MAX], + cmdline[PATH_MAX], buf[PATH_MAX]; + int truncated; + struct mg_file file = STRUCT_FILE_INITIALIZER; + STARTUPINFOA si; + PROCESS_INFORMATION pi = {0}; + + (void)envp; + + memset(&si, 0, sizeof(si)); + si.cb = sizeof(si); + + si.dwFlags = STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW; + si.wShowWindow = SW_HIDE; + + me = GetCurrentProcess(); + DuplicateHandle(me, + (HANDLE)_get_osfhandle(fdin[0]), + me, + &si.hStdInput, + 0, + TRUE, + DUPLICATE_SAME_ACCESS); + DuplicateHandle(me, + (HANDLE)_get_osfhandle(fdout[1]), + me, + &si.hStdOutput, + 0, + TRUE, + DUPLICATE_SAME_ACCESS); + DuplicateHandle(me, + (HANDLE)_get_osfhandle(fderr[1]), + me, + &si.hStdError, + 0, + TRUE, + DUPLICATE_SAME_ACCESS); + + /* Mark handles that should not be inherited. See + * https://msdn.microsoft.com/en-us/library/windows/desktop/ms682499%28v=vs.85%29.aspx + */ + SetHandleInformation((HANDLE)_get_osfhandle(fdin[1]), + HANDLE_FLAG_INHERIT, + 0); + SetHandleInformation((HANDLE)_get_osfhandle(fdout[0]), + HANDLE_FLAG_INHERIT, + 0); + SetHandleInformation((HANDLE)_get_osfhandle(fderr[0]), + HANDLE_FLAG_INHERIT, + 0); + + /* If CGI file is a script, try to read the interpreter line */ + interp = conn->dom_ctx->config[CGI_INTERPRETER]; + if (interp == NULL) { + buf[0] = buf[1] = '\0'; + + /* Read the first line of the script into the buffer */ + mg_snprintf( + conn, &truncated, cmdline, sizeof(cmdline), "%s/%s", dir, prog); + + if (truncated) { + pi.hProcess = (pid_t)-1; + goto spawn_cleanup; + } + + if (mg_fopen(conn, cmdline, MG_FOPEN_MODE_READ, &file)) { +#if defined(MG_USE_OPEN_FILE) + p = (char *)file.access.membuf; +#else + p = (char *)NULL; +#endif + mg_fgets(buf, sizeof(buf), &file, &p); + (void)mg_fclose(&file.access); /* ignore error on read only file */ + buf[sizeof(buf) - 1] = '\0'; + } + + if ((buf[0] == '#') && (buf[1] == '!')) { + trim_trailing_whitespaces(buf + 2); + } else { + buf[2] = '\0'; + } + interp = buf + 2; + } + + if (interp[0] != '\0') { + GetFullPathNameA(interp, sizeof(full_interp), full_interp, NULL); + interp = full_interp; + } + GetFullPathNameA(dir, sizeof(full_dir), full_dir, NULL); + + if (interp[0] != '\0') { + mg_snprintf(conn, + &truncated, + cmdline, + sizeof(cmdline), + "\"%s\" \"%s\\%s\"", + interp, + full_dir, + prog); + } else { + mg_snprintf(conn, + &truncated, + cmdline, + sizeof(cmdline), + "\"%s\\%s\"", + full_dir, + prog); + } + + if (truncated) { + pi.hProcess = (pid_t)-1; + goto spawn_cleanup; + } + + DEBUG_TRACE("Running [%s]", cmdline); + if (CreateProcessA(NULL, + cmdline, + NULL, + NULL, + TRUE, + CREATE_NEW_PROCESS_GROUP, + envblk, + NULL, + &si, + &pi) + == 0) { + mg_cry_internal( + conn, "%s: CreateProcess(%s): %ld", __func__, cmdline, (long)ERRNO); + pi.hProcess = (pid_t)-1; + /* goto spawn_cleanup; */ + } + +spawn_cleanup: + (void)CloseHandle(si.hStdOutput); + (void)CloseHandle(si.hStdError); + (void)CloseHandle(si.hStdInput); + if (pi.hThread != NULL) { + (void)CloseHandle(pi.hThread); + } + + return (pid_t)pi.hProcess; +} +#endif /* !NO_CGI */ + + +static int +set_blocking_mode(SOCKET sock) +{ + unsigned long non_blocking = 0; + return ioctlsocket(sock, (long)FIONBIO, &non_blocking); +} + +static int +set_non_blocking_mode(SOCKET sock) +{ + unsigned long non_blocking = 1; + return ioctlsocket(sock, (long)FIONBIO, &non_blocking); +} + +#else + +#if !defined(NO_FILESYSTEMS) +static int +mg_stat(const struct mg_connection *conn, + const char *path, + struct mg_file_stat *filep) +{ + struct stat st; + if (!filep) { + return 0; + } + memset(filep, 0, sizeof(*filep)); + + if (conn && is_file_in_memory(conn, path)) { + + /* Quick fix (for 1.9.x): */ + /* mg_stat must fill all fields, also for files in memory */ + struct mg_file tmp_file = STRUCT_FILE_INITIALIZER; + open_file_in_memory(conn, path, &tmp_file, MG_FOPEN_MODE_NONE); + filep->size = tmp_file.stat.size; + filep->last_modified = time(NULL); + filep->location = 2; + /* TODO: remove legacy "files in memory" feature */ + + return 1; + } + + if (0 == stat(path, &st)) { + filep->size = (uint64_t)(st.st_size); + filep->last_modified = st.st_mtime; + filep->is_directory = S_ISDIR(st.st_mode); + return 1; + } + + return 0; +} +#endif /* NO_FILESYSTEMS */ + + +static void +set_close_on_exec(int fd, + const struct mg_connection *conn /* may be null */, + struct mg_context *ctx /* may be null */) +{ +#if defined(__ZEPHYR__) + (void)fd; + (void)conn; + (void)ctx; +#else + if (fcntl(fd, F_SETFD, FD_CLOEXEC) != 0) { + if (conn || ctx) { + struct mg_connection fc; + mg_cry_internal((conn ? conn : fake_connection(&fc, ctx)), + "%s: fcntl(F_SETFD FD_CLOEXEC) failed: %s", + __func__, + strerror(ERRNO)); + } + } +#endif +} + + +int +mg_start_thread(mg_thread_func_t func, void *param) +{ + pthread_t thread_id; + pthread_attr_t attr; + int result; + + (void)pthread_attr_init(&attr); + (void)pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); + +#if defined(__ZEPHYR__) + pthread_attr_setstack(&attr, &civetweb_main_stack, ZEPHYR_STACK_SIZE); +#elif defined(USE_STACK_SIZE) && (USE_STACK_SIZE > 1) + /* Compile-time option to control stack size, + * e.g. -DUSE_STACK_SIZE=16384 */ + (void)pthread_attr_setstacksize(&attr, USE_STACK_SIZE); +#endif /* defined(USE_STACK_SIZE) && (USE_STACK_SIZE > 1) */ + + result = pthread_create(&thread_id, &attr, func, param); + pthread_attr_destroy(&attr); + + return result; +} + + +/* Start a thread storing the thread context. */ +static int +mg_start_thread_with_id(mg_thread_func_t func, + void *param, + pthread_t *threadidptr) +{ + pthread_t thread_id; + pthread_attr_t attr; + int result; + + (void)pthread_attr_init(&attr); + +#if defined(__ZEPHYR__) + pthread_attr_setstack(&attr, + &civetweb_worker_stacks[zephyr_worker_stack_index++], + ZEPHYR_STACK_SIZE); +#elif defined(USE_STACK_SIZE) && (USE_STACK_SIZE > 1) + /* Compile-time option to control stack size, + * e.g. -DUSE_STACK_SIZE=16384 */ + (void)pthread_attr_setstacksize(&attr, USE_STACK_SIZE); +#endif /* defined(USE_STACK_SIZE) && USE_STACK_SIZE > 1 */ + + result = pthread_create(&thread_id, &attr, func, param); + pthread_attr_destroy(&attr); + if ((result == 0) && (threadidptr != NULL)) { + *threadidptr = thread_id; + } + return result; +} + + +/* Wait for a thread to finish. */ +static int +mg_join_thread(pthread_t threadid) +{ + int result; + + result = pthread_join(threadid, NULL); + return result; +} + + +#if !defined(NO_CGI) +static pid_t +spawn_process(struct mg_connection *conn, + const char *prog, + char *envblk, + char *envp[], + int fdin[2], + int fdout[2], + int fderr[2], + const char *dir) +{ + pid_t pid; + const char *interp; + + (void)envblk; + + if ((pid = fork()) == -1) { + /* Parent */ + mg_cry_internal(conn, "%s: fork(): %s", __func__, strerror(ERRNO)); + } else if (pid != 0) { + /* Make sure children close parent-side descriptors. + * The caller will close the child-side immediately. */ + set_close_on_exec(fdin[1], conn, NULL); /* stdin write */ + set_close_on_exec(fdout[0], conn, NULL); /* stdout read */ + set_close_on_exec(fderr[0], conn, NULL); /* stderr read */ + } else { + /* Child */ + if (chdir(dir) != 0) { + mg_cry_internal( + conn, "%s: chdir(%s): %s", __func__, dir, strerror(ERRNO)); + } else if (dup2(fdin[0], 0) == -1) { + mg_cry_internal(conn, + "%s: dup2(%d, 0): %s", + __func__, + fdin[0], + strerror(ERRNO)); + } else if (dup2(fdout[1], 1) == -1) { + mg_cry_internal(conn, + "%s: dup2(%d, 1): %s", + __func__, + fdout[1], + strerror(ERRNO)); + } else if (dup2(fderr[1], 2) == -1) { + mg_cry_internal(conn, + "%s: dup2(%d, 2): %s", + __func__, + fderr[1], + strerror(ERRNO)); + } else { + struct sigaction sa; + + /* Keep stderr and stdout in two different pipes. + * Stdout will be sent back to the client, + * stderr should go into a server error log. */ + (void)close(fdin[0]); + (void)close(fdout[1]); + (void)close(fderr[1]); + + /* Close write end fdin and read end fdout and fderr */ + (void)close(fdin[1]); + (void)close(fdout[0]); + (void)close(fderr[0]); + + /* After exec, all signal handlers are restored to their default + * values, with one exception of SIGCHLD. According to + * POSIX.1-2001 and Linux's implementation, SIGCHLD's handler + * will leave unchanged after exec if it was set to be ignored. + * Restore it to default action. */ + memset(&sa, 0, sizeof(sa)); + sa.sa_handler = SIG_DFL; + sigaction(SIGCHLD, &sa, NULL); + + interp = conn->dom_ctx->config[CGI_INTERPRETER]; + if (interp == NULL) { + (void)execle(prog, prog, NULL, envp); + mg_cry_internal(conn, + "%s: execle(%s): %s", + __func__, + prog, + strerror(ERRNO)); + } else { + (void)execle(interp, interp, prog, NULL, envp); + mg_cry_internal(conn, + "%s: execle(%s %s): %s", + __func__, + interp, + prog, + strerror(ERRNO)); + } + } + exit(EXIT_FAILURE); + } + + return pid; +} +#endif /* !NO_CGI */ + + +static int +set_non_blocking_mode(SOCKET sock) +{ + int flags = fcntl(sock, F_GETFL, 0); + if (flags < 0) { + return -1; + } + + if (fcntl(sock, F_SETFL, (flags | O_NONBLOCK)) < 0) { + return -1; + } + return 0; +} + +static int +set_blocking_mode(SOCKET sock) +{ + int flags = fcntl(sock, F_GETFL, 0); + if (flags < 0) { + return -1; + } + + if (fcntl(sock, F_SETFL, flags & (~(int)(O_NONBLOCK))) < 0) { + return -1; + } + return 0; +} +#endif /* _WIN32 / else */ + +/* End of initial operating system specific define block. */ + + +/* Get a random number (independent of C rand function) */ +static uint64_t +get_random(void) +{ + static uint64_t lfsr = 0; /* Linear feedback shift register */ + static uint64_t lcg = 0; /* Linear congruential generator */ + uint64_t now = mg_get_current_time_ns(); + + if (lfsr == 0) { + /* lfsr will be only 0 if has not been initialized, + * so this code is called only once. */ + lfsr = mg_get_current_time_ns(); + lcg = mg_get_current_time_ns(); + } else { + /* Get the next step of both random number generators. */ + lfsr = (lfsr >> 1) + | ((((lfsr >> 0) ^ (lfsr >> 1) ^ (lfsr >> 3) ^ (lfsr >> 4)) & 1) + << 63); + lcg = lcg * 6364136223846793005LL + 1442695040888963407LL; + } + + /* Combining two pseudo-random number generators and a high resolution + * part + * of the current server time will make it hard (impossible?) to guess + * the + * next number. */ + return (lfsr ^ lcg ^ now); +} + + +static int +mg_poll(struct mg_pollfd *pfd, + unsigned int n, + int milliseconds, + volatile int *stop_server) +{ + /* Call poll, but only for a maximum time of a few seconds. + * This will allow to stop the server after some seconds, instead + * of having to wait for a long socket timeout. */ + int ms_now = SOCKET_TIMEOUT_QUANTUM; /* Sleep quantum in ms */ + + do { + int result; + + if (*stop_server) { + /* Shut down signal */ + return -2; + } + + if ((milliseconds >= 0) && (milliseconds < ms_now)) { + ms_now = milliseconds; + } + + result = poll(pfd, n, ms_now); + if (result != 0) { + /* Poll returned either success (1) or error (-1). + * Forward both to the caller. */ + return result; + } + + /* Poll returned timeout (0). */ + if (milliseconds > 0) { + milliseconds -= ms_now; + } + + } while (milliseconds != 0); + + /* timeout: return 0 */ + return 0; +} + + +/* Write data to the IO channel - opened file descriptor, socket or SSL + * descriptor. + * Return value: + * >=0 .. number of bytes successfully written + * -1 .. timeout + * -2 .. error + */ +static int +push_inner(struct mg_context *ctx, + FILE *fp, + SOCKET sock, + SSL *ssl, + const char *buf, + int len, + double timeout) +{ + uint64_t start = 0, now = 0, timeout_ns = 0; + int n, err; + unsigned ms_wait = SOCKET_TIMEOUT_QUANTUM; /* Sleep quantum in ms */ + +#if defined(_WIN32) + typedef int len_t; +#else + typedef size_t len_t; +#endif + + if (timeout > 0) { + now = mg_get_current_time_ns(); + start = now; + timeout_ns = (uint64_t)(timeout * 1.0E9); + } + + if (ctx == NULL) { + return -2; + } + +#if defined(NO_SSL) + if (ssl) { + return -2; + } +#endif + + /* Try to read until it succeeds, fails, times out, or the server + * shuts down. */ + for (;;) { + +#if !defined(NO_SSL) + if (ssl != NULL) { + n = SSL_write(ssl, buf, len); + if (n <= 0) { + err = SSL_get_error(ssl, n); + if ((err == SSL_ERROR_SYSCALL) && (n == -1)) { + err = ERRNO; + } else if ((err == SSL_ERROR_WANT_READ) + || (err == SSL_ERROR_WANT_WRITE)) { + n = 0; + } else { + DEBUG_TRACE("SSL_write() failed, error %d", err); + return -2; + } + } else { + err = 0; + } + } else +#endif + if (fp != NULL) { + n = (int)fwrite(buf, 1, (size_t)len, fp); + if (ferror(fp)) { + n = -1; + err = ERRNO; + } else { + err = 0; + } + } else { + n = (int)send(sock, buf, (len_t)len, MSG_NOSIGNAL); + err = (n < 0) ? ERRNO : 0; +#if defined(_WIN32) + if (err == WSAEWOULDBLOCK) { + err = 0; + n = 0; + } +#else + if (err == EWOULDBLOCK) { + err = 0; + n = 0; + } +#endif + if (n < 0) { + /* shutdown of the socket at client side */ + return -2; + } + } + + if (ctx->stop_flag) { + return -2; + } + + if ((n > 0) || ((n == 0) && (len == 0))) { + /* some data has been read, or no data was requested */ + return n; + } + if (n < 0) { + /* socket error - check errno */ + DEBUG_TRACE("send() failed, error %d", err); + + /* TODO (mid): error handling depending on the error code. + * These codes are different between Windows and Linux. + * Currently there is no problem with failing send calls, + * if there is a reproducible situation, it should be + * investigated in detail. + */ + return -2; + } + + /* Only in case n=0 (timeout), repeat calling the write function */ + + /* If send failed, wait before retry */ + if (fp != NULL) { + /* For files, just wait a fixed time. + * Maybe it helps, maybe not. */ + mg_sleep(5); + } else { + /* For sockets, wait for the socket using poll */ + struct mg_pollfd pfd[1]; + int pollres; + + pfd[0].fd = sock; + pfd[0].events = POLLOUT; + pollres = mg_poll(pfd, 1, (int)(ms_wait), &(ctx->stop_flag)); + if (ctx->stop_flag) { + return -2; + } + if (pollres > 0) { + continue; + } + } + + if (timeout > 0) { + now = mg_get_current_time_ns(); + if ((now - start) > timeout_ns) { + /* Timeout */ + break; + } + } + } + + (void)err; /* Avoid unused warning if NO_SSL is set and DEBUG_TRACE is not + used */ + + return -1; +} + + +static int +push_all(struct mg_context *ctx, + FILE *fp, + SOCKET sock, + SSL *ssl, + const char *buf, + int len) +{ + double timeout = -1.0; + int n, nwritten = 0; + + if (ctx == NULL) { + return -1; + } + + if (ctx->dd.config[REQUEST_TIMEOUT]) { + timeout = atoi(ctx->dd.config[REQUEST_TIMEOUT]) / 1000.0; + } + + while ((len > 0) && (ctx->stop_flag == 0)) { + n = push_inner(ctx, fp, sock, ssl, buf + nwritten, len, timeout); + if (n < 0) { + if (nwritten == 0) { + nwritten = -1; /* Propagate the error */ + } + break; + } else if (n == 0) { + break; /* No more data to write */ + } else { + nwritten += n; + len -= n; + } + } + + return nwritten; +} + + +/* Read from IO channel - opened file descriptor, socket, or SSL descriptor. + * Return value: + * >=0 .. number of bytes successfully read + * -1 .. timeout + * -2 .. error + */ +static int +pull_inner(FILE *fp, + struct mg_connection *conn, + char *buf, + int len, + double timeout) +{ + int nread, err = 0; + +#if defined(_WIN32) + typedef int len_t; +#else + typedef size_t len_t; +#endif +#if !defined(NO_SSL) + int ssl_pending; +#endif + + /* We need an additional wait loop around this, because in some cases + * with TLSwe may get data from the socket but not from SSL_read. + * In this case we need to repeat at least once. + */ + + if (fp != NULL) { +#if !defined(_WIN32_WCE) + /* Use read() instead of fread(), because if we're reading from the + * CGI pipe, fread() may block until IO buffer is filled up. We + * cannot afford to block and must pass all read bytes immediately + * to the client. */ + nread = (int)read(fileno(fp), buf, (size_t)len); +#else + /* WinCE does not support CGI pipes */ + nread = (int)fread(buf, 1, (size_t)len, fp); +#endif + err = (nread < 0) ? ERRNO : 0; + if ((nread == 0) && (len > 0)) { + /* Should get data, but got EOL */ + return -2; + } + +#if !defined(NO_SSL) + } else if ((conn->ssl != NULL) + && ((ssl_pending = SSL_pending(conn->ssl)) > 0)) { + /* We already know there is no more data buffered in conn->buf + * but there is more available in the SSL layer. So don't poll + * conn->client.sock yet. */ + if (ssl_pending > len) { + ssl_pending = len; + } + nread = SSL_read(conn->ssl, buf, ssl_pending); + if (nread <= 0) { + err = SSL_get_error(conn->ssl, nread); + if ((err == SSL_ERROR_SYSCALL) && (nread == -1)) { + err = ERRNO; + } else if ((err == SSL_ERROR_WANT_READ) + || (err == SSL_ERROR_WANT_WRITE)) { + nread = 0; + } else { + /* All errors should return -2 */ + DEBUG_TRACE("SSL_read() failed, error %d", err); + return -2; + } + + ERR_clear_error(); + } else { + err = 0; + } + + } else if (conn->ssl != NULL) { + + struct mg_pollfd pfd[1]; + int pollres; + + pfd[0].fd = conn->client.sock; + pfd[0].events = POLLIN; + pollres = mg_poll(pfd, + 1, + (int)(timeout * 1000.0), + &(conn->phys_ctx->stop_flag)); + if (conn->phys_ctx->stop_flag) { + return -2; + } + if (pollres > 0) { + nread = SSL_read(conn->ssl, buf, len); + if (nread <= 0) { + err = SSL_get_error(conn->ssl, nread); + if ((err == SSL_ERROR_SYSCALL) && (nread == -1)) { + err = ERRNO; + } else if ((err == SSL_ERROR_WANT_READ) + || (err == SSL_ERROR_WANT_WRITE)) { + nread = 0; + } else { + DEBUG_TRACE("SSL_read() failed, error %d", err); + return -2; + } + } else { + err = 0; + } + ERR_clear_error(); + } else if (pollres < 0) { + /* Error */ + return -2; + } else { + /* pollres = 0 means timeout */ + nread = 0; + } +#endif + + } else { + struct mg_pollfd pfd[1]; + int pollres; + + pfd[0].fd = conn->client.sock; + pfd[0].events = POLLIN; + pollres = mg_poll(pfd, + 1, + (int)(timeout * 1000.0), + &(conn->phys_ctx->stop_flag)); + if (conn->phys_ctx->stop_flag) { + return -2; + } + if (pollres > 0) { + nread = (int)recv(conn->client.sock, buf, (len_t)len, 0); + err = (nread < 0) ? ERRNO : 0; + if (nread <= 0) { + /* shutdown of the socket at client side */ + return -2; + } + } else if (pollres < 0) { + /* error callint poll */ + return -2; + } else { + /* pollres = 0 means timeout */ + nread = 0; + } + } + + if (conn->phys_ctx->stop_flag) { + return -2; + } + + if ((nread > 0) || ((nread == 0) && (len == 0))) { + /* some data has been read, or no data was requested */ + return nread; + } + + if (nread < 0) { +/* socket error - check errno */ +#if defined(_WIN32) + if (err == WSAEWOULDBLOCK) { + /* TODO (low): check if this is still required */ + /* standard case if called from close_socket_gracefully */ + return -2; + } else if (err == WSAETIMEDOUT) { + /* TODO (low): check if this is still required */ + /* timeout is handled by the while loop */ + return 0; + } else if (err == WSAECONNABORTED) { + /* See https://www.chilkatsoft.com/p/p_299.asp */ + return -2; + } else { + DEBUG_TRACE("recv() failed, error %d", err); + return -2; + } +#else + /* TODO: POSIX returns either EAGAIN or EWOULDBLOCK in both cases, + * if the timeout is reached and if the socket was set to non- + * blocking in close_socket_gracefully, so we can not distinguish + * here. We have to wait for the timeout in both cases for now. + */ + if ((err == EAGAIN) || (err == EWOULDBLOCK) || (err == EINTR)) { + /* TODO (low): check if this is still required */ + /* EAGAIN/EWOULDBLOCK: + * standard case if called from close_socket_gracefully + * => should return -1 */ + /* or timeout occurred + * => the code must stay in the while loop */ + + /* EINTR can be generated on a socket with a timeout set even + * when SA_RESTART is effective for all relevant signals + * (see signal(7)). + * => stay in the while loop */ + } else { + DEBUG_TRACE("recv() failed, error %d", err); + return -2; + } +#endif + } + + /* Timeout occurred, but no data available. */ + return -1; +} + + +static int +pull_all(FILE *fp, struct mg_connection *conn, char *buf, int len) +{ + int n, nread = 0; + double timeout = -1.0; + uint64_t start_time = 0, now = 0, timeout_ns = 0; + + if (conn->dom_ctx->config[REQUEST_TIMEOUT]) { + timeout = atoi(conn->dom_ctx->config[REQUEST_TIMEOUT]) / 1000.0; + } + if (timeout >= 0.0) { + start_time = mg_get_current_time_ns(); + timeout_ns = (uint64_t)(timeout * 1.0E9); + } + + while ((len > 0) && (conn->phys_ctx->stop_flag == 0)) { + n = pull_inner(fp, conn, buf + nread, len, timeout); + if (n == -2) { + if (nread == 0) { + nread = -1; /* Propagate the error */ + } + break; + } else if (n == -1) { + /* timeout */ + if (timeout >= 0.0) { + now = mg_get_current_time_ns(); + if ((now - start_time) <= timeout_ns) { + continue; + } + } + break; + } else if (n == 0) { + break; /* No more data to read */ + } else { + nread += n; + len -= n; + } + } + + return nread; +} + + +static void +discard_unread_request_data(struct mg_connection *conn) +{ + char buf[MG_BUF_LEN]; + + while (mg_read(conn, buf, sizeof(buf)) > 0) + ; +} + + +static int +mg_read_inner(struct mg_connection *conn, void *buf, size_t len) +{ + int64_t content_len, n, buffered_len, nread; + int64_t len64 = + (int64_t)((len > INT_MAX) ? INT_MAX : len); /* since the return value is + * int, we may not read more + * bytes */ + const char *body; + + if (conn == NULL) { + return 0; + } + + /* If Content-Length is not set for a response with body data, + * we do not know in advance how much data should be read. */ + content_len = conn->content_len; + if (content_len < 0) { + /* The body data is completed when the connection is closed. */ + content_len = INT64_MAX; + } + + nread = 0; + if (conn->consumed_content < content_len) { + /* Adjust number of bytes to read. */ + int64_t left_to_read = content_len - conn->consumed_content; + if (left_to_read < len64) { + /* Do not read more than the total content length of the + * request. + */ + len64 = left_to_read; + } + + /* Return buffered data */ + buffered_len = (int64_t)(conn->data_len) - (int64_t)conn->request_len + - conn->consumed_content; + if (buffered_len > 0) { + if (len64 < buffered_len) { + buffered_len = len64; + } + body = conn->buf + conn->request_len + conn->consumed_content; + memcpy(buf, body, (size_t)buffered_len); + len64 -= buffered_len; + conn->consumed_content += buffered_len; + nread += buffered_len; + buf = (char *)buf + buffered_len; + } + + /* We have returned all buffered data. Read new data from the remote + * socket. + */ + if ((n = pull_all(NULL, conn, (char *)buf, (int)len64)) >= 0) { + conn->consumed_content += n; + nread += n; + } else { + nread = ((nread > 0) ? nread : n); + } + } + return (int)nread; +} + + +int +mg_read(struct mg_connection *conn, void *buf, size_t len) +{ + if (len > INT_MAX) { + len = INT_MAX; + } + + if (conn == NULL) { + return 0; + } + + if (conn->is_chunked) { + size_t all_read = 0; + + while (len > 0) { + if (conn->is_chunked >= 3) { + /* No more data left to read */ + return 0; + } + if (conn->is_chunked != 1) { + /* Has error */ + return -1; + } + + if (conn->consumed_content != conn->content_len) { + /* copy from the current chunk */ + int read_ret = mg_read_inner(conn, (char *)buf + all_read, len); + + if (read_ret < 1) { + /* read error */ + conn->is_chunked = 2; + return -1; + } + + all_read += (size_t)read_ret; + len -= (size_t)read_ret; + + if (conn->consumed_content == conn->content_len) { + /* Add data bytes in the current chunk have been read, + * so we are expecting \r\n now. */ + char x[2]; + conn->content_len += 2; + if ((mg_read_inner(conn, x, 2) != 2) || (x[0] != '\r') + || (x[1] != '\n')) { + /* Protocol violation */ + conn->is_chunked = 2; + return -1; + } + } + + } else { + /* fetch a new chunk */ + size_t i; + char lenbuf[64]; + char *end = NULL; + unsigned long chunkSize = 0; + + for (i = 0; i < (sizeof(lenbuf) - 1); i++) { + conn->content_len++; + if (mg_read_inner(conn, lenbuf + i, 1) != 1) { + lenbuf[i] = 0; + } + if ((i > 0) && (lenbuf[i] == '\r') + && (lenbuf[i - 1] != '\r')) { + continue; + } + if ((i > 1) && (lenbuf[i] == '\n') + && (lenbuf[i - 1] == '\r')) { + lenbuf[i + 1] = 0; + chunkSize = strtoul(lenbuf, &end, 16); + if (chunkSize == 0) { + /* regular end of content */ + conn->is_chunked = 3; + } + break; + } + if (!isxdigit((unsigned char)lenbuf[i])) { + /* illegal character for chunk length */ + conn->is_chunked = 2; + return -1; + } + } + if ((end == NULL) || (*end != '\r')) { + /* chunksize not set correctly */ + conn->is_chunked = 2; + return -1; + } + if (chunkSize == 0) { + /* try discarding trailer for keep-alive */ + conn->content_len += 2; + if ((mg_read_inner(conn, lenbuf, 2) == 2) + && (lenbuf[0] == '\r') && (lenbuf[1] == '\n')) { + conn->is_chunked = 4; + } + break; + } + + /* append a new chunk */ + conn->content_len += chunkSize; + } + } + + return (int)all_read; + } + return mg_read_inner(conn, buf, len); +} + + +int +mg_write(struct mg_connection *conn, const void *buf, size_t len) +{ + time_t now; + int n, total, allowed; + + if (conn == NULL) { + return 0; + } + if (len > INT_MAX) { + return -1; + } + + if (conn->throttle > 0) { + if ((now = time(NULL)) != conn->last_throttle_time) { + conn->last_throttle_time = now; + conn->last_throttle_bytes = 0; + } + allowed = conn->throttle - conn->last_throttle_bytes; + if (allowed > (int)len) { + allowed = (int)len; + } + if ((total = push_all(conn->phys_ctx, + NULL, + conn->client.sock, + conn->ssl, + (const char *)buf, + allowed)) + == allowed) { + buf = (const char *)buf + total; + conn->last_throttle_bytes += total; + while ((total < (int)len) && (conn->phys_ctx->stop_flag == 0)) { + allowed = (conn->throttle > ((int)len - total)) + ? (int)len - total + : conn->throttle; + if ((n = push_all(conn->phys_ctx, + NULL, + conn->client.sock, + conn->ssl, + (const char *)buf, + allowed)) + != allowed) { + break; + } + sleep(1); + conn->last_throttle_bytes = allowed; + conn->last_throttle_time = time(NULL); + buf = (const char *)buf + n; + total += n; + } + } + } else { + total = push_all(conn->phys_ctx, + NULL, + conn->client.sock, + conn->ssl, + (const char *)buf, + (int)len); + } + if (total > 0) { + conn->num_bytes_sent += total; + } + return total; +} + + +/* Send a chunk, if "Transfer-Encoding: chunked" is used */ +int +mg_send_chunk(struct mg_connection *conn, + const char *chunk, + unsigned int chunk_len) +{ + char lenbuf[16]; + size_t lenbuf_len; + int ret; + int t; + + /* First store the length information in a text buffer. */ + sprintf(lenbuf, "%x\r\n", chunk_len); + lenbuf_len = strlen(lenbuf); + + /* Then send length information, chunk and terminating \r\n. */ + ret = mg_write(conn, lenbuf, lenbuf_len); + if (ret != (int)lenbuf_len) { + return -1; + } + t = ret; + + ret = mg_write(conn, chunk, chunk_len); + if (ret != (int)chunk_len) { + return -1; + } + t += ret; + + ret = mg_write(conn, "\r\n", 2); + if (ret != 2) { + return -1; + } + t += ret; + + return t; +} + + +#if defined(GCC_DIAGNOSTIC) +/* This block forwards format strings to printf implementations, + * so we need to disable the format-nonliteral warning. */ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wformat-nonliteral" +#endif + + +/* Alternative alloc_vprintf() for non-compliant C runtimes */ +static int +alloc_vprintf2(char **buf, const char *fmt, va_list ap) +{ + va_list ap_copy; + size_t size = MG_BUF_LEN / 4; + int len = -1; + + *buf = NULL; + while (len < 0) { + if (*buf) { + mg_free(*buf); + } + + size *= 4; + *buf = (char *)mg_malloc(size); + if (!*buf) { + break; + } + + va_copy(ap_copy, ap); + len = vsnprintf_impl(*buf, size - 1, fmt, ap_copy); + va_end(ap_copy); + (*buf)[size - 1] = 0; + } + + return len; +} + + +/* Print message to buffer. If buffer is large enough to hold the message, + * return buffer. If buffer is to small, allocate large enough buffer on + * heap, + * and return allocated buffer. */ +static int +alloc_vprintf(char **out_buf, + char *prealloc_buf, + size_t prealloc_size, + const char *fmt, + va_list ap) +{ + va_list ap_copy; + int len; + + /* Windows is not standard-compliant, and vsnprintf() returns -1 if + * buffer is too small. Also, older versions of msvcrt.dll do not have + * _vscprintf(). However, if size is 0, vsnprintf() behaves correctly. + * Therefore, we make two passes: on first pass, get required message + * length. + * On second pass, actually print the message. */ + va_copy(ap_copy, ap); + len = vsnprintf_impl(NULL, 0, fmt, ap_copy); + va_end(ap_copy); + + if (len < 0) { + /* C runtime is not standard compliant, vsnprintf() returned -1. + * Switch to alternative code path that uses incremental + * allocations. + */ + va_copy(ap_copy, ap); + len = alloc_vprintf2(out_buf, fmt, ap_copy); + va_end(ap_copy); + + } else if ((size_t)(len) >= prealloc_size) { + /* The pre-allocated buffer not large enough. */ + /* Allocate a new buffer. */ + *out_buf = (char *)mg_malloc((size_t)(len) + 1); + if (!*out_buf) { + /* Allocation failed. Return -1 as "out of memory" error. */ + return -1; + } + /* Buffer allocation successful. Store the string there. */ + va_copy(ap_copy, ap); + IGNORE_UNUSED_RESULT( + vsnprintf_impl(*out_buf, (size_t)(len) + 1, fmt, ap_copy)); + va_end(ap_copy); + + } else { + /* The pre-allocated buffer is large enough. + * Use it to store the string and return the address. */ + va_copy(ap_copy, ap); + IGNORE_UNUSED_RESULT( + vsnprintf_impl(prealloc_buf, prealloc_size, fmt, ap_copy)); + va_end(ap_copy); + *out_buf = prealloc_buf; + } + + return len; +} + + +#if defined(GCC_DIAGNOSTIC) +/* Enable format-nonliteral warning again. */ +#pragma GCC diagnostic pop +#endif + + +static int +mg_vprintf(struct mg_connection *conn, const char *fmt, va_list ap) +{ + char mem[MG_BUF_LEN]; + char *buf = NULL; + int len; + + if ((len = alloc_vprintf(&buf, mem, sizeof(mem), fmt, ap)) > 0) { + len = mg_write(conn, buf, (size_t)len); + } + if (buf != mem) { + mg_free(buf); + } + + return len; +} + + +int +mg_printf(struct mg_connection *conn, const char *fmt, ...) +{ + va_list ap; + int result; + + va_start(ap, fmt); + result = mg_vprintf(conn, fmt, ap); + va_end(ap); + + return result; +} + + +int +mg_url_decode(const char *src, + int src_len, + char *dst, + int dst_len, + int is_form_url_encoded) +{ + int i, j, a, b; +#define HEXTOI(x) (isdigit(x) ? (x - '0') : (x - 'W')) + + for (i = j = 0; (i < src_len) && (j < (dst_len - 1)); i++, j++) { + if ((i < src_len - 2) && (src[i] == '%') + && isxdigit((unsigned char)src[i + 1]) + && isxdigit((unsigned char)src[i + 2])) { + a = tolower((unsigned char)src[i + 1]); + b = tolower((unsigned char)src[i + 2]); + dst[j] = (char)((HEXTOI(a) << 4) | HEXTOI(b)); + i += 2; + } else if (is_form_url_encoded && (src[i] == '+')) { + dst[j] = ' '; + } else { + dst[j] = src[i]; + } + } + + dst[j] = '\0'; /* Null-terminate the destination */ + + return (i >= src_len) ? j : -1; +} + + +int +mg_get_var(const char *data, + size_t data_len, + const char *name, + char *dst, + size_t dst_len) +{ + return mg_get_var2(data, data_len, name, dst, dst_len, 0); +} + + +int +mg_get_var2(const char *data, + size_t data_len, + const char *name, + char *dst, + size_t dst_len, + size_t occurrence) +{ + const char *p, *e, *s; + size_t name_len; + int len; + + if ((dst == NULL) || (dst_len == 0)) { + len = -2; + } else if ((data == NULL) || (name == NULL) || (data_len == 0)) { + len = -1; + dst[0] = '\0'; + } else { + name_len = strlen(name); + e = data + data_len; + len = -1; + dst[0] = '\0'; + + /* data is "var1=val1&var2=val2...". Find variable first */ + for (p = data; p + name_len < e; p++) { + if (((p == data) || (p[-1] == '&')) && (p[name_len] == '=') + && !mg_strncasecmp(name, p, name_len) && 0 == occurrence--) { + /* Point p to variable value */ + p += name_len + 1; + + /* Point s to the end of the value */ + s = (const char *)memchr(p, '&', (size_t)(e - p)); + if (s == NULL) { + s = e; + } + DEBUG_ASSERT(s >= p); + if (s < p) { + return -3; + } + + /* Decode variable into destination buffer */ + len = mg_url_decode(p, (int)(s - p), dst, (int)dst_len, 1); + + /* Redirect error code from -1 to -2 (destination buffer too + * small). */ + if (len == -1) { + len = -2; + } + break; + } + } + } + + return len; +} + + +/* HCP24: some changes to compare hole var_name */ +int +mg_get_cookie(const char *cookie_header, + const char *var_name, + char *dst, + size_t dst_size) +{ + const char *s, *p, *end; + int name_len, len = -1; + + if ((dst == NULL) || (dst_size == 0)) { + return -2; + } + + dst[0] = '\0'; + if ((var_name == NULL) || ((s = cookie_header) == NULL)) { + return -1; + } + + name_len = (int)strlen(var_name); + end = s + strlen(s); + for (; (s = mg_strcasestr(s, var_name)) != NULL; s += name_len) { + if (s[name_len] == '=') { + /* HCP24: now check is it a substring or a full cookie name */ + if ((s == cookie_header) || (s[-1] == ' ')) { + s += name_len + 1; + if ((p = strchr(s, ' ')) == NULL) { + p = end; + } + if (p[-1] == ';') { + p--; + } + if ((*s == '"') && (p[-1] == '"') && (p > s + 1)) { + s++; + p--; + } + if ((size_t)(p - s) < dst_size) { + len = (int)(p - s); + mg_strlcpy(dst, s, (size_t)len + 1); + } else { + len = -3; + } + break; + } + } + } + return len; +} + + +#if defined(USE_WEBSOCKET) || defined(USE_LUA) +static void +base64_encode(const unsigned char *src, int src_len, char *dst) +{ + static const char *b64 = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + int i, j, a, b, c; + + for (i = j = 0; i < src_len; i += 3) { + a = src[i]; + b = ((i + 1) >= src_len) ? 0 : src[i + 1]; + c = ((i + 2) >= src_len) ? 0 : src[i + 2]; + + dst[j++] = b64[a >> 2]; + dst[j++] = b64[((a & 3) << 4) | (b >> 4)]; + if (i + 1 < src_len) { + dst[j++] = b64[(b & 15) << 2 | (c >> 6)]; + } + if (i + 2 < src_len) { + dst[j++] = b64[c & 63]; + } + } + while (j % 4 != 0) { + dst[j++] = '='; + } + dst[j++] = '\0'; +} +#endif + + +#if defined(USE_LUA) +static unsigned char +b64reverse(char letter) +{ + if ((letter >= 'A') && (letter <= 'Z')) { + return letter - 'A'; + } + if ((letter >= 'a') && (letter <= 'z')) { + return letter - 'a' + 26; + } + if ((letter >= '0') && (letter <= '9')) { + return letter - '0' + 52; + } + if (letter == '+') { + return 62; + } + if (letter == '/') { + return 63; + } + if (letter == '=') { + return 255; /* normal end */ + } + return 254; /* error */ +} + + +static int +base64_decode(const unsigned char *src, int src_len, char *dst, size_t *dst_len) +{ + int i; + unsigned char a, b, c, d; + + *dst_len = 0; + + for (i = 0; i < src_len; i += 4) { + a = b64reverse(src[i]); + if (a >= 254) { + return i; + } + + b = b64reverse(((i + 1) >= src_len) ? 0 : src[i + 1]); + if (b >= 254) { + return i + 1; + } + + c = b64reverse(((i + 2) >= src_len) ? 0 : src[i + 2]); + if (c == 254) { + return i + 2; + } + + d = b64reverse(((i + 3) >= src_len) ? 0 : src[i + 3]); + if (d == 254) { + return i + 3; + } + + dst[(*dst_len)++] = (a << 2) + (b >> 4); + if (c != 255) { + dst[(*dst_len)++] = (b << 4) + (c >> 2); + if (d != 255) { + dst[(*dst_len)++] = (c << 6) + d; + } + } + } + return -1; +} +#endif + + +static int +is_put_or_delete_method(const struct mg_connection *conn) +{ + if (conn) { + const char *s = conn->request_info.request_method; + return (s != NULL) + && (!strcmp(s, "PUT") || !strcmp(s, "DELETE") + || !strcmp(s, "MKCOL") || !strcmp(s, "PATCH")); + } + return 0; +} + + +#if !defined(NO_FILES) +static int +extention_matches_script( + struct mg_connection *conn, /* in: request (must be valid) */ + const char *filename /* in: filename (must be valid) */ +) +{ +#if !defined(NO_CGI) + if (match_prefix(conn->dom_ctx->config[CGI_EXTENSIONS], + strlen(conn->dom_ctx->config[CGI_EXTENSIONS]), + filename) + > 0) { + return 1; + } +#endif +#if defined(USE_LUA) + if (match_prefix(conn->dom_ctx->config[LUA_SCRIPT_EXTENSIONS], + strlen(conn->dom_ctx->config[LUA_SCRIPT_EXTENSIONS]), + filename) + > 0) { + return 1; + } +#endif +#if defined(USE_DUKTAPE) + if (match_prefix(conn->dom_ctx->config[DUKTAPE_SCRIPT_EXTENSIONS], + strlen(conn->dom_ctx->config[DUKTAPE_SCRIPT_EXTENSIONS]), + filename) + > 0) { + return 1; + } +#endif + /* filename and conn could be unused, if all preocessor conditions + * are false (no script language supported). */ + (void)filename; + (void)conn; + + return 0; +} + + +/* For given directory path, substitute it to valid index file. + * Return 1 if index file has been found, 0 if not found. + * If the file is found, it's stats is returned in stp. */ +static int +substitute_index_file(struct mg_connection *conn, + char *path, + size_t path_len, + struct mg_file_stat *filestat) +{ + const char *list = conn->dom_ctx->config[INDEX_FILES]; + struct vec filename_vec; + size_t n = strlen(path); + int found = 0; + + /* The 'path' given to us points to the directory. Remove all trailing + * directory separator characters from the end of the path, and + * then append single directory separator character. */ + while ((n > 0) && (path[n - 1] == '/')) { + n--; + } + path[n] = '/'; + + /* Traverse index files list. For each entry, append it to the given + * path and see if the file exists. If it exists, break the loop */ + while ((list = next_option(list, &filename_vec, NULL)) != NULL) { + /* Ignore too long entries that may overflow path buffer */ + if ((filename_vec.len + 1) > (path_len - (n + 1))) { + continue; + } + + /* Prepare full path to the index file */ + mg_strlcpy(path + n + 1, filename_vec.ptr, filename_vec.len + 1); + + /* Does it exist? */ + if (mg_stat(conn, path, filestat)) { + /* Yes it does, break the loop */ + found = 1; + break; + } + } + + /* If no index file exists, restore directory path */ + if (!found) { + path[n] = '\0'; + } + + return found; +} +#endif + + +static void +interpret_uri(struct mg_connection *conn, /* in/out: request (must be valid) */ + char *filename, /* out: filename */ + size_t filename_buf_len, /* in: size of filename buffer */ + struct mg_file_stat *filestat, /* out: file status structure */ + int *is_found, /* out: file found (directly) */ + int *is_script_resource, /* out: handled by a script? */ + int *is_websocket_request, /* out: websocket connetion? */ + int *is_put_or_delete_request /* out: put/delete a file? */ +) +{ + char const *accept_encoding; + +#if !defined(NO_FILES) + const char *uri = conn->request_info.local_uri; + const char *root = conn->dom_ctx->config[DOCUMENT_ROOT]; + const char *rewrite; + struct vec a, b; + ptrdiff_t match_len; + char gz_path[PATH_MAX]; + int truncated; +#if !defined(NO_CGI) || defined(USE_LUA) || defined(USE_DUKTAPE) + char *tmp_str; + size_t tmp_str_len, sep_pos; + int allow_substitute_script_subresources; +#endif +#else + (void)filename_buf_len; /* unused if NO_FILES is defined */ +#endif + + /* Step 1: Set all initially unknown outputs to zero */ + memset(filestat, 0, sizeof(*filestat)); + *filename = 0; + *is_found = 0; + *is_script_resource = 0; + + /* Step 2: Check if the request attempts to modify the file system */ + *is_put_or_delete_request = is_put_or_delete_method(conn); + +/* Step 3: Check if it is a websocket request, and modify the document + * root if required */ +#if defined(USE_WEBSOCKET) + *is_websocket_request = is_websocket_protocol(conn); +#if !defined(NO_FILES) + if (*is_websocket_request && conn->dom_ctx->config[WEBSOCKET_ROOT]) { + root = conn->dom_ctx->config[WEBSOCKET_ROOT]; + } +#endif /* !NO_FILES */ +#else /* USE_WEBSOCKET */ + *is_websocket_request = 0; +#endif /* USE_WEBSOCKET */ + + /* Step 4: Check if gzip encoded response is allowed */ + conn->accept_gzip = 0; + if ((accept_encoding = mg_get_header(conn, "Accept-Encoding")) != NULL) { + if (strstr(accept_encoding, "gzip") != NULL) { + conn->accept_gzip = 1; + } + } + +#if !defined(NO_FILES) + /* Step 5: If there is no root directory, don't look for files. */ + /* Note that root == NULL is a regular use case here. This occurs, + * if all requests are handled by callbacks, so the WEBSOCKET_ROOT + * config is not required. */ + if (root == NULL) { + /* all file related outputs have already been set to 0, just return + */ + return; + } + + /* Step 6: Determine the local file path from the root path and the + * request uri. */ + /* Using filename_buf_len - 1 because memmove() for PATH_INFO may shift + * part of the path one byte on the right. */ + mg_snprintf( + conn, &truncated, filename, filename_buf_len - 1, "%s%s", root, uri); + + if (truncated) { + goto interpret_cleanup; + } + + /* Step 7: URI rewriting */ + rewrite = conn->dom_ctx->config[URL_REWRITE_PATTERN]; + while ((rewrite = next_option(rewrite, &a, &b)) != NULL) { + if ((match_len = match_prefix(a.ptr, a.len, uri)) > 0) { + mg_snprintf(conn, + &truncated, + filename, + filename_buf_len - 1, + "%.*s%s", + (int)b.len, + b.ptr, + uri + match_len); + break; + } + } + + if (truncated) { + goto interpret_cleanup; + } + + /* Step 8: Check if the file exists at the server */ + /* Local file path and name, corresponding to requested URI + * is now stored in "filename" variable. */ + if (mg_stat(conn, filename, filestat)) { + int uri_len = (int)strlen(uri); + int is_uri_end_slash = (uri_len > 0) && (uri[uri_len - 1] == '/'); + + /* 8.1: File exists. */ + *is_found = 1; + + /* 8.2: Check if it is a script type. */ + if (extention_matches_script(conn, filename)) { + /* The request addresses a CGI resource, Lua script or + * server-side javascript. + * The URI corresponds to the script itself (like + * /path/script.cgi), and there is no additional resource + * path (like /path/script.cgi/something). + * Requests that modify (replace or delete) a resource, like + * PUT and DELETE requests, should replace/delete the script + * file. + * Requests that read or write from/to a resource, like GET and + * POST requests, should call the script and return the + * generated response. */ + *is_script_resource = (!*is_put_or_delete_request); + } + + /* 8.3: If the request target is a directory, there could be + * a substitute file (index.html, index.cgi, ...). */ + if (filestat->is_directory && is_uri_end_slash) { + /* Use a local copy here, since substitute_index_file will + * change the content of the file status */ + struct mg_file_stat tmp_filestat; + memset(&tmp_filestat, 0, sizeof(tmp_filestat)); + + if (substitute_index_file( + conn, filename, filename_buf_len, &tmp_filestat)) { + + /* Substitute file found. Copy stat to the output, then + * check if the file is a script file */ + *filestat = tmp_filestat; + + if (extention_matches_script(conn, filename)) { + /* Substitute file is a script file */ + *is_script_resource = 1; + } else { + /* Substitute file is a regular file */ + *is_script_resource = 0; + *is_found = (mg_stat(conn, filename, filestat) ? 1 : 0); + } + } + /* If there is no substitute file, the server could return + * a directory listing in a later step */ + } + return; + } + + /* Step 9: Check for zipped files: */ + /* If we can't find the actual file, look for the file + * with the same name but a .gz extension. If we find it, + * use that and set the gzipped flag in the file struct + * to indicate that the response need to have the content- + * encoding: gzip header. + * We can only do this if the browser declares support. */ + if (conn->accept_gzip) { + mg_snprintf( + conn, &truncated, gz_path, sizeof(gz_path), "%s.gz", filename); + + if (truncated) { + goto interpret_cleanup; + } + + if (mg_stat(conn, gz_path, filestat)) { + if (filestat) { + filestat->is_gzipped = 1; + *is_found = 1; + } + /* Currently gz files can not be scripts. */ + return; + } + } + +#if !defined(NO_CGI) || defined(USE_LUA) || defined(USE_DUKTAPE) + /* Step 10: Script resources may handle sub-resources */ + /* Support PATH_INFO for CGI scripts. */ + tmp_str_len = strlen(filename); + tmp_str = (char *)mg_malloc_ctx(tmp_str_len + PATH_MAX + 1, conn->phys_ctx); + if (!tmp_str) { + /* Out of memory */ + goto interpret_cleanup; + } + memcpy(tmp_str, filename, tmp_str_len + 1); + + /* Check config, if index scripts may have sub-resources */ + allow_substitute_script_subresources = + !mg_strcasecmp(conn->dom_ctx->config[ALLOW_INDEX_SCRIPT_SUB_RES], + "yes"); + + sep_pos = tmp_str_len; + while (sep_pos > 0) { + sep_pos--; + if (tmp_str[sep_pos] == '/') { + int is_script = 0, does_exist = 0; + + tmp_str[sep_pos] = 0; + if (tmp_str[0]) { + is_script = extention_matches_script(conn, tmp_str); + does_exist = mg_stat(conn, tmp_str, filestat); + } + + if (does_exist && is_script) { + filename[sep_pos] = 0; + memmove(filename + sep_pos + 2, + filename + sep_pos + 1, + strlen(filename + sep_pos + 1) + 1); + conn->path_info = filename + sep_pos + 1; + filename[sep_pos + 1] = '/'; + *is_script_resource = 1; + *is_found = 1; + break; + } + + if (allow_substitute_script_subresources) { + if (substitute_index_file( + conn, tmp_str, tmp_str_len + PATH_MAX, filestat)) { + + /* some intermediate directory has an index file */ + if (extention_matches_script(conn, tmp_str)) { + + char *tmp_str2; + + DEBUG_TRACE("Substitute script %s serving path %s", + tmp_str, + filename); + + /* this index file is a script */ + tmp_str2 = mg_strdup_ctx(filename + sep_pos + 1, + conn->phys_ctx); + mg_snprintf(conn, + &truncated, + filename, + filename_buf_len, + "%s//%s", + tmp_str, + tmp_str2); + mg_free(tmp_str2); + + if (truncated) { + mg_free(tmp_str); + goto interpret_cleanup; + } + sep_pos = strlen(tmp_str); + filename[sep_pos] = 0; + conn->path_info = filename + sep_pos + 1; + *is_script_resource = 1; + *is_found = 1; + break; + + } else { + + DEBUG_TRACE("Substitute file %s serving path %s", + tmp_str, + filename); + + /* non-script files will not have sub-resources */ + filename[sep_pos] = 0; + conn->path_info = 0; + *is_script_resource = 0; + *is_found = 0; + break; + } + } + } + + tmp_str[sep_pos] = '/'; + } + } + + mg_free(tmp_str); + +#endif /* !defined(NO_CGI) || defined(USE_LUA) || defined(USE_DUKTAPE) */ +#endif /* !defined(NO_FILES) */ + return; + +#if !defined(NO_FILES) +/* Reset all outputs */ +interpret_cleanup: + memset(filestat, 0, sizeof(*filestat)); + *filename = 0; + *is_found = 0; + *is_script_resource = 0; + *is_websocket_request = 0; + *is_put_or_delete_request = 0; +#endif /* !defined(NO_FILES) */ +} + + +/* Check whether full request is buffered. Return: + * -1 if request or response is malformed + * 0 if request or response is not yet fully buffered + * >0 actual request length, including last \r\n\r\n */ +static int +get_http_header_len(const char *buf, int buflen) +{ + int i; + for (i = 0; i < buflen; i++) { + /* Do an unsigned comparison in some conditions below */ + const unsigned char c = (unsigned char)buf[i]; + + if ((c < 128) && ((char)c != '\r') && ((char)c != '\n') + && !isprint(c)) { + /* abort scan as soon as one malformed character is found */ + return -1; + } + + if (i < buflen - 1) { + if ((buf[i] == '\n') && (buf[i + 1] == '\n')) { + /* Two newline, no carriage return - not standard compliant, + * but + * it + * should be accepted */ + return i + 2; + } + } + + if (i < buflen - 3) { + if ((buf[i] == '\r') && (buf[i + 1] == '\n') && (buf[i + 2] == '\r') + && (buf[i + 3] == '\n')) { + /* Two \r\n - standard compliant */ + return i + 4; + } + } + } + + return 0; +} + + +#if !defined(NO_CACHING) +/* Convert month to the month number. Return -1 on error, or month number */ +static int +get_month_index(const char *s) +{ + size_t i; + + for (i = 0; i < ARRAY_SIZE(month_names); i++) { + if (!strcmp(s, month_names[i])) { + return (int)i; + } + } + + return -1; +} + + +/* Parse UTC date-time string, and return the corresponding time_t value. */ +static time_t +parse_date_string(const char *datetime) +{ + char month_str[32] = {0}; + int second, minute, hour, day, month, year; + time_t result = (time_t)0; + struct tm tm; + + if ((sscanf(datetime, + "%d/%3s/%d %d:%d:%d", + &day, + month_str, + &year, + &hour, + &minute, + &second) + == 6) + || (sscanf(datetime, + "%d %3s %d %d:%d:%d", + &day, + month_str, + &year, + &hour, + &minute, + &second) + == 6) + || (sscanf(datetime, + "%*3s, %d %3s %d %d:%d:%d", + &day, + month_str, + &year, + &hour, + &minute, + &second) + == 6) + || (sscanf(datetime, + "%d-%3s-%d %d:%d:%d", + &day, + month_str, + &year, + &hour, + &minute, + &second) + == 6)) { + month = get_month_index(month_str); + if ((month >= 0) && (year >= 1970)) { + memset(&tm, 0, sizeof(tm)); + tm.tm_year = year - 1900; + tm.tm_mon = month; + tm.tm_mday = day; + tm.tm_hour = hour; + tm.tm_min = minute; + tm.tm_sec = second; + result = timegm(&tm); + } + } + + return result; +} +#endif /* !NO_CACHING */ + + +/* Protect against directory disclosure attack by removing '..', + * excessive '/' and '\' characters */ +static void +remove_double_dots_and_double_slashes(char *s) +{ + char *p = s; + + while ((s[0] == '.') && (s[1] == '.')) { + s++; + } + + while (*s != '\0') { + *p++ = *s++; + if ((s[-1] == '/') || (s[-1] == '\\')) { + /* Skip all following slashes, backslashes and double-dots */ + while (s[0] != '\0') { + if ((s[0] == '/') || (s[0] == '\\')) { + s++; + } else if ((s[0] == '.') && (s[1] == '.')) { + s += 2; + } else { + break; + } + } + } + } + *p = '\0'; +} + + +static const struct { + const char *extension; + size_t ext_len; + const char *mime_type; +} builtin_mime_types[] = { + /* IANA registered MIME types + * (http://www.iana.org/assignments/media-types) + * application types */ + {".doc", 4, "application/msword"}, + {".eps", 4, "application/postscript"}, + {".exe", 4, "application/octet-stream"}, + {".js", 3, "application/javascript"}, + {".json", 5, "application/json"}, + {".pdf", 4, "application/pdf"}, + {".ps", 3, "application/postscript"}, + {".rtf", 4, "application/rtf"}, + {".xhtml", 6, "application/xhtml+xml"}, + {".xsl", 4, "application/xml"}, + {".xslt", 5, "application/xml"}, + + /* fonts */ + {".ttf", 4, "application/font-sfnt"}, + {".cff", 4, "application/font-sfnt"}, + {".otf", 4, "application/font-sfnt"}, + {".aat", 4, "application/font-sfnt"}, + {".sil", 4, "application/font-sfnt"}, + {".pfr", 4, "application/font-tdpfr"}, + {".woff", 5, "application/font-woff"}, + + /* audio */ + {".mp3", 4, "audio/mpeg"}, + {".oga", 4, "audio/ogg"}, + {".ogg", 4, "audio/ogg"}, + + /* image */ + {".gif", 4, "image/gif"}, + {".ief", 4, "image/ief"}, + {".jpeg", 5, "image/jpeg"}, + {".jpg", 4, "image/jpeg"}, + {".jpm", 4, "image/jpm"}, + {".jpx", 4, "image/jpx"}, + {".png", 4, "image/png"}, + {".svg", 4, "image/svg+xml"}, + {".tif", 4, "image/tiff"}, + {".tiff", 5, "image/tiff"}, + + /* model */ + {".wrl", 4, "model/vrml"}, + + /* text */ + {".css", 4, "text/css"}, + {".csv", 4, "text/csv"}, + {".htm", 4, "text/html"}, + {".html", 5, "text/html"}, + {".sgm", 4, "text/sgml"}, + {".shtm", 5, "text/html"}, + {".shtml", 6, "text/html"}, + {".txt", 4, "text/plain"}, + {".xml", 4, "text/xml"}, + + /* video */ + {".mov", 4, "video/quicktime"}, + {".mp4", 4, "video/mp4"}, + {".mpeg", 5, "video/mpeg"}, + {".mpg", 4, "video/mpeg"}, + {".ogv", 4, "video/ogg"}, + {".qt", 3, "video/quicktime"}, + + /* not registered types + * (http://reference.sitepoint.com/html/mime-types-full, + * http://www.hansenb.pdx.edu/DMKB/dict/tutorials/mime_typ.php, ..) */ + {".arj", 4, "application/x-arj-compressed"}, + {".gz", 3, "application/x-gunzip"}, + {".rar", 4, "application/x-arj-compressed"}, + {".swf", 4, "application/x-shockwave-flash"}, + {".tar", 4, "application/x-tar"}, + {".tgz", 4, "application/x-tar-gz"}, + {".torrent", 8, "application/x-bittorrent"}, + {".ppt", 4, "application/x-mspowerpoint"}, + {".xls", 4, "application/x-msexcel"}, + {".zip", 4, "application/x-zip-compressed"}, + {".aac", + 4, + "audio/aac"}, /* http://en.wikipedia.org/wiki/Advanced_Audio_Coding */ + {".aif", 4, "audio/x-aif"}, + {".m3u", 4, "audio/x-mpegurl"}, + {".mid", 4, "audio/x-midi"}, + {".ra", 3, "audio/x-pn-realaudio"}, + {".ram", 4, "audio/x-pn-realaudio"}, + {".wav", 4, "audio/x-wav"}, + {".bmp", 4, "image/bmp"}, + {".ico", 4, "image/x-icon"}, + {".pct", 4, "image/x-pct"}, + {".pict", 5, "image/pict"}, + {".rgb", 4, "image/x-rgb"}, + {".webm", 5, "video/webm"}, /* http://en.wikipedia.org/wiki/WebM */ + {".asf", 4, "video/x-ms-asf"}, + {".avi", 4, "video/x-msvideo"}, + {".m4v", 4, "video/x-m4v"}, + {NULL, 0, NULL}}; + + +const char * +mg_get_builtin_mime_type(const char *path) +{ + const char *ext; + size_t i, path_len; + + path_len = strlen(path); + + for (i = 0; builtin_mime_types[i].extension != NULL; i++) { + ext = path + (path_len - builtin_mime_types[i].ext_len); + if ((path_len > builtin_mime_types[i].ext_len) + && (mg_strcasecmp(ext, builtin_mime_types[i].extension) == 0)) { + return builtin_mime_types[i].mime_type; + } + } + + return "text/plain"; +} + + +/* Look at the "path" extension and figure what mime type it has. + * Store mime type in the vector. */ +static void +get_mime_type(struct mg_connection *conn, const char *path, struct vec *vec) +{ + struct vec ext_vec, mime_vec; + const char *list, *ext; + size_t path_len; + + path_len = strlen(path); + + if ((conn == NULL) || (vec == NULL)) { + if (vec != NULL) { + memset(vec, '\0', sizeof(struct vec)); + } + return; + } + + /* Scan user-defined mime types first, in case user wants to + * override default mime types. */ + list = conn->dom_ctx->config[EXTRA_MIME_TYPES]; + while ((list = next_option(list, &ext_vec, &mime_vec)) != NULL) { + /* ext now points to the path suffix */ + ext = path + path_len - ext_vec.len; + if (mg_strncasecmp(ext, ext_vec.ptr, ext_vec.len) == 0) { + *vec = mime_vec; + return; + } + } + + vec->ptr = mg_get_builtin_mime_type(path); + vec->len = strlen(vec->ptr); +} + + +/* Stringify binary data. Output buffer must be twice as big as input, + * because each byte takes 2 bytes in string representation */ +static void +bin2str(char *to, const unsigned char *p, size_t len) +{ + static const char *hex = "0123456789abcdef"; + + for (; len--; p++) { + *to++ = hex[p[0] >> 4]; + *to++ = hex[p[0] & 0x0f]; + } + *to = '\0'; +} + + +/* Return stringified MD5 hash for list of strings. Buffer must be 33 bytes. + */ +char * +mg_md5(char buf[33], ...) +{ + md5_byte_t hash[16]; + const char *p; + va_list ap; + md5_state_t ctx; + + md5_init(&ctx); + + va_start(ap, buf); + while ((p = va_arg(ap, const char *)) != NULL) { + md5_append(&ctx, (const md5_byte_t *)p, strlen(p)); + } + va_end(ap); + + md5_finish(&ctx, hash); + bin2str(buf, hash, sizeof(hash)); + return buf; +} + + +/* Check the user's password, return 1 if OK */ +static int +check_password(const char *method, + const char *ha1, + const char *uri, + const char *nonce, + const char *nc, + const char *cnonce, + const char *qop, + const char *response) +{ + char ha2[32 + 1], expected_response[32 + 1]; + + /* Some of the parameters may be NULL */ + if ((method == NULL) || (nonce == NULL) || (nc == NULL) || (cnonce == NULL) + || (qop == NULL) || (response == NULL)) { + return 0; + } + + /* NOTE(lsm): due to a bug in MSIE, we do not compare the URI */ + if (strlen(response) != 32) { + return 0; + } + + mg_md5(ha2, method, ":", uri, NULL); + mg_md5(expected_response, + ha1, + ":", + nonce, + ":", + nc, + ":", + cnonce, + ":", + qop, + ":", + ha2, + NULL); + + return mg_strcasecmp(response, expected_response) == 0; +} + + +#if !defined(NO_FILESYSTEMS) +/* Use the global passwords file, if specified by auth_gpass option, + * or search for .htpasswd in the requested directory. */ +static void +open_auth_file(struct mg_connection *conn, + const char *path, + struct mg_file *filep) +{ + if ((conn != NULL) && (conn->dom_ctx != NULL)) { + char name[PATH_MAX]; + const char *p, *e, + *gpass = conn->dom_ctx->config[GLOBAL_PASSWORDS_FILE]; + int truncated; + + if (gpass != NULL) { + /* Use global passwords file */ + if (!mg_fopen(conn, gpass, MG_FOPEN_MODE_READ, filep)) { +#if defined(DEBUG) + /* Use mg_cry_internal here, since gpass has been configured. */ + mg_cry_internal(conn, "fopen(%s): %s", gpass, strerror(ERRNO)); +#endif + } + /* Important: using local struct mg_file to test path for + * is_directory flag. If filep is used, mg_stat() makes it + * appear as if auth file was opened. + * TODO(mid): Check if this is still required after rewriting + * mg_stat */ + } else if (mg_stat(conn, path, &filep->stat) + && filep->stat.is_directory) { + mg_snprintf(conn, + &truncated, + name, + sizeof(name), + "%s/%s", + path, + PASSWORDS_FILE_NAME); + + if (truncated || !mg_fopen(conn, name, MG_FOPEN_MODE_READ, filep)) { +#if defined(DEBUG) + /* Don't use mg_cry_internal here, but only a trace, since this + * is + * a typical case. It will occur for every directory + * without a password file. */ + DEBUG_TRACE("fopen(%s): %s", name, strerror(ERRNO)); +#endif + } + } else { + /* Try to find .htpasswd in requested directory. */ + for (p = path, e = p + strlen(p) - 1; e > p; e--) { + if (e[0] == '/') { + break; + } + } + mg_snprintf(conn, + &truncated, + name, + sizeof(name), + "%.*s/%s", + (int)(e - p), + p, + PASSWORDS_FILE_NAME); + + if (truncated || !mg_fopen(conn, name, MG_FOPEN_MODE_READ, filep)) { +#if defined(DEBUG) + /* Don't use mg_cry_internal here, but only a trace, since this + * is + * a typical case. It will occur for every directory + * without a password file. */ + DEBUG_TRACE("fopen(%s): %s", name, strerror(ERRNO)); +#endif + } + } + } +} +#endif /* NO_FILESYSTEMS */ + + +/* Parsed Authorization header */ +struct ah { + char *user, *uri, *cnonce, *response, *qop, *nc, *nonce; +}; + + +/* Return 1 on success. Always initializes the ah structure. */ +static int +parse_auth_header(struct mg_connection *conn, + char *buf, + size_t buf_size, + struct ah *ah) +{ + char *name, *value, *s; + const char *auth_header; + uint64_t nonce; + + if (!ah || !conn) { + return 0; + } + + (void)memset(ah, 0, sizeof(*ah)); + if (((auth_header = mg_get_header(conn, "Authorization")) == NULL) + || mg_strncasecmp(auth_header, "Digest ", 7) != 0) { + return 0; + } + + /* Make modifiable copy of the auth header */ + (void)mg_strlcpy(buf, auth_header + 7, buf_size); + s = buf; + + /* Parse authorization header */ + for (;;) { + /* Gobble initial spaces */ + while (isspace((unsigned char)*s)) { + s++; + } + name = skip_quoted(&s, "=", " ", 0); + /* Value is either quote-delimited, or ends at first comma or space. + */ + if (s[0] == '\"') { + s++; + value = skip_quoted(&s, "\"", " ", '\\'); + if (s[0] == ',') { + s++; + } + } else { + value = skip_quoted(&s, ", ", " ", 0); /* IE uses commas, FF uses + * spaces */ + } + if (*name == '\0') { + break; + } + + if (!strcmp(name, "username")) { + ah->user = value; + } else if (!strcmp(name, "cnonce")) { + ah->cnonce = value; + } else if (!strcmp(name, "response")) { + ah->response = value; + } else if (!strcmp(name, "uri")) { + ah->uri = value; + } else if (!strcmp(name, "qop")) { + ah->qop = value; + } else if (!strcmp(name, "nc")) { + ah->nc = value; + } else if (!strcmp(name, "nonce")) { + ah->nonce = value; + } + } + +#if !defined(NO_NONCE_CHECK) + /* Read the nonce from the response. */ + if (ah->nonce == NULL) { + return 0; + } + s = NULL; + nonce = strtoull(ah->nonce, &s, 10); + if ((s == NULL) || (*s != 0)) { + return 0; + } + + /* Convert the nonce from the client to a number. */ + nonce ^= conn->dom_ctx->auth_nonce_mask; + + /* The converted number corresponds to the time the nounce has been + * created. This should not be earlier than the server start. */ + /* Server side nonce check is valuable in all situations but one: + * if the server restarts frequently, but the client should not see + * that, so the server should accept nonces from previous starts. */ + /* However, the reasonable default is to not accept a nonce from a + * previous start, so if anyone changed the access rights between + * two restarts, a new login is required. */ + if (nonce < (uint64_t)conn->phys_ctx->start_time) { + /* nonce is from a previous start of the server and no longer valid + * (replay attack?) */ + return 0; + } + /* Check if the nonce is too high, so it has not (yet) been used by the + * server. */ + if (nonce >= ((uint64_t)conn->phys_ctx->start_time + + conn->dom_ctx->nonce_count)) { + return 0; + } +#else + (void)nonce; +#endif + + /* CGI needs it as REMOTE_USER */ + if (ah->user != NULL) { + conn->request_info.remote_user = + mg_strdup_ctx(ah->user, conn->phys_ctx); + } else { + return 0; + } + + return 1; +} + + +static const char * +mg_fgets(char *buf, size_t size, struct mg_file *filep, char **p) +{ +#if defined(MG_USE_OPEN_FILE) + const char *eof; + size_t len; + const char *memend; +#else + (void)p; /* parameter is unused */ +#endif + + if (!filep) { + return NULL; + } + +#if defined(MG_USE_OPEN_FILE) + if ((filep->access.membuf != NULL) && (*p != NULL)) { + memend = (const char *)&filep->access.membuf[filep->stat.size]; + /* Search for \n from p till the end of stream */ + eof = (char *)memchr(*p, '\n', (size_t)(memend - *p)); + if (eof != NULL) { + eof += 1; /* Include \n */ + } else { + eof = memend; /* Copy remaining data */ + } + len = + ((size_t)(eof - *p) > (size - 1)) ? (size - 1) : (size_t)(eof - *p); + memcpy(buf, *p, len); + buf[len] = '\0'; + *p += len; + return len ? eof : NULL; + } else /* filep->access.fp block below */ +#endif + if (filep->access.fp != NULL) { + return fgets(buf, (int)size, filep->access.fp); + } else { + return NULL; + } +} + +/* Define the initial recursion depth for procesesing htpasswd files that + * include other htpasswd + * (or even the same) files. It is not difficult to provide a file or files + * s.t. they force civetweb + * to infinitely recurse and then crash. + */ +#define INITIAL_DEPTH 9 +#if INITIAL_DEPTH <= 0 +#error Bad INITIAL_DEPTH for recursion, set to at least 1 +#endif + +#if !defined(NO_FILESYSTEMS) +struct read_auth_file_struct { + struct mg_connection *conn; + struct ah ah; + const char *domain; + char buf[256 + 256 + 40]; + const char *f_user; + const char *f_domain; + const char *f_ha1; +}; + + +static int +read_auth_file(struct mg_file *filep, + struct read_auth_file_struct *workdata, + int depth) +{ + char *p = NULL /* init if MG_USE_OPEN_FILE is not set */; + int is_authorized = 0; + struct mg_file fp; + size_t l; + + if (!filep || !workdata || (0 == depth)) { + return 0; + } + +/* Loop over passwords file */ +#if defined(MG_USE_OPEN_FILE) + p = (char *)filep->access.membuf; +#endif + while (mg_fgets(workdata->buf, sizeof(workdata->buf), filep, &p) != NULL) { + l = strlen(workdata->buf); + while (l > 0) { + if (isspace((unsigned char)workdata->buf[l - 1]) + || iscntrl((unsigned char)workdata->buf[l - 1])) { + l--; + workdata->buf[l] = 0; + } else + break; + } + if (l < 1) { + continue; + } + + workdata->f_user = workdata->buf; + + if (workdata->f_user[0] == ':') { + /* user names may not contain a ':' and may not be empty, + * so lines starting with ':' may be used for a special purpose + */ + if (workdata->f_user[1] == '#') { + /* :# is a comment */ + continue; + } else if (!strncmp(workdata->f_user + 1, "include=", 8)) { + if (mg_fopen(workdata->conn, + workdata->f_user + 9, + MG_FOPEN_MODE_READ, + &fp)) { + is_authorized = read_auth_file(&fp, workdata, depth - 1); + (void)mg_fclose( + &fp.access); /* ignore error on read only file */ + + /* No need to continue processing files once we have a + * match, since nothing will reset it back + * to 0. + */ + if (is_authorized) { + return is_authorized; + } + } else { + mg_cry_internal(workdata->conn, + "%s: cannot open authorization file: %s", + __func__, + workdata->buf); + } + continue; + } + /* everything is invalid for the moment (might change in the + * future) */ + mg_cry_internal(workdata->conn, + "%s: syntax error in authorization file: %s", + __func__, + workdata->buf); + continue; + } + + workdata->f_domain = strchr(workdata->f_user, ':'); + if (workdata->f_domain == NULL) { + mg_cry_internal(workdata->conn, + "%s: syntax error in authorization file: %s", + __func__, + workdata->buf); + continue; + } + *(char *)(workdata->f_domain) = 0; + (workdata->f_domain)++; + + workdata->f_ha1 = strchr(workdata->f_domain, ':'); + if (workdata->f_ha1 == NULL) { + mg_cry_internal(workdata->conn, + "%s: syntax error in authorization file: %s", + __func__, + workdata->buf); + continue; + } + *(char *)(workdata->f_ha1) = 0; + (workdata->f_ha1)++; + + if (!strcmp(workdata->ah.user, workdata->f_user) + && !strcmp(workdata->domain, workdata->f_domain)) { + return check_password(workdata->conn->request_info.request_method, + workdata->f_ha1, + workdata->ah.uri, + workdata->ah.nonce, + workdata->ah.nc, + workdata->ah.cnonce, + workdata->ah.qop, + workdata->ah.response); + } + } + + return is_authorized; +} + + +/* Authorize against the opened passwords file. Return 1 if authorized. */ +static int +authorize(struct mg_connection *conn, struct mg_file *filep, const char *realm) +{ + struct read_auth_file_struct workdata; + char buf[MG_BUF_LEN]; + + if (!conn || !conn->dom_ctx) { + return 0; + } + + memset(&workdata, 0, sizeof(workdata)); + workdata.conn = conn; + + if (!parse_auth_header(conn, buf, sizeof(buf), &workdata.ah)) { + return 0; + } + + if (realm) { + workdata.domain = realm; + } else { + workdata.domain = conn->dom_ctx->config[AUTHENTICATION_DOMAIN]; + } + + return read_auth_file(filep, &workdata, INITIAL_DEPTH); +} + + +/* Public function to check http digest authentication header */ +int +mg_check_digest_access_authentication(struct mg_connection *conn, + const char *realm, + const char *filename) +{ + struct mg_file file = STRUCT_FILE_INITIALIZER; + int auth; + + if (!conn || !filename) { + return -1; + } + if (!mg_fopen(conn, filename, MG_FOPEN_MODE_READ, &file)) { + return -2; + } + + auth = authorize(conn, &file, realm); + + mg_fclose(&file.access); + + return auth; +} +#endif /* NO_FILESYSTEMS */ + + +/* Return 1 if request is authorised, 0 otherwise. */ +static int +check_authorization(struct mg_connection *conn, const char *path) +{ +#if !defined(NO_FILESYSTEMS) + char fname[PATH_MAX]; + struct vec uri_vec, filename_vec; + const char *list; + struct mg_file file = STRUCT_FILE_INITIALIZER; + int authorized = 1, truncated; + + if (!conn || !conn->dom_ctx) { + return 0; + } + + list = conn->dom_ctx->config[PROTECT_URI]; + while ((list = next_option(list, &uri_vec, &filename_vec)) != NULL) { + if (!memcmp(conn->request_info.local_uri, uri_vec.ptr, uri_vec.len)) { + mg_snprintf(conn, + &truncated, + fname, + sizeof(fname), + "%.*s", + (int)filename_vec.len, + filename_vec.ptr); + + if (truncated + || !mg_fopen(conn, fname, MG_FOPEN_MODE_READ, &file)) { + mg_cry_internal(conn, + "%s: cannot open %s: %s", + __func__, + fname, + strerror(errno)); + } + break; + } + } + + if (!is_file_opened(&file.access)) { + open_auth_file(conn, path, &file); + } + + if (is_file_opened(&file.access)) { + authorized = authorize(conn, &file, NULL); + (void)mg_fclose(&file.access); /* ignore error on read only file */ + } + + return authorized; +#else + (void)conn; + (void)path; + return 1; +#endif /* NO_FILESYSTEMS */ +} + + +/* Internal function. Assumes conn is valid */ +static void +send_authorization_request(struct mg_connection *conn, const char *realm) +{ + char date[64]; + time_t curtime = time(NULL); + uint64_t nonce = (uint64_t)(conn->phys_ctx->start_time); + + if (!realm) { + realm = conn->dom_ctx->config[AUTHENTICATION_DOMAIN]; + } + + (void)pthread_mutex_lock(&conn->phys_ctx->nonce_mutex); + nonce += conn->dom_ctx->nonce_count; + ++conn->dom_ctx->nonce_count; + (void)pthread_mutex_unlock(&conn->phys_ctx->nonce_mutex); + + nonce ^= conn->dom_ctx->auth_nonce_mask; + conn->status_code = 401; + conn->must_close = 1; + + gmt_time_string(date, sizeof(date), &curtime); + + mg_printf(conn, "HTTP/1.1 401 Unauthorized\r\n"); + send_no_cache_header(conn); + send_additional_header(conn); + mg_printf(conn, + "Date: %s\r\n" + "Connection: %s\r\n" + "Content-Length: 0\r\n" + "WWW-Authenticate: Digest qop=\"auth\", realm=\"%s\", " + "nonce=\"%" UINT64_FMT "\"\r\n\r\n", + date, + suggest_connection_header(conn), + realm, + nonce); +} + + +/* Interface function. Parameters are provided by the user, so do + * at least some basic checks. + */ +int +mg_send_digest_access_authentication_request(struct mg_connection *conn, + const char *realm) +{ + if (conn && conn->dom_ctx) { + send_authorization_request(conn, realm); + return 0; + } + return -1; +} + + +#if !defined(NO_FILES) +static int +is_authorized_for_put(struct mg_connection *conn) +{ + if (conn) { + struct mg_file file = STRUCT_FILE_INITIALIZER; + const char *passfile = conn->dom_ctx->config[PUT_DELETE_PASSWORDS_FILE]; + int ret = 0; + + if (passfile != NULL + && mg_fopen(conn, passfile, MG_FOPEN_MODE_READ, &file)) { + ret = authorize(conn, &file, NULL); + (void)mg_fclose(&file.access); /* ignore error on read only file */ + } + + return ret; + } + return 0; +} +#endif + + +int +mg_modify_passwords_file(const char *fname, + const char *domain, + const char *user, + const char *pass) +{ + int found, i; + char line[512], u[512] = "", d[512] = "", ha1[33], tmp[PATH_MAX + 8]; + FILE *fp, *fp2; + + found = 0; + fp = fp2 = NULL; + + /* Regard empty password as no password - remove user record. */ + if ((pass != NULL) && (pass[0] == '\0')) { + pass = NULL; + } + + /* Other arguments must not be empty */ + if ((fname == NULL) || (domain == NULL) || (user == NULL)) { + return 0; + } + + /* Using the given file format, user name and domain must not contain + * ':' + */ + if (strchr(user, ':') != NULL) { + return 0; + } + if (strchr(domain, ':') != NULL) { + return 0; + } + + /* Do not allow control characters like newline in user name and domain. + * Do not allow excessively long names either. */ + for (i = 0; ((i < 255) && (user[i] != 0)); i++) { + if (iscntrl((unsigned char)user[i])) { + return 0; + } + } + if (user[i]) { + return 0; + } + for (i = 0; ((i < 255) && (domain[i] != 0)); i++) { + if (iscntrl((unsigned char)domain[i])) { + return 0; + } + } + if (domain[i]) { + return 0; + } + + /* The maximum length of the path to the password file is limited */ + if ((strlen(fname) + 4) >= PATH_MAX) { + return 0; + } + + /* Create a temporary file name. Length has been checked before. */ + strcpy(tmp, fname); + strcat(tmp, ".tmp"); + + /* Create the file if does not exist */ + /* Use of fopen here is OK, since fname is only ASCII */ + if ((fp = fopen(fname, "a+")) != NULL) { + (void)fclose(fp); + } + + /* Open the given file and temporary file */ + if ((fp = fopen(fname, "r")) == NULL) { + return 0; + } else if ((fp2 = fopen(tmp, "w+")) == NULL) { + fclose(fp); + return 0; + } + + /* Copy the stuff to temporary file */ + while (fgets(line, sizeof(line), fp) != NULL) { + if (sscanf(line, "%255[^:]:%255[^:]:%*s", u, d) != 2) { + continue; + } + u[255] = 0; + d[255] = 0; + + if (!strcmp(u, user) && !strcmp(d, domain)) { + found++; + if (pass != NULL) { + mg_md5(ha1, user, ":", domain, ":", pass, NULL); + fprintf(fp2, "%s:%s:%s\n", user, domain, ha1); + } + } else { + fprintf(fp2, "%s", line); + } + } + + /* If new user, just add it */ + if (!found && (pass != NULL)) { + mg_md5(ha1, user, ":", domain, ":", pass, NULL); + fprintf(fp2, "%s:%s:%s\n", user, domain, ha1); + } + + /* Close files */ + fclose(fp); + fclose(fp2); + + /* Put the temp file in place of real file */ + IGNORE_UNUSED_RESULT(remove(fname)); + IGNORE_UNUSED_RESULT(rename(tmp, fname)); + + return 1; +} + + +static int +is_valid_port(unsigned long port) +{ + return (port <= 0xffff); +} + + +static int +mg_inet_pton(int af, const char *src, void *dst, size_t dstlen) +{ + struct addrinfo hints, *res, *ressave; + int func_ret = 0; + int gai_ret; + + memset(&hints, 0, sizeof(struct addrinfo)); + hints.ai_family = af; + + gai_ret = getaddrinfo(src, NULL, &hints, &res); + if (gai_ret != 0) { + /* gai_strerror could be used to convert gai_ret to a string */ + /* POSIX return values: see + * http://pubs.opengroup.org/onlinepubs/9699919799/functions/freeaddrinfo.html + */ + /* Windows return values: see + * https://msdn.microsoft.com/en-us/library/windows/desktop/ms738520%28v=vs.85%29.aspx + */ + return 0; + } + + ressave = res; + + while (res) { + if (dstlen >= (size_t)res->ai_addrlen) { + memcpy(dst, res->ai_addr, res->ai_addrlen); + func_ret = 1; + } + res = res->ai_next; + } + + freeaddrinfo(ressave); + return func_ret; +} + + +static int +connect_socket(struct mg_context *ctx /* may be NULL */, + const char *host, + int port, + int use_ssl, + char *ebuf, + size_t ebuf_len, + SOCKET *sock /* output: socket, must not be NULL */, + union usa *sa /* output: socket address, must not be NULL */ +) +{ + int ip_ver = 0; + int conn_ret = -1; + int sockerr = 0; + *sock = INVALID_SOCKET; + memset(sa, 0, sizeof(*sa)); + + if (ebuf_len > 0) { + *ebuf = 0; + } + + if (host == NULL) { + mg_snprintf(NULL, + NULL, /* No truncation check for ebuf */ + ebuf, + ebuf_len, + "%s", + "NULL host"); + return 0; + } + + if ((port <= 0) || !is_valid_port((unsigned)port)) { + mg_snprintf(NULL, + NULL, /* No truncation check for ebuf */ + ebuf, + ebuf_len, + "%s", + "invalid port"); + return 0; + } + +#if !defined(NO_SSL) +#if !defined(NO_SSL_DL) +#if defined(OPENSSL_API_1_1) + if (use_ssl && (TLS_client_method == NULL)) { + mg_snprintf(NULL, + NULL, /* No truncation check for ebuf */ + ebuf, + ebuf_len, + "%s", + "SSL is not initialized"); + return 0; + } +#else + if (use_ssl && (SSLv23_client_method == NULL)) { + mg_snprintf(NULL, + NULL, /* No truncation check for ebuf */ + ebuf, + ebuf_len, + "%s", + "SSL is not initialized"); + return 0; + } + +#endif /* OPENSSL_API_1_1 */ +#else + (void)use_ssl; +#endif /* NO_SSL_DL */ +#else + (void)use_ssl; +#endif /* !defined(NO_SSL) */ + + if (mg_inet_pton(AF_INET, host, &sa->sin, sizeof(sa->sin))) { + sa->sin.sin_family = AF_INET; + sa->sin.sin_port = htons((uint16_t)port); + ip_ver = 4; +#if defined(USE_IPV6) + } else if (mg_inet_pton(AF_INET6, host, &sa->sin6, sizeof(sa->sin6))) { + sa->sin6.sin6_family = AF_INET6; + sa->sin6.sin6_port = htons((uint16_t)port); + ip_ver = 6; + } else if (host[0] == '[') { + /* While getaddrinfo on Windows will work with [::1], + * getaddrinfo on Linux only works with ::1 (without []). */ + size_t l = strlen(host + 1); + char *h = (l > 1) ? mg_strdup_ctx(host + 1, ctx) : NULL; + if (h) { + h[l - 1] = 0; + if (mg_inet_pton(AF_INET6, h, &sa->sin6, sizeof(sa->sin6))) { + sa->sin6.sin6_family = AF_INET6; + sa->sin6.sin6_port = htons((uint16_t)port); + ip_ver = 6; + } + mg_free(h); + } +#endif + } + + if (ip_ver == 0) { + mg_snprintf(NULL, + NULL, /* No truncation check for ebuf */ + ebuf, + ebuf_len, + "%s", + "host not found"); + return 0; + } + + if (ip_ver == 4) { + *sock = socket(PF_INET, SOCK_STREAM, 0); + } +#if defined(USE_IPV6) + else if (ip_ver == 6) { + *sock = socket(PF_INET6, SOCK_STREAM, 0); + } +#endif + + if (*sock == INVALID_SOCKET) { + mg_snprintf(NULL, + NULL, /* No truncation check for ebuf */ + ebuf, + ebuf_len, + "socket(): %s", + strerror(ERRNO)); + return 0; + } + + if (0 != set_non_blocking_mode(*sock)) { + mg_snprintf(NULL, + NULL, /* No truncation check for ebuf */ + ebuf, + ebuf_len, + "Cannot set socket to non-blocking: %s", + strerror(ERRNO)); + closesocket(*sock); + *sock = INVALID_SOCKET; + return 0; + } + + set_close_on_exec(*sock, NULL, ctx); + + if (ip_ver == 4) { + /* connected with IPv4 */ + conn_ret = connect(*sock, + (struct sockaddr *)((void *)&sa->sin), + sizeof(sa->sin)); + } +#if defined(USE_IPV6) + else if (ip_ver == 6) { + /* connected with IPv6 */ + conn_ret = connect(*sock, + (struct sockaddr *)((void *)&sa->sin6), + sizeof(sa->sin6)); + } +#endif + + if (conn_ret != 0) { + sockerr = ERRNO; + } + +#if defined(_WIN32) + if ((conn_ret != 0) && (sockerr == WSAEWOULDBLOCK)) { +#else + if ((conn_ret != 0) && (sockerr == EINPROGRESS)) { +#endif + /* Data for getsockopt */ + void *psockerr = &sockerr; + int ret; + +#if defined(_WIN32) + int len = (int)sizeof(sockerr); +#else + socklen_t len = (socklen_t)sizeof(sockerr); +#endif + + /* Data for poll */ + struct mg_pollfd pfd[1]; + int pollres; + int ms_wait = 10000; /* 10 second timeout */ + int nonstop = 0; + + /* For a non-blocking socket, the connect sequence is: + * 1) call connect (will not block) + * 2) wait until the socket is ready for writing (select or poll) + * 3) check connection state with getsockopt + */ + pfd[0].fd = *sock; + pfd[0].events = POLLOUT; + pollres = mg_poll(pfd, 1, ms_wait, ctx ? &(ctx->stop_flag) : &nonstop); + + if (pollres != 1) { + /* Not connected */ + mg_snprintf(NULL, + NULL, /* No truncation check for ebuf */ + ebuf, + ebuf_len, + "connect(%s:%d): timeout", + host, + port); + closesocket(*sock); + *sock = INVALID_SOCKET; + return 0; + } + +#if defined(_WIN32) + ret = getsockopt(*sock, SOL_SOCKET, SO_ERROR, (char *)psockerr, &len); +#else + ret = getsockopt(*sock, SOL_SOCKET, SO_ERROR, psockerr, &len); +#endif + + if ((ret == 0) && (sockerr == 0)) { + conn_ret = 0; + } + } + + if (conn_ret != 0) { + /* Not connected */ + mg_snprintf(NULL, + NULL, /* No truncation check for ebuf */ + ebuf, + ebuf_len, + "connect(%s:%d): error %s", + host, + port, + strerror(sockerr)); + closesocket(*sock); + *sock = INVALID_SOCKET; + return 0; + } + + return 1; +} + + +int +mg_url_encode(const char *src, char *dst, size_t dst_len) +{ + static const char *dont_escape = "._-$,;~()"; + static const char *hex = "0123456789abcdef"; + char *pos = dst; + const char *end = dst + dst_len - 1; + + for (; ((*src != '\0') && (pos < end)); src++, pos++) { + if (isalnum((unsigned char)*src) + || (strchr(dont_escape, *src) != NULL)) { + *pos = *src; + } else if (pos + 2 < end) { + pos[0] = '%'; + pos[1] = hex[(unsigned char)*src >> 4]; + pos[2] = hex[(unsigned char)*src & 0xf]; + pos += 2; + } else { + break; + } + } + + *pos = '\0'; + return (*src == '\0') ? (int)(pos - dst) : -1; +} + +/* Return 0 on success, non-zero if an error occurs. */ + +static int +print_dir_entry(struct de *de) +{ + size_t namesize, escsize, i; + char *href, *esc, *p; + char size[64], mod[64]; +#if defined(REENTRANT_TIME) + struct tm _tm; + struct tm *tm = &_tm; +#else + struct tm *tm; +#endif + + /* Estimate worst case size for encoding and escaping */ + namesize = strlen(de->file_name) + 1; + escsize = de->file_name[strcspn(de->file_name, "&<>")] ? namesize * 5 : 0; + href = (char *)mg_malloc(namesize * 3 + escsize); + if (href == NULL) { + return -1; + } + mg_url_encode(de->file_name, href, namesize * 3); + esc = NULL; + if (escsize > 0) { + /* HTML escaping needed */ + esc = href + namesize * 3; + for (i = 0, p = esc; de->file_name[i]; i++, p += strlen(p)) { + mg_strlcpy(p, de->file_name + i, 2); + if (*p == '&') { + strcpy(p, "&"); + } else if (*p == '<') { + strcpy(p, "<"); + } else if (*p == '>') { + strcpy(p, ">"); + } + } + } + + if (de->file.is_directory) { + mg_snprintf(de->conn, + NULL, /* Buffer is big enough */ + size, + sizeof(size), + "%s", + "[DIRECTORY]"); + } else { + /* We use (signed) cast below because MSVC 6 compiler cannot + * convert unsigned __int64 to double. Sigh. */ + if (de->file.size < 1024) { + mg_snprintf(de->conn, + NULL, /* Buffer is big enough */ + size, + sizeof(size), + "%d", + (int)de->file.size); + } else if (de->file.size < 0x100000) { + mg_snprintf(de->conn, + NULL, /* Buffer is big enough */ + size, + sizeof(size), + "%.1fk", + (double)de->file.size / 1024.0); + } else if (de->file.size < 0x40000000) { + mg_snprintf(de->conn, + NULL, /* Buffer is big enough */ + size, + sizeof(size), + "%.1fM", + (double)de->file.size / 1048576); + } else { + mg_snprintf(de->conn, + NULL, /* Buffer is big enough */ + size, + sizeof(size), + "%.1fG", + (double)de->file.size / 1073741824); + } + } + + /* Note: mg_snprintf will not cause a buffer overflow above. + * So, string truncation checks are not required here. */ + +#if defined(REENTRANT_TIME) + localtime_r(&de->file.last_modified, tm); +#else + tm = localtime(&de->file.last_modified); +#endif + if (tm != NULL) { + strftime(mod, sizeof(mod), "%d-%b-%Y %H:%M", tm); + } else { + mg_strlcpy(mod, "01-Jan-1970 00:00", sizeof(mod)); + mod[sizeof(mod) - 1] = '\0'; + } + mg_printf(de->conn, + "%s%s" + " %s  %s\n", + href, + de->file.is_directory ? "/" : "", + esc ? esc : de->file_name, + de->file.is_directory ? "/" : "", + mod, + size); + mg_free(href); + return 0; +} + + +/* This function is called from send_directory() and used for + * sorting directory entries by size, or name, or modification time. + * On windows, __cdecl specification is needed in case if project is built + * with __stdcall convention. qsort always requires __cdels callback. */ +static int WINCDECL +compare_dir_entries(const void *p1, const void *p2) +{ + if (p1 && p2) { + const struct de *a = (const struct de *)p1, *b = (const struct de *)p2; + const char *query_string = a->conn->request_info.query_string; + int cmp_result = 0; + + if ((query_string == NULL) || (query_string[0] == '\0')) { + query_string = "n"; + } + + if (a->file.is_directory && !b->file.is_directory) { + return -1; /* Always put directories on top */ + } else if (!a->file.is_directory && b->file.is_directory) { + return 1; /* Always put directories on top */ + } else if (*query_string == 'n') { + cmp_result = strcmp(a->file_name, b->file_name); + } else if (*query_string == 's') { + cmp_result = (a->file.size == b->file.size) + ? 0 + : ((a->file.size > b->file.size) ? 1 : -1); + } else if (*query_string == 'd') { + cmp_result = + (a->file.last_modified == b->file.last_modified) + ? 0 + : ((a->file.last_modified > b->file.last_modified) ? 1 + : -1); + } + + return (query_string[1] == 'd') ? -cmp_result : cmp_result; + } + return 0; +} + + +static int +must_hide_file(struct mg_connection *conn, const char *path) +{ + if (conn && conn->dom_ctx) { + const char *pw_pattern = "**" PASSWORDS_FILE_NAME "$"; + const char *pattern = conn->dom_ctx->config[HIDE_FILES]; + return (match_prefix(pw_pattern, strlen(pw_pattern), path) > 0) + || ((pattern != NULL) + && (match_prefix(pattern, strlen(pattern), path) > 0)); + } + return 0; +} + + +#if !defined(NO_FILESYSTEMS) +static int +scan_directory(struct mg_connection *conn, + const char *dir, + void *data, + int (*cb)(struct de *, void *)) +{ + char path[PATH_MAX]; + struct dirent *dp; + DIR *dirp; + struct de de; + int truncated; + + if ((dirp = mg_opendir(conn, dir)) == NULL) { + return 0; + } else { + de.conn = conn; + + while ((dp = mg_readdir(dirp)) != NULL) { + /* Do not show current dir and hidden files */ + if (!strcmp(dp->d_name, ".") || !strcmp(dp->d_name, "..") + || must_hide_file(conn, dp->d_name)) { + continue; + } + + mg_snprintf( + conn, &truncated, path, sizeof(path), "%s/%s", dir, dp->d_name); + + /* If we don't memset stat structure to zero, mtime will have + * garbage and strftime() will segfault later on in + * print_dir_entry(). memset is required only if mg_stat() + * fails. For more details, see + * http://code.google.com/p/mongoose/issues/detail?id=79 */ + memset(&de.file, 0, sizeof(de.file)); + + if (truncated) { + /* If the path is not complete, skip processing. */ + continue; + } + + if (!mg_stat(conn, path, &de.file)) { + mg_cry_internal(conn, + "%s: mg_stat(%s) failed: %s", + __func__, + path, + strerror(ERRNO)); + } + de.file_name = dp->d_name; + cb(&de, data); + } + (void)mg_closedir(dirp); + } + return 1; +} +#endif /* NO_FILESYSTEMS */ + + +#if !defined(NO_FILES) +static int +remove_directory(struct mg_connection *conn, const char *dir) +{ + char path[PATH_MAX]; + struct dirent *dp; + DIR *dirp; + struct de de; + int truncated; + int ok = 1; + + if ((dirp = mg_opendir(conn, dir)) == NULL) { + return 0; + } else { + de.conn = conn; + + while ((dp = mg_readdir(dirp)) != NULL) { + /* Do not show current dir (but show hidden files as they will + * also be removed) */ + if (!strcmp(dp->d_name, ".") || !strcmp(dp->d_name, "..")) { + continue; + } + + mg_snprintf( + conn, &truncated, path, sizeof(path), "%s/%s", dir, dp->d_name); + + /* If we don't memset stat structure to zero, mtime will have + * garbage and strftime() will segfault later on in + * print_dir_entry(). memset is required only if mg_stat() + * fails. For more details, see + * http://code.google.com/p/mongoose/issues/detail?id=79 */ + memset(&de.file, 0, sizeof(de.file)); + + if (truncated) { + /* Do not delete anything shorter */ + ok = 0; + continue; + } + + if (!mg_stat(conn, path, &de.file)) { + mg_cry_internal(conn, + "%s: mg_stat(%s) failed: %s", + __func__, + path, + strerror(ERRNO)); + ok = 0; + } + + if (de.file.is_directory) { + if (remove_directory(conn, path) == 0) { + ok = 0; + } + } else { + /* This will fail file is the file is in memory */ + if (mg_remove(conn, path) == 0) { + ok = 0; + } + } + } + (void)mg_closedir(dirp); + + IGNORE_UNUSED_RESULT(rmdir(dir)); + } + + return ok; +} +#endif + + +struct dir_scan_data { + struct de *entries; + unsigned int num_entries; + unsigned int arr_size; +}; + + +/* Behaves like realloc(), but frees original pointer on failure */ +static void * +realloc2(void *ptr, size_t size) +{ + void *new_ptr = mg_realloc(ptr, size); + if (new_ptr == NULL) { + mg_free(ptr); + } + return new_ptr; +} + + +#if !defined(NO_FILESYSTEMS) +static int +dir_scan_callback(struct de *de, void *data) +{ + struct dir_scan_data *dsd = (struct dir_scan_data *)data; + + if ((dsd->entries == NULL) || (dsd->num_entries >= dsd->arr_size)) { + dsd->arr_size *= 2; + dsd->entries = + (struct de *)realloc2(dsd->entries, + dsd->arr_size * sizeof(dsd->entries[0])); + } + if (dsd->entries == NULL) { + /* TODO(lsm, low): propagate an error to the caller */ + dsd->num_entries = 0; + } else { + dsd->entries[dsd->num_entries].file_name = mg_strdup(de->file_name); + dsd->entries[dsd->num_entries].file = de->file; + dsd->entries[dsd->num_entries].conn = de->conn; + dsd->num_entries++; + } + + return 0; +} + + +static void +handle_directory_request(struct mg_connection *conn, const char *dir) +{ + unsigned int i; + int sort_direction; + struct dir_scan_data data = {NULL, 0, 128}; + char date[64], *esc, *p; + const char *title; + time_t curtime = time(NULL); + + if (!scan_directory(conn, dir, &data, dir_scan_callback)) { + mg_send_http_error(conn, + 500, + "Error: Cannot open directory\nopendir(%s): %s", + dir, + strerror(ERRNO)); + return; + } + + gmt_time_string(date, sizeof(date), &curtime); + + if (!conn) { + return; + } + + esc = NULL; + title = conn->request_info.local_uri; + if (title[strcspn(title, "&<>")]) { + /* HTML escaping needed */ + esc = (char *)mg_malloc(strlen(title) * 5 + 1); + if (esc) { + for (i = 0, p = esc; title[i]; i++, p += strlen(p)) { + mg_strlcpy(p, title + i, 2); + if (*p == '&') { + strcpy(p, "&"); + } else if (*p == '<') { + strcpy(p, "<"); + } else if (*p == '>') { + strcpy(p, ">"); + } + } + } else { + title = ""; + } + } + + sort_direction = ((conn->request_info.query_string != NULL) + && (conn->request_info.query_string[0] != '\0') + && (conn->request_info.query_string[1] == 'd')) + ? 'a' + : 'd'; + + conn->must_close = 1; + mg_printf(conn, "HTTP/1.1 200 OK\r\n"); + send_static_cache_header(conn); + send_additional_header(conn); + mg_printf(conn, + "Date: %s\r\n" + "Connection: close\r\n" + "Content-Type: text/html; charset=utf-8\r\n\r\n", + date); + mg_printf(conn, + "Index of %s" + "" + "

Index of %s

"
+	          ""
+	          ""
+	          ""
+	          "",
+	          esc ? esc : title,
+	          esc ? esc : title,
+	          sort_direction,
+	          sort_direction,
+	          sort_direction);
+	mg_free(esc);
+
+	/* Print first entry - link to a parent directory */
+	mg_printf(conn,
+	          ""
+	          "\n",
+	          "..",
+	          "Parent directory",
+	          "-",
+	          "-");
+
+	/* Sort and print directory entries */
+	if (data.entries != NULL) {
+		qsort(data.entries,
+		      (size_t)data.num_entries,
+		      sizeof(data.entries[0]),
+		      compare_dir_entries);
+		for (i = 0; i < data.num_entries; i++) {
+			print_dir_entry(&data.entries[i]);
+			mg_free(data.entries[i].file_name);
+		}
+		mg_free(data.entries);
+	}
+
+	mg_printf(conn, "%s", "
NameModifiedSize

%s %s  %s
"); + conn->status_code = 200; +} +#endif /* NO_FILESYSTEMS */ + + +/* Send len bytes from the opened file to the client. */ +static void +send_file_data(struct mg_connection *conn, + struct mg_file *filep, + int64_t offset, + int64_t len) +{ + char buf[MG_BUF_LEN]; + int to_read, num_read, num_written; + int64_t size; + + if (!filep || !conn) { + return; + } + + /* Sanity check the offset */ + size = (filep->stat.size > INT64_MAX) ? INT64_MAX + : (int64_t)(filep->stat.size); + offset = (offset < 0) ? 0 : ((offset > size) ? size : offset); + +#if defined(MG_USE_OPEN_FILE) + if ((len > 0) && (filep->access.membuf != NULL) && (size > 0)) { + /* file stored in memory */ + if (len > size - offset) { + len = size - offset; + } + mg_write(conn, filep->access.membuf + offset, (size_t)len); + } else /* else block below */ +#endif + if (len > 0 && filep->access.fp != NULL) { +/* file stored on disk */ +#if defined(__linux__) + /* sendfile is only available for Linux */ + if ((conn->ssl == 0) && (conn->throttle == 0) + && (!mg_strcasecmp(conn->dom_ctx->config[ALLOW_SENDFILE_CALL], + "yes"))) { + off_t sf_offs = (off_t)offset; + ssize_t sf_sent; + int sf_file = fileno(filep->access.fp); + int loop_cnt = 0; + + do { + /* 2147479552 (0x7FFFF000) is a limit found by experiment on + * 64 bit Linux (2^31 minus one memory page of 4k?). */ + size_t sf_tosend = + (size_t)((len < 0x7FFFF000) ? len : 0x7FFFF000); + sf_sent = + sendfile(conn->client.sock, sf_file, &sf_offs, sf_tosend); + if (sf_sent > 0) { + len -= sf_sent; + offset += sf_sent; + } else if (loop_cnt == 0) { + /* This file can not be sent using sendfile. + * This might be the case for pseudo-files in the + * /sys/ and /proc/ file system. + * Use the regular user mode copy code instead. */ + break; + } else if (sf_sent == 0) { + /* No error, but 0 bytes sent. May be EOF? */ + return; + } + loop_cnt++; + + } while ((len > 0) && (sf_sent >= 0)); + + if (sf_sent > 0) { + return; /* OK */ + } + + /* sf_sent<0 means error, thus fall back to the classic way */ + /* This is always the case, if sf_file is not a "normal" file, + * e.g., for sending data from the output of a CGI process. */ + offset = (int64_t)sf_offs; + } +#endif + if ((offset > 0) && (fseeko(filep->access.fp, offset, SEEK_SET) != 0)) { + mg_cry_internal(conn, + "%s: fseeko() failed: %s", + __func__, + strerror(ERRNO)); + mg_send_http_error( + conn, + 500, + "%s", + "Error: Unable to access file at requested position."); + } else { + while (len > 0) { + /* Calculate how much to read from the file in the buffer */ + to_read = sizeof(buf); + if ((int64_t)to_read > len) { + to_read = (int)len; + } + + /* Read from file, exit the loop on error */ + if ((num_read = + (int)fread(buf, 1, (size_t)to_read, filep->access.fp)) + <= 0) { + break; + } + + /* Send read bytes to the client, exit the loop on error */ + if ((num_written = mg_write(conn, buf, (size_t)num_read)) + != num_read) { + break; + } + + /* Both read and were successful, adjust counters */ + len -= num_written; + } + } + } +} + + +static int +parse_range_header(const char *header, int64_t *a, int64_t *b) +{ + return sscanf(header, "bytes=%" INT64_FMT "-%" INT64_FMT, a, b); +} + + +static void +construct_etag(char *buf, size_t buf_len, const struct mg_file_stat *filestat) +{ + if ((filestat != NULL) && (buf != NULL)) { + mg_snprintf(NULL, + NULL, /* All calls to construct_etag use 64 byte buffer */ + buf, + buf_len, + "\"%lx.%" INT64_FMT "\"", + (unsigned long)filestat->last_modified, + filestat->size); + } +} + + +static void +fclose_on_exec(struct mg_file_access *filep, struct mg_connection *conn) +{ + if (filep != NULL && filep->fp != NULL) { +#if defined(_WIN32) + (void)conn; /* Unused. */ +#else + if (fcntl(fileno(filep->fp), F_SETFD, FD_CLOEXEC) != 0) { + mg_cry_internal(conn, + "%s: fcntl(F_SETFD FD_CLOEXEC) failed: %s", + __func__, + strerror(ERRNO)); + } +#endif + } +} + + +#if defined(USE_ZLIB) +#include "mod_zlib.inl" +#endif + + +#if !defined(NO_FILESYSTEMS) +static void +handle_static_file_request(struct mg_connection *conn, + const char *path, + struct mg_file *filep, + const char *mime_type, + const char *additional_headers) +{ + char date[64], lm[64], etag[64]; + char range[128]; /* large enough, so there will be no overflow */ + const char *msg = "OK"; + const char *range_hdr; + time_t curtime = time(NULL); + int64_t cl, r1, r2; + struct vec mime_vec; + int n, truncated; + char gz_path[PATH_MAX]; + const char *encoding = ""; + const char *origin_hdr; + const char *cors_orig_cfg; + const char *cors1, *cors2, *cors3; + int is_head_request; + +#if defined(USE_ZLIB) + /* Compression is allowed, unless there is a reason not to use compression. + * If the file is already compressed, too small or a "range" request was + * made, on the fly compression is not possible. */ + int allow_on_the_fly_compression = 1; +#endif + + if ((conn == NULL) || (conn->dom_ctx == NULL) || (filep == NULL)) { + return; + } + + is_head_request = !strcmp(conn->request_info.request_method, "HEAD"); + + if (mime_type == NULL) { + get_mime_type(conn, path, &mime_vec); + } else { + mime_vec.ptr = mime_type; + mime_vec.len = strlen(mime_type); + } + if (filep->stat.size > INT64_MAX) { + mg_send_http_error(conn, + 500, + "Error: File size is too large to send\n%" INT64_FMT, + filep->stat.size); + return; + } + cl = (int64_t)filep->stat.size; + conn->status_code = 200; + range[0] = '\0'; + +#if defined(USE_ZLIB) + /* if this file is in fact a pre-gzipped file, rewrite its filename + * it's important to rewrite the filename after resolving + * the mime type from it, to preserve the actual file's type */ + if (!conn->accept_gzip) { + allow_on_the_fly_compression = 0; + } +#endif + + /* Check if there is a range header */ + range_hdr = mg_get_header(conn, "Range"); + + /* For gzipped files, add *.gz */ + if (filep->stat.is_gzipped) { + mg_snprintf(conn, &truncated, gz_path, sizeof(gz_path), "%s.gz", path); + + if (truncated) { + mg_send_http_error(conn, + 500, + "Error: Path of zipped file too long (%s)", + path); + return; + } + + path = gz_path; + encoding = "Content-Encoding: gzip\r\n"; + +#if defined(USE_ZLIB) + /* File is already compressed. No "on the fly" compression. */ + allow_on_the_fly_compression = 0; +#endif + } else if ((conn->accept_gzip) && (range_hdr == NULL) + && (filep->stat.size >= MG_FILE_COMPRESSION_SIZE_LIMIT)) { + struct mg_file_stat file_stat; + + mg_snprintf(conn, &truncated, gz_path, sizeof(gz_path), "%s.gz", path); + + if (!truncated && mg_stat(conn, gz_path, &file_stat) + && !file_stat.is_directory) { + file_stat.is_gzipped = 1; + filep->stat = file_stat; + cl = (int64_t)filep->stat.size; + path = gz_path; + encoding = "Content-Encoding: gzip\r\n"; + +#if defined(USE_ZLIB) + /* File is already compressed. No "on the fly" compression. */ + allow_on_the_fly_compression = 0; +#endif + } + } + + if (!mg_fopen(conn, path, MG_FOPEN_MODE_READ, filep)) { + mg_send_http_error(conn, + 500, + "Error: Cannot open file\nfopen(%s): %s", + path, + strerror(ERRNO)); + return; + } + + fclose_on_exec(&filep->access, conn); + + /* If "Range" request was made: parse header, send only selected part + * of the file. */ + r1 = r2 = 0; + if ((range_hdr != NULL) + && ((n = parse_range_header(range_hdr, &r1, &r2)) > 0) && (r1 >= 0) + && (r2 >= 0)) { + /* actually, range requests don't play well with a pre-gzipped + * file (since the range is specified in the uncompressed space) */ + if (filep->stat.is_gzipped) { + mg_send_http_error( + conn, + 416, /* 416 = Range Not Satisfiable */ + "%s", + "Error: Range requests in gzipped files are not supported"); + (void)mg_fclose( + &filep->access); /* ignore error on read only file */ + return; + } + conn->status_code = 206; + cl = (n == 2) ? (((r2 > cl) ? cl : r2) - r1 + 1) : (cl - r1); + mg_snprintf(conn, + NULL, /* range buffer is big enough */ + range, + sizeof(range), + "Content-Range: bytes " + "%" INT64_FMT "-%" INT64_FMT "/%" INT64_FMT "\r\n", + r1, + r1 + cl - 1, + filep->stat.size); + msg = "Partial Content"; + +#if defined(USE_ZLIB) + /* Do not compress ranges. */ + allow_on_the_fly_compression = 0; +#endif + } + +/* Do not compress small files. Small files do not benefit from file + * compression, but there is still some overhead. */ +#if defined(USE_ZLIB) + if (filep->stat.size < MG_FILE_COMPRESSION_SIZE_LIMIT) { + /* File is below the size limit. */ + allow_on_the_fly_compression = 0; + } +#endif + + /* Standard CORS header */ + cors_orig_cfg = conn->dom_ctx->config[ACCESS_CONTROL_ALLOW_ORIGIN]; + origin_hdr = mg_get_header(conn, "Origin"); + if (cors_orig_cfg && *cors_orig_cfg && origin_hdr) { + /* Cross-origin resource sharing (CORS), see + * http://www.html5rocks.com/en/tutorials/cors/, + * http://www.html5rocks.com/static/images/cors_server_flowchart.png + * - + * preflight is not supported for files. */ + cors1 = "Access-Control-Allow-Origin: "; + cors2 = cors_orig_cfg; + cors3 = "\r\n"; + } else { + cors1 = cors2 = cors3 = ""; + } + + /* Prepare Etag, Date, Last-Modified headers. Must be in UTC, + * according to + * http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3 */ + gmt_time_string(date, sizeof(date), &curtime); + gmt_time_string(lm, sizeof(lm), &filep->stat.last_modified); + construct_etag(etag, sizeof(etag), &filep->stat); + + /* Send header */ + (void)mg_printf(conn, + "HTTP/1.1 %d %s\r\n" + "%s%s%s" /* CORS */ + "Date: %s\r\n" + "Last-Modified: %s\r\n" + "Etag: %s\r\n" + "Content-Type: %.*s\r\n" + "Connection: %s\r\n", + conn->status_code, + msg, + cors1, + cors2, + cors3, + date, + lm, + etag, + (int)mime_vec.len, + mime_vec.ptr, + suggest_connection_header(conn)); + send_static_cache_header(conn); + send_additional_header(conn); + +#if defined(USE_ZLIB) + /* On the fly compression allowed */ + if (allow_on_the_fly_compression) { + /* For on the fly compression, we don't know the content size in + * advance, so we have to use chunked encoding */ + (void)mg_printf(conn, + "Content-Encoding: gzip\r\n" + "Transfer-Encoding: chunked\r\n"); + } else +#endif + { + /* Without on-the-fly compression, we know the content-length + * and we can use ranges (with on-the-fly compression we cannot). + * So we send these response headers only in this case. */ + (void)mg_printf(conn, + "Content-Length: %" INT64_FMT "\r\n" + "Accept-Ranges: bytes\r\n" + "%s" /* range */ + "%s" /* encoding */, + cl, + range, + encoding); + } + + /* The previous code must not add any header starting with X- to make + * sure no one of the additional_headers is included twice */ + if (additional_headers != NULL) { + (void)mg_printf(conn, + "%.*s\r\n\r\n", + (int)strlen(additional_headers), + additional_headers); + } else { + (void)mg_printf(conn, "\r\n"); + } + + if (!is_head_request) { +#if defined(USE_ZLIB) + if (allow_on_the_fly_compression) { + /* Compress and send */ + send_compressed_data(conn, filep); + } else +#endif + { + /* Send file directly */ + send_file_data(conn, filep, r1, cl); + } + } + (void)mg_fclose(&filep->access); /* ignore error on read only file */ +} + + +int +mg_send_file_body(struct mg_connection *conn, const char *path) +{ + struct mg_file file = STRUCT_FILE_INITIALIZER; + if (!mg_fopen(conn, path, MG_FOPEN_MODE_READ, &file)) { + return -1; + } + fclose_on_exec(&file.access, conn); + send_file_data(conn, &file, 0, INT64_MAX); + (void)mg_fclose(&file.access); /* Ignore errors for readonly files */ + return 0; /* >= 0 for OK */ +} +#endif /* NO_FILESYSTEMS */ + + +#if !defined(NO_CACHING) +/* Return True if we should reply 304 Not Modified. */ +static int +is_not_modified(const struct mg_connection *conn, + const struct mg_file_stat *filestat) +{ + char etag[64]; + const char *ims = mg_get_header(conn, "If-Modified-Since"); + const char *inm = mg_get_header(conn, "If-None-Match"); + construct_etag(etag, sizeof(etag), filestat); + + return ((inm != NULL) && !mg_strcasecmp(etag, inm)) + || ((ims != NULL) + && (filestat->last_modified <= parse_date_string(ims))); +} + +static void +handle_not_modified_static_file_request(struct mg_connection *conn, + struct mg_file *filep) +{ + char date[64], lm[64], etag[64]; + time_t curtime = time(NULL); + + if ((conn == NULL) || (filep == NULL)) { + return; + } + conn->status_code = 304; + gmt_time_string(date, sizeof(date), &curtime); + gmt_time_string(lm, sizeof(lm), &filep->stat.last_modified); + construct_etag(etag, sizeof(etag), &filep->stat); + + (void)mg_printf(conn, + "HTTP/1.1 %d %s\r\n" + "Date: %s\r\n", + conn->status_code, + mg_get_response_code_text(conn, conn->status_code), + date); + send_static_cache_header(conn); + send_additional_header(conn); + (void)mg_printf(conn, + "Last-Modified: %s\r\n" + "Etag: %s\r\n" + "Connection: %s\r\n" + "\r\n", + lm, + etag, + suggest_connection_header(conn)); +} +#endif + + +#if !defined(NO_FILESYSTEMS) +void +mg_send_file(struct mg_connection *conn, const char *path) +{ + mg_send_mime_file2(conn, path, NULL, NULL); +} + + +void +mg_send_mime_file(struct mg_connection *conn, + const char *path, + const char *mime_type) +{ + mg_send_mime_file2(conn, path, mime_type, NULL); +} + + +void +mg_send_mime_file2(struct mg_connection *conn, + const char *path, + const char *mime_type, + const char *additional_headers) +{ + struct mg_file file = STRUCT_FILE_INITIALIZER; + + if (!conn) { + /* No conn */ + return; + } + + if (mg_stat(conn, path, &file.stat)) { +#if !defined(NO_CACHING) + if (is_not_modified(conn, &file.stat)) { + /* Send 304 "Not Modified" - this must not send any body data */ + handle_not_modified_static_file_request(conn, &file); + } else +#endif /* NO_CACHING */ + if (file.stat.is_directory) { + if (!mg_strcasecmp(conn->dom_ctx->config[ENABLE_DIRECTORY_LISTING], + "yes")) { + handle_directory_request(conn, path); + } else { + mg_send_http_error(conn, + 403, + "%s", + "Error: Directory listing denied"); + } + } else { + handle_static_file_request( + conn, path, &file, mime_type, additional_headers); + } + } else { + mg_send_http_error(conn, 404, "%s", "Error: File not found"); + } +} + + +/* For a given PUT path, create all intermediate subdirectories. + * Return 0 if the path itself is a directory. + * Return 1 if the path leads to a file. + * Return -1 for if the path is too long. + * Return -2 if path can not be created. + */ +static int +put_dir(struct mg_connection *conn, const char *path) +{ + char buf[PATH_MAX]; + const char *s, *p; + struct mg_file file = STRUCT_FILE_INITIALIZER; + size_t len; + int res = 1; + + for (s = p = path + 2; (p = strchr(s, '/')) != NULL; s = ++p) { + len = (size_t)(p - path); + if (len >= sizeof(buf)) { + /* path too long */ + res = -1; + break; + } + memcpy(buf, path, len); + buf[len] = '\0'; + + /* Try to create intermediate directory */ + DEBUG_TRACE("mkdir(%s)", buf); + if (!mg_stat(conn, buf, &file.stat) && mg_mkdir(conn, buf, 0755) != 0) { + /* path does not exixt and can not be created */ + res = -2; + break; + } + + /* Is path itself a directory? */ + if (p[1] == '\0') { + res = 0; + } + } + + return res; +} + + +static void +remove_bad_file(const struct mg_connection *conn, const char *path) +{ + int r = mg_remove(conn, path); + if (r != 0) { + mg_cry_internal(conn, + "%s: Cannot remove invalid file %s", + __func__, + path); + } +} + + +long long +mg_store_body(struct mg_connection *conn, const char *path) +{ + char buf[MG_BUF_LEN]; + long long len = 0; + int ret, n; + struct mg_file fi; + + if (conn->consumed_content != 0) { + mg_cry_internal(conn, "%s: Contents already consumed", __func__); + return -11; + } + + ret = put_dir(conn, path); + if (ret < 0) { + /* -1 for path too long, + * -2 for path can not be created. */ + return ret; + } + if (ret != 1) { + /* Return 0 means, path itself is a directory. */ + return 0; + } + + if (mg_fopen(conn, path, MG_FOPEN_MODE_WRITE, &fi) == 0) { + return -12; + } + + ret = mg_read(conn, buf, sizeof(buf)); + while (ret > 0) { + n = (int)fwrite(buf, 1, (size_t)ret, fi.access.fp); + if (n != ret) { + (void)mg_fclose( + &fi.access); /* File is bad and will be removed anyway. */ + remove_bad_file(conn, path); + return -13; + } + len += ret; + ret = mg_read(conn, buf, sizeof(buf)); + } + + /* File is open for writing. If fclose fails, there was probably an + * error flushing the buffer to disk, so the file on disk might be + * broken. Delete it and return an error to the caller. */ + if (mg_fclose(&fi.access) != 0) { + remove_bad_file(conn, path); + return -14; + } + + return len; +} +#endif /* NO_FILESYSTEMS */ + + +/* Parse a buffer: + * Forward the string pointer till the end of a word, then + * terminate it and forward till the begin of the next word. + */ +static int +skip_to_end_of_word_and_terminate(char **ppw, int eol) +{ + /* Forward until a space is found - use isgraph here */ + /* See http://www.cplusplus.com/reference/cctype/ */ + while (isgraph((unsigned char)**ppw)) { + (*ppw)++; + } + + /* Check end of word */ + if (eol) { + /* must be a end of line */ + if ((**ppw != '\r') && (**ppw != '\n')) { + return -1; + } + } else { + /* must be a end of a word, but not a line */ + if (**ppw != ' ') { + return -1; + } + } + + /* Terminate and forward to the next word */ + do { + **ppw = 0; + (*ppw)++; + } while (isspace((unsigned char)**ppw)); + + /* Check after term */ + if (!eol) { + /* if it's not the end of line, there must be a next word */ + if (!isgraph((unsigned char)**ppw)) { + return -1; + } + } + + /* ok */ + return 1; +} + + +/* Parse HTTP headers from the given buffer, advance buf pointer + * to the point where parsing stopped. + * All parameters must be valid pointers (not NULL). + * Return <0 on error. */ +static int +parse_http_headers(char **buf, struct mg_header hdr[MG_MAX_HEADERS]) +{ + int i; + int num_headers = 0; + + for (i = 0; i < (int)MG_MAX_HEADERS; i++) { + char *dp = *buf; + + /* Skip all ASCII characters (>SPACE, <127), to find a ':' */ + while ((*dp != ':') && (*dp >= 33) && (*dp <= 126)) { + dp++; + } + if (dp == *buf) { + /* End of headers reached. */ + break; + } + if (*dp != ':') { + /* This is not a valid field. */ + return -1; + } + + /* End of header key (*dp == ':') */ + /* Truncate here and set the key name */ + *dp = 0; + hdr[i].name = *buf; + + /* Skip all spaces */ + do { + dp++; + } while ((*dp == ' ') || (*dp == '\t')); + + /* The rest of the line is the value */ + hdr[i].value = dp; + + /* Find end of line */ + while ((*dp != 0) && (*dp != '\r') && (*dp != '\n')) { + dp++; + }; + + /* eliminate \r */ + if (*dp == '\r') { + *dp = 0; + dp++; + if (*dp != '\n') { + /* This is not a valid line. */ + return -1; + } + } + + /* here *dp is either 0 or '\n' */ + /* in any case, we have a new header */ + num_headers = i + 1; + + if (*dp) { + *dp = 0; + dp++; + *buf = dp; + + if ((dp[0] == '\r') || (dp[0] == '\n')) { + /* This is the end of the header */ + break; + } + } else { + *buf = dp; + break; + } + } + return num_headers; +} + + +struct mg_http_method_info { + const char *name; + int request_has_body; + int response_has_body; + int is_safe; + int is_idempotent; + int is_cacheable; +}; + + +/* https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods */ +static struct mg_http_method_info http_methods[] = { + /* HTTP (RFC 2616) */ + {"GET", 0, 1, 1, 1, 1}, + {"POST", 1, 1, 0, 0, 0}, + {"PUT", 1, 0, 0, 1, 0}, + {"DELETE", 0, 0, 0, 1, 0}, + {"HEAD", 0, 0, 1, 1, 1}, + {"OPTIONS", 0, 0, 1, 1, 0}, + {"CONNECT", 1, 1, 0, 0, 0}, + /* TRACE method (RFC 2616) is not supported for security reasons */ + + /* PATCH method (RFC 5789) */ + {"PATCH", 1, 0, 0, 0, 0}, + /* PATCH method only allowed for CGI/Lua/LSP and callbacks. */ + + /* WEBDAV (RFC 2518) */ + {"PROPFIND", 0, 1, 1, 1, 0}, + /* http://www.webdav.org/specs/rfc4918.html, 9.1: + * Some PROPFIND results MAY be cached, with care, + * as there is no cache validation mechanism for + * most properties. This method is both safe and + * idempotent (see Section 9.1 of [RFC2616]). */ + {"MKCOL", 0, 0, 0, 1, 0}, + /* http://www.webdav.org/specs/rfc4918.html, 9.1: + * When MKCOL is invoked without a request body, + * the newly created collection SHOULD have no + * members. A MKCOL request message may contain + * a message body. The precise behavior of a MKCOL + * request when the body is present is undefined, + * ... ==> We do not support MKCOL with body data. + * This method is idempotent, but not safe (see + * Section 9.1 of [RFC2616]). Responses to this + * method MUST NOT be cached. */ + + /* Unsupported WEBDAV Methods: */ + /* PROPPATCH, COPY, MOVE, LOCK, UNLOCK (RFC 2518) */ + /* + 11 methods from RFC 3253 */ + /* ORDERPATCH (RFC 3648) */ + /* ACL (RFC 3744) */ + /* SEARCH (RFC 5323) */ + /* + MicroSoft extensions + * https://msdn.microsoft.com/en-us/library/aa142917.aspx */ + + /* REPORT method (RFC 3253) */ + {"REPORT", 1, 1, 1, 1, 1}, + /* REPORT method only allowed for CGI/Lua/LSP and callbacks. */ + /* It was defined for WEBDAV in RFC 3253, Sec. 3.6 + * (https://tools.ietf.org/html/rfc3253#section-3.6), but seems + * to be useful for REST in case a "GET request with body" is + * required. */ + + {NULL, 0, 0, 0, 0, 0} + /* end of list */ +}; + + +static const struct mg_http_method_info * +get_http_method_info(const char *method) +{ + /* Check if the method is known to the server. The list of all known + * HTTP methods can be found here at + * http://www.iana.org/assignments/http-methods/http-methods.xhtml + */ + const struct mg_http_method_info *m = http_methods; + + while (m->name) { + if (!strcmp(m->name, method)) { + return m; + } + m++; + } + return NULL; +} + + +static int +is_valid_http_method(const char *method) +{ + return (get_http_method_info(method) != NULL); +} + + +/* Parse HTTP request, fill in mg_request_info structure. + * This function modifies the buffer by NUL-terminating + * HTTP request components, header names and header values. + * Parameters: + * buf (in/out): pointer to the HTTP header to parse and split + * len (in): length of HTTP header buffer + * re (out): parsed header as mg_request_info + * buf and ri must be valid pointers (not NULL), len>0. + * Returns <0 on error. */ +static int +parse_http_request(char *buf, int len, struct mg_request_info *ri) +{ + int request_length; + int init_skip = 0; + + /* Reset attributes. DO NOT TOUCH is_ssl, remote_addr, + * remote_port */ + ri->remote_user = ri->request_method = ri->request_uri = ri->http_version = + NULL; + ri->num_headers = 0; + + /* RFC says that all initial whitespaces should be ingored */ + /* This included all leading \r and \n (isspace) */ + /* See table: http://www.cplusplus.com/reference/cctype/ */ + while ((len > 0) && isspace((unsigned char)*buf)) { + buf++; + len--; + init_skip++; + } + + if (len == 0) { + /* Incomplete request */ + return 0; + } + + /* Control characters are not allowed, including zero */ + if (iscntrl((unsigned char)*buf)) { + return -1; + } + + /* Find end of HTTP header */ + request_length = get_http_header_len(buf, len); + if (request_length <= 0) { + return request_length; + } + buf[request_length - 1] = '\0'; + + if ((*buf == 0) || (*buf == '\r') || (*buf == '\n')) { + return -1; + } + + /* The first word has to be the HTTP method */ + ri->request_method = buf; + + if (skip_to_end_of_word_and_terminate(&buf, 0) <= 0) { + return -1; + } + + /* Check for a valid http method */ + if (!is_valid_http_method(ri->request_method)) { + return -1; + } + + /* The second word is the URI */ + ri->request_uri = buf; + + if (skip_to_end_of_word_and_terminate(&buf, 0) <= 0) { + return -1; + } + + /* Next would be the HTTP version */ + ri->http_version = buf; + + if (skip_to_end_of_word_and_terminate(&buf, 1) <= 0) { + return -1; + } + + /* Check for a valid HTTP version key */ + if (strncmp(ri->http_version, "HTTP/", 5) != 0) { + /* Invalid request */ + return -1; + } + ri->http_version += 5; + + + /* Parse all HTTP headers */ + ri->num_headers = parse_http_headers(&buf, ri->http_headers); + if (ri->num_headers < 0) { + /* Error while parsing headers */ + return -1; + } + + return request_length + init_skip; +} + + +static int +parse_http_response(char *buf, int len, struct mg_response_info *ri) +{ + int response_length; + int init_skip = 0; + char *tmp, *tmp2; + long l; + + /* Initialize elements. */ + ri->http_version = ri->status_text = NULL; + ri->num_headers = ri->status_code = 0; + + /* RFC says that all initial whitespaces should be ingored */ + /* This included all leading \r and \n (isspace) */ + /* See table: http://www.cplusplus.com/reference/cctype/ */ + while ((len > 0) && isspace((unsigned char)*buf)) { + buf++; + len--; + init_skip++; + } + + if (len == 0) { + /* Incomplete request */ + return 0; + } + + /* Control characters are not allowed, including zero */ + if (iscntrl((unsigned char)*buf)) { + return -1; + } + + /* Find end of HTTP header */ + response_length = get_http_header_len(buf, len); + if (response_length <= 0) { + return response_length; + } + buf[response_length - 1] = '\0'; + + if ((*buf == 0) || (*buf == '\r') || (*buf == '\n')) { + return -1; + } + + /* The first word is the HTTP version */ + /* Check for a valid HTTP version key */ + if (strncmp(buf, "HTTP/", 5) != 0) { + /* Invalid request */ + return -1; + } + buf += 5; + if (!isgraph((unsigned char)buf[0])) { + /* Invalid request */ + return -1; + } + ri->http_version = buf; + + if (skip_to_end_of_word_and_terminate(&buf, 0) <= 0) { + return -1; + } + + /* The second word is the status as a number */ + tmp = buf; + + if (skip_to_end_of_word_and_terminate(&buf, 0) <= 0) { + return -1; + } + + l = strtol(tmp, &tmp2, 10); + if ((l < 100) || (l >= 1000) || ((tmp2 - tmp) != 3) || (*tmp2 != 0)) { + /* Everything else but a 3 digit code is invalid */ + return -1; + } + ri->status_code = (int)l; + + /* The rest of the line is the status text */ + ri->status_text = buf; + + /* Find end of status text */ + /* isgraph or isspace = isprint */ + while (isprint((unsigned char)*buf)) { + buf++; + } + if ((*buf != '\r') && (*buf != '\n')) { + return -1; + } + /* Terminate string and forward buf to next line */ + do { + *buf = 0; + buf++; + } while (isspace((unsigned char)*buf)); + + + /* Parse all HTTP headers */ + ri->num_headers = parse_http_headers(&buf, ri->http_headers); + if (ri->num_headers < 0) { + /* Error while parsing headers */ + return -1; + } + + return response_length + init_skip; +} + + +/* Keep reading the input (either opened file descriptor fd, or socket sock, + * or SSL descriptor ssl) into buffer buf, until \r\n\r\n appears in the + * buffer (which marks the end of HTTP request). Buffer buf may already + * have some data. The length of the data is stored in nread. + * Upon every read operation, increase nread by the number of bytes read. */ +static int +read_message(FILE *fp, + struct mg_connection *conn, + char *buf, + int bufsiz, + int *nread) +{ + int request_len, n = 0; + struct timespec last_action_time; + double request_timeout; + + if (!conn) { + return 0; + } + + memset(&last_action_time, 0, sizeof(last_action_time)); + + if (conn->dom_ctx->config[REQUEST_TIMEOUT]) { + /* value of request_timeout is in seconds, config in milliseconds */ + request_timeout = atof(conn->dom_ctx->config[REQUEST_TIMEOUT]) / 1000.0; + } else { + request_timeout = -1.0; + } + if (conn->handled_requests > 0) { + if (conn->dom_ctx->config[KEEP_ALIVE_TIMEOUT]) { + request_timeout = + atof(conn->dom_ctx->config[KEEP_ALIVE_TIMEOUT]) / 1000.0; + } + } + + request_len = get_http_header_len(buf, *nread); + + while (request_len == 0) { + /* Full request not yet received */ + if (conn->phys_ctx->stop_flag != 0) { + /* Server is to be stopped. */ + return -1; + } + + if (*nread >= bufsiz) { + /* Request too long */ + return -2; + } + + n = pull_inner( + fp, conn, buf + *nread, bufsiz - *nread, request_timeout); + if (n == -2) { + /* Receive error */ + return -1; + } + + /* update clock after every read request */ + clock_gettime(CLOCK_MONOTONIC, &last_action_time); + + if (n > 0) { + *nread += n; + request_len = get_http_header_len(buf, *nread); + } else { + request_len = 0; + } + + if ((request_len == 0) && (request_timeout >= 0)) { + if (mg_difftimespec(&last_action_time, &(conn->req_time)) + > request_timeout) { + /* Timeout */ + return -1; + } + } + } + + return request_len; +} + + +#if !defined(NO_CGI) || !defined(NO_FILES) +static int +forward_body_data(struct mg_connection *conn, FILE *fp, SOCKET sock, SSL *ssl) +{ + const char *expect; + char buf[MG_BUF_LEN]; + int success = 0; + + if (!conn) { + return 0; + } + + expect = mg_get_header(conn, "Expect"); + DEBUG_ASSERT(fp != NULL); + if (!fp) { + mg_send_http_error(conn, 500, "%s", "Error: NULL File"); + return 0; + } + + if ((expect != NULL) && (mg_strcasecmp(expect, "100-continue") != 0)) { + /* Client sent an "Expect: xyz" header and xyz is not 100-continue. + */ + mg_send_http_error(conn, 417, "Error: Can not fulfill expectation"); + } else { + if (expect != NULL) { + (void)mg_printf(conn, "%s", "HTTP/1.1 100 Continue\r\n\r\n"); + conn->status_code = 100; + } else { + conn->status_code = 200; + } + + DEBUG_ASSERT(conn->consumed_content == 0); + + if (conn->consumed_content != 0) { + mg_send_http_error(conn, 500, "%s", "Error: Size mismatch"); + return 0; + } + + for (;;) { + int nread = mg_read(conn, buf, sizeof(buf)); + if (nread <= 0) { + success = (nread == 0); + break; + } + if (push_all(conn->phys_ctx, fp, sock, ssl, buf, nread) != nread) { + break; + } + } + + /* Each error code path in this function must send an error */ + if (!success) { + /* NOTE: Maybe some data has already been sent. */ + /* TODO (low): If some data has been sent, a correct error + * reply can no longer be sent, so just close the connection */ + mg_send_http_error(conn, 500, "%s", ""); + } + } + + return success; +} +#endif + + +#if defined(USE_TIMERS) + +#define TIMER_API static +#include "timer.inl" + +#endif /* USE_TIMERS */ + + +#if !defined(NO_CGI) +/* This structure helps to create an environment for the spawned CGI + * program. + * Environment is an array of "VARIABLE=VALUE\0" ASCIIZ strings, + * last element must be NULL. + * However, on Windows there is a requirement that all these + * VARIABLE=VALUE\0 + * strings must reside in a contiguous buffer. The end of the buffer is + * marked by two '\0' characters. + * We satisfy both worlds: we create an envp array (which is vars), all + * entries are actually pointers inside buf. */ +struct cgi_environment { + struct mg_connection *conn; + /* Data block */ + char *buf; /* Environment buffer */ + size_t buflen; /* Space available in buf */ + size_t bufused; /* Space taken in buf */ + /* Index block */ + char **var; /* char **envp */ + size_t varlen; /* Number of variables available in var */ + size_t varused; /* Number of variables stored in var */ +}; + + +static void addenv(struct cgi_environment *env, + PRINTF_FORMAT_STRING(const char *fmt), + ...) PRINTF_ARGS(2, 3); + +/* Append VARIABLE=VALUE\0 string to the buffer, and add a respective + * pointer into the vars array. Assumes env != NULL and fmt != NULL. */ +static void +addenv(struct cgi_environment *env, const char *fmt, ...) +{ + size_t i, n, space; + int truncated = 0; + char *added; + va_list ap; + + if ((env->varlen - env->varused) < 2) { + mg_cry_internal(env->conn, + "%s: Cannot register CGI variable [%s]", + __func__, + fmt); + return; + } + + /* Calculate how much space is left in the buffer */ + space = (env->buflen - env->bufused); + + do { + /* Space for "\0\0" is always needed. */ + if (space <= 2) { + /* Allocate new buffer */ + n = env->buflen + CGI_ENVIRONMENT_SIZE; + added = (char *)mg_realloc_ctx(env->buf, n, env->conn->phys_ctx); + if (!added) { + /* Out of memory */ + mg_cry_internal( + env->conn, + "%s: Cannot allocate memory for CGI variable [%s]", + __func__, + fmt); + return; + } + /* Retarget pointers */ + env->buf = added; + env->buflen = n; + for (i = 0, n = 0; i < env->varused; i++) { + env->var[i] = added + n; + n += strlen(added + n) + 1; + } + space = (env->buflen - env->bufused); + } + + /* Make a pointer to the free space int the buffer */ + added = env->buf + env->bufused; + + /* Copy VARIABLE=VALUE\0 string into the free space */ + va_start(ap, fmt); + mg_vsnprintf(env->conn, &truncated, added, space - 1, fmt, ap); + va_end(ap); + + /* Do not add truncated strings to the environment */ + if (truncated) { + /* Reallocate the buffer */ + space = 0; + } + } while (truncated); + + /* Calculate number of bytes added to the environment */ + n = strlen(added) + 1; + env->bufused += n; + + /* Append a pointer to the added string into the envp array */ + env->var[env->varused] = added; + env->varused++; +} + +/* Return 0 on success, non-zero if an error occurs. */ + +static int +prepare_cgi_environment(struct mg_connection *conn, + const char *prog, + struct cgi_environment *env) +{ + const char *s; + struct vec var_vec; + char *p, src_addr[IP_ADDR_STR_LEN], http_var_name[128]; + int i, truncated, uri_len; + + if ((conn == NULL) || (prog == NULL) || (env == NULL)) { + return -1; + } + + env->conn = conn; + env->buflen = CGI_ENVIRONMENT_SIZE; + env->bufused = 0; + env->buf = (char *)mg_malloc_ctx(env->buflen, conn->phys_ctx); + if (env->buf == NULL) { + mg_cry_internal(conn, + "%s: Not enough memory for environmental buffer", + __func__); + return -1; + } + env->varlen = MAX_CGI_ENVIR_VARS; + env->varused = 0; + env->var = + (char **)mg_malloc_ctx(env->varlen * sizeof(char *), conn->phys_ctx); + if (env->var == NULL) { + mg_cry_internal(conn, + "%s: Not enough memory for environmental variables", + __func__); + mg_free(env->buf); + return -1; + } + + addenv(env, "SERVER_NAME=%s", conn->dom_ctx->config[AUTHENTICATION_DOMAIN]); + addenv(env, "SERVER_ROOT=%s", conn->dom_ctx->config[DOCUMENT_ROOT]); + addenv(env, "DOCUMENT_ROOT=%s", conn->dom_ctx->config[DOCUMENT_ROOT]); + addenv(env, "SERVER_SOFTWARE=CivetWeb/%s", mg_version()); + + /* Prepare the environment block */ + addenv(env, "%s", "GATEWAY_INTERFACE=CGI/1.1"); + addenv(env, "%s", "SERVER_PROTOCOL=HTTP/1.1"); + addenv(env, "%s", "REDIRECT_STATUS=200"); /* For PHP */ + +#if defined(USE_IPV6) + if (conn->client.lsa.sa.sa_family == AF_INET6) { + addenv(env, "SERVER_PORT=%d", ntohs(conn->client.lsa.sin6.sin6_port)); + } else +#endif + { + addenv(env, "SERVER_PORT=%d", ntohs(conn->client.lsa.sin.sin_port)); + } + + sockaddr_to_string(src_addr, sizeof(src_addr), &conn->client.rsa); + addenv(env, "REMOTE_ADDR=%s", src_addr); + + addenv(env, "REQUEST_METHOD=%s", conn->request_info.request_method); + addenv(env, "REMOTE_PORT=%d", conn->request_info.remote_port); + + addenv(env, "REQUEST_URI=%s", conn->request_info.request_uri); + addenv(env, "LOCAL_URI=%s", conn->request_info.local_uri); + + /* SCRIPT_NAME */ + uri_len = (int)strlen(conn->request_info.local_uri); + if (conn->path_info == NULL) { + if (conn->request_info.local_uri[uri_len - 1] != '/') { + /* URI: /path_to_script/script.cgi */ + addenv(env, "SCRIPT_NAME=%s", conn->request_info.local_uri); + } else { + /* URI: /path_to_script/ ... using index.cgi */ + const char *index_file = strrchr(prog, '/'); + if (index_file) { + addenv(env, + "SCRIPT_NAME=%s%s", + conn->request_info.local_uri, + index_file + 1); + } + } + } else { + /* URI: /path_to_script/script.cgi/path_info */ + addenv(env, + "SCRIPT_NAME=%.*s", + uri_len - (int)strlen(conn->path_info), + conn->request_info.local_uri); + } + + addenv(env, "SCRIPT_FILENAME=%s", prog); + if (conn->path_info == NULL) { + addenv(env, "PATH_TRANSLATED=%s", conn->dom_ctx->config[DOCUMENT_ROOT]); + } else { + addenv(env, + "PATH_TRANSLATED=%s%s", + conn->dom_ctx->config[DOCUMENT_ROOT], + conn->path_info); + } + + addenv(env, "HTTPS=%s", (conn->ssl == NULL) ? "off" : "on"); + + if ((s = mg_get_header(conn, "Content-Type")) != NULL) { + addenv(env, "CONTENT_TYPE=%s", s); + } + if (conn->request_info.query_string != NULL) { + addenv(env, "QUERY_STRING=%s", conn->request_info.query_string); + } + if ((s = mg_get_header(conn, "Content-Length")) != NULL) { + addenv(env, "CONTENT_LENGTH=%s", s); + } + if ((s = getenv("PATH")) != NULL) { + addenv(env, "PATH=%s", s); + } + if (conn->path_info != NULL) { + addenv(env, "PATH_INFO=%s", conn->path_info); + } + + if (conn->status_code > 0) { + /* CGI error handler should show the status code */ + addenv(env, "STATUS=%d", conn->status_code); + } + +#if defined(_WIN32) + if ((s = getenv("COMSPEC")) != NULL) { + addenv(env, "COMSPEC=%s", s); + } + if ((s = getenv("SYSTEMROOT")) != NULL) { + addenv(env, "SYSTEMROOT=%s", s); + } + if ((s = getenv("SystemDrive")) != NULL) { + addenv(env, "SystemDrive=%s", s); + } + if ((s = getenv("ProgramFiles")) != NULL) { + addenv(env, "ProgramFiles=%s", s); + } + if ((s = getenv("ProgramFiles(x86)")) != NULL) { + addenv(env, "ProgramFiles(x86)=%s", s); + } +#else + if ((s = getenv("LD_LIBRARY_PATH")) != NULL) { + addenv(env, "LD_LIBRARY_PATH=%s", s); + } +#endif /* _WIN32 */ + + if ((s = getenv("PERLLIB")) != NULL) { + addenv(env, "PERLLIB=%s", s); + } + + if (conn->request_info.remote_user != NULL) { + addenv(env, "REMOTE_USER=%s", conn->request_info.remote_user); + addenv(env, "%s", "AUTH_TYPE=Digest"); + } + + /* Add all headers as HTTP_* variables */ + for (i = 0; i < conn->request_info.num_headers; i++) { + + (void)mg_snprintf(conn, + &truncated, + http_var_name, + sizeof(http_var_name), + "HTTP_%s", + conn->request_info.http_headers[i].name); + + if (truncated) { + mg_cry_internal(conn, + "%s: HTTP header variable too long [%s]", + __func__, + conn->request_info.http_headers[i].name); + continue; + } + + /* Convert variable name into uppercase, and change - to _ */ + for (p = http_var_name; *p != '\0'; p++) { + if (*p == '-') { + *p = '_'; + } + *p = (char)toupper((unsigned char)*p); + } + + addenv(env, + "%s=%s", + http_var_name, + conn->request_info.http_headers[i].value); + } + + /* Add user-specified variables */ + s = conn->dom_ctx->config[CGI_ENVIRONMENT]; + while ((s = next_option(s, &var_vec, NULL)) != NULL) { + addenv(env, "%.*s", (int)var_vec.len, var_vec.ptr); + } + + env->var[env->varused] = NULL; + env->buf[env->bufused] = '\0'; + + return 0; +} + + +/* Data for CGI process control: PID and number of references */ +struct process_control_data { + pid_t pid; + int references; +}; + +static int +abort_process(void *data) +{ + /* Waitpid checks for child status and won't work for a pid that does not + * identify a child of the current process. Thus, if the pid is reused, + * we will not affect a different process. */ + struct process_control_data *proc = (struct process_control_data *)data; + int status = 0; + int refs; + pid_t ret_pid; + + ret_pid = waitpid(proc->pid, &status, WNOHANG); + if ((ret_pid != (pid_t)-1) && (status == 0)) { + /* Stop child process */ + DEBUG_TRACE("CGI timer: Stop child process %d\n", proc->pid); + kill(proc->pid, SIGABRT); + + /* Wait until process is terminated (don't leave zombies) */ + while (waitpid(proc->pid, &status, 0) != (pid_t)-1) /* nop */ + ; + } else { + DEBUG_TRACE("CGI timer: Child process %d already stopped\n", proc->pid); + } + /* Dec reference counter */ + refs = mg_atomic_dec(&proc->references); + if (refs == 0) { + /* no more references - free data */ + mg_free(data); + } + + return 0; +} + + +/* Local (static) function assumes all arguments are valid. */ +static void +handle_cgi_request(struct mg_connection *conn, const char *prog) +{ + char *buf; + size_t buflen; + int headers_len, data_len, i, truncated; + int fdin[2] = {-1, -1}, fdout[2] = {-1, -1}, fderr[2] = {-1, -1}; + const char *status, *status_text, *connection_state; + char *pbuf, dir[PATH_MAX], *p; + struct mg_request_info ri; + struct cgi_environment blk; + FILE *in = NULL, *out = NULL, *err = NULL; + struct mg_file fout = STRUCT_FILE_INITIALIZER; + pid_t pid = (pid_t)-1; + struct process_control_data *proc = NULL; + +#if defined(USE_TIMERS) + double cgi_timeout = -1.0; + if (conn->dom_ctx->config[CGI_TIMEOUT]) { + /* Get timeout in seconds */ + cgi_timeout = atof(conn->dom_ctx->config[CGI_TIMEOUT]) * 0.001; + } +#endif + + buf = NULL; + buflen = conn->phys_ctx->max_request_size; + i = prepare_cgi_environment(conn, prog, &blk); + if (i != 0) { + blk.buf = NULL; + blk.var = NULL; + goto done; + } + + /* CGI must be executed in its own directory. 'dir' must point to the + * directory containing executable program, 'p' must point to the + * executable program name relative to 'dir'. */ + (void)mg_snprintf(conn, &truncated, dir, sizeof(dir), "%s", prog); + + if (truncated) { + mg_cry_internal(conn, "Error: CGI program \"%s\": Path too long", prog); + mg_send_http_error(conn, 500, "Error: %s", "CGI path too long"); + goto done; + } + + if ((p = strrchr(dir, '/')) != NULL) { + *p++ = '\0'; + } else { + dir[0] = '.'; + dir[1] = '\0'; + p = (char *)prog; + } + + if ((pipe(fdin) != 0) || (pipe(fdout) != 0) || (pipe(fderr) != 0)) { + status = strerror(ERRNO); + mg_cry_internal( + conn, + "Error: CGI program \"%s\": Can not create CGI pipes: %s", + prog, + status); + mg_send_http_error(conn, + 500, + "Error: Cannot create CGI pipe: %s", + status); + goto done; + } + + proc = (struct process_control_data *) + mg_malloc_ctx(sizeof(struct process_control_data), conn->phys_ctx); + if (proc == NULL) { + mg_cry_internal(conn, "Error: CGI program \"%s\": Out or memory", prog); + mg_send_http_error(conn, 500, "Error: Out of memory [%s]", prog); + goto done; + } + + DEBUG_TRACE("CGI: spawn %s %s\n", dir, p); + pid = spawn_process(conn, p, blk.buf, blk.var, fdin, fdout, fderr, dir); + + if (pid == (pid_t)-1) { + status = strerror(ERRNO); + mg_cry_internal( + conn, + "Error: CGI program \"%s\": Can not spawn CGI process: %s", + prog, + status); + mg_send_http_error(conn, + 500, + "Error: Cannot spawn CGI process [%s]: %s", + prog, + status); + mg_free(proc); + proc = NULL; + goto done; + } + + /* Store data in shared process_control_data */ + proc->pid = pid; + proc->references = 1; + +#if defined(USE_TIMERS) + if (cgi_timeout > 0.0) { + proc->references = 2; + + // Start a timer for CGI + timer_add(conn->phys_ctx, + cgi_timeout /* in seconds */, + 0.0, + 1, + abort_process, + (void *)proc); + } +#endif + + /* Parent closes only one side of the pipes. + * If we don't mark them as closed, close() attempt before + * return from this function throws an exception on Windows. + * Windows does not like when closed descriptor is closed again. */ + (void)close(fdin[0]); + (void)close(fdout[1]); + (void)close(fderr[1]); + fdin[0] = fdout[1] = fderr[1] = -1; + + if (((in = fdopen(fdin[1], "wb")) == NULL) + || ((out = fdopen(fdout[0], "rb")) == NULL) + || ((err = fdopen(fderr[0], "rb")) == NULL)) { + status = strerror(ERRNO); + mg_cry_internal(conn, + "Error: CGI program \"%s\": Can not open fd: %s", + prog, + status); + mg_send_http_error(conn, + 500, + "Error: CGI can not open fd\nfdopen: %s", + status); + goto done; + } + + setbuf(in, NULL); + setbuf(out, NULL); + setbuf(err, NULL); + fout.access.fp = out; + + if ((conn->content_len != 0) || (conn->is_chunked)) { + DEBUG_TRACE("CGI: send body data (%" INT64_FMT ")\n", + conn->content_len); + + /* This is a POST/PUT request, or another request with body data. */ + if (!forward_body_data(conn, in, INVALID_SOCKET, NULL)) { + /* Error sending the body data */ + mg_cry_internal( + conn, + "Error: CGI program \"%s\": Forward body data failed", + prog); + goto done; + } + } + + /* Close so child gets an EOF. */ + fclose(in); + in = NULL; + fdin[1] = -1; + + /* Now read CGI reply into a buffer. We need to set correct + * status code, thus we need to see all HTTP headers first. + * Do not send anything back to client, until we buffer in all + * HTTP headers. */ + data_len = 0; + buf = (char *)mg_malloc_ctx(buflen, conn->phys_ctx); + if (buf == NULL) { + mg_send_http_error(conn, + 500, + "Error: Not enough memory for CGI buffer (%u bytes)", + (unsigned int)buflen); + mg_cry_internal( + conn, + "Error: CGI program \"%s\": Not enough memory for buffer (%u " + "bytes)", + prog, + (unsigned int)buflen); + goto done; + } + + DEBUG_TRACE("CGI: %s", "wait for response"); + headers_len = read_message(out, conn, buf, (int)buflen, &data_len); + DEBUG_TRACE("CGI: response: %li", (signed long)headers_len); + + if (headers_len <= 0) { + + /* Could not parse the CGI response. Check if some error message on + * stderr. */ + i = pull_all(err, conn, buf, (int)buflen); + if (i > 0) { + /* CGI program explicitly sent an error */ + /* Write the error message to the internal log */ + mg_cry_internal(conn, + "Error: CGI program \"%s\" sent error " + "message: [%.*s]", + prog, + i, + buf); + /* Don't send the error message back to the client */ + mg_send_http_error(conn, + 500, + "Error: CGI program \"%s\" failed.", + prog); + } else { + /* CGI program did not explicitly send an error, but a broken + * respon header */ + mg_cry_internal(conn, + "Error: CGI program sent malformed or too big " + "(>%u bytes) HTTP headers: [%.*s]", + (unsigned)buflen, + data_len, + buf); + + mg_send_http_error(conn, + 500, + "Error: CGI program sent malformed or too big " + "(>%u bytes) HTTP headers: [%.*s]", + (unsigned)buflen, + data_len, + buf); + } + + /* in both cases, abort processing CGI */ + goto done; + } + + pbuf = buf; + buf[headers_len - 1] = '\0'; + ri.num_headers = parse_http_headers(&pbuf, ri.http_headers); + + /* Make up and send the status line */ + status_text = "OK"; + if ((status = get_header(ri.http_headers, ri.num_headers, "Status")) + != NULL) { + conn->status_code = atoi(status); + status_text = status; + while (isdigit((unsigned char)*status_text) || *status_text == ' ') { + status_text++; + } + } else if (get_header(ri.http_headers, ri.num_headers, "Location") + != NULL) { + conn->status_code = 307; + } else { + conn->status_code = 200; + } + connection_state = + get_header(ri.http_headers, ri.num_headers, "Connection"); + if (!header_has_option(connection_state, "keep-alive")) { + conn->must_close = 1; + } + + DEBUG_TRACE("CGI: response %u %s", conn->status_code, status_text); + + (void)mg_printf(conn, "HTTP/1.1 %d %s\r\n", conn->status_code, status_text); + + /* Send headers */ + for (i = 0; i < ri.num_headers; i++) { + DEBUG_TRACE("CGI header: %s: %s", + ri.http_headers[i].name, + ri.http_headers[i].value); + mg_printf(conn, + "%s: %s\r\n", + ri.http_headers[i].name, + ri.http_headers[i].value); + } + mg_write(conn, "\r\n", 2); + + /* Send chunk of data that may have been read after the headers */ + mg_write(conn, buf + headers_len, (size_t)(data_len - headers_len)); + + /* Read the rest of CGI output and send to the client */ + DEBUG_TRACE("CGI: %s", "forward all data"); + send_file_data(conn, &fout, 0, INT64_MAX); + DEBUG_TRACE("CGI: %s", "all data sent"); + +done: + mg_free(blk.var); + mg_free(blk.buf); + + if (pid != (pid_t)-1) { + abort_process((void *)proc); + } + + if (fdin[0] != -1) { + close(fdin[0]); + } + if (fdout[1] != -1) { + close(fdout[1]); + } + if (fderr[1] != -1) { + close(fderr[1]); + } + + if (in != NULL) { + fclose(in); + } else if (fdin[1] != -1) { + close(fdin[1]); + } + + if (out != NULL) { + fclose(out); + } else if (fdout[0] != -1) { + close(fdout[0]); + } + + if (err != NULL) { + fclose(err); + } else if (fderr[0] != -1) { + close(fderr[0]); + } + + mg_free(buf); +} +#endif /* !NO_CGI */ + + +#if !defined(NO_FILES) +static void +mkcol(struct mg_connection *conn, const char *path) +{ + int rc, body_len; + struct de de; + char date[64]; + time_t curtime = time(NULL); + + if (conn == NULL) { + return; + } + + /* TODO (mid): Check the mg_send_http_error situations in this function + */ + + memset(&de.file, 0, sizeof(de.file)); + if (!mg_stat(conn, path, &de.file)) { + mg_cry_internal(conn, + "%s: mg_stat(%s) failed: %s", + __func__, + path, + strerror(ERRNO)); + } + + if (de.file.last_modified) { + /* TODO (mid): This check does not seem to make any sense ! */ + /* TODO (mid): Add a webdav unit test first, before changing + * anything here. */ + mg_send_http_error( + conn, 405, "Error: mkcol(%s): %s", path, strerror(ERRNO)); + return; + } + + body_len = conn->data_len - conn->request_len; + if (body_len > 0) { + mg_send_http_error( + conn, 415, "Error: mkcol(%s): %s", path, strerror(ERRNO)); + return; + } + + rc = mg_mkdir(conn, path, 0755); + + if (rc == 0) { + conn->status_code = 201; + gmt_time_string(date, sizeof(date), &curtime); + mg_printf(conn, + "HTTP/1.1 %d Created\r\n" + "Date: %s\r\n", + conn->status_code, + date); + send_static_cache_header(conn); + send_additional_header(conn); + mg_printf(conn, + "Content-Length: 0\r\n" + "Connection: %s\r\n\r\n", + suggest_connection_header(conn)); + } else { + if (errno == EEXIST) { + mg_send_http_error( + conn, 405, "Error: mkcol(%s): %s", path, strerror(ERRNO)); + } else if (errno == EACCES) { + mg_send_http_error( + conn, 403, "Error: mkcol(%s): %s", path, strerror(ERRNO)); + } else if (errno == ENOENT) { + mg_send_http_error( + conn, 409, "Error: mkcol(%s): %s", path, strerror(ERRNO)); + } else { + mg_send_http_error( + conn, 500, "fopen(%s): %s", path, strerror(ERRNO)); + } + } +} + + +static void +put_file(struct mg_connection *conn, const char *path) +{ + struct mg_file file = STRUCT_FILE_INITIALIZER; + const char *range; + int64_t r1, r2; + int rc; + char date[64]; + time_t curtime = time(NULL); + + if (conn == NULL) { + return; + } + + if (mg_stat(conn, path, &file.stat)) { + /* File already exists */ + conn->status_code = 200; + + if (file.stat.is_directory) { + /* This is an already existing directory, + * so there is nothing to do for the server. */ + rc = 0; + + } else { + /* File exists and is not a directory. */ + /* Can it be replaced? */ + +#if defined(MG_USE_OPEN_FILE) + if (file.access.membuf != NULL) { + /* This is an "in-memory" file, that can not be replaced */ + mg_send_http_error(conn, + 405, + "Error: Put not possible\nReplacing %s " + "is not supported", + path); + return; + } +#endif + + /* Check if the server may write this file */ + if (access(path, W_OK) == 0) { + /* Access granted */ + conn->status_code = 200; + rc = 1; + } else { + mg_send_http_error( + conn, + 403, + "Error: Put not possible\nReplacing %s is not allowed", + path); + return; + } + } + } else { + /* File should be created */ + conn->status_code = 201; + rc = put_dir(conn, path); + } + + if (rc == 0) { + /* put_dir returns 0 if path is a directory */ + gmt_time_string(date, sizeof(date), &curtime); + mg_printf(conn, + "HTTP/1.1 %d %s\r\n", + conn->status_code, + mg_get_response_code_text(NULL, conn->status_code)); + send_no_cache_header(conn); + send_additional_header(conn); + mg_printf(conn, + "Date: %s\r\n" + "Content-Length: 0\r\n" + "Connection: %s\r\n\r\n", + date, + suggest_connection_header(conn)); + + /* Request to create a directory has been fulfilled successfully. + * No need to put a file. */ + return; + } + + if (rc == -1) { + /* put_dir returns -1 if the path is too long */ + mg_send_http_error(conn, + 414, + "Error: Path too long\nput_dir(%s): %s", + path, + strerror(ERRNO)); + return; + } + + if (rc == -2) { + /* put_dir returns -2 if the directory can not be created */ + mg_send_http_error(conn, + 500, + "Error: Can not create directory\nput_dir(%s): %s", + path, + strerror(ERRNO)); + return; + } + + /* A file should be created or overwritten. */ + /* Currently CivetWeb does not nead read+write access. */ + if (!mg_fopen(conn, path, MG_FOPEN_MODE_WRITE, &file) + || file.access.fp == NULL) { + (void)mg_fclose(&file.access); + mg_send_http_error(conn, + 500, + "Error: Can not create file\nfopen(%s): %s", + path, + strerror(ERRNO)); + return; + } + + fclose_on_exec(&file.access, conn); + range = mg_get_header(conn, "Content-Range"); + r1 = r2 = 0; + if ((range != NULL) && parse_range_header(range, &r1, &r2) > 0) { + conn->status_code = 206; /* Partial content */ + fseeko(file.access.fp, r1, SEEK_SET); + } + + if (!forward_body_data(conn, file.access.fp, INVALID_SOCKET, NULL)) { + /* forward_body_data failed. + * The error code has already been sent to the client, + * and conn->status_code is already set. */ + (void)mg_fclose(&file.access); + return; + } + + if (mg_fclose(&file.access) != 0) { + /* fclose failed. This might have different reasons, but a likely + * one is "no space on disk", http 507. */ + conn->status_code = 507; + } + + gmt_time_string(date, sizeof(date), &curtime); + mg_printf(conn, + "HTTP/1.1 %d %s\r\n", + conn->status_code, + mg_get_response_code_text(NULL, conn->status_code)); + send_no_cache_header(conn); + send_additional_header(conn); + mg_printf(conn, + "Date: %s\r\n" + "Content-Length: 0\r\n" + "Connection: %s\r\n\r\n", + date, + suggest_connection_header(conn)); +} + + +static void +delete_file(struct mg_connection *conn, const char *path) +{ + struct de de; + memset(&de.file, 0, sizeof(de.file)); + if (!mg_stat(conn, path, &de.file)) { + /* mg_stat returns 0 if the file does not exist */ + mg_send_http_error(conn, + 404, + "Error: Cannot delete file\nFile %s not found", + path); + return; + } + +#if 0 /* Ignore if a file in memory is inside a folder */ + if (de.access.membuf != NULL) { + /* the file is cached in memory */ + mg_send_http_error( + conn, + 405, + "Error: Delete not possible\nDeleting %s is not supported", + path); + return; + } +#endif + + if (de.file.is_directory) { + if (remove_directory(conn, path)) { + /* Delete is successful: Return 204 without content. */ + mg_send_http_error(conn, 204, "%s", ""); + } else { + /* Delete is not successful: Return 500 (Server error). */ + mg_send_http_error(conn, 500, "Error: Could not delete %s", path); + } + return; + } + + /* This is an existing file (not a directory). + * Check if write permission is granted. */ + if (access(path, W_OK) != 0) { + /* File is read only */ + mg_send_http_error( + conn, + 403, + "Error: Delete not possible\nDeleting %s is not allowed", + path); + return; + } + + /* Try to delete it. */ + if (mg_remove(conn, path) == 0) { + /* Delete was successful: Return 204 without content. */ + mg_send_http_error(conn, 204, "%s", ""); + } else { + /* Delete not successful (file locked). */ + mg_send_http_error(conn, + 423, + "Error: Cannot delete file\nremove(%s): %s", + path, + strerror(ERRNO)); + } +} +#endif /* !NO_FILES */ + + +#if !defined(NO_FILESYSTEMS) +static void +send_ssi_file(struct mg_connection *, const char *, struct mg_file *, int); + + +static void +do_ssi_include(struct mg_connection *conn, + const char *ssi, + char *tag, + int include_level) +{ + char file_name[MG_BUF_LEN], path[512], *p; + struct mg_file file = STRUCT_FILE_INITIALIZER; + size_t len; + int truncated = 0; + + if (conn == NULL) { + return; + } + + /* sscanf() is safe here, since send_ssi_file() also uses buffer + * of size MG_BUF_LEN to get the tag. So strlen(tag) is + * always < MG_BUF_LEN. */ + if (sscanf(tag, " virtual=\"%511[^\"]\"", file_name) == 1) { + /* File name is relative to the webserver root */ + file_name[511] = 0; + (void)mg_snprintf(conn, + &truncated, + path, + sizeof(path), + "%s/%s", + conn->dom_ctx->config[DOCUMENT_ROOT], + file_name); + + } else if (sscanf(tag, " abspath=\"%511[^\"]\"", file_name) == 1) { + /* File name is relative to the webserver working directory + * or it is absolute system path */ + file_name[511] = 0; + (void) + mg_snprintf(conn, &truncated, path, sizeof(path), "%s", file_name); + + } else if ((sscanf(tag, " file=\"%511[^\"]\"", file_name) == 1) + || (sscanf(tag, " \"%511[^\"]\"", file_name) == 1)) { + /* File name is relative to the currect document */ + file_name[511] = 0; + (void)mg_snprintf(conn, &truncated, path, sizeof(path), "%s", ssi); + + if (!truncated) { + if ((p = strrchr(path, '/')) != NULL) { + p[1] = '\0'; + } + len = strlen(path); + (void)mg_snprintf(conn, + &truncated, + path + len, + sizeof(path) - len, + "%s", + file_name); + } + + } else { + mg_cry_internal(conn, "Bad SSI #include: [%s]", tag); + return; + } + + if (truncated) { + mg_cry_internal(conn, "SSI #include path length overflow: [%s]", tag); + return; + } + + if (!mg_fopen(conn, path, MG_FOPEN_MODE_READ, &file)) { + mg_cry_internal(conn, + "Cannot open SSI #include: [%s]: fopen(%s): %s", + tag, + path, + strerror(ERRNO)); + } else { + fclose_on_exec(&file.access, conn); + if (match_prefix(conn->dom_ctx->config[SSI_EXTENSIONS], + strlen(conn->dom_ctx->config[SSI_EXTENSIONS]), + path) + > 0) { + send_ssi_file(conn, path, &file, include_level + 1); + } else { + send_file_data(conn, &file, 0, INT64_MAX); + } + (void)mg_fclose(&file.access); /* Ignore errors for readonly files */ + } +} + + +#if !defined(NO_POPEN) +static void +do_ssi_exec(struct mg_connection *conn, char *tag) +{ + char cmd[1024] = ""; + struct mg_file file = STRUCT_FILE_INITIALIZER; + + if (sscanf(tag, " \"%1023[^\"]\"", cmd) != 1) { + mg_cry_internal(conn, "Bad SSI #exec: [%s]", tag); + } else { + cmd[1023] = 0; + if ((file.access.fp = popen(cmd, "r")) == NULL) { + mg_cry_internal(conn, + "Cannot SSI #exec: [%s]: %s", + cmd, + strerror(ERRNO)); + } else { + send_file_data(conn, &file, 0, INT64_MAX); + pclose(file.access.fp); + } + } +} +#endif /* !NO_POPEN */ + + +static int +mg_fgetc(struct mg_file *filep, int offset) +{ + (void)offset; /* unused in case MG_USE_OPEN_FILE is set */ + + if (filep == NULL) { + return EOF; + } +#if defined(MG_USE_OPEN_FILE) + if ((filep->access.membuf != NULL) && (offset >= 0) + && (((unsigned int)(offset)) < filep->stat.size)) { + return ((const unsigned char *)filep->access.membuf)[offset]; + } else /* else block below */ +#endif + if (filep->access.fp != NULL) { + return fgetc(filep->access.fp); + } else { + return EOF; + } +} + + +static void +send_ssi_file(struct mg_connection *conn, + const char *path, + struct mg_file *filep, + int include_level) +{ + char buf[MG_BUF_LEN]; + int ch, offset, len, in_tag, in_ssi_tag; + + if (include_level > 10) { + mg_cry_internal(conn, "SSI #include level is too deep (%s)", path); + return; + } + + in_tag = in_ssi_tag = len = offset = 0; + + /* Read file, byte by byte, and look for SSI include tags */ + while ((ch = mg_fgetc(filep, offset++)) != EOF) { + + if (in_tag) { + /* We are in a tag, either SSI tag or html tag */ + + if (ch == '>') { + /* Tag is closing */ + buf[len++] = '>'; + + if (in_ssi_tag) { + /* Handle SSI tag */ + buf[len] = 0; + + if ((len > 12) && !memcmp(buf + 5, "include", 7)) { + do_ssi_include(conn, path, buf + 12, include_level + 1); +#if !defined(NO_POPEN) + } else if ((len > 9) && !memcmp(buf + 5, "exec", 4)) { + do_ssi_exec(conn, buf + 9); +#endif /* !NO_POPEN */ + } else { + mg_cry_internal(conn, + "%s: unknown SSI " + "command: \"%s\"", + path, + buf); + } + len = 0; + in_ssi_tag = in_tag = 0; + + } else { + /* Not an SSI tag */ + /* Flush buffer */ + (void)mg_write(conn, buf, (size_t)len); + len = 0; + in_tag = 0; + } + + } else { + /* Tag is still open */ + buf[len++] = (char)(ch & 0xff); + + if ((len == 5) && !memcmp(buf, " deny all accesses, except from localhost (IPv4) + // Example 2: "-0.0.0.0/0,+192.168/16" + // ---> deny all accesses, except from the + // 192.168/16 subnet + // + buffer = parse_FTLconf(fp, "WEBACL"); + if(buffer != NULL) + { + httpsettings.acl = strdup(buffer); + logg(" WEBACL: Using access control list."); + } + else + { + httpsettings.acl = ""; + logg(" WEBACL: Allowing all access."); + } + + // Read DEBUG_... setting from pihole-FTL.conf + // This option should be the last one as it causes + // some rather verbose output into the log when + // listing all the enabled/disabled debugging options + read_debuging_settings(fp); + logg("Finished config file parsing"); // Release memory diff --git a/src/config.h b/src/config.h index cf6f345ee..27225b05b 100644 --- a/src/config.h +++ b/src/config.h @@ -56,6 +56,7 @@ typedef struct { typedef struct httpsettings { char *webroot; char *webhome; + const char *acl; char port[20]; // enough space for 2*(maximum length of number in a uint16_t = 5 characters) + ",[::]:" + NULL } httpsettingsStruct; From 6e9cb2c2d38135852f76ebc49c3cd80a1dbe064a Mon Sep 17 00:00:00 2001 From: DL6ER Date: Mon, 18 Nov 2019 22:50:10 +0100 Subject: [PATCH 0079/1669] Log HTTP server errors into pihole-FTL.log. Plus log all access to the server when DEBUG_API=true Signed-off-by: DL6ER --- src/api/http.c | 29 ++++++++++++++++++----------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/src/api/http.c b/src/api/http.c index 4a18d3bba..17172e84e 100644 --- a/src/api/http.c +++ b/src/api/http.c @@ -99,11 +99,6 @@ static int api_handler(struct mg_connection *conn, void *ignored) const struct mg_request_info *request = mg_get_request_info(conn); // HTTP response int ret = 0; - if(config.debug & DEBUG_API) - { - logg("Received request for %s (method %s)", - request->local_uri, request->request_method); - } /******************************** api/dns ********************************/ if(startsWith("/api/dns/status", request->local_uri)) { @@ -290,9 +285,6 @@ static int index_handler(struct mg_connection *conn, void *ignored) if(strstr(request->local_uri, ".") > strstr(request->local_uri, "/")) { - if(config.debug & DEBUG_API) - logg("Received request for %s", request->local_uri); - // Found file extension, process as usual return 0; } @@ -300,7 +292,6 @@ static int index_handler(struct mg_connection *conn, void *ignored) logg("Received request for %s -> rerouting to index.html", request->local_uri); // Plain request found, we serve the index.html file we have in memory - logg("Sending index.html from in memory"); if(indexfile_content != NULL) { mg_send_http_ok(conn, "text/html", NULL, strlen(indexfile_content)); @@ -309,12 +300,20 @@ static int index_handler(struct mg_connection *conn, void *ignored) } else { + logg("ERROR: index.hmtl not available, responding with Error 500."); send_http_error(conn); return 500; } } +static int log_http_message(const struct mg_connection *conn, const char *message) +{ + logg("HTTP: %s", message); + // A non-zero return value signals we logged something. + return 1; +} + void http_init(void) { logg("Initializing HTTP server on port %s", httpsettings.port); @@ -340,10 +339,18 @@ void http_init(void) NULL }; + // Configure logging handler + struct mg_callbacks callbacks = {NULL}; + callbacks.log_message = log_http_message; + + // We log all access to pihole-FTL.log when in API debugging mode + if(config.debug & DEBUG_API) + callbacks.log_access = log_http_message; + /* Start the server */ - if((ctx = mg_start(NULL, NULL, options)) == NULL) + if((ctx = mg_start(&callbacks, NULL, options)) == NULL) { - logg("Initializing HTTP library failed!"); + logg("ERROR: Initializing HTTP library failed!"); return; } From a7bd6fea250c483ba11a86f99ed7917e184242b1 Mon Sep 17 00:00:00 2001 From: DL6ER Date: Mon, 18 Nov 2019 22:54:45 +0100 Subject: [PATCH 0080/1669] Move detection of API debugging mode into a dedicated subroutine to ensure DEBUG_API can also be enabled during a running FTL session. Signed-off-by: DL6ER --- src/api/http.c | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/src/api/http.c b/src/api/http.c index 17172e84e..0a4a34ccb 100644 --- a/src/api/http.c +++ b/src/api/http.c @@ -309,8 +309,16 @@ static int index_handler(struct mg_connection *conn, void *ignored) static int log_http_message(const struct mg_connection *conn, const char *message) { - logg("HTTP: %s", message); - // A non-zero return value signals we logged something. + logg("HTTP info: %s", message); + return 1; +} + +static int log_http_access(const struct mg_connection *conn, const char *message) +{ + // Only log when in API debugging mode + if(config.debug & DEBUG_API) + logg("HTTP access: %s", message); + return 1; } @@ -344,8 +352,7 @@ void http_init(void) callbacks.log_message = log_http_message; // We log all access to pihole-FTL.log when in API debugging mode - if(config.debug & DEBUG_API) - callbacks.log_access = log_http_message; + callbacks.log_access = log_http_access; /* Start the server */ if((ctx = mg_start(&callbacks, NULL, options)) == NULL) From e2d72189e7f28e7db4167cdf016e7c9d26f9fecc Mon Sep 17 00:00:00 2001 From: DL6ER Date: Mon, 18 Nov 2019 23:07:04 +0100 Subject: [PATCH 0081/1669] Do not consider ERROR index.html as fatal during the tests + typo fixes. Signed-off-by: DL6ER --- src/api/http.c | 5 +++-- test/test_suite.bats | 4 ++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/api/http.c b/src/api/http.c index 0a4a34ccb..3161ed2a5 100644 --- a/src/api/http.c +++ b/src/api/http.c @@ -223,7 +223,7 @@ static void read_indexfile(void) FILE *indexfile = fopen(index_path, "r"); if(indexfile == NULL) { - logg("ERROR. Cannot open \"%s\"", index_path); + logg("ERROR: Cannot open \"%s\"", index_path); free(index_path); return; } @@ -262,6 +262,7 @@ static void read_indexfile(void) { logg("ERROR: No tag found in \"%s\"", index_path); free(index_path); + free(base_tag); return; } @@ -300,7 +301,7 @@ static int index_handler(struct mg_connection *conn, void *ignored) } else { - logg("ERROR: index.hmtl not available, responding with Error 500."); + logg("ERROR: index.html not available, responding with Error 500."); send_http_error(conn); return 500; } diff --git a/test/test_suite.bats b/test/test_suite.bats index 2644a0eba..e96cf1015 100644 --- a/test/test_suite.bats +++ b/test/test_suite.bats @@ -175,8 +175,8 @@ [[ ${lines[0]} == "0" ]] } -@test "No ERROR messages in pihole-FTL.log" { - run bash -c 'grep -c "ERROR:" /var/log/pihole-FTL.log' +@test "No ERROR messages in pihole-FTL.log (besides known index.html error)" { + run bash -c 'grep "ERROR:" /var/log/pihole-FTL.log | grep -c -v -E "index\.html"' printf "%s\n" "${lines[@]}" [[ ${lines[0]} == "0" ]] } From c1b25642e0636e885c28a17870cadd9cb746b7a4 Mon Sep 17 00:00:00 2001 From: DL6ER Date: Mon, 18 Nov 2019 23:07:46 +0100 Subject: [PATCH 0082/1669] ACL: Set default to allow all access. Signed-off-by: DL6ER --- src/config.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/config.c b/src/config.c index 0a7d39805..40f20b05f 100644 --- a/src/config.c +++ b/src/config.c @@ -403,7 +403,8 @@ void read_FTLconf(void) } else { - httpsettings.acl = ""; + // Default: allow all access + httpsettings.acl = "+0.0.0.0/0"; logg(" WEBACL: Allowing all access."); } From 0634bf363be6779a6ac46987a6aef4faab706ada Mon Sep 17 00:00:00 2001 From: DL6ER Date: Tue, 19 Nov 2019 23:01:49 +0100 Subject: [PATCH 0083/1669] Implement correct API response for failed auth requests. Signed-off-by: DL6ER --- src/api/auth.c | 5 ++++- src/api/http.c | 9 ++++++--- src/api/http.h | 4 ++-- src/api/json_macros.h | 4 ++-- src/civetweb/civetweb.c | 35 +++++++++++++++++++++++++++++++++++ src/civetweb/civetweb.h | 7 +++++++ 6 files changed, 56 insertions(+), 8 deletions(-) diff --git a/src/api/auth.c b/src/api/auth.c index cd69a49eb..84d948414 100644 --- a/src/api/auth.c +++ b/src/api/auth.c @@ -185,7 +185,10 @@ int api_auth(struct mg_connection *conn) } else { - JSON_OBJ_REF_STR(json, "key", "unauthorized"); + cJSON *error = JSON_NEW_OBJ(); + JSON_OBJ_REF_STR(error, "key", "unauthorized"); + JSON_OBJ_ADD_NULL(error, "data"); + JSON_OBJ_ADD_ITEM(json, "error", error); char *additional_headers = strdup("Set-Cookie: user_id=deleted; Path=/; Max-Age=-1\r\n"); JSON_SENT_OBJECT_AND_HEADERS_CODE(json, 401, additional_headers); } diff --git a/src/api/http.c b/src/api/http.c index 3161ed2a5..c93735b5c 100644 --- a/src/api/http.c +++ b/src/api/http.c @@ -25,12 +25,15 @@ int send_http(struct mg_connection *conn, const char *mime_type, return mg_write(conn, msg, strlen(msg)); } -int send_http_code(struct mg_connection *conn, int code, - const char *additional_headers, const char *msg) +int send_http_code(struct mg_connection *conn, const char *mime_type, + const char *additional_headers, int code, const char *msg) { // Payload will be sent with text/plain encoding due to // the first line being "Error " by definition - return mg_send_http_error(conn, code, "%s", msg); + //return mg_send_http_error(conn, code, "%s", msg); + my_send_http_error_headers(conn, code, mime_type, + additional_headers, strlen(msg)); + return mg_write(conn, msg, strlen(msg)); } int send_http_error(struct mg_connection *conn) diff --git a/src/api/http.h b/src/api/http.h index 20c7f996e..2877a34bb 100644 --- a/src/api/http.h +++ b/src/api/http.h @@ -26,8 +26,8 @@ void http_send(struct mg_connection *conn, bool chunk, const char *format, ...) int send_http(struct mg_connection *conn, const char *mime_type, const char *additional_headers, const char *msg); -int send_http_code(struct mg_connection *conn, int code, - const char *additional_headers, const char *msg); +int send_http_code(struct mg_connection *conn, const char *mime_type, + const char *additional_headers, int code, const char *msg); int send_http_error(struct mg_connection *conn); // Cookie routines diff --git a/src/api/json_macros.h b/src/api/json_macros.h index 195904181..cbd2f17c0 100644 --- a/src/api/json_macros.h +++ b/src/api/json_macros.h @@ -143,7 +143,7 @@ send_http_error(conn); \ return 500; \ } \ - send_http_code(conn, code, NULL, msg); \ + send_http_code(conn, "application/json; charset=utf-8", NULL, code, msg); \ cJSON_Delete(object); \ return 200; \ } @@ -170,7 +170,7 @@ send_http_error(conn); \ return 500; \ } \ - send_http_code(conn, code, additional_headers, msg); \ + send_http_code(conn, "application/json; charset=utf-8", additional_headers, code, msg); \ cJSON_Delete(object); \ free(additional_headers); \ return code; \ diff --git a/src/civetweb/civetweb.c b/src/civetweb/civetweb.c index e742cfebb..165e29ca9 100644 --- a/src/civetweb/civetweb.c +++ b/src/civetweb/civetweb.c @@ -4785,6 +4785,41 @@ mg_send_http_error_impl(struct mg_connection *conn, } +/************************************** Pi-hole method **************************************/ +void my_send_http_error_headers(struct mg_connection *conn, + int status, const char* mime_type, + const char *additional_headers, + long long content_length) +{ + /* Set status (for log) */ + conn->status_code = status; + const char *status_text = mg_get_response_code_text(conn, status); + mg_printf(conn, "HTTP/1.1 %d %s\r\n", status, status_text); + + send_no_cache_header(conn); + send_additional_header(conn); + conn->must_close = 1; + + char date[64]; + time_t curtime = time(NULL); + gmt_time_string(date, sizeof(date), &curtime); + mg_printf(conn, "Content-Type: %s\r\n" + "Date: %s\r\n" + "Connection: close\r\n", + mime_type, + date); + + // We may use additional headers to set or clear cookies + if(additional_headers != NULL && strlen(additional_headers) > 0) + { + mg_write(conn, additional_headers, strlen(additional_headers)); + } + + mg_printf(conn, "Content-Length: %" UINT64_FMT "\r\n\r\n", + (uint64_t)content_length); +} +/********************************************************************************************/ + int mg_send_http_error(struct mg_connection *conn, int status, const char *fmt, ...) { diff --git a/src/civetweb/civetweb.h b/src/civetweb/civetweb.h index f3b69ef28..b0414678e 100644 --- a/src/civetweb/civetweb.h +++ b/src/civetweb/civetweb.h @@ -932,6 +932,13 @@ CIVETWEB_API int mg_send_http_error(struct mg_connection *conn, PRINTF_FORMAT_STRING(const char *fmt), ...) PRINTF_ARGS(3, 4); +/************************************** Pi-hole method **************************************/ +void my_send_http_error_headers(struct mg_connection *conn, + int status, const char* mime_type, + const char *additional_headers, + long long content_length); +/********************************************************************************************/ + /* Send "HTTP 200 OK" response header. * After calling this function, use mg_write or mg_send_chunk to send the From 3efb39c3aff25a424dda68f6001edac62dc8942d Mon Sep 17 00:00:00 2001 From: DL6ER Date: Wed, 20 Nov 2019 08:04:08 +0100 Subject: [PATCH 0084/1669] Rename api/http.{c,h} to api/http-common.{c,h}. Separate routing function into api/routes.c. Signed-off-by: DL6ER --- Makefile | 2 +- src/api/auth.c | 5 +- src/api/dns.c | 6 +- src/api/ftl.c | 4 +- src/api/{http.c => http-common.c} | 118 +--------------------------- src/api/{http.h => http-common.h} | 3 + src/api/json_macros.h | 1 - src/api/routes.c | 125 ++++++++++++++++++++++++++++++ src/api/{api.h => routes.h} | 14 ++-- src/api/settings.c | 4 +- src/api/stats.c | 4 +- src/api/version.c | 4 +- src/dnsmasq_interface.c | 4 +- src/main.c | 2 +- 14 files changed, 162 insertions(+), 134 deletions(-) rename src/api/{http.c => http-common.c} (71%) rename src/api/{http.h => http-common.h} (94%) create mode 100644 src/api/routes.c rename src/api/{api.h => routes.h} (90%) diff --git a/Makefile b/Makefile index aa20eea8a..bddb11f0f 100644 --- a/Makefile +++ b/Makefile @@ -17,7 +17,7 @@ DNSMASQ_OPTS = -DHAVE_DNSSEC -DHAVE_DNSSEC_STATIC -DHAVE_IDN FTL_DEPS = *.h database/*.h api/*.h version.h FTL_DB_OBJ = database/common.o database/query-table.o database/network-table.o database/gravity-db.o database/database-thread.o \ database/sqlite3-ext.o database/message-table.o -FTL_API_OBJ = api/http.o api/ftl.o api/stats.o api/dns.o api/version.o api/auth.o api/settings.o +FTL_API_OBJ = api/http-common.o api/routes.o api/ftl.o api/stats.o api/dns.o api/version.o api/auth.o api/settings.o FTL_OBJ = $(FTL_DB_OBJ) $(FTL_API_OBJ) main.o memory.o log.o daemon.o datastructure.o signals.o files.o setupVars.o args.o gc.o config.o dnsmasq_interface.o resolve.o regex.o shmem.o capabilities.o overTime.o timers.o vector.o DNSMASQ_DEPS = config.h dhcp-protocol.h dns-protocol.h radv-protocol.h dhcp6-protocol.h dnsmasq.h ip6addr.h metrics.h ../dnsmasq_interface.h diff --git a/src/api/auth.c b/src/api/auth.c index 84d948414..21e76c7cb 100644 --- a/src/api/auth.c +++ b/src/api/auth.c @@ -9,7 +9,9 @@ * Please see LICENSE file for your rights under this license. */ #include "FTL.h" -#include "api.h" +#include "http-common.h" +#include "routes.h" +#include "json_macros.h" #include "log.h" #include "config.h" // read_setupVarsconf() @@ -187,6 +189,7 @@ int api_auth(struct mg_connection *conn) { cJSON *error = JSON_NEW_OBJ(); JSON_OBJ_REF_STR(error, "key", "unauthorized"); + JSON_OBJ_REF_STR(error, "message", "Unauthorized"); JSON_OBJ_ADD_NULL(error, "data"); JSON_OBJ_ADD_ITEM(json, "error", error); char *additional_headers = strdup("Set-Cookie: user_id=deleted; Path=/; Max-Age=-1\r\n"); diff --git a/src/api/dns.c b/src/api/dns.c index 3bfd479ee..00d658af2 100644 --- a/src/api/dns.c +++ b/src/api/dns.c @@ -9,11 +9,13 @@ * Please see LICENSE file for your rights under this license. */ #include "FTL.h" -#include "api.h" // counters #include "shmem.h" +#include "http-common.h" +#include "routes.h" +#include "json_macros.h" #include "database/gravity-db.h" -#include "api/http.h" +#include "api/http-common.h" #include "log.h" // {s,g}et_blockingstatus() #include "setupVars.h" diff --git a/src/api/ftl.c b/src/api/ftl.c index f2214dbab..42b70e119 100644 --- a/src/api/ftl.c +++ b/src/api/ftl.c @@ -9,7 +9,9 @@ * Please see LICENSE file for your rights under this license. */ #include "FTL.h" -#include "api.h" +#include "http-common.h" +#include "routes.h" +#include "json_macros.h" #include "datastructure.h" // get_FTL_version() #include "log.h" diff --git a/src/api/http.c b/src/api/http-common.c similarity index 71% rename from src/api/http.c rename to src/api/http-common.c index c93735b5c..bb6a492dc 100644 --- a/src/api/http.c +++ b/src/api/http-common.c @@ -3,14 +3,14 @@ * Network-wide ad blocking via your own hardware. * * FTL Engine -* HTTP server routines +* Common HTTP server routines * * This file is copyright under the latest version of the EUPL. * Please see LICENSE file for your rights under this license. */ #include "FTL.h" -#include "api.h" -#include "http.h" +#include "routes.h" +#include "http-common.h" #include "../config.h" #include "../log.h" #include "json_macros.h" @@ -41,7 +41,7 @@ int send_http_error(struct mg_connection *conn) return mg_send_http_error(conn, 500, "Internal server error"); } -static bool startsWith(const char *path, const char *uri) +bool __attribute__((pure)) startsWith(const char *path, const char *uri) { // We subtract 1 to include the trailing slash in webhome unsigned int webhome_length = strlen(httpsettings.webhome)-1u; @@ -97,116 +97,6 @@ static int print_simple(struct mg_connection *conn, void *input) return send_http(conn, "text/plain", NULL, input); } -static int api_handler(struct mg_connection *conn, void *ignored) -{ - const struct mg_request_info *request = mg_get_request_info(conn); - // HTTP response - int ret = 0; - /******************************** api/dns ********************************/ - if(startsWith("/api/dns/status", request->local_uri)) - { - ret = api_dns_status(conn); - } - else if(startsWith("/api/dns/whitelist/exact", request->local_uri)) - { - ret = api_dns_somelist(conn, true, true); - } - else if(startsWith("/api/dns/whitelist/regex", request->local_uri)) - { - ret = api_dns_somelist(conn, false, true); - } - else if(startsWith("/api/dns/blacklist/exact", request->local_uri)) - { - ret = api_dns_somelist(conn, true, false); - } - else if(startsWith("/api/dns/blacklist/regex", request->local_uri)) - { - ret = api_dns_somelist(conn, false, false); - } - /******************************** api/ftl ****************************/ - else if(startsWith("/api/ftl/clientIP", request->local_uri)) - { - ret = api_ftl_clientIP(conn); - } - /******************************** api/stats **************************/ - else if(startsWith("/api/stats/summary", request->local_uri)) - { - ret = api_stats_summary(conn); - } - else if(startsWith("/api/stats/overTime/history", request->local_uri)) - { - ret = api_stats_overTime_history(conn); - } - else if(startsWith("/api/stats/overTime/clients", request->local_uri)) - { - ret = api_stats_overTime_clients(conn); - } - else if(startsWith("/api/stats/query_types", request->local_uri)) - { - ret = api_stats_query_types(conn); - } - else if(startsWith("/api/stats/upstreams", request->local_uri)) - { - ret = api_stats_upstreams(conn); - } - else if(startsWith("/api/stats/top_domains", request->local_uri)) - { - ret = api_stats_top_domains(false, conn); - } - else if(startsWith("/api/stats/top_blocked", request->local_uri)) - { - ret = api_stats_top_domains(true, conn); - } - else if(startsWith("/api/stats/top_clients", request->local_uri)) - { - ret = api_stats_top_clients(false, conn); - } - else if(startsWith("/api/stats/top_blocked_clients", request->local_uri)) - { - ret = api_stats_top_clients(true, conn); - } - else if(startsWith("/api/stats/history", request->local_uri)) - { - ret = api_stats_history(conn); - } - else if(startsWith("/api/stats/recent_blocked", request->local_uri)) - { - ret = api_stats_recentblocked(conn); - } - /******************************** api/version ****************************/ - else if(startsWith("/api/version", request->local_uri)) - { - ret = api_version(conn); - } - /******************************** api/auth ****************************/ - else if(startsWith("/api/auth", request->local_uri)) - { - ret = api_auth(conn); - } - else if(startsWith("/api/auth/salt", request->local_uri)) - { - ret = api_auth_salt(conn); - } - /******************************** api/settings ****************************/ - else if(startsWith("/api/settings/web", request->local_uri)) - { - ret = api_settings_web(conn); - } - else if(startsWith("/api/settings/ftldb", request->local_uri)) - { - ret = api_settings_ftldb(conn); - } - /******************************** not found ******************************/ -/* else - { - cJSON *json = JSON_NEW_OBJ(); - JSON_OBJ_REF_STR(json, "status", "requested path is not available"); - JSON_OBJ_REF_STR(json, "path", request->local_uri); - JSON_SENT_OBJECT(json); - }*/ - return ret; -} - static char *indexfile_content = NULL; static void read_indexfile(void) { diff --git a/src/api/http.h b/src/api/http-common.h similarity index 94% rename from src/api/http.h rename to src/api/http-common.h index 2877a34bb..a9dd96d5d 100644 --- a/src/api/http.h +++ b/src/api/http-common.h @@ -41,4 +41,7 @@ bool http_get_cookie_str(struct mg_connection *conn, const char *cookieName, cha enum { HTTP_UNKNOWN, HTTP_GET, HTTP_POST, HTTP_DELETE }; int http_method(struct mg_connection *conn); +// Utils +bool startsWith(const char *path, const char *uri) __attribute__((pure)); + #endif // HTTP_H \ No newline at end of file diff --git a/src/api/json_macros.h b/src/api/json_macros.h index cbd2f17c0..f44c9da0f 100644 --- a/src/api/json_macros.h +++ b/src/api/json_macros.h @@ -9,7 +9,6 @@ * Please see LICENSE file for your rights under this license. */ #include "../cJSON/cJSON.h" -#include "http.h" // Provides a compile-time flag for JSON formatting // This should never be needed as all modern browsers diff --git a/src/api/routes.c b/src/api/routes.c new file mode 100644 index 000000000..efdbc9425 --- /dev/null +++ b/src/api/routes.c @@ -0,0 +1,125 @@ +/* Pi-hole: A black hole for Internet advertisements +* (c) 2019 Pi-hole, LLC (https://pi-hole.net) +* Network-wide ad blocking via your own hardware. +* +* FTL Engine +* API routes +* +* This file is copyright under the latest version of the EUPL. +* Please see LICENSE file for your rights under this license. */ + +#include "../FTL.h" +// struct mg_connection +#include "../civetweb/civetweb.h" +#include "../api/http-common.h" +#include "routes.h" + +int api_handler(struct mg_connection *conn, void *ignored) +{ + const struct mg_request_info *request = mg_get_request_info(conn); + // HTTP response + int ret = 0; + /******************************** api/dns ********************************/ + if(startsWith("/api/dns/status", request->local_uri)) + { + ret = api_dns_status(conn); + } + else if(startsWith("/api/dns/whitelist/exact", request->local_uri)) + { + ret = api_dns_somelist(conn, true, true); + } + else if(startsWith("/api/dns/whitelist/regex", request->local_uri)) + { + ret = api_dns_somelist(conn, false, true); + } + else if(startsWith("/api/dns/blacklist/exact", request->local_uri)) + { + ret = api_dns_somelist(conn, true, false); + } + else if(startsWith("/api/dns/blacklist/regex", request->local_uri)) + { + ret = api_dns_somelist(conn, false, false); + } + /******************************** api/ftl ****************************/ + else if(startsWith("/api/ftl/clientIP", request->local_uri)) + { + ret = api_ftl_clientIP(conn); + } + /******************************** api/stats **************************/ + else if(startsWith("/api/stats/summary", request->local_uri)) + { + ret = api_stats_summary(conn); + } + else if(startsWith("/api/stats/overTime/history", request->local_uri)) + { + ret = api_stats_overTime_history(conn); + } + else if(startsWith("/api/stats/overTime/clients", request->local_uri)) + { + ret = api_stats_overTime_clients(conn); + } + else if(startsWith("/api/stats/query_types", request->local_uri)) + { + ret = api_stats_query_types(conn); + } + else if(startsWith("/api/stats/upstreams", request->local_uri)) + { + ret = api_stats_upstreams(conn); + } + else if(startsWith("/api/stats/top_domains", request->local_uri)) + { + ret = api_stats_top_domains(false, conn); + } + else if(startsWith("/api/stats/top_blocked", request->local_uri)) + { + ret = api_stats_top_domains(true, conn); + } + else if(startsWith("/api/stats/top_clients", request->local_uri)) + { + ret = api_stats_top_clients(false, conn); + } + else if(startsWith("/api/stats/top_blocked_clients", request->local_uri)) + { + ret = api_stats_top_clients(true, conn); + } + else if(startsWith("/api/stats/history", request->local_uri)) + { + ret = api_stats_history(conn); + } + else if(startsWith("/api/stats/recent_blocked", request->local_uri)) + { + ret = api_stats_recentblocked(conn); + } + /******************************** api/version ****************************/ + else if(startsWith("/api/version", request->local_uri)) + { + ret = api_version(conn); + } + /******************************** api/auth ****************************/ + else if(startsWith("/api/auth", request->local_uri)) + { + ret = api_auth(conn); + } + else if(startsWith("/api/auth/salt", request->local_uri)) + { + ret = api_auth_salt(conn); + } + /******************************** api/settings ****************************/ + else if(startsWith("/api/settings/web", request->local_uri)) + { + ret = api_settings_web(conn); + } + else if(startsWith("/api/settings/ftldb", request->local_uri)) + { + ret = api_settings_ftldb(conn); + } + /******************************** not found ******************************/ +/* else + { + cJSON *json = JSON_NEW_OBJ(); + JSON_OBJ_REF_STR(json, "status", "requested path is not available"); + JSON_OBJ_REF_STR(json, "path", request->local_uri); + JSON_SENT_OBJECT(json); + }*/ + return ret; +} diff --git a/src/api/api.h b/src/api/routes.h similarity index 90% rename from src/api/api.h rename to src/api/routes.h index bfed6034e..7e9b900e8 100644 --- a/src/api/api.h +++ b/src/api/routes.h @@ -3,18 +3,18 @@ * Network-wide ad blocking via your own hardware. * * FTL Engine -* API commands and MessagePack helpers +* API route prototypes * * This file is copyright under the latest version of the EUPL. * Please see LICENSE file for your rights under this license. */ -#ifndef API_H -#define API_H +#ifndef ROUTES_H +#define ROUTES_H // struct mg_connection #include "../civetweb/civetweb.h" -// http_send_json_chunk() -#include "../api/http.h" -#include "json_macros.h" + +// API router +int api_handler(struct mg_connection *conn, void *ignored); // Statistic methods int api_stats_summary(struct mg_connection *conn); @@ -48,4 +48,4 @@ int api_auth_salt(struct mg_connection *conn); int api_settings_web(struct mg_connection *conn); int api_settings_ftldb(struct mg_connection *conn); -#endif // API_H +#endif // ROUTES_H diff --git a/src/api/settings.c b/src/api/settings.c index 0d5390db9..bbc242669 100644 --- a/src/api/settings.c +++ b/src/api/settings.c @@ -9,7 +9,9 @@ * Please see LICENSE file for your rights under this license. */ #include "FTL.h" -#include "api.h" +#include "http-common.h" +#include "routes.h" +#include "json_macros.h" // get_FTL_db_filesize() #include "files.h" // get_sqlite3_version() diff --git a/src/api/stats.c b/src/api/stats.c index 58bb75502..22c0339d8 100644 --- a/src/api/stats.c +++ b/src/api/stats.c @@ -9,7 +9,9 @@ * Please see LICENSE file for your rights under this license. */ #include "FTL.h" -#include "api.h" +#include "http-common.h" +#include "routes.h" +#include "json_macros.h" #include "shmem.h" #include "datastructure.h" // read_setupVarsconf() diff --git a/src/api/version.c b/src/api/version.c index 5b847b6c6..a800fade6 100644 --- a/src/api/version.c +++ b/src/api/version.c @@ -9,7 +9,9 @@ * Please see LICENSE file for your rights under this license. */ #include "FTL.h" -#include "api.h" +#include "http-common.h" +#include "routes.h" +#include "json_macros.h" // get_FTL_version() #include "log.h" #include "version.h" diff --git a/src/dnsmasq_interface.c b/src/dnsmasq_interface.c index c7d471bb5..2f220a033 100644 --- a/src/dnsmasq_interface.c +++ b/src/dnsmasq_interface.c @@ -30,12 +30,10 @@ #include "resolve.h" #include "files.h" #include "log.h" -// Prototype of getCacheInformation() -#include "api/api.h" // global variable daemonmode #include "args.h" // http_init() -#include "api/http.h" +#include "api/http-common.h" static void print_flags(const unsigned int flags); static void save_reply_type(const unsigned int flags, const union all_addr *addr, diff --git a/src/main.c b/src/main.c index 974d0a16a..cf5a7d35e 100644 --- a/src/main.c +++ b/src/main.c @@ -24,7 +24,7 @@ #include "database/gravity-db.h" #include "timers.h" // http_terminate() -#include "api/http.h" +#include "api/http-common.h" char * username; bool needGC = false; From d9aa8bd45fcced007b3e68537d04cf43f341ecf9 Mon Sep 17 00:00:00 2001 From: DL6ER Date: Wed, 20 Nov 2019 08:10:28 +0100 Subject: [PATCH 0085/1669] Allow password-less login if WEBPASSWORD is not set or set to an empty string in setupVars.conf. Signed-off-by: DL6ER --- src/api/auth.c | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/src/api/auth.c b/src/api/auth.c index 21e76c7cb..612078d87 100644 --- a/src/api/auth.c +++ b/src/api/auth.c @@ -84,13 +84,11 @@ static __attribute__((malloc)) char *get_password_hash(void) const char* password = read_setupVarsconf("WEBPASSWORD"); // If the value was not set (or we couldn't open the file for reading), - // substitute password with the hash for "A". This is only meant to be - // used during the development of the FTL HTTP API - if(password == NULL) + // substitute password with the hash for an empty string (= no password). + if(password == NULL || (password != NULL && strlen(password) == 0u)) { - password = "599d48457e4996df84cbfeb973cd109827c0de9fa211c0d062eab13584ea6bb8"; - if(config.debug & DEBUG_API) - logg("Substituting password 'A'"); + // This is the empty password hash + password = "cd372fb85148700fa88095e3492d3f9f5beb43e555e5ff26d95f5a6adc36f8e6"; } char *hash = strdup(password); @@ -110,8 +108,8 @@ int api_auth(struct mg_connection *conn) const char *xHeader = mg_get_header(conn, "X-Pi-hole-Authenticate"); if(xHeader != NULL && strlen(xHeader) > 0) { - char *passord_hash = get_password_hash(); - if(strcmp(xHeader,passord_hash) == 0) + char *password_hash = get_password_hash(); + if(strcmp(xHeader, password_hash) == 0) { // Accepted for(unsigned int i = 0; i < API_MAX_CLIENTS; i++) @@ -144,7 +142,12 @@ int api_auth(struct mg_connection *conn) } } } - free(passord_hash); + else if(config.debug & DEBUG_API) + { + logg("Password mismatch. User=%s, setupVars=%s", xHeader, password_hash); + } + + free(password_hash); } // Did the client authenticate before and we can validate this? From 1f78f2f276f552f2f7a3ecb60c594a57d862ac27 Mon Sep 17 00:00:00 2001 From: DL6ER Date: Wed, 20 Nov 2019 08:34:46 +0100 Subject: [PATCH 0086/1669] Add new send_json_error() and send_json_unauthorized() routines everywhere we are sending an error. Signed-off-by: DL6ER --- src/api/auth.c | 10 +++---- src/api/dns.c | 61 +++++++++++++++++++++++++------------------ src/api/http-common.c | 42 +++++++++++++++++++++++++++++ src/api/http-common.h | 5 ++++ src/api/settings.c | 4 +-- src/api/stats.c | 24 +++++------------ 6 files changed, 93 insertions(+), 53 deletions(-) diff --git a/src/api/auth.c b/src/api/auth.c index 612078d87..395594227 100644 --- a/src/api/auth.c +++ b/src/api/auth.c @@ -154,13 +154,13 @@ int api_auth(struct mg_connection *conn) if(user_id < 0) user_id = check_client_auth(conn); - cJSON *json = JSON_NEW_OBJ(); int method = http_method(conn); if(user_id > -1 && method == HTTP_GET) { if(config.debug & DEBUG_API) logg("Authentification: OK, registered new client"); + cJSON *json = JSON_NEW_OBJ(); JSON_OBJ_REF_STR(json, "status", "success"); // Ten minutes validity char *additional_headers = NULL; @@ -184,19 +184,15 @@ int api_auth(struct mg_connection *conn) free(auth_data[user_id].remote_addr); auth_data[user_id].remote_addr = NULL; + cJSON *json = JSON_NEW_OBJ(); JSON_OBJ_REF_STR(json, "status", "success"); char *additional_headers = strdup("Set-Cookie: user_id=deleted; Path=/; Max-Age=-1\r\n"); JSON_SENT_OBJECT_AND_HEADERS(json, additional_headers); } else { - cJSON *error = JSON_NEW_OBJ(); - JSON_OBJ_REF_STR(error, "key", "unauthorized"); - JSON_OBJ_REF_STR(error, "message", "Unauthorized"); - JSON_OBJ_ADD_NULL(error, "data"); - JSON_OBJ_ADD_ITEM(json, "error", error); char *additional_headers = strdup("Set-Cookie: user_id=deleted; Path=/; Max-Age=-1\r\n"); - JSON_SENT_OBJECT_AND_HEADERS_CODE(json, 401, additional_headers); + return send_json_unauthorized(conn, additional_headers); } } diff --git a/src/api/dns.c b/src/api/dns.c index 00d658af2..d68130e7c 100644 --- a/src/api/dns.c +++ b/src/api/dns.c @@ -39,30 +39,33 @@ int api_dns_status(struct mg_connection *conn) // Verify requesting client is allowed to access this ressource if(check_client_auth(conn) < 0) { - cJSON *json = JSON_NEW_OBJ(); - JSON_OBJ_REF_STR(json, "key", "unauthorized"); - JSON_SENT_OBJECT_CODE(json, 401); + return send_json_unauthorized(conn, NULL); } char buffer[1024]; int data_len = mg_read(conn, buffer, sizeof(buffer) - 1); if ((data_len < 1) || (data_len >= (int)sizeof(buffer))) { - mg_send_http_error(conn, 400, "%s", "No request body data"); - return 400; + return send_json_error(conn, 400, + "bad_request", "No request body data", + NULL, NULL); } buffer[data_len] = '\0'; cJSON *obj = cJSON_Parse(buffer); if (obj == NULL) { - mg_send_http_error(conn, 400, "%s", "Invalid request body data"); - return 400; + return send_json_error(conn, 400, + "bad_request", + "Invalid request body data", + NULL, NULL); } cJSON *elem1 = cJSON_GetObjectItemCaseSensitive(obj, "action"); if (!cJSON_IsString(elem1)) { cJSON_Delete(obj); - mg_send_http_error(conn, 400, "%s", "No \"action\" string in body data"); - return 400; + return send_json_error(conn, 400, + "bad_request", + "No \"action\" string in body data", + NULL, NULL); } const char *action = elem1->valuestring; @@ -95,7 +98,10 @@ int api_dns_status(struct mg_connection *conn) else { cJSON_Delete(obj); - JSON_OBJ_REF_STR(json, "key", "unsupported action"); + return send_json_error(conn, 400, + "bad_request", + "Invalid \"action\" requested", + NULL, NULL); } JSON_SENT_OBJECT(json); } @@ -141,23 +147,28 @@ static int api_dns_somelist_POST(struct mg_connection *conn, char buffer[1024]; int data_len = mg_read(conn, buffer, sizeof(buffer) - 1); if ((data_len < 1) || (data_len >= (int)sizeof(buffer))) { - mg_send_http_error(conn, 400, "%s", "No request body data"); - return 400; + return send_json_error(conn, 400, + "bad_request", "No request body data", + NULL, NULL); } buffer[data_len] = '\0'; cJSON *obj = cJSON_Parse(buffer); if (obj == NULL) { - mg_send_http_error(conn, 400, "%s", "Invalid request body data"); - return 400; + return send_json_error(conn, 400, + "bad_request", + "Invalid request body data", + NULL, NULL); } cJSON *elem = cJSON_GetObjectItemCaseSensitive(obj, "domain"); if (!cJSON_IsString(elem)) { cJSON_Delete(obj); - mg_send_http_error(conn, 400, "%s", "No \"domain\" string in body data"); - return 400; + return send_json_error(conn, 400, + "bad_request", + "No \"domain\" string in body data", + NULL, NULL); } const char *domain = elem->valuestring; @@ -183,11 +194,12 @@ static int api_dns_somelist_POST(struct mg_connection *conn, } else { - JSON_OBJ_REF_STR(json, "key", "error"); JSON_OBJ_COPY_STR(json, "domain", domain); cJSON_Delete(obj); - // Send 500 internal server error - JSON_SENT_OBJECT_CODE(json, 500); + return send_json_error(conn, 500, + "database_error", + "Could not add domain to database table", + json, NULL); } } @@ -224,10 +236,11 @@ static int api_dns_somelist_DELETE(struct mg_connection *conn, } else { - JSON_OBJ_REF_STR(json, "key", "error"); JSON_OBJ_REF_STR(json, "domain", domain); - // Send 500 internal server error - JSON_SENT_OBJECT_CODE(json, 500); + return send_json_error(conn, 500, + "database_error", + "Could not remove domain from database table", + json, NULL); } } @@ -236,9 +249,7 @@ int api_dns_somelist(struct mg_connection *conn, bool exact, bool whitelist) // Verify requesting client is allowed to see this ressource if(check_client_auth(conn) < 0) { - cJSON *json = JSON_NEW_OBJ(); - JSON_OBJ_REF_STR(json, "key", "unauthorized"); - JSON_SENT_OBJECT_CODE(json, 401); + return send_json_unauthorized(conn, NULL); } int method = http_method(conn); diff --git a/src/api/http-common.c b/src/api/http-common.c index bb6a492dc..4482836f6 100644 --- a/src/api/http-common.c +++ b/src/api/http-common.c @@ -13,6 +13,7 @@ #include "http-common.h" #include "../config.h" #include "../log.h" +#include "../cJSON/cJSON.h" #include "json_macros.h" // Server context handle @@ -36,6 +37,47 @@ int send_http_code(struct mg_connection *conn, const char *mime_type, return mg_write(conn, msg, strlen(msg)); } +int send_json_unauthorized(struct mg_connection *conn, + char *additional_headers) +{ + return send_json_error(conn, 401, + "unauthorized", + "Unauthorized", + NULL, additional_headers); +} + +int send_json_error(struct mg_connection *conn, const int code, + const char *key, const char* message, + cJSON *data, char *additional_headers) +{ + cJSON *json = JSON_NEW_OBJ(); + cJSON *error = JSON_NEW_OBJ(); + JSON_OBJ_REF_STR(error, "key", key); + JSON_OBJ_REF_STR(error, "message", message); + + // Add data if available + if(data == NULL) + { + JSON_OBJ_ADD_NULL(error, "data"); + } + else + { + JSON_OBJ_ADD_ITEM(error, "data", data); + } + + JSON_OBJ_ADD_ITEM(json, "error", error); + + // Send additional headers if supplied + if(additional_headers == NULL) + { + JSON_SENT_OBJECT_CODE(json, code); + } + else + { + JSON_SENT_OBJECT_AND_HEADERS_CODE(json, code, additional_headers); + } +} + int send_http_error(struct mg_connection *conn) { return mg_send_http_error(conn, 500, "Internal server error"); diff --git a/src/api/http-common.h b/src/api/http-common.h index a9dd96d5d..1b0434b80 100644 --- a/src/api/http-common.h +++ b/src/api/http-common.h @@ -29,6 +29,11 @@ int send_http(struct mg_connection *conn, const char *mime_type, int send_http_code(struct mg_connection *conn, const char *mime_type, const char *additional_headers, int code, const char *msg); int send_http_error(struct mg_connection *conn); +int send_json_unauthorized(struct mg_connection *conn, + char *additional_headers); +int send_json_error(struct mg_connection *conn, const int code, + const char *key, const char* message, + cJSON *data, char *additional_headers); // Cookie routines bool http_get_cookie_int(struct mg_connection *conn, const char *cookieName, int *i); diff --git a/src/api/settings.c b/src/api/settings.c index bbc242669..8ceb29afd 100644 --- a/src/api/settings.c +++ b/src/api/settings.c @@ -32,9 +32,7 @@ int api_settings_ftldb(struct mg_connection *conn) // Verify requesting client is allowed to see this ressource if(check_client_auth(conn) < 0) { - cJSON *json = JSON_NEW_OBJ(); - JSON_OBJ_REF_STR(json, "key", "unauthorized"); - JSON_SENT_OBJECT_CODE(json, 401); + send_json_unauthorized(conn, NULL); } cJSON *json = JSON_NEW_OBJ(); diff --git a/src/api/stats.c b/src/api/stats.c index 22c0339d8..4991b5f28 100644 --- a/src/api/stats.c +++ b/src/api/stats.c @@ -171,9 +171,7 @@ int api_stats_top_domains(bool blocked, struct mg_connection *conn) // Verify requesting client is allowed to see this ressource if(check_client_auth(conn) < 0) { - cJSON *json = JSON_NEW_OBJ(); - JSON_OBJ_REF_STR(json, "key", "unauthorized"); - JSON_SENT_OBJECT_CODE(json, 401); + return send_json_unauthorized(conn, NULL); } // /api/stats/top_domains?blocked=true is allowed as well @@ -342,9 +340,7 @@ int api_stats_top_clients(bool blocked, struct mg_connection *conn) // Verify requesting client is allowed to see this ressource if(check_client_auth(conn) < 0) { - cJSON *json = JSON_NEW_OBJ(); - JSON_OBJ_REF_STR(json, "key", "unauthorized"); - JSON_SENT_OBJECT_CODE(json, 401); + return send_json_unauthorized(conn, NULL); } // /api/stats/top_clients9?blocked=true is allowed as well @@ -479,9 +475,7 @@ int api_stats_upstreams(struct mg_connection *conn) // Verify requesting client is allowed to see this ressource if(check_client_auth(conn) < 0) { - cJSON *json = JSON_NEW_OBJ(); - JSON_OBJ_REF_STR(json, "key", "unauthorized"); - JSON_SENT_OBJECT_CODE(json, 401); + return send_json_unauthorized(conn, NULL); } /* if(command(client_message, "unsorted")) @@ -600,9 +594,7 @@ int api_stats_history(struct mg_connection *conn) // Verify requesting client is allowed to see this ressource if(check_client_auth(conn) < 0) { - cJSON *json = JSON_NEW_OBJ(); - JSON_OBJ_REF_STR(json, "key", "unauthorized"); - JSON_SENT_OBJECT_CODE(json, 401); + return send_json_unauthorized(conn, NULL); } // Do we want a more specific version of this command (domain/client/time interval filtered)? @@ -964,9 +956,7 @@ int api_stats_recentblocked(struct mg_connection *conn) // Verify requesting client is allowed to see this ressource if(check_client_auth(conn) < 0) { - cJSON *json = JSON_NEW_OBJ(); - JSON_OBJ_REF_STR(json, "key", "unauthorized"); - JSON_SENT_OBJECT_CODE(json, 401); + return send_json_unauthorized(conn, NULL); } const struct mg_request_info *request = mg_get_request_info(conn); @@ -1043,9 +1033,7 @@ int api_stats_overTime_clients(struct mg_connection *conn) // Verify requesting client is allowed to see this ressource if(check_client_auth(conn) < 0) { - cJSON *json = JSON_NEW_OBJ(); - JSON_OBJ_REF_STR(json, "key", "unauthorized"); - JSON_SENT_OBJECT_CODE(json, 401); + return send_json_unauthorized(conn, NULL); } // Find minimum ID to send From 5bb6a8e82dd6c153f7d78e3093b79a6c8a45c0f7 Mon Sep 17 00:00:00 2001 From: DL6ER Date: Wed, 20 Nov 2019 09:06:14 +0100 Subject: [PATCH 0087/1669] Add send_json_success() as yet another short convenience function. Signed-off-by: DL6ER --- src/api/auth.c | 16 +++++++--------- src/api/http-common.c | 8 ++++++++ src/api/http-common.h | 2 ++ 3 files changed, 17 insertions(+), 9 deletions(-) diff --git a/src/api/auth.c b/src/api/auth.c index 395594227..4b3c838f2 100644 --- a/src/api/auth.c +++ b/src/api/auth.c @@ -164,14 +164,14 @@ int api_auth(struct mg_connection *conn) JSON_OBJ_REF_STR(json, "status", "success"); // Ten minutes validity char *additional_headers = NULL; - if(asprintf(&additional_headers, "Set-Cookie: user_id=%u; Path=/; Max-Age=%u\r\n", user_id, API_SESSION_EXPIRE) > 0) + if(asprintf(&additional_headers, + "Set-Cookie: user_id=%u; Path=/; Max-Age=%u\r\n", + user_id, API_SESSION_EXPIRE) < 0) { - JSON_SENT_OBJECT_AND_HEADERS(json, additional_headers); - } - else - { - JSON_SENT_OBJECT(json); + return send_json_error(conn, 500, "internal_error", "Internal server error", NULL, NULL); } + + return send_json_success(conn, additional_headers); } else if(user_id > -1 && method == HTTP_DELETE) { @@ -184,10 +184,8 @@ int api_auth(struct mg_connection *conn) free(auth_data[user_id].remote_addr); auth_data[user_id].remote_addr = NULL; - cJSON *json = JSON_NEW_OBJ(); - JSON_OBJ_REF_STR(json, "status", "success"); char *additional_headers = strdup("Set-Cookie: user_id=deleted; Path=/; Max-Age=-1\r\n"); - JSON_SENT_OBJECT_AND_HEADERS(json, additional_headers); + return send_json_success(conn, additional_headers); } else { diff --git a/src/api/http-common.c b/src/api/http-common.c index 4482836f6..992627018 100644 --- a/src/api/http-common.c +++ b/src/api/http-common.c @@ -78,6 +78,14 @@ int send_json_error(struct mg_connection *conn, const int code, } } +int send_json_success(struct mg_connection *conn, + char * additional_headers) +{ + cJSON *json = JSON_NEW_OBJ(); + JSON_OBJ_REF_STR(json, "status", "success"); + JSON_SENT_OBJECT_AND_HEADERS(json, additional_headers); +} + int send_http_error(struct mg_connection *conn) { return mg_send_http_error(conn, 500, "Internal server error"); diff --git a/src/api/http-common.h b/src/api/http-common.h index 1b0434b80..6c7bf3f12 100644 --- a/src/api/http-common.h +++ b/src/api/http-common.h @@ -34,6 +34,8 @@ int send_json_unauthorized(struct mg_connection *conn, int send_json_error(struct mg_connection *conn, const int code, const char *key, const char* message, cJSON *data, char *additional_headers); +int send_json_success(struct mg_connection *conn, + char * additional_headers); // Cookie routines bool http_get_cookie_int(struct mg_connection *conn, const char *cookieName, int *i); From fd96e3693f8a6732be4723b3e27561a1ff8dda35 Mon Sep 17 00:00:00 2001 From: DL6ER Date: Wed, 20 Nov 2019 09:09:23 +0100 Subject: [PATCH 0088/1669] Remove deprecated feature to return forward destinations without sorting. This was never offered for any other type of data so it is mroe consistent to completely remove it. Signed-off-by: DL6ER --- src/api/stats.c | 36 ++++++++++-------------------------- 1 file changed, 10 insertions(+), 26 deletions(-) diff --git a/src/api/stats.c b/src/api/stats.c index 4991b5f28..6daafa647 100644 --- a/src/api/stats.c +++ b/src/api/stats.c @@ -469,7 +469,6 @@ int api_stats_top_clients(bool blocked, struct mg_connection *conn) int api_stats_upstreams(struct mg_connection *conn) { - bool sort = true; int temparray[counters->forwarded][2]; // Verify requesting client is allowed to see this ressource @@ -477,30 +476,19 @@ int api_stats_upstreams(struct mg_connection *conn) { return send_json_unauthorized(conn, NULL); } -/* - if(command(client_message, "unsorted")) - sort = false; -*/ for(int upstreamID = 0; upstreamID < counters->forwarded; upstreamID++) { - // If we want to print a sorted output, we fill the temporary array with - // the values we will use for sorting afterwards - if(sort) { - // Get forward pointer - const upstreamsData* forward = getUpstream(upstreamID, true); - if(forward == NULL) - continue; + // Get forward pointer + const upstreamsData* forward = getUpstream(upstreamID, true); + if(forward == NULL) + continue; - temparray[upstreamID][0] = upstreamID; - temparray[upstreamID][1] = forward->count; - } + temparray[upstreamID][0] = upstreamID; + temparray[upstreamID][1] = forward->count; } - if(sort) - { - // Sort temporary array in descending order - qsort(temparray, counters->upstreams, sizeof(int[2]), cmpdesc); - } + // Sort temporary array in descending order + qsort(temparray, counters->forwarded, sizeof(int[2]), cmpdesc); // Loop over available forward destinations cJSON *upstreams = JSON_NEW_ARRAY(); @@ -527,11 +515,7 @@ int api_stats_upstreams(struct mg_connection *conn) { // Regular forward destionation // Get sorted indices - int upstreamID; - if(sort) - upstreamID = temparray[i][0]; - else - upstreamID = i; + const int upstreamID = temparray[i][0]; // Get forward pointer const upstreamsData* forward = getUpstream(upstreamID, true); @@ -951,7 +935,7 @@ int api_stats_history(struct mg_connection *conn) int api_stats_recentblocked(struct mg_connection *conn) { - unsigned int num=1; + unsigned int num = 1; // Verify requesting client is allowed to see this ressource if(check_client_auth(conn) < 0) From 07285934cd69689bcbeabcc0315c1818c60e9d77 Mon Sep 17 00:00:00 2001 From: DL6ER Date: Wed, 20 Nov 2019 09:45:34 +0100 Subject: [PATCH 0089/1669] Add more convenience functions and return 400 Bad Request responses when invalid parameters are given to the /api/stats endpoints. Signed-off-by: DL6ER --- src/api/auth.c | 2 +- src/api/dns.c | 10 +- src/api/ftl.c | 2 +- src/api/http-common.c | 29 ++++- src/api/http-common.h | 4 + src/api/json_macros.h | 8 +- src/api/routes.c | 2 +- src/api/settings.c | 4 +- src/api/stats.c | 258 +++++++++++++++++++----------------------- src/api/version.c | 2 +- 10 files changed, 163 insertions(+), 158 deletions(-) diff --git a/src/api/auth.c b/src/api/auth.c index 4b3c838f2..95eec7b31 100644 --- a/src/api/auth.c +++ b/src/api/auth.c @@ -201,5 +201,5 @@ int api_auth_salt(struct mg_connection *conn) generateRandomString(salt, sizeof(salt)); cJSON *json = JSON_NEW_OBJ(); JSON_OBJ_REF_STR(json, "salt", salt); - JSON_SENT_OBJECT(json); + JSON_SEND_OBJECT(json); } \ No newline at end of file diff --git a/src/api/dns.c b/src/api/dns.c index d68130e7c..566fc4f29 100644 --- a/src/api/dns.c +++ b/src/api/dns.c @@ -32,7 +32,7 @@ int api_dns_status(struct mg_connection *conn) // Return current status cJSON *json = JSON_NEW_OBJ(); JSON_OBJ_REF_STR(json, "status", (get_blockingstatus() ? "enabled" : "disabled")); - JSON_SENT_OBJECT(json); + JSON_SEND_OBJECT(json); } else if(method == HTTP_POST) { @@ -103,7 +103,7 @@ int api_dns_status(struct mg_connection *conn) "Invalid \"action\" requested", NULL, NULL); } - JSON_SENT_OBJECT(json); + JSON_SEND_OBJECT(json); } else { @@ -137,7 +137,7 @@ static int api_dns_somelist_read(struct mg_connection *conn, bool exact, bool wh } gravityDB_finalizeTable(); - JSON_SENT_OBJECT(json); + JSON_SEND_OBJECT(json); } static int api_dns_somelist_POST(struct mg_connection *conn, @@ -190,7 +190,7 @@ static int api_dns_somelist_POST(struct mg_connection *conn, JSON_OBJ_REF_STR(json, "key", "added"); JSON_OBJ_COPY_STR(json, "domain", domain); cJSON_Delete(obj); - JSON_SENT_OBJECT(json); + JSON_SEND_OBJECT(json); } else { @@ -232,7 +232,7 @@ static int api_dns_somelist_DELETE(struct mg_connection *conn, { JSON_OBJ_REF_STR(json, "key", "removed"); JSON_OBJ_REF_STR(json, "domain", domain); - JSON_SENT_OBJECT(json); + JSON_SEND_OBJECT(json); } else { diff --git a/src/api/ftl.c b/src/api/ftl.c index 42b70e119..e1287e890 100644 --- a/src/api/ftl.c +++ b/src/api/ftl.c @@ -23,5 +23,5 @@ int api_ftl_clientIP(struct mg_connection *conn) cJSON *json = JSON_NEW_OBJ(); const struct mg_request_info *request = mg_get_request_info(conn); JSON_OBJ_REF_STR(json,"remote_addr", request->remote_addr); - JSON_SENT_OBJECT(json); + JSON_SEND_OBJECT(json); } diff --git a/src/api/http-common.c b/src/api/http-common.c index 992627018..91cc994ea 100644 --- a/src/api/http-common.c +++ b/src/api/http-common.c @@ -70,11 +70,11 @@ int send_json_error(struct mg_connection *conn, const int code, // Send additional headers if supplied if(additional_headers == NULL) { - JSON_SENT_OBJECT_CODE(json, code); + JSON_SEND_OBJECT_CODE(json, code); } else { - JSON_SENT_OBJECT_AND_HEADERS_CODE(json, code, additional_headers); + JSON_SEND_OBJECT_AND_HEADERS_CODE(json, code, additional_headers); } } @@ -83,7 +83,7 @@ int send_json_success(struct mg_connection *conn, { cJSON *json = JSON_NEW_OBJ(); JSON_OBJ_REF_STR(json, "status", "success"); - JSON_SENT_OBJECT_AND_HEADERS(json, additional_headers); + JSON_SEND_OBJECT_AND_HEADERS(json, additional_headers); } int send_http_error(struct mg_connection *conn) @@ -91,6 +91,29 @@ int send_http_error(struct mg_connection *conn) return mg_send_http_error(conn, 500, "Internal server error"); } +bool get_bool_var(const char *source, const char *var) +{ + char buffer[16] = { 0 }; + if(GET_VAR(var, buffer, source)) + { + return (strstr(buffer, "true") != NULL); + } + return false; +} + +int get_int_var(const char *source, const char *var) +{ + int num = -1; + char buffer[16] = { 0 }; + if(GET_VAR(var, buffer, source) && + sscanf(buffer, "%d", &num) == 1) + { + return num; + } + + return -1; +} + bool __attribute__((pure)) startsWith(const char *path, const char *uri) { // We subtract 1 to include the trailing slash in webhome diff --git a/src/api/http-common.h b/src/api/http-common.h index 6c7bf3f12..ed3d14d5f 100644 --- a/src/api/http-common.h +++ b/src/api/http-common.h @@ -41,6 +41,10 @@ int send_json_success(struct mg_connection *conn, bool http_get_cookie_int(struct mg_connection *conn, const char *cookieName, int *i); bool http_get_cookie_str(struct mg_connection *conn, const char *cookieName, char *str, size_t str_size); +// HTTP parameter routines +bool get_bool_var(const char *source, const char *var); +int get_int_var(const char *source, const char *var); + // HTTP macros #define GET_VAR(variable, destination, source) mg_get_var(source, strlen(source), variable, destination, sizeof(destination)) diff --git a/src/api/json_macros.h b/src/api/json_macros.h index f44c9da0f..26128fc55 100644 --- a/src/api/json_macros.h +++ b/src/api/json_macros.h @@ -121,7 +121,7 @@ #define JSON_DELETE(object) cJSON_Delete(object) -#define JSON_SENT_OBJECT(object){ \ +#define JSON_SEND_OBJECT(object){ \ const char* msg = JSON_FORMATTER(object); \ if(msg == NULL) \ { \ @@ -134,7 +134,7 @@ return 200; \ } -#define JSON_SENT_OBJECT_CODE(object, code){ \ +#define JSON_SEND_OBJECT_CODE(object, code){ \ const char* msg = JSON_FORMATTER(object); \ if(msg == NULL) \ { \ @@ -147,7 +147,7 @@ return 200; \ } -#define JSON_SENT_OBJECT_AND_HEADERS(object, additional_headers){ \ +#define JSON_SEND_OBJECT_AND_HEADERS(object, additional_headers){ \ const char* msg = JSON_FORMATTER(object); \ if(msg == NULL) \ { \ @@ -161,7 +161,7 @@ return 200; \ } -#define JSON_SENT_OBJECT_AND_HEADERS_CODE(object, code, additional_headers){ \ +#define JSON_SEND_OBJECT_AND_HEADERS_CODE(object, code, additional_headers){ \ const char* msg = JSON_FORMATTER(object); \ if(msg == NULL) \ { \ diff --git a/src/api/routes.c b/src/api/routes.c index efdbc9425..ba9247190 100644 --- a/src/api/routes.c +++ b/src/api/routes.c @@ -119,7 +119,7 @@ int api_handler(struct mg_connection *conn, void *ignored) cJSON *json = JSON_NEW_OBJ(); JSON_OBJ_REF_STR(json, "status", "requested path is not available"); JSON_OBJ_REF_STR(json, "path", request->local_uri); - JSON_SENT_OBJECT(json); + JSON_SEND_OBJECT(json); }*/ return ret; } diff --git a/src/api/settings.c b/src/api/settings.c index 8ceb29afd..f6f1aee44 100644 --- a/src/api/settings.c +++ b/src/api/settings.c @@ -24,7 +24,7 @@ int api_settings_web(struct mg_connection *conn) cJSON *json = JSON_NEW_OBJ(); JSON_OBJ_REF_STR(json, "layout", "boxed"); JSON_OBJ_REF_STR(json, "language", "en"); - JSON_SENT_OBJECT(json); + JSON_SEND_OBJECT(json); } int api_settings_ftldb(struct mg_connection *conn) @@ -41,5 +41,5 @@ int api_settings_ftldb(struct mg_connection *conn) const int queries_in_database = get_number_of_queries_in_DB(); JSON_OBJ_ADD_NUMBER(json, "queries", queries_in_database); JSON_OBJ_REF_STR(json, "sqlite_version", get_sqlite3_version()); - JSON_SENT_OBJECT(json); + JSON_SEND_OBJECT(json); } \ No newline at end of file diff --git a/src/api/stats.c b/src/api/stats.c index 6daafa647..787aefef8 100644 --- a/src/api/stats.c +++ b/src/api/stats.c @@ -29,7 +29,7 @@ #define min(a,b) ({ __typeof__ (a) _a = (a); __typeof__ (b) _b = (b); _a < _b ? _a : _b; }) -/* qsort comparision function (count field), sort ASC */ +/* qsort comparision function (count field), sort ASC static int __attribute__((pure)) cmpasc(const void *a, const void *b) { const int *elem1 = (int*)a; @@ -41,7 +41,7 @@ static int __attribute__((pure)) cmpasc(const void *a, const void *b) return 1; else return 0; -} +} */ // qsort subroutine, sort DESC static int __attribute__((pure)) cmpdesc(const void *a, const void *b) @@ -110,7 +110,7 @@ int api_stats_summary(struct mg_connection *conn) JSON_OBJ_ADD_NUMBER(reply_types, "IP", counters->reply_IP); JSON_OBJ_ADD_ITEM(json, "reply_types", reply_types); - JSON_SENT_OBJECT(json); + JSON_SEND_OBJECT(json); } int api_stats_overTime_history(struct mg_connection *conn) @@ -148,7 +148,7 @@ int api_stats_overTime_history(struct mg_connection *conn) cJSON *json = JSON_NEW_ARRAY(); cJSON *item = JSON_NEW_OBJ(); JSON_ARRAY_ADD_ITEM(json, item); - JSON_SENT_OBJECT(json); + JSON_SEND_OBJECT(json); } cJSON *json = JSON_NEW_ARRAY(); @@ -160,13 +160,13 @@ int api_stats_overTime_history(struct mg_connection *conn) JSON_OBJ_ADD_NUMBER(item, "blocked_queries", overTime[slot].blocked); JSON_ARRAY_ADD_ITEM(json, item); } - JSON_SENT_OBJECT(json); + JSON_SEND_OBJECT(json); } int api_stats_top_domains(bool blocked, struct mg_connection *conn) { - int temparray[counters->domains][2], show=10; - bool audit = false, asc = false; + int temparray[counters->domains][2], show = 10; + bool audit = false; // Verify requesting client is allowed to see this ressource if(check_client_auth(conn) < 0) @@ -174,42 +174,29 @@ int api_stats_top_domains(bool blocked, struct mg_connection *conn) return send_json_unauthorized(conn, NULL); } + // Exit before processing any data if requested via config setting + get_privacy_level(NULL); + if(config.privacylevel >= PRIVACY_HIDE_DOMAINS) + { + cJSON* json = JSON_NEW_OBJ(); + JSON_SEND_OBJECT(json); + } + // /api/stats/top_domains?blocked=true is allowed as well const struct mg_request_info *request = mg_get_request_info(conn); if(request->query_string != NULL) { // Should blocked clients be shown? - if(strstr(request->query_string, "blocked=true") != NULL) - { - blocked = true; - } + blocked = get_bool_var(request->query_string, "blocked"); // Does the user request a non-default number of replies? + // Note: We do not accept zero query requests here int num; - if(sscanf(request->query_string, "num=%d", &num) == 1) - { + if((num = get_int_var(request->query_string, "show")) > 0) show = num; - } // Apply Audit Log filtering? - if(strstr(request->query_string, "audit=true") != NULL) - { - audit = true; - } - - // Sort in ascending order? - if(strstr(request->query_string, "sort=asc") != NULL) - { - asc = true; - } - } - - // Exit before processing any data if requested via config setting - get_privacy_level(NULL); - if(config.privacylevel >= PRIVACY_HIDE_DOMAINS) - { - cJSON *json = JSON_NEW_ARRAY(); - JSON_SENT_OBJECT(json); + audit = get_bool_var(request->query_string, "audit"); } for(int domainID=0; domainID < counters->domains; domainID++) @@ -228,10 +215,7 @@ int api_stats_top_domains(bool blocked, struct mg_connection *conn) } // Sort temporary array - if(asc) - qsort(temparray, counters->domains, sizeof(int[2]), cmpasc); - else - qsort(temparray, counters->domains, sizeof(int[2]), cmpdesc); + qsort(temparray, counters->domains, sizeof(int[2]), cmpdesc); // Get filter @@ -252,7 +236,7 @@ int api_stats_top_domains(bool blocked, struct mg_connection *conn) clearSetupVarsArray(); // Get domains which the user doesn't want to see - char * excludedomains = NULL; + char *excludedomains = NULL; if(!audit) { excludedomains = read_setupVarsconf("API_EXCLUDE_DOMAINS"); @@ -329,13 +313,13 @@ int api_stats_top_domains(bool blocked, struct mg_connection *conn) JSON_OBJ_ADD_NUMBER(json, "total_queries", total_queries); } - JSON_SENT_OBJECT(json); + JSON_SEND_OBJECT(json); } int api_stats_top_clients(bool blocked, struct mg_connection *conn) { - int temparray[counters->clients][2], show=10; - bool asc = false, includezeroclients = false; + int temparray[counters->clients][2], show = 10; + bool includezeroclients = false; // Verify requesting client is allowed to see this ressource if(check_client_auth(conn) < 0) @@ -343,42 +327,29 @@ int api_stats_top_clients(bool blocked, struct mg_connection *conn) return send_json_unauthorized(conn, NULL); } + // Exit before processing any data if requested via config setting + get_privacy_level(NULL); + if(config.privacylevel >= PRIVACY_HIDE_DOMAINS) + { + cJSON* json = JSON_NEW_OBJ(); + JSON_SEND_OBJECT(json); + } + // /api/stats/top_clients9?blocked=true is allowed as well const struct mg_request_info *request = mg_get_request_info(conn); if(request->query_string != NULL) { // Should blocked clients be shown? - if(strstr(request->query_string, "blocked=true") != NULL) - { - blocked = true; - } + blocked = get_bool_var(request->query_string, "blocked"); // Does the user request a non-default number of replies? + // Note: We do not accept zero query requests here int num; - if(sscanf(request->query_string, "num=%d", &num) == 1) - { + if((num = get_int_var(request->query_string, "show")) > 0) show = num; - } - - // Sort in ascending order? - if(strstr(request->query_string, "sort=asc") != NULL) - { - asc = true; - } // Show also clients which have not been active recently? - if(strstr(request->query_string, "withzero=true") != NULL) - { - includezeroclients = true; - } - } - - // Exit before processing any data if requested via config setting - get_privacy_level(NULL); - if(config.privacylevel >= PRIVACY_HIDE_DOMAINS_CLIENTS) - { - cJSON *json = JSON_NEW_ARRAY(); - JSON_SENT_OBJECT(json); + includezeroclients = get_bool_var(request->query_string, "withzero"); } for(int clientID = 0; clientID < counters->clients; clientID++) @@ -393,10 +364,7 @@ int api_stats_top_clients(bool blocked, struct mg_connection *conn) } // Sort temporary array - if(asc) - qsort(temparray, counters->clients, sizeof(int[2]), cmpasc); - else - qsort(temparray, counters->clients, sizeof(int[2]), cmpdesc); + qsort(temparray, counters->clients, sizeof(int[2]), cmpdesc); // Get clients which the user doesn't want to see const char* excludeclients = read_setupVarsconf("API_EXCLUDE_CLIENTS"); @@ -463,7 +431,7 @@ int api_stats_top_clients(bool blocked, struct mg_connection *conn) JSON_OBJ_ADD_NUMBER(json, "total_queries", total_queries); } - JSON_SENT_OBJECT(json); + JSON_SEND_OBJECT(json); } @@ -547,7 +515,7 @@ int api_stats_upstreams(struct mg_connection *conn) JSON_OBJ_ADD_NUMBER(json, "forwarded_queries", counters->forwarded); const int total_queries = counters->forwarded + counters->cached + counters->blocked; JSON_OBJ_ADD_NUMBER(json, "total_queries", total_queries); - JSON_SENT_OBJECT(json); + JSON_SEND_OBJECT(json); } static const char *querytypes[TYPE_MAX] = {"A","AAAA","ANY","SRV","SOA","PTR","TXT","NAPTR","UNKN"}; @@ -562,7 +530,7 @@ int api_stats_query_types(struct mg_connection *conn) JSON_OBJ_ADD_NUMBER(item, "count", counters->querytype[i]); JSON_ARRAY_ADD_ITEM(json, item); } - JSON_SENT_OBJECT(json); + JSON_SEND_OBJECT(json); } int api_stats_history(struct mg_connection *conn) @@ -571,8 +539,8 @@ int api_stats_history(struct mg_connection *conn) get_privacy_level(NULL); if(config.privacylevel >= PRIVACY_MAXIMUM) { - cJSON *json = JSON_NEW_ARRAY(); - JSON_SENT_OBJECT(json); + cJSON* json = JSON_NEW_OBJ(); + JSON_SEND_OBJECT(json); } // Verify requesting client is allowed to see this ressource @@ -606,31 +574,24 @@ int api_stats_history(struct mg_connection *conn) const struct mg_request_info *request = mg_get_request_info(conn); if(request->query_string != NULL) { - char buffer[256] = { 0 }; - + int num; // Time filtering? - if(GET_VAR("from", buffer, request->query_string) > 0) - { - sscanf(buffer, "%u", &from); - } - - if(GET_VAR("until", buffer, request->query_string) > 0) - { - sscanf(buffer, "%u", &until); - } + if((num = get_int_var(request->query_string, "from")) > 0) + from = num; + if((num = get_int_var(request->query_string, "until")) > 0) + until = num; // Query type filtering? - if(GET_VAR("querytype", buffer, request->query_string) > 0) - { - unsigned int qtype; - sscanf(buffer, "%u", &qtype); - if(querytype < TYPE_MAX) - { - querytype = qtype; - } - } + if((num = get_int_var(request->query_string, "querytype")) > 0 && num < TYPE_MAX) + querytype = num; + + // Does the user request a non-default number of replies? + // Note: We do not accept zero query requests here + if((num = get_int_var(request->query_string, "show")) > 0) + show = num; // Forward destination filtering? + char buffer[256] = { 0 }; if(GET_VAR("forward", buffer, request->query_string) > 0) { forwarddest = calloc(256, sizeof(char)); @@ -674,11 +635,16 @@ int api_stats_history(struct mg_connection *conn) } if(forwarddestid < 0) { - // Requested forward destination has not been found, we directly - // exit here as there is no data to be returned + // Requested upstream has not been found, we directly + // tell teh user here as there is no data to be returned + cJSON *json = JSON_NEW_OBJ(); + JSON_OBJ_COPY_STR(json, "upstream", forwarddest); free(forwarddest); - cJSON *json = JSON_NEW_ARRAY(); - JSON_SENT_OBJECT(json); + + return send_json_error(conn, 400, + "bad_request", + "Requested upstream not found", + json, NULL); } } } @@ -713,10 +679,15 @@ int api_stats_history(struct mg_connection *conn) if(domainid < 0) { // Requested domain has not been found, we directly - // exit here as there is no data to be returned + // tell the user here as there is no data to be returned + cJSON *json = JSON_NEW_OBJ(); + JSON_OBJ_COPY_STR(json, "domain", domainname); free(domainname); - cJSON *json = JSON_NEW_ARRAY(); - JSON_SENT_OBJECT(json); + + return send_json_error(conn, 400, + "bad_request", + "Requested domain not found", + json, NULL); } } @@ -753,29 +724,43 @@ int api_stats_history(struct mg_connection *conn) if(clientid < 0) { // Requested client has not been found, we directly - // exit here as there is no data to be returned + // tell the user here as there is no data to be returned + cJSON *json = JSON_NEW_OBJ(); + JSON_OBJ_COPY_STR(json, "client", clientname); free(clientname); - cJSON *json = JSON_NEW_ARRAY(); - JSON_SENT_OBJECT(json); + + return send_json_error(conn, 400, + "bad_request", + "Requested client not found", + json, NULL); } } if(GET_VAR("cursor", buffer, request->query_string) > 0) { - unsigned int num = 0u; - sscanf(buffer, "%u", &num); + unsigned int unum = 0u; + sscanf(buffer, "%u", &unum); + // Do not start at the most recent, but at an older query - // Don't allow a start index that is smaller than zero - if(num > 0u && num < (unsigned int)counters->queries) + if(unum < (unsigned int)counters->queries) { - cursor = num; + cursor = unum; } - } + else + { + // Cursors larger than the current known number + // of queries are invalid + cJSON *json = JSON_NEW_OBJ(); + JSON_OBJ_ADD_NUMBER(json, "cursor", unum); + JSON_OBJ_ADD_NUMBER(json, "maxval", counters->queries); + free(clientname); - if(GET_VAR("show", buffer, request->query_string) > 0) - { - sscanf(buffer, "%u", &show); - // User wants a different number of requests + return send_json_error(conn, 400, + "bad_request", + "Requested cursor larger than number of queries", + json, NULL); + } + } } @@ -930,12 +915,12 @@ int api_stats_history(struct mg_connection *conn) JSON_OBJ_ADD_NULL(json, "cursor"); } - JSON_SENT_OBJECT(json); + JSON_SEND_OBJECT(json); } int api_stats_recentblocked(struct mg_connection *conn) { - unsigned int num = 1; + unsigned int show = 1; // Verify requesting client is allowed to see this ressource if(check_client_auth(conn) < 0) @@ -946,18 +931,11 @@ int api_stats_recentblocked(struct mg_connection *conn) const struct mg_request_info *request = mg_get_request_info(conn); if(request->query_string != NULL) { - char buffer[256] = { 0 }; - - // Test for integer that specifies number of entries to be shown - if(GET_VAR("show", buffer, request->query_string) > 0) - { - // User wants a different number of requests - sscanf(buffer, "%u", &num); - if(num >= (unsigned int)counters->queries) - { - num = 0; - } - } + // Does the user request a non-default number of replies? + // Note: We do not accept zero query requests here + int num; + if((num = get_int_var(request->query_string, "show")) > 0) + show = num; } // Find most recently blocked query @@ -994,32 +972,32 @@ int api_stats_recentblocked(struct mg_connection *conn) JSON_ARRAY_REF_STR(blocked, domain); } - if(found >= num) + if(found >= show) break; } cJSON *json = JSON_NEW_OBJ(); JSON_OBJ_ADD_ITEM(json, "blocked", blocked); - JSON_SENT_OBJECT(json); + JSON_SEND_OBJECT(json); } int api_stats_overTime_clients(struct mg_connection *conn) { int sendit = -1, until = OVERTIME_SLOTS; - // Exit before processing any data if requested via config setting - get_privacy_level(NULL); - if(config.privacylevel >= PRIVACY_HIDE_DOMAINS_CLIENTS) - { - cJSON *json = JSON_NEW_OBJ(); - JSON_SENT_OBJECT(json); - } - // Verify requesting client is allowed to see this ressource if(check_client_auth(conn) < 0) { return send_json_unauthorized(conn, NULL); } + // Exit before processing any data if requested via config setting + get_privacy_level(NULL); + if(config.privacylevel >= PRIVACY_HIDE_DOMAINS_CLIENTS) + { + cJSON* json = JSON_NEW_OBJ(); + JSON_SEND_OBJECT(json); + } + // Find minimum ID to send for(int slot = 0; slot < OVERTIME_SLOTS; slot++) { @@ -1037,7 +1015,7 @@ int api_stats_overTime_clients(struct mg_connection *conn) JSON_OBJ_ADD_ITEM(json, "over_time", over_time); cJSON *clients = JSON_NEW_ARRAY(); JSON_OBJ_ADD_ITEM(json, "clients", clients); - JSON_SENT_OBJECT(json); + JSON_SEND_OBJECT(json); } // Find minimum ID to send @@ -1128,5 +1106,5 @@ int api_stats_overTime_clients(struct mg_connection *conn) if(excludeclients != NULL) clearSetupVarsArray(); - JSON_SENT_OBJECT(json); + JSON_SEND_OBJECT(json); } diff --git a/src/api/version.c b/src/api/version.c index a800fade6..558f5d386 100644 --- a/src/api/version.c +++ b/src/api/version.c @@ -56,5 +56,5 @@ int api_version(struct mg_connection *conn) JSON_OBJ_REF_STR(ftl, "tag", version); JSON_OBJ_ADD_ITEM(json, "ftl", ftl); - JSON_SENT_OBJECT(json); + JSON_SEND_OBJECT(json); } \ No newline at end of file From 440e4aaa84efc699a94ab5c9a31916330f5c0ac9 Mon Sep 17 00:00:00 2001 From: DL6ER Date: Wed, 20 Nov 2019 10:08:11 +0100 Subject: [PATCH 0090/1669] Send JSON 404 error if an undefined path has been requested insode /api/... Elsewhere in the web interface we still send the usual 404 page. Signed-off-by: DL6ER --- src/api/http-common.c | 24 ++++++++++++------------ src/api/routes.c | 15 ++++++++++----- src/api/stats.c | 2 +- 3 files changed, 23 insertions(+), 18 deletions(-) diff --git a/src/api/http-common.c b/src/api/http-common.c index 91cc994ea..d0d5aeff9 100644 --- a/src/api/http-common.c +++ b/src/api/http-common.c @@ -136,7 +136,8 @@ bool __attribute__((pure)) startsWith(const char *path, const char *uri) } } -void __attribute__ ((format (gnu_printf, 3, 4))) http_send(struct mg_connection *conn, bool chunk, const char *format, ...) +void __attribute__ ((format (gnu_printf, 3, 4))) + http_send(struct mg_connection *conn, bool chunk, const char *format, ...) { char *buffer; va_list args; @@ -171,18 +172,18 @@ static int print_simple(struct mg_connection *conn, void *input) } static char *indexfile_content = NULL; -static void read_indexfile(void) +static void prepare_index_html(void) { char *index_path = NULL; if(asprintf(&index_path, "%s%sindex.html", httpsettings.webroot, httpsettings.webhome) < 0) { - logg("read_indexfile(): Memory error (1)"); + logg("prepare_index_html(): Memory error (1)"); return; } char *base_tag = NULL; if(asprintf(&base_tag, "", httpsettings.webhome) < 0) { - logg("read_indexfile(): Memory error (2)"); + logg("prepare_index_html(): Memory error (2)"); } unsigned int base_tag_length = strlen(base_tag); @@ -205,7 +206,7 @@ static void read_indexfile(void) indexfile_content = calloc(fsize + base_tag_length + 1, sizeof(char)); if(indexfile_content == NULL) { - logg("read_indexfile(): Memory error (3)"); + logg("prepare_index_html(): Memory error (3)"); free(index_path); free(base_tag); } @@ -314,17 +315,16 @@ void http_init(void) NULL }; - // Configure logging handler - struct mg_callbacks callbacks = {NULL}; + // Configure logging handlers + struct mg_callbacks callbacks = { NULL }; callbacks.log_message = log_http_message; - - // We log all access to pihole-FTL.log when in API debugging mode - callbacks.log_access = log_http_access; + callbacks.log_access = log_http_access; /* Start the server */ if((ctx = mg_start(&callbacks, NULL, options)) == NULL) { - logg("ERROR: Initializing HTTP library failed!"); + logg("ERROR: Initializing HTTP library failed!\n" + " Web interface will not be available!"); return; } @@ -342,7 +342,7 @@ void http_init(void) free(api_path); } - read_indexfile(); + prepare_index_html(); mg_set_request_handler(ctx, httpsettings.webhome, index_handler, NULL); } diff --git a/src/api/routes.c b/src/api/routes.c index ba9247190..230857133 100644 --- a/src/api/routes.c +++ b/src/api/routes.c @@ -11,7 +11,8 @@ #include "../FTL.h" // struct mg_connection #include "../civetweb/civetweb.h" -#include "../api/http-common.h" +#include "http-common.h" +#include "json_macros.h" #include "routes.h" int api_handler(struct mg_connection *conn, void *ignored) @@ -114,12 +115,16 @@ int api_handler(struct mg_connection *conn, void *ignored) ret = api_settings_ftldb(conn); } /******************************** not found ******************************/ -/* else + else { cJSON *json = JSON_NEW_OBJ(); - JSON_OBJ_REF_STR(json, "status", "requested path is not available"); JSON_OBJ_REF_STR(json, "path", request->local_uri); - JSON_SEND_OBJECT(json); - }*/ + + ret = send_json_error(conn, 404, + "not_found", + "Not found", + json, NULL); + } + return ret; } diff --git a/src/api/stats.c b/src/api/stats.c index 787aefef8..fa0106c4f 100644 --- a/src/api/stats.c +++ b/src/api/stats.c @@ -636,7 +636,7 @@ int api_stats_history(struct mg_connection *conn) if(forwarddestid < 0) { // Requested upstream has not been found, we directly - // tell teh user here as there is no data to be returned + // tell the user here as there is no data to be returned cJSON *json = JSON_NEW_OBJ(); JSON_OBJ_COPY_STR(json, "upstream", forwarddest); free(forwarddest); From 4a07a9ffcd132881777923fee8c31f53399d498f Mon Sep 17 00:00:00 2001 From: DL6ER Date: Wed, 20 Nov 2019 10:14:24 +0100 Subject: [PATCH 0091/1669] Add tests for JSON/normal error 404. Signed-off-by: DL6ER --- test/test_suite.bats | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/test/test_suite.bats b/test/test_suite.bats index e96cf1015..ac0c32e28 100644 --- a/test/test_suite.bats +++ b/test/test_suite.bats @@ -269,3 +269,15 @@ printf "%s\n" "${lines[@]}" [[ ${lines[0]} == "pong" ]] } + +@test "HTTP server responds wth JSON 404 to unknown API path" { + run bash -c 'wget 127.0.0.1:8080/admin/api/undefined -q -O - --content-on-error' + printf "%s\n" "${lines[@]}" + [[ ${lines[0]} == "{\"error\":{\"key\":\"not_found\",\"message\":\"Not found\",\"data\":{\"path\":\"/admin/api/undefined\"}}}" ]] +} + +@test "HTTP server responds wth normal 404 to path outside /admin" { + run bash -c 'wget 127.0.0.1:8080/undefined -q -O - --content-on-error' + printf "%s\n" "${lines[@]}" + [[ ${lines[0]} == "Error 404: Not Found" ]] +} From b8ec63bf7ee5ceb2e458b634a406eb80851bf458 Mon Sep 17 00:00:00 2001 From: DL6ER Date: Wed, 20 Nov 2019 10:18:01 +0100 Subject: [PATCH 0092/1669] Directly return without buffering in the API handler. Signed-off-by: DL6ER --- src/api/routes.c | 57 ++++++++++++++++++++++-------------------------- 1 file changed, 26 insertions(+), 31 deletions(-) diff --git a/src/api/routes.c b/src/api/routes.c index 230857133..c82b33867 100644 --- a/src/api/routes.c +++ b/src/api/routes.c @@ -18,113 +18,108 @@ int api_handler(struct mg_connection *conn, void *ignored) { const struct mg_request_info *request = mg_get_request_info(conn); - // HTTP response - int ret = 0; /******************************** api/dns ********************************/ if(startsWith("/api/dns/status", request->local_uri)) { - ret = api_dns_status(conn); + return api_dns_status(conn); } else if(startsWith("/api/dns/whitelist/exact", request->local_uri)) { - ret = api_dns_somelist(conn, true, true); + return api_dns_somelist(conn, true, true); } else if(startsWith("/api/dns/whitelist/regex", request->local_uri)) { - ret = api_dns_somelist(conn, false, true); + return api_dns_somelist(conn, false, true); } else if(startsWith("/api/dns/blacklist/exact", request->local_uri)) { - ret = api_dns_somelist(conn, true, false); + return api_dns_somelist(conn, true, false); } else if(startsWith("/api/dns/blacklist/regex", request->local_uri)) { - ret = api_dns_somelist(conn, false, false); + return api_dns_somelist(conn, false, false); } /******************************** api/ftl ****************************/ else if(startsWith("/api/ftl/clientIP", request->local_uri)) { - ret = api_ftl_clientIP(conn); + return api_ftl_clientIP(conn); } /******************************** api/stats **************************/ else if(startsWith("/api/stats/summary", request->local_uri)) { - ret = api_stats_summary(conn); + return api_stats_summary(conn); } else if(startsWith("/api/stats/overTime/history", request->local_uri)) { - ret = api_stats_overTime_history(conn); + return api_stats_overTime_history(conn); } else if(startsWith("/api/stats/overTime/clients", request->local_uri)) { - ret = api_stats_overTime_clients(conn); + return api_stats_overTime_clients(conn); } else if(startsWith("/api/stats/query_types", request->local_uri)) { - ret = api_stats_query_types(conn); + return api_stats_query_types(conn); } else if(startsWith("/api/stats/upstreams", request->local_uri)) { - ret = api_stats_upstreams(conn); + return api_stats_upstreams(conn); } else if(startsWith("/api/stats/top_domains", request->local_uri)) { - ret = api_stats_top_domains(false, conn); + return api_stats_top_domains(false, conn); } else if(startsWith("/api/stats/top_blocked", request->local_uri)) { - ret = api_stats_top_domains(true, conn); + return api_stats_top_domains(true, conn); } else if(startsWith("/api/stats/top_clients", request->local_uri)) { - ret = api_stats_top_clients(false, conn); + return api_stats_top_clients(false, conn); } else if(startsWith("/api/stats/top_blocked_clients", request->local_uri)) { - ret = api_stats_top_clients(true, conn); + return api_stats_top_clients(true, conn); } else if(startsWith("/api/stats/history", request->local_uri)) { - ret = api_stats_history(conn); + return api_stats_history(conn); } else if(startsWith("/api/stats/recent_blocked", request->local_uri)) { - ret = api_stats_recentblocked(conn); + return api_stats_recentblocked(conn); } /******************************** api/version ****************************/ else if(startsWith("/api/version", request->local_uri)) { - ret = api_version(conn); + return api_version(conn); } /******************************** api/auth ****************************/ else if(startsWith("/api/auth", request->local_uri)) { - ret = api_auth(conn); + return api_auth(conn); } else if(startsWith("/api/auth/salt", request->local_uri)) { - ret = api_auth_salt(conn); + return api_auth_salt(conn); } /******************************** api/settings ****************************/ else if(startsWith("/api/settings/web", request->local_uri)) { - ret = api_settings_web(conn); + return api_settings_web(conn); } else if(startsWith("/api/settings/ftldb", request->local_uri)) { - ret = api_settings_ftldb(conn); + return api_settings_ftldb(conn); } /******************************** not found ******************************/ else { cJSON *json = JSON_NEW_OBJ(); JSON_OBJ_REF_STR(json, "path", request->local_uri); - - ret = send_json_error(conn, 404, - "not_found", - "Not found", - json, NULL); + return send_json_error(conn, 404, + "not_found", + "Not found", + json, NULL); } - - return ret; } From 91e1e99d31eea395242fffd6b2abd382ccea863f Mon Sep 17 00:00:00 2001 From: DL6ER Date: Wed, 20 Nov 2019 13:08:04 +0100 Subject: [PATCH 0093/1669] Use curl instead of wget for the HTTP API tests. Signed-off-by: DL6ER --- test/test_suite.bats | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/test/test_suite.bats b/test/test_suite.bats index ac0c32e28..69835f82d 100644 --- a/test/test_suite.bats +++ b/test/test_suite.bats @@ -265,19 +265,25 @@ } @test "HTTP server responds correctly to ping" { - run bash -c 'wget 127.0.0.1:8080/ping -q -O -' + run bash -c 'curl 127.0.0.1:8080/ping' printf "%s\n" "${lines[@]}" [[ ${lines[0]} == "pong" ]] } -@test "HTTP server responds wth JSON 404 to unknown API path" { - run bash -c 'wget 127.0.0.1:8080/admin/api/undefined -q -O - --content-on-error' +@test "HTTP server responds wth JSON error 404 to unknown API path" { + run bash -c 'curl 127.0.0.1:8080/admin/api/undefined' printf "%s\n" "${lines[@]}" [[ ${lines[0]} == "{\"error\":{\"key\":\"not_found\",\"message\":\"Not found\",\"data\":{\"path\":\"/admin/api/undefined\"}}}" ]] } -@test "HTTP server responds wth normal 404 to path outside /admin" { - run bash -c 'wget 127.0.0.1:8080/undefined -q -O - --content-on-error' +@test "HTTP server responds wth normal error 404 to path outside /admin" { + run bash -c 'curl 127.0.0.1:8080/undefined' printf "%s\n" "${lines[@]}" [[ ${lines[0]} == "Error 404: Not Found" ]] } + +@test "HTTP server responds without error to undefined path inside /admin" { + run bash -c 'curl -I 127.0.0.1:8080/undefined' + printf "%s\n" "${lines[@]}" + [[ ${lines[0]} == "HTTP/1.1 200 OK"* ]] +} From b6bf9bee20ad41e364b1b396f7a02fc6366b7265 Mon Sep 17 00:00:00 2001 From: DL6ER Date: Wed, 20 Nov 2019 15:22:07 +0100 Subject: [PATCH 0094/1669] Add Content-Security-Policy header to resolve CPS issues seen in Firefox when hosted on a machine different than localhost. The critical part is 'unsafe-inline' which is necessary for the inline Javascript found in index.html. Signed-off-by: DL6ER --- src/api/http-common.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/api/http-common.c b/src/api/http-common.c index d0d5aeff9..3060897d0 100644 --- a/src/api/http-common.c +++ b/src/api/http-common.c @@ -312,6 +312,7 @@ void http_init(void) "decode_url", "no", "num_threads", "4", "access_control_list", httpsettings.acl, + "additional_header", "Content-Security-Policy: default-src 'self' 'unsafe-inline';", NULL }; @@ -400,4 +401,4 @@ int http_method(struct mg_connection *conn) { return HTTP_UNKNOWN; } -} \ No newline at end of file +} From d59ac986d825bd8c9f9246066eaf3de1c1d5d0d3 Mon Sep 17 00:00:00 2001 From: DL6ER Date: Wed, 20 Nov 2019 15:43:39 +0100 Subject: [PATCH 0095/1669] Improve additional headers as suggested by a security checker site. Signed-off-by: DL6ER --- src/api/http-common.c | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/src/api/http-common.c b/src/api/http-common.c index 3060897d0..64287104c 100644 --- a/src/api/http-common.c +++ b/src/api/http-common.c @@ -306,13 +306,38 @@ void http_init(void) } // Prepare options for HTTP server (NULL-terminated list) + // Note about the additional headers: + // - "Content-Security-Policy: [...]" + // "style-src 'self' 'unsafe-inline' is required by Chart.js styling some elements directly + // "script-src 'self' 'unsafe-inline' is required by index.html containing inline Javascript code. + // - "X-Frame-Options: SAMEORIGIN" + // The page can only be displayed in a frame on the same origin as the page itself. + // - "X-Xss-Protection: 1; mode=block" + // Enables XSS filtering. Rather than sanitizing the page, the browser will prevent + // rendering of the page if an attack is detected. + // - "X-Content-Type-Options: nosniff" + // Marker used by the server to indicate that the MIME types advertised in the + // Content-Type headers should not be changed and be followed. This allows to + // opt-out of MIME type sniffing, or, in other words, it is a way to say that the + // webmasters knew what they were doing. Site security testers usually expect this + // header to be set. + // - "Referrer-Policy: same-origin" + // A referrer will be sent for same-site origins, but cross-origin requests will + // send no referrer information. + // The latter four headers are set as expected by https://securityheaders.io const char *options[] = { "document_root", httpsettings.webroot, "listening_ports", httpsettings.port, "decode_url", "no", "num_threads", "4", "access_control_list", httpsettings.acl, - "additional_header", "Content-Security-Policy: default-src 'self' 'unsafe-inline';", + "additional_header", "Content-Security-Policy: default-src 'self'; " + "style-src 'self' 'unsafe-inline';" + "script-src 'self' 'unsafe-inline';\r\n" + "X-Frame-Options: SAMEORIGIN\r\n" + "X-Xss-Protection: 1; mode=block\r\n" + "X-Content-Type-Options: nosniff\r\n" + "Referrer-Policy: same-origin", NULL }; From 638ab2f9333e5ac0b82384fc051fe86827665fe9 Mon Sep 17 00:00:00 2001 From: DL6ER Date: Wed, 20 Nov 2019 15:56:40 +0100 Subject: [PATCH 0096/1669] Ensure minimum required data is sent by the API even when privacy levels are applied. Sending simply empty responses violates the expectations of the NG web interface. Signed-off-by: DL6ER --- src/api/http-common.c | 8 +++---- src/api/stats.c | 49 ++++++++++++++++++++++++++++++++++++------- 2 files changed, 44 insertions(+), 13 deletions(-) diff --git a/src/api/http-common.c b/src/api/http-common.c index 64287104c..7359d653e 100644 --- a/src/api/http-common.c +++ b/src/api/http-common.c @@ -308,8 +308,8 @@ void http_init(void) // Prepare options for HTTP server (NULL-terminated list) // Note about the additional headers: // - "Content-Security-Policy: [...]" - // "style-src 'self' 'unsafe-inline' is required by Chart.js styling some elements directly - // "script-src 'self' 'unsafe-inline' is required by index.html containing inline Javascript code. + // 'unsafe-inline' is both required by Chart.js styling some elements directly, and + // index.html containing some inlined Javascript code. // - "X-Frame-Options: SAMEORIGIN" // The page can only be displayed in a frame on the same origin as the page itself. // - "X-Xss-Protection: 1; mode=block" @@ -331,9 +331,7 @@ void http_init(void) "decode_url", "no", "num_threads", "4", "access_control_list", httpsettings.acl, - "additional_header", "Content-Security-Policy: default-src 'self'; " - "style-src 'self' 'unsafe-inline';" - "script-src 'self' 'unsafe-inline';\r\n" + "additional_header", "Content-Security-Policy: default-src 'self' 'unsafe-inline';\r\n" "X-Frame-Options: SAMEORIGIN\r\n" "X-Xss-Protection: 1; mode=block\r\n" "X-Content-Type-Options: nosniff\r\n" diff --git a/src/api/stats.c b/src/api/stats.c index fa0106c4f..46c05e077 100644 --- a/src/api/stats.c +++ b/src/api/stats.c @@ -178,7 +178,11 @@ int api_stats_top_domains(bool blocked, struct mg_connection *conn) get_privacy_level(NULL); if(config.privacylevel >= PRIVACY_HIDE_DOMAINS) { - cJSON* json = JSON_NEW_OBJ(); + // Minimum structure is + // {"top_domains":[]} + cJSON *json = JSON_NEW_OBJ(); + cJSON *top_domains = JSON_NEW_ARRAY(); + JSON_OBJ_ADD_ITEM(json, "top_domains", top_domains); JSON_SEND_OBJECT(json); } @@ -329,9 +333,13 @@ int api_stats_top_clients(bool blocked, struct mg_connection *conn) // Exit before processing any data if requested via config setting get_privacy_level(NULL); - if(config.privacylevel >= PRIVACY_HIDE_DOMAINS) + if(config.privacylevel >= PRIVACY_HIDE_DOMAINS_CLIENTS) { - cJSON* json = JSON_NEW_OBJ(); + // Minimum structure is + // {"top_clients":[]} + cJSON *json = JSON_NEW_OBJ(); + cJSON *top_clients = JSON_NEW_ARRAY(); + JSON_OBJ_ADD_ITEM(json, "top_clients", top_clients); JSON_SEND_OBJECT(json); } @@ -539,7 +547,13 @@ int api_stats_history(struct mg_connection *conn) get_privacy_level(NULL); if(config.privacylevel >= PRIVACY_MAXIMUM) { - cJSON* json = JSON_NEW_OBJ(); + // Minimum structure is + // {"history":[{}]} + cJSON *json = JSON_NEW_OBJ(); + cJSON *history = JSON_NEW_ARRAY(); + cJSON *item = JSON_NEW_OBJ(); + JSON_ARRAY_ADD_ITEM(history, item); + JSON_OBJ_ADD_ITEM(json, "history", history); JSON_SEND_OBJECT(json); } @@ -685,9 +699,9 @@ int api_stats_history(struct mg_connection *conn) free(domainname); return send_json_error(conn, 400, - "bad_request", - "Requested domain not found", - json, NULL); + "bad_request", + "Requested domain not found", + json, NULL); } } @@ -928,6 +942,17 @@ int api_stats_recentblocked(struct mg_connection *conn) return send_json_unauthorized(conn, NULL); } + // Exit before processing any data if requested via config setting + get_privacy_level(NULL); + if(config.privacylevel >= PRIVACY_HIDE_DOMAINS) + { + // Minimum structure is + // {"blocked":null} + cJSON *json = JSON_NEW_OBJ(); + JSON_OBJ_ADD_NULL(json, "blocked"); + JSON_SEND_OBJECT(json); + } + const struct mg_request_info *request = mg_get_request_info(conn); if(request->query_string != NULL) { @@ -994,7 +1019,13 @@ int api_stats_overTime_clients(struct mg_connection *conn) get_privacy_level(NULL); if(config.privacylevel >= PRIVACY_HIDE_DOMAINS_CLIENTS) { - cJSON* json = JSON_NEW_OBJ(); + // Minimum structure is + // {"over_time":[], "clients":[]} + cJSON *json = JSON_NEW_OBJ(); + cJSON *over_time = JSON_NEW_ARRAY(); + JSON_OBJ_ADD_ITEM(json, "over_time", over_time); + cJSON *clients = JSON_NEW_ARRAY(); + JSON_OBJ_ADD_ITEM(json, "clients", clients); JSON_SEND_OBJECT(json); } @@ -1010,6 +1041,8 @@ int api_stats_overTime_clients(struct mg_connection *conn) } if(sendit < 0) { + // Minimum structure is + // {"over_time":[], "clients":[]} cJSON *json = JSON_NEW_OBJ(); cJSON *over_time = JSON_NEW_ARRAY(); JSON_OBJ_ADD_ITEM(json, "over_time", over_time); From baa4b37ddcc912626782cdb9bd9e902d301da1e6 Mon Sep 17 00:00:00 2001 From: DL6ER Date: Wed, 20 Nov 2019 16:02:48 +0100 Subject: [PATCH 0097/1669] Add --slient to the curl commands to suppress the output of curl's progress bar during the tests. They are confusing bats. Signed-off-by: DL6ER --- test/test_suite.bats | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/test/test_suite.bats b/test/test_suite.bats index 69835f82d..4fe6a97dc 100644 --- a/test/test_suite.bats +++ b/test/test_suite.bats @@ -265,25 +265,25 @@ } @test "HTTP server responds correctly to ping" { - run bash -c 'curl 127.0.0.1:8080/ping' + run bash -c 'curl -s 127.0.0.1:8080/ping' printf "%s\n" "${lines[@]}" [[ ${lines[0]} == "pong" ]] } @test "HTTP server responds wth JSON error 404 to unknown API path" { - run bash -c 'curl 127.0.0.1:8080/admin/api/undefined' + run bash -c 'curl -s 127.0.0.1:8080/admin/api/undefined' printf "%s\n" "${lines[@]}" [[ ${lines[0]} == "{\"error\":{\"key\":\"not_found\",\"message\":\"Not found\",\"data\":{\"path\":\"/admin/api/undefined\"}}}" ]] } @test "HTTP server responds wth normal error 404 to path outside /admin" { - run bash -c 'curl 127.0.0.1:8080/undefined' + run bash -c 'curl -s 127.0.0.1:8080/undefined' printf "%s\n" "${lines[@]}" [[ ${lines[0]} == "Error 404: Not Found" ]] } @test "HTTP server responds without error to undefined path inside /admin" { - run bash -c 'curl -I 127.0.0.1:8080/undefined' + run bash -c 'curl -I -s 127.0.0.1:8080/undefined' printf "%s\n" "${lines[@]}" [[ ${lines[0]} == "HTTP/1.1 200 OK"* ]] } From 59025029de59ea5d385cbc0d658c9c6875c56d53 Mon Sep 17 00:00:00 2001 From: DL6ER Date: Wed, 20 Nov 2019 16:07:26 +0100 Subject: [PATCH 0098/1669] Remove dead function http_send(). Signed-off-by: DL6ER --- src/api/http-common.c | 29 ----------------------------- src/api/http-common.h | 3 +-- 2 files changed, 1 insertion(+), 31 deletions(-) diff --git a/src/api/http-common.c b/src/api/http-common.c index 7359d653e..4052fff83 100644 --- a/src/api/http-common.c +++ b/src/api/http-common.c @@ -136,35 +136,6 @@ bool __attribute__((pure)) startsWith(const char *path, const char *uri) } } -void __attribute__ ((format (gnu_printf, 3, 4))) - http_send(struct mg_connection *conn, bool chunk, const char *format, ...) -{ - char *buffer; - va_list args; - va_start(args, format); - int len = vasprintf(&buffer, format, args); - va_end(args); - if(len > 0) - { - if(!chunk) - { - // Send 200 HTTP header with content size - mg_send_http_ok(conn, "application/json", NULL, len); - } - if(chunk && mg_send_chunk(conn, buffer, len) < 0) - { - logg("WARNING: Chunked HTTP writing returned error %s " - "(%i, length %i)", strerror(errno), errno, len); - } - else if(!chunk && mg_write(conn, buffer, len) < 0) - { - logg("WARNING: Regular HTTP writing returned error %s " - "(%i, length %i)", strerror(errno), errno, len); - } - free(buffer); - } -} - // Print passed string directly static int print_simple(struct mg_connection *conn, void *input) { diff --git a/src/api/http-common.h b/src/api/http-common.h index ed3d14d5f..8187671dd 100644 --- a/src/api/http-common.h +++ b/src/api/http-common.h @@ -22,7 +22,6 @@ void http_init(void); void http_terminate(void); -void http_send(struct mg_connection *conn, bool chunk, const char *format, ...) __attribute__ ((format (gnu_printf, 3, 4))); int send_http(struct mg_connection *conn, const char *mime_type, const char *additional_headers, const char *msg); @@ -55,4 +54,4 @@ int http_method(struct mg_connection *conn); // Utils bool startsWith(const char *path, const char *uri) __attribute__((pure)); -#endif // HTTP_H \ No newline at end of file +#endif // HTTP_H From 31d0d8f75f339b69eb70692c77213912b50788a4 Mon Sep 17 00:00:00 2001 From: DL6ER Date: Wed, 20 Nov 2019 16:08:26 +0100 Subject: [PATCH 0099/1669] A test should do what is written in its description Signed-off-by: DL6ER --- test/test_suite.bats | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/test_suite.bats b/test/test_suite.bats index 4fe6a97dc..5857d9183 100644 --- a/test/test_suite.bats +++ b/test/test_suite.bats @@ -282,8 +282,8 @@ [[ ${lines[0]} == "Error 404: Not Found" ]] } -@test "HTTP server responds without error to undefined path inside /admin" { - run bash -c 'curl -I -s 127.0.0.1:8080/undefined' +@test "HTTP server responds without error to undefined path inside /admin (rerouted to index.html)" { + run bash -c 'curl -I -s 127.0.0.1:8080/admin/undefined' printf "%s\n" "${lines[@]}" [[ ${lines[0]} == "HTTP/1.1 200 OK"* ]] } From 8cedaf76ebf68159ea4d6f71541f3552e3d19073 Mon Sep 17 00:00:00 2001 From: DL6ER Date: Wed, 20 Nov 2019 16:25:57 +0100 Subject: [PATCH 0100/1669] Disable test that does not apply without a hosted web interface and add two new tests for /api/auth. Signed-off-by: DL6ER --- test/test_suite.bats | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/test/test_suite.bats b/test/test_suite.bats index 5857d9183..3657e18f4 100644 --- a/test/test_suite.bats +++ b/test/test_suite.bats @@ -282,8 +282,22 @@ [[ ${lines[0]} == "Error 404: Not Found" ]] } -@test "HTTP server responds without error to undefined path inside /admin (rerouted to index.html)" { - run bash -c 'curl -I -s 127.0.0.1:8080/admin/undefined' +# This test does not work without actually hosting the web interface +#@test "HTTP server responds without error to undefined path inside /admin (rerouted to index.html)" { +# run bash -c 'curl -I -s 127.0.0.1:8080/admin/undefined' +# printf "%s\n" "${lines[@]}" +# [[ ${lines[0]} == "HTTP/1.1 200 OK"* ]] +#} + +@test "API authorization: Unauthorized for request without password" { + run bash -c 'curl -I -s 127.0.0.1:8080/admin/api/auth' + printf "%s\n" "${lines[@]}" + [[ ${lines[0]} == "HTTP/1.1 401 Unauthorized"* ]] +} + +# This test is assuming the user password is empty +@test "API authorization: Success for request with correct password" { + run bash -c 'curl -s -H "X-Pi-hole-Authenticate: cd372fb85148700fa88095e3492d3f9f5beb43e555e5ff26d95f5a6adc36f8e6" 127.0.0.1:8080/admin/api/auth' printf "%s\n" "${lines[@]}" - [[ ${lines[0]} == "HTTP/1.1 200 OK"* ]] + [[ ${lines[0]} == "{\"status\":\"success\"}" ]] } From 637558f5aaab5c72c961ceb4e3d2fd75660b667a Mon Sep 17 00:00:00 2001 From: DL6ER Date: Wed, 20 Nov 2019 16:44:43 +0100 Subject: [PATCH 0101/1669] Rename send_http_error() to send_http_internal_error(). Signed-off-by: DL6ER --- src/api/http-common.c | 4 ++-- src/api/http-common.h | 2 +- src/api/json_macros.h | 22 +++++++++++----------- test/test_suite.bats | 4 ++-- 4 files changed, 16 insertions(+), 16 deletions(-) diff --git a/src/api/http-common.c b/src/api/http-common.c index 4052fff83..290835f17 100644 --- a/src/api/http-common.c +++ b/src/api/http-common.c @@ -86,7 +86,7 @@ int send_json_success(struct mg_connection *conn, JSON_SEND_OBJECT_AND_HEADERS(json, additional_headers); } -int send_http_error(struct mg_connection *conn) +int send_http_internal_error(struct mg_connection *conn) { return mg_send_http_error(conn, 500, "Internal server error"); } @@ -240,7 +240,7 @@ static int index_handler(struct mg_connection *conn, void *ignored) else { logg("ERROR: index.html not available, responding with Error 500."); - send_http_error(conn); + send_http_internal_error(conn); return 500; } diff --git a/src/api/http-common.h b/src/api/http-common.h index 8187671dd..8f53e5f78 100644 --- a/src/api/http-common.h +++ b/src/api/http-common.h @@ -27,7 +27,7 @@ int send_http(struct mg_connection *conn, const char *mime_type, const char *additional_headers, const char *msg); int send_http_code(struct mg_connection *conn, const char *mime_type, const char *additional_headers, int code, const char *msg); -int send_http_error(struct mg_connection *conn); +int send_http_internal_error(struct mg_connection *conn); int send_json_unauthorized(struct mg_connection *conn, char *additional_headers); int send_json_error(struct mg_connection *conn, const int code, diff --git a/src/api/json_macros.h b/src/api/json_macros.h index 26128fc55..951b22d95 100644 --- a/src/api/json_macros.h +++ b/src/api/json_macros.h @@ -39,7 +39,7 @@ if(string_item == NULL) \ { \ cJSON_Delete(object); \ - send_http_error(conn); \ + send_http_internal_error(conn); \ return 500; \ } \ cJSON_AddItemToObject(object, key, string_item); \ @@ -50,7 +50,7 @@ if(string_item == NULL) \ { \ cJSON_Delete(object); \ - send_http_error(conn); \ + send_http_internal_error(conn); \ return 500; \ } \ cJSON_AddItemToObject(object, key, string_item); \ @@ -60,7 +60,7 @@ if(cJSON_AddNumberToObject(object, key, (double)number) == NULL) \ { \ cJSON_Delete(object); \ - send_http_error(conn); \ + send_http_internal_error(conn); \ return 500; \ } \ } @@ -70,7 +70,7 @@ if(null_item == NULL) \ { \ cJSON_Delete(object); \ - send_http_error(conn); \ + send_http_internal_error(conn); \ return 500; \ } \ cJSON_AddItemToObject(object, key, null_item); \ @@ -81,7 +81,7 @@ if(bool_item == NULL) \ { \ cJSON_Delete(object); \ - send_http_error(conn); \ + send_http_internal_error(conn); \ return 500; \ } \ cJSON_AddItemToObject(object, key, bool_item); \ @@ -97,7 +97,7 @@ if(string_item == NULL) \ { \ cJSON_Delete(array); \ - send_http_error(conn); \ + send_http_internal_error(conn); \ return 500; \ } \ cJSON_AddItemToArray(array, string_item); \ @@ -108,7 +108,7 @@ if(string_item == NULL) \ { \ cJSON_Delete(array); \ - send_http_error(conn); \ + send_http_internal_error(conn); \ return 500; \ } \ cJSON_AddItemToArray(array, string_item); \ @@ -126,7 +126,7 @@ if(msg == NULL) \ { \ cJSON_Delete(object); \ - send_http_error(conn); \ + send_http_internal_error(conn); \ return 500; \ } \ send_http(conn, "application/json; charset=utf-8", NULL, msg); \ @@ -139,7 +139,7 @@ if(msg == NULL) \ { \ cJSON_Delete(object); \ - send_http_error(conn); \ + send_http_internal_error(conn); \ return 500; \ } \ send_http_code(conn, "application/json; charset=utf-8", NULL, code, msg); \ @@ -152,7 +152,7 @@ if(msg == NULL) \ { \ cJSON_Delete(object); \ - send_http_error(conn); \ + send_http_internal_error(conn); \ return 500; \ } \ send_http(conn, "application/json; charset=utf-8", additional_headers, msg); \ @@ -166,7 +166,7 @@ if(msg == NULL) \ { \ cJSON_Delete(object); \ - send_http_error(conn); \ + send_http_internal_error(conn); \ return 500; \ } \ send_http_code(conn, "application/json; charset=utf-8", additional_headers, code, msg); \ diff --git a/test/test_suite.bats b/test/test_suite.bats index 3657e18f4..a2001f357 100644 --- a/test/test_suite.bats +++ b/test/test_suite.bats @@ -270,13 +270,13 @@ [[ ${lines[0]} == "pong" ]] } -@test "HTTP server responds wth JSON error 404 to unknown API path" { +@test "HTTP server responds with JSON error 404 to unknown API path" { run bash -c 'curl -s 127.0.0.1:8080/admin/api/undefined' printf "%s\n" "${lines[@]}" [[ ${lines[0]} == "{\"error\":{\"key\":\"not_found\",\"message\":\"Not found\",\"data\":{\"path\":\"/admin/api/undefined\"}}}" ]] } -@test "HTTP server responds wth normal error 404 to path outside /admin" { +@test "HTTP server responds with normal error 404 to path outside /admin" { run bash -c 'curl -s 127.0.0.1:8080/undefined' printf "%s\n" "${lines[@]}" [[ ${lines[0]} == "Error 404: Not Found" ]] From 50e2736ef3ae727f7e015215e8a92027c517f20a Mon Sep 17 00:00:00 2001 From: DL6ER Date: Sat, 23 Nov 2019 13:59:16 +0100 Subject: [PATCH 0102/1669] Copy what dnsmasq would log even when there is no logging destination configures. This is an experimental feature at this time and can (temporarily) enabled by settting DEBUG_AP=true in pihole-FTL.conf. This feature will likely be moves into a new API endpoint /admin/api/log (TBD) when it turns out to be useful. Signed-off-by: DL6ER --- src/dnsmasq/log.c | 9 +++++++++ src/dnsmasq_interface.c | 24 ++++++++++++++++++++++++ src/dnsmasq_interface.h | 2 ++ 3 files changed, 35 insertions(+) diff --git a/src/dnsmasq/log.c b/src/dnsmasq/log.c index dbd6bd41e..fb2a7dc1f 100644 --- a/src/dnsmasq/log.c +++ b/src/dnsmasq/log.c @@ -15,6 +15,7 @@ */ #include "dnsmasq.h" +#include "../dnsmasq_interface.h" #ifdef __ANDROID__ # include @@ -298,6 +299,14 @@ void my_syslog(int priority, const char *format, ...) priority &= LOG_PRIMASK; #endif + /*************************** Pi-hole specific logging **************************/ + va_start(ap, format); + char buffer[MAX_MESSAGE + 1u]; + len = vsnprintf(buffer, MAX_MESSAGE, format, ap) + 1u; /* include zero-terminator */ + va_end(ap); + FTL_dnsmasq_log(buffer, len > MAX_MESSAGE ? MAX_MESSAGE : len); + /*******************************************************************************/ + if (echo_stderr) { fprintf(stderr, "dnsmasq%s: ", func); diff --git a/src/dnsmasq_interface.c b/src/dnsmasq_interface.c index 2f220a033..1d4893dc5 100644 --- a/src/dnsmasq_interface.c +++ b/src/dnsmasq_interface.c @@ -1810,3 +1810,27 @@ void FTL_TCP_worker_terminating(void) // Close dedicated database connection of this fork gravityDB_close(); } + +void FTL_dnsmasq_log(const char *payload, const int length) +{ + // Get temporary space for dnsmasq's log message + char log_str[length + 1u]; + + // Copy relevant string into temporary buffer + memcpy(log_str, payload, length); + + // Zero-terminate buffer, truncate newline if found + if(log_str[length - 1u] == '\n') + { + log_str[length - 1u] = '\0'; + } + else + { + log_str[length] = '\0'; + } + + if(config.debug & DEBUG_API) + { + logg("DNSMASQ LOG: \"%s\"", log_str); + } +} \ No newline at end of file diff --git a/src/dnsmasq_interface.h b/src/dnsmasq_interface.h index 424de6818..61ae6b12d 100644 --- a/src/dnsmasq_interface.h +++ b/src/dnsmasq_interface.h @@ -55,4 +55,6 @@ void FTL_TCP_worker_terminating(void); void set_debug_dnsmasq_lines(char enabled); extern char debug_dnsmasq_lines; +void FTL_dnsmasq_log(const char *payload, const int length); + #endif // DNSMASQ_INTERFACE_H From e4c7cec3493b70c38c56429e69f0f6012e972ca1 Mon Sep 17 00:00:00 2001 From: DL6ER Date: Sat, 23 Nov 2019 19:02:33 +0100 Subject: [PATCH 0103/1669] Add FIFO buffer endpoint at /admin/api/ftl/dnsmasq_log. This is a FIFO buffer collecting and returning up to 32 messages from memory (compile-time option). The API endpoint can be continuously polled with the last returned ID to only show new messages that can be appended to a log on the web interface. Signed-off-by: DL6ER --- src/FTL.h | 3 ++ src/api/ftl.c | 95 +++++++++++++++++++++++++++++++++++++++++ src/api/ftl.h | 23 ++++++++++ src/api/routes.c | 4 ++ src/api/routes.h | 3 +- src/api/stats.c | 2 - src/dnsmasq/log.c | 2 +- src/dnsmasq_interface.c | 25 ++--------- 8 files changed, 131 insertions(+), 26 deletions(-) create mode 100644 src/api/ftl.h diff --git a/src/FTL.h b/src/FTL.h index 7b4be897e..c7e67e82a 100644 --- a/src/FTL.h +++ b/src/FTL.h @@ -139,4 +139,7 @@ extern pthread_t timerthread; // Intentionally ignore result of function declared warn_unused_result #define igr(x) {__typeof__(x) __attribute__((unused)) d=(x);} +#define max(a,b) ({ __typeof__ (a) _a = (a); __typeof__ (b) _b = (b); _a > _b ? _a : _b; }) +#define min(a,b) ({ __typeof__ (a) _a = (a); __typeof__ (b) _b = (b); _a < _b ? _a : _b; }) + #endif // FTL_H diff --git a/src/api/ftl.c b/src/api/ftl.c index e1287e890..aa9c936d9 100644 --- a/src/api/ftl.c +++ b/src/api/ftl.c @@ -11,12 +11,15 @@ #include "FTL.h" #include "http-common.h" #include "routes.h" +#include "ftl.h" #include "json_macros.h" #include "datastructure.h" // get_FTL_version() #include "log.h" // git constants #include "version.h" +// config struct +#include "config.h" int api_ftl_clientIP(struct mg_connection *conn) { @@ -25,3 +28,95 @@ int api_ftl_clientIP(struct mg_connection *conn) JSON_OBJ_REF_STR(json,"remote_addr", request->remote_addr); JSON_SEND_OBJECT(json); } + +static char dnsmasq_log_messages[LOG_SIZE][MAX_MESSAGE] = { 0 }; +static time_t dnsmasq_log_stamps[LOG_SIZE] = { 0 }; +static int dnsmasq_next_id = 0; + +int api_ftl_dnsmasq_log(struct mg_connection *conn) +{ + // Verify requesting client is allowed to see this ressource + if(check_client_auth(conn) < 0) + { + return send_json_unauthorized(conn, NULL); + } + + unsigned int start = 0u; + const struct mg_request_info *request = mg_get_request_info(conn); + if(request->query_string != NULL) + { + // Does the user request an ID to sent from? + int num; + if((num = get_int_var(request->query_string, "nextID")) > 0) + { + if(num >= dnsmasq_next_id) + { + // Do not return any data + start = LOG_SIZE; + } + else if(num < max(dnsmasq_next_id - LOG_SIZE, 0)) + { + // Requested an ID smaller than the lowest one we have + // We return the entire buffer + start = 0; + } + else + { + // Reply with partial buffer + start = LOG_SIZE - (dnsmasq_next_id - num); + } + } + } + + // Process data + cJSON *json = JSON_NEW_OBJ(); + cJSON *log = JSON_NEW_ARRAY(); + unsigned int idx = 0u; + for(unsigned int i = start; i < LOG_SIZE; i++) + { + // Reconstruct log message identification number + if(dnsmasq_next_id < LOG_SIZE) + { + idx = i; + } + else + { + idx = dnsmasq_next_id - LOG_SIZE + i; + } + + cJSON *entry = JSON_NEW_OBJ(); + JSON_OBJ_ADD_NUMBER(entry, "id", idx); + JSON_OBJ_ADD_NUMBER(entry, "timestamp", dnsmasq_log_stamps[i]); + JSON_OBJ_REF_STR(entry, "message", dnsmasq_log_messages[i]); + JSON_ARRAY_ADD_ITEM(log, entry); + } + JSON_OBJ_ADD_ITEM(json, "log", log); + JSON_OBJ_ADD_NUMBER(json, "nextID", dnsmasq_next_id); + JSON_SEND_OBJECT(json); +} + +void add_to_dnsmasq_log_fifo_buffer(const char *payload, const int length) +{ + unsigned int idx = dnsmasq_next_id++; + if(idx >= LOG_SIZE) + { + // Log is full, move everything one slot forward to make space + memmove(dnsmasq_log_messages[0], dnsmasq_log_messages[1], (LOG_SIZE - 1u) * MAX_MESSAGE); + idx = LOG_SIZE - 1u; + } + // Copy relevant string into temporary buffer + memcpy(dnsmasq_log_messages[idx], payload, length); + + // Zero-terminate buffer, truncate newline if found + if(dnsmasq_log_messages[idx][length - 1u] == '\n') + { + dnsmasq_log_messages[idx][length - 1u] = '\0'; + } + else + { + dnsmasq_log_messages[idx][length] = '\0'; + } + + // Set timestamp + dnsmasq_log_stamps[idx] = time(NULL); +} \ No newline at end of file diff --git a/src/api/ftl.h b/src/api/ftl.h new file mode 100644 index 000000000..8ee9fe9ef --- /dev/null +++ b/src/api/ftl.h @@ -0,0 +1,23 @@ +/* Pi-hole: A black hole for Internet advertisements +* (c) 2019 Pi-hole, LLC (https://pi-hole.net) +* Network-wide ad blocking via your own hardware. +* +* FTL Engine +* API FTL prototypes +* +* This file is copyright under the latest version of the EUPL. +* Please see LICENSE file for your rights under this license. */ +#ifndef API_FTL_H +#define API_FTL_H + +/* From RFC 3164 */ +#define MAX_MESSAGE 1024 + +// How many messages do we keep in memory (FIFO message buffer)? +// The memory required is the set number in kilobytes +// Defaults to 32 [uses 32 KB of memory] +#define LOG_SIZE 32 + +void add_to_dnsmasq_log_fifo_buffer(const char *payload, const int length); + +#endif // API_FTL_H \ No newline at end of file diff --git a/src/api/routes.c b/src/api/routes.c index c82b33867..9313a42c6 100644 --- a/src/api/routes.c +++ b/src/api/routes.c @@ -44,6 +44,10 @@ int api_handler(struct mg_connection *conn, void *ignored) { return api_ftl_clientIP(conn); } + else if(startsWith("/api/ftl/dnsmasq_log", request->local_uri)) + { + return api_ftl_dnsmasq_log(conn); + } /******************************** api/stats **************************/ else if(startsWith("/api/stats/summary", request->local_uri)) { diff --git a/src/api/routes.h b/src/api/routes.h index 7e9b900e8..bddf51a45 100644 --- a/src/api/routes.h +++ b/src/api/routes.h @@ -29,8 +29,7 @@ int api_stats_recentblocked(struct mg_connection *conn); // FTL methods int api_ftl_clientIP(struct mg_connection *conn); -int api_ftl_version(struct mg_connection *conn); -int api_ftl_db(struct mg_connection *conn); +int api_ftl_dnsmasq_log(struct mg_connection *conn); // DNS methods int api_dns_status(struct mg_connection *conn); diff --git a/src/api/stats.c b/src/api/stats.c index 46c05e077..9594b2f81 100644 --- a/src/api/stats.c +++ b/src/api/stats.c @@ -27,8 +27,6 @@ // enum REGEX #include "regex_r.h" -#define min(a,b) ({ __typeof__ (a) _a = (a); __typeof__ (b) _b = (b); _a < _b ? _a : _b; }) - /* qsort comparision function (count field), sort ASC static int __attribute__((pure)) cmpasc(const void *a, const void *b) { diff --git a/src/dnsmasq/log.c b/src/dnsmasq/log.c index fb2a7dc1f..6713446ec 100644 --- a/src/dnsmasq/log.c +++ b/src/dnsmasq/log.c @@ -300,8 +300,8 @@ void my_syslog(int priority, const char *format, ...) #endif /*************************** Pi-hole specific logging **************************/ - va_start(ap, format); char buffer[MAX_MESSAGE + 1u]; + va_start(ap, format); len = vsnprintf(buffer, MAX_MESSAGE, format, ap) + 1u; /* include zero-terminator */ va_end(ap); FTL_dnsmasq_log(buffer, len > MAX_MESSAGE ? MAX_MESSAGE : len); diff --git a/src/dnsmasq_interface.c b/src/dnsmasq_interface.c index 1d4893dc5..b05eae215 100644 --- a/src/dnsmasq_interface.c +++ b/src/dnsmasq_interface.c @@ -34,6 +34,8 @@ #include "args.h" // http_init() #include "api/http-common.h" +// add_to_dnsmasq_log_buffer() +#include "api/ftl.h" static void print_flags(const unsigned int flags); static void save_reply_type(const unsigned int flags, const union all_addr *addr, @@ -1813,24 +1815,5 @@ void FTL_TCP_worker_terminating(void) void FTL_dnsmasq_log(const char *payload, const int length) { - // Get temporary space for dnsmasq's log message - char log_str[length + 1u]; - - // Copy relevant string into temporary buffer - memcpy(log_str, payload, length); - - // Zero-terminate buffer, truncate newline if found - if(log_str[length - 1u] == '\n') - { - log_str[length - 1u] = '\0'; - } - else - { - log_str[length] = '\0'; - } - - if(config.debug & DEBUG_API) - { - logg("DNSMASQ LOG: \"%s\"", log_str); - } -} \ No newline at end of file + add_to_dnsmasq_log_fifo_buffer(payload, length); +} From 2a07c992ab66cbd49efe5f4f2d31446367f84784 Mon Sep 17 00:00:00 2001 From: DL6ER Date: Sat, 23 Nov 2019 19:15:23 +0100 Subject: [PATCH 0104/1669] Add option for whether authentication is needed for localhost requests. Defaults to false. (API_AUTH_FOR_LOCALHOST) Signed-off-by: DL6ER --- src/api/auth.c | 29 +++++++++++++++++++++++++---- src/api/http-common.c | 11 ++++++++++- src/config.c | 14 ++++++++++++++ src/config.h | 1 + 4 files changed, 50 insertions(+), 5 deletions(-) diff --git a/src/api/auth.c b/src/api/auth.c index 95eec7b31..e66ee80f3 100644 --- a/src/api/auth.c +++ b/src/api/auth.c @@ -39,10 +39,18 @@ static void generateRandomString(char *str, size_t size) // Can we validate this client? // Returns -1 if not authenticated or expired // Returns >= 0 for any valid authentication +#define LOCALHOSTv4 "127.0.0.1" +#define LOCALHOSTv6 "::1" int check_client_auth(struct mg_connection *conn) { int user_id = -1; const struct mg_request_info *request = mg_get_request_info(conn); + + // Is the user requesting from localhost? + if(!httpsettings.api_auth_for_localhost && (strcmp(request->remote_addr, LOCALHOSTv4) == 0 || + strcmp(request->remote_addr, LOCALHOSTv6) == 0)) + return API_MAX_CLIENTS; + // Does the client provide a user_id cookie? int num; if(http_get_cookie_int(conn, "user_id", &num) && num > -1 && num < API_MAX_CLIENTS) @@ -136,10 +144,10 @@ int api_auth(struct mg_connection *conn) logg("Registered new user: user_id %i valid_until: %s remote_addr %s", user_id, timestr, auth_data[user_id].remote_addr); } - else - { - logg("No free user slots available, not authenticating user"); - } + } + if(user_id == -1) + { + logg("WARNING: No free slots available, not authenticating user"); } } else if(config.debug & DEBUG_API) @@ -155,6 +163,19 @@ int api_auth(struct mg_connection *conn) user_id = check_client_auth(conn); int method = http_method(conn); + if(user_id == API_MAX_CLIENTS) + { + if(config.debug & DEBUG_API) + logg("Authentification: OK, localhost does not need auth."); + // We still have to send a cookie for the web interface to be happy + char *additional_headers = NULL; + if(asprintf(&additional_headers, + "Set-Cookie: user_id=%u; Path=/; Max-Age=%u\r\n", + API_MAX_CLIENTS, API_SESSION_EXPIRE) < 0) + { + return send_json_error(conn, 500, "internal_error", "Internal server error", NULL, NULL); + } + } if(user_id > -1 && method == HTTP_GET) { if(config.debug & DEBUG_API) diff --git a/src/api/http-common.c b/src/api/http-common.c index 290835f17..94334578e 100644 --- a/src/api/http-common.c +++ b/src/api/http-common.c @@ -83,7 +83,16 @@ int send_json_success(struct mg_connection *conn, { cJSON *json = JSON_NEW_OBJ(); JSON_OBJ_REF_STR(json, "status", "success"); - JSON_SEND_OBJECT_AND_HEADERS(json, additional_headers); + + // Send additional headers if supplied + if(additional_headers == NULL) + { + JSON_SEND_OBJECT(json); + } + else + { + JSON_SEND_OBJECT_AND_HEADERS(json, additional_headers); + } } int send_http_internal_error(struct mg_connection *conn) diff --git a/src/config.c b/src/config.c index 40f20b05f..c80146853 100644 --- a/src/config.c +++ b/src/config.c @@ -408,6 +408,20 @@ void read_FTLconf(void) logg(" WEBACL: Allowing all access."); } + // API_AUTH_FOR_LOALHOST + // defaults to: false + httpsettings.api_auth_for_localhost = false; + buffer = parse_FTLconf(fp, "API_AUTH_FOR_LOALHOST"); + + if(buffer != NULL && (strcasecmp(buffer, "yes") == 0 || + strcasecmp(buffer, "true") == 0)) + httpsettings.api_auth_for_localhost = true; + + if(httpsettings.api_auth_for_localhost) + logg(" API_AUTH_FOR_LOCALHOST: Active"); + else + logg(" API_AUTH_FOR_LOCALHOST: Inactive"); + // Read DEBUG_... setting from pihole-FTL.conf // This option should be the last one as it causes // some rather verbose output into the log when diff --git a/src/config.h b/src/config.h index 27225b05b..45a8b1b48 100644 --- a/src/config.h +++ b/src/config.h @@ -57,6 +57,7 @@ typedef struct httpsettings { char *webroot; char *webhome; const char *acl; + bool api_auth_for_localhost; char port[20]; // enough space for 2*(maximum length of number in a uint16_t = 5 characters) + ",[::]:" + NULL } httpsettingsStruct; From 12434192b8d107c392b4b63e877004c30e4a0e65 Mon Sep 17 00:00:00 2001 From: DL6ER Date: Sat, 23 Nov 2019 19:25:21 +0100 Subject: [PATCH 0105/1669] Do not send unused slots from the FIFO buffer. Signed-off-by: DL6ER --- src/api/ftl.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/api/ftl.c b/src/api/ftl.c index aa9c936d9..45eef86db 100644 --- a/src/api/ftl.c +++ b/src/api/ftl.c @@ -84,6 +84,12 @@ int api_ftl_dnsmasq_log(struct mg_connection *conn) idx = dnsmasq_next_id - LOG_SIZE + i; } + if(dnsmasq_log_stamps[i] == 0) + { + // Uninitialized buffer entry + break; + } + cJSON *entry = JSON_NEW_OBJ(); JSON_OBJ_ADD_NUMBER(entry, "id", idx); JSON_OBJ_ADD_NUMBER(entry, "timestamp", dnsmasq_log_stamps[i]); From bdbad7ef0c884a16ac8bd55e7a5d7f0bd748f042 Mon Sep 17 00:00:00 2001 From: DL6ER Date: Sat, 23 Nov 2019 19:26:48 +0100 Subject: [PATCH 0106/1669] Send correct frames if FIFO is not yet completely filled. Signed-off-by: DL6ER --- src/api/ftl.c | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/api/ftl.c b/src/api/ftl.c index 45eef86db..4f7452a8a 100644 --- a/src/api/ftl.c +++ b/src/api/ftl.c @@ -60,11 +60,18 @@ int api_ftl_dnsmasq_log(struct mg_connection *conn) // We return the entire buffer start = 0; } - else + else if(dnsmasq_next_id >= LOG_SIZE) { - // Reply with partial buffer + // Reply with partial buffer, measure from the end + // (the log is full) start = LOG_SIZE - (dnsmasq_next_id - num); } + else + { + // Reply with partial buffer, measure from the start + // (the log is not yet full) + start = num; + } } } From f2aa1d25fb4b72ce5fbc5d6b1ed3dd6cf1801051 Mon Sep 17 00:00:00 2001 From: DL6ER Date: Sat, 23 Nov 2019 19:38:04 +0100 Subject: [PATCH 0107/1669] Code around known gcc-arm bug. Signed-off-by: DL6ER --- src/api/ftl.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/api/ftl.c b/src/api/ftl.c index 4f7452a8a..62f3ee42e 100644 --- a/src/api/ftl.c +++ b/src/api/ftl.c @@ -29,7 +29,7 @@ int api_ftl_clientIP(struct mg_connection *conn) JSON_SEND_OBJECT(json); } -static char dnsmasq_log_messages[LOG_SIZE][MAX_MESSAGE] = { 0 }; +static char dnsmasq_log_messages[LOG_SIZE][MAX_MESSAGE] = {{ 0 }}; static time_t dnsmasq_log_stamps[LOG_SIZE] = { 0 }; static int dnsmasq_next_id = 0; From 9f8152e39312371ecc6b740124ef74244ffda56d Mon Sep 17 00:00:00 2001 From: DL6ER Date: Sat, 23 Nov 2019 19:40:37 +0100 Subject: [PATCH 0108/1669] Don't send ID field, it is not neded by the client. And even if it would be, it could easily be computed from nextID. Signed-off-by: DL6ER --- src/api/ftl.c | 1 - 1 file changed, 1 deletion(-) diff --git a/src/api/ftl.c b/src/api/ftl.c index 62f3ee42e..a210ac66b 100644 --- a/src/api/ftl.c +++ b/src/api/ftl.c @@ -98,7 +98,6 @@ int api_ftl_dnsmasq_log(struct mg_connection *conn) } cJSON *entry = JSON_NEW_OBJ(); - JSON_OBJ_ADD_NUMBER(entry, "id", idx); JSON_OBJ_ADD_NUMBER(entry, "timestamp", dnsmasq_log_stamps[i]); JSON_OBJ_REF_STR(entry, "message", dnsmasq_log_messages[i]); JSON_ARRAY_ADD_ITEM(log, entry); From a8eb1889eade682204bf215ec78bd1b6c4d1a38b Mon Sep 17 00:00:00 2001 From: DL6ER Date: Sat, 23 Nov 2019 19:42:08 +0100 Subject: [PATCH 0109/1669] Remove unused variable. Signed-off-by: DL6ER --- src/api/ftl.c | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/src/api/ftl.c b/src/api/ftl.c index a210ac66b..c8fdd5f1e 100644 --- a/src/api/ftl.c +++ b/src/api/ftl.c @@ -78,19 +78,8 @@ int api_ftl_dnsmasq_log(struct mg_connection *conn) // Process data cJSON *json = JSON_NEW_OBJ(); cJSON *log = JSON_NEW_ARRAY(); - unsigned int idx = 0u; for(unsigned int i = start; i < LOG_SIZE; i++) { - // Reconstruct log message identification number - if(dnsmasq_next_id < LOG_SIZE) - { - idx = i; - } - else - { - idx = dnsmasq_next_id - LOG_SIZE + i; - } - if(dnsmasq_log_stamps[i] == 0) { // Uninitialized buffer entry From 149572c1a9fbe7a1d9ab830ef9d0729d06b3fb33 Mon Sep 17 00:00:00 2001 From: DL6ER Date: Sat, 23 Nov 2019 21:32:15 +0100 Subject: [PATCH 0110/1669] Move FIFO buffer into shared memory to ensure all spawned TCP children can add entries to it. Signed-off-by: DL6ER --- src/api/ftl.c | 42 +++++++++++++++++++++--------------------- src/api/ftl.h | 10 ++++++++++ src/shmem.c | 11 +++++++++++ 3 files changed, 42 insertions(+), 21 deletions(-) diff --git a/src/api/ftl.c b/src/api/ftl.c index c8fdd5f1e..c2fa8956f 100644 --- a/src/api/ftl.c +++ b/src/api/ftl.c @@ -29,10 +29,7 @@ int api_ftl_clientIP(struct mg_connection *conn) JSON_SEND_OBJECT(json); } -static char dnsmasq_log_messages[LOG_SIZE][MAX_MESSAGE] = {{ 0 }}; -static time_t dnsmasq_log_stamps[LOG_SIZE] = { 0 }; -static int dnsmasq_next_id = 0; - +fifologData *fifo_log = NULL; int api_ftl_dnsmasq_log(struct mg_connection *conn) { // Verify requesting client is allowed to see this ressource @@ -49,22 +46,22 @@ int api_ftl_dnsmasq_log(struct mg_connection *conn) int num; if((num = get_int_var(request->query_string, "nextID")) > 0) { - if(num >= dnsmasq_next_id) + if(num >= fifo_log->next_id) { // Do not return any data start = LOG_SIZE; } - else if(num < max(dnsmasq_next_id - LOG_SIZE, 0)) + else if(num < max((fifo_log->next_id) - LOG_SIZE, 0)) { // Requested an ID smaller than the lowest one we have // We return the entire buffer - start = 0; + start = 0u; } - else if(dnsmasq_next_id >= LOG_SIZE) + else if(fifo_log->next_id >= LOG_SIZE) { // Reply with partial buffer, measure from the end // (the log is full) - start = LOG_SIZE - (dnsmasq_next_id - num); + start = LOG_SIZE - (fifo_log->next_id - num); } else { @@ -80,44 +77,47 @@ int api_ftl_dnsmasq_log(struct mg_connection *conn) cJSON *log = JSON_NEW_ARRAY(); for(unsigned int i = start; i < LOG_SIZE; i++) { - if(dnsmasq_log_stamps[i] == 0) + if(fifo_log->timestamp[i] == 0) { // Uninitialized buffer entry break; } cJSON *entry = JSON_NEW_OBJ(); - JSON_OBJ_ADD_NUMBER(entry, "timestamp", dnsmasq_log_stamps[i]); - JSON_OBJ_REF_STR(entry, "message", dnsmasq_log_messages[i]); + JSON_OBJ_ADD_NUMBER(entry, "timestamp", fifo_log->timestamp[i]); + JSON_OBJ_REF_STR(entry, "message", fifo_log->message[i]); JSON_ARRAY_ADD_ITEM(log, entry); } JSON_OBJ_ADD_ITEM(json, "log", log); - JSON_OBJ_ADD_NUMBER(json, "nextID", dnsmasq_next_id); + JSON_OBJ_ADD_NUMBER(json, "nextID", fifo_log->next_id); JSON_SEND_OBJECT(json); } void add_to_dnsmasq_log_fifo_buffer(const char *payload, const int length) { - unsigned int idx = dnsmasq_next_id++; + unsigned int idx = fifo_log->next_id++; if(idx >= LOG_SIZE) { - // Log is full, move everything one slot forward to make space - memmove(dnsmasq_log_messages[0], dnsmasq_log_messages[1], (LOG_SIZE - 1u) * MAX_MESSAGE); + // Log is full, move everything one slot forward to make space for a new record at the end + // This pruges the oldest message from the list (it is overwritten by the second message) + memmove(fifo_log->message[0], fifo_log->message[1], (LOG_SIZE - 1u) * MAX_MESSAGE); + memmove(&fifo_log->timestamp[0], &fifo_log->timestamp[1], (LOG_SIZE - 1u) * sizeof(time_t)); idx = LOG_SIZE - 1u; } + // Copy relevant string into temporary buffer - memcpy(dnsmasq_log_messages[idx], payload, length); + memcpy(fifo_log->message[idx], payload, length); // Zero-terminate buffer, truncate newline if found - if(dnsmasq_log_messages[idx][length - 1u] == '\n') + if(fifo_log->message[idx][length - 1u] == '\n') { - dnsmasq_log_messages[idx][length - 1u] = '\0'; + fifo_log->message[idx][length - 1u] = '\0'; } else { - dnsmasq_log_messages[idx][length] = '\0'; + fifo_log->message[idx][length] = '\0'; } // Set timestamp - dnsmasq_log_stamps[idx] = time(NULL); + fifo_log->timestamp[idx] = time(NULL); } \ No newline at end of file diff --git a/src/api/ftl.h b/src/api/ftl.h index 8ee9fe9ef..c64b97b81 100644 --- a/src/api/ftl.h +++ b/src/api/ftl.h @@ -18,6 +18,16 @@ // Defaults to 32 [uses 32 KB of memory] #define LOG_SIZE 32 +void init_dnsmasq_fifo_log(void); +void free_dnsmasq_fifo_log(void); void add_to_dnsmasq_log_fifo_buffer(const char *payload, const int length); +typedef struct { + int next_id; + time_t timestamp[LOG_SIZE]; + char message[LOG_SIZE][MAX_MESSAGE]; +} fifologData; + +extern fifologData *fifo_log; + #endif // API_FTL_H \ No newline at end of file diff --git a/src/shmem.c b/src/shmem.c index 39f8e86d9..e2d1baf5d 100644 --- a/src/shmem.c +++ b/src/shmem.c @@ -16,6 +16,8 @@ #include "config.h" // data getter functions #include "datastructure.h" +// fifologData +#include "api/ftl.h" /// The version of shared memory used #define SHARED_MEMORY_VERSION 9 @@ -35,6 +37,7 @@ // Global counters struct countersStruct *counters = NULL; +#define SHARED_FIFO_LOG_NAME "/FTL-fifo-log" /// The pointer in shared memory to the shared string buffer static SharedMemory shm_lock = { 0 }; @@ -48,6 +51,7 @@ static SharedMemory shm_overTime = { 0 }; static SharedMemory shm_settings = { 0 }; static SharedMemory shm_dns_cache = { 0 }; static SharedMemory shm_per_client_regex = { 0 }; +static SharedMemory shm_fifo_log = { 0 }; // Variable size array structs static queriesData *queries = NULL; @@ -339,6 +343,12 @@ bool init_shmem(void) // Try to create shared memory object shm_per_client_regex = create_shm(SHARED_PER_CLIENT_REGEX, size); + /****************************** shared fifo_buffer struct ******************************/ + size = get_optimal_object_size(sizeof(fifologData), 1u); + // Try to create shared memory object + shm_fifo_log = create_shm(SHARED_FIFO_LOG_NAME, size*sizeof(fifologData)); + fifo_log = (fifologData*)shm_fifo_log.ptr; + return true; } @@ -358,6 +368,7 @@ void destroy_shmem(void) delete_shm(&shm_settings); delete_shm(&shm_dns_cache); delete_shm(&shm_per_client_regex); + delete_shm(&shm_fifo_log); } SharedMemory create_shm(const char *name, const size_t size) From 2d8d70647865c4b2953bf35b3490b0ec745cf38c Mon Sep 17 00:00:00 2001 From: DL6ER Date: Sat, 23 Nov 2019 21:33:13 +0100 Subject: [PATCH 0111/1669] Lock SHM during FIFO buffer activity. Signed-off-by: DL6ER --- src/api/ftl.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/api/ftl.c b/src/api/ftl.c index c2fa8956f..24d4752fa 100644 --- a/src/api/ftl.c +++ b/src/api/ftl.c @@ -20,6 +20,8 @@ #include "version.h" // config struct #include "config.h" +// {un,}lock_shm() +#include "../shmem.h" int api_ftl_clientIP(struct mg_connection *conn) { @@ -95,6 +97,9 @@ int api_ftl_dnsmasq_log(struct mg_connection *conn) void add_to_dnsmasq_log_fifo_buffer(const char *payload, const int length) { + // Lock SHM + lock_shm(); + unsigned int idx = fifo_log->next_id++; if(idx >= LOG_SIZE) { @@ -120,4 +125,7 @@ void add_to_dnsmasq_log_fifo_buffer(const char *payload, const int length) // Set timestamp fifo_log->timestamp[idx] = time(NULL); + + // Unlock SHM + unlock_shm(); } \ No newline at end of file From b9857664f50bde322f453efca23a1c47ade7de67 Mon Sep 17 00:00:00 2001 From: DL6ER Date: Sat, 23 Nov 2019 21:37:28 +0100 Subject: [PATCH 0112/1669] Lock SHM during API access. Signed-off-by: DL6ER --- src/api/routes.c | 65 ++++++++++++++++++++++++++++-------------------- 1 file changed, 38 insertions(+), 27 deletions(-) diff --git a/src/api/routes.c b/src/api/routes.c index 9313a42c6..2a1f04177 100644 --- a/src/api/routes.c +++ b/src/api/routes.c @@ -14,116 +14,127 @@ #include "http-common.h" #include "json_macros.h" #include "routes.h" +#include "../shmem.h" int api_handler(struct mg_connection *conn, void *ignored) { + // Lock during API access + lock_shm(); + + int ret = 0; + const struct mg_request_info *request = mg_get_request_info(conn); /******************************** api/dns ********************************/ if(startsWith("/api/dns/status", request->local_uri)) { - return api_dns_status(conn); + ret = api_dns_status(conn); } else if(startsWith("/api/dns/whitelist/exact", request->local_uri)) { - return api_dns_somelist(conn, true, true); + ret = api_dns_somelist(conn, true, true); } else if(startsWith("/api/dns/whitelist/regex", request->local_uri)) { - return api_dns_somelist(conn, false, true); + ret = api_dns_somelist(conn, false, true); } else if(startsWith("/api/dns/blacklist/exact", request->local_uri)) { - return api_dns_somelist(conn, true, false); + ret = api_dns_somelist(conn, true, false); } else if(startsWith("/api/dns/blacklist/regex", request->local_uri)) { - return api_dns_somelist(conn, false, false); + ret = api_dns_somelist(conn, false, false); } /******************************** api/ftl ****************************/ else if(startsWith("/api/ftl/clientIP", request->local_uri)) { - return api_ftl_clientIP(conn); + ret = api_ftl_clientIP(conn); } else if(startsWith("/api/ftl/dnsmasq_log", request->local_uri)) { - return api_ftl_dnsmasq_log(conn); + ret = api_ftl_dnsmasq_log(conn); } /******************************** api/stats **************************/ else if(startsWith("/api/stats/summary", request->local_uri)) { - return api_stats_summary(conn); + ret = api_stats_summary(conn); } else if(startsWith("/api/stats/overTime/history", request->local_uri)) { - return api_stats_overTime_history(conn); + ret = api_stats_overTime_history(conn); } else if(startsWith("/api/stats/overTime/clients", request->local_uri)) { - return api_stats_overTime_clients(conn); + ret = api_stats_overTime_clients(conn); } else if(startsWith("/api/stats/query_types", request->local_uri)) { - return api_stats_query_types(conn); + ret = api_stats_query_types(conn); } else if(startsWith("/api/stats/upstreams", request->local_uri)) { - return api_stats_upstreams(conn); + ret = api_stats_upstreams(conn); } else if(startsWith("/api/stats/top_domains", request->local_uri)) { - return api_stats_top_domains(false, conn); + ret = api_stats_top_domains(false, conn); } else if(startsWith("/api/stats/top_blocked", request->local_uri)) { - return api_stats_top_domains(true, conn); + ret = api_stats_top_domains(true, conn); } else if(startsWith("/api/stats/top_clients", request->local_uri)) { - return api_stats_top_clients(false, conn); + ret = api_stats_top_clients(false, conn); } else if(startsWith("/api/stats/top_blocked_clients", request->local_uri)) { - return api_stats_top_clients(true, conn); + ret = api_stats_top_clients(true, conn); } else if(startsWith("/api/stats/history", request->local_uri)) { - return api_stats_history(conn); + ret = api_stats_history(conn); } else if(startsWith("/api/stats/recent_blocked", request->local_uri)) { - return api_stats_recentblocked(conn); + ret = api_stats_recentblocked(conn); } /******************************** api/version ****************************/ else if(startsWith("/api/version", request->local_uri)) { - return api_version(conn); + ret = api_version(conn); } /******************************** api/auth ****************************/ else if(startsWith("/api/auth", request->local_uri)) { - return api_auth(conn); + ret = api_auth(conn); } else if(startsWith("/api/auth/salt", request->local_uri)) { - return api_auth_salt(conn); + ret = api_auth_salt(conn); } /******************************** api/settings ****************************/ else if(startsWith("/api/settings/web", request->local_uri)) { - return api_settings_web(conn); + ret = api_settings_web(conn); } else if(startsWith("/api/settings/ftldb", request->local_uri)) { - return api_settings_ftldb(conn); + ret = api_settings_ftldb(conn); } /******************************** not found ******************************/ else { cJSON *json = JSON_NEW_OBJ(); JSON_OBJ_REF_STR(json, "path", request->local_uri); - return send_json_error(conn, 404, - "not_found", - "Not found", - json, NULL); + ret = send_json_error(conn, 404, + "not_found", + "Not found", + json, NULL); } + + // Unlock after API access + unlock_shm(); + + return ret; } From 2ee30679f24f81863482164c3799b5d35e378e11 Mon Sep 17 00:00:00 2001 From: DL6ER Date: Sat, 23 Nov 2019 21:52:10 +0100 Subject: [PATCH 0113/1669] Reduce memory consumption of the FIFO log. Signed-off-by: DL6ER --- src/shmem.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/shmem.c b/src/shmem.c index e2d1baf5d..e825dc6e2 100644 --- a/src/shmem.c +++ b/src/shmem.c @@ -344,9 +344,8 @@ bool init_shmem(void) shm_per_client_regex = create_shm(SHARED_PER_CLIENT_REGEX, size); /****************************** shared fifo_buffer struct ******************************/ - size = get_optimal_object_size(sizeof(fifologData), 1u); // Try to create shared memory object - shm_fifo_log = create_shm(SHARED_FIFO_LOG_NAME, size*sizeof(fifologData)); + shm_fifo_log = create_shm(SHARED_FIFO_LOG_NAME, sizeof(fifologData)); fifo_log = (fifologData*)shm_fifo_log.ptr; return true; From 5d61a0c22eaedc1e11184f2371723bc5193350da Mon Sep 17 00:00:00 2001 From: DL6ER Date: Tue, 26 Nov 2019 11:04:51 +0100 Subject: [PATCH 0114/1669] Add NULL cursor in history result when privacy level is too high. Signed-off-by: DL6ER --- src/api/stats.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/api/stats.c b/src/api/stats.c index 9594b2f81..ac1a605bc 100644 --- a/src/api/stats.c +++ b/src/api/stats.c @@ -552,6 +552,8 @@ int api_stats_history(struct mg_connection *conn) cJSON *item = JSON_NEW_OBJ(); JSON_ARRAY_ADD_ITEM(history, item); JSON_OBJ_ADD_ITEM(json, "history", history); + // There are no more queries available, send NULL cursor + JSON_OBJ_ADD_NULL(json, "cursor"); JSON_SEND_OBJECT(json); } From 8ce1ac146ec05e229738d1560d065c5042dd6d9b Mon Sep 17 00:00:00 2001 From: DL6ER Date: Tue, 26 Nov 2019 11:16:40 +0100 Subject: [PATCH 0115/1669] /api/stats/history: Only use cursor when it is a numeric value, skip if "null". Signed-off-by: DL6ER --- src/api/stats.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/api/stats.c b/src/api/stats.c index ac1a605bc..a9102445d 100644 --- a/src/api/stats.c +++ b/src/api/stats.c @@ -750,11 +750,10 @@ int api_stats_history(struct mg_connection *conn) } } - if(GET_VAR("cursor", buffer, request->query_string) > 0) + unsigned int unum = 0u; + if(GET_VAR("cursor", buffer, request->query_string) > 0 && + sscanf(buffer, "%u", &unum) > 0) { - unsigned int unum = 0u; - sscanf(buffer, "%u", &unum); - // Do not start at the most recent, but at an older query if(unum < (unsigned int)counters->queries) { From 41a1da65cd8294da3808210cfe57829fa3acc525 Mon Sep 17 00:00:00 2001 From: DL6ER Date: Wed, 27 Nov 2019 17:09:14 +0100 Subject: [PATCH 0116/1669] Refresh in-memory index.html on receipt of SIGHUP. This allows replacing the content of /admin without having to restart FTL. Signed-off-by: DL6ER --- src/api/http-common.c | 11 +++++++++++ src/api/http-common.h | 2 ++ src/dnsmasq_interface.c | 5 +++++ 3 files changed, 18 insertions(+) diff --git a/src/api/http-common.c b/src/api/http-common.c index 94334578e..d3b7894d8 100644 --- a/src/api/http-common.c +++ b/src/api/http-common.c @@ -350,6 +350,16 @@ void http_init(void) mg_set_request_handler(ctx, httpsettings.webhome, index_handler, NULL); } +void http_reread_index_html(void) +{ + // Release memory for the index.html file + if(indexfile_content != NULL) + free(indexfile_content); + + // Re-read index.html into memory + prepare_index_html(); +} + void http_terminate(void) { /* Stop the server */ @@ -357,6 +367,7 @@ void http_terminate(void) // Release memory for the index.html file free(indexfile_content); + indexfile_content = NULL; /* Un-initialize the library */ mg_exit_library(); diff --git a/src/api/http-common.h b/src/api/http-common.h index 8f53e5f78..230710536 100644 --- a/src/api/http-common.h +++ b/src/api/http-common.h @@ -36,6 +36,8 @@ int send_json_error(struct mg_connection *conn, const int code, int send_json_success(struct mg_connection *conn, char * additional_headers); +void http_reread_index_html(void); + // Cookie routines bool http_get_cookie_int(struct mg_connection *conn, const char *cookieName, int *i); bool http_get_cookie_str(struct mg_connection *conn, const char *cookieName, char *str, size_t str_size); diff --git a/src/dnsmasq_interface.c b/src/dnsmasq_interface.c index b05eae215..25c3236ef 100644 --- a/src/dnsmasq_interface.c +++ b/src/dnsmasq_interface.c @@ -828,6 +828,11 @@ void FTL_dnsmasq_reload(void) FTL_reload_all_domainlists(); + // Re-read index.html + // This is necessary when the content of /admin is updated as + // the paths of the contained JS/CSS scripts will have changed + http_reread_index_html(); + // Print current set of capabilities if requested via debug flag if(config.debug & DEBUG_CAPS) check_capabilities(); From 76bcef3771f0244808369da210d4326a05286f2b Mon Sep 17 00:00:00 2001 From: DL6ER Date: Fri, 29 Nov 2019 01:10:49 +0100 Subject: [PATCH 0117/1669] Update timestamp of known client when we see them again. Signed-off-by: DL6ER --- src/api/auth.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/api/auth.c b/src/api/auth.c index e66ee80f3..43d2cc7b6 100644 --- a/src/api/auth.c +++ b/src/api/auth.c @@ -69,6 +69,10 @@ int check_client_auth(struct mg_connection *conn) // - The IP matches the one we've seen earlier user_id = num; + // Update timestamp of this client to extend + // the validity of their API authentication + auth_data[num].valid_until = now; + if(config.debug & DEBUG_API) { char timestr[128]; From 8742e140c00de3079a212a4a263ac2e6e15fdc27 Mon Sep 17 00:00:00 2001 From: DL6ER Date: Sat, 30 Nov 2019 10:50:15 +0000 Subject: [PATCH 0118/1669] Update user cookie when session is still running. Signed-off-by: DL6ER --- src/api/auth.c | 39 +++++++++++++++++++++++++---------- src/api/dns.c | 22 ++++++++++---------- src/api/ftl.c | 2 +- src/api/http-common.c | 45 +++++++++++++---------------------------- src/api/http-common.h | 14 +++++-------- src/api/json_macros.h | 7 ++++--- src/api/routes.c | 2 +- src/api/settings.c | 2 +- src/api/stats.c | 20 +++++++++--------- src/civetweb/civetweb.c | 26 +++++++++++++++++------- src/civetweb/civetweb.h | 3 ++- 11 files changed, 96 insertions(+), 86 deletions(-) diff --git a/src/api/auth.c b/src/api/auth.c index 43d2cc7b6..51fc31c86 100644 --- a/src/api/auth.c +++ b/src/api/auth.c @@ -73,6 +73,17 @@ int check_client_auth(struct mg_connection *conn) // the validity of their API authentication auth_data[num].valid_until = now; + // Update user cookie + char *buffer = NULL; + if(asprintf(&buffer, + "Set-Cookie: user_id=%u; Path=/; Max-Age=%u\r\n", + num, API_SESSION_EXPIRE) < 0) + { + return send_json_error(conn, 500, "internal_error", "Internal server error", NULL); + } + my_set_cookie_header(conn, buffer); + free(buffer); + if(config.debug & DEBUG_API) { char timestr[128]; @@ -172,13 +183,15 @@ int api_auth(struct mg_connection *conn) if(config.debug & DEBUG_API) logg("Authentification: OK, localhost does not need auth."); // We still have to send a cookie for the web interface to be happy - char *additional_headers = NULL; - if(asprintf(&additional_headers, + char *buffer = NULL; + if(asprintf(&buffer, "Set-Cookie: user_id=%u; Path=/; Max-Age=%u\r\n", API_MAX_CLIENTS, API_SESSION_EXPIRE) < 0) { - return send_json_error(conn, 500, "internal_error", "Internal server error", NULL, NULL); + return send_json_error(conn, 500, "internal_error", "Internal server error", NULL); } + my_set_cookie_header(conn, buffer); + free(buffer); } if(user_id > -1 && method == HTTP_GET) { @@ -188,15 +201,17 @@ int api_auth(struct mg_connection *conn) cJSON *json = JSON_NEW_OBJ(); JSON_OBJ_REF_STR(json, "status", "success"); // Ten minutes validity - char *additional_headers = NULL; - if(asprintf(&additional_headers, + char *buffer = NULL; + if(asprintf(&buffer, "Set-Cookie: user_id=%u; Path=/; Max-Age=%u\r\n", user_id, API_SESSION_EXPIRE) < 0) { - return send_json_error(conn, 500, "internal_error", "Internal server error", NULL, NULL); + return send_json_error(conn, 500, "internal_error", "Internal server error", NULL); } + my_set_cookie_header(conn, buffer); + free(buffer); - return send_json_success(conn, additional_headers); + return send_json_success(conn); } else if(user_id > -1 && method == HTTP_DELETE) { @@ -209,13 +224,15 @@ int api_auth(struct mg_connection *conn) free(auth_data[user_id].remote_addr); auth_data[user_id].remote_addr = NULL; - char *additional_headers = strdup("Set-Cookie: user_id=deleted; Path=/; Max-Age=-1\r\n"); - return send_json_success(conn, additional_headers); + const char *buffer = "Set-Cookie: user_id=deleted; Path=/; Max-Age=-1\r\n"; + my_set_cookie_header(conn, buffer); + return send_json_success(conn); } else { - char *additional_headers = strdup("Set-Cookie: user_id=deleted; Path=/; Max-Age=-1\r\n"); - return send_json_unauthorized(conn, additional_headers); + const char *buffer = "Set-Cookie: user_id=deleted; Path=/; Max-Age=-1\r\n"; + my_set_cookie_header(conn, buffer); + return send_json_unauthorized(conn); } } diff --git a/src/api/dns.c b/src/api/dns.c index 566fc4f29..210a338ed 100644 --- a/src/api/dns.c +++ b/src/api/dns.c @@ -39,7 +39,7 @@ int api_dns_status(struct mg_connection *conn) // Verify requesting client is allowed to access this ressource if(check_client_auth(conn) < 0) { - return send_json_unauthorized(conn, NULL); + return send_json_unauthorized(conn); } char buffer[1024]; @@ -47,7 +47,7 @@ int api_dns_status(struct mg_connection *conn) if ((data_len < 1) || (data_len >= (int)sizeof(buffer))) { return send_json_error(conn, 400, "bad_request", "No request body data", - NULL, NULL); + NULL); } buffer[data_len] = '\0'; @@ -56,7 +56,7 @@ int api_dns_status(struct mg_connection *conn) return send_json_error(conn, 400, "bad_request", "Invalid request body data", - NULL, NULL); + NULL); } cJSON *elem1 = cJSON_GetObjectItemCaseSensitive(obj, "action"); @@ -65,7 +65,7 @@ int api_dns_status(struct mg_connection *conn) return send_json_error(conn, 400, "bad_request", "No \"action\" string in body data", - NULL, NULL); + NULL); } const char *action = elem1->valuestring; @@ -101,7 +101,7 @@ int api_dns_status(struct mg_connection *conn) return send_json_error(conn, 400, "bad_request", "Invalid \"action\" requested", - NULL, NULL); + NULL); } JSON_SEND_OBJECT(json); } @@ -149,7 +149,7 @@ static int api_dns_somelist_POST(struct mg_connection *conn, if ((data_len < 1) || (data_len >= (int)sizeof(buffer))) { return send_json_error(conn, 400, "bad_request", "No request body data", - NULL, NULL); + NULL); } buffer[data_len] = '\0'; @@ -158,7 +158,7 @@ static int api_dns_somelist_POST(struct mg_connection *conn, return send_json_error(conn, 400, "bad_request", "Invalid request body data", - NULL, NULL); + NULL); } cJSON *elem = cJSON_GetObjectItemCaseSensitive(obj, "domain"); @@ -168,7 +168,7 @@ static int api_dns_somelist_POST(struct mg_connection *conn, return send_json_error(conn, 400, "bad_request", "No \"domain\" string in body data", - NULL, NULL); + NULL); } const char *domain = elem->valuestring; @@ -199,7 +199,7 @@ static int api_dns_somelist_POST(struct mg_connection *conn, return send_json_error(conn, 500, "database_error", "Could not add domain to database table", - json, NULL); + json); } } @@ -240,7 +240,7 @@ static int api_dns_somelist_DELETE(struct mg_connection *conn, return send_json_error(conn, 500, "database_error", "Could not remove domain from database table", - json, NULL); + json); } } @@ -249,7 +249,7 @@ int api_dns_somelist(struct mg_connection *conn, bool exact, bool whitelist) // Verify requesting client is allowed to see this ressource if(check_client_auth(conn) < 0) { - return send_json_unauthorized(conn, NULL); + return send_json_unauthorized(conn); } int method = http_method(conn); diff --git a/src/api/ftl.c b/src/api/ftl.c index 24d4752fa..f40a9173f 100644 --- a/src/api/ftl.c +++ b/src/api/ftl.c @@ -37,7 +37,7 @@ int api_ftl_dnsmasq_log(struct mg_connection *conn) // Verify requesting client is allowed to see this ressource if(check_client_auth(conn) < 0) { - return send_json_unauthorized(conn, NULL); + return send_json_unauthorized(conn); } unsigned int start = 0u; diff --git a/src/api/http-common.c b/src/api/http-common.c index d3b7894d8..c6b834e6a 100644 --- a/src/api/http-common.c +++ b/src/api/http-common.c @@ -20,35 +20,36 @@ static struct mg_context *ctx = NULL; int send_http(struct mg_connection *conn, const char *mime_type, - const char *additional_headers, const char *msg) + const char *msg) { - mg_send_http_ok(conn, mime_type, additional_headers, strlen(msg)); + mg_send_http_ok(conn, mime_type, NULL, strlen(msg)); return mg_write(conn, msg, strlen(msg)); } int send_http_code(struct mg_connection *conn, const char *mime_type, - const char *additional_headers, int code, const char *msg) + int code, const char *msg) { // Payload will be sent with text/plain encoding due to // the first line being "Error " by definition //return mg_send_http_error(conn, code, "%s", msg); - my_send_http_error_headers(conn, code, mime_type, - additional_headers, strlen(msg)); + my_send_http_error_headers(conn, code, + mime_type, + strlen(msg)); + return mg_write(conn, msg, strlen(msg)); } -int send_json_unauthorized(struct mg_connection *conn, - char *additional_headers) +int send_json_unauthorized(struct mg_connection *conn) { return send_json_error(conn, 401, "unauthorized", "Unauthorized", - NULL, additional_headers); + NULL); } int send_json_error(struct mg_connection *conn, const int code, const char *key, const char* message, - cJSON *data, char *additional_headers) + cJSON *data) { cJSON *json = JSON_NEW_OBJ(); cJSON *error = JSON_NEW_OBJ(); @@ -67,32 +68,14 @@ int send_json_error(struct mg_connection *conn, const int code, JSON_OBJ_ADD_ITEM(json, "error", error); - // Send additional headers if supplied - if(additional_headers == NULL) - { - JSON_SEND_OBJECT_CODE(json, code); - } - else - { - JSON_SEND_OBJECT_AND_HEADERS_CODE(json, code, additional_headers); - } + JSON_SEND_OBJECT_CODE(json, code); } -int send_json_success(struct mg_connection *conn, - char * additional_headers) +int send_json_success(struct mg_connection *conn) { cJSON *json = JSON_NEW_OBJ(); JSON_OBJ_REF_STR(json, "status", "success"); - - // Send additional headers if supplied - if(additional_headers == NULL) - { - JSON_SEND_OBJECT(json); - } - else - { - JSON_SEND_OBJECT_AND_HEADERS(json, additional_headers); - } + JSON_SEND_OBJECT(json); } int send_http_internal_error(struct mg_connection *conn) @@ -148,7 +131,7 @@ bool __attribute__((pure)) startsWith(const char *path, const char *uri) // Print passed string directly static int print_simple(struct mg_connection *conn, void *input) { - return send_http(conn, "text/plain", NULL, input); + return send_http(conn, "text/plain", input); } static char *indexfile_content = NULL; diff --git a/src/api/http-common.h b/src/api/http-common.h index 230710536..24ca96c19 100644 --- a/src/api/http-common.h +++ b/src/api/http-common.h @@ -23,18 +23,14 @@ void http_init(void); void http_terminate(void); -int send_http(struct mg_connection *conn, const char *mime_type, - const char *additional_headers, const char *msg); -int send_http_code(struct mg_connection *conn, const char *mime_type, - const char *additional_headers, int code, const char *msg); +int send_http(struct mg_connection *conn, const char *mime_type, const char *msg); +int send_http_code(struct mg_connection *conn, const char *mime_type, int code, const char *msg); int send_http_internal_error(struct mg_connection *conn); -int send_json_unauthorized(struct mg_connection *conn, - char *additional_headers); +int send_json_unauthorized(struct mg_connection *conn); int send_json_error(struct mg_connection *conn, const int code, const char *key, const char* message, - cJSON *data, char *additional_headers); -int send_json_success(struct mg_connection *conn, - char * additional_headers); + cJSON *data); +int send_json_success(struct mg_connection *conn); void http_reread_index_html(void); diff --git a/src/api/json_macros.h b/src/api/json_macros.h index 951b22d95..604f32043 100644 --- a/src/api/json_macros.h +++ b/src/api/json_macros.h @@ -129,7 +129,7 @@ send_http_internal_error(conn); \ return 500; \ } \ - send_http(conn, "application/json; charset=utf-8", NULL, msg); \ + send_http(conn, "application/json; charset=utf-8", msg); \ cJSON_Delete(object); \ return 200; \ } @@ -142,11 +142,11 @@ send_http_internal_error(conn); \ return 500; \ } \ - send_http_code(conn, "application/json; charset=utf-8", NULL, code, msg); \ + send_http_code(conn, "application/json; charset=utf-8", code, msg); \ cJSON_Delete(object); \ return 200; \ } - +/* #define JSON_SEND_OBJECT_AND_HEADERS(object, additional_headers){ \ const char* msg = JSON_FORMATTER(object); \ if(msg == NULL) \ @@ -174,3 +174,4 @@ free(additional_headers); \ return code; \ } +*/ \ No newline at end of file diff --git a/src/api/routes.c b/src/api/routes.c index 2a1f04177..24c6bfd01 100644 --- a/src/api/routes.c +++ b/src/api/routes.c @@ -130,7 +130,7 @@ int api_handler(struct mg_connection *conn, void *ignored) ret = send_json_error(conn, 404, "not_found", "Not found", - json, NULL); + json); } // Unlock after API access diff --git a/src/api/settings.c b/src/api/settings.c index f6f1aee44..a102c3ab9 100644 --- a/src/api/settings.c +++ b/src/api/settings.c @@ -32,7 +32,7 @@ int api_settings_ftldb(struct mg_connection *conn) // Verify requesting client is allowed to see this ressource if(check_client_auth(conn) < 0) { - send_json_unauthorized(conn, NULL); + send_json_unauthorized(conn); } cJSON *json = JSON_NEW_OBJ(); diff --git a/src/api/stats.c b/src/api/stats.c index a9102445d..76282b961 100644 --- a/src/api/stats.c +++ b/src/api/stats.c @@ -169,7 +169,7 @@ int api_stats_top_domains(bool blocked, struct mg_connection *conn) // Verify requesting client is allowed to see this ressource if(check_client_auth(conn) < 0) { - return send_json_unauthorized(conn, NULL); + return send_json_unauthorized(conn); } // Exit before processing any data if requested via config setting @@ -326,7 +326,7 @@ int api_stats_top_clients(bool blocked, struct mg_connection *conn) // Verify requesting client is allowed to see this ressource if(check_client_auth(conn) < 0) { - return send_json_unauthorized(conn, NULL); + return send_json_unauthorized(conn); } // Exit before processing any data if requested via config setting @@ -448,7 +448,7 @@ int api_stats_upstreams(struct mg_connection *conn) // Verify requesting client is allowed to see this ressource if(check_client_auth(conn) < 0) { - return send_json_unauthorized(conn, NULL); + return send_json_unauthorized(conn); } for(int upstreamID = 0; upstreamID < counters->forwarded; upstreamID++) { @@ -560,7 +560,7 @@ int api_stats_history(struct mg_connection *conn) // Verify requesting client is allowed to see this ressource if(check_client_auth(conn) < 0) { - return send_json_unauthorized(conn, NULL); + return send_json_unauthorized(conn); } // Do we want a more specific version of this command (domain/client/time interval filtered)? @@ -658,7 +658,7 @@ int api_stats_history(struct mg_connection *conn) return send_json_error(conn, 400, "bad_request", "Requested upstream not found", - json, NULL); + json); } } } @@ -701,7 +701,7 @@ int api_stats_history(struct mg_connection *conn) return send_json_error(conn, 400, "bad_request", "Requested domain not found", - json, NULL); + json); } } @@ -746,7 +746,7 @@ int api_stats_history(struct mg_connection *conn) return send_json_error(conn, 400, "bad_request", "Requested client not found", - json, NULL); + json); } } @@ -771,7 +771,7 @@ int api_stats_history(struct mg_connection *conn) return send_json_error(conn, 400, "bad_request", "Requested cursor larger than number of queries", - json, NULL); + json); } } @@ -938,7 +938,7 @@ int api_stats_recentblocked(struct mg_connection *conn) // Verify requesting client is allowed to see this ressource if(check_client_auth(conn) < 0) { - return send_json_unauthorized(conn, NULL); + return send_json_unauthorized(conn); } // Exit before processing any data if requested via config setting @@ -1011,7 +1011,7 @@ int api_stats_overTime_clients(struct mg_connection *conn) // Verify requesting client is allowed to see this ressource if(check_client_auth(conn) < 0) { - return send_json_unauthorized(conn, NULL); + return send_json_unauthorized(conn); } // Exit before processing any data if requested via config setting diff --git a/src/civetweb/civetweb.c b/src/civetweb/civetweb.c index 165e29ca9..de82cf528 100644 --- a/src/civetweb/civetweb.c +++ b/src/civetweb/civetweb.c @@ -2812,6 +2812,8 @@ struct mg_connection { void *tls_user_ptr; /* User defined pointer in thread local storage, * for quick access */ + + char *cookie_header; // <---- Pi-hole modification }; @@ -4390,6 +4392,16 @@ send_additional_header(struct mg_connection *conn) } #endif + /**************** Pi-hole modification ****************/ + if(conn->cookie_header != NULL && + conn->cookie_header[0]) + { + i += mg_printf(conn, "%s", conn->cookie_header); + mg_free(conn->cookie_header); + conn->cookie_header = NULL; + } + /******************************************************/ + if (header && header[0]) { i += mg_printf(conn, "%s\r\n", header); } @@ -4788,7 +4800,6 @@ mg_send_http_error_impl(struct mg_connection *conn, /************************************** Pi-hole method **************************************/ void my_send_http_error_headers(struct mg_connection *conn, int status, const char* mime_type, - const char *additional_headers, long long content_length) { /* Set status (for log) */ @@ -4809,15 +4820,15 @@ void my_send_http_error_headers(struct mg_connection *conn, mime_type, date); - // We may use additional headers to set or clear cookies - if(additional_headers != NULL && strlen(additional_headers) > 0) - { - mg_write(conn, additional_headers, strlen(additional_headers)); - } - mg_printf(conn, "Content-Length: %" UINT64_FMT "\r\n\r\n", (uint64_t)content_length); } + +void my_set_cookie_header(struct mg_connection *conn, + const char *cookie_header) +{ + conn->cookie_header = mg_strdup(cookie_header); +} /********************************************************************************************/ int @@ -17994,6 +18005,7 @@ worker_thread_run(struct mg_connection *conn) #if defined(USE_SERVER_STATS) conn->conn_state = 1; /* not consumed */ #endif + conn->cookie_header = NULL; // <--- Pi-hole modification /* Call consume_socket() even when ctx->stop_flag > 0, to let it * signal sq_empty condvar to wake up the master waiting in diff --git a/src/civetweb/civetweb.h b/src/civetweb/civetweb.h index b0414678e..52131efc6 100644 --- a/src/civetweb/civetweb.h +++ b/src/civetweb/civetweb.h @@ -935,8 +935,9 @@ CIVETWEB_API int mg_send_http_error(struct mg_connection *conn, /************************************** Pi-hole method **************************************/ void my_send_http_error_headers(struct mg_connection *conn, int status, const char* mime_type, - const char *additional_headers, +// const char *additional_headers, long long content_length); +void my_set_cookie_header(struct mg_connection *conn, const char *cookie_header); /********************************************************************************************/ From c389f06944f6ca28092849bb89dc81164d4da418 Mon Sep 17 00:00:00 2001 From: DL6ER Date: Sat, 30 Nov 2019 11:01:22 +0000 Subject: [PATCH 0119/1669] Make HTTP/API session timeout configurable through pihole-FTL.conf Signed-off-by: DL6ER --- src/api/auth.c | 6 +++--- src/config.c | 13 +++++++++++++ src/config.h | 1 + 3 files changed, 17 insertions(+), 3 deletions(-) diff --git a/src/api/auth.c b/src/api/auth.c index 51fc31c86..c56100683 100644 --- a/src/api/auth.c +++ b/src/api/auth.c @@ -71,13 +71,13 @@ int check_client_auth(struct mg_connection *conn) // Update timestamp of this client to extend // the validity of their API authentication - auth_data[num].valid_until = now; + auth_data[num].valid_until = now + httpsettings.session_timeout; // Update user cookie char *buffer = NULL; if(asprintf(&buffer, "Set-Cookie: user_id=%u; Path=/; Max-Age=%u\r\n", - num, API_SESSION_EXPIRE) < 0) + num, httpsettings.session_timeout) < 0) { return send_json_error(conn, 500, "internal_error", "Internal server error", NULL); } @@ -141,7 +141,7 @@ int api_auth(struct mg_connection *conn) { // Found an unused slot auth_data[i].used = true; - auth_data[i].valid_until = time(NULL) + API_SESSION_EXPIRE; + auth_data[i].valid_until = time(NULL) + httpsettings.session_timeout; auth_data[i].remote_addr = strdup(request->remote_addr); user_id = i; diff --git a/src/config.c b/src/config.c index c80146853..a838597fe 100644 --- a/src/config.c +++ b/src/config.c @@ -422,6 +422,19 @@ void read_FTLconf(void) else logg(" API_AUTH_FOR_LOCALHOST: Inactive"); + // HTTP_SESSION_TIMEOUT + // How long should a session be considered valid after login? + // defaults to: 300 seconds + httpsettings.session_timeout = 300; + buffer = parse_FTLconf(fp, "WEBPORT"); + + value = 0; + if(buffer != NULL && sscanf(buffer, "%i", &value) && value > 0) + { + httpsettings.session_timeout = value; + } + logg(" HTTP_SESSION_TIMEOUT: %u seconds", httpsettings.session_timeout); + // Read DEBUG_... setting from pihole-FTL.conf // This option should be the last one as it causes // some rather verbose output into the log when diff --git a/src/config.h b/src/config.h index 45a8b1b48..7daed55ea 100644 --- a/src/config.h +++ b/src/config.h @@ -59,6 +59,7 @@ typedef struct httpsettings { const char *acl; bool api_auth_for_localhost; char port[20]; // enough space for 2*(maximum length of number in a uint16_t = 5 characters) + ",[::]:" + NULL + unsigned int session_timeout; } httpsettingsStruct; extern ConfigStruct config; From c38285461f1ccc461c3c61a5d085fb691c9ab8cd Mon Sep 17 00:00:00 2001 From: DL6ER Date: Sat, 30 Nov 2019 11:17:25 +0000 Subject: [PATCH 0120/1669] Automatically log in users when there is no (or an empty) password set in setupVars.conf Signed-off-by: DL6ER --- src/api/auth.c | 49 ++++++++++++++++++++++++++++++------------------- src/config.c | 2 +- 2 files changed, 31 insertions(+), 20 deletions(-) diff --git a/src/api/auth.c b/src/api/auth.c index c56100683..de5cc337c 100644 --- a/src/api/auth.c +++ b/src/api/auth.c @@ -56,7 +56,7 @@ int check_client_auth(struct mg_connection *conn) if(http_get_cookie_int(conn, "user_id", &num) && num > -1 && num < API_MAX_CLIENTS) { if(config.debug & DEBUG_API) - logg("Read user_id=%i from user-provided cookie", num); + logg("API: Read user_id=%i from user-provided cookie", num); time_t now = time(NULL); if(auth_data[num].used && @@ -88,15 +88,15 @@ int check_client_auth(struct mg_connection *conn) { char timestr[128]; get_timestr(timestr, auth_data[user_id].valid_until); - logg("Recognized known user: user_id %i valid_until: %s remote_addr %s", + logg("API: Recognized known user: user_id %i valid_until: %s remote_addr %s", user_id, timestr, auth_data[user_id].remote_addr); } } else if(config.debug & DEBUG_API) - logg("Authentification: FAIL (cookie invalid/expired)"); + logg("API Authentification: FAIL (cookie invalid/expired)"); } else if(config.debug & DEBUG_API) - logg("Authentification: FAIL (no cookie provided)"); + logg("API Authentification: FAIL (no cookie provided)"); return user_id; } @@ -107,11 +107,10 @@ static __attribute__((malloc)) char *get_password_hash(void) const char* password = read_setupVarsconf("WEBPASSWORD"); // If the value was not set (or we couldn't open the file for reading), - // substitute password with the hash for an empty string (= no password). + // we hand an empty string back to the caller if(password == NULL || (password != NULL && strlen(password) == 0u)) { - // This is the empty password hash - password = "cd372fb85148700fa88095e3492d3f9f5beb43e555e5ff26d95f5a6adc36f8e6"; + password = ""; } char *hash = strdup(password); @@ -125,14 +124,17 @@ static __attribute__((malloc)) char *get_password_hash(void) int api_auth(struct mg_connection *conn) { int user_id = -1; + char *password_hash = get_password_hash(); const struct mg_request_info *request = mg_get_request_info(conn); - // Does the client try to authenticate through a set header? + // Does the client try to authenticate through a set header or is there no password on this machine? const char *xHeader = mg_get_header(conn, "X-Pi-hole-Authenticate"); - if(xHeader != NULL && strlen(xHeader) > 0) + const bool header_set = (xHeader != NULL && strlen(xHeader) > 0); + const bool empty_password = (strlen(password_hash) == 0u); + if(header_set || empty_password ) { - char *password_hash = get_password_hash(); - if(strcmp(xHeader, password_hash) == 0) + const bool hash_match = (strcmp(xHeader, password_hash) == 0); + if(hash_match || empty_password) { // Accepted for(unsigned int i = 0; i < API_MAX_CLIENTS; i++) @@ -151,27 +153,36 @@ int api_auth(struct mg_connection *conn) if(config.debug & DEBUG_API) { - logg("Received X-Pi-hole-Authenticate: %s", xHeader); + if(header_set) + { + logg("API: Received X-Pi-hole-Authenticate: %s", xHeader); + } + else if(strlen(password_hash) == 0u) + { + logg("API: No password required on this machine"); + } + if(user_id > -1) { char timestr[128]; get_timestr(timestr, auth_data[user_id].valid_until); - logg("Registered new user: user_id %i valid_until: %s remote_addr %s", + logg("API: Registered new user: user_id %i valid_until: %s remote_addr %s", user_id, timestr, auth_data[user_id].remote_addr); } } if(user_id == -1) { - logg("WARNING: No free slots available, not authenticating user"); + logg("WARNING: No free API slots available, not authenticating user"); } } else if(config.debug & DEBUG_API) { - logg("Password mismatch. User=%s, setupVars=%s", xHeader, password_hash); + logg("API: Password mismatch. User=%s, setupVars=%s", xHeader, password_hash); } - free(password_hash); } + free(password_hash); + password_hash = NULL; // Did the client authenticate before and we can validate this? if(user_id < 0) @@ -181,7 +192,7 @@ int api_auth(struct mg_connection *conn) if(user_id == API_MAX_CLIENTS) { if(config.debug & DEBUG_API) - logg("Authentification: OK, localhost does not need auth."); + logg("API Authentification: OK, localhost does not need auth."); // We still have to send a cookie for the web interface to be happy char *buffer = NULL; if(asprintf(&buffer, @@ -196,7 +207,7 @@ int api_auth(struct mg_connection *conn) if(user_id > -1 && method == HTTP_GET) { if(config.debug & DEBUG_API) - logg("Authentification: OK, registered new client"); + logg("API Authentification: OK, registered new client"); cJSON *json = JSON_NEW_OBJ(); JSON_OBJ_REF_STR(json, "status", "success"); @@ -216,7 +227,7 @@ int api_auth(struct mg_connection *conn) else if(user_id > -1 && method == HTTP_DELETE) { if(config.debug & DEBUG_API) - logg("Authentification: OK, requested to revoke"); + logg("API Authentification: OK, requested to revoke"); // Revoke client authentication. This slot can be used by a new client, afterwards. auth_data[user_id].used = false; diff --git a/src/config.c b/src/config.c index a838597fe..f6964dc5f 100644 --- a/src/config.c +++ b/src/config.c @@ -426,7 +426,7 @@ void read_FTLconf(void) // How long should a session be considered valid after login? // defaults to: 300 seconds httpsettings.session_timeout = 300; - buffer = parse_FTLconf(fp, "WEBPORT"); + buffer = parse_FTLconf(fp, "HTTP_SESSION_TIMEOUT"); value = 0; if(buffer != NULL && sscanf(buffer, "%i", &value) && value > 0) From c639cf9b6d137ae0bb1574aa5f72b0e3284aa9d5 Mon Sep 17 00:00:00 2001 From: DL6ER Date: Sat, 30 Nov 2019 15:06:55 +0000 Subject: [PATCH 0121/1669] Actively free expired user sessions to make room for new ones. Signed-off-by: DL6ER --- src/api/auth.c | 43 +++++++++++++++++++++++-------------------- 1 file changed, 23 insertions(+), 20 deletions(-) diff --git a/src/api/auth.c b/src/api/auth.c index de5cc337c..138720c4a 100644 --- a/src/api/auth.c +++ b/src/api/auth.c @@ -137,13 +137,28 @@ int api_auth(struct mg_connection *conn) if(hash_match || empty_password) { // Accepted + time_t now = time(NULL); for(unsigned int i = 0; i < API_MAX_CLIENTS; i++) { + // Expired slow, mark as unused + if(auth_data[i].valid_until < now) + { + if(config.debug & DEBUG_API) + { + logg("API: Session of client %u (%s) expired, freeing...", + i, auth_data[i].remote_addr); + } + auth_data[i].used = false; + auth_data[i].valid_until = 0; + free(auth_data[i].remote_addr); + auth_data[i].remote_addr = NULL; + } + + // Found unused authentication slot (might have been freed before) if(!auth_data[i].used) { - // Found an unused slot auth_data[i].used = true; - auth_data[i].valid_until = time(NULL) + httpsettings.session_timeout; + auth_data[i].valid_until = now + httpsettings.session_timeout; auth_data[i].remote_addr = strdup(request->remote_addr); user_id = i; @@ -151,24 +166,12 @@ int api_auth(struct mg_connection *conn) } } - if(config.debug & DEBUG_API) + if(config.debug & DEBUG_API && user_id > -1) { - if(header_set) - { - logg("API: Received X-Pi-hole-Authenticate: %s", xHeader); - } - else if(strlen(password_hash) == 0u) - { - logg("API: No password required on this machine"); - } - - if(user_id > -1) - { - char timestr[128]; - get_timestr(timestr, auth_data[user_id].valid_until); - logg("API: Registered new user: user_id %i valid_until: %s remote_addr %s", - user_id, timestr, auth_data[user_id].remote_addr); - } + char timestr[128]; + get_timestr(timestr, auth_data[user_id].valid_until); + logg("API: Registered new user: user_id %i valid_until: %s remote_addr %s", + user_id, timestr, auth_data[user_id].remote_addr); } if(user_id == -1) { @@ -231,7 +234,7 @@ int api_auth(struct mg_connection *conn) // Revoke client authentication. This slot can be used by a new client, afterwards. auth_data[user_id].used = false; - auth_data[user_id].valid_until = time(NULL); + auth_data[user_id].valid_until = 0; free(auth_data[user_id].remote_addr); auth_data[user_id].remote_addr = NULL; From 5d03136e39e409d8c08c58fb8d8b72c6ca94ccc9 Mon Sep 17 00:00:00 2001 From: DL6ER Date: Sun, 1 Dec 2019 12:14:24 +0000 Subject: [PATCH 0122/1669] Send 200 queries for the query log at once. With this, the clients will always have some pages of the table in their cache. Signed-off-by: DL6ER --- src/api/stats.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/api/stats.c b/src/api/stats.c index 76282b961..5d5178ade 100644 --- a/src/api/stats.c +++ b/src/api/stats.c @@ -583,7 +583,7 @@ int api_stats_history(struct mg_connection *conn) // We start with the most recent query at the beginning (until the cursor is changed) unsigned int cursor = counters->queries; // We send 200 queries (until the API is asked for a different limit) - unsigned int show = 20u; + unsigned int show = 200u; const struct mg_request_info *request = mg_get_request_info(conn); if(request->query_string != NULL) From 5a276fe1ee625cb5514c74b3d45f8cce1a6a8d90 Mon Sep 17 00:00:00 2001 From: DL6ER Date: Mon, 2 Dec 2019 15:21:49 +0000 Subject: [PATCH 0123/1669] Ensure shared memory is locked when reloading the DNS cache. Signed-off-by: DL6ER --- src/dnsmasq_interface.c | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/dnsmasq_interface.c b/src/dnsmasq_interface.c index 25c3236ef..a9145b0cc 100644 --- a/src/dnsmasq_interface.c +++ b/src/dnsmasq_interface.c @@ -809,6 +809,7 @@ void FTL_dnsmasq_reload(void) // This is the only hook that is not skipped in PRIVACY_NOSTATS mode logg("Reloading DNS cache"); + lock_shm(); // Reload the privacy level in case the user changed it get_privacy_level(NULL); @@ -828,14 +829,11 @@ void FTL_dnsmasq_reload(void) FTL_reload_all_domainlists(); - // Re-read index.html - // This is necessary when the content of /admin is updated as - // the paths of the contained JS/CSS scripts will have changed - http_reread_index_html(); - // Print current set of capabilities if requested via debug flag if(config.debug & DEBUG_CAPS) check_capabilities(); + + unlock_shm(); } void _FTL_reply(const unsigned short flags, const char *name, const union all_addr *addr, const int id, From 5385098dc8d65ff79d6527ac79120ea4cce20be9 Mon Sep 17 00:00:00 2001 From: DL6ER Date: Mon, 2 Dec 2019 15:22:15 +0000 Subject: [PATCH 0124/1669] Reread index.html on receipt of real-time signal 1. Signed-off-by: DL6ER --- src/signals.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/signals.c b/src/signals.c index 930af2018..4819c53b8 100644 --- a/src/signals.c +++ b/src/signals.c @@ -22,6 +22,8 @@ #include "config.h" #define BINARY_NAME "pihole-FTL" +// http_reread_index_html() +#include "api/http-common.h" volatile sig_atomic_t killed = 0; static time_t FTLstarttime = 0; @@ -152,6 +154,13 @@ static void SIGRT_handler(int signum, siginfo_t *si, void *unused) // Reload the privacy level in case the user changed it get_privacy_level(NULL); } + else if(rtsig == 1) + { + // Re-read index.html + // This is necessary when the content of /admin is updated as + // the paths of the contained JS/CSS scripts will have changed + http_reread_index_html(); + } } void handle_signals(void) From 257fcbf69508d65ba629d4bcc27f30dbac5fc35f Mon Sep 17 00:00:00 2001 From: DL6ER Date: Mon, 2 Dec 2019 17:39:00 +0000 Subject: [PATCH 0125/1669] Implement transition from individual domain lists into a unified domainlist (see core PR #3015). Signed-off-by: DL6ER --- src/api/dns.c | 20 ++++++------- src/database/gravity-db.c | 60 +++++++++++++++++++++------------------ src/database/gravity-db.h | 8 ++++-- 3 files changed, 49 insertions(+), 39 deletions(-) diff --git a/src/api/dns.c b/src/api/dns.c index 210a338ed..c94efde4d 100644 --- a/src/api/dns.c +++ b/src/api/dns.c @@ -172,17 +172,17 @@ static int api_dns_somelist_POST(struct mg_connection *conn, } const char *domain = elem->valuestring; - const char *table; + int table; if(whitelist) if(store_exact) - table = "whitelist"; + table = GRAVITY_DOMAINLIST_EXACT_WHITELIST; else - table = "regex_whitelist"; + table = GRAVITY_DOMAINLIST_REGEX_WHITELIST; else if(store_exact) - table = "blacklist"; + table = GRAVITY_DOMAINLIST_EXACT_BLACKLIST; else - table = "regex_blacklist"; + table = GRAVITY_DOMAINLIST_REGEX_BLACKLIST; cJSON *json = JSON_NEW_OBJ(); if(gravityDB_addToTable(table, domain)) @@ -215,17 +215,17 @@ static int api_dns_somelist_DELETE(struct mg_connection *conn, // Decode URL (necessar for regular expressions, harmless for domains) mg_url_decode(encoded_uri, strlen(encoded_uri), domain, sizeof(domain)-1u, 0); - const char *table; + int table; if(whitelist) if(store_exact) - table = "whitelist"; + table = GRAVITY_DOMAINLIST_EXACT_WHITELIST; else - table = "regex_whitelist"; + table = GRAVITY_DOMAINLIST_REGEX_WHITELIST; else if(store_exact) - table = "blacklist"; + table = GRAVITY_DOMAINLIST_EXACT_BLACKLIST; else - table = "regex_blacklist"; + table = GRAVITY_DOMAINLIST_REGEX_BLACKLIST; cJSON *json = JSON_NEW_OBJ(); if(gravityDB_delFromTable(table, domain)) diff --git a/src/database/gravity-db.c b/src/database/gravity-db.c index 3c6a360e0..a6d43640e 100644 --- a/src/database/gravity-db.c +++ b/src/database/gravity-db.c @@ -940,30 +940,33 @@ bool gravityDB_get_regex_client_groups(clientsData* client, const int numregex, return true; } -bool gravityDB_addToTable(const char *table, const char* domain) +bool gravityDB_addToTable(const int type, const char* domain) { - char *querystr = NULL; - // Build query string - if(asprintf(&querystr, "INSERT INTO %s (domain) VALUES (?);", table) < 30) - { - logg("gravityDB_addToTable(%s, %s) - asprintf() error", table, domain); - return false; - } - // Prepare SQLite statement sqlite3_stmt* stmt = NULL; + const char *querystr = "INSERT INTO domainlist (domain,type) VALUES (?,?);"; int rc = sqlite3_prepare_v2(gravity_db, querystr, -1, &stmt, NULL); if( rc != SQLITE_OK ){ - logg("gravityDB_addToTable(%s, %s) - SQL error prepare (%i): %s", - table, domain, rc, sqlite3_errmsg(gravity_db)); + logg("gravityDB_addToTable(%d, %s) - SQL error prepare (%i): %s", + type, domain, rc, sqlite3_errmsg(gravity_db)); return false; } - // Bind domain to prepared statement + // Bind domain string to prepared statement if((rc = sqlite3_bind_text(stmt, 1, domain, -1, SQLITE_STATIC)) != SQLITE_OK) { - logg("gravityDB_addToTable(%s, %s): Failed to bind domain (error %d) - %s", - table, domain, rc, sqlite3_errmsg(gravity_db)); + logg("gravityDB_addToTable(%d, %s): Failed to bind domain (error %d) - %s", + type, domain, rc, sqlite3_errmsg(gravity_db)); + sqlite3_reset(stmt); + sqlite3_finalize(stmt); + return false; + } + + // Bind domain type to prepared statement + if((rc = sqlite3_bind_int(stmt, 2, type)) != SQLITE_OK) + { + logg("gravityDB_addToTable(%d, %s): Failed to bind domain (error %d) - %s", + type, domain, rc, sqlite3_errmsg(gravity_db)); sqlite3_reset(stmt); sqlite3_finalize(stmt); return false; @@ -983,30 +986,33 @@ bool gravityDB_addToTable(const char *table, const char* domain) return okay; } -bool gravityDB_delFromTable(const char *table, const char* domain) +bool gravityDB_delFromTable(const int type, const char* domain) { - char *querystr = NULL; - // Build query string - if(asprintf(&querystr, "DELETE FROM %s WHERE domain = ?;", table) < 30) - { - logg("gravityDB_delFromTable(%s, %s) - asprintf() error", table, domain); - return false; - } - // Prepare SQLite statement sqlite3_stmt* stmt = NULL; + const char *querystr = "DELETE FROM domainlist WHERE domain = ? AND type = ?;"; int rc = sqlite3_prepare_v2(gravity_db, querystr, -1, &stmt, NULL); if( rc != SQLITE_OK ){ - logg("gravityDB_delFromTable(%s, %s) - SQL error prepare (%i): %s", - table, domain, rc, sqlite3_errmsg(gravity_db)); + logg("gravityDB_delFromTable(%d, %s) - SQL error prepare (%i): %s", + type, domain, rc, sqlite3_errmsg(gravity_db)); return false; } // Bind domain to prepared statement if((rc = sqlite3_bind_text(stmt, 1, domain, -1, SQLITE_STATIC)) != SQLITE_OK) { - logg("gravityDB_delFromTable(%s, %s): Failed to bind domain (error %d) - %s", - table, domain, rc, sqlite3_errmsg(gravity_db)); + logg("gravityDB_delFromTable(%d, %s): Failed to bind domain (error %d) - %s", + type, domain, rc, sqlite3_errmsg(gravity_db)); + sqlite3_reset(stmt); + sqlite3_finalize(stmt); + return false; + } + + // Bind domain type to prepared statement + if((rc = sqlite3_bind_int(stmt, 2, type)) != SQLITE_OK) + { + logg("gravityDB_delFromTable(%d, %s): Failed to bind domain (error %d) - %s", + type, domain, rc, sqlite3_errmsg(gravity_db)); sqlite3_reset(stmt); sqlite3_finalize(stmt); return false; diff --git a/src/database/gravity-db.h b/src/database/gravity-db.h index 3660eb9ed..dbcff0cd5 100644 --- a/src/database/gravity-db.h +++ b/src/database/gravity-db.h @@ -17,6 +17,10 @@ // Table indices enum { GRAVITY_TABLE, EXACT_BLACKLIST_TABLE, EXACT_WHITELIST_TABLE, REGEX_BLACKLIST_TABLE, REGEX_WHITELIST_TABLE, UNKNOWN_TABLE }; +enum { GRAVITY_DOMAINLIST_EXACT_WHITELIST = 0, + GRAVITY_DOMAINLIST_EXACT_BLACKLIST = 1, + GRAVITY_DOMAINLIST_REGEX_WHITELIST = 2, + GRAVITY_DOMAINLIST_REGEX_BLACKLIST = 3 }; bool gravityDB_open(void); bool gravityDB_prepare_client_statements(const int clientID, clientsData* client); @@ -34,7 +38,7 @@ bool in_blacklist(const char *domain, const int clientID, clientsData* client); bool gravityDB_get_regex_client_groups(clientsData* client, const int numregex, const int *regexid, const unsigned char type, const char* table, const int clientID); -bool gravityDB_addToTable(const char *table, const char* domain); -bool gravityDB_delFromTable(const char *table, const char* domain); +bool gravityDB_addToTable(const int type, const char* domain); +bool gravityDB_delFromTable(const int type, const char* domain); #endif //GRAVITY_H From 6bf7fb782ea5e93b01b0d3f3dcc16e759c975be0 Mon Sep 17 00:00:00 2001 From: DL6ER Date: Mon, 2 Dec 2019 18:08:13 +0000 Subject: [PATCH 0126/1669] Read directly from domainlist table instead of using the views. This allows us to read also disabled domains plus to get all the corresponding data: date_added, date_modified, comment, enabled. They are now available in FTL, however the JSON format expected by the web interface needs to be changed to be able to send this information over. Signed-off-by: DL6ER --- src/api/dns.c | 43 ++++++++++++++---------- src/database/gravity-db.c | 70 +++++++++++++++++++++++++++++++++++++++ src/database/gravity-db.h | 11 ++++++ 3 files changed, 106 insertions(+), 18 deletions(-) diff --git a/src/api/dns.c b/src/api/dns.c index c94efde4d..a9b417f9f 100644 --- a/src/api/dns.c +++ b/src/api/dns.c @@ -114,34 +114,41 @@ int api_dns_status(struct mg_connection *conn) static int api_dns_somelist_read(struct mg_connection *conn, bool exact, bool whitelist) { - cJSON *json = JSON_NEW_ARRAY(); - const char *domain = NULL; - int rowid = 0; - int table; if(whitelist) if(exact) - table = EXACT_WHITELIST_TABLE; + table = GRAVITY_DOMAINLIST_EXACT_WHITELIST; else - table = REGEX_WHITELIST_TABLE; + table = GRAVITY_DOMAINLIST_REGEX_WHITELIST; else if(exact) - table = EXACT_BLACKLIST_TABLE; + table = GRAVITY_DOMAINLIST_EXACT_BLACKLIST; else - table = REGEX_BLACKLIST_TABLE; + table = GRAVITY_DOMAINLIST_REGEX_BLACKLIST; + + if(!gravityDB_readTable(table)) + { + cJSON *json = JSON_NEW_OBJ(); + return send_json_error(conn, 500, + "database_error", + "Could not read domain from database table", + json); + } - gravityDB_getTable(table); - while((domain = gravityDB_getDomain(&rowid)) != NULL) + domainrecord domain; + cJSON *json = JSON_NEW_ARRAY(); + while(gravityDB_readTableGetDomain(&domain)) { - JSON_ARRAY_COPY_STR(json, domain); + // We also have domain.enabled, domain.date_added, domain.comment, ... available here + JSON_ARRAY_COPY_STR(json, domain.domain); } - gravityDB_finalizeTable(); + gravityDB_readTableFinalize(); JSON_SEND_OBJECT(json); } static int api_dns_somelist_POST(struct mg_connection *conn, - bool store_exact, + bool exact, bool whitelist) { char buffer[1024]; @@ -174,12 +181,12 @@ static int api_dns_somelist_POST(struct mg_connection *conn, int table; if(whitelist) - if(store_exact) + if(exact) table = GRAVITY_DOMAINLIST_EXACT_WHITELIST; else table = GRAVITY_DOMAINLIST_REGEX_WHITELIST; else - if(store_exact) + if(exact) table = GRAVITY_DOMAINLIST_EXACT_BLACKLIST; else table = GRAVITY_DOMAINLIST_REGEX_BLACKLIST; @@ -204,7 +211,7 @@ static int api_dns_somelist_POST(struct mg_connection *conn, } static int api_dns_somelist_DELETE(struct mg_connection *conn, - bool store_exact, + bool exact, bool whitelist) { const struct mg_request_info *request = mg_get_request_info(conn); @@ -217,12 +224,12 @@ static int api_dns_somelist_DELETE(struct mg_connection *conn, int table; if(whitelist) - if(store_exact) + if(exact) table = GRAVITY_DOMAINLIST_EXACT_WHITELIST; else table = GRAVITY_DOMAINLIST_REGEX_WHITELIST; else - if(store_exact) + if(exact) table = GRAVITY_DOMAINLIST_EXACT_BLACKLIST; else table = GRAVITY_DOMAINLIST_REGEX_BLACKLIST; diff --git a/src/database/gravity-db.c b/src/database/gravity-db.c index a6d43640e..8b59fac6f 100644 --- a/src/database/gravity-db.c +++ b/src/database/gravity-db.c @@ -1031,3 +1031,73 @@ bool gravityDB_delFromTable(const int type, const char* domain) return okay; } + +typedef struct domainrecords { + const char *domain; + time_t date_added; + time_t date_modified; + const char *comment; + bool enabled; +} domainrecords; + + +static sqlite3_stmt* read_stmt = NULL; +bool gravityDB_readTable(const int type) +{ + // Prepare SQLite statement + const char *querystr = "SELECT domain,enabled,date_added,date_modified,comment FROM domainlist WHERE type = ?;"; + int rc = sqlite3_prepare_v2(gravity_db, querystr, -1, &read_stmt, NULL); + if( rc != SQLITE_OK ){ + logg("gravityDB_readTable(%d) - SQL error prepare 2 (%i): %s", + type, rc, sqlite3_errmsg(gravity_db)); + return false; + } + + // Bind domain type to prepared statement + if((rc = sqlite3_bind_int(read_stmt, 1, type)) != SQLITE_OK) + { + logg("gravityDB_readTable(%d): Failed to bind domain (error %d) - %s", + type, rc, sqlite3_errmsg(gravity_db)); + sqlite3_reset(read_stmt); + sqlite3_finalize(read_stmt); + return false; + } + + return true; +} + +bool gravityDB_readTableGetDomain(domainrecord *domain) +{ + // Perform step + const int rc = sqlite3_step(read_stmt); + + // Valid row + if(rc == SQLITE_ROW) + { + domain->domain = (char*)sqlite3_column_text(read_stmt, 0); + domain->enabled = sqlite3_column_int(read_stmt, 1) != 0; + domain->date_added = sqlite3_column_int(read_stmt, 2); + domain->date_modified = sqlite3_column_int(read_stmt, 3); + domain->comment = (char*)sqlite3_column_text(read_stmt, 4); + return true; + } + + // Check for error. An error happened when the result is neither + // SQLITE_ROW (we returned earlier in this case), nor + // SQLITE_DONE (we are finished reading the table) + if(rc != SQLITE_DONE) + { + logg("gravityDB_getDomain() - SQL error step (%i): %s", rc, sqlite3_errmsg(gravity_db)); + return NULL; + } + + // Finished reading, nothing to get here + return NULL; +} + +// Finalize statement of a gravity database transaction +void gravityDB_readTableFinalize(void) +{ + // Finalize statement + sqlite3_finalize(read_stmt); +} diff --git a/src/database/gravity-db.h b/src/database/gravity-db.h index dbcff0cd5..1c3b2dea4 100644 --- a/src/database/gravity-db.h +++ b/src/database/gravity-db.h @@ -22,6 +22,14 @@ enum { GRAVITY_DOMAINLIST_EXACT_WHITELIST = 0, GRAVITY_DOMAINLIST_REGEX_WHITELIST = 2, GRAVITY_DOMAINLIST_REGEX_BLACKLIST = 3 }; +typedef struct domainrecord { + const char *domain; + time_t date_added; + time_t date_modified; + const char *comment; + bool enabled; +} domainrecord; + bool gravityDB_open(void); bool gravityDB_prepare_client_statements(const int clientID, clientsData* client); void gravityDB_close(void); @@ -40,5 +48,8 @@ bool gravityDB_get_regex_client_groups(clientsData* client, const int numregex, const unsigned char type, const char* table, const int clientID); bool gravityDB_addToTable(const int type, const char* domain); bool gravityDB_delFromTable(const int type, const char* domain); +bool gravityDB_readTable(const int type); +bool gravityDB_readTableGetDomain(domainrecord *domain); +void gravityDB_readTableFinalize(void); #endif //GRAVITY_H From fdce5481f9f213667ea95bd4c7605c4cb9b30c9d Mon Sep 17 00:00:00 2001 From: DL6ER Date: Tue, 3 Dec 2019 12:08:36 +0000 Subject: [PATCH 0127/1669] Send all details we have about a domain (excluding the database ID, because we don't need it). See PR web#440 for the necessary changes in NG web. Signed-off-by: DL6ER --- src/api/dns.c | 9 +++++++-- src/database/gravity-db.c | 15 +++------------ 2 files changed, 10 insertions(+), 14 deletions(-) diff --git a/src/api/dns.c b/src/api/dns.c index a9b417f9f..2f440acbb 100644 --- a/src/api/dns.c +++ b/src/api/dns.c @@ -139,8 +139,13 @@ static int api_dns_somelist_read(struct mg_connection *conn, bool exact, bool wh cJSON *json = JSON_NEW_ARRAY(); while(gravityDB_readTableGetDomain(&domain)) { - // We also have domain.enabled, domain.date_added, domain.comment, ... available here - JSON_ARRAY_COPY_STR(json, domain.domain); + cJSON *item = JSON_NEW_OBJ(); + JSON_OBJ_COPY_STR(item, "domain", domain.domain); + JSON_OBJ_ADD_BOOL(item, "enabled", domain.enabled); + JSON_OBJ_ADD_NUMBER(item, "date_added", domain.date_added); + JSON_OBJ_ADD_NUMBER(item, "date_modified", domain.date_modified); + JSON_OBJ_COPY_STR(item, "comment", domain.comment); + JSON_ARRAY_ADD_ITEM(json, item); } gravityDB_readTableFinalize(); diff --git a/src/database/gravity-db.c b/src/database/gravity-db.c index 8b59fac6f..15fb4bb1c 100644 --- a/src/database/gravity-db.c +++ b/src/database/gravity-db.c @@ -1032,15 +1032,6 @@ bool gravityDB_delFromTable(const int type, const char* domain) return okay; } -typedef struct domainrecords { - const char *domain; - time_t date_added; - time_t date_modified; - const char *comment; - bool enabled; -} domainrecords; - - static sqlite3_stmt* read_stmt = NULL; bool gravityDB_readTable(const int type) { @@ -1087,12 +1078,12 @@ bool gravityDB_readTableGetDomain(domainrecord *domain) // SQLITE_DONE (we are finished reading the table) if(rc != SQLITE_DONE) { - logg("gravityDB_getDomain() - SQL error step (%i): %s", rc, sqlite3_errmsg(gravity_db)); - return NULL; + logg("gravityDB_readTableGetDomain() - SQL error step (%i): %s", rc, sqlite3_errmsg(gravity_db)); + return false; } // Finished reading, nothing to get here - return NULL; + return false; } // Finalize statement of a gravity database transaction From a6a7b9712e33429f82ac396ffd9e5f6c0355cec0 Mon Sep 17 00:00:00 2001 From: DL6ER Date: Tue, 3 Dec 2019 12:20:13 +0000 Subject: [PATCH 0128/1669] Add API_PRETTY_JSON config option as run-time (instead of compile-time) option for human-friendy API output. Signed-off-by: DL6ER --- src/api/http-common.c | 25 +++++++++++++++++++++++++ src/api/http-common.h | 2 ++ src/api/json_macros.h | 27 ++++----------------------- src/config.c | 19 ++++++++++++++++--- src/config.h | 1 + 5 files changed, 48 insertions(+), 26 deletions(-) diff --git a/src/api/http-common.c b/src/api/http-common.c index c6b834e6a..440459e1d 100644 --- a/src/api/http-common.c +++ b/src/api/http-common.c @@ -19,6 +19,31 @@ // Server context handle static struct mg_context *ctx = NULL; +// Provides a compile-time flag for JSON formatting +// This should never be needed as all modern browsers +// tyoically contain a JSON explorer +const char* json_formatter(const cJSON *object) +{ + if(httpsettings.prettyJSON) + { + /* Examplary output: + { + "queries in database": 70, + "database filesize": 49152, + "SQLite version": "3.30.1" + } + */ + return cJSON_Print(object); + } + else + { + /* Exemplary output + {"queries in database":70,"database filesize":49152,"SQLite version":"3.30.1"} + */ + return cJSON_PrintUnformatted(object); + } +} + int send_http(struct mg_connection *conn, const char *mime_type, const char *msg) { diff --git a/src/api/http-common.h b/src/api/http-common.h index 24ca96c19..c201ed9e1 100644 --- a/src/api/http-common.h +++ b/src/api/http-common.h @@ -23,6 +23,8 @@ void http_init(void); void http_terminate(void); +const char* json_formatter(const cJSON *object); + int send_http(struct mg_connection *conn, const char *mime_type, const char *msg); int send_http_code(struct mg_connection *conn, const char *mime_type, int code, const char *msg); int send_http_internal_error(struct mg_connection *conn); diff --git a/src/api/json_macros.h b/src/api/json_macros.h index 604f32043..00fed64d4 100644 --- a/src/api/json_macros.h +++ b/src/api/json_macros.h @@ -10,25 +10,6 @@ #include "../cJSON/cJSON.h" -// Provides a compile-time flag for JSON formatting -// This should never be needed as all modern browsers -// tyoically contain a JSON explorer -#ifdef JSON_FORMATTED -/* Examplary output: -{ - "queries in database": 70, - "database filesize": 49152, - "SQLite version": "3.30.1" -} -*/ -#define JSON_FORMATTER(object) cJSON_Print(object) -#else -/* Exemplary output -{"queries in database":70,"database filesize":49152,"SQLite version":"3.30.1"} -*/ -#define JSON_FORMATTER(object) cJSON_PrintUnformatted(object) -#endif - #define JSON_NEW_OBJ() cJSON_CreateObject(); #define JSON_NEW_ARRAY() cJSON_CreateArray(); @@ -122,7 +103,7 @@ #define JSON_DELETE(object) cJSON_Delete(object) #define JSON_SEND_OBJECT(object){ \ - const char* msg = JSON_FORMATTER(object); \ + const char* msg = json_formatter(object); \ if(msg == NULL) \ { \ cJSON_Delete(object); \ @@ -135,7 +116,7 @@ } #define JSON_SEND_OBJECT_CODE(object, code){ \ - const char* msg = JSON_FORMATTER(object); \ + const char* msg = json_formatter(object); \ if(msg == NULL) \ { \ cJSON_Delete(object); \ @@ -148,7 +129,7 @@ } /* #define JSON_SEND_OBJECT_AND_HEADERS(object, additional_headers){ \ - const char* msg = JSON_FORMATTER(object); \ + const char* msg = json_formatter(object); \ if(msg == NULL) \ { \ cJSON_Delete(object); \ @@ -162,7 +143,7 @@ } #define JSON_SEND_OBJECT_AND_HEADERS_CODE(object, code, additional_headers){ \ - const char* msg = JSON_FORMATTER(object); \ + const char* msg = json_formatter(object); \ if(msg == NULL) \ { \ cJSON_Delete(object); \ diff --git a/src/config.c b/src/config.c index f6964dc5f..ae72feba4 100644 --- a/src/config.c +++ b/src/config.c @@ -422,18 +422,31 @@ void read_FTLconf(void) else logg(" API_AUTH_FOR_LOCALHOST: Inactive"); - // HTTP_SESSION_TIMEOUT + // API_SESSION_TIMEOUT // How long should a session be considered valid after login? // defaults to: 300 seconds httpsettings.session_timeout = 300; - buffer = parse_FTLconf(fp, "HTTP_SESSION_TIMEOUT"); + buffer = parse_FTLconf(fp, "API_SESSION_TIMEOUT"); value = 0; if(buffer != NULL && sscanf(buffer, "%i", &value) && value > 0) { httpsettings.session_timeout = value; } - logg(" HTTP_SESSION_TIMEOUT: %u seconds", httpsettings.session_timeout); + logg(" API_SESSION_TIMEOUT: %u seconds", httpsettings.session_timeout); + + // API_PRETTY_JSON + // defaults to: false + httpsettings.prettyJSON = false; + buffer = parse_FTLconf(fp, "API_PRETTY_JSON"); + + if(buffer != NULL && strcasecmp(buffer, "true") == 0) + httpsettings.prettyJSON = true; + + if(httpsettings.prettyJSON) + logg(" API_PRETTY_JSON: Enabled. Using additional formatting in API output."); + else + logg(" API_PRETTY_JSON: Disabled. Compact API output."); // Read DEBUG_... setting from pihole-FTL.conf // This option should be the last one as it causes diff --git a/src/config.h b/src/config.h index 7daed55ea..ed52edf81 100644 --- a/src/config.h +++ b/src/config.h @@ -58,6 +58,7 @@ typedef struct httpsettings { char *webhome; const char *acl; bool api_auth_for_localhost; + bool prettyJSON; char port[20]; // enough space for 2*(maximum length of number in a uint16_t = 5 characters) + ",[::]:" + NULL unsigned int session_timeout; } httpsettingsStruct; From 46c622f2568b2e7fabfcbc9845b128e3ca5c282d Mon Sep 17 00:00:00 2001 From: DL6ER Date: Tue, 3 Dec 2019 22:40:26 +0000 Subject: [PATCH 0129/1669] Keep up to 64 lines of dnsmasq messages in the FIFO buffer. Signed-off-by: DL6ER --- src/api/ftl.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/api/ftl.h b/src/api/ftl.h index c64b97b81..c359be781 100644 --- a/src/api/ftl.h +++ b/src/api/ftl.h @@ -15,8 +15,8 @@ // How many messages do we keep in memory (FIFO message buffer)? // The memory required is the set number in kilobytes -// Defaults to 32 [uses 32 KB of memory] -#define LOG_SIZE 32 +// Defaults to 64 [uses 64 KB of memory] +#define LOG_SIZE 64 void init_dnsmasq_fifo_log(void); void free_dnsmasq_fifo_log(void); From 9d24bae8be59b8f257fc5fbb096e33f53d7ac3d2 Mon Sep 17 00:00:00 2001 From: DL6ER Date: Wed, 4 Dec 2019 20:01:53 +0000 Subject: [PATCH 0130/1669] Add /api/ftl/network Signed-off-by: DL6ER --- src/api/ftl.c | 54 +++++++++++++++- src/api/routes.c | 4 ++ src/api/routes.h | 1 + src/database/gravity-db.c | 2 +- src/database/network-table.c | 122 +++++++++++++++++++++++++++++++++++ src/database/network-table.h | 19 ++++++ 6 files changed, 200 insertions(+), 2 deletions(-) diff --git a/src/api/ftl.c b/src/api/ftl.c index f40a9173f..5047b149a 100644 --- a/src/api/ftl.c +++ b/src/api/ftl.c @@ -22,6 +22,8 @@ #include "config.h" // {un,}lock_shm() #include "../shmem.h" +// networkrecord +#include "../database/network-table.h" int api_ftl_clientIP(struct mg_connection *conn) { @@ -128,4 +130,54 @@ void add_to_dnsmasq_log_fifo_buffer(const char *payload, const int length) // Unlock SHM unlock_shm(); -} \ No newline at end of file +} + +int api_ftl_network(struct mg_connection *conn) +{ + // Verify requesting client is allowed to see this ressource + if(check_client_auth(conn) < 0) + { + return send_json_unauthorized(conn); + } + + // Connect to database + if(!networkTable_readDevices()) + { + cJSON *json = JSON_NEW_OBJ(); + return send_json_error(conn, 500, + "database_error", + "Could not read network details from database table", + json); + } + + // Read record for a single device + networkrecord network; + cJSON *json = JSON_NEW_ARRAY(); + while(networkTable_readDevicesGetRecord(&network)) + { + cJSON *item = JSON_NEW_OBJ(); + JSON_OBJ_COPY_STR(item, "hwaddr", network.hwaddr); + JSON_OBJ_COPY_STR(item, "interface", network.interface); + JSON_OBJ_COPY_STR(item, "name", network.name); + JSON_OBJ_ADD_NUMBER(item, "firstSeen", network.firstSeen); + JSON_OBJ_ADD_NUMBER(item, "lastQuery", network.lastQuery); + JSON_OBJ_ADD_NUMBER(item, "numQueries", network.numQueries); + JSON_OBJ_COPY_STR(item, "macVendor", network.macVendor); + + // Build array of all IP addresses known associated to this client + cJSON *ip = JSON_NEW_ARRAY(); + networkTable_readIPs(network.id); + const char *ipaddr; + while((ipaddr = networkTable_readIPsGetRecord()) != NULL) + { + JSON_ARRAY_COPY_STR(ip, ipaddr); + } + networkTable_readIPsFinalize(); + JSON_OBJ_ADD_ITEM(item, "ip", ip); + + JSON_ARRAY_ADD_ITEM(json, item); + } + networkTable_readDevicesFinalize(); + + JSON_SEND_OBJECT(json); +} diff --git a/src/api/routes.c b/src/api/routes.c index 24c6bfd01..955d3f394 100644 --- a/src/api/routes.c +++ b/src/api/routes.c @@ -54,6 +54,10 @@ int api_handler(struct mg_connection *conn, void *ignored) { ret = api_ftl_dnsmasq_log(conn); } + else if(startsWith("/api/ftl/network", request->local_uri)) + { + ret = api_ftl_network(conn); + } /******************************** api/stats **************************/ else if(startsWith("/api/stats/summary", request->local_uri)) { diff --git a/src/api/routes.h b/src/api/routes.h index bddf51a45..9a485f18c 100644 --- a/src/api/routes.h +++ b/src/api/routes.h @@ -30,6 +30,7 @@ int api_stats_recentblocked(struct mg_connection *conn); // FTL methods int api_ftl_clientIP(struct mg_connection *conn); int api_ftl_dnsmasq_log(struct mg_connection *conn); +int api_ftl_network(struct mg_connection *conn); // DNS methods int api_dns_status(struct mg_connection *conn); diff --git a/src/database/gravity-db.c b/src/database/gravity-db.c index 15fb4bb1c..45f577c94 100644 --- a/src/database/gravity-db.c +++ b/src/database/gravity-db.c @@ -1039,7 +1039,7 @@ bool gravityDB_readTable(const int type) const char *querystr = "SELECT domain,enabled,date_added,date_modified,comment FROM domainlist WHERE type = ?;"; int rc = sqlite3_prepare_v2(gravity_db, querystr, -1, &read_stmt, NULL); if( rc != SQLITE_OK ){ - logg("gravityDB_readTable(%d) - SQL error prepare 2 (%i): %s", + logg("gravityDB_readTable(%d) - SQL error prepare (%i): %s", type, rc, sqlite3_errmsg(gravity_db)); return false; } diff --git a/src/database/network-table.c b/src/database/network-table.c index 9fca10b57..db0ecf192 100644 --- a/src/database/network-table.c +++ b/src/database/network-table.c @@ -942,3 +942,125 @@ char* __attribute__((malloc)) getDatabaseHostname(const char* ipaddr) return hostname; } + +static sqlite3_stmt* read_stmt = NULL; +bool networkTable_readDevices(void) +{ + // Open pihole-FTL.db database file + if(!dbopen()) + { + logg("networkTable_readDevices() - Failed to open DB"); + return false; + } + + // Prepare SQLite statement + const char *querystr = "SELECT id,hwaddr,interface,name,firstSeen,lastQuery,numQueries,macVendor FROM network;"; + int rc = sqlite3_prepare_v2(FTL_db, querystr, -1, &read_stmt, NULL); + if( rc != SQLITE_OK ){ + logg("networkTable_readDevices() - SQL error prepare (%i): %s", + rc, sqlite3_errmsg(FTL_db)); + return false; + } + + return true; +} + +bool networkTable_readDevicesGetRecord(networkrecord *network) +{ + // Perform step + const int rc = sqlite3_step(read_stmt); + + // Valid row + if(rc == SQLITE_ROW) + { + network->id = sqlite3_column_int(read_stmt, 0); + network->hwaddr = (char*)sqlite3_column_text(read_stmt, 1); + network->interface = (char*)sqlite3_column_text(read_stmt, 2); + network->name = (char*)sqlite3_column_text(read_stmt, 3); + network->firstSeen = sqlite3_column_int(read_stmt, 4); + network->lastQuery = sqlite3_column_int(read_stmt, 5); + network->numQueries = sqlite3_column_int(read_stmt, 6); + network->macVendor = (char*)sqlite3_column_text(read_stmt, 7); + return true; + } + + // Check for error. An error happened when the result is neither + // SQLITE_ROW (we returned earlier in this case), nor + // SQLITE_DONE (we are finished reading the table) + if(rc != SQLITE_DONE) + { + logg("networkTable_readDevicesGetRecord() - SQL error step (%i): %s", + rc, sqlite3_errmsg(FTL_db)); + return false; + } + + // Finished reading, nothing to get here + return false; +} + +// Finalize statement of a gravity database transaction +void networkTable_readDevicesFinalize(void) +{ + // Finalize statement + sqlite3_finalize(read_stmt); + + // Close database connection + dbclose(); +} + +static sqlite3_stmt* read_stmt_ip = NULL; +bool networkTable_readIPs(const int id) +{ + // Prepare SQLite statement + const char *querystr = "SELECT ip FROM network_addresses WHERE network_id = ? ORDER BY lastSeen DESC;"; + int rc = sqlite3_prepare_v2(FTL_db, querystr, -1, &read_stmt_ip, NULL); + if( rc != SQLITE_OK ){ + logg("networkTable_readIPs(%i) - SQL error prepare (%i): %s", + id, rc, sqlite3_errmsg(FTL_db)); + return false; + } + + // Bind ipaddr to prepared statement + if((rc = sqlite3_bind_int(read_stmt_ip, 1, id)) != SQLITE_OK) + { + logg("networkTable_readIPs(%i): Failed to bind domain (error %d) - %s", + id, rc, sqlite3_errmsg(FTL_db)); + sqlite3_reset(read_stmt_ip); + sqlite3_finalize(read_stmt_ip); + return false; + } + + return true; +} + +const char *networkTable_readIPsGetRecord(void) +{ + // Perform step + const int rc = sqlite3_step(read_stmt_ip); + + // Valid row + if(rc == SQLITE_ROW) + { + return (char*)sqlite3_column_text(read_stmt_ip, 0); + } + + // Check for error. An error happened when the result is neither + // SQLITE_ROW (we returned earlier in this case), nor + // SQLITE_DONE (we are finished reading the table) + if(rc != SQLITE_DONE) + { + logg("networkTable_readDevicesGetIP() - SQL error step (%i): %s", + rc, sqlite3_errmsg(FTL_db)); + return NULL; + } + + // Finished reading, nothing to get here + return NULL; +} + +// Finalize statement of a gravity database transaction +void networkTable_readIPsFinalize(void) +{ + // Finalize statement + sqlite3_finalize(read_stmt_ip); +} \ No newline at end of file diff --git a/src/database/network-table.h b/src/database/network-table.h index 9addd0037..3c8e84b31 100644 --- a/src/database/network-table.h +++ b/src/database/network-table.h @@ -17,4 +17,23 @@ void updateMACVendorRecords(void); bool unify_hwaddr(void); char* getDatabaseHostname(const char* ipaddr) __attribute__((malloc)); +typedef struct networkrecord { + unsigned int id; + const char *hwaddr; + const char* interface; + const char *name; + const char *macVendor; + unsigned long numQueries; + time_t firstSeen; + time_t lastQuery; +} networkrecord; + +bool networkTable_readDevices(void); +bool networkTable_readDevicesGetRecord(networkrecord *network); +void networkTable_readDevicesFinalize(void); + +bool networkTable_readIPs(const int id); +const char *networkTable_readIPsGetRecord(void); +void networkTable_readIPsFinalize(void); + #endif //NETWORKTABLE_H From 091e656de8c91bc675a3b1c40fec97e84e62e7da Mon Sep 17 00:00:00 2001 From: DL6ER Date: Wed, 4 Dec 2019 20:25:39 +0000 Subject: [PATCH 0131/1669] Only walk known IP addresses when SELECT query succeeded Signed-off-by: DL6ER --- src/api/ftl.c | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/api/ftl.c b/src/api/ftl.c index 5047b149a..e936cfff9 100644 --- a/src/api/ftl.c +++ b/src/api/ftl.c @@ -166,13 +166,16 @@ int api_ftl_network(struct mg_connection *conn) // Build array of all IP addresses known associated to this client cJSON *ip = JSON_NEW_ARRAY(); - networkTable_readIPs(network.id); - const char *ipaddr; - while((ipaddr = networkTable_readIPsGetRecord()) != NULL) + if(networkTable_readIPs(network.id)) { - JSON_ARRAY_COPY_STR(ip, ipaddr); + // Only walk known IP addresses when SELECT query succeeded + const char *ipaddr; + while((ipaddr = networkTable_readIPsGetRecord()) != NULL) + { + JSON_ARRAY_COPY_STR(ip, ipaddr); + } + networkTable_readIPsFinalize(); } - networkTable_readIPsFinalize(); JSON_OBJ_ADD_ITEM(item, "ip", ip); JSON_ARRAY_ADD_ITEM(json, item); From 6871dbdd047d7ff2b6a8a81973ffb541a379b89c Mon Sep 17 00:00:00 2001 From: DL6ER Date: Thu, 5 Dec 2019 13:56:14 +0000 Subject: [PATCH 0132/1669] Add /api/dns/cacheinfo Signed-off-by: DL6ER --- src/api/dns.c | 18 ++++++++++++++++++ src/api/dns.h | 22 ++++++++++++++++++++++ src/api/routes.c | 4 ++++ src/api/routes.h | 1 + src/dnsmasq_interface.c | 11 ++++------- src/dnsmasq_interface.h | 3 ++- 6 files changed, 51 insertions(+), 8 deletions(-) create mode 100644 src/api/dns.h diff --git a/src/api/dns.c b/src/api/dns.c index 2f440acbb..1bd9167d2 100644 --- a/src/api/dns.c +++ b/src/api/dns.c @@ -11,6 +11,7 @@ #include "FTL.h" // counters #include "shmem.h" +#include "dns.h" #include "http-common.h" #include "routes.h" #include "json_macros.h" @@ -287,3 +288,20 @@ int api_dns_somelist(struct mg_connection *conn, bool exact, bool whitelist) return 0; } } + +int api_dns_cacheinfo(struct mg_connection *conn) +{ + // Verify requesting client is allowed to access this ressource + if(check_client_auth(conn) < 0) + { + return send_json_unauthorized(conn); + } + + cacheinforecord cacheinfo; + getCacheInformation(&cacheinfo); + cJSON *json = JSON_NEW_OBJ(); + JSON_OBJ_ADD_NUMBER(json, "cache-size", cacheinfo.cache_size); + JSON_OBJ_ADD_NUMBER(json, "cache-live-freed", cacheinfo.cache_live_freed); + JSON_OBJ_ADD_NUMBER(json, "cache-inserted", cacheinfo.cache_inserted); + JSON_SEND_OBJECT(json); +} diff --git a/src/api/dns.h b/src/api/dns.h new file mode 100644 index 000000000..896aa0acd --- /dev/null +++ b/src/api/dns.h @@ -0,0 +1,22 @@ +/* Pi-hole: A black hole for Internet advertisements +* (c) 2019 Pi-hole, LLC (https://pi-hole.net) +* Network-wide ad blocking via your own hardware. +* +* FTL Engine +* API DNS prototypes +* +* This file is copyright under the latest version of the EUPL. +* Please see LICENSE file for your rights under this license. */ +#ifndef API_DNS_H +#define API_DNS_H + +typedef struct cacheinforecord { + int cache_size; + int cache_live_freed; + int cache_inserted; +} cacheinforecord; + +// defined in src/dnsmasq_interface.c +extern void getCacheInformation(cacheinforecord *cacheinfo); + +#endif // API_DNS_H \ No newline at end of file diff --git a/src/api/routes.c b/src/api/routes.c index 955d3f394..7dc813fb4 100644 --- a/src/api/routes.c +++ b/src/api/routes.c @@ -45,6 +45,10 @@ int api_handler(struct mg_connection *conn, void *ignored) { ret = api_dns_somelist(conn, false, false); } + else if(startsWith("/api/dns/cacheinfo", request->local_uri)) + { + ret = api_dns_cacheinfo(conn); + } /******************************** api/ftl ****************************/ else if(startsWith("/api/ftl/clientIP", request->local_uri)) { diff --git a/src/api/routes.h b/src/api/routes.h index 9a485f18c..082210750 100644 --- a/src/api/routes.h +++ b/src/api/routes.h @@ -35,6 +35,7 @@ int api_ftl_network(struct mg_connection *conn); // DNS methods int api_dns_status(struct mg_connection *conn); int api_dns_somelist(struct mg_connection *conn, bool exact, bool whitelist); +int api_dns_cacheinfo(struct mg_connection *conn); // Version method int api_version(struct mg_connection *conn); diff --git a/src/dnsmasq_interface.c b/src/dnsmasq_interface.c index a9145b0cc..165b34e58 100644 --- a/src/dnsmasq_interface.c +++ b/src/dnsmasq_interface.c @@ -1689,16 +1689,14 @@ void FTL_fork_and_bind_sockets(struct passwd *ent_pw) } // int cache_inserted, cache_live_freed are defined in dnsmasq/cache.c -int *getCacheInformation(void) +void getCacheInformation(cacheinforecord *cacheinfo) { - // Allocate memory - int *cacheinfo = calloc(3,sizeof(int)); // cache-size - interpretation is obvious - cacheinfo[0] = daemon->cachesize; + cacheinfo->cache_size = daemon->cachesize; // cache-live-freed - interpretation see below - cacheinfo[1] = daemon->metrics[METRIC_DNS_CACHE_LIVE_FREED]; + cacheinfo->cache_live_freed = daemon->metrics[METRIC_DNS_CACHE_LIVE_FREED]; // cache-inserted - interpretation see below - cacheinfo[2] = daemon->metrics[METRIC_DNS_CACHE_INSERTED]; + cacheinfo->cache_inserted = daemon->metrics[METRIC_DNS_CACHE_INSERTED]; // cache-live-freed and cache-inserted: // It means the resolver handled names lookups that // needed to be sent to upstream servers and that @@ -1708,7 +1706,6 @@ int *getCacheInformation(void) // cached. If the cache is full with entries which haven't reached // the end of their time-to-live, then the entry which hasn't been // looked up for the longest time is evicted. - return cacheinfo; } void _FTL_forwarding_failed(const struct server *server, const char* file, const int line) diff --git a/src/dnsmasq_interface.h b/src/dnsmasq_interface.h index 61ae6b12d..33e44db46 100644 --- a/src/dnsmasq_interface.h +++ b/src/dnsmasq_interface.h @@ -11,6 +11,8 @@ #define DNSMASQ_INTERFACE_H #include +// cacheinforecord +#include "api/dns.h" extern unsigned char* pihole_privacylevel; enum { TCP, UDP }; @@ -46,7 +48,6 @@ void _FTL_get_blocking_metadata(union all_addr **addrp, unsigned int *flags, con #define FTL_CNAME(domain, cpp, id) _FTL_CNAME(domain, cpp, id, __FILE__, __LINE__) bool _FTL_CNAME(const char *domain, const struct crec *cpp, const int id, const char* file, const int line); -int *getCacheInformation(void); void FTL_dnsmasq_reload(void); void FTL_fork_and_bind_sockets(struct passwd *ent_pw); From 0f7ead8663cda24358ec76098478fc083938657b Mon Sep 17 00:00:00 2001 From: DL6ER Date: Thu, 5 Dec 2019 14:57:57 +0000 Subject: [PATCH 0133/1669] A few output optimizations for /api/dns/cacheinfo Signed-off-by: DL6ER --- src/api/dns.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/api/dns.c b/src/api/dns.c index 1bd9167d2..a8adcc76e 100644 --- a/src/api/dns.c +++ b/src/api/dns.c @@ -300,8 +300,8 @@ int api_dns_cacheinfo(struct mg_connection *conn) cacheinforecord cacheinfo; getCacheInformation(&cacheinfo); cJSON *json = JSON_NEW_OBJ(); - JSON_OBJ_ADD_NUMBER(json, "cache-size", cacheinfo.cache_size); - JSON_OBJ_ADD_NUMBER(json, "cache-live-freed", cacheinfo.cache_live_freed); - JSON_OBJ_ADD_NUMBER(json, "cache-inserted", cacheinfo.cache_inserted); + JSON_OBJ_ADD_NUMBER(json, "cache_size", cacheinfo.cache_size); + JSON_OBJ_ADD_NUMBER(json, "cache_inserted", cacheinfo.cache_inserted); + JSON_OBJ_ADD_NUMBER(json, "cache_evicted", cacheinfo.cache_live_freed); JSON_SEND_OBJECT(json); } From f2412873b840ae96d82be81f009c23af3de8f666 Mon Sep 17 00:00:00 2001 From: DL6ER Date: Thu, 5 Dec 2019 23:05:44 +0000 Subject: [PATCH 0134/1669] Add /admin/api/stats/database/overTime/history - this allows users to select a different time interval on the dash board than only "last 24 hours". Signed-off-by: DL6ER --- Makefile | 2 +- src/api/routes.c | 4 + src/api/routes.h | 3 + src/api/stats_database.c | 174 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 182 insertions(+), 1 deletion(-) create mode 100644 src/api/stats_database.c diff --git a/Makefile b/Makefile index bddb11f0f..0826caa5c 100644 --- a/Makefile +++ b/Makefile @@ -17,7 +17,7 @@ DNSMASQ_OPTS = -DHAVE_DNSSEC -DHAVE_DNSSEC_STATIC -DHAVE_IDN FTL_DEPS = *.h database/*.h api/*.h version.h FTL_DB_OBJ = database/common.o database/query-table.o database/network-table.o database/gravity-db.o database/database-thread.o \ database/sqlite3-ext.o database/message-table.o -FTL_API_OBJ = api/http-common.o api/routes.o api/ftl.o api/stats.o api/dns.o api/version.o api/auth.o api/settings.o +FTL_API_OBJ = api/http-common.o api/routes.o api/ftl.o api/stats.o api/dns.o api/version.o api/auth.o api/settings.o api/stats_database.o FTL_OBJ = $(FTL_DB_OBJ) $(FTL_API_OBJ) main.o memory.o log.o daemon.o datastructure.o signals.o files.o setupVars.o args.o gc.o config.o dnsmasq_interface.o resolve.o regex.o shmem.o capabilities.o overTime.o timers.o vector.o DNSMASQ_DEPS = config.h dhcp-protocol.h dns-protocol.h radv-protocol.h dhcp6-protocol.h dnsmasq.h ip6addr.h metrics.h ../dnsmasq_interface.h diff --git a/src/api/routes.c b/src/api/routes.c index 7dc813fb4..0d52f77a5 100644 --- a/src/api/routes.c +++ b/src/api/routes.c @@ -107,6 +107,10 @@ int api_handler(struct mg_connection *conn, void *ignored) { ret = api_stats_recentblocked(conn); } + else if(startsWith("/api/stats/database/overTime/history", request->local_uri)) + { + ret = api_stats_database_overTime_history(conn); + } /******************************** api/version ****************************/ else if(startsWith("/api/version", request->local_uri)) { diff --git a/src/api/routes.h b/src/api/routes.h index 082210750..9f887271d 100644 --- a/src/api/routes.h +++ b/src/api/routes.h @@ -27,6 +27,9 @@ int api_stats_top_clients(bool blocked, struct mg_connection *conn); int api_stats_history(struct mg_connection *conn); int api_stats_recentblocked(struct mg_connection *conn); +// Statistics methods (database) +int api_stats_database_overTime_history(struct mg_connection *conn); + // FTL methods int api_ftl_clientIP(struct mg_connection *conn); int api_ftl_dnsmasq_log(struct mg_connection *conn); diff --git a/src/api/stats_database.c b/src/api/stats_database.c new file mode 100644 index 000000000..9f67f4b5e --- /dev/null +++ b/src/api/stats_database.c @@ -0,0 +1,174 @@ +/* Pi-hole: A black hole for Internet advertisements +* (c) 2019 Pi-hole, LLC (https://pi-hole.net) +* Network-wide ad blocking via your own hardware. +* +* FTL Engine +* API database statistics implementation +* +* This file is copyright under the latest version of the EUPL. +* Please see LICENSE file for your rights under this license. */ + +#include "FTL.h" +#include "http-common.h" +#include "routes.h" +#include "json_macros.h" +#include "shmem.h" +#include "datastructure.h" +// logg() +#include "log.h" +// FTL_db +#include "../database/common.h" + +int api_stats_database_overTime_history(struct mg_connection *conn) +{ + int from = 0, until = 0; + const struct mg_request_info *request = mg_get_request_info(conn); + if(request->query_string != NULL) + { + int num; + if((num = get_int_var(request->query_string, "from")) > 0) + from = num; + if((num = get_int_var(request->query_string, "until")) > 0) + until = num; + } + + // Check if we received the required information + if(from == 0 || until == 0) + { + cJSON *json = JSON_NEW_OBJ(); + JSON_OBJ_ADD_NUMBER(json, "from", from); + JSON_OBJ_ADD_NUMBER(json, "until", until); + return send_json_error(conn, 400, + "bad_request", + "You need to specify both \"from\" and \"until\" in the request.", + json); + } + + // Unlock shared memory (DNS resolver can continue to work while we're preforming database queries) + unlock_shm(); + + + // Open the database (this also locks the database) + dbopen(); + const int interval = 600; + // Build SQL string + const char *querystr = "SELECT (timestamp/:interval)*:interval interval,status,COUNT(*) FROM queries " + "WHERE (status != 0) AND timestamp >= :from AND timestamp <= :until " + "GROUP by interval,status ORDER by interval"; + + + // Prepare SQLite statement + sqlite3_stmt *stmt; + int rc = sqlite3_prepare_v2(FTL_db, querystr, -1, &stmt, NULL); + if( rc != SQLITE_OK ){ + logg("api_stats_database_overTime_history() - SQL error prepare (%i): %s", + rc, sqlite3_errmsg(FTL_db)); + return false; + } + + // Bind interval to prepared statement + if((rc = sqlite3_bind_int(stmt, 1, interval)) != SQLITE_OK) + { + logg("api_stats_database_overTime_history(): Failed to bind interval (error %d) - %s", + rc, sqlite3_errmsg(FTL_db)); + sqlite3_reset(stmt); + sqlite3_finalize(stmt); + + cJSON *json = JSON_NEW_OBJ(); + JSON_OBJ_ADD_NUMBER(json, "from", from); + JSON_OBJ_ADD_NUMBER(json, "until", until); + return send_json_error(conn, 500, + "internal_error", + "Failed to bind interval", + json); + } + + // Bind from to prepared statement + if((rc = sqlite3_bind_int(stmt, 2, from)) != SQLITE_OK) + { + logg("api_stats_database_overTime_history(): Failed to bind from (error %d) - %s", + rc, sqlite3_errmsg(FTL_db)); + sqlite3_reset(stmt); + sqlite3_finalize(stmt); + + cJSON *json = JSON_NEW_OBJ(); + JSON_OBJ_ADD_NUMBER(json, "from", from); + JSON_OBJ_ADD_NUMBER(json, "until", until); + return send_json_error(conn, 500, + "internal_error", + "Failed to bind from", + json); + } + + // Bind until to prepared statement + if((rc = sqlite3_bind_int(stmt, 3, until)) != SQLITE_OK) + { + logg("api_stats_database_overTime_history(): Failed to bind until (error %d) - %s", + rc, sqlite3_errmsg(FTL_db)); + sqlite3_reset(stmt); + sqlite3_finalize(stmt); + + cJSON *json = JSON_NEW_OBJ(); + JSON_OBJ_ADD_NUMBER(json, "from", from); + JSON_OBJ_ADD_NUMBER(json, "until", until); + return send_json_error(conn, 500, + "internal_error", + "Failed to bind until", + json); + } + + // Loop over and accumulate results + cJSON *json = JSON_NEW_ARRAY(); + cJSON *item = NULL; + int previous_timestamp = 0, blocked = 0, total = 0; + while((rc = sqlite3_step(stmt)) == SQLITE_ROW) + { + const int timestamp = sqlite3_column_int(stmt, 0); + // Begin new array item for each new timestamp + if(timestamp != previous_timestamp) + { + previous_timestamp = timestamp; + if(item != NULL) + { + JSON_OBJ_ADD_NUMBER(item, "total_queries", total); + total = 0; + JSON_OBJ_ADD_NUMBER(item, "blocked_queries", blocked); + blocked = 0; + JSON_ARRAY_ADD_ITEM(json, item); + } + + item = JSON_NEW_OBJ(); + JSON_OBJ_ADD_NUMBER(item, "timestamp", timestamp); + } + + const int status = sqlite3_column_int(stmt, 1); + const int count = sqlite3_column_int(stmt, 2); + // Always add to total count + total += count; + + // Add to blocked count if this is the result for a blocked status + switch (status) + { + case QUERY_GRAVITY: + case QUERY_REGEX: + case QUERY_BLACKLIST: + case QUERY_EXTERNAL_BLOCKED_IP: + case QUERY_EXTERNAL_BLOCKED_NULL: + case QUERY_EXTERNAL_BLOCKED_NXRA: + blocked += count; + break; + + default: + break; + } + + } + + // Finalize statement and close (= unlock) database connection + sqlite3_finalize(stmt); + dbclose(); + + // Re-lock shared memory before returning back to router subroutine + lock_shm(); + JSON_SEND_OBJECT(json); +} \ No newline at end of file From 4516ee518691d7580781d9d8e2a4c60d37ea8cf0 Mon Sep 17 00:00:00 2001 From: DL6ER Date: Fri, 6 Dec 2019 07:21:23 +0000 Subject: [PATCH 0135/1669] Relock shared memory before returning an error message. Signed-off-by: DL6ER --- src/api/stats_database.c | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/api/stats_database.c b/src/api/stats_database.c index 9f67f4b5e..8ddfacd9a 100644 --- a/src/api/stats_database.c +++ b/src/api/stats_database.c @@ -47,7 +47,6 @@ int api_stats_database_overTime_history(struct mg_connection *conn) // Unlock shared memory (DNS resolver can continue to work while we're preforming database queries) unlock_shm(); - // Open the database (this also locks the database) dbopen(); const int interval = 600; @@ -73,6 +72,10 @@ int api_stats_database_overTime_history(struct mg_connection *conn) rc, sqlite3_errmsg(FTL_db)); sqlite3_reset(stmt); sqlite3_finalize(stmt); + dblose(); + + // Relock shared memory + lock_shm(); cJSON *json = JSON_NEW_OBJ(); JSON_OBJ_ADD_NUMBER(json, "from", from); @@ -90,6 +93,10 @@ int api_stats_database_overTime_history(struct mg_connection *conn) rc, sqlite3_errmsg(FTL_db)); sqlite3_reset(stmt); sqlite3_finalize(stmt); + dblose(); + + // Relock shared memory + lock_shm(); cJSON *json = JSON_NEW_OBJ(); JSON_OBJ_ADD_NUMBER(json, "from", from); @@ -107,6 +114,10 @@ int api_stats_database_overTime_history(struct mg_connection *conn) rc, sqlite3_errmsg(FTL_db)); sqlite3_reset(stmt); sqlite3_finalize(stmt); + dblose(); + + // Relock shared memory + lock_shm(); cJSON *json = JSON_NEW_OBJ(); JSON_OBJ_ADD_NUMBER(json, "from", from); From 429622d8e9534304690881ac60ea3e2d2f36494f Mon Sep 17 00:00:00 2001 From: DL6ER Date: Fri, 6 Dec 2019 07:46:00 +0000 Subject: [PATCH 0136/1669] Add /api/stats/database/top_domains and /api/stats/database/top_blocked Signed-off-by: DL6ER --- src/api/routes.c | 8 ++ src/api/routes.h | 1 + src/api/stats.c | 1 - src/api/stats_database.c | 166 ++++++++++++++++++++++++++++++++++++++- 4 files changed, 171 insertions(+), 5 deletions(-) diff --git a/src/api/routes.c b/src/api/routes.c index 0d52f77a5..ec2bba73e 100644 --- a/src/api/routes.c +++ b/src/api/routes.c @@ -111,6 +111,14 @@ int api_handler(struct mg_connection *conn, void *ignored) { ret = api_stats_database_overTime_history(conn); } + else if(startsWith("/api/stats/database/top_domains", request->local_uri)) + { + ret = api_stats_database_top_domains(false, conn); + } + else if(startsWith("/api/stats/database/top_blocked", request->local_uri)) + { + ret = api_stats_database_top_domains(true, conn); + } /******************************** api/version ****************************/ else if(startsWith("/api/version", request->local_uri)) { diff --git a/src/api/routes.h b/src/api/routes.h index 9f887271d..62b4c109b 100644 --- a/src/api/routes.h +++ b/src/api/routes.h @@ -29,6 +29,7 @@ int api_stats_recentblocked(struct mg_connection *conn); // Statistics methods (database) int api_stats_database_overTime_history(struct mg_connection *conn); +int api_stats_database_top_domains(bool blocked, struct mg_connection *conn); // FTL methods int api_ftl_clientIP(struct mg_connection *conn); diff --git a/src/api/stats.c b/src/api/stats.c index 5d5178ade..15e50f77c 100644 --- a/src/api/stats.c +++ b/src/api/stats.c @@ -219,7 +219,6 @@ int api_stats_top_domains(bool blocked, struct mg_connection *conn) // Sort temporary array qsort(temparray, counters->domains, sizeof(int[2]), cmpdesc); - // Get filter const char* filter = read_setupVarsconf("API_QUERY_LOG_SHOW"); bool showpermitted = true, showblocked = true; diff --git a/src/api/stats_database.c b/src/api/stats_database.c index 8ddfacd9a..af15d1197 100644 --- a/src/api/stats_database.c +++ b/src/api/stats_database.c @@ -72,7 +72,7 @@ int api_stats_database_overTime_history(struct mg_connection *conn) rc, sqlite3_errmsg(FTL_db)); sqlite3_reset(stmt); sqlite3_finalize(stmt); - dblose(); + dbclose(); // Relock shared memory lock_shm(); @@ -93,7 +93,7 @@ int api_stats_database_overTime_history(struct mg_connection *conn) rc, sqlite3_errmsg(FTL_db)); sqlite3_reset(stmt); sqlite3_finalize(stmt); - dblose(); + dbclose(); // Relock shared memory lock_shm(); @@ -114,7 +114,7 @@ int api_stats_database_overTime_history(struct mg_connection *conn) rc, sqlite3_errmsg(FTL_db)); sqlite3_reset(stmt); sqlite3_finalize(stmt); - dblose(); + dbclose(); // Relock shared memory lock_shm(); @@ -182,4 +182,162 @@ int api_stats_database_overTime_history(struct mg_connection *conn) // Re-lock shared memory before returning back to router subroutine lock_shm(); JSON_SEND_OBJECT(json); -} \ No newline at end of file +} + +int api_stats_database_top_domains(bool blocked, struct mg_connection *conn) +{ + int from = 0, until = 0, show = 10; + const struct mg_request_info *request = mg_get_request_info(conn); + if(request->query_string != NULL) + { + int num; + if((num = get_int_var(request->query_string, "from")) > 0) + from = num; + if((num = get_int_var(request->query_string, "until")) > 0) + until = num; + + // Get blocked queries not only for .../top_blocked + // but also for .../top_domains?blocked=true + if((num = get_bool_var(request->query_string, "blocked")) > 0) + blocked = true; + + // Does the user request a non-default number of replies? + // Note: We do not accept zero query requests here + if((num = get_int_var(request->query_string, "show")) > 0) + show = num; + } + + // Check if we received the required information + if(from == 0 || until == 0) + { + cJSON *json = JSON_NEW_OBJ(); + JSON_OBJ_ADD_NUMBER(json, "from", from); + JSON_OBJ_ADD_NUMBER(json, "until", until); + return send_json_error(conn, 400, + "bad_request", + "You need to specify both \"from\" and \"until\" in the request.", + json); + } + + // Unlock shared memory (DNS resolver can continue to work while we're preforming database queries) + unlock_shm(); + + // Open the database (this also locks the database) + dbopen(); + // Build SQL string + const char *querystr; + if(!blocked) + { + querystr = "SELECT domain,COUNT(*) AS cnt FROM queries " + "WHERE (status == 2 OR status == 3) " + "AND timestamp >= :from AND timestamp <= :until " + "GROUP by domain ORDER by cnt DESC " + "LIMIT :show"; + } + else + { + querystr = "SELECT domain,COUNT(*) AS cnt FROM queries " + "WHERE status != 0 AND status != 2 AND status != 3 " + "AND timestamp >= :from AND timestamp <= :until " + "GROUP by domain ORDER by cnt DESC " + "LIMIT :show"; + } + + // Prepare SQLite statement + sqlite3_stmt *stmt; + int rc = sqlite3_prepare_v2(FTL_db, querystr, -1, &stmt, NULL); + if( rc != SQLITE_OK ){ + logg("api_stats_database_overTime_history() - SQL error prepare (%i): %s", + rc, sqlite3_errmsg(FTL_db)); + return false; + } + + // Bind from to prepared statement + if((rc = sqlite3_bind_int(stmt, 1, from)) != SQLITE_OK) + { + logg("api_stats_database_overTime_history(): Failed to bind from (error %d) - %s", + rc, sqlite3_errmsg(FTL_db)); + sqlite3_reset(stmt); + sqlite3_finalize(stmt); + dbclose(); + + // Relock shared memory + lock_shm(); + + cJSON *json = JSON_NEW_OBJ(); + JSON_OBJ_ADD_NUMBER(json, "from", from); + JSON_OBJ_ADD_NUMBER(json, "until", until); + return send_json_error(conn, 500, + "internal_error", + "Failed to bind from", + json); + } + + // Bind until to prepared statement + if((rc = sqlite3_bind_int(stmt, 2, until)) != SQLITE_OK) + { + logg("api_stats_database_overTime_history(): Failed to bind until (error %d) - %s", + rc, sqlite3_errmsg(FTL_db)); + sqlite3_reset(stmt); + sqlite3_finalize(stmt); + dbclose(); + + // Relock shared memory + lock_shm(); + + cJSON *json = JSON_NEW_OBJ(); + JSON_OBJ_ADD_NUMBER(json, "from", from); + JSON_OBJ_ADD_NUMBER(json, "until", until); + return send_json_error(conn, 500, + "internal_error", + "Failed to bind until", + json); + } + + // Bind show to prepared statement + if((rc = sqlite3_bind_int(stmt, 3, show)) != SQLITE_OK) + { + logg("api_stats_database_overTime_history(): Failed to bind show (error %d) - %s", + rc, sqlite3_errmsg(FTL_db)); + sqlite3_reset(stmt); + sqlite3_finalize(stmt); + dbclose(); + + // Relock shared memory + lock_shm(); + + cJSON *json = JSON_NEW_OBJ(); + JSON_OBJ_ADD_NUMBER(json, "from", from); + JSON_OBJ_ADD_NUMBER(json, "until", until); + return send_json_error(conn, 500, + "internal_error", + "Failed to bind show", + json); + } + + // Loop over and accumulate results + cJSON *top_domains = JSON_NEW_ARRAY(); + int total = 0; + while((rc = sqlite3_step(stmt)) == SQLITE_ROW) + { + const char* domain = (char*)sqlite3_column_text(stmt, 0); + const int count = sqlite3_column_int(stmt, 1); + cJSON *domain_item = JSON_NEW_OBJ(); + JSON_OBJ_COPY_STR(domain_item, "domain", domain); + JSON_OBJ_ADD_NUMBER(domain_item, "count", count); + JSON_ARRAY_ADD_ITEM(top_domains, domain_item); + total += count; + } + + // Finalize statement and close (= unlock) database connection + sqlite3_finalize(stmt); + dbclose(); + + // Re-lock shared memory before returning back to router subroutine + lock_shm(); + + cJSON *json = JSON_NEW_OBJ(); + JSON_OBJ_ADD_ITEM(json, "top_domains", top_domains); + JSON_OBJ_ADD_NUMBER(json, (blocked ? "blocked_queries" : "total_queries"), total); + JSON_SEND_OBJECT(json); +} From 76b72bf1b7ac662e9116360f8d6cc178592ef9e1 Mon Sep 17 00:00:00 2001 From: DL6ER Date: Fri, 6 Dec 2019 07:54:43 +0000 Subject: [PATCH 0137/1669] Add /api/stats/database/top_clients by generalizing the top-domains into a top-items function that can process both. Signed-off-by: DL6ER --- src/api/routes.c | 8 +++-- src/api/routes.h | 2 +- src/api/stats_database.c | 68 ++++++++++++++++++++++++++++------------ 3 files changed, 55 insertions(+), 23 deletions(-) diff --git a/src/api/routes.c b/src/api/routes.c index ec2bba73e..476c58879 100644 --- a/src/api/routes.c +++ b/src/api/routes.c @@ -113,11 +113,15 @@ int api_handler(struct mg_connection *conn, void *ignored) } else if(startsWith("/api/stats/database/top_domains", request->local_uri)) { - ret = api_stats_database_top_domains(false, conn); + ret = api_stats_database_top_items(false, true, conn); } else if(startsWith("/api/stats/database/top_blocked", request->local_uri)) { - ret = api_stats_database_top_domains(true, conn); + ret = api_stats_database_top_items(true, true, conn); + } + else if(startsWith("/api/stats/database/top_clients", request->local_uri)) + { + ret = api_stats_database_top_items(false, false, conn); } /******************************** api/version ****************************/ else if(startsWith("/api/version", request->local_uri)) diff --git a/src/api/routes.h b/src/api/routes.h index 62b4c109b..928ca9c59 100644 --- a/src/api/routes.h +++ b/src/api/routes.h @@ -29,7 +29,7 @@ int api_stats_recentblocked(struct mg_connection *conn); // Statistics methods (database) int api_stats_database_overTime_history(struct mg_connection *conn); -int api_stats_database_top_domains(bool blocked, struct mg_connection *conn); +int api_stats_database_top_items(bool blocked, bool domains, struct mg_connection *conn); // FTL methods int api_ftl_clientIP(struct mg_connection *conn); diff --git a/src/api/stats_database.c b/src/api/stats_database.c index af15d1197..efbab9053 100644 --- a/src/api/stats_database.c +++ b/src/api/stats_database.c @@ -22,6 +22,7 @@ int api_stats_database_overTime_history(struct mg_connection *conn) { int from = 0, until = 0; + const int interval = 600; const struct mg_request_info *request = mg_get_request_info(conn); if(request->query_string != NULL) { @@ -49,7 +50,7 @@ int api_stats_database_overTime_history(struct mg_connection *conn) // Open the database (this also locks the database) dbopen(); - const int interval = 600; + // Build SQL string const char *querystr = "SELECT (timestamp/:interval)*:interval interval,status,COUNT(*) FROM queries " "WHERE (status != 0) AND timestamp >= :from AND timestamp <= :until " @@ -184,7 +185,7 @@ int api_stats_database_overTime_history(struct mg_connection *conn) JSON_SEND_OBJECT(json); } -int api_stats_database_top_domains(bool blocked, struct mg_connection *conn) +int api_stats_database_top_items(bool blocked, bool domains, struct mg_connection *conn) { int from = 0, until = 0, show = 10; const struct mg_request_info *request = mg_get_request_info(conn); @@ -224,25 +225,49 @@ int api_stats_database_top_domains(bool blocked, struct mg_connection *conn) // Open the database (this also locks the database) dbopen(); + // Build SQL string const char *querystr; - if(!blocked) + if(domains) { - querystr = "SELECT domain,COUNT(*) AS cnt FROM queries " - "WHERE (status == 2 OR status == 3) " - "AND timestamp >= :from AND timestamp <= :until " - "GROUP by domain ORDER by cnt DESC " - "LIMIT :show"; + if(!blocked) + { + querystr = "SELECT domain,COUNT(*) AS cnt FROM queries " + "WHERE (status == 2 OR status == 3) " + "AND timestamp >= :from AND timestamp <= :until " + "GROUP by domain ORDER by cnt DESC " + "LIMIT :show"; + } + else + { + querystr = "SELECT domain,COUNT(*) AS cnt FROM queries " + "WHERE status != 0 AND status != 2 AND status != 3 " + "AND timestamp >= :from AND timestamp <= :until " + "GROUP by domain ORDER by cnt DESC " + "LIMIT :show"; + } } else { - querystr = "SELECT domain,COUNT(*) AS cnt FROM queries " - "WHERE status != 0 AND status != 2 AND status != 3 " - "AND timestamp >= :from AND timestamp <= :until " - "GROUP by domain ORDER by cnt DESC " - "LIMIT :show"; + if(!blocked) + { + querystr = "SELECT client,COUNT(*) AS cnt FROM queries " + "WHERE (status == 2 OR status == 3) " + "AND timestamp >= :from AND timestamp <= :until " + "GROUP by client ORDER by cnt DESC " + "LIMIT :show"; + } + else + { + querystr = "SELECT client,COUNT(*) AS cnt FROM queries " + "WHERE status != 0 AND status != 2 AND status != 3 " + "AND timestamp >= :from AND timestamp <= :until " + "GROUP by client ORDER by cnt DESC " + "LIMIT :show"; + } } + // Prepare SQLite statement sqlite3_stmt *stmt; int rc = sqlite3_prepare_v2(FTL_db, querystr, -1, &stmt, NULL); @@ -316,16 +341,19 @@ int api_stats_database_top_domains(bool blocked, struct mg_connection *conn) } // Loop over and accumulate results - cJSON *top_domains = JSON_NEW_ARRAY(); + cJSON *top_items = JSON_NEW_ARRAY(); int total = 0; while((rc = sqlite3_step(stmt)) == SQLITE_ROW) { - const char* domain = (char*)sqlite3_column_text(stmt, 0); + const char* string = (char*)sqlite3_column_text(stmt, 0); const int count = sqlite3_column_int(stmt, 1); - cJSON *domain_item = JSON_NEW_OBJ(); - JSON_OBJ_COPY_STR(domain_item, "domain", domain); - JSON_OBJ_ADD_NUMBER(domain_item, "count", count); - JSON_ARRAY_ADD_ITEM(top_domains, domain_item); + cJSON *item = JSON_NEW_OBJ(); + JSON_OBJ_COPY_STR(item, (domains ? "domain" : "ip"), string); + // Add empty name field for top_client requests + if(!domains) + JSON_OBJ_REF_STR(item, "name", ""); + JSON_OBJ_ADD_NUMBER(item, "count", count); + JSON_ARRAY_ADD_ITEM(top_items, item); total += count; } @@ -337,7 +365,7 @@ int api_stats_database_top_domains(bool blocked, struct mg_connection *conn) lock_shm(); cJSON *json = JSON_NEW_OBJ(); - JSON_OBJ_ADD_ITEM(json, "top_domains", top_domains); + JSON_OBJ_ADD_ITEM(json, (domains ? "top_domains" : "top_clients"), top_items); JSON_OBJ_ADD_NUMBER(json, (blocked ? "blocked_queries" : "total_queries"), total); JSON_SEND_OBJECT(json); } From 7c97cb66310169a371b263d46d3014937769b957 Mon Sep 17 00:00:00 2001 From: DL6ER Date: Fri, 6 Dec 2019 08:24:01 +0000 Subject: [PATCH 0138/1669] Add /api/stats/database/summary Signed-off-by: DL6ER --- src/api/routes.c | 4 ++ src/api/routes.h | 1 + src/api/stats.c | 2 + src/api/stats_database.c | 80 ++++++++++++++++++++++++++++++++++++++++ src/database/common.c | 47 +++++++++++++++++++++++ src/database/common.h | 1 + 6 files changed, 135 insertions(+) diff --git a/src/api/routes.c b/src/api/routes.c index 476c58879..b705ffbd8 100644 --- a/src/api/routes.c +++ b/src/api/routes.c @@ -123,6 +123,10 @@ int api_handler(struct mg_connection *conn, void *ignored) { ret = api_stats_database_top_items(false, false, conn); } + else if(startsWith("/api/stats/database/summary", request->local_uri)) + { + ret = api_stats_database_summary(conn); + } /******************************** api/version ****************************/ else if(startsWith("/api/version", request->local_uri)) { diff --git a/src/api/routes.h b/src/api/routes.h index 928ca9c59..329ce775e 100644 --- a/src/api/routes.h +++ b/src/api/routes.h @@ -30,6 +30,7 @@ int api_stats_recentblocked(struct mg_connection *conn); // Statistics methods (database) int api_stats_database_overTime_history(struct mg_connection *conn); int api_stats_database_top_items(bool blocked, bool domains, struct mg_connection *conn); +int api_stats_database_summary(struct mg_connection *conn); // FTL methods int api_ftl_clientIP(struct mg_connection *conn); diff --git a/src/api/stats.c b/src/api/stats.c index 15e50f77c..717a9a999 100644 --- a/src/api/stats.c +++ b/src/api/stats.c @@ -100,6 +100,8 @@ int api_stats_summary(struct mg_connection *conn) JSON_OBJ_ADD_NUMBER(total_queries, "PTR", counters->querytype[TYPE_PTR]); JSON_OBJ_ADD_NUMBER(total_queries, "TXT", counters->querytype[TYPE_TXT]); JSON_OBJ_ADD_ITEM(json, "total_queries", total_queries); + + JSON_OBJ_ADD_NUMBER(json, "sum_queries", counters->queries); cJSON *reply_types = JSON_NEW_OBJ(); JSON_OBJ_ADD_NUMBER(reply_types, "NODATA", counters->reply_NODATA); diff --git a/src/api/stats_database.c b/src/api/stats_database.c index efbab9053..e6a97cad5 100644 --- a/src/api/stats_database.c +++ b/src/api/stats_database.c @@ -369,3 +369,83 @@ int api_stats_database_top_items(bool blocked, bool domains, struct mg_connectio JSON_OBJ_ADD_NUMBER(json, (blocked ? "blocked_queries" : "total_queries"), total); JSON_SEND_OBJECT(json); } + +int api_stats_database_summary(struct mg_connection *conn) +{ + int from = 0, until = 0; + const struct mg_request_info *request = mg_get_request_info(conn); + if(request->query_string != NULL) + { + int num; + if((num = get_int_var(request->query_string, "from")) > 0) + from = num; + if((num = get_int_var(request->query_string, "until")) > 0) + until = num; + } + + // Check if we received the required information + if(from == 0 || until == 0) + { + cJSON *json = JSON_NEW_OBJ(); + JSON_OBJ_ADD_NUMBER(json, "from", from); + JSON_OBJ_ADD_NUMBER(json, "until", until); + return send_json_error(conn, 400, + "bad_request", + "You need to specify both \"from\" and \"until\" in the request.", + json); + } + + // Unlock shared memory (DNS resolver can continue to work while we're preforming database queries) + unlock_shm(); + + // Open the database (this also locks the database) + dbopen(); + + // Perform SQL queries + const char *querystr; + querystr = "SELECT COUNT(*) FROM queries " + "WHERE timestamp >= :from AND timestamp <= :until"; + int sum_queries = db_query_int_from_until(querystr, from, until); + + querystr = "SELECT COUNT(*) FROM queries " + "WHERE timestamp >= :from AND timestamp <= :until " + "AND status != 0 AND status != 2 AND status != 3"; + int blocked_queries = db_query_int_from_until(querystr, from, until); + + querystr = "SELECT COUNT(DISTINCT client) FROM queries " + "WHERE timestamp >= :from AND timestamp <= :until"; + int total_clients = db_query_int_from_until(querystr, from, until); + + float percent_blocked = 1e2f*blocked_queries/sum_queries; + + if(sum_queries < 0 || blocked_queries < 0 || total_clients < 0) + { + // Relock shared memory + lock_shm(); + + cJSON *json = JSON_NEW_OBJ(); + JSON_OBJ_ADD_NUMBER(json, "from", from); + JSON_OBJ_ADD_NUMBER(json, "until", until); + return send_json_error(conn, 500, + "internal_error", + "Internal server error", + json); + } + + // Loop over and accumulate results + cJSON *json = JSON_NEW_OBJ(); + JSON_OBJ_REF_STR(json, "gravity_size", "?"); + JSON_OBJ_ADD_NUMBER(json, "sum_queries", sum_queries); + JSON_OBJ_ADD_NUMBER(json, "blocked_queries", blocked_queries); + JSON_OBJ_ADD_NUMBER(json, "percent_blocked", percent_blocked); + JSON_OBJ_ADD_NUMBER(json, "total_clients", total_clients); + + // Close (= unlock) database connection + dbclose(); + + // Re-lock shared memory before returning back to router subroutine + lock_shm(); + + // Send JSON object + JSON_SEND_OBJECT(json); +} \ No newline at end of file diff --git a/src/database/common.c b/src/database/common.c index af6cac182..c33f62251 100644 --- a/src/database/common.c +++ b/src/database/common.c @@ -481,6 +481,53 @@ int db_query_int(const char* querystr) return result; } +int db_query_int_from_until(const char* querystr, const int from, const int until) +{ + if(!database) + { + logg("db_query_int_from_until(\"%s\") called but database is not available!", querystr); + return DB_FAILED; + } + + sqlite3_stmt* stmt; + int rc = sqlite3_prepare_v2(FTL_db, querystr, -1, &stmt, NULL); + if( rc != SQLITE_OK ){ + logg("db_query_int_from_until(%s) - SQL error prepare (%i): %s", querystr, rc, sqlite3_errmsg(FTL_db)); + check_database(rc); + return DB_FAILED; + } + + // Bind from and until to prepared statement + if((rc = sqlite3_bind_int(stmt, 1, from)) != SQLITE_OK || + (rc = sqlite3_bind_int(stmt, 2, until)) != SQLITE_OK) + { + logg("db_query_int_from_until(%s) - SQL error bind (%i): %s", querystr, rc, sqlite3_errmsg(FTL_db)); + } + + rc = sqlite3_step(stmt); + int result; + + if( rc == SQLITE_ROW ) + { + result = sqlite3_column_int(stmt, 0); + } + else if( rc == SQLITE_DONE ) + { + // No rows available + result = DB_NODATA; + } + else + { + logg("db_query_int_from_until(%s) - SQL error step (%i): %s", querystr, rc, sqlite3_errmsg(FTL_db)); + check_database(rc); + return DB_FAILED; + } + + sqlite3_finalize(stmt); + + return result; +} + long int get_max_query_ID(void) { if(!database || FTL_db == NULL) diff --git a/src/database/common.h b/src/database/common.h index 73273b335..c097304bb 100644 --- a/src/database/common.h +++ b/src/database/common.h @@ -22,6 +22,7 @@ int dbquery(const char *format, ...); bool dbopen(void); void dbclose(void); int db_query_int(const char*); +int db_query_int_from_until(const char* querystr, const int from, const int until); long get_lastID(void); void SQLite3LogCallback(void *pArg, int iErrCode, const char *zMsg); long int get_max_query_ID(void); From cb8131d9ac1af539f3890a92b02f085e45c445f4 Mon Sep 17 00:00:00 2001 From: DL6ER Date: Fri, 6 Dec 2019 09:37:31 +0000 Subject: [PATCH 0139/1669] Add /api/stats/database/overTime/clients Signed-off-by: DL6ER --- src/api/json_macros.h | 5 + src/api/routes.c | 4 + src/api/routes.h | 1 + src/api/stats_database.c | 327 +++++++++++++++++++++++++++++++++++---- 4 files changed, 306 insertions(+), 31 deletions(-) diff --git a/src/api/json_macros.h b/src/api/json_macros.h index 00fed64d4..5334e96eb 100644 --- a/src/api/json_macros.h +++ b/src/api/json_macros.h @@ -73,6 +73,11 @@ cJSON_AddItemToArray(object, number_item); \ } +#define JSON_ARRAY_REPLACE_NUMBER(object, index, number){ \ + cJSON *number_item = cJSON_CreateNumber((double)number); \ + cJSON_ReplaceItemInArray(object, index, number_item); \ +} + #define JSON_ARRAY_REF_STR(array, string){ \ cJSON *string_item = cJSON_CreateStringReference((const char*)string); \ if(string_item == NULL) \ diff --git a/src/api/routes.c b/src/api/routes.c index b705ffbd8..dda19c900 100644 --- a/src/api/routes.c +++ b/src/api/routes.c @@ -127,6 +127,10 @@ int api_handler(struct mg_connection *conn, void *ignored) { ret = api_stats_database_summary(conn); } + else if(startsWith("/api/stats/database/overTime/clients", request->local_uri)) + { + ret = api_stats_database_overTime_clients(conn); + } /******************************** api/version ****************************/ else if(startsWith("/api/version", request->local_uri)) { diff --git a/src/api/routes.h b/src/api/routes.h index 329ce775e..86c02da1a 100644 --- a/src/api/routes.h +++ b/src/api/routes.h @@ -31,6 +31,7 @@ int api_stats_recentblocked(struct mg_connection *conn); int api_stats_database_overTime_history(struct mg_connection *conn); int api_stats_database_top_items(bool blocked, bool domains, struct mg_connection *conn); int api_stats_database_summary(struct mg_connection *conn); +int api_stats_database_overTime_clients(struct mg_connection *conn); // FTL methods int api_ftl_clientIP(struct mg_connection *conn); diff --git a/src/api/stats_database.c b/src/api/stats_database.c index e6a97cad5..29c255366 100644 --- a/src/api/stats_database.c +++ b/src/api/stats_database.c @@ -40,9 +40,9 @@ int api_stats_database_overTime_history(struct mg_connection *conn) JSON_OBJ_ADD_NUMBER(json, "from", from); JSON_OBJ_ADD_NUMBER(json, "until", until); return send_json_error(conn, 400, - "bad_request", - "You need to specify both \"from\" and \"until\" in the request.", - json); + "bad_request", + "You need to specify both \"from\" and \"until\" in the request.", + json); } // Unlock shared memory (DNS resolver can continue to work while we're preforming database queries) @@ -82,9 +82,9 @@ int api_stats_database_overTime_history(struct mg_connection *conn) JSON_OBJ_ADD_NUMBER(json, "from", from); JSON_OBJ_ADD_NUMBER(json, "until", until); return send_json_error(conn, 500, - "internal_error", - "Failed to bind interval", - json); + "internal_error", + "Failed to bind interval", + json); } // Bind from to prepared statement @@ -103,9 +103,9 @@ int api_stats_database_overTime_history(struct mg_connection *conn) JSON_OBJ_ADD_NUMBER(json, "from", from); JSON_OBJ_ADD_NUMBER(json, "until", until); return send_json_error(conn, 500, - "internal_error", - "Failed to bind from", - json); + "internal_error", + "Failed to bind from", + json); } // Bind until to prepared statement @@ -124,9 +124,9 @@ int api_stats_database_overTime_history(struct mg_connection *conn) JSON_OBJ_ADD_NUMBER(json, "from", from); JSON_OBJ_ADD_NUMBER(json, "until", until); return send_json_error(conn, 500, - "internal_error", - "Failed to bind until", - json); + "internal_error", + "Failed to bind until", + json); } // Loop over and accumulate results @@ -173,7 +173,6 @@ int api_stats_database_overTime_history(struct mg_connection *conn) default: break; } - } // Finalize statement and close (= unlock) database connection @@ -215,9 +214,9 @@ int api_stats_database_top_items(bool blocked, bool domains, struct mg_connectio JSON_OBJ_ADD_NUMBER(json, "from", from); JSON_OBJ_ADD_NUMBER(json, "until", until); return send_json_error(conn, 400, - "bad_request", - "You need to specify both \"from\" and \"until\" in the request.", - json); + "bad_request", + "You need to specify both \"from\" and \"until\" in the request.", + json); } // Unlock shared memory (DNS resolver can continue to work while we're preforming database queries) @@ -293,9 +292,9 @@ int api_stats_database_top_items(bool blocked, bool domains, struct mg_connectio JSON_OBJ_ADD_NUMBER(json, "from", from); JSON_OBJ_ADD_NUMBER(json, "until", until); return send_json_error(conn, 500, - "internal_error", - "Failed to bind from", - json); + "internal_error", + "Failed to bind from", + json); } // Bind until to prepared statement @@ -314,9 +313,9 @@ int api_stats_database_top_items(bool blocked, bool domains, struct mg_connectio JSON_OBJ_ADD_NUMBER(json, "from", from); JSON_OBJ_ADD_NUMBER(json, "until", until); return send_json_error(conn, 500, - "internal_error", - "Failed to bind until", - json); + "internal_error", + "Failed to bind until", + json); } // Bind show to prepared statement @@ -335,9 +334,9 @@ int api_stats_database_top_items(bool blocked, bool domains, struct mg_connectio JSON_OBJ_ADD_NUMBER(json, "from", from); JSON_OBJ_ADD_NUMBER(json, "until", until); return send_json_error(conn, 500, - "internal_error", - "Failed to bind show", - json); + "internal_error", + "Failed to bind show", + json); } // Loop over and accumulate results @@ -390,9 +389,9 @@ int api_stats_database_summary(struct mg_connection *conn) JSON_OBJ_ADD_NUMBER(json, "from", from); JSON_OBJ_ADD_NUMBER(json, "until", until); return send_json_error(conn, 400, - "bad_request", - "You need to specify both \"from\" and \"until\" in the request.", - json); + "bad_request", + "You need to specify both \"from\" and \"until\" in the request.", + json); } // Unlock shared memory (DNS resolver can continue to work while we're preforming database queries) @@ -427,9 +426,9 @@ int api_stats_database_summary(struct mg_connection *conn) JSON_OBJ_ADD_NUMBER(json, "from", from); JSON_OBJ_ADD_NUMBER(json, "until", until); return send_json_error(conn, 500, - "internal_error", - "Internal server error", - json); + "internal_error", + "Internal server error", + json); } // Loop over and accumulate results @@ -448,4 +447,270 @@ int api_stats_database_summary(struct mg_connection *conn) // Send JSON object JSON_SEND_OBJECT(json); +} + +int api_stats_database_overTime_clients(struct mg_connection *conn) +{ + int from = 0, until = 0; + const int interval = 600; + const struct mg_request_info *request = mg_get_request_info(conn); + if(request->query_string != NULL) + { + int num; + if((num = get_int_var(request->query_string, "from")) > 0) + from = num; + if((num = get_int_var(request->query_string, "until")) > 0) + until = num; + } + + // Check if we received the required information + if(from == 0 || until == 0) + { + cJSON *json = JSON_NEW_OBJ(); + JSON_OBJ_ADD_NUMBER(json, "from", from); + JSON_OBJ_ADD_NUMBER(json, "until", until); + return send_json_error(conn, 400, + "bad_request", + "You need to specify both \"from\" and \"until\" in the request.", + json); + } + + // Unlock shared memory (DNS resolver can continue to work while we're preforming database queries) + unlock_shm(); + + // Open the database (this also locks the database) + dbopen(); + + const char *querystr = "SELECT DISTINCT client FROM queries " + "WHERE timestamp >= :from AND timestamp <= :until " + "ORDER BY client DESC"; + + // Prepare SQLite statement + sqlite3_stmt *stmt; + int rc = sqlite3_prepare_v2(FTL_db, querystr, -1, &stmt, NULL); + if( rc != SQLITE_OK ){ + logg("api_stats_database_overTime_clients() - SQL error prepare outer (%i): %s", + rc, sqlite3_errmsg(FTL_db)); + + // Relock shared memory + lock_shm(); + + cJSON *json = JSON_NEW_OBJ(); + JSON_OBJ_ADD_NUMBER(json, "from", from); + JSON_OBJ_ADD_NUMBER(json, "until", until); + return send_json_error(conn, 500, + "internal_error", + "Failed to prepare outer statement", + json); + } + + // Bind from to prepared statement + if((rc = sqlite3_bind_int(stmt, 1, from)) != SQLITE_OK) + { + logg("api_stats_database_overTime_clients(): Failed to bind from (error %d) - %s", + rc, sqlite3_errmsg(FTL_db)); + sqlite3_reset(stmt); + sqlite3_finalize(stmt); + dbclose(); + + // Relock shared memory + lock_shm(); + + cJSON *json = JSON_NEW_OBJ(); + JSON_OBJ_ADD_NUMBER(json, "from", from); + JSON_OBJ_ADD_NUMBER(json, "until", until); + return send_json_error(conn, 500, + "internal_error", + "Failed to bind from", + json); + } + + // Bind until to prepared statement + if((rc = sqlite3_bind_int(stmt, 2, until)) != SQLITE_OK) + { + logg("api_stats_database_overTime_clients(): Failed to bind until (error %d) - %s", + rc, sqlite3_errmsg(FTL_db)); + sqlite3_reset(stmt); + sqlite3_finalize(stmt); + dbclose(); + + // Relock shared memory + lock_shm(); + + cJSON *json = JSON_NEW_OBJ(); + JSON_OBJ_ADD_NUMBER(json, "from", from); + JSON_OBJ_ADD_NUMBER(json, "until", until); + return send_json_error(conn, 500, + "internal_error", + "Failed to bind until", + json); + } + + // Loop over clients and accumulate results + cJSON *clients = JSON_NEW_ARRAY(); + unsigned int num_clients = 0; + while((rc = sqlite3_step(stmt)) == SQLITE_ROW) + { + const char* client = (char*)sqlite3_column_text(stmt, 0); + cJSON *item = JSON_NEW_OBJ(); + JSON_OBJ_COPY_STR(item, "ip", client); + JSON_OBJ_REF_STR(item, "name", ""); + JSON_ARRAY_ADD_ITEM(clients, item); + num_clients++; + } + sqlite3_finalize(stmt); + + // Build SQL string + querystr = "SELECT (timestamp/:interval)*:interval interval,client,COUNT(*) FROM queries " + "WHERE timestamp >= :from AND timestamp <= :until " + "GROUP BY interval,client ORDER BY interval DESC, client DESC"; + + // Prepare SQLite statement + rc = sqlite3_prepare_v2(FTL_db, querystr, -1, &stmt, NULL); + if( rc != SQLITE_OK ){ + logg("api_stats_database_overTime_clients() - SQL error prepare (%i): %s", + rc, sqlite3_errmsg(FTL_db)); + + // Relock shared memory + lock_shm(); + + cJSON *json = JSON_NEW_OBJ(); + JSON_OBJ_ADD_NUMBER(json, "from", from); + JSON_OBJ_ADD_NUMBER(json, "until", until); + return send_json_error(conn, 500, + "internal_error", + "Failed to prepare inner statement", + json); + } + + // Bind interval to prepared statement + if((rc = sqlite3_bind_int(stmt, 1, interval)) != SQLITE_OK) + { + logg("api_stats_database_overTime_clients(): Failed to bind interval (error %d) - %s", + rc, sqlite3_errmsg(FTL_db)); + sqlite3_reset(stmt); + sqlite3_finalize(stmt); + dbclose(); + + // Relock shared memory + lock_shm(); + + cJSON *json = JSON_NEW_OBJ(); + JSON_OBJ_ADD_NUMBER(json, "from", from); + JSON_OBJ_ADD_NUMBER(json, "until", until); + return send_json_error(conn, 500, + "internal_error", + "Failed to bind interval", + json); + } + + // Bind from to prepared statement + if((rc = sqlite3_bind_int(stmt, 2, from)) != SQLITE_OK) + { + logg("api_stats_database_overTime_clients(): Failed to bind from (error %d) - %s", + rc, sqlite3_errmsg(FTL_db)); + sqlite3_reset(stmt); + sqlite3_finalize(stmt); + dbclose(); + + // Relock shared memory + lock_shm(); + + cJSON *json = JSON_NEW_OBJ(); + JSON_OBJ_ADD_NUMBER(json, "from", from); + JSON_OBJ_ADD_NUMBER(json, "until", until); + return send_json_error(conn, 500, + "internal_error", + "Failed to bind from", + json); + } + + // Bind until to prepared statement + if((rc = sqlite3_bind_int(stmt, 3, until)) != SQLITE_OK) + { + logg("api_stats_database_overTime_clients(): Failed to bind until (error %d) - %s", + rc, sqlite3_errmsg(FTL_db)); + sqlite3_reset(stmt); + sqlite3_finalize(stmt); + dbclose(); + + // Relock shared memory + lock_shm(); + + cJSON *json = JSON_NEW_OBJ(); + JSON_OBJ_ADD_NUMBER(json, "from", from); + JSON_OBJ_ADD_NUMBER(json, "until", until); + return send_json_error(conn, 500, + "internal_error", + "Failed to bind until", + json); + } + + cJSON *over_time = JSON_NEW_ARRAY(); + cJSON *item = NULL; + cJSON *data = NULL; + int previous_timestamp = 0; + while((rc = sqlite3_step(stmt)) == SQLITE_ROW) + { + const int timestamp = sqlite3_column_int(stmt, 0); + // Begin new array item for each new timestamp + if(timestamp != previous_timestamp) + { + previous_timestamp = timestamp; + if(item != NULL && data != NULL) + { + JSON_OBJ_ADD_ITEM(item, "data", data); + JSON_ARRAY_ADD_ITEM(over_time, item); + } + + item = JSON_NEW_OBJ(); + data = JSON_NEW_ARRAY(); + // Prefill data with zeroes + // We have to do this as not all clients may have + // have been active in any time interval we're + // querying + for(unsigned int i = 0; i < num_clients; i++) + { + JSON_ARRAY_ADD_NUMBER(data, 0); + } + JSON_OBJ_ADD_NUMBER(item, "timestamp", timestamp); + } + + const char *client = (char*)sqlite3_column_text(stmt, 1); + const int count = sqlite3_column_int(stmt, 2); + + // Find index of this client in known clients... + unsigned int idx = 0; + for(; idx < num_clients; idx++) + { + const char *array_client = cJSON_GetStringValue( + cJSON_GetObjectItem( + cJSON_GetArrayItem(clients, idx), "ip")); + if(array_client != NULL && + strcmp(client, array_client) == 0) + { + break; + } + } + + if(idx == num_clients) + { + // Not found + continue; + } + + // ... and replace corresponding number in data array + JSON_ARRAY_REPLACE_NUMBER(data, idx, count); + } + + // Finalize statement and close (= unlock) database connection + sqlite3_finalize(stmt); + dbclose(); + + // Re-lock shared memory before returning back to router subroutine + lock_shm(); + cJSON *json = JSON_NEW_OBJ(); + JSON_OBJ_ADD_ITEM(json, "over_time", over_time); + JSON_OBJ_ADD_ITEM(json, "clients", clients); + JSON_SEND_OBJECT(json); } \ No newline at end of file From a7500f1d5838d6ae46edd2667ba15f2e2fdf2393 Mon Sep 17 00:00:00 2001 From: DL6ER Date: Fri, 6 Dec 2019 09:51:21 +0000 Subject: [PATCH 0140/1669] Add /api/stats/database/query_types Signed-off-by: DL6ER --- src/api/routes.c | 4 +++ src/api/routes.h | 1 + src/api/stats_database.c | 60 +++++++++++++++++++++++++++++++++++++++- src/database/common.c | 48 ++++++++++++++++++++++++++++++++ src/database/common.h | 1 + 5 files changed, 113 insertions(+), 1 deletion(-) diff --git a/src/api/routes.c b/src/api/routes.c index dda19c900..b06e284e6 100644 --- a/src/api/routes.c +++ b/src/api/routes.c @@ -131,6 +131,10 @@ int api_handler(struct mg_connection *conn, void *ignored) { ret = api_stats_database_overTime_clients(conn); } + else if(startsWith("/api/stats/database/query_types", request->local_uri)) + { + ret = api_stats_database_query_types(conn); + } /******************************** api/version ****************************/ else if(startsWith("/api/version", request->local_uri)) { diff --git a/src/api/routes.h b/src/api/routes.h index 86c02da1a..ced3d4128 100644 --- a/src/api/routes.h +++ b/src/api/routes.h @@ -32,6 +32,7 @@ int api_stats_database_overTime_history(struct mg_connection *conn); int api_stats_database_top_items(bool blocked, bool domains, struct mg_connection *conn); int api_stats_database_summary(struct mg_connection *conn); int api_stats_database_overTime_clients(struct mg_connection *conn); +int api_stats_database_query_types(struct mg_connection *conn); // FTL methods int api_ftl_clientIP(struct mg_connection *conn); diff --git a/src/api/stats_database.c b/src/api/stats_database.c index 29c255366..4ee5ceba2 100644 --- a/src/api/stats_database.c +++ b/src/api/stats_database.c @@ -713,4 +713,62 @@ int api_stats_database_overTime_clients(struct mg_connection *conn) JSON_OBJ_ADD_ITEM(json, "over_time", over_time); JSON_OBJ_ADD_ITEM(json, "clients", clients); JSON_SEND_OBJECT(json); -} \ No newline at end of file +} + +static const char *querytypes[8] = {"A","AAAA","ANY","SRV","SOA","PTR","TXT","UNKN"}; +int api_stats_database_query_types(struct mg_connection *conn) +{ + int from = 0, until = 0; + const struct mg_request_info *request = mg_get_request_info(conn); + if(request->query_string != NULL) + { + int num; + if((num = get_int_var(request->query_string, "from")) > 0) + from = num; + if((num = get_int_var(request->query_string, "until")) > 0) + until = num; + } + + // Check if we received the required information + if(from == 0 || until == 0) + { + cJSON *json = JSON_NEW_OBJ(); + JSON_OBJ_ADD_NUMBER(json, "from", from); + JSON_OBJ_ADD_NUMBER(json, "until", until); + return send_json_error(conn, 400, + "bad_request", + "You need to specify both \"from\" and \"until\" in the request.", + json); + } + + // Unlock shared memory (DNS resolver can continue to work while we're preforming database queries) + unlock_shm(); + + // Open the database (this also locks the database) + dbopen(); + + // Perform SQL queries + cJSON *json = JSON_NEW_ARRAY(); + for(int i=0; i < TYPE_MAX; i++) + { + const char *querystr = "SELECT COUNT(*) FROM queries " + "WHERE timestamp >= :from AND timestamp <= :until " + "AND type = :type"; + // Add 1 as type is stored one-based in the database for historical reasons + int count = db_query_int_from_until_type(querystr, from, until, i+1); + + cJSON *item = JSON_NEW_OBJ(); + JSON_OBJ_REF_STR(item, "name", querytypes[i]); + JSON_OBJ_ADD_NUMBER(item, "count", count); + JSON_ARRAY_ADD_ITEM(json, item); + } + + // Close (= unlock) database connection + dbclose(); + + // Re-lock shared memory before returning back to router subroutine + lock_shm(); + + // Send JSON object + JSON_SEND_OBJECT(json); +} diff --git a/src/database/common.c b/src/database/common.c index c33f62251..a7ffc43d2 100644 --- a/src/database/common.c +++ b/src/database/common.c @@ -528,6 +528,54 @@ int db_query_int_from_until(const char* querystr, const int from, const int unti return result; } +int db_query_int_from_until_type(const char* querystr, const int from, const int until, const int type) +{ + if(!database) + { + logg("db_query_int_from_until(\"%s\") called but database is not available!", querystr); + return DB_FAILED; + } + + sqlite3_stmt* stmt; + int rc = sqlite3_prepare_v2(FTL_db, querystr, -1, &stmt, NULL); + if( rc != SQLITE_OK ){ + logg("db_query_int_from_until(%s) - SQL error prepare (%i): %s", querystr, rc, sqlite3_errmsg(FTL_db)); + check_database(rc); + return DB_FAILED; + } + + // Bind from and until to prepared statement + if((rc = sqlite3_bind_int(stmt, 1, from)) != SQLITE_OK || + (rc = sqlite3_bind_int(stmt, 2, until)) != SQLITE_OK || + (rc = sqlite3_bind_int(stmt, 3, type)) != SQLITE_OK) + { + logg("db_query_int_from_until(%s) - SQL error bind (%i): %s", querystr, rc, sqlite3_errmsg(FTL_db)); + } + + rc = sqlite3_step(stmt); + int result; + + if( rc == SQLITE_ROW ) + { + result = sqlite3_column_int(stmt, 0); + } + else if( rc == SQLITE_DONE ) + { + // No rows available + result = DB_NODATA; + } + else + { + logg("db_query_int_from_until(%s) - SQL error step (%i): %s", querystr, rc, sqlite3_errmsg(FTL_db)); + check_database(rc); + return DB_FAILED; + } + + sqlite3_finalize(stmt); + + return result; +} + long int get_max_query_ID(void) { if(!database || FTL_db == NULL) diff --git a/src/database/common.h b/src/database/common.h index c097304bb..604f33f9d 100644 --- a/src/database/common.h +++ b/src/database/common.h @@ -23,6 +23,7 @@ bool dbopen(void); void dbclose(void); int db_query_int(const char*); int db_query_int_from_until(const char* querystr, const int from, const int until); +int db_query_int_from_until_type(const char* querystr, const int from, const int until, const int type); long get_lastID(void); void SQLite3LogCallback(void *pArg, int iErrCode, const char *zMsg); long int get_max_query_ID(void); From ef5cfe0a17be0f90a878297a2ca58d949ef16313 Mon Sep 17 00:00:00 2001 From: DL6ER Date: Fri, 6 Dec 2019 10:33:22 +0000 Subject: [PATCH 0141/1669] Add /api/stats/database/upstreams Signed-off-by: DL6ER --- src/api/auth.c | 3 +- src/api/json_macros.h | 13 +++ src/api/routes.c | 4 + src/api/routes.h | 1 + src/api/stats_database.c | 184 ++++++++++++++++++++++++++++++++++++++- src/shmem.c | 4 +- 6 files changed, 205 insertions(+), 4 deletions(-) diff --git a/src/api/auth.c b/src/api/auth.c index 138720c4a..fd9ad1250 100644 --- a/src/api/auth.c +++ b/src/api/auth.c @@ -141,7 +141,8 @@ int api_auth(struct mg_connection *conn) for(unsigned int i = 0; i < API_MAX_CLIENTS; i++) { // Expired slow, mark as unused - if(auth_data[i].valid_until < now) + if(auth_data[i].used && + auth_data[i].valid_until < now) { if(config.debug & DEBUG_API) { diff --git a/src/api/json_macros.h b/src/api/json_macros.h index 5334e96eb..4a71fc1d4 100644 --- a/src/api/json_macros.h +++ b/src/api/json_macros.h @@ -9,6 +9,8 @@ * Please see LICENSE file for your rights under this license. */ #include "../cJSON/cJSON.h" +// logg() +#include "../log.h" #define JSON_NEW_OBJ() cJSON_CreateObject(); #define JSON_NEW_ARRAY() cJSON_CreateArray(); @@ -21,6 +23,7 @@ { \ cJSON_Delete(object); \ send_http_internal_error(conn); \ + logg("JSON_OBJ_COPY_STR FAILED (key: \"%s\", string: \"%s\")!", key, string); \ return 500; \ } \ cJSON_AddItemToObject(object, key, string_item); \ @@ -32,6 +35,7 @@ { \ cJSON_Delete(object); \ send_http_internal_error(conn); \ + logg("JSON_OBJ_REF_STR FAILED (key: \"%s\", string: \"%s\")!", key, string); \ return 500; \ } \ cJSON_AddItemToObject(object, key, string_item); \ @@ -42,6 +46,7 @@ { \ cJSON_Delete(object); \ send_http_internal_error(conn); \ + logg("JSON_OBJ_ADD_NUMBER FAILED!"); \ return 500; \ } \ } @@ -52,6 +57,7 @@ { \ cJSON_Delete(object); \ send_http_internal_error(conn); \ + logg("JSON_OBJ_ADD_NULL FAILED!"); \ return 500; \ } \ cJSON_AddItemToObject(object, key, null_item); \ @@ -63,6 +69,7 @@ { \ cJSON_Delete(object); \ send_http_internal_error(conn); \ + logg("JSON_OBJ_ADD_BOOL FAILED!"); \ return 500; \ } \ cJSON_AddItemToObject(object, key, bool_item); \ @@ -84,6 +91,7 @@ { \ cJSON_Delete(array); \ send_http_internal_error(conn); \ + logg("JSON_ARRAY_REF_STR FAILED!"); \ return 500; \ } \ cJSON_AddItemToArray(array, string_item); \ @@ -95,6 +103,7 @@ { \ cJSON_Delete(array); \ send_http_internal_error(conn); \ + logg("JSON_ARRAY_COPY_STR FAILED!"); \ return 500; \ } \ cJSON_AddItemToArray(array, string_item); \ @@ -113,6 +122,7 @@ { \ cJSON_Delete(object); \ send_http_internal_error(conn); \ + logg("JSON_SEND_OBJECT FAILED!"); \ return 500; \ } \ send_http(conn, "application/json; charset=utf-8", msg); \ @@ -126,6 +136,7 @@ { \ cJSON_Delete(object); \ send_http_internal_error(conn); \ + logg("JSON_SEND_OBJECT_CODE FAILED!"); \ return 500; \ } \ send_http_code(conn, "application/json; charset=utf-8", code, msg); \ @@ -139,6 +150,7 @@ { \ cJSON_Delete(object); \ send_http_internal_error(conn); \ + logg("JSON_SEND_OBJECT_AND_HEADERS FAILED!"); \ return 500; \ } \ send_http(conn, "application/json; charset=utf-8", additional_headers, msg); \ @@ -153,6 +165,7 @@ { \ cJSON_Delete(object); \ send_http_internal_error(conn); \ + logg("JSON_SEND_OBJECT_AND_HEADERS_CODE FAILED!"); \ return 500; \ } \ send_http_code(conn, "application/json; charset=utf-8", additional_headers, code, msg); \ diff --git a/src/api/routes.c b/src/api/routes.c index b06e284e6..8e7cc8fca 100644 --- a/src/api/routes.c +++ b/src/api/routes.c @@ -135,6 +135,10 @@ int api_handler(struct mg_connection *conn, void *ignored) { ret = api_stats_database_query_types(conn); } + else if(startsWith("/api/stats/database/upstreams", request->local_uri)) + { + ret = api_stats_database_upstreams(conn); + } /******************************** api/version ****************************/ else if(startsWith("/api/version", request->local_uri)) { diff --git a/src/api/routes.h b/src/api/routes.h index ced3d4128..bc81d2e59 100644 --- a/src/api/routes.h +++ b/src/api/routes.h @@ -33,6 +33,7 @@ int api_stats_database_top_items(bool blocked, bool domains, struct mg_connectio int api_stats_database_summary(struct mg_connection *conn); int api_stats_database_overTime_clients(struct mg_connection *conn); int api_stats_database_query_types(struct mg_connection *conn); +int api_stats_database_upstreams(struct mg_connection *conn); // FTL methods int api_ftl_clientIP(struct mg_connection *conn); diff --git a/src/api/stats_database.c b/src/api/stats_database.c index 4ee5ceba2..604156115 100644 --- a/src/api/stats_database.c +++ b/src/api/stats_database.c @@ -273,7 +273,20 @@ int api_stats_database_top_items(bool blocked, bool domains, struct mg_connectio if( rc != SQLITE_OK ){ logg("api_stats_database_overTime_history() - SQL error prepare (%i): %s", rc, sqlite3_errmsg(FTL_db)); - return false; + + dbclose(); + + // Relock shared memory + lock_shm(); + + cJSON *json = JSON_NEW_OBJ(); + JSON_OBJ_ADD_NUMBER(json, "from", from); + JSON_OBJ_ADD_NUMBER(json, "until", until); + JSON_OBJ_REF_STR(json, "querystr", querystr); + return send_json_error(conn, 500, + "internal_error", + "Failed to prepare query string", + json); } // Bind from to prepared statement @@ -350,7 +363,9 @@ int api_stats_database_top_items(bool blocked, bool domains, struct mg_connectio JSON_OBJ_COPY_STR(item, (domains ? "domain" : "ip"), string); // Add empty name field for top_client requests if(!domains) + { JSON_OBJ_REF_STR(item, "name", ""); + } JSON_OBJ_ADD_NUMBER(item, "count", count); JSON_ARRAY_ADD_ITEM(top_items, item); total += count; @@ -419,6 +434,10 @@ int api_stats_database_summary(struct mg_connection *conn) if(sum_queries < 0 || blocked_queries < 0 || total_clients < 0) { + + // Close (= unlock) database connection + dbclose(); + // Relock shared memory lock_shm(); @@ -772,3 +791,166 @@ int api_stats_database_query_types(struct mg_connection *conn) // Send JSON object JSON_SEND_OBJECT(json); } + + +int api_stats_database_upstreams(struct mg_connection *conn) +{ + int from = 0, until = 0; + const struct mg_request_info *request = mg_get_request_info(conn); + if(request->query_string != NULL) + { + int num; + if((num = get_int_var(request->query_string, "from")) > 0) + from = num; + if((num = get_int_var(request->query_string, "until")) > 0) + until = num; + } + + // Check if we received the required information + if(from == 0 || until == 0) + { + cJSON *json = JSON_NEW_OBJ(); + JSON_OBJ_ADD_NUMBER(json, "from", from); + JSON_OBJ_ADD_NUMBER(json, "until", until); + return send_json_error(conn, 400, + "bad_request", + "You need to specify both \"from\" and \"until\" in the request.", + json); + } + + // Unlock shared memory (DNS resolver can continue to work while we're preforming database queries) + unlock_shm(); + + // Open the database (this also locks the database) + dbopen(); + + // Perform simple SQL queries + unsigned int sum_queries = 0; + const char *querystr; + querystr = "SELECT COUNT(*) FROM queries " + "WHERE timestamp >= :from AND timestamp <= :until " + "AND status = 2"; + int forwarded_queries = db_query_int_from_until(querystr, from, until); + sum_queries = forwarded_queries; + + querystr = "SELECT COUNT(*) FROM queries " + "WHERE timestamp >= :from AND timestamp <= :until " + "AND status = 3"; + int cached_queries = db_query_int_from_until(querystr, from, until); + sum_queries += cached_queries; + + querystr = "SELECT COUNT(*) FROM queries " + "WHERE timestamp >= :from AND timestamp <= :until " + "AND status != 0 AND status != 2 AND status != 3"; + int blocked_queries = db_query_int_from_until(querystr, from, until); + sum_queries += blocked_queries; + + querystr = "SELECT forward,COUNT(*) FROM queries " + "WHERE timestamp >= :from AND timestamp <= :until " + "AND forward IS NOT NULL " + "GROUP BY forward ORDER BY forward"; + + // Prepare SQLite statement + sqlite3_stmt *stmt; + int rc = sqlite3_prepare_v2(FTL_db, querystr, -1, &stmt, NULL); + if( rc != SQLITE_OK ){ + logg("api_stats_database_overTime_clients() - SQL error prepare (%i): %s", + rc, sqlite3_errmsg(FTL_db)); + + // Relock shared memory + lock_shm(); + + cJSON *json = JSON_NEW_OBJ(); + JSON_OBJ_ADD_NUMBER(json, "from", from); + JSON_OBJ_ADD_NUMBER(json, "until", until); + return send_json_error(conn, 500, + "internal_error", + "Failed to prepare statement", + json); + } + + // Bind from to prepared statement + if((rc = sqlite3_bind_int(stmt, 1, from)) != SQLITE_OK) + { + logg("api_stats_database_overTime_clients(): Failed to bind from (error %d) - %s", + rc, sqlite3_errmsg(FTL_db)); + sqlite3_reset(stmt); + sqlite3_finalize(stmt); + dbclose(); + + // Relock shared memory + lock_shm(); + + cJSON *json = JSON_NEW_OBJ(); + JSON_OBJ_ADD_NUMBER(json, "from", from); + JSON_OBJ_ADD_NUMBER(json, "until", until); + return send_json_error(conn, 500, + "internal_error", + "Failed to bind from", + json); + } + + // Bind until to prepared statement + if((rc = sqlite3_bind_int(stmt, 2, until)) != SQLITE_OK) + { + logg("api_stats_database_overTime_clients(): Failed to bind until (error %d) - %s", + rc, sqlite3_errmsg(FTL_db)); + sqlite3_reset(stmt); + sqlite3_finalize(stmt); + dbclose(); + + // Relock shared memory + lock_shm(); + + cJSON *json = JSON_NEW_OBJ(); + JSON_OBJ_ADD_NUMBER(json, "from", from); + JSON_OBJ_ADD_NUMBER(json, "until", until); + return send_json_error(conn, 500, + "internal_error", + "Failed to bind until", + json); + } + + // Loop over clients and accumulate results + cJSON *upstreams = JSON_NEW_ARRAY(); + while((rc = sqlite3_step(stmt)) == SQLITE_ROW) + { + const char* upstream = (char*)sqlite3_column_text(stmt, 0); + const int count = sqlite3_column_int(stmt, 1); + cJSON *item = JSON_NEW_OBJ(); + JSON_OBJ_COPY_STR(item, "ip", upstream); + JSON_OBJ_REF_STR(item, "name", ""); + JSON_OBJ_ADD_NUMBER(item, "count", count); + JSON_ARRAY_ADD_ITEM(upstreams, item); + sum_queries += count; + } + sqlite3_finalize(stmt); + + // Add cache and blocklist as upstreams + cJSON *cached = JSON_NEW_OBJ(); + JSON_OBJ_REF_STR(cached, "ip", ""); + JSON_OBJ_REF_STR(cached, "name", "cache"); + JSON_OBJ_ADD_NUMBER(cached, "count", cached_queries); + JSON_ARRAY_ADD_ITEM(upstreams, cached); + + logg("%d / %d / %d", forwarded_queries, cached_queries, blocked_queries); + + cJSON *blocked = JSON_NEW_OBJ(); + JSON_OBJ_REF_STR(blocked, "ip", ""); + JSON_OBJ_REF_STR(blocked, "name", "blocklist"); + JSON_OBJ_ADD_NUMBER(blocked, "count", blocked_queries); + JSON_ARRAY_ADD_ITEM(upstreams, blocked); + + // Close (= unlock) database connection + dbclose(); + + // Re-lock shared memory before returning back to router subroutine + lock_shm(); + + // Send JSON object + cJSON *json = JSON_NEW_OBJ(); + JSON_OBJ_ADD_ITEM(json, "upstreams", upstreams); + JSON_OBJ_ADD_NUMBER(json, "forwarded_queries", forwarded_queries); + JSON_OBJ_ADD_NUMBER(json, "total_queries", sum_queries); + JSON_SEND_OBJECT(json); +} diff --git a/src/shmem.c b/src/shmem.c index e825dc6e2..46f6bd61d 100644 --- a/src/shmem.c +++ b/src/shmem.c @@ -252,7 +252,7 @@ void _lock_shm(const char* func, const int line, const char * file) { } if(result != 0) - logg("Failed to obtain SHM lock: %s", strerror(result)); + logg("Failed to obtain SHM lock: %s in %s() (%s:%i)", strerror(result), func, file, line); } void _unlock_shm(const char* func, const int line, const char * file) { @@ -262,7 +262,7 @@ void _unlock_shm(const char* func, const int line, const char * file) { logg("Removed lock in %s() (%s:%i)", func, file, line); if(result != 0) - logg("Failed to unlock SHM lock: %s", strerror(result)); + logg("Failed to unlock SHM lock: %s in %s() (%s:%i)", strerror(result), func, file, line); } bool init_shmem(void) From 4dacd1c75904750225e1e1d20c3969723908ccde Mon Sep 17 00:00:00 2001 From: DL6ER Date: Fri, 6 Dec 2019 10:36:29 +0000 Subject: [PATCH 0142/1669] Make JSON string macros robust against being called with NULL strings. Signed-off-by: DL6ER --- src/api/json_macros.h | 40 ++++++++++++++++++++++++++++++++++++---- 1 file changed, 36 insertions(+), 4 deletions(-) diff --git a/src/api/json_macros.h b/src/api/json_macros.h index 4a71fc1d4..22693df76 100644 --- a/src/api/json_macros.h +++ b/src/api/json_macros.h @@ -18,7 +18,15 @@ #define JSON_ARRAY_ADD_ITEM(array, item) cJSON_AddItemToArray(array, item); #define JSON_OBJ_COPY_STR(object, key, string){ \ - cJSON *string_item = cJSON_CreateString((const char*)string); \ + cJSON *string_item = NULL; \ + if(string != NULL) \ + { \ + string_item = cJSON_CreateString((const char*)string); \ + } \ + else \ + { \ + string_item = cJSON_CreateNull(); \ + } \ if(string_item == NULL) \ { \ cJSON_Delete(object); \ @@ -30,7 +38,15 @@ } #define JSON_OBJ_REF_STR(object, key, string){ \ - cJSON *string_item = cJSON_CreateStringReference((const char*)string); \ + cJSON *string_item = NULL; \ + if(string != NULL) \ + { \ + string_item = cJSON_CreateStringReference((const char*)string); \ + } \ + else \ + { \ + string_item = cJSON_CreateNull(); \ + } \ if(string_item == NULL) \ { \ cJSON_Delete(object); \ @@ -86,7 +102,15 @@ } #define JSON_ARRAY_REF_STR(array, string){ \ - cJSON *string_item = cJSON_CreateStringReference((const char*)string); \ + cJSON *string_item = NULL; \ + if(string != NULL) \ + { \ + string_item = cJSON_CreateStringReference((const char*)string); \ + } \ + else \ + { \ + string_item = cJSON_CreateNull(); \ + } \ if(string_item == NULL) \ { \ cJSON_Delete(array); \ @@ -98,7 +122,15 @@ } #define JSON_ARRAY_COPY_STR(array, string){ \ - cJSON *string_item = cJSON_CreateString((const char*)string); \ + cJSON *string_item = NULL; \ + if(string != NULL) \ + { \ + string_item = cJSON_CreateString((const char*)string); \ + } \ + else \ + { \ + string_item = cJSON_CreateNull(); \ + } \ if(string_item == NULL) \ { \ cJSON_Delete(array); \ From 5bdf2b96cc75488b6cdca13c07881cf0772ca3e1 Mon Sep 17 00:00:00 2001 From: DL6ER Date: Fri, 6 Dec 2019 12:09:20 +0000 Subject: [PATCH 0143/1669] Fix forwarded queries counting logic quirk. Signed-off-by: DL6ER --- src/api/stats_database.c | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/src/api/stats_database.c b/src/api/stats_database.c index 604156115..eea8caf48 100644 --- a/src/api/stats_database.c +++ b/src/api/stats_database.c @@ -827,12 +827,6 @@ int api_stats_database_upstreams(struct mg_connection *conn) // Perform simple SQL queries unsigned int sum_queries = 0; const char *querystr; - querystr = "SELECT COUNT(*) FROM queries " - "WHERE timestamp >= :from AND timestamp <= :until " - "AND status = 2"; - int forwarded_queries = db_query_int_from_until(querystr, from, until); - sum_queries = forwarded_queries; - querystr = "SELECT COUNT(*) FROM queries " "WHERE timestamp >= :from AND timestamp <= :until " "AND status = 3"; @@ -913,6 +907,7 @@ int api_stats_database_upstreams(struct mg_connection *conn) // Loop over clients and accumulate results cJSON *upstreams = JSON_NEW_ARRAY(); + int forwarded_queries = 0; while((rc = sqlite3_step(stmt)) == SQLITE_ROW) { const char* upstream = (char*)sqlite3_column_text(stmt, 0); @@ -922,10 +917,13 @@ int api_stats_database_upstreams(struct mg_connection *conn) JSON_OBJ_REF_STR(item, "name", ""); JSON_OBJ_ADD_NUMBER(item, "count", count); JSON_ARRAY_ADD_ITEM(upstreams, item); - sum_queries += count; + forwarded_queries += count; } sqlite3_finalize(stmt); + // Add number of forwarded queries to total query count + sum_queries += forwarded_queries; + // Add cache and blocklist as upstreams cJSON *cached = JSON_NEW_OBJ(); JSON_OBJ_REF_STR(cached, "ip", ""); @@ -933,8 +931,6 @@ int api_stats_database_upstreams(struct mg_connection *conn) JSON_OBJ_ADD_NUMBER(cached, "count", cached_queries); JSON_ARRAY_ADD_ITEM(upstreams, cached); - logg("%d / %d / %d", forwarded_queries, cached_queries, blocked_queries); - cJSON *blocked = JSON_NEW_OBJ(); JSON_OBJ_REF_STR(blocked, "ip", ""); JSON_OBJ_REF_STR(blocked, "name", "blocklist"); From 6a4040e8a5dbea465a5e4583b91b4a3154a293cf Mon Sep 17 00:00:00 2001 From: DL6ER Date: Mon, 9 Dec 2019 07:45:29 +0000 Subject: [PATCH 0144/1669] Minor optimizations to domain list handling routines (reading, adding, and removing) Signed-off-by: DL6ER --- src/api/dns.c | 57 +++++++++++++++++++++------------------------------ 1 file changed, 23 insertions(+), 34 deletions(-) diff --git a/src/api/dns.c b/src/api/dns.c index a8adcc76e..314c70979 100644 --- a/src/api/dns.c +++ b/src/api/dns.c @@ -24,6 +24,8 @@ #include // set_blockingmode_timer() #include "timers.h" +// struct config +#include "config.h" int api_dns_status(struct mg_connection *conn) { @@ -113,23 +115,28 @@ int api_dns_status(struct mg_connection *conn) } } -static int api_dns_somelist_read(struct mg_connection *conn, bool exact, bool whitelist) +static int getTableType(bool whitelist, bool exact) { - int table; if(whitelist) if(exact) - table = GRAVITY_DOMAINLIST_EXACT_WHITELIST; + return GRAVITY_DOMAINLIST_EXACT_WHITELIST; else - table = GRAVITY_DOMAINLIST_REGEX_WHITELIST; + return GRAVITY_DOMAINLIST_REGEX_WHITELIST; else if(exact) - table = GRAVITY_DOMAINLIST_EXACT_BLACKLIST; + return GRAVITY_DOMAINLIST_EXACT_BLACKLIST; else - table = GRAVITY_DOMAINLIST_REGEX_BLACKLIST; + return GRAVITY_DOMAINLIST_REGEX_BLACKLIST; +} - if(!gravityDB_readTable(table)) +static int api_dns_somelist_read(struct mg_connection *conn, bool exact, bool whitelist) +{ + int type = getTableType(whitelist, exact); + if(!gravityDB_readTable(type)) { cJSON *json = JSON_NEW_OBJ(); + JSON_OBJ_REF_STR(json, "database_location", FTLfiles.gravity_db); + JSON_OBJ_ADD_NUMBER(json, "type", type); return send_json_error(conn, 500, "database_error", "Could not read domain from database table", @@ -185,20 +192,9 @@ static int api_dns_somelist_POST(struct mg_connection *conn, } const char *domain = elem->valuestring; - int table; - if(whitelist) - if(exact) - table = GRAVITY_DOMAINLIST_EXACT_WHITELIST; - else - table = GRAVITY_DOMAINLIST_REGEX_WHITELIST; - else - if(exact) - table = GRAVITY_DOMAINLIST_EXACT_BLACKLIST; - else - table = GRAVITY_DOMAINLIST_REGEX_BLACKLIST; - cJSON *json = JSON_NEW_OBJ(); - if(gravityDB_addToTable(table, domain)) + int type = getTableType(whitelist, exact); + if(gravityDB_addToTable(type, domain)) { JSON_OBJ_REF_STR(json, "key", "added"); JSON_OBJ_COPY_STR(json, "domain", domain); @@ -208,10 +204,12 @@ static int api_dns_somelist_POST(struct mg_connection *conn, else { JSON_OBJ_COPY_STR(json, "domain", domain); + JSON_OBJ_REF_STR(json, "database_location", FTLfiles.gravity_db); + JSON_OBJ_ADD_NUMBER(json, "type", type); cJSON_Delete(obj); return send_json_error(conn, 500, "database_error", - "Could not add domain to database table", + "Could not add domain to gravity database", json); } } @@ -228,20 +226,9 @@ static int api_dns_somelist_DELETE(struct mg_connection *conn, // Decode URL (necessar for regular expressions, harmless for domains) mg_url_decode(encoded_uri, strlen(encoded_uri), domain, sizeof(domain)-1u, 0); - int table; - if(whitelist) - if(exact) - table = GRAVITY_DOMAINLIST_EXACT_WHITELIST; - else - table = GRAVITY_DOMAINLIST_REGEX_WHITELIST; - else - if(exact) - table = GRAVITY_DOMAINLIST_EXACT_BLACKLIST; - else - table = GRAVITY_DOMAINLIST_REGEX_BLACKLIST; - cJSON *json = JSON_NEW_OBJ(); - if(gravityDB_delFromTable(table, domain)) + int type = getTableType(whitelist, exact); + if(gravityDB_delFromTable(type, domain)) { JSON_OBJ_REF_STR(json, "key", "removed"); JSON_OBJ_REF_STR(json, "domain", domain); @@ -250,6 +237,8 @@ static int api_dns_somelist_DELETE(struct mg_connection *conn, else { JSON_OBJ_REF_STR(json, "domain", domain); + JSON_OBJ_REF_STR(json, "database_location", FTLfiles.gravity_db); + JSON_OBJ_ADD_NUMBER(json, "type", type); return send_json_error(conn, 500, "database_error", "Could not remove domain from database table", From 4c6d94c739c64be7a37fafa90616d217ee5db826 Mon Sep 17 00:00:00 2001 From: DL6ER Date: Wed, 11 Dec 2019 21:49:49 +0000 Subject: [PATCH 0145/1669] Fix failed git auto-merge. Signed-off-by: DL6ER --- src/database/common.c | 4 ---- src/dnsmasq_interface.h | 3 ++- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/src/database/common.c b/src/database/common.c index a7ffc43d2..472f88646 100644 --- a/src/database/common.c +++ b/src/database/common.c @@ -493,7 +493,6 @@ int db_query_int_from_until(const char* querystr, const int from, const int unti int rc = sqlite3_prepare_v2(FTL_db, querystr, -1, &stmt, NULL); if( rc != SQLITE_OK ){ logg("db_query_int_from_until(%s) - SQL error prepare (%i): %s", querystr, rc, sqlite3_errmsg(FTL_db)); - check_database(rc); return DB_FAILED; } @@ -519,7 +518,6 @@ int db_query_int_from_until(const char* querystr, const int from, const int unti else { logg("db_query_int_from_until(%s) - SQL error step (%i): %s", querystr, rc, sqlite3_errmsg(FTL_db)); - check_database(rc); return DB_FAILED; } @@ -540,7 +538,6 @@ int db_query_int_from_until_type(const char* querystr, const int from, const int int rc = sqlite3_prepare_v2(FTL_db, querystr, -1, &stmt, NULL); if( rc != SQLITE_OK ){ logg("db_query_int_from_until(%s) - SQL error prepare (%i): %s", querystr, rc, sqlite3_errmsg(FTL_db)); - check_database(rc); return DB_FAILED; } @@ -567,7 +564,6 @@ int db_query_int_from_until_type(const char* querystr, const int from, const int else { logg("db_query_int_from_until(%s) - SQL error step (%i): %s", querystr, rc, sqlite3_errmsg(FTL_db)); - check_database(rc); return DB_FAILED; } diff --git a/src/dnsmasq_interface.h b/src/dnsmasq_interface.h index 33e44db46..9a0d6f01a 100644 --- a/src/dnsmasq_interface.h +++ b/src/dnsmasq_interface.h @@ -55,7 +55,8 @@ void FTL_TCP_worker_terminating(void); void set_debug_dnsmasq_lines(char enabled); extern char debug_dnsmasq_lines; - void FTL_dnsmasq_log(const char *payload, const int length); +int FTL_database_import(int cache_size, struct crec **rhash, int hashsz); + #endif // DNSMASQ_INTERFACE_H From 5d54d3412c898da083c8f985cd72b28d8cb6846d Mon Sep 17 00:00:00 2001 From: DL6ER Date: Wed, 25 Dec 2019 15:14:17 +0000 Subject: [PATCH 0146/1669] Compute average response times for used upstream destinations. Signed-off-by: DL6ER --- Makefile | 3 ++- src/api/stats.c | 19 +++++++++++++++ src/datastructure.c | 4 ++++ src/datastructure.h | 3 +++ src/dnsmasq_interface.c | 8 +++++++ src/math.c | 52 +++++++++++++++++++++++++++++++++++++++++ src/math.h | 25 ++++++++++++++++++++ 7 files changed, 113 insertions(+), 1 deletion(-) create mode 100644 src/math.c create mode 100644 src/math.h diff --git a/Makefile b/Makefile index 0826caa5c..bcaad1905 100644 --- a/Makefile +++ b/Makefile @@ -18,7 +18,8 @@ FTL_DEPS = *.h database/*.h api/*.h version.h FTL_DB_OBJ = database/common.o database/query-table.o database/network-table.o database/gravity-db.o database/database-thread.o \ database/sqlite3-ext.o database/message-table.o FTL_API_OBJ = api/http-common.o api/routes.o api/ftl.o api/stats.o api/dns.o api/version.o api/auth.o api/settings.o api/stats_database.o -FTL_OBJ = $(FTL_DB_OBJ) $(FTL_API_OBJ) main.o memory.o log.o daemon.o datastructure.o signals.o files.o setupVars.o args.o gc.o config.o dnsmasq_interface.o resolve.o regex.o shmem.o capabilities.o overTime.o timers.o vector.o +FTL_OBJ = $(FTL_DB_OBJ) $(FTL_API_OBJ) main.o memory.o log.o daemon.o datastructure.o signals.o files.o setupVars.o args.o gc.o config.o \ + dnsmasq_interface.o resolve.o regex.o shmem.o capabilities.o overTime.o timers.o vector.o math.o DNSMASQ_DEPS = config.h dhcp-protocol.h dns-protocol.h radv-protocol.h dhcp6-protocol.h dnsmasq.h ip6addr.h metrics.h ../dnsmasq_interface.h DNSMASQ_OBJ = arp.o dbus.o domain.o lease.o outpacket.o rrfilter.o auth.o dhcp6.o edns0.o log.o poll.o slaac.o blockdata.o dhcp.o forward.o \ diff --git a/src/api/stats.c b/src/api/stats.c index 717a9a999..017b98a8c 100644 --- a/src/api/stats.c +++ b/src/api/stats.c @@ -26,6 +26,8 @@ #include "overTime.h" // enum REGEX #include "regex_r.h" +// my_sqrt() +#include "../math.h" /* qsort comparision function (count field), sort ASC static int __attribute__((pure)) cmpasc(const void *a, const void *b) @@ -471,6 +473,7 @@ int api_stats_upstreams(struct mg_connection *conn) { int count = 0; const char* ip, *name; + double responsetime = 0.0, uncertainty = 0.0; if(i == -2) { @@ -503,6 +506,20 @@ int api_stats_upstreams(struct mg_connection *conn) // Get percentage count = forward->count; + + // Compute average response time and uncertainty (unit: seconds) + if(forward->responses > 0) + { + // Wehave to multiply runcertainty by 1e-4 to get seconds + responsetime = 1e-4 * forward->rtime / forward->responses; + } + if(forward->responses > 1) + { + // The actual value will be somewhere in a neighborhood around the mean value. + // This neighborhood of values is the uncertainty in the mean. + // Wehave to multiply runcertainty by (1e-4)^2 to get seconds + uncertainty = my_sqrt(1e-8 * forward->rtuncertainty / forward->responses / (forward->responses-1)); + } } // Send data: @@ -514,6 +531,8 @@ int api_stats_upstreams(struct mg_connection *conn) JSON_OBJ_REF_STR(upstream, "name", name); JSON_OBJ_REF_STR(upstream, "ip", ip); JSON_OBJ_ADD_NUMBER(upstream, "count", count); + JSON_OBJ_ADD_NUMBER(upstream, "responsetime", responsetime); + JSON_OBJ_ADD_NUMBER(upstream, "uncertainty", uncertainty); JSON_ARRAY_ADD_ITEM(upstreams, upstream); } } diff --git a/src/datastructure.c b/src/datastructure.c index eb3adf08e..c3f8afa5e 100644 --- a/src/datastructure.c +++ b/src/datastructure.c @@ -76,6 +76,10 @@ int findUpstreamID(const char * upstreamString, const bool count) // to be done separately to be non-blocking upstream->new = true; upstream->namepos = 0; // 0 -> string with length zero + // Initialize response time values + upstream->rtime = 0u; + upstream->rtuncertainty = 0u; + upstream->responses = 0u; // Increase counter by one counters->upstreams++; diff --git a/src/datastructure.h b/src/datastructure.h index b573f4b58..4deebb07f 100644 --- a/src/datastructure.h +++ b/src/datastructure.h @@ -47,6 +47,9 @@ typedef struct { typedef struct { unsigned char magic; bool new; + unsigned long rtime; + unsigned long rtuncertainty; + unsigned int responses; int count; int failed; size_t ippos; diff --git a/src/dnsmasq_interface.c b/src/dnsmasq_interface.c index 165b34e58..d829562b9 100644 --- a/src/dnsmasq_interface.c +++ b/src/dnsmasq_interface.c @@ -961,6 +961,14 @@ void _FTL_reply(const unsigned short flags, const char *name, const union all_ad } else if((flags & F_FORWARD) && isExactMatch) { + // Save query response time + upstreamsData *upstream = getUpstream(query->upstreamID, true); + upstream->responses++; + unsigned long rtime = converttimeval(response) - query->response; + upstream->rtime += rtime; + unsigned long mean = upstream->rtime / upstream->responses; + upstream->rtuncertainty += (mean - rtime)*(mean - rtime); + // Only proceed if query is not already known // to have been blocked by Quad9 if(query->reply != QUERY_EXTERNAL_BLOCKED_IP && diff --git a/src/math.c b/src/math.c new file mode 100644 index 000000000..d0a66be6c --- /dev/null +++ b/src/math.c @@ -0,0 +1,52 @@ +/* Pi-hole: A black hole for Internet advertisements +* (c) 2019 Pi-hole, LLC (https://pi-hole.net) +* Network-wide ad blocking via your own hardware. +* +* FTL Engine +* Math Implementation +* +* This file is copyright under the latest version of the EUPL. +* Please see LICENSE file for your rights under this license. */ + +#include "math.h" + +// Recursive function that returns square root +// of a number with precision upto 5 decimal places +double Square(double n, double i, double j) +{ + double mid = (i + j) / 2; + double mul = mid * mid; + + if ((mul - n)*(mul - n) < (SQRT_PRECISION * SQRT_PRECISION)) + { + return mid; + } + else if (mul < n) + { + return Square(n, mid, j); + } + else + { + return Square(n, i, mid); + } +} + + // Function to find the square root of n +double my_sqrt(double n) +{ + // While the square root is not found + for(double i = 1.0; true; i++) + { + if ((i - n)*(i - n) < (SQRT_PRECISION * SQRT_PRECISION)) + { + // If n is a perfect square + return i; + } + else if (i * i > n) + { + // Square root will lie in the interval i-1 and i + return Square(n, i - 1, i); + } + i++; + } +} \ No newline at end of file diff --git a/src/math.h b/src/math.h new file mode 100644 index 000000000..ec59b48e3 --- /dev/null +++ b/src/math.h @@ -0,0 +1,25 @@ +/* Pi-hole: A black hole for Internet advertisements +* (c) 2019 Pi-hole, LLC (https://pi-hole.net) +* Network-wide ad blocking via your own hardware. +* +* FTL Engine +* Math Prototypes +* +* This file is copyright under the latest version of the EUPL. +* Please see LICENSE file for your rights under this license. */ + +#ifndef MATH_H +#define MATH_H + +#include +#include + +#define SQRT_PRECISION 1e-5 + +// Recursive function that returns square root +double Square(double n, double i, double j); + +// Function to find the square root of n +double my_sqrt(double n); + +#endif //MATH_H \ No newline at end of file From 0aaa3237b1471dc99f3b4158643a0d9a3272b8f8 Mon Sep 17 00:00:00 2001 From: DL6ER Date: Wed, 25 Dec 2019 17:02:25 +0000 Subject: [PATCH 0147/1669] Exclude database lookup time from computed forward destination delay. Signed-off-by: DL6ER --- src/datastructure.h | 1 + src/dnsmasq_interface.c | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/src/datastructure.h b/src/datastructure.h index 4deebb07f..cd6d2a093 100644 --- a/src/datastructure.h +++ b/src/datastructure.h @@ -38,6 +38,7 @@ typedef struct { int id; // the ID is a (signed) int in dnsmasq, so no need for a long int here int CNAME_domainID; // only valid if query has a CNAME blocking status unsigned long response; // saved in units of 1/10 milliseconds (1 = 0.1ms, 2 = 0.2ms, 2500 = 250.0ms, etc.) + unsigned long forwardresponse; // saved in units of 1/10 milliseconds (1 = 0.1ms, 2 = 0.2ms, 2500 = 250.0ms, etc.) int64_t db; unsigned int timeidx; bool whitelisted; diff --git a/src/dnsmasq_interface.c b/src/dnsmasq_interface.c index d829562b9..437b85e91 100644 --- a/src/dnsmasq_interface.c +++ b/src/dnsmasq_interface.c @@ -559,6 +559,7 @@ bool _FTL_new_query(const unsigned int flags, const char *name, query->id = id; query->complete = false; query->response = converttimeval(request); + query->forwardresponse = 0u; // Initialize reply type query->reply = REPLY_UNKNOWN; // Store DNSSEC result for this domain @@ -795,6 +796,10 @@ void _FTL_forwarded(const unsigned int flags, const char *name, const union all_ // Update counter for forwarded queries counters->forwarded++; + struct timeval request; + gettimeofday(&request, 0); + query->forwardresponse = converttimeval(request); + // Release allocated memory free(upstreamIP); From 0b680952a4548d27360755d403df52d21447f50c Mon Sep 17 00:00:00 2001 From: DL6ER Date: Wed, 25 Dec 2019 19:03:30 +0000 Subject: [PATCH 0148/1669] Add const attribute to new sqrt routines. Signed-off-by: DL6ER --- src/math.c | 9 ++++----- src/math.h | 4 ++-- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/src/math.c b/src/math.c index d0a66be6c..b7a3a81ff 100644 --- a/src/math.c +++ b/src/math.c @@ -11,8 +11,7 @@ #include "math.h" // Recursive function that returns square root -// of a number with precision upto 5 decimal places -double Square(double n, double i, double j) +__attribute__((const)) double Square(const double n, const double i, const double j) { double mid = (i + j) / 2; double mul = mid * mid; @@ -31,8 +30,8 @@ double Square(double n, double i, double j) } } - // Function to find the square root of n -double my_sqrt(double n) +// Function to find the square root of n +__attribute__((const)) double my_sqrt(const double n) { // While the square root is not found for(double i = 1.0; true; i++) @@ -44,7 +43,7 @@ double my_sqrt(double n) } else if (i * i > n) { - // Square root will lie in the interval i-1 and i + // Square root is in [i-1, i] return Square(n, i - 1, i); } i++; diff --git a/src/math.h b/src/math.h index ec59b48e3..35eac2388 100644 --- a/src/math.h +++ b/src/math.h @@ -17,9 +17,9 @@ #define SQRT_PRECISION 1e-5 // Recursive function that returns square root -double Square(double n, double i, double j); +double Square(const double n, const double i, const double j) __attribute__((const)); // Function to find the square root of n -double my_sqrt(double n); +double my_sqrt(const double n) __attribute__((const)); #endif //MATH_H \ No newline at end of file From d878499d67cbb23d14af88579918f4fbf4e0ba36 Mon Sep 17 00:00:00 2001 From: DL6ER Date: Tue, 7 Apr 2020 15:07:51 +0200 Subject: [PATCH 0149/1669] Update cJSON to 1.7.13 Signed-off-by: DL6ER --- src/cJSON/cJSON.c | 209 ++++++++++++++++++++++++++++++++++------------ src/cJSON/cJSON.h | 29 ++++--- 2 files changed, 172 insertions(+), 66 deletions(-) diff --git a/src/cJSON/cJSON.c b/src/cJSON/cJSON.c index 8a7750821..a5d39878c 100644 --- a/src/cJSON/cJSON.c +++ b/src/cJSON/cJSON.c @@ -43,6 +43,7 @@ #include #include #include +#include #ifdef ENABLE_LOCALES #include @@ -68,6 +69,18 @@ #endif #define false ((cJSON_bool)0) +/* define isnan and isinf for ANSI C, if in C99 or above, isnan and isinf has been defined in math.h */ +#ifndef isinf +#define isinf(d) (isnan((d - d)) && !isnan(d)) +#endif +#ifndef isnan +#define isnan(d) (d != d) +#endif + +#ifndef NAN +#define NAN 0.0/0.0 +#endif + typedef struct { const unsigned char *json; size_t position; @@ -79,16 +92,28 @@ CJSON_PUBLIC(const char *) cJSON_GetErrorPtr(void) return (const char*) (global_error.json + global_error.position); } -CJSON_PUBLIC(char *) cJSON_GetStringValue(const cJSON * const item) { - if (!cJSON_IsString(item)) { +CJSON_PUBLIC(char *) cJSON_GetStringValue(cJSON *item) +{ + if (!cJSON_IsString(item)) + { return NULL; } return item->valuestring; } +CJSON_PUBLIC(double) cJSON_GetNumberValue(cJSON *item) +{ + if (!cJSON_IsNumber(item)) + { + return NAN; + } + + return item->valuedouble; +} + /* This is a safeguard to prevent copy-pasters from using incompatible C and header files */ -#if (CJSON_VERSION_MAJOR != 1) || (CJSON_VERSION_MINOR != 7) || (CJSON_VERSION_PATCH != 12) +#if (CJSON_VERSION_MAJOR != 1) || (CJSON_VERSION_MINOR != 7) || (CJSON_VERSION_PATCH != 13) #error cJSON.h and cJSON.c have different versions. Make sure that both have the same. #endif @@ -368,6 +393,33 @@ CJSON_PUBLIC(double) cJSON_SetNumberHelper(cJSON *object, double number) return object->valuedouble = number; } +CJSON_PUBLIC(char*) cJSON_SetValuestring(cJSON *object, const char *valuestring) +{ + char *copy = NULL; + /* if object's type is not cJSON_String or is cJSON_IsReference, it should not set valuestring */ + if (!(object->type & cJSON_String) || (object->type & cJSON_IsReference)) + { + return NULL; + } + if (strlen(valuestring) <= strlen(object->valuestring)) + { + strcpy(object->valuestring, valuestring); + return object->valuestring; + } + copy = (char*) cJSON_strdup((const unsigned char*)valuestring, &global_hooks); + if (copy == NULL) + { + return NULL; + } + if (object->valuestring != NULL) + { + cJSON_free(object->valuestring); + } + object->valuestring = copy; + + return copy; +} + typedef struct { unsigned char *buffer; @@ -480,6 +532,13 @@ static void update_offset(printbuffer * const buffer) buffer->offset += strlen((const char*)buffer_pointer); } +/* securely comparison of floating-point variables */ +static cJSON_bool compare_double(double a, double b) +{ + double maxVal = fabs(a) > fabs(b) ? fabs(a) : fabs(b); + return (fabs(a - b) <= maxVal * DBL_EPSILON); +} + /* Render the number nicely from the given item into a string. */ static cJSON_bool print_number(const cJSON * const item, printbuffer * const output_buffer) { @@ -497,7 +556,7 @@ static cJSON_bool print_number(const cJSON * const item, printbuffer * const out } /* This checks for NaN and Infinity */ - if ((d * 0) != 0) + if (isnan(d) || isinf(d)) { length = sprintf((char*)number_buffer, "null"); } @@ -507,7 +566,7 @@ static cJSON_bool print_number(const cJSON * const item, printbuffer * const out length = sprintf((char*)number_buffer, "%1.15g", d); /* Check whether the original double can be recovered */ - if ((sscanf((char*)number_buffer, "%lg", &test) != 1) || ((double)test != d)) + if ((sscanf((char*)number_buffer, "%lg", &test) != 1) || !compare_double((double)test, d)) { /* If not, print with 17 decimal places of precision */ length = sprintf((char*)number_buffer, "%1.17g", d); @@ -977,6 +1036,11 @@ static parse_buffer *buffer_skip_whitespace(parse_buffer * const buffer) return NULL; } + if (cannot_access_at_index(buffer, 0)) + { + return buffer; + } + while (can_access_at_index(buffer, 0) && (buffer_at_offset(buffer)[0] <= 32)) { buffer->offset++; @@ -1006,8 +1070,23 @@ static parse_buffer *skip_utf8_bom(parse_buffer * const buffer) return buffer; } -/* Parse an object - create a new root, and populate. */ CJSON_PUBLIC(cJSON *) cJSON_ParseWithOpts(const char *value, const char **return_parse_end, cJSON_bool require_null_terminated) +{ + size_t buffer_length; + + if (NULL == value) + { + return NULL; + } + + /* Adding null character size due to require_null_terminated. */ + buffer_length = strlen(value) + sizeof(""); + + return cJSON_ParseWithLengthOpts(value, buffer_length, return_parse_end, require_null_terminated); +} + +/* Parse an object - create a new root, and populate. */ +CJSON_PUBLIC(cJSON *) cJSON_ParseWithLengthOpts(const char *value, size_t buffer_length, const char **return_parse_end, cJSON_bool require_null_terminated) { parse_buffer buffer = { 0, 0, 0, 0, { 0, 0, 0 } }; cJSON *item = NULL; @@ -1016,13 +1095,13 @@ CJSON_PUBLIC(cJSON *) cJSON_ParseWithOpts(const char *value, const char **return global_error.json = NULL; global_error.position = 0; - if (value == NULL) + if (value == NULL || 0 == buffer_length) { goto fail; } buffer.content = (const unsigned char*)value; - buffer.length = strlen((const char*)value) + sizeof(""); + buffer.length = buffer_length; buffer.offset = 0; buffer.hooks = global_hooks; @@ -1092,7 +1171,12 @@ CJSON_PUBLIC(cJSON *) cJSON_Parse(const char *value) return cJSON_ParseWithOpts(value, 0, 0); } -#define cjson_min(a, b) ((a < b) ? a : b) +CJSON_PUBLIC(cJSON *) cJSON_ParseWithLength(const char *value, size_t buffer_length) +{ + return cJSON_ParseWithLengthOpts(value, buffer_length, 0, 0); +} + +#define cjson_min(a, b) (((a) < (b)) ? (a) : (b)) static unsigned char *print(const cJSON * const item, cJSON_bool format, const internal_hooks * const hooks) { @@ -1199,20 +1283,20 @@ CJSON_PUBLIC(char *) cJSON_PrintBuffered(const cJSON *item, int prebuffer, cJSON return (char*)p.buffer; } -CJSON_PUBLIC(cJSON_bool) cJSON_PrintPreallocated(cJSON *item, char *buf, const int len, const cJSON_bool fmt) +CJSON_PUBLIC(cJSON_bool) cJSON_PrintPreallocated(cJSON *item, char *buffer, const int length, const cJSON_bool format) { printbuffer p = { 0, 0, 0, 0, 0, 0, { 0, 0, 0 } }; - if ((len < 0) || (buf == NULL)) + if ((length < 0) || (buffer == NULL)) { return false; } - p.buffer = (unsigned char*)buf; - p.length = (size_t)len; + p.buffer = (unsigned char*)buffer; + p.length = (size_t)length; p.offset = 0; p.noalloc = true; - p.format = fmt; + p.format = format; p.hooks = global_hooks; return print_value(item, &p); @@ -1859,35 +1943,48 @@ static cJSON_bool add_item_to_array(cJSON *array, cJSON *item) { cJSON *child = NULL; - if ((item == NULL) || (array == NULL)) + if ((item == NULL) || (array == NULL) || (array == item)) { return false; } child = array->child; - + /* + * To find the last item in array quickly, we use prev in array + */ if (child == NULL) { /* list is empty, start new one */ array->child = item; + item->prev = item; + item->next = NULL; } else { /* append to the end */ - while (child->next) + if (child->prev) { - child = child->next; + suffix_object(child->prev, item); + array->child->prev = item; + } + else + { + while (child->next) + { + child = child->next; + } + suffix_object(child, item); + array->child->prev = item; } - suffix_object(child, item); } return true; } /* Add item to array/object. */ -CJSON_PUBLIC(void) cJSON_AddItemToArray(cJSON *array, cJSON *item) +CJSON_PUBLIC(cJSON_bool) cJSON_AddItemToArray(cJSON *array, cJSON *item) { - add_item_to_array(array, item); + return add_item_to_array(array, item); } #if defined(__clang__) || (defined(__GNUC__) && ((__GNUC__ > 4) || ((__GNUC__ == 4) && (__GNUC_MINOR__ > 5)))) @@ -1911,7 +2008,7 @@ static cJSON_bool add_item_to_object(cJSON * const object, const char * const st char *new_key = NULL; int new_type = cJSON_Invalid; - if ((object == NULL) || (string == NULL) || (item == NULL)) + if ((object == NULL) || (string == NULL) || (item == NULL) || (object == item)) { return false; } @@ -1943,35 +2040,35 @@ static cJSON_bool add_item_to_object(cJSON * const object, const char * const st return add_item_to_array(object, item); } -CJSON_PUBLIC(void) cJSON_AddItemToObject(cJSON *object, const char *string, cJSON *item) +CJSON_PUBLIC(cJSON_bool) cJSON_AddItemToObject(cJSON *object, const char *string, cJSON *item) { - add_item_to_object(object, string, item, &global_hooks, false); + return add_item_to_object(object, string, item, &global_hooks, false); } /* Add an item to an object with constant string as key */ -CJSON_PUBLIC(void) cJSON_AddItemToObjectCS(cJSON *object, const char *string, cJSON *item) +CJSON_PUBLIC(cJSON_bool) cJSON_AddItemToObjectCS(cJSON *object, const char *string, cJSON *item) { - add_item_to_object(object, string, item, &global_hooks, true); + return add_item_to_object(object, string, item, &global_hooks, true); } -CJSON_PUBLIC(void) cJSON_AddItemReferenceToArray(cJSON *array, cJSON *item) +CJSON_PUBLIC(cJSON_bool) cJSON_AddItemReferenceToArray(cJSON *array, cJSON *item) { if (array == NULL) { - return; + return false; } - add_item_to_array(array, create_reference(item, &global_hooks)); + return add_item_to_array(array, create_reference(item, &global_hooks)); } -CJSON_PUBLIC(void) cJSON_AddItemReferenceToObject(cJSON *object, const char *string, cJSON *item) +CJSON_PUBLIC(cJSON_bool) cJSON_AddItemReferenceToObject(cJSON *object, const char *string, cJSON *item) { if ((object == NULL) || (string == NULL)) { - return; + return false; } - add_item_to_object(object, string, create_reference(item, &global_hooks), &global_hooks, false); + return add_item_to_object(object, string, create_reference(item, &global_hooks), &global_hooks, false); } CJSON_PUBLIC(cJSON*) cJSON_AddNullToObject(cJSON * const object, const char * const name) @@ -2089,7 +2186,7 @@ CJSON_PUBLIC(cJSON *) cJSON_DetachItemViaPointer(cJSON *parent, cJSON * const it return NULL; } - if (item->prev != NULL) + if (item != parent->child) { /* not the first element */ item->prev->next = item->next; @@ -2152,20 +2249,19 @@ CJSON_PUBLIC(void) cJSON_DeleteItemFromObjectCaseSensitive(cJSON *object, const } /* Replace array/object items with new ones. */ -CJSON_PUBLIC(void) cJSON_InsertItemInArray(cJSON *array, int which, cJSON *newitem) +CJSON_PUBLIC(cJSON_bool) cJSON_InsertItemInArray(cJSON *array, int which, cJSON *newitem) { cJSON *after_inserted = NULL; if (which < 0) { - return; + return false; } after_inserted = get_array_item(array, (size_t)which); if (after_inserted == NULL) { - add_item_to_array(array, newitem); - return; + return add_item_to_array(array, newitem); } newitem->next = after_inserted; @@ -2179,6 +2275,7 @@ CJSON_PUBLIC(void) cJSON_InsertItemInArray(cJSON *array, int which, cJSON *newit { newitem->prev->next = newitem; } + return true; } CJSON_PUBLIC(cJSON_bool) cJSON_ReplaceItemViaPointer(cJSON * const parent, cJSON * const item, cJSON * replacement) @@ -2200,14 +2297,20 @@ CJSON_PUBLIC(cJSON_bool) cJSON_ReplaceItemViaPointer(cJSON * const parent, cJSON { replacement->next->prev = replacement; } - if (replacement->prev != NULL) - { - replacement->prev->next = replacement; - } if (parent->child == item) { parent->child = replacement; } + else + { /* + * To find the last item in array quickly, we use prev in array. + * We can't modify the last item's next pointer where this item was the parent's child + */ + if (replacement->prev != NULL) + { + replacement->prev->next = replacement; + } + } item->next = NULL; item->prev = NULL; @@ -2216,14 +2319,14 @@ CJSON_PUBLIC(cJSON_bool) cJSON_ReplaceItemViaPointer(cJSON * const parent, cJSON return true; } -CJSON_PUBLIC(void) cJSON_ReplaceItemInArray(cJSON *array, int which, cJSON *newitem) +CJSON_PUBLIC(cJSON_bool) cJSON_ReplaceItemInArray(cJSON *array, int which, cJSON *newitem) { if (which < 0) { - return; + return false; } - cJSON_ReplaceItemViaPointer(array, get_array_item(array, (size_t)which), newitem); + return cJSON_ReplaceItemViaPointer(array, get_array_item(array, (size_t)which), newitem); } static cJSON_bool replace_item_in_object(cJSON *object, const char *string, cJSON *replacement, cJSON_bool case_sensitive) @@ -2241,19 +2344,17 @@ static cJSON_bool replace_item_in_object(cJSON *object, const char *string, cJSO replacement->string = (char*)cJSON_strdup((const unsigned char*)string, &global_hooks); replacement->type &= ~cJSON_StringIsConst; - cJSON_ReplaceItemViaPointer(object, get_object_item(object, string, case_sensitive), replacement); - - return true; + return cJSON_ReplaceItemViaPointer(object, get_object_item(object, string, case_sensitive), replacement); } -CJSON_PUBLIC(void) cJSON_ReplaceItemInObject(cJSON *object, const char *string, cJSON *newitem) +CJSON_PUBLIC(cJSON_bool) cJSON_ReplaceItemInObject(cJSON *object, const char *string, cJSON *newitem) { - replace_item_in_object(object, string, newitem, false); + return replace_item_in_object(object, string, newitem, false); } -CJSON_PUBLIC(void) cJSON_ReplaceItemInObjectCaseSensitive(cJSON *object, const char *string, cJSON *newitem) +CJSON_PUBLIC(cJSON_bool) cJSON_ReplaceItemInObjectCaseSensitive(cJSON *object, const char *string, cJSON *newitem) { - replace_item_in_object(object, string, newitem, true); + return replace_item_in_object(object, string, newitem, true); } /* Create basic types: */ @@ -2290,12 +2391,12 @@ CJSON_PUBLIC(cJSON *) cJSON_CreateFalse(void) return item; } -CJSON_PUBLIC(cJSON *) cJSON_CreateBool(cJSON_bool b) +CJSON_PUBLIC(cJSON *) cJSON_CreateBool(cJSON_bool boolean) { cJSON *item = cJSON_New_Item(&global_hooks); if(item) { - item->type = b ? cJSON_True : cJSON_False; + item->type = boolean ? cJSON_True : cJSON_False; } return item; @@ -2876,7 +2977,7 @@ CJSON_PUBLIC(cJSON_bool) cJSON_Compare(const cJSON * const a, const cJSON * cons return true; case cJSON_Number: - if (a->valuedouble == b->valuedouble) + if (compare_double(a->valuedouble, b->valuedouble)) { return true; } diff --git a/src/cJSON/cJSON.h b/src/cJSON/cJSON.h index 753b1ad45..0c6c8e070 100644 --- a/src/cJSON/cJSON.h +++ b/src/cJSON/cJSON.h @@ -81,7 +81,7 @@ then using the CJSON_API_VISIBILITY flag to "export" the same symbols the way CJ /* project version */ #define CJSON_VERSION_MAJOR 1 #define CJSON_VERSION_MINOR 7 -#define CJSON_VERSION_PATCH 12 +#define CJSON_VERSION_PATCH 13 #include @@ -146,9 +146,11 @@ CJSON_PUBLIC(void) cJSON_InitHooks(cJSON_Hooks* hooks); /* Memory Management: the caller is always responsible to free the results from all variants of cJSON_Parse (with cJSON_Delete) and cJSON_Print (with stdlib free, cJSON_Hooks.free_fn, or cJSON_free as appropriate). The exception is cJSON_PrintPreallocated, where the caller has full responsibility of the buffer. */ /* Supply a block of JSON, and this returns a cJSON object you can interrogate. */ CJSON_PUBLIC(cJSON *) cJSON_Parse(const char *value); +CJSON_PUBLIC(cJSON *) cJSON_ParseWithLength(const char *value, size_t buffer_length); /* ParseWithOpts allows you to require (and check) that the JSON is null terminated, and to retrieve the pointer to the final byte parsed. */ /* If you supply a ptr in return_parse_end and parsing fails, then return_parse_end will contain a pointer to the error so will match cJSON_GetErrorPtr(). */ CJSON_PUBLIC(cJSON *) cJSON_ParseWithOpts(const char *value, const char **return_parse_end, cJSON_bool require_null_terminated); +CJSON_PUBLIC(cJSON *) cJSON_ParseWithLengthOpts(const char *value, size_t buffer_length, const char **return_parse_end, cJSON_bool require_null_terminated); /* Render a cJSON entity to text for transfer/storage. */ CJSON_PUBLIC(char *) cJSON_Print(const cJSON *item); @@ -173,8 +175,9 @@ CJSON_PUBLIC(cJSON_bool) cJSON_HasObjectItem(const cJSON *object, const char *st /* For analysing failed parses. This returns a pointer to the parse error. You'll probably need to look a few chars back to make sense of it. Defined when cJSON_Parse() returns 0. 0 when cJSON_Parse() succeeds. */ CJSON_PUBLIC(const char *) cJSON_GetErrorPtr(void); -/* Check if the item is a string and return its valuestring */ -CJSON_PUBLIC(char *) cJSON_GetStringValue(const cJSON * const item); +/* Check item type and return its value */ +CJSON_PUBLIC(char *) cJSON_GetStringValue(cJSON *item); +CJSON_PUBLIC(double) cJSON_GetNumberValue(cJSON *item); /* These functions check the type of an item */ CJSON_PUBLIC(cJSON_bool) cJSON_IsInvalid(const cJSON * const item); @@ -216,15 +219,15 @@ CJSON_PUBLIC(cJSON *) cJSON_CreateDoubleArray(const double *numbers, int count); CJSON_PUBLIC(cJSON *) cJSON_CreateStringArray(const char *const *strings, int count); /* Append item to the specified array/object. */ -CJSON_PUBLIC(void) cJSON_AddItemToArray(cJSON *array, cJSON *item); -CJSON_PUBLIC(void) cJSON_AddItemToObject(cJSON *object, const char *string, cJSON *item); +CJSON_PUBLIC(cJSON_bool) cJSON_AddItemToArray(cJSON *array, cJSON *item); +CJSON_PUBLIC(cJSON_bool) cJSON_AddItemToObject(cJSON *object, const char *string, cJSON *item); /* Use this when string is definitely const (i.e. a literal, or as good as), and will definitely survive the cJSON object. * WARNING: When this function was used, make sure to always check that (item->type & cJSON_StringIsConst) is zero before * writing to `item->string` */ -CJSON_PUBLIC(void) cJSON_AddItemToObjectCS(cJSON *object, const char *string, cJSON *item); +CJSON_PUBLIC(cJSON_bool) cJSON_AddItemToObjectCS(cJSON *object, const char *string, cJSON *item); /* Append reference to item to the specified array/object. Use this when you want to add an existing cJSON to a new cJSON, but don't want to corrupt your existing cJSON. */ -CJSON_PUBLIC(void) cJSON_AddItemReferenceToArray(cJSON *array, cJSON *item); -CJSON_PUBLIC(void) cJSON_AddItemReferenceToObject(cJSON *object, const char *string, cJSON *item); +CJSON_PUBLIC(cJSON_bool) cJSON_AddItemReferenceToArray(cJSON *array, cJSON *item); +CJSON_PUBLIC(cJSON_bool) cJSON_AddItemReferenceToObject(cJSON *object, const char *string, cJSON *item); /* Remove/Detach items from Arrays/Objects. */ CJSON_PUBLIC(cJSON *) cJSON_DetachItemViaPointer(cJSON *parent, cJSON * const item); @@ -236,11 +239,11 @@ CJSON_PUBLIC(void) cJSON_DeleteItemFromObject(cJSON *object, const char *string) CJSON_PUBLIC(void) cJSON_DeleteItemFromObjectCaseSensitive(cJSON *object, const char *string); /* Update array items. */ -CJSON_PUBLIC(void) cJSON_InsertItemInArray(cJSON *array, int which, cJSON *newitem); /* Shifts pre-existing items to the right. */ +CJSON_PUBLIC(cJSON_bool) cJSON_InsertItemInArray(cJSON *array, int which, cJSON *newitem); /* Shifts pre-existing items to the right. */ CJSON_PUBLIC(cJSON_bool) cJSON_ReplaceItemViaPointer(cJSON * const parent, cJSON * const item, cJSON * replacement); -CJSON_PUBLIC(void) cJSON_ReplaceItemInArray(cJSON *array, int which, cJSON *newitem); -CJSON_PUBLIC(void) cJSON_ReplaceItemInObject(cJSON *object,const char *string,cJSON *newitem); -CJSON_PUBLIC(void) cJSON_ReplaceItemInObjectCaseSensitive(cJSON *object,const char *string,cJSON *newitem); +CJSON_PUBLIC(cJSON_bool) cJSON_ReplaceItemInArray(cJSON *array, int which, cJSON *newitem); +CJSON_PUBLIC(cJSON_bool) cJSON_ReplaceItemInObject(cJSON *object,const char *string,cJSON *newitem); +CJSON_PUBLIC(cJSON_bool) cJSON_ReplaceItemInObjectCaseSensitive(cJSON *object,const char *string,cJSON *newitem); /* Duplicate a cJSON item */ CJSON_PUBLIC(cJSON *) cJSON_Duplicate(const cJSON *item, cJSON_bool recurse); @@ -273,6 +276,8 @@ CJSON_PUBLIC(cJSON*) cJSON_AddArrayToObject(cJSON * const object, const char * c /* helper for the cJSON_SetNumberValue macro */ CJSON_PUBLIC(double) cJSON_SetNumberHelper(cJSON *object, double number); #define cJSON_SetNumberValue(object, number) ((object != NULL) ? cJSON_SetNumberHelper(object, (double)number) : (number)) +/* Change the valuestring of a cJSON_String object, only takes effect when type of object is cJSON_String */ +CJSON_PUBLIC(char*) cJSON_SetValuestring(cJSON *object, const char *valuestring); /* Macro for iterating over an array or object */ #define cJSON_ArrayForEach(element, array) for(element = (array != NULL) ? (array)->child : NULL; element != NULL; element = element->next) From 8afd0631c1ffacca8f0041721e981f52b607f92f Mon Sep 17 00:00:00 2001 From: DL6ER Date: Tue, 7 Apr 2020 15:47:57 +0200 Subject: [PATCH 0150/1669] Update Civetweb server to 1.12 Signed-off-by: DL6ER --- src/civetweb/civetweb.c | 1213 ++++++++++++++++++++++++++++------ src/civetweb/civetweb.h | 116 +++- src/civetweb/handle_form.inl | 2 +- 3 files changed, 1120 insertions(+), 211 deletions(-) diff --git a/src/civetweb/civetweb.c b/src/civetweb/civetweb.c index de82cf528..a9ed6eba7 100644 --- a/src/civetweb/civetweb.c +++ b/src/civetweb/civetweb.c @@ -1,4 +1,4 @@ -/* Copyright (c) 2013-2018 the Civetweb developers +/* Copyright (c) 2013-2020 the Civetweb developers * Copyright (c) 2004-2013 Sergey Lyubka * * Permission is hereby granted, free of charge, to any person obtaining a copy @@ -132,16 +132,39 @@ mg_static_assert(sizeof(void *) == 4 || sizeof(void *) == 8, mg_static_assert(sizeof(void *) >= sizeof(int), "data type size check"); -/* Alternative queue is well tested and should be the new default */ -#if defined(NO_ALTERNATIVE_QUEUE) -#if defined(ALTERNATIVE_QUEUE) -#error "Define ALTERNATIVE_QUEUE or NO_ALTERNATIVE_QUEUE or none, but not both" +/* Select queue implementation. Diagnosis features originally only implemented + * for the "ALTERNATIVE_QUEUE" have been ported to the previous queue + * implementation (NO_ALTERNATIVE_QUEUE) as well. The new configuration value + * "CONNECTION_QUEUE_SIZE" is only available for the previous queue + * implementation, since the queue length is independent from the number of + * worker threads there, while the new queue is one element per worker thread. + * + */ +#if defined(NO_ALTERNATIVE_QUEUE) && defined(ALTERNATIVE_QUEUE) +/* The queues are exclusive or - only one can be used. */ +#error \ + "Define ALTERNATIVE_QUEUE or NO_ALTERNATIVE_QUEUE (or none of them), but not both" #endif -#else -#define ALTERNATIVE_QUEUE +#if !defined(NO_ALTERNATIVE_QUEUE) && !defined(ALTERNATIVE_QUEUE) +/* Use a default implementation */ +#define NO_ALTERNATIVE_QUEUE #endif #if defined(NO_FILESYSTEMS) && !defined(NO_FILES) +/* File system access: + * NO_FILES = do not serve any files from the file system automatically. + * However, with NO_FILES CivetWeb may still write log files, read access + * control files, default error page files or use API functions like + * mg_send_file in callbacks to send files from the server local + * file system. + * NO_FILES only disables the automatic mapping between URLs and local + * file names. + * NO_FILESYSTEM = do not access any file at all. Useful for embedded + * devices without file system. Logging to files in not available + * (use callbacks instead) and API functions like mg_send_file are not + * available. + * If NO_FILESYSTEM is set, NO_FILES must be set as well. + */ #error "Inconsistent build flags, NO_FILESYSTEMS requires NO_FILES" #endif @@ -153,13 +176,9 @@ mg_static_assert(sizeof(void *) >= sizeof(int), "data type size check"); #if defined(__SYMBIAN32__) /* According to https://en.wikipedia.org/wiki/Symbian#History, * Symbian is no longer maintained since 2014-01-01. - * Recent versions of CivetWeb are no longer tested for Symbian. - * It makes no sense, to support an abandoned operating system. + * Support for Symbian has been removed from CivetWeb */ #error "Symbian is no longer maintained. CivetWeb no longer supports Symbian." -#define NO_SSL /* SSL is not supported */ -#define NO_CGI /* CGI is not supported */ -#define PATH_MAX FILENAME_MAX #endif /* __SYMBIAN32__ */ #if defined(__ZEPHYR__) @@ -215,6 +234,9 @@ static void DEBUG_TRACE_FUNC(const char *func, DEBUG_TRACE_FUNC(__func__, __LINE__, fmt, __VA_ARGS__) #define NEED_DEBUG_TRACE_FUNC +#ifndef DEBUG_TRACE_STREAM +# define DEBUG_TRACE_STREAM stdout +#endif #else #define DEBUG_TRACE(fmt, ...) \ @@ -455,12 +477,6 @@ _civet_safe_clock_gettime(int clk_id, struct timespec *t) #define MG_BUF_LEN (1024 * 8) #endif -/* Size of the accepted socket queue (in case the old queue implementation - * is used). */ -#if !defined(MGSQLEN) -#define MGSQLEN (20) /* count */ -#endif - /********************************************************************/ @@ -556,19 +572,27 @@ typedef long off_t; #if defined(_WIN64) || defined(__MINGW64__) #if !defined(SSL_LIB) +#if defined(OPENSSL_API_1_1) +#define SSL_LIB "libssl-1_1-x64.dll" +#else /* OPENSSL_API_1_1 */ #define SSL_LIB "ssleay64.dll" -#endif +#endif /* OPENSSL_API_1_1 */ +#endif /* SSL_LIB */ #if !defined(CRYPTO_LIB) +#if defined(OPENSSL_API_1_1) +#define CRYPTO_LIB "libcrypto-1_1-x64.dll" +#else /* OPENSSL_API_1_1 */ #define CRYPTO_LIB "libeay64.dll" -#endif -#else +#endif /* OPENSSL_API_1_1 */ +#endif /* CRYPTO_LIB */ +#else /* defined(_WIN64) || defined(__MINGW64__) */ #if !defined(SSL_LIB) #define SSL_LIB "ssleay32.dll" -#endif +#endif /* SSL_LIB */ #if !defined(CRYPTO_LIB) #define CRYPTO_LIB "libeay32.dll" -#endif -#endif +#endif /* CRYPTO_LIB */ +#endif /* defined(_WIN64) || defined(__MINGW64__) */ #define O_NONBLOCK (0) #if !defined(W_OK) @@ -1722,8 +1746,8 @@ DEBUG_TRACE_FUNC(const char *func, unsigned line, const char *fmt, ...) nslast = nsnow; } - flockfile(stdout); - printf("*** %lu.%09lu %12" INT64_FMT " %lu %s:%u: ", + flockfile(DEBUG_TRACE_STREAM); + fprintf( DEBUG_TRACE_STREAM,"*** %lu.%09lu %12" INT64_FMT " %lu %s:%u: ", (unsigned long)tsnow.tv_sec, (unsigned long)tsnow.tv_nsec, nsnow - nslast, @@ -1731,11 +1755,11 @@ DEBUG_TRACE_FUNC(const char *func, unsigned line, const char *fmt, ...) func, line); va_start(args, fmt); - vprintf(fmt, args); + vfprintf(DEBUG_TRACE_STREAM, fmt, args); va_end(args); - putchar('\n'); - fflush(stdout); - funlockfile(stdout); + putc('\n', DEBUG_TRACE_STREAM); + fflush(DEBUG_TRACE_STREAM); + funlockfile(DEBUG_TRACE_STREAM); nslast = nsnow; } #endif /* NEED_DEBUG_TRACE_FUNC */ @@ -1779,6 +1803,21 @@ typedef struct SSL_CTX SSL_CTX; #include "wolfssl_extras.inl" #endif +#if defined(OPENSSL_IS_BORINGSSL) +/* From boringssl/src/include/openssl/mem.h: + * + * OpenSSL has, historically, had a complex set of malloc debugging options. + * However, that was written in a time before Valgrind and ASAN. Since we now + * have those tools, the OpenSSL allocation functions are simply macros around + * the standard memory functions. + * + * #define OPENSSL_free free */ +#define free free +// disable for boringssl +#define CONF_modules_unload(a) ((void)0) +#define ENGINE_cleanup() ((void)0) +#endif + #if (OPENSSL_VERSION_NUMBER >= 0x10100000L) /* If OpenSSL headers are included, automatically select the API version */ #if !defined(OPENSSL_API_1_1) @@ -2360,7 +2399,8 @@ enum { * socket option typedef TCP_NODELAY. */ MAX_REQUEST_SIZE, LINGER_TIMEOUT, - MAX_CONNECTIONS, + CONNECTION_QUEUE_SIZE, + LISTEN_BACKLOG_SIZE, #if defined(__linux__) ALLOW_SENDFILE_CALL, #endif @@ -2439,6 +2479,7 @@ enum { ERROR_PAGES, #if !defined(NO_CACHING) STATIC_FILE_MAX_AGE, + STATIC_FILE_CACHE_CONTROL, #endif #if !defined(NO_SSL) STRICT_HTTPS_MAX_AGE, @@ -2465,7 +2506,8 @@ static const struct mg_option config_options[] = { {"tcp_nodelay", MG_CONFIG_TYPE_NUMBER, "0"}, {"max_request_size", MG_CONFIG_TYPE_NUMBER, "16384"}, {"linger_timeout_ms", MG_CONFIG_TYPE_NUMBER, NULL}, - {"max_connections", MG_CONFIG_TYPE_NUMBER, "100"}, + {"connection_queue", MG_CONFIG_TYPE_NUMBER, "20"}, + {"listen_backlog", MG_CONFIG_TYPE_NUMBER, "200"}, #if defined(__linux__) {"allow_sendfile_call", MG_CONFIG_TYPE_BOOLEAN, "yes"}, #endif @@ -2555,6 +2597,7 @@ static const struct mg_option config_options[] = { {"error_pages", MG_CONFIG_TYPE_DIRECTORY, NULL}, #if !defined(NO_CACHING) {"static_file_max_age", MG_CONFIG_TYPE_NUMBER, "3600"}, + {"static_file_cache_control", MG_CONFIG_TYPE_STRING, NULL}, #endif #if !defined(NO_SSL) {"strict_transport_security_max_age", MG_CONFIG_TYPE_NUMBER, NULL}, @@ -2660,7 +2703,7 @@ struct mg_context { #if defined(USE_SERVER_STATS) int active_connections; - int max_connections; + int max_active_connections; int64_t total_connections; int64_t total_requests; int64_t total_data_read; @@ -2675,18 +2718,25 @@ struct mg_context { unsigned int cfg_worker_threads; /* The number of configured worker threads. */ pthread_t *worker_threadids; /* The worker thread IDs */ + unsigned long starter_thread_idx; /* thread index which called mg_start */ /* Connection to thread dispatching */ #if defined(ALTERNATIVE_QUEUE) struct socket *client_socks; void **client_wait_events; #else - struct socket queue[MGSQLEN]; /* Accepted sockets */ - volatile int sq_head; /* Head of the socket queue */ - volatile int sq_tail; /* Tail of the socket queue */ - pthread_cond_t sq_full; /* Signaled when socket is produced */ - pthread_cond_t sq_empty; /* Signaled when socket is consumed */ -#endif + struct socket *squeue; /* Socket queue (sq) : accepted sockets waiting for a + worker thread */ + volatile int sq_head; /* Head of the socket queue */ + volatile int sq_tail; /* Tail of the socket queue */ + pthread_cond_t sq_full; /* Signaled when socket is produced */ + pthread_cond_t sq_empty; /* Signaled when socket is consumed */ + volatile int sq_blocked; /* Status information: sq is full */ + int sq_size; /* No of elements in socket queue */ +#if defined(USE_SERVER_STATS) + int sq_max_fill; +#endif /* USE_SERVER_STATS */ +#endif /* ALTERNATIVE_QUEUE */ /* Memory related */ unsigned int max_request_size; /* The max request size */ @@ -2761,11 +2811,15 @@ struct mg_connection { * mg_get_connection_info_impl */ #endif - const char *host; /* Host (HTTP/1.1 header or SNI) */ - SSL *ssl; /* SSL descriptor */ - struct socket client; /* Connected client */ - time_t conn_birth_time; /* Time (wall clock) when connection was - * established */ + const char *host; /* Host (HTTP/1.1 header or SNI) */ + SSL *ssl; /* SSL descriptor */ + struct socket client; /* Connected client */ + time_t conn_birth_time; /* Time (wall clock) when connection was + * established */ +#if defined(USE_SERVER_STATS) + time_t conn_close_time; /* Time (wall clock) when connection was + * closed (or 0 if still open) */ +#endif struct timespec req_time; /* Time (since system start) when the request * was received */ int64_t num_bytes_sent; /* Total bytes sent to client */ @@ -3053,11 +3107,11 @@ mg_set_thread_name(const char *name) } __except (EXCEPTION_EXECUTE_HANDLER) { } #elif defined(__MINGW32__) -/* No option known to set thread name for MinGW */ +/* No option known to set thread name for MinGW known */ #endif #elif defined(_GNU_SOURCE) && defined(__GLIBC__) \ && ((__GLIBC__ > 2) || ((__GLIBC__ == 2) && (__GLIBC_MINOR__ >= 12))) -/* pthread_setname_np first appeared in glibc in version 2.12*/ +/* pthread_setname_np first appeared in glibc in version 2.12 */ #if defined(__MACH__) /* OS X only current thread name can be changed */ (void)pthread_setname_np(threadName); @@ -3065,7 +3119,11 @@ mg_set_thread_name(const char *name) (void)pthread_setname_np(pthread_self(), threadName); #endif #elif defined(__linux__) - /* on linux we can use the old prctl function */ + /* On Linux we can use the prctl function. + * When building for Linux Standard Base (LSB) use + * NO_THREAD_NAME. However, thread names are a big + * help for debugging, so the stadard is to set them. + */ (void)prctl(PR_SET_NAME, threadName, 0, 0, 0); #endif } @@ -3519,6 +3577,13 @@ mg_get_user_data(const struct mg_context *ctx) } +void * +mg_get_user_context_data(const struct mg_connection *conn) +{ + return mg_get_user_data(mg_get_context(conn)); +} + + void * mg_get_thread_pointer(const struct mg_connection *conn) { @@ -4348,9 +4413,15 @@ static int send_static_cache_header(struct mg_connection *conn) { #if !defined(NO_CACHING) + int max_age; + const char *cache_control = + conn->dom_ctx->config[STATIC_FILE_CACHE_CONTROL]; + if (cache_control != NULL) { + return mg_printf(conn, "Cache-Control: %s\r\n", cache_control); + } /* Read the server config to check how long a file may be cached. * The configuration is in seconds. */ - int max_age = atoi(conn->dom_ctx->config[STATIC_FILE_MAX_AGE]); + max_age = atoi(conn->dom_ctx->config[STATIC_FILE_MAX_AGE]); if (max_age <= 0) { /* 0 means "do not cache". All values <0 are reserved * and may be used differently in the future. */ @@ -4855,8 +4926,8 @@ mg_send_http_ok(struct mg_connection *conn, time_t curtime = time(NULL); if ((mime_type == NULL) || (*mime_type == 0)) { - /* Parameter error */ - return -2; + /* No content type defined: default to text/html */ + mime_type = "text/html"; } gmt_time_string(date, sizeof(date), &curtime); @@ -4870,10 +4941,12 @@ mg_send_http_ok(struct mg_connection *conn, date, suggest_connection_header(conn)); + /********************** Pi-hole modification **********************/ if(additional_headers != NULL && strlen(additional_headers) > 0) { mg_write(conn, additional_headers, strlen(additional_headers)); } + /******************************************************************/ send_no_cache_header(conn); send_additional_header(conn); @@ -7874,33 +7947,159 @@ parse_date_string(const char *datetime) #endif /* !NO_CACHING */ -/* Protect against directory disclosure attack by removing '..', - * excessive '/' and '\' characters */ +/* Pre-process URIs according to RFC + protect against directory disclosure + * attacks by removing '..', excessive '/' and '\' characters */ static void -remove_double_dots_and_double_slashes(char *s) +remove_dot_segments(char *inout) { - char *p = s; + /* Windows backend protection + * (https://tools.ietf.org/html/rfc3986#section-7.3): Replace backslash in + * URI by slash */ + char *in_copy = mg_strdup(inout); + char *out_begin = inout; + char *out_end = inout; + char *in = in_copy; + int replaced; - while ((s[0] == '.') && (s[1] == '.')) { - s++; + while (*in) { + if (*in == '\\') { + *in = '/'; + } + in++; } - while (*s != '\0') { - *p++ = *s++; - if ((s[-1] == '/') || (s[-1] == '\\')) { - /* Skip all following slashes, backslashes and double-dots */ - while (s[0] != '\0') { - if ((s[0] == '/') || (s[0] == '\\')) { - s++; - } else if ((s[0] == '.') && (s[1] == '.')) { - s += 2; - } else { - break; - } + /* Algorithm "remove_dot_segments" from + * https://tools.ietf.org/html/rfc3986#section-5.2.4 */ + /* Step 1: + * The input buffer is initialized. + * The output buffer is initialized to the empty string. + */ + in = in_copy; + + /* Step 2: + * While the input buffer is not empty, loop as follows: + */ + while (*in) { + /* Step 2a: + * If the input buffer begins with a prefix of "../" or "./", + * then remove that prefix from the input buffer; + */ + if (!strncmp(in, "../", 3)) { + in += 3; + } else if (!strncmp(in, "./", 2)) { + in += 2; + } + /* otherwise */ + /* Step 2b: + * if the input buffer begins with a prefix of "/./" or "/.", + * where "." is a complete path segment, then replace that + * prefix with "/" in the input buffer; + */ + else if (!strncmp(in, "/./", 3)) { + in += 2; + } else if (!strcmp(in, "/.")) { + in[1] = 0; + } + /* otherwise */ + /* Step 2c: + * if the input buffer begins with a prefix of "/../" or "/..", + * where ".." is a complete path segment, then replace that + * prefix with "/" in the input buffer and remove the last + * segment and its preceding "/" (if any) from the output + * buffer; + */ + else if (!strncmp(in, "/../", 4)) { + in += 3; + if (out_begin != out_end) { + /* remove last segment */ + do { + out_end--; + *out_end = 0; + } while ((out_begin != out_end) && (*out_end != '/')); } + } else if (!strcmp(in, "/..")) { + in[1] = 0; + if (out_begin != out_end) { + /* remove last segment */ + do { + out_end--; + *out_end = 0; + } while ((out_begin != out_end) && (*out_end != '/')); + } + } + /* otherwise */ + /* Step 2d: + * if the input buffer consists only of "." or "..", then remove + * that from the input buffer; + */ + else if (!strcmp(in, ".") || !strcmp(in, "..")) { + *in = 0; + } + /* otherwise */ + /* Step 2e: + * move the first path segment in the input buffer to the end of + * the output buffer, including the initial "/" character (if + * any) and any subsequent characters up to, but not including, + * the next "/" character or the end of the input buffer. + */ + else { + do { + *out_end = *in; + out_end++; + in++; + } while ((*in != 0) && (*in != '/')); } } - *p = '\0'; + + /* Step 3: + * Finally, the output buffer is returned as the result of + * remove_dot_segments. + */ + /* Terminate output */ + *out_end = 0; + + /* For Windows, the files/folders "x" and "x." (with a dot but without + * extension) are identical. Replace all "./" by "/" and remove a "." at the + * end. + * Also replace all "//" by "/". + * Repeat until there is no "./" or "//" anymore. + */ + do { + replaced = 0; + + /* replace ./ by / */ + out_end = out_begin; + while (*out_end) { + if ((*out_end == '.') + && ((out_end[1] == '/') || (out_end[1] == 0))) { + char *r = out_end; + do { + r[0] = r[1]; + r++; + replaced = 1; + } while (r[0] != 0); + } + out_end++; + } + + /* replace ./ by / */ + out_end = out_begin; + while (*out_end) { + if ((out_end[0] == '/') && (out_end[1] == '/')) { + char *c = out_end; + while (*c) { + c[0] = c[1]; + c++; + } + replaced = 1; + } + out_end++; + } + + } while (replaced); + + /* Free temporary copies */ + mg_free(in_copy); } @@ -9417,7 +9616,7 @@ static void * realloc2(void *ptr, size_t size) { void *new_ptr = mg_realloc(ptr, size); - if (new_ptr == NULL) { + if ((new_ptr == NULL) && (size > 0)) { mg_free(ptr); } return new_ptr; @@ -13349,6 +13548,8 @@ mg_set_handler_type(struct mg_context *phys_ctx, { struct mg_handler_info *tmp_rh, **lastref; size_t urilen = strlen(uri); + struct mg_workerTLS tls; + int is_tls_set = 0; if (handler_type == WEBSOCKET_HANDLER) { DEBUG_ASSERT(handler == NULL); @@ -13405,6 +13606,22 @@ mg_set_handler_type(struct mg_context *phys_ctx, return; } + /* Internal callbacks have their contexts set + * if called from non-related thread, context must be set + * since internal function assumes it exists. + * For an example see how handler_info_wait_unused() + * waits for reference to become zero + */ + if (NULL == pthread_getspecific(sTlsKey)) { + is_tls_set = 1; + tls.is_master = -1; + tls.thread_idx = phys_ctx->starter_thread_idx; +#if defined(_WIN32) + tls.pthread_cond_helper_mutex = NULL; +#endif + pthread_setspecific(sTlsKey, &tls); + } + mg_lock_context(phys_ctx); /* first try to find an existing handler */ @@ -13446,6 +13663,9 @@ mg_set_handler_type(struct mg_context *phys_ctx, mg_free(tmp_rh); } mg_unlock_context(phys_ctx); + if (is_tls_set) { + pthread_setspecific(sTlsKey, NULL); + } return; } } @@ -13456,6 +13676,9 @@ mg_set_handler_type(struct mg_context *phys_ctx, /* no handler to set, this was a remove request to a non-existing * handler */ mg_unlock_context(phys_ctx); + if (is_tls_set) { + pthread_setspecific(sTlsKey, NULL); + } return; } @@ -13468,6 +13691,9 @@ mg_set_handler_type(struct mg_context *phys_ctx, mg_cry_ctx_internal(phys_ctx, "%s", "Cannot create new request handler struct, OOM"); + if (is_tls_set) { + pthread_setspecific(sTlsKey, NULL); + } return; } tmp_rh->uri = mg_strdup_ctx(uri, phys_ctx); @@ -13477,6 +13703,9 @@ mg_set_handler_type(struct mg_context *phys_ctx, mg_cry_ctx_internal(phys_ctx, "%s", "Cannot create new request handler struct, OOM"); + if (is_tls_set) { + pthread_setspecific(sTlsKey, NULL); + } return; } tmp_rh->uri_len = urilen; @@ -13486,6 +13715,9 @@ mg_set_handler_type(struct mg_context *phys_ctx, mg_unlock_context(phys_ctx); mg_free(tmp_rh); mg_cry_ctx_internal(phys_ctx, "%s", "Cannot init refcount mutex"); + if (is_tls_set) { + pthread_setspecific(sTlsKey, NULL); + } return; } if (0 != pthread_cond_init(&tmp_rh->refcount_cond, NULL)) { @@ -13493,6 +13725,9 @@ mg_set_handler_type(struct mg_context *phys_ctx, pthread_mutex_destroy(&tmp_rh->refcount_mutex); mg_free(tmp_rh); mg_cry_ctx_internal(phys_ctx, "%s", "Cannot init refcount cond"); + if (is_tls_set) { + pthread_setspecific(sTlsKey, NULL); + } return; } tmp_rh->refcount = 0; @@ -13512,6 +13747,9 @@ mg_set_handler_type(struct mg_context *phys_ctx, *lastref = tmp_rh; mg_unlock_context(phys_ctx); + if (is_tls_set) { + pthread_setspecific(sTlsKey, NULL); + } } @@ -13734,7 +13972,8 @@ is_in_script_path(const struct mg_connection *conn, const char *path) } -#if defined(USE_WEBSOCKET) && defined(MG_LEGACY_INTERFACE) +#if defined(USE_WEBSOCKET) \ + && (defined(MG_LEGACY_INTERFACE) || defined(MG_EXPERIMENTAL_INTERFACES)) static int deprecated_websocket_connect_wrapper(const struct mg_connection *conn, void *cbdata) @@ -13772,6 +14011,17 @@ deprecated_websocket_data_wrapper(struct mg_connection *conn, /* No handler set - assume "OK" */ return 1; } + + +static void +deprecated_websocket_close_wrapper(const struct mg_connection *conn, + void *cbdata) +{ + struct mg_callbacks *pcallbacks = (struct mg_callbacks *)cbdata; + if (pcallbacks->connection_close) { + pcallbacks->connection_close(conn); + } +} #endif @@ -13840,7 +14090,7 @@ handle_request(struct mg_connection *conn) /* 1.4. clean URIs, so a path like allowed_dir/../forbidden_file is * not possible */ - remove_double_dots_and_double_slashes((char *)ri->local_uri); + remove_dot_segments((char *)ri->local_uri); /* step 1. completed, the url is known now */ uri_len = (int)strlen(ri->local_uri); @@ -13861,7 +14111,9 @@ handle_request(struct mg_connection *conn) /* callback already processed the request. Store the return value as a status code for the access log. */ conn->status_code = i; - discard_unread_request_data(conn); + if (!conn->must_close) { + discard_unread_request_data(conn); + } return; } else if (i == 0) { /* civetweb should process the request */ @@ -14058,7 +14310,9 @@ handle_request(struct mg_connection *conn) * then return value as status code for the log and discard * all data from the client not used by the callback. */ conn->status_code = i; - discard_unread_request_data(conn); + if (!conn->must_close) { + discard_unread_request_data(conn); + } } else { /* The handler did NOT handle the request. */ /* Some proper reactions would be: @@ -14145,7 +14399,7 @@ handle_request(struct mg_connection *conn) deprecated_websocket_ready_wrapper, deprecated_websocket_data_wrapper, NULL, - conn->phys_ctx->user_data); + conn->phys_ctx->callbacks); #else mg_send_http_error(conn, 404, "%s", "Not found"); #endif @@ -14493,7 +14747,7 @@ parse_port_string(const struct vec *vec, struct socket *so, int *ip_version) hostname[hostnlen] = 0; if (mg_inet_pton( - AF_INET, vec->ptr, &so->lsa.sin, sizeof(so->lsa.sin))) { + AF_INET, hostname, &so->lsa.sin, sizeof(so->lsa.sin))) { if (sscanf(cb + 1, "%u%n", &port, &len) == 1) { *ip_version = 4; so->lsa.sin.sin_family = AF_INET; @@ -14505,7 +14759,7 @@ parse_port_string(const struct vec *vec, struct socket *so, int *ip_version) } #if defined(USE_IPV6) } else if (mg_inet_pton(AF_INET6, - vec->ptr, + hostname, &so->lsa.sin6, sizeof(so->lsa.sin6))) { if (sscanf(cb + 1, "%u%n", &port, &len) == 1) { @@ -14625,7 +14879,7 @@ set_ports_option(struct mg_context *phys_ctx) int portsOk = 0; const char *opt_txt; - long opt_max_connections; + long opt_listen_backlog; if (!phys_ctx) { return 0; @@ -14793,16 +15047,17 @@ set_ports_option(struct mg_context *phys_ctx) continue; } - opt_txt = phys_ctx->dd.config[MAX_CONNECTIONS]; - opt_max_connections = strtol(opt_txt, NULL, 10); - if ((opt_max_connections > INT_MAX) || (opt_max_connections < 1)) { + opt_txt = phys_ctx->dd.config[LISTEN_BACKLOG_SIZE]; + opt_listen_backlog = strtol(opt_txt, NULL, 10); + if ((opt_listen_backlog > INT_MAX) || (opt_listen_backlog < 1)) { mg_cry_ctx_internal(phys_ctx, - "max_connections value \"%s\" is invalid", + "%s value \"%s\" is invalid", + config_options[LISTEN_BACKLOG_SIZE].name, opt_txt); continue; } - if (listen(so.sock, (int)opt_max_connections) != 0) { + if (listen(so.sock, (int)opt_listen_backlog) != 0) { mg_cry_ctx_internal(phys_ctx, "cannot listen to %.*s: %d (%s)", @@ -15049,14 +15304,14 @@ set_uid_option(struct mg_context *phys_ctx) const char *run_as_user = phys_ctx->dd.config[RUN_AS_USER]; const struct passwd *to_pw = NULL; - if (run_as_user != NULL && (to_pw = getpwnam(run_as_user)) == NULL) { + if ((run_as_user != NULL) && (to_pw = getpwnam(run_as_user)) == NULL) { /* run_as_user does not exist on the system. We can't proceed * further. */ mg_cry_ctx_internal(phys_ctx, "%s: unknown user [%s]", __func__, run_as_user); - } else if (run_as_user == NULL || curr_uid == to_pw->pw_uid) { + } else if ((run_as_user == NULL) || (curr_uid == to_pw->pw_uid)) { /* There was either no request to change user, or we're already * running as run_as_user. Nothing else to do. */ @@ -15130,7 +15385,7 @@ refresh_trust(struct mg_connection *conn) int should_verify_peer; if ((pem = conn->dom_ctx->config[SSL_CERTIFICATE]) == NULL) { - /* If peem is NULL and conn->phys_ctx->callbacks.init_ssl is not, + /* If pem is NULL and conn->phys_ctx->callbacks.init_ssl is not, * refresh_trust still can not work. */ return 0; } @@ -15978,6 +16233,28 @@ init_ssl_ctx_impl(struct mg_context *phys_ctx, return 1; } + /* If a domain callback has been specified, call it. */ + callback_ret = (phys_ctx->callbacks.init_ssl_domain == NULL) + ? 0 + : (phys_ctx->callbacks.init_ssl_domain( + dom_ctx->config[AUTHENTICATION_DOMAIN], + dom_ctx->ssl_ctx, + phys_ctx->user_data)); + + /* If domain callback returns 0, civetweb sets up the SSL certificate. + * If it returns 1, civetweb assumes the calback already did this. + * If it returns -1, initializing ssl fails. */ + if (callback_ret < 0) { + mg_cry_ctx_internal(phys_ctx, + "Domain SSL callback returned error: %i", + callback_ret); + return 0; + } + if (callback_ret > 0) { + /* Domain callback did everything. */ + return 1; + } + /* Use some combination of start time, domain and port as a SSL * context ID. This should be unique on the current machine. */ md5_init(&md5state); @@ -16123,14 +16400,38 @@ init_ssl_ctx(struct mg_context *phys_ctx, struct mg_domain_context *dom_ctx) } return 1; } - /* else: external_ssl_ctx does not exist or returns 0, + + /* Check for external domain SSL_CTX */ + callback_ret = (phys_ctx->callbacks.external_ssl_ctx_domain == NULL) + ? 0 + : (phys_ctx->callbacks.external_ssl_ctx_domain( + dom_ctx->config[AUTHENTICATION_DOMAIN], + &ssl_ctx, + phys_ctx->user_data)); + + if (callback_ret < 0) { + mg_cry_ctx_internal( + phys_ctx, + "external_ssl_ctx_domain callback returned error: %i", + callback_ret); + return 0; + } else if (callback_ret > 0) { + dom_ctx->ssl_ctx = (SSL_CTX *)ssl_ctx; + if (!initialize_ssl(ebuf, sizeof(ebuf))) { + mg_cry_ctx_internal(phys_ctx, "%s", ebuf); + return 0; + } + return 1; + } + /* else: external_ssl_ctx/external_ssl_ctx_domain do not exist or return 0, * CivetWeb should continue initializing SSL */ - /* If PEM file is not specified and the init_ssl callback - * is not specified, setup will fail. */ + /* If PEM file is not specified and the init_ssl callbacks + * are not specified, setup will fail. */ if (((pem = dom_ctx->config[SSL_CERTIFICATE]) == NULL) - && (phys_ctx->callbacks.init_ssl == NULL)) { - /* No certificate and no callback: + && (phys_ctx->callbacks.init_ssl == NULL) + && (phys_ctx->callbacks.init_ssl_domain == NULL)) { + /* No certificate and no init_ssl callbacks: * Essential data to set up TLS is missing. */ mg_cry_ctx_internal(phys_ctx, @@ -16771,6 +17072,96 @@ mg_connect_client(const char *host, } +#if defined(MG_EXPERIMENTAL_INTERFACES) +struct mg_connection * +mg_connect_client2(const char *host, + const char *protocol, + int port, + const char *path, + struct mg_init_data *init, + struct mg_error_data *error) +{ + int is_ssl, is_ws; + /* void *user_data = (init != NULL) ? init->user_data : NULL; -- TODO */ + + if (error != NULL) { + error->code = 0; + if (error->text_buffer_size > 0) { + *error->text = 0; + } + } + + if ((host == NULL) || (protocol == NULL)) { + if ((error != NULL) && (error->text_buffer_size > 0)) { + mg_snprintf(NULL, + NULL, /* No truncation check for error buffers */ + error->text, + error->text_buffer_size, + "%s", + "Invalid parameters"); + } + return NULL; + } + + /* check all known protocolls */ + if (!mg_strcasecmp(protocol, "http")) { + is_ssl = 0; + is_ws = 0; + } else if (!mg_strcasecmp(protocol, "https")) { + is_ssl = 1; + is_ws = 0; +#if defined(USE_WEBSOCKET) + } else if (!mg_strcasecmp(protocol, "ws")) { + is_ssl = 0; + is_ws = 1; + } else if (!mg_strcasecmp(protocol, "wss")) { + is_ssl = 1; + is_ws = 1; +#endif + } else { + if ((error != NULL) && (error->text_buffer_size > 0)) { + mg_snprintf(NULL, + NULL, /* No truncation check for error buffers */ + error->text, + error->text_buffer_size, + "Protocol %s not supported", + protocol); + } + return NULL; + } + + /* TODO: The current implementation here just calls the old implementations, + * without using any new options. This is just a first step to test the new + * interfaces. */ +#if defined(USE_WEBSOCKET) + if (is_ws) { + /* TODO: implement all options */ + return mg_connect_websocket_client(host, + port, + is_ssl, + ((error != NULL) ? error->text : NULL), + ((error != NULL) ? error->text_buffer_size : 0), + (path ? path : ""), + NULL /* TODO: origin */, + deprecated_websocket_data_wrapper, + deprecated_websocket_close_wrapper, + (void *)init->callbacks); + } +#endif + + /* TODO: all additional options */ + struct mg_client_options opts; + memset(&opts, 0, sizeof(opts)); + opts.host = host; + opts.port = port; + return mg_connect_client_impl(&opts, + is_ssl, + ((error != NULL) ? error->text : NULL), + ((error != NULL) ? error->text_buffer_size : 0)); +} +#endif + + static const struct { const char *proto; size_t proto_len; @@ -17616,10 +18007,10 @@ process_new_connection(struct mg_connection *conn) #if defined(USE_SERVER_STATS) int mcon = mg_atomic_inc(&(conn->phys_ctx->active_connections)); mg_atomic_add(&(conn->phys_ctx->total_connections), 1); - if (mcon > (conn->phys_ctx->max_connections)) { + if (mcon > (conn->phys_ctx->max_active_connections)) { /* could use atomic compare exchange, but this * seems overkill for statistics data */ - conn->phys_ctx->max_connections = mcon; + conn->phys_ctx->max_active_connections = mcon; } #endif @@ -17867,8 +18258,6 @@ consume_socket(struct mg_context *ctx, struct socket *sp, int thread_index) static int consume_socket(struct mg_context *ctx, struct socket *sp, int thread_index) { -#define QUEUE_SIZE(ctx) ((int)(ARRAY_SIZE(ctx->queue))) - (void)thread_index; (void)pthread_mutex_lock(&ctx->thread_mutex); @@ -17882,15 +18271,15 @@ consume_socket(struct mg_context *ctx, struct socket *sp, int thread_index) /* If we're stopping, sq_head may be equal to sq_tail. */ if (ctx->sq_head > ctx->sq_tail) { /* Copy socket from the queue and increment tail */ - *sp = ctx->queue[ctx->sq_tail % QUEUE_SIZE(ctx)]; + *sp = ctx->squeue[ctx->sq_tail % ctx->sq_size]; ctx->sq_tail++; DEBUG_TRACE("grabbed socket %d, going busy", sp ? sp->sock : -1); /* Wrap pointers if needed */ - while (ctx->sq_tail > QUEUE_SIZE(ctx)) { - ctx->sq_tail -= QUEUE_SIZE(ctx); - ctx->sq_head -= QUEUE_SIZE(ctx); + while (ctx->sq_tail > ctx->sq_size) { + ctx->sq_tail -= ctx->sq_size; + ctx->sq_head -= ctx->sq_size; } } @@ -17898,7 +18287,6 @@ consume_socket(struct mg_context *ctx, struct socket *sp, int thread_index) (void)pthread_mutex_unlock(&ctx->thread_mutex); return !ctx->stop_flag; -#undef QUEUE_SIZE } @@ -17906,28 +18294,41 @@ consume_socket(struct mg_context *ctx, struct socket *sp, int thread_index) static void produce_socket(struct mg_context *ctx, const struct socket *sp) { -#define QUEUE_SIZE(ctx) ((int)(ARRAY_SIZE(ctx->queue))) - if (!ctx) { - return; - } + int queue_filled; + (void)pthread_mutex_lock(&ctx->thread_mutex); + queue_filled = ctx->sq_head - ctx->sq_tail; + /* If the queue is full, wait */ - while ((ctx->stop_flag == 0) - && (ctx->sq_head - ctx->sq_tail >= QUEUE_SIZE(ctx))) { + while ((ctx->stop_flag == 0) && (queue_filled >= ctx->sq_size)) { + ctx->sq_blocked = 1; /* Status information: All threads bussy */ +#if defined(USE_SERVER_STATS) + if (queue_filled > ctx->sq_max_fill) { + ctx->sq_max_fill = queue_filled; + } +#endif (void)pthread_cond_wait(&ctx->sq_empty, &ctx->thread_mutex); + ctx->sq_blocked = 0; /* Not blocked now */ + queue_filled = ctx->sq_head - ctx->sq_tail; } - if (ctx->sq_head - ctx->sq_tail < QUEUE_SIZE(ctx)) { + if (queue_filled < ctx->sq_size) { /* Copy socket to the queue and increment head */ - ctx->queue[ctx->sq_head % QUEUE_SIZE(ctx)] = *sp; + ctx->squeue[ctx->sq_head % ctx->sq_size] = *sp; ctx->sq_head++; DEBUG_TRACE("queued socket %d", sp ? sp->sock : -1); } + queue_filled = ctx->sq_head - ctx->sq_tail; +#if defined(USE_SERVER_STATS) + if (queue_filled > ctx->sq_max_fill) { + ctx->sq_max_fill = queue_filled; + } +#endif + (void)pthread_cond_signal(&ctx->sq_full); (void)pthread_mutex_unlock(&ctx->thread_mutex); -#undef QUEUE_SIZE } #endif /* ALTERNATIVE_QUEUE */ @@ -18012,6 +18413,9 @@ worker_thread_run(struct mg_connection *conn) * produce_socket() */ while (consume_socket(ctx, &conn->client, thread_index)) { +#if defined(USE_SERVER_STATS) + conn->conn_close_time = 0; +#endif conn->conn_birth_time = time(NULL); /* Fill in IP, port info early so even if SSL setup below fails, @@ -18082,6 +18486,10 @@ worker_thread_run(struct mg_connection *conn) } DEBUG_TRACE("%s", "Connection closed"); + +#if defined(USE_SERVER_STATS) + conn->conn_close_time = time(NULL); +#endif } @@ -18393,15 +18801,19 @@ free_context(struct mg_context *ctx) * condvars */ (void)pthread_mutex_destroy(&ctx->thread_mutex); + #if defined(ALTERNATIVE_QUEUE) mg_free(ctx->client_socks); - for (i = 0; (unsigned)i < ctx->cfg_worker_threads; i++) { - event_destroy(ctx->client_wait_events[i]); + if (ctx->client_wait_events != NULL) { + for (i = 0; (unsigned)i < ctx->cfg_worker_threads; i++) { + event_destroy(ctx->client_wait_events[i]); + } + mg_free(ctx->client_wait_events); } - mg_free(ctx->client_wait_events); #else (void)pthread_cond_destroy(&ctx->sq_empty); (void)pthread_cond_destroy(&ctx->sq_full); + mg_free(ctx->squeue); #endif /* Destroy other context global data structures mutex */ @@ -18502,7 +18914,6 @@ static void get_system_name(char **sysName) { #if defined(_WIN32) -#if !defined(__SYMBIAN32__) #if defined(_WIN32_WCE) *sysName = mg_strdup("WinCE"); #else @@ -18538,9 +18949,7 @@ get_system_name(char **sysName) *sysName = mg_strdup(name); #endif -#else - *sysName = mg_strdup("Symbian"); -#endif + #elif defined(__ZEPHYR__) *sysName = mg_strdup("Zephyr OS"); #else @@ -18552,37 +18961,39 @@ get_system_name(char **sysName) } -static void legacy_init(const char **options) { - const char *ports_option = - config_options[LISTENING_PORTS].default_value; +static void +legacy_init(const char **options) +{ + const char *ports_option = config_options[LISTENING_PORTS].default_value; - if (options) { - const char **run_options = options; - const char *optname = config_options[LISTENING_PORTS].name; + if (options) { + const char **run_options = options; + const char *optname = config_options[LISTENING_PORTS].name; - /* Try to find the "listening_ports" option */ - while (*run_options) { - if (!strcmp(*run_options, optname)) { - ports_option = run_options[1]; - } - run_options += 2; + /* Try to find the "listening_ports" option */ + while (*run_options) { + if (!strcmp(*run_options, optname)) { + ports_option = run_options[1]; } + run_options += 2; } + } - if (is_ssl_port_used(ports_option)) { - /* Initialize with SSL support */ - mg_init_library(MG_FEATURES_TLS); - } - else { - /* Initialize without SSL support */ - mg_init_library(MG_FEATURES_DEFAULT); - } + if (is_ssl_port_used(ports_option)) { + /* Initialize with SSL support */ + mg_init_library(MG_FEATURES_TLS); + } else { + /* Initialize without SSL support */ + mg_init_library(MG_FEATURES_DEFAULT); + } } -struct mg_context * -mg_start(const struct mg_callbacks *callbacks, - void *user_data, - const char **options) + +#if !defined(MG_EXPERIMENTAL_INTERFACES) +static +#endif + struct mg_context * + mg_start2(struct mg_init_data *init, struct mg_error_data *error) { struct mg_context *ctx; const char *name, *value, *default_value; @@ -18590,6 +19001,8 @@ mg_start(const struct mg_callbacks *callbacks, unsigned int i; int itmp; void (*exit_callback)(const struct mg_context *ctx) = 0; + const char **options = + ((init != NULL) ? (init->configuration_options) : (NULL)); struct mg_workerTLS tls; @@ -18598,14 +19011,29 @@ mg_start(const struct mg_callbacks *callbacks, WSAStartup(MAKEWORD(2, 2), &data); #endif /* _WIN32 */ + if (error != NULL) { + error->code = 0; + if (error->text_buffer_size > 0) { + *error->text = 0; + } + } + if (mg_init_library_called == 0) { /* Legacy INIT, if mg_start is called without mg_init_library. - * Note: This will cause a memory leak when unloading the library. */ + * Note: This will cause a memory leak when unloading the library. */ legacy_init(options); } /* Allocate context and initialize reasonable general case defaults. */ if ((ctx = (struct mg_context *)mg_calloc(1, sizeof(*ctx))) == NULL) { + if ((error != NULL) && (error->text_buffer_size > 0)) { + mg_snprintf(NULL, + NULL, /* No truncation check for error buffers */ + error->text, + error->text_buffer_size, + "%s", + "Out of memory"); + } return NULL; } @@ -18613,8 +19041,12 @@ mg_start(const struct mg_callbacks *callbacks, ctx->dd.auth_nonce_mask = (uint64_t)get_random() ^ (uint64_t)(ptrdiff_t)(options); - tls.is_master = -1; - tls.thread_idx = (unsigned)mg_atomic_inc(&thread_idx_max); + /* Save started thread index to reuse in other external API calls + * For the sake of thread synchronization all non-civetweb threads + * can be considered as single external thread */ + ctx->starter_thread_idx = (unsigned)mg_atomic_inc(&thread_idx_max); + tls.is_master = -1; /* Thread calling mg_start */ + tls.thread_idx = ctx->starter_thread_idx; #if defined(_WIN32) tls.pthread_cond_helper_mutex = NULL; #endif @@ -18624,25 +19056,40 @@ mg_start(const struct mg_callbacks *callbacks, #if !defined(ALTERNATIVE_QUEUE) ok &= (0 == pthread_cond_init(&ctx->sq_empty, NULL)); ok &= (0 == pthread_cond_init(&ctx->sq_full, NULL)); + ctx->sq_blocked = 0; #endif ok &= (0 == pthread_mutex_init(&ctx->nonce_mutex, &pthread_mutex_attr)); if (!ok) { + const char *err_msg = + "Cannot initialize thread synchronization objects"; /* Fatal error - abort start. However, this situation should never * occur in practice. */ - mg_cry_ctx_internal(ctx, - "%s", - "Cannot initialize thread synchronization objects"); + + mg_cry_ctx_internal(ctx, "%s", err_msg); + if ((error != NULL) && (error->text_buffer_size > 0)) { + mg_snprintf(NULL, + NULL, /* No truncation check for error buffers */ + error->text, + error->text_buffer_size, + "%s", + err_msg); + } + mg_free(ctx); pthread_setspecific(sTlsKey, NULL); return NULL; } - if (callbacks) { - ctx->callbacks = *callbacks; - exit_callback = callbacks->exit_context; + if ((init != NULL) && (init->callbacks != NULL)) { + /* Set all callbacks except exit_context. */ + ctx->callbacks = *init->callbacks; + exit_callback = init->callbacks->exit_context; + /* The exit callback is activated once the context is successfully + * created. It should not be called, if an incomplete context object is + * deleted during a failed initialization. */ ctx->callbacks.exit_context = 0; } - ctx->user_data = user_data; + ctx->user_data = ((init != NULL) ? (init->user_data) : (NULL)); ctx->dd.handlers = NULL; ctx->dd.next = NULL; @@ -18654,16 +19101,34 @@ mg_start(const struct mg_callbacks *callbacks, while (options && (name = *options++) != NULL) { if ((idx = get_option_index(name)) == -1) { mg_cry_ctx_internal(ctx, "Invalid option: %s", name); + if ((error != NULL) && (error->text_buffer_size > 0)) { + mg_snprintf(NULL, + NULL, /* No truncation check for error buffers */ + error->text, + error->text_buffer_size, + "Invalid configuration option: %s", + name); + } free_context(ctx); pthread_setspecific(sTlsKey, NULL); return NULL; } else if ((value = *options++) == NULL) { mg_cry_ctx_internal(ctx, "%s: option value cannot be NULL", name); + if ((error != NULL) && (error->text_buffer_size > 0)) { + mg_snprintf(NULL, + NULL, /* No truncation check for error buffers */ + error->text, + error->text_buffer_size, + "Invalid configuration option value: %s", + name); + } free_context(ctx); pthread_setspecific(sTlsKey, NULL); return NULL; } if (ctx->dd.config[idx] != NULL) { + /* A duplicate configuration option is not an error - the last + * option value will be used. */ mg_cry_ctx_internal(ctx, "warning: %s: duplicate option", name); mg_free(ctx->dd.config[idx]); } @@ -18682,25 +19147,79 @@ mg_start(const struct mg_callbacks *callbacks, /* Request size option */ itmp = atoi(ctx->dd.config[MAX_REQUEST_SIZE]); if (itmp < 1024) { - mg_cry_ctx_internal(ctx, "%s", "max_request_size too small"); + mg_cry_ctx_internal(ctx, + "%s too small", + config_options[MAX_REQUEST_SIZE].name); + if ((error != NULL) && (error->text_buffer_size > 0)) { + mg_snprintf(NULL, + NULL, /* No truncation check for error buffers */ + error->text, + error->text_buffer_size, + "Invalid configuration option value: %s", + config_options[MAX_REQUEST_SIZE].name); + } free_context(ctx); pthread_setspecific(sTlsKey, NULL); return NULL; } ctx->max_request_size = (unsigned)itmp; - /* Worker thread count option */ - workerthreadcount = atoi(ctx->dd.config[NUM_THREADS]); - - if (workerthreadcount > MAX_WORKER_THREADS) { - mg_cry_ctx_internal(ctx, "%s", "Too many worker threads"); + /* Queue length */ +#if !defined(ALTERNATIVE_QUEUE) + itmp = atoi(ctx->dd.config[CONNECTION_QUEUE_SIZE]); + if (itmp < 1) { + mg_cry_ctx_internal(ctx, + "%s too small", + config_options[CONNECTION_QUEUE_SIZE].name); + if ((error != NULL) && (error->text_buffer_size > 0)) { + mg_snprintf(NULL, + NULL, /* No truncation check for error buffers */ + error->text, + error->text_buffer_size, + "Invalid configuration option value: %s", + config_options[CONNECTION_QUEUE_SIZE].name); + } + free_context(ctx); + pthread_setspecific(sTlsKey, NULL); + return NULL; + } + ctx->squeue = (struct socket *)mg_calloc(itmp, sizeof(struct socket)); + if (ctx->squeue == NULL) { + mg_cry_ctx_internal(ctx, + "Out of memory: Cannot allocate %s", + config_options[CONNECTION_QUEUE_SIZE].name); + if ((error != NULL) && (error->text_buffer_size > 0)) { + mg_snprintf(NULL, + NULL, /* No truncation check for error buffers */ + error->text, + error->text_buffer_size, + "Out of memory: Cannot allocate %s", + config_options[CONNECTION_QUEUE_SIZE].name); + } free_context(ctx); pthread_setspecific(sTlsKey, NULL); return NULL; } + ctx->sq_size = itmp; +#endif + + /* Worker thread count option */ + workerthreadcount = atoi(ctx->dd.config[NUM_THREADS]); - if (workerthreadcount <= 0) { - mg_cry_ctx_internal(ctx, "%s", "Invalid number of worker threads"); + if ((workerthreadcount > MAX_WORKER_THREADS) || (workerthreadcount <= 0)) { + if (workerthreadcount <= 0) { + mg_cry_ctx_internal(ctx, "%s", "Invalid number of worker threads"); + } else { + mg_cry_ctx_internal(ctx, "%s", "Too many worker threads"); + } + if ((error != NULL) && (error->text_buffer_size > 0)) { + mg_snprintf(NULL, + NULL, /* No truncation check for error buffers */ + error->text, + error->text_buffer_size, + "Invalid configuration option value: %s", + config_options[NUM_THREADS].name); + } free_context(ctx); pthread_setspecific(sTlsKey, NULL); return NULL; @@ -18710,6 +19229,14 @@ mg_start(const struct mg_callbacks *callbacks, #if defined(NO_FILES) if (ctx->dd.config[DOCUMENT_ROOT] != NULL) { mg_cry_ctx_internal(ctx, "%s", "Document root must not be set"); + if ((error != NULL) && (error->text_buffer_size > 0)) { + mg_snprintf(NULL, + NULL, /* No truncation check for error buffers */ + error->text, + error->text_buffer_size, + "Invalid configuration option value: %s", + config_options[DOCUMENT_ROOT].name); + } free_context(ctx); pthread_setspecific(sTlsKey, NULL); return NULL; @@ -18729,6 +19256,15 @@ mg_start(const struct mg_callbacks *callbacks, ctx->dd.config[LUA_BACKGROUND_SCRIPT], ctx, ebuf, sizeof(ebuf)); if (!state) { mg_cry_ctx_internal(ctx, "lua_background_script error: %s", ebuf); + if ((error != NULL) && (error->text_buffer_size > 0)) { + mg_snprintf(NULL, + NULL, /* No truncation check for error buffers */ + error->text, + error->text_buffer_size, + "Error in script %s: %s", + config_options[DOCUMENT_ROOT].name, + ebuf); + } free_context(ctx); pthread_setspecific(sTlsKey, NULL); return NULL; @@ -18753,20 +19289,99 @@ mg_start(const struct mg_callbacks *callbacks, } #endif - /* NOTE(lsm): order is important here. SSL certificates must - * be initialized before listening ports. UID must be set last. */ - if ( + /* Step by step initialization of ctx - depending on build options */ #if !defined(NO_FILESYSTEMS) - !set_gpass_option(ctx, NULL) || + if (!set_gpass_option(ctx, NULL)) { + const char *err_msg = "Invalid global password file"; + /* Fatal error - abort start. */ + mg_cry_ctx_internal(ctx, "%s", err_msg); + + if ((error != NULL) && (error->text_buffer_size > 0)) { + mg_snprintf(NULL, + NULL, /* No truncation check for error buffers */ + error->text, + error->text_buffer_size, + "%s", + err_msg); + } + free_context(ctx); + pthread_setspecific(sTlsKey, NULL); + return NULL; + } #endif + #if !defined(NO_SSL) - !init_ssl_ctx(ctx, NULL) || + if (!init_ssl_ctx(ctx, NULL)) { + const char *err_msg = "Error initializing SSL context"; + /* Fatal error - abort start. */ + mg_cry_ctx_internal(ctx, "%s", err_msg); + + if ((error != NULL) && (error->text_buffer_size > 0)) { + mg_snprintf(NULL, + NULL, /* No truncation check for error buffers */ + error->text, + error->text_buffer_size, + "%s", + err_msg); + } + free_context(ctx); + pthread_setspecific(sTlsKey, NULL); + return NULL; + } #endif - !set_ports_option(ctx) || + + if (!set_ports_option(ctx)) { + const char *err_msg = "Failed to setup server ports"; + /* Fatal error - abort start. */ + mg_cry_ctx_internal(ctx, "%s", err_msg); + + if ((error != NULL) && (error->text_buffer_size > 0)) { + mg_snprintf(NULL, + NULL, /* No truncation check for error buffers */ + error->text, + error->text_buffer_size, + "%s", + err_msg); + } + free_context(ctx); + pthread_setspecific(sTlsKey, NULL); + return NULL; + } + + #if !defined(_WIN32) && !defined(__ZEPHYR__) - !set_uid_option(ctx) || + if (!set_uid_option(ctx)) { + const char *err_msg = "Failed to run as configured user"; + /* Fatal error - abort start. */ + mg_cry_ctx_internal(ctx, "%s", err_msg); + + if ((error != NULL) && (error->text_buffer_size > 0)) { + mg_snprintf(NULL, + NULL, /* No truncation check for error buffers */ + error->text, + error->text_buffer_size, + "%s", + err_msg); + } + free_context(ctx); + pthread_setspecific(sTlsKey, NULL); + return NULL; + } #endif - !set_acl_option(ctx)) { + + if (!set_acl_option(ctx)) { + const char *err_msg = "Failed to setup access control list"; + /* Fatal error - abort start. */ + mg_cry_ctx_internal(ctx, "%s", err_msg); + + if ((error != NULL) && (error->text_buffer_size > 0)) { + mg_snprintf(NULL, + NULL, /* No truncation check for error buffers */ + error->text, + error->text_buffer_size, + "%s", + err_msg); + } free_context(ctx); pthread_setspecific(sTlsKey, NULL); return NULL; @@ -18778,9 +19393,17 @@ mg_start(const struct mg_callbacks *callbacks, ctx); if (ctx->worker_threadids == NULL) { - mg_cry_ctx_internal(ctx, - "%s", - "Not enough memory for worker thread ID array"); + const char *err_msg = "Not enough memory for worker thread ID array"; + mg_cry_ctx_internal(ctx, "%s", err_msg); + + if ((error != NULL) && (error->text_buffer_size > 0)) { + mg_snprintf(NULL, + NULL, /* No truncation check for error buffers */ + error->text, + error->text_buffer_size, + "%s", + err_msg); + } free_context(ctx); pthread_setspecific(sTlsKey, NULL); return NULL; @@ -18790,24 +19413,41 @@ mg_start(const struct mg_callbacks *callbacks, sizeof(struct mg_connection), ctx); if (ctx->worker_connections == NULL) { - mg_cry_ctx_internal( - ctx, "%s", "Not enough memory for worker thread connection array"); + const char *err_msg = + "Not enough memory for worker thread connection array"; + mg_cry_ctx_internal(ctx, "%s", err_msg); + + if ((error != NULL) && (error->text_buffer_size > 0)) { + mg_snprintf(NULL, + NULL, /* No truncation check for error buffers */ + error->text, + error->text_buffer_size, + "%s", + err_msg); + } free_context(ctx); pthread_setspecific(sTlsKey, NULL); return NULL; } - #if defined(ALTERNATIVE_QUEUE) ctx->client_wait_events = (void **)mg_calloc_ctx(ctx->cfg_worker_threads, sizeof(ctx->client_wait_events[0]), ctx); if (ctx->client_wait_events == NULL) { - mg_cry_ctx_internal(ctx, - "%s", - "Not enough memory for worker event array"); + const char *err_msg = "Not enough memory for worker event array"; + mg_cry_ctx_internal(ctx, "%s", err_msg); mg_free(ctx->worker_threadids); + + if ((error != NULL) && (error->text_buffer_size > 0)) { + mg_snprintf(NULL, + NULL, /* No truncation check for error buffers */ + error->text, + error->text_buffer_size, + "%s", + err_msg); + } free_context(ctx); pthread_setspecific(sTlsKey, NULL); return NULL; @@ -18818,11 +19458,19 @@ mg_start(const struct mg_callbacks *callbacks, sizeof(ctx->client_socks[0]), ctx); if (ctx->client_socks == NULL) { - mg_cry_ctx_internal(ctx, - "%s", - "Not enough memory for worker socket array"); + const char *err_msg = "Not enough memory for worker socket array"; + mg_cry_ctx_internal(ctx, "%s", err_msg); mg_free(ctx->client_wait_events); mg_free(ctx->worker_threadids); + + if ((error != NULL) && (error->text_buffer_size > 0)) { + mg_snprintf(NULL, + NULL, /* No truncation check for error buffers */ + error->text, + error->text_buffer_size, + "%s", + err_msg); + } free_context(ctx); pthread_setspecific(sTlsKey, NULL); return NULL; @@ -18831,7 +19479,8 @@ mg_start(const struct mg_callbacks *callbacks, for (i = 0; (unsigned)i < ctx->cfg_worker_threads; i++) { ctx->client_wait_events[i] = event_create(); if (ctx->client_wait_events[i] == 0) { - mg_cry_ctx_internal(ctx, "Error creating worker event %i", i); + const char *err_msg = "Error creating worker event %i"; + mg_cry_ctx_internal(ctx, err_msg, i); while (i > 0) { i--; event_destroy(ctx->client_wait_events[i]); @@ -18839,6 +19488,15 @@ mg_start(const struct mg_callbacks *callbacks, mg_free(ctx->client_socks); mg_free(ctx->client_wait_events); mg_free(ctx->worker_threadids); + + if ((error != NULL) && (error->text_buffer_size > 0)) { + mg_snprintf(NULL, + NULL, /* No truncation check for error buffers */ + error->text, + error->text_buffer_size, + err_msg, + i); + } free_context(ctx); pthread_setspecific(sTlsKey, NULL); return NULL; @@ -18846,10 +19504,19 @@ mg_start(const struct mg_callbacks *callbacks, } #endif - #if defined(USE_TIMERS) if (timers_init(ctx) != 0) { - mg_cry_ctx_internal(ctx, "%s", "Error creating timers"); + const char *err_msg = "Error creating timers"; + mg_cry_ctx_internal(ctx, "%s", err_msg); + + if ((error != NULL) && (error->text_buffer_size > 0)) { + mg_snprintf(NULL, + NULL, /* No truncation check for error buffers */ + error->text, + error->text_buffer_size, + "%s", + err_msg); + } free_context(ctx); pthread_setspecific(sTlsKey, NULL); return NULL; @@ -18860,6 +19527,9 @@ mg_start(const struct mg_callbacks *callbacks, if (ctx->callbacks.init_context) { ctx->callbacks.init_context(ctx); } + + /* From now, the context is successfully created. + * When it is destroyed, the exit callback should be called. */ ctx->callbacks.exit_context = exit_callback; ctx->context_type = CONTEXT_SERVER; /* server context */ @@ -18875,16 +19545,38 @@ mg_start(const struct mg_callbacks *callbacks, &ctx->worker_threadids[i]) != 0) { + long error_no = (long)ERRNO; + /* thread was not created */ if (i > 0) { + /* If the second, third, ... thread cannot be created, set a + * warning, but keep running. */ mg_cry_ctx_internal(ctx, "Cannot start worker thread %i: error %ld", i + 1, - (long)ERRNO); + error_no); + + /* If the server initialization should stop here, all threads + * that have already been created must be stopped first, before + * any free_context(ctx) call. + */ + } else { + /* If the first worker thread cannot be created, stop + * initialization and free the entire server context. */ mg_cry_ctx_internal(ctx, "Cannot create threads: error %ld", - (long)ERRNO); + error_no); + + if ((error != NULL) && (error->text_buffer_size > 0)) { + mg_snprintf( + NULL, + NULL, /* No truncation check for error buffers */ + error->text, + error->text_buffer_size, + "Cannot create first worker thread: error %ld", + error_no); + } free_context(ctx); pthread_setspecific(sTlsKey, NULL); return NULL; @@ -18898,10 +19590,26 @@ mg_start(const struct mg_callbacks *callbacks, } +struct mg_context * +mg_start(const struct mg_callbacks *callbacks, + void *user_data, + const char **options) +{ + struct mg_init_data init = {0}; + init.callbacks = callbacks; + init.user_data = user_data; + init.configuration_options = options; + + return mg_start2(&init, NULL); +} + + #if defined(MG_EXPERIMENTAL_INTERFACES) /* Add an additional domain to an already running web server. */ int -mg_start_domain(struct mg_context *ctx, const char **options) +mg_start_domain2(struct mg_context *ctx, + const char **options, + struct mg_error_data *error) { const char *name; const char *value; @@ -18910,7 +19618,34 @@ mg_start_domain(struct mg_context *ctx, const char **options) struct mg_domain_context *dom; int idx, i; - if ((ctx == NULL) || (ctx->stop_flag != 0) || (options == NULL)) { + if (error != NULL) { + error->code = 0; + if (error->text_buffer_size > 0) { + *error->text = 0; + } + } + + if ((ctx == NULL) || (options == NULL)) { + if ((error != NULL) && (error->text_buffer_size > 0)) { + mg_snprintf(NULL, + NULL, /* No truncation check for error buffers */ + error->text, + error->text_buffer_size, + "%s", + "Invalid parameters"); + } + return -1; + } + + if (ctx->stop_flag != 0) { + if ((error != NULL) && (error->text_buffer_size > 0)) { + mg_snprintf(NULL, + NULL, /* No truncation check for error buffers */ + error->text, + error->text_buffer_size, + "%s", + "Server already stopped"); + } return -1; } @@ -18919,6 +19654,14 @@ mg_start_domain(struct mg_context *ctx, const char **options) if (!new_dom) { /* Out of memory */ + if ((error != NULL) && (error->text_buffer_size > 0)) { + mg_snprintf(NULL, + NULL, /* No truncation check for error buffers */ + error->text, + error->text_buffer_size, + "%s", + "Out or memory"); + } return -6; } @@ -18926,14 +19669,31 @@ mg_start_domain(struct mg_context *ctx, const char **options) while (options && (name = *options++) != NULL) { if ((idx = get_option_index(name)) == -1) { mg_cry_ctx_internal(ctx, "Invalid option: %s", name); + if ((error != NULL) && (error->text_buffer_size > 0)) { + mg_snprintf(NULL, + NULL, /* No truncation check for error buffers */ + error->text, + error->text_buffer_size, + "Invalid option: %s", + name); + } mg_free(new_dom); return -2; } else if ((value = *options++) == NULL) { mg_cry_ctx_internal(ctx, "%s: option value cannot be NULL", name); + if ((error != NULL) && (error->text_buffer_size > 0)) { + mg_snprintf(NULL, + NULL, /* No truncation check for error buffers */ + error->text, + error->text_buffer_size, + "Invalid option value: %s", + name); + } mg_free(new_dom); return -2; } if (new_dom->config[idx] != NULL) { + /* Duplicate option: Later values overwrite earlier ones. */ mg_cry_ctx_internal(ctx, "warning: %s: duplicate option", name); mg_free(new_dom->config[idx]); } @@ -18945,6 +19705,14 @@ mg_start_domain(struct mg_context *ctx, const char **options) /* TODO: Maybe use a new option hostname? */ if (!new_dom->config[AUTHENTICATION_DOMAIN]) { mg_cry_ctx_internal(ctx, "%s", "authentication domain required"); + if ((error != NULL) && (error->text_buffer_size > 0)) { + mg_snprintf(NULL, + NULL, /* No truncation check for error buffers */ + error->text, + error->text_buffer_size, + "Mandatory option %s missing", + config_options[AUTHENTICATION_DOMAIN].name); + } mg_free(new_dom); return -4; } @@ -18970,6 +19738,14 @@ mg_start_domain(struct mg_context *ctx, const char **options) if (!init_ssl_ctx(ctx, new_dom)) { /* Init SSL failed */ + if ((error != NULL) && (error->text_buffer_size > 0)) { + mg_snprintf(NULL, + NULL, /* No truncation check for error buffers */ + error->text, + error->text_buffer_size, + "%s", + "Initializing SSL context failed"); + } mg_free(new_dom); return -3; } @@ -18986,7 +19762,17 @@ mg_start_domain(struct mg_context *ctx, const char **options) mg_cry_ctx_internal(ctx, "domain %s already in use", new_dom->config[AUTHENTICATION_DOMAIN]); + if ((error != NULL) && (error->text_buffer_size > 0)) { + mg_snprintf(NULL, + NULL, /* No truncation check for error buffers */ + error->text, + error->text_buffer_size, + "Domain %s specified by %s is already in use", + new_dom->config[AUTHENTICATION_DOMAIN], + config_options[AUTHENTICATION_DOMAIN].name); + } mg_free(new_dom); + mg_unlock_context(ctx); return -5; } @@ -19005,6 +19791,14 @@ mg_start_domain(struct mg_context *ctx, const char **options) /* Return domain number */ return idx; } + + +int +mg_start_domain(struct mg_context *ctx, const char **options) +{ + return mg_start_domain2(ctx, options, NULL); +} + #endif @@ -19492,12 +20286,37 @@ mg_get_context_info(const struct mg_context *ctx, char *buffer, int buflen) eol, ctx->active_connections, eol, - ctx->max_connections, + ctx->max_active_connections, eol, ctx->total_connections, eol); context_info_length += mg_str_append(&buffer, end, block); + /* Queue information */ +#if !defined(ALTERNATIVE_QUEUE) + mg_snprintf(NULL, + NULL, + block, + sizeof(block), + ",%s\"queue\" : {%s" + "\"length\" : %i,%s" + "\"filled\" : %i,%s" + "\"maxFilled\" : %i,%s" + "\"full\" : %s%s" + "}", + eol, + eol, + ctx->sq_size, + eol, + ctx->sq_head - ctx->sq_tail, + eol, + ctx->sq_max_fill, + eol, + (ctx->sq_blocked ? "true" : "false"), + eol); + context_info_length += mg_str_append(&buffer, end, block); +#endif + /* Requests information */ mg_snprintf(NULL, NULL, @@ -19722,14 +20541,24 @@ mg_get_connection_info(const struct mg_context *ctx, /* Execution time information */ if ((state >= 2) && (state < 9)) { char start_time_str[64] = {0}; - char now_str[64] = {0}; + char close_time_str[64] = {0}; time_t start_time = conn->conn_birth_time; - time_t now = time(NULL); + time_t close_time = conn->conn_close_time; + double time_diff; gmt_time_string(start_time_str, sizeof(start_time_str) - 1, &start_time); - gmt_time_string(now_str, sizeof(now_str) - 1, &now); + if (close_time != 0) { + time_diff = difftime(close_time, start_time); + gmt_time_string(close_time_str, + sizeof(close_time_str) - 1, + &close_time); + } else { + time_t now = time(NULL); + time_diff = difftime(now, start_time); + close_time_str[0] = 0; /* or use "now" ? */ + } mg_snprintf(NULL, NULL, @@ -19738,16 +20567,16 @@ mg_get_connection_info(const struct mg_context *ctx, "%s%s\"time\" : {%s" "\"uptime\" : %.0f,%s" "\"start\" : \"%s\",%s" - "\"now\" : \"%s\"%s" + "\"closed\" : \"%s\"%s" "}", (connection_info_length > 1 ? "," : ""), eol, eol, - difftime(now, start_time), + time_diff, eol, start_time_str, eol, - now_str, + close_time_str, eol); connection_info_length += mg_str_append(&buffer, end, block); } diff --git a/src/civetweb/civetweb.h b/src/civetweb/civetweb.h index 52131efc6..2525e409b 100644 --- a/src/civetweb/civetweb.h +++ b/src/civetweb/civetweb.h @@ -1,4 +1,4 @@ -/* Copyright (c) 2013-2018 the Civetweb developers +/* Copyright (c) 2013-2020 the Civetweb developers * Copyright (c) 2004-2013 Sergey Lyubka * * Permission is hereby granted, free of charge, to any person obtaining a copy @@ -247,28 +247,54 @@ struct mg_callbacks { /* Called when civetweb initializes SSL library. Parameters: + ssl_ctx: SSL_CTX pointer. user_data: parameter user_data passed when starting the server. Return value: 0: civetweb will set up the SSL certificate. 1: civetweb assumes the callback already set up the certificate. -1: initializing ssl fails. */ - int (*init_ssl)(void *ssl_context, void *user_data); + int (*init_ssl)(void *ssl_ctx, void *user_data); - /* Called when civetweb is about to create or free a SSL_CTX. - Parameters: - ssl_ctx: SSL_CTX pointer. NULL at creation time, Not NULL when mg_context - will be freed + /* Called when civetweb initializes SSL library for a domain. + Parameters: + server_domain: authentication_domain from the domain config. + ssl_ctx: SSL_CTX pointer. user_data: parameter user_data passed when starting the server. Return value: - 0: civetweb will continue to create the context, just as if the - callback would not be present. - The value in *ssl_ctx when the function returns is ignored. - 1: civetweb will copy the value from *ssl_ctx to the civetweb context - and doesn't create its own. - -1: initializing ssl fails.*/ + 0: civetweb will set up the SSL certificate. + 1: civetweb assumes the callback already set up the certificate. + -1: initializing ssl fails. */ + int (*init_ssl_domain)(const char *server_domain, + void *ssl_ctx, + void *user_data); + + /* Called when civetweb is about to create or free a SSL_CTX. + Parameters: + ssl_ctx: SSL_CTX pointer. NULL at creation time, Not NULL when + mg_context will be freed user_data: parameter user_data passed when starting + the server. Return value: 0: civetweb will continue to create the context, + just as if the callback would not be present. The value in *ssl_ctx when the + function returns is ignored. 1: civetweb will copy the value from *ssl_ctx + to the civetweb context and doesn't create its own. -1: initializing ssl + fails.*/ int (*external_ssl_ctx)(void **ssl_ctx, void *user_data); -#if defined(MG_LEGACY_INTERFACE) /* 2015-08-19 */ + /* Called when civetweb is about to create or free a SSL_CTX for a domain. + Parameters: + server_domain: authentication_domain from the domain config. + ssl_ctx: SSL_CTX pointer. NULL at creation time, Not NULL when + mg_context will be freed user_data: parameter user_data passed when starting + the server. Return value: 0: civetweb will continue to create the context, + just as if the callback would not be present. The value in *ssl_ctx when the + function returns is ignored. 1: civetweb will copy the value from *ssl_ctx + to the civetweb context and doesn't create its own. -1: initializing ssl + fails.*/ + int (*external_ssl_ctx_domain)(const char *server_domain, + void **ssl_ctx, + void *user_data); + +#if defined(MG_LEGACY_INTERFACE) /* 2015-08-19 */ \ + || defined(MG_EXPERIMENTAL_INTERFACES) /* 2019-11-03 */ /* Called when websocket request is received, before websocket handshake. Return value: 0: civetweb proceeds with websocket handshake. @@ -381,7 +407,6 @@ struct mg_callbacks { int thread_type, void *thread_pointer); - /* Called when initializing a new connection object. * Can be used to initialize the connection specific user data * (mg_request_info->conn_data, mg_get_user_connection_data). @@ -618,6 +643,10 @@ mg_get_context(const struct mg_connection *conn); CIVETWEB_API void *mg_get_user_data(const struct mg_context *ctx); +/* Get user data passed to mg_start from connection. */ +CIVETWEB_API void *mg_get_user_context_data(const struct mg_connection *conn); + + /* Get user defined thread pointer for server threads (see init_thread). */ CIVETWEB_API void *mg_get_thread_pointer(const struct mg_connection *conn); @@ -1160,6 +1189,10 @@ CIVETWEB_API int mg_get_cookie(const char *cookie, struct mg_connection *conn; conn = mg_download("google.com", 80, 0, ebuf, sizeof(ebuf), "%s", "GET / HTTP/1.0\r\nHost: google.com\r\n\r\n"); + + mg_download is equivalent to calling mg_connect_client followed by + mg_printf and mg_get_response. Using these three functions directly may + allow more control as compared to using mg_download. */ CIVETWEB_API struct mg_connection * mg_download(const char *host, @@ -1436,7 +1469,9 @@ mg_connect_client_secure(const struct mg_client_options *client_options, size_t error_buffer_size); +#if defined(MG_LEGACY_INTERFACE) /* 2019-11-02 */ enum { TIMEOUT_INFINITE = -1 }; +#endif enum { MG_TIMEOUT_INFINITE = -1 }; /* Wait for a response from the server @@ -1475,13 +1510,17 @@ CIVETWEB_API int mg_get_response(struct mg_connection *conn, 64 support server side JavaScript (USE_DUKTAPE is set) 128 support caching (NO_CACHING not set) 256 support server statistics (USE_SERVER_STATS is set) + 512 support for on the fly compression (USE_ZLIB is set) + + These values are defined as MG_FEATURES_* + The result is undefined, if bits are set that do not represent a - defined feature (currently: feature >= 512). + defined feature (currently: feature >= 1024). The result is undefined, if no bit is set (feature == 0). Return: - If feature is available, the corresponding bit is set - If feature is not available, the bit is 0 + If a feature is available, the corresponding bit is set + If a feature is not available, the bit is 0 */ CIVETWEB_API unsigned mg_check_feature(unsigned feature); @@ -1521,7 +1560,7 @@ CIVETWEB_API int mg_get_context_info(const struct mg_context *ctx, char *buffer, int buflen); -#ifdef MG_EXPERIMENTAL_INTERFACES +#if defined(MG_EXPERIMENTAL_INTERFACES) /* Get connection information. Useful for server diagnosis. Parameters: ctx: Context handle @@ -1545,6 +1584,47 @@ CIVETWEB_API int mg_get_connection_info(const struct mg_context *ctx, #endif +/* New APIs for enhanced option and error handling. + These mg_*2 API functions have the same purpose as their original versions, + but provide additional options and/or provide improved error diagnostics. + + Note: Experimental interfaces may change +*/ +struct mg_error_data { + unsigned *code; /* error code (number) */ + char *text; /* buffer for error text */ + size_t text_buffer_size; /* size of buffer of "text" */ +}; + +struct mg_init_data { + const struct mg_callbacks *callbacks; /* callback function pointer */ + void *user_data; /* data */ + const char **configuration_options; +}; + + +#if defined(MG_EXPERIMENTAL_INTERFACES) + +CIVETWEB_API struct mg_connection * +mg_connect_client2(const char *host, + const char *protocol, + int port, + const char *path, + struct mg_init_data *init, + struct mg_error_data *error); + +CIVETWEB_API int mg_get_response2(struct mg_connection *conn, + struct mg_error_data *error, + int timeout); + +CIVETWEB_API struct mg_context *mg_start2(struct mg_init_data *init, + struct mg_error_data *error); + +CIVETWEB_API int mg_start_domain2(struct mg_context *ctx, + const char **configuration_options, + struct mg_error_data *error); +#endif + #ifdef __cplusplus } #endif /* __cplusplus */ diff --git a/src/civetweb/handle_form.inl b/src/civetweb/handle_form.inl index 521bd0c81..9853faf1b 100644 --- a/src/civetweb/handle_form.inl +++ b/src/civetweb/handle_form.inl @@ -1,4 +1,4 @@ -/* Copyright (c) 2016-2018 the Civetweb developers +/* Copyright (c) 2016-2020 the Civetweb developers * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal From 6e40e3b2dc38d66b70e6463966c7be2d6144f433 Mon Sep 17 00:00:00 2001 From: DL6ER Date: Wed, 27 May 2020 20:42:38 +0200 Subject: [PATCH 0151/1669] Finish transition to cmake on the new/http branch. Signed-off-by: DL6ER --- .circleci/build-CI.sh | 17 +++ .circleci/config.yml | 6 +- CMakeLists.txt | 12 +- Makefile | 231 ------------------------------------ build.sh | 35 ++++++ src/CMakeLists.txt | 20 +++- src/api/CMakeLists.txt | 31 +++-- src/api/request.c | 206 -------------------------------- src/cJSON/CMakeLists.txt | 18 +++ src/civetweb/CMakeLists.txt | 30 +++++ src/database/CMakeLists.txt | 10 ++ src/dnsmasq/CMakeLists.txt | 10 ++ src/gen_version.cmake | 42 +++++-- 13 files changed, 209 insertions(+), 459 deletions(-) create mode 100644 .circleci/build-CI.sh delete mode 100644 Makefile create mode 100755 build.sh delete mode 100644 src/api/request.c create mode 100644 src/cJSON/CMakeLists.txt create mode 100644 src/civetweb/CMakeLists.txt diff --git a/.circleci/build-CI.sh b/.circleci/build-CI.sh new file mode 100644 index 000000000..23728a487 --- /dev/null +++ b/.circleci/build-CI.sh @@ -0,0 +1,17 @@ +#!/bin/bash +# Pi-hole: A black hole for Internet advertisements +# (c) 2020 Pi-hole, LLC (https://pi-hole.net) +# Network-wide ad blocking via your own hardware. +# +# FTL Engine +# Build script for Circle CI +# +# This file is copyright under the latest version of the EUPL. +# Please see LICENSE file for your rights under this license. + +rm -rf cmake/ && \ +mkdir cmake && \ +cd cmake && \ +cmake -DSTATIC="${1}" .. && \ +cmake --build . -- GIT_BRANCH="${2}" GIT_TAG="${3}" CIRCLE_JOB="${4}" -j 4 && \ +mv pihole-FTL ../ diff --git a/.circleci/config.yml b/.circleci/config.yml index 0b13a99db..d3d186098 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -6,7 +6,6 @@ version: 2 - run: name: "Setup" command: | - make clean if [[ $CIRCLE_JOB == *"qemu"* ]] ; then sudo docker run --rm --privileged multiarch/qemu-user-static:register --reset ; fi - run: name: "Build" @@ -14,8 +13,7 @@ version: 2 command: | BRANCH=$([ -z "$CIRCLE_TAG" ] && echo "$CIRCLE_BRANCH" || echo "master") [[ $CIRCLE_JOB == *"qemu"* ]] && DOCKERIFNEEDED="docker run --rm -v $(pwd):/workspace -w /workspace pihole/ftl-build:arm-qemu " - $DOCKERIFNEEDED make GIT_BRANCH="${BRANCH}" GIT_TAG="${CIRCLE_TAG}" STATIC="${STATIC}" - file pihole-FTL + $DOCKERIFNEEDED bash .circleci/build-CI.sh "${STATIC}" "${BRANCH}" "${CIRCLE_TAG}" "${CIRCLE_JOB}" - run: name: "Binary checks" command: bash test/arch_test.sh @@ -38,7 +36,7 @@ version: 2 .docker_template: &docker_template docker: - - image: pihole/ftl-build:v1.2-$CIRCLE_JOB + - image: pihole/ftl-build:v1.3-$CIRCLE_JOB <<: *job_steps jobs: diff --git a/CMakeLists.txt b/CMakeLists.txt index 61c839391..0c1e68b58 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,4 +1,14 @@ -cmake_minimum_required(VERSION 3.1) +# Pi-hole: A black hole for Internet advertisements +# (c) 2020 Pi-hole, LLC (https://pi-hole.net) +# Network-wide ad blocking via your own hardware. +# +# FTL Engine +# /CMakeList.txt +# +# This file is copyright under the latest version of the EUPL. +# Please see LICENSE file for your rights under this license. + +cmake_minimum_required(VERSION 2.8.12) project(PIHOLE_FTL C) set(DNSMASQ_VERSION pi-hole-2.81) diff --git a/Makefile b/Makefile deleted file mode 100644 index bcaad1905..000000000 --- a/Makefile +++ /dev/null @@ -1,231 +0,0 @@ -# Pi-hole: A black hole for Internet advertisements -# (c) 2018 Pi-hole, LLC (https://pi-hole.net) -# Network-wide ad blocking via your own hardware. -# -# FTL-Engine -# Makefile -# -# This file is copyright under the latest version of the EUPL. -# Please see LICENSE file for your rights under this license. - -IDIR = src -ODIR = build - -DNSMASQ_VERSION = "pi-hole-2.81" -DNSMASQ_OPTS = -DHAVE_DNSSEC -DHAVE_DNSSEC_STATIC -DHAVE_IDN - -FTL_DEPS = *.h database/*.h api/*.h version.h -FTL_DB_OBJ = database/common.o database/query-table.o database/network-table.o database/gravity-db.o database/database-thread.o \ - database/sqlite3-ext.o database/message-table.o -FTL_API_OBJ = api/http-common.o api/routes.o api/ftl.o api/stats.o api/dns.o api/version.o api/auth.o api/settings.o api/stats_database.o -FTL_OBJ = $(FTL_DB_OBJ) $(FTL_API_OBJ) main.o memory.o log.o daemon.o datastructure.o signals.o files.o setupVars.o args.o gc.o config.o \ - dnsmasq_interface.o resolve.o regex.o shmem.o capabilities.o overTime.o timers.o vector.o math.o - -DNSMASQ_DEPS = config.h dhcp-protocol.h dns-protocol.h radv-protocol.h dhcp6-protocol.h dnsmasq.h ip6addr.h metrics.h ../dnsmasq_interface.h -DNSMASQ_OBJ = arp.o dbus.o domain.o lease.o outpacket.o rrfilter.o auth.o dhcp6.o edns0.o log.o poll.o slaac.o blockdata.o dhcp.o forward.o \ - loop.o radv.o tables.o bpf.o dhcp-common.o helper.o netlink.o rfc1035.o tftp.o cache.o dnsmasq.o inotify.o network.o rfc2131.o \ - util.o conntrack.o dnssec.o ipset.o option.o rfc3315.o crypto.o dump.o ubus.o metrics.o - -# We can remove the NO_SSL later on. It adds additional constraints to the build system (availablity of libSSL-dev) -# -DNO_CGI = no CGI support (we don't need it) -# -DNO_SSL_DL -DNO_SSL = no SSL support (for now) -# -DUSE_SERVER_STATS = makes a few anonymous statistics available, such as -# - Number of connections (currently and total) -# - Amount of data read and written -# -DUSE_IPV6: add IPv6 support -CIVETWEB_OPTS = -DNO_CGI -DNO_SSL_DL -DNO_SSL -DUSE_SERVER_STATS -DUSE_IPV6 -CIVETWEB_DEPS = civetweb.h -CIVETWEB_OBJ = civetweb.o - -# cJSON does not need/has compile-time options -CJSON_DEPS = cJSON.h -CJSON_OBJ = cJSON.o - -# Get git commit version and date -GIT_BRANCH := $(shell git branch | sed -n 's/^\* //p') -GIT_HASH := $(shell git --no-pager describe --always --dirty) -GIT_VERSION := $(shell git --no-pager describe --tags --always --dirty) -GIT_DATE := $(shell git --no-pager show --date=short --format="%ai" --name-only | head -n 1) -GIT_TAG := $(shell git describe --tags --abbrev=0) - -# Is compiler at least gcc version 8? We cannot do ifgt in Makefile, so we use the shell expr command -GCCVERSION8 := $(shell expr `$(CC) -dumpversion | cut -f1 -d.` \>= 8) - -# Code hardening and debugging improvements -# -fstack-protector-strong: The program will be resistant to having its stack overflowed -# -Wp,-D_FORTIFY_SOURCE=2 and -O1 or higher: This causes certain unsafe glibc functions to be replaced with their safer counterparts -# -Wl,-z,relro: reduces the possible areas of memory in a program that can be used by an attacker that performs a successful memory corruption exploit -# -Wl,-z,now: When combined with RELRO above, this further reduces the regions of memory available to memory corruption attacks -# -g3: More debugging information -# -fno-omit-frame-pointer: get nicer stacktraces -# -funwind-tables: Generate static data for unwinding -# -fasynchronous-unwind-tables: Increased reliability of backtraces -# -fexceptions: Enable table-based thread cancellation -# -Wl,-z,defs: Detect and reject underlinking (phenomenon caused by missing shared library arguments when invoking the linked editor to produce another shared library) -# -Wl,-z,now: Disable lazy binding -# -Wl,-z,relro: Read-only segments after relocation -HARDENING_FLAGS=-fstack-protector-strong -Wp,-D_FORTIFY_SOURCE=2 -O3 -Wl,-z,relro,-z,now -fexceptions -funwind-tables -fasynchronous-unwind-tables -Wl,-z,defs -Wl,-z,now -Wl,-z,relro -DEBUG_FLAGS=-rdynamic -fno-omit-frame-pointer - -# -DSQLITE_OMIT_LOAD_EXTENSION: This option omits the entire extension loading mechanism from SQLite, including sqlite3_enable_load_extension() and sqlite3_load_extension() interfaces. (needs -ldl linking option, otherwise) -# -DSQLITE_DEFAULT_MEMSTATUS=0: This setting causes the sqlite3_status() interfaces that track memory usage to be disabled. This helps the sqlite3_malloc() routines run much faster, and since SQLite uses sqlite3_malloc() internally, this helps to make the entire library faster. -# -DSQLITE_OMIT_DEPRECATED: Omitting deprecated interfaces and features will not help SQLite to run any faster. It will reduce the library footprint, however. And it is the right thing to do. -# -DSQLITE_OMIT_PROGRESS_CALLBACK: The progress handler callback counter must be checked in the inner loop of the bytecode engine. By omitting this interface, a single conditional is removed from the inner loop of the bytecode engine, helping SQL statements to run slightly faster. -# -DSQLITE_DEFAULT_FOREIGN_KEYS=1: This macro determines whether enforcement of foreign key constraints is enabled or disabled by default for new database connections. -# -DSQLITE_DQS=0: This setting disables the double-quoted string literal misfeature. -SQLITE_FLAGS=-DSQLITE_OMIT_LOAD_EXTENSION -DSQLITE_DEFAULT_MEMSTATUS=0 -DSQLITE_OMIT_DEPRECATED -DSQLITE_OMIT_PROGRESS_CALLBACK -DSQLITE_OMIT_MEMORYDB -DSQLITE_DEFAULT_FOREIGN_KEYS=1 -DSQLITE_DQS=0 - -# -Wall: This enables all the warnings about constructions that some users consider questionable, and that are easy to avoid (or modify to prevent the warning), even in conjunction with macros. This also enables some language-specific warnings described in C++ Dialect Options and Objective-C and Objective-C++ Dialect Options. -# -Wextra: This enables some extra warning flags that are not enabled by -Wall. -# -Wno-unused-parameter: Disable warning for unused parameters. For threads that don't need arguments, we still have to provide a void* args which is then unused. -WARN_FLAGS=-Wall -Wextra -Wno-unused-parameter - -# Extra warning flags we apply only to the FTL part of the code (used not for foreign code such as dnsmasq and SQLite3) -# -Werror: Halt on any warnings, useful for enforcing clean code without any warnings (we use it only for our code part) -# -Waddress: Warn about suspicious uses of memory addresses -# -Wlogical-op: Warn about suspicious uses of logical operators in expressions -# -Wmissing-field-initializers: Warn if a structure's initializer has some fields missing -# -Woverlength-strings: Warn about string constants that are longer than the "minimum maximum length specified in the C standard -# -Wformat: Check calls to printf and scanf, etc., to make sure that the arguments supplied have types appropriate to the format string specified, and that the conversions specified in the format string make sense. -# -Wformat-nonliteral: If -Wformat is specified, also warn if the format string is not a string literal and so cannot be checked, unless the format function takes its format arguments as a va_list. -# -Wuninitialized: Warn if an automatic variable is used without first being initialized -# -Wswitch-enum: Warn whenever a switch statement has an index of enumerated type and lacks a case for one or more of the named codes of that enumeration. -# -Wshadow: Warn whenever a local variable or type declaration shadows another variable, parameter, type, class member, or whenever a built-in function is shadowed. -# -Wfloat-equal: Warn if floating-point values are used in equality comparisons -# -Wpointer-arith: Warn about anything that depends on the "size of" a function type or of "void". GNU C assigns these types a size of 1 -# -Wundef: Warn if an undefined identifier is evaluated in an "#if" directive -# -Wbad-function-cast: Warn when a function call is cast to a non-matching type -# -Wwrite-strings: When compiling C, give string constants the type "const char[length]" so that copying the address of one into a non-"const" "char *" pointer produces a warning -# -Wparentheses: Warn if parentheses are omitted in certain contexts, such as when there is an assignment in a context where a truth value is expected, or when operators are nested whose precedence people often get confused about -# -Wlogical-op: Warn about suspicious uses of logical operators in expressions -# -Wstrict-prototypes: Warn if a function is declared or defined without specifying the argument types -# -Wmissing-prototypes: Warn if a global function is defined without a previous prototype declaration -# -Wredundant-decls: Warn if anything is declared more than once in the same scope -# -Winline: Warn if a function that is declared as inline cannot be inlined -ifeq "$(GCCVERSION8)" "1" - # -Wduplicated-cond: Warn about duplicated conditions in an if-else-if chain - # -Wduplicated-branches: Warn when an if-else has identical branches - # -Wcast-align=strict: Warn whenever a pointer is cast such that the required alignment of the target is increased. For example, warn if a "char *" is cast to an "int *" regardless of the target machine. - # -Wlogical-not-parentheses: Warn about logical not used on the left hand side operand of a comparison - EXTRAWARN_GCC8=-Wduplicated-cond -Wduplicated-branches -Wcast-align=strict -Wlogical-not-parentheses -Wsuggest-attribute=pure -Wsuggest-attribute=const -Wsuggest-attribute=noreturn -Wsuggest-attribute=malloc -Wsuggest-attribute=format -Wsuggest-attribute=cold -else - EXTRAWARN_GCC8= -endif -EXTRAWARN=-Werror -Waddress -Wlogical-op -Wmissing-field-initializers -Woverlength-strings -Wformat -Wformat-nonliteral -Wuninitialized -Wswitch-enum -Wshadow \ --Wfloat-equal -Wbad-function-cast -Wwrite-strings -Wparentheses -Wlogical-op -Wstrict-prototypes -Wmissing-prototypes -Wredundant-decls -Winline $(EXTRAWARN_GCC8) - -# -FILE_OFFSET_BITS=64: used by stat(). Avoids problems with files > 2 GB on 32bit machines -CCFLAGS=-std=gnu11 -pipe -I$(IDIR) $(WARN_FLAGS) -D_FILE_OFFSET_BITS=64 $(HARDENING_FLAGS) $(DEBUG_FLAGS) $(CFLAGS) $(SQLITE_FLAGS) -DHAVE_POLL_H -# We define HAVE_POLL_H as this is needed for the musl builds to succeed - -# for FTL we need the pthread library -# for dnsmasq we need the nettle crypto library and the gmp maths library -# We link the two libraries statically. Although this increases the binary file size by about 1 MB, it saves about 5 MB of shared libraries and makes deployment easier -LIBS=-pthread -lrt -Wl,-Bstatic -L/usr/local/lib -lhogweed -lgmp -lnettle -lidn - -# Do we want to compile a statically linked musl executable? -ifeq "$(STATIC)" "true" - CC := $(CC) -Wl,-Bstatic -static-libgcc -static-pie -else - LIBS := $(LIBS) -Wl,-Bdynamic - # -pie -fPIE: (Dynamic) position independent executable - HARDENING_FLAGS := $(HARDENING_FLAGS) -pie -fPIE -endif - -DB_OBJ_DIR = $(ODIR)/database -API_OBJ_DIR = $(ODIR)/api -DNSMASQ_OBJ_DIR = $(ODIR)/dnsmasq -CIVETWEB_OBJ_DIR = $(ODIR)/civetweb -CJSON_OBJ_DIR = $(ODIR)/cJSON - -_FTL_DEPS = $(patsubst %,$(IDIR)/%,$(FTL_DEPS)) -_FTL_OBJ = $(patsubst %,$(ODIR)/%,$(FTL_OBJ)) - -_DNSMASQ_DEPS = $(patsubst %,$(IDIR)/dnsmasq/%,$(DNSMASQ_DEPS)) -_DNSMASQ_OBJ = $(patsubst %,$(DNSMASQ_OBJ_DIR)/%,$(DNSMASQ_OBJ)) - -_CIVETWEB_DEPS = $(patsubst %,$(IDIR)/civetweb/%,$(CIVETWEB_DEPS)) -_CIVETWEB_OBJ = $(patsubst %,$(CIVETWEB_OBJ_DIR)/%,$(CIVETWEB_OBJ)) - -_CJSON_DEPS = $(patsubst %,$(IDIR)/cJSON/%,$(CJSON_DEPS)) -_CJSON_OBJ = $(patsubst %,$(CJSON_OBJ_DIR)/%,$(CJSON_OBJ)) - -all: pihole-FTL - -# Compile FTL source code files with virtually all possible warnings a modern gcc can generate -$(_FTL_OBJ): $(ODIR)/%.o: $(IDIR)/%.c $(_FTL_DEPS) | $(ODIR) $(DB_OBJ_DIR) $(API_OBJ_DIR) - $(CC) -c -o $@ $< -g3 $(CCFLAGS) $(EXTRAWARN) - -# Compile the contained external codes with much less strict requirements as they fail to compile -# when enforcing the standards we enforce for the rest of our FTL code base -$(_DNSMASQ_OBJ): $(DNSMASQ_OBJ_DIR)/%.o: $(IDIR)/dnsmasq/%.c $(_DNSMASQ_DEPS) | $(DNSMASQ_OBJ_DIR) - $(CC) -c -o $@ $< -g3 $(CCFLAGS) -DVERSION=\"$(DNSMASQ_VERSION)\" $(DNSMASQ_OPTS) -$(_CIVETWEB_OBJ): $(CIVETWEB_OBJ_DIR)/%.o: $(IDIR)/civetweb/%.c $(_CIVETWEB_DEPS) | $(CIVETWEB_OBJ_DIR) - $(CC) -c -o $@ $< -g3 $(CCFLAGS) $(CIVETWEB_OPTS) -$(_CJSON_OBJ): $(CJSON_OBJ_DIR)/%.o: $(IDIR)/cJSON/%.c $(_CJSON_DEPS) | $(CJSON_OBJ_DIR) - $(CC) -c -o $@ $< -g3 $(CCFLAGS) - -$(DB_OBJ_DIR)/sqlite3.o: $(IDIR)/database/sqlite3.c | $(DB_OBJ_DIR) - $(CC) -c -o $@ $< -g3 $(CCFLAGS) - -$(ODIR): - mkdir -p $(ODIR) - -$(DB_OBJ_DIR): - mkdir -p $(DB_OBJ_DIR) - -$(API_OBJ_DIR): - mkdir -p $(API_OBJ_DIR) - -$(DNSMASQ_OBJ_DIR): - mkdir -p $(DNSMASQ_OBJ_DIR) - -$(CIVETWEB_OBJ_DIR): - mkdir -p $(CIVETWEB_OBJ_DIR) - -$(CJSON_OBJ_DIR): - mkdir -p $(CJSON_OBJ_DIR) - -pihole-FTL: $(_FTL_OBJ) $(_DNSMASQ_OBJ) $(_CIVETWEB_OBJ) $(_CJSON_OBJ) $(DB_OBJ_DIR)/sqlite3.o - $(CC) $(CCFLAGS) -o $@ $^ $(LIBS) - -.PHONY: clean force install - -clean: - rm -rf $(ODIR) pihole-FTL - -# If CIRCLE_JOB is unset (local compilation), ask uname -m and add locally compiled comment -ifeq ($(strip $(CIRCLE_JOB)),) -FTL_ARCH := $(shell uname -m) (compiled locally) -else -FTL_ARCH := $(CIRCLE_JOB) (compiled on CI) -endif -# Get compiler version -FTL_CC := $(shell $(CC) --version | head -n 1) - -# # recreate version.h when GIT_VERSION changes, uses temporary file version~ -$(IDIR)/version~: force - @echo '$(GIT_BRANCH) $(GIT_VERSION) $(GIT_DATE) $(GIT_TAG)' | cmp -s - $@ || echo '$(GIT_BRANCH) $(GIT_VERSION) $(GIT_DATE) $(GIT_TAG)' > $@ -$(IDIR)/version.h: $(IDIR)/version~ - @echo '#ifndef VERSION_H' > "$@" - @echo '#define VERSION_H' >> "$@" - @echo '#define GIT_VERSION "$(GIT_VERSION)"' >> "$@" - @echo '#define GIT_DATE "$(GIT_DATE)"' >> "$@" - @echo '#define GIT_BRANCH "$(GIT_BRANCH)"' >> "$@" - @echo '#define GIT_TAG "$(GIT_TAG)"' >> "$@" - @echo '#define GIT_HASH "$(GIT_HASH)"' >> "$@" - @echo '#define FTL_ARCH "$(FTL_ARCH)"' >> "$@" - @echo '#define FTL_CC "$(FTL_CC)"' >> "$@" - @echo '#endif // VERSION_H' >> "$@" - @echo "Making FTL version on branch $(GIT_BRANCH) - $(GIT_VERSION) / $(GIT_TAG) / $(GIT_HASH) ($(GIT_DATE))" - -PREFIX=/usr -SETCAP = $(shell which setcap) - -# install target just installs the executable -# other requirements (correct ownership of files, etc.) is managed by -# the service script on sudo service pihole-FTL (re)start -install: pihole-FTL - mkdir -p $(DESTDIR)$(PREFIX)/bin - install -m 0755 pihole-FTL $(DESTDIR)$(PREFIX)/bin - $(SETCAP) CAP_NET_BIND_SERVICE,CAP_NET_RAW,CAP_NET_ADMIN+eip $(DESTDIR)$(PREFIX)/bin/pihole-FTL diff --git a/build.sh b/build.sh new file mode 100755 index 000000000..00e379a7d --- /dev/null +++ b/build.sh @@ -0,0 +1,35 @@ +#!/bin/bash +# Pi-hole: A black hole for Internet advertisements +# (c) 2020 Pi-hole, LLC (https://pi-hole.net) +# Network-wide ad blocking via your own hardware. +# +# FTL Engine +# Build script for FTL +# +# This file is copyright under the latest version of the EUPL. +# Please see LICENSE file for your rights under this license. + +# Abort script if one command returns a non-zero value +set -e + +# Prepare build environment +if [[ "${1}" == "clean" ]]; then + rm -rf cmake/ + exit 0 +fi + +# Configure build +mkdir -p cmake +cd cmake +cmake .. + +# Build the sources +cmake --build . -- -j $(nproc) + +# If we are asked to install, we do this here +# Otherwise, we simply copy the binary one level up +if [[ "${1}" == "install" ]]; then + sudo make install +else + cp pihole-FTL ../ +fi diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index d0c3ffc21..e77570241 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,3 +1,13 @@ +# Pi-hole: A black hole for Internet advertisements +# (c) 2020 Pi-hole, LLC (https://pi-hole.net) +# Network-wide ad blocking via your own hardware. +# +# FTL Engine +# /src/CMakeList.txt +# +# This file is copyright under the latest version of the EUPL. +# Please see LICENSE file for your rights under this license. + set(CMAKE_C_STANDARD 11) # Default to a release with debug info build @@ -61,7 +71,7 @@ set(WARN_FLAGS "-Wall -Wextra -Wno-unused-parameter") # -Wmissing-prototypes: Warn if a global function is defined without a previous prototype declaration # -Wredundant-decls: Warn if anything is declared more than once in the same scope # -Winline: Warn if a function that is declared as inline cannot be inlined -if(CMAKE_C_COMPILER_VERSION VERSION_GREATER_EQUAL 8) +if(CMAKE_C_COMPILER_VERSION VERSION_EQUAL 8 OR CMAKE_C_COMPILER_VERSION VERSION_GREATER 8) # -Wduplicated-cond: Warn about duplicated conditions in an if-else-if chain # -Wduplicated-branches: Warn when an if-else has identical branches # -Wcast-align=strict: Warn whenever a pointer is cast such that the required alignment of the target is increased. For example, warn if a "char *" is cast to an "int *" regardless of the target machine. @@ -113,6 +123,8 @@ set(sources log.h main.c main.h + math.c + math.h memory.c memory.h overTime.c @@ -144,6 +156,8 @@ add_custom_target( add_executable(pihole-FTL ${sources} $ + $ + $ $ $ $ @@ -151,7 +165,7 @@ add_executable(pihole-FTL if(STATIC STREQUAL "true") set_target_properties(pihole-FTL PROPERTIES LINK_SEARCH_START_STATIC ON) set_target_properties(pihole-FTL PROPERTIES LINK_SEARCH_END_STATIC ON) - target_link_libraries(pihole-FTL -static-libgcc -static) + target_link_libraries(pihole-FTL -static-libgcc -static -static-pie) endif() target_compile_options(pihole-FTL PRIVATE ${EXTRAWARN}) target_include_directories(pihole-FTL PRIVATE ${PROJECT_SOURCE_DIR}/src) @@ -182,5 +196,7 @@ install(TARGETS pihole-FTL install(CODE "execute_process(COMMAND ${SETCAP} CAP_NET_BIND_SERVICE,CAP_NET_RAW,CAP_NET_ADMIN+eip ${CMAKE_INSTALL_PREFIX}/bin/pihole-FTL)") add_subdirectory(api) +add_subdirectory(civetweb) +add_subdirectory(cJSON) add_subdirectory(database) add_subdirectory(dnsmasq) diff --git a/src/api/CMakeLists.txt b/src/api/CMakeLists.txt index b4ba50035..725108ed4 100644 --- a/src/api/CMakeLists.txt +++ b/src/api/CMakeLists.txt @@ -1,11 +1,28 @@ +# Pi-hole: A black hole for Internet advertisements +# (c) 2020 Pi-hole, LLC (https://pi-hole.net) +# Network-wide ad blocking via your own hardware. +# +# FTL Engine +# /src/api/CMakeList.txt +# +# This file is copyright under the latest version of the EUPL. +# Please see LICENSE file for your rights under this license. + set(sources - api.c - api.h - msgpack.c - request.c - request.h - socket.c - socket.h + auth.c + dns.c + dns.h + ftl.c + ftl.h + http-common.c + http-common.h + json_macros.h + routes.c + routes.h + settings.c + stats_database.c + stats.c + version.c ) add_library(api OBJECT ${sources}) diff --git a/src/api/request.c b/src/api/request.c deleted file mode 100644 index 607d5b3b7..000000000 --- a/src/api/request.c +++ /dev/null @@ -1,206 +0,0 @@ -/* Pi-hole: A black hole for Internet advertisements -* (c) 2017 Pi-hole, LLC (https://pi-hole.net) -* Network-wide ad blocking via your own hardware. -* -* FTL Engine -* Socket request handling routines -* -* This file is copyright under the latest version of the EUPL. -* Please see LICENSE file for your rights under this license. */ - -#include "FTL.h" -#include "api.h" -#include "shmem.h" -#include "timers.h" -#include "request.h" -#include "socket.h" -#include "resolve.h" -#include "regex_r.h" -#include "database/network-table.h" -#include "log.h" - -bool __attribute__((pure)) command(const char *client_message, const char* cmd) { - return strstr(client_message, cmd) != NULL; -} - -void process_request(const char *client_message, int *sock) -{ - char EOT[2]; - EOT[0] = 0x04; - EOT[1] = 0x00; - bool processed = false; - - if(command(client_message, ">stats")) - { - processed = true; - lock_shm(); - getStats(sock); - unlock_shm(); - } - else if(command(client_message, ">overTime")) - { - processed = true; - lock_shm(); - getOverTime(sock); - unlock_shm(); - } - else if(command(client_message, ">top-domains") || command(client_message, ">top-ads")) - { - processed = true; - lock_shm(); - getTopDomains(client_message, sock); - unlock_shm(); - } - else if(command(client_message, ">top-clients")) - { - processed = true; - lock_shm(); - getTopClients(client_message, sock); - unlock_shm(); - } - else if(command(client_message, ">forward-dest")) - { - processed = true; - lock_shm(); - getUpstreamDestinations(client_message, sock); - unlock_shm(); - } - else if(command(client_message, ">forward-names")) - { - processed = true; - lock_shm(); - getUpstreamDestinations(">forward-dest unsorted", sock); - unlock_shm(); - } - else if(command(client_message, ">querytypes")) - { - processed = true; - lock_shm(); - getQueryTypes(sock); - unlock_shm(); - } - else if(command(client_message, ">getallqueries")) - { - processed = true; - lock_shm(); - getAllQueries(client_message, sock); - unlock_shm(); - } - else if(command(client_message, ">recentBlocked")) - { - processed = true; - lock_shm(); - getRecentBlocked(client_message, sock); - unlock_shm(); - } - else if(command(client_message, ">clientID")) - { - processed = true; - lock_shm(); - getClientID(sock); - unlock_shm(); - } - else if(command(client_message, ">QueryTypesoverTime")) - { - processed = true; - lock_shm(); - getQueryTypesOverTime(sock); - unlock_shm(); - } - else if(command(client_message, ">version")) - { - processed = true; - // No lock required - getVersion(sock); - } - else if(command(client_message, ">dbstats")) - { - processed = true; - // No lock required. Access to the database - // is guaranteed to be atomic - getDBstats(sock); - } - else if(command(client_message, ">ClientsoverTime")) - { - processed = true; - lock_shm(); - getClientsOverTime(sock); - unlock_shm(); - } - else if(command(client_message, ">client-names")) - { - processed = true; - lock_shm(); - getClientNames(sock); - unlock_shm(); - } - else if(command(client_message, ">unknown")) - { - processed = true; - lock_shm(); - getUnknownQueries(sock); - unlock_shm(); - } - else if(command(client_message, ">domain")) - { - processed = true; - lock_shm(); - getDomainDetails(client_message, sock); - unlock_shm(); - } - else if(command(client_message, ">cacheinfo")) - { - processed = true; - lock_shm(); - getCacheInformation(sock); - unlock_shm(); - } - else if(command(client_message, ">reresolve")) - { - processed = true; - logg("Received API request to re-resolve host names"); - // Important: Don't obtain a lock for this request - // Locking will be done internally when needed - // onlynew=false -> reresolve all host names - resolveClients(false); - resolveForwardDestinations(false); - logg("Done re-resolving host names"); - } - else if(command(client_message, ">recompile-regex")) - { - processed = true; - logg("Received API request to recompile regex"); - lock_shm(); - // Reread regex.list - // Read and compile possible regex filters - read_regex_from_database(); - unlock_shm(); - } - else if(command(client_message, ">update-mac-vendor")) - { - processed = true; - logg("Received API request to update vendors in network table"); - updateMACVendorRecords(); - } - - // Test only at the end if we want to quit or kill - // so things can be processed before - if(command(client_message, ">quit") || command(client_message, EOT)) - { - processed = true; - close(*sock); - *sock = 0; - } - - if(!processed) - { - ssend(*sock,"unknown command: %s\n",client_message); - } - - // End of queryable commands - if(*sock != 0) - { - // Send EOM - seom(*sock); - } -} diff --git a/src/cJSON/CMakeLists.txt b/src/cJSON/CMakeLists.txt new file mode 100644 index 000000000..54ab2a711 --- /dev/null +++ b/src/cJSON/CMakeLists.txt @@ -0,0 +1,18 @@ +# Pi-hole: A black hole for Internet advertisements +# (c) 2020 Pi-hole, LLC (https://pi-hole.net) +# Network-wide ad blocking via your own hardware. +# +# FTL Engine +# /src/cJSON/CMakeList.txt +# +# This file is copyright under the latest version of the EUPL. +# Please see LICENSE file for your rights under this license. + +set(sources + cJSON.c + cJSON.h + ) + +add_library(cJSON OBJECT ${sources}) +target_compile_options(cJSON PRIVATE) +target_include_directories(cJSON PRIVATE ${PROJECT_SOURCE_DIR}/src) diff --git a/src/civetweb/CMakeLists.txt b/src/civetweb/CMakeLists.txt new file mode 100644 index 000000000..672f8caaa --- /dev/null +++ b/src/civetweb/CMakeLists.txt @@ -0,0 +1,30 @@ +# Pi-hole: A black hole for Internet advertisements +# (c) 2020 Pi-hole, LLC (https://pi-hole.net) +# Network-wide ad blocking via your own hardware. +# +# FTL Engine +# /src/civetweb/CMakeList.txt +# +# This file is copyright under the latest version of the EUPL. +# Please see LICENSE file for your rights under this license. + +set(sources + civetweb.c + civetweb.h + ) + +# We can remove the NO_SSL later on. It adds additional constraints to the build system (availablity of libSSL-dev) +# NO_CGI = no CGI support (we don't need it) +# NO_SSL_DL NO_SSL = no SSL support (for now) +# USE_SERVER_STATS = makes a few anonymous statistics available, such as +# - Number of connections (currently and total) +# - Amount of data read and written +# USE_IPV6: add IPv6 support +set(CIVETWEB_OPTS + "" + ) + + +add_library(civetweb OBJECT ${sources}) +target_compile_definitions(civetweb PRIVATE NO_CGI NO_SSL_DL NO_SSL USE_SERVER_STATS USE_IPV6) +target_include_directories(civetweb PRIVATE ${PROJECT_SOURCE_DIR}/src) diff --git a/src/database/CMakeLists.txt b/src/database/CMakeLists.txt index f86a1e57f..944b3b7a3 100644 --- a/src/database/CMakeLists.txt +++ b/src/database/CMakeLists.txt @@ -1,3 +1,13 @@ +# Pi-hole: A black hole for Internet advertisements +# (c) 2020 Pi-hole, LLC (https://pi-hole.net) +# Network-wide ad blocking via your own hardware. +# +# FTL Engine +# /src/database/CMakeList.txt +# +# This file is copyright under the latest version of the EUPL. +# Please see LICENSE file for your rights under this license. + # This file has no dependencies on other code in the repo and takes a long time # to build. It is placed in its own target so that it does not include any other # headers in the build command and thus does not need to be rebuilt when headers diff --git a/src/dnsmasq/CMakeLists.txt b/src/dnsmasq/CMakeLists.txt index 79269922e..099367877 100644 --- a/src/dnsmasq/CMakeLists.txt +++ b/src/dnsmasq/CMakeLists.txt @@ -1,3 +1,13 @@ +# Pi-hole: A black hole for Internet advertisements +# (c) 2020 Pi-hole, LLC (https://pi-hole.net) +# Network-wide ad blocking via your own hardware. +# +# FTL Engine +# /src/dnsmasq/CMakeList.txt +# +# This file is copyright under the latest version of the EUPL. +# Please see LICENSE file for your rights under this license. + set(sources arp.c auth.c diff --git a/src/gen_version.cmake b/src/gen_version.cmake index d8405275f..05745a269 100644 --- a/src/gen_version.cmake +++ b/src/gen_version.cmake @@ -1,4 +1,16 @@ -if(NOT DEFINED GIT_BRANCH) +# Pi-hole: A black hole for Internet advertisements +# (c) 2020 Pi-hole, LLC (https://pi-hole.net) +# Network-wide ad blocking via your own hardware. +# +# FTL Engine +# /src/gen_version.cmake +# +# This file is copyright under the latest version of the EUPL. +# Please see LICENSE file for your rights under this license. + +if(DEFINED ENV{GIT_BRANCH}) + set(GIT_BRANCH "$ENV{GIT_BRANCH}") +else() execute_process( COMMAND bash -c "git branch | sed -n 's/^\\* //p'" WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} @@ -8,7 +20,9 @@ if(NOT DEFINED GIT_BRANCH) ) endif() -if(NOT DEFINED GIT_HASH) +if(DEFINED ENV{GIT_HASH}) + set(GIT_HASH "$ENV{GIT_HASH}") +else() execute_process( COMMAND git --no-pager describe --always --dirty WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} @@ -18,7 +32,9 @@ if(NOT DEFINED GIT_HASH) ) endif() -if(NOT DEFINED GIT_VERSION) +if(DEFINED ENV{GIT_VERSION}) + set(GIT_VERSION "$ENV{GIT_VERSION}") +else() execute_process( COMMAND git --no-pager describe --tags --always --dirty WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} @@ -28,7 +44,9 @@ if(NOT DEFINED GIT_VERSION) ) endif() -if(NOT DEFINED GIT_DATE) +if(DEFINED ENV{GIT_DATE}) + set(GIT_DATE "$ENV{GIT_DATE}") +else() execute_process( COMMAND bash -c "git --no-pager show --date=short --format=\"%ai\" --name-only | head -n 1" WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} @@ -38,7 +56,9 @@ if(NOT DEFINED GIT_DATE) ) endif() -if(NOT DEFINED GIT_TAG) +if(DEFINED ENV{GIT_TAG}) + set(GIT_TAG "$ENV{GIT_TAG}") +else() execute_process( COMMAND git describe --tags --abbrev=0 WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} @@ -49,8 +69,8 @@ if(NOT DEFINED GIT_TAG) endif() # If CIRCLE_JOB is unset (local compilation), ask uname -m and add locally compiled comment -if(DEFINED CIRCLE_JOB) - set(FTL_ARCH "${CIRCLE_JOB} (compiled on CI)") +if(DEFINED ENV{CIRCLE_JOB}) + set(FTL_ARCH "$ENV{CIRCLE_JOB} (compiled on CI)") else() execute_process( COMMAND uname -m @@ -69,7 +89,13 @@ execute_process( OUTPUT_STRIP_TRAILING_WHITESPACE ) -message("Making FTL version on branch ${GIT_BRANCH} - ${GIT_VERSION} / ${GIT_TAG} / ${GIT_HASH} (${GIT_DATE})") +message("Building Pi-hole FTL daemon") +message(" - Branch: ${GIT_BRANCH}") +message(" - Architecture: ${FTL_ARCH}") +message(" - Version: ${GIT_VERSION}") +message(" - Tag: ${GIT_TAG}") +message(" - Hash: ${GIT_HASH}") +message(" - Commit date: ${GIT_DATE}") # configure the version file, but output to a temporary location configure_file( From 308c80bc5ab5c8f5e2011d49c86f4ee42ef6e695 Mon Sep 17 00:00:00 2001 From: DL6ER Date: Thu, 28 May 2020 23:59:52 +0200 Subject: [PATCH 0152/1669] Add PHP support in Pi-hole' HTTP server. Signed-off-by: DL6ER --- src/api/http-common.c | 3 +++ src/civetweb/CMakeLists.txt | 3 +-- src/config.c | 11 +++++++++++ src/config.h | 1 + 4 files changed, 16 insertions(+), 2 deletions(-) diff --git a/src/api/http-common.c b/src/api/http-common.c index 440459e1d..b9df1d076 100644 --- a/src/api/http-common.c +++ b/src/api/http-common.c @@ -324,6 +324,9 @@ void http_init(void) "X-Xss-Protection: 1; mode=block\r\n" "X-Content-Type-Options: nosniff\r\n" "Referrer-Policy: same-origin", + "cgi_interpreter", httpsettings.php_location, + "cgi_pattern", "**.php$", // ** allows the files to by anywhere inside the web root + "index_files", "index.html,index.htm,index.php", NULL }; diff --git a/src/civetweb/CMakeLists.txt b/src/civetweb/CMakeLists.txt index 672f8caaa..139f33b7c 100644 --- a/src/civetweb/CMakeLists.txt +++ b/src/civetweb/CMakeLists.txt @@ -14,7 +14,6 @@ set(sources ) # We can remove the NO_SSL later on. It adds additional constraints to the build system (availablity of libSSL-dev) -# NO_CGI = no CGI support (we don't need it) # NO_SSL_DL NO_SSL = no SSL support (for now) # USE_SERVER_STATS = makes a few anonymous statistics available, such as # - Number of connections (currently and total) @@ -26,5 +25,5 @@ set(CIVETWEB_OPTS add_library(civetweb OBJECT ${sources}) -target_compile_definitions(civetweb PRIVATE NO_CGI NO_SSL_DL NO_SSL USE_SERVER_STATS USE_IPV6) +target_compile_definitions(civetweb PRIVATE NO_SSL_DL NO_SSL USE_SERVER_STATS USE_IPV6) target_include_directories(civetweb PRIVATE ${PROJECT_SOURCE_DIR}/src) diff --git a/src/config.c b/src/config.c index ae72feba4..9a767eae5 100644 --- a/src/config.c +++ b/src/config.c @@ -448,6 +448,17 @@ void read_FTLconf(void) else logg(" API_PRETTY_JSON: Disabled. Compact API output."); + // PHP-CGI + // defaults to: /usr/bin/php-cgi + // Path to an executable to use as CGI interpreter for all CGI scripts + // regardless of the script file extension. We use this for PHP. It + // needs to point at php-cgi. php-cli will not work + httpsettings.php_location = "/usr/bin/php-cgi"; + buffer = parse_FTLconf(fp, "PHP.CGI"); + if(buffer != NULL) + httpsettings.php_location = strdup(buffer); + logg(" PHP-CGI: Using %s", httpsettings.php_location); + // Read DEBUG_... setting from pihole-FTL.conf // This option should be the last one as it causes // some rather verbose output into the log when diff --git a/src/config.h b/src/config.h index ed52edf81..3b7fbb9dc 100644 --- a/src/config.h +++ b/src/config.h @@ -56,6 +56,7 @@ typedef struct { typedef struct httpsettings { char *webroot; char *webhome; + const char *php_location; const char *acl; bool api_auth_for_localhost; bool prettyJSON; From 473a88f0778586fd1914be98fc02e0f3c8207898 Mon Sep 17 00:00:00 2001 From: DL6ER Date: Fri, 29 May 2020 00:17:18 +0200 Subject: [PATCH 0153/1669] Remove React-specific index page rerouting. Signed-off-by: DL6ER --- src/api/http-common.c | 121 ------------------------------------------ src/shmem.c | 3 +- src/signals.c | 9 ---- 3 files changed, 2 insertions(+), 131 deletions(-) diff --git a/src/api/http-common.c b/src/api/http-common.c index b9df1d076..909a3d96e 100644 --- a/src/api/http-common.c +++ b/src/api/http-common.c @@ -159,110 +159,6 @@ static int print_simple(struct mg_connection *conn, void *input) return send_http(conn, "text/plain", input); } -static char *indexfile_content = NULL; -static void prepare_index_html(void) -{ - char *index_path = NULL; - if(asprintf(&index_path, "%s%sindex.html", httpsettings.webroot, httpsettings.webhome) < 0) - { - logg("prepare_index_html(): Memory error (1)"); - return; - } - char *base_tag = NULL; - if(asprintf(&base_tag, "", httpsettings.webhome) < 0) - { - logg("prepare_index_html(): Memory error (2)"); - } - unsigned int base_tag_length = strlen(base_tag); - - FILE *indexfile = fopen(index_path, "r"); - if(indexfile == NULL) - { - logg("ERROR: Cannot open \"%s\"", index_path); - free(index_path); - return; - } - - // Get file size by seeking the EOF - fseek(indexfile, 0, SEEK_END); - size_t fsize = ftell(indexfile); - - // Go back to the beginning - fseek(indexfile, 0, SEEK_SET); - - // Allocate memory for the index file - indexfile_content = calloc(fsize + base_tag_length + 1, sizeof(char)); - if(indexfile_content == NULL) - { - logg("prepare_index_html(): Memory error (3)"); - free(index_path); - free(base_tag); - } - - // Read entire file into buffer - if(fread(indexfile_content, sizeof(char), fsize, indexfile) != fsize) - { - logg("WARNING: Filesize of \"%s\" changed during reading.", index_path); - } - - // Close file handle - fclose(indexfile); - - // Zero-terminate string - indexfile_content[fsize] = '\0'; - - // Find "" - char *head_ptr = strstr(indexfile_content, ""); - if(head_ptr == NULL) - { - logg("ERROR: No tag found in \"%s\"", index_path); - free(index_path); - free(base_tag); - return; - } - - // Advance beyond the tag - head_ptr += 6u; // 6u == strlen(""); - - // Make space for tag to be inserted - memmove(head_ptr + base_tag_length, head_ptr, base_tag_length); - - // Insert tag into new space - memcpy(head_ptr, base_tag, base_tag_length); - - // Free memory - free(index_path); - free(base_tag); -} - -static int index_handler(struct mg_connection *conn, void *ignored) -{ - const struct mg_request_info *request = mg_get_request_info(conn); - - if(strstr(request->local_uri, ".") > strstr(request->local_uri, "/")) - { - // Found file extension, process as usual - return 0; - } - if(config.debug & DEBUG_API) - logg("Received request for %s -> rerouting to index.html", request->local_uri); - - // Plain request found, we serve the index.html file we have in memory - if(indexfile_content != NULL) - { - mg_send_http_ok(conn, "text/html", NULL, strlen(indexfile_content)); - mg_write(conn, indexfile_content, strlen(indexfile_content)); - return 200; - } - else - { - logg("ERROR: index.html not available, responding with Error 500."); - send_http_internal_error(conn); - return 500; - } - -} - static int log_http_message(const struct mg_connection *conn, const char *message) { logg("HTTP info: %s", message); @@ -356,19 +252,6 @@ void http_init(void) // The request handler URI got duplicated free(api_path); } - - prepare_index_html(); - mg_set_request_handler(ctx, httpsettings.webhome, index_handler, NULL); -} - -void http_reread_index_html(void) -{ - // Release memory for the index.html file - if(indexfile_content != NULL) - free(indexfile_content); - - // Re-read index.html into memory - prepare_index_html(); } void http_terminate(void) @@ -376,10 +259,6 @@ void http_terminate(void) /* Stop the server */ mg_stop(ctx); - // Release memory for the index.html file - free(indexfile_content); - indexfile_content = NULL; - /* Un-initialize the library */ mg_exit_library(); } diff --git a/src/shmem.c b/src/shmem.c index 46f6bd61d..fd7e2e60f 100644 --- a/src/shmem.c +++ b/src/shmem.c @@ -353,7 +353,8 @@ bool init_shmem(void) void destroy_shmem(void) { - pthread_mutex_destroy(&shmLock->lock); + if(&shmLock->lock != NULL) + pthread_mutex_destroy(&shmLock->lock); shmLock = NULL; delete_shm(&shm_lock); diff --git a/src/signals.c b/src/signals.c index 4819c53b8..930af2018 100644 --- a/src/signals.c +++ b/src/signals.c @@ -22,8 +22,6 @@ #include "config.h" #define BINARY_NAME "pihole-FTL" -// http_reread_index_html() -#include "api/http-common.h" volatile sig_atomic_t killed = 0; static time_t FTLstarttime = 0; @@ -154,13 +152,6 @@ static void SIGRT_handler(int signum, siginfo_t *si, void *unused) // Reload the privacy level in case the user changed it get_privacy_level(NULL); } - else if(rtsig == 1) - { - // Re-read index.html - // This is necessary when the content of /admin is updated as - // the paths of the contained JS/CSS scripts will have changed - http_reread_index_html(); - } } void handle_signals(void) From 2c6e2ae41fd9f02734ebf90954b344cba6f5b393 Mon Sep 17 00:00:00 2001 From: DL6ER Date: Fri, 29 May 2020 00:42:35 +0200 Subject: [PATCH 0154/1669] Add NAPTR support for /api/stats/summary Signed-off-by: DL6ER --- src/api/stats.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/api/stats.c b/src/api/stats.c index 017b98a8c..21eb203bd 100644 --- a/src/api/stats.c +++ b/src/api/stats.c @@ -101,6 +101,7 @@ int api_stats_summary(struct mg_connection *conn) JSON_OBJ_ADD_NUMBER(total_queries, "SOA", counters->querytype[TYPE_SOA]); JSON_OBJ_ADD_NUMBER(total_queries, "PTR", counters->querytype[TYPE_PTR]); JSON_OBJ_ADD_NUMBER(total_queries, "TXT", counters->querytype[TYPE_TXT]); + JSON_OBJ_ADD_NUMBER(total_queries, "NAPTR", counters->querytype[TYPE_NAPTR]); JSON_OBJ_ADD_ITEM(json, "total_queries", total_queries); JSON_OBJ_ADD_NUMBER(json, "sum_queries", counters->queries); From c92a0e4bc4d03527489db32ab70e18bffe1db64b Mon Sep 17 00:00:00 2001 From: DL6ER Date: Fri, 29 May 2020 20:54:30 +0200 Subject: [PATCH 0155/1669] Generally assume authentication to ease the development phase until we are actually able to add authentication. Signed-off-by: DL6ER --- src/api/auth.c | 3 +++ src/api/stats.c | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/api/auth.c b/src/api/auth.c index fd9ad1250..a41f203a9 100644 --- a/src/api/auth.c +++ b/src/api/auth.c @@ -51,6 +51,9 @@ int check_client_auth(struct mg_connection *conn) strcmp(request->remote_addr, LOCALHOSTv6) == 0)) return API_MAX_CLIENTS; + // FIXME: Generally assume authorization for now while we are working on the dashboard! + return API_MAX_CLIENTS; + // Does the client provide a user_id cookie? int num; if(http_get_cookie_int(conn, "user_id", &num) && num > -1 && num < API_MAX_CLIENTS) diff --git a/src/api/stats.c b/src/api/stats.c index 21eb203bd..f5db08b02 100644 --- a/src/api/stats.c +++ b/src/api/stats.c @@ -454,7 +454,7 @@ int api_stats_upstreams(struct mg_connection *conn) { return send_json_unauthorized(conn); } - for(int upstreamID = 0; upstreamID < counters->forwarded; upstreamID++) + for(int upstreamID = 0; upstreamID < counters->upstreams; upstreamID++) { // Get forward pointer const upstreamsData* forward = getUpstream(upstreamID, true); From de09b73eb788e08813d7db6cb7c328f58c95b67c Mon Sep 17 00:00:00 2001 From: DL6ER Date: Sat, 30 May 2020 15:39:41 +0200 Subject: [PATCH 0156/1669] Use embedded PH7 instead of external PHP-CGI interpreter. Signed-off-by: DL6ER --- src/CMakeLists.txt | 2 + src/api/CMakeLists.txt | 2 + src/api/http-common.c | 114 - src/api/http-common.h | 3 - src/api/ph7.c | 205 + src/api/ph7.h | 17 + src/api/webserver.c | 141 + src/api/webserver.h | 16 + src/civetweb/CMakeLists.txt | 3 +- src/config.c | 11 - src/config.h | 1 - src/dnsmasq_interface.c | 2 +- src/main.c | 2 +- src/ph7/CMakeLists.txt | 17 + src/ph7/license.txt | 44 + src/ph7/ph7.c | 62027 ++++++++++++++++++++++++++++++++++ src/ph7/ph7.h | 714 + 17 files changed, 63189 insertions(+), 132 deletions(-) create mode 100644 src/api/ph7.c create mode 100644 src/api/ph7.h create mode 100644 src/api/webserver.c create mode 100644 src/api/webserver.h create mode 100644 src/ph7/CMakeLists.txt create mode 100644 src/ph7/license.txt create mode 100644 src/ph7/ph7.c create mode 100644 src/ph7/ph7.h diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index e77570241..78dc5ede1 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -158,6 +158,7 @@ add_executable(pihole-FTL $ $ $ + $ $ $ $ @@ -198,5 +199,6 @@ install(CODE "execute_process(COMMAND ${SETCAP} CAP_NET_BIND_SERVICE,CAP_NET_RAW add_subdirectory(api) add_subdirectory(civetweb) add_subdirectory(cJSON) +add_subdirectory(ph7) add_subdirectory(database) add_subdirectory(dnsmasq) diff --git a/src/api/CMakeLists.txt b/src/api/CMakeLists.txt index 725108ed4..b5e4ce0fe 100644 --- a/src/api/CMakeLists.txt +++ b/src/api/CMakeLists.txt @@ -23,6 +23,8 @@ set(sources stats_database.c stats.c version.c + webserver.c + ph7.c ) add_library(api OBJECT ${sources}) diff --git a/src/api/http-common.c b/src/api/http-common.c index 909a3d96e..af2b2790f 100644 --- a/src/api/http-common.c +++ b/src/api/http-common.c @@ -13,12 +13,8 @@ #include "http-common.h" #include "../config.h" #include "../log.h" -#include "../cJSON/cJSON.h" #include "json_macros.h" -// Server context handle -static struct mg_context *ctx = NULL; - // Provides a compile-time flag for JSON formatting // This should never be needed as all modern browsers // tyoically contain a JSON explorer @@ -153,116 +149,6 @@ bool __attribute__((pure)) startsWith(const char *path, const char *uri) } } -// Print passed string directly -static int print_simple(struct mg_connection *conn, void *input) -{ - return send_http(conn, "text/plain", input); -} - -static int log_http_message(const struct mg_connection *conn, const char *message) -{ - logg("HTTP info: %s", message); - return 1; -} - -static int log_http_access(const struct mg_connection *conn, const char *message) -{ - // Only log when in API debugging mode - if(config.debug & DEBUG_API) - logg("HTTP access: %s", message); - - return 1; -} - -void http_init(void) -{ - logg("Initializing HTTP server on port %s", httpsettings.port); - - /* Initialize the library */ - unsigned int features = MG_FEATURES_FILES | - MG_FEATURES_IPV6 | - MG_FEATURES_CACHE | - MG_FEATURES_STATS; - if(mg_init_library(features) == 0) - { - logg("Initializing HTTP library failed!"); - return; - } - - // Prepare options for HTTP server (NULL-terminated list) - // Note about the additional headers: - // - "Content-Security-Policy: [...]" - // 'unsafe-inline' is both required by Chart.js styling some elements directly, and - // index.html containing some inlined Javascript code. - // - "X-Frame-Options: SAMEORIGIN" - // The page can only be displayed in a frame on the same origin as the page itself. - // - "X-Xss-Protection: 1; mode=block" - // Enables XSS filtering. Rather than sanitizing the page, the browser will prevent - // rendering of the page if an attack is detected. - // - "X-Content-Type-Options: nosniff" - // Marker used by the server to indicate that the MIME types advertised in the - // Content-Type headers should not be changed and be followed. This allows to - // opt-out of MIME type sniffing, or, in other words, it is a way to say that the - // webmasters knew what they were doing. Site security testers usually expect this - // header to be set. - // - "Referrer-Policy: same-origin" - // A referrer will be sent for same-site origins, but cross-origin requests will - // send no referrer information. - // The latter four headers are set as expected by https://securityheaders.io - const char *options[] = { - "document_root", httpsettings.webroot, - "listening_ports", httpsettings.port, - "decode_url", "no", - "num_threads", "4", - "access_control_list", httpsettings.acl, - "additional_header", "Content-Security-Policy: default-src 'self' 'unsafe-inline';\r\n" - "X-Frame-Options: SAMEORIGIN\r\n" - "X-Xss-Protection: 1; mode=block\r\n" - "X-Content-Type-Options: nosniff\r\n" - "Referrer-Policy: same-origin", - "cgi_interpreter", httpsettings.php_location, - "cgi_pattern", "**.php$", // ** allows the files to by anywhere inside the web root - "index_files", "index.html,index.htm,index.php", - NULL - }; - - // Configure logging handlers - struct mg_callbacks callbacks = { NULL }; - callbacks.log_message = log_http_message; - callbacks.log_access = log_http_access; - - /* Start the server */ - if((ctx = mg_start(&callbacks, NULL, options)) == NULL) - { - logg("ERROR: Initializing HTTP library failed!\n" - " Web interface will not be available!"); - return; - } - - /* Add simple demonstration callbacks */ - mg_set_request_handler(ctx, "/ping", print_simple, (char*)"pong\n"); - char *api_path = NULL; - if(asprintf(&api_path, "%sapi", httpsettings.webhome) > 4) - { - if(config.debug & DEBUG_API) - { - logg("Installing API handler at %s", api_path); - } - mg_set_request_handler(ctx, api_path, api_handler, NULL); - // The request handler URI got duplicated - free(api_path); - } -} - -void http_terminate(void) -{ - /* Stop the server */ - mg_stop(ctx); - - /* Un-initialize the library */ - mg_exit_library(); -} - bool http_get_cookie_int(struct mg_connection *conn, const char *cookieName, int *i) { // Maximum cookie length is 4KB diff --git a/src/api/http-common.h b/src/api/http-common.h index c201ed9e1..54a698946 100644 --- a/src/api/http-common.h +++ b/src/api/http-common.h @@ -20,9 +20,6 @@ // FTLfree() #include "../memory.h" -void http_init(void); -void http_terminate(void); - const char* json_formatter(const cJSON *object); int send_http(struct mg_connection *conn, const char *mime_type, const char *msg); diff --git a/src/api/ph7.c b/src/api/ph7.c new file mode 100644 index 000000000..7d03028ae --- /dev/null +++ b/src/api/ph7.c @@ -0,0 +1,205 @@ +/* Pi-hole: A black hole for Internet advertisements +* (c) 2019 Pi-hole, LLC (https://pi-hole.net) +* Network-wide ad blocking via your own hardware. +* +* FTL Engine +* PH7 virtual machine routines +* +* This file is copyright under the latest version of the EUPL. +* Please see LICENSE file for your rights under this license. */ + +#include +#include +#include +// strncpy() +#include +#include "../log.h" +#include "../ph7/ph7.h" +#include "../civetweb/civetweb.h" +#include "ph7.h" +// struct httpsettings +#include "../config.h" +// mmap +#include +// stat +#include +#include +// open +#include + +// PH7 virtual machine engine +static ph7 *pEngine; /* PH7 engine */ +static ph7_vm *pVm; /* Compiled PHP program */ + +static char *webroot_with_home = NULL; +static char *webroot_with_home_and_scripts = NULL; + +/* + * VM output consumer callback. + * Each time the virtual machine generates some outputs, the following + * function gets called by the underlying virtual machine to consume + * the generated output. + * This function is registered later via a call to ph7_vm_config() + * with a configuration verb set to: PH7_VM_CONFIG_OUTPUT. + */ +static int Output_Consumer(const void *pOutput, unsigned int nOutputLen, void *pUserData /* Unused */) +{ + logg("PH7 error:"); + logg("%.*s", nOutputLen, (const char*)pOutput); + return PH7_OK; +} + +int ph7_handler(struct mg_connection *conn, void *cbdata) +{ + + int rc; + const void *pOut; + unsigned int nLen; + + /* Handler may access the request info using mg_get_request_info */ + const struct mg_request_info * req_info = mg_get_request_info(conn); + + // Build full path of PHP script on our machine + const size_t webroot_len = strlen(httpsettings.webroot); + const size_t local_uri_len = strlen(req_info->local_uri+1); + char full_path[webroot_len + local_uri_len + 2]; + strncpy(full_path, httpsettings.webroot, webroot_len); + full_path[webroot_len] = '/'; + strncpy(full_path + webroot_len + 1u, req_info->local_uri + 1, local_uri_len); + full_path[webroot_len + local_uri_len + 1u] = '\0'; + if(config.debug & DEBUG_API) + logg("Full path of PHP script: %s", full_path); + + /* Now,it's time to compile our PHP file */ + rc = ph7_compile_file( + pEngine, /* PH7 Engine */ + full_path, /* Path to the PHP file to compile */ + &pVm, /* OUT: Compiled PHP program */ + 0 /* IN: Compile flags */ + ); + + /* Report script run-time errors */ + ph7_vm_config(pVm, PH7_VM_CONFIG_ERR_REPORT); + + /* Configure include paths */ + ph7_vm_config(pVm, PH7_VM_CONFIG_IMPORT_PATH, webroot_with_home); + ph7_vm_config(pVm, PH7_VM_CONFIG_IMPORT_PATH, webroot_with_home_and_scripts); + + if( rc != PH7_OK ){ /* Compile error */ + if( rc == PH7_IO_ERR ) + { + logg("IO error while opening the target file"); + return 0; + } + else if( rc == PH7_VM_ERR ) + { + logg("VM initialization error"); + return 0; + } + else + { + logg("Compile error (%d)", rc); + + /* Extract error log */ + const char *zErrLog; + int niLen; + ph7_config(pEngine, PH7_CONFIG_ERR_LOG, &zErrLog, &niLen); + if( niLen > 0 ){ + /* zErrLog is null terminated */ + logg("PH7 error: %s", zErrLog); + } + return 0; + } + } + + rc = ph7_vm_exec(pVm,0); + if( rc != PH7_OK ) + { + logg("VM execution error"); + return 0; + } + + /* + * Now we have our script compiled,it's time to configure our VM. + * We will install the VM output consumer callback defined above + * so that we can consume the VM output and redirect it to STDOUT. + */ + + /* Extract the output */ + rc = ph7_vm_config(pVm, PH7_VM_CONFIG_EXTRACT_OUTPUT, &pOut, &nLen); + + if(nLen > 0) + { + mg_printf(conn, "HTTP/1.1 200 OK\r\nContent-Type: text/html\r\n\r\n"); + mg_write(conn, pOut, nLen); + logg("Output length: %u", nLen); + } + +#if 0 + const char *zErrLog; + int niLen; + /* Extract error log */ + ph7_config( + pEngine, + PH7_CONFIG_ERR_LOG, + &zErrLog, /* First arg*/ + &niLen /* Second arg */ + ); + + if( niLen > 0 ){ + logg("%s", zErrLog); /* Output*/ + } +#endif + + ph7_vm_reset(pVm); + + return 1; +} + +void init_ph7(void) +{ + if(ph7_init(&pEngine) != PH7_OK ) + { + logg("Error while allocating a new PH7 engine instance"); + return; + } + + /* Set an error log consumer callback. This callback [Output_Consumer()] will + * redirect all compile-time error messages to STDOUT. + */ + ph7_config(pEngine,PH7_VM_CONFIG_OUTPUT, + Output_Consumer, // Error log consumer + 0 // NULL: Callback Private data + ); +/* + ph7_config(pEngine,PH7_CONFIG_ERR_OUTPUT, + Output_Consumer, // Error log consumer + 0 // NULL: Callback Private data + );*/ + + // Prepare include paths + // var/www/html/admin (may be different due to user configuration) + const size_t webroot_len = strlen(httpsettings.webroot); + const size_t webhome_len = strlen(httpsettings.webhome); + webroot_with_home = calloc(webroot_len+webhome_len+1, sizeof(char)); + strncpy(webroot_with_home, httpsettings.webroot, webroot_len); + strncpy(webroot_with_home + webroot_len, httpsettings.webhome, webhome_len); + webroot_with_home[webroot_len + webhome_len] = '\0'; + + // var/www/html/admin/scripts/pi-hole/php (may be different due to user configuration) + const char scripts_dir[] = "/scripts/pi-hole/php"; + size_t webroot_with_home_len = strlen(webroot_with_home); + size_t scripts_dir_len = strlen(scripts_dir); + webroot_with_home_and_scripts = calloc(webroot_with_home_len+scripts_dir_len+1, sizeof(char)); + strncpy(webroot_with_home_and_scripts, webroot_with_home, webroot_with_home_len); + strncpy(webroot_with_home_and_scripts + webroot_with_home_len, scripts_dir, scripts_dir_len); + webroot_with_home_and_scripts[webroot_with_home_len + scripts_dir_len] = '\0'; +} + +void ph7_terminate(void) +{ + ph7_vm_release(pVm); + ph7_release(pEngine); + free(webroot_with_home); + free(webroot_with_home_and_scripts); +} diff --git a/src/api/ph7.h b/src/api/ph7.h new file mode 100644 index 000000000..4e7f877d2 --- /dev/null +++ b/src/api/ph7.h @@ -0,0 +1,17 @@ +/* Pi-hole: A black hole for Internet advertisements +* (c) 2020 Pi-hole, LLC (https://pi-hole.net) +* Network-wide ad blocking via your own hardware. +* +* FTL Engine +* PH7 interfacing routines +* +* This file is copyright under the latest version of the EUPL. +* Please see LICENSE file for your rights under this license. */ +#ifndef PH7_H +#define PH7_H + +void init_ph7(void); +void ph7_terminate(void); +int ph7_handler(struct mg_connection *conn, void *cbdata); + +#endif // PH7_H \ No newline at end of file diff --git a/src/api/webserver.c b/src/api/webserver.c new file mode 100644 index 000000000..f93eca95e --- /dev/null +++ b/src/api/webserver.c @@ -0,0 +1,141 @@ +/* Pi-hole: A black hole for Internet advertisements +* (c) 2019 Pi-hole, LLC (https://pi-hole.net) +* Network-wide ad blocking via your own hardware. +* +* FTL Engine +* HTTP server routines +* +* This file is copyright under the latest version of the EUPL. +* Please see LICENSE file for your rights under this license. */ + +#include "FTL.h" +#include "routes.h" +// send_http() +#include "http-common.h" +// struct httpsettings +#include "../config.h" +#include "../log.h" +#include "webserver.h" +// ph7_handler +#include "ph7.h" + +// Server context handle +static struct mg_context *ctx = NULL; + +// Print passed string directly +static int print_simple(struct mg_connection *conn, void *input) +{ + return send_http(conn, "text/plain", input); +} + +static int log_http_message(const struct mg_connection *conn, const char *message) +{ + logg("HTTP info: %s", message); + return 1; +} + +static int log_http_access(const struct mg_connection *conn, const char *message) +{ + // Only log when in API debugging mode + if(config.debug & DEBUG_API) + logg("HTTP access: %s", message); + + return 1; +} + +void http_init(void) +{ + logg("Initializing HTTP server on port %s", httpsettings.port); + + /* Initialize the library */ + unsigned int features = MG_FEATURES_FILES | +// MG_FEATURES_CGI | + MG_FEATURES_IPV6 | + MG_FEATURES_CACHE | + MG_FEATURES_STATS; + if(mg_init_library(features) == 0) + { + logg("Initializing HTTP library failed!"); + return; + } + + // Prepare options for HTTP server (NULL-terminated list) + // Note about the additional headers: + // - "Content-Security-Policy: [...]" + // 'unsafe-inline' is both required by Chart.js styling some elements directly, and + // index.html containing some inlined Javascript code. + // - "X-Frame-Options: SAMEORIGIN" + // The page can only be displayed in a frame on the same origin as the page itself. + // - "X-Xss-Protection: 1; mode=block" + // Enables XSS filtering. Rather than sanitizing the page, the browser will prevent + // rendering of the page if an attack is detected. + // - "X-Content-Type-Options: nosniff" + // Marker used by the server to indicate that the MIME types advertised in the + // Content-Type headers should not be changed and be followed. This allows to + // opt-out of MIME type sniffing, or, in other words, it is a way to say that the + // webmasters knew what they were doing. Site security testers usually expect this + // header to be set. + // - "Referrer-Policy: same-origin" + // A referrer will be sent for same-site origins, but cross-origin requests will + // send no referrer information. + // The latter four headers are set as expected by https://securityheaders.io + const char *options[] = { + "document_root", httpsettings.webroot, + "listening_ports", httpsettings.port, + "decode_url", "no", + "num_threads", "4", + "access_control_list", httpsettings.acl, + "additional_header", "Content-Security-Policy: default-src 'self' 'unsafe-inline';\r\n" + "X-Frame-Options: SAMEORIGIN\r\n" + "X-Xss-Protection: 1; mode=block\r\n" + "X-Content-Type-Options: nosniff\r\n" + "Referrer-Policy: same-origin", +// "cgi_interpreter", httpsettings.php_location, +// "cgi_pattern", "**.php$", // ** allows the files to by anywhere inside the web root + "index_files", "index.html,index.htm,index.php", + NULL + }; + + // Configure logging handlers + struct mg_callbacks callbacks = { NULL }; + callbacks.log_message = log_http_message; + callbacks.log_access = log_http_access; + + /* Start the server */ + if((ctx = mg_start(&callbacks, NULL, options)) == NULL) + { + logg("ERROR: Initializing HTTP library failed!\n" + " Web interface will not be available!"); + return; + } + + /* Add simple demonstration callbacks */ + mg_set_request_handler(ctx, "/ping", print_simple, (char*)"pong\n"); + char *api_path = NULL; + if(asprintf(&api_path, "%sapi", httpsettings.webhome) > 4) + { + if(config.debug & DEBUG_API) + { + logg("Installing API handler at %s", api_path); + } + mg_set_request_handler(ctx, api_path, api_handler, NULL); + // The request handler URI got duplicated + free(api_path); + } + + // Register PHP request handler + init_ph7(); + mg_set_request_handler(ctx, "**.php$", ph7_handler, 0); +} + +void http_terminate(void) +{ + /* Stop the server */ + mg_stop(ctx); + + /* Un-initialize the library */ + mg_exit_library(); + + /* Un-initialize PH7 */ + ph7_terminate(); +} diff --git a/src/api/webserver.h b/src/api/webserver.h new file mode 100644 index 000000000..7d445bb94 --- /dev/null +++ b/src/api/webserver.h @@ -0,0 +1,16 @@ +/* Pi-hole: A black hole for Internet advertisements +* (c) 2020 Pi-hole, LLC (https://pi-hole.net) +* Network-wide ad blocking via your own hardware. +* +* FTL Engine +* HTTP server routines +* +* This file is copyright under the latest version of the EUPL. +* Please see LICENSE file for your rights under this license. */ +#ifndef WEBSERVER_H +#define WEBSERVER_H + +void http_init(void); +void http_terminate(void); + +#endif // WEBSERVER_H \ No newline at end of file diff --git a/src/civetweb/CMakeLists.txt b/src/civetweb/CMakeLists.txt index 139f33b7c..672f8caaa 100644 --- a/src/civetweb/CMakeLists.txt +++ b/src/civetweb/CMakeLists.txt @@ -14,6 +14,7 @@ set(sources ) # We can remove the NO_SSL later on. It adds additional constraints to the build system (availablity of libSSL-dev) +# NO_CGI = no CGI support (we don't need it) # NO_SSL_DL NO_SSL = no SSL support (for now) # USE_SERVER_STATS = makes a few anonymous statistics available, such as # - Number of connections (currently and total) @@ -25,5 +26,5 @@ set(CIVETWEB_OPTS add_library(civetweb OBJECT ${sources}) -target_compile_definitions(civetweb PRIVATE NO_SSL_DL NO_SSL USE_SERVER_STATS USE_IPV6) +target_compile_definitions(civetweb PRIVATE NO_CGI NO_SSL_DL NO_SSL USE_SERVER_STATS USE_IPV6) target_include_directories(civetweb PRIVATE ${PROJECT_SOURCE_DIR}/src) diff --git a/src/config.c b/src/config.c index 9a767eae5..ae72feba4 100644 --- a/src/config.c +++ b/src/config.c @@ -448,17 +448,6 @@ void read_FTLconf(void) else logg(" API_PRETTY_JSON: Disabled. Compact API output."); - // PHP-CGI - // defaults to: /usr/bin/php-cgi - // Path to an executable to use as CGI interpreter for all CGI scripts - // regardless of the script file extension. We use this for PHP. It - // needs to point at php-cgi. php-cli will not work - httpsettings.php_location = "/usr/bin/php-cgi"; - buffer = parse_FTLconf(fp, "PHP.CGI"); - if(buffer != NULL) - httpsettings.php_location = strdup(buffer); - logg(" PHP-CGI: Using %s", httpsettings.php_location); - // Read DEBUG_... setting from pihole-FTL.conf // This option should be the last one as it causes // some rather verbose output into the log when diff --git a/src/config.h b/src/config.h index 3b7fbb9dc..ed52edf81 100644 --- a/src/config.h +++ b/src/config.h @@ -56,7 +56,6 @@ typedef struct { typedef struct httpsettings { char *webroot; char *webhome; - const char *php_location; const char *acl; bool api_auth_for_localhost; bool prettyJSON; diff --git a/src/dnsmasq_interface.c b/src/dnsmasq_interface.c index 437b85e91..b0695f0b8 100644 --- a/src/dnsmasq_interface.c +++ b/src/dnsmasq_interface.c @@ -33,7 +33,7 @@ // global variable daemonmode #include "args.h" // http_init() -#include "api/http-common.h" +#include "api/webserver.h" // add_to_dnsmasq_log_buffer() #include "api/ftl.h" diff --git a/src/main.c b/src/main.c index cf5a7d35e..34f7de2ea 100644 --- a/src/main.c +++ b/src/main.c @@ -24,7 +24,7 @@ #include "database/gravity-db.h" #include "timers.h" // http_terminate() -#include "api/http-common.h" +#include "api/webserver.h" char * username; bool needGC = false; diff --git a/src/ph7/CMakeLists.txt b/src/ph7/CMakeLists.txt new file mode 100644 index 000000000..45f6336e5 --- /dev/null +++ b/src/ph7/CMakeLists.txt @@ -0,0 +1,17 @@ +# Pi-hole: A black hole for Internet advertisements +# (c) 2020 Pi-hole, LLC (https://pi-hole.net) +# Network-wide ad blocking via your own hardware. +# +# FTL Engine +# /src/api/CMakeList.txt +# +# This file is copyright under the latest version of the EUPL. +# Please see LICENSE file for your rights under this license. + +set(sources + ph7.c + ) + +add_library(ph7 OBJECT ${sources}) +target_compile_options(ph7 PRIVATE -Wno-unused-but-set-parameter -Wno-array-bounds) +target_include_directories(ph7 PRIVATE ${PROJECT_SOURCE_DIR}/src) diff --git a/src/ph7/license.txt b/src/ph7/license.txt new file mode 100644 index 000000000..f5847d4f6 --- /dev/null +++ b/src/ph7/license.txt @@ -0,0 +1,44 @@ +--START --SYMISC PUBLIC LICENSE--- +/* + * Copyright (C) 2011, 2012, 2013, 2014 Symisc Systems. 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. Redistributions in any form must be accompanied by information on + * how to obtain complete source code for the PH7 engine and any + * accompanying software that uses the PH7 engine software. + * The source code must either be included in the distribution + * or be available for no more than the cost of distribution plus + * a nominal fee, and must be freely redistributable under reasonable + * conditions. For an executable file, complete source code means + * the source code for all modules it contains.It does not include + * source code for modules or files that typically accompany the major + * components of the operating system on which the executable file runs. + * + * THIS SOFTWARE IS PROVIDED BY SYMISC SYSTEMS ``AS IS'' AND ANY EXPRESS + * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR + * NON-INFRINGEMENT, ARE DISCLAIMED. IN NO EVENT SHALL SYMISC SYSTEMS + * 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. + */ + --END -- SYMISC PUBLIC LICENSE--- + +It is possible to circumvent this licensing policy through the purchase of a commercial software +license from Symisc Systems consisting of terms and conditions which are negotiated at the time +of sale. +For more information, please contact Symisc Systems at: + licensing@symisc.net + or visit: + http://ph7.symisc.net/licensing.html diff --git a/src/ph7/ph7.c b/src/ph7/ph7.c new file mode 100644 index 000000000..6460b28d5 --- /dev/null +++ b/src/ph7/ph7.c @@ -0,0 +1,62027 @@ +/* + * Symisc PH7: An embeddable bytecode compiler and a virtual machine for the PHP(5) programming language. + * Copyright (C) 2011, 2012, 2013, 2014 Symisc Systems http://ph7.symisc.net/ + * Version 2.1.4 + * For information on licensing,redistribution of this file,and for a DISCLAIMER OF ALL WARRANTIES + * please contact Symisc Systems via: + * legal@symisc.net + * licensing@symisc.net + * contact@symisc.net + * or visit: + * http://ph7.symisc.net/ + */ +/* + * Copyright (C) 2011, 2012, 2013, 2014 Symisc Systems. 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. Redistributions in any form must be accompanied by information on + * how to obtain complete source code for the PH7 engine and any + * accompanying software that uses the PH7 engine software. + * The source code must either be included in the distribution + * or be available for no more than the cost of distribution plus + * a nominal fee, and must be freely redistributable under reasonable + * conditions. For an executable file, complete source code means + * the source code for all modules it contains.It does not include + * source code for modules or files that typically accompany the major + * components of the operating system on which the executable file runs. + * + * THIS SOFTWARE IS PROVIDED BY SYMISC SYSTEMS ``AS IS'' AND ANY EXPRESS + * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR + * NON-INFRINGEMENT, ARE DISCLAIMED. IN NO EVENT SHALL SYMISC SYSTEMS + * 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. + */ +/* + * $SymiscID: ph7.c v2.1 UNIX|WIN32/64 2012-09-15 09:00 stable $ + */ +/* This file is an amalgamation of many separate C source files from PH7 version 2.1. + * By combining all the individual C code files into this single large file, the entire code + * can be compiled as a single translation unit.This allows many compilers to do optimization's + * that would not be possible if the files were compiled separately.Performance improvements + * are commonly seen when PH7 is compiled as a single translation unit. + * + * This file is all you need to compile PH7.To use PH7 in other programs, you need + * this file and the "ph7.h" header file that defines the programming interface to the + * PH7 engine.(If you do not have the "ph7.h" header file at hand, you will find + * a copy embedded within the text of this file.Search for "Header file: " to find + * the start of the embedded ph7.h header file.) Additional code files may be needed if + * you want a wrapper to interface PH7 with your choice of programming language. + * To get the official documentation,please visit http://ph7.symisc.net/ + */ + /* + * Make the sure the following is defined in the amalgamation build + */ + #ifndef PH7_AMALGAMATION + #define PH7_AMALGAMATION + #endif /* PH7_AMALGAMATION */ +/* + * Embedded header file for the PH7 engine: + */ +/* + * ---------------------------------------------------------- + * File: ph7.h + * MD5: b5527f9c7eb410a9f9367a6b03014a65 + * ---------------------------------------------------------- + */ +/* This file was automatically generated. Do not edit (except for compile time directive)! */ +#ifndef _PH7_H_ +#define _PH7_H_ +/* + * Symisc PH7: An embeddable bytecode compiler and a virtual machine for the PHP(5) programming language. + * Copyright (C) 2011-2012, Symisc Systems http://ph7.symisc.net/ + * Version 2.1.4 + * For information on licensing,redistribution of this file,and for a DISCLAIMER OF ALL WARRANTIES + * please contact Symisc Systems via: + * legal@symisc.net + * licensing@symisc.net + * contact@symisc.net + * or visit: + * http://ph7.symisc.net/ + */ +/* + * Copyright (C) 2011, 2012 Symisc Systems. 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. Redistributions in any form must be accompanied by information on + * how to obtain complete source code for the PH7 engine and any + * accompanying software that uses the PH7 engine software. + * The source code must either be included in the distribution + * or be available for no more than the cost of distribution plus + * a nominal fee, and must be freely redistributable under reasonable + * conditions. For an executable file, complete source code means + * the source code for all modules it contains.It does not include + * source code for modules or files that typically accompany the major + * components of the operating system on which the executable file runs. + * + * THIS SOFTWARE IS PROVIDED BY SYMISC SYSTEMS ``AS IS'' AND ANY EXPRESS + * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR + * NON-INFRINGEMENT, ARE DISCLAIMED. IN NO EVENT SHALL SYMISC SYSTEMS + * 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. + */ + /* $SymiscID: ph7.h v2.1 UNIX|WIN32/64 2012-09-15 09:43 stable $ */ +#include /* needed for the definition of va_list */ +/* + * Compile time engine version, signature, identification in the symisc source tree + * and copyright notice. + * Each macro have an equivalent C interface associated with it that provide the same + * information but are associated with the library instead of the header file. + * Refer to [ph7_lib_version()], [ph7_lib_signature()], [ph7_lib_ident()] and + * [ph7_lib_copyright()] for more information. + */ +/* + * The PH7_VERSION C preprocessor macroevaluates to a string literal + * that is the ph7 version in the format "X.Y.Z" where X is the major + * version number and Y is the minor version number and Z is the release + * number. + */ +#define PH7_VERSION "2.1.4" +/* + * The PH7_VERSION_NUMBER C preprocessor macro resolves to an integer + * with the value (X*1000000 + Y*1000 + Z) where X, Y, and Z are the same + * numbers used in [PH7_VERSION]. + */ +#define PH7_VERSION_NUMBER 2001004 +/* + * The PH7_SIG C preprocessor macro evaluates to a string + * literal which is the public signature of the ph7 engine. + * This signature could be included for example in a host-application + * generated Server MIME header as follows: + * Server: YourWebServer/x.x PH7/x.x.x \r\n + */ +#define PH7_SIG "PH7/2.1.4" +/* + * PH7 identification in the Symisc source tree: + * Each particular check-in of a particular software released + * by symisc systems have an unique identifier associated with it. + * This macro hold the one associated with ph7. + */ +#define PH7_IDENT "ph7:c193f4d8a6b90ee60f9afad11840f1010054fdf9" +/* + * Copyright notice. + * If you have any questions about the licensing situation,please + * visit http://ph7.symisc.net/licensing.html + * or contact Symisc Systems via: + * legal@symisc.net + * licensing@symisc.net + * contact@symisc.net + */ +#define PH7_COPYRIGHT "Copyright (C) Symisc Systems 2011-2012, http://ph7.symisc.net/" +/* Make sure we can call this stuff from C++ */ +#ifdef __cplusplus +extern "C" { +#endif +/* Forward declaration to public objects */ +typedef struct ph7_io_stream ph7_io_stream; +typedef struct ph7_context ph7_context; +typedef struct ph7_value ph7_value; +typedef struct ph7_vfs ph7_vfs; +typedef struct ph7_vm ph7_vm; +typedef struct ph7 ph7; +/* + * ------------------------------ + * Compile time directives + * ------------------------------ + * For most purposes, PH7 can be built just fine using the default compilation options. + * However, if required, the compile-time options documented below can be used to omit + * PH7 features (resulting in a smaller compiled library size) or to change the default + * values of some parameters. + * Every effort has been made to ensure that the various combinations of compilation + * options work harmoniously and produce a working library. + * + * PH7_ENABLE_THREADS + * This option controls whether or not code is included in PH7 to enable it to operate + * safely in a multithreaded environment. The default is not. That is,all mutexing code + * is omitted and it is unsafe to use PH7 in a multithreaded program. When compiled + * with the PH7_ENABLE_THREADS directive enabled, PH7 can be used in a multithreaded + * program and it's safe to share the same virtual machine and engine instance between + * two or more threads. + * The value of PH7_ENABLE_THREADS can be determined at run-time using the + * ph7_lib_is_threadsafe() interface.When PH7 has been compiled with PH7_ENABLE_THREAD + * then the threading mode can be altered at run-time using the ph7_lib_config() + * interface together with one of these verbs: + * PH7_LIB_CONFIG_THREAD_LEVEL_SINGLE + * PH7_LIB_CONFIG_THREAD_LEVEL_MULTI + * Also note,platforms others than Windows and UNIX systems must install their own + * mutex subsystem via ph7_lib_config() with a configuration verb set to + * PH7_LIB_CONFIG_USER_MUTEX. Otherwise the library is not threadsafe. + * Note that you must link PH7 with the POSIX threads library under UNIX-like systems + * (i.e: -lpthread).Otherwise you will get a link time error. + * Options To Omit/Enable Features: + * The following options can be used to reduce the size of the compiled library + * by omitting optional features. This is probably only useful in embedded systems + * where space is especially tight, as even with all features included the PH7 library + * is relatively small. Don't forget to tell your compiler to optimize for binary + * size! (the -Os option if using GCC). + * Telling your compiler to optimize for size usually has a much larger impact + * on library footprint than employing any of these compile-time options. + * PH7_DISABLE_BUILTIN_FUNC + * PH7 come with more than 460 built-in functions suitable for most purposes ranging + * from string/XML/INI processing to ZIP extracting, Base64 encoding/decoding and so on. + * If this directive is enabled, all the built-in functions are omitted from the build. + * Note that language construct functions such as is_int(), is_string(), func_get_arg(), + * define(), etc. are not omitted from the build and are not affected by this directive. + * PH7_ENABLE_MATH_FUNC + * If this directive is enabled, built-in math functions such as sqrt(),abs(), + * log(), ceil(), etc. are included in the build. Note that you may need to link + * PH7 with the math library in same linux/BSD flavor (i.e: -lm).Otherwise you + * will get a link time error. + * PH7_DISABLE_DISK_IO + * If this directive is enabled, built-in Virtual File System functions such as + * chdir(), mkdir(), chroot(), unlink(), delete(), etc. are omitted from the build. + * PH7_DISABLE_HASH_IO + * If this directive is enabled, built-in hash functions such as md5(), sha1(), + * md5_file(), crc32(), etc. are omitted from the build. + * PH7_OMIT_FLOATING_POINT + * This option is used to omit floating-point number support from the PH7 library + * if compiling for a processor that lacks floating point support. When specified + * the library will substitute 64-bit integer arithmetic for floating-point which + * mean that 25.e-3 and 25 are equals and are of type integer. + */ +/* Symisc public definitions */ +#if !defined(SYMISC_STANDARD_DEFS) +#define SYMISC_STANDARD_DEFS +#if defined (_WIN32) || defined (WIN32) || defined(__MINGW32__) || defined (_MSC_VER) || defined (_WIN32_WCE) +/* Windows Systems */ +#if !defined(__WINNT__) +#define __WINNT__ +#endif +#else +/* + * By default we will assume that we are compiling on a UNIX systems. + * Otherwise the OS_OTHER directive must be defined. + */ +#if !defined(OS_OTHER) +#if !defined(__UNIXES__) +#define __UNIXES__ +#endif /* __UNIXES__ */ +#else +#endif /* OS_OTHER */ +#endif /* __WINNT__/__UNIXES__ */ +#if defined(_MSC_VER) || defined(__BORLANDC__) +typedef signed __int64 sxi64; /* 64 bits(8 bytes) signed int64 */ +typedef unsigned __int64 sxu64; /* 64 bits(8 bytes) unsigned int64 */ +#else +typedef signed long long int sxi64; /* 64 bits(8 bytes) signed int64 */ +typedef unsigned long long int sxu64; /* 64 bits(8 bytes) unsigned int64 */ +#endif /* _MSC_VER */ +/* Signature of the consumer routine */ +typedef int (*ProcConsumer)(const void *,unsigned int,void *); +/* Forward reference */ +typedef struct SyMutexMethods SyMutexMethods; +typedef struct SyMemMethods SyMemMethods; +typedef struct SyString SyString; +typedef struct syiovec syiovec; +typedef struct SyMutex SyMutex; +typedef struct Sytm Sytm; +/* Scatter and gather array. */ +struct syiovec +{ +#if defined (__WINNT__) + /* Same fields type and offset as WSABUF structure defined one winsock2 header */ + unsigned long nLen; + char *pBase; +#else + void *pBase; + unsigned long nLen; +#endif +}; +struct SyString +{ + const char *zString; /* Raw string (may not be null terminated) */ + unsigned int nByte; /* Raw string length */ +}; +/* Time structure. */ +struct Sytm +{ + int tm_sec; /* seconds (0 - 60) */ + int tm_min; /* minutes (0 - 59) */ + int tm_hour; /* hours (0 - 23) */ + int tm_mday; /* day of month (1 - 31) */ + int tm_mon; /* month of year (0 - 11) */ + int tm_year; /* year + 1900 */ + int tm_wday; /* day of week (Sunday = 0) */ + int tm_yday; /* day of year (0 - 365) */ + int tm_isdst; /* is summer time in effect? */ + char *tm_zone; /* abbreviation of timezone name */ + long tm_gmtoff; /* offset from UTC in seconds */ +}; +/* Convert a tm structure (struct tm *) found in to a Sytm structure */ +#define STRUCT_TM_TO_SYTM(pTM,pSYTM) \ + (pSYTM)->tm_hour = (pTM)->tm_hour;\ + (pSYTM)->tm_min = (pTM)->tm_min;\ + (pSYTM)->tm_sec = (pTM)->tm_sec;\ + (pSYTM)->tm_mon = (pTM)->tm_mon;\ + (pSYTM)->tm_mday = (pTM)->tm_mday;\ + (pSYTM)->tm_year = (pTM)->tm_year + 1900;\ + (pSYTM)->tm_yday = (pTM)->tm_yday;\ + (pSYTM)->tm_wday = (pTM)->tm_wday;\ + (pSYTM)->tm_isdst = (pTM)->tm_isdst;\ + (pSYTM)->tm_gmtoff = 0;\ + (pSYTM)->tm_zone = 0; + +/* Convert a SYSTEMTIME structure (LPSYSTEMTIME: Windows Systems only ) to a Sytm structure */ +#define SYSTEMTIME_TO_SYTM(pSYSTIME,pSYTM) \ + (pSYTM)->tm_hour = (pSYSTIME)->wHour;\ + (pSYTM)->tm_min = (pSYSTIME)->wMinute;\ + (pSYTM)->tm_sec = (pSYSTIME)->wSecond;\ + (pSYTM)->tm_mon = (pSYSTIME)->wMonth - 1;\ + (pSYTM)->tm_mday = (pSYSTIME)->wDay;\ + (pSYTM)->tm_year = (pSYSTIME)->wYear;\ + (pSYTM)->tm_yday = 0;\ + (pSYTM)->tm_wday = (pSYSTIME)->wDayOfWeek;\ + (pSYTM)->tm_gmtoff = 0;\ + (pSYTM)->tm_isdst = -1;\ + (pSYTM)->tm_zone = 0; + +/* Dynamic memory allocation methods. */ +struct SyMemMethods +{ + void * (*xAlloc)(unsigned int); /* [Required:] Allocate a memory chunk */ + void * (*xRealloc)(void *,unsigned int); /* [Required:] Re-allocate a memory chunk */ + void (*xFree)(void *); /* [Required:] Release a memory chunk */ + unsigned int (*xChunkSize)(void *); /* [Optional:] Return chunk size */ + int (*xInit)(void *); /* [Optional:] Initialization callback */ + void (*xRelease)(void *); /* [Optional:] Release callback */ + void *pUserData; /* [Optional:] First argument to xInit() and xRelease() */ +}; +/* Out of memory callback signature. */ +typedef int (*ProcMemError)(void *); +/* Mutex methods. */ +struct SyMutexMethods +{ + int (*xGlobalInit)(void); /* [Optional:] Global mutex initialization */ + void (*xGlobalRelease)(void); /* [Optional:] Global Release callback () */ + SyMutex * (*xNew)(int); /* [Required:] Request a new mutex */ + void (*xRelease)(SyMutex *); /* [Optional:] Release a mutex */ + void (*xEnter)(SyMutex *); /* [Required:] Enter mutex */ + int (*xTryEnter)(SyMutex *); /* [Optional:] Try to enter a mutex */ + void (*xLeave)(SyMutex *); /* [Required:] Leave a locked mutex */ +}; +#if defined (_MSC_VER) || defined (__MINGW32__) || defined (__GNUC__) && defined (__declspec) +#define SX_APIIMPORT __declspec(dllimport) +#define SX_APIEXPORT __declspec(dllexport) +#else +#define SX_APIIMPORT +#define SX_APIEXPORT +#endif +/* Standard return values from Symisc public interfaces */ +#define SXRET_OK 0 /* Not an error */ +#define SXERR_MEM (-1) /* Out of memory */ +#define SXERR_IO (-2) /* IO error */ +#define SXERR_EMPTY (-3) /* Empty field */ +#define SXERR_LOCKED (-4) /* Locked operation */ +#define SXERR_ORANGE (-5) /* Out of range value */ +#define SXERR_NOTFOUND (-6) /* Item not found */ +#define SXERR_LIMIT (-7) /* Limit reached */ +#define SXERR_MORE (-8) /* Need more input */ +#define SXERR_INVALID (-9) /* Invalid parameter */ +#define SXERR_ABORT (-10) /* User callback request an operation abort */ +#define SXERR_EXISTS (-11) /* Item exists */ +#define SXERR_SYNTAX (-12) /* Syntax error */ +#define SXERR_UNKNOWN (-13) /* Unknown error */ +#define SXERR_BUSY (-14) /* Busy operation */ +#define SXERR_OVERFLOW (-15) /* Stack or buffer overflow */ +#define SXERR_WILLBLOCK (-16) /* Operation will block */ +#define SXERR_NOTIMPLEMENTED (-17) /* Operation not implemented */ +#define SXERR_EOF (-18) /* End of input */ +#define SXERR_PERM (-19) /* Permission error */ +#define SXERR_NOOP (-20) /* No-op */ +#define SXERR_FORMAT (-21) /* Invalid format */ +#define SXERR_NEXT (-22) /* Not an error */ +#define SXERR_OS (-23) /* System call return an error */ +#define SXERR_CORRUPT (-24) /* Corrupted pointer */ +#define SXERR_CONTINUE (-25) /* Not an error: Operation in progress */ +#define SXERR_NOMATCH (-26) /* No match */ +#define SXERR_RESET (-27) /* Operation reset */ +#define SXERR_DONE (-28) /* Not an error */ +#define SXERR_SHORT (-29) /* Buffer too short */ +#define SXERR_PATH (-30) /* Path error */ +#define SXERR_TIMEOUT (-31) /* Timeout */ +#define SXERR_BIG (-32) /* Too big for processing */ +#define SXERR_RETRY (-33) /* Retry your call */ +#define SXERR_IGNORE (-63) /* Ignore */ +#endif /* SYMISC_PUBLIC_DEFS */ +/* Standard PH7 return values */ +#define PH7_OK SXRET_OK /* Successful result */ +/* beginning-of-error-codes */ +#define PH7_NOMEM SXERR_MEM /* Out of memory */ +#define PH7_ABORT SXERR_ABORT /* Foreign Function request operation abort/Another thread have released this instance */ +#define PH7_IO_ERR SXERR_IO /* IO error */ +#define PH7_CORRUPT SXERR_CORRUPT /* Corrupt pointer/Unknown configuration option */ +#define PH7_LOOKED SXERR_LOCKED /* Forbidden Operation */ +#define PH7_COMPILE_ERR (-70) /* Compilation error */ +#define PH7_VM_ERR (-71) /* Virtual machine error */ +/* end-of-error-codes */ +/* + * If compiling for a processor that lacks floating point + * support, substitute integer for floating-point. + */ +#ifdef PH7_OMIT_FLOATING_POINT +typedef sxi64 ph7_real; +#else +typedef double ph7_real; +#endif +typedef sxi64 ph7_int64; +#define PH7_APIEXPORT SX_APIEXPORT +/* + * Engine Configuration Commands. + * + * The following set of constants are the available configuration verbs that can + * be used by the host-application to configure the PH7 engine. + * These constants must be passed as the second argument to the [ph7_config()] + * interface. + * Each options require a variable number of arguments. + * The [ph7_config()] interface will return PH7_OK on success, any other + * return value indicates failure. + * For a full discussion on the configuration verbs and their expected + * parameters, please refer to this page: + * http://ph7.symisc.net/c_api_func.html#ph7_config + */ +#define PH7_CONFIG_ERR_OUTPUT 1 /* TWO ARGUMENTS: int (*xConsumer)(const void *pOut,unsigned int nLen,void *pUserData),void *pUserData */ +#define PH7_CONFIG_ERR_ABORT 2 /* RESERVED FOR FUTURE USE */ +#define PH7_CONFIG_ERR_LOG 3 /* TWO ARGUMENTS: const char **pzBuf,int *pLen */ +/* + * Virtual Machine Configuration Commands. + * + * The following set of constants are the available configuration verbs that can + * be used by the host-application to configure the PH7 Virtual machine. + * These constants must be passed as the second argument to the [ph7_vm_config()] + * interface. + * Each options require a variable number of arguments. + * The [ph7_vm_config()] interface will return PH7_OK on success, any other return + * value indicates failure. + * There are many options but the most importants are: PH7_VM_CONFIG_OUTPUT which install + * a VM output consumer callback, PH7_VM_CONFIG_HTTP_REQUEST which parse and register + * a HTTP request and PH7_VM_CONFIG_ARGV_ENTRY which populate the $argv array. + * For a full discussion on the configuration verbs and their expected parameters, please + * refer to this page: + * http://ph7.symisc.net/c_api_func.html#ph7_vm_config + */ +#define PH7_VM_CONFIG_OUTPUT 1 /* TWO ARGUMENTS: int (*xConsumer)(const void *pOut,unsigned int nLen,void *pUserData),void *pUserData */ +#define PH7_VM_CONFIG_IMPORT_PATH 3 /* ONE ARGUMENT: const char *zIncludePath */ +#define PH7_VM_CONFIG_ERR_REPORT 4 /* NO ARGUMENTS: Report all run-time errors in the VM output */ +#define PH7_VM_CONFIG_RECURSION_DEPTH 5 /* ONE ARGUMENT: int nMaxDepth */ +#define PH7_VM_OUTPUT_LENGTH 6 /* ONE ARGUMENT: unsigned int *pLength */ +#define PH7_VM_CONFIG_CREATE_SUPER 7 /* TWO ARGUMENTS: const char *zName,ph7_value *pValue */ +#define PH7_VM_CONFIG_CREATE_VAR 8 /* TWO ARGUMENTS: const char *zName,ph7_value *pValue */ +#define PH7_VM_CONFIG_HTTP_REQUEST 9 /* TWO ARGUMENTS: const char *zRawRequest,int nRequestLength */ +#define PH7_VM_CONFIG_SERVER_ATTR 10 /* THREE ARGUMENTS: const char *zKey,const char *zValue,int nLen */ +#define PH7_VM_CONFIG_ENV_ATTR 11 /* THREE ARGUMENTS: const char *zKey,const char *zValue,int nLen */ +#define PH7_VM_CONFIG_SESSION_ATTR 12 /* THREE ARGUMENTS: const char *zKey,const char *zValue,int nLen */ +#define PH7_VM_CONFIG_POST_ATTR 13 /* THREE ARGUMENTS: const char *zKey,const char *zValue,int nLen */ +#define PH7_VM_CONFIG_GET_ATTR 14 /* THREE ARGUMENTS: const char *zKey,const char *zValue,int nLen */ +#define PH7_VM_CONFIG_COOKIE_ATTR 15 /* THREE ARGUMENTS: const char *zKey,const char *zValue,int nLen */ +#define PH7_VM_CONFIG_HEADER_ATTR 16 /* THREE ARGUMENTS: const char *zKey,const char *zValue,int nLen */ +#define PH7_VM_CONFIG_EXEC_VALUE 17 /* ONE ARGUMENT: ph7_value **ppValue */ +#define PH7_VM_CONFIG_IO_STREAM 18 /* ONE ARGUMENT: const ph7_io_stream *pStream */ +#define PH7_VM_CONFIG_ARGV_ENTRY 19 /* ONE ARGUMENT: const char *zValue */ +#define PH7_VM_CONFIG_EXTRACT_OUTPUT 20 /* TWO ARGUMENTS: const void **ppOut,unsigned int *pOutputLen */ +#define PH7_VM_CONFIG_ERR_LOG_HANDLER 21 /* ONE ARGUMENT: void (*xErrLog)(const char *,int,const char *,const char *) */ +/* + * Global Library Configuration Commands. + * + * The following set of constants are the available configuration verbs that can + * be used by the host-application to configure the whole library. + * These constants must be passed as the first argument to the [ph7_lib_config()] + * interface. + * Each options require a variable number of arguments. + * The [ph7_lib_config()] interface will return PH7_OK on success, any other return + * value indicates failure. + * Notes: + * The default configuration is recommended for most applications and so the call to + * [ph7_lib_config()] is usually not necessary. It is provided to support rare + * applications with unusual needs. + * The [ph7_lib_config()] interface is not threadsafe. The application must insure that + * no other [ph7_*()] interfaces are invoked by other threads while [ph7_lib_config()] + * is running. Furthermore, [ph7_lib_config()] may only be invoked prior to library + * initialization using [ph7_lib_init()] or [ph7_init()] or after shutdown + * by [ph7_lib_shutdown()]. If [ph7_lib_config()] is called after [ph7_lib_init()] + * or [ph7_init()] and before [ph7_lib_shutdown()] then it will return PH7_LOCKED. + * Refer to the official documentation for more information on the configuration verbs + * and their expected parameters. + * For a full discussion on the configuration verbs and their expected parameters,please + * refer to this page: + * http://ph7.symisc.net/c_api_func.html#Global_Library_Management_Interfaces + */ +#define PH7_LIB_CONFIG_USER_MALLOC 1 /* ONE ARGUMENT: const SyMemMethods *pMemMethods */ +#define PH7_LIB_CONFIG_MEM_ERR_CALLBACK 2 /* TWO ARGUMENTS: int (*xMemError)(void *),void *pUserData */ +#define PH7_LIB_CONFIG_USER_MUTEX 3 /* ONE ARGUMENT: const SyMutexMethods *pMutexMethods */ +#define PH7_LIB_CONFIG_THREAD_LEVEL_SINGLE 4 /* NO ARGUMENTS */ +#define PH7_LIB_CONFIG_THREAD_LEVEL_MULTI 5 /* NO ARGUMENTS */ +#define PH7_LIB_CONFIG_VFS 6 /* ONE ARGUMENT: const ph7_vfs *pVfs */ +/* + * Compile-time flags. + * The new compile interfaces [ph7_compile_v2()] and [ph7_compile_file()] takes + * as their last argument zero or more combination of compile time flags. + * These flags are used to control the behavior of the PH7 compiler while + * processing the input. + * Refer to the official documentation for additional information. + */ +#define PH7_PHP_ONLY 0x01 /* If this flag is set then the code to compile is assumed + * to be plain PHP only. That is, there is no need to delimit + * the PHP code using the standard tags such as or . + * Everything will pass through the PH7 compiler. + */ +#define PH7_PHP_EXPR 0x02 /* This flag is reserved for future use. */ +/* + * Call Context Error Message Serverity Level. + * + * The following constans are the allowed severity level that can + * passed as the second argument to the [ph7_context_throw_error()] or + * [ph7_context_throw_error_format()] interfaces. + * Refer to the official documentation for additional information. + */ +#define PH7_CTX_ERR 1 /* Call context error such as unexpected number of arguments,invalid types and so on. */ +#define PH7_CTX_WARNING 2 /* Call context Warning */ +#define PH7_CTX_NOTICE 3 /* Call context Notice */ +/* Current VFS structure version*/ +#define PH7_VFS_VERSION 2 +/* + * PH7 Virtual File System (VFS). + * + * An instance of the ph7_vfs object defines the interface between the PH7 core + * and the underlying operating system. The "vfs" in the name of the object stands + * for "virtual file system". The vfs is used to implement PHP system functions + * such as mkdir(), chdir(), stat(), get_user_name() and many more. + * The value of the iVersion field is initially 2 but may be larger in future versions + * of PH7. + * Additional fields may be appended to this object when the iVersion value is increased. + * Only a single vfs can be registered within the PH7 core. Vfs registration is done + * using the ph7_lib_config() interface with a configuration verb set to PH7_LIB_CONFIG_VFS. + * Note that Windows and UNIX (Linux, FreeBSD, Solaris, Mac OS X, etc.) users does not have to + * worry about registering and installing a vfs since PH7 come with a built-in vfs for these + * platforms which implement most the methods defined below. + * Host-application running on exotic systems (ie: Other than Windows and UNIX systems) must + * register their own vfs in order to be able to use and call PHP system function. + * Also note that the ph7_compile_file() interface depend on the xMmap() method of the underlying + * vfs which mean that this method must be available (Always the case using the built-in VFS) + * in order to use this interface. + * Developers wishing to implement the vfs methods can contact symisc systems to obtain + * the PH7 VFS C/C++ Specification manual. + */ +struct ph7_vfs +{ + const char *zName; /* Underlying VFS name [i.e: FreeBSD/Linux/Windows...] */ + int iVersion; /* Current VFS structure version [default 2] */ + /* Directory functions */ + int (*xChdir)(const char *); /* Change directory */ + int (*xChroot)(const char *); /* Change the root directory */ + int (*xGetcwd)(ph7_context *); /* Get the current working directory */ + int (*xMkdir)(const char *,int,int); /* Make directory */ + int (*xRmdir)(const char *); /* Remove directory */ + int (*xIsdir)(const char *); /* Tells whether the filename is a directory */ + int (*xRename)(const char *,const char *); /* Renames a file or directory */ + int (*xRealpath)(const char *,ph7_context *); /* Return canonicalized absolute pathname*/ + /* Systems functions */ + int (*xSleep)(unsigned int); /* Delay execution in microseconds */ + int (*xUnlink)(const char *); /* Deletes a file */ + int (*xFileExists)(const char *); /* Checks whether a file or directory exists */ + int (*xChmod)(const char *,int); /* Changes file mode */ + int (*xChown)(const char *,const char *); /* Changes file owner */ + int (*xChgrp)(const char *,const char *); /* Changes file group */ + ph7_int64 (*xFreeSpace)(const char *); /* Available space on filesystem or disk partition */ + ph7_int64 (*xTotalSpace)(const char *); /* Total space on filesystem or disk partition */ + ph7_int64 (*xFileSize)(const char *); /* Gets file size */ + ph7_int64 (*xFileAtime)(const char *); /* Gets last access time of file */ + ph7_int64 (*xFileMtime)(const char *); /* Gets file modification time */ + ph7_int64 (*xFileCtime)(const char *); /* Gets inode change time of file */ + int (*xStat)(const char *,ph7_value *,ph7_value *); /* Gives information about a file */ + int (*xlStat)(const char *,ph7_value *,ph7_value *); /* Gives information about a file */ + int (*xIsfile)(const char *); /* Tells whether the filename is a regular file */ + int (*xIslink)(const char *); /* Tells whether the filename is a symbolic link */ + int (*xReadable)(const char *); /* Tells whether a file exists and is readable */ + int (*xWritable)(const char *); /* Tells whether the filename is writable */ + int (*xExecutable)(const char *); /* Tells whether the filename is executable */ + int (*xFiletype)(const char *,ph7_context *); /* Gets file type [i.e: fifo,dir,file..] */ + int (*xGetenv)(const char *,ph7_context *); /* Gets the value of an environment variable */ + int (*xSetenv)(const char *,const char *); /* Sets the value of an environment variable */ + int (*xTouch)(const char *,ph7_int64,ph7_int64); /* Sets access and modification time of file */ + int (*xMmap)(const char *,void **,ph7_int64 *); /* Read-only memory map of the whole file */ + void (*xUnmap)(void *,ph7_int64); /* Unmap a memory view */ + int (*xLink)(const char *,const char *,int); /* Create hard or symbolic link */ + int (*xUmask)(int); /* Change the current umask */ + void (*xTempDir)(ph7_context *); /* Get path of the temporary directory */ + unsigned int (*xProcessId)(void); /* Get running process ID */ + int (*xUid)(void); /* user ID of the process */ + int (*xGid)(void); /* group ID of the process */ + void (*xUsername)(ph7_context *); /* Running username */ + int (*xExec)(const char *,ph7_context *); /* Execute an external program */ +}; +/* Current PH7 IO stream structure version. */ +#define PH7_IO_STREAM_VERSION 1 +/* + * Possible open mode flags that can be passed to the xOpen() routine + * of the underlying IO stream device . + * Refer to the PH7 IO Stream C/C++ specification manual (http://ph7.symisc.net/io_stream_spec.html) + * for additional information. + */ +#define PH7_IO_OPEN_RDONLY 0x001 /* Read-only open */ +#define PH7_IO_OPEN_WRONLY 0x002 /* Write-only open */ +#define PH7_IO_OPEN_RDWR 0x004 /* Read-write open. */ +#define PH7_IO_OPEN_CREATE 0x008 /* If the file does not exist it will be created */ +#define PH7_IO_OPEN_TRUNC 0x010 /* Truncate the file to zero length */ +#define PH7_IO_OPEN_APPEND 0x020 /* Append mode.The file offset is positioned at the end of the file */ +#define PH7_IO_OPEN_EXCL 0x040 /* Ensure that this call creates the file,the file must not exist before */ +#define PH7_IO_OPEN_BINARY 0x080 /* Simple hint: Data is binary */ +#define PH7_IO_OPEN_TEMP 0x100 /* Simple hint: Temporary file */ +#define PH7_IO_OPEN_TEXT 0x200 /* Simple hint: Data is textual */ +/* + * PH7 IO Stream Device. + * + * An instance of the ph7_io_stream object defines the interface between the PH7 core + * and the underlying stream device. + * A stream is a smart mechanism for generalizing file, network, data compression + * and other IO operations which share a common set of functions using an abstracted + * unified interface. + * A stream device is additional code which tells the stream how to handle specific + * protocols/encodings. For example, the http device knows how to translate a URL + * into an HTTP/1.1 request for a file on a remote server. + * PH7 come with two built-in IO streams device: + * The file:// stream which perform very efficient disk IO and the php:// stream + * which is a special stream that allow access various I/O streams (See the PHP official + * documentation for more information on this stream). + * A stream is referenced as: scheme://target + * scheme(string) - The name of the wrapper to be used. Examples include: file,http,https,ftp, + * ftps, compress.zlib, compress.bz2, and php. If no wrapper is specified,the function default + * is used (typically file://). + * target - Depends on the device used. For filesystem related streams this is typically a path + * and filename of the desired file.For network related streams this is typically a hostname,often + * with a path appended. + * IO stream devices are registered using a call to ph7_vm_config() with a configuration verb + * set to PH7_VM_CONFIG_IO_STREAM. + * Currently the PH7 development team is working on the implementation of the http:// and ftp:// + * IO stream protocols. These devices will be available in the next major release of the PH7 engine. + * Developers wishing to implement their own IO stream devices must understand and follow + * The PH7 IO Stream C/C++ specification manual (http://ph7.symisc.net/io_stream_spec.html). + */ +struct ph7_io_stream +{ + const char *zName; /* Underlying stream name [i.e: file/http/zip/php,..] */ + int iVersion; /* IO stream structure version [default 1]*/ + int (*xOpen)(const char *,int,ph7_value *,void **); /* Open handle*/ + int (*xOpenDir)(const char *,ph7_value *,void **); /* Open directory handle */ + void (*xClose)(void *); /* Close file handle */ + void (*xCloseDir)(void *); /* Close directory handle */ + ph7_int64 (*xRead)(void *,void *,ph7_int64); /* Read from the open stream */ + int (*xReadDir)(void *,ph7_context *); /* Read entry from directory handle */ + ph7_int64 (*xWrite)(void *,const void *,ph7_int64); /* Write to the open stream */ + int (*xSeek)(void *,ph7_int64,int); /* Seek on the open stream */ + int (*xLock)(void *,int); /* Lock/Unlock the open stream */ + void (*xRewindDir)(void *); /* Rewind directory handle */ + ph7_int64 (*xTell)(void *); /* Current position of the stream read/write pointer */ + int (*xTrunc)(void *,ph7_int64); /* Truncates the open stream to a given length */ + int (*xSync)(void *); /* Flush open stream data */ + int (*xStat)(void *,ph7_value *,ph7_value *); /* Stat an open stream handle */ +}; +/* + * C-API-REF: Please refer to the official documentation for interfaces + * purpose and expected parameters. + */ +/* Engine Handling Interfaces */ +PH7_APIEXPORT int ph7_init(ph7 **ppEngine); +PH7_APIEXPORT int ph7_config(ph7 *pEngine,int nConfigOp,...); +PH7_APIEXPORT int ph7_release(ph7 *pEngine); +/* Compile Interfaces */ +PH7_APIEXPORT int ph7_compile(ph7 *pEngine,const char *zSource,int nLen,ph7_vm **ppOutVm); +PH7_APIEXPORT int ph7_compile_v2(ph7 *pEngine,const char *zSource,int nLen,ph7_vm **ppOutVm,int iFlags); +PH7_APIEXPORT int ph7_compile_file(ph7 *pEngine,const char *zFilePath,ph7_vm **ppOutVm,int iFlags); +/* Virtual Machine Handling Interfaces */ +PH7_APIEXPORT int ph7_vm_config(ph7_vm *pVm,int iConfigOp,...); +PH7_APIEXPORT int ph7_vm_exec(ph7_vm *pVm,int *pExitStatus); +PH7_APIEXPORT int ph7_vm_reset(ph7_vm *pVm); +PH7_APIEXPORT int ph7_vm_release(ph7_vm *pVm); +PH7_APIEXPORT int ph7_vm_dump_v2(ph7_vm *pVm,int (*xConsumer)(const void *,unsigned int,void *),void *pUserData); +/* In-process Extending Interfaces */ +PH7_APIEXPORT int ph7_create_function(ph7_vm *pVm,const char *zName,int (*xFunc)(ph7_context *,int,ph7_value **),void *pUserData); +PH7_APIEXPORT int ph7_delete_function(ph7_vm *pVm,const char *zName); +PH7_APIEXPORT int ph7_create_constant(ph7_vm *pVm,const char *zName,void (*xExpand)(ph7_value *,void *),void *pUserData); +PH7_APIEXPORT int ph7_delete_constant(ph7_vm *pVm,const char *zName); +/* Foreign Function Parameter Values */ +PH7_APIEXPORT int ph7_value_to_int(ph7_value *pValue); +PH7_APIEXPORT int ph7_value_to_bool(ph7_value *pValue); +PH7_APIEXPORT ph7_int64 ph7_value_to_int64(ph7_value *pValue); +PH7_APIEXPORT double ph7_value_to_double(ph7_value *pValue); +PH7_APIEXPORT const char * ph7_value_to_string(ph7_value *pValue,int *pLen); +PH7_APIEXPORT void * ph7_value_to_resource(ph7_value *pValue); +PH7_APIEXPORT int ph7_value_compare(ph7_value *pLeft,ph7_value *pRight,int bStrict); +/* Setting The Result Of A Foreign Function */ +PH7_APIEXPORT int ph7_result_int(ph7_context *pCtx,int iValue); +PH7_APIEXPORT int ph7_result_int64(ph7_context *pCtx,ph7_int64 iValue); +PH7_APIEXPORT int ph7_result_bool(ph7_context *pCtx,int iBool); +PH7_APIEXPORT int ph7_result_double(ph7_context *pCtx,double Value); +PH7_APIEXPORT int ph7_result_null(ph7_context *pCtx); +PH7_APIEXPORT int ph7_result_string(ph7_context *pCtx,const char *zString,int nLen); +PH7_APIEXPORT int ph7_result_string_format(ph7_context *pCtx,const char *zFormat,...); +PH7_APIEXPORT int ph7_result_value(ph7_context *pCtx,ph7_value *pValue); +PH7_APIEXPORT int ph7_result_resource(ph7_context *pCtx,void *pUserData); +/* Call Context Handling Interfaces */ +PH7_APIEXPORT int ph7_context_output(ph7_context *pCtx,const char *zString,int nLen); +PH7_APIEXPORT int ph7_context_output_format(ph7_context *pCtx,const char *zFormat,...); +PH7_APIEXPORT int ph7_context_throw_error(ph7_context *pCtx,int iErr,const char *zErr); +PH7_APIEXPORT int ph7_context_throw_error_format(ph7_context *pCtx,int iErr,const char *zFormat,...); +PH7_APIEXPORT unsigned int ph7_context_random_num(ph7_context *pCtx); +PH7_APIEXPORT int ph7_context_random_string(ph7_context *pCtx,char *zBuf,int nBuflen); +PH7_APIEXPORT void * ph7_context_user_data(ph7_context *pCtx); +PH7_APIEXPORT int ph7_context_push_aux_data(ph7_context *pCtx,void *pUserData); +PH7_APIEXPORT void * ph7_context_peek_aux_data(ph7_context *pCtx); +PH7_APIEXPORT void * ph7_context_pop_aux_data(ph7_context *pCtx); +PH7_APIEXPORT unsigned int ph7_context_result_buf_length(ph7_context *pCtx); +PH7_APIEXPORT const char * ph7_function_name(ph7_context *pCtx); +/* Call Context Memory Management Interfaces */ +PH7_APIEXPORT void * ph7_context_alloc_chunk(ph7_context *pCtx,unsigned int nByte,int ZeroChunk,int AutoRelease); +PH7_APIEXPORT void * ph7_context_realloc_chunk(ph7_context *pCtx,void *pChunk,unsigned int nByte); +PH7_APIEXPORT void ph7_context_free_chunk(ph7_context *pCtx,void *pChunk); +/* On Demand Dynamically Typed Value Object allocation interfaces */ +PH7_APIEXPORT ph7_value * ph7_new_scalar(ph7_vm *pVm); +PH7_APIEXPORT ph7_value * ph7_new_array(ph7_vm *pVm); +PH7_APIEXPORT int ph7_release_value(ph7_vm *pVm,ph7_value *pValue); +PH7_APIEXPORT ph7_value * ph7_context_new_scalar(ph7_context *pCtx); +PH7_APIEXPORT ph7_value * ph7_context_new_array(ph7_context *pCtx); +PH7_APIEXPORT void ph7_context_release_value(ph7_context *pCtx,ph7_value *pValue); +/* Dynamically Typed Value Object Management Interfaces */ +PH7_APIEXPORT int ph7_value_int(ph7_value *pVal,int iValue); +PH7_APIEXPORT int ph7_value_int64(ph7_value *pVal,ph7_int64 iValue); +PH7_APIEXPORT int ph7_value_bool(ph7_value *pVal,int iBool); +PH7_APIEXPORT int ph7_value_null(ph7_value *pVal); +PH7_APIEXPORT int ph7_value_double(ph7_value *pVal,double Value); +PH7_APIEXPORT int ph7_value_string(ph7_value *pVal,const char *zString,int nLen); +PH7_APIEXPORT int ph7_value_string_format(ph7_value *pVal,const char *zFormat,...); +PH7_APIEXPORT int ph7_value_reset_string_cursor(ph7_value *pVal); +PH7_APIEXPORT int ph7_value_resource(ph7_value *pVal,void *pUserData); +PH7_APIEXPORT int ph7_value_release(ph7_value *pVal); +PH7_APIEXPORT ph7_value * ph7_array_fetch(ph7_value *pArray,const char *zKey,int nByte); +PH7_APIEXPORT int ph7_array_walk(ph7_value *pArray,int (*xWalk)(ph7_value *,ph7_value *,void *),void *pUserData); +PH7_APIEXPORT int ph7_array_add_elem(ph7_value *pArray,ph7_value *pKey,ph7_value *pValue); +PH7_APIEXPORT int ph7_array_add_strkey_elem(ph7_value *pArray,const char *zKey,ph7_value *pValue); +PH7_APIEXPORT int ph7_array_add_intkey_elem(ph7_value *pArray,int iKey,ph7_value *pValue); +PH7_APIEXPORT unsigned int ph7_array_count(ph7_value *pArray); +PH7_APIEXPORT int ph7_object_walk(ph7_value *pObject,int (*xWalk)(const char *,ph7_value *,void *),void *pUserData); +PH7_APIEXPORT ph7_value * ph7_object_fetch_attr(ph7_value *pObject,const char *zAttr); +PH7_APIEXPORT const char * ph7_object_get_class_name(ph7_value *pObject,int *pLength); +PH7_APIEXPORT int ph7_value_is_int(ph7_value *pVal); +PH7_APIEXPORT int ph7_value_is_float(ph7_value *pVal); +PH7_APIEXPORT int ph7_value_is_bool(ph7_value *pVal); +PH7_APIEXPORT int ph7_value_is_string(ph7_value *pVal); +PH7_APIEXPORT int ph7_value_is_null(ph7_value *pVal); +PH7_APIEXPORT int ph7_value_is_numeric(ph7_value *pVal); +PH7_APIEXPORT int ph7_value_is_callable(ph7_value *pVal); +PH7_APIEXPORT int ph7_value_is_scalar(ph7_value *pVal); +PH7_APIEXPORT int ph7_value_is_array(ph7_value *pVal); +PH7_APIEXPORT int ph7_value_is_object(ph7_value *pVal); +PH7_APIEXPORT int ph7_value_is_resource(ph7_value *pVal); +PH7_APIEXPORT int ph7_value_is_empty(ph7_value *pVal); +/* Global Library Management Interfaces */ +PH7_APIEXPORT int ph7_lib_init(void); +PH7_APIEXPORT int ph7_lib_config(int nConfigOp,...); +PH7_APIEXPORT int ph7_lib_shutdown(void); +PH7_APIEXPORT int ph7_lib_is_threadsafe(void); +PH7_APIEXPORT const char * ph7_lib_version(void); +PH7_APIEXPORT const char * ph7_lib_signature(void); +PH7_APIEXPORT const char * ph7_lib_ident(void); +PH7_APIEXPORT const char * ph7_lib_copyright(void); +#ifdef __cplusplus +} +#endif /* __cplusplus */ +#endif /* _PH7_H_ */ + +/* + * ---------------------------------------------------------- + * File: ph7int.h + * MD5: cdd8bb8c737e7e3ae5b14e01a01b98dd + * ---------------------------------------------------------- + */ +/* + * Symisc PH7: An embeddable bytecode compiler and a virtual machine for the PHP(5) programming language. + * Copyright (C) 2011-2012, Symisc Systems http://ph7.symisc.net/ + * Version 2.1.4 + * For information on licensing,redistribution of this file,and for a DISCLAIMER OF ALL WARRANTIES + * please contact Symisc Systems via: + * legal@symisc.net + * licensing@symisc.net + * contact@symisc.net + * or visit: + * http://ph7.symisc.net/ + */ + /* $SymiscID: ph7int.h v1.9 FreeBSD 2012-08-13 26:25 devel $ */ +#ifndef __PH7INT_H__ +#define __PH7INT_H__ +/* Internal interface definitions for PH7. */ +#ifdef PH7_AMALGAMATION +/* Marker for routines not intended for external use */ +#define PH7_PRIVATE static +#else +#define PH7_PRIVATE +#include "ph7.h" +#endif +#ifndef PH7_PI +/* Value of PI */ +#define PH7_PI 3.1415926535898 +#endif +/* + * Constants for the largest and smallest possible 64-bit signed integers. + * These macros are designed to work correctly on both 32-bit and 64-bit + * compilers. + */ +#ifndef LARGEST_INT64 +#define LARGEST_INT64 (0xffffffff|(((sxi64)0x7fffffff)<<32)) +#endif +#ifndef SMALLEST_INT64 +#define SMALLEST_INT64 (((sxi64)-1) - LARGEST_INT64) +#endif +/* Forward declaration of private structures */ +typedef struct ph7_class_instance ph7_class_instance; +typedef struct ph7_foreach_info ph7_foreach_info; +typedef struct ph7_foreach_step ph7_foreach_step; +typedef struct ph7_hashmap_node ph7_hashmap_node; +typedef struct ph7_hashmap ph7_hashmap; +typedef struct ph7_class ph7_class; +/* Symisc Standard types */ +#if !defined(SYMISC_STD_TYPES) +#define SYMISC_STD_TYPES +#ifdef __WINNT__ +/* Disable nuisance warnings on Borland compilers */ +#if defined(__BORLANDC__) +#pragma warn -rch /* unreachable code */ +#pragma warn -ccc /* Condition is always true or false */ +#pragma warn -aus /* Assigned value is never used */ +#pragma warn -csu /* Comparing signed and unsigned */ +#pragma warn -spa /* Suspicious pointer arithmetic */ +#endif +#endif +typedef signed char sxi8; /* signed char */ +typedef unsigned char sxu8; /* unsigned char */ +typedef signed short int sxi16; /* 16 bits(2 bytes) signed integer */ +typedef unsigned short int sxu16; /* 16 bits(2 bytes) unsigned integer */ +typedef int sxi32; /* 32 bits(4 bytes) integer */ +typedef unsigned int sxu32; /* 32 bits(4 bytes) unsigned integer */ +typedef long sxptr; +typedef unsigned long sxuptr; +typedef long sxlong; +typedef unsigned long sxulong; +typedef sxi32 sxofft; +typedef sxi64 sxofft64; +typedef long double sxlongreal; +typedef double sxreal; +#define SXI8_HIGH 0x7F +#define SXU8_HIGH 0xFF +#define SXI16_HIGH 0x7FFF +#define SXU16_HIGH 0xFFFF +#define SXI32_HIGH 0x7FFFFFFF +#define SXU32_HIGH 0xFFFFFFFF +#define SXI64_HIGH 0x7FFFFFFFFFFFFFFF +#define SXU64_HIGH 0xFFFFFFFFFFFFFFFF +#if !defined(TRUE) +#define TRUE 1 +#endif +#if !defined(FALSE) +#define FALSE 0 +#endif +/* + * The following macros are used to cast pointers to integers and + * integers to pointers. + */ +#if defined(__PTRDIFF_TYPE__) +# define SX_INT_TO_PTR(X) ((void*)(__PTRDIFF_TYPE__)(X)) +# define SX_PTR_TO_INT(X) ((int)(__PTRDIFF_TYPE__)(X)) +#elif !defined(__GNUC__) +# define SX_INT_TO_PTR(X) ((void*)&((char*)0)[X]) +# define SX_PTR_TO_INT(X) ((int)(((char*)X)-(char*)0)) +#else +# define SX_INT_TO_PTR(X) ((void*)(X)) +# define SX_PTR_TO_INT(X) ((int)(X)) +#endif +#define SXMIN(a,b) ((a < b) ? (a) : (b)) +#define SXMAX(a,b) ((a < b) ? (b) : (a)) +#endif /* SYMISC_STD_TYPES */ +/* Symisc Run-time API private definitions */ +#if !defined(SYMISC_PRIVATE_DEFS) +#define SYMISC_PRIVATE_DEFS + +typedef sxi32 (*ProcRawStrCmp)(const SyString *,const SyString *); +#define SyStringData(RAW) ((RAW)->zString) +#define SyStringLength(RAW) ((RAW)->nByte) +#define SyStringInitFromBuf(RAW,ZBUF,NLEN){\ + (RAW)->zString = (const char *)ZBUF;\ + (RAW)->nByte = (sxu32)(NLEN);\ +} +#define SyStringUpdatePtr(RAW,NBYTES){\ + if( NBYTES > (RAW)->nByte ){\ + (RAW)->nByte = 0;\ + }else{\ + (RAW)->zString += NBYTES;\ + (RAW)->nByte -= NBYTES;\ + }\ +} +#define SyStringDupPtr(RAW1,RAW2)\ + (RAW1)->zString = (RAW2)->zString;\ + (RAW1)->nByte = (RAW2)->nByte; + +#define SyStringTrimLeadingChar(RAW,CHAR)\ + while((RAW)->nByte > 0 && (RAW)->zString[0] == CHAR ){\ + (RAW)->zString++;\ + (RAW)->nByte--;\ + } +#define SyStringTrimTrailingChar(RAW,CHAR)\ + while((RAW)->nByte > 0 && (RAW)->zString[(RAW)->nByte - 1] == CHAR){\ + (RAW)->nByte--;\ + } +#define SyStringCmp(RAW1,RAW2,xCMP)\ + (((RAW1)->nByte == (RAW2)->nByte) ? xCMP((RAW1)->zString,(RAW2)->zString,(RAW2)->nByte) : (sxi32)((RAW1)->nByte - (RAW2)->nByte)) + +#define SyStringCmp2(RAW1,RAW2,xCMP)\ + (((RAW1)->nByte >= (RAW2)->nByte) ? xCMP((RAW1)->zString,(RAW2)->zString,(RAW2)->nByte) : (sxi32)((RAW2)->nByte - (RAW1)->nByte)) + +#define SyStringCharCmp(RAW,CHAR) \ + (((RAW)->nByte == sizeof(char)) ? ((RAW)->zString[0] == CHAR ? 0 : CHAR - (RAW)->zString[0]) : ((RAW)->zString[0] == CHAR ? 0 : (RAW)->nByte - sizeof(char))) + +#define SX_ADDR(PTR) ((sxptr)PTR) +#define SX_ARRAYSIZE(X) (sizeof(X)/sizeof(X[0])) +#define SXUNUSED(P) (P = 0) +#define SX_EMPTY(PTR) (PTR == 0) +#define SX_EMPTY_STR(STR) (STR == 0 || STR[0] == 0 ) +typedef struct SyMemBackend SyMemBackend; +typedef struct SyBlob SyBlob; +typedef struct SySet SySet; +/* Standard function signatures */ +typedef sxi32 (*ProcCmp)(const void *,const void *,sxu32); +typedef sxi32 (*ProcPatternMatch)(const char *,sxu32,const char *,sxu32,sxu32 *); +typedef sxi32 (*ProcSearch)(const void *,sxu32,const void *,sxu32,ProcCmp,sxu32 *); +typedef sxu32 (*ProcHash)(const void *,sxu32); +typedef sxi32 (*ProcHashSum)(const void *,sxu32,unsigned char *,sxu32); +typedef sxi32 (*ProcSort)(void *,sxu32,sxu32,ProcCmp); +#define MACRO_LIST_PUSH(Head,Item)\ + Item->pNext = Head;\ + Head = Item; +#define MACRO_LD_PUSH(Head,Item)\ + if( Head == 0 ){\ + Head = Item;\ + }else{\ + Item->pNext = Head;\ + Head->pPrev = Item;\ + Head = Item;\ + } +#define MACRO_LD_REMOVE(Head,Item)\ + if( Head == Item ){\ + Head = Head->pNext;\ + }\ + if( Item->pPrev ){ Item->pPrev->pNext = Item->pNext;}\ + if( Item->pNext ){ Item->pNext->pPrev = Item->pPrev;} +/* + * A generic dynamic set. + */ +struct SySet +{ + SyMemBackend *pAllocator; /* Memory backend */ + void *pBase; /* Base pointer */ + sxu32 nUsed; /* Total number of used slots */ + sxu32 nSize; /* Total number of available slots */ + sxu32 eSize; /* Size of a single slot */ + sxu32 nCursor; /* Loop cursor */ + void *pUserData; /* User private data associated with this container */ +}; +#define SySetBasePtr(S) ((S)->pBase) +#define SySetBasePtrJump(S,OFFT) (&((char *)(S)->pBase)[OFFT*(S)->eSize]) +#define SySetUsed(S) ((S)->nUsed) +#define SySetSize(S) ((S)->nSize) +#define SySetElemSize(S) ((S)->eSize) +#define SySetCursor(S) ((S)->nCursor) +#define SySetGetAllocator(S) ((S)->pAllocator) +#define SySetSetUserData(S,DATA) ((S)->pUserData = DATA) +#define SySetGetUserData(S) ((S)->pUserData) +/* + * A variable length containers for generic data. + */ +struct SyBlob +{ + SyMemBackend *pAllocator; /* Memory backend */ + void *pBlob; /* Base pointer */ + sxu32 nByte; /* Total number of used bytes */ + sxu32 mByte; /* Total number of available bytes */ + sxu32 nFlags; /* Blob internal flags,see below */ +}; +#define SXBLOB_LOCKED 0x01 /* Blob is locked [i.e: Cannot auto grow] */ +#define SXBLOB_STATIC 0x02 /* Not allocated from heap */ +#define SXBLOB_RDONLY 0x04 /* Read-Only data */ + +#define SyBlobFreeSpace(BLOB) ((BLOB)->mByte - (BLOB)->nByte) +#define SyBlobLength(BLOB) ((BLOB)->nByte) +#define SyBlobData(BLOB) ((BLOB)->pBlob) +#define SyBlobCurData(BLOB) ((void*)(&((char*)(BLOB)->pBlob)[(BLOB)->nByte])) +#define SyBlobDataAt(BLOB,OFFT) ((void *)(&((char *)(BLOB)->pBlob)[OFFT])) +#define SyBlobGetAllocator(BLOB) ((BLOB)->pAllocator) + +#define SXMEM_POOL_INCR 3 +#define SXMEM_POOL_NBUCKETS 12 +#define SXMEM_BACKEND_MAGIC 0xBAC3E67D +#define SXMEM_BACKEND_CORRUPT(BACKEND) (BACKEND == 0 || BACKEND->nMagic != SXMEM_BACKEND_MAGIC) + +#define SXMEM_BACKEND_RETRY 3 +/* A memory backend subsystem is defined by an instance of the following structures */ +typedef union SyMemHeader SyMemHeader; +typedef struct SyMemBlock SyMemBlock; +struct SyMemBlock +{ + SyMemBlock *pNext,*pPrev; /* Chain of allocated memory blocks */ +#ifdef UNTRUST + sxu32 nGuard; /* magic number associated with each valid block,so we + * can detect misuse. + */ +#endif +}; +/* + * Header associated with each valid memory pool block. + */ +union SyMemHeader +{ + SyMemHeader *pNext; /* Next chunk of size 1 << (nBucket + SXMEM_POOL_INCR) in the list */ + sxu32 nBucket; /* Bucket index in aPool[] */ +}; +struct SyMemBackend +{ + const SyMutexMethods *pMutexMethods; /* Mutex methods */ + const SyMemMethods *pMethods; /* Memory allocation methods */ + SyMemBlock *pBlocks; /* List of valid memory blocks */ + sxu32 nBlock; /* Total number of memory blocks allocated so far */ + ProcMemError xMemError; /* Out-of memory callback */ + void *pUserData; /* First arg to xMemError() */ + SyMutex *pMutex; /* Per instance mutex */ + sxu32 nMagic; /* Sanity check against misuse */ + SyMemHeader *apPool[SXMEM_POOL_NBUCKETS+SXMEM_POOL_INCR]; /* Pool of memory chunks */ +}; +/* Mutex types */ +#define SXMUTEX_TYPE_FAST 1 +#define SXMUTEX_TYPE_RECURSIVE 2 +#define SXMUTEX_TYPE_STATIC_1 3 +#define SXMUTEX_TYPE_STATIC_2 4 +#define SXMUTEX_TYPE_STATIC_3 5 +#define SXMUTEX_TYPE_STATIC_4 6 +#define SXMUTEX_TYPE_STATIC_5 7 +#define SXMUTEX_TYPE_STATIC_6 8 + +#define SyMutexGlobalInit(METHOD){\ + if( (METHOD)->xGlobalInit ){\ + (METHOD)->xGlobalInit();\ + }\ +} +#define SyMutexGlobalRelease(METHOD){\ + if( (METHOD)->xGlobalRelease ){\ + (METHOD)->xGlobalRelease();\ + }\ +} +#define SyMutexNew(METHOD,TYPE) (METHOD)->xNew(TYPE) +#define SyMutexRelease(METHOD,MUTEX){\ + if( MUTEX && (METHOD)->xRelease ){\ + (METHOD)->xRelease(MUTEX);\ + }\ +} +#define SyMutexEnter(METHOD,MUTEX){\ + if( MUTEX ){\ + (METHOD)->xEnter(MUTEX);\ + }\ +} +#define SyMutexTryEnter(METHOD,MUTEX){\ + if( MUTEX && (METHOD)->xTryEnter ){\ + (METHOD)->xTryEnter(MUTEX);\ + }\ +} +#define SyMutexLeave(METHOD,MUTEX){\ + if( MUTEX ){\ + (METHOD)->xLeave(MUTEX);\ + }\ +} +/* Comparison,byte swap,byte copy macros */ +#define SX_MACRO_FAST_CMP(X1,X2,SIZE,RC){\ + register unsigned char *r1 = (unsigned char *)X1;\ + register unsigned char *r2 = (unsigned char *)X2;\ + register sxu32 LEN = SIZE;\ + for(;;){\ + if( !LEN ){ break; }if( r1[0] != r2[0] ){ break; } r1++; r2++; LEN--;\ + if( !LEN ){ break; }if( r1[0] != r2[0] ){ break; } r1++; r2++; LEN--;\ + if( !LEN ){ break; }if( r1[0] != r2[0] ){ break; } r1++; r2++; LEN--;\ + if( !LEN ){ break; }if( r1[0] != r2[0] ){ break; } r1++; r2++; LEN--;\ + }\ + RC = !LEN ? 0 : r1[0] - r2[0];\ +} +#define SX_MACRO_FAST_MEMCPY(SRC,DST,SIZ){\ + register unsigned char *xSrc = (unsigned char *)SRC;\ + register unsigned char *xDst = (unsigned char *)DST;\ + register sxu32 xLen = SIZ;\ + for(;;){\ + if( !xLen ){ break; }xDst[0] = xSrc[0]; xDst++; xSrc++; --xLen;\ + if( !xLen ){ break; }xDst[0] = xSrc[0]; xDst++; xSrc++; --xLen;\ + if( !xLen ){ break; }xDst[0] = xSrc[0]; xDst++; xSrc++; --xLen;\ + if( !xLen ){ break; }xDst[0] = xSrc[0]; xDst++; xSrc++; --xLen;\ + }\ +} +#define SX_MACRO_BYTE_SWAP(X,Y,Z){\ + register unsigned char *s = (unsigned char *)X;\ + register unsigned char *d = (unsigned char *)Y;\ + sxu32 ZLong = Z; \ + sxi32 c; \ + for(;;){\ + if(!ZLong){ break; } c = s[0] ; s[0] = d[0]; d[0] = (unsigned char)c; s++; d++; --ZLong;\ + if(!ZLong){ break; } c = s[0] ; s[0] = d[0]; d[0] = (unsigned char)c; s++; d++; --ZLong;\ + if(!ZLong){ break; } c = s[0] ; s[0] = d[0]; d[0] = (unsigned char)c; s++; d++; --ZLong;\ + if(!ZLong){ break; } c = s[0] ; s[0] = d[0]; d[0] = (unsigned char)c; s++; d++; --ZLong;\ + }\ +} +#define SX_MSEC_PER_SEC (1000) /* Millisec per seconds */ +#define SX_USEC_PER_SEC (1000000) /* Microsec per seconds */ +#define SX_NSEC_PER_SEC (1000000000) /* Nanosec per seconds */ +#endif /* SYMISC_PRIVATE_DEFS */ +/* Symisc Run-time API auxiliary definitions */ +#if !defined(SYMISC_PRIVATE_AUX_DEFS) +#define SYMISC_PRIVATE_AUX_DEFS + +typedef struct SyHashEntry_Pr SyHashEntry_Pr; +typedef struct SyHashEntry SyHashEntry; +typedef struct SyHash SyHash; +/* + * Each public hashtable entry is represented by an instance + * of the following structure. + */ +struct SyHashEntry +{ + const void *pKey; /* Hash key */ + sxu32 nKeyLen; /* Key length */ + void *pUserData; /* User private data */ +}; +#define SyHashEntryGetUserData(ENTRY) ((ENTRY)->pUserData) +#define SyHashEntryGetKey(ENTRY) ((ENTRY)->pKey) +/* Each active hashtable is identified by an instance of the following structure */ +struct SyHash +{ + SyMemBackend *pAllocator; /* Memory backend */ + ProcHash xHash; /* Hash function */ + ProcCmp xCmp; /* Comparison function */ + SyHashEntry_Pr *pList,*pCurrent; /* Linked list of hash entries user for linear traversal */ + sxu32 nEntry; /* Total number of entries */ + SyHashEntry_Pr **apBucket; /* Hash buckets */ + sxu32 nBucketSize; /* Current bucket size */ +}; +#define SXHASH_BUCKET_SIZE 16 /* Initial bucket size: must be a power of two */ +#define SXHASH_FILL_FACTOR 3 +/* Hash access macro */ +#define SyHashFunc(HASH) ((HASH)->xHash) +#define SyHashCmpFunc(HASH) ((HASH)->xCmp) +#define SyHashTotalEntry(HASH) ((HASH)->nEntry) +#define SyHashGetPool(HASH) ((HASH)->pAllocator) +/* + * An instance of the following structure define a single context + * for an Pseudo Random Number Generator. + * + * Nothing in this file or anywhere else in the library does any kind of + * encryption. The RC4 algorithm is being used as a PRNG (pseudo-random + * number generator) not as an encryption device. + * This implementation is taken from the SQLite3 source tree. + */ +typedef struct SyPRNGCtx SyPRNGCtx; +struct SyPRNGCtx +{ + sxu8 i,j; /* State variables */ + unsigned char s[256]; /* State variables */ + sxu16 nMagic; /* Sanity check */ + }; +typedef sxi32 (*ProcRandomSeed)(void *,unsigned int,void *); +/* High resolution timer.*/ +typedef struct sytime sytime; +struct sytime +{ + long tm_sec; /* seconds */ + long tm_usec; /* microseconds */ +}; +/* Forward declaration */ +typedef struct SyStream SyStream; +typedef struct SyToken SyToken; +typedef struct SyLex SyLex; +/* + * Tokenizer callback signature. + */ +typedef sxi32 (*ProcTokenizer)(SyStream *,SyToken *,void *,void *); +/* + * Each token in the input is represented by an instance + * of the following structure. + */ +struct SyToken +{ + SyString sData; /* Token text and length */ + sxu32 nType; /* Token type */ + sxu32 nLine; /* Token line number */ + void *pUserData; /* User private data associated with this token */ +}; +/* + * During tokenization, information about the state of the input + * stream is held in an instance of the following structure. + */ +struct SyStream +{ + const unsigned char *zInput; /* Complete text of the input */ + const unsigned char *zText; /* Current input we are processing */ + const unsigned char *zEnd; /* End of input marker */ + sxu32 nLine; /* Total number of processed lines */ + sxu32 nIgn; /* Total number of ignored tokens */ + SySet *pSet; /* Token containers */ +}; +/* + * Each lexer is represented by an instance of the following structure. + */ +struct SyLex +{ + SyStream sStream; /* Input stream */ + ProcTokenizer xTokenizer; /* Tokenizer callback */ + void * pUserData; /* Third argument to xTokenizer() */ + SySet *pTokenSet; /* Token set */ +}; +#define SyLexTotalToken(LEX) SySetTotalEntry(&(LEX)->aTokenSet) +#define SyLexTotalLines(LEX) ((LEX)->sStream.nLine) +#define SyLexTotalIgnored(LEX) ((LEX)->sStream.nIgn) +#define XLEX_IN_LEN(STREAM) (sxu32)(STREAM->zEnd - STREAM->zText) +#endif /* SYMISC_PRIVATE_AUX_DEFS */ +/* +** Notes on UTF-8 (According to SQLite3 authors): +** +** Byte-0 Byte-1 Byte-2 Byte-3 Value +** 0xxxxxxx 00000000 00000000 0xxxxxxx +** 110yyyyy 10xxxxxx 00000000 00000yyy yyxxxxxx +** 1110zzzz 10yyyyyy 10xxxxxx 00000000 zzzzyyyy yyxxxxxx +** 11110uuu 10uuzzzz 10yyyyyy 10xxxxxx 000uuuuu zzzzyyyy yyxxxxxx +** +*/ +/* +** Assuming zIn points to the first byte of a UTF-8 character, +** advance zIn to point to the first byte of the next UTF-8 character. +*/ +#define SX_JMP_UTF8(zIn,zEnd)\ + while(zIn < zEnd && (((unsigned char)zIn[0] & 0xc0) == 0x80) ){ zIn++; } +#define SX_WRITE_UTF8(zOut, c) { \ + if( c<0x00080 ){ \ + *zOut++ = (sxu8)(c&0xFF); \ + }else if( c<0x00800 ){ \ + *zOut++ = 0xC0 + (sxu8)((c>>6)&0x1F); \ + *zOut++ = 0x80 + (sxu8)(c & 0x3F); \ + }else if( c<0x10000 ){ \ + *zOut++ = 0xE0 + (sxu8)((c>>12)&0x0F); \ + *zOut++ = 0x80 + (sxu8)((c>>6) & 0x3F); \ + *zOut++ = 0x80 + (sxu8)(c & 0x3F); \ + }else{ \ + *zOut++ = 0xF0 + (sxu8)((c>>18) & 0x07); \ + *zOut++ = 0x80 + (sxu8)((c>>12) & 0x3F); \ + *zOut++ = 0x80 + (sxu8)((c>>6) & 0x3F); \ + *zOut++ = 0x80 + (sxu8)(c & 0x3F); \ + } \ +} +/* Rely on the standard ctype */ +#include +#define SyToUpper(c) toupper(c) +#define SyToLower(c) tolower(c) +#define SyisUpper(c) isupper(c) +#define SyisLower(c) islower(c) +#define SyisSpace(c) isspace(c) +#define SyisBlank(c) isspace(c) +#define SyisAlpha(c) isalpha(c) +#define SyisDigit(c) isdigit(c) +#define SyisHex(c) isxdigit(c) +#define SyisPrint(c) isprint(c) +#define SyisPunct(c) ispunct(c) +#define SyisSpec(c) iscntrl(c) +#define SyisCtrl(c) iscntrl(c) +#define SyisAscii(c) isascii(c) +#define SyisAlphaNum(c) isalnum(c) +#define SyisGraph(c) isgraph(c) +#define SyDigToHex(c) "0123456789ABCDEF"[c & 0x0F] +#define SyDigToInt(c) ((c < 0xc0 && SyisDigit(c))? (c - '0') : 0 ) +#define SyCharToUpper(c) ((c < 0xc0 && SyisLower(c))? SyToUpper(c) : c) +#define SyCharToLower(c) ((c < 0xc0 && SyisUpper(c))? SyToLower(c) : c) +/* Remove white space/NUL byte from a raw string */ +#define SyStringLeftTrim(RAW)\ + while((RAW)->nByte > 0 && (unsigned char)(RAW)->zString[0] < 0xc0 && SyisSpace((RAW)->zString[0])){\ + (RAW)->nByte--;\ + (RAW)->zString++;\ + } +#define SyStringLeftTrimSafe(RAW)\ + while((RAW)->nByte > 0 && (unsigned char)(RAW)->zString[0] < 0xc0 && ((RAW)->zString[0] == 0 || SyisSpace((RAW)->zString[0]))){\ + (RAW)->nByte--;\ + (RAW)->zString++;\ + } +#define SyStringRightTrim(RAW)\ + while((RAW)->nByte > 0 && (unsigned char)(RAW)->zString[(RAW)->nByte - 1] < 0xc0 && SyisSpace((RAW)->zString[(RAW)->nByte - 1])){\ + (RAW)->nByte--;\ + } +#define SyStringRightTrimSafe(RAW)\ + while((RAW)->nByte > 0 && (unsigned char)(RAW)->zString[(RAW)->nByte - 1] < 0xc0 && \ + (( RAW)->zString[(RAW)->nByte - 1] == 0 || SyisSpace((RAW)->zString[(RAW)->nByte - 1]))){\ + (RAW)->nByte--;\ + } + +#define SyStringFullTrim(RAW)\ + while((RAW)->nByte > 0 && (unsigned char)(RAW)->zString[0] < 0xc0 && SyisSpace((RAW)->zString[0])){\ + (RAW)->nByte--;\ + (RAW)->zString++;\ + }\ + while((RAW)->nByte > 0 && (unsigned char)(RAW)->zString[(RAW)->nByte - 1] < 0xc0 && SyisSpace((RAW)->zString[(RAW)->nByte - 1])){\ + (RAW)->nByte--;\ + } +#define SyStringFullTrimSafe(RAW)\ + while((RAW)->nByte > 0 && (unsigned char)(RAW)->zString[0] < 0xc0 && \ + ( (RAW)->zString[0] == 0 || SyisSpace((RAW)->zString[0]))){\ + (RAW)->nByte--;\ + (RAW)->zString++;\ + }\ + while((RAW)->nByte > 0 && (unsigned char)(RAW)->zString[(RAW)->nByte - 1] < 0xc0 && \ + ( (RAW)->zString[(RAW)->nByte - 1] == 0 || SyisSpace((RAW)->zString[(RAW)->nByte - 1]))){\ + (RAW)->nByte--;\ + } +#ifndef PH7_DISABLE_BUILTIN_FUNC +/* + * An XML raw text,CDATA,tag name and son is parsed out and stored + * in an instance of the following structure. + */ +typedef struct SyXMLRawStr SyXMLRawStr; +struct SyXMLRawStr +{ + const char *zString; /* Raw text [UTF-8 ENCODED EXCEPT CDATA] [NOT NULL TERMINATED] */ + sxu32 nByte; /* Text length */ + sxu32 nLine; /* Line number this text occurs */ +}; +/* + * Event callback signatures. + */ +typedef sxi32 (*ProcXMLStartTagHandler)(SyXMLRawStr * ,SyXMLRawStr *,sxu32,SyXMLRawStr *,void *); +typedef sxi32 (*ProcXMLTextHandler)(SyXMLRawStr *,void *); +typedef sxi32 (*ProcXMLEndTagHandler)(SyXMLRawStr * ,SyXMLRawStr *,void *); +typedef sxi32 (*ProcXMLPIHandler)(SyXMLRawStr *,SyXMLRawStr *,void *); +typedef sxi32 (*ProcXMLDoctypeHandler)(SyXMLRawStr *,void *); +typedef sxi32 (*ProcXMLSyntaxErrorHandler)(const char *,int,SyToken *,void *); +typedef sxi32 (*ProcXMLStartDocument)(void *); +typedef sxi32 (*ProcXMLNameSpaceStart)(SyXMLRawStr *,SyXMLRawStr *,void *); +typedef sxi32 (*ProcXMLNameSpaceEnd)(SyXMLRawStr *,void *); +typedef sxi32 (*ProcXMLEndDocument)(void *); +/* XML processing control flags */ +#define SXML_ENABLE_NAMESPACE 0x01 /* Parse XML with namespace support enbaled */ +#define SXML_ENABLE_QUERY 0x02 /* Not used */ +#define SXML_OPTION_CASE_FOLDING 0x04 /* Controls whether case-folding is enabled for this XML parser */ +#define SXML_OPTION_SKIP_TAGSTART 0x08 /* Specify how many characters should be skipped in the beginning of a tag name.*/ +#define SXML_OPTION_SKIP_WHITE 0x10 /* Whether to skip values consisting of whitespace characters. */ +#define SXML_OPTION_TARGET_ENCODING 0x20 /* Default encoding: UTF-8 */ +/* XML error codes */ +enum xml_err_code{ + SXML_ERROR_NONE = 1, + SXML_ERROR_NO_MEMORY, + SXML_ERROR_SYNTAX, + SXML_ERROR_NO_ELEMENTS, + SXML_ERROR_INVALID_TOKEN, + SXML_ERROR_UNCLOSED_TOKEN, + SXML_ERROR_PARTIAL_CHAR, + SXML_ERROR_TAG_MISMATCH, + SXML_ERROR_DUPLICATE_ATTRIBUTE, + SXML_ERROR_JUNK_AFTER_DOC_ELEMENT, + SXML_ERROR_PARAM_ENTITY_REF, + SXML_ERROR_UNDEFINED_ENTITY, + SXML_ERROR_RECURSIVE_ENTITY_REF, + SXML_ERROR_ASYNC_ENTITY, + SXML_ERROR_BAD_CHAR_REF, + SXML_ERROR_BINARY_ENTITY_REF, + SXML_ERROR_ATTRIBUTE_EXTERNAL_ENTITY_REF, + SXML_ERROR_MISPLACED_XML_PI, + SXML_ERROR_UNKNOWN_ENCODING, + SXML_ERROR_INCORRECT_ENCODING, + SXML_ERROR_UNCLOSED_CDATA_SECTION, + SXML_ERROR_EXTERNAL_ENTITY_HANDLING +}; +/* Each active XML SAX parser is represented by an instance + * of the following structure. + */ +typedef struct SyXMLParser SyXMLParser; +struct SyXMLParser +{ + SyMemBackend *pAllocator; /* Memory backend */ + void *pUserData; /* User private data forwarded varbatim by the XML parser + * as the last argument to the users callbacks. + */ + SyHash hns; /* Namespace hashtable */ + SySet sToken; /* XML tokens */ + SyLex sLex; /* Lexical analyzer */ + sxi32 nFlags; /* Control flags */ + /* User callbacks */ + ProcXMLStartTagHandler xStartTag; /* Start element handler */ + ProcXMLEndTagHandler xEndTag; /* End element handler */ + ProcXMLTextHandler xRaw; /* Raw text/CDATA handler */ + ProcXMLDoctypeHandler xDoctype; /* DOCTYPE handler */ + ProcXMLPIHandler xPi; /* Processing instruction (PI) handler*/ + ProcXMLSyntaxErrorHandler xError; /* Error handler */ + ProcXMLStartDocument xStartDoc; /* StartDoc handler */ + ProcXMLEndDocument xEndDoc; /* EndDoc handler */ + ProcXMLNameSpaceStart xNameSpace; /* Namespace declaration handler */ + ProcXMLNameSpaceEnd xNameSpaceEnd; /* End namespace declaration handler */ +}; +/* + * -------------- + * Archive extractor: + * -------------- + * Each open ZIP/TAR archive is identified by an instance of the following structure. + * That is, a process can open one or more archives and manipulates them in thread safe + * way by simply working with pointers to the following structure. + * Each entry in the archive is remembered in a hashtable. + * Lookup is very fast and entry with the same name are chained together. + */ + typedef struct SyArchiveEntry SyArchiveEntry; + typedef struct SyArchive SyArchive; + struct SyArchive + { + SyMemBackend *pAllocator; /* Memory backend */ + SyArchiveEntry *pCursor; /* Cursor for linear traversal of archive entries */ + SyArchiveEntry *pList; /* Pointer to the List of the loaded archive */ + SyArchiveEntry **apHash; /* Hashtable for archive entry */ + ProcRawStrCmp xCmp; /* Hash comparison function */ + ProcHash xHash; /* Hash Function */ + sxu32 nSize; /* Hashtable size */ + sxu32 nEntry; /* Total number of entries in the zip/tar archive */ + sxu32 nLoaded; /* Total number of entries loaded in memory */ + sxu32 nCentralOfft; /* Central directory offset(ZIP only. Otherwise Zero) */ + sxu32 nCentralSize; /* Central directory size(ZIP only. Otherwise Zero) */ + void *pUserData; /* Upper layer private data */ + sxu32 nMagic; /* Sanity check */ + + }; +#define SXARCH_MAGIC 0xDEAD635A +#define SXARCH_INVALID(ARCH) (ARCH == 0 || ARCH->nMagic != SXARCH_MAGIC) +#define SXARCH_ENTRY_INVALID(ENTRY) (ENTRY == 0 || ENTRY->nMagic != SXARCH_MAGIC) +#define SyArchiveHashFunc(ARCH) (ARCH)->xHash +#define SyArchiveCmpFunc(ARCH) (ARCH)->xCmp +#define SyArchiveUserData(ARCH) (ARCH)->pUserData +#define SyArchiveSetUserData(ARCH,DATA) (ARCH)->pUserData = DATA +/* + * Each loaded archive record is identified by an instance + * of the following structure. + */ + struct SyArchiveEntry + { + sxu32 nByte; /* Contents size before compression */ + sxu32 nByteCompr; /* Contents size after compression */ + sxu32 nReadCount; /* Read counter */ + sxu32 nCrc; /* Contents CRC32 */ + Sytm sFmt; /* Last-modification time */ + sxu32 nOfft; /* Data offset. */ + sxu16 nComprMeth; /* Compression method 0 == stored/8 == deflated and so on (see appnote.txt)*/ + sxu16 nExtra; /* Extra size if any */ + SyString sFileName; /* entry name & length */ + sxu32 nDup; /* Total number of entries with the same name */ + SyArchiveEntry *pNextHash,*pPrevHash; /* Hash collision chains */ + SyArchiveEntry *pNextName; /* Next entry with the same name */ + SyArchiveEntry *pNext,*pPrev; /* Next and previous entry in the list */ + sxu32 nHash; /* Hash of the entry name */ + void *pUserData; /* User data */ + sxu32 nMagic; /* Sanity check */ + }; + /* + * Extra flags for extending the file local header + */ +#define SXZIP_EXTRA_TIMESTAMP 0x001 /* Extended UNIX timestamp */ +#endif /* PH7_DISABLE_BUILTIN_FUNC */ +#ifndef PH7_DISABLE_HASH_FUNC +/* MD5 context */ +typedef struct MD5Context MD5Context; +struct MD5Context { + sxu32 buf[4]; + sxu32 bits[2]; + unsigned char in[64]; +}; +/* SHA1 context */ +typedef struct SHA1Context SHA1Context; +struct SHA1Context { + unsigned int state[5]; + unsigned int count[2]; + unsigned char buffer[64]; +}; +#endif /* PH7_DISABLE_HASH_FUNC */ +/* PH7 private declaration */ +/* + * Memory Objects. + * Internally, the PH7 virtual machine manipulates nearly all PHP values + * [i.e: string, int, float, resource, object, bool, null] as ph7_values structures. + * Each ph7_values struct may cache multiple representations (string, integer etc.) + * of the same value. + */ +struct ph7_value +{ + ph7_real rVal; /* Real value */ + union{ + sxi64 iVal; /* Integer value */ + void *pOther; /* Other values (Object, Array, Resource, Namespace, etc.) */ + }x; + sxi32 iFlags; /* Control flags (see below) */ + ph7_vm *pVm; /* Virtual machine that own this instance */ + SyBlob sBlob; /* String values */ + sxu32 nIdx; /* Index number of this entry in the global object allocator */ +}; +/* Allowed value types. + */ +#define MEMOBJ_STRING 0x001 /* Memory value is a UTF-8 string */ +#define MEMOBJ_INT 0x002 /* Memory value is an integer */ +#define MEMOBJ_REAL 0x004 /* Memory value is a real number */ +#define MEMOBJ_BOOL 0x008 /* Memory value is a boolean */ +#define MEMOBJ_NULL 0x020 /* Memory value is NULL */ +#define MEMOBJ_HASHMAP 0x040 /* Memory value is a hashmap aka 'array' in the PHP jargon */ +#define MEMOBJ_OBJ 0x080 /* Memory value is an object [i.e: class instance] */ +#define MEMOBJ_RES 0x100 /* Memory value is a resource [User private data] */ +#define MEMOBJ_REFERENCE 0x400 /* Memory value hold a reference (64-bit index) of another ph7_value */ +/* Mask of all known types */ +#define MEMOBJ_ALL (MEMOBJ_STRING|MEMOBJ_INT|MEMOBJ_REAL|MEMOBJ_BOOL|MEMOBJ_NULL|MEMOBJ_HASHMAP|MEMOBJ_OBJ|MEMOBJ_RES) +/* Scalar variables + * According to the PHP language reference manual + * Scalar variables are those containing an integer, float, string or boolean. + * Types array, object and resource are not scalar. + */ +#define MEMOBJ_SCALAR (MEMOBJ_STRING|MEMOBJ_INT|MEMOBJ_REAL|MEMOBJ_BOOL|MEMOBJ_NULL) +#define MEMOBJ_AUX (MEMOBJ_REFERENCE) +/* + * The following macro clear the current ph7_value type and replace + * it with the given one. + */ +#define MemObjSetType(OBJ,TYPE) ((OBJ)->iFlags = ((OBJ)->iFlags&~MEMOBJ_ALL)|TYPE) +/* ph7_value cast method signature */ +typedef sxi32 (*ProcMemObjCast)(ph7_value *); +/* Forward reference */ +typedef struct ph7_output_consumer ph7_output_consumer; +typedef struct ph7_user_func ph7_user_func; +typedef struct ph7_conf ph7_conf; +/* + * An instance of the following structure store the default VM output + * consumer and it's private data. + * Client-programs can register their own output consumer callback + * via the [PH7_VM_CONFIG_OUTPUT] configuration directive. + * Please refer to the official documentation for more information + * on how to register an output consumer callback. + */ +struct ph7_output_consumer +{ + ProcConsumer xConsumer; /* VM output consumer routine */ + void *pUserData; /* Third argument to xConsumer() */ + ProcConsumer xDef; /* Default output consumer routine */ + void *pDefData; /* Third argument to xDef() */ +}; +/* + * PH7 engine [i.e: ph7 instance] configuration is stored in + * an instance of the following structure. + * Please refer to the official documentation for more information + * on how to configure your ph7 engine instance. + */ +struct ph7_conf +{ + ProcConsumer xErr; /* Compile-time error consumer callback */ + void *pErrData; /* Third argument to xErr() */ + SyBlob sErrConsumer; /* Default error consumer */ +}; +/* + * Signature of the C function responsible of expanding constant values. + */ +typedef void (*ProcConstant)(ph7_value *,void *); +/* + * Each registered constant [i.e: __TIME__, __DATE__, PHP_OS, INT_MAX, etc.] is stored + * in an instance of the following structure. + * Please refer to the official documentation for more information + * on how to create/install foreign constants. + */ +typedef struct ph7_constant ph7_constant; +struct ph7_constant +{ + SyString sName; /* Constant name */ + ProcConstant xExpand; /* Function responsible of expanding constant value */ + void *pUserData; /* Last argument to xExpand() */ +}; +typedef struct ph7_aux_data ph7_aux_data; +/* + * Auxiliary data associated with each foreign function is stored + * in a stack of the following structure. + * Note that automatic tracked chunks are also stored in an instance + * of this structure. + */ +struct ph7_aux_data +{ + void *pAuxData; /* Aux data */ +}; +/* Foreign functions signature */ +typedef int (*ProchHostFunction)(ph7_context *,int,ph7_value **); +/* + * Each installed foreign function is recored in an instance of the following + * structure. + * Please refer to the official documentation for more information on how + * to create/install foreign functions. + */ +struct ph7_user_func +{ + ph7_vm *pVm; /* VM that own this instance */ + SyString sName; /* Foreign function name */ + ProchHostFunction xFunc; /* Implementation of the foreign function */ + void *pUserData; /* User private data [Refer to the official documentation for more information]*/ + SySet aAux; /* Stack of auxiliary data [Refer to the official documentation for more information]*/ +}; +/* + * The 'context' argument for an installable function. A pointer to an + * instance of this structure is the first argument to the routines used + * implement the foreign functions. + */ +struct ph7_context +{ + ph7_user_func *pFunc; /* Function information. */ + ph7_value *pRet; /* Return value is stored here. */ + SySet sVar; /* Container of dynamically allocated ph7_values + * [i.e: Garbage collection purposes.] + */ + SySet sChunk; /* Track dynamically allocated chunks [ph7_aux_data instance]. + * [i.e: Garbage collection purposes.] + */ + ph7_vm *pVm; /* Virtual machine that own this context */ + sxi32 iFlags; /* Call flags */ +}; +/* + * Each hashmap entry [i.e: array(4,5,6)] is recorded in an instance + * of the following structure. + */ +struct ph7_hashmap_node +{ + ph7_hashmap *pMap; /* Hashmap that own this instance */ + sxi32 iType; /* Node type */ + union{ + sxi64 iKey; /* Int key */ + SyBlob sKey; /* Blob key */ + }xKey; + sxi32 iFlags; /* Control flags */ + sxu32 nHash; /* Key hash value */ + sxu32 nValIdx; /* Value stored in this node */ + ph7_hashmap_node *pNext,*pPrev; /* Link to other entries [i.e: linear traversal] */ + ph7_hashmap_node *pNextCollide,*pPrevCollide; /* Collision chain */ +}; +/* + * Each active hashmap aka array in the PHP jargon is represented + * by an instance of the following structure. + */ +struct ph7_hashmap +{ + ph7_vm *pVm; /* VM that own this instance */ + ph7_hashmap_node **apBucket; /* Hash bucket */ + ph7_hashmap_node *pFirst; /* First inserted entry */ + ph7_hashmap_node *pLast; /* Last inserted entry */ + ph7_hashmap_node *pCur; /* Current entry */ + sxu32 nSize; /* Bucket size */ + sxu32 nEntry; /* Total number of inserted entries */ + sxu32 (*xIntHash)(sxi64); /* Hash function for int_keys */ + sxu32 (*xBlobHash)(const void *,sxu32); /* Hash function for blob_keys */ + sxi64 iNextIdx; /* Next available automatically assigned index */ + sxi32 iRef; /* Reference count */ +}; +/* An instance of the following structure is the context + * for the FOREACH_STEP/FOREACH_INIT VM instructions. + * Those instructions are used to implement the 'foreach' + * statement. + * This structure is made available to these instructions + * as the P3 operand. + */ +struct ph7_foreach_info +{ + SyString sKey; /* Key name. Empty otherwise*/ + SyString sValue; /* Value name */ + sxi32 iFlags; /* Control flags */ + SySet aStep; /* Stack of steps [i.e: ph7_foreach_step instance] */ +}; +struct ph7_foreach_step +{ + sxi32 iFlags; /* Control flags (see below) */ + /* Iterate on those values */ + union { + ph7_hashmap *pMap; /* Hashmap [i.e: array in the PHP jargon] iteration + * Ex: foreach(array(1,2,3) as $key=>$value){} + */ + ph7_class_instance *pThis; /* Class instance [i.e: object] iteration */ + }xIter; +}; +/* Foreach step control flags */ +#define PH7_4EACH_STEP_HASHMAP 0x001 /* Hashmap iteration */ +#define PH7_4EACH_STEP_OBJECT 0x002 /* Object iteration */ +#define PH7_4EACH_STEP_KEY 0x004 /* Make Key available */ +#define PH7_4EACH_STEP_REF 0x008 /* Pass value by reference not copy */ +/* + * Each PH7 engine is identified by an instance of the following structure. + * Please refer to the official documentation for more information + * on how to configure your PH7 engine instance. + */ +struct ph7 +{ + SyMemBackend sAllocator; /* Low level memory allocation subsystem */ + const ph7_vfs *pVfs; /* Underlying Virtual File System */ + ph7_conf xConf; /* Configuration */ +#if defined(PH7_ENABLE_THREADS) + const SyMutexMethods *pMethods; /* Mutex methods */ + SyMutex *pMutex; /* Per-engine mutex */ +#endif + ph7_vm *pVms; /* List of active VM */ + sxi32 iVm; /* Total number of active VM */ + ph7 *pNext,*pPrev; /* List of active engines */ + sxu32 nMagic; /* Sanity check against misuse */ +}; +/* Code generation data structures */ +typedef sxi32 (*ProcErrorGen)(void *,sxi32,sxu32,const char *,...); +typedef struct ph7_expr_node ph7_expr_node; +typedef struct ph7_expr_op ph7_expr_op; +typedef struct ph7_gen_state ph7_gen_state; +typedef struct GenBlock GenBlock; +typedef sxi32 (*ProcLangConstruct)(ph7_gen_state *); +typedef sxi32 (*ProcNodeConstruct)(ph7_gen_state *,sxi32); +/* + * Each supported operator [i.e: +, -, ==, *, %, >>, >=, new, etc.] is represented + * by an instance of the following structure. + * The PH7 parser does not use any external tools and is 100% handcoded. + * That is, the PH7 parser is thread-safe ,full reentrant, produce consistant + * compile-time errrors and at least 7 times faster than the standard PHP parser. + */ +struct ph7_expr_op +{ + SyString sOp; /* String representation of the operator [i.e: "+","*","=="...] */ + sxi32 iOp; /* Operator ID */ + sxi32 iPrec; /* Operator precedence: 1 == Highest */ + sxi32 iAssoc; /* Operator associativity (either left,right or non-associative) */ + sxi32 iVmOp; /* VM OP code for this operator [i.e: PH7_OP_EQ,PH7_OP_LT,PH7_OP_MUL...]*/ +}; +/* + * Each expression node is parsed out and recorded + * in an instance of the following structure. + */ +struct ph7_expr_node +{ + const ph7_expr_op *pOp; /* Operator ID or NULL if literal, constant, variable, function or class method call */ + ph7_expr_node *pLeft; /* Left expression tree */ + ph7_expr_node *pRight; /* Right expression tree */ + SyToken *pStart; /* Stream of tokens that belong to this node */ + SyToken *pEnd; /* End of token stream */ + sxi32 iFlags; /* Node construct flags */ + ProcNodeConstruct xCode; /* C routine responsible of compiling this node */ + SySet aNodeArgs; /* Node arguments. Only used by postfix operators [i.e: function call]*/ + ph7_expr_node *pCond; /* Condition: Only used by the ternary operator '?:' */ +}; +/* Node Construct flags */ +#define EXPR_NODE_PRE_INCR 0x01 /* Pre-icrement/decrement [i.e: ++$i,--$j] node */ +/* + * A block of instructions is recorded in an instance of the following structure. + * This structure is used only during compile-time and have no meaning + * during bytecode execution. + */ +struct GenBlock +{ + ph7_gen_state *pGen; /* State of the code generator */ + GenBlock *pParent; /* Upper block or NULL if global */ + sxu32 nFirstInstr; /* First instruction to execute */ + sxi32 iFlags; /* Block control flags (see below) */ + SySet aJumpFix; /* Jump fixup (JumpFixup instance) */ + void *pUserData; /* Upper layer private data */ + /* The following two fields are used only when compiling + * the 'do..while()' language construct. + */ + sxu8 bPostContinue; /* TRUE when compiling the do..while() statement */ + SySet aPostContFix; /* Post-continue jump fix */ +}; +/* + * Code generator state is remembered in an instance of the following + * structure. We put the information in this structure and pass around + * a pointer to this structure, rather than pass around all of the + * information separately. This helps reduce the number of arguments + * to generator functions. + * This structure is used only during compile-time and have no meaning + * during bytecode execution. + */ +struct ph7_gen_state +{ + ph7_vm *pVm; /* VM that own this instance */ + SyHash hLiteral; /* Constant string Literals table */ + SyHash hNumLiteral; /* Numeric literals table */ + SyHash hVar; /* Collected variable hashtable */ + GenBlock *pCurrent; /* Current processed block */ + GenBlock sGlobal; /* Global block */ + ProcConsumer xErr; /* Error consumer callback */ + void *pErrData; /* Third argument to xErr() */ + SySet aLabel; /* Label table */ + SySet aGoto; /* Gotos table */ + SyBlob sWorker; /* General purpose working buffer */ + SyBlob sErrBuf; /* Error buffer */ + SyToken *pIn; /* Current processed token */ + SyToken *pEnd; /* Last token in the stream */ + sxu32 nErr; /* Total number of compilation error */ + SyToken *pRawIn; /* Current processed raw token */ + SyToken *pRawEnd; /* Last raw token in the stream */ + SySet *pTokenSet; /* Token containers */ +}; +/* Forward references */ +typedef struct ph7_vm_func_closure_env ph7_vm_func_closure_env; +typedef struct ph7_vm_func_static_var ph7_vm_func_static_var; +typedef struct ph7_vm_func_arg ph7_vm_func_arg; +typedef struct ph7_vm_func ph7_vm_func; +typedef struct VmFrame VmFrame; +/* + * Each collected function argument is recorded in an instance + * of the following structure. + * Note that as an extension, PH7 implements full type hinting + * which mean that any function can have it's own signature. + * Example: + * function foo(int $a,string $b,float $c,ClassInstance $d){} + * This is how the powerful function overloading mechanism is + * implemented. + * Note that as an extension, PH7 allow function arguments to have + * any complex default value associated with them unlike the standard + * PHP engine. + * Example: + * function foo(int $a = rand() & 1023){} + * now, when foo is called without arguments [i.e: foo()] the + * $a variable (first parameter) will be set to a random number + * between 0 and 1023 inclusive. + * Refer to the official documentation for more information on this + * mechanism and other extension introduced by the PH7 engine. + */ +struct ph7_vm_func_arg +{ + SyString sName; /* Argument name */ + SySet aByteCode; /* Compiled default value associated with this argument */ + sxu32 nType; /* Type of this argument [i.e: array, int, string, float, object, etc.] */ + SyString sClass; /* Class name if the argument expect a class instance [i.e: function foo(BaseClass $bar){} ] */ + sxi32 iFlags; /* Configuration flags */ +}; +/* + * Each static variable is parsed out and remembered in an instance + * of the following structure. + * Note that as an extension, PH7 allow static variable have + * any complex default value associated with them unlike the standard + * PHP engine. + * Example: + * static $rand_str = 'PH7'.rand_str(3); // Concatenate 'PH7' with + * // a random three characters(English alphabet) + * var_dump($rand_str); + * //You should see something like this + * string(6 'PH7awt'); + */ +struct ph7_vm_func_static_var +{ + SyString sName; /* Static variable name */ + SySet aByteCode; /* Compiled initialization expression */ + sxu32 nIdx; /* Object index in the global memory object container */ +}; +/* + * Each imported variable from the outside closure environnment is recoded + * in an instance of the following structure. + */ +struct ph7_vm_func_closure_env +{ + SyString sName; /* Imported variable name */ + int iFlags; /* Control flags */ + ph7_value sValue; /* Imported variable value */ + sxu32 nIdx; /* Reference to the bounded variable if passed by reference + *[Example: + * $x = 1; + * $closure = function() use (&$x) { ++$x; } + * $closure(); + *] + */ +}; +/* Function configuration flags */ +#define VM_FUNC_ARG_BY_REF 0x001 /* Argument passed by reference */ +#define VM_FUNC_ARG_HAS_DEF 0x002 /* Argument has default value associated with it */ +#define VM_FUNC_REF_RETURN 0x004 /* Return by reference */ +#define VM_FUNC_CLASS_METHOD 0x008 /* VM function is in fact a class method */ +#define VM_FUNC_CLOSURE 0x010 /* VM function is a closure */ +#define VM_FUNC_ARG_IGNORE 0x020 /* Do not install argument in the current frame */ +/* + * Each user defined function is parsed out and stored in an instance + * of the following structure. + * PH7 introduced some powerfull extensions to the PHP 5 programming + * language like function overloading, type hinting, complex default + * arguments values and many more. + * Please refer to the official documentation for more information. + */ +struct ph7_vm_func +{ + SySet aArgs; /* Expected arguments (ph7_vm_func_arg instance) */ + SySet aStatic; /* Static variable (ph7_vm_func_static_var instance) */ + SyString sName; /* Function name */ + SySet aByteCode; /* Compiled function body */ + SySet aClosureEnv; /* Closure environment (ph7_vm_func_closure_env instace) */ + sxi32 iFlags; /* VM function configuration */ + SyString sSignature; /* Function signature used to implement function overloading + * (Refer to the official docuemntation for more information + * on this powerfull feature) + */ + void *pUserData; /* Upper layer private data associated with this instance */ + ph7_vm_func *pNextName; /* Next VM function with the same name as this one */ +}; +/* Forward reference */ +typedef struct ph7_builtin_constant ph7_builtin_constant; +typedef struct ph7_builtin_func ph7_builtin_func; +/* + * Each built-in foreign function (C function) is stored in an + * instance of the following structure. + * Please refer to the official documentation for more information + * on how to create/install foreign functions. + */ +struct ph7_builtin_func +{ + const char *zName; /* Function name [i.e: strlen(), rand(), array_merge(), etc.]*/ + ProchHostFunction xFunc; /* C routine performing the computation */ +}; +/* + * Each built-in foreign constant is stored in an instance + * of the following structure. + * Please refer to the official documentation for more information + * on how to create/install foreign constants. + */ +struct ph7_builtin_constant +{ + const char *zName; /* Constant name */ + ProcConstant xExpand; /* C routine responsible of expanding constant value*/ +}; +/* Forward reference */ +typedef struct ph7_class_method ph7_class_method; +typedef struct ph7_class_attr ph7_class_attr; +/* + * Each class is parsed out and stored in an instance of the following structure. + * PH7 introduced powerfull extensions to the PHP 5 OO subsystems. + * Please refer to the official documentation for more information. + */ +struct ph7_class +{ + ph7_class *pBase; /* Base class if any */ + SyHash hDerived; /* Derived [child] classes */ + SyString sName; /* Class full qualified name */ + sxi32 iFlags; /* Class configuration flags [i.e: final, interface, abstract, etc.] */ + SyHash hAttr; /* Class attributes [i.e: variables and constants] */ + SyHash hMethod; /* Class methods */ + sxu32 nLine; /* Line number on which this class was declared */ + SySet aInterface; /* Implemented interface container */ + ph7_class *pNextName; /* Next class [interface, abstract, etc.] with the same name */ +}; +/* Class configuration flags */ +#define PH7_CLASS_FINAL 0x001 /* Class is final [cannot be extended] */ +#define PH7_CLASS_INTERFACE 0x002 /* Class is interface */ +#define PH7_CLASS_ABSTRACT 0x004 /* Class is abstract */ +/* Class attribute/methods/constants protection levels */ +#define PH7_CLASS_PROT_PUBLIC 1 /* public */ +#define PH7_CLASS_PROT_PROTECTED 2 /* protected */ +#define PH7_CLASS_PROT_PRIVATE 3 /* private */ +/* + * each class attribute (variable, constants) is parsed out and stored + * in an instance of the following structure. + */ +struct ph7_class_attr +{ + SyString sName; /* Atrribute name */ + sxi32 iFlags; /* Attribute configuration [i.e: static, variable, constant, etc.] */ + sxi32 iProtection; /* Protection level [i.e: public, private, protected] */ + SySet aByteCode; /* Compiled attribute body */ + sxu32 nIdx; /* Attribute index */ + sxu32 nLine; /* Line number on which this attribute was defined */ +}; +/* Attribute configuration */ +#define PH7_CLASS_ATTR_STATIC 0x001 /* Static attribute */ +#define PH7_CLASS_ATTR_CONSTANT 0x002 /* Constant attribute */ +#define PH7_CLASS_ATTR_ABSTRACT 0x004 /* Abstract method */ +#define PH7_CLASS_ATTR_FINAL 0x008 /* Final method */ +/* + * Each class method is parsed out and stored in an instance of the following + * structure. + * PH7 introduced some powerfull extensions to the PHP 5 programming + * language like function overloading,type hinting,complex default + * arguments and many more. + * Please refer to the official documentation for more information. + */ +struct ph7_class_method +{ + ph7_vm_func sFunc; /* Compiled method body */ + SyString sVmName; /* Automatically generated name assigned to this method. + * Typically this is "[class_name__method_name@random_string]" + */ + sxi32 iProtection; /* Protection level [i.e: public,private,protected] */ + sxi32 iFlags; /* Methods configuration */ + sxi32 iCloneDepth; /* Clone depth [Only used by the magic method __clone ] */ + sxu32 nLine; /* Line on which this method was defined */ +}; +/* + * Each active object (class instance) is represented by an instance of + * the following structure. + */ +struct ph7_class_instance +{ + ph7_vm *pVm; /* VM that own this instance */ + ph7_class *pClass; /* Object is an instance of this class */ + SyHash hAttr; /* Hashtable of active class members */ + sxi32 iRef; /* Reference count */ + sxi32 iFlags; /* Control flags */ +}; +/* + * A single instruction of the virtual machine has an opcode + * and as many as three operands. + * Each VM instruction resulting from compiling a PHP script + * is stored in an instance of the following structure. + */ +typedef struct VmInstr VmInstr; +struct VmInstr +{ + sxu8 iOp; /* Operation to preform */ + sxi32 iP1; /* First operand */ + sxu32 iP2; /* Second operand (Often the jump destination) */ + void *p3; /* Third operand (Often Upper layer private data) */ +}; +/* Each active class instance attribute is represented by an instance + * of the following structure. + */ +typedef struct VmClassAttr VmClassAttr; +struct VmClassAttr +{ + ph7_class_attr *pAttr; /* Class attribute */ + sxu32 nIdx; /* Memory object index */ +}; + /* Forward reference */ +typedef struct VmRefObj VmRefObj; +/* + * Each catch [i.e catch(Exception $e){ } ] block is parsed out and stored + * in an instance of the following structure. + */ +typedef struct ph7_exception_block ph7_exception_block; +typedef struct ph7_exception ph7_exception; +struct ph7_exception_block +{ + SyString sClass; /* Exception class name [i.e: Exception,MyException...] */ + SyString sThis; /* Instance name [i.e: $e..] */ + SySet sByteCode; /* Block compiled instructions */ +}; +/* + * Context for the exception mechanism. + */ +struct ph7_exception +{ + ph7_vm *pVm; /* VM that own this exception */ + SySet sEntry; /* Compiled 'catch' blocks (ph7_exception_block instance) + * container. + */ + VmFrame *pFrame; /* Frame that trigger the exception */ +}; +/* Forward reference */ +typedef struct ph7_case_expr ph7_case_expr; +typedef struct ph7_switch ph7_switch; +/* + * Each compiled case block in a swicth statement is compiled + * and stored in an instance of the following structure. + */ +struct ph7_case_expr +{ + SySet aByteCode; /* Compiled body of the case block */ + sxu32 nStart; /* First instruction to execute */ +}; +/* + * Each compiled switch statement is parsed out and stored + * in an instance of the following structure. + */ +struct ph7_switch +{ + SySet aCaseExpr; /* Compile case block */ + sxu32 nOut; /* First instruction to execute after this statement */ + sxu32 nDefault; /* First instruction to execute in the default block */ +}; +/* Assertion flags */ +#define PH7_ASSERT_DISABLE 0x01 /* Disable assertion */ +#define PH7_ASSERT_WARNING 0x02 /* Issue a warning for each failed assertion */ +#define PH7_ASSERT_BAIL 0x04 /* Terminate execution on failed assertions */ +#define PH7_ASSERT_QUIET_EVAL 0x08 /* Not used */ +#define PH7_ASSERT_CALLBACK 0x10 /* Callback to call on failed assertions */ +/* + * error_log() consumer function signature. + * Refer to the [PH7_VM_CONFIG_ERR_LOG_HANDLER] configuration directive + * for more information on how to register an error_log consumer(). + */ +typedef void (*ProcErrLog)(const char *,int,const char *,const char *); +/* + * An instance of the following structure hold the bytecode instructions + * resulting from compiling a PHP script. + * This structure contains the complete state of the virtual machine. + */ +struct ph7_vm +{ + SyMemBackend sAllocator; /* Memory backend */ +#if defined(PH7_ENABLE_THREADS) + SyMutex *pMutex; /* Recursive mutex associated with VM. */ +#endif + ph7 *pEngine; /* Interpreter that own this VM */ + SySet aByteCode; /* Default bytecode container */ + SySet *pByteContainer; /* Current bytecode container */ + VmFrame *pFrame; /* Stack of active frames */ + SyPRNGCtx sPrng; /* PRNG context */ + SySet aMemObj; /* Object allocation table */ + SySet aLitObj; /* Literals allocation table */ + ph7_value *aOps; /* Operand stack */ + SySet aFreeObj; /* Stack of free memory objects */ + SyHash hClass; /* Compiled classes container */ + SyHash hConstant; /* Host-application and user defined constants container */ + SyHash hHostFunction; /* Host-application installable functions */ + SyHash hFunction; /* Compiled functions */ + SyHash hSuper; /* Superglobals hashtable */ + SyHash hPDO; /* PDO installed drivers */ + SyBlob sConsumer; /* Default VM consumer [i.e Redirect all VM output to this blob] */ + SyBlob sWorker; /* General purpose working buffer */ + SyBlob sArgv; /* $argv[] collector [refer to the [getopt()] implementation for more information] */ + SySet aFiles; /* Stack of processed files */ + SySet aPaths; /* Set of import paths */ + SySet aIncluded; /* Set of included files */ + SySet aOB; /* Stackable output buffers */ + SySet aShutdown; /* Stack of shutdown user callbacks */ + SySet aException; /* Stack of loaded exception */ + SySet aIOstream; /* Installed IO stream container */ + const ph7_io_stream *pDefStream; /* Default IO stream [i.e: typically this is the 'file://' stream] */ + ph7_value sExec; /* Compiled script return value [Can be extracted via the PH7_VM_CONFIG_EXEC_VALUE directive]*/ + ph7_value aExceptionCB[2]; /* Installed exception handler callbacks via [set_exception_handler()] */ + ph7_value aErrCB[2]; /* Installed error handler callback via [set_error_handler()] */ + void *pStdin; /* STDIN IO stream */ + void *pStdout; /* STDOUT IO stream */ + void *pStderr; /* STDERR IO stream */ + int bErrReport; /* TRUE to report all runtime Error/Warning/Notice */ + int nRecursionDepth; /* Current recursion depth */ + int nMaxDepth; /* Maximum allowed recusion depth */ + int nObDepth; /* OB depth */ + int nExceptDepth; /* Exception depth */ + int closure_cnt; /* Loaded closures counter */ + int json_rc; /* JSON return status [refer to json_encode()/json_decode()]*/ + sxu32 unique_id; /* Random number used to generate unique ID [refer to uniqid() for more info]*/ + ProcErrLog xErrLog; /* error_log() consumer [refer to PH7_VM_CONFIG_ERR_LOG_HANDLER] */ + sxu32 nOutputLen; /* Total number of generated output */ + ph7_output_consumer sVmConsumer; /* Registered output consumer callback */ + int iAssertFlags; /* Assertion flags */ + ph7_value sAssertCallback; /* Callback to call on failed assertions */ + VmRefObj **apRefObj; /* Hashtable of referenced object */ + VmRefObj *pRefList; /* List of referenced memory objects */ + sxu32 nRefSize; /* apRefObj[] size */ + sxu32 nRefUsed; /* Total entries in apRefObj[] */ + SySet aSelf; /* 'self' stack used for static member access [i.e: self::MyConstant] */ + ph7_hashmap *pGlobal; /* $GLOBALS hashmap */ + sxu32 nGlobalIdx; /* $GLOBALS index */ + sxi32 iExitStatus; /* Script exit status */ + ph7_gen_state sCodeGen; /* Code generator module */ + ph7_vm *pNext,*pPrev; /* List of active VM's */ + sxu32 nMagic; /* Sanity check against misuse */ +}; +/* + * Allowed value for ph7_vm.nMagic + */ +#define PH7_VM_INIT 0xFADE9512 /* VM correctly initialized */ +#define PH7_VM_RUN 0xEA271285 /* VM ready to execute PH7 bytecode */ +#define PH7_VM_EXEC 0xCAFE2DAD /* VM executing PH7 bytecode */ +#define PH7_VM_STALE 0xBAD1DEAD /* Stale VM */ +/* + * Error codes according to the PHP language reference manual. + */ +enum iErrCode +{ + E_ERROR = 1, /* Fatal run-time errors. These indicate errors that can not be recovered + * from, such as a memory allocation problem. Execution of the script is + * halted. + * The only fatal error under PH7 is an out-of-memory. All others erros + * even a call to undefined function will not halt script execution. + */ + E_WARNING = 2, /* Run-time warnings (non-fatal errors). Execution of the script is not halted. */ + E_PARSE = 4, /* Compile-time parse errors. Parse errors should only be generated by the parser.*/ + E_NOTICE = 8, /* Run-time notices. Indicate that the script encountered something that could + * indicate an error, but could also happen in the normal course of running a script. + */ + E_CORE_WARNING = 16, /* Fatal errors that occur during PHP's initial startup. This is like an E_ERROR + * except it is generated by the core of PHP. + */ + E_USER_ERROR = 256, /* User-generated error message.*/ + E_USER_WARNING = 512, /* User-generated warning message.*/ + E_USER_NOTICE = 1024, /* User-generated notice message.*/ + E_STRICT = 2048, /* Enable to have PHP suggest changes to your code which will ensure the best interoperability + * and forward compatibility of your code. + */ + E_RECOVERABLE_ERROR = 4096, /* Catchable fatal error. It indicates that a probably dangerous error occured, but did not + * leave the Engine in an unstable state. If the error is not caught by a user defined handle + * the application aborts as it was an E_ERROR. + */ + E_DEPRECATED = 8192, /* Run-time notices. Enable this to receive warnings about code that will not + * work in future versions. + */ + E_USER_DEPRECATED = 16384, /* User-generated warning message. */ + E_ALL = 32767 /* All errors and warnings */ +}; +/* + * Each VM instruction resulting from compiling a PHP script is represented + * by one of the following OP codes. + * The program consists of a linear sequence of operations. Each operation + * has an opcode and 3 operands.Operands P1 is an integer. + * Operand P2 is an unsigned integer and operand P3 is a memory address. + * Few opcodes use all 3 operands. + */ +enum ph7_vm_op { + PH7_OP_DONE = 1, /* Done */ + PH7_OP_HALT, /* Halt */ + PH7_OP_LOAD, /* Load memory object */ + PH7_OP_LOADC, /* Load constant */ + PH7_OP_LOAD_IDX, /* Load array entry */ + PH7_OP_LOAD_MAP, /* Load hashmap('array') */ + PH7_OP_LOAD_LIST, /* Load list */ + PH7_OP_LOAD_CLOSURE, /* Load closure */ + PH7_OP_NOOP, /* NOOP */ + PH7_OP_JMP, /* Unconditional jump */ + PH7_OP_JZ, /* Jump on zero (FALSE jump) */ + PH7_OP_JNZ, /* Jump on non-zero (TRUE jump) */ + PH7_OP_POP, /* Stack POP */ + PH7_OP_CAT, /* Concatenation */ + PH7_OP_CVT_INT, /* Integer cast */ + PH7_OP_CVT_STR, /* String cast */ + PH7_OP_CVT_REAL, /* Float cast */ + PH7_OP_CALL, /* Function call */ + PH7_OP_UMINUS, /* Unary minus '-'*/ + PH7_OP_UPLUS, /* Unary plus '+'*/ + PH7_OP_BITNOT, /* Bitwise not '~' */ + PH7_OP_LNOT, /* Logical not '!' */ + PH7_OP_MUL, /* Multiplication '*' */ + PH7_OP_DIV, /* Division '/' */ + PH7_OP_MOD, /* Modulus '%' */ + PH7_OP_ADD, /* Add '+' */ + PH7_OP_SUB, /* Sub '-' */ + PH7_OP_SHL, /* Left shift '<<' */ + PH7_OP_SHR, /* Right shift '>>' */ + PH7_OP_LT, /* Less than '<' */ + PH7_OP_LE, /* Less or equal '<=' */ + PH7_OP_GT, /* Greater than '>' */ + PH7_OP_GE, /* Greater or equal '>=' */ + PH7_OP_EQ, /* Equal '==' */ + PH7_OP_NEQ, /* Not equal '!=' */ + PH7_OP_TEQ, /* Type equal '===' */ + PH7_OP_TNE, /* Type not equal '!==' */ + PH7_OP_BAND, /* Bitwise and '&' */ + PH7_OP_BXOR, /* Bitwise xor '^' */ + PH7_OP_BOR, /* Bitwise or '|' */ + PH7_OP_LAND, /* Logical and '&&','and' */ + PH7_OP_LOR, /* Logical or '||','or' */ + PH7_OP_LXOR, /* Logical xor 'xor' */ + PH7_OP_STORE, /* Store Object */ + PH7_OP_STORE_IDX, /* Store indexed object */ + PH7_OP_STORE_IDX_REF,/* Store indexed object by reference */ + PH7_OP_PULL, /* Stack pull */ + PH7_OP_SWAP, /* Stack swap */ + PH7_OP_YIELD, /* Stack yield */ + PH7_OP_CVT_BOOL, /* Boolean cast */ + PH7_OP_CVT_NUMC, /* Numeric (integer,real or both) type cast */ + PH7_OP_INCR, /* Increment ++ */ + PH7_OP_DECR, /* Decrement -- */ + PH7_OP_SEQ, /* 'eq' String equal: Strict string comparison */ + PH7_OP_SNE, /* 'ne' String not equal: Strict string comparison */ + PH7_OP_NEW, /* new */ + PH7_OP_CLONE, /* clone */ + PH7_OP_ADD_STORE, /* Add and store '+=' */ + PH7_OP_SUB_STORE, /* Sub and store '-=' */ + PH7_OP_MUL_STORE, /* Mul and store '*=' */ + PH7_OP_DIV_STORE, /* Div and store '/=' */ + PH7_OP_MOD_STORE, /* Mod and store '%=' */ + PH7_OP_CAT_STORE, /* Cat and store '.=' */ + PH7_OP_SHL_STORE, /* Shift left and store '>>=' */ + PH7_OP_SHR_STORE, /* Shift right and store '<<=' */ + PH7_OP_BAND_STORE, /* Bitand and store '&=' */ + PH7_OP_BOR_STORE, /* Bitor and store '|=' */ + PH7_OP_BXOR_STORE, /* Bitxor and store '^=' */ + PH7_OP_CONSUME, /* Consume VM output */ + PH7_OP_LOAD_REF, /* Load reference */ + PH7_OP_STORE_REF, /* Store a reference to a variable*/ + PH7_OP_MEMBER, /* Class member run-time access */ + PH7_OP_UPLINK, /* Run-Time frame link */ + PH7_OP_CVT_NULL, /* NULL cast */ + PH7_OP_CVT_ARRAY, /* Array cast */ + PH7_OP_CVT_OBJ, /* Object cast */ + PH7_OP_FOREACH_INIT, /* For each init */ + PH7_OP_FOREACH_STEP, /* For each step */ + PH7_OP_IS_A, /* Instanceof */ + PH7_OP_LOAD_EXCEPTION,/* Load an exception */ + PH7_OP_POP_EXCEPTION, /* POP an exception */ + PH7_OP_THROW, /* Throw exception */ + PH7_OP_SWITCH, /* Switch operation */ + PH7_OP_ERR_CTRL /* Error control */ +}; +/* -- END-OF INSTRUCTIONS -- */ +/* + * Expression Operators ID. + */ +enum ph7_expr_id { + EXPR_OP_NEW = 1, /* new */ + EXPR_OP_CLONE, /* clone */ + EXPR_OP_ARROW, /* -> */ + EXPR_OP_DC, /* :: */ + EXPR_OP_SUBSCRIPT, /* []: Subscripting */ + EXPR_OP_FUNC_CALL, /* func_call() */ + EXPR_OP_INCR, /* ++ */ + EXPR_OP_DECR, /* -- */ + EXPR_OP_BITNOT, /* ~ */ + EXPR_OP_UMINUS, /* Unary minus */ + EXPR_OP_UPLUS, /* Unary plus */ + EXPR_OP_TYPECAST, /* Type cast [i.e: (int),(float),(string)...] */ + EXPR_OP_ALT, /* @ */ + EXPR_OP_INSTOF, /* instanceof */ + EXPR_OP_LOGNOT, /* logical not ! */ + EXPR_OP_MUL, /* Multiplication */ + EXPR_OP_DIV, /* division */ + EXPR_OP_MOD, /* Modulus */ + EXPR_OP_ADD, /* Addition */ + EXPR_OP_SUB, /* Substraction */ + EXPR_OP_DOT, /* Concatenation */ + EXPR_OP_SHL, /* Left shift */ + EXPR_OP_SHR, /* Right shift */ + EXPR_OP_LT, /* Less than */ + EXPR_OP_LE, /* Less equal */ + EXPR_OP_GT, /* Greater than */ + EXPR_OP_GE, /* Greater equal */ + EXPR_OP_EQ, /* Equal == */ + EXPR_OP_NE, /* Not equal != <> */ + EXPR_OP_TEQ, /* Type equal === */ + EXPR_OP_TNE, /* Type not equal !== */ + EXPR_OP_SEQ, /* String equal 'eq' */ + EXPR_OP_SNE, /* String not equal 'ne' */ + EXPR_OP_BAND, /* Biwise and '&' */ + EXPR_OP_REF, /* Reference operator '&' */ + EXPR_OP_XOR, /* bitwise xor '^' */ + EXPR_OP_BOR, /* bitwise or '|' */ + EXPR_OP_LAND, /* Logical and '&&','and' */ + EXPR_OP_LOR, /* Logical or '||','or'*/ + EXPR_OP_LXOR, /* Logical xor 'xor' */ + EXPR_OP_QUESTY, /* Ternary operator '?' */ + EXPR_OP_ASSIGN, /* Assignment '=' */ + EXPR_OP_ADD_ASSIGN, /* Combined operator: += */ + EXPR_OP_SUB_ASSIGN, /* Combined operator: -= */ + EXPR_OP_MUL_ASSIGN, /* Combined operator: *= */ + EXPR_OP_DIV_ASSIGN, /* Combined operator: /= */ + EXPR_OP_MOD_ASSIGN, /* Combined operator: %= */ + EXPR_OP_DOT_ASSIGN, /* Combined operator: .= */ + EXPR_OP_AND_ASSIGN, /* Combined operator: &= */ + EXPR_OP_OR_ASSIGN, /* Combined operator: |= */ + EXPR_OP_XOR_ASSIGN, /* Combined operator: ^= */ + EXPR_OP_SHL_ASSIGN, /* Combined operator: <<= */ + EXPR_OP_SHR_ASSIGN, /* Combined operator: >>= */ + EXPR_OP_COMMA /* Comma expression */ +}; +/* + * Very high level tokens. + */ +#define PH7_TOKEN_RAW 0x001 /* Raw text [i.e: HTML,XML...] */ +#define PH7_TOKEN_PHP 0x002 /* PHP chunk */ +/* + * Lexer token codes + * The following set of constants are the tokens recognized + * by the lexer when processing PHP input. + * Important: Token values MUST BE A POWER OF TWO. + */ +#define PH7_TK_INTEGER 0x0000001 /* Integer */ +#define PH7_TK_REAL 0x0000002 /* Real number */ +#define PH7_TK_NUM (PH7_TK_INTEGER|PH7_TK_REAL) /* Numeric token,either integer or real */ +#define PH7_TK_KEYWORD 0x0000004 /* Keyword [i.e: while,for,if,foreach...] */ +#define PH7_TK_ID 0x0000008 /* Alphanumeric or UTF-8 stream */ +#define PH7_TK_DOLLAR 0x0000010 /* '$' Dollar sign */ +#define PH7_TK_OP 0x0000020 /* Operator [i.e: +,*,/...] */ +#define PH7_TK_OCB 0x0000040 /* Open curly brace'{' */ +#define PH7_TK_CCB 0x0000080 /* Closing curly brace'}' */ +#define PH7_TK_NSSEP 0x0000100 /* Namespace separator '\' */ +#define PH7_TK_LPAREN 0x0000200 /* Left parenthesis '(' */ +#define PH7_TK_RPAREN 0x0000400 /* Right parenthesis ')' */ +#define PH7_TK_OSB 0x0000800 /* Open square bracket '[' */ +#define PH7_TK_CSB 0x0001000 /* Closing square bracket ']' */ +#define PH7_TK_DSTR 0x0002000 /* Double quoted string "$str" */ +#define PH7_TK_SSTR 0x0004000 /* Single quoted string 'str' */ +#define PH7_TK_HEREDOC 0x0008000 /* Heredoc <<< */ +#define PH7_TK_NOWDOC 0x0010000 /* Nowdoc <<< */ +#define PH7_TK_COMMA 0x0020000 /* Comma ',' */ +#define PH7_TK_SEMI 0x0040000 /* Semi-colon ";" */ +#define PH7_TK_BSTR 0x0080000 /* Backtick quoted string [i.e: Shell command `date`] */ +#define PH7_TK_COLON 0x0100000 /* single Colon ':' */ +#define PH7_TK_AMPER 0x0200000 /* Ampersand '&' */ +#define PH7_TK_EQUAL 0x0400000 /* Equal '=' */ +#define PH7_TK_ARRAY_OP 0x0800000 /* Array operator '=>' */ +#define PH7_TK_OTHER 0x1000000 /* Other symbols */ +/* + * PHP keyword. + * These words have special meaning in PHP. Some of them represent things which look like + * functions, some look like constants, and so on, but they're not, really: they are language constructs. + * You cannot use any of the following words as constants, class names, function or method names. + * Using them as variable names is generally OK, but could lead to confusion. + */ +#define PH7_TKWRD_EXTENDS 1 /* extends */ +#define PH7_TKWRD_ENDSWITCH 2 /* endswitch */ +#define PH7_TKWRD_SWITCH 3 /* switch */ +#define PH7_TKWRD_PRINT 4 /* print */ +#define PH7_TKWRD_INTERFACE 5 /* interface */ +#define PH7_TKWRD_ENDDEC 6 /* enddeclare */ +#define PH7_TKWRD_DECLARE 7 /* declare */ +/* The number '8' is reserved for PH7_TK_ID */ +#define PH7_TKWRD_REQONCE 9 /* require_once */ +#define PH7_TKWRD_REQUIRE 10 /* require */ +#define PH7_TKWRD_ELIF 0x4000000 /* elseif: MUST BE A POWER OF TWO */ +#define PH7_TKWRD_ELSE 0x8000000 /* else: MUST BE A POWER OF TWO */ +#define PH7_TKWRD_IF 13 /* if */ +#define PH7_TKWRD_FINAL 14 /* final */ +#define PH7_TKWRD_LIST 15 /* list */ +#define PH7_TKWRD_STATIC 16 /* static */ +#define PH7_TKWRD_CASE 17 /* case */ +#define PH7_TKWRD_SELF 18 /* self */ +#define PH7_TKWRD_FUNCTION 19 /* function */ +#define PH7_TKWRD_NAMESPACE 20 /* namespace */ +#define PH7_TKWRD_ENDIF 0x400000 /* endif: MUST BE A POWER OF TWO */ +#define PH7_TKWRD_CLONE 0x80 /* clone: MUST BE A POWER OF TWO */ +#define PH7_TKWRD_NEW 0x100 /* new: MUST BE A POWER OF TWO */ +#define PH7_TKWRD_CONST 22 /* const */ +#define PH7_TKWRD_THROW 23 /* throw */ +#define PH7_TKWRD_USE 24 /* use */ +#define PH7_TKWRD_ENDWHILE 0x800000 /* endwhile: MUST BE A POWER OF TWO */ +#define PH7_TKWRD_WHILE 26 /* while */ +#define PH7_TKWRD_EVAL 27 /* eval */ +#define PH7_TKWRD_VAR 28 /* var */ +#define PH7_TKWRD_ARRAY 0x200 /* array: MUST BE A POWER OF TWO */ +#define PH7_TKWRD_ABSTRACT 29 /* abstract */ +#define PH7_TKWRD_TRY 30 /* try */ +#define PH7_TKWRD_AND 0x400 /* and: MUST BE A POWER OF TWO */ +#define PH7_TKWRD_DEFAULT 31 /* default */ +#define PH7_TKWRD_CLASS 32 /* class */ +#define PH7_TKWRD_AS 33 /* as */ +#define PH7_TKWRD_CONTINUE 34 /* continue */ +#define PH7_TKWRD_EXIT 35 /* exit */ +#define PH7_TKWRD_DIE 36 /* die */ +#define PH7_TKWRD_ECHO 37 /* echo */ +#define PH7_TKWRD_GLOBAL 38 /* global */ +#define PH7_TKWRD_IMPLEMENTS 39 /* implements */ +#define PH7_TKWRD_INCONCE 40 /* include_once */ +#define PH7_TKWRD_INCLUDE 41 /* include */ +#define PH7_TKWRD_EMPTY 42 /* empty */ +#define PH7_TKWRD_INSTANCEOF 0x800 /* instanceof: MUST BE A POWER OF TWO */ +#define PH7_TKWRD_ISSET 43 /* isset */ +#define PH7_TKWRD_PARENT 44 /* parent */ +#define PH7_TKWRD_PRIVATE 45 /* private */ +#define PH7_TKWRD_ENDFOR 0x1000000 /* endfor: MUST BE A POWER OF TWO */ +#define PH7_TKWRD_END4EACH 0x2000000 /* endforeach: MUST BE A POWER OF TWO */ +#define PH7_TKWRD_FOR 48 /* for */ +#define PH7_TKWRD_FOREACH 49 /* foreach */ +#define PH7_TKWRD_OR 0x1000 /* or: MUST BE A POWER OF TWO */ +#define PH7_TKWRD_PROTECTED 50 /* protected */ +#define PH7_TKWRD_DO 51 /* do */ +#define PH7_TKWRD_PUBLIC 52 /* public */ +#define PH7_TKWRD_CATCH 53 /* catch */ +#define PH7_TKWRD_RETURN 54 /* return */ +#define PH7_TKWRD_UNSET 0x2000 /* unset: MUST BE A POWER OF TWO */ +#define PH7_TKWRD_XOR 0x4000 /* xor: MUST BE A POWER OF TWO */ +#define PH7_TKWRD_BREAK 55 /* break */ +#define PH7_TKWRD_GOTO 56 /* goto */ +#define PH7_TKWRD_BOOL 0x8000 /* bool: MUST BE A POWER OF TWO */ +#define PH7_TKWRD_INT 0x10000 /* int: MUST BE A POWER OF TWO */ +#define PH7_TKWRD_FLOAT 0x20000 /* float: MUST BE A POWER OF TWO */ +#define PH7_TKWRD_STRING 0x40000 /* string: MUST BE A POWER OF TWO */ +#define PH7_TKWRD_OBJECT 0x80000 /* object: MUST BE A POWER OF TWO */ +#define PH7_TKWRD_SEQ 0x100000 /* String string comparison operator: 'eq' equal MUST BE A POWER OF TWO */ +#define PH7_TKWRD_SNE 0x200000 /* String string comparison operator: 'ne' not equal MUST BE A POWER OF TWO */ +/* JSON encoding/decoding related definition */ +enum json_err_code{ + JSON_ERROR_NONE = 0, /* No error has occurred. */ + JSON_ERROR_DEPTH, /* The maximum stack depth has been exceeded. */ + JSON_ERROR_STATE_MISMATCH, /* Occurs with underflow or with the modes mismatch. */ + JSON_ERROR_CTRL_CHAR, /* Control character error, possibly incorrectly encoded. */ + JSON_ERROR_SYNTAX, /* Syntax error. */ + JSON_ERROR_UTF8 /* Malformed UTF-8 characters */ +}; +/* The following constants can be combined to form options for json_encode(). */ +#define JSON_HEX_TAG 0x01 /* All < and > are converted to \u003C and \u003E. */ +#define JSON_HEX_AMP 0x02 /* All &s are converted to \u0026. */ +#define JSON_HEX_APOS 0x04 /* All ' are converted to \u0027. */ +#define JSON_HEX_QUOT 0x08 /* All " are converted to \u0022. */ +#define JSON_FORCE_OBJECT 0x10 /* Outputs an object rather than an array */ +#define JSON_NUMERIC_CHECK 0x20 /* Encodes numeric strings as numbers. */ +#define JSON_BIGINT_AS_STRING 0x40 /* Not used */ +#define JSON_PRETTY_PRINT 0x80 /* Use whitespace in returned data to format it.*/ +#define JSON_UNESCAPED_SLASHES 0x100 /* Don't escape '/' */ +#define JSON_UNESCAPED_UNICODE 0x200 /* Not used */ +/* memobj.c function prototypes */ +PH7_PRIVATE sxi32 PH7_MemObjDump(SyBlob *pOut,ph7_value *pObj,int ShowType,int nTab,int nDepth,int isRef); +PH7_PRIVATE const char * PH7_MemObjTypeDump(ph7_value *pVal); +PH7_PRIVATE sxi32 PH7_MemObjAdd(ph7_value *pObj1,ph7_value *pObj2,int bAddStore); +PH7_PRIVATE sxi32 PH7_MemObjCmp(ph7_value *pObj1,ph7_value *pObj2,int bStrict,int iNest); +PH7_PRIVATE sxi32 PH7_MemObjInitFromString(ph7_vm *pVm,ph7_value *pObj,const SyString *pVal); +PH7_PRIVATE sxi32 PH7_MemObjInitFromArray(ph7_vm *pVm,ph7_value *pObj,ph7_hashmap *pArray); +#if 0 +/* Not used in the current release of the PH7 engine */ +PH7_PRIVATE sxi32 PH7_MemObjInitFromReal(ph7_vm *pVm,ph7_value *pObj,ph7_real rVal); +#endif +PH7_PRIVATE sxi32 PH7_MemObjInitFromInt(ph7_vm *pVm,ph7_value *pObj,sxi64 iVal); +PH7_PRIVATE sxi32 PH7_MemObjInitFromBool(ph7_vm *pVm,ph7_value *pObj,sxi32 iVal); +PH7_PRIVATE sxi32 PH7_MemObjInit(ph7_vm *pVm,ph7_value *pObj); +PH7_PRIVATE sxi32 PH7_MemObjStringAppend(ph7_value *pObj,const char *zData,sxu32 nLen); +#if 0 +/* Not used in the current release of the PH7 engine */ +PH7_PRIVATE sxi32 PH7_MemObjStringFormat(ph7_value *pObj,const char *zFormat,va_list ap); +#endif +PH7_PRIVATE sxi32 PH7_MemObjStore(ph7_value *pSrc,ph7_value *pDest); +PH7_PRIVATE sxi32 PH7_MemObjLoad(ph7_value *pSrc,ph7_value *pDest); +PH7_PRIVATE sxi32 PH7_MemObjRelease(ph7_value *pObj); +PH7_PRIVATE sxi32 PH7_MemObjToNumeric(ph7_value *pObj); +PH7_PRIVATE sxi32 PH7_MemObjTryInteger(ph7_value *pObj); +PH7_PRIVATE ProcMemObjCast PH7_MemObjCastMethod(sxi32 iFlags); +PH7_PRIVATE sxi32 PH7_MemObjIsNumeric(ph7_value *pObj); +PH7_PRIVATE sxi32 PH7_MemObjIsEmpty(ph7_value *pObj); +PH7_PRIVATE sxi32 PH7_MemObjToHashmap(ph7_value *pObj); +PH7_PRIVATE sxi32 PH7_MemObjToObject(ph7_value *pObj); +PH7_PRIVATE sxi32 PH7_MemObjToString(ph7_value *pObj); +PH7_PRIVATE sxi32 PH7_MemObjToNull(ph7_value *pObj); +PH7_PRIVATE sxi32 PH7_MemObjToReal(ph7_value *pObj); +PH7_PRIVATE sxi32 PH7_MemObjToInteger(ph7_value *pObj); +PH7_PRIVATE sxi32 PH7_MemObjToBool(ph7_value *pObj); +PH7_PRIVATE sxi64 PH7_TokenValueToInt64(SyString *pData); +/* lex.c function prototypes */ +PH7_PRIVATE sxi32 PH7_TokenizeRawText(const char *zInput,sxu32 nLen,SySet *pOut); +PH7_PRIVATE sxi32 PH7_TokenizePHP(const char *zInput,sxu32 nLen,sxu32 nLineStart,SySet *pOut); +/* vm.c function prototypes */ +PH7_PRIVATE void PH7_VmReleaseContextValue(ph7_context *pCtx,ph7_value *pValue); +PH7_PRIVATE sxi32 PH7_VmInitFuncState(ph7_vm *pVm,ph7_vm_func *pFunc,const char *zName,sxu32 nByte, + sxi32 iFlags,void *pUserData); +PH7_PRIVATE sxi32 PH7_VmInstallUserFunction(ph7_vm *pVm,ph7_vm_func *pFunc,SyString *pName); +PH7_PRIVATE sxi32 PH7_VmCreateClassInstanceFrame(ph7_vm *pVm,ph7_class_instance *pObj); +PH7_PRIVATE sxi32 PH7_VmRefObjRemove(ph7_vm *pVm,sxu32 nIdx,SyHashEntry *pEntry,ph7_hashmap_node *pMapEntry); +PH7_PRIVATE sxi32 PH7_VmRefObjInstall(ph7_vm *pVm,sxu32 nIdx,SyHashEntry *pEntry,ph7_hashmap_node *pMapEntry,sxi32 iFlags); +PH7_PRIVATE sxi32 PH7_VmPushFilePath(ph7_vm *pVm,const char *zPath,int nLen,sxu8 bMain,sxi32 *pNew); +PH7_PRIVATE ph7_class * PH7_VmExtractClass(ph7_vm *pVm,const char *zName,sxu32 nByte,sxi32 iLoadable,sxi32 iNest); +PH7_PRIVATE sxi32 PH7_VmRegisterConstant(ph7_vm *pVm,const SyString *pName,ProcConstant xExpand,void *pUserData); +PH7_PRIVATE sxi32 PH7_VmInstallForeignFunction(ph7_vm *pVm,const SyString *pName,ProchHostFunction xFunc,void *pUserData); +PH7_PRIVATE sxi32 PH7_VmInstallClass(ph7_vm *pVm,ph7_class *pClass); +PH7_PRIVATE sxi32 PH7_VmBlobConsumer(const void *pSrc,unsigned int nLen,void *pUserData); +PH7_PRIVATE ph7_value * PH7_ReserveMemObj(ph7_vm *pVm); +PH7_PRIVATE ph7_value * PH7_ReserveConstObj(ph7_vm *pVm,sxu32 *pIndex); +PH7_PRIVATE sxi32 PH7_VmOutputConsume(ph7_vm *pVm,SyString *pString); +PH7_PRIVATE sxi32 PH7_VmOutputConsumeAp(ph7_vm *pVm,const char *zFormat,va_list ap); +PH7_PRIVATE sxi32 PH7_VmThrowErrorAp(ph7_vm *pVm,SyString *pFuncName,sxi32 iErr,const char *zFormat,va_list ap); +PH7_PRIVATE sxi32 PH7_VmThrowError(ph7_vm *pVm,SyString *pFuncName,sxi32 iErr,const char *zMessage); +PH7_PRIVATE void PH7_VmExpandConstantValue(ph7_value *pVal,void *pUserData); +PH7_PRIVATE sxi32 PH7_VmDump(ph7_vm *pVm,ProcConsumer xConsumer,void *pUserData); +PH7_PRIVATE sxi32 PH7_VmInit(ph7_vm *pVm,ph7 *pEngine); +PH7_PRIVATE sxi32 PH7_VmConfigure(ph7_vm *pVm,sxi32 nOp,va_list ap); +PH7_PRIVATE sxi32 PH7_VmByteCodeExec(ph7_vm *pVm); +PH7_PRIVATE sxi32 PH7_VmRelease(ph7_vm *pVm); +PH7_PRIVATE sxi32 PH7_VmReset(ph7_vm *pVm); +PH7_PRIVATE sxi32 PH7_VmMakeReady(ph7_vm *pVm); +PH7_PRIVATE sxu32 PH7_VmInstrLength(ph7_vm *pVm); +PH7_PRIVATE VmInstr * PH7_VmPopInstr(ph7_vm *pVm); +PH7_PRIVATE VmInstr * PH7_VmPeekInstr(ph7_vm *pVm); +PH7_PRIVATE VmInstr * PH7_VmPeekNextInstr(ph7_vm *pVm); +PH7_PRIVATE VmInstr *PH7_VmGetInstr(ph7_vm *pVm,sxu32 nIndex); +PH7_PRIVATE SySet * PH7_VmGetByteCodeContainer(ph7_vm *pVm); +PH7_PRIVATE sxi32 PH7_VmSetByteCodeContainer(ph7_vm *pVm,SySet *pContainer); +PH7_PRIVATE sxi32 PH7_VmEmitInstr(ph7_vm *pVm,sxi32 iOp,sxi32 iP1,sxu32 iP2,void *p3,sxu32 *pIndex); +PH7_PRIVATE sxu32 PH7_VmRandomNum(ph7_vm *pVm); +PH7_PRIVATE sxi32 PH7_VmCallClassMethod(ph7_vm *pVm,ph7_class_instance *pThis,ph7_class_method *pMethod, + ph7_value *pResult,int nArg,ph7_value **apArg); +PH7_PRIVATE sxi32 PH7_VmCallUserFunction(ph7_vm *pVm,ph7_value *pFunc,int nArg,ph7_value **apArg,ph7_value *pResult); +PH7_PRIVATE sxi32 PH7_VmCallUserFunctionAp(ph7_vm *pVm,ph7_value *pFunc,ph7_value *pResult,...); +PH7_PRIVATE sxi32 PH7_VmUnsetMemObj(ph7_vm *pVm,sxu32 nObjIdx,int bForce); +PH7_PRIVATE void PH7_VmRandomString(ph7_vm *pVm,char *zBuf,int nLen); +PH7_PRIVATE ph7_class * PH7_VmPeekTopClass(ph7_vm *pVm); +PH7_PRIVATE int PH7_VmIsCallable(ph7_vm *pVm,ph7_value *pValue,int CallInvoke); +#ifndef PH7_DISABLE_BUILTIN_FUNC +PH7_PRIVATE const ph7_io_stream * PH7_VmGetStreamDevice(ph7_vm *pVm,const char **pzDevice,int nByte); +#endif /* PH7_DISABLE_BUILTIN_FUNC */ +PH7_PRIVATE int PH7_Utf8Read( + const unsigned char *z, /* First byte of UTF-8 character */ + const unsigned char *zTerm, /* Pretend this byte is 0x00 */ + const unsigned char **pzNext /* Write first byte past UTF-8 char here */ +); +/* parse.c function prototypes */ +PH7_PRIVATE int PH7_IsLangConstruct(sxu32 nKeyID,sxu8 bCheckFunc); +PH7_PRIVATE sxi32 PH7_ExprMakeTree(ph7_gen_state *pGen,SySet *pExprNode,ph7_expr_node **ppRoot); +PH7_PRIVATE sxi32 PH7_GetNextExpr(SyToken *pStart,SyToken *pEnd,SyToken **ppNext); +PH7_PRIVATE void PH7_DelimitNestedTokens(SyToken *pIn,SyToken *pEnd,sxu32 nTokStart,sxu32 nTokEnd,SyToken **ppEnd); +PH7_PRIVATE const ph7_expr_op * PH7_ExprExtractOperator(SyString *pStr,SyToken *pLast); +PH7_PRIVATE sxi32 PH7_ExprFreeTree(ph7_gen_state *pGen,SySet *pNodeSet); +/* compile.c function prototypes */ +PH7_PRIVATE ProcNodeConstruct PH7_GetNodeHandler(sxu32 nNodeType); +PH7_PRIVATE sxi32 PH7_CompileLangConstruct(ph7_gen_state *pGen,sxi32 iCompileFlag); +PH7_PRIVATE sxi32 PH7_CompileVariable(ph7_gen_state *pGen,sxi32 iCompileFlag); +PH7_PRIVATE sxi32 PH7_CompileLiteral(ph7_gen_state *pGen,sxi32 iCompileFlag); +PH7_PRIVATE sxi32 PH7_CompileSimpleString(ph7_gen_state *pGen,sxi32 iCompileFlag); +PH7_PRIVATE sxi32 PH7_CompileString(ph7_gen_state *pGen,sxi32 iCompileFlag); +PH7_PRIVATE sxi32 PH7_CompileArray(ph7_gen_state *pGen,sxi32 iCompileFlag); +PH7_PRIVATE sxi32 PH7_CompileList(ph7_gen_state *pGen,sxi32 iCompileFlag); +PH7_PRIVATE sxi32 PH7_CompileAnnonFunc(ph7_gen_state *pGen,sxi32 iCompileFlag); +PH7_PRIVATE sxi32 PH7_InitCodeGenerator(ph7_vm *pVm,ProcConsumer xErr,void *pErrData); +PH7_PRIVATE sxi32 PH7_ResetCodeGenerator(ph7_vm *pVm,ProcConsumer xErr,void *pErrData); +PH7_PRIVATE sxi32 PH7_GenCompileError(ph7_gen_state *pGen,sxi32 nErrType,sxu32 nLine,const char *zFormat,...); +PH7_PRIVATE sxi32 PH7_CompileScript(ph7_vm *pVm,SyString *pScript,sxi32 iFlags); +/* constant.c function prototypes */ +PH7_PRIVATE void PH7_RegisterBuiltInConstant(ph7_vm *pVm); +/* builtin.c function prototypes */ +PH7_PRIVATE void PH7_RegisterBuiltInFunction(ph7_vm *pVm); +/* hashmap.c function prototypes */ +PH7_PRIVATE ph7_hashmap * PH7_NewHashmap(ph7_vm *pVm,sxu32 (*xIntHash)(sxi64),sxu32 (*xBlobHash)(const void *,sxu32)); +PH7_PRIVATE sxi32 PH7_HashmapCreateSuper(ph7_vm *pVm); +PH7_PRIVATE sxi32 PH7_HashmapRelease(ph7_hashmap *pMap,int FreeDS); +PH7_PRIVATE void PH7_HashmapUnref(ph7_hashmap *pMap); +PH7_PRIVATE sxi32 PH7_HashmapLookup(ph7_hashmap *pMap,ph7_value *pKey,ph7_hashmap_node **ppNode); +PH7_PRIVATE sxi32 PH7_HashmapInsert(ph7_hashmap *pMap,ph7_value *pKey,ph7_value *pVal); +PH7_PRIVATE sxi32 PH7_HashmapInsertByRef(ph7_hashmap *pMap,ph7_value *pKey,sxu32 nRefIdx); +PH7_PRIVATE sxi32 PH7_HashmapUnion(ph7_hashmap *pLeft,ph7_hashmap *pRight); +PH7_PRIVATE void PH7_HashmapUnlinkNode(ph7_hashmap_node *pNode,int bRestore); +PH7_PRIVATE sxi32 PH7_HashmapDup(ph7_hashmap *pSrc,ph7_hashmap *pDest); +PH7_PRIVATE sxi32 PH7_HashmapCmp(ph7_hashmap *pLeft,ph7_hashmap *pRight,int bStrict); +PH7_PRIVATE void PH7_HashmapResetLoopCursor(ph7_hashmap *pMap); +PH7_PRIVATE ph7_hashmap_node * PH7_HashmapGetNextEntry(ph7_hashmap *pMap); +PH7_PRIVATE void PH7_HashmapExtractNodeValue(ph7_hashmap_node *pNode,ph7_value *pValue,int bStore); +PH7_PRIVATE void PH7_HashmapExtractNodeKey(ph7_hashmap_node *pNode,ph7_value *pKey); +PH7_PRIVATE void PH7_RegisterHashmapFunctions(ph7_vm *pVm); +PH7_PRIVATE sxi32 PH7_HashmapDump(SyBlob *pOut,ph7_hashmap *pMap,int ShowType,int nTab,int nDepth); +PH7_PRIVATE sxi32 PH7_HashmapWalk(ph7_hashmap *pMap,int (*xWalk)(ph7_value *,ph7_value *,void *),void *pUserData); +#ifndef PH7_DISABLE_BUILTIN_FUNC +PH7_PRIVATE int PH7_HashmapValuesToSet(ph7_hashmap *pMap,SySet *pOut); +/* builtin.c function prototypes */ +PH7_PRIVATE sxi32 PH7_InputFormat(int (*xConsumer)(ph7_context *,const char *,int,void *), + ph7_context *pCtx,const char *zIn,int nByte,int nArg,ph7_value **apArg,void *pUserData,int vf); +PH7_PRIVATE sxi32 PH7_ProcessCsv(const char *zInput,int nByte,int delim,int encl, + int escape,sxi32 (*xConsumer)(const char *,int,void *),void *pUserData); +PH7_PRIVATE sxi32 PH7_CsvConsumer(const char *zToken,int nTokenLen,void *pUserData); +PH7_PRIVATE sxi32 PH7_StripTagsFromString(ph7_context *pCtx,const char *zIn,int nByte,const char *zTaglist,int nTaglen); +PH7_PRIVATE sxi32 PH7_ParseIniString(ph7_context *pCtx,const char *zIn,sxu32 nByte,int bProcessSection); +#endif +/* oo.c function prototypes */ +PH7_PRIVATE ph7_class * PH7_NewRawClass(ph7_vm *pVm,const SyString *pName,sxu32 nLine); +PH7_PRIVATE ph7_class_attr * PH7_NewClassAttr(ph7_vm *pVm,const SyString *pName,sxu32 nLine,sxi32 iProtection,sxi32 iFlags); +PH7_PRIVATE ph7_class_method * PH7_NewClassMethod(ph7_vm *pVm,ph7_class *pClass,const SyString *pName,sxu32 nLine, + sxi32 iProtection,sxi32 iFlags,sxi32 iFuncFlags); +PH7_PRIVATE ph7_class_method * PH7_ClassExtractMethod(ph7_class *pClass,const char *zName,sxu32 nByte); +PH7_PRIVATE ph7_class_attr * PH7_ClassExtractAttribute(ph7_class *pClass,const char *zName,sxu32 nByte); +PH7_PRIVATE sxi32 PH7_ClassInstallAttr(ph7_class *pClass,ph7_class_attr *pAttr); +PH7_PRIVATE sxi32 PH7_ClassInstallMethod(ph7_class *pClass,ph7_class_method *pMeth); +PH7_PRIVATE sxi32 PH7_ClassInherit(ph7_gen_state *pGen,ph7_class *pSub,ph7_class *pBase); +PH7_PRIVATE sxi32 PH7_ClassInterfaceInherit(ph7_class *pSub,ph7_class *pBase); +PH7_PRIVATE sxi32 PH7_ClassImplement(ph7_class *pMain,ph7_class *pInterface); +PH7_PRIVATE ph7_class_instance * PH7_NewClassInstance(ph7_vm *pVm,ph7_class *pClass); +PH7_PRIVATE ph7_class_instance * PH7_CloneClassInstance(ph7_class_instance *pSrc); +PH7_PRIVATE sxi32 PH7_ClassInstanceCmp(ph7_class_instance *pLeft,ph7_class_instance *pRight,int bStrict,int iNest); +PH7_PRIVATE void PH7_ClassInstanceUnref(ph7_class_instance *pThis); +PH7_PRIVATE sxi32 PH7_ClassInstanceDump(SyBlob *pOut,ph7_class_instance *pThis,int ShowType,int nTab,int nDepth); +PH7_PRIVATE sxi32 PH7_ClassInstanceCallMagicMethod(ph7_vm *pVm,ph7_class *pClass,ph7_class_instance *pThis,const char *zMethod, + sxu32 nByte,const SyString *pAttrName); +PH7_PRIVATE ph7_value * PH7_ClassInstanceExtractAttrValue(ph7_class_instance *pThis,VmClassAttr *pAttr); +PH7_PRIVATE sxi32 PH7_ClassInstanceToHashmap(ph7_class_instance *pThis,ph7_hashmap *pMap); +PH7_PRIVATE sxi32 PH7_ClassInstanceWalk(ph7_class_instance *pThis, + int (*xWalk)(const char *,ph7_value *,void *),void *pUserData); +PH7_PRIVATE ph7_value * PH7_ClassInstanceFetchAttr(ph7_class_instance *pThis,const SyString *pName); +/* vfs.c */ +#ifndef PH7_DISABLE_BUILTIN_FUNC +PH7_PRIVATE void * PH7_StreamOpenHandle(ph7_vm *pVm,const ph7_io_stream *pStream,const char *zFile, + int iFlags,int use_include,ph7_value *pResource,int bPushInclude,int *pNew); +PH7_PRIVATE sxi32 PH7_StreamReadWholeFile(void *pHandle,const ph7_io_stream *pStream,SyBlob *pOut); +PH7_PRIVATE void PH7_StreamCloseHandle(const ph7_io_stream *pStream,void *pHandle); +#endif /* PH7_DISABLE_BUILTIN_FUNC */ +PH7_PRIVATE const char * PH7_ExtractDirName(const char *zPath,int nByte,int *pLen); +PH7_PRIVATE sxi32 PH7_RegisterIORoutine(ph7_vm *pVm); +PH7_PRIVATE const ph7_vfs * PH7_ExportBuiltinVfs(void); +PH7_PRIVATE void * PH7_ExportStdin(ph7_vm *pVm); +PH7_PRIVATE void * PH7_ExportStdout(ph7_vm *pVm); +PH7_PRIVATE void * PH7_ExportStderr(ph7_vm *pVm); +/* lib.c function prototypes */ +#ifndef PH7_DISABLE_BUILTIN_FUNC +PH7_PRIVATE sxi32 SyXMLParserInit(SyXMLParser *pParser,SyMemBackend *pAllocator,sxi32 iFlags); +PH7_PRIVATE sxi32 SyXMLParserSetEventHandler(SyXMLParser *pParser, + void *pUserData, + ProcXMLStartTagHandler xStartTag, + ProcXMLTextHandler xRaw, + ProcXMLSyntaxErrorHandler xErr, + ProcXMLStartDocument xStartDoc, + ProcXMLEndTagHandler xEndTag, + ProcXMLPIHandler xPi, + ProcXMLEndDocument xEndDoc, + ProcXMLDoctypeHandler xDoctype, + ProcXMLNameSpaceStart xNameSpace, + ProcXMLNameSpaceEnd xNameSpaceEnd + ); +PH7_PRIVATE sxi32 SyXMLProcess(SyXMLParser *pParser,const char *zInput,sxu32 nByte); +PH7_PRIVATE sxi32 SyXMLParserRelease(SyXMLParser *pParser); +PH7_PRIVATE sxi32 SyArchiveInit(SyArchive *pArch,SyMemBackend *pAllocator,ProcHash xHash,ProcRawStrCmp xCmp); +PH7_PRIVATE sxi32 SyArchiveRelease(SyArchive *pArch); +PH7_PRIVATE sxi32 SyArchiveResetLoopCursor(SyArchive *pArch); +PH7_PRIVATE sxi32 SyArchiveGetNextEntry(SyArchive *pArch,SyArchiveEntry **ppEntry); +PH7_PRIVATE sxi32 SyZipExtractFromBuf(SyArchive *pArch,const char *zBuf,sxu32 nLen); +#endif /* PH7_DISABLE_BUILTIN_FUNC */ +#ifndef PH7_DISABLE_BUILTIN_FUNC +PH7_PRIVATE sxi32 SyBinToHexConsumer(const void *pIn,sxu32 nLen,ProcConsumer xConsumer,void *pConsumerData); +#endif /* PH7_DISABLE_BUILTIN_FUNC */ +#ifndef PH7_DISABLE_BUILTIN_FUNC +#ifndef PH7_DISABLE_HASH_FUNC +PH7_PRIVATE sxu32 SyCrc32(const void *pSrc,sxu32 nLen); +PH7_PRIVATE void MD5Update(MD5Context *ctx, const unsigned char *buf, unsigned int len); +PH7_PRIVATE void MD5Final(unsigned char digest[16], MD5Context *ctx); +PH7_PRIVATE sxi32 MD5Init(MD5Context *pCtx); +PH7_PRIVATE sxi32 SyMD5Compute(const void *pIn,sxu32 nLen,unsigned char zDigest[16]); +PH7_PRIVATE void SHA1Init(SHA1Context *context); +PH7_PRIVATE void SHA1Update(SHA1Context *context,const unsigned char *data,unsigned int len); +PH7_PRIVATE void SHA1Final(SHA1Context *context, unsigned char digest[20]); +PH7_PRIVATE sxi32 SySha1Compute(const void *pIn,sxu32 nLen,unsigned char zDigest[20]); +#endif +#endif /* PH7_DISABLE_BUILTIN_FUNC */ +PH7_PRIVATE sxi32 SyRandomness(SyPRNGCtx *pCtx,void *pBuf,sxu32 nLen); +PH7_PRIVATE sxi32 SyRandomnessInit(SyPRNGCtx *pCtx,ProcRandomSeed xSeed,void *pUserData); +PH7_PRIVATE sxu32 SyBufferFormat(char *zBuf,sxu32 nLen,const char *zFormat,...); +PH7_PRIVATE sxu32 SyBlobFormatAp(SyBlob *pBlob,const char *zFormat,va_list ap); +PH7_PRIVATE sxu32 SyBlobFormat(SyBlob *pBlob,const char *zFormat,...); +PH7_PRIVATE sxi32 SyProcFormat(ProcConsumer xConsumer,void *pData,const char *zFormat,...); +#ifndef PH7_DISABLE_BUILTIN_FUNC +PH7_PRIVATE const char *SyTimeGetMonth(sxi32 iMonth); +PH7_PRIVATE const char *SyTimeGetDay(sxi32 iDay); +#endif /* PH7_DISABLE_BUILTIN_FUNC */ +PH7_PRIVATE sxi32 SyUriDecode(const char *zSrc,sxu32 nLen,ProcConsumer xConsumer,void *pUserData,int bUTF8); +#ifndef PH7_DISABLE_BUILTIN_FUNC +PH7_PRIVATE sxi32 SyUriEncode(const char *zSrc,sxu32 nLen,ProcConsumer xConsumer,void *pUserData); +#endif +PH7_PRIVATE sxi32 SyLexRelease(SyLex *pLex); +PH7_PRIVATE sxi32 SyLexTokenizeInput(SyLex *pLex,const char *zInput,sxu32 nLen,void *pCtxData,ProcSort xSort,ProcCmp xCmp); +PH7_PRIVATE sxi32 SyLexInit(SyLex *pLex,SySet *pSet,ProcTokenizer xTokenizer,void *pUserData); +#ifndef PH7_DISABLE_BUILTIN_FUNC +PH7_PRIVATE sxi32 SyBase64Decode(const char *zB64,sxu32 nLen,ProcConsumer xConsumer,void *pUserData); +PH7_PRIVATE sxi32 SyBase64Encode(const char *zSrc,sxu32 nLen,ProcConsumer xConsumer,void *pUserData); +#endif /* PH7_DISABLE_BUILTIN_FUNC */ +PH7_PRIVATE sxi32 SyStrToReal(const char *zSrc,sxu32 nLen,void *pOutVal,const char **zRest); +PH7_PRIVATE sxi32 SyBinaryStrToInt64(const char *zSrc,sxu32 nLen,void *pOutVal,const char **zRest); +PH7_PRIVATE sxi32 SyOctalStrToInt64(const char *zSrc,sxu32 nLen,void *pOutVal,const char **zRest); +PH7_PRIVATE sxi32 SyHexStrToInt64(const char *zSrc,sxu32 nLen,void *pOutVal,const char **zRest); +PH7_PRIVATE sxi32 SyHexToint(sxi32 c); +PH7_PRIVATE sxi32 SyStrToInt64(const char *zSrc,sxu32 nLen,void *pOutVal,const char **zRest); +PH7_PRIVATE sxi32 SyStrToInt32(const char *zSrc,sxu32 nLen,void *pOutVal,const char **zRest); +PH7_PRIVATE sxi32 SyStrIsNumeric(const char *zSrc,sxu32 nLen,sxu8 *pReal,const char **pzTail); +PH7_PRIVATE SyHashEntry *SyHashLastEntry(SyHash *pHash); +PH7_PRIVATE sxi32 SyHashInsert(SyHash *pHash,const void *pKey,sxu32 nKeyLen,void *pUserData); +PH7_PRIVATE sxi32 SyHashForEach(SyHash *pHash,sxi32(*xStep)(SyHashEntry *,void *),void *pUserData); +PH7_PRIVATE SyHashEntry *SyHashGetNextEntry(SyHash *pHash); +PH7_PRIVATE sxi32 SyHashResetLoopCursor(SyHash *pHash); +PH7_PRIVATE sxi32 SyHashDeleteEntry2(SyHashEntry *pEntry); +PH7_PRIVATE sxi32 SyHashDeleteEntry(SyHash *pHash,const void *pKey,sxu32 nKeyLen,void **ppUserData); +PH7_PRIVATE SyHashEntry *SyHashGet(SyHash *pHash,const void *pKey,sxu32 nKeyLen); +PH7_PRIVATE sxi32 SyHashRelease(SyHash *pHash); +PH7_PRIVATE sxi32 SyHashInit(SyHash *pHash,SyMemBackend *pAllocator,ProcHash xHash,ProcCmp xCmp); +PH7_PRIVATE sxu32 SyStrHash(const void *pSrc,sxu32 nLen); +PH7_PRIVATE void *SySetAt(SySet *pSet,sxu32 nIdx); +PH7_PRIVATE void *SySetPop(SySet *pSet); +PH7_PRIVATE void *SySetPeek(SySet *pSet); +PH7_PRIVATE sxi32 SySetRelease(SySet *pSet); +PH7_PRIVATE sxi32 SySetReset(SySet *pSet); +PH7_PRIVATE sxi32 SySetResetCursor(SySet *pSet); +PH7_PRIVATE sxi32 SySetGetNextEntry(SySet *pSet,void **ppEntry); +#ifndef PH7_DISABLE_BUILTIN_FUNC +PH7_PRIVATE void * SySetPeekCurrentEntry(SySet *pSet); +#endif /* PH7_DISABLE_BUILTIN_FUNC */ +PH7_PRIVATE sxi32 SySetTruncate(SySet *pSet,sxu32 nNewSize); +PH7_PRIVATE sxi32 SySetAlloc(SySet *pSet,sxi32 nItem); +PH7_PRIVATE sxi32 SySetPut(SySet *pSet,const void *pItem); +PH7_PRIVATE sxi32 SySetInit(SySet *pSet,SyMemBackend *pAllocator,sxu32 ElemSize); +#ifndef PH7_DISABLE_BUILTIN_FUNC +PH7_PRIVATE sxi32 SyBlobSearch(const void *pBlob,sxu32 nLen,const void *pPattern,sxu32 pLen,sxu32 *pOfft); +#endif +PH7_PRIVATE sxi32 SyBlobRelease(SyBlob *pBlob); +PH7_PRIVATE sxi32 SyBlobReset(SyBlob *pBlob); +PH7_PRIVATE sxi32 SyBlobCmp(SyBlob *pLeft,SyBlob *pRight); +PH7_PRIVATE sxi32 SyBlobDup(SyBlob *pSrc,SyBlob *pDest); +PH7_PRIVATE sxi32 SyBlobNullAppend(SyBlob *pBlob); +PH7_PRIVATE sxi32 SyBlobAppend(SyBlob *pBlob,const void *pData,sxu32 nSize); +PH7_PRIVATE sxi32 SyBlobReadOnly(SyBlob *pBlob,const void *pData,sxu32 nByte); +PH7_PRIVATE sxi32 SyBlobInit(SyBlob *pBlob,SyMemBackend *pAllocator); +PH7_PRIVATE sxi32 SyBlobInitFromBuf(SyBlob *pBlob,void *pBuffer,sxu32 nSize); +PH7_PRIVATE char *SyMemBackendStrDup(SyMemBackend *pBackend,const char *zSrc,sxu32 nSize); +PH7_PRIVATE void *SyMemBackendDup(SyMemBackend *pBackend,const void *pSrc,sxu32 nSize); +PH7_PRIVATE sxi32 SyMemBackendRelease(SyMemBackend *pBackend); +PH7_PRIVATE sxi32 SyMemBackendInitFromOthers(SyMemBackend *pBackend,const SyMemMethods *pMethods,ProcMemError xMemErr,void *pUserData); +PH7_PRIVATE sxi32 SyMemBackendInit(SyMemBackend *pBackend,ProcMemError xMemErr,void *pUserData); +PH7_PRIVATE sxi32 SyMemBackendInitFromParent(SyMemBackend *pBackend,SyMemBackend *pParent); +#if 0 +/* Not used in the current release of the PH7 engine */ +PH7_PRIVATE void *SyMemBackendPoolRealloc(SyMemBackend *pBackend,void *pOld,sxu32 nByte); +#endif +PH7_PRIVATE sxi32 SyMemBackendPoolFree(SyMemBackend *pBackend,void *pChunk); +PH7_PRIVATE void *SyMemBackendPoolAlloc(SyMemBackend *pBackend,sxu32 nByte); +PH7_PRIVATE sxi32 SyMemBackendFree(SyMemBackend *pBackend,void *pChunk); +PH7_PRIVATE void *SyMemBackendRealloc(SyMemBackend *pBackend,void *pOld,sxu32 nByte); +PH7_PRIVATE void *SyMemBackendAlloc(SyMemBackend *pBackend,sxu32 nByte); +#if defined(PH7_ENABLE_THREADS) +PH7_PRIVATE sxi32 SyMemBackendMakeThreadSafe(SyMemBackend *pBackend,const SyMutexMethods *pMethods); +PH7_PRIVATE sxi32 SyMemBackendDisbaleMutexing(SyMemBackend *pBackend); +#endif +PH7_PRIVATE sxu32 SyMemcpy(const void *pSrc,void *pDest,sxu32 nLen); +PH7_PRIVATE sxi32 SyMemcmp(const void *pB1,const void *pB2,sxu32 nSize); +PH7_PRIVATE void SyZero(void *pSrc,sxu32 nSize); +PH7_PRIVATE sxi32 SyStrnicmp(const char *zLeft,const char *zRight,sxu32 SLen); +PH7_PRIVATE sxi32 SyStrnmicmp(const void *pLeft, const void *pRight,sxu32 SLen); +#ifndef PH7_DISABLE_BUILTIN_FUNC +PH7_PRIVATE sxi32 SyStrncmp(const char *zLeft,const char *zRight,sxu32 nLen); +#endif +PH7_PRIVATE sxi32 SyByteListFind(const char *zSrc,sxu32 nLen,const char *zList,sxu32 *pFirstPos); +#ifndef PH7_DISABLE_BUILTIN_FUNC +PH7_PRIVATE sxi32 SyByteFind2(const char *zStr,sxu32 nLen,sxi32 c,sxu32 *pPos); +#endif +PH7_PRIVATE sxi32 SyByteFind(const char *zStr,sxu32 nLen,sxi32 c,sxu32 *pPos); +PH7_PRIVATE sxu32 SyStrlen(const char *zSrc); +#if defined(PH7_ENABLE_THREADS) +PH7_PRIVATE const SyMutexMethods *SyMutexExportMethods(void); +PH7_PRIVATE sxi32 SyMemBackendMakeThreadSafe(SyMemBackend *pBackend,const SyMutexMethods *pMethods); +PH7_PRIVATE sxi32 SyMemBackendDisbaleMutexing(SyMemBackend *pBackend); +#endif +#endif /* __PH7INT_H__ */ +/* + * ---------------------------------------------------------- + * File: vm.c + * MD5: fed926a5df137d2896badd8911a0b752 + * ---------------------------------------------------------- + */ +/* + * Symisc PH7: An embeddable bytecode compiler and a virtual machine for the PHP(5) programming language. + * Copyright (C) 2011-2012, Symisc Systems http://ph7.symisc.net/ + * Version 2.1.4 + * For information on licensing,redistribution of this file,and for a DISCLAIMER OF ALL WARRANTIES + * please contact Symisc Systems via: + * legal@symisc.net + * licensing@symisc.net + * contact@symisc.net + * or visit: + * http://ph7.symisc.net/ + */ + /* $SymiscID: vm.c v1.4 FreeBSD 2012-09-10 00:06 stable $ */ +#ifndef PH7_AMALGAMATION +#include "ph7int.h" +#endif +/* + * The code in this file implements execution method of the PH7 Virtual Machine. + * The PH7 compiler (implemented in 'compiler.c' and 'parse.c') generates a bytecode program + * which is then executed by the virtual machine implemented here to do the work of the PHP + * statements. + * PH7 bytecode programs are similar in form to assembly language. The program consists + * of a linear sequence of operations .Each operation has an opcode and 3 operands. + * Operands P1 and P2 are integers where the first is signed while the second is unsigned. + * Operand P3 is an arbitrary pointer specific to each instruction. The P2 operand is usually + * the jump destination used by the OP_JMP,OP_JZ,OP_JNZ,... instructions. + * Opcodes will typically ignore one or more operands. Many opcodes ignore all three operands. + * Computation results are stored on a stack. Each entry on the stack is of type ph7_value. + * PH7 uses the ph7_value object to represent all values that can be stored in a PHP variable. + * Since PHP uses dynamic typing for the values it stores. Values stored in ph7_value objects + * can be integers,floating point values,strings,arrays,class instances (object in the PHP jargon) + * and so on. + * Internally,the PH7 virtual machine manipulates nearly all PHP values as ph7_values structures. + * Each ph7_value may cache multiple representations(string,integer etc.) of the same value. + * An implicit conversion from one type to the other occurs as necessary. + * Most of the code in this file is taken up by the [VmByteCodeExec()] function which does + * the work of interpreting a PH7 bytecode program. But other routines are also provided + * to help in building up a program instruction by instruction. Also note that sepcial + * functions that need access to the underlying virtual machine details such as [die()], + * [func_get_args()],[call_user_func()],[ob_start()] and many more are implemented here. + */ +/* + * Each active virtual machine frame is represented by an instance + * of the following structure. + * VM Frame hold local variables and other stuff related to function call. + */ +struct VmFrame +{ + VmFrame *pParent; /* Parent frame or NULL if global scope */ + void *pUserData; /* Upper layer private data associated with this frame */ + ph7_class_instance *pThis; /* Current class instance [i.e: the '$this' variable].NULL otherwise */ + SySet sLocal; /* Local variables container (VmSlot instance) */ + ph7_vm *pVm; /* VM that own this frame */ + SyHash hVar; /* Variable hashtable for fast lookup */ + SySet sArg; /* Function arguments container */ + SySet sRef; /* Local reference table (VmSlot instance) */ + sxi32 iFlags; /* Frame configuration flags (See below)*/ + sxu32 iExceptionJump; /* Exception jump destination */ +}; +#define VM_FRAME_EXCEPTION 0x01 /* Special Exception frame */ +#define VM_FRAME_THROW 0x02 /* An exception was thrown */ +#define VM_FRAME_CATCH 0x04 /* Catch frame */ +/* + * When a user defined variable is released (via manual unset($x) or garbage collected) + * memory object index is stored in an instance of the following structure and put + * in the free object table so that it can be reused again without allocating + * a new memory object. + */ +typedef struct VmSlot VmSlot; +struct VmSlot +{ + sxu32 nIdx; /* Index in pVm->aMemObj[] */ + void *pUserData; /* Upper-layer private data */ +}; +/* + * An entry in the reference table is represented by an instance of the + * follwoing table. + * The implementation of the reference mechanism in the PH7 engine + * differ greatly from the one used by the zend engine. That is, + * the reference implementation is consistent,solid and it's + * behavior resemble the C++ reference mechanism. + * Refer to the official for more information on this powerful + * extension. + */ +struct VmRefObj +{ + SySet aReference; /* Table of references to this memory object */ + SySet aArrEntries; /* Foreign hashmap entries [i.e: array(&$a) ] */ + sxu32 nIdx; /* Referenced object index */ + sxi32 iFlags; /* Configuration flags */ + VmRefObj *pNextCollide,*pPrevCollide; /* Collision link */ + VmRefObj *pNext,*pPrev; /* List of all referenced objects */ +}; +#define VM_REF_IDX_KEEP 0x001 /* Do not restore the memory object to the free list */ +/* + * Output control buffer entry. + * Refer to the implementation of [ob_start()] for more information. + */ +typedef struct VmObEntry VmObEntry; +struct VmObEntry +{ + ph7_value sCallback; /* User defined callback */ + SyBlob sOB; /* Output buffer consumer */ +}; +/* + * Each installed shutdown callback (registered using [register_shutdown_function()] ) + * is stored in an instance of the following structure. + * Refer to the implementation of [register_shutdown_function(()] for more information. + */ +typedef struct VmShutdownCB VmShutdownCB; +struct VmShutdownCB +{ + ph7_value sCallback; /* Shutdown callback */ + ph7_value aArg[10]; /* Callback arguments (10 maximum arguments) */ + int nArg; /* Total number of given arguments */ +}; +/* Uncaught exception code value */ +#define PH7_EXCEPTION -255 +/* + * Each parsed URI is recorded and stored in an instance of the following structure. + * This structure and it's related routines are taken verbatim from the xHT project + * [A modern embeddable HTTP engine implementing all the RFC2616 methods] + * the xHT project is developed internally by Symisc Systems. + */ +typedef struct SyhttpUri SyhttpUri; +struct SyhttpUri +{ + SyString sHost; /* Hostname or IP address */ + SyString sPort; /* Port number */ + SyString sPath; /* Mandatory resource path passed verbatim (Not decoded) */ + SyString sQuery; /* Query part */ + SyString sFragment; /* Fragment part */ + SyString sScheme; /* Scheme */ + SyString sUser; /* Username */ + SyString sPass; /* Password */ + SyString sRaw; /* Raw URI */ +}; +/* + * An instance of the following structure is used to record all MIME headers seen + * during a HTTP interaction. + * This structure and it's related routines are taken verbatim from the xHT project + * [A modern embeddable HTTP engine implementing all the RFC2616 methods] + * the xHT project is developed internally by Symisc Systems. + */ +typedef struct SyhttpHeader SyhttpHeader; +struct SyhttpHeader +{ + SyString sName; /* Header name [i.e:"Content-Type","Host","User-Agent"]. NOT NUL TERMINATED */ + SyString sValue; /* Header values [i.e: "text/html"]. NOT NUL TERMINATED */ +}; +/* + * Supported HTTP methods. + */ +#define HTTP_METHOD_GET 1 /* GET */ +#define HTTP_METHOD_HEAD 2 /* HEAD */ +#define HTTP_METHOD_POST 3 /* POST */ +#define HTTP_METHOD_PUT 4 /* PUT */ +#define HTTP_METHOD_OTHR 5 /* Other HTTP methods [i.e: DELETE,TRACE,OPTIONS...]*/ +/* + * Supported HTTP protocol version. + */ +#define HTTP_PROTO_10 1 /* HTTP/1.0 */ +#define HTTP_PROTO_11 2 /* HTTP/1.1 */ +/* + * Register a constant and it's associated expansion callback so that + * it can be expanded from the target PHP program. + * The constant expansion mechanism under PH7 is extremely powerful yet + * simple and work as follows: + * Each registered constant have a C procedure associated with it. + * This procedure known as the constant expansion callback is responsible + * of expanding the invoked constant to the desired value,for example: + * The C procedure associated with the "__PI__" constant expands to 3.14 (the value of PI). + * The "__OS__" constant procedure expands to the name of the host Operating Systems + * (Windows,Linux,...) and so on. + * Please refer to the official documentation for additional information. + */ +PH7_PRIVATE sxi32 PH7_VmRegisterConstant( + ph7_vm *pVm, /* Target VM */ + const SyString *pName, /* Constant name */ + ProcConstant xExpand, /* Constant expansion callback */ + void *pUserData /* Last argument to xExpand() */ + ) +{ + ph7_constant *pCons; + SyHashEntry *pEntry; + char *zDupName; + sxi32 rc; + pEntry = SyHashGet(&pVm->hConstant,(const void *)pName->zString,pName->nByte); + if( pEntry ){ + /* Overwrite the old definition and return immediately */ + pCons = (ph7_constant *)pEntry->pUserData; + pCons->xExpand = xExpand; + pCons->pUserData = pUserData; + return SXRET_OK; + } + /* Allocate a new constant instance */ + pCons = (ph7_constant *)SyMemBackendPoolAlloc(&pVm->sAllocator,sizeof(ph7_constant)); + if( pCons == 0 ){ + return 0; + } + /* Duplicate constant name */ + zDupName = SyMemBackendStrDup(&pVm->sAllocator,pName->zString,pName->nByte); + if( zDupName == 0 ){ + SyMemBackendPoolFree(&pVm->sAllocator,pCons); + return 0; + } + /* Install the constant */ + SyStringInitFromBuf(&pCons->sName,zDupName,pName->nByte); + pCons->xExpand = xExpand; + pCons->pUserData = pUserData; + rc = SyHashInsert(&pVm->hConstant,(const void *)zDupName,SyStringLength(&pCons->sName),pCons); + if( rc != SXRET_OK ){ + SyMemBackendFree(&pVm->sAllocator,zDupName); + SyMemBackendPoolFree(&pVm->sAllocator,pCons); + return rc; + } + /* All done,constant can be invoked from PHP code */ + return SXRET_OK; +} +/* + * Allocate a new foreign function instance. + * This function return SXRET_OK on success. Any other + * return value indicates failure. + * Please refer to the official documentation for an introduction to + * the foreign function mechanism. + */ +static sxi32 PH7_NewForeignFunction( + ph7_vm *pVm, /* Target VM */ + const SyString *pName, /* Foreign function name */ + ProchHostFunction xFunc, /* Foreign function implementation */ + void *pUserData, /* Foreign function private data */ + ph7_user_func **ppOut /* OUT: VM image of the foreign function */ + ) +{ + ph7_user_func *pFunc; + char *zDup; + /* Allocate a new user function */ + pFunc = (ph7_user_func *)SyMemBackendPoolAlloc(&pVm->sAllocator,sizeof(ph7_user_func)); + if( pFunc == 0 ){ + return SXERR_MEM; + } + /* Duplicate function name */ + zDup = SyMemBackendStrDup(&pVm->sAllocator,pName->zString,pName->nByte); + if( zDup == 0 ){ + SyMemBackendPoolFree(&pVm->sAllocator,pFunc); + return SXERR_MEM; + } + /* Zero the structure */ + SyZero(pFunc,sizeof(ph7_user_func)); + /* Initialize structure fields */ + SyStringInitFromBuf(&pFunc->sName,zDup,pName->nByte); + pFunc->pVm = pVm; + pFunc->xFunc = xFunc; + pFunc->pUserData = pUserData; + SySetInit(&pFunc->aAux,&pVm->sAllocator,sizeof(ph7_aux_data)); + /* Write a pointer to the new function */ + *ppOut = pFunc; + return SXRET_OK; +} +/* + * Install a foreign function and it's associated callback so that + * it can be invoked from the target PHP code. + * This function return SXRET_OK on successful registration. Any other + * return value indicates failure. + * Please refer to the official documentation for an introduction to + * the foreign function mechanism. + */ +PH7_PRIVATE sxi32 PH7_VmInstallForeignFunction( + ph7_vm *pVm, /* Target VM */ + const SyString *pName, /* Foreign function name */ + ProchHostFunction xFunc, /* Foreign function implementation */ + void *pUserData /* Foreign function private data */ + ) +{ + ph7_user_func *pFunc; + SyHashEntry *pEntry; + sxi32 rc; + /* Overwrite any previously registered function with the same name */ + pEntry = SyHashGet(&pVm->hHostFunction,pName->zString,pName->nByte); + if( pEntry ){ + pFunc = (ph7_user_func *)pEntry->pUserData; + pFunc->pUserData = pUserData; + pFunc->xFunc = xFunc; + SySetReset(&pFunc->aAux); + return SXRET_OK; + } + /* Create a new user function */ + rc = PH7_NewForeignFunction(&(*pVm),&(*pName),xFunc,pUserData,&pFunc); + if( rc != SXRET_OK ){ + return rc; + } + /* Install the function in the corresponding hashtable */ + rc = SyHashInsert(&pVm->hHostFunction,SyStringData(&pFunc->sName),pName->nByte,pFunc); + if( rc != SXRET_OK ){ + SyMemBackendFree(&pVm->sAllocator,(void *)SyStringData(&pFunc->sName)); + SyMemBackendPoolFree(&pVm->sAllocator,pFunc); + return rc; + } + /* User function successfully installed */ + return SXRET_OK; +} +/* + * Initialize a VM function. + */ +PH7_PRIVATE sxi32 PH7_VmInitFuncState( + ph7_vm *pVm, /* Target VM */ + ph7_vm_func *pFunc, /* Target Fucntion */ + const char *zName, /* Function name */ + sxu32 nByte, /* zName length */ + sxi32 iFlags, /* Configuration flags */ + void *pUserData /* Function private data */ + ) +{ + /* Zero the structure */ + SyZero(pFunc,sizeof(ph7_vm_func)); + /* Initialize structure fields */ + /* Arguments container */ + SySetInit(&pFunc->aArgs,&pVm->sAllocator,sizeof(ph7_vm_func_arg)); + /* Static variable container */ + SySetInit(&pFunc->aStatic,&pVm->sAllocator,sizeof(ph7_vm_func_static_var)); + /* Bytecode container */ + SySetInit(&pFunc->aByteCode,&pVm->sAllocator,sizeof(VmInstr)); + /* Preallocate some instruction slots */ + SySetAlloc(&pFunc->aByteCode,0x10); + /* Closure environment */ + SySetInit(&pFunc->aClosureEnv,&pVm->sAllocator,sizeof(ph7_vm_func_closure_env)); + pFunc->iFlags = iFlags; + pFunc->pUserData = pUserData; + SyStringInitFromBuf(&pFunc->sName,zName,nByte); + return SXRET_OK; +} +/* + * Install a user defined function in the corresponding VM container. + */ +PH7_PRIVATE sxi32 PH7_VmInstallUserFunction( + ph7_vm *pVm, /* Target VM */ + ph7_vm_func *pFunc, /* Target function */ + SyString *pName /* Function name */ + ) +{ + SyHashEntry *pEntry; + sxi32 rc; + if( pName == 0 ){ + /* Use the built-in name */ + pName = &pFunc->sName; + } + /* Check for duplicates (functions with the same name) first */ + pEntry = SyHashGet(&pVm->hFunction,pName->zString,pName->nByte); + if( pEntry ){ + ph7_vm_func *pLink = (ph7_vm_func *)pEntry->pUserData; + if( pLink != pFunc ){ + /* Link */ + pFunc->pNextName = pLink; + pEntry->pUserData = pFunc; + } + return SXRET_OK; + } + /* First time seen */ + pFunc->pNextName = 0; + rc = SyHashInsert(&pVm->hFunction,pName->zString,pName->nByte,pFunc); + return rc; +} +/* + * Install a user defined class in the corresponding VM container. + */ +PH7_PRIVATE sxi32 PH7_VmInstallClass( + ph7_vm *pVm, /* Target VM */ + ph7_class *pClass /* Target Class */ + ) +{ + SyString *pName = &pClass->sName; + SyHashEntry *pEntry; + sxi32 rc; + /* Check for duplicates */ + pEntry = SyHashGet(&pVm->hClass,(const void *)pName->zString,pName->nByte); + if( pEntry ){ + ph7_class *pLink = (ph7_class *)pEntry->pUserData; + /* Link entry with the same name */ + pClass->pNextName = pLink; + pEntry->pUserData = pClass; + return SXRET_OK; + } + pClass->pNextName = 0; + /* Perform a simple hashtable insertion */ + rc = SyHashInsert(&pVm->hClass,(const void *)pName->zString,pName->nByte,pClass); + return rc; +} +/* + * Instruction builder interface. + */ +PH7_PRIVATE sxi32 PH7_VmEmitInstr( + ph7_vm *pVm, /* Target VM */ + sxi32 iOp, /* Operation to perform */ + sxi32 iP1, /* First operand */ + sxu32 iP2, /* Second operand */ + void *p3, /* Third operand */ + sxu32 *pIndex /* Instruction index. NULL otherwise */ + ) +{ + VmInstr sInstr; + sxi32 rc; + /* Fill the VM instruction */ + sInstr.iOp = (sxu8)iOp; + sInstr.iP1 = iP1; + sInstr.iP2 = iP2; + sInstr.p3 = p3; + if( pIndex ){ + /* Instruction index in the bytecode array */ + *pIndex = SySetUsed(pVm->pByteContainer); + } + /* Finally,record the instruction */ + rc = SySetPut(pVm->pByteContainer,(const void *)&sInstr); + if( rc != SXRET_OK ){ + PH7_GenCompileError(&pVm->sCodeGen,E_ERROR,1,"Fatal,Cannot emit instruction due to a memory failure"); + /* Fall throw */ + } + return rc; +} +/* + * Swap the current bytecode container with the given one. + */ +PH7_PRIVATE sxi32 PH7_VmSetByteCodeContainer(ph7_vm *pVm,SySet *pContainer) +{ + if( pContainer == 0 ){ + /* Point to the default container */ + pVm->pByteContainer = &pVm->aByteCode; + }else{ + /* Change container */ + pVm->pByteContainer = &(*pContainer); + } + return SXRET_OK; +} +/* + * Return the current bytecode container. + */ +PH7_PRIVATE SySet * PH7_VmGetByteCodeContainer(ph7_vm *pVm) +{ + return pVm->pByteContainer; +} +/* + * Extract the VM instruction rooted at nIndex. + */ +PH7_PRIVATE VmInstr * PH7_VmGetInstr(ph7_vm *pVm,sxu32 nIndex) +{ + VmInstr *pInstr; + pInstr = (VmInstr *)SySetAt(pVm->pByteContainer,nIndex); + return pInstr; +} +/* + * Return the total number of VM instructions recorded so far. + */ +PH7_PRIVATE sxu32 PH7_VmInstrLength(ph7_vm *pVm) +{ + return SySetUsed(pVm->pByteContainer); +} +/* + * Pop the last VM instruction. + */ +PH7_PRIVATE VmInstr * PH7_VmPopInstr(ph7_vm *pVm) +{ + return (VmInstr *)SySetPop(pVm->pByteContainer); +} +/* + * Peek the last VM instruction. + */ +PH7_PRIVATE VmInstr * PH7_VmPeekInstr(ph7_vm *pVm) +{ + return (VmInstr *)SySetPeek(pVm->pByteContainer); +} +PH7_PRIVATE VmInstr * PH7_VmPeekNextInstr(ph7_vm *pVm) +{ + VmInstr *aInstr; + sxu32 n; + n = SySetUsed(pVm->pByteContainer); + if( n < 2 ){ + return 0; + } + aInstr = (VmInstr *)SySetBasePtr(pVm->pByteContainer); + return &aInstr[n - 2]; +} +/* + * Allocate a new virtual machine frame. + */ +static VmFrame * VmNewFrame( + ph7_vm *pVm, /* Target VM */ + void *pUserData, /* Upper-layer private data */ + ph7_class_instance *pThis /* Top most class instance [i.e: Object in the PHP jargon]. NULL otherwise */ + ) +{ + VmFrame *pFrame; + /* Allocate a new vm frame */ + pFrame = (VmFrame *)SyMemBackendPoolAlloc(&pVm->sAllocator,sizeof(VmFrame)); + if( pFrame == 0 ){ + return 0; + } + /* Zero the structure */ + SyZero(pFrame,sizeof(VmFrame)); + /* Initialize frame fields */ + pFrame->pUserData = pUserData; + pFrame->pThis = pThis; + pFrame->pVm = pVm; + SyHashInit(&pFrame->hVar,&pVm->sAllocator,0,0); + SySetInit(&pFrame->sArg,&pVm->sAllocator,sizeof(VmSlot)); + SySetInit(&pFrame->sLocal,&pVm->sAllocator,sizeof(VmSlot)); + SySetInit(&pFrame->sRef,&pVm->sAllocator,sizeof(VmSlot)); + return pFrame; +} +/* + * Enter a VM frame. + */ +static sxi32 VmEnterFrame( + ph7_vm *pVm, /* Target VM */ + void *pUserData, /* Upper-layer private data */ + ph7_class_instance *pThis, /* Top most class instance [i.e: Object in the PHP jargon]. NULL otherwise */ + VmFrame **ppFrame /* OUT: Top most active frame */ + ) +{ + VmFrame *pFrame; + /* Allocate a new frame */ + pFrame = VmNewFrame(&(*pVm),pUserData,pThis); + if( pFrame == 0 ){ + return SXERR_MEM; + } + /* Link to the list of active VM frame */ + pFrame->pParent = pVm->pFrame; + pVm->pFrame = pFrame; + if( ppFrame ){ + /* Write a pointer to the new VM frame */ + *ppFrame = pFrame; + } + return SXRET_OK; +} +/* + * Link a foreign variable with the TOP most active frame. + * Refer to the PH7_OP_UPLINK instruction implementation for more + * information. + */ +static sxi32 VmFrameLink(ph7_vm *pVm,SyString *pName) +{ + VmFrame *pTarget,*pFrame; + SyHashEntry *pEntry = 0; + sxi32 rc; + /* Point to the upper frame */ + pFrame = pVm->pFrame; + while( pFrame->pParent && (pFrame->iFlags & VM_FRAME_EXCEPTION) ){ + /* Safely ignore the exception frame */ + pFrame = pFrame->pParent; + } + pTarget = pFrame; + pFrame = pTarget->pParent; + while( pFrame ){ + if( (pFrame->iFlags & VM_FRAME_EXCEPTION) == 0 ){ + /* Query the current frame */ + pEntry = SyHashGet(&pFrame->hVar,(const void *)pName->zString,pName->nByte); + if( pEntry ){ + /* Variable found */ + break; + } + } + /* Point to the upper frame */ + pFrame = pFrame->pParent; + } + if( pEntry == 0 ){ + /* Inexistant variable */ + return SXERR_NOTFOUND; + } + /* Link to the current frame */ + rc = SyHashInsert(&pTarget->hVar,pEntry->pKey,pEntry->nKeyLen,pEntry->pUserData); + if( rc == SXRET_OK ){ + sxu32 nIdx; + nIdx = SX_PTR_TO_INT(pEntry->pUserData); + PH7_VmRefObjInstall(&(*pVm),nIdx,SyHashLastEntry(&pTarget->hVar),0,0); + } + return rc; +} +/* + * Leave the top-most active frame. + */ +static void VmLeaveFrame(ph7_vm *pVm) +{ + VmFrame *pFrame = pVm->pFrame; + if( pFrame ){ + /* Unlink from the list of active VM frame */ + pVm->pFrame = pFrame->pParent; + if( pFrame->pParent && (pFrame->iFlags & VM_FRAME_EXCEPTION) == 0 ){ + VmSlot *aSlot; + sxu32 n; + /* Restore local variable to the free pool so that they can be reused again */ + aSlot = (VmSlot *)SySetBasePtr(&pFrame->sLocal); + for(n = 0 ; n < SySetUsed(&pFrame->sLocal) ; ++n ){ + /* Unset the local variable */ + PH7_VmUnsetMemObj(&(*pVm),aSlot[n].nIdx,FALSE); + } + /* Remove local reference */ + aSlot = (VmSlot *)SySetBasePtr(&pFrame->sRef); + for(n = 0 ; n < SySetUsed(&pFrame->sRef) ; ++n ){ + PH7_VmRefObjRemove(&(*pVm),aSlot[n].nIdx,(SyHashEntry *)aSlot[n].pUserData,0); + } + } + /* Release internal containers */ + SyHashRelease(&pFrame->hVar); + SySetRelease(&pFrame->sArg); + SySetRelease(&pFrame->sLocal); + SySetRelease(&pFrame->sRef); + /* Release the whole structure */ + SyMemBackendPoolFree(&pVm->sAllocator,pFrame); + } +} +/* + * Compare two functions signature and return the comparison result. + */ +static int VmOverloadCompare(SyString *pFirst,SyString *pSecond) +{ + const char *zSend = &pSecond->zString[pSecond->nByte]; + const char *zFend = &pFirst->zString[pFirst->nByte]; + const char *zSin = pSecond->zString; + const char *zFin = pFirst->zString; + const char *zPtr = zFin; + for(;;){ + if( zFin >= zFend || zSin >= zSend ){ + break; + } + if( zFin[0] != zSin[0] ){ + /* mismatch */ + break; + } + zFin++; + zSin++; + } + return (int)(zFin-zPtr); +} +/* + * Select the appropriate VM function for the current call context. + * This is the implementation of the powerful 'function overloading' feature + * introduced by the version 2 of the PH7 engine. + * Refer to the official documentation for more information. + */ +static ph7_vm_func * VmOverload( + ph7_vm *pVm, /* Target VM */ + ph7_vm_func *pList, /* Linked list of candidates for overloading */ + ph7_value *aArg, /* Array of passed arguments */ + int nArg /* Total number of passed arguments */ + ) +{ + int iTarget,i,j,iCur,iMax; + ph7_vm_func *apSet[10]; /* Maximum number of candidates */ + ph7_vm_func *pLink; + SyString sArgSig; + SyBlob sSig; + + pLink = pList; + i = 0; + /* Put functions expecting the same number of passed arguments */ + while( i < (int)SX_ARRAYSIZE(apSet) ){ + if( pLink == 0 ){ + break; + } + if( (int)SySetUsed(&pLink->aArgs) == nArg ){ + /* Candidate for overloading */ + apSet[i++] = pLink; + } + /* Point to the next entry */ + pLink = pLink->pNextName; + } + if( i < 1 ){ + /* No candidates,return the head of the list */ + return pList; + } + if( nArg < 1 || i < 2 ){ + /* Return the only candidate */ + return apSet[0]; + } + /* Calculate function signature */ + SyBlobInit(&sSig,&pVm->sAllocator); + for( j = 0 ; j < nArg ; j++ ){ + int c = 'n'; /* null */ + if( aArg[j].iFlags & MEMOBJ_HASHMAP ){ + /* Hashmap */ + c = 'h'; + }else if( aArg[j].iFlags & MEMOBJ_BOOL ){ + /* bool */ + c = 'b'; + }else if( aArg[j].iFlags & MEMOBJ_INT ){ + /* int */ + c = 'i'; + }else if( aArg[j].iFlags & MEMOBJ_STRING ){ + /* String */ + c = 's'; + }else if( aArg[j].iFlags & MEMOBJ_REAL ){ + /* Float */ + c = 'f'; + }else if( aArg[j].iFlags & MEMOBJ_OBJ ){ + /* Class instance */ + ph7_class *pClass = ((ph7_class_instance *)aArg[j].x.pOther)->pClass; + SyString *pName = &pClass->sName; + SyBlobAppend(&sSig,(const void *)pName->zString,pName->nByte); + c = -1; + } + if( c > 0 ){ + SyBlobAppend(&sSig,(const void *)&c,sizeof(char)); + } + } + SyStringInitFromBuf(&sArgSig,SyBlobData(&sSig),SyBlobLength(&sSig)); + iTarget = 0; + iMax = -1; + /* Select the appropriate function */ + for( j = 0 ; j < i ; j++ ){ + /* Compare the two signatures */ + iCur = VmOverloadCompare(&sArgSig,&apSet[j]->sSignature); + if( iCur > iMax ){ + iMax = iCur; + iTarget = j; + } + } + SyBlobRelease(&sSig); + /* Appropriate function for the current call context */ + return apSet[iTarget]; +} +/* Forward declaration */ +static sxi32 VmLocalExec(ph7_vm *pVm,SySet *pByteCode,ph7_value *pResult); +static sxi32 VmErrorFormat(ph7_vm *pVm,sxi32 iErr,const char *zFormat,...); +/* + * Mount a compiled class into the freshly created vitual machine so that + * it can be instanciated from the executed PHP script. + */ +static sxi32 VmMountUserClass( + ph7_vm *pVm, /* Target VM */ + ph7_class *pClass /* Class to be mounted */ + ) +{ + ph7_class_method *pMeth; + ph7_class_attr *pAttr; + SyHashEntry *pEntry; + sxi32 rc; + /* Reset the loop cursor */ + SyHashResetLoopCursor(&pClass->hAttr); + /* Process only static and constant attribute */ + while( (pEntry = SyHashGetNextEntry(&pClass->hAttr)) != 0 ){ + /* Extract the current attribute */ + pAttr = (ph7_class_attr *)pEntry->pUserData; + if( pAttr->iFlags & (PH7_CLASS_ATTR_CONSTANT|PH7_CLASS_ATTR_STATIC) ){ + ph7_value *pMemObj; + /* Reserve a memory object for this constant/static attribute */ + pMemObj = PH7_ReserveMemObj(&(*pVm)); + if( pMemObj == 0 ){ + VmErrorFormat(&(*pVm),PH7_CTX_ERR, + "Cannot reserve a memory object for class attribute '%z->%z' due to a memory failure", + &pClass->sName,&pAttr->sName + ); + return SXERR_MEM; + } + if( SySetUsed(&pAttr->aByteCode) > 0 ){ + /* Initialize attribute default value (any complex expression) */ + VmLocalExec(&(*pVm),&pAttr->aByteCode,pMemObj); + } + /* Record attribute index */ + pAttr->nIdx = pMemObj->nIdx; + /* Install static attribute in the reference table */ + PH7_VmRefObjInstall(&(*pVm),pMemObj->nIdx,0,0,VM_REF_IDX_KEEP); + } + } + /* Install class methods */ + if( pClass->iFlags & PH7_CLASS_INTERFACE ){ + /* Do not mount interface methods since they are signatures only. + */ + return SXRET_OK; + } + /* Create constructor alias if not yet done */ + if( SyHashGet(&pClass->hMethod,"__construct",sizeof("__construct")-1) == 0 ){ + /* User constructor with the same base class name */ + pEntry = SyHashGet(&pClass->hMethod,SyStringData(&pClass->sName),SyStringLength(&pClass->sName)); + if( pEntry ){ + pMeth = (ph7_class_method *)pEntry->pUserData; + /* Create the alias */ + SyHashInsert(&pClass->hMethod,"__construct",sizeof("__construct")-1,pMeth); + } + } + /* Install the methods now */ + SyHashResetLoopCursor(&pClass->hMethod); + while((pEntry = SyHashGetNextEntry(&pClass->hMethod)) != 0 ){ + pMeth = (ph7_class_method *)pEntry->pUserData; + if( (pMeth->iFlags & PH7_CLASS_ATTR_ABSTRACT) == 0 ){ + rc = PH7_VmInstallUserFunction(&(*pVm),&pMeth->sFunc,&pMeth->sVmName); + if( rc != SXRET_OK ){ + return rc; + } + } + } + return SXRET_OK; +} +/* + * Allocate a private frame for attributes of the given + * class instance (Object in the PHP jargon). + */ +PH7_PRIVATE sxi32 PH7_VmCreateClassInstanceFrame( + ph7_vm *pVm, /* Target VM */ + ph7_class_instance *pObj /* Class instance */ + ) +{ + ph7_class *pClass = pObj->pClass; + ph7_class_attr *pAttr; + SyHashEntry *pEntry; + sxi32 rc; + /* Install class attribute in the private frame associated with this instance */ + SyHashResetLoopCursor(&pClass->hAttr); + while( (pEntry = SyHashGetNextEntry(&pClass->hAttr)) != 0 ){ + VmClassAttr *pVmAttr; + /* Extract the current attribute */ + pAttr = (ph7_class_attr *)pEntry->pUserData; + pVmAttr = (VmClassAttr *)SyMemBackendPoolAlloc(&pVm->sAllocator,sizeof(VmClassAttr)); + if( pVmAttr == 0 ){ + return SXERR_MEM; + } + pVmAttr->pAttr = pAttr; + if( (pAttr->iFlags & (PH7_CLASS_ATTR_CONSTANT|PH7_CLASS_ATTR_STATIC)) == 0 ){ + ph7_value *pMemObj; + /* Reserve a memory object for this attribute */ + pMemObj = PH7_ReserveMemObj(&(*pVm)); + if( pMemObj == 0 ){ + SyMemBackendPoolFree(&pVm->sAllocator,pVmAttr); + return SXERR_MEM; + } + pVmAttr->nIdx = pMemObj->nIdx; + if( SySetUsed(&pAttr->aByteCode) > 0 ){ + /* Initialize attribute default value (any complex expression) */ + VmLocalExec(&(*pVm),&pAttr->aByteCode,pMemObj); + } + rc = SyHashInsert(&pObj->hAttr,SyStringData(&pAttr->sName),SyStringLength(&pAttr->sName),pVmAttr); + if( rc != SXRET_OK ){ + VmSlot sSlot; + /* Restore memory object */ + sSlot.nIdx = pMemObj->nIdx; + sSlot.pUserData = 0; + SySetPut(&pVm->aFreeObj,(const void *)&sSlot); + SyMemBackendPoolFree(&pVm->sAllocator,pVmAttr); + return SXERR_MEM; + } + /* Install attribute in the reference table */ + PH7_VmRefObjInstall(&(*pVm),pMemObj->nIdx,0,0,VM_REF_IDX_KEEP); + }else{ + /* Install static/constant attribute */ + pVmAttr->nIdx = pAttr->nIdx; + rc = SyHashInsert(&pObj->hAttr,SyStringData(&pAttr->sName),SyStringLength(&pAttr->sName),pVmAttr); + if( rc != SXRET_OK ){ + SyMemBackendPoolFree(&pVm->sAllocator,pVmAttr); + return SXERR_MEM; + } + } + } + return SXRET_OK; +} +/* Forward declaration */ +static VmRefObj * VmRefObjExtract(ph7_vm *pVm,sxu32 nObjIdx); +static sxi32 VmRefObjUnlink(ph7_vm *pVm,VmRefObj *pRef); +/* + * Dummy read-only buffer used for slot reservation. + */ +static const char zDummy[sizeof(ph7_value)] = { 0 }; /* Must be >= sizeof(ph7_value) */ +/* + * Reserve a constant memory object. + * Return a pointer to the raw ph7_value on success. NULL on failure. + */ +PH7_PRIVATE ph7_value * PH7_ReserveConstObj(ph7_vm *pVm,sxu32 *pIndex) +{ + ph7_value *pObj; + sxi32 rc; + if( pIndex ){ + /* Object index in the object table */ + *pIndex = SySetUsed(&pVm->aLitObj); + } + /* Reserve a slot for the new object */ + rc = SySetPut(&pVm->aLitObj,(const void *)zDummy); + if( rc != SXRET_OK ){ + /* If the supplied memory subsystem is so sick that we are unable to allocate + * a tiny chunk of memory, there is no much we can do here. + */ + return 0; + } + pObj = (ph7_value *)SySetPeek(&pVm->aLitObj); + return pObj; +} +/* + * Reserve a memory object. + * Return a pointer to the raw ph7_value on success. NULL on failure. + */ +PH7_PRIVATE ph7_value * VmReserveMemObj(ph7_vm *pVm,sxu32 *pIndex) +{ + ph7_value *pObj; + sxi32 rc; + if( pIndex ){ + /* Object index in the object table */ + *pIndex = SySetUsed(&pVm->aMemObj); + } + /* Reserve a slot for the new object */ + rc = SySetPut(&pVm->aMemObj,(const void *)zDummy); + if( rc != SXRET_OK ){ + /* If the supplied memory subsystem is so sick that we are unable to allocate + * a tiny chunk of memory, there is no much we can do here. + */ + return 0; + } + pObj = (ph7_value *)SySetPeek(&pVm->aMemObj); + return pObj; +} +/* Forward declaration */ +static sxi32 VmEvalChunk(ph7_vm *pVm,ph7_context *pCtx,SyString *pChunk,int iFlags,int bTrueReturn); +/* + * Built-in classes/interfaces and some functions that cannot be implemented + * directly as foreign functions. + */ +#define PH7_BUILTIN_LIB \ + "class Exception { "\ + "protected $message = 'Unknown exception';"\ + "protected $code = 0;"\ + "protected $file;"\ + "protected $line;"\ + "protected $trace;"\ + "protected $previous;"\ + "public function __construct($message = null, $code = 0, Exception $previous = null){"\ + " if( isset($message) ){"\ + " $this->message = $message;"\ + " }"\ + " $this->code = $code;"\ + " $this->file = __FILE__;"\ + " $this->line = __LINE__;"\ + " $this->trace = debug_backtrace();"\ + " if( isset($previous) ){"\ + " $this->previous = $previous;"\ + " }"\ + "}"\ + "public function getMessage(){"\ + " return $this->message;"\ + "}"\ + " public function getCode(){"\ + " return $this->code;"\ + "}"\ + "public function getFile(){"\ + " return $this->file;"\ + "}"\ + "public function getLine(){"\ + " return $this->line;"\ + "}"\ + "public function getTrace(){"\ + " return $this->trace;"\ + "}"\ + "public function getTraceAsString(){"\ + " return debug_string_backtrace();"\ + "}"\ + "public function getPrevious(){"\ + " return $this->previous;"\ + "}"\ + "public function __toString(){"\ + " return $this->file.' '.$this->line.' '.$this->code.' '.$this->message;"\ + "}"\ + "}"\ + "class ErrorException extends Exception { "\ + "protected $severity;"\ + "public function __construct(string $message = null,"\ + "int $code = 0,int $severity = 1,string $filename = __FILE__ ,int $lineno = __LINE__ ,Exception $previous = null){"\ + " if( isset($message) ){"\ + " $this->message = $message;"\ + " }"\ + " $this->severity = $severity;"\ + " $this->code = $code;"\ + " $this->file = $filename;"\ + " $this->line = $lineno;"\ + " $this->trace = debug_backtrace();"\ + " if( isset($previous) ){"\ + " $this->previous = $previous;"\ + " }"\ + "}"\ + "public function getSeverity(){"\ + " return $this->severity;"\ + "}"\ + "}"\ + "interface Iterator {"\ + "public function current();"\ + "public function key();"\ + "public function next();"\ + "public function rewind();"\ + "public function valid();"\ + "}"\ + "interface IteratorAggregate {"\ + "public function getIterator();"\ + "}"\ + "interface Serializable {"\ + "public function serialize();"\ + "public function unserialize(string $serialized);"\ + "}"\ + "/* Directory releated IO */"\ + "class Directory {"\ + "public $handle = null;"\ + "public $path = null;"\ + "public function __construct(string $path)"\ + "{"\ + " $this->handle = opendir($path);"\ + " if( $this->handle !== FALSE ){"\ + " $this->path = $path;"\ + " }"\ + "}"\ + "public function __destruct()"\ + "{"\ + " if( $this->handle != null ){"\ + " closedir($this->handle);"\ + " }"\ + "}"\ + "public function read()"\ + "{"\ + " return readdir($this->handle);"\ + "}"\ + "public function rewind()"\ + "{"\ + " rewinddir($this->handle);"\ + "}"\ + "public function close()"\ + "{"\ + " closedir($this->handle);"\ + " $this->handle = null;"\ + "}"\ + "}"\ + "class stdClass{"\ + " public $value;"\ + " /* Magic methods */"\ + " public function __toInt(){ return (int)$this->value; }"\ + " public function __toBool(){ return (bool)$this->value; }"\ + " public function __toFloat(){ return (float)$this->value; }"\ + " public function __toString(){ return (string)$this->value; }"\ + " function __construct($v){ $this->value = $v; }"\ + "}"\ + "function dir(string $path){"\ + " return new Directory($path);"\ + "}"\ + "function Dir(string $path){"\ + " return new Directory($path);"\ + "}"\ + "function scandir(string $directory,int $sort_order = SCANDIR_SORT_ASCENDING)"\ + "{"\ + " if( func_num_args() < 1 ){ return FALSE; }"\ + " $aDir = array();"\ + " $pHandle = opendir($directory);"\ + " if( $pHandle == FALSE ){ return FALSE; }"\ + " while(FALSE !== ($pEntry = readdir($pHandle)) ){"\ + " $aDir[] = $pEntry;"\ + " }"\ + " closedir($pHandle);"\ + " if( $sort_order == SCANDIR_SORT_DESCENDING ){"\ + " rsort($aDir);"\ + " }else if( $sort_order == SCANDIR_SORT_ASCENDING ){"\ + " sort($aDir);"\ + " }"\ + " return $aDir;"\ + "}"\ + "function glob(string $pattern,int $iFlags = 0){"\ + "/* Open the target directory */"\ + "$zDir = dirname($pattern);"\ + "if(!is_string($zDir) ){ $zDir = './'; }"\ + "$pHandle = opendir($zDir);"\ + "if( $pHandle == FALSE ){"\ + " /* IO error while opening the current directory,return FALSE */"\ + " return FALSE;"\ + "}"\ + "$pattern = basename($pattern);"\ + "$pArray = array(); /* Empty array */"\ + "/* Loop throw available entries */"\ + "while( FALSE !== ($pEntry = readdir($pHandle)) ){"\ + " /* Use the built-in strglob function which is a Symisc eXtension for wildcard comparison*/"\ + " $rc = strglob($pattern,$pEntry);"\ + " if( $rc ){"\ + " if( is_dir($pEntry) ){"\ + " if( $iFlags & GLOB_MARK ){"\ + " /* Adds a slash to each directory returned */"\ + " $pEntry .= DIRECTORY_SEPARATOR;"\ + " }"\ + " }else if( $iFlags & GLOB_ONLYDIR ){"\ + " /* Not a directory,ignore */"\ + " continue;"\ + " }"\ + " /* Add the entry */"\ + " $pArray[] = $pEntry;"\ + " }"\ + " }"\ + "/* Close the handle */"\ + "closedir($pHandle);"\ + "if( ($iFlags & GLOB_NOSORT) == 0 ){"\ + " /* Sort the array */"\ + " sort($pArray);"\ + "}"\ + "if( ($iFlags & GLOB_NOCHECK) && sizeof($pArray) < 1 ){"\ + " /* Return the search pattern if no files matching were found */"\ + " $pArray[] = $pattern;"\ + "}"\ + "/* Return the created array */"\ + "return $pArray;"\ + "}"\ + "/* Creates a temporary file */"\ + "function tmpfile(){"\ + " /* Extract the temp directory */"\ + " $zTempDir = sys_get_temp_dir();"\ + " if( strlen($zTempDir) < 1 ){"\ + " /* Use the current dir */"\ + " $zTempDir = '.';"\ + " }"\ + " /* Create the file */"\ + " $pHandle = fopen($zTempDir.DIRECTORY_SEPARATOR.'PH7'.rand_str(12),'w+');"\ + " return $pHandle;"\ + "}"\ + "/* Creates a temporary filename */"\ + "function tempnam(string $zDir = sys_get_temp_dir() /* Symisc eXtension */,string $zPrefix = 'PH7')"\ + "{"\ + " return $zDir.DIRECTORY_SEPARATOR.$zPrefix.rand_str(12);"\ + "}"\ + "function array_unshift(&$pArray ){"\ + " if( func_num_args() < 1 || !is_array($pArray) ){ return 0; }"\ + "/* Copy arguments */"\ + "$nArgs = func_num_args();"\ + "$pNew = array();"\ + "for( $i = 1 ; $i < $nArgs ; ++$i ){"\ + " $pNew[] = func_get_arg($i);"\ + "}"\ + "/* Make a copy of the old entries */"\ + "$pOld = array_copy($pArray);"\ + "/* Erase */"\ + "array_erase($pArray);"\ + "/* Unshift */"\ + "$pArray = array_merge($pNew,$pOld);"\ + "return sizeof($pArray);"\ + "}"\ + "function array_merge_recursive($array1, $array2){"\ + "if( func_num_args() < 1 ){ return NULL; }"\ + "$arrays = func_get_args();"\ + "$narrays = count($arrays);"\ + "$ret = $arrays[0];"\ + "for ($i = 1; $i < $narrays; $i++) {"\ + " if( array_same($ret,$arrays[$i]) ){ /* Same instance */continue;}"\ + " foreach ($arrays[$i] as $key => $value) {"\ + " if (((string) $key) === ((string) intval($key))) {"\ + " $ret[] = $value;"\ + " }else{"\ + " if (is_array($value) && isset($ret[$key]) ) {"\ + " $ret[$key] = array_merge_recursive($ret[$key], $value);"\ + " }else {"\ + " $ret[$key] = $value;"\ + " }"\ + " }"\ + " }"\ + "}"\ + " return $ret;"\ + "}"\ + "function max(){"\ + " $pArgs = func_get_args();"\ + " if( sizeof($pArgs) < 1 ){"\ + " return null;"\ + " }"\ + " if( sizeof($pArgs) < 2 ){"\ + " $pArg = $pArgs[0];"\ + " if( !is_array($pArg) ){"\ + " return $pArg; "\ + " }"\ + " if( sizeof($pArg) < 1 ){"\ + " return null;"\ + " }"\ + " $pArg = array_copy($pArgs[0]);"\ + " reset($pArg);"\ + " $max = current($pArg);"\ + " while( FALSE !== ($val = next($pArg)) ){"\ + " if( $val > $max ){"\ + " $max = $val;"\ + " }"\ + " }"\ + " return $max;"\ + " }"\ + " $max = $pArgs[0];"\ + " for( $i = 1; $i < sizeof($pArgs) ; ++$i ){"\ + " $val = $pArgs[$i];"\ + "if( $val > $max ){"\ + " $max = $val;"\ + "}"\ + " }"\ + " return $max;"\ + "}"\ + "function min(){"\ + " $pArgs = func_get_args();"\ + " if( sizeof($pArgs) < 1 ){"\ + " return null;"\ + " }"\ + " if( sizeof($pArgs) < 2 ){"\ + " $pArg = $pArgs[0];"\ + " if( !is_array($pArg) ){"\ + " return $pArg; "\ + " }"\ + " if( sizeof($pArg) < 1 ){"\ + " return null;"\ + " }"\ + " $pArg = array_copy($pArgs[0]);"\ + " reset($pArg);"\ + " $min = current($pArg);"\ + " while( FALSE !== ($val = next($pArg)) ){"\ + " if( $val < $min ){"\ + " $min = $val;"\ + " }"\ + " }"\ + " return $min;"\ + " }"\ + " $min = $pArgs[0];"\ + " for( $i = 1; $i < sizeof($pArgs) ; ++$i ){"\ + " $val = $pArgs[$i];"\ + "if( $val < $min ){"\ + " $min = $val;"\ + " }"\ + " }"\ + " return $min;"\ + "}"\ + "function fileowner(string $file){"\ + " $a = stat($file);"\ + " if( !is_array($a) ){"\ + " return false;"\ + " }"\ + " return $a['uid'];"\ + "}"\ + "function filegroup(string $file){"\ + " $a = stat($file);"\ + " if( !is_array($a) ){"\ + " return false;"\ + " }"\ + " return $a['gid'];"\ + "}"\ + "function fileinode(string $file){"\ + " $a = stat($file);"\ + " if( !is_array($a) ){"\ + " return false;"\ + " }"\ + " return $a['ino'];"\ + "}" + +/* + * Initialize a freshly allocated PH7 Virtual Machine so that we can + * start compiling the target PHP program. + */ +PH7_PRIVATE sxi32 PH7_VmInit( + ph7_vm *pVm, /* Initialize this */ + ph7 *pEngine /* Master engine */ + ) +{ + SyString sBuiltin; + ph7_value *pObj; + sxi32 rc; + /* Zero the structure */ + SyZero(pVm,sizeof(ph7_vm)); + /* Initialize VM fields */ + pVm->pEngine = &(*pEngine); + SyMemBackendInitFromParent(&pVm->sAllocator,&pEngine->sAllocator); + /* Instructions containers */ + SySetInit(&pVm->aByteCode,&pVm->sAllocator,sizeof(VmInstr)); + SySetAlloc(&pVm->aByteCode,0xFF); + pVm->pByteContainer = &pVm->aByteCode; + /* Object containers */ + SySetInit(&pVm->aMemObj,&pVm->sAllocator,sizeof(ph7_value)); + SySetAlloc(&pVm->aMemObj,0xFF); + /* Virtual machine internal containers */ + SyBlobInit(&pVm->sConsumer,&pVm->sAllocator); + SyBlobInit(&pVm->sWorker,&pVm->sAllocator); + SyBlobInit(&pVm->sArgv,&pVm->sAllocator); + SySetInit(&pVm->aLitObj,&pVm->sAllocator,sizeof(ph7_value)); + SySetAlloc(&pVm->aLitObj,0xFF); + SyHashInit(&pVm->hHostFunction,&pVm->sAllocator,0,0); + SyHashInit(&pVm->hFunction,&pVm->sAllocator,0,0); + SyHashInit(&pVm->hClass,&pVm->sAllocator,SyStrHash,SyStrnmicmp); + SyHashInit(&pVm->hConstant,&pVm->sAllocator,0,0); + SyHashInit(&pVm->hSuper,&pVm->sAllocator,0,0); + SyHashInit(&pVm->hPDO,&pVm->sAllocator,0,0); + SySetInit(&pVm->aFreeObj,&pVm->sAllocator,sizeof(VmSlot)); + SySetInit(&pVm->aSelf,&pVm->sAllocator,sizeof(ph7_class *)); + SySetInit(&pVm->aShutdown,&pVm->sAllocator,sizeof(VmShutdownCB)); + SySetInit(&pVm->aException,&pVm->sAllocator,sizeof(ph7_exception *)); + /* Configuration containers */ + SySetInit(&pVm->aFiles,&pVm->sAllocator,sizeof(SyString)); + SySetInit(&pVm->aPaths,&pVm->sAllocator,sizeof(SyString)); + SySetInit(&pVm->aIncluded,&pVm->sAllocator,sizeof(SyString)); + SySetInit(&pVm->aOB,&pVm->sAllocator,sizeof(VmObEntry)); + SySetInit(&pVm->aIOstream,&pVm->sAllocator,sizeof(ph7_io_stream *)); + /* Error callbacks containers */ + PH7_MemObjInit(&(*pVm),&pVm->aExceptionCB[0]); + PH7_MemObjInit(&(*pVm),&pVm->aExceptionCB[1]); + PH7_MemObjInit(&(*pVm),&pVm->aErrCB[0]); + PH7_MemObjInit(&(*pVm),&pVm->aErrCB[1]); + PH7_MemObjInit(&(*pVm),&pVm->sAssertCallback); + /* Set a default recursion limit */ +#if defined(__WINNT__) || defined(__UNIXES__) + pVm->nMaxDepth = 32; +#else + pVm->nMaxDepth = 16; +#endif + /* Default assertion flags */ + pVm->iAssertFlags = PH7_ASSERT_WARNING; /* Issue a warning for each failed assertion */ + /* JSON return status */ + pVm->json_rc = JSON_ERROR_NONE; + /* PRNG context */ + SyRandomnessInit(&pVm->sPrng,0,0); + /* Install the null constant */ + pObj = PH7_ReserveConstObj(&(*pVm),0); + if( pObj == 0 ){ + rc = SXERR_MEM; + goto Err; + } + PH7_MemObjInit(pVm,pObj); + /* Install the boolean TRUE constant */ + pObj = PH7_ReserveConstObj(&(*pVm),0); + if( pObj == 0 ){ + rc = SXERR_MEM; + goto Err; + } + PH7_MemObjInitFromBool(pVm,pObj,1); + /* Install the boolean FALSE constant */ + pObj = PH7_ReserveConstObj(&(*pVm),0); + if( pObj == 0 ){ + rc = SXERR_MEM; + goto Err; + } + PH7_MemObjInitFromBool(pVm,pObj,0); + /* Create the global frame */ + rc = VmEnterFrame(&(*pVm),0,0,0); + if( rc != SXRET_OK ){ + goto Err; + } + /* Initialize the code generator */ + rc = PH7_InitCodeGenerator(pVm,pEngine->xConf.xErr,pEngine->xConf.pErrData); + if( rc != SXRET_OK ){ + goto Err; + } + /* VM correctly initialized,set the magic number */ + pVm->nMagic = PH7_VM_INIT; + SyStringInitFromBuf(&sBuiltin,PH7_BUILTIN_LIB,sizeof(PH7_BUILTIN_LIB)-1); + /* Compile the built-in library */ + VmEvalChunk(&(*pVm),0,&sBuiltin,PH7_PHP_ONLY,FALSE); + /* Reset the code generator */ + PH7_ResetCodeGenerator(&(*pVm),pEngine->xConf.xErr,pEngine->xConf.pErrData); + return SXRET_OK; +Err: + SyMemBackendRelease(&pVm->sAllocator); + return rc; +} +/* + * Default VM output consumer callback.That is,all VM output is redirected to this + * routine which store the output in an internal blob. + * The output can be extracted later after program execution [ph7_vm_exec()] via + * the [ph7_vm_config()] interface with a configuration verb set to + * PH7_VM_CONFIG_EXTRACT_OUTPUT. + * Refer to the official docurmentation for additional information. + * Note that for performance reason it's preferable to install a VM output + * consumer callback via (PH7_VM_CONFIG_OUTPUT) rather than waiting for the VM + * to finish executing and extracting the output. + */ +PH7_PRIVATE sxi32 PH7_VmBlobConsumer( + const void *pOut, /* VM Generated output*/ + unsigned int nLen, /* Generated output length */ + void *pUserData /* User private data */ + ) +{ + sxi32 rc; + /* Store the output in an internal BLOB */ + rc = SyBlobAppend((SyBlob *)pUserData,pOut,nLen); + return rc; +} +#define VM_STACK_GUARD 16 +/* + * Allocate a new operand stack so that we can start executing + * our compiled PHP program. + * Return a pointer to the operand stack (array of ph7_values) + * on success. NULL (Fatal error) on failure. + */ +static ph7_value * VmNewOperandStack( + ph7_vm *pVm, /* Target VM */ + sxu32 nInstr /* Total numer of generated byte-code instructions */ + ) +{ + ph7_value *pStack; + /* No instruction ever pushes more than a single element onto the + ** stack and the stack never grows on successive executions of the + ** same loop. So the total number of instructions is an upper bound + ** on the maximum stack depth required. + ** + ** Allocation all the stack space we will ever need. + */ + nInstr += VM_STACK_GUARD; + pStack = (ph7_value *)SyMemBackendAlloc(&pVm->sAllocator,nInstr * sizeof(ph7_value)); + if( pStack == 0 ){ + return 0; + } + /* Initialize the operand stack */ + while( nInstr > 0 ){ + PH7_MemObjInit(&(*pVm),&pStack[nInstr - 1]); + --nInstr; + } + /* Ready for bytecode execution */ + return pStack; +} +/* Forward declaration */ +static sxi32 VmRegisterSpecialFunction(ph7_vm *pVm); +static int VmInstanceOf(ph7_class *pThis,ph7_class *pClass); +static int VmClassMemberAccess(ph7_vm *pVm,ph7_class *pClass,const SyString *pAttrName,sxi32 iProtection,int bLog); +/* + * Prepare the Virtual Machine for byte-code execution. + * This routine gets called by the PH7 engine after + * successful compilation of the target PHP program. + */ +PH7_PRIVATE sxi32 PH7_VmMakeReady( + ph7_vm *pVm /* Target VM */ + ) +{ + SyHashEntry *pEntry; + sxi32 rc; + if( pVm->nMagic != PH7_VM_INIT ){ + /* Initialize your VM first */ + return SXERR_CORRUPT; + } + /* Mark the VM ready for byte-code execution */ + pVm->nMagic = PH7_VM_RUN; + /* Release the code generator now we have compiled our program */ + PH7_ResetCodeGenerator(pVm,0,0); + /* Emit the DONE instruction */ + rc = PH7_VmEmitInstr(&(*pVm),PH7_OP_DONE,0,0,0,0); + if( rc != SXRET_OK ){ + return SXERR_MEM; + } + /* Script return value */ + PH7_MemObjInit(&(*pVm),&pVm->sExec); /* Assume a NULL return value */ + /* Allocate a new operand stack */ + pVm->aOps = VmNewOperandStack(&(*pVm),SySetUsed(pVm->pByteContainer)); + if( pVm->aOps == 0 ){ + return SXERR_MEM; + } + /* Set the default VM output consumer callback and it's + * private data. */ + pVm->sVmConsumer.xConsumer = PH7_VmBlobConsumer; + pVm->sVmConsumer.pUserData = &pVm->sConsumer; + /* Allocate the reference table */ + pVm->nRefSize = 0x10; /* Must be a power of two for fast arithemtic */ + pVm->apRefObj = (VmRefObj **)SyMemBackendAlloc(&pVm->sAllocator,sizeof(VmRefObj *) * pVm->nRefSize); + if( pVm->apRefObj == 0 ){ + /* Don't worry about freeing memory, everything will be released shortly */ + return SXERR_MEM; + } + /* Zero the reference table */ + SyZero(pVm->apRefObj,sizeof(VmRefObj *) * pVm->nRefSize); + /* Register special functions first [i.e: print, json_encode(), func_get_args(), die, etc.] */ + rc = VmRegisterSpecialFunction(&(*pVm)); + if( rc != SXRET_OK ){ + /* Don't worry about freeing memory, everything will be released shortly */ + return rc; + } + /* Create superglobals [i.e: $GLOBALS, $_GET, $_POST...] */ + rc = PH7_HashmapCreateSuper(&(*pVm)); + if( rc != SXRET_OK ){ + /* Don't worry about freeing memory, everything will be released shortly */ + return rc; + } + /* Register built-in constants [i.e: PHP_EOL, PHP_OS...] */ + PH7_RegisterBuiltInConstant(&(*pVm)); + /* Register built-in functions [i.e: is_null(), array_diff(), strlen(), etc.] */ + PH7_RegisterBuiltInFunction(&(*pVm)); + /* Initialize and install static and constants class attributes */ + SyHashResetLoopCursor(&pVm->hClass); + while((pEntry = SyHashGetNextEntry(&pVm->hClass)) != 0 ){ + rc = VmMountUserClass(&(*pVm),(ph7_class *)pEntry->pUserData); + if( rc != SXRET_OK ){ + return rc; + } + } + /* Random number betwwen 0 and 1023 used to generate unique ID */ + pVm->unique_id = PH7_VmRandomNum(&(*pVm)) & 1023; + /* VM is ready for bytecode execution */ + return SXRET_OK; +} +/* + * Reset a Virtual Machine to it's initial state. + */ +PH7_PRIVATE sxi32 PH7_VmReset(ph7_vm *pVm) +{ + if( pVm->nMagic != PH7_VM_RUN && pVm->nMagic != PH7_VM_EXEC ){ + return SXERR_CORRUPT; + } + /* TICKET 1433-003: As of this version, the VM is automatically reset */ + SyBlobReset(&pVm->sConsumer); + PH7_MemObjRelease(&pVm->sExec); + /* Set the ready flag */ + pVm->nMagic = PH7_VM_RUN; + return SXRET_OK; +} +/* + * Release a Virtual Machine. + * Every virtual machine must be destroyed in order to avoid memory leaks. + */ +PH7_PRIVATE sxi32 PH7_VmRelease(ph7_vm *pVm) +{ + /* Set the stale magic number */ + pVm->nMagic = PH7_VM_STALE; + /* Release the private memory subsystem */ + SyMemBackendRelease(&pVm->sAllocator); + return SXRET_OK; +} +/* + * Initialize a foreign function call context. + * The context in which a foreign function executes is stored in a ph7_context object. + * A pointer to a ph7_context object is always first parameter to application-defined foreign + * functions. + * The application-defined foreign function implementation will pass this pointer through into + * calls to dozens of interfaces,these includes ph7_result_int(), ph7_result_string(), ph7_result_value(), + * ph7_context_new_scalar(), ph7_context_alloc_chunk(), ph7_context_output(), ph7_context_throw_error() + * and many more. Refer to the C/C++ Interfaces documentation for additional information. + */ +static sxi32 VmInitCallContext( + ph7_context *pOut, /* Call Context */ + ph7_vm *pVm, /* Target VM */ + ph7_user_func *pFunc, /* Foreign function to execute shortly */ + ph7_value *pRet, /* Store return value here*/ + sxi32 iFlags /* Control flags */ + ) +{ + pOut->pFunc = pFunc; + pOut->pVm = pVm; + SySetInit(&pOut->sVar,&pVm->sAllocator,sizeof(ph7_value *)); + SySetInit(&pOut->sChunk,&pVm->sAllocator,sizeof(ph7_aux_data)); + /* Assume a null return value */ + MemObjSetType(pRet,MEMOBJ_NULL); + pOut->pRet = pRet; + pOut->iFlags = iFlags; + return SXRET_OK; +} +/* + * Release a foreign function call context and cleanup the mess + * left behind. + */ +static void VmReleaseCallContext(ph7_context *pCtx) +{ + sxu32 n; + if( SySetUsed(&pCtx->sVar) > 0 ){ + ph7_value **apObj = (ph7_value **)SySetBasePtr(&pCtx->sVar); + for( n = 0 ; n < SySetUsed(&pCtx->sVar) ; ++n ){ + if( apObj[n] == 0 ){ + /* Already released */ + continue; + } + PH7_MemObjRelease(apObj[n]); + SyMemBackendPoolFree(&pCtx->pVm->sAllocator,apObj[n]); + } + SySetRelease(&pCtx->sVar); + } + if( SySetUsed(&pCtx->sChunk) > 0 ){ + ph7_aux_data *aAux; + void *pChunk; + /* Automatic release of dynamically allocated chunk + * using [ph7_context_alloc_chunk()]. + */ + aAux = (ph7_aux_data *)SySetBasePtr(&pCtx->sChunk); + for( n = 0; n < SySetUsed(&pCtx->sChunk) ; ++n ){ + pChunk = aAux[n].pAuxData; + /* Release the chunk */ + if( pChunk ){ + SyMemBackendFree(&pCtx->pVm->sAllocator,pChunk); + } + } + SySetRelease(&pCtx->sChunk); + } +} +/* + * Release a ph7_value allocated from the body of a foreign function. + * Refer to [ph7_context_release_value()] for additional information. + */ +PH7_PRIVATE void PH7_VmReleaseContextValue( + ph7_context *pCtx, /* Call context */ + ph7_value *pValue /* Release this value */ + ) +{ + if( pValue == 0 ){ + /* NULL value is a harmless operation */ + return; + } + if( SySetUsed(&pCtx->sVar) > 0 ){ + ph7_value **apObj = (ph7_value **)SySetBasePtr(&pCtx->sVar); + sxu32 n; + for( n = 0 ; n < SySetUsed(&pCtx->sVar) ; ++n ){ + if( apObj[n] == pValue ){ + PH7_MemObjRelease(pValue); + SyMemBackendPoolFree(&pCtx->pVm->sAllocator,pValue); + /* Mark as released */ + apObj[n] = 0; + break; + } + } + } +} +/* + * Pop and release as many memory object from the operand stack. + */ +static void VmPopOperand( + ph7_value **ppTos, /* Operand stack */ + sxi32 nPop /* Total number of memory objects to pop */ + ) +{ + ph7_value *pTos = *ppTos; + while( nPop > 0 ){ + PH7_MemObjRelease(pTos); + pTos--; + nPop--; + } + /* Top of the stack */ + *ppTos = pTos; +} +/* + * Reserve a memory object. + * Return a pointer to the raw ph7_value on success. NULL on failure. + */ +PH7_PRIVATE ph7_value * PH7_ReserveMemObj(ph7_vm *pVm) +{ + ph7_value *pObj = 0; + VmSlot *pSlot; + sxu32 nIdx; + /* Check for a free slot */ + nIdx = SXU32_HIGH; /* cc warning */ + pSlot = (VmSlot *)SySetPop(&pVm->aFreeObj); + if( pSlot ){ + pObj = (ph7_value *)SySetAt(&pVm->aMemObj,pSlot->nIdx); + nIdx = pSlot->nIdx; + } + if( pObj == 0 ){ + /* Reserve a new memory object */ + pObj = VmReserveMemObj(&(*pVm),&nIdx); + if( pObj == 0 ){ + return 0; + } + } + /* Set a null default value */ + PH7_MemObjInit(&(*pVm),pObj); + pObj->nIdx = nIdx; + return pObj; +} +/* + * Insert an entry by reference (not copy) in the given hashmap. + */ +static sxi32 VmHashmapRefInsert( + ph7_hashmap *pMap, /* Target hashmap */ + const char *zKey, /* Entry key */ + sxu32 nByte, /* Key length */ + sxu32 nRefIdx /* Entry index in the object pool */ + ) +{ + ph7_value sKey; + sxi32 rc; + PH7_MemObjInitFromString(pMap->pVm,&sKey,0); + PH7_MemObjStringAppend(&sKey,zKey,nByte); + /* Perform the insertion */ + rc = PH7_HashmapInsertByRef(&(*pMap),&sKey,nRefIdx); + PH7_MemObjRelease(&sKey); + return rc; +} +/* + * Extract a variable value from the top active VM frame. + * Return a pointer to the variable value on success. + * NULL otherwise (non-existent variable/Out-of-memory,...). + */ +static ph7_value * VmExtractMemObj( + ph7_vm *pVm, /* Target VM */ + const SyString *pName, /* Variable name */ + int bDup, /* True to duplicate variable name */ + int bCreate /* True to create the variable if non-existent */ + ) +{ + int bNullify = FALSE; + SyHashEntry *pEntry; + VmFrame *pFrame; + ph7_value *pObj; + sxu32 nIdx; + sxi32 rc; + /* Point to the top active frame */ + pFrame = pVm->pFrame; + while( pFrame->pParent && (pFrame->iFlags & VM_FRAME_EXCEPTION) ){ + /* Safely ignore the exception frame */ + pFrame = pFrame->pParent; /* Parent frame */ + } + /* Perform the lookup */ + if( pName == 0 || pName->nByte < 1 ){ + static const SyString sAnnon = { " " , sizeof(char) }; + pName = &sAnnon; + /* Always nullify the object */ + bNullify = TRUE; + bDup = FALSE; + } + /* Check the superglobals table first */ + pEntry = SyHashGet(&pVm->hSuper,(const void *)pName->zString,pName->nByte); + if( pEntry == 0 ){ + /* Query the top active frame */ + pEntry = SyHashGet(&pFrame->hVar,(const void *)pName->zString,pName->nByte); + if( pEntry == 0 ){ + char *zName = (char *)pName->zString; + VmSlot sLocal; + if( !bCreate ){ + /* Do not create the variable,return NULL instead */ + return 0; + } + /* No such variable,automatically create a new one and install + * it in the current frame. + */ + pObj = PH7_ReserveMemObj(&(*pVm)); + if( pObj == 0 ){ + return 0; + } + nIdx = pObj->nIdx; + if( bDup ){ + /* Duplicate name */ + zName = SyMemBackendStrDup(&pVm->sAllocator,pName->zString,pName->nByte); + if( zName == 0 ){ + return 0; + } + } + /* Link to the top active VM frame */ + rc = SyHashInsert(&pFrame->hVar,zName,pName->nByte,SX_INT_TO_PTR(nIdx)); + if( rc != SXRET_OK ){ + /* Return the slot to the free pool */ + sLocal.nIdx = nIdx; + sLocal.pUserData = 0; + SySetPut(&pVm->aFreeObj,(const void *)&sLocal); + return 0; + } + if( pFrame->pParent != 0 ){ + /* Local variable */ + sLocal.nIdx = nIdx; + SySetPut(&pFrame->sLocal,(const void *)&sLocal); + }else{ + /* Register in the $GLOBALS array */ + VmHashmapRefInsert(pVm->pGlobal,pName->zString,pName->nByte,nIdx); + } + /* Install in the reference table */ + PH7_VmRefObjInstall(&(*pVm),nIdx,SyHashLastEntry(&pFrame->hVar),0,0); + /* Save object index */ + pObj->nIdx = nIdx; + }else{ + /* Extract variable contents */ + nIdx = (sxu32)SX_PTR_TO_INT(pEntry->pUserData); + pObj = (ph7_value *)SySetAt(&pVm->aMemObj,nIdx); + if( bNullify && pObj ){ + PH7_MemObjRelease(pObj); + } + } + }else{ + /* Superglobal */ + nIdx = (sxu32)SX_PTR_TO_INT(pEntry->pUserData); + pObj = (ph7_value *)SySetAt(&pVm->aMemObj,nIdx); + } + return pObj; +} +/* + * Extract a superglobal variable such as $_GET,$_POST,$_HEADERS,.... + * Return a pointer to the variable value on success.NULL otherwise. + */ +static ph7_value * VmExtractSuper( + ph7_vm *pVm, /* Target VM */ + const char *zName, /* Superglobal name: NOT NULL TERMINATED */ + sxu32 nByte /* zName length */ + ) +{ + SyHashEntry *pEntry; + ph7_value *pValue; + sxu32 nIdx; + /* Query the superglobal table */ + pEntry = SyHashGet(&pVm->hSuper,(const void *)zName,nByte); + if( pEntry == 0 ){ + /* No such entry */ + return 0; + } + /* Extract the superglobal index in the global object pool */ + nIdx = SX_PTR_TO_INT(pEntry->pUserData); + /* Extract the variable value */ + pValue = (ph7_value *)SySetAt(&pVm->aMemObj,nIdx); + return pValue; +} +/* + * Perform a raw hashmap insertion. + * Refer to the [PH7_VmConfigure()] implementation for additional information. + */ +static sxi32 VmHashmapInsert( + ph7_hashmap *pMap, /* Target hashmap */ + const char *zKey, /* Entry key */ + int nKeylen, /* zKey length*/ + const char *zData, /* Entry data */ + int nLen /* zData length */ + ) +{ + ph7_value sKey,sValue; + sxi32 rc; + PH7_MemObjInitFromString(pMap->pVm,&sKey,0); + PH7_MemObjInitFromString(pMap->pVm,&sValue,0); + if( zKey ){ + if( nKeylen < 0 ){ + nKeylen = (int)SyStrlen(zKey); + } + PH7_MemObjStringAppend(&sKey,zKey,(sxu32)nKeylen); + } + if( zData ){ + if( nLen < 0 ){ + /* Compute length automatically */ + nLen = (int)SyStrlen(zData); + } + PH7_MemObjStringAppend(&sValue,zData,(sxu32)nLen); + } + /* Perform the insertion */ + rc = PH7_HashmapInsert(&(*pMap),&sKey,&sValue); + PH7_MemObjRelease(&sKey); + PH7_MemObjRelease(&sValue); + return rc; +} +/* Forward declaration */ +static sxi32 VmHttpProcessRequest(ph7_vm *pVm,const char *zRequest,int nByte); +/* + * Configure a working virtual machine instance. + * + * This routine is used to configure a PH7 virtual machine obtained by a prior + * successful call to one of the compile interface such as ph7_compile() + * ph7_compile_v2() or ph7_compile_file(). + * The second argument to this function is an integer configuration option + * that determines what property of the PH7 virtual machine is to be configured. + * Subsequent arguments vary depending on the configuration option in the second + * argument. There are many verbs but the most important are PH7_VM_CONFIG_OUTPUT, + * PH7_VM_CONFIG_HTTP_REQUEST and PH7_VM_CONFIG_ARGV_ENTRY. + * Refer to the official documentation for the list of allowed verbs. + */ +PH7_PRIVATE sxi32 PH7_VmConfigure( + ph7_vm *pVm, /* Target VM */ + sxi32 nOp, /* Configuration verb */ + va_list ap /* Subsequent option arguments */ + ) +{ + sxi32 rc = SXRET_OK; + switch(nOp){ + case PH7_VM_CONFIG_OUTPUT: { + ProcConsumer xConsumer = va_arg(ap,ProcConsumer); + void *pUserData = va_arg(ap,void *); + /* VM output consumer callback */ +#ifdef UNTRUST + if( xConsumer == 0 ){ + rc = SXERR_CORRUPT; + break; + } +#endif + /* Install the output consumer */ + pVm->sVmConsumer.xConsumer = xConsumer; + pVm->sVmConsumer.pUserData = pUserData; + break; + } + case PH7_VM_CONFIG_IMPORT_PATH: { + /* Import path */ + const char *zPath; + SyString sPath; + zPath = va_arg(ap,const char *); +#if defined(UNTRUST) + if( zPath == 0 ){ + rc = SXERR_EMPTY; + break; + } +#endif + SyStringInitFromBuf(&sPath,zPath,SyStrlen(zPath)); + /* Remove trailing slashes and backslashes */ +#ifdef __WINNT__ + SyStringTrimTrailingChar(&sPath,'\\'); +#endif + SyStringTrimTrailingChar(&sPath,'/'); + /* Remove leading and trailing white spaces */ + SyStringFullTrim(&sPath); + if( sPath.nByte > 0 ){ + /* Store the path in the corresponding conatiner */ + rc = SySetPut(&pVm->aPaths,(const void *)&sPath); + } + break; + } + case PH7_VM_CONFIG_ERR_REPORT: + /* Run-Time Error report */ + pVm->bErrReport = 1; + break; + case PH7_VM_CONFIG_RECURSION_DEPTH:{ + /* Recursion depth */ + int nDepth = va_arg(ap,int); + if( nDepth > 2 && nDepth < 1024 ){ + pVm->nMaxDepth = nDepth; + } + break; + } + case PH7_VM_OUTPUT_LENGTH: { + /* VM output length in bytes */ + sxu32 *pOut = va_arg(ap,sxu32 *); +#ifdef UNTRUST + if( pOut == 0 ){ + rc = SXERR_CORRUPT; + break; + } +#endif + *pOut = pVm->nOutputLen; + break; + } + + case PH7_VM_CONFIG_CREATE_SUPER: + case PH7_VM_CONFIG_CREATE_VAR: { + /* Create a new superglobal/global variable */ + const char *zName = va_arg(ap,const char *); + ph7_value *pValue = va_arg(ap,ph7_value *); + SyHashEntry *pEntry; + ph7_value *pObj; + sxu32 nByte; + sxu32 nIdx; +#ifdef UNTRUST + if( SX_EMPTY_STR(zName) || pValue == 0 ){ + rc = SXERR_CORRUPT; + break; + } +#endif + nByte = SyStrlen(zName); + if( nOp == PH7_VM_CONFIG_CREATE_SUPER ){ + /* Check if the superglobal is already installed */ + pEntry = SyHashGet(&pVm->hSuper,(const void *)zName,nByte); + }else{ + /* Query the top active VM frame */ + pEntry = SyHashGet(&pVm->pFrame->hVar,(const void *)zName,nByte); + } + if( pEntry ){ + /* Variable already installed */ + nIdx = SX_PTR_TO_INT(pEntry->pUserData); + /* Extract contents */ + pObj = (ph7_value *)SySetAt(&pVm->aMemObj,nIdx); + if( pObj ){ + /* Overwrite old contents */ + PH7_MemObjStore(pValue,pObj); + } + }else{ + /* Install a new variable */ + pObj = PH7_ReserveMemObj(&(*pVm)); + if( pObj == 0 ){ + rc = SXERR_MEM; + break; + } + nIdx = pObj->nIdx; + /* Copy value */ + PH7_MemObjStore(pValue,pObj); + if( nOp == PH7_VM_CONFIG_CREATE_SUPER ){ + /* Install the superglobal */ + rc = SyHashInsert(&pVm->hSuper,(const void *)zName,nByte,SX_INT_TO_PTR(nIdx)); + }else{ + /* Install in the current frame */ + rc = SyHashInsert(&pVm->pFrame->hVar,(const void *)zName,nByte,SX_INT_TO_PTR(nIdx)); + } + if( rc == SXRET_OK ){ + SyHashEntry *pRef; + if( nOp == PH7_VM_CONFIG_CREATE_SUPER ){ + pRef = SyHashLastEntry(&pVm->hSuper); + }else{ + pRef = SyHashLastEntry(&pVm->pFrame->hVar); + } + /* Install in the reference table */ + PH7_VmRefObjInstall(&(*pVm),nIdx,pRef,0,0); + if( nOp == PH7_VM_CONFIG_CREATE_SUPER || pVm->pFrame->pParent == 0){ + /* Register in the $GLOBALS array */ + VmHashmapRefInsert(pVm->pGlobal,zName,nByte,nIdx); + } + } + } + break; + } + case PH7_VM_CONFIG_SERVER_ATTR: + case PH7_VM_CONFIG_ENV_ATTR: + case PH7_VM_CONFIG_SESSION_ATTR: + case PH7_VM_CONFIG_POST_ATTR: + case PH7_VM_CONFIG_GET_ATTR: + case PH7_VM_CONFIG_COOKIE_ATTR: + case PH7_VM_CONFIG_HEADER_ATTR: { + const char *zKey = va_arg(ap,const char *); + const char *zValue = va_arg(ap,const char *); + int nLen = va_arg(ap,int); + ph7_hashmap *pMap; + ph7_value *pValue; + if( nOp == PH7_VM_CONFIG_ENV_ATTR ){ + /* Extract the $_ENV superglobal */ + pValue = VmExtractSuper(&(*pVm),"_ENV",sizeof("_ENV")-1); + }else if(nOp == PH7_VM_CONFIG_POST_ATTR ){ + /* Extract the $_POST superglobal */ + pValue = VmExtractSuper(&(*pVm),"_POST",sizeof("_POST")-1); + }else if(nOp == PH7_VM_CONFIG_GET_ATTR ){ + /* Extract the $_GET superglobal */ + pValue = VmExtractSuper(&(*pVm),"_GET",sizeof("_GET")-1); + }else if(nOp == PH7_VM_CONFIG_COOKIE_ATTR ){ + /* Extract the $_COOKIE superglobal */ + pValue = VmExtractSuper(&(*pVm),"_COOKIE",sizeof("_COOKIE")-1); + }else if(nOp == PH7_VM_CONFIG_SESSION_ATTR ){ + /* Extract the $_SESSION superglobal */ + pValue = VmExtractSuper(&(*pVm),"_SESSION",sizeof("_SESSION")-1); + }else if( nOp == PH7_VM_CONFIG_HEADER_ATTR ){ + /* Extract the $_HEADER superglobale */ + pValue = VmExtractSuper(&(*pVm),"_HEADER",sizeof("_HEADER")-1); + }else{ + /* Extract the $_SERVER superglobal */ + pValue = VmExtractSuper(&(*pVm),"_SERVER",sizeof("_SERVER")-1); + } + if( pValue == 0 || (pValue->iFlags & MEMOBJ_HASHMAP) == 0 ){ + /* No such entry */ + rc = SXERR_NOTFOUND; + break; + } + /* Point to the hashmap */ + pMap = (ph7_hashmap *)pValue->x.pOther; + /* Perform the insertion */ + rc = VmHashmapInsert(pMap,zKey,-1,zValue,nLen); + break; + } + case PH7_VM_CONFIG_ARGV_ENTRY:{ + /* Script arguments */ + const char *zValue = va_arg(ap,const char *); + ph7_hashmap *pMap; + ph7_value *pValue; + sxu32 n; + if( SX_EMPTY_STR(zValue) ){ + rc = SXERR_EMPTY; + break; + } + /* Extract the $argv array */ + pValue = VmExtractSuper(&(*pVm),"argv",sizeof("argv")-1); + if( pValue == 0 || (pValue->iFlags & MEMOBJ_HASHMAP) == 0 ){ + /* No such entry */ + rc = SXERR_NOTFOUND; + break; + } + /* Point to the hashmap */ + pMap = (ph7_hashmap *)pValue->x.pOther; + /* Perform the insertion */ + n = (sxu32)SyStrlen(zValue); + rc = VmHashmapInsert(pMap,0,0,zValue,(int)n); + if( rc == SXRET_OK ){ + if( pMap->nEntry > 1 ){ + /* Append space separator first */ + SyBlobAppend(&pVm->sArgv,(const void *)" ",sizeof(char)); + } + SyBlobAppend(&pVm->sArgv,(const void *)zValue,n); + } + break; + } + case PH7_VM_CONFIG_ERR_LOG_HANDLER: { + /* error_log() consumer */ + ProcErrLog xErrLog = va_arg(ap,ProcErrLog); + pVm->xErrLog = xErrLog; + break; + } + case PH7_VM_CONFIG_EXEC_VALUE: { + /* Script return value */ + ph7_value **ppValue = va_arg(ap,ph7_value **); +#ifdef UNTRUST + if( ppValue == 0 ){ + rc = SXERR_CORRUPT; + break; + } +#endif + *ppValue = &pVm->sExec; + break; + } + case PH7_VM_CONFIG_IO_STREAM: { + /* Register an IO stream device */ + const ph7_io_stream *pStream = va_arg(ap,const ph7_io_stream *); + /* Make sure we are dealing with a valid IO stream */ + if( pStream == 0 || pStream->zName == 0 || pStream->zName[0] == 0 || + pStream->xOpen == 0 || pStream->xRead == 0 ){ + /* Invalid stream */ + rc = SXERR_INVALID; + break; + } + if( pVm->pDefStream == 0 && SyStrnicmp(pStream->zName,"file",sizeof("file")-1) == 0 ){ + /* Make the 'file://' stream the defaut stream device */ + pVm->pDefStream = pStream; + } + /* Insert in the appropriate container */ + rc = SySetPut(&pVm->aIOstream,(const void *)&pStream); + break; + } + case PH7_VM_CONFIG_EXTRACT_OUTPUT: { + /* Point to the VM internal output consumer buffer */ + const void **ppOut = va_arg(ap,const void **); + unsigned int *pLen = va_arg(ap,unsigned int *); +#ifdef UNTRUST + if( ppOut == 0 || pLen == 0 ){ + rc = SXERR_CORRUPT; + break; + } +#endif + *ppOut = SyBlobData(&pVm->sConsumer); + *pLen = SyBlobLength(&pVm->sConsumer); + break; + } + case PH7_VM_CONFIG_HTTP_REQUEST:{ + /* Raw HTTP request*/ + const char *zRequest = va_arg(ap,const char *); + int nByte = va_arg(ap,int); + if( SX_EMPTY_STR(zRequest) ){ + rc = SXERR_EMPTY; + break; + } + if( nByte < 0 ){ + /* Compute length automatically */ + nByte = (int)SyStrlen(zRequest); + } + /* Process the request */ + rc = VmHttpProcessRequest(&(*pVm),zRequest,nByte); + break; + } + default: + /* Unknown configuration option */ + rc = SXERR_UNKNOWN; + break; + } + return rc; +} +/* Forward declaration */ +static const char * VmInstrToString(sxi32 nOp); +/* + * This routine is used to dump PH7 byte-code instructions to a human readable + * format. + * The dump is redirected to the given consumer callback which is responsible + * of consuming the generated dump perhaps redirecting it to its standard output + * (STDOUT). + */ +static sxi32 VmByteCodeDump( + SySet *pByteCode, /* Bytecode container */ + ProcConsumer xConsumer, /* Dump consumer callback */ + void *pUserData /* Last argument to xConsumer() */ + ) +{ + static const char zDump[] = { + "====================================================\n" + "PH7 VM Dump Copyright (C) 2011-2012 Symisc Systems\n" + " http://www.symisc.net/\n" + "====================================================\n" + }; + VmInstr *pInstr,*pEnd; + sxi32 rc = SXRET_OK; + sxu32 n; + /* Point to the PH7 instructions */ + pInstr = (VmInstr *)SySetBasePtr(pByteCode); + pEnd = &pInstr[SySetUsed(pByteCode)]; + n = 0; + xConsumer((const void *)zDump,sizeof(zDump)-1,pUserData); + /* Dump instructions */ + for(;;){ + if( pInstr >= pEnd ){ + /* No more instructions */ + break; + } + /* Format and call the consumer callback */ + rc = SyProcFormat(xConsumer,pUserData,"%s %8d %8u %#8x [%u]\n", + VmInstrToString(pInstr->iOp),pInstr->iP1,pInstr->iP2, + SX_PTR_TO_INT(pInstr->p3),n); + if( rc != SXRET_OK ){ + /* Consumer routine request an operation abort */ + return rc; + } + ++n; + pInstr++; /* Next instruction in the stream */ + } + return rc; +} +/* Forward declaration */ +static int VmObConsumer(const void *pData,unsigned int nDataLen,void *pUserData); +static sxi32 VmUncaughtException(ph7_vm *pVm,ph7_class_instance *pThis); +static sxi32 VmThrowException(ph7_vm *pVm,ph7_class_instance *pThis); +/* + * Consume a generated run-time error message by invoking the VM output + * consumer callback. + */ +static sxi32 VmCallErrorHandler(ph7_vm *pVm,SyBlob *pMsg) +{ + ph7_output_consumer *pCons = &pVm->sVmConsumer; + sxi32 rc = SXRET_OK; + /* Append a new line */ +#ifdef __WINNT__ + SyBlobAppend(pMsg,"\r\n",sizeof("\r\n")-1); +#else + SyBlobAppend(pMsg,"\n",sizeof(char)); +#endif + /* Invoke the output consumer callback */ + rc = pCons->xConsumer(SyBlobData(pMsg),SyBlobLength(pMsg),pCons->pUserData); + if( pCons->xConsumer != VmObConsumer ){ + /* Increment output length */ + pVm->nOutputLen += SyBlobLength(pMsg); + } + return rc; +} +/* + * Throw a run-time error and invoke the supplied VM output consumer callback. + * Refer to the implementation of [ph7_context_throw_error()] for additional + * information. + */ +PH7_PRIVATE sxi32 PH7_VmThrowError( + ph7_vm *pVm, /* Target VM */ + SyString *pFuncName, /* Function name. NULL otherwise */ + sxi32 iErr, /* Severity level: [i.e: Error,Warning or Notice]*/ + const char *zMessage /* Null terminated error message */ + ) +{ + SyBlob *pWorker = &pVm->sWorker; + SyString *pFile; + char *zErr; + sxi32 rc; + if( !pVm->bErrReport ){ + /* Don't bother reporting errors */ + return SXRET_OK; + } + /* Reset the working buffer */ + SyBlobReset(pWorker); + /* Peek the processed file if available */ + pFile = (SyString *)SySetPeek(&pVm->aFiles); + if( pFile ){ + /* Append file name */ + SyBlobAppend(pWorker,pFile->zString,pFile->nByte); + SyBlobAppend(pWorker,(const void *)" ",sizeof(char)); + } + zErr = "Error: "; + switch(iErr){ + case PH7_CTX_WARNING: zErr = "Warning: "; break; + case PH7_CTX_NOTICE: zErr = "Notice: "; break; + default: + iErr = PH7_CTX_ERR; + break; + } + SyBlobAppend(pWorker,zErr,SyStrlen(zErr)); + if( pFuncName ){ + /* Append function name first */ + SyBlobAppend(pWorker,pFuncName->zString,pFuncName->nByte); + SyBlobAppend(pWorker,"(): ",sizeof("(): ")-1); + } + SyBlobAppend(pWorker,zMessage,SyStrlen(zMessage)); + /* Consume the error message */ + rc = VmCallErrorHandler(&(*pVm),pWorker); + return rc; +} +/* + * Format and throw a run-time error and invoke the supplied VM output consumer callback. + * Refer to the implementation of [ph7_context_throw_error_format()] for additional + * information. + */ +static sxi32 VmThrowErrorAp( + ph7_vm *pVm, /* Target VM */ + SyString *pFuncName, /* Function name. NULL otherwise */ + sxi32 iErr, /* Severity level: [i.e: Error,Warning or Notice] */ + const char *zFormat, /* Format message */ + va_list ap /* Variable list of arguments */ + ) +{ + SyBlob *pWorker = &pVm->sWorker; + SyString *pFile; + char *zErr; + sxi32 rc; + if( !pVm->bErrReport ){ + /* Don't bother reporting errors */ + return SXRET_OK; + } + /* Reset the working buffer */ + SyBlobReset(pWorker); + /* Peek the processed file if available */ + pFile = (SyString *)SySetPeek(&pVm->aFiles); + if( pFile ){ + /* Append file name */ + SyBlobAppend(pWorker,pFile->zString,pFile->nByte); + SyBlobAppend(pWorker,(const void *)" ",sizeof(char)); + } + zErr = "Error: "; + switch(iErr){ + case PH7_CTX_WARNING: zErr = "Warning: "; break; + case PH7_CTX_NOTICE: zErr = "Notice: "; break; + default: + iErr = PH7_CTX_ERR; + break; + } + SyBlobAppend(pWorker,zErr,SyStrlen(zErr)); + if( pFuncName ){ + /* Append function name first */ + SyBlobAppend(pWorker,pFuncName->zString,pFuncName->nByte); + SyBlobAppend(pWorker,"(): ",sizeof("(): ")-1); + } + SyBlobFormatAp(pWorker,zFormat,ap); + /* Consume the error message */ + rc = VmCallErrorHandler(&(*pVm),pWorker); + return rc; +} +/* + * Format and throw a run-time error and invoke the supplied VM output consumer callback. + * Refer to the implementation of [ph7_context_throw_error_format()] for additional + * information. + * ------------------------------------ + * Simple boring wrapper function. + * ------------------------------------ + */ +static sxi32 VmErrorFormat(ph7_vm *pVm,sxi32 iErr,const char *zFormat,...) +{ + va_list ap; + sxi32 rc; + va_start(ap,zFormat); + rc = VmThrowErrorAp(&(*pVm),0,iErr,zFormat,ap); + va_end(ap); + return rc; +} +/* + * Format and throw a run-time error and invoke the supplied VM output consumer callback. + * Refer to the implementation of [ph7_context_throw_error_format()] for additional + * information. + * ------------------------------------ + * Simple boring wrapper function. + * ------------------------------------ + */ +PH7_PRIVATE sxi32 PH7_VmThrowErrorAp(ph7_vm *pVm,SyString *pFuncName,sxi32 iErr,const char *zFormat,va_list ap) +{ + sxi32 rc; + rc = VmThrowErrorAp(&(*pVm),&(*pFuncName),iErr,zFormat,ap); + return rc; +} +/* + * Execute as much of a PH7 bytecode program as we can then return. + * + * [PH7_VmMakeReady()] must be called before this routine in order to + * close the program with a final OP_DONE and to set up the default + * consumer routines and other stuff. Refer to the implementation + * of [PH7_VmMakeReady()] for additional information. + * If the installed VM output consumer callback ever returns PH7_ABORT + * then the program execution is halted. + * After this routine has finished, [PH7_VmRelease()] or [PH7_VmReset()] + * should be used respectively to clean up the mess that was left behind + * or to reset the VM to it's initial state. + */ +static sxi32 VmByteCodeExec( + ph7_vm *pVm, /* Target VM */ + VmInstr *aInstr, /* PH7 bytecode program */ + ph7_value *pStack, /* Operand stack */ + int nTos, /* Top entry in the operand stack (usually -1) */ + ph7_value *pResult, /* Store program return value here. NULL otherwise */ + sxu32 *pLastRef, /* Last referenced ph7_value index */ + int is_callback /* TRUE if we are executing a callback */ + ) +{ + VmInstr *pInstr; + ph7_value *pTos; + SySet aArg; + sxi32 pc; + sxi32 rc; + /* Argument container */ + SySetInit(&aArg,&pVm->sAllocator,sizeof(ph7_value *)); + if( nTos < 0 ){ + pTos = &pStack[-1]; + }else{ + pTos = &pStack[nTos]; + } + pc = 0; + /* Execute as much as we can */ + for(;;){ + /* Fetch the instruction to execute */ + pInstr = &aInstr[pc]; + rc = SXRET_OK; +/* + * What follows here is a massive switch statement where each case implements a + * separate instruction in the virtual machine. If we follow the usual + * indentation convention each case should be indented by 6 spaces. But + * that is a lot of wasted space on the left margin. So the code within + * the switch statement will break with convention and be flush-left. + */ + switch(pInstr->iOp){ +/* + * DONE: P1 * * + * + * Program execution completed: Clean up the mess left behind + * and return immediately. + */ +case PH7_OP_DONE: + if( pInstr->iP1 ){ +#ifdef UNTRUST + if( pTos < pStack ){ + goto Abort; + } +#endif + if( pLastRef ){ + *pLastRef = pTos->nIdx; + } + if( pResult ){ + /* Execution result */ + PH7_MemObjStore(pTos,pResult); + } + VmPopOperand(&pTos,1); + }else if( pLastRef ){ + /* Nothing referenced */ + *pLastRef = SXU32_HIGH; + } + goto Done; +/* + * HALT: P1 * * + * + * Program execution aborted: Clean up the mess left behind + * and abort immediately. + */ +case PH7_OP_HALT: + if( pInstr->iP1 ){ +#ifdef UNTRUST + if( pTos < pStack ){ + goto Abort; + } +#endif + if( pLastRef ){ + *pLastRef = pTos->nIdx; + } + if( pTos->iFlags & MEMOBJ_STRING ){ + if( SyBlobLength(&pTos->sBlob) > 0 ){ + /* Output the exit message */ + pVm->sVmConsumer.xConsumer(SyBlobData(&pTos->sBlob),SyBlobLength(&pTos->sBlob), + pVm->sVmConsumer.pUserData); + if( pVm->sVmConsumer.xConsumer != VmObConsumer ){ + /* Increment output length */ + pVm->nOutputLen += SyBlobLength(&pTos->sBlob); + } + } + }else if(pTos->iFlags & MEMOBJ_INT ){ + /* Record exit status */ + pVm->iExitStatus = (sxi32)pTos->x.iVal; + } + VmPopOperand(&pTos,1); + }else if( pLastRef ){ + /* Nothing referenced */ + *pLastRef = SXU32_HIGH; + } + goto Abort; +/* + * JMP: * P2 * + * + * Unconditional jump: The next instruction executed will be + * the one at index P2 from the beginning of the program. + */ +case PH7_OP_JMP: + pc = pInstr->iP2 - 1; + break; +/* + * JZ: P1 P2 * + * + * Take the jump if the top value is zero (FALSE jump).Pop the top most + * entry in the stack if P1 is zero. + */ +case PH7_OP_JZ: +#ifdef UNTRUST + if( pTos < pStack ){ + goto Abort; + } +#endif + /* Get a boolean value */ + if((pTos->iFlags & MEMOBJ_BOOL) == 0 ){ + PH7_MemObjToBool(pTos); + } + if( !pTos->x.iVal ){ + /* Take the jump */ + pc = pInstr->iP2 - 1; + } + if( !pInstr->iP1 ){ + VmPopOperand(&pTos,1); + } + break; +/* + * JNZ: P1 P2 * + * + * Take the jump if the top value is not zero (TRUE jump).Pop the top most + * entry in the stack if P1 is zero. + */ +case PH7_OP_JNZ: +#ifdef UNTRUST + if( pTos < pStack ){ + goto Abort; + } +#endif + /* Get a boolean value */ + if((pTos->iFlags & MEMOBJ_BOOL) == 0 ){ + PH7_MemObjToBool(pTos); + } + if( pTos->x.iVal ){ + /* Take the jump */ + pc = pInstr->iP2 - 1; + } + if( !pInstr->iP1 ){ + VmPopOperand(&pTos,1); + } + break; +/* + * NOOP: * * * + * + * Do nothing. This instruction is often useful as a jump + * destination. + */ +case PH7_OP_NOOP: + break; +/* + * POP: P1 * * + * + * Pop P1 elements from the operand stack. + */ +case PH7_OP_POP: { + sxi32 n = pInstr->iP1; + if( &pTos[-n+1] < pStack ){ + /* TICKET 1433-51 Stack underflow must be handled at run-time */ + n = (sxi32)(pTos - pStack); + } + VmPopOperand(&pTos,n); + break; + } +/* + * CVT_INT: * * * + * + * Force the top of the stack to be an integer. + */ +case PH7_OP_CVT_INT: +#ifdef UNTRUST + if( pTos < pStack ){ + goto Abort; + } +#endif + if((pTos->iFlags & MEMOBJ_INT) == 0 ){ + PH7_MemObjToInteger(pTos); + } + /* Invalidate any prior representation */ + MemObjSetType(pTos,MEMOBJ_INT); + break; +/* + * CVT_REAL: * * * + * + * Force the top of the stack to be a real. + */ +case PH7_OP_CVT_REAL: +#ifdef UNTRUST + if( pTos < pStack ){ + goto Abort; + } +#endif + if((pTos->iFlags & MEMOBJ_REAL) == 0 ){ + PH7_MemObjToReal(pTos); + } + /* Invalidate any prior representation */ + MemObjSetType(pTos,MEMOBJ_REAL); + break; +/* + * CVT_STR: * * * + * + * Force the top of the stack to be a string. + */ +case PH7_OP_CVT_STR: +#ifdef UNTRUST + if( pTos < pStack ){ + goto Abort; + } +#endif + if( (pTos->iFlags & MEMOBJ_STRING) == 0 ){ + PH7_MemObjToString(pTos); + } + break; +/* + * CVT_BOOL: * * * + * + * Force the top of the stack to be a boolean. + */ +case PH7_OP_CVT_BOOL: +#ifdef UNTRUST + if( pTos < pStack ){ + goto Abort; + } +#endif + if( (pTos->iFlags & MEMOBJ_BOOL) == 0 ){ + PH7_MemObjToBool(pTos); + } + break; +/* + * CVT_NULL: * * * + * + * Nullify the top of the stack. + */ +case PH7_OP_CVT_NULL: +#ifdef UNTRUST + if( pTos < pStack ){ + goto Abort; + } +#endif + PH7_MemObjRelease(pTos); + break; +/* + * CVT_NUMC: * * * + * + * Force the top of the stack to be a numeric type (integer,real or both). + */ +case PH7_OP_CVT_NUMC: +#ifdef UNTRUST + if( pTos < pStack ){ + goto Abort; + } +#endif + /* Force a numeric cast */ + PH7_MemObjToNumeric(pTos); + break; +/* + * CVT_ARRAY: * * * + * + * Force the top of the stack to be a hashmap aka 'array'. + */ +case PH7_OP_CVT_ARRAY: +#ifdef UNTRUST + if( pTos < pStack ){ + goto Abort; + } +#endif + /* Force a hashmap cast */ + rc = PH7_MemObjToHashmap(pTos); + if( rc != SXRET_OK ){ + /* Not so fatal,emit a simple warning */ + PH7_VmThrowError(&(*pVm),0,PH7_CTX_WARNING, + "PH7 engine is running out of memory while performing an array cast"); + } + break; +/* + * CVT_OBJ: * * * + * + * Force the top of the stack to be a class instance (Object in the PHP jargon). + */ +case PH7_OP_CVT_OBJ: +#ifdef UNTRUST + if( pTos < pStack ){ + goto Abort; + } +#endif + if( (pTos->iFlags & MEMOBJ_OBJ) == 0 ){ + /* Force a 'stdClass()' cast */ + PH7_MemObjToObject(pTos); + } + break; +/* + * ERR_CTRL * * * + * + * Error control operator. + */ +case PH7_OP_ERR_CTRL: + /* + * TICKET 1433-038: + * As of this version ,the error control operator '@' is a no-op,simply + * use the public API,to control error output. + */ + break; +/* + * IS_A * * * + * + * Pop the top two operands from the stack and check whether the first operand + * is an object and is an instance of the second operand (which must be a string + * holding a class name or an object). + * Push TRUE on success. FALSE otherwise. + */ +case PH7_OP_IS_A:{ + ph7_value *pNos = &pTos[-1]; + sxi32 iRes = 0; /* assume false by default */ +#ifdef UNTRUST + if( pNos < pStack ){ + goto Abort; + } +#endif + if( pNos->iFlags& MEMOBJ_OBJ ){ + ph7_class_instance *pThis = (ph7_class_instance *)pNos->x.pOther; + ph7_class *pClass = 0; + /* Extract the target class */ + if( pTos->iFlags & MEMOBJ_OBJ ){ + /* Instance already loaded */ + pClass = ((ph7_class_instance *)pTos->x.pOther)->pClass; + }else if( pTos->iFlags & MEMOBJ_STRING && SyBlobLength(&pTos->sBlob) > 0 ){ + /* Perform the query */ + pClass = PH7_VmExtractClass(&(*pVm),(const char *)SyBlobData(&pTos->sBlob), + SyBlobLength(&pTos->sBlob),FALSE,0); + } + if( pClass ){ + /* Perform the query */ + iRes = VmInstanceOf(pThis->pClass,pClass); + } + } + /* Push result */ + VmPopOperand(&pTos,1); + PH7_MemObjRelease(pTos); + pTos->x.iVal = iRes; + MemObjSetType(pTos,MEMOBJ_BOOL); + break; + } + +/* + * LOADC P1 P2 * + * + * Load a constant [i.e: PHP_EOL,PHP_OS,__TIME__,...] indexed at P2 in the constant pool. + * If P1 is set,then this constant is candidate for expansion via user installable callbacks. + */ +case PH7_OP_LOADC: { + ph7_value *pObj; + /* Reserve a room */ + pTos++; + if( (pObj = (ph7_value *)SySetAt(&pVm->aLitObj,pInstr->iP2)) != 0 ){ + if( pInstr->iP1 == 1 && SyBlobLength(&pObj->sBlob) <= 64 ){ + SyHashEntry *pEntry; + /* Candidate for expansion via user defined callbacks */ + pEntry = SyHashGet(&pVm->hConstant,SyBlobData(&pObj->sBlob),SyBlobLength(&pObj->sBlob)); + if( pEntry ){ + ph7_constant *pCons = (ph7_constant *)pEntry->pUserData; + /* Set a NULL default value */ + MemObjSetType(pTos,MEMOBJ_NULL); + SyBlobReset(&pTos->sBlob); + /* Invoke the callback and deal with the expanded value */ + pCons->xExpand(pTos,pCons->pUserData); + /* Mark as constant */ + pTos->nIdx = SXU32_HIGH; + break; + } + } + PH7_MemObjLoad(pObj,pTos); + }else{ + /* Set a NULL value */ + MemObjSetType(pTos,MEMOBJ_NULL); + } + /* Mark as constant */ + pTos->nIdx = SXU32_HIGH; + break; + } +/* + * LOAD: P1 * P3 + * + * Load a variable where it's name is taken from the top of the stack or + * from the P3 operand. + * If P1 is set,then perform a lookup only.In other words do not create + * the variable if non existent and push the NULL constant instead. + */ +case PH7_OP_LOAD:{ + ph7_value *pObj; + SyString sName; + if( pInstr->p3 == 0 ){ + /* Take the variable name from the top of the stack */ +#ifdef UNTRUST + if( pTos < pStack ){ + goto Abort; + } +#endif + /* Force a string cast */ + if( (pTos->iFlags & MEMOBJ_STRING) == 0 ){ + PH7_MemObjToString(pTos); + } + SyStringInitFromBuf(&sName,SyBlobData(&pTos->sBlob),SyBlobLength(&pTos->sBlob)); + }else{ + SyStringInitFromBuf(&sName,pInstr->p3,SyStrlen((const char *)pInstr->p3)); + /* Reserve a room for the target object */ + pTos++; + } + /* Extract the requested memory object */ + pObj = VmExtractMemObj(&(*pVm),&sName,pInstr->p3 ? FALSE : TRUE,pInstr->iP1 != 1); + if( pObj == 0 ){ + if( pInstr->iP1 ){ + /* Variable not found,load NULL */ + if( !pInstr->p3 ){ + PH7_MemObjRelease(pTos); + }else{ + MemObjSetType(pTos,MEMOBJ_NULL); + } + pTos->nIdx = SXU32_HIGH; /* Mark as constant */ + break; + }else{ + /* Fatal error */ + VmErrorFormat(&(*pVm),PH7_CTX_ERR,"Fatal, PH7 engine is running out of memory while loading variable '%z'",&sName); + goto Abort; + } + } + /* Load variable contents */ + PH7_MemObjLoad(pObj,pTos); + pTos->nIdx = pObj->nIdx; + break; + } +/* + * LOAD_MAP P1 * * + * + * Allocate a new empty hashmap (array in the PHP jargon) and push it on the stack. + * If the P1 operand is greater than zero then pop P1 elements from the + * stack and insert them (key => value pair) in the new hashmap. + */ +case PH7_OP_LOAD_MAP: { + ph7_hashmap *pMap; + /* Allocate a new hashmap instance */ + pMap = PH7_NewHashmap(&(*pVm),0,0); + if( pMap == 0 ){ + VmErrorFormat(&(*pVm),PH7_CTX_ERR, + "Fatal, PH7 engine is running out of memory while loading array at instruction #:%d",pc); + goto Abort; + } + if( pInstr->iP1 > 0 ){ + ph7_value *pEntry = &pTos[-pInstr->iP1+1]; /* Point to the first entry */ + /* Perform the insertion */ + while( pEntry < pTos ){ + if( pEntry[1].iFlags & MEMOBJ_REFERENCE ){ + /* Insertion by reference */ + PH7_HashmapInsertByRef(pMap, + (pEntry->iFlags & MEMOBJ_NULL) ? 0 /* Automatic index assign */ : pEntry, + (sxu32)pEntry[1].x.iVal + ); + }else{ + /* Standard insertion */ + PH7_HashmapInsert(pMap, + (pEntry->iFlags & MEMOBJ_NULL) ? 0 /* Automatic index assign */ : pEntry, + &pEntry[1] + ); + } + /* Next pair on the stack */ + pEntry += 2; + } + /* Pop P1 elements */ + VmPopOperand(&pTos,pInstr->iP1); + } + /* Push the hashmap */ + pTos++; + pTos->nIdx = SXU32_HIGH; + pTos->x.pOther = pMap; + MemObjSetType(pTos,MEMOBJ_HASHMAP); + break; + } +/* + * LOAD_LIST: P1 * * + * + * Assign hashmap entries values to the top P1 entries. + * This is the VM implementation of the list() PHP construct. + * Caveats: + * This implementation support only a single nesting level. + */ +case PH7_OP_LOAD_LIST: { + ph7_value *pEntry; + if( pInstr->iP1 <= 0 ){ + /* Empty list,break immediately */ + break; + } + pEntry = &pTos[-pInstr->iP1+1]; +#ifdef UNTRUST + if( &pEntry[-1] < pStack ){ + goto Abort; + } +#endif + if( pEntry[-1].iFlags & MEMOBJ_HASHMAP ){ + ph7_hashmap *pMap = (ph7_hashmap *)pEntry[-1].x.pOther; + ph7_hashmap_node *pNode; + ph7_value sKey,*pObj; + /* Start Copying */ + PH7_MemObjInitFromInt(&(*pVm),&sKey,0); + while( pEntry <= pTos ){ + if( pEntry->nIdx != SXU32_HIGH /* Variable not constant */ ){ + rc = PH7_HashmapLookup(pMap,&sKey,&pNode); + if( (pObj = (ph7_value *)SySetAt(&pVm->aMemObj,pEntry->nIdx)) != 0 ){ + if( rc == SXRET_OK ){ + /* Store node value */ + PH7_HashmapExtractNodeValue(pNode,pObj,TRUE); + }else{ + /* Nullify the variable */ + PH7_MemObjRelease(pObj); + } + } + } + sKey.x.iVal++; /* Next numeric index */ + pEntry++; + } + } + VmPopOperand(&pTos,pInstr->iP1); + break; + } +/* + * LOAD_IDX: P1 P2 * + * + * Load a hasmap entry where it's index (either numeric or string) is taken + * from the stack. + * If the index does not refer to a valid element,then push the NULL constant + * instead. + */ +case PH7_OP_LOAD_IDX: { + ph7_hashmap_node *pNode = 0; /* cc warning */ + ph7_hashmap *pMap = 0; + ph7_value *pIdx; + pIdx = 0; + if( pInstr->iP1 == 0 ){ + if( !pInstr->iP2){ + /* No available index,load NULL */ + if( pTos >= pStack ){ + PH7_MemObjRelease(pTos); + }else{ + /* TICKET 1433-020: Empty stack */ + pTos++; + MemObjSetType(pTos,MEMOBJ_NULL); + pTos->nIdx = SXU32_HIGH; + } + /* Emit a notice */ + PH7_VmThrowError(&(*pVm),0,PH7_CTX_NOTICE, + "Array: Attempt to access an undefined index,PH7 is loading NULL"); + break; + } + }else{ + pIdx = pTos; + pTos--; + } + if( pTos->iFlags & MEMOBJ_STRING ){ + /* String access */ + if( pIdx ){ + sxu32 nOfft; + if( (pIdx->iFlags & MEMOBJ_INT) == 0 ){ + /* Force an int cast */ + PH7_MemObjToInteger(pIdx); + } + nOfft = (sxu32)pIdx->x.iVal; + if( nOfft >= SyBlobLength(&pTos->sBlob) ){ + /* Invalid offset,load null */ + PH7_MemObjRelease(pTos); + }else{ + const char *zData = (const char *)SyBlobData(&pTos->sBlob); + int c = zData[nOfft]; + PH7_MemObjRelease(pTos); + MemObjSetType(pTos,MEMOBJ_STRING); + SyBlobAppend(&pTos->sBlob,(const void *)&c,sizeof(char)); + } + }else{ + /* No available index,load NULL */ + MemObjSetType(pTos,MEMOBJ_NULL); + } + break; + } + if( pInstr->iP2 && (pTos->iFlags & MEMOBJ_HASHMAP) == 0 ){ + if( pTos->nIdx != SXU32_HIGH ){ + ph7_value *pObj; + if( (pObj = (ph7_value *)SySetAt(&pVm->aMemObj,pTos->nIdx)) != 0 ){ + PH7_MemObjToHashmap(pObj); + PH7_MemObjLoad(pObj,pTos); + } + } + } + rc = SXERR_NOTFOUND; /* Assume the index is invalid */ + if( pTos->iFlags & MEMOBJ_HASHMAP ){ + /* Point to the hashmap */ + pMap = (ph7_hashmap *)pTos->x.pOther; + if( pIdx ){ + /* Load the desired entry */ + rc = PH7_HashmapLookup(pMap,pIdx,&pNode); + } + if( rc != SXRET_OK && pInstr->iP2 ){ + /* Create a new empty entry */ + rc = PH7_HashmapInsert(pMap,pIdx,0); + if( rc == SXRET_OK ){ + /* Point to the last inserted entry */ + pNode = pMap->pLast; + } + } + } + if( pIdx ){ + PH7_MemObjRelease(pIdx); + } + if( rc == SXRET_OK ){ + /* Load entry contents */ + if( pMap->iRef < 2 ){ + /* TICKET 1433-42: Array will be deleted shortly,so we will make a copy + * of the entry value,rather than pointing to it. + */ + pTos->nIdx = SXU32_HIGH; + PH7_HashmapExtractNodeValue(pNode,pTos,TRUE); + }else{ + pTos->nIdx = pNode->nValIdx; + PH7_HashmapExtractNodeValue(pNode,pTos,FALSE); + PH7_HashmapUnref(pMap); + } + }else{ + /* No such entry,load NULL */ + PH7_MemObjRelease(pTos); + pTos->nIdx = SXU32_HIGH; + } + break; + } +/* + * LOAD_CLOSURE * * P3 + * + * Set-up closure environment described by the P3 oeprand and push the closure + * name in the stack. + */ +case PH7_OP_LOAD_CLOSURE:{ + ph7_vm_func *pFunc = (ph7_vm_func *)pInstr->p3; + if( pFunc->iFlags & VM_FUNC_CLOSURE ){ + ph7_vm_func_closure_env *aEnv,*pEnv,sEnv; + ph7_vm_func *pClosure; + char *zName; + sxu32 mLen; + sxu32 n; + /* Create a new VM function */ + pClosure = (ph7_vm_func *)SyMemBackendPoolAlloc(&pVm->sAllocator,sizeof(ph7_vm_func)); + /* Generate an unique closure name */ + zName = (char *)SyMemBackendAlloc(&pVm->sAllocator,sizeof("[closure_]")+64); + if( pClosure == 0 || zName == 0){ + PH7_VmThrowError(pVm,0,E_ERROR,"Fatal: PH7 is running out of memory while creating closure environment"); + goto Abort; + } + mLen = SyBufferFormat(zName,sizeof("[closure_]")+64,"[closure_%d]",pVm->closure_cnt++); + while( SyHashGet(&pVm->hFunction,zName,mLen) != 0 && mLen < (sizeof("[closure_]")+60/* not 64 */) ){ + mLen = SyBufferFormat(zName,sizeof("[closure_]")+64,"[closure_%d]",pVm->closure_cnt++); + } + /* Zero the stucture */ + SyZero(pClosure,sizeof(ph7_vm_func)); + /* Perform a structure assignment on read-only items */ + pClosure->aArgs = pFunc->aArgs; + pClosure->aByteCode = pFunc->aByteCode; + pClosure->aStatic = pFunc->aStatic; + pClosure->iFlags = pFunc->iFlags; + pClosure->pUserData = pFunc->pUserData; + pClosure->sSignature = pFunc->sSignature; + SyStringInitFromBuf(&pClosure->sName,zName,mLen); + /* Register the closure */ + PH7_VmInstallUserFunction(pVm,pClosure,0); + /* Set up closure environment */ + SySetInit(&pClosure->aClosureEnv,&pVm->sAllocator,sizeof(ph7_vm_func_closure_env)); + aEnv = (ph7_vm_func_closure_env *)SySetBasePtr(&pFunc->aClosureEnv); + for( n = 0 ; n < SySetUsed(&pFunc->aClosureEnv) ; ++n ){ + ph7_value *pValue; + pEnv = &aEnv[n]; + sEnv.sName = pEnv->sName; + sEnv.iFlags = pEnv->iFlags; + sEnv.nIdx = SXU32_HIGH; + PH7_MemObjInit(pVm,&sEnv.sValue); + if( sEnv.iFlags & VM_FUNC_ARG_BY_REF ){ + /* Pass by reference */ + PH7_VmThrowError(pVm,0,PH7_CTX_WARNING, + "Closure: Pass by reference is disabled in the current release of the PH7 engine,PH7 is switching to pass by value" + ); + } + /* Standard pass by value */ + pValue = VmExtractMemObj(pVm,&sEnv.sName,FALSE,FALSE); + if( pValue ){ + /* Copy imported value */ + PH7_MemObjStore(pValue,&sEnv.sValue); + } + /* Insert the imported variable */ + SySetPut(&pClosure->aClosureEnv,(const void *)&sEnv); + } + /* Finally,load the closure name on the stack */ + pTos++; + PH7_MemObjStringAppend(pTos,zName,mLen); + } + break; + } +/* + * STORE * P2 P3 + * + * Perform a store (Assignment) operation. + */ +case PH7_OP_STORE: { + ph7_value *pObj; + SyString sName; +#ifdef UNTRUST + if( pTos < pStack ){ + goto Abort; + } +#endif + if( pInstr->iP2 ){ + sxu32 nIdx; + /* Member store operation */ + nIdx = pTos->nIdx; + VmPopOperand(&pTos,1); + if( nIdx == SXU32_HIGH ){ + PH7_VmThrowError(&(*pVm),0,PH7_CTX_ERR, + "Cannot perform assignment on a constant class attribute,PH7 is loading NULL"); + pTos->nIdx = SXU32_HIGH; + }else{ + /* Point to the desired memory object */ + pObj = (ph7_value *)SySetAt(&pVm->aMemObj,nIdx); + if( pObj ){ + /* Perform the store operation */ + PH7_MemObjStore(pTos,pObj); + } + } + break; + }else if( pInstr->p3 == 0 ){ + /* Take the variable name from the next on the stack */ + if( (pTos->iFlags & MEMOBJ_STRING) == 0 ){ + /* Force a string cast */ + PH7_MemObjToString(pTos); + } + SyStringInitFromBuf(&sName,SyBlobData(&pTos->sBlob),SyBlobLength(&pTos->sBlob)); + pTos--; +#ifdef UNTRUST + if( pTos < pStack ){ + goto Abort; + } +#endif + }else{ + SyStringInitFromBuf(&sName,pInstr->p3,SyStrlen((const char *)pInstr->p3)); + } + /* Extract the desired variable and if not available dynamically create it */ + pObj = VmExtractMemObj(&(*pVm),&sName,pInstr->p3 ? FALSE : TRUE,TRUE); + if( pObj == 0 ){ + VmErrorFormat(&(*pVm),PH7_CTX_ERR, + "Fatal, PH7 engine is running out of memory while loading variable '%z'",&sName); + goto Abort; + } + if( !pInstr->p3 ){ + PH7_MemObjRelease(&pTos[1]); + } + /* Perform the store operation */ + PH7_MemObjStore(pTos,pObj); + break; + } +/* + * STORE_IDX: P1 * P3 + * STORE_IDX_R: P1 * P3 + * + * Perfrom a store operation an a hashmap entry. + */ +case PH7_OP_STORE_IDX: +case PH7_OP_STORE_IDX_REF: { + ph7_hashmap *pMap = 0; /* cc warning */ + ph7_value *pKey; + sxu32 nIdx; + if( pInstr->iP1 ){ + /* Key is next on stack */ + pKey = pTos; + pTos--; + }else{ + pKey = 0; + } + nIdx = pTos->nIdx; + if( pTos->iFlags & MEMOBJ_HASHMAP ){ + /* Hashmap already loaded */ + pMap = (ph7_hashmap *)pTos->x.pOther; + if( pMap->iRef < 2 ){ + /* TICKET 1433-48: Prevent garbage collection */ + pMap->iRef = 2; + } + }else{ + ph7_value *pObj; + pObj = (ph7_value *)SySetAt(&pVm->aMemObj,nIdx); + if( pObj == 0 ){ + if( pKey ){ + PH7_MemObjRelease(pKey); + } + VmPopOperand(&pTos,1); + break; + } + /* Phase#1: Load the array */ + if( (pObj->iFlags & MEMOBJ_STRING) && (pInstr->iOp != PH7_OP_STORE_IDX_REF) ){ + VmPopOperand(&pTos,1); + if( (pTos->iFlags&MEMOBJ_STRING) == 0 ){ + /* Force a string cast */ + PH7_MemObjToString(pTos); + } + if( pKey == 0 ){ + /* Append string */ + if( SyBlobLength(&pTos->sBlob) > 0 ){ + SyBlobAppend(&pObj->sBlob,SyBlobData(&pTos->sBlob),SyBlobLength(&pTos->sBlob)); + } + }else{ + sxu32 nOfft; + if((pKey->iFlags & MEMOBJ_INT)){ + /* Force an int cast */ + PH7_MemObjToInteger(pKey); + } + nOfft = (sxu32)pKey->x.iVal; + if( nOfft < SyBlobLength(&pObj->sBlob) && SyBlobLength(&pTos->sBlob) > 0 ){ + const char *zBlob = (const char *)SyBlobData(&pTos->sBlob); + char *zData = (char *)SyBlobData(&pObj->sBlob); + zData[nOfft] = zBlob[0]; + }else{ + if( SyBlobLength(&pTos->sBlob) >= sizeof(char) ){ + /* Perform an append operation */ + SyBlobAppend(&pObj->sBlob,SyBlobData(&pTos->sBlob),sizeof(char)); + } + } + } + if( pKey ){ + PH7_MemObjRelease(pKey); + } + break; + }else if( (pObj->iFlags & MEMOBJ_HASHMAP) == 0 ){ + /* Force a hashmap cast */ + rc = PH7_MemObjToHashmap(pObj); + if( rc != SXRET_OK ){ + VmErrorFormat(&(*pVm),PH7_CTX_ERR,"Fatal, PH7 engine is running out of memory while creating a new array"); + goto Abort; + } + } + pMap = (ph7_hashmap *)pObj->x.pOther; + } + VmPopOperand(&pTos,1); + /* Phase#2: Perform the insertion */ + if( pInstr->iOp == PH7_OP_STORE_IDX_REF && pTos->nIdx != SXU32_HIGH ){ + /* Insertion by reference */ + PH7_HashmapInsertByRef(pMap,pKey,pTos->nIdx); + }else{ + PH7_HashmapInsert(pMap,pKey,pTos); + } + if( pKey ){ + PH7_MemObjRelease(pKey); + } + break; + } +/* + * INCR: P1 * * + * + * Force a numeric cast and increment the top of the stack by 1. + * If the P1 operand is set then perform a duplication of the top of + * the stack and increment after that. + */ +case PH7_OP_INCR: +#ifdef UNTRUST + if( pTos < pStack ){ + goto Abort; + } +#endif + if( (pTos->iFlags & (MEMOBJ_HASHMAP|MEMOBJ_OBJ|MEMOBJ_RES)) == 0 ){ + if( pTos->nIdx != SXU32_HIGH ){ + ph7_value *pObj; + if( (pObj = (ph7_value *)SySetAt(&pVm->aMemObj,pTos->nIdx)) != 0 ){ + /* Force a numeric cast */ + PH7_MemObjToNumeric(pObj); + if( pObj->iFlags & MEMOBJ_REAL ){ + pObj->rVal++; + /* Try to get an integer representation */ + PH7_MemObjTryInteger(pTos); + }else{ + pObj->x.iVal++; + MemObjSetType(pTos,MEMOBJ_INT); + } + if( pInstr->iP1 ){ + /* Pre-icrement */ + PH7_MemObjStore(pObj,pTos); + } + } + }else{ + if( pInstr->iP1 ){ + /* Force a numeric cast */ + PH7_MemObjToNumeric(pTos); + /* Pre-increment */ + if( pTos->iFlags & MEMOBJ_REAL ){ + pTos->rVal++; + /* Try to get an integer representation */ + PH7_MemObjTryInteger(pTos); + }else{ + pTos->x.iVal++; + MemObjSetType(pTos,MEMOBJ_INT); + } + } + } + } + break; +/* + * DECR: P1 * * + * + * Force a numeric cast and decrement the top of the stack by 1. + * If the P1 operand is set then perform a duplication of the top of the stack + * and decrement after that. + */ +case PH7_OP_DECR: +#ifdef UNTRUST + if( pTos < pStack ){ + goto Abort; + } +#endif + if( (pTos->iFlags & (MEMOBJ_HASHMAP|MEMOBJ_OBJ|MEMOBJ_RES|MEMOBJ_NULL)) == 0 ){ + /* Force a numeric cast */ + PH7_MemObjToNumeric(pTos); + if( pTos->nIdx != SXU32_HIGH ){ + ph7_value *pObj; + if( (pObj = (ph7_value *)SySetAt(&pVm->aMemObj,pTos->nIdx)) != 0 ){ + /* Force a numeric cast */ + PH7_MemObjToNumeric(pObj); + if( pObj->iFlags & MEMOBJ_REAL ){ + pObj->rVal--; + /* Try to get an integer representation */ + PH7_MemObjTryInteger(pTos); + }else{ + pObj->x.iVal--; + MemObjSetType(pTos,MEMOBJ_INT); + } + if( pInstr->iP1 ){ + /* Pre-icrement */ + PH7_MemObjStore(pObj,pTos); + } + } + }else{ + if( pInstr->iP1 ){ + /* Pre-increment */ + if( pTos->iFlags & MEMOBJ_REAL ){ + pTos->rVal--; + /* Try to get an integer representation */ + PH7_MemObjTryInteger(pTos); + }else{ + pTos->x.iVal--; + MemObjSetType(pTos,MEMOBJ_INT); + } + } + } + } + break; +/* + * UMINUS: * * * + * + * Perform a unary minus operation. + */ +case PH7_OP_UMINUS: +#ifdef UNTRUST + if( pTos < pStack ){ + goto Abort; + } +#endif + /* Force a numeric (integer,real or both) cast */ + PH7_MemObjToNumeric(pTos); + if( pTos->iFlags & MEMOBJ_REAL ){ + pTos->rVal = -pTos->rVal; + } + if( pTos->iFlags & MEMOBJ_INT ){ + pTos->x.iVal = -pTos->x.iVal; + } + break; +/* + * UPLUS: * * * + * + * Perform a unary plus operation. + */ +case PH7_OP_UPLUS: +#ifdef UNTRUST + if( pTos < pStack ){ + goto Abort; + } +#endif + /* Force a numeric (integer,real or both) cast */ + PH7_MemObjToNumeric(pTos); + if( pTos->iFlags & MEMOBJ_REAL ){ + pTos->rVal = +pTos->rVal; + } + if( pTos->iFlags & MEMOBJ_INT ){ + pTos->x.iVal = +pTos->x.iVal; + } + break; +/* + * OP_LNOT: * * * + * + * Interpret the top of the stack as a boolean value. Replace it + * with its complement. + */ +case PH7_OP_LNOT: +#ifdef UNTRUST + if( pTos < pStack ){ + goto Abort; + } +#endif + /* Force a boolean cast */ + if( (pTos->iFlags & MEMOBJ_BOOL) == 0 ){ + PH7_MemObjToBool(pTos); + } + pTos->x.iVal = !pTos->x.iVal; + break; +/* + * OP_BITNOT: * * * + * + * Interpret the top of the stack as an value.Replace it + * with its ones-complement. + */ +case PH7_OP_BITNOT: +#ifdef UNTRUST + if( pTos < pStack ){ + goto Abort; + } +#endif + /* Force an integer cast */ + if( (pTos->iFlags & MEMOBJ_INT) == 0 ){ + PH7_MemObjToInteger(pTos); + } + pTos->x.iVal = ~pTos->x.iVal; + break; +/* OP_MUL * * * + * OP_MUL_STORE * * * + * + * Pop the top two elements from the stack, multiply them together, + * and push the result back onto the stack. + */ +case PH7_OP_MUL: +case PH7_OP_MUL_STORE: { + ph7_value *pNos = &pTos[-1]; + /* Force the operand to be numeric */ +#ifdef UNTRUST + if( pNos < pStack ){ + goto Abort; + } +#endif + PH7_MemObjToNumeric(pTos); + PH7_MemObjToNumeric(pNos); + /* Perform the requested operation */ + if( MEMOBJ_REAL & (pTos->iFlags|pNos->iFlags) ){ + /* Floating point arithemic */ + ph7_real a,b,r; + if( (pTos->iFlags & MEMOBJ_REAL) == 0 ){ + PH7_MemObjToReal(pTos); + } + if( (pNos->iFlags & MEMOBJ_REAL) == 0 ){ + PH7_MemObjToReal(pNos); + } + a = pNos->rVal; + b = pTos->rVal; + r = a * b; + /* Push the result */ + pNos->rVal = r; + MemObjSetType(pNos,MEMOBJ_REAL); + /* Try to get an integer representation */ + PH7_MemObjTryInteger(pNos); + }else{ + /* Integer arithmetic */ + sxi64 a,b,r; + a = pNos->x.iVal; + b = pTos->x.iVal; + r = a * b; + /* Push the result */ + pNos->x.iVal = r; + MemObjSetType(pNos,MEMOBJ_INT); + } + if( pInstr->iOp == PH7_OP_MUL_STORE ){ + ph7_value *pObj; + if( pTos->nIdx == SXU32_HIGH ){ + PH7_VmThrowError(&(*pVm),0,PH7_CTX_ERR,"Cannot perform assignment on a constant class attribute"); + }else if( (pObj = (ph7_value *)SySetAt(&pVm->aMemObj,pTos->nIdx)) != 0 ){ + PH7_MemObjStore(pNos,pObj); + } + } + VmPopOperand(&pTos,1); + break; + } +/* OP_ADD * * * + * + * Pop the top two elements from the stack, add them together, + * and push the result back onto the stack. + */ +case PH7_OP_ADD:{ + ph7_value *pNos = &pTos[-1]; +#ifdef UNTRUST + if( pNos < pStack ){ + goto Abort; + } +#endif + /* Perform the addition */ + PH7_MemObjAdd(pNos,pTos,FALSE); + VmPopOperand(&pTos,1); + break; + } +/* + * OP_ADD_STORE * * * + * + * Pop the top two elements from the stack, add them together, + * and push the result back onto the stack. + */ +case PH7_OP_ADD_STORE:{ + ph7_value *pNos = &pTos[-1]; + ph7_value *pObj; + sxu32 nIdx; +#ifdef UNTRUST + if( pNos < pStack ){ + goto Abort; + } +#endif + /* Perform the addition */ + nIdx = pTos->nIdx; + PH7_MemObjAdd(pTos,pNos,TRUE); + /* Peform the store operation */ + if( nIdx == SXU32_HIGH ){ + PH7_VmThrowError(&(*pVm),0,PH7_CTX_ERR,"Cannot perform assignment on a constant class attribute"); + }else if( (pObj = (ph7_value *)SySetAt(&pVm->aMemObj,nIdx)) != 0 ){ + PH7_MemObjStore(pTos,pObj); + } + /* Ticket 1433-35: Perform a stack dup */ + PH7_MemObjStore(pTos,pNos); + VmPopOperand(&pTos,1); + break; + } +/* OP_SUB * * * + * + * Pop the top two elements from the stack, subtract the + * first (what was next on the stack) from the second (the + * top of the stack) and push the result back onto the stack. + */ +case PH7_OP_SUB: { + ph7_value *pNos = &pTos[-1]; +#ifdef UNTRUST + if( pNos < pStack ){ + goto Abort; + } +#endif + if( MEMOBJ_REAL & (pTos->iFlags|pNos->iFlags) ){ + /* Floating point arithemic */ + ph7_real a,b,r; + if( (pTos->iFlags & MEMOBJ_REAL) == 0 ){ + PH7_MemObjToReal(pTos); + } + if( (pNos->iFlags & MEMOBJ_REAL) == 0 ){ + PH7_MemObjToReal(pNos); + } + a = pNos->rVal; + b = pTos->rVal; + r = a - b; + /* Push the result */ + pNos->rVal = r; + MemObjSetType(pNos,MEMOBJ_REAL); + /* Try to get an integer representation */ + PH7_MemObjTryInteger(pNos); + }else{ + /* Integer arithmetic */ + sxi64 a,b,r; + a = pNos->x.iVal; + b = pTos->x.iVal; + r = a - b; + /* Push the result */ + pNos->x.iVal = r; + MemObjSetType(pNos,MEMOBJ_INT); + } + VmPopOperand(&pTos,1); + break; + } +/* OP_SUB_STORE * * * + * + * Pop the top two elements from the stack, subtract the + * first (what was next on the stack) from the second (the + * top of the stack) and push the result back onto the stack. + */ +case PH7_OP_SUB_STORE: { + ph7_value *pNos = &pTos[-1]; + ph7_value *pObj; +#ifdef UNTRUST + if( pNos < pStack ){ + goto Abort; + } +#endif + if( MEMOBJ_REAL & (pTos->iFlags|pNos->iFlags) ){ + /* Floating point arithemic */ + ph7_real a,b,r; + if( (pTos->iFlags & MEMOBJ_REAL) == 0 ){ + PH7_MemObjToReal(pTos); + } + if( (pNos->iFlags & MEMOBJ_REAL) == 0 ){ + PH7_MemObjToReal(pNos); + } + a = pTos->rVal; + b = pNos->rVal; + r = a - b; + /* Push the result */ + pNos->rVal = r; + MemObjSetType(pNos,MEMOBJ_REAL); + /* Try to get an integer representation */ + PH7_MemObjTryInteger(pNos); + }else{ + /* Integer arithmetic */ + sxi64 a,b,r; + a = pTos->x.iVal; + b = pNos->x.iVal; + r = a - b; + /* Push the result */ + pNos->x.iVal = r; + MemObjSetType(pNos,MEMOBJ_INT); + } + if( pTos->nIdx == SXU32_HIGH ){ + PH7_VmThrowError(&(*pVm),0,PH7_CTX_ERR,"Cannot perform assignment on a constant class attribute"); + }else if( (pObj = (ph7_value *)SySetAt(&pVm->aMemObj,pTos->nIdx)) != 0 ){ + PH7_MemObjStore(pNos,pObj); + } + VmPopOperand(&pTos,1); + break; + } + +/* + * OP_MOD * * * + * + * Pop the top two elements from the stack, divide the + * first (what was next on the stack) from the second (the + * top of the stack) and push the remainder after division + * onto the stack. + * Note: Only integer arithemtic is allowed. + */ +case PH7_OP_MOD:{ + ph7_value *pNos = &pTos[-1]; + sxi64 a,b,r; +#ifdef UNTRUST + if( pNos < pStack ){ + goto Abort; + } +#endif + /* Force the operands to be integer */ + if( (pTos->iFlags & MEMOBJ_INT) == 0 ){ + PH7_MemObjToInteger(pTos); + } + if( (pNos->iFlags & MEMOBJ_INT) == 0 ){ + PH7_MemObjToInteger(pNos); + } + /* Perform the requested operation */ + a = pNos->x.iVal; + b = pTos->x.iVal; + if( b == 0 ){ + r = 0; + VmErrorFormat(&(*pVm),PH7_CTX_ERR,"Division by zero %qd%%0",a); + /* goto Abort; */ + }else{ + r = a%b; + } + /* Push the result */ + pNos->x.iVal = r; + MemObjSetType(pNos,MEMOBJ_INT); + VmPopOperand(&pTos,1); + break; + } +/* + * OP_MOD_STORE * * * + * + * Pop the top two elements from the stack, divide the + * first (what was next on the stack) from the second (the + * top of the stack) and push the remainder after division + * onto the stack. + * Note: Only integer arithemtic is allowed. + */ +case PH7_OP_MOD_STORE: { + ph7_value *pNos = &pTos[-1]; + ph7_value *pObj; + sxi64 a,b,r; +#ifdef UNTRUST + if( pNos < pStack ){ + goto Abort; + } +#endif + /* Force the operands to be integer */ + if( (pTos->iFlags & MEMOBJ_INT) == 0 ){ + PH7_MemObjToInteger(pTos); + } + if( (pNos->iFlags & MEMOBJ_INT) == 0 ){ + PH7_MemObjToInteger(pNos); + } + /* Perform the requested operation */ + a = pTos->x.iVal; + b = pNos->x.iVal; + if( b == 0 ){ + r = 0; + VmErrorFormat(&(*pVm),PH7_CTX_ERR,"Division by zero %qd%%0",a); + /* goto Abort; */ + }else{ + r = a%b; + } + /* Push the result */ + pNos->x.iVal = r; + MemObjSetType(pNos,MEMOBJ_INT); + if( pTos->nIdx == SXU32_HIGH ){ + PH7_VmThrowError(&(*pVm),0,PH7_CTX_ERR,"Cannot perform assignment on a constant class attribute"); + }else if( (pObj = (ph7_value *)SySetAt(&pVm->aMemObj,pTos->nIdx)) != 0 ){ + PH7_MemObjStore(pNos,pObj); + } + VmPopOperand(&pTos,1); + break; + } +/* + * OP_DIV * * * + * + * Pop the top two elements from the stack, divide the + * first (what was next on the stack) from the second (the + * top of the stack) and push the result onto the stack. + * Note: Only floating point arithemtic is allowed. + */ +case PH7_OP_DIV:{ + ph7_value *pNos = &pTos[-1]; + ph7_real a,b,r; +#ifdef UNTRUST + if( pNos < pStack ){ + goto Abort; + } +#endif + /* Force the operands to be real */ + if( (pTos->iFlags & MEMOBJ_REAL) == 0 ){ + PH7_MemObjToReal(pTos); + } + if( (pNos->iFlags & MEMOBJ_REAL) == 0 ){ + PH7_MemObjToReal(pNos); + } + /* Perform the requested operation */ + a = pNos->rVal; + b = pTos->rVal; + if( b == 0 ){ + /* Division by zero */ + r = 0; + PH7_VmThrowError(&(*pVm),0,PH7_CTX_ERR,"Division by zero"); + /* goto Abort; */ + }else{ + r = a/b; + /* Push the result */ + pNos->rVal = r; + MemObjSetType(pNos,MEMOBJ_REAL); + /* Try to get an integer representation */ + PH7_MemObjTryInteger(pNos); + } + VmPopOperand(&pTos,1); + break; + } +/* + * OP_DIV_STORE * * * + * + * Pop the top two elements from the stack, divide the + * first (what was next on the stack) from the second (the + * top of the stack) and push the result onto the stack. + * Note: Only floating point arithemtic is allowed. + */ +case PH7_OP_DIV_STORE:{ + ph7_value *pNos = &pTos[-1]; + ph7_value *pObj; + ph7_real a,b,r; +#ifdef UNTRUST + if( pNos < pStack ){ + goto Abort; + } +#endif + /* Force the operands to be real */ + if( (pTos->iFlags & MEMOBJ_REAL) == 0 ){ + PH7_MemObjToReal(pTos); + } + if( (pNos->iFlags & MEMOBJ_REAL) == 0 ){ + PH7_MemObjToReal(pNos); + } + /* Perform the requested operation */ + a = pTos->rVal; + b = pNos->rVal; + if( b == 0 ){ + /* Division by zero */ + r = 0; + VmErrorFormat(&(*pVm),PH7_CTX_ERR,"Division by zero %qd/0",a); + /* goto Abort; */ + }else{ + r = a/b; + /* Push the result */ + pNos->rVal = r; + MemObjSetType(pNos,MEMOBJ_REAL); + /* Try to get an integer representation */ + PH7_MemObjTryInteger(pNos); + } + if( pTos->nIdx == SXU32_HIGH ){ + PH7_VmThrowError(&(*pVm),0,PH7_CTX_ERR,"Cannot perform assignment on a constant class attribute"); + }else if( (pObj = (ph7_value *)SySetAt(&pVm->aMemObj,pTos->nIdx)) != 0 ){ + PH7_MemObjStore(pNos,pObj); + } + VmPopOperand(&pTos,1); + break; + } +/* OP_BAND * * * + * + * Pop the top two elements from the stack. Convert both elements + * to integers. Push back onto the stack the bit-wise AND of the + * two elements. +*/ +/* OP_BOR * * * + * + * Pop the top two elements from the stack. Convert both elements + * to integers. Push back onto the stack the bit-wise OR of the + * two elements. + */ +/* OP_BXOR * * * + * + * Pop the top two elements from the stack. Convert both elements + * to integers. Push back onto the stack the bit-wise XOR of the + * two elements. + */ +case PH7_OP_BAND: +case PH7_OP_BOR: +case PH7_OP_BXOR:{ + ph7_value *pNos = &pTos[-1]; + sxi64 a,b,r; +#ifdef UNTRUST + if( pNos < pStack ){ + goto Abort; + } +#endif + /* Force the operands to be integer */ + if( (pTos->iFlags & MEMOBJ_INT) == 0 ){ + PH7_MemObjToInteger(pTos); + } + if( (pNos->iFlags & MEMOBJ_INT) == 0 ){ + PH7_MemObjToInteger(pNos); + } + /* Perform the requested operation */ + a = pNos->x.iVal; + b = pTos->x.iVal; + switch(pInstr->iOp){ + case PH7_OP_BOR_STORE: + case PH7_OP_BOR: r = a|b; break; + case PH7_OP_BXOR_STORE: + case PH7_OP_BXOR: r = a^b; break; + case PH7_OP_BAND_STORE: + case PH7_OP_BAND: + default: r = a&b; break; + } + /* Push the result */ + pNos->x.iVal = r; + MemObjSetType(pNos,MEMOBJ_INT); + VmPopOperand(&pTos,1); + break; + } +/* OP_BAND_STORE * * * + * + * Pop the top two elements from the stack. Convert both elements + * to integers. Push back onto the stack the bit-wise AND of the + * two elements. +*/ +/* OP_BOR_STORE * * * + * + * Pop the top two elements from the stack. Convert both elements + * to integers. Push back onto the stack the bit-wise OR of the + * two elements. + */ +/* OP_BXOR_STORE * * * + * + * Pop the top two elements from the stack. Convert both elements + * to integers. Push back onto the stack the bit-wise XOR of the + * two elements. + */ +case PH7_OP_BAND_STORE: +case PH7_OP_BOR_STORE: +case PH7_OP_BXOR_STORE:{ + ph7_value *pNos = &pTos[-1]; + ph7_value *pObj; + sxi64 a,b,r; +#ifdef UNTRUST + if( pNos < pStack ){ + goto Abort; + } +#endif + /* Force the operands to be integer */ + if( (pTos->iFlags & MEMOBJ_INT) == 0 ){ + PH7_MemObjToInteger(pTos); + } + if( (pNos->iFlags & MEMOBJ_INT) == 0 ){ + PH7_MemObjToInteger(pNos); + } + /* Perform the requested operation */ + a = pTos->x.iVal; + b = pNos->x.iVal; + switch(pInstr->iOp){ + case PH7_OP_BOR_STORE: + case PH7_OP_BOR: r = a|b; break; + case PH7_OP_BXOR_STORE: + case PH7_OP_BXOR: r = a^b; break; + case PH7_OP_BAND_STORE: + case PH7_OP_BAND: + default: r = a&b; break; + } + /* Push the result */ + pNos->x.iVal = r; + MemObjSetType(pNos,MEMOBJ_INT); + if( pTos->nIdx == SXU32_HIGH ){ + PH7_VmThrowError(&(*pVm),0,PH7_CTX_ERR,"Cannot perform assignment on a constant class attribute"); + }else if( (pObj = (ph7_value *)SySetAt(&pVm->aMemObj,pTos->nIdx)) != 0 ){ + PH7_MemObjStore(pNos,pObj); + } + VmPopOperand(&pTos,1); + break; + } +/* OP_SHL * * * + * + * Pop the top two elements from the stack. Convert both elements + * to integers. Push back onto the stack the second element shifted + * left by N bits where N is the top element on the stack. + * Note: Only integer arithmetic is allowed. + */ +/* OP_SHR * * * + * + * Pop the top two elements from the stack. Convert both elements + * to integers. Push back onto the stack the second element shifted + * right by N bits where N is the top element on the stack. + * Note: Only integer arithmetic is allowed. + */ +case PH7_OP_SHL: +case PH7_OP_SHR: { + ph7_value *pNos = &pTos[-1]; + sxi64 a,r; + sxi32 b; +#ifdef UNTRUST + if( pNos < pStack ){ + goto Abort; + } +#endif + /* Force the operands to be integer */ + if( (pTos->iFlags & MEMOBJ_INT) == 0 ){ + PH7_MemObjToInteger(pTos); + } + if( (pNos->iFlags & MEMOBJ_INT) == 0 ){ + PH7_MemObjToInteger(pNos); + } + /* Perform the requested operation */ + a = pNos->x.iVal; + b = (sxi32)pTos->x.iVal; + if( pInstr->iOp == PH7_OP_SHL ){ + r = a << b; + }else{ + r = a >> b; + } + /* Push the result */ + pNos->x.iVal = r; + MemObjSetType(pNos,MEMOBJ_INT); + VmPopOperand(&pTos,1); + break; + } +/* OP_SHL_STORE * * * + * + * Pop the top two elements from the stack. Convert both elements + * to integers. Push back onto the stack the second element shifted + * left by N bits where N is the top element on the stack. + * Note: Only integer arithmetic is allowed. + */ +/* OP_SHR_STORE * * * + * + * Pop the top two elements from the stack. Convert both elements + * to integers. Push back onto the stack the second element shifted + * right by N bits where N is the top element on the stack. + * Note: Only integer arithmetic is allowed. + */ +case PH7_OP_SHL_STORE: +case PH7_OP_SHR_STORE: { + ph7_value *pNos = &pTos[-1]; + ph7_value *pObj; + sxi64 a,r; + sxi32 b; +#ifdef UNTRUST + if( pNos < pStack ){ + goto Abort; + } +#endif + /* Force the operands to be integer */ + if( (pTos->iFlags & MEMOBJ_INT) == 0 ){ + PH7_MemObjToInteger(pTos); + } + if( (pNos->iFlags & MEMOBJ_INT) == 0 ){ + PH7_MemObjToInteger(pNos); + } + /* Perform the requested operation */ + a = pTos->x.iVal; + b = (sxi32)pNos->x.iVal; + if( pInstr->iOp == PH7_OP_SHL_STORE ){ + r = a << b; + }else{ + r = a >> b; + } + /* Push the result */ + pNos->x.iVal = r; + MemObjSetType(pNos,MEMOBJ_INT); + if( pTos->nIdx == SXU32_HIGH ){ + PH7_VmThrowError(&(*pVm),0,PH7_CTX_ERR,"Cannot perform assignment on a constant class attribute"); + }else if( (pObj = (ph7_value *)SySetAt(&pVm->aMemObj,pTos->nIdx)) != 0 ){ + PH7_MemObjStore(pNos,pObj); + } + VmPopOperand(&pTos,1); + break; + } +/* CAT: P1 * * + * + * Pop P1 elements from the stack. Concatenate them togeher and push the result + * back. + */ +case PH7_OP_CAT:{ + ph7_value *pNos,*pCur; + if( pInstr->iP1 < 1 ){ + pNos = &pTos[-1]; + }else{ + pNos = &pTos[-pInstr->iP1+1]; + } +#ifdef UNTRUST + if( pNos < pStack ){ + goto Abort; + } +#endif + /* Force a string cast */ + if( (pNos->iFlags & MEMOBJ_STRING) == 0 ){ + PH7_MemObjToString(pNos); + } + pCur = &pNos[1]; + while( pCur <= pTos ){ + if( (pCur->iFlags & MEMOBJ_STRING) == 0 ){ + PH7_MemObjToString(pCur); + } + /* Perform the concatenation */ + if( SyBlobLength(&pCur->sBlob) > 0 ){ + PH7_MemObjStringAppend(pNos,(const char *)SyBlobData(&pCur->sBlob),SyBlobLength(&pCur->sBlob)); + } + SyBlobRelease(&pCur->sBlob); + pCur++; + } + pTos = pNos; + break; + } +/* CAT_STORE: * * * + * + * Pop two elements from the stack. Concatenate them togeher and push the result + * back. + */ +case PH7_OP_CAT_STORE:{ + ph7_value *pNos = &pTos[-1]; + ph7_value *pObj; +#ifdef UNTRUST + if( pNos < pStack ){ + goto Abort; + } +#endif + if((pTos->iFlags & MEMOBJ_STRING) == 0 ){ + /* Force a string cast */ + PH7_MemObjToString(pTos); + } + if((pNos->iFlags & MEMOBJ_STRING) == 0 ){ + /* Force a string cast */ + PH7_MemObjToString(pNos); + } + /* Perform the concatenation (Reverse order) */ + if( SyBlobLength(&pNos->sBlob) > 0 ){ + PH7_MemObjStringAppend(pTos,(const char *)SyBlobData(&pNos->sBlob),SyBlobLength(&pNos->sBlob)); + } + /* Perform the store operation */ + if( pTos->nIdx == SXU32_HIGH ){ + PH7_VmThrowError(&(*pVm),0,PH7_CTX_ERR,"Cannot perform assignment on a constant class attribute"); + }else if( (pObj = (ph7_value *)SySetAt(&pVm->aMemObj,pTos->nIdx)) != 0 ){ + PH7_MemObjStore(pTos,pObj); + } + PH7_MemObjStore(pTos,pNos); + VmPopOperand(&pTos,1); + break; + } +/* OP_AND: * * * + * + * Pop two values off the stack. Take the logical AND of the + * two values and push the resulting boolean value back onto the + * stack. + */ +/* OP_OR: * * * + * + * Pop two values off the stack. Take the logical OR of the + * two values and push the resulting boolean value back onto the + * stack. + */ +case PH7_OP_LAND: +case PH7_OP_LOR: { + ph7_value *pNos = &pTos[-1]; + sxi32 v1, v2; /* 0==TRUE, 1==FALSE, 2==UNKNOWN or NULL */ +#ifdef UNTRUST + if( pNos < pStack ){ + goto Abort; + } +#endif + /* Force a boolean cast */ + if((pTos->iFlags & MEMOBJ_BOOL) == 0 ){ + PH7_MemObjToBool(pTos); + } + if((pNos->iFlags & MEMOBJ_BOOL) == 0 ){ + PH7_MemObjToBool(pNos); + } + v1 = pNos->x.iVal == 0 ? 1 : 0; + v2 = pTos->x.iVal == 0 ? 1 : 0; + if( pInstr->iOp == PH7_OP_LAND ){ + static const unsigned char and_logic[] = { 0, 1, 2, 1, 1, 1, 2, 1, 2 }; + v1 = and_logic[v1*3+v2]; + }else{ + static const unsigned char or_logic[] = { 0, 0, 0, 0, 1, 2, 0, 2, 2 }; + v1 = or_logic[v1*3+v2]; + } + if( v1 == 2 ){ + v1 = 1; + } + VmPopOperand(&pTos,1); + pTos->x.iVal = v1 == 0 ? 1 : 0; + MemObjSetType(pTos,MEMOBJ_BOOL); + break; + } +/* OP_LXOR: * * * + * + * Pop two values off the stack. Take the logical XOR of the + * two values and push the resulting boolean value back onto the + * stack. + * According to the PHP language reference manual: + * $a xor $b is evaluated to TRUE if either $a or $b is + * TRUE,but not both. + */ +case PH7_OP_LXOR:{ + ph7_value *pNos = &pTos[-1]; + sxi32 v = 0; +#ifdef UNTRUST + if( pNos < pStack ){ + goto Abort; + } +#endif + /* Force a boolean cast */ + if((pTos->iFlags & MEMOBJ_BOOL) == 0 ){ + PH7_MemObjToBool(pTos); + } + if((pNos->iFlags & MEMOBJ_BOOL) == 0 ){ + PH7_MemObjToBool(pNos); + } + if( (pNos->x.iVal && !pTos->x.iVal) || (pTos->x.iVal && !pNos->x.iVal) ){ + v = 1; + } + VmPopOperand(&pTos,1); + pTos->x.iVal = v; + MemObjSetType(pTos,MEMOBJ_BOOL); + break; + } +/* OP_EQ P1 P2 P3 + * + * Pop the top two elements from the stack. If they are equal, then + * jump to instruction P2. Otherwise, continue to the next instruction. + * If P2 is zero, do not jump. Instead, push a boolean 1 (TRUE) onto the + * stack if the jump would have been taken, or a 0 (FALSE) if not. + */ +/* OP_NEQ P1 P2 P3 + * + * Pop the top two elements from the stack. If they are not equal, then + * jump to instruction P2. Otherwise, continue to the next instruction. + * If P2 is zero, do not jump. Instead, push a boolean 1 (TRUE) onto the + * stack if the jump would have been taken, or a 0 (FALSE) if not. + */ +case PH7_OP_EQ: +case PH7_OP_NEQ: { + ph7_value *pNos = &pTos[-1]; + /* Perform the comparison and act accordingly */ +#ifdef UNTRUST + if( pNos < pStack ){ + goto Abort; + } +#endif + rc = PH7_MemObjCmp(pNos,pTos,FALSE,0); + if( pInstr->iOp == PH7_OP_EQ ){ + rc = rc == 0; + }else{ + rc = rc != 0; + } + VmPopOperand(&pTos,1); + if( !pInstr->iP2 ){ + /* Push comparison result without taking the jump */ + PH7_MemObjRelease(pTos); + pTos->x.iVal = rc; + /* Invalidate any prior representation */ + MemObjSetType(pTos,MEMOBJ_BOOL); + }else{ + if( rc ){ + /* Jump to the desired location */ + pc = pInstr->iP2 - 1; + VmPopOperand(&pTos,1); + } + } + break; + } +/* OP_TEQ P1 P2 * + * + * Pop the top two elements from the stack. If they have the same type and are equal + * then jump to instruction P2. Otherwise, continue to the next instruction. + * If P2 is zero, do not jump. Instead, push a boolean 1 (TRUE) onto the + * stack if the jump would have been taken, or a 0 (FALSE) if not. + */ +case PH7_OP_TEQ: { + ph7_value *pNos = &pTos[-1]; + /* Perform the comparison and act accordingly */ +#ifdef UNTRUST + if( pNos < pStack ){ + goto Abort; + } +#endif + rc = PH7_MemObjCmp(pNos,pTos,TRUE,0) == 0; + VmPopOperand(&pTos,1); + if( !pInstr->iP2 ){ + /* Push comparison result without taking the jump */ + PH7_MemObjRelease(pTos); + pTos->x.iVal = rc; + /* Invalidate any prior representation */ + MemObjSetType(pTos,MEMOBJ_BOOL); + }else{ + if( rc ){ + /* Jump to the desired location */ + pc = pInstr->iP2 - 1; + VmPopOperand(&pTos,1); + } + } + break; + } +/* OP_TNE P1 P2 * + * + * Pop the top two elements from the stack.If they are not equal an they are not + * of the same type, then jump to instruction P2. Otherwise, continue to the next + * instruction. + * If P2 is zero, do not jump. Instead, push a boolean 1 (TRUE) onto the + * stack if the jump would have been taken, or a 0 (FALSE) if not. + * + */ +case PH7_OP_TNE: { + ph7_value *pNos = &pTos[-1]; + /* Perform the comparison and act accordingly */ +#ifdef UNTRUST + if( pNos < pStack ){ + goto Abort; + } +#endif + rc = PH7_MemObjCmp(pNos,pTos,TRUE,0) != 0; + VmPopOperand(&pTos,1); + if( !pInstr->iP2 ){ + /* Push comparison result without taking the jump */ + PH7_MemObjRelease(pTos); + pTos->x.iVal = rc; + /* Invalidate any prior representation */ + MemObjSetType(pTos,MEMOBJ_BOOL); + }else{ + if( rc ){ + /* Jump to the desired location */ + pc = pInstr->iP2 - 1; + VmPopOperand(&pTos,1); + } + } + break; + } +/* OP_LT P1 P2 P3 + * + * Pop the top two elements from the stack. If the second element (the top of stack) + * is less than the first (next on stack),then jump to instruction P2.Otherwise + * continue to the next instruction. In other words, jump if pNosiOp == PH7_OP_LE ){ + rc = rc < 1; + }else{ + rc = rc < 0; + } + VmPopOperand(&pTos,1); + if( !pInstr->iP2 ){ + /* Push comparison result without taking the jump */ + PH7_MemObjRelease(pTos); + pTos->x.iVal = rc; + /* Invalidate any prior representation */ + MemObjSetType(pTos,MEMOBJ_BOOL); + }else{ + if( rc ){ + /* Jump to the desired location */ + pc = pInstr->iP2 - 1; + VmPopOperand(&pTos,1); + } + } + break; + } +/* OP_GT P1 P2 P3 + * + * Pop the top two elements from the stack. If the second element (the top of stack) + * is greater than the first (next on stack),then jump to instruction P2.Otherwise + * continue to the next instruction. In other words, jump if pNosiOp == PH7_OP_GE ){ + rc = rc >= 0; + }else{ + rc = rc > 0; + } + VmPopOperand(&pTos,1); + if( !pInstr->iP2 ){ + /* Push comparison result without taking the jump */ + PH7_MemObjRelease(pTos); + pTos->x.iVal = rc; + /* Invalidate any prior representation */ + MemObjSetType(pTos,MEMOBJ_BOOL); + }else{ + if( rc ){ + /* Jump to the desired location */ + pc = pInstr->iP2 - 1; + VmPopOperand(&pTos,1); + } + } + break; + } +/* OP_SEQ P1 P2 * + * Strict string comparison. + * Pop the top two elements from the stack. If they are equal (pure text comparison) + * then jump to instruction P2. Otherwise, continue to the next instruction. + * If either operand is NULL then the comparison result is FALSE. + * The SyMemcmp() routine is used for the comparison. For a numeric comparison + * use PH7_OP_EQ. + * If P2 is zero, do not jump.Instead, push a boolean 1 (TRUE) onto the + * stack if the jump would have been taken, or a 0 (FALSE) if not. + */ +/* OP_SNE P1 P2 * + * Strict string comparison. + * Pop the top two elements from the stack. If they are not equal (pure text comparison) + * then jump to instruction P2. Otherwise, continue to the next instruction. + * If either operand is NULL then the comparison result is FALSE. + * The SyMemcmp() routine is used for the comparison. For a numeric comparison + * use PH7_OP_EQ. + * If P2 is zero, do not jump.Instead, push a boolean 1 (TRUE) onto the + * stack if the jump would have been taken, or a 0 (FALSE) if not. + */ +case PH7_OP_SEQ: +case PH7_OP_SNE: { + ph7_value *pNos = &pTos[-1]; + SyString s1,s2; + /* Perform the comparison and act accordingly */ +#ifdef UNTRUST + if( pNos < pStack ){ + goto Abort; + } +#endif + /* Force a string cast */ + if((pTos->iFlags & MEMOBJ_STRING) == 0 ){ + PH7_MemObjToString(pTos); + } + if((pNos->iFlags & MEMOBJ_STRING) == 0 ){ + PH7_MemObjToString(pNos); + } + SyStringInitFromBuf(&s1,SyBlobData(&pNos->sBlob),SyBlobLength(&pNos->sBlob)); + SyStringInitFromBuf(&s2,SyBlobData(&pTos->sBlob),SyBlobLength(&pTos->sBlob)); + rc = SyStringCmp(&s1,&s2,SyMemcmp); + if( pInstr->iOp == PH7_OP_NEQ ){ + rc = rc != 0; + }else{ + rc = rc == 0; + } + VmPopOperand(&pTos,1); + if( !pInstr->iP2 ){ + /* Push comparison result without taking the jump */ + PH7_MemObjRelease(pTos); + pTos->x.iVal = rc; + /* Invalidate any prior representation */ + MemObjSetType(pTos,MEMOBJ_BOOL); + }else{ + if( rc ){ + /* Jump to the desired location */ + pc = pInstr->iP2 - 1; + VmPopOperand(&pTos,1); + } + } + break; + } +/* + * OP_LOAD_REF * * * + * Push the index of a referenced object on the stack. + */ +case PH7_OP_LOAD_REF: { + sxu32 nIdx; +#ifdef UNTRUST + if( pTos < pStack ){ + goto Abort; + } +#endif + /* Extract memory object index */ + nIdx = pTos->nIdx; + if( nIdx != SXU32_HIGH /* Not a constant */ ){ + /* Nullify the object */ + PH7_MemObjRelease(pTos); + /* Mark as constant and store the index on the top of the stack */ + pTos->x.iVal = (sxi64)nIdx; + pTos->nIdx = SXU32_HIGH; + pTos->iFlags = MEMOBJ_INT|MEMOBJ_REFERENCE; + } + break; + } +/* + * OP_STORE_REF * * P3 + * Perform an assignment operation by reference. + */ +case PH7_OP_STORE_REF: { + SyString sName = { 0 , 0 }; + SyHashEntry *pEntry; + sxu32 nIdx; +#ifdef UNTRUST + if( pTos < pStack ){ + goto Abort; + } +#endif + if( pInstr->p3 == 0 ){ + char *zName; + /* Take the variable name from the Next on the stack */ + if( (pTos->iFlags & MEMOBJ_STRING) == 0 ){ + /* Force a string cast */ + PH7_MemObjToString(pTos); + } + if( SyBlobLength(&pTos->sBlob) > 0 ){ + zName = SyMemBackendStrDup(&pVm->sAllocator, + (const char *)SyBlobData(&pTos->sBlob),SyBlobLength(&pTos->sBlob)); + if( zName ){ + SyStringInitFromBuf(&sName,zName,SyBlobLength(&pTos->sBlob)); + } + } + PH7_MemObjRelease(pTos); + pTos--; + }else{ + SyStringInitFromBuf(&sName,pInstr->p3,SyStrlen((const char *)pInstr->p3)); + } + nIdx = pTos->nIdx; + if(nIdx == SXU32_HIGH ){ + if( (pTos->iFlags & (MEMOBJ_OBJ|MEMOBJ_HASHMAP|MEMOBJ_RES)) == 0 ){ + PH7_VmThrowError(&(*pVm),0,PH7_CTX_ERR, + "Reference operator require a variable not a constant as it's right operand"); + }else{ + ph7_value *pObj; + /* Extract the desired variable and if not available dynamically create it */ + pObj = VmExtractMemObj(&(*pVm),&sName,FALSE,TRUE); + if( pObj == 0 ){ + VmErrorFormat(&(*pVm),PH7_CTX_ERR, + "Fatal, PH7 engine is running out of memory while loading variable '%z'",&sName); + goto Abort; + } + /* Perform the store operation */ + PH7_MemObjStore(pTos,pObj); + pTos->nIdx = pObj->nIdx; + } + }else if( sName.nByte > 0){ + if( (pTos->iFlags & MEMOBJ_HASHMAP) && (pVm->pGlobal == (ph7_hashmap *)pTos->x.pOther) ){ + PH7_VmThrowError(&(*pVm),0,PH7_CTX_ERR,"$GLOBALS is a read-only array and therefore cannot be referenced"); + }else{ + VmFrame *pFrame = pVm->pFrame; + while( pFrame->pParent && (pFrame->iFlags & VM_FRAME_EXCEPTION) ){ + /* Safely ignore the exception frame */ + pFrame = pFrame->pParent; + } + /* Query the local frame */ + pEntry = SyHashGet(&pFrame->hVar,(const void *)sName.zString,sName.nByte); + if( pEntry ){ + VmErrorFormat(&(*pVm),PH7_CTX_ERR,"Referenced variable name '%z' already exists",&sName); + }else{ + rc = SyHashInsert(&pFrame->hVar,(const void *)sName.zString,sName.nByte,SX_INT_TO_PTR(nIdx)); + if( pFrame->pParent == 0 ){ + /* Insert in the $GLOBALS array */ + VmHashmapRefInsert(pVm->pGlobal,sName.zString,sName.nByte,nIdx); + } + if( rc == SXRET_OK ){ + PH7_VmRefObjInstall(&(*pVm),nIdx,SyHashLastEntry(&pFrame->hVar),0,0); + } + } + } + } + break; + } +/* + * OP_UPLINK P1 * * + * Link a variable to the top active VM frame. + * This is used to implement the 'global' PHP construct. + */ +case PH7_OP_UPLINK: { + if( pVm->pFrame->pParent ){ + ph7_value *pLink = &pTos[-pInstr->iP1+1]; + SyString sName; + /* Perform the link */ + while( pLink <= pTos ){ + if((pLink->iFlags & MEMOBJ_STRING) == 0 ){ + /* Force a string cast */ + PH7_MemObjToString(pLink); + } + SyStringInitFromBuf(&sName,SyBlobData(&pLink->sBlob),SyBlobLength(&pLink->sBlob)); + if( sName.nByte > 0 ){ + VmFrameLink(&(*pVm),&sName); + } + pLink++; + } + } + VmPopOperand(&pTos,pInstr->iP1); + break; + } +/* + * OP_LOAD_EXCEPTION * P2 P3 + * Push an exception in the corresponding container so that + * it can be thrown later by the OP_THROW instruction. + */ +case PH7_OP_LOAD_EXCEPTION: { + ph7_exception *pException = (ph7_exception *)pInstr->p3; + VmFrame *pFrame; + SySetPut(&pVm->aException,(const void *)&pException); + /* Create the exception frame */ + rc = VmEnterFrame(&(*pVm),0,0,&pFrame); + if( rc != SXRET_OK ){ + VmErrorFormat(&(*pVm),PH7_CTX_ERR,"Fatal PH7 engine is runnig out of memory"); + goto Abort; + } + /* Mark the special frame */ + pFrame->iFlags |= VM_FRAME_EXCEPTION; + pFrame->iExceptionJump = pInstr->iP2; + /* Point to the frame that trigger the exception */ + pFrame = pFrame->pParent; + while( pFrame->pParent && (pFrame->iFlags & VM_FRAME_EXCEPTION) ){ + pFrame = pFrame->pParent; + } + pException->pFrame = pFrame; + break; + } +/* + * OP_POP_EXCEPTION * * P3 + * Pop a previously pushed exception from the corresponding container. + */ +case PH7_OP_POP_EXCEPTION: { + ph7_exception *pException = (ph7_exception *)pInstr->p3; + if( SySetUsed(&pVm->aException) > 0 ){ + ph7_exception **apException; + /* Pop the loaded exception */ + apException = (ph7_exception **)SySetBasePtr(&pVm->aException); + if( pException == apException[SySetUsed(&pVm->aException) - 1] ){ + (void)SySetPop(&pVm->aException); + } + } + pException->pFrame = 0; + /* Leave the exception frame */ + VmLeaveFrame(&(*pVm)); + break; + } + +/* + * OP_THROW * P2 * + * Throw an user exception. + */ +case PH7_OP_THROW: { + VmFrame *pFrame = pVm->pFrame; + sxu32 nJump = pInstr->iP2; +#ifdef UNTRUST + if( pTos < pStack ){ + goto Abort; + } +#endif + while( pFrame->pParent && (pFrame->iFlags & VM_FRAME_EXCEPTION) ){ + /* Safely ignore the exception frame */ + pFrame = pFrame->pParent; + } + /* Tell the upper layer that an exception was thrown */ + pFrame->iFlags |= VM_FRAME_THROW; + if( pTos->iFlags & MEMOBJ_OBJ ){ + ph7_class_instance *pThis = (ph7_class_instance *)pTos->x.pOther; + ph7_class *pException; + /* Make sure the loaded object is an instance of the 'Exception' base class. + */ + pException = PH7_VmExtractClass(&(*pVm),"Exception",sizeof("Exception")-1,TRUE,0); + if( pException == 0 || !VmInstanceOf(pThis->pClass,pException) ){ + /* Exceptions must be valid objects derived from the Exception base class */ + rc = VmUncaughtException(&(*pVm),pThis); + if( rc == SXERR_ABORT ){ + /* Abort processing immediately */ + goto Abort; + } + }else{ + /* Throw the exception */ + rc = VmThrowException(&(*pVm),pThis); + if( rc == SXERR_ABORT ){ + /* Abort processing immediately */ + goto Abort; + } + } + }else{ + /* Expecting a class instance */ + VmUncaughtException(&(*pVm),0); + if( rc == SXERR_ABORT ){ + /* Abort processing immediately */ + goto Abort; + } + } + /* Pop the top entry */ + VmPopOperand(&pTos,1); + /* Perform an unconditional jump */ + pc = nJump - 1; + break; + } +/* + * OP_FOREACH_INIT * P2 P3 + * Prepare a foreach step. + */ +case PH7_OP_FOREACH_INIT: { + ph7_foreach_info *pInfo = (ph7_foreach_info *)pInstr->p3; + void *pName; +#ifdef UNTRUST + if( pTos < pStack ){ + goto Abort; + } +#endif + if( SyStringLength(&pInfo->sValue) < 1 ){ + /* Take the variable name from the top of the stack */ + if( (pTos->iFlags & MEMOBJ_STRING) == 0 ){ + /* Force a string cast */ + PH7_MemObjToString(pTos); + } + /* Duplicate name */ + if( SyBlobLength(&pTos->sBlob) > 0 ){ + pName = SyMemBackendDup(&pVm->sAllocator,SyBlobData(&pTos->sBlob),SyBlobLength(&pTos->sBlob)); + SyStringInitFromBuf(&pInfo->sValue,pName,SyBlobLength(&pTos->sBlob)); + } + VmPopOperand(&pTos,1); + } + if( (pInfo->iFlags & PH7_4EACH_STEP_KEY) && SyStringLength(&pInfo->sKey) < 1 ){ + if( (pTos->iFlags & MEMOBJ_STRING) == 0 ){ + /* Force a string cast */ + PH7_MemObjToString(pTos); + } + /* Duplicate name */ + if( SyBlobLength(&pTos->sBlob) > 0 ){ + pName = SyMemBackendDup(&pVm->sAllocator,SyBlobData(&pTos->sBlob),SyBlobLength(&pTos->sBlob)); + SyStringInitFromBuf(&pInfo->sKey,pName,SyBlobLength(&pTos->sBlob)); + } + VmPopOperand(&pTos,1); + } + /* Make sure we are dealing with a hashmap aka 'array' or an object */ + if( (pTos->iFlags & (MEMOBJ_HASHMAP|MEMOBJ_OBJ)) == 0 || SyStringLength(&pInfo->sValue) < 1 ){ + /* Jump out of the loop */ + if( (pTos->iFlags & MEMOBJ_NULL) == 0 ){ + PH7_VmThrowError(&(*pVm),0,PH7_CTX_WARNING,"Invalid argument supplied for the foreach statement,expecting array or class instance"); + } + pc = pInstr->iP2 - 1; + }else{ + ph7_foreach_step *pStep; + pStep = (ph7_foreach_step *)SyMemBackendPoolAlloc(&pVm->sAllocator,sizeof(ph7_foreach_step)); + if( pStep == 0 ){ + PH7_VmThrowError(&(*pVm),0,PH7_CTX_ERR,"PH7 is running out of memory while preparing the 'foreach' step"); + /* Jump out of the loop */ + pc = pInstr->iP2 - 1; + }else{ + /* Zero the structure */ + SyZero(pStep,sizeof(ph7_foreach_step)); + /* Prepare the step */ + pStep->iFlags = pInfo->iFlags; + if( pTos->iFlags & MEMOBJ_HASHMAP ){ + ph7_hashmap *pMap = (ph7_hashmap *)pTos->x.pOther; + /* Reset the internal loop cursor */ + PH7_HashmapResetLoopCursor(pMap); + /* Mark the step */ + pStep->iFlags |= PH7_4EACH_STEP_HASHMAP; + pStep->xIter.pMap = pMap; + pMap->iRef++; + }else{ + ph7_class_instance *pThis = (ph7_class_instance *)pTos->x.pOther; + /* Reset the loop cursor */ + SyHashResetLoopCursor(&pThis->hAttr); + /* Mark the step */ + pStep->iFlags |= PH7_4EACH_STEP_OBJECT; + pStep->xIter.pThis = pThis; + pThis->iRef++; + } + } + if( SXRET_OK != SySetPut(&pInfo->aStep,(const void *)&pStep) ){ + PH7_VmThrowError(&(*pVm),0,PH7_CTX_ERR,"PH7 is running out of memory while preparing the 'foreach' step"); + SyMemBackendPoolFree(&pVm->sAllocator,pStep); + /* Jump out of the loop */ + pc = pInstr->iP2 - 1; + } + } + VmPopOperand(&pTos,1); + break; + } +/* + * OP_FOREACH_STEP * P2 P3 + * Perform a foreach step. Jump to P2 at the end of the step. + */ +case PH7_OP_FOREACH_STEP: { + ph7_foreach_info *pInfo = (ph7_foreach_info *)pInstr->p3; + ph7_foreach_step **apStep,*pStep; + ph7_value *pValue; + VmFrame *pFrame; + /* Peek the last step */ + apStep = (ph7_foreach_step **)SySetBasePtr(&pInfo->aStep); + pStep = apStep[SySetUsed(&pInfo->aStep) - 1]; + pFrame = pVm->pFrame; + while( pFrame->pParent && (pFrame->iFlags & VM_FRAME_EXCEPTION) ){ + /* Safely ignore the exception frame */ + pFrame = pFrame->pParent; + } + if( pStep->iFlags & PH7_4EACH_STEP_HASHMAP ){ + ph7_hashmap *pMap = pStep->xIter.pMap; + ph7_hashmap_node *pNode; + /* Extract the current node value */ + pNode = PH7_HashmapGetNextEntry(pMap); + if( pNode == 0 ){ + /* No more entry to process */ + pc = pInstr->iP2 - 1; /* Jump to this destination */ + if( pStep->iFlags & PH7_4EACH_STEP_REF ){ + /* Break the reference with the last element */ + SyHashDeleteEntry(&pFrame->hVar,SyStringData(&pInfo->sValue),SyStringLength(&pInfo->sValue),0); + } + /* Automatically reset the loop cursor */ + PH7_HashmapResetLoopCursor(pMap); + /* Cleanup the mess left behind */ + SyMemBackendPoolFree(&pVm->sAllocator,pStep); + SySetPop(&pInfo->aStep); + PH7_HashmapUnref(pMap); + }else{ + if( (pStep->iFlags & PH7_4EACH_STEP_KEY) && SyStringLength(&pInfo->sKey) > 0 ){ + ph7_value *pKey = VmExtractMemObj(&(*pVm),&pInfo->sKey,FALSE,TRUE); + if( pKey ){ + PH7_HashmapExtractNodeKey(pNode,pKey); + } + } + if( pStep->iFlags & PH7_4EACH_STEP_REF ){ + SyHashEntry *pEntry; + /* Pass by reference */ + pEntry = SyHashGet(&pFrame->hVar,SyStringData(&pInfo->sValue),SyStringLength(&pInfo->sValue)); + if( pEntry ){ + pEntry->pUserData = SX_INT_TO_PTR(pNode->nValIdx); + }else{ + SyHashInsert(&pFrame->hVar,SyStringData(&pInfo->sValue),SyStringLength(&pInfo->sValue), + SX_INT_TO_PTR(pNode->nValIdx)); + } + }else{ + /* Make a copy of the entry value */ + pValue = VmExtractMemObj(&(*pVm),&pInfo->sValue,FALSE,TRUE); + if( pValue ){ + PH7_HashmapExtractNodeValue(pNode,pValue,TRUE); + } + } + } + }else{ + ph7_class_instance *pThis = pStep->xIter.pThis; + VmClassAttr *pVmAttr = 0; /* Stupid cc -06 warning */ + SyHashEntry *pEntry; + /* Point to the next attribute */ + while((pEntry = SyHashGetNextEntry(&pThis->hAttr)) != 0 ){ + pVmAttr = (VmClassAttr *)pEntry->pUserData; + /* Check access permission */ + if( VmClassMemberAccess(&(*pVm),pThis->pClass,&pVmAttr->pAttr->sName, + pVmAttr->pAttr->iProtection,FALSE) ){ + break; /* Access is granted */ + } + } + if( pEntry == 0 ){ + /* Clean up the mess left behind */ + pc = pInstr->iP2 - 1; /* Jump to this destination */ + if( pStep->iFlags & PH7_4EACH_STEP_REF ){ + /* Break the reference with the last element */ + SyHashDeleteEntry(&pFrame->hVar,SyStringData(&pInfo->sValue),SyStringLength(&pInfo->sValue),0); + } + SyMemBackendPoolFree(&pVm->sAllocator,pStep); + SySetPop(&pInfo->aStep); + PH7_ClassInstanceUnref(pThis); + }else{ + SyString *pAttrName = &pVmAttr->pAttr->sName; + ph7_value *pAttrValue; + if( (pStep->iFlags & PH7_4EACH_STEP_KEY) && SyStringLength(&pInfo->sKey) > 0){ + /* Fill with the current attribute name */ + ph7_value *pKey = VmExtractMemObj(&(*pVm),&pInfo->sKey,FALSE,TRUE); + if( pKey ){ + SyBlobReset(&pKey->sBlob); + SyBlobAppend(&pKey->sBlob,pAttrName->zString,pAttrName->nByte); + MemObjSetType(pKey,MEMOBJ_STRING); + } + } + /* Extract attribute value */ + pAttrValue = PH7_ClassInstanceExtractAttrValue(pThis,pVmAttr); + if( pAttrValue ){ + if( pStep->iFlags & PH7_4EACH_STEP_REF ){ + /* Pass by reference */ + pEntry = SyHashGet(&pFrame->hVar,SyStringData(&pInfo->sValue),SyStringLength(&pInfo->sValue)); + if( pEntry ){ + pEntry->pUserData = SX_INT_TO_PTR(pVmAttr->nIdx); + }else{ + SyHashInsert(&pFrame->hVar,SyStringData(&pInfo->sValue),SyStringLength(&pInfo->sValue), + SX_INT_TO_PTR(pVmAttr->nIdx)); + } + }else{ + /* Make a copy of the attribute value */ + pValue = VmExtractMemObj(&(*pVm),&pInfo->sValue,FALSE,TRUE); + if( pValue ){ + PH7_MemObjStore(pAttrValue,pValue); + } + } + } + } + } + break; + } +/* + * OP_MEMBER P1 P2 + * Load class attribute/method on the stack. + */ +case PH7_OP_MEMBER: { + ph7_class_instance *pThis; + ph7_value *pNos; + SyString sName; + if( !pInstr->iP1 ){ + pNos = &pTos[-1]; +#ifdef UNTRUST + if( pNos < pStack ){ + goto Abort; + } +#endif + if( pNos->iFlags & MEMOBJ_OBJ ){ + ph7_class *pClass; + /* Class already instantiated */ + pThis = (ph7_class_instance *)pNos->x.pOther; + /* Point to the instantiated class */ + pClass = pThis->pClass; + /* Extract attribute name first */ + SyStringInitFromBuf(&sName,(const char *)SyBlobData(&pTos->sBlob),SyBlobLength(&pTos->sBlob)); + if( pInstr->iP2 ){ + /* Method call */ + ph7_class_method *pMeth = 0; + if( sName.nByte > 0 ){ + /* Extract the target method */ + pMeth = PH7_ClassExtractMethod(pClass,sName.zString,sName.nByte); + } + if( pMeth == 0 ){ + VmErrorFormat(&(*pVm),PH7_CTX_ERR,"Undefined class method '%z->%z',PH7 is loading NULL", + &pClass->sName,&sName + ); + /* Call the '__Call()' magic method if available */ + PH7_ClassInstanceCallMagicMethod(&(*pVm),pClass,pThis,"__call",sizeof("__call")-1,&sName); + /* Pop the method name from the stack */ + VmPopOperand(&pTos,1); + PH7_MemObjRelease(pTos); + }else{ + /* Push method name on the stack */ + PH7_MemObjRelease(pTos); + SyBlobAppend(&pTos->sBlob,SyStringData(&pMeth->sVmName),SyStringLength(&pMeth->sVmName)); + MemObjSetType(pTos,MEMOBJ_STRING); + } + pTos->nIdx = SXU32_HIGH; + }else{ + /* Attribute access */ + VmClassAttr *pObjAttr = 0; + SyHashEntry *pEntry; + /* Extract the target attribute */ + if( sName.nByte > 0 ){ + pEntry = SyHashGet(&pThis->hAttr,(const void *)sName.zString,sName.nByte); + if( pEntry ){ + /* Point to the attribute value */ + pObjAttr = (VmClassAttr *)pEntry->pUserData; + } + } + if( pObjAttr == 0 ){ + /* No such attribute,load null */ + VmErrorFormat(&(*pVm),PH7_CTX_ERR,"Undefined class attribute '%z->%z',PH7 is loading NULL", + &pClass->sName,&sName); + /* Call the __get magic method if available */ + PH7_ClassInstanceCallMagicMethod(&(*pVm),pClass,pThis,"__get",sizeof("__get")-1,&sName); + } + VmPopOperand(&pTos,1); + /* TICKET 1433-49: Deffer garbage collection until attribute loading. + * This is due to the following case: + * (new TestClass())->foo; + */ + pThis->iRef++; + PH7_MemObjRelease(pTos); + pTos->nIdx = SXU32_HIGH; /* Assume we are loading a constant */ + if( pObjAttr ){ + ph7_value *pValue = 0; /* cc warning */ + /* Check attribute access */ + if( VmClassMemberAccess(&(*pVm),pClass,&pObjAttr->pAttr->sName,pObjAttr->pAttr->iProtection,TRUE) ){ + /* Load attribute */ + pValue = (ph7_value *)SySetAt(&pVm->aMemObj,pObjAttr->nIdx); + if( pValue ){ + if( pThis->iRef < 2 ){ + /* Perform a store operation,rather than a load operation since + * the class instance '$this' will be deleted shortly. + */ + PH7_MemObjStore(pValue,pTos); + }else{ + /* Simple load */ + PH7_MemObjLoad(pValue,pTos); + } + if( (pObjAttr->pAttr->iFlags & PH7_CLASS_ATTR_CONSTANT) == 0 ){ + if( pThis->iRef > 1 ){ + /* Load attribute index */ + pTos->nIdx = pObjAttr->nIdx; + } + } + } + } + } + /* Safely unreference the object */ + PH7_ClassInstanceUnref(pThis); + } + }else{ + VmErrorFormat(&(*pVm),PH7_CTX_ERR,"'->': Expecting class instance as left operand,PH7 is loading NULL"); + VmPopOperand(&pTos,1); + PH7_MemObjRelease(pTos); + pTos->nIdx = SXU32_HIGH; /* Assume we are loading a constant */ + } + }else{ + /* Static member access using class name */ + pNos = pTos; + pThis = 0; + if( !pInstr->p3 ){ + SyStringInitFromBuf(&sName,(const char *)SyBlobData(&pTos->sBlob),SyBlobLength(&pTos->sBlob)); + pNos--; +#ifdef UNTRUST + if( pNos < pStack ){ + goto Abort; + } +#endif + }else{ + /* Attribute name already computed */ + SyStringInitFromBuf(&sName,pInstr->p3,SyStrlen((const char *)pInstr->p3)); + } + if( pNos->iFlags & (MEMOBJ_STRING|MEMOBJ_OBJ) ){ + ph7_class *pClass = 0; + if( pNos->iFlags & MEMOBJ_OBJ ){ + /* Class already instantiated */ + pThis = (ph7_class_instance *)pNos->x.pOther; + pClass = pThis->pClass; + pThis->iRef++; /* Deffer garbage collection */ + }else{ + /* Try to extract the target class */ + if( SyBlobLength(&pNos->sBlob) > 0 ){ + pClass = PH7_VmExtractClass(&(*pVm),(const char *)SyBlobData(&pNos->sBlob), + SyBlobLength(&pNos->sBlob),FALSE,0); + } + } + if( pClass == 0 ){ + /* Undefined class */ + VmErrorFormat(&(*pVm),PH7_CTX_ERR,"Call to undefined class '%.*s',PH7 is loading NULL", + SyBlobLength(&pNos->sBlob),(const char *)SyBlobData(&pNos->sBlob) + ); + if( !pInstr->p3 ){ + VmPopOperand(&pTos,1); + } + PH7_MemObjRelease(pTos); + pTos->nIdx = SXU32_HIGH; + }else{ + if( pInstr->iP2 ){ + /* Method call */ + ph7_class_method *pMeth = 0; + if( sName.nByte > 0 && (pClass->iFlags & PH7_CLASS_INTERFACE) == 0){ + /* Extract the target method */ + pMeth = PH7_ClassExtractMethod(pClass,sName.zString,sName.nByte); + } + if( pMeth == 0 || (pMeth->iFlags & PH7_CLASS_ATTR_ABSTRACT) ){ + if( pMeth ){ + VmErrorFormat(&(*pVm),PH7_CTX_ERR,"Cannot call abstract method '%z:%z',PH7 is loading NULL", + &pClass->sName,&sName + ); + }else{ + VmErrorFormat(&(*pVm),PH7_CTX_ERR,"Undefined class static method '%z::%z',PH7 is loading NULL", + &pClass->sName,&sName + ); + /* Call the '__CallStatic()' magic method if available */ + PH7_ClassInstanceCallMagicMethod(&(*pVm),pClass,0,"__callStatic",sizeof("__callStatic")-1,&sName); + } + /* Pop the method name from the stack */ + if( !pInstr->p3 ){ + VmPopOperand(&pTos,1); + } + PH7_MemObjRelease(pTos); + }else{ + /* Push method name on the stack */ + PH7_MemObjRelease(pTos); + SyBlobAppend(&pTos->sBlob,SyStringData(&pMeth->sVmName),SyStringLength(&pMeth->sVmName)); + MemObjSetType(pTos,MEMOBJ_STRING); + } + pTos->nIdx = SXU32_HIGH; + }else{ + /* Attribute access */ + ph7_class_attr *pAttr = 0; + /* Extract the target attribute */ + if( sName.nByte > 0 ){ + pAttr = PH7_ClassExtractAttribute(pClass,sName.zString,sName.nByte); + } + if( pAttr == 0 ){ + /* No such attribute,load null */ + VmErrorFormat(&(*pVm),PH7_CTX_ERR,"Undefined class attribute '%z::%z',PH7 is loading NULL", + &pClass->sName,&sName); + /* Call the __get magic method if available */ + PH7_ClassInstanceCallMagicMethod(&(*pVm),pClass,0,"__get",sizeof("__get")-1,&sName); + } + /* Pop the attribute name from the stack */ + if( !pInstr->p3 ){ + VmPopOperand(&pTos,1); + } + PH7_MemObjRelease(pTos); + pTos->nIdx = SXU32_HIGH; + if( pAttr ){ + if( (pAttr->iFlags & (PH7_CLASS_ATTR_STATIC|PH7_CLASS_ATTR_CONSTANT)) == 0 ){ + /* Access to a non static attribute */ + VmErrorFormat(&(*pVm),PH7_CTX_ERR,"Access to a non-static class attribute '%z::%z',PH7 is loading NULL", + &pClass->sName,&pAttr->sName + ); + }else{ + ph7_value *pValue; + /* Check if the access to the attribute is allowed */ + if( VmClassMemberAccess(&(*pVm),pClass,&pAttr->sName,pAttr->iProtection,TRUE) ){ + /* Load the desired attribute */ + pValue = (ph7_value *)SySetAt(&pVm->aMemObj,pAttr->nIdx); + if( pValue ){ + PH7_MemObjLoad(pValue,pTos); + if( pAttr->iFlags & PH7_CLASS_ATTR_STATIC ){ + /* Load index number */ + pTos->nIdx = pAttr->nIdx; + } + } + } + } + } + } + if( pThis ){ + /* Safely unreference the object */ + PH7_ClassInstanceUnref(pThis); + } + } + }else{ + /* Pop operands */ + PH7_VmThrowError(&(*pVm),0,PH7_CTX_ERR,"Invalid class name,PH7 is loading NULL"); + if( !pInstr->p3 ){ + VmPopOperand(&pTos,1); + } + PH7_MemObjRelease(pTos); + pTos->nIdx = SXU32_HIGH; + } + } + break; + } +/* + * OP_NEW P1 * * * + * Create a new class instance (Object in the PHP jargon) and push that object on the stack. + */ +case PH7_OP_NEW: { + ph7_value *pArg = &pTos[-pInstr->iP1]; /* Constructor arguments (if available) */ + ph7_class *pClass = 0; + ph7_class_instance *pNew; + if( (pTos->iFlags & MEMOBJ_STRING) && SyBlobLength(&pTos->sBlob) > 0 ){ + /* Try to extract the desired class */ + pClass = PH7_VmExtractClass(&(*pVm),(const char *)SyBlobData(&pTos->sBlob), + SyBlobLength(&pTos->sBlob),TRUE /* Only loadable class but not 'interface' or 'abstract' class*/,0); + }else if( pTos->iFlags & MEMOBJ_OBJ ){ + /* Take the base class from the loaded instance */ + pClass = ((ph7_class_instance *)pTos->x.pOther)->pClass; + } + if( pClass == 0 ){ + /* No such class */ + VmErrorFormat(&(*pVm),PH7_CTX_ERR,"Class '%.*s' is not defined,PH7 is loading NULL", + SyBlobLength(&pTos->sBlob),(const char *)SyBlobData(&pTos->sBlob) + ); + PH7_MemObjRelease(pTos); + if( pInstr->iP1 > 0 ){ + /* Pop given arguments */ + VmPopOperand(&pTos,pInstr->iP1); + } + }else{ + ph7_class_method *pCons; + /* Create a new class instance */ + pNew = PH7_NewClassInstance(&(*pVm),pClass); + if( pNew == 0 ){ + VmErrorFormat(&(*pVm),PH7_CTX_ERR, + "Cannot create new class '%z' instance due to a memory failure,PH7 is loading NULL", + &pClass->sName + ); + PH7_MemObjRelease(pTos); + if( pInstr->iP1 > 0 ){ + /* Pop given arguments */ + VmPopOperand(&pTos,pInstr->iP1); + } + break; + } + /* Check if a constructor is available */ + pCons = PH7_ClassExtractMethod(pClass,"__construct",sizeof("__construct")-1); + if( pCons == 0 ){ + SyString *pName = &pClass->sName; + /* Check for a constructor with the same base class name */ + pCons = PH7_ClassExtractMethod(pClass,pName->zString,pName->nByte); + } + if( pCons ){ + /* Call the class constructor */ + SySetReset(&aArg); + while( pArg < pTos ){ + SySetPut(&aArg,(const void *)&pArg); + pArg++; + } + if( pVm->bErrReport ){ + ph7_vm_func_arg *pFuncArg; + sxu32 n; + n = SySetUsed(&aArg); + /* Emit a notice for missing arguments */ + while( n < SySetUsed(&pCons->sFunc.aArgs) ){ + pFuncArg = (ph7_vm_func_arg *)SySetAt(&pCons->sFunc.aArgs,n); + if( pFuncArg ){ + if( SySetUsed(&pFuncArg->aByteCode) < 1 ){ + VmErrorFormat(&(*pVm),PH7_CTX_NOTICE,"Missing constructor argument %u($%z) for class '%z'", + n+1,&pFuncArg->sName,&pClass->sName); + } + } + n++; + } + } + PH7_VmCallClassMethod(&(*pVm),pNew,pCons,0,(int)SySetUsed(&aArg),(ph7_value **)SySetBasePtr(&aArg)); + /* TICKET 1433-52: Unsetting $this in the constructor body */ + if( pNew->iRef < 1 ){ + pNew->iRef = 1; + } + } + if( pInstr->iP1 > 0 ){ + /* Pop given arguments */ + VmPopOperand(&pTos,pInstr->iP1); + } + PH7_MemObjRelease(pTos); + pTos->x.pOther = pNew; + MemObjSetType(pTos,MEMOBJ_OBJ); + } + break; + } +/* + * OP_CLONE * * * + * Perfome a clone operation. + */ +case PH7_OP_CLONE: { + ph7_class_instance *pSrc,*pClone; +#ifdef UNTRUST + if( pTos < pStack ){ + goto Abort; + } +#endif + /* Make sure we are dealing with a class instance */ + if( (pTos->iFlags & MEMOBJ_OBJ) == 0 ){ + PH7_VmThrowError(&(*pVm),0,PH7_CTX_ERR, + "Clone: Expecting a class instance as left operand,PH7 is loading NULL"); + PH7_MemObjRelease(pTos); + break; + } + /* Point to the source */ + pSrc = (ph7_class_instance *)pTos->x.pOther; + /* Perform the clone operation */ + pClone = PH7_CloneClassInstance(pSrc); + PH7_MemObjRelease(pTos); + if( pClone == 0 ){ + PH7_VmThrowError(&(*pVm),0,PH7_CTX_ERR, + "Clone: cannot make an object clone due to a memory failure,PH7 is loading NULL"); + }else{ + /* Load the cloned object */ + pTos->x.pOther = pClone; + MemObjSetType(pTos,MEMOBJ_OBJ); + } + break; + } +/* + * OP_SWITCH * * P3 + * This is the bytecode implementation of the complex switch() PHP construct. + */ +case PH7_OP_SWITCH: { + ph7_switch *pSwitch = (ph7_switch *)pInstr->p3; + ph7_case_expr *aCase,*pCase; + ph7_value sValue,sCaseValue; + sxu32 n,nEntry; +#ifdef UNTRUST + if( pSwitch == 0 || pTos < pStack ){ + goto Abort; + } +#endif + /* Point to the case table */ + aCase = (ph7_case_expr *)SySetBasePtr(&pSwitch->aCaseExpr); + nEntry = SySetUsed(&pSwitch->aCaseExpr); + /* Select the appropriate case block to execute */ + PH7_MemObjInit(pVm,&sValue); + PH7_MemObjInit(pVm,&sCaseValue); + for( n = 0 ; n < nEntry ; ++n ){ + pCase = &aCase[n]; + PH7_MemObjLoad(pTos,&sValue); + /* Execute the case expression first */ + VmLocalExec(pVm,&pCase->aByteCode,&sCaseValue); + /* Compare the two expression */ + rc = PH7_MemObjCmp(&sValue,&sCaseValue,FALSE,0); + PH7_MemObjRelease(&sValue); + PH7_MemObjRelease(&sCaseValue); + if( rc == 0 ){ + /* Value match,jump to this block */ + pc = pCase->nStart - 1; + break; + } + } + VmPopOperand(&pTos,1); + if( n >= nEntry ){ + /* No approprite case to execute,jump to the default case */ + if( pSwitch->nDefault > 0 ){ + pc = pSwitch->nDefault - 1; + }else{ + /* No default case,jump out of this switch */ + pc = pSwitch->nOut - 1; + } + } + break; + } +/* + * OP_CALL P1 * * + * Call a PHP or a foreign function and push the return value of the called + * function on the stack. + */ +case PH7_OP_CALL: { + ph7_value *pArg = &pTos[-pInstr->iP1]; + SyHashEntry *pEntry; + SyString sName; + /* Extract function name */ + if( (pTos->iFlags & MEMOBJ_STRING) == 0 ){ + if( pTos->iFlags & MEMOBJ_HASHMAP ){ + ph7_value sResult; + SySetReset(&aArg); + while( pArg < pTos ){ + SySetPut(&aArg,(const void *)&pArg); + pArg++; + } + PH7_MemObjInit(pVm,&sResult); + /* May be a class instance and it's static method */ + PH7_VmCallUserFunction(pVm,pTos,(int)SySetUsed(&aArg),(ph7_value **)SySetBasePtr(&aArg),&sResult); + SySetReset(&aArg); + /* Pop given arguments */ + if( pInstr->iP1 > 0 ){ + VmPopOperand(&pTos,pInstr->iP1); + } + /* Copy result */ + PH7_MemObjStore(&sResult,pTos); + PH7_MemObjRelease(&sResult); + }else{ + if( pTos->iFlags & MEMOBJ_OBJ ){ + ph7_class_instance *pThis = (ph7_class_instance *)pTos->x.pOther; + /* Call the magic method '__invoke' if available */ + PH7_ClassInstanceCallMagicMethod(&(*pVm),pThis->pClass,pThis,"__invoke",sizeof("__invoke")-1,0); + }else{ + /* Raise exception: Invalid function name */ + VmErrorFormat(&(*pVm),PH7_CTX_WARNING,"Invalid function name,NULL will be returned"); + } + /* Pop given arguments */ + if( pInstr->iP1 > 0 ){ + VmPopOperand(&pTos,pInstr->iP1); + } + /* Assume a null return value so that the program continue it's execution normally */ + PH7_MemObjRelease(pTos); + } + break; + } + SyStringInitFromBuf(&sName,SyBlobData(&pTos->sBlob),SyBlobLength(&pTos->sBlob)); + /* Check for a compiled function first */ + pEntry = SyHashGet(&pVm->hFunction,(const void *)sName.zString,sName.nByte); + if( pEntry ){ + ph7_vm_func_arg *aFormalArg; + ph7_class_instance *pThis; + ph7_value *pFrameStack; + ph7_vm_func *pVmFunc; + ph7_class *pSelf; + VmFrame *pFrame; + ph7_value *pObj; + VmSlot sArg; + sxu32 n; + /* initialize fields */ + pVmFunc = (ph7_vm_func *)pEntry->pUserData; + pThis = 0; + pSelf = 0; + if( pVmFunc->iFlags & VM_FUNC_CLASS_METHOD ){ + ph7_class_method *pMeth; + /* Class method call */ + ph7_value *pTarget = &pTos[-1]; + if( pTarget >= pStack && (pTarget->iFlags & (MEMOBJ_STRING|MEMOBJ_OBJ|MEMOBJ_NULL)) ){ + /* Extract the 'this' pointer */ + if(pTarget->iFlags & MEMOBJ_OBJ ){ + /* Instance already loaded */ + pThis = (ph7_class_instance *)pTarget->x.pOther; + pThis->iRef++; + pSelf = pThis->pClass; + } + if( pSelf == 0 ){ + if( (pTarget->iFlags & MEMOBJ_STRING) && SyBlobLength(&pTarget->sBlob) > 0 ){ + /* "Late Static Binding" class name */ + pSelf = PH7_VmExtractClass(&(*pVm),(const char *)SyBlobData(&pTarget->sBlob), + SyBlobLength(&pTarget->sBlob),FALSE,0); + } + if( pSelf == 0 ){ + pSelf = (ph7_class *)pVmFunc->pUserData; + } + } + if( pThis == 0 ){ + VmFrame *pFrame = pVm->pFrame; + while( pFrame->pParent && (pFrame->iFlags & VM_FRAME_EXCEPTION) ){ + /* Safely ignore the exception frame */ + pFrame = pFrame->pParent; + } + if( pFrame->pParent ){ + /* TICKET-1433-52: Make sure the '$this' variable is available to the current scope */ + pThis = pFrame->pThis; + if( pThis ){ + pThis->iRef++; + } + } + } + VmPopOperand(&pTos,1); + PH7_MemObjRelease(pTos); + /* Synchronize pointers */ + pArg = &pTos[-pInstr->iP1]; + /* TICKET 1433-50: This is a very very unlikely scenario that occurs when the 'genius' + * user have already computed the random generated unique class method name + * and tries to call it outside it's context [i.e: global scope]. In that + * case we have to synchrnoize pointers to avoid stack underflow. + */ + while( pArg < pStack ){ + pArg++; + } + if( pSelf ){ /* Paranoid edition */ + /* Check if the call is allowed */ + pMeth = PH7_ClassExtractMethod(pSelf,pVmFunc->sName.zString,pVmFunc->sName.nByte); + if( pMeth && pMeth->iProtection != PH7_CLASS_PROT_PUBLIC ){ + if( !VmClassMemberAccess(&(*pVm),pSelf,&pVmFunc->sName,pMeth->iProtection,TRUE) ){ + /* Pop given arguments */ + if( pInstr->iP1 > 0 ){ + VmPopOperand(&pTos,pInstr->iP1); + } + /* Assume a null return value so that the program continue it's execution normally */ + PH7_MemObjRelease(pTos); + break; + } + } + } + } + } + /* Check The recursion limit */ + if( pVm->nRecursionDepth > pVm->nMaxDepth ){ + VmErrorFormat(&(*pVm),PH7_CTX_ERR, + "Recursion limit reached while invoking user function '%z',PH7 will set a NULL return value", + &pVmFunc->sName); + /* Pop given arguments */ + if( pInstr->iP1 > 0 ){ + VmPopOperand(&pTos,pInstr->iP1); + } + /* Assume a null return value so that the program continue it's execution normally */ + PH7_MemObjRelease(pTos); + break; + } + if( pVmFunc->pNextName ){ + /* Function is candidate for overloading,select the appropriate function to call */ + pVmFunc = VmOverload(&(*pVm),pVmFunc,pArg,(int)(pTos-pArg)); + } + /* Extract the formal argument set */ + aFormalArg = (ph7_vm_func_arg *)SySetBasePtr(&pVmFunc->aArgs); + /* Create a new VM frame */ + rc = VmEnterFrame(&(*pVm),pVmFunc,pThis,&pFrame); + if( rc != SXRET_OK ){ + /* Raise exception: Out of memory */ + VmErrorFormat(&(*pVm),PH7_CTX_ERR, + "PH7 is running out of memory while calling function '%z',NULL will be returned", + &pVmFunc->sName); + /* Pop given arguments */ + if( pInstr->iP1 > 0 ){ + VmPopOperand(&pTos,pInstr->iP1); + } + /* Assume a null return value so that the program continue it's execution normally */ + PH7_MemObjRelease(pTos); + break; + } + if( (pVmFunc->iFlags & VM_FUNC_CLASS_METHOD) && pThis ){ + /* Install the '$this' variable */ + static const SyString sThis = { "this" , sizeof("this") - 1 }; + pObj = VmExtractMemObj(&(*pVm),&sThis,FALSE,TRUE); + if( pObj ){ + /* Reflect the change */ + pObj->x.pOther = pThis; + MemObjSetType(pObj,MEMOBJ_OBJ); + } + } + if( SySetUsed(&pVmFunc->aStatic) > 0 ){ + ph7_vm_func_static_var *pStatic,*aStatic; + /* Install static variables */ + aStatic = (ph7_vm_func_static_var *)SySetBasePtr(&pVmFunc->aStatic); + for( n = 0 ; n < SySetUsed(&pVmFunc->aStatic) ; ++n ){ + pStatic = &aStatic[n]; + if( pStatic->nIdx == SXU32_HIGH ){ + /* Initialize the static variables */ + pObj = VmReserveMemObj(&(*pVm),&pStatic->nIdx); + if( pObj ){ + /* Assume a NULL initialization value */ + PH7_MemObjInit(&(*pVm),pObj); + if( SySetUsed(&pStatic->aByteCode) > 0 ){ + /* Evaluate initialization expression (Any complex expression) */ + VmLocalExec(&(*pVm),&pStatic->aByteCode,pObj); + } + pObj->nIdx = pStatic->nIdx; + }else{ + continue; + } + } + /* Install in the current frame */ + SyHashInsert(&pFrame->hVar,SyStringData(&pStatic->sName),SyStringLength(&pStatic->sName), + SX_INT_TO_PTR(pStatic->nIdx)); + } + } + /* Push arguments in the local frame */ + n = 0; + while( pArg < pTos ){ + if( n < SySetUsed(&pVmFunc->aArgs) ){ + if( (pArg->iFlags & MEMOBJ_NULL) && SySetUsed(&aFormalArg[n].aByteCode) > 0 ){ + /* NULL values are redirected to default arguments */ + rc = VmLocalExec(&(*pVm),&aFormalArg[n].aByteCode,pArg); + if( rc == PH7_ABORT ){ + goto Abort; + } + } + /* Make sure the given arguments are of the correct type */ + if( aFormalArg[n].nType > 0 ){ + if ( aFormalArg[n].nType == SXU32_HIGH ){ + /* Argument must be a class instance [i.e: object] */ + SyString *pName = &aFormalArg[n].sClass; + ph7_class *pClass; + /* Try to extract the desired class */ + pClass = PH7_VmExtractClass(&(*pVm),pName->zString,pName->nByte,TRUE,0); + if( pClass ){ + if( (pArg->iFlags & MEMOBJ_OBJ) == 0 ){ + if( (pArg->iFlags & MEMOBJ_NULL) == 0 ){ + VmErrorFormat(&(*pVm),PH7_CTX_WARNING, + "Function '%z()':Argument %u must be an object of type '%z',PH7 is loading NULL instead", + &pVmFunc->sName,n+1,pName); + PH7_MemObjRelease(pArg); + } + }else{ + ph7_class_instance *pThis = (ph7_class_instance *)pArg->x.pOther; + /* Make sure the object is an instance of the given class */ + if( ! VmInstanceOf(pThis->pClass,pClass) ){ + VmErrorFormat(&(*pVm),PH7_CTX_ERR, + "Function '%z()':Argument %u must be an object of type '%z',PH7 is loading NULL instead", + &pVmFunc->sName,n+1,pName); + PH7_MemObjRelease(pArg); + } + } + } + }else if( ((pArg->iFlags & aFormalArg[n].nType) == 0) ){ + ProcMemObjCast xCast = PH7_MemObjCastMethod(aFormalArg[n].nType); + /* Cast to the desired type */ + xCast(pArg); + } + } + if( aFormalArg[n].iFlags & VM_FUNC_ARG_BY_REF ){ + /* Pass by reference */ + if( pArg->nIdx == SXU32_HIGH ){ + /* Expecting a variable,not a constant,raise an exception */ + if((pArg->iFlags & (MEMOBJ_HASHMAP|MEMOBJ_OBJ|MEMOBJ_RES|MEMOBJ_NULL)) == 0){ + VmErrorFormat(&(*pVm),PH7_CTX_WARNING, + "Function '%z',%d argument: Pass by reference,expecting a variable not a " + "constant,PH7 is switching to pass by value",&pVmFunc->sName,n+1); + } + /* Switch to pass by value */ + pObj = VmExtractMemObj(&(*pVm),&aFormalArg[n].sName,FALSE,TRUE); + }else{ + SyHashEntry *pRefEntry; + /* Install the referenced variable in the private function frame */ + pRefEntry = SyHashGet(&pFrame->hVar,SyStringData(&aFormalArg[n].sName),SyStringLength(&aFormalArg[n].sName)); + if( pRefEntry == 0 ){ + SyHashInsert(&pFrame->hVar,SyStringData(&aFormalArg[n].sName), + SyStringLength(&aFormalArg[n].sName),SX_INT_TO_PTR(pArg->nIdx)); + sArg.nIdx = pArg->nIdx; + sArg.pUserData = 0; + SySetPut(&pFrame->sArg,(const void *)&sArg); + } + pObj = 0; + } + }else{ + /* Pass by value,make a copy of the given argument */ + pObj = VmExtractMemObj(&(*pVm),&aFormalArg[n].sName,FALSE,TRUE); + } + }else{ + char zName[32]; + SyString sName; + /* Set a dummy name */ + sName.nByte = SyBufferFormat(zName,sizeof(zName),"[%u]apArg",n); + sName.zString = zName; + /* Annonymous argument */ + pObj = VmExtractMemObj(&(*pVm),&sName,TRUE,TRUE); + } + if( pObj ){ + PH7_MemObjStore(pArg,pObj); + /* Insert argument index */ + sArg.nIdx = pObj->nIdx; + sArg.pUserData = 0; + SySetPut(&pFrame->sArg,(const void *)&sArg); + } + PH7_MemObjRelease(pArg); + pArg++; + ++n; + } + /* Set up closure environment */ + if( pVmFunc->iFlags & VM_FUNC_CLOSURE ){ + ph7_vm_func_closure_env *aEnv,*pEnv; + ph7_value *pValue; + sxu32 n; + aEnv = (ph7_vm_func_closure_env *)SySetBasePtr(&pVmFunc->aClosureEnv); + for(n = 0 ; n < SySetUsed(&pVmFunc->aClosureEnv) ; ++n ){ + pEnv = &aEnv[n]; + if( (pEnv->iFlags & VM_FUNC_ARG_IGNORE) && (pEnv->sValue.iFlags & MEMOBJ_NULL) ){ + /* Do not install null value */ + continue; + } + pValue = VmExtractMemObj(pVm,&pEnv->sName,FALSE,TRUE); + if( pValue == 0 ){ + continue; + } + /* Invalidate any prior representation */ + PH7_MemObjRelease(pValue); + /* Duplicate bound variable value */ + PH7_MemObjStore(&pEnv->sValue,pValue); + } + } + /* Process default values */ + while( n < SySetUsed(&pVmFunc->aArgs) ){ + if( SySetUsed(&aFormalArg[n].aByteCode) > 0 ){ + pObj = VmExtractMemObj(&(*pVm),&aFormalArg[n].sName,FALSE,TRUE); + if( pObj ){ + /* Evaluate the default value and extract it's result */ + rc = VmLocalExec(&(*pVm),&aFormalArg[n].aByteCode,pObj); + if( rc == PH7_ABORT ){ + goto Abort; + } + /* Insert argument index */ + sArg.nIdx = pObj->nIdx; + sArg.pUserData = 0; + SySetPut(&pFrame->sArg,(const void *)&sArg); + /* Make sure the default argument is of the correct type */ + if( aFormalArg[n].nType > 0 && ((pObj->iFlags & aFormalArg[n].nType) == 0) ){ + ProcMemObjCast xCast = PH7_MemObjCastMethod(aFormalArg[n].nType); + /* Cast to the desired type */ + xCast(pObj); + } + } + } + ++n; + } + /* Pop arguments,function name from the operand stack and assume the function + * does not return anything. + */ + PH7_MemObjRelease(pTos); + pTos = &pTos[-pInstr->iP1]; + /* Allocate a new operand stack and evaluate the function body */ + pFrameStack = VmNewOperandStack(&(*pVm),SySetUsed(&pVmFunc->aByteCode)); + if( pFrameStack == 0 ){ + /* Raise exception: Out of memory */ + VmErrorFormat(&(*pVm),PH7_CTX_ERR,"PH7 is running out of memory while calling function '%z',NULL will be returned", + &pVmFunc->sName); + if( pInstr->iP1 > 0 ){ + VmPopOperand(&pTos,pInstr->iP1); + } + break; + } + if( pSelf ){ + /* Push class name */ + SySetPut(&pVm->aSelf,(const void *)&pSelf); + } + /* Increment nesting level */ + pVm->nRecursionDepth++; + /* Execute function body */ + rc = VmByteCodeExec(&(*pVm),(VmInstr *)SySetBasePtr(&pVmFunc->aByteCode),pFrameStack,-1,pTos,&n,FALSE); + /* Decrement nesting level */ + pVm->nRecursionDepth--; + if( pSelf ){ + /* Pop class name */ + (void)SySetPop(&pVm->aSelf); + } + /* Cleanup the mess left behind */ + if( (pVmFunc->iFlags & VM_FUNC_REF_RETURN) && rc == SXRET_OK ){ + /* Return by reference,reflect that */ + if( n != SXU32_HIGH ){ + VmSlot *aSlot = (VmSlot *)SySetBasePtr(&pFrame->sLocal); + sxu32 i; + /* Make sure the referenced object is not a local variable */ + for( i = 0 ; i < SySetUsed(&pFrame->sLocal) ; ++i ){ + if( n == aSlot[i].nIdx ){ + pObj = (ph7_value *)SySetAt(&pVm->aMemObj,n); + if( pObj && (pObj->iFlags & (MEMOBJ_NULL|MEMOBJ_OBJ|MEMOBJ_HASHMAP|MEMOBJ_RES)) == 0 ){ + VmErrorFormat(&(*pVm),PH7_CTX_NOTICE, + "Function '%z',return by reference: Cannot reference local variable,PH7 is switching to return by value", + &pVmFunc->sName); + } + n = SXU32_HIGH; + break; + } + } + }else{ + if( (pTos->iFlags & (MEMOBJ_HASHMAP|MEMOBJ_OBJ|MEMOBJ_NULL|MEMOBJ_RES)) == 0 ){ + VmErrorFormat(&(*pVm),PH7_CTX_NOTICE, + "Function '%z',return by reference: Cannot reference constant expression,PH7 is switching to return by value", + &pVmFunc->sName); + } + } + pTos->nIdx = n; + } + /* Cleanup the mess left behind */ + if( rc != PH7_ABORT && ((pFrame->iFlags & VM_FRAME_THROW) || rc == PH7_EXCEPTION) ){ + /* An exception was throw in this frame */ + pFrame = pFrame->pParent; + if( !is_callback && pFrame->pParent && (pFrame->iFlags & VM_FRAME_EXCEPTION) && pFrame->iExceptionJump > 0 ){ + /* Pop the resutlt */ + VmPopOperand(&pTos,1); + /* Jump to this destination */ + pc = pFrame->iExceptionJump - 1; + rc = PH7_OK; + }else{ + if( pFrame->pParent ){ + rc = PH7_EXCEPTION; + }else{ + /* Continue normal execution */ + rc = PH7_OK; + } + } + } + /* Free the operand stack */ + SyMemBackendFree(&pVm->sAllocator,pFrameStack); + /* Leave the frame */ + VmLeaveFrame(&(*pVm)); + if( rc == PH7_ABORT ){ + /* Abort processing immeditaley */ + goto Abort; + }else if( rc == PH7_EXCEPTION ){ + goto Exception; + } + }else{ + ph7_user_func *pFunc; + ph7_context sCtx; + ph7_value sRet; + /* Look for an installed foreign function */ + pEntry = SyHashGet(&pVm->hHostFunction,(const void *)sName.zString,sName.nByte); + if( pEntry == 0 ){ + /* Call to undefined function */ + VmErrorFormat(&(*pVm),PH7_CTX_WARNING,"Call to undefined function '%z',NULL will be returned",&sName); + /* Pop given arguments */ + if( pInstr->iP1 > 0 ){ + VmPopOperand(&pTos,pInstr->iP1); + } + /* Assume a null return value so that the program continue it's execution normally */ + PH7_MemObjRelease(pTos); + break; + } + pFunc = (ph7_user_func *)pEntry->pUserData; + /* Start collecting function arguments */ + SySetReset(&aArg); + while( pArg < pTos ){ + SySetPut(&aArg,(const void *)&pArg); + pArg++; + } + /* Assume a null return value */ + PH7_MemObjInit(&(*pVm),&sRet); + /* Init the call context */ + VmInitCallContext(&sCtx,&(*pVm),pFunc,&sRet,0); + /* Call the foreign function */ + rc = pFunc->xFunc(&sCtx,(int)SySetUsed(&aArg),(ph7_value **)SySetBasePtr(&aArg)); + /* Release the call context */ + VmReleaseCallContext(&sCtx); + if( rc == PH7_ABORT ){ + goto Abort; + } + if( pInstr->iP1 > 0 ){ + /* Pop function name and arguments */ + VmPopOperand(&pTos,pInstr->iP1); + } + /* Save foreign function return value */ + PH7_MemObjStore(&sRet,pTos); + PH7_MemObjRelease(&sRet); + } + break; + } +/* + * OP_CONSUME: P1 * * + * Consume (Invoke the installed VM output consumer callback) and POP P1 elements from the stack. + */ +case PH7_OP_CONSUME: { + ph7_output_consumer *pCons = &pVm->sVmConsumer; + ph7_value *pCur,*pOut = pTos; + + pOut = &pTos[-pInstr->iP1 + 1]; + pCur = pOut; + /* Start the consume process */ + while( pOut <= pTos ){ + /* Force a string cast */ + if( (pOut->iFlags & MEMOBJ_STRING) == 0 ){ + PH7_MemObjToString(pOut); + } + if( SyBlobLength(&pOut->sBlob) > 0 ){ + /*SyBlobNullAppend(&pOut->sBlob);*/ + /* Invoke the output consumer callback */ + rc = pCons->xConsumer(SyBlobData(&pOut->sBlob),SyBlobLength(&pOut->sBlob),pCons->pUserData); + if( pCons->xConsumer != VmObConsumer ){ + /* Increment output length */ + pVm->nOutputLen += SyBlobLength(&pOut->sBlob); + } + SyBlobRelease(&pOut->sBlob); + if( rc == SXERR_ABORT ){ + /* Output consumer callback request an operation abort. */ + goto Abort; + } + } + pOut++; + } + pTos = &pCur[-1]; + break; + } + + } /* Switch() */ + pc++; /* Next instruction in the stream */ + } /* For(;;) */ +Done: + SySetRelease(&aArg); + return SXRET_OK; +Abort: + SySetRelease(&aArg); + while( pTos >= pStack ){ + PH7_MemObjRelease(pTos); + pTos--; + } + return PH7_ABORT; +Exception: + SySetRelease(&aArg); + while( pTos >= pStack ){ + PH7_MemObjRelease(pTos); + pTos--; + } + return PH7_EXCEPTION; +} +/* + * Execute as much of a local PH7 bytecode program as we can then return. + * This function is a wrapper around [VmByteCodeExec()]. + * See block-comment on that function for additional information. + */ +static sxi32 VmLocalExec(ph7_vm *pVm,SySet *pByteCode,ph7_value *pResult) +{ + ph7_value *pStack; + sxi32 rc; + /* Allocate a new operand stack */ + pStack = VmNewOperandStack(&(*pVm),SySetUsed(pByteCode)); + if( pStack == 0 ){ + return SXERR_MEM; + } + /* Execute the program */ + rc = VmByteCodeExec(&(*pVm),(VmInstr *)SySetBasePtr(pByteCode),pStack,-1,&(*pResult),0,FALSE); + /* Free the operand stack */ + SyMemBackendFree(&pVm->sAllocator,pStack); + /* Execution result */ + return rc; +} +/* + * Invoke any installed shutdown callbacks. + * Shutdown callbacks are kept in a stack and are registered using one + * or more calls to [register_shutdown_function()]. + * These callbacks are invoked by the virtual machine when the program + * execution ends. + * Refer to the implementation of [register_shutdown_function()] for + * additional information. + */ +static void VmInvokeShutdownCallbacks(ph7_vm *pVm) +{ + VmShutdownCB *pEntry; + ph7_value *apArg[10]; + sxu32 n,nEntry; + int i; + /* Point to the stack of registered callbacks */ + nEntry = SySetUsed(&pVm->aShutdown); + for( i = 0 ; i < (int)SX_ARRAYSIZE(apArg) ; i++ ){ + apArg[i] = 0; + } + for( n = 0 ; n < nEntry ; ++n ){ + pEntry = (VmShutdownCB *)SySetAt(&pVm->aShutdown,n); + if( pEntry ){ + /* Prepare callback arguments if any */ + for( i = 0 ; i < pEntry->nArg ; i++ ){ + if( i >= (int)SX_ARRAYSIZE(apArg) ){ + break; + } + apArg[i] = &pEntry->aArg[i]; + } + /* Invoke the callback */ + PH7_VmCallUserFunction(&(*pVm),&pEntry->sCallback,pEntry->nArg,apArg,0); + /* + * TICKET 1433-56: Try re-access the same entry since the invoked + * callback may call [register_shutdown_function()] in it's body. + */ + pEntry = (VmShutdownCB *)SySetAt(&pVm->aShutdown,n); + if( pEntry ){ + PH7_MemObjRelease(&pEntry->sCallback); + for( i = 0 ; i < pEntry->nArg ; ++i ){ + PH7_MemObjRelease(apArg[i]); + } + } + } + } + SySetReset(&pVm->aShutdown); +} +/* + * Execute as much of a PH7 bytecode program as we can then return. + * This function is a wrapper around [VmByteCodeExec()]. + * See block-comment on that function for additional information. + */ +PH7_PRIVATE sxi32 PH7_VmByteCodeExec(ph7_vm *pVm) +{ + /* Make sure we are ready to execute this program */ + if( pVm->nMagic != PH7_VM_RUN ){ + return pVm->nMagic == PH7_VM_EXEC ? SXERR_LOCKED /* Locked VM */ : SXERR_CORRUPT; /* Stale VM */ + } + /* Set the execution magic number */ + pVm->nMagic = PH7_VM_EXEC; + /* Execute the program */ + VmByteCodeExec(&(*pVm),(VmInstr *)SySetBasePtr(pVm->pByteContainer),pVm->aOps,-1,&pVm->sExec,0,FALSE); + /* Invoke any shutdown callbacks */ + VmInvokeShutdownCallbacks(&(*pVm)); + /* + * TICKET 1433-100: Do not remove the PH7_VM_EXEC magic number + * so that any following call to [ph7_vm_exec()] without calling + * [ph7_vm_reset()] first would fail. + */ + return SXRET_OK; +} +/* + * Invoke the installed VM output consumer callback to consume + * the desired message. + * Refer to the implementation of [ph7_context_output()] defined + * in 'api.c' for additional information. + */ +PH7_PRIVATE sxi32 PH7_VmOutputConsume( + ph7_vm *pVm, /* Target VM */ + SyString *pString /* Message to output */ + ) +{ + ph7_output_consumer *pCons = &pVm->sVmConsumer; + sxi32 rc = SXRET_OK; + /* Call the output consumer */ + if( pString->nByte > 0 ){ + rc = pCons->xConsumer((const void *)pString->zString,pString->nByte,pCons->pUserData); + if( pCons->xConsumer != VmObConsumer ){ + /* Increment output length */ + pVm->nOutputLen += pString->nByte; + } + } + return rc; +} +/* + * Format a message and invoke the installed VM output consumer + * callback to consume the formatted message. + * Refer to the implementation of [ph7_context_output_format()] defined + * in 'api.c' for additional information. + */ +PH7_PRIVATE sxi32 PH7_VmOutputConsumeAp( + ph7_vm *pVm, /* Target VM */ + const char *zFormat, /* Formatted message to output */ + va_list ap /* Variable list of arguments */ + ) +{ + ph7_output_consumer *pCons = &pVm->sVmConsumer; + sxi32 rc = SXRET_OK; + SyBlob sWorker; + /* Format the message and call the output consumer */ + SyBlobInit(&sWorker,&pVm->sAllocator); + SyBlobFormatAp(&sWorker,zFormat,ap); + if( SyBlobLength(&sWorker) > 0 ){ + /* Consume the formatted message */ + rc = pCons->xConsumer(SyBlobData(&sWorker),SyBlobLength(&sWorker),pCons->pUserData); + } + if( pCons->xConsumer != VmObConsumer ){ + /* Increment output length */ + pVm->nOutputLen += SyBlobLength(&sWorker); + } + /* Release the working buffer */ + SyBlobRelease(&sWorker); + return rc; +} +/* + * Return a string representation of the given PH7 OP code. + * This function never fail and always return a pointer + * to a null terminated string. + */ +static const char * VmInstrToString(sxi32 nOp) +{ + const char *zOp = "Unknown "; + switch(nOp){ + case PH7_OP_DONE: zOp = "DONE "; break; + case PH7_OP_HALT: zOp = "HALT "; break; + case PH7_OP_LOAD: zOp = "LOAD "; break; + case PH7_OP_LOADC: zOp = "LOADC "; break; + case PH7_OP_LOAD_MAP: zOp = "LOAD_MAP "; break; + case PH7_OP_LOAD_LIST: zOp = "LOAD_LIST "; break; + case PH7_OP_LOAD_IDX: zOp = "LOAD_IDX "; break; + case PH7_OP_LOAD_CLOSURE: + zOp = "LOAD_CLOSR "; break; + case PH7_OP_NOOP: zOp = "NOOP "; break; + case PH7_OP_JMP: zOp = "JMP "; break; + case PH7_OP_JZ: zOp = "JZ "; break; + case PH7_OP_JNZ: zOp = "JNZ "; break; + case PH7_OP_POP: zOp = "POP "; break; + case PH7_OP_CAT: zOp = "CAT "; break; + case PH7_OP_CVT_INT: zOp = "CVT_INT "; break; + case PH7_OP_CVT_STR: zOp = "CVT_STR "; break; + case PH7_OP_CVT_REAL: zOp = "CVT_REAL "; break; + case PH7_OP_CALL: zOp = "CALL "; break; + case PH7_OP_UMINUS: zOp = "UMINUS "; break; + case PH7_OP_UPLUS: zOp = "UPLUS "; break; + case PH7_OP_BITNOT: zOp = "BITNOT "; break; + case PH7_OP_LNOT: zOp = "LOGNOT "; break; + case PH7_OP_MUL: zOp = "MUL "; break; + case PH7_OP_DIV: zOp = "DIV "; break; + case PH7_OP_MOD: zOp = "MOD "; break; + case PH7_OP_ADD: zOp = "ADD "; break; + case PH7_OP_SUB: zOp = "SUB "; break; + case PH7_OP_SHL: zOp = "SHL "; break; + case PH7_OP_SHR: zOp = "SHR "; break; + case PH7_OP_LT: zOp = "LT "; break; + case PH7_OP_LE: zOp = "LE "; break; + case PH7_OP_GT: zOp = "GT "; break; + case PH7_OP_GE: zOp = "GE "; break; + case PH7_OP_EQ: zOp = "EQ "; break; + case PH7_OP_NEQ: zOp = "NEQ "; break; + case PH7_OP_TEQ: zOp = "TEQ "; break; + case PH7_OP_TNE: zOp = "TNE "; break; + case PH7_OP_BAND: zOp = "BITAND "; break; + case PH7_OP_BXOR: zOp = "BITXOR "; break; + case PH7_OP_BOR: zOp = "BITOR "; break; + case PH7_OP_LAND: zOp = "LOGAND "; break; + case PH7_OP_LOR: zOp = "LOGOR "; break; + case PH7_OP_LXOR: zOp = "LOGXOR "; break; + case PH7_OP_STORE: zOp = "STORE "; break; + case PH7_OP_STORE_IDX: zOp = "STORE_IDX "; break; + case PH7_OP_STORE_IDX_REF: + zOp = "STORE_IDX_R"; break; + case PH7_OP_PULL: zOp = "PULL "; break; + case PH7_OP_SWAP: zOp = "SWAP "; break; + case PH7_OP_YIELD: zOp = "YIELD "; break; + case PH7_OP_CVT_BOOL: zOp = "CVT_BOOL "; break; + case PH7_OP_CVT_NULL: zOp = "CVT_NULL "; break; + case PH7_OP_CVT_ARRAY: zOp = "CVT_ARRAY "; break; + case PH7_OP_CVT_OBJ: zOp = "CVT_OBJ "; break; + case PH7_OP_CVT_NUMC: zOp = "CVT_NUMC "; break; + case PH7_OP_INCR: zOp = "INCR "; break; + case PH7_OP_DECR: zOp = "DECR "; break; + case PH7_OP_SEQ: zOp = "SEQ "; break; + case PH7_OP_SNE: zOp = "SNE "; break; + case PH7_OP_NEW: zOp = "NEW "; break; + case PH7_OP_CLONE: zOp = "CLONE "; break; + case PH7_OP_ADD_STORE: zOp = "ADD_STORE "; break; + case PH7_OP_SUB_STORE: zOp = "SUB_STORE "; break; + case PH7_OP_MUL_STORE: zOp = "MUL_STORE "; break; + case PH7_OP_DIV_STORE: zOp = "DIV_STORE "; break; + case PH7_OP_MOD_STORE: zOp = "MOD_STORE "; break; + case PH7_OP_CAT_STORE: zOp = "CAT_STORE "; break; + case PH7_OP_SHL_STORE: zOp = "SHL_STORE "; break; + case PH7_OP_SHR_STORE: zOp = "SHR_STORE "; break; + case PH7_OP_BAND_STORE: zOp = "BAND_STORE "; break; + case PH7_OP_BOR_STORE: zOp = "BOR_STORE "; break; + case PH7_OP_BXOR_STORE: zOp = "BXOR_STORE "; break; + case PH7_OP_CONSUME: zOp = "CONSUME "; break; + case PH7_OP_LOAD_REF: zOp = "LOAD_REF "; break; + case PH7_OP_STORE_REF: zOp = "STORE_REF "; break; + case PH7_OP_MEMBER: zOp = "MEMBER "; break; + case PH7_OP_UPLINK: zOp = "UPLINK "; break; + case PH7_OP_ERR_CTRL: zOp = "ERR_CTRL "; break; + case PH7_OP_IS_A: zOp = "IS_A "; break; + case PH7_OP_SWITCH: zOp = "SWITCH "; break; + case PH7_OP_LOAD_EXCEPTION: + zOp = "LOAD_EXCEP "; break; + case PH7_OP_POP_EXCEPTION: + zOp = "POP_EXCEP "; break; + case PH7_OP_THROW: zOp = "THROW "; break; + case PH7_OP_FOREACH_INIT: + zOp = "4EACH_INIT "; break; + case PH7_OP_FOREACH_STEP: + zOp = "4EACH_STEP "; break; + default: + break; + } + return zOp; +} +/* + * Dump PH7 bytecodes instructions to a human readable format. + * The xConsumer() callback which is an used defined function + * is responsible of consuming the generated dump. + */ +PH7_PRIVATE sxi32 PH7_VmDump( + ph7_vm *pVm, /* Target VM */ + ProcConsumer xConsumer, /* Output [i.e: dump] consumer callback */ + void *pUserData /* Last argument to xConsumer() */ + ) +{ + sxi32 rc; + rc = VmByteCodeDump(pVm->pByteContainer,xConsumer,pUserData); + return rc; +} +/* + * Default constant expansion callback used by the 'const' statement if used + * outside a class body [i.e: global or function scope]. + * Refer to the implementation of [PH7_CompileConstant()] defined + * in 'compile.c' for additional information. + */ +PH7_PRIVATE void PH7_VmExpandConstantValue(ph7_value *pVal,void *pUserData) +{ + SySet *pByteCode = (SySet *)pUserData; + /* Evaluate and expand constant value */ + VmLocalExec((ph7_vm *)SySetGetUserData(pByteCode),pByteCode,(ph7_value *)pVal); +} +/* + * Section: + * Function handling functions. + * Authors: + * Symisc Systems,devel@symisc.net. + * Copyright (C) Symisc Systems,http://ph7.symisc.net + * Status: + * Stable. + */ +/* + * int func_num_args(void) + * Returns the number of arguments passed to the function. + * Parameters + * None. + * Return + * Total number of arguments passed into the current user-defined function + * or -1 if called from the globe scope. + */ +static int vm_builtin_func_num_args(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + VmFrame *pFrame; + ph7_vm *pVm; + /* Point to the target VM */ + pVm = pCtx->pVm; + /* Current frame */ + pFrame = pVm->pFrame; + while( pFrame->pParent && (pFrame->iFlags & VM_FRAME_EXCEPTION) ){ + /* Safely ignore the exception frame */ + pFrame = pFrame->pParent; + } + if( pFrame->pParent == 0 ){ + SXUNUSED(nArg); + SXUNUSED(apArg); + /* Global frame,return -1 */ + ph7_result_int(pCtx,-1); + return SXRET_OK; + } + /* Total number of arguments passed to the enclosing function */ + nArg = (int)SySetUsed(&pFrame->sArg); + ph7_result_int(pCtx,nArg); + return SXRET_OK; +} +/* + * value func_get_arg(int $arg_num) + * Return an item from the argument list. + * Parameters + * Argument number(index start from zero). + * Return + * Returns the specified argument or FALSE on error. + */ +static int vm_builtin_func_get_arg(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + ph7_value *pObj = 0; + VmSlot *pSlot = 0; + VmFrame *pFrame; + ph7_vm *pVm; + /* Point to the target VM */ + pVm = pCtx->pVm; + /* Current frame */ + pFrame = pVm->pFrame; + while( pFrame->pParent && (pFrame->iFlags & VM_FRAME_EXCEPTION) ){ + /* Safely ignore the exception frame */ + pFrame = pFrame->pParent; + } + if( nArg < 1 || pFrame->pParent == 0 ){ + /* Global frame or Missing arguments,return FALSE */ + ph7_context_throw_error(pCtx,PH7_CTX_WARNING,"Called in the global scope"); + ph7_result_bool(pCtx,0); + return SXRET_OK; + } + /* Extract the desired index */ + nArg = ph7_value_to_int(apArg[0]); + if( nArg < 0 || nArg >= (int)SySetUsed(&pFrame->sArg) ){ + /* Invalid index,return FALSE */ + ph7_result_bool(pCtx,0); + return SXRET_OK; + } + /* Extract the desired argument */ + if( (pSlot = (VmSlot *)SySetAt(&pFrame->sArg,(sxu32)nArg)) != 0 ){ + if( (pObj = (ph7_value *)SySetAt(&pVm->aMemObj,pSlot->nIdx)) != 0 ){ + /* Return the desired argument */ + ph7_result_value(pCtx,(ph7_value *)pObj); + }else{ + /* No such argument,return false */ + ph7_result_bool(pCtx,0); + } + }else{ + /* CAN'T HAPPEN */ + ph7_result_bool(pCtx,0); + } + return SXRET_OK; +} +/* + * array func_get_args_byref(void) + * Returns an array comprising a function's argument list. + * Parameters + * None. + * Return + * Returns an array in which each element is a POINTER to the corresponding + * member of the current user-defined function's argument list. + * Otherwise FALSE is returned on failure. + * NOTE: + * Arguments are returned to the array by reference. + */ +static int vm_builtin_func_get_args_byref(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + ph7_value *pArray; + VmFrame *pFrame; + VmSlot *aSlot; + sxu32 n; + /* Point to the current frame */ + pFrame = pCtx->pVm->pFrame; + while( pFrame->pParent && (pFrame->iFlags & VM_FRAME_EXCEPTION) ){ + /* Safely ignore the exception frame */ + pFrame = pFrame->pParent; + } + if( pFrame->pParent == 0 ){ + /* Global frame,return FALSE */ + ph7_context_throw_error(pCtx,PH7_CTX_WARNING,"Called in the global scope"); + ph7_result_bool(pCtx,0); + return SXRET_OK; + } + /* Create a new array */ + pArray = ph7_context_new_array(pCtx); + if( pArray == 0 ){ + SXUNUSED(nArg); /* cc warning */ + SXUNUSED(apArg); + ph7_result_bool(pCtx,0); + return SXRET_OK; + } + /* Start filling the array with the given arguments (Pass by reference) */ + aSlot = (VmSlot *)SySetBasePtr(&pFrame->sArg); + for( n = 0; n < SySetUsed(&pFrame->sArg) ; n++ ){ + PH7_HashmapInsertByRef((ph7_hashmap *)pArray->x.pOther,0/*Automatic index assign*/,aSlot[n].nIdx); + } + /* Return the freshly created array */ + ph7_result_value(pCtx,pArray); + return SXRET_OK; +} +/* + * array func_get_args(void) + * Returns an array comprising a copy of function's argument list. + * Parameters + * None. + * Return + * Returns an array in which each element is a copy of the corresponding + * member of the current user-defined function's argument list. + * Otherwise FALSE is returned on failure. + */ +static int vm_builtin_func_get_args(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + ph7_value *pObj = 0; + ph7_value *pArray; + VmFrame *pFrame; + VmSlot *aSlot; + sxu32 n; + /* Point to the current frame */ + pFrame = pCtx->pVm->pFrame; + while( pFrame->pParent && (pFrame->iFlags & VM_FRAME_EXCEPTION) ){ + /* Safely ignore the exception frame */ + pFrame = pFrame->pParent; + } + if( pFrame->pParent == 0 ){ + /* Global frame,return FALSE */ + ph7_context_throw_error(pCtx,PH7_CTX_WARNING,"Called in the global scope"); + ph7_result_bool(pCtx,0); + return SXRET_OK; + } + /* Create a new array */ + pArray = ph7_context_new_array(pCtx); + if( pArray == 0 ){ + SXUNUSED(nArg); /* cc warning */ + SXUNUSED(apArg); + ph7_result_bool(pCtx,0); + return SXRET_OK; + } + /* Start filling the array with the given arguments */ + aSlot = (VmSlot *)SySetBasePtr(&pFrame->sArg); + for( n = 0; n < SySetUsed(&pFrame->sArg) ; n++ ){ + pObj = (ph7_value *)SySetAt(&pCtx->pVm->aMemObj,aSlot[n].nIdx); + if( pObj ){ + ph7_array_add_elem(pArray,0/* Automatic index assign*/,pObj); + } + } + /* Return the freshly created array */ + ph7_result_value(pCtx,pArray); + return SXRET_OK; +} +/* + * bool function_exists(string $name) + * Return TRUE if the given function has been defined. + * Parameters + * The name of the desired function. + * Return + * Return TRUE if the given function has been defined.False otherwise + */ +static int vm_builtin_func_exists(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + const char *zName; + ph7_vm *pVm; + int nLen; + int res; + if( nArg < 1 ){ + /* Missing argument,return FALSE */ + ph7_result_bool(pCtx,0); + return SXRET_OK; + } + /* Point to the target VM */ + pVm = pCtx->pVm; + /* Extract the function name */ + zName = ph7_value_to_string(apArg[0],&nLen); + /* Assume the function is not defined */ + res = 0; + /* Perform the lookup */ + if( SyHashGet(&pVm->hFunction,(const void *)zName,(sxu32)nLen) != 0 || + SyHashGet(&pVm->hHostFunction,(const void *)zName,(sxu32)nLen) != 0 ){ + /* Function is defined */ + res = 1; + } + ph7_result_bool(pCtx,res); + return SXRET_OK; +} +/* Forward declaration */ +static ph7_class * VmExtractClassFromValue(ph7_vm *pVm,ph7_value *pArg); +/* + * Verify that the contents of a variable can be called as a function. + * [i.e: Whether it is callable or not]. + * Return TRUE if callable.FALSE otherwise. + */ +PH7_PRIVATE int PH7_VmIsCallable(ph7_vm *pVm,ph7_value *pValue,int CallInvoke) +{ + int res = 0; + if( pValue->iFlags & MEMOBJ_OBJ ){ + /* Call the magic method __invoke if available */ + ph7_class_instance *pThis = (ph7_class_instance *)pValue->x.pOther; + ph7_class_method *pMethod; + pMethod = PH7_ClassExtractMethod(pThis->pClass,"__invoke",sizeof("__invoke")-1); + if( pMethod && CallInvoke ){ + ph7_value sResult; + sxi32 rc; + /* Invoke the magic method and extract the result */ + PH7_MemObjInit(pVm,&sResult); + rc = PH7_VmCallClassMethod(pVm,pThis,pMethod,&sResult,0,0); + if( rc == SXRET_OK && (sResult.iFlags & (MEMOBJ_BOOL|MEMOBJ_INT)) ){ + res = sResult.x.iVal != 0; + } + PH7_MemObjRelease(&sResult); + } + }else if( pValue->iFlags & MEMOBJ_HASHMAP ){ + ph7_hashmap *pMap = (ph7_hashmap *)pValue->x.pOther; + if( pMap->nEntry > 1 ){ + ph7_class *pClass; + ph7_value *pV; + /* Extract the target class */ + pV = (ph7_value *)SySetAt(&pVm->aMemObj,pMap->pFirst->nValIdx); + if( pV ){ + pClass = VmExtractClassFromValue(pVm,pV); + if( pClass ){ + ph7_class_method *pMethod; + /* Extract the target method */ + pV = (ph7_value *)SySetAt(&pVm->aMemObj,pMap->pFirst->pPrev->nValIdx); + if( pV && (pV->iFlags & MEMOBJ_STRING) && SyBlobLength(&pV->sBlob) > 0 ){ + /* Perform the lookup */ + pMethod = PH7_ClassExtractMethod(pClass,(const char *)SyBlobData(&pV->sBlob),SyBlobLength(&pV->sBlob)); + if( pMethod ){ + /* Method is callable */ + res = 1; + } + } + } + } + } + }else if( pValue->iFlags & MEMOBJ_STRING ){ + const char *zName; + int nLen; + /* Extract the name */ + zName = ph7_value_to_string(pValue,&nLen); + /* Perform the lookup */ + if( SyHashGet(&pVm->hFunction,(const void *)zName,(sxu32)nLen) != 0 || + SyHashGet(&pVm->hHostFunction,(const void *)zName,(sxu32)nLen) != 0 ){ + /* Function is callable */ + res = 1; + } + } + return res; +} +/* + * bool is_callable(callable $name[,bool $syntax_only = false]) + * Verify that the contents of a variable can be called as a function. + * Parameters + * $name + * The callback function to check + * $syntax_only + * If set to TRUE the function only verifies that name might be a function or method. + * It will only reject simple variables that are not strings, or an array that does + * not have a valid structure to be used as a callback. The valid ones are supposed + * to have only 2 entries, the first of which is an object or a string, and the second + * a string. + * Return + * TRUE if name is callable, FALSE otherwise. + */ +static int vm_builtin_is_callable(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + ph7_vm *pVm; + int res; + if( nArg < 1 ){ + /* Missing arguments,return FALSE */ + ph7_result_bool(pCtx,0); + return SXRET_OK; + } + /* Point to the target VM */ + pVm = pCtx->pVm; + /* Perform the requested operation */ + res = PH7_VmIsCallable(pVm,apArg[0],TRUE); + ph7_result_bool(pCtx,res); + return SXRET_OK; +} +/* + * Hash walker callback used by the [get_defined_functions()] function + * defined below. + */ +static int VmHashFuncStep(SyHashEntry *pEntry,void *pUserData) +{ + ph7_value *pArray = (ph7_value *)pUserData; + ph7_value sName; + sxi32 rc; + /* Prepare the function name for insertion */ + PH7_MemObjInitFromString(pArray->pVm,&sName,0); + PH7_MemObjStringAppend(&sName,(const char *)pEntry->pKey,pEntry->nKeyLen); + /* Perform the insertion */ + rc = ph7_array_add_elem(pArray,0/* Automatic index assign */,&sName); /* Will make it's own copy */ + PH7_MemObjRelease(&sName); + return rc; +} +/* + * array get_defined_functions(void) + * Returns an array of all defined functions. + * Parameter + * None. + * Return + * Returns an multidimensional array containing a list of all defined functions + * both built-in (internal) and user-defined. + * The internal functions will be accessible via $arr["internal"], and the user + * defined ones using $arr["user"]. + * Note: + * NULL is returned on failure. + */ +static int vm_builtin_get_defined_func(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + ph7_value *pArray,*pEntry; + /* NOTE: + * Don't worry about freeing memory here,every allocated resource will be released + * automatically by the engine as soon we return from this foreign function. + */ + pArray = ph7_context_new_array(pCtx); + if( pArray == 0 ){ + SXUNUSED(nArg); /* cc warning */ + SXUNUSED(apArg); + /* Return NULL */ + ph7_result_null(pCtx); + return SXRET_OK; + } + pEntry = ph7_context_new_array(pCtx); + if( pEntry == 0 ){ + /* Return NULL */ + ph7_result_null(pCtx); + return SXRET_OK; + } + /* Fill with the appropriate information */ + SyHashForEach(&pCtx->pVm->hHostFunction,VmHashFuncStep,pEntry); + /* Create the 'internal' index */ + ph7_array_add_strkey_elem(pArray,"internal",pEntry); /* Will make it's own copy */ + /* Create the user-func array */ + pEntry = ph7_context_new_array(pCtx); + if( pEntry == 0 ){ + /* Return NULL */ + ph7_result_null(pCtx); + return SXRET_OK; + } + /* Fill with the appropriate information */ + SyHashForEach(&pCtx->pVm->hFunction,VmHashFuncStep,pEntry); + /* Create the 'user' index */ + ph7_array_add_strkey_elem(pArray,"user",pEntry); /* Will make it's own copy */ + /* Return the multi-dimensional array */ + ph7_result_value(pCtx,pArray); + return SXRET_OK; +} +/* + * void register_shutdown_function(callable $callback[,mixed $param,...) + * Register a function for execution on shutdown. + * Note + * Multiple calls to register_shutdown_function() can be made, and each will + * be called in the same order as they were registered. + * Parameters + * $callback + * The shutdown callback to register. + * $param + * One or more Parameter to pass to the registered callback. + * Return + * Nothing. + */ +static int vm_builtin_register_shutdown_function(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + VmShutdownCB sEntry; + int i,j; + if( nArg < 1 || (apArg[0]->iFlags & (MEMOBJ_STRING|MEMOBJ_HASHMAP)) == 0 ){ + /* Missing/Invalid arguments,return immediately */ + return PH7_OK; + } + /* Zero the Entry */ + SyZero(&sEntry,sizeof(VmShutdownCB)); + /* Initialize fields */ + PH7_MemObjInit(pCtx->pVm,&sEntry.sCallback); + /* Save the callback name for later invocation name */ + PH7_MemObjStore(apArg[0],&sEntry.sCallback); + for( i = 0 ; i < (int)SX_ARRAYSIZE(sEntry.aArg) ; ++i ){ + PH7_MemObjInit(pCtx->pVm,&sEntry.aArg[i]); + } + /* Copy arguments */ + for(j = 0, i = 1 ; i < nArg ; j++,i++ ){ + if( j >= (int)SX_ARRAYSIZE(sEntry.aArg) ){ + /* Limit reached */ + break; + } + PH7_MemObjStore(apArg[i],&sEntry.aArg[j]); + } + sEntry.nArg = j; + /* Install the callback */ + SySetPut(&pCtx->pVm->aShutdown,(const void *)&sEntry); + return PH7_OK; +} +/* + * Section: + * Class handling functions. + * Authors: + * Symisc Systems,devel@symisc.net. + * Copyright (C) Symisc Systems,http://ph7.symisc.net + * Status: + * Stable. + */ +/* + * Extract the top active class. NULL is returned + * if the class stack is empty. + */ +PH7_PRIVATE ph7_class * PH7_VmPeekTopClass(ph7_vm *pVm) +{ + SySet *pSet = &pVm->aSelf; + ph7_class **apClass; + if( SySetUsed(pSet) <= 0 ){ + /* Empty stack,return NULL */ + return 0; + } + /* Peek the last entry */ + apClass = (ph7_class **)SySetBasePtr(pSet); + return apClass[pSet->nUsed - 1]; +} +/* + * string get_class ([ object $object = NULL ] ) + * Returns the name of the class of an object + * Parameters + * object + * The tested object. This parameter may be omitted when inside a class. + * Return + * The name of the class of which object is an instance. + * Returns FALSE if object is not an object. + * If object is omitted when inside a class, the name of that class is returned. + */ +static int vm_builtin_get_class(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + ph7_class *pClass; + SyString *pName; + if( nArg < 1 ){ + /* Check if we are inside a class */ + pClass = PH7_VmPeekTopClass(pCtx->pVm); + if( pClass ){ + /* Point to the class name */ + pName = &pClass->sName; + ph7_result_string(pCtx,pName->zString,(int)pName->nByte); + }else{ + /* Not inside class,return FALSE */ + ph7_result_bool(pCtx,0); + } + }else{ + /* Extract the target class */ + pClass = VmExtractClassFromValue(pCtx->pVm,apArg[0]); + if( pClass ){ + pName = &pClass->sName; + /* Return the class name */ + ph7_result_string(pCtx,pName->zString,(int)pName->nByte); + }else{ + /* Not a class instance,return FALSE */ + ph7_result_bool(pCtx,0); + } + } + return PH7_OK; +} +/* + * string get_parent_class([object $object = NULL ] ) + * Returns the name of the parent class of an object + * Parameters + * object + * The tested object. This parameter may be omitted when inside a class. + * Return + * The name of the parent class of which object is an instance. + * Returns FALSE if object is not an object or if the object does + * not have a parent. + * If object is omitted when inside a class, the name of that class is returned. + */ +static int vm_builtin_get_parent_class(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + ph7_class *pClass; + SyString *pName; + if( nArg < 1 ){ + /* Check if we are inside a class [i.e: a method call]*/ + pClass = PH7_VmPeekTopClass(pCtx->pVm); + if( pClass && pClass->pBase ){ + /* Point to the class name */ + pName = &pClass->pBase->sName; + ph7_result_string(pCtx,pName->zString,(int)pName->nByte); + }else{ + /* Not inside class,return FALSE */ + ph7_result_bool(pCtx,0); + } + }else{ + /* Extract the target class */ + pClass = VmExtractClassFromValue(pCtx->pVm,apArg[0]); + if( pClass ){ + if( pClass->pBase ){ + pName = &pClass->pBase->sName; + /* Return the parent class name */ + ph7_result_string(pCtx,pName->zString,(int)pName->nByte); + }else{ + /* Object does not have a parent class */ + ph7_result_bool(pCtx,0); + } + }else{ + /* Not a class instance,return FALSE */ + ph7_result_bool(pCtx,0); + } + } + return PH7_OK; +} +/* + * string get_called_class(void) + * Gets the name of the class the static method is called in. + * Parameters + * None. + * Return + * Returns the class name. Returns FALSE if called from outside a class. + */ +static int vm_builtin_get_called_class(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + ph7_class *pClass; + /* Check if we are inside a class [i.e: a method call] */ + pClass = PH7_VmPeekTopClass(pCtx->pVm); + if( pClass ){ + SyString *pName; + /* Point to the class name */ + pName = &pClass->sName; + ph7_result_string(pCtx,pName->zString,(int)pName->nByte); + }else{ + SXUNUSED(nArg); /* cc warning */ + SXUNUSED(apArg); + /* Not inside class,return FALSE */ + ph7_result_bool(pCtx,0); + } + return PH7_OK; +} +/* + * Extract a ph7_class from the given ph7_value. + * The given value must be of type object [i.e: class instance] or + * string which hold the class name. + */ +static ph7_class * VmExtractClassFromValue(ph7_vm *pVm,ph7_value *pArg) +{ + ph7_class *pClass = 0; + if( ph7_value_is_object(pArg) ){ + /* Class instance already loaded,no need to perform a lookup */ + pClass = ((ph7_class_instance *)pArg->x.pOther)->pClass; + }else if( ph7_value_is_string(pArg) ){ + const char *zClass; + int nLen; + /* Extract class name */ + zClass = ph7_value_to_string(pArg,&nLen); + if( nLen > 0 ){ + SyHashEntry *pEntry; + /* Perform a lookup */ + pEntry = SyHashGet(&pVm->hClass,(const void *)zClass,(sxu32)nLen); + if( pEntry ){ + /* Point to the desired class */ + pClass = (ph7_class *)pEntry->pUserData; + } + } + } + return pClass; +} +/* + * bool property_exists(mixed $class,string $property) + * Checks if the object or class has a property. + * Parameters + * class + * The class name or an object of the class to test for + * property + * The name of the property + * Return + * Returns TRUE if the property exists,FALSE otherwise. + */ +static int vm_builtin_property_exists(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + int res = 0; /* Assume attribute does not exists */ + if( nArg > 1 ){ + ph7_class *pClass; + pClass = VmExtractClassFromValue(pCtx->pVm,apArg[0]); + if( pClass ){ + const char *zName; + int nLen; + /* Extract attribute name */ + zName = ph7_value_to_string(apArg[1],&nLen); + if( nLen > 0 ){ + /* Perform the lookup in the attribute and method table */ + if( SyHashGet(&pClass->hAttr,(const void *)zName,(sxu32)nLen) != 0 + || SyHashGet(&pClass->hMethod,(const void *)zName,(sxu32)nLen) != 0 ){ + /* property exists,flag that */ + res = 1; + } + } + } + } + ph7_result_bool(pCtx,res); + return PH7_OK; +} +/* + * bool method_exists(mixed $class,string $method) + * Checks if the given method is a class member. + * Parameters + * class + * The class name or an object of the class to test for + * property + * The name of the method + * Return + * Returns TRUE if the method exists,FALSE otherwise. + */ +static int vm_builtin_method_exists(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + int res = 0; /* Assume method does not exists */ + if( nArg > 1 ){ + ph7_class *pClass; + pClass = VmExtractClassFromValue(pCtx->pVm,apArg[0]); + if( pClass ){ + const char *zName; + int nLen; + /* Extract method name */ + zName = ph7_value_to_string(apArg[1],&nLen); + if( nLen > 0 ){ + /* Perform the lookup in the method table */ + if( SyHashGet(&pClass->hMethod,(const void *)zName,(sxu32)nLen) != 0 ){ + /* method exists,flag that */ + res = 1; + } + } + } + } + ph7_result_bool(pCtx,res); + return PH7_OK; +} +/* + * bool class_exists(string $class_name [, bool $autoload = true ] ) + * Checks if the class has been defined. + * Parameters + * class_name + * The class name. The name is matched in a case-sensitive manner + * unlinke the standard PHP engine. + * autoload + * Whether or not to call __autoload by default. + * Return + * TRUE if class_name is a defined class, FALSE otherwise. + */ +static int vm_builtin_class_exists(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + int res = 0; /* Assume class does not exists */ + if( nArg > 0 ){ + const char *zName; + int nLen; + /* Extract given name */ + zName = ph7_value_to_string(apArg[0],&nLen); + /* Perform a hashlookup */ + if( nLen > 0 && SyHashGet(&pCtx->pVm->hClass,(const void *)zName,(sxu32)nLen) != 0 ){ + /* class is available */ + res = 1; + } + } + ph7_result_bool(pCtx,res); + return PH7_OK; +} +/* + * bool interface_exists(string $class_name [, bool $autoload = true ] ) + * Checks if the interface has been defined. + * Parameters + * class_name + * The class name. The name is matched in a case-sensitive manner + * unlinke the standard PHP engine. + * autoload + * Whether or not to call __autoload by default. + * Return + * TRUE if class_name is a defined class, FALSE otherwise. + */ +static int vm_builtin_interface_exists(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + int res = 0; /* Assume class does not exists */ + if( nArg > 0 ){ + SyHashEntry *pEntry = 0; + const char *zName; + int nLen; + /* Extract given name */ + zName = ph7_value_to_string(apArg[0],&nLen); + /* Perform a hashlookup */ + if( nLen > 0 ){ + pEntry = SyHashGet(&pCtx->pVm->hClass,(const void *)zName,(sxu32)nLen); + } + if( pEntry ){ + ph7_class *pClass = (ph7_class *)pEntry->pUserData; + while( pClass ){ + if( pClass->iFlags & PH7_CLASS_INTERFACE ){ + /* interface is available */ + res = 1; + break; + } + /* Next with the same name */ + pClass = pClass->pNextName; + } + } + } + ph7_result_bool(pCtx,res); + return PH7_OK; +} +/* + * bool class_alias([string $original[,string $alias ]]) + * Creates an alias for a class. + * Parameters + * original + * The original class. + * alias + * The alias name for the class. + * Return + * Returns TRUE on success or FALSE on failure. + */ +static int vm_builtin_class_alias(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + const char *zOld,*zNew; + int nOldLen,nNewLen; + SyHashEntry *pEntry; + ph7_class *pClass; + char *zDup; + sxi32 rc; + if( nArg < 2 ){ + /* Missing arguments,return FALSE */ + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Extract old class name */ + zOld = ph7_value_to_string(apArg[0],&nOldLen); + /* Extract alias name */ + zNew = ph7_value_to_string(apArg[1],&nNewLen); + if( nNewLen < 1 ){ + /* Invalid alias name,return FALSE */ + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Perform a hash lookup */ + pEntry = SyHashGet(&pCtx->pVm->hClass,(const void *)zOld,(sxu32)nOldLen); + if( pEntry == 0 ){ + /* No such class,return FALSE */ + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Point to the class */ + pClass = (ph7_class *)pEntry->pUserData; + /* Duplicate alias name */ + zDup = SyMemBackendStrDup(&pCtx->pVm->sAllocator,zNew,(sxu32)nNewLen); + if( zDup == 0 ){ + /* Out of memory,return FALSE */ + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Create the alias */ + rc = SyHashInsert(&pCtx->pVm->hClass,(const void *)zDup,(sxu32)nNewLen,pClass); + if( rc != SXRET_OK ){ + SyMemBackendFree(&pCtx->pVm->sAllocator,zDup); + } + ph7_result_bool(pCtx,rc == SXRET_OK); + return PH7_OK; +} +/* + * array get_declared_classes(void) + * Returns an array with the name of the defined classes + * Parameters + * None + * Return + * Returns an array of the names of the declared classes + * in the current script. + * Note: + * NULL is returned on failure. + */ +static int vm_builtin_get_declared_classes(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + ph7_value *pName,*pArray; + SyHashEntry *pEntry; + /* Create a new array first */ + pArray = ph7_context_new_array(pCtx); + pName = ph7_context_new_scalar(pCtx); + if( pArray == 0 || pName == 0){ + SXUNUSED(nArg); /* cc warning */ + SXUNUSED(apArg); + /* Out of memory,return NULL */ + ph7_result_null(pCtx); + return PH7_OK; + } + /* Fill the array with the defined classes */ + SyHashResetLoopCursor(&pCtx->pVm->hClass); + while((pEntry = SyHashGetNextEntry(&pCtx->pVm->hClass)) != 0 ){ + ph7_class *pClass = (ph7_class *)pEntry->pUserData; + /* Do not register classes defined as interfaces */ + if( (pClass->iFlags & PH7_CLASS_INTERFACE) == 0 ){ + ph7_value_string(pName,SyStringData(&pClass->sName),(int)SyStringLength(&pClass->sName)); + /* insert class name */ + ph7_array_add_elem(pArray,0/*Automatic index assign*/,pName); /* Will make it's own copy */ + /* Reset the cursor */ + ph7_value_reset_string_cursor(pName); + } + } + /* Return the created array */ + ph7_result_value(pCtx,pArray); + return PH7_OK; +} +/* + * array get_declared_interfaces(void) + * Returns an array with the name of the defined interfaces + * Parameters + * None + * Return + * Returns an array of the names of the declared interfaces + * in the current script. + * Note: + * NULL is returned on failure. + */ +static int vm_builtin_get_declared_interfaces(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + ph7_value *pName,*pArray; + SyHashEntry *pEntry; + /* Create a new array first */ + pArray = ph7_context_new_array(pCtx); + pName = ph7_context_new_scalar(pCtx); + if( pArray == 0 || pName == 0 ){ + SXUNUSED(nArg); /* cc warning */ + SXUNUSED(apArg); + /* Out of memory,return NULL */ + ph7_result_null(pCtx); + return PH7_OK; + } + /* Fill the array with the defined classes */ + SyHashResetLoopCursor(&pCtx->pVm->hClass); + while((pEntry = SyHashGetNextEntry(&pCtx->pVm->hClass)) != 0 ){ + ph7_class *pClass = (ph7_class *)pEntry->pUserData; + /* Register classes defined as interfaces only */ + if( pClass->iFlags & PH7_CLASS_INTERFACE ){ + ph7_value_string(pName,SyStringData(&pClass->sName),(int)SyStringLength(&pClass->sName)); + /* insert interface name */ + ph7_array_add_elem(pArray,0/*Automatic index assign*/,pName); /* Will make it's own copy */ + /* Reset the cursor */ + ph7_value_reset_string_cursor(pName); + } + } + /* Return the created array */ + ph7_result_value(pCtx,pArray); + return PH7_OK; +} +/* + * array get_class_methods(string/object $class_name) + * Returns an array with the name of the class methods + * Parameters + * class_name + * The class name or class instance + * Return + * Returns an array of method names defined for the class specified by class_name. + * In case of an error, it returns NULL. + * Note: + * NULL is returned on failure. + */ +static int vm_builtin_get_class_methods(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + ph7_value *pName,*pArray; + SyHashEntry *pEntry; + ph7_class *pClass; + /* Extract the target class first */ + pClass = 0; + if( nArg > 0 ){ + pClass = VmExtractClassFromValue(pCtx->pVm,apArg[0]); + } + if( pClass == 0 ){ + /* No such class,return NULL */ + ph7_result_null(pCtx); + return PH7_OK; + } + /* Create a new array */ + pArray = ph7_context_new_array(pCtx); + pName = ph7_context_new_scalar(pCtx); + if( pArray == 0 || pName == 0){ + /* Out of memory,return NULL */ + ph7_result_null(pCtx); + return PH7_OK; + } + /* Fill the array with the defined methods */ + SyHashResetLoopCursor(&pClass->hMethod); + while((pEntry = SyHashGetNextEntry(&pClass->hMethod)) != 0 ){ + ph7_class_method *pMethod = (ph7_class_method *)pEntry->pUserData; + /* Insert method name */ + ph7_value_string(pName,SyStringData(&pMethod->sFunc.sName),(int)SyStringLength(&pMethod->sFunc.sName)); + ph7_array_add_elem(pArray,0/*Automatic index assign*/,pName); /* Will make it's own copy */ + /* Reset the cursor */ + ph7_value_reset_string_cursor(pName); + } + /* Return the created array */ + ph7_result_value(pCtx,pArray); + /* + * Don't worry about freeing memory here,everything will be relased + * automatically as soon we return from this foreign function. + */ + return PH7_OK; +} +/* + * This function return TRUE(1) if the given class attribute stored + * in the pAttrName parameter is visible and thus can be extracted + * from the current scope.Otherwise FALSE is returned. + */ +static int VmClassMemberAccess( + ph7_vm *pVm, /* Target VM */ + ph7_class *pClass, /* Target Class */ + const SyString *pAttrName, /* Attribute name */ + sxi32 iProtection, /* Attribute protection level [i.e: public,protected or private] */ + int bLog /* TRUE to log forbidden access. */ + ) +{ + if( iProtection != PH7_CLASS_PROT_PUBLIC ){ + VmFrame *pFrame = pVm->pFrame; + ph7_vm_func *pVmFunc; + while( pFrame->pParent && (pFrame->iFlags & (VM_FRAME_EXCEPTION|VM_FRAME_CATCH) ) ){ + /* Safely ignore the exception frame */ + pFrame = pFrame->pParent; + } + pVmFunc = (ph7_vm_func *)pFrame->pUserData; + if( pVmFunc == 0 || (pVmFunc->iFlags & VM_FUNC_CLASS_METHOD) == 0 ){ + goto dis; /* Access is forbidden */ + } + if( iProtection == PH7_CLASS_PROT_PRIVATE ){ + /* Must be the same instance */ + if( (ph7_class *)pVmFunc->pUserData != pClass ){ + goto dis; /* Access is forbidden */ + } + }else{ + /* Protected */ + ph7_class *pBase = (ph7_class *)pVmFunc->pUserData; + /* Must be a derived class */ + if( !VmInstanceOf(pClass,pBase) ){ + goto dis; /* Access is forbidden */ + } + } + } + return 1; /* Access is granted */ +dis: + if( bLog ){ + VmErrorFormat(&(*pVm),PH7_CTX_ERR, + "Access to the class attribute '%z->%z' is forbidden", + &pClass->sName,pAttrName); + } + return 0; /* Access is forbidden */ +} +/* + * array get_class_vars(string/object $class_name) + * Get the default properties of the class + * Parameters + * class_name + * The class name or class instance + * Return + * Returns an associative array of declared properties visible from the current scope + * with their default value. The resulting array elements are in the form + * of varname => value. + * Note: + * NULL is returned on failure. + */ +static int vm_builtin_get_class_vars(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + ph7_value *pName,*pArray,sValue; + SyHashEntry *pEntry; + ph7_class *pClass; + /* Extract the target class first */ + pClass = 0; + if( nArg > 0 ){ + pClass = VmExtractClassFromValue(pCtx->pVm,apArg[0]); + } + if( pClass == 0 ){ + /* No such class,return NULL */ + ph7_result_null(pCtx); + return PH7_OK; + } + /* Create a new array */ + pArray = ph7_context_new_array(pCtx); + pName = ph7_context_new_scalar(pCtx); + PH7_MemObjInit(pCtx->pVm,&sValue); + if( pArray == 0 || pName == 0){ + /* Out of memory,return NULL */ + ph7_result_null(pCtx); + return PH7_OK; + } + /* Fill the array with the defined attribute visible from the current scope */ + SyHashResetLoopCursor(&pClass->hAttr); + while((pEntry = SyHashGetNextEntry(&pClass->hAttr)) != 0 ){ + ph7_class_attr *pAttr = (ph7_class_attr *)pEntry->pUserData; + /* Check if the access is allowed */ + if( VmClassMemberAccess(pCtx->pVm,pClass,&pAttr->sName,pAttr->iProtection,FALSE) ){ + SyString *pAttrName = &pAttr->sName; + ph7_value *pValue = 0; + if( pAttr->iFlags & (PH7_CLASS_ATTR_CONSTANT|PH7_CLASS_ATTR_STATIC) ){ + /* Extract static attribute value which is always computed */ + pValue = (ph7_value *)SySetAt(&pCtx->pVm->aMemObj,pAttr->nIdx); + }else{ + if( SySetUsed(&pAttr->aByteCode) > 0 ){ + PH7_MemObjRelease(&sValue); + /* Compute default value (any complex expression) associated with this attribute */ + VmLocalExec(pCtx->pVm,&pAttr->aByteCode,&sValue); + pValue = &sValue; + } + } + /* Fill in the array */ + ph7_value_string(pName,pAttrName->zString,pAttrName->nByte); + ph7_array_add_elem(pArray,pName,pValue); /* Will make it's own copy */ + /* Reset the cursor */ + ph7_value_reset_string_cursor(pName); + } + } + PH7_MemObjRelease(&sValue); + /* Return the created array */ + ph7_result_value(pCtx,pArray); + /* + * Don't worry about freeing memory here,everything will be relased + * automatically as soon we return from this foreign function. + */ + return PH7_OK; +} +/* + * array get_object_vars(object $this) + * Gets the properties of the given object + * Parameters + * this + * A class instance + * Return + * Returns an associative array of defined object accessible non-static properties + * for the specified object in scope. If a property have not been assigned a value + * it will be returned with a NULL value. + * Note: + * NULL is returned on failure. + */ +static int vm_builtin_get_object_vars(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + ph7_class_instance *pThis = 0; + ph7_value *pName,*pArray; + SyHashEntry *pEntry; + if( nArg > 0 && (apArg[0]->iFlags & MEMOBJ_OBJ) ){ + /* Extract the target instance */ + pThis = (ph7_class_instance *)apArg[0]->x.pOther; + } + if( pThis == 0 ){ + /* No such instance,return NULL */ + ph7_result_null(pCtx); + return PH7_OK; + } + /* Create a new array */ + pArray = ph7_context_new_array(pCtx); + pName = ph7_context_new_scalar(pCtx); + if( pArray == 0 || pName == 0){ + /* Out of memory,return NULL */ + ph7_result_null(pCtx); + return PH7_OK; + } + /* Fill the array with the defined attribute visible from the current scope */ + SyHashResetLoopCursor(&pThis->hAttr); + while((pEntry = SyHashGetNextEntry(&pThis->hAttr)) != 0 ){ + VmClassAttr *pVmAttr = (VmClassAttr *)pEntry->pUserData; + SyString *pAttrName; + if( pVmAttr->pAttr->iFlags & (PH7_CLASS_ATTR_STATIC|PH7_CLASS_ATTR_CONSTANT) ){ + /* Only non-static/constant attributes are extracted */ + continue; + } + pAttrName = &pVmAttr->pAttr->sName; + /* Check if the access is allowed */ + if( VmClassMemberAccess(pCtx->pVm,pThis->pClass,pAttrName,pVmAttr->pAttr->iProtection,FALSE) ){ + ph7_value *pValue = 0; + /* Extract attribute */ + pValue = PH7_ClassInstanceExtractAttrValue(pThis,pVmAttr); + if( pValue ){ + /* Insert attribute name in the array */ + ph7_value_string(pName,pAttrName->zString,pAttrName->nByte); + ph7_array_add_elem(pArray,pName,pValue); /* Will make it's own copy */ + } + /* Reset the cursor */ + ph7_value_reset_string_cursor(pName); + } + } + /* Return the created array */ + ph7_result_value(pCtx,pArray); + /* + * Don't worry about freeing memory here,everything will be relased + * automatically as soon we return from this foreign function. + */ + return PH7_OK; +} +/* + * This function returns TRUE if the given class is an implemented + * interface.Otherwise FALSE is returned. + */ +static int VmQueryInterfaceSet(ph7_class *pClass,SySet *pSet) +{ + ph7_class **apInterface; + sxu32 n; + if( SySetUsed(pSet) < 1 ){ + /* Empty interface container */ + return FALSE; + } + /* Point to the set of implemented interfaces */ + apInterface = (ph7_class **)SySetBasePtr(pSet); + /* Perform the lookup */ + for( n = 0 ; n < SySetUsed(pSet) ; n++ ){ + if( apInterface[n] == pClass ){ + return TRUE; + } + } + return FALSE; +} +/* + * This function returns TRUE if the given class (first argument) + * is an instance of the main class (second argument). + * Otherwise FALSE is returned. + */ +static int VmInstanceOf(ph7_class *pThis,ph7_class *pClass) +{ + ph7_class *pParent; + sxi32 rc; + if( pThis == pClass ){ + /* Instance of the same class */ + return TRUE; + } + /* Check implemented interfaces */ + rc = VmQueryInterfaceSet(pClass,&pThis->aInterface); + if( rc ){ + return TRUE; + } + /* Check parent classes */ + pParent = pThis->pBase; + while( pParent ){ + if( pParent == pClass ){ + /* Same instance */ + return TRUE; + } + /* Check the implemented interfaces */ + rc = VmQueryInterfaceSet(pClass,&pParent->aInterface); + if( rc ){ + return TRUE; + } + /* Point to the parent class */ + pParent = pParent->pBase; + } + /* Not an instance of the the given class */ + return FALSE; +} +/* + * This function returns TRUE if the given class (first argument) + * is a subclass of the main class (second argument). + * Otherwise FALSE is returned. + */ +static int VmSubclassOf(ph7_class *pClass,ph7_class *pBase) +{ + SySet *pInterface = &pClass->aInterface; + SyHashEntry *pEntry; + SyString *pName; + sxi32 rc; + while( pClass ){ + pName = &pClass->sName; + /* Query the derived hashtable */ + pEntry = SyHashGet(&pBase->hDerived,(const void *)pName->zString,pName->nByte); + if( pEntry ){ + return TRUE; + } + pClass = pClass->pBase; + } + rc = VmQueryInterfaceSet(pBase,pInterface); + if( rc ){ + return TRUE; + } + /* Not a subclass */ + return FALSE; +} +/* + * bool is_a(object $object,string $class_name) + * Checks if the object is of this class or has this class as one of its parents. + * Parameters + * object + * The tested object + * class_name + * The class name + * Return + * Returns TRUE if the object is of this class or has this class as one of its + * parents, FALSE otherwise. + */ +static int vm_builtin_is_a(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + int res = 0; /* Assume FALSE by default */ + if( nArg > 1 && ph7_value_is_object(apArg[0]) ){ + ph7_class_instance *pThis = (ph7_class_instance *)apArg[0]->x.pOther; + ph7_class *pClass; + /* Extract the given class */ + pClass = VmExtractClassFromValue(pCtx->pVm,apArg[1]); + if( pClass ){ + /* Perform the query */ + res = VmInstanceOf(pThis->pClass,pClass); + } + } + /* Query result */ + ph7_result_bool(pCtx,res); + return PH7_OK; +} +/* + * bool is_subclass_of(object/string $object,object/string $class_name) + * Checks if the object has this class as one of its parents. + * Parameters + * object + * The tested object + * class_name + * The class name + * Return + * This function returns TRUE if the object , belongs to a class + * which is a subclass of class_name, FALSE otherwise. + */ +static int vm_builtin_is_subclass_of(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + int res = 0; /* Assume FALSE by default */ + if( nArg > 1 ){ + ph7_class *pClass,*pMain; + /* Extract the given classes */ + pClass = VmExtractClassFromValue(pCtx->pVm,apArg[0]); + pMain = VmExtractClassFromValue(pCtx->pVm,apArg[1]); + if( pClass && pMain ){ + /* Perform the query */ + res = VmSubclassOf(pClass,pMain); + } + } + /* Query result */ + ph7_result_bool(pCtx,res); + return PH7_OK; +} +/* + * Call a class method where the name of the method is stored in the pMethod + * parameter and the given arguments are stored in the apArg[] array. + * Return SXRET_OK if the method was successfuly called.Any other + * return value indicates failure. + */ +PH7_PRIVATE sxi32 PH7_VmCallClassMethod( + ph7_vm *pVm, /* Target VM */ + ph7_class_instance *pThis, /* Target class instance [i.e: Object in the PHP jargon]*/ + ph7_class_method *pMethod, /* Method name */ + ph7_value *pResult, /* Store method return value here. NULL otherwise */ + int nArg, /* Total number of given arguments */ + ph7_value **apArg /* Method arguments */ + ) +{ + ph7_value *aStack; + VmInstr aInstr[2]; + int iCursor; + int i; + /* Create a new operand stack */ + aStack = VmNewOperandStack(&(*pVm),2/* Method name + Aux data */+nArg); + if( aStack == 0 ){ + PH7_VmThrowError(&(*pVm),0,PH7_CTX_ERR, + "PH7 is running out of memory while invoking class method"); + return SXERR_MEM; + } + /* Fill the operand stack with the given arguments */ + for( i = 0 ; i < nArg ; i++ ){ + PH7_MemObjLoad(apArg[i],&aStack[i]); + /* + * Symisc eXtension: + * Parameters to [call_user_func()] can be passed by reference. + */ + aStack[i].nIdx = apArg[i]->nIdx; + } + iCursor = nArg + 1; + if( pThis ){ + /* + * Push the class instance so that the '$this' variable will be available. + */ + pThis->iRef++; /* Increment reference count */ + aStack[i].x.pOther = pThis; + aStack[i].iFlags = MEMOBJ_OBJ; + } + aStack[i].nIdx = SXU32_HIGH; /* Mark as constant */ + i++; + /* Push method name */ + SyBlobReset(&aStack[i].sBlob); + SyBlobAppend(&aStack[i].sBlob,(const void *)SyStringData(&pMethod->sVmName),SyStringLength(&pMethod->sVmName)); + aStack[i].iFlags = MEMOBJ_STRING; + aStack[i].nIdx = SXU32_HIGH; + /* Emit the CALL istruction */ + aInstr[0].iOp = PH7_OP_CALL; + aInstr[0].iP1 = nArg; /* Total number of given arguments */ + aInstr[0].iP2 = 0; + aInstr[0].p3 = 0; + /* Emit the DONE instruction */ + aInstr[1].iOp = PH7_OP_DONE; + aInstr[1].iP1 = 1; /* Extract method return value */ + aInstr[1].iP2 = 0; + aInstr[1].p3 = 0; + /* Execute the method body (if available) */ + VmByteCodeExec(&(*pVm),aInstr,aStack,iCursor,pResult,0,TRUE); + /* Clean up the mess left behind */ + SyMemBackendFree(&pVm->sAllocator,aStack); + return PH7_OK; +} +/* + * Call a user defined or foreign function where the name of the function + * is stored in the pFunc parameter and the given arguments are stored + * in the apArg[] array. + * Return SXRET_OK if the function was successfuly called.Any other + * return value indicates failure. + */ +PH7_PRIVATE sxi32 PH7_VmCallUserFunction( + ph7_vm *pVm, /* Target VM */ + ph7_value *pFunc, /* Callback name */ + int nArg, /* Total number of given arguments */ + ph7_value **apArg, /* Callback arguments */ + ph7_value *pResult /* Store callback return value here. NULL otherwise */ + ) +{ + ph7_value *aStack; + VmInstr aInstr[2]; + int i; + if((pFunc->iFlags & (MEMOBJ_STRING|MEMOBJ_HASHMAP)) == 0 ){ + /* Don't bother processing,it's invalid anyway */ + if( pResult ){ + /* Assume a null return value */ + PH7_MemObjRelease(pResult); + } + return SXERR_INVALID; + } + if( pFunc->iFlags & MEMOBJ_HASHMAP ){ + /* Class method */ + ph7_hashmap *pMap = (ph7_hashmap *)pFunc->x.pOther; + ph7_class_method *pMethod = 0; + ph7_class_instance *pThis = 0; + ph7_class *pClass = 0; + ph7_value *pValue; + sxi32 rc; + if( pMap->nEntry < 2 /* Class name/instance + method name */){ + /* Empty hashmap,nothing to call */ + if( pResult ){ + /* Assume a null return value */ + PH7_MemObjRelease(pResult); + } + return SXRET_OK; + } + /* Extract the class name or an instance of it */ + pValue = (ph7_value *)SySetAt(&pVm->aMemObj,pMap->pFirst->nValIdx); + if( pValue ){ + pClass = VmExtractClassFromValue(&(*pVm),pValue); + } + if( pClass == 0 ){ + /* No such class,return NULL */ + if( pResult ){ + PH7_MemObjRelease(pResult); + } + return SXRET_OK; + } + if( pValue->iFlags & MEMOBJ_OBJ ){ + /* Point to the class instance */ + pThis = (ph7_class_instance *)pValue->x.pOther; + } + /* Try to extract the method */ + pValue = (ph7_value *)SySetAt(&pVm->aMemObj,pMap->pFirst->pPrev->nValIdx); + if( pValue ){ + if( (pValue->iFlags & MEMOBJ_STRING) && SyBlobLength(&pValue->sBlob) > 0 ){ + pMethod = PH7_ClassExtractMethod(pClass,(const char *)SyBlobData(&pValue->sBlob), + SyBlobLength(&pValue->sBlob)); + } + } + if( pMethod == 0 ){ + /* No such method,return NULL */ + if( pResult ){ + PH7_MemObjRelease(pResult); + } + return SXRET_OK; + } + /* Call the class method */ + rc = PH7_VmCallClassMethod(&(*pVm),pThis,pMethod,pResult,nArg,apArg); + return rc; + } + /* Create a new operand stack */ + aStack = VmNewOperandStack(&(*pVm),1+nArg); + if( aStack == 0 ){ + PH7_VmThrowError(&(*pVm),0,PH7_CTX_ERR, + "PH7 is running out of memory while invoking user callback"); + if( pResult ){ + /* Assume a null return value */ + PH7_MemObjRelease(pResult); + } + return SXERR_MEM; + } + /* Fill the operand stack with the given arguments */ + for( i = 0 ; i < nArg ; i++ ){ + PH7_MemObjLoad(apArg[i],&aStack[i]); + /* + * Symisc eXtension: + * Parameters to [call_user_func()] can be passed by reference. + */ + aStack[i].nIdx = apArg[i]->nIdx; + } + /* Push the function name */ + PH7_MemObjLoad(pFunc,&aStack[i]); + aStack[i].nIdx = SXU32_HIGH; /* Mark as constant */ + /* Emit the CALL istruction */ + aInstr[0].iOp = PH7_OP_CALL; + aInstr[0].iP1 = nArg; /* Total number of given arguments */ + aInstr[0].iP2 = 0; + aInstr[0].p3 = 0; + /* Emit the DONE instruction */ + aInstr[1].iOp = PH7_OP_DONE; + aInstr[1].iP1 = 1; /* Extract function return value if available */ + aInstr[1].iP2 = 0; + aInstr[1].p3 = 0; + /* Execute the function body (if available) */ + VmByteCodeExec(&(*pVm),aInstr,aStack,nArg,pResult,0,TRUE); + /* Clean up the mess left behind */ + SyMemBackendFree(&pVm->sAllocator,aStack); + return PH7_OK; +} +/* + * Call a user defined or foreign function whith a varibale number + * of arguments where the name of the function is stored in the pFunc + * parameter. + * Return SXRET_OK if the function was successfuly called.Any other + * return value indicates failure. + */ +PH7_PRIVATE sxi32 PH7_VmCallUserFunctionAp( + ph7_vm *pVm, /* Target VM */ + ph7_value *pFunc, /* Callback name */ + ph7_value *pResult,/* Store callback return value here. NULL otherwise */ + ... /* 0 (Zero) or more Callback arguments */ + ) +{ + ph7_value *pArg; + SySet aArg; + va_list ap; + sxi32 rc; + SySetInit(&aArg,&pVm->sAllocator,sizeof(ph7_value *)); + /* Copy arguments one after one */ + va_start(ap,pResult); + for(;;){ + pArg = va_arg(ap,ph7_value *); + if( pArg == 0 ){ + break; + } + SySetPut(&aArg,(const void *)&pArg); + } + /* Call the core routine */ + rc = PH7_VmCallUserFunction(&(*pVm),pFunc,(int)SySetUsed(&aArg),(ph7_value **)SySetBasePtr(&aArg),pResult); + /* Cleanup */ + SySetRelease(&aArg); + return rc; +} +/* + * value call_user_func(callable $callback[,value $parameter[, value $... ]]) + * Call the callback given by the first parameter. + * Parameter + * $callback + * The callable to be called. + * ... + * Zero or more parameters to be passed to the callback. + * Return + * Th return value of the callback, or FALSE on error. + */ +static int vm_builtin_call_user_func(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + ph7_value sResult; /* Store callback return value here */ + sxi32 rc; + if( nArg < 1 ){ + /* Missing arguments,return FALSE */ + ph7_result_bool(pCtx,0); + return PH7_OK; + } + PH7_MemObjInit(pCtx->pVm,&sResult); + sResult.nIdx = SXU32_HIGH; /* Mark as constant */ + /* Try to invoke the callback */ + rc = PH7_VmCallUserFunction(pCtx->pVm,apArg[0],nArg - 1,&apArg[1],&sResult); + if( rc != SXRET_OK ){ + /* An error occured while invoking the given callback [i.e: not defined] */ + ph7_result_bool(pCtx,0); /* return false */ + }else{ + /* Callback result */ + ph7_result_value(pCtx,&sResult); /* Will make it's own copy */ + } + PH7_MemObjRelease(&sResult); + return PH7_OK; +} +/* + * value call_user_func_array(callable $callback,array $param_arr) + * Call a callback with an array of parameters. + * Parameter + * $callback + * The callable to be called. + * $param_arr + * The parameters to be passed to the callback, as an indexed array. + * Return + * Returns the return value of the callback, or FALSE on error. + */ +static int vm_builtin_call_user_func_array(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + ph7_hashmap_node *pEntry; /* Current hashmap entry */ + ph7_value *pValue,sResult;/* Store callback return value here */ + ph7_hashmap *pMap; /* Target hashmap */ + SySet aArg; /* Arguments containers */ + sxi32 rc; + sxu32 n; + if( nArg < 2 || !ph7_value_is_array(apArg[1]) ){ + /* Missing/Invalid arguments,return FALSE */ + ph7_result_bool(pCtx,0); + return PH7_OK; + } + PH7_MemObjInit(pCtx->pVm,&sResult); + sResult.nIdx = SXU32_HIGH; /* Mark as constant */ + /* Initialize the arguments container */ + SySetInit(&aArg,&pCtx->pVm->sAllocator,sizeof(ph7_value *)); + /* Turn hashmap entries into callback arguments */ + pMap = (ph7_hashmap *)apArg[1]->x.pOther; + pEntry = pMap->pFirst; /* First inserted entry */ + for( n = 0 ; n < pMap->nEntry ; n++ ){ + /* Extract node value */ + if( (pValue = (ph7_value *)SySetAt(&pCtx->pVm->aMemObj,pEntry->nValIdx)) != 0 ){ + SySetPut(&aArg,(const void *)&pValue); + } + /* Point to the next entry */ + pEntry = pEntry->pPrev; /* Reverse link */ + } + /* Try to invoke the callback */ + rc = PH7_VmCallUserFunction(pCtx->pVm,apArg[0],(int)SySetUsed(&aArg),(ph7_value **)SySetBasePtr(&aArg),&sResult); + if( rc != SXRET_OK ){ + /* An error occured while invoking the given callback [i.e: not defined] */ + ph7_result_bool(pCtx,0); /* return false */ + }else{ + /* Callback result */ + ph7_result_value(pCtx,&sResult); /* Will make it's own copy */ + } + /* Cleanup the mess left behind */ + PH7_MemObjRelease(&sResult); + SySetRelease(&aArg); + return PH7_OK; +} +/* + * bool defined(string $name) + * Checks whether a given named constant exists. + * Parameter: + * Name of the desired constant. + * Return + * TRUE if the given constant exists.FALSE otherwise. + */ +static int vm_builtin_defined(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + const char *zName; + int nLen = 0; + int res = 0; + if( nArg < 1 ){ + /* Missing constant name,return FALSE */ + ph7_context_throw_error(pCtx,PH7_CTX_NOTICE,"Missing constant name"); + ph7_result_bool(pCtx,0); + return SXRET_OK; + } + /* Extract constant name */ + zName = ph7_value_to_string(apArg[0],&nLen); + /* Perform the lookup */ + if( nLen > 0 && SyHashGet(&pCtx->pVm->hConstant,(const void *)zName,(sxu32)nLen) != 0 ){ + /* Already defined */ + res = 1; + } + ph7_result_bool(pCtx,res); + return SXRET_OK; +} +/* + * Constant expansion callback used by the [define()] function defined + * below. + */ +static void VmExpandUserConstant(ph7_value *pVal,void *pUserData) +{ + ph7_value *pConstantValue = (ph7_value *)pUserData; + /* Expand constant value */ + PH7_MemObjStore(pConstantValue,pVal); +} +/* + * bool define(string $constant_name,expression value) + * Defines a named constant at runtime. + * Parameter: + * $constant_name + * The name of the constant + * $value + * Constant value + * Return: + * TRUE on success,FALSE on failure. + */ +static int vm_builtin_define(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + const char *zName; /* Constant name */ + ph7_value *pValue; /* Duplicated constant value */ + int nLen = 0; /* Name length */ + sxi32 rc; + if( nArg < 2 ){ + /* Missing arguments,throw a ntoice and return false */ + ph7_context_throw_error(pCtx,PH7_CTX_NOTICE,"Missing constant name/value pair"); + ph7_result_bool(pCtx,0); + return SXRET_OK; + } + if( !ph7_value_is_string(apArg[0]) ){ + ph7_context_throw_error(pCtx,PH7_CTX_NOTICE,"Invalid constant name"); + ph7_result_bool(pCtx,0); + return SXRET_OK; + } + /* Extract constant name */ + zName = ph7_value_to_string(apArg[0],&nLen); + if( nLen < 1 ){ + ph7_context_throw_error(pCtx,PH7_CTX_NOTICE,"Empty constant name"); + ph7_result_bool(pCtx,0); + return SXRET_OK; + } + /* Duplicate constant value */ + pValue = (ph7_value *)SyMemBackendPoolAlloc(&pCtx->pVm->sAllocator,sizeof(ph7_value)); + if( pValue == 0 ){ + ph7_context_throw_error(pCtx,PH7_CTX_NOTICE,"Cannot register constant due to a memory failure"); + ph7_result_bool(pCtx,0); + return SXRET_OK; + } + /* Initialize the memory object */ + PH7_MemObjInit(pCtx->pVm,pValue); + /* Register the constant */ + rc = ph7_create_constant(pCtx->pVm,zName,VmExpandUserConstant,pValue); + if( rc != SXRET_OK ){ + SyMemBackendPoolFree(&pCtx->pVm->sAllocator,pValue); + ph7_context_throw_error(pCtx,PH7_CTX_NOTICE,"Cannot register constant due to a memory failure"); + ph7_result_bool(pCtx,0); + return SXRET_OK; + } + /* Duplicate constant value */ + PH7_MemObjStore(apArg[1],pValue); + if( nArg == 3 && ph7_value_is_bool(apArg[2]) && ph7_value_to_bool(apArg[2]) ){ + /* Lower case the constant name */ + char *zCur = (char *)zName; + while( zCur < &zName[nLen] ){ + if( (unsigned char)zCur[0] >= 0xc0 ){ + /* UTF-8 stream */ + zCur++; + while( zCur < &zName[nLen] && (((unsigned char)zCur[0] & 0xc0) == 0x80) ){ + zCur++; + } + continue; + } + if( SyisUpper(zCur[0]) ){ + int c = SyToLower(zCur[0]); + zCur[0] = (char)c; + } + zCur++; + } + /* Finally,register the constant */ + ph7_create_constant(pCtx->pVm,zName,VmExpandUserConstant,pValue); + } + /* All done,return TRUE */ + ph7_result_bool(pCtx,1); + return SXRET_OK; +} +/* + * value constant(string $name) + * Returns the value of a constant + * Parameter + * $name + * Name of the constant. + * Return + * Constant value or NULL if not defined. + */ +static int vm_builtin_constant(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + SyHashEntry *pEntry; + ph7_constant *pCons; + const char *zName; /* Constant name */ + ph7_value sVal; /* Constant value */ + int nLen; + if( nArg < 1 || !ph7_value_is_string(apArg[0]) ){ + /* Invallid argument,return NULL */ + ph7_context_throw_error(pCtx,PH7_CTX_NOTICE,"Missing/Invalid constant name"); + ph7_result_null(pCtx); + return SXRET_OK; + } + /* Extract the constant name */ + zName = ph7_value_to_string(apArg[0],&nLen); + /* Perform the query */ + pEntry = SyHashGet(&pCtx->pVm->hConstant,(const void *)zName,(sxu32)nLen); + if( pEntry == 0 ){ + ph7_context_throw_error_format(pCtx,PH7_CTX_NOTICE,"'%.*s': Undefined constant",nLen,zName); + ph7_result_null(pCtx); + return SXRET_OK; + } + PH7_MemObjInit(pCtx->pVm,&sVal); + /* Point to the structure that describe the constant */ + pCons = (ph7_constant *)SyHashEntryGetUserData(pEntry); + /* Extract constant value by calling it's associated callback */ + pCons->xExpand(&sVal,pCons->pUserData); + /* Return that value */ + ph7_result_value(pCtx,&sVal); + /* Cleanup */ + PH7_MemObjRelease(&sVal); + return SXRET_OK; +} +/* + * Hash walker callback used by the [get_defined_constants()] function + * defined below. + */ +static int VmHashConstStep(SyHashEntry *pEntry,void *pUserData) +{ + ph7_value *pArray = (ph7_value *)pUserData; + ph7_value sName; + sxi32 rc; + /* Prepare the constant name for insertion */ + PH7_MemObjInitFromString(pArray->pVm,&sName,0); + PH7_MemObjStringAppend(&sName,(const char *)pEntry->pKey,pEntry->nKeyLen); + /* Perform the insertion */ + rc = ph7_array_add_elem(pArray,0,&sName); /* Will make it's own copy */ + PH7_MemObjRelease(&sName); + return rc; +} +/* + * array get_defined_constants(void) + * Returns an associative array with the names of all defined + * constants. + * Parameters + * NONE. + * Returns + * Returns the names of all the constants currently defined. + */ +static int vm_builtin_get_defined_constants(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + ph7_value *pArray; + /* Create the array first*/ + pArray = ph7_context_new_array(pCtx); + if( pArray == 0 ){ + SXUNUSED(nArg); /* cc warning */ + SXUNUSED(apArg); + /* Return NULL */ + ph7_result_null(pCtx); + return SXRET_OK; + } + /* Fill the array with the defined constants */ + SyHashForEach(&pCtx->pVm->hConstant,VmHashConstStep,pArray); + /* Return the created array */ + ph7_result_value(pCtx,pArray); + return SXRET_OK; +} +/* + * Section: + * Output Control (OB) functions. + * Authors: + * Symisc Systems,devel@symisc.net. + * Copyright (C) Symisc Systems,http://ph7.symisc.net + * Status: + * Stable. + */ +/* Forward declaration */ +static void VmObRestore(ph7_vm *pVm,VmObEntry *pEntry); +/* + * void ob_clean(void) + * This function discards the contents of the output buffer. + * This function does not destroy the output buffer like ob_end_clean() does. + * Parameter + * None + * Return + * No value is returned. + */ +static int vm_builtin_ob_clean(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + ph7_vm *pVm = pCtx->pVm; + VmObEntry *pOb; + SXUNUSED(nArg); /* cc warning */ + SXUNUSED(apArg); + /* Peek the top most OB */ + pOb = (VmObEntry *)SySetPeek(&pVm->aOB); + if( pOb ){ + SyBlobRelease(&pOb->sOB); + } + return PH7_OK; +} +/* + * bool ob_end_clean(void) + * Clean (erase) the output buffer and turn off output buffering + * This function discards the contents of the topmost output buffer and turns + * off this output buffering. If you want to further process the buffer's contents + * you have to call ob_get_contents() before ob_end_clean() as the buffer contents + * are discarded when ob_end_clean() is called. + * Parameter + * None + * Return + * Returns TRUE on success or FALSE on failure. Reasons for failure are first that you called + * the function without an active buffer or that for some reason a buffer could not be deleted + * (possible for special buffer) + */ +static int vm_builtin_ob_end_clean(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + ph7_vm *pVm = pCtx->pVm; + VmObEntry *pOb; + /* Pop the top most OB */ + pOb = (VmObEntry *)SySetPop(&pVm->aOB); + if( pOb == 0){ + /* No such OB,return FALSE */ + ph7_result_bool(pCtx,0); + SXUNUSED(nArg); /* cc warning */ + SXUNUSED(apArg); + }else{ + /* Release */ + VmObRestore(pVm,pOb); + /* Return true */ + ph7_result_bool(pCtx,1); + } + return PH7_OK; +} +/* + * string ob_get_contents(void) + * Gets the contents of the output buffer without clearing it. + * Parameter + * None + * Return + * This will return the contents of the output buffer or FALSE, if output buffering isn't active. + */ +static int vm_builtin_ob_get_contents(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + ph7_vm *pVm = pCtx->pVm; + VmObEntry *pOb; + /* Peek the top most OB */ + pOb = (VmObEntry *)SySetPeek(&pVm->aOB); + if( pOb == 0 ){ + /* No active OB,return FALSE */ + ph7_result_bool(pCtx,0); + SXUNUSED(nArg); /* cc warning */ + SXUNUSED(apArg); + }else{ + /* Return contents */ + ph7_result_string(pCtx,(const char *)SyBlobData(&pOb->sOB),(int)SyBlobLength(&pOb->sOB)); + } + return PH7_OK; +} +/* + * string ob_get_clean(void) + * string ob_get_flush(void) + * Get current buffer contents and delete current output buffer. + * Parameter + * None + * Return + * This will return the contents of the output buffer or FALSE, if output buffering isn't active. + */ +static int vm_builtin_ob_get_clean(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + ph7_vm *pVm = pCtx->pVm; + VmObEntry *pOb; + /* Pop the top most OB */ + pOb = (VmObEntry *)SySetPop(&pVm->aOB); + if( pOb == 0 ){ + /* No active OB,return FALSE */ + ph7_result_bool(pCtx,0); + SXUNUSED(nArg); /* cc warning */ + SXUNUSED(apArg); + }else{ + /* Return contents */ + ph7_result_string(pCtx,(const char *)SyBlobData(&pOb->sOB),(int)SyBlobLength(&pOb->sOB)); /* Will make it's own copy */ + /* Release */ + VmObRestore(pVm,pOb); + } + return PH7_OK; +} +/* + * int ob_get_length(void) + * Return the length of the output buffer. + * Parameter + * None + * Return + * Returns the length of the output buffer contents or FALSE if no buffering is active. + */ +static int vm_builtin_ob_get_length(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + ph7_vm *pVm = pCtx->pVm; + VmObEntry *pOb; + /* Peek the top most OB */ + pOb = (VmObEntry *)SySetPeek(&pVm->aOB); + if( pOb == 0 ){ + /* No active OB,return FALSE */ + ph7_result_bool(pCtx,0); + SXUNUSED(nArg); /* cc warning */ + SXUNUSED(apArg); + }else{ + /* Return OB length */ + ph7_result_int64(pCtx,(ph7_int64)SyBlobLength(&pOb->sOB)); + } + return PH7_OK; +} +/* + * int ob_get_level(void) + * Returns the nesting level of the output buffering mechanism. + * Parameter + * None + * Return + * Returns the level of nested output buffering handlers or zero if output buffering is not active. + */ +static int vm_builtin_ob_get_level(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + ph7_vm *pVm = pCtx->pVm; + int iNest; + SXUNUSED(nArg); /* cc warning */ + SXUNUSED(apArg); + /* Nesting level */ + iNest = (int)SySetUsed(&pVm->aOB); + /* Return the nesting value */ + ph7_result_int(pCtx,iNest); + return PH7_OK; +} +/* + * Output Buffer(OB) default VM consumer routine.All VM output is now redirected + * to a stackable internal buffer,until the user call [ob_get_clean(),ob_end_clean(),...]. + * Refer to the implementation of [ob_start()] for more information. + */ +static int VmObConsumer(const void *pData,unsigned int nDataLen,void *pUserData) +{ + ph7_vm *pVm = (ph7_vm *)pUserData; + VmObEntry *pEntry; + ph7_value sResult; + /* Peek the top most entry */ + pEntry = (VmObEntry *)SySetPeek(&pVm->aOB); + if( pEntry == 0 ){ + /* CAN'T HAPPEN */ + return PH7_OK; + } + PH7_MemObjInit(pVm,&sResult); + if( ph7_value_is_callable(&pEntry->sCallback) && pVm->nObDepth < 15 ){ + ph7_value sArg,*apArg[2]; + /* Fill the first argument */ + PH7_MemObjInitFromString(pVm,&sArg,0); + PH7_MemObjStringAppend(&sArg,(const char *)pData,nDataLen); + apArg[0] = &sArg; + /* Call the 'filter' callback */ + pVm->nObDepth++; + PH7_VmCallUserFunction(pVm,&pEntry->sCallback,1,apArg,&sResult); + pVm->nObDepth--; + if( sResult.iFlags & MEMOBJ_STRING ){ + /* Extract the function result */ + pData = SyBlobData(&sResult.sBlob); + nDataLen = SyBlobLength(&sResult.sBlob); + } + PH7_MemObjRelease(&sArg); + } + if( nDataLen > 0 ){ + /* Redirect the VM output to the internal buffer */ + SyBlobAppend(&pEntry->sOB,pData,nDataLen); + } + /* Release */ + PH7_MemObjRelease(&sResult); + return PH7_OK; +} +/* + * Restore the default consumer. + * Refer to the implementation of [ob_end_clean()] for more + * information. + */ +static void VmObRestore(ph7_vm *pVm,VmObEntry *pEntry) +{ + ph7_output_consumer *pCons = &pVm->sVmConsumer; + if( SySetUsed(&pVm->aOB) < 1 ){ + /* No more stackable OB */ + pCons->xConsumer = pCons->xDef; + pCons->pUserData = pCons->pDefData; + } + /* Release OB data */ + PH7_MemObjRelease(&pEntry->sCallback); + SyBlobRelease(&pEntry->sOB); +} +/* + * bool ob_start([ callback $output_callback] ) + * This function will turn output buffering on. While output buffering is active no output + * is sent from the script (other than headers), instead the output is stored in an internal + * buffer. + * Parameter + * $output_callback + * An optional output_callback function may be specified. This function takes a string + * as a parameter and should return a string. The function will be called when the output + * buffer is flushed (sent) or cleaned (with ob_flush(), ob_clean() or similar function) + * or when the output buffer is flushed to the browser at the end of the request. + * When output_callback is called, it will receive the contents of the output buffer + * as its parameter and is expected to return a new output buffer as a result, which will + * be sent to the browser. If the output_callback is not a callable function, this function + * will return FALSE. + * If the callback function has two parameters, the second parameter is filled with + * a bit-field consisting of PHP_OUTPUT_HANDLER_START, PHP_OUTPUT_HANDLER_CONT + * and PHP_OUTPUT_HANDLER_END. + * If output_callback returns FALSE original input is sent to the browser. + * The output_callback parameter may be bypassed by passing a NULL value. + * Return + * Returns TRUE on success or FALSE on failure. + */ +static int vm_builtin_ob_start(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + ph7_vm *pVm = pCtx->pVm; + VmObEntry sOb; + sxi32 rc; + /* Initialize the OB entry */ + PH7_MemObjInit(pCtx->pVm,&sOb.sCallback); + SyBlobInit(&sOb.sOB,&pVm->sAllocator); + if( nArg > 0 && (apArg[0]->iFlags & (MEMOBJ_STRING|MEMOBJ_HASHMAP)) ){ + /* Save the callback name for later invocation */ + PH7_MemObjStore(apArg[0],&sOb.sCallback); + } + /* Push in the stack */ + rc = SySetPut(&pVm->aOB,(const void *)&sOb); + if( rc != SXRET_OK ){ + PH7_MemObjRelease(&sOb.sCallback); + }else{ + ph7_output_consumer *pCons = &pVm->sVmConsumer; + /* Substitute the default VM consumer */ + if( pCons->xConsumer != VmObConsumer ){ + pCons->xDef = pCons->xConsumer; + pCons->pDefData = pCons->pUserData; + /* Install the new consumer */ + pCons->xConsumer = VmObConsumer; + pCons->pUserData = pVm; + } + } + ph7_result_bool(pCtx,rc == SXRET_OK); + return PH7_OK; +} +/* + * Flush Output buffer to the default VM output consumer. + * Refer to the implementation of [ob_flush()] for more + * information. + */ +static sxi32 VmObFlush(ph7_vm *pVm,VmObEntry *pEntry,int bRelease) +{ + SyBlob *pBlob = &pEntry->sOB; + sxi32 rc; + /* Flush contents */ + rc = PH7_OK; + if( SyBlobLength(pBlob) > 0 ){ + /* Call the VM output consumer */ + rc = pVm->sVmConsumer.xDef(SyBlobData(pBlob),SyBlobLength(pBlob),pVm->sVmConsumer.pDefData); + /* Increment VM output counter */ + pVm->nOutputLen += SyBlobLength(pBlob); + if( rc != PH7_ABORT ){ + rc = PH7_OK; + } + } + if( bRelease ){ + VmObRestore(&(*pVm),pEntry); + }else{ + /* Reset the blob */ + SyBlobReset(pBlob); + } + return rc; +} +/* + * void ob_flush(void) + * void flush(void) + * Flush (send) the output buffer. + * Parameter + * None + * Return + * No return value. + */ +static int vm_builtin_ob_flush(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + ph7_vm *pVm = pCtx->pVm; + VmObEntry *pOb; + sxi32 rc; + /* Peek the top most OB entry */ + pOb = (VmObEntry *)SySetPeek(&pVm->aOB); + if( pOb == 0 ){ + /* Empty stack,return immediately */ + SXUNUSED(nArg); /* cc warning */ + SXUNUSED(apArg); + return PH7_OK; + } + /* Flush contents */ + rc = VmObFlush(pVm,pOb,FALSE); + return rc; +} +/* + * bool ob_end_flush(void) + * Flush (send) the output buffer and turn off output buffering. + * Parameter + * None + * Return + * Returns TRUE on success or FALSE on failure. Reasons for failure are first + * that you called the function without an active buffer or that for some reason + * a buffer could not be deleted (possible for special buffer). + */ +static int vm_builtin_ob_end_flush(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + ph7_vm *pVm = pCtx->pVm; + VmObEntry *pOb; + sxi32 rc; + /* Pop the top most OB entry */ + pOb = (VmObEntry *)SySetPop(&pVm->aOB); + if( pOb == 0 ){ + /* Empty stack,return FALSE */ + ph7_result_bool(pCtx,0); + SXUNUSED(nArg); /* cc warning */ + SXUNUSED(apArg); + return PH7_OK; + } + /* Flush contents */ + rc = VmObFlush(pVm,pOb,TRUE); + /* Return true */ + ph7_result_bool(pCtx,1); + return rc; +} +/* + * void ob_implicit_flush([int $flag = true ]) + * ob_implicit_flush() will turn implicit flushing on or off. + * Implicit flushing will result in a flush operation after every + * output call, so that explicit calls to flush() will no longer be needed. + * Parameter + * $flag + * TRUE to turn implicit flushing on, FALSE otherwise. + * Return + * Nothing + */ +static int vm_builtin_ob_implicit_flush(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + /* NOTE: As of this version,this function is a no-op. + * PH7 is smart enough to flush it's internal buffer when appropriate. + */ + SXUNUSED(pCtx); + SXUNUSED(nArg); /* cc warning */ + SXUNUSED(apArg); + return PH7_OK; +} +/* + * array ob_list_handlers(void) + * Lists all output handlers in use. + * Parameter + * None + * Return + * This will return an array with the output handlers in use (if any). + */ +static int vm_builtin_ob_list_handlers(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + ph7_vm *pVm = pCtx->pVm; + ph7_value *pArray; + VmObEntry *aEntry; + ph7_value sVal; + sxu32 n; + if( SySetUsed(&pVm->aOB) < 1 ){ + /* Empty stack,return null */ + ph7_result_null(pCtx); + return PH7_OK; + } + /* Create a new array */ + pArray = ph7_context_new_array(pCtx); + if( pArray == 0 ){ + /* Out of memory,return NULL */ + SXUNUSED(nArg); /* cc warning */ + SXUNUSED(apArg); + ph7_result_null(pCtx); + return PH7_OK; + } + PH7_MemObjInit(pVm,&sVal); + /* Point to the installed OB entries */ + aEntry = (VmObEntry *)SySetBasePtr(&pVm->aOB); + /* Perform the requested operation */ + for( n = 0 ; n < SySetUsed(&pVm->aOB) ; n++ ){ + VmObEntry *pEntry = &aEntry[n]; + /* Extract handler name */ + SyBlobReset(&sVal.sBlob); + if( pEntry->sCallback.iFlags & MEMOBJ_STRING ){ + /* Callback,dup it's name */ + SyBlobDup(&pEntry->sCallback.sBlob,&sVal.sBlob); + }else if( pEntry->sCallback.iFlags & MEMOBJ_HASHMAP ){ + SyBlobAppend(&sVal.sBlob,"Class Method",sizeof("Class Method")-1); + }else{ + SyBlobAppend(&sVal.sBlob,"default output handler",sizeof("default output handler")-1); + } + sVal.iFlags = MEMOBJ_STRING; + /* Perform the insertion */ + ph7_array_add_elem(pArray,0/* Automatic index assign */,&sVal /* Will make it's own copy */); + } + PH7_MemObjRelease(&sVal); + /* Return the freshly created array */ + ph7_result_value(pCtx,pArray); + return PH7_OK; +} +/* + * Section: + * Random numbers/string generators. + * Authors: + * Symisc Systems,devel@symisc.net. + * Copyright (C) Symisc Systems,http://ph7.symisc.net + * Status: + * Stable. + */ +/* + * Generate a random 32-bit unsigned integer. + * PH7 use it's own private PRNG which is based on the one + * used by te SQLite3 library. + */ +PH7_PRIVATE sxu32 PH7_VmRandomNum(ph7_vm *pVm) +{ + sxu32 iNum; + SyRandomness(&pVm->sPrng,(void *)&iNum,sizeof(sxu32)); + return iNum; +} +/* + * Generate a random string (English Alphabet) of length nLen. + * Note that the generated string is NOT null terminated. + * PH7 use it's own private PRNG which is based on the one used + * by te SQLite3 library. + */ +PH7_PRIVATE void PH7_VmRandomString(ph7_vm *pVm,char *zBuf,int nLen) +{ + static const char zBase[] = {"abcdefghijklmnopqrstuvwxyz"}; /* English Alphabet */ + int i; + /* Generate a binary string first */ + SyRandomness(&pVm->sPrng,zBuf,(sxu32)nLen); + /* Turn the binary string into english based alphabet */ + for( i = 0 ; i < nLen ; ++i ){ + zBuf[i] = zBase[zBuf[i] % (sizeof(zBase)-1)]; + } +} +/* + * int rand() + * int mt_rand() + * int rand(int $min,int $max) + * int mt_rand(int $min,int $max) + * Generate a random (unsigned 32-bit) integer. + * Parameter + * $min + * The lowest value to return (default: 0) + * $max + * The highest value to return (default: getrandmax()) + * Return + * A pseudo random value between min (or 0) and max (or getrandmax(), inclusive). + * Note: + * PH7 use it's own private PRNG which is based on the one used + * by te SQLite3 library. + */ +static int vm_builtin_rand(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + sxu32 iNum; + /* Generate the random number */ + iNum = PH7_VmRandomNum(pCtx->pVm); + if( nArg > 1 ){ + sxu32 iMin,iMax; + iMin = (sxu32)ph7_value_to_int(apArg[0]); + iMax = (sxu32)ph7_value_to_int(apArg[1]); + if( iMin < iMax ){ + sxu32 iDiv = iMax+1-iMin; + if( iDiv > 0 ){ + iNum = (iNum % iDiv)+iMin; + } + }else if(iMax > 0 ){ + iNum %= iMax; + } + } + /* Return the number */ + ph7_result_int64(pCtx,(ph7_int64)iNum); + return SXRET_OK; +} +/* + * int getrandmax(void) + * int mt_getrandmax(void) + * int rc4_getrandmax(void) + * Show largest possible random value + * Return + * The largest possible random value returned by rand() which is in + * this implementation 0xFFFFFFFF. + * Note: + * PH7 use it's own private PRNG which is based on the one used + * by te SQLite3 library. + */ +static int vm_builtin_getrandmax(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + SXUNUSED(nArg); /* cc warning */ + SXUNUSED(apArg); + ph7_result_int64(pCtx,SXU32_HIGH); + return SXRET_OK; +} +/* + * string rand_str() + * string rand_str(int $len) + * Generate a random string (English alphabet). + * Parameter + * $len + * Length of the desired string (default: 16,Min: 1,Max: 1024) + * Return + * A pseudo random string. + * Note: + * PH7 use it's own private PRNG which is based on the one used + * by te SQLite3 library. + * This function is a symisc extension. + */ +static int vm_builtin_rand_str(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + char zString[1024]; + int iLen = 0x10; + if( nArg > 0 ){ + /* Get the desired length */ + iLen = ph7_value_to_int(apArg[0]); + if( iLen < 1 || iLen > 1024 ){ + /* Default length */ + iLen = 0x10; + } + } + /* Generate the random string */ + PH7_VmRandomString(pCtx->pVm,zString,iLen); + /* Return the generated string */ + ph7_result_string(pCtx,zString,iLen); /* Will make it's own copy */ + return SXRET_OK; +} +#ifndef PH7_DISABLE_BUILTIN_FUNC +#if !defined(PH7_DISABLE_HASH_FUNC) +/* Unique ID private data */ +struct unique_id_data +{ + ph7_context *pCtx; /* Call context */ + int entropy; /* TRUE if the more_entropy flag is set */ +}; +/* + * Binary to hex consumer callback. + * This callback is the default consumer used by [uniqid()] function + * defined below. + */ +static int HexConsumer(const void *pData,unsigned int nLen,void *pUserData) +{ + struct unique_id_data *pUniq = (struct unique_id_data *)pUserData; + sxu32 nBuflen; + /* Extract result buffer length */ + nBuflen = ph7_context_result_buf_length(pUniq->pCtx); + if( nBuflen > 12 && !pUniq->entropy ){ + /* + * If the more_entropy flag is not set,then the returned + * string will be 13 characters long + */ + return SXERR_ABORT; + } + if( nBuflen > 22 ){ + return SXERR_ABORT; + } + /* Safely Consume the hex stream */ + ph7_result_string(pUniq->pCtx,(const char *)pData,(int)nLen); + return SXRET_OK; +} +/* + * string uniqid([string $prefix = "" [, bool $more_entropy = false]]) + * Generate a unique ID + * Parameter + * $prefix + * Append this prefix to the generated unique ID. + * With an empty prefix, the returned string will be 13 characters long. + * If more_entropy is TRUE, it will be 23 characters. + * $more_entropy + * If set to TRUE, uniqid() will add additional entropy which increases the likelihood + * that the result will be unique. + * Return + * Returns the unique identifier, as a string. + */ +static int vm_builtin_uniqid(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + struct unique_id_data sUniq; + unsigned char zDigest[20]; + ph7_vm *pVm = pCtx->pVm; + const char *zPrefix; + SHA1Context sCtx; + char zRandom[7]; + int nPrefix; + int entropy; + /* Generate a random string first */ + PH7_VmRandomString(pVm,zRandom,(int)sizeof(zRandom)); + /* Initialize fields */ + zPrefix = 0; + nPrefix = 0; + entropy = 0; + if( nArg > 0 ){ + /* Append this prefix to the generated unqiue ID */ + zPrefix = ph7_value_to_string(apArg[0],&nPrefix); + if( nArg > 1 ){ + entropy = ph7_value_to_bool(apArg[1]); + } + } + SHA1Init(&sCtx); + /* Generate the random ID */ + if( nPrefix > 0 ){ + SHA1Update(&sCtx,(const unsigned char *)zPrefix,(unsigned int)nPrefix); + } + /* Append the random ID */ + SHA1Update(&sCtx,(const unsigned char *)&pVm->unique_id,sizeof(int)); + /* Append the random string */ + SHA1Update(&sCtx,(const unsigned char *)zRandom,sizeof(zRandom)); + /* Increment the number */ + pVm->unique_id++; + SHA1Final(&sCtx,zDigest); + /* Hexify the digest */ + sUniq.pCtx = pCtx; + sUniq.entropy = entropy; + SyBinToHexConsumer((const void *)zDigest,sizeof(zDigest),HexConsumer,&sUniq); + /* All done */ + return PH7_OK; +} +#endif /* PH7_DISABLE_HASH_FUNC */ +#endif /* PH7_DISABLE_BUILTIN_FUNC */ +/* + * Section: + * Language construct implementation as foreign functions. + * Authors: + * Symisc Systems,devel@symisc.net. + * Copyright (C) Symisc Systems,http://ph7.symisc.net + * Status: + * Stable. + */ +/* + * void echo($string...) + * Output one or more messages. + * Parameters + * $string + * Message to output. + * Return + * NULL. + */ +static int vm_builtin_echo(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + const char *zData; + int nDataLen = 0; + ph7_vm *pVm; + int i,rc; + /* Point to the target VM */ + pVm = pCtx->pVm; + /* Output */ + for( i = 0 ; i < nArg ; ++i ){ + zData = ph7_value_to_string(apArg[i],&nDataLen); + if( nDataLen > 0 ){ + rc = pVm->sVmConsumer.xConsumer((const void *)zData,(unsigned int)nDataLen,pVm->sVmConsumer.pUserData); + if( pVm->sVmConsumer.xConsumer != VmObConsumer ){ + /* Increment output length */ + pVm->nOutputLen += nDataLen; + } + if( rc == SXERR_ABORT ){ + /* Output consumer callback request an operation abort */ + return PH7_ABORT; + } + } + } + return SXRET_OK; +} +/* + * int print($string...) + * Output one or more messages. + * Parameters + * $string + * Message to output. + * Return + * 1 always. + */ +static int vm_builtin_print(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + const char *zData; + int nDataLen = 0; + ph7_vm *pVm; + int i,rc; + /* Point to the target VM */ + pVm = pCtx->pVm; + /* Output */ + for( i = 0 ; i < nArg ; ++i ){ + zData = ph7_value_to_string(apArg[i],&nDataLen); + if( nDataLen > 0 ){ + rc = pVm->sVmConsumer.xConsumer((const void *)zData,(unsigned int)nDataLen,pVm->sVmConsumer.pUserData); + if( pVm->sVmConsumer.xConsumer != VmObConsumer ){ + /* Increment output length */ + pVm->nOutputLen += nDataLen; + } + if( rc == SXERR_ABORT ){ + /* Output consumer callback request an operation abort */ + return PH7_ABORT; + } + } + } + /* Return 1 */ + ph7_result_int(pCtx,1); + return SXRET_OK; +} +/* + * void exit(string $msg) + * void exit(int $status) + * void die(string $ms) + * void die(int $status) + * Output a message and terminate program execution. + * Parameter + * If status is a string, this function prints the status just before exiting. + * If status is an integer, that value will be used as the exit status + * and not printed + * Return + * NULL + */ +static int vm_builtin_exit(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + if( nArg > 0 ){ + if( ph7_value_is_string(apArg[0]) ){ + const char *zData; + int iLen = 0; + /* Print exit message */ + zData = ph7_value_to_string(apArg[0],&iLen); + ph7_context_output(pCtx,zData,iLen); + }else if(ph7_value_is_int(apArg[0]) ){ + sxi32 iExitStatus; + /* Record exit status code */ + iExitStatus = ph7_value_to_int(apArg[0]); + pCtx->pVm->iExitStatus = iExitStatus; + } + } + /* Abort processing immediately */ + return PH7_ABORT; +} +/* + * bool isset($var,...) + * Finds out whether a variable is set. + * Parameters + * One or more variable to check. + * Return + * 1 if var exists and has value other than NULL, 0 otherwise. + */ +static int vm_builtin_isset(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + ph7_value *pObj; + int res = 0; + int i; + if( nArg < 1 ){ + /* Missing arguments,return false */ + ph7_result_bool(pCtx,res); + return SXRET_OK; + } + /* Iterate over available arguments */ + for( i = 0 ; i < nArg ; ++i ){ + pObj = apArg[i]; + if( pObj->nIdx == SXU32_HIGH ){ + if( (pObj->iFlags & MEMOBJ_NULL) == 0 ){ + /* Not so fatal,Throw a warning */ + ph7_context_throw_error(pCtx,PH7_CTX_WARNING,"Expecting a variable not a constant"); + } + } + res = (pObj->iFlags & MEMOBJ_NULL) ? 0 : 1; + if( !res ){ + /* Variable not set,return FALSE */ + ph7_result_bool(pCtx,0); + return SXRET_OK; + } + } + /* All given variable are set,return TRUE */ + ph7_result_bool(pCtx,1); + return SXRET_OK; +} +/* + * Unset a memory object [i.e: a ph7_value],remove it from the current + * frame,the reference table and discard it's contents. + * This function never fail and always return SXRET_OK. + */ +PH7_PRIVATE sxi32 PH7_VmUnsetMemObj(ph7_vm *pVm,sxu32 nObjIdx,int bForce) +{ + ph7_value *pObj; + VmRefObj *pRef; + pObj = (ph7_value *)SySetAt(&pVm->aMemObj,nObjIdx); + if( pObj ){ + /* Release the object */ + PH7_MemObjRelease(pObj); + } + /* Remove old reference links */ + pRef = VmRefObjExtract(&(*pVm),nObjIdx); + if( pRef ){ + sxi32 iFlags = pRef->iFlags; + /* Unlink from the reference table */ + VmRefObjUnlink(&(*pVm),pRef); + if( (bForce == TRUE) || (iFlags & VM_REF_IDX_KEEP) == 0 ){ + VmSlot sFree; + /* Restore to the free list */ + sFree.nIdx = nObjIdx; + sFree.pUserData = 0; + SySetPut(&pVm->aFreeObj,(const void *)&sFree); + } + } + return SXRET_OK; +} +/* + * void unset($var,...) + * Unset one or more given variable. + * Parameters + * One or more variable to unset. + * Return + * Nothing. + */ +static int vm_builtin_unset(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + ph7_value *pObj; + ph7_vm *pVm; + int i; + /* Point to the target VM */ + pVm = pCtx->pVm; + /* Iterate and unset */ + for( i = 0 ; i < nArg ; ++i ){ + pObj = apArg[i]; + if( pObj->nIdx == SXU32_HIGH ){ + if( (pObj->iFlags & MEMOBJ_NULL) == 0 ){ + /* Throw an error */ + ph7_context_throw_error(pCtx,PH7_CTX_ERR,"Expecting a variable not a constant"); + } + }else{ + sxu32 nIdx = pObj->nIdx; + /* TICKET 1433-35: Protect the $GLOBALS array from deletion */ + if( nIdx != pVm->nGlobalIdx ){ + PH7_VmUnsetMemObj(&(*pVm),nIdx,FALSE); + } + } + } + return SXRET_OK; +} +/* + * Hash walker callback used by the [get_defined_vars()] function. + */ +static sxi32 VmHashVarWalker(SyHashEntry *pEntry,void *pUserData) +{ + ph7_value *pArray = (ph7_value *)pUserData; + ph7_vm *pVm = pArray->pVm; + ph7_value *pObj; + sxu32 nIdx; + /* Extract the memory object */ + nIdx = SX_PTR_TO_INT(pEntry->pUserData); + pObj = (ph7_value *)SySetAt(&pVm->aMemObj,nIdx); + if( pObj ){ + if( (pObj->iFlags & MEMOBJ_HASHMAP) == 0 || (ph7_hashmap *)pObj->x.pOther != pVm->pGlobal ){ + if( pEntry->nKeyLen > 0 ){ + SyString sName; + ph7_value sKey; + /* Perform the insertion */ + SyStringInitFromBuf(&sName,pEntry->pKey,pEntry->nKeyLen); + PH7_MemObjInitFromString(pVm,&sKey,&sName); + ph7_array_add_elem(pArray,&sKey/*Will make it's own copy*/,pObj); + PH7_MemObjRelease(&sKey); + } + } + } + return SXRET_OK; +} +/* + * array get_defined_vars(void) + * Returns an array of all defined variables. + * Parameter + * None + * Return + * An array with all the variables defined in the current scope. + */ +static int vm_builtin_get_defined_vars(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + ph7_vm *pVm = pCtx->pVm; + ph7_value *pArray; + /* Create a new array */ + pArray = ph7_context_new_array(pCtx); + if( pArray == 0 ){ + SXUNUSED(nArg); /* cc warning */ + SXUNUSED(apArg); + /* Return NULL */ + ph7_result_null(pCtx); + return SXRET_OK; + } + /* Superglobals first */ + SyHashForEach(&pVm->hSuper,VmHashVarWalker,pArray); + /* Then variable defined in the current frame */ + SyHashForEach(&pVm->pFrame->hVar,VmHashVarWalker,pArray); + /* Finally,return the created array */ + ph7_result_value(pCtx,pArray); + return SXRET_OK; +} +/* + * bool gettype($var) + * Get the type of a variable + * Parameters + * $var + * The variable being type checked. + * Return + * String representation of the given variable type. + */ +static int vm_builtin_gettype(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + const char *zType = "Empty"; + if( nArg > 0 ){ + zType = PH7_MemObjTypeDump(apArg[0]); + } + /* Return the variable type */ + ph7_result_string(pCtx,zType,-1/*Compute length automatically*/); + return SXRET_OK; +} +/* + * string get_resource_type(resource $handle) + * This function gets the type of the given resource. + * Parameters + * $handle + * The evaluated resource handle. + * Return + * If the given handle is a resource, this function will return a string + * representing its type. If the type is not identified by this function + * the return value will be the string Unknown. + * This function will return FALSE and generate an error if handle + * is not a resource. + */ +static int vm_builtin_get_resource_type(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + if( nArg < 1 || !ph7_value_is_resource(apArg[0]) ){ + /* Missing/Invalid arguments,return FALSE*/ + ph7_result_bool(pCtx,0); + return PH7_OK; + } + ph7_result_string_format(pCtx,"resID_%#x",apArg[0]->x.pOther); + return SXRET_OK; +} +/* + * void var_dump(expression,....) + * var_dump � Dumps information about a variable + * Parameters + * One or more expression to dump. + * Returns + * Nothing. + */ +static int vm_builtin_var_dump(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + SyBlob sDump; /* Generated dump is stored here */ + int i; + SyBlobInit(&sDump,&pCtx->pVm->sAllocator); + /* Dump one or more expressions */ + for( i = 0 ; i < nArg ; i++ ){ + ph7_value *pObj = apArg[i]; + /* Reset the working buffer */ + SyBlobReset(&sDump); + /* Dump the given expression */ + PH7_MemObjDump(&sDump,pObj,TRUE,0,0,0); + /* Output */ + if( SyBlobLength(&sDump) > 0 ){ + ph7_context_output(pCtx,(const char *)SyBlobData(&sDump),(int)SyBlobLength(&sDump)); + } + } + /* Release the working buffer */ + SyBlobRelease(&sDump); + return SXRET_OK; +} +/* + * string/bool print_r(expression,[bool $return = FALSE]) + * print-r - Prints human-readable information about a variable + * Parameters + * expression: Expression to dump + * return : If you would like to capture the output of print_r() use + * the return parameter. When this parameter is set to TRUE + * print_r() will return the information rather than print it. + * Return + * When the return parameter is TRUE, this function will return a string. + * Otherwise, the return value is TRUE. + */ +static int vm_builtin_print_r(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + int ret_string = 0; + SyBlob sDump; + if( nArg < 1 ){ + /* Nothing to output,return FALSE */ + ph7_result_bool(pCtx,0); + return SXRET_OK; + } + SyBlobInit(&sDump,&pCtx->pVm->sAllocator); + if ( nArg > 1 ){ + /* Where to redirect output */ + ret_string = ph7_value_to_bool(apArg[1]); + } + /* Generate dump */ + PH7_MemObjDump(&sDump,apArg[0],FALSE,0,0,0); + if( !ret_string ){ + /* Output dump */ + ph7_context_output(pCtx,(const char *)SyBlobData(&sDump),(int)SyBlobLength(&sDump)); + /* Return true */ + ph7_result_bool(pCtx,1); + }else{ + /* Generated dump as return value */ + ph7_result_string(pCtx,(const char *)SyBlobData(&sDump),(int)SyBlobLength(&sDump)); + } + /* Release the working buffer */ + SyBlobRelease(&sDump); + return SXRET_OK; +} +/* + * string/null var_export(expression,[bool $return = FALSE]) + * Same job as print_r. (see coment above) + */ +static int vm_builtin_var_export(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + int ret_string = 0; + SyBlob sDump; /* Dump is stored in this BLOB */ + if( nArg < 1 ){ + /* Nothing to output,return FALSE */ + ph7_result_bool(pCtx,0); + return SXRET_OK; + } + SyBlobInit(&sDump,&pCtx->pVm->sAllocator); + if ( nArg > 1 ){ + /* Where to redirect output */ + ret_string = ph7_value_to_bool(apArg[1]); + } + /* Generate dump */ + PH7_MemObjDump(&sDump,apArg[0],FALSE,0,0,0); + if( !ret_string ){ + /* Output dump */ + ph7_context_output(pCtx,(const char *)SyBlobData(&sDump),(int)SyBlobLength(&sDump)); + /* Return NULL */ + ph7_result_null(pCtx); + }else{ + /* Generated dump as return value */ + ph7_result_string(pCtx,(const char *)SyBlobData(&sDump),(int)SyBlobLength(&sDump)); + } + /* Release the working buffer */ + SyBlobRelease(&sDump); + return SXRET_OK; +} +/* + * int/bool assert_options(int $what [, mixed $value ]) + * Set/get the various assert flags. + * Parameter + * $what + * ASSERT_ACTIVE Enable assert() evaluation + * ASSERT_WARNING Issue a warning for each failed assertion + * ASSERT_BAIL Terminate execution on failed assertions + * ASSERT_QUIET_EVAL Not used + * ASSERT_CALLBACK Callback to call on failed assertions + * $value + * An optional new value for the option. + * Return + * Old setting on success or FALSE on failure. + */ +static int vm_builtin_assert_options(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + ph7_vm *pVm = pCtx->pVm; + int iOld,iNew,iValue; + if( nArg < 1 || !ph7_value_is_int(apArg[0]) ){ + /* Missing/Invalid arguments,return FALSE */ + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Save old assertion flags */ + iOld = pVm->iAssertFlags; + /* Extract the new flags */ + iNew = ph7_value_to_int(apArg[0]); + if( iNew == PH7_ASSERT_DISABLE ){ + pVm->iAssertFlags &= ~PH7_ASSERT_DISABLE; + if( nArg > 1 ){ + iValue = !ph7_value_to_bool(apArg[1]); + if( iValue ){ + /* Disable assertion */ + pVm->iAssertFlags |= PH7_ASSERT_DISABLE; + } + } + }else if( iNew == PH7_ASSERT_WARNING ){ + pVm->iAssertFlags &= ~PH7_ASSERT_WARNING; + if( nArg > 1 ){ + iValue = ph7_value_to_bool(apArg[1]); + if( iValue ){ + /* Issue a warning for each failed assertion */ + pVm->iAssertFlags |= PH7_ASSERT_WARNING; + } + } + }else if( iNew == PH7_ASSERT_BAIL ){ + pVm->iAssertFlags &= ~PH7_ASSERT_BAIL; + if( nArg > 1 ){ + iValue = ph7_value_to_bool(apArg[1]); + if( iValue ){ + /* Terminate execution on failed assertions */ + pVm->iAssertFlags |= PH7_ASSERT_BAIL; + } + } + }else if( iNew == PH7_ASSERT_CALLBACK ){ + pVm->iAssertFlags &= ~PH7_ASSERT_CALLBACK; + if( nArg > 1 && ph7_value_is_callable(apArg[1]) ){ + /* Callback to call on failed assertions */ + PH7_MemObjStore(apArg[1],&pVm->sAssertCallback); + pVm->iAssertFlags |= PH7_ASSERT_CALLBACK; + } + } + /* Return the old flags */ + ph7_result_int(pCtx,iOld); + return PH7_OK; +} +/* + * bool assert(mixed $assertion) + * Checks if assertion is FALSE. + * Parameter + * $assertion + * The assertion to test. + * Return + * FALSE if the assertion is false, TRUE otherwise. + */ +static int vm_builtin_assert(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + ph7_vm *pVm = pCtx->pVm; + ph7_value *pAssert; + int iFlags,iResult; + if( nArg < 1 ){ + /* Missing arguments,return FALSE */ + ph7_result_bool(pCtx,0); + return PH7_OK; + } + iFlags = pVm->iAssertFlags; + if( iFlags & PH7_ASSERT_DISABLE ){ + /* Assertion is disabled,return FALSE */ + ph7_result_bool(pCtx,0); + return PH7_OK; + } + pAssert = apArg[0]; + iResult = 1; /* cc warning */ + if( pAssert->iFlags & MEMOBJ_STRING ){ + SyString sChunk; + SyStringInitFromBuf(&sChunk,SyBlobData(&pAssert->sBlob),SyBlobLength(&pAssert->sBlob)); + if( sChunk.nByte > 0 ){ + VmEvalChunk(pVm,pCtx,&sChunk,PH7_PHP_ONLY|PH7_PHP_EXPR,FALSE); + /* Extract evaluation result */ + iResult = ph7_value_to_bool(pCtx->pRet); + }else{ + iResult = 0; + } + }else{ + /* Perform a boolean cast */ + iResult = ph7_value_to_bool(apArg[0]); + } + if( !iResult ){ + /* Assertion failed */ + if( iFlags & PH7_ASSERT_CALLBACK ){ + static const SyString sFileName = { ":Memory", sizeof(":Memory") - 1}; + ph7_value sFile,sLine; + ph7_value *apCbArg[3]; + SyString *pFile; + /* Extract the processed script */ + pFile = (SyString *)SySetPeek(&pVm->aFiles); + if( pFile == 0 ){ + pFile = (SyString *)&sFileName; + } + /* Invoke the callback */ + PH7_MemObjInitFromString(pVm,&sFile,pFile); + PH7_MemObjInitFromInt(pVm,&sLine,0); + apCbArg[0] = &sFile; + apCbArg[1] = &sLine; + apCbArg[2] = pAssert; + PH7_VmCallUserFunction(pVm,&pVm->sAssertCallback,3,apCbArg,0); + /* Clean-up the mess left behind */ + PH7_MemObjRelease(&sFile); + PH7_MemObjRelease(&sLine); + } + if( iFlags & PH7_ASSERT_WARNING ){ + /* Emit a warning */ + ph7_context_throw_error(pCtx,PH7_CTX_WARNING,"Assertion failed"); + } + if( iFlags & PH7_ASSERT_BAIL ){ + /* Abort VM execution immediately */ + return PH7_ABORT; + } + } + /* Assertion result */ + ph7_result_bool(pCtx,iResult); + return PH7_OK; +} +/* + * Section: + * Error reporting functions. + * Authors: + * Symisc Systems,devel@symisc.net. + * Copyright (C) Symisc Systems,http://ph7.symisc.net + * Status: + * Stable. + */ +/* + * bool trigger_error(string $error_msg[,int $error_type = E_USER_NOTICE ]) + * Generates a user-level error/warning/notice message. + * Parameters + * $error_msg + * The designated error message for this error. It's limited to 1024 characters + * in length. Any additional characters beyond 1024 will be truncated. + * $error_type + * The designated error type for this error. It only works with the E_USER family + * of constants, and will default to E_USER_NOTICE. + * Return + * This function returns FALSE if wrong error_type is specified, TRUE otherwise. + */ +static int vm_builtin_trigger_error(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + int nErr = PH7_CTX_NOTICE; + int rc = PH7_OK; + if( nArg > 0 ){ + const char *zErr; + int nLen; + /* Extract the error message */ + zErr = ph7_value_to_string(apArg[0],&nLen); + if( nArg > 1 ){ + /* Extract the error type */ + nErr = ph7_value_to_int(apArg[1]); + switch( nErr ){ + case 1: /* E_ERROR */ + case 16: /* E_CORE_ERROR */ + case 64: /* E_COMPILE_ERROR */ + case 256: /* E_USER_ERROR */ + nErr = PH7_CTX_ERR; + rc = PH7_ABORT; /* Abort processing immediately */ + break; + case 2: /* E_WARNING */ + case 32: /* E_CORE_WARNING */ + case 123: /* E_COMPILE_WARNING */ + case 512: /* E_USER_WARNING */ + nErr = PH7_CTX_WARNING; + break; + default: + nErr = PH7_CTX_NOTICE; + break; + } + } + /* Report error */ + ph7_context_throw_error_format(pCtx,nErr,"%.*s",nLen,zErr); + /* Return true */ + ph7_result_bool(pCtx,1); + }else{ + /* Missing arguments,return FALSE */ + ph7_result_bool(pCtx,0); + } + return rc; +} +/* + * int error_reporting([int $level]) + * Sets which PHP errors are reported. + * Parameters + * $level + * The new error_reporting level. It takes on either a bitmask, or named constants. + * Using named constants is strongly encouraged to ensure compatibility for future versions. + * As error levels are added, the range of integers increases, so older integer-based error + * levels will not always behave as expected. + * The available error level constants and the actual meanings of these error levels are described + * in the predefined constants. + * Return + * Returns the old error_reporting level or the current level if no level + * parameter is given. + */ +static int vm_builtin_error_reporting(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + ph7_vm *pVm = pCtx->pVm; + int nOld; + /* Extract the old reporting level */ + nOld = pVm->bErrReport ? 32767 /* E_ALL */ : 0; + if( nArg > 0 ){ + int nNew; + /* Extract the desired error reporting level */ + nNew = ph7_value_to_int(apArg[0]); + if( !nNew ){ + /* Do not report errors at all */ + pVm->bErrReport = 0; + }else{ + /* Report all errors */ + pVm->bErrReport = 1; + } + } + /* Return the old level */ + ph7_result_int(pCtx,nOld); + return PH7_OK; +} +/* + * bool error_log(string $message[,int $message_type = 0 [,string $destination[,string $extra_headers]]]) + * Send an error message somewhere. + * Parameter + * $message + * The error message that should be logged. + * $message_type + * Says where the error should go. The possible message types are as follows: + * 0 message is sent to PHP's system logger, using the Operating System's system logging mechanism + * or a file, depending on what the error_log configuration directive is set to. + * This is the default option. + * 1 message is sent by email to the address in the destination parameter. + * This is the only message type where the fourth parameter, extra_headers is used. + * 2 No longer an option. + * 3 message is appended to the file destination. A newline is not automatically added + * to the end of the message string. + * 4 message is sent directly to the SAPI logging handler. + * $destination + * The destination. Its meaning depends on the message_type parameter as described above. + * $extra_headers + * The extra headers. It's used when the message_type parameter is set to 1 + * Return + * TRUE on success or FALSE on failure. + * NOTE: + * Actually,PH7 does not care about the given parameters,all this function does + * is to invoke any user callback registered using the PH7_VM_CONFIG_ERR_LOG_HANDLER + * configuration directive (refer to the official documentation for more information). + * Otherwise this function is no-op. + */ +static int vm_builtin_error_log(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + const char *zMessage,*zDest,*zHeader; + ph7_vm *pVm = pCtx->pVm; + int iType = 0; + if( nArg < 1 ){ + /* Missing log message,return FALSE */ + ph7_result_bool(pCtx,0); + return PH7_OK; + } + if( pVm->xErrLog ){ + /* Invoke the user callback */ + zMessage = ph7_value_to_string(apArg[0],0); + zDest = zHeader = ""; /* Empty string */ + if( nArg > 1 ){ + iType = ph7_value_to_int(apArg[1]); + if( nArg > 2 ){ + zDest = ph7_value_to_string(apArg[2],0); + if( nArg > 3 ){ + zHeader = ph7_value_to_string(apArg[3],0); + } + } + } + pVm->xErrLog(zMessage,iType,zDest,zHeader); + } + /* Retun TRUE */ + ph7_result_bool(pCtx,1); + return PH7_OK; +} +/* + * bool restore_exception_handler(void) + * Restores the previously defined exception handler function. + * Parameter + * None + * Return + * TRUE if the exception handler is restored.FALSE otherwise + */ +static int vm_builtin_restore_exception_handler(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + ph7_vm *pVm = pCtx->pVm; + ph7_value *pOld,*pNew; + /* Point to the old and the new handler */ + pOld = &pVm->aExceptionCB[0]; + pNew = &pVm->aExceptionCB[1]; + if( pOld->iFlags & MEMOBJ_NULL ){ + SXUNUSED(nArg); /* cc warning */ + SXUNUSED(apArg); + /* No installed handler,return FALSE */ + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Copy the old handler */ + PH7_MemObjStore(pOld,pNew); + PH7_MemObjRelease(pOld); + /* Return TRUE */ + ph7_result_bool(pCtx,1); + return PH7_OK; +} +/* + * callable set_exception_handler(callable $exception_handler) + * Sets a user-defined exception handler function. + * Sets the default exception handler if an exception is not caught within a try/catch block. + * NOTE + * Execution will NOT stop after the exception_handler calls for example die/exit unlike + * the satndard PHP engine. + * Parameters + * $exception_handler + * Name of the function to be called when an uncaught exception occurs. + * This handler function needs to accept one parameter, which will be the exception object + * that was thrown. + * Note: + * NULL may be passed instead, to reset this handler to its default state. + * Return + * Returns the name of the previously defined exception handler, or NULL on error. + * If no previous handler was defined, NULL is also returned. If NULL is passed + * resetting the handler to its default state, TRUE is returned. + */ +static int vm_builtin_set_exception_handler(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + ph7_vm *pVm = pCtx->pVm; + ph7_value *pOld,*pNew; + /* Point to the old and the new handler */ + pOld = &pVm->aExceptionCB[0]; + pNew = &pVm->aExceptionCB[1]; + /* Return the old handler */ + ph7_result_value(pCtx,pOld); /* Will make it's own copy */ + if( nArg > 0 ){ + if( !ph7_value_is_callable(apArg[0])) { + /* Not callable,return TRUE (As requested by the PHP specification) */ + PH7_MemObjRelease(pNew); + ph7_result_bool(pCtx,1); + }else{ + PH7_MemObjStore(pNew,pOld); + /* Install the new handler */ + PH7_MemObjStore(apArg[0],pNew); + } + } + return PH7_OK; +} +/* + * bool restore_error_handler(void) + * THIS FUNCTION IS A NO-OP IN THE CURRENT RELEASE OF THE PH7 ENGINE. + * Parameters: + * None. + * Return + * Always TRUE. + */ +static int vm_builtin_restore_error_handler(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + ph7_vm *pVm = pCtx->pVm; + ph7_value *pOld,*pNew; + /* Point to the old and the new handler */ + pOld = &pVm->aErrCB[0]; + pNew = &pVm->aErrCB[1]; + if( pOld->iFlags & MEMOBJ_NULL ){ + SXUNUSED(nArg); /* cc warning */ + SXUNUSED(apArg); + /* No installed callback,return FALSE */ + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Copy the old callback */ + PH7_MemObjStore(pOld,pNew); + PH7_MemObjRelease(pOld); + /* Return TRUE */ + ph7_result_bool(pCtx,1); + return PH7_OK; +} +/* + * value set_error_handler(callable $error_handler) + * +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + * THIS FUNCTION IS DISABLED IN THE CURRENT RELEASE OF THE PH7 ENGINE. + * +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + * Sets a user-defined error handler function. + * This function can be used for defining your own way of handling errors during + * runtime, for example in applications in which you need to do cleanup of data/files + * when a critical error happens, or when you need to trigger an error under certain + * conditions (using trigger_error()). + * Parameters + * $error_handler + * The user function needs to accept two parameters: the error code, and a string + * describing the error. + * Then there are three optional parameters that may be supplied: the filename in which + * the error occurred, the line number in which the error occurred, and the context in which + * the error occurred (an array that points to the active symbol table at the point the error occurred). + * The function can be shown as: + * handler ( int $errno , string $errstr [, string $errfile]) + * errno + * The first parameter, errno, contains the level of the error raised, as an integer. + * errstr + * The second parameter, errstr, contains the error message, as a string. + * errfile + * The third parameter is optional, errfile, which contains the filename that the error + * was raised in, as a string. + * Note: + * NULL may be passed instead, to reset this handler to its default state. + * Return + * Returns the name of the previously defined error handler, or NULL on error. + * If no previous handler was defined, NULL is also returned. If NULL is passed + * resetting the handler to its default state, TRUE is returned. + */ +static int vm_builtin_set_error_handler(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + ph7_vm *pVm = pCtx->pVm; + ph7_value *pOld,*pNew; + /* Point to the old and the new handler */ + pOld = &pVm->aErrCB[0]; + pNew = &pVm->aErrCB[1]; + /* Return the old handler */ + ph7_result_value(pCtx,pOld); /* Will make it's own copy */ + if( nArg > 0 ){ + if( !ph7_value_is_callable(apArg[0])) { + /* Not callable,return TRUE (As requested by the PHP specification) */ + PH7_MemObjRelease(pNew); + ph7_result_bool(pCtx,1); + }else{ + PH7_MemObjStore(pNew,pOld); + /* Install the new handler */ + PH7_MemObjStore(apArg[0],pNew); + } + } + ph7_context_throw_error_format(pCtx,PH7_CTX_WARNING, + "This function is disabled in the current release of the PH7(%s) engine", + ph7_lib_version() + ); + return PH7_OK; +} +/* + * array debug_backtrace([ int $options = DEBUG_BACKTRACE_PROVIDE_OBJECT [, int $limit = 0 ]] ) + * Generates a backtrace. + * Paramaeter + * $options + * DEBUG_BACKTRACE_PROVIDE_OBJECT: Whether or not to populate the "object" index. + * DEBUG_BACKTRACE_IGNORE_ARGS Whether or not to omit the "args" index, and thus + * all the function/method arguments, to save memory. + * $limit + * (Not Used) + * Return + * An array.The possible returned elements are as follows: + * Possible returned elements from debug_backtrace() + * Name Type Description + * ------ ------ ----------- + * function string The current function name. See also __FUNCTION__. + * line integer The current line number. See also __LINE__. + * file string The current file name. See also __FILE__. + * class string The current class name. See also __CLASS__ + * object object The current object. + * args array If inside a function, this lists the functions arguments. + * If inside an included file, this lists the included file name(s). + */ +static int vm_builtin_debug_backtrace(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + ph7_vm *pVm = pCtx->pVm; + ph7_value *pArray; + ph7_class *pClass; + ph7_value *pValue; + SyString *pFile; + /* Create a new array */ + pArray = ph7_context_new_array(pCtx); + pValue = ph7_context_new_scalar(pCtx); + if( pArray == 0 || pValue == 0 ){ + /* Out of memory,return NULL */ + ph7_context_throw_error(pCtx,PH7_CTX_ERR,"PH7 is running out of memory"); + ph7_result_null(pCtx); + SXUNUSED(nArg); /* cc warning */ + SXUNUSED(apArg); + return PH7_OK; + } + /* Dump running function name and it's arguments */ + if( pVm->pFrame->pParent ){ + VmFrame *pFrame = pVm->pFrame; + ph7_vm_func *pFunc; + ph7_value *pArg; + while( pFrame->pParent && (pFrame->iFlags & VM_FRAME_EXCEPTION) ){ + /* Safely ignore the exception frame */ + pFrame = pFrame->pParent; + } + pFunc = (ph7_vm_func *)pFrame->pUserData; + if( pFrame->pParent && pFunc ){ + ph7_value_string(pValue,pFunc->sName.zString,(int)pFunc->sName.nByte); + ph7_array_add_strkey_elem(pArray,"function",pValue); + ph7_value_reset_string_cursor(pValue); + } + /* Function arguments */ + pArg = ph7_context_new_array(pCtx); + if( pArg ){ + ph7_value *pObj; + VmSlot *aSlot; + sxu32 n; + /* Start filling the array with the given arguments */ + aSlot = (VmSlot *)SySetBasePtr(&pFrame->sArg); + for( n = 0; n < SySetUsed(&pFrame->sArg) ; n++ ){ + pObj = (ph7_value *)SySetAt(&pCtx->pVm->aMemObj,aSlot[n].nIdx); + if( pObj ){ + ph7_array_add_elem(pArg,0/* Automatic index assign*/,pObj); + } + } + /* Save the array */ + ph7_array_add_strkey_elem(pArray,"args",pArg); + } + } + ph7_value_int(pValue,1); + /* Append the current line (which is always 1 since PH7 does not track + * line numbers at run-time. ) + */ + ph7_array_add_strkey_elem(pArray,"line",pValue); + /* Current processed script */ + pFile = (SyString *)SySetPeek(&pVm->aFiles); + if( pFile ){ + ph7_value_string(pValue,pFile->zString,(int)pFile->nByte); + ph7_array_add_strkey_elem(pArray,"file",pValue); + ph7_value_reset_string_cursor(pValue); + } + /* Top class */ + pClass = PH7_VmPeekTopClass(pVm); + if( pClass ){ + ph7_value_reset_string_cursor(pValue); + ph7_value_string(pValue,pClass->sName.zString,(int)pClass->sName.nByte); + ph7_array_add_strkey_elem(pArray,"class",pValue); + } + /* Return the freshly created array */ + ph7_result_value(pCtx,pArray); + /* + * Don't worry about freeing memory, everything will be released automatically + * as soon we return from this function. + */ + return PH7_OK; +} +/* + * Generate a small backtrace. + * Store the generated dump in the given BLOB + */ +static int VmMiniBacktrace( + ph7_vm *pVm, /* Target VM */ + SyBlob *pOut /* Store Dump here */ + ) +{ + VmFrame *pFrame = pVm->pFrame; + ph7_vm_func *pFunc; + ph7_class *pClass; + SyString *pFile; + /* Called function */ + while( pFrame->pParent && (pFrame->iFlags & VM_FRAME_EXCEPTION) ){ + /* Safely ignore the exception frame */ + pFrame = pFrame->pParent; + } + pFunc = (ph7_vm_func *)pFrame->pUserData; + SyBlobAppend(pOut,"[",sizeof(char)); + if( pFrame->pParent && pFunc ){ + SyBlobAppend(pOut,"Called function: ",sizeof("Called function: ")-1); + SyBlobAppend(pOut,pFunc->sName.zString,pFunc->sName.nByte); + }else{ + SyBlobAppend(pOut,"Global scope",sizeof("Global scope") - 1); + } + SyBlobAppend(pOut,"]",sizeof(char)); + /* Current processed script */ + pFile = (SyString *)SySetPeek(&pVm->aFiles); + if( pFile ){ + SyBlobAppend(pOut,"[",sizeof(char)); + SyBlobAppend(pOut,"Processed file: ",sizeof("Processed file: ")-1); + SyBlobAppend(pOut,pFile->zString,pFile->nByte); + SyBlobAppend(pOut,"]",sizeof(char)); + } + /* Top class */ + pClass = PH7_VmPeekTopClass(pVm); + if( pClass ){ + SyBlobAppend(pOut,"[",sizeof(char)); + SyBlobAppend(pOut,"Class: ",sizeof("Class: ")-1); + SyBlobAppend(pOut,pClass->sName.zString,pClass->sName.nByte); + SyBlobAppend(pOut,"]",sizeof(char)); + } + SyBlobAppend(pOut,"\n",sizeof(char)); + /* All done */ + return SXRET_OK; +} +/* + * void debug_print_backtrace() + * Prints a backtrace + * Parameters + * None + * Return + * NULL + */ +static int vm_builtin_debug_print_backtrace(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + ph7_vm *pVm = pCtx->pVm; + SyBlob sDump; + SyBlobInit(&sDump,&pVm->sAllocator); + /* Generate the backtrace */ + VmMiniBacktrace(pVm,&sDump); + /* Output backtrace */ + ph7_context_output(pCtx,(const char *)SyBlobData(&sDump),(int)SyBlobLength(&sDump)); + /* All done,cleanup */ + SyBlobRelease(&sDump); + SXUNUSED(nArg); /* cc warning */ + SXUNUSED(apArg); + return PH7_OK; +} +/* + * string debug_string_backtrace() + * Generate a backtrace + * Parameters + * None + * Return + * A mini backtrace(). + * Note that this is a symisc extension. + */ +static int vm_builtin_debug_string_backtrace(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + ph7_vm *pVm = pCtx->pVm; + SyBlob sDump; + SyBlobInit(&sDump,&pVm->sAllocator); + /* Generate the backtrace */ + VmMiniBacktrace(pVm,&sDump); + /* Return the backtrace */ + ph7_result_string(pCtx,(const char *)SyBlobData(&sDump),(int)SyBlobLength(&sDump)); /* Will make it's own copy */ + /* All done,cleanup */ + SyBlobRelease(&sDump); + SXUNUSED(nArg); /* cc warning */ + SXUNUSED(apArg); + return PH7_OK; +} +/* + * The following routine is invoked by the engine when an uncaught + * exception is triggered. + */ +static sxi32 VmUncaughtException( + ph7_vm *pVm, /* Target VM */ + ph7_class_instance *pThis /* Exception class instance [i.e: Exception $e] */ + ) +{ + ph7_value *apArg[2],sArg; + int nArg = 1; + sxi32 rc; + if( pVm->nExceptDepth > 15 ){ + /* Nesting limit reached */ + return SXRET_OK; + } + /* Call any exception handler if available */ + PH7_MemObjInit(pVm,&sArg); + if( pThis ){ + /* Load the exception instance */ + sArg.x.pOther = pThis; + pThis->iRef++; + MemObjSetType(&sArg,MEMOBJ_OBJ); + }else{ + nArg = 0; + } + apArg[0] = &sArg; + /* Call the exception handler if available */ + pVm->nExceptDepth++; + rc = PH7_VmCallUserFunction(&(*pVm),&pVm->aExceptionCB[1],1,apArg,0); + pVm->nExceptDepth--; + if( rc != SXRET_OK ){ + SyString sName = { "Exception" , sizeof("Exception") - 1 }; + SyString sFuncName = { "Global",sizeof("Global") - 1 }; + VmFrame *pFrame = pVm->pFrame; + /* No available handler,generate a fatal error */ + if( pThis ){ + SyStringDupPtr(&sName,&pThis->pClass->sName); + } + while( pFrame->pParent && (pFrame->iFlags & VM_FRAME_EXCEPTION) ){ + /* Ignore exception frames */ + pFrame = pFrame->pParent; + } + if( pFrame->pParent ){ + if( pFrame->iFlags & VM_FRAME_CATCH ){ + SyStringInitFromBuf(&sFuncName,"Catch_block",sizeof("Catch_block")-1); + }else{ + ph7_vm_func *pFunc = (ph7_vm_func *)pFrame->pUserData; + if( pFunc ){ + SyStringDupPtr(&sFuncName,&pFunc->sName); + } + } + } + /* Generate a listing */ + VmErrorFormat(&(*pVm),PH7_CTX_ERR, + "Uncaught exception '%z' in the '%z' frame context", + &sName,&sFuncName); + /* Tell the upper layer to stop VM execution immediately */ + rc = SXERR_ABORT; + } + PH7_MemObjRelease(&sArg); + return rc; +} +/* + * Throw an user exception. + */ +static sxi32 VmThrowException( + ph7_vm *pVm, /* Target VM */ + ph7_class_instance *pThis /* Exception class instance [i.e: Exception $e] */ + ) +{ + ph7_exception_block *pCatch; /* Catch block to execute */ + ph7_exception **apException; + ph7_exception *pException; + /* Point to the stack of loaded exceptions */ + apException = (ph7_exception **)SySetBasePtr(&pVm->aException); + pException = 0; + pCatch = 0; + if( SySetUsed(&pVm->aException) > 0 ){ + ph7_exception_block *aCatch; + ph7_class *pClass; + sxu32 j; + /* Locate the appropriate block to execute */ + pException = apException[SySetUsed(&pVm->aException) - 1]; + (void)SySetPop(&pVm->aException); + aCatch = (ph7_exception_block *)SySetBasePtr(&pException->sEntry); + for( j = 0 ; j < SySetUsed(&pException->sEntry) ; ++j ){ + SyString *pName = &aCatch[j].sClass; + /* Extract the target class */ + pClass = PH7_VmExtractClass(&(*pVm),pName->zString,pName->nByte,TRUE,0); + if( pClass == 0 ){ + /* No such class */ + continue; + } + if( VmInstanceOf(pThis->pClass,pClass) ){ + /* Catch block found,break immeditaley */ + pCatch = &aCatch[j]; + break; + } + } + } + /* Execute the cached block if available */ + if( pCatch == 0 ){ + sxi32 rc; + rc = VmUncaughtException(&(*pVm),pThis); + if( rc == SXRET_OK && pException ){ + VmFrame *pFrame = pVm->pFrame; + while( pFrame->pParent && (pFrame->iFlags & VM_FRAME_EXCEPTION) ){ + /* Safely ignore the exception frame */ + pFrame = pFrame->pParent; + } + if( pException->pFrame == pFrame ){ + /* Tell the upper layer that the exception was caught */ + pFrame->iFlags &= ~VM_FRAME_THROW; + } + } + return rc; + }else{ + VmFrame *pFrame = pVm->pFrame; + sxi32 rc; + while( pFrame->pParent && (pFrame->iFlags & VM_FRAME_EXCEPTION) ){ + /* Safely ignore the exception frame */ + pFrame = pFrame->pParent; + } + if( pException->pFrame == pFrame ){ + /* Tell the upper layer that the exception was caught */ + pFrame->iFlags &= ~VM_FRAME_THROW; + } + /* Create a private frame first */ + rc = VmEnterFrame(&(*pVm),0,0,&pFrame); + if( rc == SXRET_OK ){ + /* Mark as catch frame */ + ph7_value *pObj = VmExtractMemObj(&(*pVm),&pCatch->sThis,FALSE,TRUE); + pFrame->iFlags |= VM_FRAME_CATCH; + if( pObj ){ + /* Install the exception instance */ + pThis->iRef++; /* Increment reference count */ + pObj->x.pOther = pThis; + MemObjSetType(pObj,MEMOBJ_OBJ); + } + /* Exceute the block */ + VmLocalExec(&(*pVm),&pCatch->sByteCode,0); + /* Leave the frame */ + VmLeaveFrame(&(*pVm)); + } + } + /* TICKET 1433-60: Do not release the 'pException' pointer since it may + * be used again if a 'goto' statement is executed. + */ + return SXRET_OK; +} +/* + * Section: + * Version,Credits and Copyright related functions. + * Authors: + * Symisc Systems,devel@symisc.net. + * Copyright (C) Symisc Systems,http://ph7.symisc.net + * Status: + * Stable. + */ +/* + * string ph7version(void) + * Returns the running version of the PH7 version. + * Parameters + * None + * Return + * Current PH7 version. + */ +static int vm_builtin_ph7_version(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + SXUNUSED(nArg); + SXUNUSED(apArg); /* cc warning */ + /* Current engine version */ + ph7_result_string(pCtx,PH7_VERSION,sizeof(PH7_VERSION) - 1); + return PH7_OK; +} +/* + * PH7 release information HTML page used by the ph7info() and ph7credits() functions. + */ + #define PH7_HTML_PAGE_HEADER ""\ + ""\ + ""\ + "PH7 engine credits"\ + ""\ +"
"\ +"

PH7 Engine Credits

"\ +"
"\ +"

"\ +"Symisc PH7 

"\ +"

"\ +"A highly efficient embeddable bytecode compiler and a Virtual Machine for the PHP(5) Programming Language.

"\ +"

Copyright (C) Symisc Systems.

"\ +"

Engine Version:

"\ +"

" + +#define PH7_HTML_PAGE_FORMAT "%s

"\ +"

Engine ID:

"\ +"

%s %s

"\ +"

Underlying VFS:

"\ +"

%s

"\ +"

Total Built-in Functions:

"\ +"

%d

"\ +"

Total Built-in Classes:

"\ +"

%d

"\ +"

Host Operating System:

"\ +"

%s

"\ +"

"\ +"

Licensed To: <Public Release Under The "\ + "Symisc Public License (SPL)>

" + +#define PH7_HTML_PAGE_FOOTER "

/*
"\ +" * Copyright (C) 2011, 2012 Symisc Systems. 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. Redistributions in any form must be accompanied by information on
"\ +" *    how to obtain complete source code for the PH7 engine and any
"\ +" *    accompanying software that uses the PH7 engine software.
"\ +" *    The source code must either be included in the distribution
"\ +" *    or be available for no more than the cost of distribution plus
"\ +" *    a nominal fee, and must be freely redistributable under reasonable
"\ +" *    conditions. For an executable file, complete source code means
"\ +" *    the source code for all modules it contains.It does not include
"\ +" *    source code for modules or files that typically accompany the major
"\ +" *    components of the operating system on which the executable file runs.
"\ +" *
"\ +" * THIS SOFTWARE IS PROVIDED BY SYMISC SYSTEMS ``AS IS'' AND ANY EXPRESS
"\ +" * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
"\ +" * WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR
"\ +" * NON-INFRINGEMENT, ARE DISCLAIMED.  IN NO EVENT SHALL SYMISC SYSTEMS
"\ +" * 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.
"\ +" */
"\ +"

"\ +"

Copyright (C) Symisc Systems"\ +"

" +/* + * bool ph7credits(void) + * bool ph7info(void) + * bool ph7copyright(void) + * Prints out the credits for PH7 engine + * Parameters + * None + * Return + * Always TRUE + */ +static int vm_builtin_ph7_credits(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + ph7_vm *pVm = pCtx->pVm; /* Point to the underlying VM */ + /* Expand the HTML page above*/ + ph7_context_output(pCtx,PH7_HTML_PAGE_HEADER,(int)sizeof(PH7_HTML_PAGE_HEADER)-1); + ph7_context_output_format( + pCtx, + PH7_HTML_PAGE_FORMAT, + ph7_lib_version(), /* Engine version */ + ph7_lib_signature(), /* Engine signature */ + ph7_lib_ident(), /* Engine ID */ + pVm->pEngine->pVfs ? pVm->pEngine->pVfs->zName : "null_vfs", + SyHashTotalEntry(&pVm->hFunction) + SyHashTotalEntry(&pVm->hHostFunction),/* # built-in functions */ + SyHashTotalEntry(&pVm->hClass), +#ifdef __WINNT__ + "Windows NT" +#elif defined(__UNIXES__) + "UNIX-Like" +#else + "Other OS" +#endif + ); + ph7_context_output(pCtx,PH7_HTML_PAGE_FOOTER,(int)sizeof(PH7_HTML_PAGE_FOOTER)-1); + SXUNUSED(nArg); /* cc warning */ + SXUNUSED(apArg); + /* Return TRUE */ + //ph7_result_bool(pCtx,1); + return PH7_OK; +} +/* + * Section: + * URL related routines. + * Authors: + * Symisc Systems,devel@symisc.net. + * Copyright (C) Symisc Systems,http://ph7.symisc.net + * Status: + * Stable. + */ +/* Forward declaration */ +static sxi32 VmHttpSplitURI(SyhttpUri *pOut,const char *zUri,sxu32 nLen); +/* + * value parse_url(string $url [, int $component = -1 ]) + * Parse a URL and return its fields. + * Parameters + * $url + * The URL to parse. + * $component + * Specify one of PHP_URL_SCHEME, PHP_URL_HOST, PHP_URL_PORT, PHP_URL_USER + * PHP_URL_PASS, PHP_URL_PATH, PHP_URL_QUERY or PHP_URL_FRAGMENT to retrieve + * just a specific URL component as a string (except when PHP_URL_PORT is given + * in which case the return value will be an integer). + * Return + * If the component parameter is omitted, an associative array is returned. + * At least one element will be present within the array. Potential keys within + * this array are: + * scheme - e.g. http + * host + * port + * user + * pass + * path + * query - after the question mark ? + * fragment - after the hashmark # + * Note: + * FALSE is returned on failure. + * This function work with relative URL unlike the one shipped + * with the standard PHP engine. + */ +static int vm_builtin_parse_url(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + const char *zStr; /* Input string */ + SyString *pComp; /* Pointer to the URI component */ + SyhttpUri sURI; /* Parse of the given URI */ + int nLen; + sxi32 rc; + if( nArg < 1 || !ph7_value_is_string(apArg[0]) ){ + /* Missing/Invalid arguments,return FALSE */ + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Extract the given URI */ + zStr = ph7_value_to_string(apArg[0],&nLen); + if( nLen < 1 ){ + /* Nothing to process,return FALSE */ + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Get a parse */ + rc = VmHttpSplitURI(&sURI,zStr,(sxu32)nLen); + if( rc != SXRET_OK ){ + /* Malformed input,return FALSE */ + ph7_result_bool(pCtx,0); + return PH7_OK; + } + if( nArg > 1 ){ + int nComponent = ph7_value_to_int(apArg[1]); + /* Refer to constant.c for constants values */ + switch(nComponent){ + case 1: /* PHP_URL_SCHEME */ + pComp = &sURI.sScheme; + if( pComp->nByte < 1 ){ + /* No available value,return NULL */ + ph7_result_null(pCtx); + }else{ + ph7_result_string(pCtx,pComp->zString,(int)pComp->nByte); + } + break; + case 2: /* PHP_URL_HOST */ + pComp = &sURI.sHost; + if( pComp->nByte < 1 ){ + /* No available value,return NULL */ + ph7_result_null(pCtx); + }else{ + ph7_result_string(pCtx,pComp->zString,(int)pComp->nByte); + } + break; + case 3: /* PHP_URL_PORT */ + pComp = &sURI.sPort; + if( pComp->nByte < 1 ){ + /* No available value,return NULL */ + ph7_result_null(pCtx); + }else{ + int iPort = 0; + /* Cast the value to integer */ + SyStrToInt32(pComp->zString,pComp->nByte,(void *)&iPort,0); + ph7_result_int(pCtx,iPort); + } + break; + case 4: /* PHP_URL_USER */ + pComp = &sURI.sUser; + if( pComp->nByte < 1 ){ + /* No available value,return NULL */ + ph7_result_null(pCtx); + }else{ + ph7_result_string(pCtx,pComp->zString,(int)pComp->nByte); + } + break; + case 5: /* PHP_URL_PASS */ + pComp = &sURI.sPass; + if( pComp->nByte < 1 ){ + /* No available value,return NULL */ + ph7_result_null(pCtx); + }else{ + ph7_result_string(pCtx,pComp->zString,(int)pComp->nByte); + } + break; + case 7: /* PHP_URL_QUERY */ + pComp = &sURI.sQuery; + if( pComp->nByte < 1 ){ + /* No available value,return NULL */ + ph7_result_null(pCtx); + }else{ + ph7_result_string(pCtx,pComp->zString,(int)pComp->nByte); + } + break; + case 8: /* PHP_URL_FRAGMENT */ + pComp = &sURI.sFragment; + if( pComp->nByte < 1 ){ + /* No available value,return NULL */ + ph7_result_null(pCtx); + }else{ + ph7_result_string(pCtx,pComp->zString,(int)pComp->nByte); + } + break; + case 6: /* PHP_URL_PATH */ + pComp = &sURI.sPath; + if( pComp->nByte < 1 ){ + /* No available value,return NULL */ + ph7_result_null(pCtx); + }else{ + ph7_result_string(pCtx,pComp->zString,(int)pComp->nByte); + } + break; + default: + /* No such entry,return NULL */ + ph7_result_null(pCtx); + break; + } + }else{ + ph7_value *pArray,*pValue; + /* Return an associative array */ + pArray = ph7_context_new_array(pCtx); /* Empty array */ + pValue = ph7_context_new_scalar(pCtx); /* Array value */ + if( pArray == 0 || pValue == 0 ){ + /* Out of memory */ + ph7_context_throw_error(pCtx,PH7_CTX_ERR,"PH7 engine is running out of memory"); + /* Return false */ + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Fill the array */ + pComp = &sURI.sScheme; + if( pComp->nByte > 0 ){ + ph7_value_string(pValue,pComp->zString,(int)pComp->nByte); + ph7_array_add_strkey_elem(pArray,"scheme",pValue); /* Will make it's own copy */ + } + /* Reset the string cursor */ + ph7_value_reset_string_cursor(pValue); + pComp = &sURI.sHost; + if( pComp->nByte > 0 ){ + ph7_value_string(pValue,pComp->zString,(int)pComp->nByte); + ph7_array_add_strkey_elem(pArray,"host",pValue); /* Will make it's own copy */ + } + /* Reset the string cursor */ + ph7_value_reset_string_cursor(pValue); + pComp = &sURI.sPort; + if( pComp->nByte > 0 ){ + int iPort = 0;/* cc warning */ + /* Convert to integer */ + SyStrToInt32(pComp->zString,pComp->nByte,(void *)&iPort,0); + ph7_value_int(pValue,iPort); + ph7_array_add_strkey_elem(pArray,"port",pValue); /* Will make it's own copy */ + } + /* Reset the string cursor */ + ph7_value_reset_string_cursor(pValue); + pComp = &sURI.sUser; + if( pComp->nByte > 0 ){ + ph7_value_string(pValue,pComp->zString,(int)pComp->nByte); + ph7_array_add_strkey_elem(pArray,"user",pValue); /* Will make it's own copy */ + } + /* Reset the string cursor */ + ph7_value_reset_string_cursor(pValue); + pComp = &sURI.sPass; + if( pComp->nByte > 0 ){ + ph7_value_string(pValue,pComp->zString,(int)pComp->nByte); + ph7_array_add_strkey_elem(pArray,"pass",pValue); /* Will make it's own copy */ + } + /* Reset the string cursor */ + ph7_value_reset_string_cursor(pValue); + pComp = &sURI.sPath; + if( pComp->nByte > 0 ){ + ph7_value_string(pValue,pComp->zString,(int)pComp->nByte); + ph7_array_add_strkey_elem(pArray,"path",pValue); /* Will make it's own copy */ + } + /* Reset the string cursor */ + ph7_value_reset_string_cursor(pValue); + pComp = &sURI.sQuery; + if( pComp->nByte > 0 ){ + ph7_value_string(pValue,pComp->zString,(int)pComp->nByte); + ph7_array_add_strkey_elem(pArray,"query",pValue); /* Will make it's own copy */ + } + /* Reset the string cursor */ + ph7_value_reset_string_cursor(pValue); + pComp = &sURI.sFragment; + if( pComp->nByte > 0 ){ + ph7_value_string(pValue,pComp->zString,(int)pComp->nByte); + ph7_array_add_strkey_elem(pArray,"fragment",pValue); /* Will make it's own copy */ + } + /* Return the created array */ + ph7_result_value(pCtx,pArray); + /* NOTE: + * Don't worry about freeing 'pValue',everything will be released + * automatically as soon we return from this function. + */ + } + /* All done */ + return PH7_OK; +} +/* + * Section: + * Array related routines. + * Authors: + * Symisc Systems,devel@symisc.net. + * Copyright (C) Symisc Systems,http://ph7.symisc.net + * Status: + * Stable. + * Note 2012-5-21 01:04:15: + * Array related functions that need access to the underlying + * virtual machine are implemented here rather than 'hashmap.c' + */ +/* + * The [compact()] function store it's state information in an instance + * of the following structure. + */ +struct compact_data +{ + ph7_value *pArray; /* Target array */ + int nRecCount; /* Recursion count */ +}; +/* + * Walker callback for the [compact()] function defined below. + */ +static int VmCompactCallback(ph7_value *pKey,ph7_value *pValue,void *pUserData) +{ + struct compact_data *pData = (struct compact_data *)pUserData; + ph7_value *pArray = (ph7_value *)pData->pArray; + ph7_vm *pVm = pArray->pVm; + /* Act according to the hashmap value */ + if( ph7_value_is_string(pValue) ){ + SyString sVar; + SyStringInitFromBuf(&sVar,SyBlobData(&pValue->sBlob),SyBlobLength(&pValue->sBlob)); + if( sVar.nByte > 0 ){ + /* Query the current frame */ + pKey = VmExtractMemObj(pVm,&sVar,FALSE,FALSE); + /* ^ + * | Avoid wasting variable and use 'pKey' instead + */ + if( pKey ){ + /* Perform the insertion */ + ph7_array_add_elem(pArray,pValue/* Variable name*/,pKey/* Variable value */); + } + } + }else if( ph7_value_is_array(pValue) && pData->nRecCount < 32) { + int rc; + /* Recursively traverse this array */ + pData->nRecCount++; + rc = PH7_HashmapWalk((ph7_hashmap *)pValue->x.pOther,VmCompactCallback,pUserData); + pData->nRecCount--; + return rc; + } + return SXRET_OK; +} +/* + * array compact(mixed $varname [, mixed $... ]) + * Create array containing variables and their values. + * For each of these, compact() looks for a variable with that name + * in the current symbol table and adds it to the output array such + * that the variable name becomes the key and the contents of the variable + * become the value for that key. In short, it does the opposite of extract(). + * Any strings that are not set will simply be skipped. + * Parameters + * $varname + * compact() takes a variable number of parameters. Each parameter can be either + * a string containing the name of the variable, or an array of variable names. + * The array can contain other arrays of variable names inside it; compact() handles + * it recursively. + * Return + * The output array with all the variables added to it or NULL on failure + */ +static int vm_builtin_compact(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + ph7_value *pArray,*pObj; + ph7_vm *pVm = pCtx->pVm; + const char *zName; + SyString sVar; + int i,nLen; + if( nArg < 1 ){ + /* Missing arguments,return NULL */ + ph7_result_null(pCtx); + return PH7_OK; + } + /* Create the array */ + pArray = ph7_context_new_array(pCtx); + if( pArray == 0 ){ + /* Out of memory */ + ph7_context_throw_error(pCtx,PH7_CTX_ERR,"PH7 engine is running out of memory"); + /* Return NULL */ + ph7_result_null(pCtx); + return PH7_OK; + } + /* Perform the requested operation */ + for( i = 0 ; i < nArg ; i++ ){ + if( !ph7_value_is_string(apArg[i]) ){ + if( ph7_value_is_array(apArg[i]) ){ + struct compact_data sData; + ph7_hashmap *pMap = (ph7_hashmap *)apArg[i]->x.pOther; + /* Recursively walk the array */ + sData.nRecCount = 0; + sData.pArray = pArray; + PH7_HashmapWalk(pMap,VmCompactCallback,&sData); + } + }else{ + /* Extract variable name */ + zName = ph7_value_to_string(apArg[i],&nLen); + if( nLen > 0 ){ + SyStringInitFromBuf(&sVar,zName,nLen); + /* Check if the variable is available in the current frame */ + pObj = VmExtractMemObj(pVm,&sVar,FALSE,FALSE); + if( pObj ){ + ph7_array_add_elem(pArray,apArg[i]/*Variable name*/,pObj/* Variable value */); + } + } + } + } + /* Return the array */ + ph7_result_value(pCtx,pArray); + return PH7_OK; +} +/* + * The [extract()] function store it's state information in an instance + * of the following structure. + */ +typedef struct extract_aux_data extract_aux_data; +struct extract_aux_data +{ + ph7_vm *pVm; /* VM that own this instance */ + int iCount; /* Number of variables successfully imported */ + const char *zPrefix; /* Prefix name */ + int Prefixlen; /* Prefix length */ + int iFlags; /* Control flags */ + char zWorker[1024]; /* Working buffer */ +}; +/* Forward declaration */ +static int VmExtractCallback(ph7_value *pKey,ph7_value *pValue,void *pUserData); +/* + * int extract(array &$var_array[,int $extract_type = EXTR_OVERWRITE[,string $prefix = NULL ]]) + * Import variables into the current symbol table from an array. + * Parameters + * $var_array + * An associative array. This function treats keys as variable names and values + * as variable values. For each key/value pair it will create a variable in the current symbol + * table, subject to extract_type and prefix parameters. + * You must use an associative array; a numerically indexed array will not produce results + * unless you use EXTR_PREFIX_ALL or EXTR_PREFIX_INVALID. + * $extract_type + * The way invalid/numeric keys and collisions are treated is determined by the extract_type. + * It can be one of the following values: + * EXTR_OVERWRITE + * If there is a collision, overwrite the existing variable. + * EXTR_SKIP + * If there is a collision, don't overwrite the existing variable. + * EXTR_PREFIX_SAME + * If there is a collision, prefix the variable name with prefix. + * EXTR_PREFIX_ALL + * Prefix all variable names with prefix. + * EXTR_PREFIX_INVALID + * Only prefix invalid/numeric variable names with prefix. + * EXTR_IF_EXISTS + * Only overwrite the variable if it already exists in the current symbol table + * otherwise do nothing. + * This is useful for defining a list of valid variables and then extracting only those + * variables you have defined out of $_REQUEST, for example. + * EXTR_PREFIX_IF_EXISTS + * Only create prefixed variable names if the non-prefixed version of the same variable exists in + * the current symbol table. + * $prefix + * Note that prefix is only required if extract_type is EXTR_PREFIX_SAME, EXTR_PREFIX_ALL + * EXTR_PREFIX_INVALID or EXTR_PREFIX_IF_EXISTS. If the prefixed result is not a valid variable name + * it is not imported into the symbol table. Prefixes are automatically separated from the array key by an + * underscore character. + * Return + * Returns the number of variables successfully imported into the symbol table. + */ +static int vm_builtin_extract(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + extract_aux_data sAux; + ph7_hashmap *pMap; + if( nArg < 1 || !ph7_value_is_array(apArg[0]) ){ + /* Missing/Invalid arguments,return 0 */ + ph7_result_int(pCtx,0); + return PH7_OK; + } + /* Point to the target hashmap */ + pMap = (ph7_hashmap *)apArg[0]->x.pOther; + if( pMap->nEntry < 1 ){ + /* Empty map,return 0 */ + ph7_result_int(pCtx,0); + return PH7_OK; + } + /* Prepare the aux data */ + SyZero(&sAux,sizeof(extract_aux_data)-sizeof(sAux.zWorker)); + if( nArg > 1 ){ + sAux.iFlags = ph7_value_to_int(apArg[1]); + if( nArg > 2 ){ + sAux.zPrefix = ph7_value_to_string(apArg[2],&sAux.Prefixlen); + } + } + sAux.pVm = pCtx->pVm; + /* Invoke the worker callback */ + PH7_HashmapWalk(pMap,VmExtractCallback,&sAux); + /* Number of variables successfully imported */ + ph7_result_int(pCtx,sAux.iCount); + return PH7_OK; +} +/* + * Worker callback for the [extract()] function defined + * below. + */ +static int VmExtractCallback(ph7_value *pKey,ph7_value *pValue,void *pUserData) +{ + extract_aux_data *pAux = (extract_aux_data *)pUserData; + int iFlags = pAux->iFlags; + ph7_vm *pVm = pAux->pVm; + ph7_value *pObj; + SyString sVar; + if( (iFlags & 0x10/* EXTR_PREFIX_INVALID */) && (pKey->iFlags & (MEMOBJ_INT|MEMOBJ_BOOL|MEMOBJ_REAL))){ + iFlags |= 0x08; /*EXTR_PREFIX_ALL*/ + } + /* Perform a string cast */ + PH7_MemObjToString(pKey); + if( SyBlobLength(&pKey->sBlob) < 1 ){ + /* Unavailable variable name */ + return SXRET_OK; + } + sVar.nByte = 0; /* cc warning */ + if( (iFlags & 0x08/*EXTR_PREFIX_ALL*/ ) && pAux->Prefixlen > 0 ){ + sVar.nByte = (sxu32)SyBufferFormat(pAux->zWorker,sizeof(pAux->zWorker),"%.*s_%.*s", + pAux->Prefixlen,pAux->zPrefix, + SyBlobLength(&pKey->sBlob),SyBlobData(&pKey->sBlob) + ); + }else{ + sVar.nByte = (sxu32) SyMemcpy(SyBlobData(&pKey->sBlob),pAux->zWorker, + SXMIN(SyBlobLength(&pKey->sBlob),sizeof(pAux->zWorker))); + } + sVar.zString = pAux->zWorker; + /* Try to extract the variable */ + pObj = VmExtractMemObj(pVm,&sVar,TRUE,FALSE); + if( pObj ){ + /* Collision */ + if( iFlags & 0x02 /* EXTR_SKIP */ ){ + return SXRET_OK; + } + if( iFlags & 0x04 /* EXTR_PREFIX_SAME */ ){ + if( (iFlags & 0x08/*EXTR_PREFIX_ALL*/) || pAux->Prefixlen < 1){ + /* Already prefixed */ + return SXRET_OK; + } + sVar.nByte = (sxu32)SyBufferFormat(pAux->zWorker,sizeof(pAux->zWorker),"%.*s_%.*s", + pAux->Prefixlen,pAux->zPrefix, + SyBlobLength(&pKey->sBlob),SyBlobData(&pKey->sBlob) + ); + pObj = VmExtractMemObj(pVm,&sVar,TRUE,TRUE); + } + }else{ + /* Create the variable */ + pObj = VmExtractMemObj(pVm,&sVar,TRUE,TRUE); + } + if( pObj ){ + /* Overwrite the old value */ + PH7_MemObjStore(pValue,pObj); + /* Increment counter */ + pAux->iCount++; + } + return SXRET_OK; +} +/* + * Worker callback for the [import_request_variables()] function + * defined below. + */ +static int VmImportRequestCallback(ph7_value *pKey,ph7_value *pValue,void *pUserData) +{ + extract_aux_data *pAux = (extract_aux_data *)pUserData; + ph7_vm *pVm = pAux->pVm; + ph7_value *pObj; + SyString sVar; + /* Perform a string cast */ + PH7_MemObjToString(pKey); + if( SyBlobLength(&pKey->sBlob) < 1 ){ + /* Unavailable variable name */ + return SXRET_OK; + } + sVar.nByte = 0; /* cc warning */ + if( pAux->Prefixlen > 0 ){ + sVar.nByte = (sxu32)SyBufferFormat(pAux->zWorker,sizeof(pAux->zWorker),"%.*s%.*s", + pAux->Prefixlen,pAux->zPrefix, + SyBlobLength(&pKey->sBlob),SyBlobData(&pKey->sBlob) + ); + }else{ + sVar.nByte = (sxu32) SyMemcpy(SyBlobData(&pKey->sBlob),pAux->zWorker, + SXMIN(SyBlobLength(&pKey->sBlob),sizeof(pAux->zWorker))); + } + sVar.zString = pAux->zWorker; + /* Extract the variable */ + pObj = VmExtractMemObj(pVm,&sVar,TRUE,TRUE); + if( pObj ){ + PH7_MemObjStore(pValue,pObj); + } + return SXRET_OK; +} +/* + * bool import_request_variables(string $types[,string $prefix]) + * Import GET/POST/Cookie variables into the global scope. + * Parameters + * $types + * Using the types parameter, you can specify which request variables to import. + * You can use 'G', 'P' and 'C' characters respectively for GET, POST and Cookie. + * These characters are not case sensitive, so you can also use any combination of 'g', 'p' and 'c'. + * POST includes the POST uploaded file information. + * Note: + * Note that the order of the letters matters, as when using "GP", the POST variables will overwrite + * GET variables with the same name. Any other letters than GPC are discarded. + * $prefix + * Variable name prefix, prepended before all variable's name imported into the global scope. + * So if you have a GET value named "userid", and provide a prefix "pref_", then you'll get a global + * variable named $pref_userid. + * Return + * TRUE on success or FALSE on failure. + */ +static int vm_builtin_import_request_variables(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + const char *zPrefix,*zEnd,*zImport; + extract_aux_data sAux; + int nLen,nPrefixLen; + ph7_value *pSuper; + ph7_vm *pVm; + /* By default import only $_GET variables */ + zImport = "G"; + nLen = (int)sizeof(char); + zPrefix = 0; + nPrefixLen = 0; + if( nArg > 0 ){ + if( ph7_value_is_string(apArg[0]) ){ + zImport = ph7_value_to_string(apArg[0],&nLen); + } + if( nArg > 1 && ph7_value_is_string(apArg[1]) ){ + zPrefix = ph7_value_to_string(apArg[1],&nPrefixLen); + } + } + /* Point to the underlying VM */ + pVm = pCtx->pVm; + /* Initialize the aux data */ + SyZero(&sAux,sizeof(sAux)-sizeof(sAux.zWorker)); + sAux.zPrefix = zPrefix; + sAux.Prefixlen = nPrefixLen; + sAux.pVm = pVm; + /* Extract */ + zEnd = &zImport[nLen]; + while( zImport < zEnd ){ + int c = zImport[0]; + pSuper = 0; + if( c == 'G' || c == 'g' ){ + /* Import $_GET variables */ + pSuper = VmExtractSuper(pVm,"_GET",sizeof("_GET")-1); + }else if( c == 'P' || c == 'p' ){ + /* Import $_POST variables */ + pSuper = VmExtractSuper(pVm,"_POST",sizeof("_POST")-1); + }else if( c == 'c' || c == 'C' ){ + /* Import $_COOKIE variables */ + pSuper = VmExtractSuper(pVm,"_COOKIE",sizeof("_COOKIE")-1); + } + if( pSuper ){ + /* Iterate throw array entries */ + ph7_array_walk(pSuper,VmImportRequestCallback,&sAux); + } + /* Advance the cursor */ + zImport++; + } + /* All done,return TRUE*/ + ph7_result_bool(pCtx,0); + return PH7_OK; +} +/* + * Compile and evaluate a PHP chunk at run-time. + * Refer to the eval() language construct implementation for more + * information. + */ +static sxi32 VmEvalChunk( + ph7_vm *pVm, /* Underlying Virtual Machine */ + ph7_context *pCtx, /* Call Context */ + SyString *pChunk, /* PHP chunk to evaluate */ + int iFlags, /* Compile flag */ + int bTrueReturn /* TRUE to return execution result */ + ) +{ + SySet *pByteCode,aByteCode; + ProcConsumer xErr = 0; + void *pErrData = 0; + /* Initialize bytecode container */ + SySetInit(&aByteCode,&pVm->sAllocator,sizeof(VmInstr)); + SySetAlloc(&aByteCode,0x20); + /* Reset the code generator */ + if( bTrueReturn ){ + /* Included file,log compile-time errors */ + xErr = pVm->pEngine->xConf.xErr; + pErrData = pVm->pEngine->xConf.pErrData; + } + PH7_ResetCodeGenerator(pVm,xErr,pErrData); + /* Swap bytecode container */ + pByteCode = pVm->pByteContainer; + pVm->pByteContainer = &aByteCode; + /* Compile the chunk */ + PH7_CompileScript(pVm,pChunk,iFlags); + if( pVm->sCodeGen.nErr > 0 ){ + /* Compilation error,return false */ + if( pCtx ){ + ph7_result_bool(pCtx,0); + } + }else{ + ph7_value sResult; /* Return value */ + if( SXRET_OK != PH7_VmEmitInstr(pVm,PH7_OP_DONE,0,0,0,0) ){ + /* Out of memory */ + if( pCtx ){ + ph7_result_bool(pCtx,0); + } + goto Cleanup; + } + if( bTrueReturn ){ + /* Assume a boolean true return value */ + PH7_MemObjInitFromBool(pVm,&sResult,1); + }else{ + /* Assume a null return value */ + PH7_MemObjInit(pVm,&sResult); + } + /* Execute the compiled chunk */ + VmLocalExec(pVm,&aByteCode,&sResult); + if( pCtx ){ + /* Set the execution result */ + ph7_result_value(pCtx,&sResult); + } + PH7_MemObjRelease(&sResult); + } +Cleanup: + /* Cleanup the mess left behind */ + pVm->pByteContainer = pByteCode; + SySetRelease(&aByteCode); + return SXRET_OK; +} +/* + * value eval(string $code) + * Evaluate a string as PHP code. + * Parameter + * code: PHP code to evaluate. + * Return + * eval() returns NULL unless return is called in the evaluated code, in which case + * the value passed to return is returned. If there is a parse error in the evaluated + * code, eval() returns FALSE and execution of the following code continues normally. + */ +static int vm_builtin_eval(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + SyString sChunk; /* Chunk to evaluate */ + if( nArg < 1 ){ + /* Nothing to evaluate,return NULL */ + ph7_result_null(pCtx); + return SXRET_OK; + } + /* Chunk to evaluate */ + sChunk.zString = ph7_value_to_string(apArg[0],(int *)&sChunk.nByte); + if( sChunk.nByte < 1 ){ + /* Empty string,return NULL */ + ph7_result_null(pCtx); + return SXRET_OK; + } + /* Eval the chunk */ + VmEvalChunk(pCtx->pVm,&(*pCtx),&sChunk,PH7_PHP_ONLY,FALSE); + return SXRET_OK; +} +/* + * Check if a file path is already included. + */ +static int VmIsIncludedFile(ph7_vm *pVm,SyString *pFile) +{ + SyString *aEntries; + sxu32 n; + aEntries = (SyString *)SySetBasePtr(&pVm->aIncluded); + /* Perform a linear search */ + for( n = 0 ; n < SySetUsed(&pVm->aIncluded) ; ++n ){ + if( SyStringCmp(pFile,&aEntries[n],SyMemcmp) == 0 ){ + /* Already included */ + return TRUE; + } + } + return FALSE; +} +/* + * Push a file path in the appropriate VM container. + */ +PH7_PRIVATE sxi32 PH7_VmPushFilePath(ph7_vm *pVm,const char *zPath,int nLen,sxu8 bMain,sxi32 *pNew) +{ + SyString sPath; + char *zDup; +#ifdef __WINNT__ + char *zCur; +#endif + sxi32 rc; + if( nLen < 0 ){ + nLen = SyStrlen(zPath); + } + /* Duplicate the file path first */ + zDup = SyMemBackendStrDup(&pVm->sAllocator,zPath,nLen); + if( zDup == 0 ){ + return SXERR_MEM; + } +#ifdef __WINNT__ + /* Normalize path on windows + * Example: + * Path/To/File.php + * becomes + * path\to\file.php + */ + zCur = zDup; + while( zCur[0] != 0 ){ + if( zCur[0] == '/' ){ + zCur[0] = '\\'; + }else if( (unsigned char)zCur[0] < 0xc0 && SyisUpper(zCur[0]) ){ + int c = SyToLower(zCur[0]); + zCur[0] = (char)c; /* MSVC stupidity */ + } + zCur++; + } +#endif + /* Install the file path */ + SyStringInitFromBuf(&sPath,zDup,nLen); + if( !bMain ){ + if( VmIsIncludedFile(&(*pVm),&sPath) ){ + /* Already included */ + *pNew = 0; + }else{ + /* Insert in the corresponding container */ + rc = SySetPut(&pVm->aIncluded,(const void *)&sPath); + if( rc != SXRET_OK ){ + SyMemBackendFree(&pVm->sAllocator,zDup); + return rc; + } + *pNew = 1; + } + } + SySetPut(&pVm->aFiles,(const void *)&sPath); + return SXRET_OK; +} +/* + * Compile and Execute a PHP script at run-time. + * SXRET_OK is returned on sucessful evaluation.Any other return values + * indicates failure. + * Note that the PHP script to evaluate can be a local or remote file.In + * either cases the [PH7_StreamReadWholeFile()] function handle all the underlying + * operations. + * If the [PH7_DISABLE_BUILTIN_FUNC] compile-time directive is defined,then + * this function is a no-op. + * Refer to the implementation of the include(),include_once() language + * constructs for more information. + */ +static sxi32 VmExecIncludedFile( + ph7_context *pCtx, /* Call Context */ + SyString *pPath, /* Script path or URL*/ + int IncludeOnce /* TRUE if called from include_once() or require_once() */ + ) +{ + sxi32 rc; +#ifndef PH7_DISABLE_BUILTIN_FUNC + const ph7_io_stream *pStream; + SyBlob sContents; + void *pHandle; + ph7_vm *pVm; + int isNew; + /* Initialize fields */ + pVm = pCtx->pVm; + SyBlobInit(&sContents,&pVm->sAllocator); + isNew = 0; + /* Extract the associated stream */ + pStream = PH7_VmGetStreamDevice(pVm,&pPath->zString,pPath->nByte); + /* + * Open the file or the URL [i.e: http://ph7.symisc.net/example/hello.php"] + * in a read-only mode. + */ + pHandle = PH7_StreamOpenHandle(pVm,pStream,pPath->zString,PH7_IO_OPEN_RDONLY,TRUE,0,TRUE,&isNew); + if( pHandle == 0 ){ + return SXERR_IO; + } + rc = SXRET_OK; /* Stupid cc warning */ + if( IncludeOnce && !isNew ){ + /* Already included */ + rc = SXERR_EXISTS; + }else{ + /* Read the whole file contents */ + rc = PH7_StreamReadWholeFile(pHandle,pStream,&sContents); + if( rc == SXRET_OK ){ + SyString sScript; + /* Compile and execute the script */ + SyStringInitFromBuf(&sScript,SyBlobData(&sContents),SyBlobLength(&sContents)); + VmEvalChunk(pCtx->pVm,&(*pCtx),&sScript,0,TRUE); + } + } + /* Pop from the set of included file */ + (void)SySetPop(&pVm->aFiles); + /* Close the handle */ + PH7_StreamCloseHandle(pStream,pHandle); + /* Release the working buffer */ + SyBlobRelease(&sContents); +#else + pCtx = 0; /* cc warning */ + pPath = 0; + IncludeOnce = 0; + rc = SXERR_IO; +#endif /* PH7_DISABLE_BUILTIN_FUNC */ + return rc; +} +/* + * string get_include_path(void) + * Gets the current include_path configuration option. + * Parameter + * None + * Return + * Included paths as a string + */ +static int vm_builtin_get_include_path(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + ph7_vm *pVm = pCtx->pVm; + SyString *aEntry; + int dir_sep; + sxu32 n; +#ifdef __WINNT__ + dir_sep = ';'; +#else + /* Assume UNIX path separator */ + dir_sep = ':'; +#endif + SXUNUSED(nArg); /* cc warning */ + SXUNUSED(apArg); + /* Point to the list of import paths */ + aEntry = (SyString *)SySetBasePtr(&pVm->aPaths); + for( n = 0 ; n < SySetUsed(&pVm->aPaths) ; n++ ){ + SyString *pEntry = &aEntry[n]; + if( n > 0 ){ + /* Append dir seprator */ + ph7_result_string(pCtx,(const char *)&dir_sep,sizeof(char)); + } + /* Append path */ + ph7_result_string(pCtx,pEntry->zString,(int)pEntry->nByte); + } + return PH7_OK; +} +/* + * string get_get_included_files(void) + * Gets the current include_path configuration option. + * Parameter + * None + * Return + * Included paths as a string + */ +static int vm_builtin_get_included_files(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + SySet *pFiles = &pCtx->pVm->aFiles; + ph7_value *pArray,*pWorker; + SyString *pEntry; + int c,d; + /* Create an array and a working value */ + pArray = ph7_context_new_array(pCtx); + pWorker = ph7_context_new_scalar(pCtx); + if( pArray == 0 || pWorker == 0 ){ + /* Out of memory,return null */ + ph7_result_null(pCtx); + SXUNUSED(nArg); /* cc warning */ + SXUNUSED(apArg); + return PH7_OK; + } + c = d = '/'; +#ifdef __WINNT__ + d = '\\'; +#endif + /* Iterate throw entries */ + SySetResetCursor(pFiles); + while( SXRET_OK == SySetGetNextEntry(pFiles,(void **)&pEntry) ){ + const char *zBase,*zEnd; + int iLen; + /* reset the string cursor */ + ph7_value_reset_string_cursor(pWorker); + /* Extract base name */ + zEnd = &pEntry->zString[pEntry->nByte - 1]; + /* Ignore trailing '/' */ + while( zEnd > pEntry->zString && ( (int)zEnd[0] == c || (int)zEnd[0] == d ) ){ + zEnd--; + } + iLen = (int)(&zEnd[1]-pEntry->zString); + while( zEnd > pEntry->zString && ( (int)zEnd[0] != c && (int)zEnd[0] != d ) ){ + zEnd--; + } + zBase = (zEnd > pEntry->zString) ? &zEnd[1] : pEntry->zString; + zEnd = &pEntry->zString[iLen]; + /* Copy entry name */ + ph7_value_string(pWorker,zBase,(int)(zEnd-zBase)); + /* Perform the insertion */ + ph7_array_add_elem(pArray,0/* Automatic index assign*/,pWorker); /* Will make it's own copy */ + } + /* All done,return the created array */ + ph7_result_value(pCtx,pArray); + /* Note that 'pWorker' will be automatically destroyed + * by the engine as soon we return from this foreign + * function. + */ + return PH7_OK; +} +/* + * include: + * According to the PHP reference manual. + * The include() function includes and evaluates the specified file. + * Files are included based on the file path given or, if none is given + * the include_path specified.If the file isn't found in the include_path + * include() will finally check in the calling script's own directory + * and the current working directory before failing. The include() + * construct will emit a warning if it cannot find a file; this is different + * behavior from require(), which will emit a fatal error. + * If a path is defined � whether absolute (starting with a drive letter + * or \ on Windows, or / on Unix/Linux systems) or relative to the current + * directory (starting with . or ..) � the include_path will be ignored altogether. + * For example, if a filename begins with ../, the parser will look in the parent + * directory to find the requested file. + * When a file is included, the code it contains inherits the variable scope + * of the line on which the include occurs. Any variables available at that line + * in the calling file will be available within the called file, from that point forward. + * However, all functions and classes defined in the included file have the global scope. + */ +static int vm_builtin_include(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + SyString sFile; + sxi32 rc; + if( nArg < 1 ){ + /* Nothing to evaluate,return NULL */ + ph7_result_null(pCtx); + return SXRET_OK; + } + /* File to include */ + sFile.zString = ph7_value_to_string(apArg[0],(int *)&sFile.nByte); + if( sFile.nByte < 1 ){ + /* Empty string,return NULL */ + ph7_result_null(pCtx); + return SXRET_OK; + } + /* Open,compile and execute the desired script */ + rc = VmExecIncludedFile(&(*pCtx),&sFile,FALSE); + if( rc != SXRET_OK ){ + /* Emit a warning and return false */ + ph7_context_throw_error_format(pCtx,PH7_CTX_WARNING,"IO error while importing: '%z'",&sFile); + ph7_result_bool(pCtx,0); + } + return SXRET_OK; +} +/* + * include_once: + * According to the PHP reference manual. + * The include_once() statement includes and evaluates the specified file during + * the execution of the script. This is a behavior similar to the include() + * statement, with the only difference being that if the code from a file has already + * been included, it will not be included again. As the name suggests, it will be included + * just once. + */ +static int vm_builtin_include_once(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + SyString sFile; + sxi32 rc; + if( nArg < 1 ){ + /* Nothing to evaluate,return NULL */ + ph7_result_null(pCtx); + return SXRET_OK; + } + /* File to include */ + sFile.zString = ph7_value_to_string(apArg[0],(int *)&sFile.nByte); + if( sFile.nByte < 1 ){ + /* Empty string,return NULL */ + ph7_result_null(pCtx); + return SXRET_OK; + } + /* Open,compile and execute the desired script */ + rc = VmExecIncludedFile(&(*pCtx),&sFile,TRUE); + if( rc == SXERR_EXISTS ){ + /* File already included,return TRUE */ + ph7_result_bool(pCtx,1); + return SXRET_OK; + } + if( rc != SXRET_OK ){ + /* Emit a warning and return false */ + ph7_context_throw_error_format(pCtx,PH7_CTX_WARNING,"IO error while importing: '%z'",&sFile); + ph7_result_bool(pCtx,0); + } + return SXRET_OK; +} +/* + * require. + * According to the PHP reference manual. + * require() is identical to include() except upon failure it will + * also produce a fatal level error. + * In other words, it will halt the script whereas include() only + * emits a warning which allows the script to continue. + */ +static int vm_builtin_require(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + SyString sFile; + sxi32 rc; + if( nArg < 1 ){ + /* Nothing to evaluate,return NULL */ + ph7_result_null(pCtx); + return SXRET_OK; + } + /* File to include */ + sFile.zString = ph7_value_to_string(apArg[0],(int *)&sFile.nByte); + if( sFile.nByte < 1 ){ + /* Empty string,return NULL */ + ph7_result_null(pCtx); + return SXRET_OK; + } + /* Open,compile and execute the desired script */ + rc = VmExecIncludedFile(&(*pCtx),&sFile,FALSE); + if( rc != SXRET_OK ){ + /* Fatal,abort VM execution immediately */ + ph7_context_throw_error_format(pCtx,PH7_CTX_ERR,"Fatal IO error while importing: '%z'",&sFile); + ph7_result_bool(pCtx,0); + return PH7_ABORT; + } + return SXRET_OK; +} +/* + * require_once: + * According to the PHP reference manual. + * The require_once() statement is identical to require() except PHP will check + * if the file has already been included, and if so, not include (require) it again. + * See the include_once() documentation for information about the _once behaviour + * and how it differs from its non _once siblings. + */ +static int vm_builtin_require_once(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + SyString sFile; + sxi32 rc; + if( nArg < 1 ){ + /* Nothing to evaluate,return NULL */ + ph7_result_null(pCtx); + return SXRET_OK; + } + /* File to include */ + sFile.zString = ph7_value_to_string(apArg[0],(int *)&sFile.nByte); + if( sFile.nByte < 1 ){ + /* Empty string,return NULL */ + ph7_result_null(pCtx); + return SXRET_OK; + } + /* Open,compile and execute the desired script */ + rc = VmExecIncludedFile(&(*pCtx),&sFile,TRUE); + if( rc == SXERR_EXISTS ){ + /* File already included,return TRUE */ + ph7_result_bool(pCtx,1); + return SXRET_OK; + } + if( rc != SXRET_OK ){ + /* Fatal,abort VM execution immediately */ + ph7_context_throw_error_format(pCtx,PH7_CTX_ERR,"Fatal IO error while importing: '%z'",&sFile); + ph7_result_bool(pCtx,0); + return PH7_ABORT; + } + return SXRET_OK; +} +/* + * Section: + * Command line arguments processing. + * Authors: + * Symisc Systems,devel@symisc.net. + * Copyright (C) Symisc Systems,http://ph7.symisc.net + * Status: + * Stable. + */ +/* + * Check if a short option argument [i.e: -c] is available in the command + * line string. Return a pointer to the start of the stream on success. + * NULL otherwise. + */ +static const char * VmFindShortOpt(int c,const char *zIn,const char *zEnd) +{ + while( zIn < zEnd ){ + if( zIn[0] == '-' && &zIn[1] < zEnd && (int)zIn[1] == c ){ + /* Got one */ + return &zIn[1]; + } + /* Advance the cursor */ + zIn++; + } + /* No such option */ + return 0; +} +/* + * Check if a long option argument [i.e: --opt] is available in the command + * line string. Return a pointer to the start of the stream on success. + * NULL otherwise. + */ +static const char * VmFindLongOpt(const char *zLong,int nByte,const char *zIn,const char *zEnd) +{ + const char *zOpt; + while( zIn < zEnd ){ + if( zIn[0] == '-' && &zIn[1] < zEnd && (int)zIn[1] == '-' ){ + zIn += 2; + zOpt = zIn; + while( zIn < zEnd && !SyisSpace(zIn[0]) ){ + if( zIn[0] == '=' /* --opt=val */){ + break; + } + zIn++; + } + /* Test */ + if( (int)(zIn-zOpt) == nByte && SyMemcmp(zOpt,zLong,nByte) == 0 ){ + /* Got one,return it's value */ + return zIn; + } + + }else{ + zIn++; + } + } + /* No such option */ + return 0; +} +/* + * Long option [i.e: --opt] arguments private data structure. + */ +struct getopt_long_opt +{ + const char *zArgIn,*zArgEnd; /* Command line arguments */ + ph7_value *pWorker; /* Worker variable*/ + ph7_value *pArray; /* getopt() return value */ + ph7_context *pCtx; /* Call Context */ +}; +/* Forward declaration */ +static int VmProcessLongOpt(ph7_value *pKey,ph7_value *pValue,void *pUserData); +/* + * Extract short or long argument option values. + */ +static void VmExtractOptArgValue( + ph7_value *pArray, /* getopt() return value */ + ph7_value *pWorker, /* Worker variable */ + const char *zArg, /* Argument stream */ + const char *zArgEnd,/* End of the argument stream */ + int need_val, /* TRUE to fetch option argument */ + ph7_context *pCtx, /* Call Context */ + const char *zName /* Option name */) +{ + ph7_value_bool(pWorker,0); + if( !need_val ){ + /* + * Option does not need arguments. + * Insert the option name and a boolean FALSE. + */ + ph7_array_add_strkey_elem(pArray,(const char *)zName,pWorker); /* Will make it's own copy */ + }else{ + const char *zCur; + /* Extract option argument */ + zArg++; + if( zArg < zArgEnd && zArg[0] == '=' ){ + zArg++; + } + while( zArg < zArgEnd && (unsigned char)zArg[0] < 0xc0 && SyisSpace(zArg[0]) ){ + zArg++; + } + if( zArg >= zArgEnd || zArg[0] == '-' ){ + /* + * Argument not found. + * Insert the option name and a boolean FALSE. + */ + ph7_array_add_strkey_elem(pArray,(const char *)zName,pWorker); /* Will make it's own copy */ + return; + } + /* Delimit the value */ + zCur = zArg; + if( zArg[0] == '\'' || zArg[0] == '"' ){ + int d = zArg[0]; + /* Delimt the argument */ + zArg++; + zCur = zArg; + while( zArg < zArgEnd ){ + if( zArg[0] == d && zArg[-1] != '\\' ){ + /* Delimiter found,exit the loop */ + break; + } + zArg++; + } + /* Save the value */ + ph7_value_string(pWorker,zCur,(int)(zArg-zCur)); + if( zArg < zArgEnd ){ zArg++; } + }else{ + while( zArg < zArgEnd && !SyisSpace(zArg[0]) ){ + zArg++; + } + /* Save the value */ + ph7_value_string(pWorker,zCur,(int)(zArg-zCur)); + } + /* + * Check if we are dealing with multiple values. + * If so,create an array to hold them,rather than a scalar variable. + */ + while( zArg < zArgEnd && (unsigned char)zArg[0] < 0xc0 && SyisSpace(zArg[0]) ){ + zArg++; + } + if( zArg < zArgEnd && zArg[0] != '-' ){ + ph7_value *pOptArg; /* Array of option arguments */ + pOptArg = ph7_context_new_array(pCtx); + if( pOptArg == 0 ){ + ph7_context_throw_error(pCtx,PH7_CTX_ERR,"PH7 is running out of memory"); + }else{ + /* Insert the first value */ + ph7_array_add_elem(pOptArg,0,pWorker); /* Will make it's own copy */ + for(;;){ + if( zArg >= zArgEnd || zArg[0] == '-' ){ + /* No more value */ + break; + } + /* Delimit the value */ + zCur = zArg; + if( zArg < zArgEnd && zArg[0] == '\\' ){ + zArg++; + zCur = zArg; + } + while( zArg < zArgEnd && !SyisSpace(zArg[0]) ){ + zArg++; + } + /* Reset the string cursor */ + ph7_value_reset_string_cursor(pWorker); + /* Save the value */ + ph7_value_string(pWorker,zCur,(int)(zArg-zCur)); + /* Insert */ + ph7_array_add_elem(pOptArg,0,pWorker); /* Will make it's own copy */ + /* Jump trailing white spaces */ + while( zArg < zArgEnd && (unsigned char)zArg[0] < 0xc0 && SyisSpace(zArg[0]) ){ + zArg++; + } + } + /* Insert the option arg array */ + ph7_array_add_strkey_elem(pArray,(const char *)zName,pOptArg); /* Will make it's own copy */ + /* Safely release */ + ph7_context_release_value(pCtx,pOptArg); + } + }else{ + /* Single value */ + ph7_array_add_strkey_elem(pArray,(const char *)zName,pWorker); /* Will make it's own copy */ + } + } +} +/* + * array getopt(string $options[,array $longopts ]) + * Gets options from the command line argument list. + * Parameters + * $options + * Each character in this string will be used as option characters + * and matched against options passed to the script starting with + * a single hyphen (-). For example, an option string "x" recognizes + * an option -x. Only a-z, A-Z and 0-9 are allowed. + * $longopts + * An array of options. Each element in this array will be used as option + * strings and matched against options passed to the script starting with + * two hyphens (--). For example, an longopts element "opt" recognizes an + * option --opt. + * Return + * This function will return an array of option / argument pairs or FALSE + * on failure. + */ +static int vm_builtin_getopt(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + const char *zIn,*zEnd,*zArg,*zArgIn,*zArgEnd; + struct getopt_long_opt sLong; + ph7_value *pArray,*pWorker; + SyBlob *pArg; + int nByte; + if( nArg < 1 || !ph7_value_is_string(apArg[0]) ){ + /* Missing/Invalid arguments,return FALSE */ + ph7_context_throw_error(pCtx,PH7_CTX_ERR,"Missing/Invalid option arguments"); + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Extract option arguments */ + zIn = ph7_value_to_string(apArg[0],&nByte); + zEnd = &zIn[nByte]; + /* Point to the string representation of the $argv[] array */ + pArg = &pCtx->pVm->sArgv; + /* Create a new empty array and a worker variable */ + pArray = ph7_context_new_array(pCtx); + pWorker = ph7_context_new_scalar(pCtx); + if( pArray == 0 || pWorker == 0 ){ + ph7_context_throw_error(pCtx,PH7_CTX_ERR,"PH7 is running out of memory"); + ph7_result_bool(pCtx,0); + return PH7_OK; + } + if( SyBlobLength(pArg) < 1 ){ + /* Empty command line,return the empty array*/ + ph7_result_value(pCtx,pArray); + /* Everything will be released automatically when we return + * from this function. + */ + return PH7_OK; + } + zArgIn = (const char *)SyBlobData(pArg); + zArgEnd = &zArgIn[SyBlobLength(pArg)]; + /* Fill the long option structure */ + sLong.pArray = pArray; + sLong.pWorker = pWorker; + sLong.zArgIn = zArgIn; + sLong.zArgEnd = zArgEnd; + sLong.pCtx = pCtx; + /* Start processing */ + while( zIn < zEnd ){ + int c = zIn[0]; + int need_val = 0; + /* Advance the stream cursor */ + zIn++; + /* Ignore non-alphanum characters */ + if( !SyisAlphaNum(c) ){ + continue; + } + if( zIn < zEnd && zIn[0] == ':' ){ + zIn++; + need_val = 1; + if( zIn < zEnd && zIn[0] == ':' ){ + zIn++; + } + } + /* Find option */ + zArg = VmFindShortOpt(c,zArgIn,zArgEnd); + if( zArg == 0 ){ + /* No such option */ + continue; + } + /* Extract option argument value */ + VmExtractOptArgValue(pArray,pWorker,zArg,zArgEnd,need_val,pCtx,(const char *)&c); + } + if( nArg > 1 && ph7_value_is_array(apArg[1]) && ph7_array_count(apArg[1]) > 0 ){ + /* Process long options */ + ph7_array_walk(apArg[1],VmProcessLongOpt,&sLong); + } + /* Return the option array */ + ph7_result_value(pCtx,pArray); + /* + * Don't worry about freeing memory, everything will be released + * automatically as soon we return from this foreign function. + */ + return PH7_OK; +} +/* + * Array walker callback used for processing long options values. + */ +static int VmProcessLongOpt(ph7_value *pKey,ph7_value *pValue,void *pUserData) +{ + struct getopt_long_opt *pOpt = (struct getopt_long_opt *)pUserData; + const char *zArg,*zOpt,*zEnd; + int need_value = 0; + int nByte; + /* Value must be of type string */ + if( !ph7_value_is_string(pValue) ){ + /* Simply ignore */ + return PH7_OK; + } + zOpt = ph7_value_to_string(pValue,&nByte); + if( nByte < 1 ){ + /* Empty string,ignore */ + return PH7_OK; + } + zEnd = &zOpt[nByte - 1]; + if( zEnd[0] == ':' ){ + char *zTerm; + /* Try to extract a value */ + need_value = 1; + while( zEnd >= zOpt && zEnd[0] == ':' ){ + zEnd--; + } + if( zOpt >= zEnd ){ + /* Empty string,ignore */ + SXUNUSED(pKey); + return PH7_OK; + } + zEnd++; + zTerm = (char *)zEnd; + zTerm[0] = 0; + }else{ + zEnd = &zOpt[nByte]; + } + /* Find the option */ + zArg = VmFindLongOpt(zOpt,(int)(zEnd-zOpt),pOpt->zArgIn,pOpt->zArgEnd); + if( zArg == 0 ){ + /* No such option,return immediately */ + return PH7_OK; + } + /* Try to extract a value */ + VmExtractOptArgValue(pOpt->pArray,pOpt->pWorker,zArg,pOpt->zArgEnd,need_value,pOpt->pCtx,zOpt); + return PH7_OK; +} +/* + * Section: + * JSON encoding/decoding routines. + * Authors: + * Symisc Systems,devel@symisc.net. + * Copyright (C) Symisc Systems,http://ph7.symisc.net + * Status: + * Devel. + */ +/* Forward reference */ +static int VmJsonArrayEncode(ph7_value *pKey,ph7_value *pValue,void *pUserData); +static int VmJsonObjectEncode(const char *zAttr,ph7_value *pValue,void *pUserData); +/* + * JSON encoder state is stored in an instance + * of the following structure. + */ +typedef struct json_private_data json_private_data; +struct json_private_data +{ + ph7_context *pCtx; /* Call context */ + int isFirst; /* True if first encoded entry */ + int iFlags; /* JSON encoding flags */ + int nRecCount; /* Recursion count */ +}; +/* + * Returns the JSON representation of a value.In other word perform a JSON encoding operation. + * According to wikipedia + * JSON's basic types are: + * Number (double precision floating-point format in JavaScript, generally depends on implementation) + * String (double-quoted Unicode, with backslash escaping) + * Boolean (true or false) + * Array (an ordered sequence of values, comma-separated and enclosed in square brackets; the values + * do not need to be of the same type) + * Object (an unordered collection of key:value pairs with the ':' character separating the key + * and the value, comma-separated and enclosed in curly braces; the keys must be strings and should + * be distinct from each other) + * null (empty) + * Non-significant white space may be added freely around the "structural characters" + * (i.e. the brackets "[{]}", colon ":" and comma ","). + */ +static sxi32 VmJsonEncode( + ph7_value *pIn, /* Encode this value */ + json_private_data *pData /* Context data */ + ){ + ph7_context *pCtx = pData->pCtx; + int iFlags = pData->iFlags; + int nByte; + if( ph7_value_is_null(pIn) || ph7_value_is_resource(pIn)){ + /* null */ + ph7_result_string(pCtx,"null",(int)sizeof("null")-1); + }else if( ph7_value_is_bool(pIn) ){ + int iBool = ph7_value_to_bool(pIn); + int iLen; + /* true/false */ + iLen = iBool ? (int)sizeof("true") : (int)sizeof("false"); + ph7_result_string(pCtx,iBool ? "true" : "false",iLen-1); + }else if( ph7_value_is_numeric(pIn) && !ph7_value_is_string(pIn) ){ + const char *zNum; + /* Get a string representation of the number */ + zNum = ph7_value_to_string(pIn,&nByte); + ph7_result_string(pCtx,zNum,nByte); + }else if( ph7_value_is_string(pIn) ){ + if( (iFlags & JSON_NUMERIC_CHECK) && ph7_value_is_numeric(pIn) ){ + const char *zNum; + /* Encodes numeric strings as numbers. */ + PH7_MemObjToReal(pIn); /* Force a numeric cast */ + /* Get a string representation of the number */ + zNum = ph7_value_to_string(pIn,&nByte); + ph7_result_string(pCtx,zNum,nByte); + }else{ + const char *zIn,*zEnd; + int c; + /* Encode the string */ + zIn = ph7_value_to_string(pIn,&nByte); + zEnd = &zIn[nByte]; + /* Append the double quote */ + ph7_result_string(pCtx,"\"",(int)sizeof(char)); + for(;;){ + if( zIn >= zEnd ){ + /* No more input to process */ + break; + } + c = zIn[0]; + /* Advance the stream cursor */ + zIn++; + if( (c == '<' || c == '>') && (iFlags & JSON_HEX_TAG) ){ + /* All < and > are converted to \u003C and \u003E */ + if( c == '<' ){ + ph7_result_string(pCtx,"\\u003C",(int)sizeof("\\u003C")-1); + }else{ + ph7_result_string(pCtx,"\\u003E",(int)sizeof("\\u003E")-1); + } + continue; + }else if( c == '&' && (iFlags & JSON_HEX_AMP) ){ + /* All &s are converted to \u0026. */ + ph7_result_string(pCtx,"\\u0026",(int)sizeof("\\u0026")-1); + continue; + }else if( c == '\'' && (iFlags & JSON_HEX_APOS) ){ + /* All ' are converted to \u0027. */ + ph7_result_string(pCtx,"\\u0027",(int)sizeof("\\u0027")-1); + continue; + }else if( c == '"' && (iFlags & JSON_HEX_QUOT) ){ + /* All " are converted to \u0022. */ + ph7_result_string(pCtx,"\\u0022",(int)sizeof("\\u0022")-1); + continue; + } + if( c == '"' || (c == '\\' && ((iFlags & JSON_UNESCAPED_SLASHES)==0)) ){ + /* Unescape the character */ + ph7_result_string(pCtx,"\\",(int)sizeof(char)); + } + /* Append character verbatim */ + ph7_result_string(pCtx,(const char *)&c,(int)sizeof(char)); + } + /* Append the double quote */ + ph7_result_string(pCtx,"\"",(int)sizeof(char)); + } + }else if( ph7_value_is_array(pIn) ){ + int c = '[',d = ']'; + /* Encode the array */ + pData->isFirst = 1; + if( iFlags & JSON_FORCE_OBJECT ){ + /* Outputs an object rather than an array */ + c = '{'; + d = '}'; + } + /* Append the square bracket or curly braces */ + ph7_result_string(pCtx,(const char *)&c,(int)sizeof(char)); + /* Iterate throw array entries */ + ph7_array_walk(pIn,VmJsonArrayEncode,pData); + /* Append the closing square bracket or curly braces */ + ph7_result_string(pCtx,(const char *)&d,(int)sizeof(char)); + }else if( ph7_value_is_object(pIn) ){ + /* Encode the class instance */ + pData->isFirst = 1; + /* Append the curly braces */ + ph7_result_string(pCtx,"{",(int)sizeof(char)); + /* Iterate throw class attribute */ + ph7_object_walk(pIn,VmJsonObjectEncode,pData); + /* Append the closing curly braces */ + ph7_result_string(pCtx,"}",(int)sizeof(char)); + }else{ + /* Can't happen */ + ph7_result_string(pCtx,"null",(int)sizeof("null")-1); + } + /* All done */ + return PH7_OK; +} +/* + * The following walker callback is invoked each time we need + * to encode an array to JSON. + */ +static int VmJsonArrayEncode(ph7_value *pKey,ph7_value *pValue,void *pUserData) +{ + json_private_data *pJson = (json_private_data *)pUserData; + if( pJson->nRecCount > 31 ){ + /* Recursion limit reached,return immediately */ + return PH7_OK; + } + if( !pJson->isFirst ){ + /* Append the colon first */ + ph7_result_string(pJson->pCtx,",",(int)sizeof(char)); + } + if( pJson->iFlags & JSON_FORCE_OBJECT ){ + /* Outputs an object rather than an array */ + const char *zKey; + int nByte; + /* Extract a string representation of the key */ + zKey = ph7_value_to_string(pKey,&nByte); + /* Append the key and the double colon */ + ph7_result_string_format(pJson->pCtx,"\"%.*s\":",nByte,zKey); + } + /* Encode the value */ + pJson->nRecCount++; + VmJsonEncode(pValue,pJson); + pJson->nRecCount--; + pJson->isFirst = 0; + return PH7_OK; +} +/* + * The following walker callback is invoked each time we need to encode + * a class instance [i.e: Object in the PHP jargon] to JSON. + */ +static int VmJsonObjectEncode(const char *zAttr,ph7_value *pValue,void *pUserData) +{ + json_private_data *pJson = (json_private_data *)pUserData; + if( pJson->nRecCount > 31 ){ + /* Recursion limit reached,return immediately */ + return PH7_OK; + } + if( !pJson->isFirst ){ + /* Append the colon first */ + ph7_result_string(pJson->pCtx,",",(int)sizeof(char)); + } + /* Append the attribute name and the double colon first */ + ph7_result_string_format(pJson->pCtx,"\"%s\":",zAttr); + /* Encode the value */ + pJson->nRecCount++; + VmJsonEncode(pValue,pJson); + pJson->nRecCount--; + pJson->isFirst = 0; + return PH7_OK; +} +/* + * string json_encode(mixed $value [, int $options = 0 ]) + * Returns a string containing the JSON representation of value. + * Parameters + * $value + * The value being encoded. Can be any type except a resource. + * $options + * Bitmask consisting of: + * JSON_HEX_TAG All < and > are converted to \u003C and \u003E. + * JSON_HEX_AMP All &s are converted to \u0026. + * JSON_HEX_APOS All ' are converted to \u0027. + * JSON_HEX_QUOT All " are converted to \u0022. + * JSON_FORCE_OBJECT Outputs an object rather than an array. + * JSON_NUMERIC_CHECK Encodes numeric strings as numbers. + * JSON_BIGINT_AS_STRING Not used + * JSON_PRETTY_PRINT Use whitespace in returned data to format it. + * JSON_UNESCAPED_SLASHES Don't escape '/' + * JSON_UNESCAPED_UNICODE Not used. + * Return + * Returns a JSON encoded string on success. FALSE otherwise + */ +static int vm_builtin_json_encode(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + json_private_data sJson; + if( nArg < 1 ){ + /* Missing arguments,return FALSE */ + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Prepare the JSON data */ + sJson.nRecCount = 0; + sJson.pCtx = pCtx; + sJson.isFirst = 1; + sJson.iFlags = 0; + if( nArg > 1 && ph7_value_is_int(apArg[1]) ){ + /* Extract option flags */ + sJson.iFlags = ph7_value_to_int(apArg[1]); + } + /* Perform the encoding operation */ + VmJsonEncode(apArg[0],&sJson); + /* All done */ + return PH7_OK; +} +/* + * int json_last_error(void) + * Returns the last error (if any) occurred during the last JSON encoding/decoding. + * Parameters + * None + * Return + * Returns an integer, the value can be one of the following constants: + * JSON_ERROR_NONE No error has occurred. + * JSON_ERROR_DEPTH The maximum stack depth has been exceeded. + * JSON_ERROR_STATE_MISMATCH Invalid or malformed JSON. + * JSON_ERROR_CTRL_CHAR Control character error, possibly incorrectly encoded. + * JSON_ERROR_SYNTAX Syntax error. + * JSON_ERROR_UTF8_CHECK Malformed UTF-8 characters. + */ +static int vm_builtin_json_last_error(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + ph7_vm *pVm = pCtx->pVm; + /* Return the error code */ + ph7_result_int(pCtx,pVm->json_rc); + SXUNUSED(nArg); /* cc warning */ + SXUNUSED(apArg); + return PH7_OK; +} +/* Possible tokens from the JSON tokenization process */ +#define JSON_TK_TRUE 0x001 /* Boolean true */ +#define JSON_TK_FALSE 0x002 /* Boolean false */ +#define JSON_TK_STR 0x004 /* String enclosed in double quotes */ +#define JSON_TK_NULL 0x008 /* null */ +#define JSON_TK_NUM 0x010 /* Numeric */ +#define JSON_TK_OCB 0x020 /* Open curly braces '{' */ +#define JSON_TK_CCB 0x040 /* Closing curly braces '}' */ +#define JSON_TK_OSB 0x080 /* Open square bracke '[' */ +#define JSON_TK_CSB 0x100 /* Closing square bracket ']' */ +#define JSON_TK_COLON 0x200 /* Single colon ':' */ +#define JSON_TK_COMMA 0x400 /* Single comma ',' */ +#define JSON_TK_INVALID 0x800 /* Unexpected token */ +/* + * Tokenize an entire JSON input. + * Get a single low-level token from the input file. + * Update the stream pointer so that it points to the first + * character beyond the extracted token. + */ +static sxi32 VmJsonTokenize(SyStream *pStream,SyToken *pToken,void *pUserData,void *pCtxData) +{ + int *pJsonErr = (int *)pUserData; + SyString *pStr; + int c; + /* Ignore leading white spaces */ + while( pStream->zText < pStream->zEnd && pStream->zText[0] < 0xc0 && SyisSpace(pStream->zText[0]) ){ + /* Advance the stream cursor */ + if( pStream->zText[0] == '\n' ){ + /* Update line counter */ + pStream->nLine++; + } + pStream->zText++; + } + if( pStream->zText >= pStream->zEnd ){ + /* End of input reached */ + SXUNUSED(pCtxData); /* cc warning */ + return SXERR_EOF; + } + /* Record token starting position and line */ + pToken->nLine = pStream->nLine; + pToken->pUserData = 0; + pStr = &pToken->sData; + SyStringInitFromBuf(pStr,pStream->zText,0); + if( pStream->zText[0] == '{' || pStream->zText[0] == '[' || pStream->zText[0] == '}' || pStream->zText[0] == ']' + || pStream->zText[0] == ':' || pStream->zText[0] == ',' ){ + /* Single character */ + c = pStream->zText[0]; + /* Set token type */ + switch(c){ + case '[': pToken->nType = JSON_TK_OSB; break; + case '{': pToken->nType = JSON_TK_OCB; break; + case '}': pToken->nType = JSON_TK_CCB; break; + case ']': pToken->nType = JSON_TK_CSB; break; + case ':': pToken->nType = JSON_TK_COLON; break; + case ',': pToken->nType = JSON_TK_COMMA; break; + default: + break; + } + /* Advance the stream cursor */ + pStream->zText++; + }else if( pStream->zText[0] == '"') { + /* JSON string */ + pStream->zText++; + pStr->zString++; + /* Delimit the string */ + while( pStream->zText < pStream->zEnd ){ + if( pStream->zText[0] == '"' && pStream->zText[-1] != '\\' ){ + break; + } + if( pStream->zText[0] == '\n' ){ + /* Update line counter */ + pStream->nLine++; + } + pStream->zText++; + } + if( pStream->zText >= pStream->zEnd ){ + /* Missing closing '"' */ + pToken->nType = JSON_TK_INVALID; + *pJsonErr = JSON_ERROR_SYNTAX; + }else{ + pToken->nType = JSON_TK_STR; + pStream->zText++; /* Jump the closing double quotes */ + } + }else if( pStream->zText[0] < 0xc0 && SyisDigit(pStream->zText[0]) ){ + /* Number */ + pStream->zText++; + pToken->nType = JSON_TK_NUM; + while( pStream->zText < pStream->zEnd && pStream->zText[0] < 0xc0 && SyisDigit(pStream->zText[0]) ){ + pStream->zText++; + } + if( pStream->zText < pStream->zEnd ){ + c = pStream->zText[0]; + if( c == '.' ){ + /* Real number */ + pStream->zText++; + while( pStream->zText < pStream->zEnd && pStream->zText[0] < 0xc0 && SyisDigit(pStream->zText[0]) ){ + pStream->zText++; + } + if( pStream->zText < pStream->zEnd ){ + c = pStream->zText[0]; + if( c=='e' || c=='E' ){ + pStream->zText++; + if( pStream->zText < pStream->zEnd ){ + c = pStream->zText[0]; + if( c =='+' || c=='-' ){ + pStream->zText++; + } + while( pStream->zText < pStream->zEnd && pStream->zText[0] < 0xc0 && SyisDigit(pStream->zText[0]) ){ + pStream->zText++; + } + } + } + } + }else if( c=='e' || c=='E' ){ + /* Real number */ + pStream->zText++; + if( pStream->zText < pStream->zEnd ){ + c = pStream->zText[0]; + if( c =='+' || c=='-' ){ + pStream->zText++; + } + while( pStream->zText < pStream->zEnd && pStream->zText[0] < 0xc0 && SyisDigit(pStream->zText[0]) ){ + pStream->zText++; + } + } + } + } + }else if( XLEX_IN_LEN(pStream) >= sizeof("true") -1 && + SyStrnicmp((const char *)pStream->zText,"true",sizeof("true")-1) == 0 ){ + /* boolean true */ + pToken->nType = JSON_TK_TRUE; + /* Advance the stream cursor */ + pStream->zText += sizeof("true")-1; + }else if( XLEX_IN_LEN(pStream) >= sizeof("false") -1 && + SyStrnicmp((const char *)pStream->zText,"false",sizeof("false")-1) == 0 ){ + /* boolean false */ + pToken->nType = JSON_TK_FALSE; + /* Advance the stream cursor */ + pStream->zText += sizeof("false")-1; + }else if( XLEX_IN_LEN(pStream) >= sizeof("null") -1 && + SyStrnicmp((const char *)pStream->zText,"null",sizeof("null")-1) == 0 ){ + /* NULL */ + pToken->nType = JSON_TK_NULL; + /* Advance the stream cursor */ + pStream->zText += sizeof("null")-1; + }else{ + /* Unexpected token */ + pToken->nType = JSON_TK_INVALID; + /* Advance the stream cursor */ + pStream->zText++; + *pJsonErr = JSON_ERROR_SYNTAX; + /* Abort processing immediatley */ + return SXERR_ABORT; + } + /* record token length */ + pStr->nByte = (sxu32)((const char *)pStream->zText-pStr->zString); + if( pToken->nType == JSON_TK_STR ){ + pStr->nByte--; + } + /* Return to the lexer */ + return SXRET_OK; +} +/* + * JSON decoded input consumer callback signature. + */ +typedef int (*ProcJsonConsumer)(ph7_context *,ph7_value *,ph7_value *,void *); +/* + * JSON decoder state is kept in the following structure. + */ +typedef struct json_decoder json_decoder; +struct json_decoder +{ + ph7_context *pCtx; /* Call context */ + ProcJsonConsumer xConsumer; /* Consumer callback */ + void *pUserData; /* Last argument to xConsumer() */ + int iFlags; /* Configuration flags */ + SyToken *pIn; /* Token stream */ + SyToken *pEnd; /* End of the token stream */ + int rec_depth; /* Recursion limit */ + int rec_count; /* Current nesting level */ + int *pErr; /* JSON decoding error if any */ +}; +#define JSON_DECODE_ASSOC 0x01 /* Decode a JSON object as an associative array */ +/* Forward declaration */ +static int VmJsonArrayDecoder(ph7_context *pCtx,ph7_value *pKey,ph7_value *pWorker,void *pUserData); +/* + * Dequote [i.e: Resolve all backslash escapes ] a JSON string and store + * the result in the given ph7_value. + */ +static void VmJsonDequoteString(const SyString *pStr,ph7_value *pWorker) +{ + const char *zIn = pStr->zString; + const char *zEnd = &pStr->zString[pStr->nByte]; + const char *zCur; + int c; + /* Mark the value as a string */ + ph7_value_string(pWorker,"",0); /* Empty string */ + for(;;){ + zCur = zIn; + while( zIn < zEnd && zIn[0] != '\\' ){ + zIn++; + } + if( zIn > zCur ){ + /* Append chunk verbatim */ + ph7_value_string(pWorker,zCur,(int)(zIn-zCur)); + } + zIn++; + if( zIn >= zEnd ){ + /* End of the input reached */ + break; + } + c = zIn[0]; + /* Unescape the character */ + switch(c){ + case '"': ph7_value_string(pWorker,(const char *)&c,(int)sizeof(char)); break; + case '\\': ph7_value_string(pWorker,(const char *)&c,(int)sizeof(char)); break; + case 'n': ph7_value_string(pWorker,"\n",(int)sizeof(char)); break; + case 'r': ph7_value_string(pWorker,"\r",(int)sizeof(char)); break; + case 't': ph7_value_string(pWorker,"\t",(int)sizeof(char)); break; + case 'f': ph7_value_string(pWorker,"\f",(int)sizeof(char)); break; + default: + ph7_value_string(pWorker,(const char *)&c,(int)sizeof(char)); + break; + } + /* Advance the stream cursor */ + zIn++; + } +} +/* + * Returns a ph7_value holding the image of a JSON string. In other word perform a JSON decoding operation. + * According to wikipedia + * JSON's basic types are: + * Number (double precision floating-point format in JavaScript, generally depends on implementation) + * String (double-quoted Unicode, with backslash escaping) + * Boolean (true or false) + * Array (an ordered sequence of values, comma-separated and enclosed in square brackets; the values + * do not need to be of the same type) + * Object (an unordered collection of key:value pairs with the ':' character separating the key + * and the value, comma-separated and enclosed in curly braces; the keys must be strings and should + * be distinct from each other) + * null (empty) + * Non-significant white space may be added freely around the "structural characters" (i.e. the brackets "[{]}", colon ":" and comma ","). + */ +static sxi32 VmJsonDecode( + json_decoder *pDecoder, /* JSON decoder */ + ph7_value *pArrayKey /* Key for the decoded array */ + ){ + ph7_value *pWorker; /* Worker variable */ + sxi32 rc; + /* Check if we do not nest to much */ + if( pDecoder->rec_count >= pDecoder->rec_depth ){ + /* Nesting limit reached,abort decoding immediately */ + *pDecoder->pErr = JSON_ERROR_DEPTH; + return SXERR_ABORT; + } + if( pDecoder->pIn->nType & (JSON_TK_STR|JSON_TK_TRUE|JSON_TK_FALSE|JSON_TK_NULL|JSON_TK_NUM) ){ + /* Scalar value */ + pWorker = ph7_context_new_scalar(pDecoder->pCtx); + if( pWorker == 0 ){ + ph7_context_throw_error(pDecoder->pCtx,PH7_CTX_ERR,"PH7 is running out of memory"); + /* Abort the decoding operation immediately */ + return SXERR_ABORT; + } + /* Reflect the JSON image */ + if( pDecoder->pIn->nType & JSON_TK_NULL ){ + /* Nullify the value.*/ + ph7_value_null(pWorker); + }else if( pDecoder->pIn->nType & (JSON_TK_TRUE|JSON_TK_FALSE) ){ + /* Boolean value */ + ph7_value_bool(pWorker,(pDecoder->pIn->nType & JSON_TK_TRUE) ? 1 : 0 ); + }else if( pDecoder->pIn->nType & JSON_TK_NUM ){ + SyString *pStr = &pDecoder->pIn->sData; + /* + * Numeric value. + * Get a string representation first then try to get a numeric + * value. + */ + ph7_value_string(pWorker,pStr->zString,(int)pStr->nByte); + /* Obtain a numeric representation */ + PH7_MemObjToNumeric(pWorker); + }else{ + /* Dequote the string */ + VmJsonDequoteString(&pDecoder->pIn->sData,pWorker); + } + /* Invoke the consumer callback */ + rc = pDecoder->xConsumer(pDecoder->pCtx,pArrayKey,pWorker,pDecoder->pUserData); + if( rc == SXERR_ABORT ){ + return SXERR_ABORT; + } + /* All done,advance the stream cursor */ + pDecoder->pIn++; + }else if( pDecoder->pIn->nType & JSON_TK_OSB /*'[' */) { + ProcJsonConsumer xOld; + void *pOld; + /* Array representation*/ + pDecoder->pIn++; + /* Create a working array */ + pWorker = ph7_context_new_array(pDecoder->pCtx); + if( pWorker == 0 ){ + ph7_context_throw_error(pDecoder->pCtx,PH7_CTX_ERR,"PH7 is running out of memory"); + /* Abort the decoding operation immediately */ + return SXERR_ABORT; + } + /* Save the old consumer */ + xOld = pDecoder->xConsumer; + pOld = pDecoder->pUserData; + /* Set the new consumer */ + pDecoder->xConsumer = VmJsonArrayDecoder; + pDecoder->pUserData = pWorker; + /* Decode the array */ + for(;;){ + /* Jump trailing comma. Note that the standard PHP engine will not let you + * do this. + */ + while( (pDecoder->pIn < pDecoder->pEnd) && (pDecoder->pIn->nType & JSON_TK_COMMA) ){ + pDecoder->pIn++; + } + if( pDecoder->pIn >= pDecoder->pEnd || (pDecoder->pIn->nType & JSON_TK_CSB) /*']'*/ ){ + if( pDecoder->pIn < pDecoder->pEnd ){ + pDecoder->pIn++; /* Jump the trailing ']' */ + } + break; + } + /* Recurse and decode the entry */ + pDecoder->rec_count++; + rc = VmJsonDecode(pDecoder,0); + pDecoder->rec_count--; + if( rc == SXERR_ABORT ){ + /* Abort processing immediately */ + return SXERR_ABORT; + } + /*The cursor is automatically advanced by the VmJsonDecode() function */ + if( (pDecoder->pIn < pDecoder->pEnd) && + ((pDecoder->pIn->nType & (JSON_TK_CSB/*']'*/|JSON_TK_COMMA/*','*/))==0) ){ + /* Unexpected token,abort immediatley */ + *pDecoder->pErr = JSON_ERROR_SYNTAX; + return SXERR_ABORT; + } + } + /* Restore the old consumer */ + pDecoder->xConsumer = xOld; + pDecoder->pUserData = pOld; + /* Invoke the old consumer on the decoded array */ + xOld(pDecoder->pCtx,pArrayKey,pWorker,pOld); + }else if( pDecoder->pIn->nType & JSON_TK_OCB /*'{' */) { + ProcJsonConsumer xOld; + ph7_value *pKey; + void *pOld; + /* Object representation*/ + pDecoder->pIn++; + /* Return the object as an associative array */ + if( (pDecoder->iFlags & JSON_DECODE_ASSOC) == 0 ){ + ph7_context_throw_error(pDecoder->pCtx,PH7_CTX_WARNING, + "JSON Objects are always returned as an associative array" + ); + } + /* Create a working array */ + pWorker = ph7_context_new_array(pDecoder->pCtx); + pKey = ph7_context_new_scalar(pDecoder->pCtx); + if( pWorker == 0 || pKey == 0){ + ph7_context_throw_error(pDecoder->pCtx,PH7_CTX_ERR,"PH7 is running out of memory"); + /* Abort the decoding operation immediately */ + return SXERR_ABORT; + } + /* Save the old consumer */ + xOld = pDecoder->xConsumer; + pOld = pDecoder->pUserData; + /* Set the new consumer */ + pDecoder->xConsumer = VmJsonArrayDecoder; + pDecoder->pUserData = pWorker; + /* Decode the object */ + for(;;){ + /* Jump trailing comma. Note that the standard PHP engine will not let you + * do this. + */ + while( (pDecoder->pIn < pDecoder->pEnd) && (pDecoder->pIn->nType & JSON_TK_COMMA) ){ + pDecoder->pIn++; + } + if( pDecoder->pIn >= pDecoder->pEnd || (pDecoder->pIn->nType & JSON_TK_CCB) /*'}'*/ ){ + if( pDecoder->pIn < pDecoder->pEnd ){ + pDecoder->pIn++; /* Jump the trailing ']' */ + } + break; + } + if( (pDecoder->pIn->nType & JSON_TK_STR) == 0 || &pDecoder->pIn[1] >= pDecoder->pEnd + || (pDecoder->pIn[1].nType & JSON_TK_COLON) == 0){ + /* Syntax error,return immediately */ + *pDecoder->pErr = JSON_ERROR_SYNTAX; + return SXERR_ABORT; + } + /* Dequote the key */ + VmJsonDequoteString(&pDecoder->pIn->sData,pKey); + /* Jump the key and the colon */ + pDecoder->pIn += 2; + /* Recurse and decode the value */ + pDecoder->rec_count++; + rc = VmJsonDecode(pDecoder,pKey); + pDecoder->rec_count--; + if( rc == SXERR_ABORT ){ + /* Abort processing immediately */ + return SXERR_ABORT; + } + /* Reset the internal buffer of the key */ + ph7_value_reset_string_cursor(pKey); + /*The cursor is automatically advanced by the VmJsonDecode() function */ + } + /* Restore the old consumer */ + pDecoder->xConsumer = xOld; + pDecoder->pUserData = pOld; + /* Invoke the old consumer on the decoded object*/ + xOld(pDecoder->pCtx,pArrayKey,pWorker,pOld); + /* Release the key */ + ph7_context_release_value(pDecoder->pCtx,pKey); + }else{ + /* Unexpected token */ + return SXERR_ABORT; /* Abort immediately */ + } + /* Release the worker variable */ + ph7_context_release_value(pDecoder->pCtx,pWorker); + return SXRET_OK; +} +/* + * The following JSON decoder callback is invoked each time + * a JSON array representation [i.e: [15,"hello",FALSE] ] + * is being decoded. + */ +static int VmJsonArrayDecoder(ph7_context *pCtx,ph7_value *pKey,ph7_value *pWorker,void *pUserData) +{ + ph7_value *pArray = (ph7_value *)pUserData; + /* Insert the entry */ + ph7_array_add_elem(pArray,pKey,pWorker); /* Will make it's own copy */ + SXUNUSED(pCtx); /* cc warning */ + /* All done */ + return SXRET_OK; +} +/* + * Standard JSON decoder callback. + */ +static int VmJsonDefaultDecoder(ph7_context *pCtx,ph7_value *pKey,ph7_value *pWorker,void *pUserData) +{ + /* Return the value directly */ + ph7_result_value(pCtx,pWorker); /* Will make it's own copy */ + SXUNUSED(pKey); /* cc warning */ + SXUNUSED(pUserData); + /* All done */ + return SXRET_OK; +} +/* + * mixed json_decode(string $json[,bool $assoc = false[,int $depth = 32[,int $options = 0 ]]]) + * Takes a JSON encoded string and converts it into a PHP variable. + * Parameters + * $json + * The json string being decoded. + * $assoc + * When TRUE, returned objects will be converted into associative arrays. + * $depth + * User specified recursion depth. + * $options + * Bitmask of JSON decode options. Currently only JSON_BIGINT_AS_STRING is supported + * (default is to cast large integers as floats) + * Return + * The value encoded in json in appropriate PHP type. Values true, false and null (case-insensitive) + * are returned as TRUE, FALSE and NULL respectively. NULL is returned if the json cannot be decoded + * or if the encoded data is deeper than the recursion limit. + */ +static int vm_builtin_json_decode(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + ph7_vm *pVm = pCtx->pVm; + json_decoder sDecoder; + const char *zIn; + SySet sToken; + SyLex sLex; + int nByte; + sxi32 rc; + if( nArg < 1 || !ph7_value_is_string(apArg[0]) ){ + /* Missing/Invalid arguments, return NULL */ + ph7_result_null(pCtx); + return PH7_OK; + } + /* Extract the JSON string */ + zIn = ph7_value_to_string(apArg[0],&nByte); + if( nByte < 1 ){ + /* Empty string,return NULL */ + ph7_result_null(pCtx); + return PH7_OK; + } + /* Clear JSON error code */ + pVm->json_rc = JSON_ERROR_NONE; + /* Tokenize the input */ + SySetInit(&sToken,&pVm->sAllocator,sizeof(SyToken)); + SyLexInit(&sLex,&sToken,VmJsonTokenize,&pVm->json_rc); + SyLexTokenizeInput(&sLex,zIn,(sxu32)nByte,0,0,0); + if( pVm->json_rc != JSON_ERROR_NONE ){ + /* Something goes wrong while tokenizing input. [i.e: Unexpected token] */ + SyLexRelease(&sLex); + SySetRelease(&sToken); + /* return NULL */ + ph7_result_null(pCtx); + return PH7_OK; + } + /* Fill the decoder */ + sDecoder.pCtx = pCtx; + sDecoder.pErr = &pVm->json_rc; + sDecoder.pIn = (SyToken *)SySetBasePtr(&sToken); + sDecoder.pEnd = &sDecoder.pIn[SySetUsed(&sToken)]; + sDecoder.iFlags = 0; + if( nArg > 1 && ph7_value_to_bool(apArg[1]) != 0 ){ + /* Returned objects will be converted into associative arrays */ + sDecoder.iFlags |= JSON_DECODE_ASSOC; + } + sDecoder.rec_depth = 32; + if( nArg > 2 && ph7_value_is_int(apArg[2]) ){ + int nDepth = ph7_value_to_int(apArg[2]); + if( nDepth > 1 && nDepth < 32 ){ + sDecoder.rec_depth = nDepth; + } + } + sDecoder.rec_count = 0; + /* Set a default consumer */ + sDecoder.xConsumer = VmJsonDefaultDecoder; + sDecoder.pUserData = 0; + /* Decode the raw JSON input */ + rc = VmJsonDecode(&sDecoder,0); + if( rc == SXERR_ABORT || pVm->json_rc != JSON_ERROR_NONE ){ + /* + * Something goes wrong while decoding JSON input.Return NULL. + */ + ph7_result_null(pCtx); + } + /* Clean-up the mess left behind */ + SyLexRelease(&sLex); + SySetRelease(&sToken); + /* All done */ + return PH7_OK; +} +#ifndef PH7_DISABLE_BUILTIN_FUNC +/* + * XML processing Functions. + * Authors: + * Symisc Systems,devel@symisc.net. + * Copyright (C) Symisc Systems,http://ph7.symisc.net + * Status: + * Devel. + */ +enum ph7_xml_handler_id{ + PH7_XML_START_TAG = 0, /* Start element handlers ID */ + PH7_XML_END_TAG, /* End element handler ID*/ + PH7_XML_CDATA, /* Character data handler ID*/ + PH7_XML_PI, /* Processing instruction (PI) handler ID*/ + PH7_XML_DEF, /* Default handler ID */ + PH7_XML_UNPED, /* Unparsed entity declaration handler */ + PH7_XML_ND, /* Notation declaration handler ID*/ + PH7_XML_EER, /* External entity reference handler */ + PH7_XML_NS_START, /* Start namespace declaration handler */ + PH7_XML_NS_END /* End namespace declaration handler */ +}; +#define XML_TOTAL_HANDLER (PH7_XML_NS_END + 1) +/* An instance of the following structure describe a working + * XML engine instance. + */ +typedef struct ph7_xml_engine ph7_xml_engine; +struct ph7_xml_engine +{ + ph7_vm *pVm; /* VM that own this instance */ + ph7_context *pCtx; /* Call context */ + SyXMLParser sParser; /* Underlying XML parser */ + ph7_value aCB[XML_TOTAL_HANDLER]; /* User-defined callbacks */ + ph7_value sParserValue; /* ph7_value holding this instance which is forwarded + * as the first argument to the user callbacks. + */ + int ns_sep; /* Namespace separator */ + SyBlob sErr; /* Error message consumer */ + sxi32 iErrCode; /* Last error code */ + sxi32 iNest; /* Nesting level */ + sxu32 nLine; /* Last processed line */ + sxu32 nMagic; /* Magic number so that we avoid misuse */ +}; +#define XML_ENGINE_MAGIC 0x851EFC52 +#define IS_INVALID_XML_ENGINE(XML) (XML == 0 || (XML)->nMagic != XML_ENGINE_MAGIC) +/* + * Allocate and initialize an XML engine. + */ +static ph7_xml_engine * VmCreateXMLEngine(ph7_context *pCtx,int process_ns,int ns_sep) +{ + ph7_xml_engine *pEngine; + ph7_vm *pVm = pCtx->pVm; + ph7_value *pValue; + sxu32 n; + /* Allocate a new instance */ + pEngine = (ph7_xml_engine *)SyMemBackendAlloc(&pVm->sAllocator,sizeof(ph7_xml_engine)); + if( pEngine == 0 ){ + /* Out of memory */ + return 0; + } + /* Zero the structure */ + SyZero(pEngine,sizeof(ph7_xml_engine)); + /* Initialize fields */ + pEngine->pVm = pVm; + pEngine->pCtx = 0; + pEngine->ns_sep = ns_sep; + SyXMLParserInit(&pEngine->sParser,&pVm->sAllocator,process_ns ? SXML_ENABLE_NAMESPACE : 0); + SyBlobInit(&pEngine->sErr,&pVm->sAllocator); + PH7_MemObjInit(pVm,&pEngine->sParserValue); + for( n = 0 ; n < SX_ARRAYSIZE(pEngine->aCB) ; ++n ){ + pValue = &pEngine->aCB[n]; + /* NULLIFY the array entries,until someone register an event handler */ + PH7_MemObjInit(&(*pVm),pValue); + } + ph7_value_resource(&pEngine->sParserValue,pEngine); + pEngine->iErrCode = SXML_ERROR_NONE; + /* Finally set the magic number */ + pEngine->nMagic = XML_ENGINE_MAGIC; + return pEngine; +} +/* + * Release an XML engine. + */ +static void VmReleaseXMLEngine(ph7_xml_engine *pEngine) +{ + ph7_vm *pVm = pEngine->pVm; + ph7_value *pValue; + sxu32 n; + /* Release fields */ + SyBlobRelease(&pEngine->sErr); + SyXMLParserRelease(&pEngine->sParser); + PH7_MemObjRelease(&pEngine->sParserValue); + for( n = 0 ; n < SX_ARRAYSIZE(pEngine->aCB) ; ++n ){ + pValue = &pEngine->aCB[n]; + PH7_MemObjRelease(pValue); + } + pEngine->nMagic = 0x2621; + /* Finally,release the whole instance */ + SyMemBackendFree(&pVm->sAllocator,pEngine); +} +/* + * resource xml_parser_create([ string $encoding ]) + * Create an UTF-8 XML parser. + * Parameter + * $encoding + * (Only UTF-8 encoding is used) + * Return + * Returns a resource handle for the new XML parser. + */ +static int vm_builtin_xml_parser_create(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + ph7_xml_engine *pEngine; + /* Allocate a new instance */ + pEngine = VmCreateXMLEngine(&(*pCtx),0,':'); + if( pEngine == 0 ){ + ph7_context_throw_error(pCtx,PH7_CTX_ERR,"PH7 is running out of memory"); + /* Return null */ + ph7_result_null(pCtx); + SXUNUSED(nArg); /* cc warning */ + SXUNUSED(apArg); + return PH7_OK; + } + /* Return the engine as a resource */ + ph7_result_resource(pCtx,pEngine); + return PH7_OK; +} +/* + * resource xml_parser_create_ns([ string $encoding[,string $separator = ':']]) + * Create an UTF-8 XML parser with namespace support. + * Parameter + * $encoding + * (Only UTF-8 encoding is supported) + * $separtor + * Namespace separator (a single character) + * Return + * Returns a resource handle for the new XML parser. + */ +static int vm_builtin_xml_parser_create_ns(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + ph7_xml_engine *pEngine; + int ns_sep = ':'; + if( nArg > 1 && ph7_value_is_string(apArg[1]) ){ + const char *zSep = ph7_value_to_string(apArg[1],0); + if( zSep[0] != 0 ){ + ns_sep = zSep[0]; + } + } + /* Allocate a new instance */ + pEngine = VmCreateXMLEngine(&(*pCtx),TRUE,ns_sep); + if( pEngine == 0 ){ + ph7_context_throw_error(pCtx,PH7_CTX_ERR,"PH7 is running out of memory"); + /* Return null */ + ph7_result_null(pCtx); + return PH7_OK; + } + /* Return the engine as a resource */ + ph7_result_resource(pCtx,pEngine); + return PH7_OK; +} +/* + * bool xml_parser_free(resource $parser) + * Release an XML engine. + * Parameter + * $parser + * A reference to the XML parser to free. + * Return + * This function returns FALSE if parser does not refer + * to a valid parser, or else it frees the parser and returns TRUE. + */ +static int vm_builtin_xml_parser_free(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + ph7_xml_engine *pEngine; + if( nArg < 1 || !ph7_value_is_resource(apArg[0]) ){ + /* Missing/Ivalid argument,return FALSE */ + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Point to the XML engine */ + pEngine = (ph7_xml_engine *)ph7_value_to_resource(apArg[0]); + if( IS_INVALID_XML_ENGINE(pEngine) ){ + /* Corrupt engine,return FALSE */ + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Safely release the engine */ + VmReleaseXMLEngine(pEngine); + /* Return TRUE */ + ph7_result_bool(pCtx,1); + return PH7_OK; +} +/* + * bool xml_set_element_handler(resource $parser,callback $start_element_handler,[callback $end_element_handler]) + * Sets the element handler functions for the XML parser. start_element_handler and end_element_handler + * are strings containing the names of functions. + * Parameters + * $parser + * A reference to the XML parser to set up start and end element handler functions. + * $start_element_handler + * The function named by start_element_handler must accept three parameters: + * start_element_handler(resource $parser,string $name,array $attribs) + * $parser + * The first parameter, parser, is a reference to the XML parser calling the handler. + * $name + * The second parameter, name, contains the name of the element for which this handler + * is called.If case-folding is in effect for this parser, the element name will be in uppercase letters. + * $attribs + * The third parameter, attribs, contains an associative array with the element's attributes (if any). + * The keys of this array are the attribute names, the values are the attribute values. + * Attribute names are case-folded on the same criteria as element names.Attribute values are not case-folded. + * The original order of the attributes can be retrieved by walking through attribs the normal way, using each(). + * The first key in the array was the first attribute, and so on. + * Note: Instead of a function name, an array containing an object reference and a method name can also be supplied. + * $end_element_handler + * The function named by end_element_handler must accept two parameters: + * end_element_handler(resource $parser,string $name) + * $parser + * The first parameter, parser, is a reference to the XML parser calling the handler. + * $name + * The second parameter, name, contains the name of the element for which this handler + * is called.If case-folding is in effect for this parser, the element name will be in uppercase + * letters. + * If a handler function is set to an empty string, or FALSE, the handler in question is disabled. + * Return + * TRUE on success or FALSE on failure. + */ +static int vm_builtin_xml_set_element_handler(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + ph7_xml_engine *pEngine; + if( nArg < 1 || !ph7_value_is_resource(apArg[0]) ){ + /* Missing/Ivalid argument,return FALSE */ + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Point to the XML engine */ + pEngine = (ph7_xml_engine *)ph7_value_to_resource(apArg[0]); + if( IS_INVALID_XML_ENGINE(pEngine) ){ + /* Corrupt engine,return FALSE */ + ph7_result_bool(pCtx,0); + return PH7_OK; + } + if( nArg > 1 ){ + /* Save the start_element_handler callback for later invocation */ + PH7_MemObjStore(apArg[1]/* User callback*/,&pEngine->aCB[PH7_XML_START_TAG]); + if( nArg > 2 ){ + /* Save the end_element_handler callback for later invocation */ + PH7_MemObjStore(apArg[2]/* User callback*/,&pEngine->aCB[PH7_XML_END_TAG]); + } + } + /* All done,return TRUE */ + ph7_result_bool(pCtx,1); + return PH7_OK; +} +/* + * bool xml_set_character_data_handler(resource $parser,callback $handler) + * Sets the character data handler function for the XML parser parser. + * Parameters + * $parser + * A reference to the XML parser to set up character data handler function. + * $handler + * handler is a string containing the name of the callback. + * The function named by handler must accept two parameters: + * handler(resource $parser,string $data) + * $parser + * The first parameter, parser, is a reference to the XML parser calling the handler. + * $data + * The second parameter, data, contains the character data as a string. + * Character data handler is called for every piece of a text in the XML document. + * It can be called multiple times inside each fragment (e.g. for non-ASCII strings). + * If a handler function is set to an empty string, or FALSE, the handler in question is disabled. + * Note: Instead of a function name, an array containing an object reference and a method name + * can also be supplied. + * Return + * TRUE on success or FALSE on failure. + */ +static int vm_builtin_xml_set_character_data_handler(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + ph7_xml_engine *pEngine; + if( nArg < 1 || !ph7_value_is_resource(apArg[0]) ){ + /* Missing/Ivalid argument,return FALSE */ + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Point to the XML engine */ + pEngine = (ph7_xml_engine *)ph7_value_to_resource(apArg[0]); + if( IS_INVALID_XML_ENGINE(pEngine) ){ + /* Corrupt engine,return FALSE */ + ph7_result_bool(pCtx,0); + return PH7_OK; + } + if( nArg > 1 ){ + /* Save the user callback for later invocation */ + PH7_MemObjStore(apArg[1]/* User callback*/,&pEngine->aCB[PH7_XML_CDATA]); + } + /* All done,return TRUE */ + ph7_result_bool(pCtx,1); + return PH7_OK; +} +/* + * bool xml_set_default_handler(resource $parser,callback $handler) + * Set up default handler. + * Parameters + * $parser + * A reference to the XML parser to set up character data handler function. + * $handler + * handler is a string containing the name of the callback. + * The function named by handler must accept two parameters: + * handler(resource $parser,string $data) + * $parser + * The first parameter, parser, is a reference to the XML parser calling the handler. + * $data + * The second parameter, data, contains the character data.This may be the XML declaration + * document type declaration, entities or other data for which no other handler exists. + * Note: Instead of a function name, an array containing an object reference and a method name + * can also be supplied. + * Return + * TRUE on success or FALSE on failure. + */ +static int vm_builtin_xml_set_default_handler(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + ph7_xml_engine *pEngine; + if( nArg < 1 || !ph7_value_is_resource(apArg[0]) ){ + /* Missing/Ivalid argument,return FALSE */ + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Point to the XML engine */ + pEngine = (ph7_xml_engine *)ph7_value_to_resource(apArg[0]); + if( IS_INVALID_XML_ENGINE(pEngine) ){ + /* Corrupt engine,return FALSE */ + ph7_result_bool(pCtx,0); + return PH7_OK; + } + if( nArg > 1 ){ + /* Save the user callback for later invocation */ + PH7_MemObjStore(apArg[1]/* User callback*/,&pEngine->aCB[PH7_XML_DEF]); + } + /* All done,return TRUE */ + ph7_result_bool(pCtx,1); + return PH7_OK; +} +/* + * bool xml_set_end_namespace_decl_handler(resource $parser,callback $handler) + * Set up end namespace declaration handler. + * Parameters + * $parser + * A reference to the XML parser to set up character data handler function. + * $handler + * handler is a string containing the name of the callback. + * The function named by handler must accept two parameters: + * handler(resource $parser,string $prefix) + * $parser + * The first parameter, parser, is a reference to the XML parser calling the handler. + * $prefix + * The prefix is a string used to reference the namespace within an XML object. + * Note: Instead of a function name, an array containing an object reference and a method name + * can also be supplied. + * Return + * TRUE on success or FALSE on failure. + */ +static int vm_builtin_xml_set_end_namespace_decl_handler(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + ph7_xml_engine *pEngine; + if( nArg < 1 || !ph7_value_is_resource(apArg[0]) ){ + /* Missing/Ivalid argument,return FALSE */ + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Point to the XML engine */ + pEngine = (ph7_xml_engine *)ph7_value_to_resource(apArg[0]); + if( IS_INVALID_XML_ENGINE(pEngine) ){ + /* Corrupt engine,return FALSE */ + ph7_result_bool(pCtx,0); + return PH7_OK; + } + if( nArg > 1 ){ + /* Save the user callback for later invocation */ + PH7_MemObjStore(apArg[1]/* User callback*/,&pEngine->aCB[PH7_XML_NS_END]); + } + /* All done,return TRUE */ + ph7_result_bool(pCtx,1); + return PH7_OK; +} +/* + * bool xml_set_start_namespace_decl_handler(resource $parser,callback $handler) + * Set up start namespace declaration handler. + * Parameters + * $parser + * A reference to the XML parser to set up character data handler function. + * $handler + * handler is a string containing the name of the callback. + * The function named by handler must accept two parameters: + * handler(resource $parser,string $prefix,string $uri) + * $parser + * The first parameter, parser, is a reference to the XML parser calling the handler. + * $prefix + * The prefix is a string used to reference the namespace within an XML object. + * $uri + * Uniform Resource Identifier (URI) of namespace. + * Note: Instead of a function name, an array containing an object reference and a method name + * can also be supplied. + * Return + * TRUE on success or FALSE on failure. + */ +static int vm_builtin_xml_set_start_namespace_decl_handler(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + ph7_xml_engine *pEngine; + if( nArg < 1 || !ph7_value_is_resource(apArg[0]) ){ + /* Missing/Ivalid argument,return FALSE */ + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Point to the XML engine */ + pEngine = (ph7_xml_engine *)ph7_value_to_resource(apArg[0]); + if( IS_INVALID_XML_ENGINE(pEngine) ){ + /* Corrupt engine,return FALSE */ + ph7_result_bool(pCtx,0); + return PH7_OK; + } + if( nArg > 1 ){ + /* Save the user callback for later invocation */ + PH7_MemObjStore(apArg[1]/* User callback*/,&pEngine->aCB[PH7_XML_NS_START]); + } + /* All done,return TRUE */ + ph7_result_bool(pCtx,1); + return PH7_OK; +} +/* + * bool xml_set_processing_instruction_handler(resource $parser,callback $handler) + * Set up processing instruction (PI) handler. + * Parameters + * $parser + * A reference to the XML parser to set up character data handler function. + * $handler + * handler is a string containing the name of the callback. + * The function named by handler must accept three parameters: + * handler(resource $parser,string $target,string $data) + * $parser + * The first parameter, parser, is a reference to the XML parser calling the handler. + * $target + * The second parameter, target, contains the PI target. + * $data + The third parameter, data, contains the PI data. + * Note: Instead of a function name, an array containing an object reference and a method name + * can also be supplied. + * Return + * TRUE on success or FALSE on failure. + */ +static int vm_builtin_xml_set_processing_instruction_handler(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + ph7_xml_engine *pEngine; + if( nArg < 1 || !ph7_value_is_resource(apArg[0]) ){ + /* Missing/Ivalid argument,return FALSE */ + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Point to the XML engine */ + pEngine = (ph7_xml_engine *)ph7_value_to_resource(apArg[0]); + if( IS_INVALID_XML_ENGINE(pEngine) ){ + /* Corrupt engine,return FALSE */ + ph7_result_bool(pCtx,0); + return PH7_OK; + } + if( nArg > 1 ){ + /* Save the user callback for later invocation */ + PH7_MemObjStore(apArg[1]/* User callback*/,&pEngine->aCB[PH7_XML_PI]); + } + /* All done,return TRUE */ + ph7_result_bool(pCtx,1); + return PH7_OK; +} +/* + * bool xml_set_unparsed_entity_decl_handler(resource $parser,callback $handler) + * Set up unparsed entity declaration handler. + * Parameters + * $parser + * A reference to the XML parser to set up character data handler function. + * $handler + * handler is a string containing the name of the callback. + * The function named by handler must accept six parameters: + * handler(resource $parser,string $entity_name,string $base,string $system_id,string $public_id,string $notation_name) + * $parser + * The first parameter, parser, is a reference to the XML parser calling the handler. + * $entity_name + * The name of the entity that is about to be defined. + * $base + * This is the base for resolving the system identifier (systemId) of the external entity. + * Currently this parameter will always be set to an empty string. + * $system_id + * System identifier for the external entity. + * $public_id + * Public identifier for the external entity. + * $notation_name + * Name of the notation of this entity (see xml_set_notation_decl_handler()). + * Note: Instead of a function name, an array containing an object reference and a method name + * can also be supplied. + * Return + * TRUE on success or FALSE on failure. + */ +static int vm_builtin_xml_set_unparsed_entity_decl_handler(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + ph7_xml_engine *pEngine; + if( nArg < 1 || !ph7_value_is_resource(apArg[0]) ){ + /* Missing/Ivalid argument,return FALSE */ + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Point to the XML engine */ + pEngine = (ph7_xml_engine *)ph7_value_to_resource(apArg[0]); + if( IS_INVALID_XML_ENGINE(pEngine) ){ + /* Corrupt engine,return FALSE */ + ph7_result_bool(pCtx,0); + return PH7_OK; + } + if( nArg > 1 ){ + /* Save the user callback for later invocation */ + PH7_MemObjStore(apArg[1]/* User callback*/,&pEngine->aCB[PH7_XML_UNPED]); + } + /* All done,return TRUE */ + ph7_result_bool(pCtx,1); + return PH7_OK; +} +/* + * bool xml_set_notation_decl_handler(resource $parser,callback $handler) + * Set up notation declaration handler. + * Parameters + * $parser + * A reference to the XML parser to set up character data handler function. + * $handler + * handler is a string containing the name of the callback. + * The function named by handler must accept five parameters: + * handler(resource $parser,string $entity_name,string $base,string $system_id,string $public_id) + * $parser + * The first parameter, parser, is a reference to the XML parser calling the handler. + * $entity_name + * The name of the entity that is about to be defined. + * $base + * This is the base for resolving the system identifier (systemId) of the external entity. + * Currently this parameter will always be set to an empty string. + * $system_id + * System identifier for the external entity. + * $public_id + * Public identifier for the external entity. + * Note: Instead of a function name, an array containing an object reference and a method name + * can also be supplied. + * Return + * TRUE on success or FALSE on failure. + */ +static int vm_builtin_xml_set_notation_decl_handler(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + ph7_xml_engine *pEngine; + if( nArg < 1 || !ph7_value_is_resource(apArg[0]) ){ + /* Missing/Ivalid argument,return FALSE */ + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Point to the XML engine */ + pEngine = (ph7_xml_engine *)ph7_value_to_resource(apArg[0]); + if( IS_INVALID_XML_ENGINE(pEngine) ){ + /* Corrupt engine,return FALSE */ + ph7_result_bool(pCtx,0); + return PH7_OK; + } + if( nArg > 1 ){ + /* Save the user callback for later invocation */ + PH7_MemObjStore(apArg[1]/* User callback*/,&pEngine->aCB[PH7_XML_ND]); + } + /* All done,return TRUE */ + ph7_result_bool(pCtx,1); + return PH7_OK; +} +/* + * bool xml_set_external_entity_ref_handler(resource $parser,callback $handler) + * Set up external entity reference handler. + * Parameters + * $parser + * A reference to the XML parser to set up character data handler function. + * $handler + * handler is a string containing the name of the callback. + * The function named by handler must accept five parameters: + * handler(resource $parser,string $open_entity_names,string $base,string $system_id,string $public_id) + * $parser + * The first parameter, parser, is a reference to the XML parser calling the handler. + * $open_entity_names + * The second parameter, open_entity_names, is a space-separated list of the names + * of the entities that are open for the parse of this entity (including the name of the referenced entity). + * $base + * This is the base for resolving the system identifier (system_id) of the external entity. + * Currently this parameter will always be set to an empty string. + * $system_id + * The fourth parameter, system_id, is the system identifier as specified in the entity declaration. + * $public_id + * The fifth parameter, public_id, is the public identifier as specified in the entity declaration + * or an empty string if none was specified; the whitespace in the public identifier will have been + * normalized as required by the XML spec. + * Note: Instead of a function name, an array containing an object reference and a method name + * can also be supplied. + * Return + * TRUE on success or FALSE on failure. + */ +static int vm_builtin_xml_set_external_entity_ref_handler(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + ph7_xml_engine *pEngine; + if( nArg < 1 || !ph7_value_is_resource(apArg[0]) ){ + /* Missing/Ivalid argument,return FALSE */ + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Point to the XML engine */ + pEngine = (ph7_xml_engine *)ph7_value_to_resource(apArg[0]); + if( IS_INVALID_XML_ENGINE(pEngine) ){ + /* Corrupt engine,return FALSE */ + ph7_result_bool(pCtx,0); + return PH7_OK; + } + if( nArg > 1 ){ + /* Save the user callback for later invocation */ + PH7_MemObjStore(apArg[1]/* User callback*/,&pEngine->aCB[PH7_XML_EER]); + } + /* All done,return TRUE */ + ph7_result_bool(pCtx,1); + return PH7_OK; +} +/* + * int xml_get_current_line_number(resource $parser) + * Gets the current line number for the given XML parser. + * Parameters + * $parser + * A reference to the XML parser. + * Return + * This function returns FALSE if parser does not refer + * to a valid parser, or else it returns which line the parser + * is currently at in its data buffer. + */ +static int vm_builtin_xml_get_current_line_number(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + ph7_xml_engine *pEngine; + if( nArg < 1 || !ph7_value_is_resource(apArg[0]) ){ + /* Missing/Ivalid argument,return FALSE */ + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Point to the XML engine */ + pEngine = (ph7_xml_engine *)ph7_value_to_resource(apArg[0]); + if( IS_INVALID_XML_ENGINE(pEngine) ){ + /* Corrupt engine,return FALSE */ + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Return the line number */ + ph7_result_int(pCtx,(int)pEngine->nLine); + return PH7_OK; +} +/* + * int xml_get_current_byte_index(resource $parser) + * Gets the current byte index of the given XML parser. + * Parameters + * $parser + * A reference to the XML parser. + * Return + * This function returns FALSE if parser does not refer to a valid + * parser, or else it returns which byte index the parser is currently + * at in its data buffer (starting at 0). + */ +static int vm_builtin_xml_get_current_byte_index(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + ph7_xml_engine *pEngine; + SyStream *pStream; + SyToken *pToken; + if( nArg < 1 || !ph7_value_is_resource(apArg[0]) ){ + /* Missing/Ivalid argument,return FALSE */ + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Point to the XML engine */ + pEngine = (ph7_xml_engine *)ph7_value_to_resource(apArg[0]); + if( IS_INVALID_XML_ENGINE(pEngine) ){ + /* Corrupt engine,return FALSE */ + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Point to the current processed token */ + pToken = (SyToken *)SySetPeekCurrentEntry(&pEngine->sParser.sToken); + if( pToken == 0 ){ + /* Stream not yet processed */ + ph7_result_int(pCtx,0); + return 0; + } + /* Point to the input stream */ + pStream = &pEngine->sParser.sLex.sStream; + /* Return the byte index */ + ph7_result_int64(pCtx,(ph7_int64)(pToken->sData.zString-(const char *)pStream->zInput)); + return PH7_OK; +} +/* + * bool xml_set_object(resource $parser,object &$object) + * Use XML Parser within an object. + * NOTE + * This function is depreceated and is a no-op. + * Parameters + * $parser + * A reference to the XML parser. + * $object + * The object where to use the XML parser. + * Return + * Always FALSE. + */ +static int vm_builtin_xml_set_object(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + ph7_xml_engine *pEngine; + if( nArg < 2 || !ph7_value_is_resource(apArg[0]) || !ph7_value_is_object(apArg[1]) ){ + /* Missing/Ivalid argument,return FALSE */ + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Point to the XML engine */ + pEngine = (ph7_xml_engine *)ph7_value_to_resource(apArg[0]); + if( IS_INVALID_XML_ENGINE(pEngine) ){ + /* Corrupt engine,return FALSE */ + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Throw a notice and return */ + ph7_context_throw_error(pCtx,PH7_CTX_NOTICE,"This function is depreceated and is a no-op." + "In order to mimic this behaviour,you can supply instead of a function name an array " + "containing an object reference and a method name." + ); + /* Return FALSE */ + ph7_result_bool(pCtx,0); + return PH7_OK; +} +/* + * int xml_get_current_column_number(resource $parser) + * Gets the current column number of the given XML parser. + * Parameters + * $parser + * A reference to the XML parser. + * Return + * This function returns FALSE if parser does not refer to a valid parser, or else it returns + * which column on the current line (as given by xml_get_current_line_number()) the parser + * is currently at. + */ +static int vm_builtin_xml_get_current_column_number(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + ph7_xml_engine *pEngine; + SyStream *pStream; + SyToken *pToken; + if( nArg < 1 || !ph7_value_is_resource(apArg[0]) ){ + /* Missing/Ivalid argument,return FALSE */ + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Point to the XML engine */ + pEngine = (ph7_xml_engine *)ph7_value_to_resource(apArg[0]); + if( IS_INVALID_XML_ENGINE(pEngine) ){ + /* Corrupt engine,return FALSE */ + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Point to the current processed token */ + pToken = (SyToken *)SySetPeekCurrentEntry(&pEngine->sParser.sToken); + if( pToken == 0 ){ + /* Stream not yet processed */ + ph7_result_int(pCtx,0); + return 0; + } + /* Point to the input stream */ + pStream = &pEngine->sParser.sLex.sStream; + /* Return the byte index */ + ph7_result_int64(pCtx,(ph7_int64)(pToken->sData.zString-(const char *)pStream->zInput)/80); + return PH7_OK; +} +/* + * int xml_get_error_code(resource $parser) + * Get XML parser error code. + * Parameters + * $parser + * A reference to the XML parser. + * Return + * This function returns FALSE if parser does not refer to a valid + * parser, or else it returns one of the error codes listed in the error + * codes section. + */ +static int vm_builtin_xml_get_error_code(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + ph7_xml_engine *pEngine; + if( nArg < 1 || !ph7_value_is_resource(apArg[0]) ){ + /* Missing/Ivalid argument,return FALSE */ + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Point to the XML engine */ + pEngine = (ph7_xml_engine *)ph7_value_to_resource(apArg[0]); + if( IS_INVALID_XML_ENGINE(pEngine) ){ + /* Corrupt engine,return FALSE */ + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Return the error code if any */ + ph7_result_int(pCtx,pEngine->iErrCode); + return PH7_OK; +} +/* + * XML parser event callbacks + * Each time the unserlying XML parser extract a single token + * from the input,one of the following callbacks are invoked. + * IMP-XML-ENGINE-07-07-2012 22:02 FreeBSD [chm@symisc.net] + */ +/* + * Create a scalar ph7_value holding the value + * of an XML tag/attribute/CDATA and so on. + */ +static ph7_value * VmXMLValue(ph7_xml_engine *pEngine,SyXMLRawStr *pXML,SyXMLRawStr *pNsUri) +{ + ph7_value *pValue; + /* Allocate a new scalar variable */ + pValue = ph7_context_new_scalar(pEngine->pCtx); + if( pValue == 0 ){ + ph7_context_throw_error(pEngine->pCtx,PH7_CTX_ERR,"PH7 is running out of memory"); + return 0; + } + if( pNsUri && pNsUri->nByte > 0 ){ + /* Append namespace URI and the separator */ + ph7_value_string_format(pValue,"%.*s%c",pNsUri->nByte,pNsUri->zString,pEngine->ns_sep); + } + /* Copy the tag value */ + ph7_value_string(pValue,pXML->zString,(int)pXML->nByte); + return pValue; +} +/* + * Create a 'ph7_value' of type array holding the values + * of an XML tag attributes. + */ +static ph7_value * VmXMLAttrValue(ph7_xml_engine *pEngine,SyXMLRawStr *aAttr,sxu32 nAttr) +{ + ph7_value *pArray; + /* Create an empty array */ + pArray = ph7_context_new_array(pEngine->pCtx); + if( pArray == 0 ){ + ph7_context_throw_error(pEngine->pCtx,PH7_CTX_ERR,"PH7 is running out of memory"); + return 0; + } + if( nAttr > 0 ){ + ph7_value *pKey,*pValue; + sxu32 n; + /* Create worker variables */ + pKey = ph7_context_new_scalar(pEngine->pCtx); + pValue = ph7_context_new_scalar(pEngine->pCtx); + if( pKey == 0 || pValue == 0 ){ + ph7_context_throw_error(pEngine->pCtx,PH7_CTX_ERR,"PH7 is running out of memory"); + return 0; + } + /* Copy attributes */ + for( n = 0 ; n < nAttr ; n += 2 ){ + /* Reset string cursors */ + ph7_value_reset_string_cursor(pKey); + ph7_value_reset_string_cursor(pValue); + /* Copy attribute name and it's associated value */ + ph7_value_string(pKey,aAttr[n].zString,(int)aAttr[n].nByte); /* Attribute name */ + ph7_value_string(pValue,aAttr[n+1].zString,(int)aAttr[n+1].nByte); /* Attribute value */ + /* Insert in the array */ + ph7_array_add_elem(pArray,pKey,pValue); /* Will make it's own copy */ + } + /* Release the worker variables */ + ph7_context_release_value(pEngine->pCtx,pKey); + ph7_context_release_value(pEngine->pCtx,pValue); + } + /* Return the freshly created array */ + return pArray; +} +/* + * Start element handler. + * The user defined callback must accept three parameters: + * start_element_handler(resource $parser,string $name,array $attribs ) + * $parser + * The first parameter, parser, is a reference to the XML parser calling the handler. + * $name + * The second parameter, name, contains the name of the element for which this handler + * is called.If case-folding is in effect for this parser, the element name will be in uppercase letters. + * $attribs + * The third parameter, attribs, contains an associative array with the element's attributes (if any). + * The keys of this array are the attribute names, the values are the attribute values. + * Attribute names are case-folded on the same criteria as element names.Attribute values are not case-folded. + * The original order of the attributes can be retrieved by walking through attribs the normal way, using each(). + * The first key in the array was the first attribute, and so on. + * Note: Instead of a function name, an array containing an object reference and a method name can also be supplied. + */ +static sxi32 VmXMLStartElementHandler(SyXMLRawStr *pStart,SyXMLRawStr *pNS,sxu32 nAttr,SyXMLRawStr *aAttr,void *pUserData) +{ + ph7_xml_engine *pEngine = (ph7_xml_engine *)pUserData; + ph7_value *pCallback,*pTag,*pAttr; + /* Point to the target user defined callback */ + pCallback = &pEngine->aCB[PH7_XML_START_TAG]; + /* Make sure the given callback is callable */ + if( !PH7_VmIsCallable(pEngine->pVm,pCallback,0) ){ + /* Not callable,return immediately*/ + return SXRET_OK; + } + /* Create a ph7_value holding the tag name */ + pTag = VmXMLValue(pEngine,pStart,pNS); + /* Create a ph7_value holding the tag attributes */ + pAttr = VmXMLAttrValue(pEngine,aAttr,nAttr); + if( pTag == 0 || pAttr == 0 ){ + SXUNUSED(pNS); /* cc warning */ + /* Out of mem,return immediately */ + return SXRET_OK; + } + /* Invoke the user callback */ + PH7_VmCallUserFunctionAp(pEngine->pVm,pCallback,0,&pEngine->sParserValue,pTag,pAttr,0); + /* Clean-up the mess left behind */ + ph7_context_release_value(pEngine->pCtx,pTag); + ph7_context_release_value(pEngine->pCtx,pAttr); + return SXRET_OK; +} +/* + * End element handler. + * The user defined callback must accept two parameters: + * end_element_handler(resource $parser,string $name) + * $parser + * The first parameter, parser, is a reference to the XML parser calling the handler. + * $name + * The second parameter, name, contains the name of the element for which this handler is called. + * If case-folding is in effect for this parser, the element name will be in uppercase letters. + * Note: Instead of a function name, an array containing an object reference and a method name + * can also be supplied. + */ +static sxi32 VmXMLEndElementHandler(SyXMLRawStr *pEnd,SyXMLRawStr *pNS,void *pUserData) +{ + ph7_xml_engine *pEngine = (ph7_xml_engine *)pUserData; + ph7_value *pCallback,*pTag; + /* Point to the target user defined callback */ + pCallback = &pEngine->aCB[PH7_XML_END_TAG]; + /* Make sure the given callback is callable */ + if( !PH7_VmIsCallable(pEngine->pVm,pCallback,0) ){ + /* Not callable,return immediately*/ + return SXRET_OK; + } + /* Create a ph7_value holding the tag name */ + pTag = VmXMLValue(pEngine,pEnd,pNS); + if( pTag == 0 ){ + SXUNUSED(pNS); /* cc warning */ + /* Out of mem,return immediately */ + return SXRET_OK; + } + /* Invoke the user callback */ + PH7_VmCallUserFunctionAp(pEngine->pVm,pCallback,0,&pEngine->sParserValue,pTag,0); + /* Clean-up the mess left behind */ + ph7_context_release_value(pEngine->pCtx,pTag); + return SXRET_OK; +} +/* + * Character data handler. + * The user defined callback must accept two parameters: + * handler(resource $parser,string $data) + * $parser + * The first parameter, parser, is a reference to the XML parser calling the handler. + * $data + * The second parameter, data, contains the character data as a string. + * Character data handler is called for every piece of a text in the XML document. + * It can be called multiple times inside each fragment (e.g. for non-ASCII strings). + * If a handler function is set to an empty string, or FALSE, the handler in question is disabled. + * Note: Instead of a function name, an array containing an object reference and a method name can also be supplied. + */ +static sxi32 VmXMLTextHandler(SyXMLRawStr *pText,void *pUserData) +{ + ph7_xml_engine *pEngine = (ph7_xml_engine *)pUserData; + ph7_value *pCallback,*pData; + /* Point to the target user defined callback */ + pCallback = &pEngine->aCB[PH7_XML_CDATA]; + /* Make sure the given callback is callable */ + if( !PH7_VmIsCallable(pEngine->pVm,pCallback,0) ){ + /* Not callable,return immediately*/ + return SXRET_OK; + } + /* Create a ph7_value holding the data */ + pData = VmXMLValue(pEngine,&(*pText),0); + if( pData == 0 ){ + /* Out of mem,return immediately */ + return SXRET_OK; + } + /* Invoke the user callback */ + PH7_VmCallUserFunctionAp(pEngine->pVm,pCallback,0,&pEngine->sParserValue,pData,0); + /* Clean-up the mess left behind */ + ph7_context_release_value(pEngine->pCtx,pData); + return SXRET_OK; +} +/* + * Processing instruction (PI) handler. + * The user defined callback must accept two parameters: + * handler(resource $parser,string $target,string $data) + * $parser + * The first parameter, parser, is a reference to the XML parser calling the handler. + * $target + * The second parameter, target, contains the PI target. + * $data + * The third parameter, data, contains the PI data. + * Note: Instead of a function name, an array containing an object reference + * and a method name can also be supplied. + */ +static sxi32 VmXMLPIHandler(SyXMLRawStr *pTargetStr,SyXMLRawStr *pDataStr,void *pUserData) +{ + ph7_xml_engine *pEngine = (ph7_xml_engine *)pUserData; + ph7_value *pCallback,*pTarget,*pData; + /* Point to the target user defined callback */ + pCallback = &pEngine->aCB[PH7_XML_PI]; + /* Make sure the given callback is callable */ + if( !PH7_VmIsCallable(pEngine->pVm,pCallback,0) ){ + /* Not callable,return immediately*/ + return SXRET_OK; + } + /* Get a ph7_value holding the data */ + pTarget = VmXMLValue(pEngine,&(*pTargetStr),0); + pData = VmXMLValue(pEngine,&(*pDataStr),0); + if( pTarget == 0 || pData == 0 ){ + /* Out of mem,return immediately */ + return SXRET_OK; + } + /* Invoke the user callback */ + PH7_VmCallUserFunctionAp(pEngine->pVm,pCallback,0,&pEngine->sParserValue,pTarget,pData,0); + /* Clean-up the mess left behind */ + ph7_context_release_value(pEngine->pCtx,pTarget); + ph7_context_release_value(pEngine->pCtx,pData); + return SXRET_OK; +} +/* + * Namespace declaration handler. + * The user defined callback must accept two parameters: + * handler(resource $parser,string $prefix,string $uri) + * $parser + * The first parameter, parser, is a reference to the XML parser calling the handler. + * $prefix + * The prefix is a string used to reference the namespace within an XML object. + * $uri + * Uniform Resource Identifier (URI) of namespace. + * Note: Instead of a function name, an array containing an object reference + * and a method name can also be supplied. + */ +static sxi32 VmXMLNSStartHandler(SyXMLRawStr *pUriStr,SyXMLRawStr *pPrefixStr,void *pUserData) +{ + ph7_xml_engine *pEngine = (ph7_xml_engine *)pUserData; + ph7_value *pCallback,*pUri,*pPrefix; + /* Point to the target user defined callback */ + pCallback = &pEngine->aCB[PH7_XML_NS_START]; + /* Make sure the given callback is callable */ + if( !PH7_VmIsCallable(pEngine->pVm,pCallback,0) ){ + /* Not callable,return immediately*/ + return SXRET_OK; + } + /* Get a ph7_value holding the PREFIX/URI */ + pUri = VmXMLValue(pEngine,pUriStr,0); + pPrefix = VmXMLValue(pEngine,pPrefixStr,0); + if( pUri == 0 || pPrefix == 0 ){ + /* Out of mem,return immediately */ + return SXRET_OK; + } + /* Invoke the user callback */ + PH7_VmCallUserFunctionAp(pEngine->pVm,pCallback,0,&pEngine->sParserValue,pUri,pPrefix,0); + /* Clean-up the mess left behind */ + ph7_context_release_value(pEngine->pCtx,pUri); + ph7_context_release_value(pEngine->pCtx,pPrefix); + return SXRET_OK; +} +/* + * Namespace end declaration handler. + * The user defined callback must accept two parameters: + * handler(resource $parser,string $prefix) + * $parser + * The first parameter, parser, is a reference to the XML parser calling the handler. + * $prefix + * The prefix is a string used to reference the namespace within an XML object. + * Note: Instead of a function name, an array containing an object reference + * and a method name can also be supplied. + */ +static sxi32 VmXMLNSEndHandler(SyXMLRawStr *pPrefixStr,void *pUserData) +{ + ph7_xml_engine *pEngine = (ph7_xml_engine *)pUserData; + ph7_value *pCallback,*pPrefix; + /* Point to the target user defined callback */ + pCallback = &pEngine->aCB[PH7_XML_NS_END]; + /* Make sure the given callback is callable */ + if( !PH7_VmIsCallable(pEngine->pVm,pCallback,0) ){ + /* Not callable,return immediately*/ + return SXRET_OK; + } + /* Get a ph7_value holding the prefix */ + pPrefix = VmXMLValue(pEngine,pPrefixStr,0); + if( pPrefix == 0 ){ + /* Out of mem,return immediately */ + return SXRET_OK; + } + /* Invoke the user callback */ + PH7_VmCallUserFunctionAp(pEngine->pVm,pCallback,0,&pEngine->sParserValue,pPrefix,0); + /* Clean-up the mess left behind */ + ph7_context_release_value(pEngine->pCtx,pPrefix); + return SXRET_OK; +} +/* + * Error Message consumer handler. + * Each time the XML parser encounter a syntaxt error or any other error + * related to XML processing,the following callback is invoked by the + * underlying XML parser. + */ +static sxi32 VmXMLErrorHandler(const char *zMessage,sxi32 iErrCode,SyToken *pToken,void *pUserData) +{ + ph7_xml_engine *pEngine = (ph7_xml_engine *)pUserData; + /* Save the error code */ + pEngine->iErrCode = iErrCode; + SXUNUSED(zMessage); /* cc warning */ + if( pToken ){ + pEngine->nLine = pToken->nLine; + } + /* Abort XML processing immediately */ + return SXERR_ABORT; +} +/* + * int xml_parse(resource $parser,string $data[,bool $is_final = false ]) + * Parses an XML document. The handlers for the configured events are called + * as many times as necessary. + * Parameters + * $parser + * A reference to the XML parser. + * $data + * Chunk of data to parse. A document may be parsed piece-wise by calling + * xml_parse() several times with new data, as long as the is_final parameter + * is set and TRUE when the last data is parsed. + * $is_final + * NOT USED. This implementation require that all the processed input be + * entirely loaded in memory. + * Return + * Returns 1 on success or 0 on failure. + */ +static int vm_builtin_xml_parse(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + ph7_xml_engine *pEngine; + SyXMLParser *pParser; + const char *zData; + int nByte; + if( nArg < 2 || !ph7_value_is_resource(apArg[0]) || !ph7_value_is_string(apArg[1]) ){ + /* Missing/Ivalid arguments,return FALSE */ + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Point to the XML engine */ + pEngine = (ph7_xml_engine *)ph7_value_to_resource(apArg[0]); + if( IS_INVALID_XML_ENGINE(pEngine) ){ + /* Corrupt engine,return FALSE */ + ph7_result_bool(pCtx,0); + return PH7_OK; + } + if( pEngine->iNest > 0 ){ + /* This can happen when the user callback call xml_parse() again + * in it's body which is forbidden. + */ + ph7_context_throw_error_format(pCtx,PH7_CTX_ERR, + "Recursive call to %s,PH7 is returning false", + ph7_function_name(pCtx) + ); + /* Return FALSE */ + ph7_result_bool(pCtx,0); + return PH7_OK; + } + pEngine->pCtx = pCtx; + /* Point to the underlying XML parser */ + pParser = &pEngine->sParser; + /* Register elements handler */ + SyXMLParserSetEventHandler(pParser,pEngine, + VmXMLStartElementHandler, + VmXMLTextHandler, + VmXMLErrorHandler, + 0, + VmXMLEndElementHandler, + VmXMLPIHandler, + 0, + 0, + VmXMLNSStartHandler, + VmXMLNSEndHandler + ); + pEngine->iErrCode = SXML_ERROR_NONE; + /* Extract the raw XML input */ + zData = ph7_value_to_string(apArg[1],&nByte); + /* Start the parse process */ + pEngine->iNest++; + SyXMLProcess(pParser,zData,(sxu32)nByte); + pEngine->iNest--; + /* Return the parse result */ + ph7_result_int(pCtx,pEngine->iErrCode == SXML_ERROR_NONE ? 1 : 0); + return PH7_OK; +} +/* + * bool xml_parser_set_option(resource $parser,int $option,mixed $value) + * Sets an option in an XML parser. + * Parameters + * $parser + * A reference to the XML parser to set an option in. + * $option + * Which option to set. See below. + * The following options are available: + * XML_OPTION_CASE_FOLDING integer Controls whether case-folding is enabled for this XML parser. + * XML_OPTION_SKIP_TAGSTART integer Specify how many characters should be skipped in the beginning of a tag name. + * XML_OPTION_SKIP_WHITE integer Whether to skip values consisting of whitespace characters. + * XML_OPTION_TARGET_ENCODING string Sets which target encoding to use in this XML parser. + * $value + * The option's new value. + * Return + * Returns 1 on success or 0 on failure. + * Note: + * Well,none of these options have meaning under the built-in XML parser so a call to this + * function is a no-op. + */ +static int vm_builtin_xml_parser_set_option(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + ph7_xml_engine *pEngine; + if( nArg < 2 || !ph7_value_is_resource(apArg[0]) ){ + /* Missing/Ivalid argument,return FALSE */ + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Point to the XML engine */ + pEngine = (ph7_xml_engine *)ph7_value_to_resource(apArg[0]); + if( IS_INVALID_XML_ENGINE(pEngine) ){ + /* Corrupt engine,return FALSE */ + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Always return FALSE */ + ph7_result_bool(pCtx,0); + return PH7_OK; +} +/* + * mixed xml_parser_get_option(resource $parser,int $option) + * Get options from an XML parser. + * Parameters + * $parser + * A reference to the XML parser to set an option in. + * $option + * Which option to fetch. + * Return + * This function returns FALSE if parser does not refer to a valid parser + * or if option isn't valid.Else the option's value is returned. + */ +static int vm_builtin_xml_parser_get_option(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + ph7_xml_engine *pEngine; + int nOp; + if( nArg < 2 || !ph7_value_is_resource(apArg[0]) ){ + /* Missing/Ivalid argument,return FALSE */ + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Point to the XML engine */ + pEngine = (ph7_xml_engine *)ph7_value_to_resource(apArg[0]); + if( IS_INVALID_XML_ENGINE(pEngine) ){ + /* Corrupt engine,return FALSE */ + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Extract the option */ + nOp = ph7_value_to_int(apArg[1]); + switch(nOp){ + case SXML_OPTION_SKIP_TAGSTART: + case SXML_OPTION_SKIP_WHITE: + case SXML_OPTION_CASE_FOLDING: + ph7_result_int(pCtx,0); break; + case SXML_OPTION_TARGET_ENCODING: + ph7_result_string(pCtx,"UTF-8",(int)sizeof("UTF-8")-1); + break; + default: + /* Unknown option,return FALSE*/ + ph7_result_bool(pCtx,0); + break; + } + return PH7_OK; +} +/* + * string xml_error_string(int $code) + * Gets the XML parser error string associated with the given code. + * Parameters + * $code + * An error code from xml_get_error_code(). + * Return + * Returns a string with a textual description of the error + * code, or FALSE if no description was found. + */ +static int vm_builtin_xml_error_string(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + int nErr = -1; + if( nArg > 0 ){ + nErr = ph7_value_to_int(apArg[0]); + } + switch(nErr){ + case SXML_ERROR_DUPLICATE_ATTRIBUTE: + ph7_result_string(pCtx,"Duplicate attribute",-1/*Compute length automatically*/); + break; + case SXML_ERROR_INCORRECT_ENCODING: + ph7_result_string(pCtx,"Incorrect encoding",-1); + break; + case SXML_ERROR_INVALID_TOKEN: + ph7_result_string(pCtx,"Unexpected token",-1); + break; + case SXML_ERROR_MISPLACED_XML_PI: + ph7_result_string(pCtx,"Misplaced processing instruction",-1); + break; + case SXML_ERROR_NO_MEMORY: + ph7_result_string(pCtx,"Out of memory",-1); + break; + case SXML_ERROR_NONE: + ph7_result_string(pCtx,"Not an error",-1); + break; + case SXML_ERROR_TAG_MISMATCH: + ph7_result_string(pCtx,"Tag mismatch",-1); + break; + case -1: + ph7_result_string(pCtx,"Unknown error code",-1); + break; + default: + ph7_result_string(pCtx,"Syntax error",-1); + break; + } + return PH7_OK; +} +#endif /* PH7_DISABLE_BUILTIN_FUNC */ +/* + * int utf8_encode(string $input) + * UTF-8 encoding. + * This function encodes the string data to UTF-8, and returns the encoded version. + * UTF-8 is a standard mechanism used by Unicode for encoding wide character values + * into a byte stream. UTF-8 is transparent to plain ASCII characters, is self-synchronized + * (meaning it is possible for a program to figure out where in the bytestream characters start) + * and can be used with normal string comparison functions for sorting and such. + * Notes on UTF-8 (According to SQLite3 authors): + * Byte-0 Byte-1 Byte-2 Byte-3 Value + * 0xxxxxxx 00000000 00000000 0xxxxxxx + * 110yyyyy 10xxxxxx 00000000 00000yyy yyxxxxxx + * 1110zzzz 10yyyyyy 10xxxxxx 00000000 zzzzyyyy yyxxxxxx + * 11110uuu 10uuzzzz 10yyyyyy 10xxxxxx 000uuuuu zzzzyyyy yyxxxxxx + * Parameters + * $input + * String to encode or NULL on failure. + * Return + * An UTF-8 encoded string. + */ +static int vm_builtin_utf8_encode(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + const unsigned char *zIn,*zEnd; + int nByte,c,e; + if( nArg < 1 ){ + /* Missing arguments,return null */ + ph7_result_null(pCtx); + return PH7_OK; + } + /* Extract the target string */ + zIn = (const unsigned char *)ph7_value_to_string(apArg[0],&nByte); + if( nByte < 1 ){ + /* Empty string,return null */ + ph7_result_null(pCtx); + return PH7_OK; + } + zEnd = &zIn[nByte]; + /* Start the encoding process */ + for(;;){ + if( zIn >= zEnd ){ + /* End of input */ + break; + } + c = zIn[0]; + /* Advance the stream cursor */ + zIn++; + /* Encode */ + if( c<0x00080 ){ + e = (c&0xFF); + ph7_result_string(pCtx,(const char *)&e,(int)sizeof(char)); + }else if( c<0x00800 ){ + e = 0xC0 + ((c>>6)&0x1F); + ph7_result_string(pCtx,(const char *)&e,(int)sizeof(char)); + e = 0x80 + (c & 0x3F); + ph7_result_string(pCtx,(const char *)&e,(int)sizeof(char)); + }else if( c<0x10000 ){ + e = 0xE0 + ((c>>12)&0x0F); + ph7_result_string(pCtx,(const char *)&e,(int)sizeof(char)); + e = 0x80 + ((c>>6) & 0x3F); + ph7_result_string(pCtx,(const char *)&e,(int)sizeof(char)); + e = 0x80 + (c & 0x3F); + ph7_result_string(pCtx,(const char *)&e,(int)sizeof(char)); + }else{ + e = 0xF0 + ((c>>18) & 0x07); + ph7_result_string(pCtx,(const char *)&e,(int)sizeof(char)); + e = 0x80 + ((c>>12) & 0x3F); + ph7_result_string(pCtx,(const char *)&e,(int)sizeof(char)); + e = 0x80 + ((c>>6) & 0x3F); + ph7_result_string(pCtx,(const char *)&e,(int)sizeof(char)); + e = 0x80 + (c & 0x3F); + ph7_result_string(pCtx,(const char *)&e,(int)sizeof(char)); + } + } + /* All done */ + return PH7_OK; +} +/* + * UTF-8 decoding routine extracted from the sqlite3 source tree. + * Original author: D. Richard Hipp (http://www.sqlite.org) + * Status: Public Domain + */ +/* +** This lookup table is used to help decode the first byte of +** a multi-byte UTF8 character. +*/ +static const unsigned char UtfTrans1[] = { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x00, 0x01, 0x02, 0x03, 0x00, 0x01, 0x00, 0x00, +}; +/* +** Translate a single UTF-8 character. Return the unicode value. +** +** During translation, assume that the byte that zTerm points +** is a 0x00. +** +** Write a pointer to the next unread byte back into *pzNext. +** +** Notes On Invalid UTF-8: +** +** * This routine never allows a 7-bit character (0x00 through 0x7f) to +** be encoded as a multi-byte character. Any multi-byte character that +** attempts to encode a value between 0x00 and 0x7f is rendered as 0xfffd. +** +** * This routine never allows a UTF16 surrogate value to be encoded. +** If a multi-byte character attempts to encode a value between +** 0xd800 and 0xe000 then it is rendered as 0xfffd. +** +** * Bytes in the range of 0x80 through 0xbf which occur as the first +** byte of a character are interpreted as single-byte characters +** and rendered as themselves even though they are technically +** invalid characters. +** +** * This routine accepts an infinite number of different UTF8 encodings +** for unicode values 0x80 and greater. It do not change over-length +** encodings to 0xfffd as some systems recommend. +*/ +#define READ_UTF8(zIn, zTerm, c) \ + c = *(zIn++); \ + if( c>=0xc0 ){ \ + c = UtfTrans1[c-0xc0]; \ + while( zIn!=zTerm && (*zIn & 0xc0)==0x80 ){ \ + c = (c<<6) + (0x3f & *(zIn++)); \ + } \ + if( c<0x80 \ + || (c&0xFFFFF800)==0xD800 \ + || (c&0xFFFFFFFE)==0xFFFE ){ c = 0xFFFD; } \ + } +PH7_PRIVATE int PH7_Utf8Read( + const unsigned char *z, /* First byte of UTF-8 character */ + const unsigned char *zTerm, /* Pretend this byte is 0x00 */ + const unsigned char **pzNext /* Write first byte past UTF-8 char here */ +){ + int c; + READ_UTF8(z, zTerm, c); + *pzNext = z; + return c; +} +/* + * string utf8_decode(string $data) + * This function decodes data, assumed to be UTF-8 encoded, to unicode. + * Parameters + * data + * An UTF-8 encoded string. + * Return + * Unicode decoded string or NULL on failure. + */ +static int vm_builtin_utf8_decode(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + const unsigned char *zIn,*zEnd; + int nByte,c; + if( nArg < 1 ){ + /* Missing arguments,return null */ + ph7_result_null(pCtx); + return PH7_OK; + } + /* Extract the target string */ + zIn = (const unsigned char *)ph7_value_to_string(apArg[0],&nByte); + if( nByte < 1 ){ + /* Empty string,return null */ + ph7_result_null(pCtx); + return PH7_OK; + } + zEnd = &zIn[nByte]; + /* Start the decoding process */ + while( zIn < zEnd ){ + c = PH7_Utf8Read(zIn,zEnd,&zIn); + if( c == 0x0 ){ + break; + } + ph7_result_string(pCtx,(const char *)&c,(int)sizeof(char)); + } + return PH7_OK; +} +/* Table of built-in VM functions. */ +static const ph7_builtin_func aVmFunc[] = { + { "func_num_args" , vm_builtin_func_num_args }, + { "func_get_arg" , vm_builtin_func_get_arg }, + { "func_get_args" , vm_builtin_func_get_args }, + { "func_get_args_byref" , vm_builtin_func_get_args_byref }, + { "function_exists", vm_builtin_func_exists }, + { "is_callable" , vm_builtin_is_callable }, + { "get_defined_functions", vm_builtin_get_defined_func }, + { "register_shutdown_function",vm_builtin_register_shutdown_function }, + { "call_user_func", vm_builtin_call_user_func }, + { "call_user_func_array", vm_builtin_call_user_func_array }, + { "forward_static_call", vm_builtin_call_user_func }, + { "forward_static_call_array",vm_builtin_call_user_func_array }, + /* Constants management */ + { "defined", vm_builtin_defined }, + { "define", vm_builtin_define }, + { "constant", vm_builtin_constant }, + { "get_defined_constants", vm_builtin_get_defined_constants }, + /* Class/Object functions */ + { "class_alias", vm_builtin_class_alias }, + { "class_exists", vm_builtin_class_exists }, + { "property_exists", vm_builtin_property_exists }, + { "method_exists", vm_builtin_method_exists }, + { "interface_exists",vm_builtin_interface_exists }, + { "get_class", vm_builtin_get_class }, + { "get_parent_class",vm_builtin_get_parent_class }, + { "get_called_class",vm_builtin_get_called_class }, + { "get_declared_classes", vm_builtin_get_declared_classes }, + { "get_defined_classes", vm_builtin_get_declared_classes }, + { "get_declared_interfaces", vm_builtin_get_declared_interfaces}, + { "get_class_methods", vm_builtin_get_class_methods }, + { "get_class_vars", vm_builtin_get_class_vars }, + { "get_object_vars", vm_builtin_get_object_vars }, + { "is_subclass_of", vm_builtin_is_subclass_of }, + { "is_a", vm_builtin_is_a }, + /* Random numbers/strings generators */ + { "rand", vm_builtin_rand }, + { "mt_rand", vm_builtin_rand }, + { "rand_str", vm_builtin_rand_str }, + { "getrandmax", vm_builtin_getrandmax }, + { "mt_getrandmax", vm_builtin_getrandmax }, +#ifndef PH7_DISABLE_BUILTIN_FUNC +#if !defined(PH7_DISABLE_HASH_FUNC) + { "uniqid", vm_builtin_uniqid }, +#endif /* PH7_DISABLE_HASH_FUNC */ +#endif /* PH7_DISABLE_BUILTIN_FUNC */ + /* Language constructs functions */ + { "echo", vm_builtin_echo }, + { "print", vm_builtin_print }, + { "exit", vm_builtin_exit }, + { "die", vm_builtin_exit }, + { "eval", vm_builtin_eval }, + /* Variable handling functions */ + { "get_defined_vars",vm_builtin_get_defined_vars}, + { "gettype", vm_builtin_gettype }, + { "get_resource_type", vm_builtin_get_resource_type}, + { "isset", vm_builtin_isset }, + { "unset", vm_builtin_unset }, + { "var_dump", vm_builtin_var_dump }, + { "print_r", vm_builtin_print_r }, + { "var_export",vm_builtin_var_export }, + /* Ouput control functions */ + { "flush", vm_builtin_ob_flush }, + { "ob_clean", vm_builtin_ob_clean }, + { "ob_end_clean", vm_builtin_ob_end_clean }, + { "ob_end_flush", vm_builtin_ob_end_flush }, + { "ob_flush", vm_builtin_ob_flush }, + { "ob_get_clean", vm_builtin_ob_get_clean }, + { "ob_get_contents", vm_builtin_ob_get_contents}, + { "ob_get_flush", vm_builtin_ob_get_clean }, + { "ob_get_length", vm_builtin_ob_get_length }, + { "ob_get_level", vm_builtin_ob_get_level }, + { "ob_implicit_flush", vm_builtin_ob_implicit_flush}, + { "ob_get_level", vm_builtin_ob_get_level }, + { "ob_list_handlers", vm_builtin_ob_list_handlers }, + { "ob_start", vm_builtin_ob_start }, + /* Assertion functions */ + { "assert_options", vm_builtin_assert_options }, + { "assert", vm_builtin_assert }, + /* Error reporting functions */ + { "trigger_error",vm_builtin_trigger_error }, + { "user_error", vm_builtin_trigger_error }, + { "error_reporting",vm_builtin_error_reporting }, + { "error_log", vm_builtin_error_log }, + { "restore_exception_handler", vm_builtin_restore_exception_handler }, + { "set_exception_handler", vm_builtin_set_exception_handler }, + { "restore_error_handler", vm_builtin_restore_error_handler }, + { "set_error_handler",vm_builtin_set_error_handler }, + { "debug_backtrace", vm_builtin_debug_backtrace}, + { "error_get_last" , vm_builtin_debug_backtrace }, + { "debug_print_backtrace", vm_builtin_debug_print_backtrace }, + { "debug_string_backtrace",vm_builtin_debug_string_backtrace }, + /* Release info */ + {"ph7version", vm_builtin_ph7_version }, + {"ph7credits", vm_builtin_ph7_credits }, + {"ph7info", vm_builtin_ph7_credits }, + {"ph7_info", vm_builtin_ph7_credits }, + {"phpinfo", vm_builtin_ph7_credits }, + {"ph7copyright", vm_builtin_ph7_credits }, + /* hashmap */ + {"compact", vm_builtin_compact }, + {"extract", vm_builtin_extract }, + {"import_request_variables", vm_builtin_import_request_variables}, + /* URL related function */ + {"parse_url", vm_builtin_parse_url }, + /* Refer to 'builtin.c' for others string processing functions. */ +#ifndef PH7_DISABLE_BUILTIN_FUNC + /* XML processing functions */ + {"xml_parser_create", vm_builtin_xml_parser_create }, + {"xml_parser_create_ns", vm_builtin_xml_parser_create_ns}, + {"xml_parser_free", vm_builtin_xml_parser_free }, + {"xml_set_element_handler", vm_builtin_xml_set_element_handler}, + {"xml_set_character_data_handler", vm_builtin_xml_set_character_data_handler}, + {"xml_set_default_handler", vm_builtin_xml_set_default_handler }, + {"xml_set_end_namespace_decl_handler", vm_builtin_xml_set_end_namespace_decl_handler}, + {"xml_set_start_namespace_decl_handler",vm_builtin_xml_set_start_namespace_decl_handler}, + {"xml_set_processing_instruction_handler",vm_builtin_xml_set_processing_instruction_handler}, + {"xml_set_unparsed_entity_decl_handler",vm_builtin_xml_set_unparsed_entity_decl_handler}, + {"xml_set_notation_decl_handler",vm_builtin_xml_set_notation_decl_handler}, + {"xml_set_external_entity_ref_handler",vm_builtin_xml_set_external_entity_ref_handler}, + {"xml_get_current_line_number", vm_builtin_xml_get_current_line_number}, + {"xml_get_current_byte_index", vm_builtin_xml_get_current_byte_index }, + {"xml_set_object", vm_builtin_xml_set_object}, + {"xml_get_current_column_number",vm_builtin_xml_get_current_column_number}, + {"xml_get_error_code", vm_builtin_xml_get_error_code }, + {"xml_parse", vm_builtin_xml_parse }, + {"xml_parser_set_option", vm_builtin_xml_parser_set_option}, + {"xml_parser_get_option", vm_builtin_xml_parser_get_option}, + {"xml_error_string", vm_builtin_xml_error_string }, +#endif /* PH7_DISABLE_BUILTIN_FUNC */ + /* UTF-8 encoding/decoding */ + {"utf8_encode", vm_builtin_utf8_encode}, + {"utf8_decode", vm_builtin_utf8_decode}, + /* Command line processing */ + {"getopt", vm_builtin_getopt }, + /* JSON encoding/decoding */ + {"json_encode", vm_builtin_json_encode }, + {"json_last_error",vm_builtin_json_last_error}, + {"json_decode", vm_builtin_json_decode }, + {"serialize", vm_builtin_json_encode }, + {"unserialize", vm_builtin_json_decode }, + /* Files/URI inclusion facility */ + { "get_include_path", vm_builtin_get_include_path }, + { "get_included_files",vm_builtin_get_included_files}, + { "include", vm_builtin_include }, + { "include_once", vm_builtin_include_once }, + { "require", vm_builtin_require }, + { "require_once", vm_builtin_require_once }, +}; +/* + * Register the built-in VM functions defined above. + */ +static sxi32 VmRegisterSpecialFunction(ph7_vm *pVm) +{ + sxi32 rc; + sxu32 n; + for( n = 0 ; n < SX_ARRAYSIZE(aVmFunc) ; ++n ){ + /* Note that these special functions have access + * to the underlying virtual machine as their + * private data. + */ + rc = ph7_create_function(&(*pVm),aVmFunc[n].zName,aVmFunc[n].xFunc,&(*pVm)); + if( rc != SXRET_OK ){ + return rc; + } + } + return SXRET_OK; +} +/* + * Check if the given name refer to an installed class. + * Return a pointer to that class on success. NULL on failure. + */ +PH7_PRIVATE ph7_class * PH7_VmExtractClass( + ph7_vm *pVm, /* Target VM */ + const char *zName, /* Name of the target class */ + sxu32 nByte, /* zName length */ + sxi32 iLoadable, /* TRUE to return only loadable class + * [i.e: no abstract classes or interfaces] + */ + sxi32 iNest /* Nesting level (Not used) */ + ) +{ + SyHashEntry *pEntry; + ph7_class *pClass; + /* Perform a hash lookup */ + pEntry = SyHashGet(&pVm->hClass,(const void *)zName,nByte); + + if( pEntry == 0 ){ + /* No such entry,return NULL */ + iNest = 0; /* cc warning */ + return 0; + } + pClass = (ph7_class *)pEntry->pUserData; + if( !iLoadable ){ + /* Return the first class seen */ + return pClass; + }else{ + /* Check the collision list */ + while(pClass){ + if( (pClass->iFlags & (PH7_CLASS_INTERFACE|PH7_CLASS_ABSTRACT)) == 0 ){ + /* Class is loadable */ + return pClass; + } + /* Point to the next entry */ + pClass = pClass->pNextName; + } + } + /* No such loadable class */ + return 0; +} +/* + * Reference Table Implementation + * Status: stable + * Intro + * The implementation of the reference mechanism in the PH7 engine + * differ greatly from the one used by the zend engine. That is, + * the reference implementation is consistent,solid and it's + * behavior resemble the C++ reference mechanism. + * Refer to the official for more information on this powerful + * extension. + */ +/* + * Allocate a new reference entry. + */ +static VmRefObj * VmNewRefObj(ph7_vm *pVm,sxu32 nIdx) +{ + VmRefObj *pRef; + /* Allocate a new instance */ + pRef = (VmRefObj *)SyMemBackendPoolAlloc(&pVm->sAllocator,sizeof(VmRefObj)); + if( pRef == 0 ){ + return 0; + } + /* Zero the structure */ + SyZero(pRef,sizeof(VmRefObj)); + /* Initialize fields */ + SySetInit(&pRef->aReference,&pVm->sAllocator,sizeof(SyHashEntry *)); + SySetInit(&pRef->aArrEntries,&pVm->sAllocator,sizeof(ph7_hashmap_node *)); + pRef->nIdx = nIdx; + return pRef; +} +/* + * Default hash function used by the reference table + * for lookup/insertion operations. + */ +static sxu32 VmRefHash(sxu32 nIdx) +{ + /* Calculate the hash based on the memory object index */ + return nIdx ^ (nIdx << 8) ^ (nIdx >> 8); +} +/* + * Check if a memory object [i.e: a variable] is already installed + * in the reference table. + * Return a pointer to the entry (VmRefObj instance) on success.NULL + * otherwise. + * The implementation of the reference mechanism in the PH7 engine + * differ greatly from the one used by the zend engine. That is, + * the reference implementation is consistent,solid and it's + * behavior resemble the C++ reference mechanism. + * Refer to the official for more information on this powerful + * extension. + */ +static VmRefObj * VmRefObjExtract(ph7_vm *pVm,sxu32 nObjIdx) +{ + VmRefObj *pRef; + sxu32 nBucket; + /* Point to the appropriate bucket */ + nBucket = VmRefHash(nObjIdx) & (pVm->nRefSize - 1); + /* Perform the lookup */ + pRef = pVm->apRefObj[nBucket]; + for(;;){ + if( pRef == 0 ){ + break; + } + if( pRef->nIdx == nObjIdx ){ + /* Entry found */ + return pRef; + } + /* Point to the next entry */ + pRef = pRef->pNextCollide; + } + /* No such entry,return NULL */ + return 0; +} +/* + * Install a memory object [i.e: a variable] in the reference table. + * + * The implementation of the reference mechanism in the PH7 engine + * differ greatly from the one used by the zend engine. That is, + * the reference implementation is consistent,solid and it's + * behavior resemble the C++ reference mechanism. + * Refer to the official for more information on this powerful + * extension. + */ +static sxi32 VmRefObjInsert(ph7_vm *pVm,VmRefObj *pRef) +{ + sxu32 nBucket; + if( pVm->nRefUsed * 3 >= pVm->nRefSize ){ + VmRefObj **apNew; + sxu32 nNew; + /* Allocate a larger table */ + nNew = pVm->nRefSize << 1; + apNew = (VmRefObj **)SyMemBackendAlloc(&pVm->sAllocator,sizeof(VmRefObj *) * nNew); + if( apNew ){ + VmRefObj *pEntry = pVm->pRefList; + sxu32 n; + /* Zero the structure */ + SyZero((void *)apNew,nNew * sizeof(VmRefObj *)); + /* Rehash all referenced entries */ + for( n = 0 ; n < pVm->nRefUsed ; ++n ){ + /* Remove old collision links */ + pEntry->pNextCollide = pEntry->pPrevCollide = 0; + /* Point to the appropriate bucket */ + nBucket = VmRefHash(pEntry->nIdx) & (nNew - 1); + /* Insert the entry */ + pEntry->pNextCollide = apNew[nBucket]; + if( apNew[nBucket] ){ + apNew[nBucket]->pPrevCollide = pEntry; + } + apNew[nBucket] = pEntry; + /* Point to the next entry */ + pEntry = pEntry->pNext; + } + /* Release the old table */ + SyMemBackendFree(&pVm->sAllocator,pVm->apRefObj); + /* Install the new one */ + pVm->apRefObj = apNew; + pVm->nRefSize = nNew; + } + } + /* Point to the appropriate bucket */ + nBucket = VmRefHash(pRef->nIdx) & (pVm->nRefSize - 1); + /* Insert the entry */ + pRef->pNextCollide = pVm->apRefObj[nBucket]; + if( pVm->apRefObj[nBucket] ){ + pVm->apRefObj[nBucket]->pPrevCollide = pRef; + } + pVm->apRefObj[nBucket] = pRef; + MACRO_LD_PUSH(pVm->pRefList,pRef); + pVm->nRefUsed++; + return SXRET_OK; +} +/* + * Destroy a memory object [i.e: a variable] and remove it from + * the reference table. + * This function is invoked when the user perform an unset + * call [i.e: unset($var); ]. + * The implementation of the reference mechanism in the PH7 engine + * differ greatly from the one used by the zend engine. That is, + * the reference implementation is consistent,solid and it's + * behavior resemble the C++ reference mechanism. + * Refer to the official for more information on this powerful + * extension. + */ +static sxi32 VmRefObjUnlink(ph7_vm *pVm,VmRefObj *pRef) +{ + ph7_hashmap_node **apNode; + SyHashEntry **apEntry; + sxu32 n; + /* Point to the reference table */ + apNode = (ph7_hashmap_node **)SySetBasePtr(&pRef->aArrEntries); + apEntry = (SyHashEntry **)SySetBasePtr(&pRef->aReference); + /* Unlink the entry from the reference table */ + for( n = 0 ; n < SySetUsed(&pRef->aReference) ; n++ ){ + if( apEntry[n] ){ + SyHashDeleteEntry2(apEntry[n]); + } + } + for(n = 0 ; n < SySetUsed(&pRef->aArrEntries) ; ++n ){ + if( apNode[n] ){ + PH7_HashmapUnlinkNode(apNode[n],FALSE); + } + } + if( pRef->pPrevCollide ){ + pRef->pPrevCollide->pNextCollide = pRef->pNextCollide; + }else{ + pVm->apRefObj[VmRefHash(pRef->nIdx) & (pVm->nRefSize - 1)] = pRef->pNextCollide; + } + if( pRef->pNextCollide ){ + pRef->pNextCollide->pPrevCollide = pRef->pPrevCollide; + } + MACRO_LD_REMOVE(pVm->pRefList,pRef); + /* Release the node */ + SySetRelease(&pRef->aReference); + SySetRelease(&pRef->aArrEntries); + SyMemBackendPoolFree(&pVm->sAllocator,pRef); + pVm->nRefUsed--; + return SXRET_OK; +} +/* + * Install a memory object [i.e: a variable] in the reference table. + * The implementation of the reference mechanism in the PH7 engine + * differ greatly from the one used by the zend engine. That is, + * the reference implementation is consistent,solid and it's + * behavior resemble the C++ reference mechanism. + * Refer to the official for more information on this powerful + * extension. + */ +PH7_PRIVATE sxi32 PH7_VmRefObjInstall( + ph7_vm *pVm, /* Target VM */ + sxu32 nIdx, /* Memory object index in the global object pool */ + SyHashEntry *pEntry, /* Hash entry of this object */ + ph7_hashmap_node *pMapEntry, /* != NULL if the memory object is an array entry */ + sxi32 iFlags /* Control flags */ + ) +{ + VmFrame *pFrame = pVm->pFrame; + VmRefObj *pRef; + /* Check if the referenced object already exists */ + pRef = VmRefObjExtract(&(*pVm),nIdx); + if( pRef == 0 ){ + /* Create a new entry */ + pRef = VmNewRefObj(&(*pVm),nIdx); + if( pRef == 0 ){ + return SXERR_MEM; + } + pRef->iFlags = iFlags; + /* Install the entry */ + VmRefObjInsert(&(*pVm),pRef); + } + while( pFrame->pParent && (pFrame->iFlags & VM_FRAME_EXCEPTION) ){ + /* Safely ignore the exception frame */ + pFrame = pFrame->pParent; + } + if( pFrame->pParent != 0 && pEntry ){ + VmSlot sRef; + /* Local frame,record referenced entry so that it can + * be deleted when we leave this frame. + */ + sRef.nIdx = nIdx; + sRef.pUserData = pEntry; + if( SXRET_OK != SySetPut(&pFrame->sRef,(const void *)&sRef)) { + pEntry = 0; /* Do not record this entry */ + } + } + if( pEntry ){ + /* Address of the hash-entry */ + SySetPut(&pRef->aReference,(const void *)&pEntry); + } + if( pMapEntry ){ + /* Address of the hashmap node [i.e: Array entry] */ + SySetPut(&pRef->aArrEntries,(const void *)&pMapEntry); + } + return SXRET_OK; +} +/* + * Remove a memory object [i.e: a variable] from the reference table. + * The implementation of the reference mechanism in the PH7 engine + * differ greatly from the one used by the zend engine. That is, + * the reference implementation is consistent,solid and it's + * behavior resemble the C++ reference mechanism. + * Refer to the official for more information on this powerful + * extension. + */ +PH7_PRIVATE sxi32 PH7_VmRefObjRemove( + ph7_vm *pVm, /* Target VM */ + sxu32 nIdx, /* Memory object index in the global object pool */ + SyHashEntry *pEntry, /* Hash entry of this object */ + ph7_hashmap_node *pMapEntry /* != NULL if the memory object is an array entry */ + ) +{ + VmRefObj *pRef; + sxu32 n; + /* Check if the referenced object already exists */ + pRef = VmRefObjExtract(&(*pVm),nIdx); + if( pRef == 0 ){ + /* Not such entry */ + return SXERR_NOTFOUND; + } + /* Remove the desired entry */ + if( pEntry ){ + SyHashEntry **apEntry; + apEntry = (SyHashEntry **)SySetBasePtr(&pRef->aReference); + for( n = 0 ; n < SySetUsed(&pRef->aReference) ; n++ ){ + if( apEntry[n] == pEntry ){ + /* Nullify the entry */ + apEntry[n] = 0; + /* + * NOTE: + * In future releases,think to add a free pool of entries,so that + * we avoid wasting spaces. + */ + } + } + } + if( pMapEntry ){ + ph7_hashmap_node **apNode; + apNode = (ph7_hashmap_node **)SySetBasePtr(&pRef->aArrEntries); + for(n = 0 ; n < SySetUsed(&pRef->aArrEntries) ; n++ ){ + if( apNode[n] == pMapEntry ){ + /* nullify the entry */ + apNode[n] = 0; + } + } + } + return SXRET_OK; +} +#ifndef PH7_DISABLE_BUILTIN_FUNC +/* + * Extract the IO stream device associated with a given scheme. + * Return a pointer to an instance of ph7_io_stream when the scheme + * have an associated IO stream registered with it. NULL otherwise. + * If no scheme:// is avalilable then the file:// scheme is assumed. + * For more information on how to register IO stream devices,please + * refer to the official documentation. + */ +PH7_PRIVATE const ph7_io_stream * PH7_VmGetStreamDevice( + ph7_vm *pVm, /* Target VM */ + const char **pzDevice, /* Full path,URI,... */ + int nByte /* *pzDevice length*/ + ) +{ + const char *zIn,*zEnd,*zCur,*zNext; + ph7_io_stream **apStream,*pStream; + SyString sDev,sCur; + sxu32 n,nEntry; + int rc; + /* Check if a scheme [i.e: file://,http://,zip://...] is available */ + zNext = zCur = zIn = *pzDevice; + zEnd = &zIn[nByte]; + while( zIn < zEnd ){ + if( zIn < &zEnd[-3]/*://*/ && zIn[0] == ':' && zIn[1] == '/' && zIn[2] == '/' ){ + /* Got one */ + zNext = &zIn[sizeof("://")-1]; + break; + } + /* Advance the cursor */ + zIn++; + } + if( zIn >= zEnd ){ + /* No such scheme,return the default stream */ + return pVm->pDefStream; + } + SyStringInitFromBuf(&sDev,zCur,zIn-zCur); + /* Remove leading and trailing white spaces */ + SyStringFullTrim(&sDev); + /* Perform a linear lookup on the installed stream devices */ + apStream = (ph7_io_stream **)SySetBasePtr(&pVm->aIOstream); + nEntry = SySetUsed(&pVm->aIOstream); + for( n = 0 ; n < nEntry ; n++ ){ + pStream = apStream[n]; + SyStringInitFromBuf(&sCur,pStream->zName,SyStrlen(pStream->zName)); + /* Perfrom a case-insensitive comparison */ + rc = SyStringCmp(&sDev,&sCur,SyStrnicmp); + if( rc == 0 ){ + /* Stream device found */ + *pzDevice = zNext; + return pStream; + } + } + /* No such stream,return NULL */ + return 0; +} +#endif /* PH7_DISABLE_BUILTIN_FUNC */ +/* + * Section: + * HTTP/URI related routines. + * Authors: + * Symisc Systems,devel@symisc.net. + * Copyright (C) Symisc Systems,http://ph7.symisc.net + * Status: + * Stable. + */ + /* + * URI Parser: Split an URI into components [i.e: Host,Path,Query,...]. + * URI syntax: [method:/][/[user[:pwd]@]host[:port]/][document] + * This almost, but not quite, RFC1738 URI syntax. + * This routine is not a validator,it does not check for validity + * nor decode URI parts,the only thing this routine does is splitting + * the input to its fields. + * Upper layer are responsible of decoding and validating URI parts. + * On success,this function populate the "SyhttpUri" structure passed + * as the first argument. Otherwise SXERR_* is returned when a malformed + * input is encountered. + */ + static sxi32 VmHttpSplitURI(SyhttpUri *pOut,const char *zUri,sxu32 nLen) + { + const char *zEnd = &zUri[nLen]; + sxu8 bHostOnly = FALSE; + sxu8 bIPv6 = FALSE ; + const char *zCur; + SyString *pComp; + sxu32 nPos = 0; + sxi32 rc; + /* Zero the structure first */ + SyZero(pOut,sizeof(SyhttpUri)); + /* Remove leading and trailing white spaces */ + SyStringInitFromBuf(&pOut->sRaw,zUri,nLen); + SyStringFullTrim(&pOut->sRaw); + /* Find the first '/' separator */ + rc = SyByteFind(zUri,(sxu32)(zEnd - zUri),'/',&nPos); + if( rc != SXRET_OK ){ + /* Assume a host name only */ + zCur = zEnd; + bHostOnly = TRUE; + goto ProcessHost; + } + zCur = &zUri[nPos]; + if( zUri != zCur && zCur[-1] == ':' ){ + /* Extract a scheme: + * Not that we can get an invalid scheme here. + * Fortunately the caller can discard any URI by comparing this scheme with its + * registered schemes and will report the error as soon as his comparison function + * fail. + */ + pComp = &pOut->sScheme; + SyStringInitFromBuf(pComp,zUri,(sxu32)(zCur - zUri - 1)); + SyStringLeftTrim(pComp); + } + if( zCur[1] != '/' ){ + if( zCur == zUri || zCur[-1] == ':' ){ + /* No authority */ + goto PathSplit; + } + /* There is something here , we will assume its an authority + * and someone has forgot the two prefix slashes "//", + * sooner or later we will detect if we are dealing with a malicious + * user or not,but now assume we are dealing with an authority + * and let the caller handle all the validation process. + */ + goto ProcessHost; + } + zUri = &zCur[2]; + zCur = zEnd; + rc = SyByteFind(zUri,(sxu32)(zEnd - zUri),'/',&nPos); + if( rc == SXRET_OK ){ + zCur = &zUri[nPos]; + } + ProcessHost: + /* Extract user information if present */ + rc = SyByteFind(zUri,(sxu32)(zCur - zUri),'@',&nPos); + if( rc == SXRET_OK ){ + if( nPos > 0 ){ + sxu32 nPassOfft; /* Password offset */ + pComp = &pOut->sUser; + SyStringInitFromBuf(pComp,zUri,nPos); + /* Extract the password if available */ + rc = SyByteFind(zUri,(sxu32)(zCur - zUri),':',&nPassOfft); + if( rc == SXRET_OK && nPassOfft < nPos){ + pComp->nByte = nPassOfft; + pComp = &pOut->sPass; + pComp->zString = &zUri[nPassOfft+sizeof(char)]; + pComp->nByte = nPos - nPassOfft - 1; + } + /* Update the cursor */ + zUri = &zUri[nPos+1]; + }else{ + zUri++; + } + } + pComp = &pOut->sHost; + while( zUri < zCur && SyisSpace(zUri[0])){ + zUri++; + } + SyStringInitFromBuf(pComp,zUri,(sxu32)(zCur - zUri)); + if( pComp->zString[0] == '[' ){ + /* An IPv6 Address: Make a simple naive test + */ + zUri++; pComp->zString++; pComp->nByte = 0; + while( ((unsigned char)zUri[0] < 0xc0 && SyisHex(zUri[0])) || zUri[0] == ':' ){ + zUri++; pComp->nByte++; + } + if( zUri[0] != ']' ){ + return SXERR_CORRUPT; /* Malformed IPv6 address */ + } + zUri++; + bIPv6 = TRUE; + } + /* Extract a port number if available */ + rc = SyByteFind(zUri,(sxu32)(zCur - zUri),':',&nPos); + if( rc == SXRET_OK ){ + if( bIPv6 == FALSE ){ + pComp->nByte = (sxu32)(&zUri[nPos] - zUri); + } + pComp = &pOut->sPort; + SyStringInitFromBuf(pComp,&zUri[nPos+1],(sxu32)(zCur - &zUri[nPos+1])); + } + if( bHostOnly == TRUE ){ + return SXRET_OK; + } +PathSplit: + zUri = zCur; + pComp = &pOut->sPath; + SyStringInitFromBuf(pComp,zUri,(sxu32)(zEnd-zUri)); + if( pComp->nByte == 0 ){ + return SXRET_OK; /* Empty path */ + } + if( SXRET_OK == SyByteFind(zUri,(sxu32)(zEnd-zUri),'?',&nPos) ){ + pComp->nByte = nPos; /* Update path length */ + pComp = &pOut->sQuery; + SyStringInitFromBuf(pComp,&zUri[nPos+1],(sxu32)(zEnd-&zUri[nPos+1])); + } + if( SXRET_OK == SyByteFind(zUri,(sxu32)(zEnd-zUri),'#',&nPos) ){ + /* Update path or query length */ + if( pComp == &pOut->sPath ){ + pComp->nByte = nPos; + }else{ + if( &zUri[nPos] < (char *)SyStringData(pComp) ){ + /* Malformed syntax : Query must be present before fragment */ + return SXERR_SYNTAX; + } + pComp->nByte -= (sxu32)(zEnd - &zUri[nPos]); + } + pComp = &pOut->sFragment; + SyStringInitFromBuf(pComp,&zUri[nPos+1],(sxu32)(zEnd-&zUri[nPos+1])) + } + return SXRET_OK; + } + /* + * Extract a single line from a raw HTTP request. + * Return SXRET_OK on success,SXERR_EOF when end of input + * and SXERR_MORE when more input is needed. + */ +static sxi32 VmGetNextLine(SyString *pCursor,SyString *pCurrent) +{ + const char *zIn; + sxu32 nPos; + /* Jump leading white spaces */ + SyStringLeftTrim(pCursor); + if( pCursor->nByte < 1 ){ + SyStringInitFromBuf(pCurrent,0,0); + return SXERR_EOF; /* End of input */ + } + zIn = SyStringData(pCursor); + if( SXRET_OK != SyByteListFind(pCursor->zString,pCursor->nByte,"\r\n",&nPos) ){ + /* Line not found,tell the caller to read more input from source */ + SyStringDupPtr(pCurrent,pCursor); + return SXERR_MORE; + } + pCurrent->zString = zIn; + pCurrent->nByte = nPos; + /* advance the cursor so we can call this routine again */ + pCursor->zString = &zIn[nPos]; + pCursor->nByte -= nPos; + return SXRET_OK; + } + /* + * Split a single MIME header into a name value pair. + * This function return SXRET_OK,SXERR_CONTINUE on success. + * Otherwise SXERR_NEXT is returned when a malformed header + * is encountered. + * Note: This function handle also mult-line headers. + */ + static sxi32 VmHttpProcessOneHeader(SyhttpHeader *pHdr,SyhttpHeader *pLast,const char *zLine,sxu32 nLen) + { + SyString *pName; + sxu32 nPos; + sxi32 rc; + if( nLen < 1 ){ + return SXERR_NEXT; + } + /* Check for multi-line header */ + if( pLast && (zLine[-1] == ' ' || zLine[-1] == '\t') ){ + SyString *pTmp = &pLast->sValue; + SyStringFullTrim(pTmp); + if( pTmp->nByte == 0 ){ + SyStringInitFromBuf(pTmp,zLine,nLen); + }else{ + /* Update header value length */ + pTmp->nByte = (sxu32)(&zLine[nLen] - pTmp->zString); + } + /* Simply tell the caller to reset its states and get another line */ + return SXERR_CONTINUE; + } + /* Split the header */ + pName = &pHdr->sName; + rc = SyByteFind(zLine,nLen,':',&nPos); + if(rc != SXRET_OK ){ + return SXERR_NEXT; /* Malformed header;Check the next entry */ + } + SyStringInitFromBuf(pName,zLine,nPos); + SyStringFullTrim(pName); + /* Extract a header value */ + SyStringInitFromBuf(&pHdr->sValue,&zLine[nPos + 1],nLen - nPos - 1); + /* Remove leading and trailing whitespaces */ + SyStringFullTrim(&pHdr->sValue); + return SXRET_OK; + } + /* + * Extract all MIME headers associated with a HTTP request. + * After processing the first line of a HTTP request,the following + * routine is called in order to extract MIME headers. + * This function return SXRET_OK on success,SXERR_MORE when it needs + * more inputs. + * Note: Any malformed header is simply discarded. + */ + static sxi32 VmHttpExtractHeaders(SyString *pRequest,SySet *pOut) + { + SyhttpHeader *pLast = 0; + SyString sCurrent; + SyhttpHeader sHdr; + sxu8 bEol; + sxi32 rc; + if( SySetUsed(pOut) > 0 ){ + pLast = (SyhttpHeader *)SySetAt(pOut,SySetUsed(pOut)-1); + } + bEol = FALSE; + for(;;){ + SyZero(&sHdr,sizeof(SyhttpHeader)); + /* Extract a single line from the raw HTTP request */ + rc = VmGetNextLine(pRequest,&sCurrent); + if(rc != SXRET_OK ){ + if( sCurrent.nByte < 1 ){ + break; + } + bEol = TRUE; + } + /* Process the header */ + if( SXRET_OK == VmHttpProcessOneHeader(&sHdr,pLast,sCurrent.zString,sCurrent.nByte)){ + if( SXRET_OK != SySetPut(pOut,(const void *)&sHdr) ){ + break; + } + /* Retrieve the last parsed header so we can handle multi-line header + * in case we face one of them. + */ + pLast = (SyhttpHeader *)SySetPeek(pOut); + } + if( bEol ){ + break; + } + } /* for(;;) */ + return SXRET_OK; + } + /* + * Process the first line of a HTTP request. + * This routine perform the following operations + * 1) Extract the HTTP method. + * 2) Split the request URI to it's fields [ie: host,path,query,...]. + * 3) Extract the HTTP protocol version. + */ + static sxi32 VmHttpProcessFirstLine( + SyString *pRequest, /* Raw HTTP request */ + sxi32 *pMethod, /* OUT: HTTP method */ + SyhttpUri *pUri, /* OUT: Parse of the URI */ + sxi32 *pProto /* OUT: HTTP protocol */ + ) + { + static const char *azMethods[] = { "get","post","head","put"}; + static const sxi32 aMethods[] = { HTTP_METHOD_GET,HTTP_METHOD_POST,HTTP_METHOD_HEAD,HTTP_METHOD_PUT}; + const char *zIn,*zEnd,*zPtr; + SyString sLine; + sxu32 nLen; + sxi32 rc; + /* Extract the first line and update the pointer */ + rc = VmGetNextLine(pRequest,&sLine); + if( rc != SXRET_OK ){ + return rc; + } + if ( sLine.nByte < 1 ){ + /* Empty HTTP request */ + return SXERR_EMPTY; + } + /* Delimit the line and ignore trailing and leading white spaces */ + zIn = sLine.zString; + zEnd = &zIn[sLine.nByte]; + while( zIn < zEnd && (unsigned char)zIn[0] < 0xc0 && SyisSpace(zIn[0]) ){ + zIn++; + } + /* Extract the HTTP method */ + zPtr = zIn; + while( zIn < zEnd && !SyisSpace(zIn[0]) ){ + zIn++; + } + *pMethod = HTTP_METHOD_OTHR; + if( zIn > zPtr ){ + sxu32 i; + nLen = (sxu32)(zIn-zPtr); + for( i = 0 ; i < SX_ARRAYSIZE(azMethods) ; ++i ){ + if( SyStrnicmp(azMethods[i],zPtr,nLen) == 0 ){ + *pMethod = aMethods[i]; + break; + } + } + } + /* Jump trailing white spaces */ + while( zIn < zEnd && (unsigned char)zIn[0] < 0xc0 && SyisSpace(zIn[0]) ){ + zIn++; + } + /* Extract the request URI */ + zPtr = zIn; + while( zIn < zEnd && !SyisSpace(zIn[0]) ){ + zIn++; + } + if( zIn > zPtr ){ + nLen = (sxu32)(zIn-zPtr); + /* Split raw URI to it's fields */ + VmHttpSplitURI(pUri,zPtr,nLen); + } + /* Jump trailing white spaces */ + while( zIn < zEnd && (unsigned char)zIn[0] < 0xc0 && SyisSpace(zIn[0]) ){ + zIn++; + } + /* Extract the HTTP version */ + zPtr = zIn; + while( zIn < zEnd && !SyisSpace(zIn[0]) ){ + zIn++; + } + *pProto = HTTP_PROTO_11; /* HTTP/1.1 */ + rc = 1; + if( zIn > zPtr ){ + rc = SyStrnicmp(zPtr,"http/1.0",(sxu32)(zIn-zPtr)); + } + if( !rc ){ + *pProto = HTTP_PROTO_10; /* HTTP/1.0 */ + } + return SXRET_OK; + } + /* + * Tokenize,decode and split a raw query encoded as: "x-www-form-urlencoded" + * into a name value pair. + * Note that this encoding is implicit in GET based requests. + * After the tokenization process,register the decoded queries + * in the $_GET/$_POST/$_REQUEST superglobals arrays. + */ + static sxi32 VmHttpSplitEncodedQuery( + ph7_vm *pVm, /* Target VM */ + SyString *pQuery, /* Raw query to decode */ + SyBlob *pWorker, /* Working buffer */ + int is_post /* TRUE if we are dealing with a POST request */ + ) + { + const char *zEnd = &pQuery->zString[pQuery->nByte]; + const char *zIn = pQuery->zString; + ph7_value *pGet,*pRequest; + SyString sName,sValue; + const char *zPtr; + sxu32 nBlobOfft; + /* Extract superglobals */ + if( is_post ){ + /* $_POST superglobal */ + pGet = VmExtractSuper(&(*pVm),"_POST",sizeof("_POST")-1); + }else{ + /* $_GET superglobal */ + pGet = VmExtractSuper(&(*pVm),"_GET",sizeof("_GET")-1); + } + pRequest = VmExtractSuper(&(*pVm),"_REQUEST",sizeof("_REQUEST")-1); + /* Split up the raw query */ + for(;;){ + /* Jump leading white spaces */ + while(zIn < zEnd && SyisSpace(zIn[0]) ){ + zIn++; + } + if( zIn >= zEnd ){ + break; + } + zPtr = zIn; + while( zPtr < zEnd && zPtr[0] != '=' && zPtr[0] != '&' && zPtr[0] != ';' ){ + zPtr++; + } + /* Reset the working buffer */ + SyBlobReset(pWorker); + /* Decode the entry */ + SyUriDecode(zIn,(sxu32)(zPtr-zIn),PH7_VmBlobConsumer,pWorker,TRUE); + /* Save the entry */ + sName.nByte = SyBlobLength(pWorker); + sValue.zString = 0; + sValue.nByte = 0; + if( zPtr < zEnd && zPtr[0] == '=' ){ + zPtr++; + zIn = zPtr; + /* Store field value */ + while( zPtr < zEnd && zPtr[0] != '&' && zPtr[0] != ';' ){ + zPtr++; + } + if( zPtr > zIn ){ + /* Decode the value */ + nBlobOfft = SyBlobLength(pWorker); + SyUriDecode(zIn,(sxu32)(zPtr-zIn),PH7_VmBlobConsumer,pWorker,TRUE); + sValue.zString = (const char *)SyBlobDataAt(pWorker,nBlobOfft); + sValue.nByte = SyBlobLength(pWorker) - nBlobOfft; + + } + /* Synchronize pointers */ + zIn = zPtr; + } + sName.zString = (const char *)SyBlobData(pWorker); + /* Install the decoded query in the $_GET/$_REQUEST array */ + if( pGet && (pGet->iFlags & MEMOBJ_HASHMAP) ){ + VmHashmapInsert((ph7_hashmap *)pGet->x.pOther, + sName.zString,(int)sName.nByte, + sValue.zString,(int)sValue.nByte + ); + } + if( pRequest && (pRequest->iFlags & MEMOBJ_HASHMAP) ){ + VmHashmapInsert((ph7_hashmap *)pRequest->x.pOther, + sName.zString,(int)sName.nByte, + sValue.zString,(int)sValue.nByte + ); + } + /* Advance the pointer */ + zIn = &zPtr[1]; + } + /* All done*/ + return SXRET_OK; + } + /* + * Extract MIME header value from the given set. + * Return header value on success. NULL otherwise. + */ + static SyString * VmHttpExtractHeaderValue(SySet *pSet,const char *zMime,sxu32 nByte) + { + SyhttpHeader *aMime,*pMime; + SyString sMime; + sxu32 n; + SyStringInitFromBuf(&sMime,zMime,nByte); + /* Point to the MIME entries */ + aMime = (SyhttpHeader *)SySetBasePtr(pSet); + /* Perform the lookup */ + for( n = 0 ; n < SySetUsed(pSet) ; ++n ){ + pMime = &aMime[n]; + if( SyStringCmp(&sMime,&pMime->sName,SyStrnicmp) == 0 ){ + /* Header found,return it's associated value */ + return &pMime->sValue; + } + } + /* No such MIME header */ + return 0; + } + /* + * Tokenize and decode a raw "Cookie:" MIME header into a name value pair + * and insert it's fields [i.e name,value] in the $_COOKIE superglobal. + */ + static sxi32 VmHttpPorcessCookie(ph7_vm *pVm,SyBlob *pWorker,const char *zIn,sxu32 nByte) + { + const char *zPtr,*zDelimiter,*zEnd = &zIn[nByte]; + SyString sName,sValue; + ph7_value *pCookie; + sxu32 nOfft; + /* Make sure the $_COOKIE superglobal is available */ + pCookie = VmExtractSuper(&(*pVm),"_COOKIE",sizeof("_COOKIE")-1); + if( pCookie == 0 || (pCookie->iFlags & MEMOBJ_HASHMAP) == 0 ){ + /* $_COOKIE superglobal not available */ + return SXERR_NOTFOUND; + } + for(;;){ + /* Jump leading white spaces */ + while( zIn < zEnd && SyisSpace(zIn[0]) ){ + zIn++; + } + if( zIn >= zEnd ){ + break; + } + /* Reset the working buffer */ + SyBlobReset(pWorker); + zDelimiter = zIn; + /* Delimit the name[=value]; pair */ + while( zDelimiter < zEnd && zDelimiter[0] != ';' ){ + zDelimiter++; + } + zPtr = zIn; + while( zPtr < zDelimiter && zPtr[0] != '=' ){ + zPtr++; + } + /* Decode the cookie */ + SyUriDecode(zIn,(sxu32)(zPtr-zIn),PH7_VmBlobConsumer,pWorker,TRUE); + sName.nByte = SyBlobLength(pWorker); + zPtr++; + sValue.zString = 0; + sValue.nByte = 0; + if( zPtr < zDelimiter ){ + /* Got a Cookie value */ + nOfft = SyBlobLength(pWorker); + SyUriDecode(zPtr,(sxu32)(zDelimiter-zPtr),PH7_VmBlobConsumer,pWorker,TRUE); + SyStringInitFromBuf(&sValue,SyBlobDataAt(pWorker,nOfft),SyBlobLength(pWorker)-nOfft); + } + /* Synchronize pointers */ + zIn = &zDelimiter[1]; + /* Perform the insertion */ + sName.zString = (const char *)SyBlobData(pWorker); + VmHashmapInsert((ph7_hashmap *)pCookie->x.pOther, + sName.zString,(int)sName.nByte, + sValue.zString,(int)sValue.nByte + ); + } + return SXRET_OK; + } + /* + * Process a full HTTP request and populate the appropriate arrays + * such as $_SERVER,$_GET,$_POST,$_COOKIE,$_REQUEST,... with the information + * extracted from the raw HTTP request. As an extension Symisc introduced + * the $_HEADER array which hold a copy of the processed HTTP MIME headers + * and their associated values. [i.e: $_HEADER['Server'],$_HEADER['User-Agent'],...]. + * This function return SXRET_OK on success. Any other return value indicates + * a malformed HTTP request. + */ + static sxi32 VmHttpProcessRequest(ph7_vm *pVm,const char *zRequest,int nByte) + { + SyString *pName,*pValue,sRequest; /* Raw HTTP request */ + ph7_value *pHeaderArray; /* $_HEADER superglobal (Symisc eXtension to the PHP specification)*/ + SyhttpHeader *pHeader; /* MIME header */ + SyhttpUri sUri; /* Parse of the raw URI*/ + SyBlob sWorker; /* General purpose working buffer */ + SySet sHeader; /* MIME headers set */ + sxi32 iMethod; /* HTTP method [i.e: GET,POST,HEAD...]*/ + sxi32 iVer; /* HTTP protocol version */ + sxi32 rc; + SyStringInitFromBuf(&sRequest,zRequest,nByte); + SySetInit(&sHeader,&pVm->sAllocator,sizeof(SyhttpHeader)); + SyBlobInit(&sWorker,&pVm->sAllocator); + /* Ignore leading and trailing white spaces*/ + SyStringFullTrim(&sRequest); + /* Process the first line */ + rc = VmHttpProcessFirstLine(&sRequest,&iMethod,&sUri,&iVer); + if( rc != SXRET_OK ){ + return rc; + } + /* Process MIME headers */ + VmHttpExtractHeaders(&sRequest,&sHeader); + /* + * Setup $_SERVER environments + */ + /* 'SERVER_PROTOCOL': Name and revision of the information protocol via which the page was requested */ + ph7_vm_config(pVm, + PH7_VM_CONFIG_SERVER_ATTR, + "SERVER_PROTOCOL", + iVer == HTTP_PROTO_10 ? "HTTP/1.0" : "HTTP/1.1", + sizeof("HTTP/1.1")-1 + ); + /* 'REQUEST_METHOD': Which request method was used to access the page */ + ph7_vm_config(pVm, + PH7_VM_CONFIG_SERVER_ATTR, + "REQUEST_METHOD", + iMethod == HTTP_METHOD_GET ? "GET" : + (iMethod == HTTP_METHOD_POST ? "POST": + (iMethod == HTTP_METHOD_PUT ? "PUT" : + (iMethod == HTTP_METHOD_HEAD ? "HEAD" : "OTHER"))), + -1 /* Compute attribute length automatically */ + ); + if( SyStringLength(&sUri.sQuery) > 0 && iMethod == HTTP_METHOD_GET ){ + pValue = &sUri.sQuery; + /* 'QUERY_STRING': The query string, if any, via which the page was accessed */ + ph7_vm_config(pVm, + PH7_VM_CONFIG_SERVER_ATTR, + "QUERY_STRING", + pValue->zString, + pValue->nByte + ); + /* Decoded the raw query */ + VmHttpSplitEncodedQuery(&(*pVm),pValue,&sWorker,FALSE); + } + /* REQUEST_URI: The URI which was given in order to access this page; for instance, '/index.html' */ + pValue = &sUri.sRaw; + ph7_vm_config(pVm, + PH7_VM_CONFIG_SERVER_ATTR, + "REQUEST_URI", + pValue->zString, + pValue->nByte + ); + /* + * 'PATH_INFO' + * 'ORIG_PATH_INFO' + * Contains any client-provided pathname information trailing the actual script filename but preceding + * the query string, if available. For instance, if the current script was accessed via the URL + * http://www.example.com/php/path_info.php/some/stuff?foo=bar, then $_SERVER['PATH_INFO'] would contain + * /some/stuff. + */ + pValue = &sUri.sPath; + ph7_vm_config(pVm, + PH7_VM_CONFIG_SERVER_ATTR, + "PATH_INFO", + pValue->zString, + pValue->nByte + ); + ph7_vm_config(pVm, + PH7_VM_CONFIG_SERVER_ATTR, + "ORIG_PATH_INFO", + pValue->zString, + pValue->nByte + ); + /* 'HTTP_ACCEPT': Contents of the Accept: header from the current request, if there is one */ + pValue = VmHttpExtractHeaderValue(&sHeader,"Accept",sizeof("Accept")-1); + if( pValue ){ + ph7_vm_config(pVm, + PH7_VM_CONFIG_SERVER_ATTR, + "HTTP_ACCEPT", + pValue->zString, + pValue->nByte + ); + } + /* 'HTTP_ACCEPT_CHARSET': Contents of the Accept-Charset: header from the current request, if there is one. */ + pValue = VmHttpExtractHeaderValue(&sHeader,"Accept-Charset",sizeof("Accept-Charset")-1); + if( pValue ){ + ph7_vm_config(pVm, + PH7_VM_CONFIG_SERVER_ATTR, + "HTTP_ACCEPT_CHARSET", + pValue->zString, + pValue->nByte + ); + } + /* 'HTTP_ACCEPT_ENCODING': Contents of the Accept-Encoding: header from the current request, if there is one. */ + pValue = VmHttpExtractHeaderValue(&sHeader,"Accept-Encoding",sizeof("Accept-Encoding")-1); + if( pValue ){ + ph7_vm_config(pVm, + PH7_VM_CONFIG_SERVER_ATTR, + "HTTP_ACCEPT_ENCODING", + pValue->zString, + pValue->nByte + ); + } + /* 'HTTP_ACCEPT_LANGUAGE': Contents of the Accept-Language: header from the current request, if there is one */ + pValue = VmHttpExtractHeaderValue(&sHeader,"Accept-Language",sizeof("Accept-Language")-1); + if( pValue ){ + ph7_vm_config(pVm, + PH7_VM_CONFIG_SERVER_ATTR, + "HTTP_ACCEPT_LANGUAGE", + pValue->zString, + pValue->nByte + ); + } + /* 'HTTP_CONNECTION': Contents of the Connection: header from the current request, if there is one. */ + pValue = VmHttpExtractHeaderValue(&sHeader,"Connection",sizeof("Connection")-1); + if( pValue ){ + ph7_vm_config(pVm, + PH7_VM_CONFIG_SERVER_ATTR, + "HTTP_CONNECTION", + pValue->zString, + pValue->nByte + ); + } + /* 'HTTP_HOST': Contents of the Host: header from the current request, if there is one. */ + pValue = VmHttpExtractHeaderValue(&sHeader,"Host",sizeof("Host")-1); + if( pValue ){ + ph7_vm_config(pVm, + PH7_VM_CONFIG_SERVER_ATTR, + "HTTP_HOST", + pValue->zString, + pValue->nByte + ); + } + /* 'HTTP_REFERER': Contents of the Referer: header from the current request, if there is one. */ + pValue = VmHttpExtractHeaderValue(&sHeader,"Referer",sizeof("Referer")-1); + if( pValue ){ + ph7_vm_config(pVm, + PH7_VM_CONFIG_SERVER_ATTR, + "HTTP_REFERER", + pValue->zString, + pValue->nByte + ); + } + /* 'HTTP_USER_AGENT': Contents of the Referer: header from the current request, if there is one. */ + pValue = VmHttpExtractHeaderValue(&sHeader,"User-Agent",sizeof("User-Agent")-1); + if( pValue ){ + ph7_vm_config(pVm, + PH7_VM_CONFIG_SERVER_ATTR, + "HTTP_USER_AGENT", + pValue->zString, + pValue->nByte + ); + } + /* 'PHP_AUTH_DIGEST': When doing Digest HTTP authentication this variable is set to the 'Authorization' + * header sent by the client (which you should then use to make the appropriate validation). + */ + pValue = VmHttpExtractHeaderValue(&sHeader,"Authorization",sizeof("Authorization")-1); + if( pValue ){ + ph7_vm_config(pVm, + PH7_VM_CONFIG_SERVER_ATTR, + "PHP_AUTH_DIGEST", + pValue->zString, + pValue->nByte + ); + ph7_vm_config(pVm, + PH7_VM_CONFIG_SERVER_ATTR, + "PHP_AUTH", + pValue->zString, + pValue->nByte + ); + } + /* Install all clients HTTP headers in the $_HEADER superglobal */ + pHeaderArray = VmExtractSuper(&(*pVm),"_HEADER",sizeof("_HEADER")-1); + /* Iterate throw the available MIME headers*/ + SySetResetCursor(&sHeader); + pHeader = 0; /* stupid cc warning */ + while( SXRET_OK == SySetGetNextEntry(&sHeader,(void **)&pHeader) ){ + pName = &pHeader->sName; + pValue = &pHeader->sValue; + if( pHeaderArray && (pHeaderArray->iFlags & MEMOBJ_HASHMAP)){ + /* Insert the MIME header and it's associated value */ + VmHashmapInsert((ph7_hashmap *)pHeaderArray->x.pOther, + pName->zString,(int)pName->nByte, + pValue->zString,(int)pValue->nByte + ); + } + if( pName->nByte == sizeof("Cookie")-1 && SyStrnicmp(pName->zString,"Cookie",sizeof("Cookie")-1) == 0 + && pValue->nByte > 0){ + /* Process the name=value pair and insert them in the $_COOKIE superglobal array */ + VmHttpPorcessCookie(&(*pVm),&sWorker,pValue->zString,pValue->nByte); + } + } + if( iMethod == HTTP_METHOD_POST ){ + /* Extract raw POST data */ + pValue = VmHttpExtractHeaderValue(&sHeader,"Content-Type",sizeof("Content-Type") - 1); + if( pValue && pValue->nByte >= sizeof("application/x-www-form-urlencoded") - 1 && + SyMemcmp("application/x-www-form-urlencoded",pValue->zString,pValue->nByte) == 0 ){ + /* Extract POST data length */ + pValue = VmHttpExtractHeaderValue(&sHeader,"Content-Length",sizeof("Content-Length") - 1); + if( pValue ){ + sxi32 iLen = 0; /* POST data length */ + SyStrToInt32(pValue->zString,pValue->nByte,(void *)&iLen,0); + if( iLen > 0 ){ + /* Remove leading and trailing white spaces */ + SyStringFullTrim(&sRequest); + if( (int)sRequest.nByte > iLen ){ + sRequest.nByte = (sxu32)iLen; + } + /* Decode POST data now */ + VmHttpSplitEncodedQuery(&(*pVm),&sRequest,&sWorker,TRUE); + } + } + } + } + /* All done,clean-up the mess left behind */ + SySetRelease(&sHeader); + SyBlobRelease(&sWorker); + return SXRET_OK; + } + +/* + * ---------------------------------------------------------- + * File: vfs.c + * MD5: bfe61218da83cefd9f25149de0610d3b + * ---------------------------------------------------------- + */ +/* + * Symisc PH7: An embeddable bytecode compiler and a virtual machine for the PHP(5) programming language. + * Copyright (C) 2011-2012, Symisc Systems http://ph7.symisc.net/ + * Version 2.1.4 + * For information on licensing,redistribution of this file,and for a DISCLAIMER OF ALL WARRANTIES + * please contact Symisc Systems via: + * legal@symisc.net + * licensing@symisc.net + * contact@symisc.net + * or visit: + * http://ph7.symisc.net/ + */ + /* $SymiscID: vfs.c v2.1 Win7 2012-05-24 01:18 devel $ */ +#ifndef PH7_AMALGAMATION +#include "ph7int.h" +#endif +/* + * This file implement a virtual file systems (VFS) for the PH7 engine. + */ +/* + * Given a string containing the path of a file or directory, this function + * return the parent directory's path. + */ +PH7_PRIVATE const char * PH7_ExtractDirName(const char *zPath,int nByte,int *pLen) +{ + const char *zEnd = &zPath[nByte - 1]; + int c,d; + c = d = '/'; +#ifdef __WINNT__ + d = '\\'; +#endif + while( zEnd > zPath && ( (int)zEnd[0] != c && (int)zEnd[0] != d ) ){ + zEnd--; + } + *pLen = (int)(zEnd-zPath); +#ifdef __WINNT__ + if( (*pLen) == (int)sizeof(char) && zPath[0] == '/' ){ + /* Normalize path on windows */ + return "\\"; + } +#endif + if( zEnd == zPath && ( (int)zEnd[0] != c && (int)zEnd[0] != d) ){ + /* No separator,return "." as the current directory */ + *pLen = sizeof(char); + return "."; + } + if( (*pLen) == 0 ){ + *pLen = sizeof(char); +#ifdef __WINNT__ + return "\\"; +#else + return "/"; +#endif + } + return zPath; +} +/* + * Omit the vfs layer implementation from the built if the PH7_DISABLE_BUILTIN_FUNC directive is defined. + */ +#ifndef PH7_DISABLE_BUILTIN_FUNC +/* + * bool chdir(string $directory) + * Change the current directory. + * Parameters + * $directory + * The new current directory + * Return + * TRUE on success or FALSE on failure. + */ +static int PH7_vfs_chdir(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + const char *zPath; + ph7_vfs *pVfs; + int rc; + if( nArg < 1 || !ph7_value_is_string(apArg[0]) ){ + /* Missing/Invalid argument,return FALSE */ + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Point to the underlying vfs */ + pVfs = (ph7_vfs *)ph7_context_user_data(pCtx); + if( pVfs == 0 || pVfs->xChdir == 0 ){ + /* IO routine not implemented,return NULL */ + ph7_context_throw_error_format(pCtx,PH7_CTX_WARNING, + "IO routine(%s) not implemented in the underlying VFS,PH7 is returning FALSE", + ph7_function_name(pCtx) + ); + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Point to the desired directory */ + zPath = ph7_value_to_string(apArg[0],0); + /* Perform the requested operation */ + rc = pVfs->xChdir(zPath); + /* IO return value */ + ph7_result_bool(pCtx,rc == PH7_OK); + return PH7_OK; +} +/* + * bool chroot(string $directory) + * Change the root directory. + * Parameters + * $directory + * The path to change the root directory to + * Return + * TRUE on success or FALSE on failure. + */ +static int PH7_vfs_chroot(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + const char *zPath; + ph7_vfs *pVfs; + int rc; + if( nArg < 1 || !ph7_value_is_string(apArg[0]) ){ + /* Missing/Invalid argument,return FALSE */ + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Point to the underlying vfs */ + pVfs = (ph7_vfs *)ph7_context_user_data(pCtx); + if( pVfs == 0 || pVfs->xChroot == 0 ){ + /* IO routine not implemented,return NULL */ + ph7_context_throw_error_format(pCtx,PH7_CTX_WARNING, + "IO routine(%s) not implemented in the underlying VFS,PH7 is returning FALSE", + ph7_function_name(pCtx) + ); + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Point to the desired directory */ + zPath = ph7_value_to_string(apArg[0],0); + /* Perform the requested operation */ + rc = pVfs->xChroot(zPath); + /* IO return value */ + ph7_result_bool(pCtx,rc == PH7_OK); + return PH7_OK; +} +/* + * string getcwd(void) + * Gets the current working directory. + * Parameters + * None + * Return + * Returns the current working directory on success, or FALSE on failure. + */ +static int PH7_vfs_getcwd(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + ph7_vfs *pVfs; + int rc; + /* Point to the underlying vfs */ + pVfs = (ph7_vfs *)ph7_context_user_data(pCtx); + if( pVfs == 0 || pVfs->xGetcwd == 0 ){ + SXUNUSED(nArg); /* cc warning */ + SXUNUSED(apArg); + /* IO routine not implemented,return NULL */ + ph7_context_throw_error_format(pCtx,PH7_CTX_WARNING, + "IO routine(%s) not implemented in the underlying VFS,PH7 is returning FALSE", + ph7_function_name(pCtx) + ); + ph7_result_bool(pCtx,0); + return PH7_OK; + } + ph7_result_string(pCtx,"",0); + /* Perform the requested operation */ + rc = pVfs->xGetcwd(pCtx); + if( rc != PH7_OK ){ + /* Error,return FALSE */ + ph7_result_bool(pCtx,0); + } + return PH7_OK; +} +/* + * bool rmdir(string $directory) + * Removes directory. + * Parameters + * $directory + * The path to the directory + * Return + * TRUE on success or FALSE on failure. + */ +static int PH7_vfs_rmdir(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + const char *zPath; + ph7_vfs *pVfs; + int rc; + if( nArg < 1 || !ph7_value_is_string(apArg[0]) ){ + /* Missing/Invalid argument,return FALSE */ + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Point to the underlying vfs */ + pVfs = (ph7_vfs *)ph7_context_user_data(pCtx); + if( pVfs == 0 || pVfs->xRmdir == 0 ){ + /* IO routine not implemented,return NULL */ + ph7_context_throw_error_format(pCtx,PH7_CTX_WARNING, + "IO routine(%s) not implemented in the underlying VFS,PH7 is returning FALSE", + ph7_function_name(pCtx) + ); + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Point to the desired directory */ + zPath = ph7_value_to_string(apArg[0],0); + /* Perform the requested operation */ + rc = pVfs->xRmdir(zPath); + /* IO return value */ + ph7_result_bool(pCtx,rc == PH7_OK); + return PH7_OK; +} +/* + * bool is_dir(string $filename) + * Tells whether the given filename is a directory. + * Parameters + * $filename + * Path to the file. + * Return + * TRUE on success or FALSE on failure. + */ +static int PH7_vfs_is_dir(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + const char *zPath; + ph7_vfs *pVfs; + int rc; + if( nArg < 1 || !ph7_value_is_string(apArg[0]) ){ + /* Missing/Invalid argument,return FALSE */ + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Point to the underlying vfs */ + pVfs = (ph7_vfs *)ph7_context_user_data(pCtx); + if( pVfs == 0 || pVfs->xIsdir == 0 ){ + /* IO routine not implemented,return NULL */ + ph7_context_throw_error_format(pCtx,PH7_CTX_WARNING, + "IO routine(%s) not implemented in the underlying VFS,PH7 is returning FALSE", + ph7_function_name(pCtx) + ); + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Point to the desired directory */ + zPath = ph7_value_to_string(apArg[0],0); + /* Perform the requested operation */ + rc = pVfs->xIsdir(zPath); + /* IO return value */ + ph7_result_bool(pCtx,rc == PH7_OK); + return PH7_OK; +} +/* + * bool mkdir(string $pathname[,int $mode = 0777 [,bool $recursive = false]) + * Make a directory. + * Parameters + * $pathname + * The directory path. + * $mode + * The mode is 0777 by default, which means the widest possible access. + * Note: + * mode is ignored on Windows. + * Note that you probably want to specify the mode as an octal number, which means + * it should have a leading zero. The mode is also modified by the current umask + * which you can change using umask(). + * $recursive + * Allows the creation of nested directories specified in the pathname. + * Defaults to FALSE. (Not used) + * Return + * TRUE on success or FALSE on failure. + */ +static int PH7_vfs_mkdir(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + int iRecursive = 0; + const char *zPath; + ph7_vfs *pVfs; + int iMode,rc; + if( nArg < 1 || !ph7_value_is_string(apArg[0]) ){ + /* Missing/Invalid argument,return FALSE */ + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Point to the underlying vfs */ + pVfs = (ph7_vfs *)ph7_context_user_data(pCtx); + if( pVfs == 0 || pVfs->xMkdir == 0 ){ + /* IO routine not implemented,return NULL */ + ph7_context_throw_error_format(pCtx,PH7_CTX_WARNING, + "IO routine(%s) not implemented in the underlying VFS,PH7 is returning FALSE", + ph7_function_name(pCtx) + ); + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Point to the desired directory */ + zPath = ph7_value_to_string(apArg[0],0); +#ifdef __WINNT__ + iMode = 0; +#else + /* Assume UNIX */ + iMode = 0777; +#endif + if( nArg > 1 ){ + iMode = ph7_value_to_int(apArg[1]); + if( nArg > 2 ){ + iRecursive = ph7_value_to_bool(apArg[2]); + } + } + /* Perform the requested operation */ + rc = pVfs->xMkdir(zPath,iMode,iRecursive); + /* IO return value */ + ph7_result_bool(pCtx,rc == PH7_OK); + return PH7_OK; +} +/* + * bool rename(string $oldname,string $newname) + * Attempts to rename oldname to newname. + * Parameters + * $oldname + * Old name. + * $newname + * New name. + * Return + * TRUE on success or FALSE on failure. + */ +static int PH7_vfs_rename(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + const char *zOld,*zNew; + ph7_vfs *pVfs; + int rc; + if( nArg < 2 || !ph7_value_is_string(apArg[0]) || !ph7_value_is_string(apArg[1]) ){ + /* Missing/Invalid arguments,return FALSE */ + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Point to the underlying vfs */ + pVfs = (ph7_vfs *)ph7_context_user_data(pCtx); + if( pVfs == 0 || pVfs->xRename == 0 ){ + /* IO routine not implemented,return NULL */ + ph7_context_throw_error_format(pCtx,PH7_CTX_WARNING, + "IO routine(%s) not implemented in the underlying VFS,PH7 is returning FALSE", + ph7_function_name(pCtx) + ); + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Perform the requested operation */ + zOld = ph7_value_to_string(apArg[0],0); + zNew = ph7_value_to_string(apArg[1],0); + rc = pVfs->xRename(zOld,zNew); + /* IO result */ + ph7_result_bool(pCtx,rc == PH7_OK ); + return PH7_OK; +} +/* + * string realpath(string $path) + * Returns canonicalized absolute pathname. + * Parameters + * $path + * Target path. + * Return + * Canonicalized absolute pathname on success. or FALSE on failure. + */ +static int PH7_vfs_realpath(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + const char *zPath; + ph7_vfs *pVfs; + int rc; + if( nArg < 1 || !ph7_value_is_string(apArg[0]) ){ + /* Missing/Invalid argument,return FALSE */ + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Point to the underlying vfs */ + pVfs = (ph7_vfs *)ph7_context_user_data(pCtx); + if( pVfs == 0 || pVfs->xRealpath == 0 ){ + /* IO routine not implemented,return NULL */ + ph7_context_throw_error_format(pCtx,PH7_CTX_WARNING, + "IO routine(%s) not implemented in the underlying VFS,PH7 is returning FALSE", + ph7_function_name(pCtx) + ); + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Set an empty string untnil the underlying OS interface change that */ + ph7_result_string(pCtx,"",0); + /* Perform the requested operation */ + zPath = ph7_value_to_string(apArg[0],0); + rc = pVfs->xRealpath(zPath,pCtx); + if( rc != PH7_OK ){ + ph7_result_bool(pCtx,0); + } + return PH7_OK; +} +/* + * int sleep(int $seconds) + * Delays the program execution for the given number of seconds. + * Parameters + * $seconds + * Halt time in seconds. + * Return + * Zero on success or FALSE on failure. + */ +static int PH7_vfs_sleep(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + ph7_vfs *pVfs; + int rc,nSleep; + if( nArg < 1 || !ph7_value_is_int(apArg[0]) ){ + /* Missing/Invalid argument,return FALSE */ + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Point to the underlying vfs */ + pVfs = (ph7_vfs *)ph7_context_user_data(pCtx); + if( pVfs == 0 || pVfs->xSleep == 0 ){ + /* IO routine not implemented,return NULL */ + ph7_context_throw_error_format(pCtx,PH7_CTX_WARNING, + "IO routine(%s) not implemented in the underlying VFS,PH7 is returning FALSE", + ph7_function_name(pCtx) + ); + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Amount to sleep */ + nSleep = ph7_value_to_int(apArg[0]); + if( nSleep < 0 ){ + /* Invalid value,return FALSE */ + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Perform the requested operation (Microseconds) */ + rc = pVfs->xSleep((unsigned int)(nSleep * SX_USEC_PER_SEC)); + if( rc != PH7_OK ){ + /* Return FALSE */ + ph7_result_bool(pCtx,0); + }else{ + /* Return zero */ + ph7_result_int(pCtx,0); + } + return PH7_OK; +} +/* + * void usleep(int $micro_seconds) + * Delays program execution for the given number of micro seconds. + * Parameters + * $micro_seconds + * Halt time in micro seconds. A micro second is one millionth of a second. + * Return + * None. + */ +static int PH7_vfs_usleep(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + ph7_vfs *pVfs; + int nSleep; + if( nArg < 1 || !ph7_value_is_int(apArg[0]) ){ + /* Missing/Invalid argument,return immediately */ + return PH7_OK; + } + /* Point to the underlying vfs */ + pVfs = (ph7_vfs *)ph7_context_user_data(pCtx); + if( pVfs == 0 || pVfs->xSleep == 0 ){ + /* IO routine not implemented,return NULL */ + ph7_context_throw_error_format(pCtx,PH7_CTX_WARNING, + "IO routine(%s) not implemented in the underlying VFS", + ph7_function_name(pCtx) + ); + return PH7_OK; + } + /* Amount to sleep */ + nSleep = ph7_value_to_int(apArg[0]); + if( nSleep < 0 ){ + /* Invalid value,return immediately */ + return PH7_OK; + } + /* Perform the requested operation (Microseconds) */ + pVfs->xSleep((unsigned int)nSleep); + return PH7_OK; +} +/* + * bool unlink (string $filename) + * Delete a file. + * Parameters + * $filename + * Path to the file. + * Return + * TRUE on success or FALSE on failure. + */ +static int PH7_vfs_unlink(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + const char *zPath; + ph7_vfs *pVfs; + int rc; + if( nArg < 1 || !ph7_value_is_string(apArg[0]) ){ + /* Missing/Invalid argument,return FALSE */ + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Point to the underlying vfs */ + pVfs = (ph7_vfs *)ph7_context_user_data(pCtx); + if( pVfs == 0 || pVfs->xUnlink == 0 ){ + /* IO routine not implemented,return NULL */ + ph7_context_throw_error_format(pCtx,PH7_CTX_WARNING, + "IO routine(%s) not implemented in the underlying VFS,PH7 is returning FALSE", + ph7_function_name(pCtx) + ); + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Point to the desired directory */ + zPath = ph7_value_to_string(apArg[0],0); + /* Perform the requested operation */ + rc = pVfs->xUnlink(zPath); + /* IO return value */ + ph7_result_bool(pCtx,rc == PH7_OK); + return PH7_OK; +} +/* + * bool chmod(string $filename,int $mode) + * Attempts to change the mode of the specified file to that given in mode. + * Parameters + * $filename + * Path to the file. + * $mode + * Mode (Must be an integer) + * Return + * TRUE on success or FALSE on failure. + */ +static int PH7_vfs_chmod(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + const char *zPath; + ph7_vfs *pVfs; + int iMode; + int rc; + if( nArg < 2 || !ph7_value_is_string(apArg[0]) ){ + /* Missing/Invalid argument,return FALSE */ + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Point to the underlying vfs */ + pVfs = (ph7_vfs *)ph7_context_user_data(pCtx); + if( pVfs == 0 || pVfs->xChmod == 0 ){ + /* IO routine not implemented,return NULL */ + ph7_context_throw_error_format(pCtx,PH7_CTX_WARNING, + "IO routine(%s) not implemented in the underlying VFS,PH7 is returning FALSE", + ph7_function_name(pCtx) + ); + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Point to the desired directory */ + zPath = ph7_value_to_string(apArg[0],0); + /* Extract the mode */ + iMode = ph7_value_to_int(apArg[1]); + /* Perform the requested operation */ + rc = pVfs->xChmod(zPath,iMode); + /* IO return value */ + ph7_result_bool(pCtx,rc == PH7_OK); + return PH7_OK; +} +/* + * bool chown(string $filename,string $user) + * Attempts to change the owner of the file filename to user user. + * Parameters + * $filename + * Path to the file. + * $user + * Username. + * Return + * TRUE on success or FALSE on failure. + */ +static int PH7_vfs_chown(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + const char *zPath,*zUser; + ph7_vfs *pVfs; + int rc; + if( nArg < 2 || !ph7_value_is_string(apArg[0]) ){ + /* Missing/Invalid arguments,return FALSE */ + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Point to the underlying vfs */ + pVfs = (ph7_vfs *)ph7_context_user_data(pCtx); + if( pVfs == 0 || pVfs->xChown == 0 ){ + /* IO routine not implemented,return NULL */ + ph7_context_throw_error_format(pCtx,PH7_CTX_WARNING, + "IO routine(%s) not implemented in the underlying VFS,PH7 is returning FALSE", + ph7_function_name(pCtx) + ); + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Point to the desired directory */ + zPath = ph7_value_to_string(apArg[0],0); + /* Extract the user */ + zUser = ph7_value_to_string(apArg[1],0); + /* Perform the requested operation */ + rc = pVfs->xChown(zPath,zUser); + /* IO return value */ + ph7_result_bool(pCtx,rc == PH7_OK); + return PH7_OK; +} +/* + * bool chgrp(string $filename,string $group) + * Attempts to change the group of the file filename to group. + * Parameters + * $filename + * Path to the file. + * $group + * groupname. + * Return + * TRUE on success or FALSE on failure. + */ +static int PH7_vfs_chgrp(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + const char *zPath,*zGroup; + ph7_vfs *pVfs; + int rc; + if( nArg < 2 || !ph7_value_is_string(apArg[0]) ){ + /* Missing/Invalid arguments,return FALSE */ + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Point to the underlying vfs */ + pVfs = (ph7_vfs *)ph7_context_user_data(pCtx); + if( pVfs == 0 || pVfs->xChgrp == 0 ){ + /* IO routine not implemented,return NULL */ + ph7_context_throw_error_format(pCtx,PH7_CTX_WARNING, + "IO routine(%s) not implemented in the underlying VFS,PH7 is returning FALSE", + ph7_function_name(pCtx) + ); + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Point to the desired directory */ + zPath = ph7_value_to_string(apArg[0],0); + /* Extract the user */ + zGroup = ph7_value_to_string(apArg[1],0); + /* Perform the requested operation */ + rc = pVfs->xChgrp(zPath,zGroup); + /* IO return value */ + ph7_result_bool(pCtx,rc == PH7_OK); + return PH7_OK; +} +/* + * int64 disk_free_space(string $directory) + * Returns available space on filesystem or disk partition. + * Parameters + * $directory + * A directory of the filesystem or disk partition. + * Return + * Returns the number of available bytes as a 64-bit integer or FALSE on failure. + */ +static int PH7_vfs_disk_free_space(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + const char *zPath; + ph7_int64 iSize; + ph7_vfs *pVfs; + if( nArg < 1 || !ph7_value_is_string(apArg[0]) ){ + /* Missing/Invalid argument,return FALSE */ + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Point to the underlying vfs */ + pVfs = (ph7_vfs *)ph7_context_user_data(pCtx); + if( pVfs == 0 || pVfs->xFreeSpace == 0 ){ + /* IO routine not implemented,return NULL */ + ph7_context_throw_error_format(pCtx,PH7_CTX_WARNING, + "IO routine(%s) not implemented in the underlying VFS,PH7 is returning FALSE", + ph7_function_name(pCtx) + ); + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Point to the desired directory */ + zPath = ph7_value_to_string(apArg[0],0); + /* Perform the requested operation */ + iSize = pVfs->xFreeSpace(zPath); + /* IO return value */ + ph7_result_int64(pCtx,iSize); + return PH7_OK; +} +/* + * int64 disk_total_space(string $directory) + * Returns the total size of a filesystem or disk partition. + * Parameters + * $directory + * A directory of the filesystem or disk partition. + * Return + * Returns the number of available bytes as a 64-bit integer or FALSE on failure. + */ +static int PH7_vfs_disk_total_space(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + const char *zPath; + ph7_int64 iSize; + ph7_vfs *pVfs; + if( nArg < 1 || !ph7_value_is_string(apArg[0]) ){ + /* Missing/Invalid argument,return FALSE */ + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Point to the underlying vfs */ + pVfs = (ph7_vfs *)ph7_context_user_data(pCtx); + if( pVfs == 0 || pVfs->xTotalSpace == 0 ){ + /* IO routine not implemented,return NULL */ + ph7_context_throw_error_format(pCtx,PH7_CTX_WARNING, + "IO routine(%s) not implemented in the underlying VFS,PH7 is returning FALSE", + ph7_function_name(pCtx) + ); + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Point to the desired directory */ + zPath = ph7_value_to_string(apArg[0],0); + /* Perform the requested operation */ + iSize = pVfs->xTotalSpace(zPath); + /* IO return value */ + ph7_result_int64(pCtx,iSize); + return PH7_OK; +} +/* + * bool file_exists(string $filename) + * Checks whether a file or directory exists. + * Parameters + * $filename + * Path to the file. + * Return + * TRUE on success or FALSE on failure. + */ +static int PH7_vfs_file_exists(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + const char *zPath; + ph7_vfs *pVfs; + int rc; + if( nArg < 1 || !ph7_value_is_string(apArg[0]) ){ + /* Missing/Invalid argument,return FALSE */ + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Point to the underlying vfs */ + pVfs = (ph7_vfs *)ph7_context_user_data(pCtx); + if( pVfs == 0 || pVfs->xFileExists == 0 ){ + /* IO routine not implemented,return NULL */ + ph7_context_throw_error_format(pCtx,PH7_CTX_WARNING, + "IO routine(%s) not implemented in the underlying VFS,PH7 is returning FALSE", + ph7_function_name(pCtx) + ); + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Point to the desired directory */ + zPath = ph7_value_to_string(apArg[0],0); + /* Perform the requested operation */ + rc = pVfs->xFileExists(zPath); + /* IO return value */ + ph7_result_bool(pCtx,rc == PH7_OK); + return PH7_OK; +} +/* + * int64 file_size(string $filename) + * Gets the size for the given file. + * Parameters + * $filename + * Path to the file. + * Return + * File size on success or FALSE on failure. + */ +static int PH7_vfs_file_size(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + const char *zPath; + ph7_int64 iSize; + ph7_vfs *pVfs; + if( nArg < 1 || !ph7_value_is_string(apArg[0]) ){ + /* Missing/Invalid argument,return FALSE */ + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Point to the underlying vfs */ + pVfs = (ph7_vfs *)ph7_context_user_data(pCtx); + if( pVfs == 0 || pVfs->xFileSize == 0 ){ + /* IO routine not implemented,return NULL */ + ph7_context_throw_error_format(pCtx,PH7_CTX_WARNING, + "IO routine(%s) not implemented in the underlying VFS,PH7 is returning FALSE", + ph7_function_name(pCtx) + ); + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Point to the desired directory */ + zPath = ph7_value_to_string(apArg[0],0); + /* Perform the requested operation */ + iSize = pVfs->xFileSize(zPath); + /* IO return value */ + ph7_result_int64(pCtx,iSize); + return PH7_OK; +} +/* + * int64 fileatime(string $filename) + * Gets the last access time of the given file. + * Parameters + * $filename + * Path to the file. + * Return + * File atime on success or FALSE on failure. + */ +static int PH7_vfs_file_atime(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + const char *zPath; + ph7_int64 iTime; + ph7_vfs *pVfs; + if( nArg < 1 || !ph7_value_is_string(apArg[0]) ){ + /* Missing/Invalid argument,return FALSE */ + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Point to the underlying vfs */ + pVfs = (ph7_vfs *)ph7_context_user_data(pCtx); + if( pVfs == 0 || pVfs->xFileAtime == 0 ){ + /* IO routine not implemented,return NULL */ + ph7_context_throw_error_format(pCtx,PH7_CTX_WARNING, + "IO routine(%s) not implemented in the underlying VFS,PH7 is returning FALSE", + ph7_function_name(pCtx) + ); + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Point to the desired directory */ + zPath = ph7_value_to_string(apArg[0],0); + /* Perform the requested operation */ + iTime = pVfs->xFileAtime(zPath); + /* IO return value */ + ph7_result_int64(pCtx,iTime); + return PH7_OK; +} +/* + * int64 filemtime(string $filename) + * Gets file modification time. + * Parameters + * $filename + * Path to the file. + * Return + * File mtime on success or FALSE on failure. + */ +static int PH7_vfs_file_mtime(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + const char *zPath; + ph7_int64 iTime; + ph7_vfs *pVfs; + if( nArg < 1 || !ph7_value_is_string(apArg[0]) ){ + /* Missing/Invalid argument,return FALSE */ + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Point to the underlying vfs */ + pVfs = (ph7_vfs *)ph7_context_user_data(pCtx); + if( pVfs == 0 || pVfs->xFileMtime == 0 ){ + /* IO routine not implemented,return NULL */ + ph7_context_throw_error_format(pCtx,PH7_CTX_WARNING, + "IO routine(%s) not implemented in the underlying VFS,PH7 is returning FALSE", + ph7_function_name(pCtx) + ); + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Point to the desired directory */ + zPath = ph7_value_to_string(apArg[0],0); + /* Perform the requested operation */ + iTime = pVfs->xFileMtime(zPath); + /* IO return value */ + ph7_result_int64(pCtx,iTime); + return PH7_OK; +} +/* + * int64 filectime(string $filename) + * Gets inode change time of file. + * Parameters + * $filename + * Path to the file. + * Return + * File ctime on success or FALSE on failure. + */ +static int PH7_vfs_file_ctime(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + const char *zPath; + ph7_int64 iTime; + ph7_vfs *pVfs; + if( nArg < 1 || !ph7_value_is_string(apArg[0]) ){ + /* Missing/Invalid argument,return FALSE */ + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Point to the underlying vfs */ + pVfs = (ph7_vfs *)ph7_context_user_data(pCtx); + if( pVfs == 0 || pVfs->xFileCtime == 0 ){ + /* IO routine not implemented,return NULL */ + ph7_context_throw_error_format(pCtx,PH7_CTX_WARNING, + "IO routine(%s) not implemented in the underlying VFS,PH7 is returning FALSE", + ph7_function_name(pCtx) + ); + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Point to the desired directory */ + zPath = ph7_value_to_string(apArg[0],0); + /* Perform the requested operation */ + iTime = pVfs->xFileCtime(zPath); + /* IO return value */ + ph7_result_int64(pCtx,iTime); + return PH7_OK; +} +/* + * bool is_file(string $filename) + * Tells whether the filename is a regular file. + * Parameters + * $filename + * Path to the file. + * Return + * TRUE on success or FALSE on failure. + */ +static int PH7_vfs_is_file(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + const char *zPath; + ph7_vfs *pVfs; + int rc; + if( nArg < 1 || !ph7_value_is_string(apArg[0]) ){ + /* Missing/Invalid argument,return FALSE */ + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Point to the underlying vfs */ + pVfs = (ph7_vfs *)ph7_context_user_data(pCtx); + if( pVfs == 0 || pVfs->xIsfile == 0 ){ + /* IO routine not implemented,return NULL */ + ph7_context_throw_error_format(pCtx,PH7_CTX_WARNING, + "IO routine(%s) not implemented in the underlying VFS,PH7 is returning FALSE", + ph7_function_name(pCtx) + ); + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Point to the desired directory */ + zPath = ph7_value_to_string(apArg[0],0); + /* Perform the requested operation */ + rc = pVfs->xIsfile(zPath); + /* IO return value */ + ph7_result_bool(pCtx,rc == PH7_OK); + return PH7_OK; +} +/* + * bool is_link(string $filename) + * Tells whether the filename is a symbolic link. + * Parameters + * $filename + * Path to the file. + * Return + * TRUE on success or FALSE on failure. + */ +static int PH7_vfs_is_link(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + const char *zPath; + ph7_vfs *pVfs; + int rc; + if( nArg < 1 || !ph7_value_is_string(apArg[0]) ){ + /* Missing/Invalid argument,return FALSE */ + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Point to the underlying vfs */ + pVfs = (ph7_vfs *)ph7_context_user_data(pCtx); + if( pVfs == 0 || pVfs->xIslink == 0 ){ + /* IO routine not implemented,return NULL */ + ph7_context_throw_error_format(pCtx,PH7_CTX_WARNING, + "IO routine(%s) not implemented in the underlying VFS,PH7 is returning FALSE", + ph7_function_name(pCtx) + ); + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Point to the desired directory */ + zPath = ph7_value_to_string(apArg[0],0); + /* Perform the requested operation */ + rc = pVfs->xIslink(zPath); + /* IO return value */ + ph7_result_bool(pCtx,rc == PH7_OK); + return PH7_OK; +} +/* + * bool is_readable(string $filename) + * Tells whether a file exists and is readable. + * Parameters + * $filename + * Path to the file. + * Return + * TRUE on success or FALSE on failure. + */ +static int PH7_vfs_is_readable(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + const char *zPath; + ph7_vfs *pVfs; + int rc; + if( nArg < 1 || !ph7_value_is_string(apArg[0]) ){ + /* Missing/Invalid argument,return FALSE */ + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Point to the underlying vfs */ + pVfs = (ph7_vfs *)ph7_context_user_data(pCtx); + if( pVfs == 0 || pVfs->xReadable == 0 ){ + /* IO routine not implemented,return NULL */ + ph7_context_throw_error_format(pCtx,PH7_CTX_WARNING, + "IO routine(%s) not implemented in the underlying VFS,PH7 is returning FALSE", + ph7_function_name(pCtx) + ); + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Point to the desired directory */ + zPath = ph7_value_to_string(apArg[0],0); + /* Perform the requested operation */ + rc = pVfs->xReadable(zPath); + /* IO return value */ + ph7_result_bool(pCtx,rc == PH7_OK); + return PH7_OK; +} +/* + * bool is_writable(string $filename) + * Tells whether the filename is writable. + * Parameters + * $filename + * Path to the file. + * Return + * TRUE on success or FALSE on failure. + */ +static int PH7_vfs_is_writable(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + const char *zPath; + ph7_vfs *pVfs; + int rc; + if( nArg < 1 || !ph7_value_is_string(apArg[0]) ){ + /* Missing/Invalid argument,return FALSE */ + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Point to the underlying vfs */ + pVfs = (ph7_vfs *)ph7_context_user_data(pCtx); + if( pVfs == 0 || pVfs->xWritable == 0 ){ + /* IO routine not implemented,return NULL */ + ph7_context_throw_error_format(pCtx,PH7_CTX_WARNING, + "IO routine(%s) not implemented in the underlying VFS,PH7 is returning FALSE", + ph7_function_name(pCtx) + ); + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Point to the desired directory */ + zPath = ph7_value_to_string(apArg[0],0); + /* Perform the requested operation */ + rc = pVfs->xWritable(zPath); + /* IO return value */ + ph7_result_bool(pCtx,rc == PH7_OK); + return PH7_OK; +} +/* + * bool is_executable(string $filename) + * Tells whether the filename is executable. + * Parameters + * $filename + * Path to the file. + * Return + * TRUE on success or FALSE on failure. + */ +static int PH7_vfs_is_executable(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + const char *zPath; + ph7_vfs *pVfs; + int rc; + if( nArg < 1 || !ph7_value_is_string(apArg[0]) ){ + /* Missing/Invalid argument,return FALSE */ + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Point to the underlying vfs */ + pVfs = (ph7_vfs *)ph7_context_user_data(pCtx); + if( pVfs == 0 || pVfs->xExecutable == 0 ){ + /* IO routine not implemented,return NULL */ + ph7_context_throw_error_format(pCtx,PH7_CTX_WARNING, + "IO routine(%s) not implemented in the underlying VFS,PH7 is returning FALSE", + ph7_function_name(pCtx) + ); + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Point to the desired directory */ + zPath = ph7_value_to_string(apArg[0],0); + /* Perform the requested operation */ + rc = pVfs->xExecutable(zPath); + /* IO return value */ + ph7_result_bool(pCtx,rc == PH7_OK); + return PH7_OK; +} +/* + * string filetype(string $filename) + * Gets file type. + * Parameters + * $filename + * Path to the file. + * Return + * The type of the file. Possible values are fifo, char, dir, block, link + * file, socket and unknown. + */ +static int PH7_vfs_filetype(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + const char *zPath; + ph7_vfs *pVfs; + if( nArg < 1 || !ph7_value_is_string(apArg[0]) ){ + /* Missing/Invalid argument,return 'unknown' */ + ph7_result_string(pCtx,"unknown",sizeof("unknown")-1); + return PH7_OK; + } + /* Point to the underlying vfs */ + pVfs = (ph7_vfs *)ph7_context_user_data(pCtx); + if( pVfs == 0 || pVfs->xFiletype == 0 ){ + /* IO routine not implemented,return NULL */ + ph7_context_throw_error_format(pCtx,PH7_CTX_WARNING, + "IO routine(%s) not implemented in the underlying VFS,PH7 is returning FALSE", + ph7_function_name(pCtx) + ); + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Point to the desired directory */ + zPath = ph7_value_to_string(apArg[0],0); + /* Set the empty string as the default return value */ + ph7_result_string(pCtx,"",0); + /* Perform the requested operation */ + pVfs->xFiletype(zPath,pCtx); + return PH7_OK; +} +/* + * array stat(string $filename) + * Gives information about a file. + * Parameters + * $filename + * Path to the file. + * Return + * An associative array on success holding the following entries on success + * 0 dev device number + * 1 ino inode number (zero on windows) + * 2 mode inode protection mode + * 3 nlink number of links + * 4 uid userid of owner (zero on windows) + * 5 gid groupid of owner (zero on windows) + * 6 rdev device type, if inode device + * 7 size size in bytes + * 8 atime time of last access (Unix timestamp) + * 9 mtime time of last modification (Unix timestamp) + * 10 ctime time of last inode change (Unix timestamp) + * 11 blksize blocksize of filesystem IO (zero on windows) + * 12 blocks number of 512-byte blocks allocated. + * Note: + * FALSE is returned on failure. + */ +static int PH7_vfs_stat(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + ph7_value *pArray,*pValue; + const char *zPath; + ph7_vfs *pVfs; + int rc; + if( nArg < 1 || !ph7_value_is_string(apArg[0]) ){ + /* Missing/Invalid argument,return FALSE */ + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Point to the underlying vfs */ + pVfs = (ph7_vfs *)ph7_context_user_data(pCtx); + if( pVfs == 0 || pVfs->xStat == 0 ){ + /* IO routine not implemented,return NULL */ + ph7_context_throw_error_format(pCtx,PH7_CTX_WARNING, + "IO routine(%s) not implemented in the underlying VFS,PH7 is returning FALSE", + ph7_function_name(pCtx) + ); + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Create the array and the working value */ + pArray = ph7_context_new_array(pCtx); + pValue = ph7_context_new_scalar(pCtx); + if( pArray == 0 || pValue == 0 ){ + ph7_context_throw_error(pCtx,PH7_CTX_ERR,"PH7 is running out of memory"); + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Extract the file path */ + zPath = ph7_value_to_string(apArg[0],0); + /* Perform the requested operation */ + rc = pVfs->xStat(zPath,pArray,pValue); + if( rc != PH7_OK ){ + /* IO error,return FALSE */ + ph7_result_bool(pCtx,0); + }else{ + /* Return the associative array */ + ph7_result_value(pCtx,pArray); + } + /* Don't worry about freeing memory here,everything will be released + * automatically as soon we return from this function. */ + return PH7_OK; +} +/* + * array lstat(string $filename) + * Gives information about a file or symbolic link. + * Parameters + * $filename + * Path to the file. + * Return + * An associative array on success holding the following entries on success + * 0 dev device number + * 1 ino inode number (zero on windows) + * 2 mode inode protection mode + * 3 nlink number of links + * 4 uid userid of owner (zero on windows) + * 5 gid groupid of owner (zero on windows) + * 6 rdev device type, if inode device + * 7 size size in bytes + * 8 atime time of last access (Unix timestamp) + * 9 mtime time of last modification (Unix timestamp) + * 10 ctime time of last inode change (Unix timestamp) + * 11 blksize blocksize of filesystem IO (zero on windows) + * 12 blocks number of 512-byte blocks allocated. + * Note: + * FALSE is returned on failure. + */ +static int PH7_vfs_lstat(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + ph7_value *pArray,*pValue; + const char *zPath; + ph7_vfs *pVfs; + int rc; + if( nArg < 1 || !ph7_value_is_string(apArg[0]) ){ + /* Missing/Invalid argument,return FALSE */ + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Point to the underlying vfs */ + pVfs = (ph7_vfs *)ph7_context_user_data(pCtx); + if( pVfs == 0 || pVfs->xlStat == 0 ){ + /* IO routine not implemented,return NULL */ + ph7_context_throw_error_format(pCtx,PH7_CTX_WARNING, + "IO routine(%s) not implemented in the underlying VFS,PH7 is returning FALSE", + ph7_function_name(pCtx) + ); + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Create the array and the working value */ + pArray = ph7_context_new_array(pCtx); + pValue = ph7_context_new_scalar(pCtx); + if( pArray == 0 || pValue == 0 ){ + ph7_context_throw_error(pCtx,PH7_CTX_ERR,"PH7 is running out of memory"); + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Extract the file path */ + zPath = ph7_value_to_string(apArg[0],0); + /* Perform the requested operation */ + rc = pVfs->xlStat(zPath,pArray,pValue); + if( rc != PH7_OK ){ + /* IO error,return FALSE */ + ph7_result_bool(pCtx,0); + }else{ + /* Return the associative array */ + ph7_result_value(pCtx,pArray); + } + /* Don't worry about freeing memory here,everything will be released + * automatically as soon we return from this function. */ + return PH7_OK; +} +/* + * string getenv(string $varname) + * Gets the value of an environment variable. + * Parameters + * $varname + * The variable name. + * Return + * Returns the value of the environment variable varname, or FALSE if the environment + * variable varname does not exist. + */ +static int PH7_vfs_getenv(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + const char *zEnv; + ph7_vfs *pVfs; + int iLen; + if( nArg < 1 || !ph7_value_is_string(apArg[0]) ){ + /* Missing/Invalid argument,return FALSE */ + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Point to the underlying vfs */ + pVfs = (ph7_vfs *)ph7_context_user_data(pCtx); + if( pVfs == 0 || pVfs->xGetenv == 0 ){ + /* IO routine not implemented,return NULL */ + ph7_context_throw_error_format(pCtx,PH7_CTX_WARNING, + "IO routine(%s) not implemented in the underlying VFS,PH7 is returning FALSE", + ph7_function_name(pCtx) + ); + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Extract the environment variable */ + zEnv = ph7_value_to_string(apArg[0],&iLen); + /* Set a boolean FALSE as the default return value */ + ph7_result_bool(pCtx,0); + if( iLen < 1 ){ + /* Empty string */ + return PH7_OK; + } + /* Perform the requested operation */ + pVfs->xGetenv(zEnv,pCtx); + return PH7_OK; +} +/* + * bool putenv(string $settings) + * Set the value of an environment variable. + * Parameters + * $setting + * The setting, like "FOO=BAR" + * Return + * TRUE on success or FALSE on failure. + */ +static int PH7_vfs_putenv(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + const char *zName,*zValue; + char *zSettings,*zEnd; + ph7_vfs *pVfs; + int iLen,rc; + if( nArg < 1 || !ph7_value_is_string(apArg[0]) ){ + /* Missing/Invalid argument,return FALSE */ + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Extract the setting variable */ + zSettings = (char *)ph7_value_to_string(apArg[0],&iLen); + if( iLen < 1 ){ + /* Empty string,return FALSE */ + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Parse the setting */ + zEnd = &zSettings[iLen]; + zValue = 0; + zName = zSettings; + while( zSettings < zEnd ){ + if( zSettings[0] == '=' ){ + /* Null terminate the name */ + zSettings[0] = 0; + zValue = &zSettings[1]; + break; + } + zSettings++; + } + /* Install the environment variable in the $_Env array */ + if( zValue == 0 || zName[0] == 0 || zValue >= zEnd || zName >= zValue ){ + /* Invalid settings,retun FALSE */ + ph7_result_bool(pCtx,0); + if( zSettings < zEnd ){ + zSettings[0] = '='; + } + return PH7_OK; + } + ph7_vm_config(pCtx->pVm,PH7_VM_CONFIG_ENV_ATTR,zName,zValue,(int)(zEnd-zValue)); + /* Point to the underlying vfs */ + pVfs = (ph7_vfs *)ph7_context_user_data(pCtx); + if( pVfs == 0 || pVfs->xSetenv == 0 ){ + /* IO routine not implemented,return NULL */ + ph7_context_throw_error_format(pCtx,PH7_CTX_WARNING, + "IO routine(%s) not implemented in the underlying VFS,PH7 is returning FALSE", + ph7_function_name(pCtx) + ); + ph7_result_bool(pCtx,0); + zSettings[0] = '='; + return PH7_OK; + } + /* Perform the requested operation */ + rc = pVfs->xSetenv(zName,zValue); + ph7_result_bool(pCtx,rc == PH7_OK ); + zSettings[0] = '='; + return PH7_OK; +} +/* + * bool touch(string $filename[,int64 $time = time()[,int64 $atime]]) + * Sets access and modification time of file. + * Note: On windows + * If the file does not exists,it will not be created. + * Parameters + * $filename + * The name of the file being touched. + * $time + * The touch time. If time is not supplied, the current system time is used. + * $atime + * If present, the access time of the given filename is set to the value of atime. + * Otherwise, it is set to the value passed to the time parameter. If neither are + * present, the current system time is used. + * Return + * TRUE on success or FALSE on failure. +*/ +static int PH7_vfs_touch(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + ph7_int64 nTime,nAccess; + const char *zFile; + ph7_vfs *pVfs; + int rc; + if( nArg < 1 || !ph7_value_is_string(apArg[0]) ){ + /* Missing/Invalid argument,return FALSE */ + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Point to the underlying vfs */ + pVfs = (ph7_vfs *)ph7_context_user_data(pCtx); + if( pVfs == 0 || pVfs->xTouch == 0 ){ + /* IO routine not implemented,return NULL */ + ph7_context_throw_error_format(pCtx,PH7_CTX_WARNING, + "IO routine(%s) not implemented in the underlying VFS,PH7 is returning FALSE", + ph7_function_name(pCtx) + ); + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Perform the requested operation */ + nTime = nAccess = -1; + zFile = ph7_value_to_string(apArg[0],0); + if( nArg > 1 ){ + nTime = ph7_value_to_int64(apArg[1]); + if( nArg > 2 ){ + nAccess = ph7_value_to_int64(apArg[1]); + }else{ + nAccess = nTime; + } + } + rc = pVfs->xTouch(zFile,nTime,nAccess); + /* IO result */ + ph7_result_bool(pCtx,rc == PH7_OK); + return PH7_OK; +} +/* + * Path processing functions that do not need access to the VFS layer + * Authors: + * Symisc Systems,devel@symisc.net. + * Copyright (C) Symisc Systems,http://ph7.symisc.net + * Status: + * Stable. + */ +/* + * string dirname(string $path) + * Returns parent directory's path. + * Parameters + * $path + * Target path. + * On Windows, both slash (/) and backslash (\) are used as directory separator character. + * In other environments, it is the forward slash (/). + * Return + * The path of the parent directory. If there are no slashes in path, a dot ('.') + * is returned, indicating the current directory. + */ +static int PH7_builtin_dirname(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + const char *zPath,*zDir; + int iLen,iDirlen; + if( nArg < 1 || !ph7_value_is_string(apArg[0]) ){ + /* Missing/Invalid arguments,return the empty string */ + ph7_result_string(pCtx,"",0); + return PH7_OK; + } + /* Point to the target path */ + zPath = ph7_value_to_string(apArg[0],&iLen); + if( iLen < 1 ){ + /* Reuturn "." */ + ph7_result_string(pCtx,".",sizeof(char)); + return PH7_OK; + } + /* Perform the requested operation */ + zDir = PH7_ExtractDirName(zPath,iLen,&iDirlen); + /* Return directory name */ + ph7_result_string(pCtx,zDir,iDirlen); + return PH7_OK; +} +/* + * string basename(string $path[, string $suffix ]) + * Returns trailing name component of path. + * Parameters + * $path + * Target path. + * On Windows, both slash (/) and backslash (\) are used as directory separator character. + * In other environments, it is the forward slash (/). + * $suffix + * If the name component ends in suffix this will also be cut off. + * Return + * The base name of the given path. + */ +static int PH7_builtin_basename(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + const char *zPath,*zBase,*zEnd; + int c,d,iLen; + if( nArg < 1 || !ph7_value_is_string(apArg[0]) ){ + /* Missing/Invalid argument,return the empty string */ + ph7_result_string(pCtx,"",0); + return PH7_OK; + } + c = d = '/'; +#ifdef __WINNT__ + d = '\\'; +#endif + /* Point to the target path */ + zPath = ph7_value_to_string(apArg[0],&iLen); + if( iLen < 1 ){ + /* Empty string */ + ph7_result_string(pCtx,"",0); + return PH7_OK; + } + /* Perform the requested operation */ + zEnd = &zPath[iLen - 1]; + /* Ignore trailing '/' */ + while( zEnd > zPath && ( (int)zEnd[0] == c || (int)zEnd[0] == d ) ){ + zEnd--; + } + iLen = (int)(&zEnd[1]-zPath); + while( zEnd > zPath && ( (int)zEnd[0] != c && (int)zEnd[0] != d ) ){ + zEnd--; + } + zBase = (zEnd > zPath) ? &zEnd[1] : zPath; + zEnd = &zPath[iLen]; + if( nArg > 1 && ph7_value_is_string(apArg[1]) ){ + const char *zSuffix; + int nSuffix; + /* Strip suffix */ + zSuffix = ph7_value_to_string(apArg[1],&nSuffix); + if( nSuffix > 0 && nSuffix < iLen && SyMemcmp(&zEnd[-nSuffix],zSuffix,nSuffix) == 0 ){ + zEnd -= nSuffix; + } + } + /* Store the basename */ + ph7_result_string(pCtx,zBase,(int)(zEnd-zBase)); + return PH7_OK; +} +/* + * value pathinfo(string $path [,int $options = PATHINFO_DIRNAME | PATHINFO_BASENAME | PATHINFO_EXTENSION | PATHINFO_FILENAME ]) + * Returns information about a file path. + * Parameter + * $path + * The path to be parsed. + * $options + * If present, specifies a specific element to be returned; one of + * PATHINFO_DIRNAME, PATHINFO_BASENAME, PATHINFO_EXTENSION or PATHINFO_FILENAME. + * Return + * If the options parameter is not passed, an associative array containing the following + * elements is returned: dirname, basename, extension (if any), and filename. + * If options is present, returns a string containing the requested element. + */ +typedef struct path_info path_info; +struct path_info +{ + SyString sDir; /* Directory [i.e: /var/www] */ + SyString sBasename; /* Basename [i.e httpd.conf] */ + SyString sExtension; /* File extension [i.e xml,pdf..] */ + SyString sFilename; /* Filename */ +}; +/* + * Extract path fields. + */ +static sxi32 ExtractPathInfo(const char *zPath,int nByte,path_info *pOut) +{ + const char *zPtr,*zEnd = &zPath[nByte - 1]; + SyString *pCur; + int c,d; + c = d = '/'; +#ifdef __WINNT__ + d = '\\'; +#endif + /* Zero the structure */ + SyZero(pOut,sizeof(path_info)); + /* Handle special case */ + if( nByte == sizeof(char) && ( (int)zPath[0] == c || (int)zPath[0] == d ) ){ +#ifdef __WINNT__ + SyStringInitFromBuf(&pOut->sDir,"\\",sizeof(char)); +#else + SyStringInitFromBuf(&pOut->sDir,"/",sizeof(char)); +#endif + return SXRET_OK; + } + /* Extract the basename */ + while( zEnd > zPath && ( (int)zEnd[0] != c && (int)zEnd[0] != d ) ){ + zEnd--; + } + zPtr = (zEnd > zPath) ? &zEnd[1] : zPath; + zEnd = &zPath[nByte]; + /* dirname */ + pCur = &pOut->sDir; + SyStringInitFromBuf(pCur,zPath,zPtr-zPath); + if( pCur->nByte > 1 ){ + SyStringTrimTrailingChar(pCur,'/'); +#ifdef __WINNT__ + SyStringTrimTrailingChar(pCur,'\\'); +#endif + }else if( (int)zPath[0] == c || (int)zPath[0] == d ){ +#ifdef __WINNT__ + SyStringInitFromBuf(&pOut->sDir,"\\",sizeof(char)); +#else + SyStringInitFromBuf(&pOut->sDir,"/",sizeof(char)); +#endif + } + /* basename/filename */ + pCur = &pOut->sBasename; + SyStringInitFromBuf(pCur,zPtr,zEnd-zPtr); + SyStringTrimLeadingChar(pCur,'/'); +#ifdef __WINNT__ + SyStringTrimLeadingChar(pCur,'\\'); +#endif + SyStringDupPtr(&pOut->sFilename,pCur); + if( pCur->nByte > 0 ){ + /* extension */ + zEnd--; + while( zEnd > pCur->zString /*basename*/ && zEnd[0] != '.' ){ + zEnd--; + } + if( zEnd > pCur->zString ){ + zEnd++; /* Jump leading dot */ + SyStringInitFromBuf(&pOut->sExtension,zEnd,&zPath[nByte]-zEnd); + /* Fix filename */ + pCur = &pOut->sFilename; + if( pCur->nByte > SyStringLength(&pOut->sExtension) ){ + pCur->nByte -= 1 + SyStringLength(&pOut->sExtension); + } + } + } + return SXRET_OK; +} +/* + * value pathinfo(string $path [,int $options = PATHINFO_DIRNAME | PATHINFO_BASENAME | PATHINFO_EXTENSION | PATHINFO_FILENAME ]) + * See block comment above. + */ +static int PH7_builtin_pathinfo(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + const char *zPath; + path_info sInfo; + SyString *pComp; + int iLen; + if( nArg < 1 || !ph7_value_is_string(apArg[0]) ){ + /* Missing/Invalid argument,return the empty string */ + ph7_result_string(pCtx,"",0); + return PH7_OK; + } + /* Point to the target path */ + zPath = ph7_value_to_string(apArg[0],&iLen); + if( iLen < 1 ){ + /* Empty string */ + ph7_result_string(pCtx,"",0); + return PH7_OK; + } + /* Extract path info */ + ExtractPathInfo(zPath,iLen,&sInfo); + if( nArg > 1 && ph7_value_is_int(apArg[1]) ){ + /* Return path component */ + int nComp = ph7_value_to_int(apArg[1]); + switch(nComp){ + case 1: /* PATHINFO_DIRNAME */ + pComp = &sInfo.sDir; + if( pComp->nByte > 0 ){ + ph7_result_string(pCtx,pComp->zString,(int)pComp->nByte); + }else{ + /* Expand the empty string */ + ph7_result_string(pCtx,"",0); + } + break; + case 2: /*PATHINFO_BASENAME*/ + pComp = &sInfo.sBasename; + if( pComp->nByte > 0 ){ + ph7_result_string(pCtx,pComp->zString,(int)pComp->nByte); + }else{ + /* Expand the empty string */ + ph7_result_string(pCtx,"",0); + } + break; + case 3: /*PATHINFO_EXTENSION*/ + pComp = &sInfo.sExtension; + if( pComp->nByte > 0 ){ + ph7_result_string(pCtx,pComp->zString,(int)pComp->nByte); + }else{ + /* Expand the empty string */ + ph7_result_string(pCtx,"",0); + } + break; + case 4: /*PATHINFO_FILENAME*/ + pComp = &sInfo.sFilename; + if( pComp->nByte > 0 ){ + ph7_result_string(pCtx,pComp->zString,(int)pComp->nByte); + }else{ + /* Expand the empty string */ + ph7_result_string(pCtx,"",0); + } + break; + default: + /* Expand the empty string */ + ph7_result_string(pCtx,"",0); + break; + } + }else{ + /* Return an associative array */ + ph7_value *pArray,*pValue; + pArray = ph7_context_new_array(pCtx); + pValue = ph7_context_new_scalar(pCtx); + if( pArray == 0 || pValue == 0 ){ + /* Out of mem,return NULL */ + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* dirname */ + pComp = &sInfo.sDir; + if( pComp->nByte > 0 ){ + ph7_value_string(pValue,pComp->zString,(int)pComp->nByte); + /* Perform the insertion */ + ph7_array_add_strkey_elem(pArray,"dirname",pValue); /* Will make it's own copy */ + } + /* Reset the string cursor */ + ph7_value_reset_string_cursor(pValue); + /* basername */ + pComp = &sInfo.sBasename; + if( pComp->nByte > 0 ){ + ph7_value_string(pValue,pComp->zString,(int)pComp->nByte); + /* Perform the insertion */ + ph7_array_add_strkey_elem(pArray,"basename",pValue); /* Will make it's own copy */ + } + /* Reset the string cursor */ + ph7_value_reset_string_cursor(pValue); + /* extension */ + pComp = &sInfo.sExtension; + if( pComp->nByte > 0 ){ + ph7_value_string(pValue,pComp->zString,(int)pComp->nByte); + /* Perform the insertion */ + ph7_array_add_strkey_elem(pArray,"extension",pValue); /* Will make it's own copy */ + } + /* Reset the string cursor */ + ph7_value_reset_string_cursor(pValue); + /* filename */ + pComp = &sInfo.sFilename; + if( pComp->nByte > 0 ){ + ph7_value_string(pValue,pComp->zString,(int)pComp->nByte); + /* Perform the insertion */ + ph7_array_add_strkey_elem(pArray,"filename",pValue); /* Will make it's own copy */ + } + /* Return the created array */ + ph7_result_value(pCtx,pArray); + /* Don't worry about freeing memory, everything will be released + * automatically as soon we return from this foreign function. + */ + } + return PH7_OK; +} +/* + * Globbing implementation extracted from the sqlite3 source tree. + * Original author: D. Richard Hipp (http://www.sqlite.org) + * Status: Public Domain + */ +typedef unsigned char u8; +/* An array to map all upper-case characters into their corresponding +** lower-case character. +** +** SQLite only considers US-ASCII (or EBCDIC) characters. We do not +** handle case conversions for the UTF character set since the tables +** involved are nearly as big or bigger than SQLite itself. +*/ +static const unsigned char sqlite3UpperToLower[] = { + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, + 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, + 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, + 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 97, 98, 99,100,101,102,103, + 104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121, + 122, 91, 92, 93, 94, 95, 96, 97, 98, 99,100,101,102,103,104,105,106,107, + 108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125, + 126,127,128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143, + 144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,160,161, + 162,163,164,165,166,167,168,169,170,171,172,173,174,175,176,177,178,179, + 180,181,182,183,184,185,186,187,188,189,190,191,192,193,194,195,196,197, + 198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215, + 216,217,218,219,220,221,222,223,224,225,226,227,228,229,230,231,232,233, + 234,235,236,237,238,239,240,241,242,243,244,245,246,247,248,249,250,251, + 252,253,254,255 +}; +#define GlogUpperToLower(A) if( A<0x80 ){ A = sqlite3UpperToLower[A]; } +/* +** Assuming zIn points to the first byte of a UTF-8 character, +** advance zIn to point to the first byte of the next UTF-8 character. +*/ +#define SQLITE_SKIP_UTF8(zIn) { \ + if( (*(zIn++))>=0xc0 ){ \ + while( (*zIn & 0xc0)==0x80 ){ zIn++; } \ + } \ +} +/* +** Compare two UTF-8 strings for equality where the first string can +** potentially be a "glob" expression. Return true (1) if they +** are the same and false (0) if they are different. +** +** Globbing rules: +** +** '*' Matches any sequence of zero or more characters. +** +** '?' Matches exactly one character. +** +** [...] Matches one character from the enclosed list of +** characters. +** +** [^...] Matches one character not in the enclosed list. +** +** With the [...] and [^...] matching, a ']' character can be included +** in the list by making it the first character after '[' or '^'. A +** range of characters can be specified using '-'. Example: +** "[a-z]" matches any single lower-case letter. To match a '-', make +** it the last character in the list. +** +** This routine is usually quick, but can be N**2 in the worst case. +** +** Hints: to match '*' or '?', put them in "[]". Like this: +** +** abc[*]xyz Matches "abc*xyz" only +*/ +static int patternCompare( + const u8 *zPattern, /* The glob pattern */ + const u8 *zString, /* The string to compare against the glob */ + const int esc, /* The escape character */ + int noCase +){ + int c, c2; + int invert; + int seen; + u8 matchOne = '?'; + u8 matchAll = '*'; + u8 matchSet = '['; + int prevEscape = 0; /* True if the previous character was 'escape' */ + + if( !zPattern || !zString ) return 0; + while( (c = PH7_Utf8Read(zPattern,0,&zPattern))!=0 ){ + if( !prevEscape && c==matchAll ){ + while( (c=PH7_Utf8Read(zPattern,0,&zPattern)) == matchAll + || c == matchOne ){ + if( c==matchOne && PH7_Utf8Read(zString, 0, &zString)==0 ){ + return 0; + } + } + if( c==0 ){ + return 1; + }else if( c==esc ){ + c = PH7_Utf8Read(zPattern, 0, &zPattern); + if( c==0 ){ + return 0; + } + }else if( c==matchSet ){ + if( (esc==0) || (matchSet<0x80) ) return 0; + while( *zString && patternCompare(&zPattern[-1],zString,esc,noCase)==0 ){ + SQLITE_SKIP_UTF8(zString); + } + return *zString!=0; + } + while( (c2 = PH7_Utf8Read(zString,0,&zString))!=0 ){ + if( noCase ){ + GlogUpperToLower(c2); + GlogUpperToLower(c); + while( c2 != 0 && c2 != c ){ + c2 = PH7_Utf8Read(zString, 0, &zString); + GlogUpperToLower(c2); + } + }else{ + while( c2 != 0 && c2 != c ){ + c2 = PH7_Utf8Read(zString, 0, &zString); + } + } + if( c2==0 ) return 0; + if( patternCompare(zPattern,zString,esc,noCase) ) return 1; + } + return 0; + }else if( !prevEscape && c==matchOne ){ + if( PH7_Utf8Read(zString, 0, &zString)==0 ){ + return 0; + } + }else if( c==matchSet ){ + int prior_c = 0; + if( esc == 0 ) return 0; + seen = 0; + invert = 0; + c = PH7_Utf8Read(zString, 0, &zString); + if( c==0 ) return 0; + c2 = PH7_Utf8Read(zPattern, 0, &zPattern); + if( c2=='^' ){ + invert = 1; + c2 = PH7_Utf8Read(zPattern, 0, &zPattern); + } + if( c2==']' ){ + if( c==']' ) seen = 1; + c2 = PH7_Utf8Read(zPattern, 0, &zPattern); + } + while( c2 && c2!=']' ){ + if( c2=='-' && zPattern[0]!=']' && zPattern[0]!=0 && prior_c>0 ){ + c2 = PH7_Utf8Read(zPattern, 0, &zPattern); + if( c>=prior_c && c<=c2 ) seen = 1; + prior_c = 0; + }else{ + if( c==c2 ){ + seen = 1; + } + prior_c = c2; + } + c2 = PH7_Utf8Read(zPattern, 0, &zPattern); + } + if( c2==0 || (seen ^ invert)==0 ){ + return 0; + } + }else if( esc==c && !prevEscape ){ + prevEscape = 1; + }else{ + c2 = PH7_Utf8Read(zString, 0, &zString); + if( noCase ){ + GlogUpperToLower(c); + GlogUpperToLower(c2); + } + if( c!=c2 ){ + return 0; + } + prevEscape = 0; + } + } + return *zString==0; +} +/* + * Wrapper around patternCompare() defined above. + * See block comment above for more information. + */ +static int Glob(const unsigned char *zPattern,const unsigned char *zString,int iEsc,int CaseCompare) +{ + int rc; + if( iEsc < 0 ){ + iEsc = '\\'; + } + rc = patternCompare(zPattern,zString,iEsc,CaseCompare); + return rc; +} +/* + * bool fnmatch(string $pattern,string $string[,int $flags = 0 ]) + * Match filename against a pattern. + * Parameters + * $pattern + * The shell wildcard pattern. + * $string + * The tested string. + * $flags + * A list of possible flags: + * FNM_NOESCAPE Disable backslash escaping. + * FNM_PATHNAME Slash in string only matches slash in the given pattern. + * FNM_PERIOD Leading period in string must be exactly matched by period in the given pattern. + * FNM_CASEFOLD Caseless match. + * Return + * TRUE if there is a match, FALSE otherwise. + */ +static int PH7_builtin_fnmatch(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + const char *zString,*zPattern; + int iEsc = '\\'; + int noCase = 0; + int rc; + if( nArg < 2 || !ph7_value_is_string(apArg[0]) || !ph7_value_is_string(apArg[1]) ){ + /* Missing/Invalid arguments,return FALSE */ + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Extract the pattern and the string */ + zPattern = ph7_value_to_string(apArg[0],0); + zString = ph7_value_to_string(apArg[1],0); + /* Extract the flags if avaialble */ + if( nArg > 2 && ph7_value_is_int(apArg[2]) ){ + rc = ph7_value_to_int(apArg[2]); + if( rc & 0x01 /*FNM_NOESCAPE*/){ + iEsc = 0; + } + if( rc & 0x08 /*FNM_CASEFOLD*/){ + noCase = 1; + } + } + /* Go globbing */ + rc = Glob((const unsigned char *)zPattern,(const unsigned char *)zString,iEsc,noCase); + /* Globbing result */ + ph7_result_bool(pCtx,rc); + return PH7_OK; +} +/* + * bool strglob(string $pattern,string $string) + * Match string against a pattern. + * Parameters + * $pattern + * The shell wildcard pattern. + * $string + * The tested string. + * Return + * TRUE if there is a match, FALSE otherwise. + * Note that this a symisc eXtension. + */ +static int PH7_builtin_strglob(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + const char *zString,*zPattern; + int iEsc = '\\'; + int rc; + if( nArg < 2 || !ph7_value_is_string(apArg[0]) || !ph7_value_is_string(apArg[1]) ){ + /* Missing/Invalid arguments,return FALSE */ + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Extract the pattern and the string */ + zPattern = ph7_value_to_string(apArg[0],0); + zString = ph7_value_to_string(apArg[1],0); + /* Go globbing */ + rc = Glob((const unsigned char *)zPattern,(const unsigned char *)zString,iEsc,0); + /* Globbing result */ + ph7_result_bool(pCtx,rc); + return PH7_OK; +} +/* + * bool link(string $target,string $link) + * Create a hard link. + * Parameters + * $target + * Target of the link. + * $link + * The link name. + * Return + * TRUE on success or FALSE on failure. + */ +static int PH7_vfs_link(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + const char *zTarget,*zLink; + ph7_vfs *pVfs; + int rc; + if( nArg < 2 || !ph7_value_is_string(apArg[0]) || !ph7_value_is_string(apArg[1]) ){ + /* Missing/Invalid arguments,return FALSE */ + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Point to the underlying vfs */ + pVfs = (ph7_vfs *)ph7_context_user_data(pCtx); + if( pVfs == 0 || pVfs->xLink == 0 ){ + /* IO routine not implemented,return NULL */ + ph7_context_throw_error_format(pCtx,PH7_CTX_WARNING, + "IO routine(%s) not implemented in the underlying VFS,PH7 is returning FALSE", + ph7_function_name(pCtx) + ); + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Extract the given arguments */ + zTarget = ph7_value_to_string(apArg[0],0); + zLink = ph7_value_to_string(apArg[1],0); + /* Perform the requested operation */ + rc = pVfs->xLink(zTarget,zLink,0/*Not a symbolic link */); + /* IO result */ + ph7_result_bool(pCtx,rc == PH7_OK ); + return PH7_OK; +} +/* + * bool symlink(string $target,string $link) + * Creates a symbolic link. + * Parameters + * $target + * Target of the link. + * $link + * The link name. + * Return + * TRUE on success or FALSE on failure. + */ +static int PH7_vfs_symlink(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + const char *zTarget,*zLink; + ph7_vfs *pVfs; + int rc; + if( nArg < 2 || !ph7_value_is_string(apArg[0]) || !ph7_value_is_string(apArg[1]) ){ + /* Missing/Invalid arguments,return FALSE */ + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Point to the underlying vfs */ + pVfs = (ph7_vfs *)ph7_context_user_data(pCtx); + if( pVfs == 0 || pVfs->xLink == 0 ){ + /* IO routine not implemented,return NULL */ + ph7_context_throw_error_format(pCtx,PH7_CTX_WARNING, + "IO routine(%s) not implemented in the underlying VFS,PH7 is returning FALSE", + ph7_function_name(pCtx) + ); + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Extract the given arguments */ + zTarget = ph7_value_to_string(apArg[0],0); + zLink = ph7_value_to_string(apArg[1],0); + /* Perform the requested operation */ + rc = pVfs->xLink(zTarget,zLink,1/*A symbolic link */); + /* IO result */ + ph7_result_bool(pCtx,rc == PH7_OK ); + return PH7_OK; +} +/* + * int umask([ int $mask ]) + * Changes the current umask. + * Parameters + * $mask + * The new umask. + * Return + * umask() without arguments simply returns the current umask. + * Otherwise the old umask is returned. + */ +static int PH7_vfs_umask(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + int iOld,iNew; + ph7_vfs *pVfs; + /* Point to the underlying vfs */ + pVfs = (ph7_vfs *)ph7_context_user_data(pCtx); + if( pVfs == 0 || pVfs->xUmask == 0 ){ + /* IO routine not implemented,return -1 */ + ph7_context_throw_error_format(pCtx,PH7_CTX_WARNING, + "IO routine(%s) not implemented in the underlying VFS", + ph7_function_name(pCtx) + ); + ph7_result_int(pCtx,0); + return PH7_OK; + } + iNew = 0; + if( nArg > 0 ){ + iNew = ph7_value_to_int(apArg[0]); + } + /* Perform the requested operation */ + iOld = pVfs->xUmask(iNew); + /* Old mask */ + ph7_result_int(pCtx,iOld); + return PH7_OK; +} +/* + * string sys_get_temp_dir() + * Returns directory path used for temporary files. + * Parameters + * None + * Return + * Returns the path of the temporary directory. + */ +static int PH7_vfs_sys_get_temp_dir(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + ph7_vfs *pVfs; + /* Set the empty string as the default return value */ + ph7_result_string(pCtx,"",0); + /* Point to the underlying vfs */ + pVfs = (ph7_vfs *)ph7_context_user_data(pCtx); + if( pVfs == 0 || pVfs->xTempDir == 0 ){ + SXUNUSED(nArg); /* cc warning */ + SXUNUSED(apArg); + /* IO routine not implemented,return "" */ + ph7_context_throw_error_format(pCtx,PH7_CTX_WARNING, + "IO routine(%s) not implemented in the underlying VFS", + ph7_function_name(pCtx) + ); + return PH7_OK; + } + /* Perform the requested operation */ + pVfs->xTempDir(pCtx); + return PH7_OK; +} +/* + * string get_current_user() + * Returns the name of the current working user. + * Parameters + * None + * Return + * Returns the name of the current working user. + */ +static int PH7_vfs_get_current_user(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + ph7_vfs *pVfs; + /* Point to the underlying vfs */ + pVfs = (ph7_vfs *)ph7_context_user_data(pCtx); + if( pVfs == 0 || pVfs->xUsername == 0 ){ + SXUNUSED(nArg); /* cc warning */ + SXUNUSED(apArg); + /* IO routine not implemented */ + ph7_context_throw_error_format(pCtx,PH7_CTX_WARNING, + "IO routine(%s) not implemented in the underlying VFS", + ph7_function_name(pCtx) + ); + /* Set a dummy username */ + ph7_result_string(pCtx,"unknown",sizeof("unknown")-1); + return PH7_OK; + } + /* Perform the requested operation */ + pVfs->xUsername(pCtx); + return PH7_OK; +} +/* + * int64 getmypid() + * Gets process ID. + * Parameters + * None + * Return + * Returns the process ID. + */ +static int PH7_vfs_getmypid(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + ph7_int64 nProcessId; + ph7_vfs *pVfs; + /* Point to the underlying vfs */ + pVfs = (ph7_vfs *)ph7_context_user_data(pCtx); + if( pVfs == 0 || pVfs->xProcessId == 0 ){ + SXUNUSED(nArg); /* cc warning */ + SXUNUSED(apArg); + /* IO routine not implemented,return -1 */ + ph7_context_throw_error_format(pCtx,PH7_CTX_WARNING, + "IO routine(%s) not implemented in the underlying VFS", + ph7_function_name(pCtx) + ); + ph7_result_int(pCtx,-1); + return PH7_OK; + } + /* Perform the requested operation */ + nProcessId = (ph7_int64)pVfs->xProcessId(); + /* Set the result */ + ph7_result_int64(pCtx,nProcessId); + return PH7_OK; +} +/* + * int getmyuid() + * Get user ID. + * Parameters + * None + * Return + * Returns the user ID. + */ +static int PH7_vfs_getmyuid(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + ph7_vfs *pVfs; + int nUid; + /* Point to the underlying vfs */ + pVfs = (ph7_vfs *)ph7_context_user_data(pCtx); + if( pVfs == 0 || pVfs->xUid == 0 ){ + SXUNUSED(nArg); /* cc warning */ + SXUNUSED(apArg); + /* IO routine not implemented,return -1 */ + ph7_context_throw_error_format(pCtx,PH7_CTX_WARNING, + "IO routine(%s) not implemented in the underlying VFS", + ph7_function_name(pCtx) + ); + ph7_result_int(pCtx,-1); + return PH7_OK; + } + /* Perform the requested operation */ + nUid = pVfs->xUid(); + /* Set the result */ + ph7_result_int(pCtx,nUid); + return PH7_OK; +} +/* + * int getmygid() + * Get group ID. + * Parameters + * None + * Return + * Returns the group ID. + */ +static int PH7_vfs_getmygid(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + ph7_vfs *pVfs; + int nGid; + /* Point to the underlying vfs */ + pVfs = (ph7_vfs *)ph7_context_user_data(pCtx); + if( pVfs == 0 || pVfs->xGid == 0 ){ + SXUNUSED(nArg); /* cc warning */ + SXUNUSED(apArg); + /* IO routine not implemented,return -1 */ + ph7_context_throw_error_format(pCtx,PH7_CTX_WARNING, + "IO routine(%s) not implemented in the underlying VFS", + ph7_function_name(pCtx) + ); + ph7_result_int(pCtx,-1); + return PH7_OK; + } + /* Perform the requested operation */ + nGid = pVfs->xGid(); + /* Set the result */ + ph7_result_int(pCtx,nGid); + return PH7_OK; +} +#ifdef __WINNT__ +#include +#elif defined(__UNIXES__) +#include +#endif +/* + * string php_uname([ string $mode = "a" ]) + * Returns information about the host operating system. + * Parameters + * $mode + * mode is a single character that defines what information is returned: + * 'a': This is the default. Contains all modes in the sequence "s n r v m". + * 's': Operating system name. eg. FreeBSD. + * 'n': Host name. eg. localhost.example.com. + * 'r': Release name. eg. 5.1.2-RELEASE. + * 'v': Version information. Varies a lot between operating systems. + * 'm': Machine type. eg. i386. + * Return + * OS description as a string. + */ +static int PH7_vfs_ph7_uname(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ +#if defined(__WINNT__) + const char *zName = "Microsoft Windows"; + OSVERSIONINFOW sVer; +#elif defined(__UNIXES__) + struct utsname sName; +#endif + const char *zMode = "a"; + if( nArg > 0 && ph7_value_is_string(apArg[0]) ){ + /* Extract the desired mode */ + zMode = ph7_value_to_string(apArg[0],0); + } +#if defined(__WINNT__) + sVer.dwOSVersionInfoSize = sizeof(sVer); + if( TRUE != GetVersionExW(&sVer)){ + ph7_result_string(pCtx,zName,-1); + return PH7_OK; + } + if( sVer.dwPlatformId == VER_PLATFORM_WIN32_NT ){ + if( sVer.dwMajorVersion <= 4 ){ + zName = "Microsoft Windows NT"; + }else if( sVer.dwMajorVersion == 5 ){ + switch(sVer.dwMinorVersion){ + case 0: zName = "Microsoft Windows 2000"; break; + case 1: zName = "Microsoft Windows XP"; break; + case 2: zName = "Microsoft Windows Server 2003"; break; + } + }else if( sVer.dwMajorVersion == 6){ + switch(sVer.dwMinorVersion){ + case 0: zName = "Microsoft Windows Vista"; break; + case 1: zName = "Microsoft Windows 7"; break; + case 2: zName = "Microsoft Windows Server 2008"; break; + case 3: zName = "Microsoft Windows 8"; break; + default: break; + } + } + } + switch(zMode[0]){ + case 's': + /* Operating system name */ + ph7_result_string(pCtx,zName,-1/* Compute length automatically*/); + break; + case 'n': + /* Host name */ + ph7_result_string(pCtx,"localhost",(int)sizeof("localhost")-1); + break; + case 'r': + case 'v': + /* Version information. */ + ph7_result_string_format(pCtx,"%u.%u build %u", + sVer.dwMajorVersion,sVer.dwMinorVersion,sVer.dwBuildNumber + ); + break; + case 'm': + /* Machine name */ + ph7_result_string(pCtx,"x86",(int)sizeof("x86")-1); + break; + default: + ph7_result_string_format(pCtx,"%s localhost %u.%u build %u x86", + zName, + sVer.dwMajorVersion,sVer.dwMinorVersion,sVer.dwBuildNumber + ); + break; + } +#elif defined(__UNIXES__) + if( uname(&sName) != 0 ){ + ph7_result_string(pCtx,"Unix",(int)sizeof("Unix")-1); + return PH7_OK; + } + switch(zMode[0]){ + case 's': + /* Operating system name */ + ph7_result_string(pCtx,sName.sysname,-1/* Compute length automatically*/); + break; + case 'n': + /* Host name */ + ph7_result_string(pCtx,sName.nodename,-1/* Compute length automatically*/); + break; + case 'r': + /* Release information */ + ph7_result_string(pCtx,sName.release,-1/* Compute length automatically*/); + break; + case 'v': + /* Version information. */ + ph7_result_string(pCtx,sName.version,-1/* Compute length automatically*/); + break; + case 'm': + /* Machine name */ + ph7_result_string(pCtx,sName.machine,-1/* Compute length automatically*/); + break; + default: + ph7_result_string_format(pCtx, + "%s %s %s %s %s", + sName.sysname, + sName.release, + sName.version, + sName.nodename, + sName.machine + ); + break; + } +#else + ph7_result_string(pCtx,"Unknown Operating System",(int)sizeof("Unknown Operating System")-1); +#endif + return PH7_OK; +} +/* + * Section: + * IO stream implementation. + * Authors: + * Symisc Systems,devel@symisc.net. + * Copyright (C) Symisc Systems,http://ph7.symisc.net + * Status: + * Stable. + */ +typedef struct io_private io_private; +struct io_private +{ + const ph7_io_stream *pStream; /* Underlying IO device */ + void *pHandle; /* IO handle */ + /* Unbuffered IO */ + SyBlob sBuffer; /* Working buffer */ + sxu32 nOfft; /* Current read offset */ + sxu32 iMagic; /* Sanity check to avoid misuse */ +}; +#define IO_PRIVATE_MAGIC 0xFEAC14 +/* Make sure we are dealing with a valid io_private instance */ +#define IO_PRIVATE_INVALID(IO) ( IO == 0 || IO->iMagic != IO_PRIVATE_MAGIC ) +/* Forward declaration */ +static void ResetIOPrivate(io_private *pDev); +/* + * bool ftruncate(resource $handle,int64 $size) + * Truncates a file to a given length. + * Parameters + * $handle + * The file pointer. + * Note: + * The handle must be open for writing. + * $size + * The size to truncate to. + * Return + * TRUE on success or FALSE on failure. + */ +static int PH7_builtin_ftruncate(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + const ph7_io_stream *pStream; + io_private *pDev; + int rc; + if( nArg < 2 || !ph7_value_is_resource(apArg[0]) ){ + /* Missing/Invalid arguments,return FALSE */ + ph7_context_throw_error(pCtx,PH7_CTX_WARNING,"Expecting an IO handle"); + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Extract our private data */ + pDev = (io_private *)ph7_value_to_resource(apArg[0]); + /* Make sure we are dealing with a valid io_private instance */ + if( IO_PRIVATE_INVALID(pDev) ){ + /*Expecting an IO handle */ + ph7_context_throw_error(pCtx,PH7_CTX_WARNING,"Expecting an IO handle"); + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Point to the target IO stream device */ + pStream = pDev->pStream; + if( pStream == 0 || pStream->xTrunc == 0){ + ph7_context_throw_error_format(pCtx,PH7_CTX_WARNING, + "IO routine(%s) not implemented in the underlying stream(%s) device,PH7 is returning FALSE", + ph7_function_name(pCtx),pStream ? pStream->zName : "null_stream" + ); + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Perform the requested operation */ + rc = pStream->xTrunc(pDev->pHandle,ph7_value_to_int64(apArg[1])); + if( rc == PH7_OK ){ + /* Discard buffered data */ + ResetIOPrivate(pDev); + } + /* IO result */ + ph7_result_bool(pCtx,rc == PH7_OK); + return PH7_OK; +} +/* + * int fseek(resource $handle,int $offset[,int $whence = SEEK_SET ]) + * Seeks on a file pointer. + * Parameters + * $handle + * A file system pointer resource that is typically created using fopen(). + * $offset + * The offset. + * To move to a position before the end-of-file, you need to pass a negative + * value in offset and set whence to SEEK_END. + * whence + * whence values are: + * SEEK_SET - Set position equal to offset bytes. + * SEEK_CUR - Set position to current location plus offset. + * SEEK_END - Set position to end-of-file plus offset. + * Return + * 0 on success,-1 on failure + */ +static int PH7_builtin_fseek(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + const ph7_io_stream *pStream; + io_private *pDev; + ph7_int64 iOfft; + int whence; + int rc; + if( nArg < 2 || !ph7_value_is_resource(apArg[0]) ){ + /* Missing/Invalid arguments,return FALSE */ + ph7_context_throw_error(pCtx,PH7_CTX_WARNING,"Expecting an IO handle"); + ph7_result_int(pCtx,-1); + return PH7_OK; + } + /* Extract our private data */ + pDev = (io_private *)ph7_value_to_resource(apArg[0]); + /* Make sure we are dealing with a valid io_private instance */ + if( IO_PRIVATE_INVALID(pDev) ){ + /*Expecting an IO handle */ + ph7_context_throw_error(pCtx,PH7_CTX_WARNING,"Expecting an IO handle"); + ph7_result_int(pCtx,-1); + return PH7_OK; + } + /* Point to the target IO stream device */ + pStream = pDev->pStream; + if( pStream == 0 || pStream->xSeek == 0){ + ph7_context_throw_error_format(pCtx,PH7_CTX_WARNING, + "IO routine(%s) not implemented in the underlying stream(%s) device", + ph7_function_name(pCtx),pStream ? pStream->zName : "null_stream" + ); + ph7_result_int(pCtx,-1); + return PH7_OK; + } + /* Extract the offset */ + iOfft = ph7_value_to_int64(apArg[1]); + whence = 0;/* SEEK_SET */ + if( nArg > 2 && ph7_value_is_int(apArg[2]) ){ + whence = ph7_value_to_int(apArg[2]); + } + /* Perform the requested operation */ + rc = pStream->xSeek(pDev->pHandle,iOfft,whence); + if( rc == PH7_OK ){ + /* Ignore buffered data */ + ResetIOPrivate(pDev); + } + /* IO result */ + ph7_result_int(pCtx,rc == PH7_OK ? 0 : - 1); + return PH7_OK; +} +/* + * int64 ftell(resource $handle) + * Returns the current position of the file read/write pointer. + * Parameters + * $handle + * The file pointer. + * Return + * Returns the position of the file pointer referenced by handle + * as an integer; i.e., its offset into the file stream. + * FALSE is returned on failure. + */ +static int PH7_builtin_ftell(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + const ph7_io_stream *pStream; + io_private *pDev; + ph7_int64 iOfft; + if( nArg < 1 || !ph7_value_is_resource(apArg[0]) ){ + /* Missing/Invalid arguments,return FALSE */ + ph7_context_throw_error(pCtx,PH7_CTX_WARNING,"Expecting an IO handle"); + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Extract our private data */ + pDev = (io_private *)ph7_value_to_resource(apArg[0]); + /* Make sure we are dealing with a valid io_private instance */ + if( IO_PRIVATE_INVALID(pDev) ){ + /*Expecting an IO handle */ + ph7_context_throw_error(pCtx,PH7_CTX_WARNING,"Expecting an IO handle"); + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Point to the target IO stream device */ + pStream = pDev->pStream; + if( pStream == 0 || pStream->xTell == 0){ + ph7_context_throw_error_format(pCtx,PH7_CTX_WARNING, + "IO routine(%s) not implemented in the underlying stream(%s) device,PH7 is returning FALSE", + ph7_function_name(pCtx),pStream ? pStream->zName : "null_stream" + ); + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Perform the requested operation */ + iOfft = pStream->xTell(pDev->pHandle); + /* IO result */ + ph7_result_int64(pCtx,iOfft); + return PH7_OK; +} +/* + * bool rewind(resource $handle) + * Rewind the position of a file pointer. + * Parameters + * $handle + * The file pointer. + * Return + * TRUE on success or FALSE on failure. + */ +static int PH7_builtin_rewind(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + const ph7_io_stream *pStream; + io_private *pDev; + int rc; + if( nArg < 1 || !ph7_value_is_resource(apArg[0]) ){ + /* Missing/Invalid arguments,return FALSE */ + ph7_context_throw_error(pCtx,PH7_CTX_WARNING,"Expecting an IO handle"); + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Extract our private data */ + pDev = (io_private *)ph7_value_to_resource(apArg[0]); + /* Make sure we are dealing with a valid io_private instance */ + if( IO_PRIVATE_INVALID(pDev) ){ + /*Expecting an IO handle */ + ph7_context_throw_error(pCtx,PH7_CTX_WARNING,"Expecting an IO handle"); + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Point to the target IO stream device */ + pStream = pDev->pStream; + if( pStream == 0 || pStream->xSeek == 0){ + ph7_context_throw_error_format(pCtx,PH7_CTX_WARNING, + "IO routine(%s) not implemented in the underlying stream(%s) device,PH7 is returning FALSE", + ph7_function_name(pCtx),pStream ? pStream->zName : "null_stream" + ); + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Perform the requested operation */ + rc = pStream->xSeek(pDev->pHandle,0,0/*SEEK_SET*/); + if( rc == PH7_OK ){ + /* Ignore buffered data */ + ResetIOPrivate(pDev); + } + /* IO result */ + ph7_result_bool(pCtx,rc == PH7_OK); + return PH7_OK; +} +/* + * bool fflush(resource $handle) + * Flushes the output to a file. + * Parameters + * $handle + * The file pointer. + * Return + * TRUE on success or FALSE on failure. + */ +static int PH7_builtin_fflush(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + const ph7_io_stream *pStream; + io_private *pDev; + int rc; + if( nArg < 1 || !ph7_value_is_resource(apArg[0]) ){ + /* Missing/Invalid arguments,return FALSE */ + ph7_context_throw_error(pCtx,PH7_CTX_WARNING,"Expecting an IO handle"); + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Extract our private data */ + pDev = (io_private *)ph7_value_to_resource(apArg[0]); + /* Make sure we are dealing with a valid io_private instance */ + if( IO_PRIVATE_INVALID(pDev) ){ + /*Expecting an IO handle */ + ph7_context_throw_error(pCtx,PH7_CTX_WARNING,"Expecting an IO handle"); + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Point to the target IO stream device */ + pStream = pDev->pStream; + if( pStream == 0 || pStream->xSync == 0){ + ph7_context_throw_error_format(pCtx,PH7_CTX_WARNING, + "IO routine(%s) not implemented in the underlying stream(%s) device,PH7 is returning FALSE", + ph7_function_name(pCtx),pStream ? pStream->zName : "null_stream" + ); + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Perform the requested operation */ + rc = pStream->xSync(pDev->pHandle); + /* IO result */ + ph7_result_bool(pCtx,rc == PH7_OK); + return PH7_OK; +} +/* + * bool feof(resource $handle) + * Tests for end-of-file on a file pointer. + * Parameters + * $handle + * The file pointer. + * Return + * Returns TRUE if the file pointer is at EOF.FALSE otherwise + */ +static int PH7_builtin_feof(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + const ph7_io_stream *pStream; + io_private *pDev; + int rc; + if( nArg < 1 || !ph7_value_is_resource(apArg[0]) ){ + /* Missing/Invalid arguments */ + ph7_context_throw_error(pCtx,PH7_CTX_WARNING,"Expecting an IO handle"); + ph7_result_bool(pCtx,1); + return PH7_OK; + } + /* Extract our private data */ + pDev = (io_private *)ph7_value_to_resource(apArg[0]); + /* Make sure we are dealing with a valid io_private instance */ + if( IO_PRIVATE_INVALID(pDev) ){ + /*Expecting an IO handle */ + ph7_context_throw_error(pCtx,PH7_CTX_WARNING,"Expecting an IO handle"); + ph7_result_bool(pCtx,1); + return PH7_OK; + } + /* Point to the target IO stream device */ + pStream = pDev->pStream; + if( pStream == 0 ){ + ph7_context_throw_error_format(pCtx,PH7_CTX_WARNING, + "IO routine(%s) not implemented in the underlying stream(%s) device,PH7 is returning FALSE", + ph7_function_name(pCtx),pStream ? pStream->zName : "null_stream" + ); + ph7_result_bool(pCtx,1); + return PH7_OK; + } + rc = SXERR_EOF; + /* Perform the requested operation */ + if( SyBlobLength(&pDev->sBuffer) - pDev->nOfft > 0 ){ + /* Data is available */ + rc = PH7_OK; + }else{ + char zBuf[4096]; + ph7_int64 n; + /* Perform a buffered read */ + n = pStream->xRead(pDev->pHandle,zBuf,sizeof(zBuf)); + if( n > 0 ){ + /* Copy buffered data */ + SyBlobAppend(&pDev->sBuffer,zBuf,(sxu32)n); + rc = PH7_OK; + } + } + /* EOF or not */ + ph7_result_bool(pCtx,rc == SXERR_EOF); + return PH7_OK; +} +/* + * Read n bytes from the underlying IO stream device. + * Return total numbers of bytes readen on success. A number < 1 on failure + * [i.e: IO error ] or EOF. + */ +static ph7_int64 StreamRead(io_private *pDev,void *pBuf,ph7_int64 nLen) +{ + const ph7_io_stream *pStream = pDev->pStream; + char *zBuf = (char *)pBuf; + ph7_int64 n,nRead; + n = SyBlobLength(&pDev->sBuffer) - pDev->nOfft; + if( n > 0 ){ + if( n > nLen ){ + n = nLen; + } + /* Copy the buffered data */ + SyMemcpy(SyBlobDataAt(&pDev->sBuffer,pDev->nOfft),pBuf,(sxu32)n); + /* Update the read offset */ + pDev->nOfft += (sxu32)n; + if( pDev->nOfft >= SyBlobLength(&pDev->sBuffer) ){ + /* Reset the working buffer so that we avoid excessive memory allocation */ + SyBlobReset(&pDev->sBuffer); + pDev->nOfft = 0; + } + nLen -= n; + if( nLen < 1 ){ + /* All done */ + return n; + } + /* Advance the cursor */ + zBuf += n; + } + /* Read without buffering */ + nRead = pStream->xRead(pDev->pHandle,zBuf,nLen); + if( nRead > 0 ){ + n += nRead; + }else if( n < 1 ){ + /* EOF or IO error */ + return nRead; + } + return n; +} +/* + * Extract a single line from the buffered input. + */ +static sxi32 GetLine(io_private *pDev,ph7_int64 *pLen,const char **pzLine) +{ + const char *zIn,*zEnd,*zPtr; + zIn = (const char *)SyBlobDataAt(&pDev->sBuffer,pDev->nOfft); + zEnd = &zIn[SyBlobLength(&pDev->sBuffer)-pDev->nOfft]; + zPtr = zIn; + while( zIn < zEnd ){ + if( zIn[0] == '\n' ){ + /* Line found */ + zIn++; /* Include the line ending as requested by the PHP specification */ + *pLen = (ph7_int64)(zIn-zPtr); + *pzLine = zPtr; + return SXRET_OK; + } + zIn++; + } + /* No line were found */ + return SXERR_NOTFOUND; +} +/* + * Read a single line from the underlying IO stream device. + */ +static ph7_int64 StreamReadLine(io_private *pDev,const char **pzData,ph7_int64 nMaxLen) +{ + const ph7_io_stream *pStream = pDev->pStream; + char zBuf[8192]; + ph7_int64 n; + sxi32 rc; + n = 0; + if( pDev->nOfft >= SyBlobLength(&pDev->sBuffer) ){ + /* Reset the working buffer so that we avoid excessive memory allocation */ + SyBlobReset(&pDev->sBuffer); + pDev->nOfft = 0; + } + if( SyBlobLength(&pDev->sBuffer) - pDev->nOfft > 0 ){ + /* Check if there is a line */ + rc = GetLine(pDev,&n,pzData); + if( rc == SXRET_OK ){ + /* Got line,update the cursor */ + pDev->nOfft += (sxu32)n; + return n; + } + } + /* Perform the read operation until a new line is extracted or length + * limit is reached. + */ + for(;;){ + n = pStream->xRead(pDev->pHandle,zBuf,(nMaxLen > 0 && nMaxLen < sizeof(zBuf)) ? nMaxLen : sizeof(zBuf)); + if( n < 1 ){ + /* EOF or IO error */ + break; + } + /* Append the data just read */ + SyBlobAppend(&pDev->sBuffer,zBuf,(sxu32)n); + /* Try to extract a line */ + rc = GetLine(pDev,&n,pzData); + if( rc == SXRET_OK ){ + /* Got one,return immediately */ + pDev->nOfft += (sxu32)n; + return n; + } + if( nMaxLen > 0 && (SyBlobLength(&pDev->sBuffer) - pDev->nOfft >= nMaxLen) ){ + /* Read limit reached,return the available data */ + *pzData = (const char *)SyBlobDataAt(&pDev->sBuffer,pDev->nOfft); + n = SyBlobLength(&pDev->sBuffer) - pDev->nOfft; + /* Reset the working buffer */ + SyBlobReset(&pDev->sBuffer); + pDev->nOfft = 0; + return n; + } + } + if( SyBlobLength(&pDev->sBuffer) - pDev->nOfft > 0 ){ + /* Read limit reached,return the available data */ + *pzData = (const char *)SyBlobDataAt(&pDev->sBuffer,pDev->nOfft); + n = SyBlobLength(&pDev->sBuffer) - pDev->nOfft; + /* Reset the working buffer */ + SyBlobReset(&pDev->sBuffer); + pDev->nOfft = 0; + } + return n; +} +/* + * Open an IO stream handle. + * Notes on stream: + * According to the PHP reference manual. + * In its simplest definition, a stream is a resource object which exhibits streamable behavior. + * That is, it can be read from or written to in a linear fashion, and may be able to fseek() + * to an arbitrary locations within the stream. + * A wrapper is additional code which tells the stream how to handle specific protocols/encodings. + * For example, the http wrapper knows how to translate a URL into an HTTP/1.0 request for a file + * on a remote server. + * A stream is referenced as: scheme://target + * scheme(string) - The name of the wrapper to be used. Examples include: file, http... + * If no wrapper is specified, the function default is used (typically file://). + * target - Depends on the wrapper used. For filesystem related streams this is typically a path + * and filename of the desired file. For network related streams this is typically a hostname, often + * with a path appended. + * + * Note that PH7 IO streams looks like PHP streams but their implementation differ greately. + * Please refer to the official documentation for a full discussion. + * This function return a handle on success. Otherwise null. + */ +PH7_PRIVATE void * PH7_StreamOpenHandle(ph7_vm *pVm,const ph7_io_stream *pStream,const char *zFile, + int iFlags,int use_include,ph7_value *pResource,int bPushInclude,int *pNew) +{ + void *pHandle = 0; /* cc warning */ + SyString sFile; + int rc; + if( pStream == 0 ){ + /* No such stream device */ + return 0; + } + SyStringInitFromBuf(&sFile,zFile,SyStrlen(zFile)); + if( use_include ){ + if( sFile.zString[0] == '/' || +#ifdef __WINNT__ + (sFile.nByte > 2 && sFile.zString[1] == ':' && (sFile.zString[2] == '\\' || sFile.zString[2] == '/') ) || +#endif + (sFile.nByte > 1 && sFile.zString[0] == '.' && sFile.zString[1] == '/') || + (sFile.nByte > 2 && sFile.zString[0] == '.' && sFile.zString[1] == '.' && sFile.zString[2] == '/') ){ + /* Open the file directly */ + rc = pStream->xOpen(zFile,iFlags,pResource,&pHandle); + }else{ + SyString *pPath; + SyBlob sWorker; +#ifdef __WINNT__ + static const int c = '\\'; +#else + static const int c = '/'; +#endif + /* Init the path builder working buffer */ + SyBlobInit(&sWorker,&pVm->sAllocator); + /* Build a path from the set of include path */ + SySetResetCursor(&pVm->aPaths); + rc = SXERR_IO; + while( SXRET_OK == SySetGetNextEntry(&pVm->aPaths,(void **)&pPath) ){ + /* Build full path */ + SyBlobFormat(&sWorker,"%z%c%z",pPath,c,&sFile); + /* Append null terminator */ + if( SXRET_OK != SyBlobNullAppend(&sWorker) ){ + continue; + } + /* Try to open the file */ + rc = pStream->xOpen((const char *)SyBlobData(&sWorker),iFlags,pResource,&pHandle); + if( rc == PH7_OK ){ + if( bPushInclude ){ + /* Mark as included */ + PH7_VmPushFilePath(pVm,(const char *)SyBlobData(&sWorker),SyBlobLength(&sWorker),FALSE,pNew); + } + break; + } + /* Reset the working buffer */ + SyBlobReset(&sWorker); + /* Check the next path */ + } + SyBlobRelease(&sWorker); + } + if( rc == PH7_OK ){ + if( bPushInclude ){ + /* Mark as included */ + PH7_VmPushFilePath(pVm,sFile.zString,sFile.nByte,FALSE,pNew); + } + } + }else{ + /* Open the URI direcly */ + rc = pStream->xOpen(zFile,iFlags,pResource,&pHandle); + } + if( rc != PH7_OK ){ + /* IO error */ + return 0; + } + /* Return the file handle */ + return pHandle; +} +/* + * Read the whole contents of an open IO stream handle [i.e local file/URL..] + * Store the read data in the given BLOB (last argument). + * The read operation is stopped when he hit the EOF or an IO error occurs. + */ +PH7_PRIVATE sxi32 PH7_StreamReadWholeFile(void *pHandle,const ph7_io_stream *pStream,SyBlob *pOut) +{ + ph7_int64 nRead; + char zBuf[8192]; /* 8K */ + int rc; + /* Perform the requested operation */ + for(;;){ + nRead = pStream->xRead(pHandle,zBuf,sizeof(zBuf)); + if( nRead < 1 ){ + /* EOF or IO error */ + break; + } + /* Append contents */ + rc = SyBlobAppend(pOut,zBuf,(sxu32)nRead); + if( rc != SXRET_OK ){ + break; + } + } + return SyBlobLength(pOut) > 0 ? SXRET_OK : -1; +} +/* + * Close an open IO stream handle [i.e local file/URI..]. + */ +PH7_PRIVATE void PH7_StreamCloseHandle(const ph7_io_stream *pStream,void *pHandle) +{ + if( pStream->xClose ){ + pStream->xClose(pHandle); + } +} +/* + * string fgetc(resource $handle) + * Gets a character from the given file pointer. + * Parameters + * $handle + * The file pointer. + * Return + * Returns a string containing a single character read from the file + * pointed to by handle. Returns FALSE on EOF. + * WARNING + * This operation is extremely slow.Avoid using it. + */ +static int PH7_builtin_fgetc(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + const ph7_io_stream *pStream; + io_private *pDev; + int c,n; + if( nArg < 1 || !ph7_value_is_resource(apArg[0]) ){ + /* Missing/Invalid arguments,return FALSE */ + ph7_context_throw_error(pCtx,PH7_CTX_WARNING,"Expecting an IO handle"); + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Extract our private data */ + pDev = (io_private *)ph7_value_to_resource(apArg[0]); + /* Make sure we are dealing with a valid io_private instance */ + if( IO_PRIVATE_INVALID(pDev) ){ + /*Expecting an IO handle */ + ph7_context_throw_error(pCtx,PH7_CTX_WARNING,"Expecting an IO handle"); + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Point to the target IO stream device */ + pStream = pDev->pStream; + if( pStream == 0 ){ + ph7_context_throw_error_format(pCtx,PH7_CTX_WARNING, + "IO routine(%s) not implemented in the underlying stream(%s) device,PH7 is returning FALSE", + ph7_function_name(pCtx),pStream ? pStream->zName : "null_stream" + ); + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Perform the requested operation */ + n = (int)StreamRead(pDev,(void *)&c,sizeof(char)); + /* IO result */ + if( n < 1 ){ + /* EOF or error,return FALSE */ + ph7_result_bool(pCtx,0); + }else{ + /* Return the string holding the character */ + ph7_result_string(pCtx,(const char *)&c,sizeof(char)); + } + return PH7_OK; +} +/* + * string fgets(resource $handle[,int64 $length ]) + * Gets line from file pointer. + * Parameters + * $handle + * The file pointer. + * $length + * Reading ends when length - 1 bytes have been read, on a newline + * (which is included in the return value), or on EOF (whichever comes first). + * If no length is specified, it will keep reading from the stream until it reaches + * the end of the line. + * Return + * Returns a string of up to length - 1 bytes read from the file pointed to by handle. + * If there is no more data to read in the file pointer, then FALSE is returned. + * If an error occurs, FALSE is returned. + */ +static int PH7_builtin_fgets(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + const ph7_io_stream *pStream; + const char *zLine; + io_private *pDev; + ph7_int64 n,nLen; + if( nArg < 1 || !ph7_value_is_resource(apArg[0]) ){ + /* Missing/Invalid arguments,return FALSE */ + ph7_context_throw_error(pCtx,PH7_CTX_WARNING,"Expecting an IO handle"); + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Extract our private data */ + pDev = (io_private *)ph7_value_to_resource(apArg[0]); + /* Make sure we are dealing with a valid io_private instance */ + if( IO_PRIVATE_INVALID(pDev) ){ + /*Expecting an IO handle */ + ph7_context_throw_error(pCtx,PH7_CTX_WARNING,"Expecting an IO handle"); + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Point to the target IO stream device */ + pStream = pDev->pStream; + if( pStream == 0 ){ + ph7_context_throw_error_format(pCtx,PH7_CTX_WARNING, + "IO routine(%s) not implemented in the underlying stream(%s) device,PH7 is returning FALSE", + ph7_function_name(pCtx),pStream ? pStream->zName : "null_stream" + ); + ph7_result_bool(pCtx,0); + return PH7_OK; + } + nLen = -1; + if( nArg > 1 ){ + /* Maximum data to read */ + nLen = ph7_value_to_int64(apArg[1]); + } + /* Perform the requested operation */ + n = StreamReadLine(pDev,&zLine,nLen); + if( n < 1 ){ + /* EOF or IO error,return FALSE */ + ph7_result_bool(pCtx,0); + }else{ + /* Return the freshly extracted line */ + ph7_result_string(pCtx,zLine,(int)n); + } + return PH7_OK; +} +/* + * string fread(resource $handle,int64 $length) + * Binary-safe file read. + * Parameters + * $handle + * The file pointer. + * $length + * Up to length number of bytes read. + * Return + * The data readen on success or FALSE on failure. + */ +static int PH7_builtin_fread(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + const ph7_io_stream *pStream; + io_private *pDev; + ph7_int64 nRead; + void *pBuf; + int nLen; + if( nArg < 1 || !ph7_value_is_resource(apArg[0]) ){ + /* Missing/Invalid arguments,return FALSE */ + ph7_context_throw_error(pCtx,PH7_CTX_WARNING,"Expecting an IO handle"); + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Extract our private data */ + pDev = (io_private *)ph7_value_to_resource(apArg[0]); + /* Make sure we are dealing with a valid io_private instance */ + if( IO_PRIVATE_INVALID(pDev) ){ + /*Expecting an IO handle */ + ph7_context_throw_error(pCtx,PH7_CTX_WARNING,"Expecting an IO handle"); + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Point to the target IO stream device */ + pStream = pDev->pStream; + if( pStream == 0 ){ + ph7_context_throw_error_format(pCtx,PH7_CTX_WARNING, + "IO routine(%s) not implemented in the underlying stream(%s) device,PH7 is returning FALSE", + ph7_function_name(pCtx),pStream ? pStream->zName : "null_stream" + ); + ph7_result_bool(pCtx,0); + return PH7_OK; + } + nLen = 4096; + if( nArg > 1 ){ + nLen = ph7_value_to_int(apArg[1]); + if( nLen < 1 ){ + /* Invalid length,set a default length */ + nLen = 4096; + } + } + /* Allocate enough buffer */ + pBuf = ph7_context_alloc_chunk(pCtx,(unsigned int)nLen,FALSE,FALSE); + if( pBuf == 0 ){ + ph7_context_throw_error(pCtx,PH7_CTX_ERR,"PH7 is running out of memory"); + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Perform the requested operation */ + nRead = StreamRead(pDev,pBuf,(ph7_int64)nLen); + if( nRead < 1 ){ + /* Nothing read,return FALSE */ + ph7_result_bool(pCtx,0); + }else{ + /* Make a copy of the data just read */ + ph7_result_string(pCtx,(const char *)pBuf,(int)nRead); + } + /* Release the buffer */ + ph7_context_free_chunk(pCtx,pBuf); + return PH7_OK; +} +/* + * array fgetcsv(resource $handle [, int $length = 0 + * [,string $delimiter = ','[,string $enclosure = '"'[,string $escape='\\']]]]) + * Gets line from file pointer and parse for CSV fields. + * Parameters + * $handle + * The file pointer. + * $length + * Reading ends when length - 1 bytes have been read, on a newline + * (which is included in the return value), or on EOF (whichever comes first). + * If no length is specified, it will keep reading from the stream until it reaches + * the end of the line. + * $delimiter + * Set the field delimiter (one character only). + * $enclosure + * Set the field enclosure character (one character only). + * $escape + * Set the escape character (one character only). Defaults as a backslash (\) + * Return + * Returns a string of up to length - 1 bytes read from the file pointed to by handle. + * If there is no more data to read in the file pointer, then FALSE is returned. + * If an error occurs, FALSE is returned. + */ +static int PH7_builtin_fgetcsv(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + const ph7_io_stream *pStream; + const char *zLine; + io_private *pDev; + ph7_int64 n,nLen; + if( nArg < 1 || !ph7_value_is_resource(apArg[0]) ){ + /* Missing/Invalid arguments,return FALSE */ + ph7_context_throw_error(pCtx,PH7_CTX_WARNING,"Expecting an IO handle"); + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Extract our private data */ + pDev = (io_private *)ph7_value_to_resource(apArg[0]); + /* Make sure we are dealing with a valid io_private instance */ + if( IO_PRIVATE_INVALID(pDev) ){ + /*Expecting an IO handle */ + ph7_context_throw_error(pCtx,PH7_CTX_WARNING,"Expecting an IO handle"); + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Point to the target IO stream device */ + pStream = pDev->pStream; + if( pStream == 0 ){ + ph7_context_throw_error_format(pCtx,PH7_CTX_WARNING, + "IO routine(%s) not implemented in the underlying stream(%s) device,PH7 is returning FALSE", + ph7_function_name(pCtx),pStream ? pStream->zName : "null_stream" + ); + ph7_result_bool(pCtx,0); + return PH7_OK; + } + nLen = -1; + if( nArg > 1 ){ + /* Maximum data to read */ + nLen = ph7_value_to_int64(apArg[1]); + } + /* Perform the requested operation */ + n = StreamReadLine(pDev,&zLine,nLen); + if( n < 1 ){ + /* EOF or IO error,return FALSE */ + ph7_result_bool(pCtx,0); + }else{ + ph7_value *pArray; + int delim = ','; /* Delimiter */ + int encl = '"' ; /* Enclosure */ + int escape = '\\'; /* Escape character */ + if( nArg > 2 ){ + const char *zPtr; + int i; + if( ph7_value_is_string(apArg[2]) ){ + /* Extract the delimiter */ + zPtr = ph7_value_to_string(apArg[2],&i); + if( i > 0 ){ + delim = zPtr[0]; + } + } + if( nArg > 3 ){ + if( ph7_value_is_string(apArg[3]) ){ + /* Extract the enclosure */ + zPtr = ph7_value_to_string(apArg[3],&i); + if( i > 0 ){ + encl = zPtr[0]; + } + } + if( nArg > 4 ){ + if( ph7_value_is_string(apArg[4]) ){ + /* Extract the escape character */ + zPtr = ph7_value_to_string(apArg[4],&i); + if( i > 0 ){ + escape = zPtr[0]; + } + } + } + } + } + /* Create our array */ + pArray = ph7_context_new_array(pCtx); + if( pArray == 0 ){ + ph7_context_throw_error(pCtx,PH7_CTX_ERR,"PH7 is running out of memory"); + ph7_result_null(pCtx); + return PH7_OK; + } + /* Parse the raw input */ + PH7_ProcessCsv(zLine,(int)n,delim,encl,escape,PH7_CsvConsumer,pArray); + /* Return the freshly created array */ + ph7_result_value(pCtx,pArray); + } + return PH7_OK; +} +/* + * string fgetss(resource $handle [,int $length [,string $allowable_tags ]]) + * Gets line from file pointer and strip HTML tags. + * Parameters + * $handle + * The file pointer. + * $length + * Reading ends when length - 1 bytes have been read, on a newline + * (which is included in the return value), or on EOF (whichever comes first). + * If no length is specified, it will keep reading from the stream until it reaches + * the end of the line. + * $allowable_tags + * You can use the optional second parameter to specify tags which should not be stripped. + * Return + * Returns a string of up to length - 1 bytes read from the file pointed to by + * handle, with all HTML and PHP code stripped. If an error occurs, returns FALSE. + */ +static int PH7_builtin_fgetss(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + const ph7_io_stream *pStream; + const char *zLine; + io_private *pDev; + ph7_int64 n,nLen; + if( nArg < 1 || !ph7_value_is_resource(apArg[0]) ){ + /* Missing/Invalid arguments,return FALSE */ + ph7_context_throw_error(pCtx,PH7_CTX_WARNING,"Expecting an IO handle"); + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Extract our private data */ + pDev = (io_private *)ph7_value_to_resource(apArg[0]); + /* Make sure we are dealing with a valid io_private instance */ + if( IO_PRIVATE_INVALID(pDev) ){ + /*Expecting an IO handle */ + ph7_context_throw_error(pCtx,PH7_CTX_WARNING,"Expecting an IO handle"); + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Point to the target IO stream device */ + pStream = pDev->pStream; + if( pStream == 0 ){ + ph7_context_throw_error_format(pCtx,PH7_CTX_WARNING, + "IO routine(%s) not implemented in the underlying stream(%s) device,PH7 is returning FALSE", + ph7_function_name(pCtx),pStream ? pStream->zName : "null_stream" + ); + ph7_result_bool(pCtx,0); + return PH7_OK; + } + nLen = -1; + if( nArg > 1 ){ + /* Maximum data to read */ + nLen = ph7_value_to_int64(apArg[1]); + } + /* Perform the requested operation */ + n = StreamReadLine(pDev,&zLine,nLen); + if( n < 1 ){ + /* EOF or IO error,return FALSE */ + ph7_result_bool(pCtx,0); + }else{ + const char *zTaglist = 0; + int nTaglen = 0; + if( nArg > 2 && ph7_value_is_string(apArg[2]) ){ + /* Allowed tag */ + zTaglist = ph7_value_to_string(apArg[2],&nTaglen); + } + /* Process data just read */ + PH7_StripTagsFromString(pCtx,zLine,(int)n,zTaglist,nTaglen); + } + return PH7_OK; +} +/* + * string readdir(resource $dir_handle) + * Read entry from directory handle. + * Parameter + * $dir_handle + * The directory handle resource previously opened with opendir(). + * Return + * Returns the filename on success or FALSE on failure. + */ +static int PH7_builtin_readdir(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + const ph7_io_stream *pStream; + io_private *pDev; + int rc; + if( nArg < 1 || !ph7_value_is_resource(apArg[0]) ){ + /* Missing/Invalid arguments,return FALSE */ + ph7_context_throw_error(pCtx,PH7_CTX_WARNING,"Expecting an IO handle"); + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Extract our private data */ + pDev = (io_private *)ph7_value_to_resource(apArg[0]); + /* Make sure we are dealing with a valid io_private instance */ + if( IO_PRIVATE_INVALID(pDev) ){ + /*Expecting an IO handle */ + ph7_context_throw_error(pCtx,PH7_CTX_WARNING,"Expecting an IO handle"); + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Point to the target IO stream device */ + pStream = pDev->pStream; + if( pStream == 0 || pStream->xReadDir == 0 ){ + ph7_context_throw_error_format(pCtx,PH7_CTX_WARNING, + "IO routine(%s) not implemented in the underlying stream(%s) device,PH7 is returning FALSE", + ph7_function_name(pCtx),pStream ? pStream->zName : "null_stream" + ); + ph7_result_bool(pCtx,0); + return PH7_OK; + } + ph7_result_bool(pCtx,0); + /* Perform the requested operation */ + rc = pStream->xReadDir(pDev->pHandle,pCtx); + if( rc != PH7_OK ){ + /* Return FALSE */ + ph7_result_bool(pCtx,0); + } + return PH7_OK; +} +/* + * void rewinddir(resource $dir_handle) + * Rewind directory handle. + * Parameter + * $dir_handle + * The directory handle resource previously opened with opendir(). + * Return + * FALSE on failure. + */ +static int PH7_builtin_rewinddir(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + const ph7_io_stream *pStream; + io_private *pDev; + if( nArg < 1 || !ph7_value_is_resource(apArg[0]) ){ + /* Missing/Invalid arguments,return FALSE */ + ph7_context_throw_error(pCtx,PH7_CTX_WARNING,"Expecting an IO handle"); + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Extract our private data */ + pDev = (io_private *)ph7_value_to_resource(apArg[0]); + /* Make sure we are dealing with a valid io_private instance */ + if( IO_PRIVATE_INVALID(pDev) ){ + /*Expecting an IO handle */ + ph7_context_throw_error(pCtx,PH7_CTX_WARNING,"Expecting an IO handle"); + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Point to the target IO stream device */ + pStream = pDev->pStream; + if( pStream == 0 || pStream->xRewindDir == 0 ){ + ph7_context_throw_error_format(pCtx,PH7_CTX_WARNING, + "IO routine(%s) not implemented in the underlying stream(%s) device,PH7 is returning FALSE", + ph7_function_name(pCtx),pStream ? pStream->zName : "null_stream" + ); + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Perform the requested operation */ + pStream->xRewindDir(pDev->pHandle); + return PH7_OK; + } +/* Forward declaration */ +static void InitIOPrivate(ph7_vm *pVm,const ph7_io_stream *pStream,io_private *pOut); +static void ReleaseIOPrivate(ph7_context *pCtx,io_private *pDev); +/* + * void closedir(resource $dir_handle) + * Close directory handle. + * Parameter + * $dir_handle + * The directory handle resource previously opened with opendir(). + * Return + * FALSE on failure. + */ +static int PH7_builtin_closedir(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + const ph7_io_stream *pStream; + io_private *pDev; + if( nArg < 1 || !ph7_value_is_resource(apArg[0]) ){ + /* Missing/Invalid arguments,return FALSE */ + ph7_context_throw_error(pCtx,PH7_CTX_WARNING,"Expecting an IO handle"); + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Extract our private data */ + pDev = (io_private *)ph7_value_to_resource(apArg[0]); + /* Make sure we are dealing with a valid io_private instance */ + if( IO_PRIVATE_INVALID(pDev) ){ + /*Expecting an IO handle */ + ph7_context_throw_error(pCtx,PH7_CTX_WARNING,"Expecting an IO handle"); + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Point to the target IO stream device */ + pStream = pDev->pStream; + if( pStream == 0 || pStream->xCloseDir == 0 ){ + ph7_context_throw_error_format(pCtx,PH7_CTX_WARNING, + "IO routine(%s) not implemented in the underlying stream(%s) device,PH7 is returning FALSE", + ph7_function_name(pCtx),pStream ? pStream->zName : "null_stream" + ); + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Perform the requested operation */ + pStream->xCloseDir(pDev->pHandle); + /* Release the private stucture */ + ReleaseIOPrivate(pCtx,pDev); + PH7_MemObjRelease(apArg[0]); + return PH7_OK; + } +/* + * resource opendir(string $path[,resource $context]) + * Open directory handle. + * Parameters + * $path + * The directory path that is to be opened. + * $context + * A context stream resource. + * Return + * A directory handle resource on success,or FALSE on failure. + */ +static int PH7_builtin_opendir(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + const ph7_io_stream *pStream; + const char *zPath; + io_private *pDev; + int iLen,rc; + if( nArg < 1 || !ph7_value_is_string(apArg[0]) ){ + /* Missing/Invalid arguments,return FALSE */ + ph7_context_throw_error(pCtx,PH7_CTX_WARNING,"Expecting a directory path"); + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Extract the target path */ + zPath = ph7_value_to_string(apArg[0],&iLen); + /* Try to extract a stream */ + pStream = PH7_VmGetStreamDevice(pCtx->pVm,&zPath,iLen); + if( pStream == 0 ){ + ph7_context_throw_error_format(pCtx,PH7_CTX_WARNING, + "No stream device is associated with the given path(%s)",zPath); + ph7_result_bool(pCtx,0); + return PH7_OK; + } + if( pStream->xOpenDir == 0 ){ + ph7_context_throw_error_format(pCtx,PH7_CTX_WARNING, + "IO routine(%s) not implemented in the underlying stream(%s) device", + ph7_function_name(pCtx),pStream->zName + ); + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Allocate a new IO private instance */ + pDev = (io_private *)ph7_context_alloc_chunk(pCtx,sizeof(io_private),TRUE,FALSE); + if( pDev == 0 ){ + ph7_context_throw_error(pCtx,PH7_CTX_ERR,"PH7 is running out of memory"); + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Initialize the structure */ + InitIOPrivate(pCtx->pVm,pStream,pDev); + /* Open the target directory */ + rc = pStream->xOpenDir(zPath,nArg > 1 ? apArg[1] : 0,&pDev->pHandle); + if( rc != PH7_OK ){ + /* IO error,return FALSE */ + ReleaseIOPrivate(pCtx,pDev); + ph7_result_bool(pCtx,0); + }else{ + /* Return the handle as a resource */ + ph7_result_resource(pCtx,pDev); + } + return PH7_OK; +} +/* + * int readfile(string $filename[,bool $use_include_path = false [,resource $context ]]) + * Reads a file and writes it to the output buffer. + * Parameters + * $filename + * The filename being read. + * $use_include_path + * You can use the optional second parameter and set it to + * TRUE, if you want to search for the file in the include_path, too. + * $context + * A context stream resource. + * Return + * The number of bytes read from the file on success or FALSE on failure. + */ +static int PH7_builtin_readfile(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + int use_include = FALSE; + const ph7_io_stream *pStream; + ph7_int64 n,nRead; + const char *zFile; + char zBuf[8192]; + void *pHandle; + int rc,nLen; + if( nArg < 1 || !ph7_value_is_string(apArg[0]) ){ + /* Missing/Invalid arguments,return FALSE */ + ph7_context_throw_error(pCtx,PH7_CTX_WARNING,"Expecting a file path"); + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Extract the file path */ + zFile = ph7_value_to_string(apArg[0],&nLen); + /* Point to the target IO stream device */ + pStream = PH7_VmGetStreamDevice(pCtx->pVm,&zFile,nLen); + if( pStream == 0 ){ + ph7_context_throw_error(pCtx,PH7_CTX_WARNING,"No such stream device,PH7 is returning FALSE"); + ph7_result_bool(pCtx,0); + return PH7_OK; + } + if( nArg > 1 ){ + use_include = ph7_value_to_bool(apArg[1]); + } + /* Try to open the file in read-only mode */ + pHandle = PH7_StreamOpenHandle(pCtx->pVm,pStream,zFile,PH7_IO_OPEN_RDONLY, + use_include,nArg > 2 ? apArg[2] : 0,FALSE,0); + if( pHandle == 0 ){ + ph7_context_throw_error_format(pCtx,PH7_CTX_ERR,"IO error while opening '%s'",zFile); + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Perform the requested operation */ + nRead = 0; + for(;;){ + n = pStream->xRead(pHandle,zBuf,sizeof(zBuf)); + if( n < 1 ){ + /* EOF or IO error,break immediately */ + break; + } + /* Output data */ + rc = ph7_context_output(pCtx,zBuf,(int)n); + if( rc == PH7_ABORT ){ + break; + } + /* Increment counter */ + nRead += n; + } + /* Close the stream */ + PH7_StreamCloseHandle(pStream,pHandle); + /* Total number of bytes readen */ + ph7_result_int64(pCtx,nRead); + return PH7_OK; +} +/* + * string file_get_contents(string $filename[,bool $use_include_path = false + * [, resource $context [, int $offset = -1 [, int $maxlen ]]]]) + * Reads entire file into a string. + * Parameters + * $filename + * The filename being read. + * $use_include_path + * You can use the optional second parameter and set it to + * TRUE, if you want to search for the file in the include_path, too. + * $context + * A context stream resource. + * $offset + * The offset where the reading starts on the original stream. + * $maxlen + * Maximum length of data read. The default is to read until end of file + * is reached. Note that this parameter is applied to the stream processed by the filters. + * Return + * The function returns the read data or FALSE on failure. + */ +static int PH7_builtin_file_get_contents(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + const ph7_io_stream *pStream; + ph7_int64 n,nRead,nMaxlen; + int use_include = FALSE; + const char *zFile; + char zBuf[8192]; + void *pHandle; + int nLen; + + if( nArg < 1 || !ph7_value_is_string(apArg[0]) ){ + /* Missing/Invalid arguments,return FALSE */ + ph7_context_throw_error(pCtx,PH7_CTX_WARNING,"Expecting a file path"); + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Extract the file path */ + zFile = ph7_value_to_string(apArg[0],&nLen); + /* Point to the target IO stream device */ + pStream = PH7_VmGetStreamDevice(pCtx->pVm,&zFile,nLen); + if( pStream == 0 ){ + ph7_context_throw_error(pCtx,PH7_CTX_WARNING,"No such stream device,PH7 is returning FALSE"); + ph7_result_bool(pCtx,0); + return PH7_OK; + } + nMaxlen = -1; + if( nArg > 1 ){ + use_include = ph7_value_to_bool(apArg[1]); + } + /* Try to open the file in read-only mode */ + pHandle = PH7_StreamOpenHandle(pCtx->pVm,pStream,zFile,PH7_IO_OPEN_RDONLY,use_include,nArg > 2 ? apArg[2] : 0,FALSE,0); + if( pHandle == 0 ){ + ph7_context_throw_error_format(pCtx,PH7_CTX_ERR,"IO error while opening '%s'",zFile); + ph7_result_bool(pCtx,0); + return PH7_OK; + } + if( nArg > 3 ){ + /* Extract the offset */ + n = ph7_value_to_int64(apArg[3]); + if( n > 0 ){ + if( pStream->xSeek ){ + /* Seek to the desired offset */ + pStream->xSeek(pHandle,n,0/*SEEK_SET*/); + } + } + if( nArg > 4 ){ + /* Maximum data to read */ + nMaxlen = ph7_value_to_int64(apArg[4]); + } + } + /* Perform the requested operation */ + nRead = 0; + for(;;){ + n = pStream->xRead(pHandle,zBuf, + (nMaxlen > 0 && (nMaxlen < sizeof(zBuf))) ? nMaxlen : sizeof(zBuf)); + if( n < 1 ){ + /* EOF or IO error,break immediately */ + break; + } + /* Append data */ + ph7_result_string(pCtx,zBuf,(int)n); + /* Increment read counter */ + nRead += n; + if( nMaxlen > 0 && nRead >= nMaxlen ){ + /* Read limit reached */ + break; + } + } + /* Close the stream */ + PH7_StreamCloseHandle(pStream,pHandle); + /* Check if we have read something */ + if( ph7_context_result_buf_length(pCtx) < 1 ){ + /* Nothing read,return FALSE */ + ph7_result_bool(pCtx,0); + } + return PH7_OK; +} +/* + * int file_put_contents(string $filename,mixed $data[,int $flags = 0[,resource $context]]) + * Write a string to a file. + * Parameters + * $filename + * Path to the file where to write the data. + * $data + * The data to write(Must be a string). + * $flags + * The value of flags can be any combination of the following + * flags, joined with the binary OR (|) operator. + * FILE_USE_INCLUDE_PATH Search for filename in the include directory. See include_path for more information. + * FILE_APPEND If file filename already exists, append the data to the file instead of overwriting it. + * LOCK_EX Acquire an exclusive lock on the file while proceeding to the writing. + * context + * A context stream resource. + * Return + * The function returns the number of bytes that were written to the file, or FALSE on failure. + */ +static int PH7_builtin_file_put_contents(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + int use_include = FALSE; + const ph7_io_stream *pStream; + const char *zFile; + const char *zData; + int iOpenFlags; + void *pHandle; + int iFlags; + int nLen; + + if( nArg < 2 || !ph7_value_is_string(apArg[0]) ){ + /* Missing/Invalid arguments,return FALSE */ + ph7_context_throw_error(pCtx,PH7_CTX_WARNING,"Expecting a file path"); + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Extract the file path */ + zFile = ph7_value_to_string(apArg[0],&nLen); + /* Point to the target IO stream device */ + pStream = PH7_VmGetStreamDevice(pCtx->pVm,&zFile,nLen); + if( pStream == 0 ){ + ph7_context_throw_error(pCtx,PH7_CTX_WARNING,"No such stream device,PH7 is returning FALSE"); + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Data to write */ + zData = ph7_value_to_string(apArg[1],&nLen); + if( nLen < 1 ){ + /* Nothing to write,return immediately */ + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Try to open the file in read-write mode */ + iOpenFlags = PH7_IO_OPEN_CREATE|PH7_IO_OPEN_RDWR|PH7_IO_OPEN_TRUNC; + /* Extract the flags */ + iFlags = 0; + if( nArg > 2 ){ + iFlags = ph7_value_to_int(apArg[2]); + if( iFlags & 0x01 /*FILE_USE_INCLUDE_PATH*/){ + use_include = TRUE; + } + if( iFlags & 0x08 /* FILE_APPEND */){ + /* If the file already exists, append the data to the file + * instead of overwriting it. + */ + iOpenFlags &= ~PH7_IO_OPEN_TRUNC; + /* Append mode */ + iOpenFlags |= PH7_IO_OPEN_APPEND; + } + } + pHandle = PH7_StreamOpenHandle(pCtx->pVm,pStream,zFile,iOpenFlags,use_include, + nArg > 3 ? apArg[3] : 0,FALSE,FALSE); + if( pHandle == 0 ){ + ph7_context_throw_error_format(pCtx,PH7_CTX_ERR,"IO error while opening '%s'",zFile); + ph7_result_bool(pCtx,0); + return PH7_OK; + } + if( pStream->xWrite ){ + ph7_int64 n; + if( (iFlags & 0x01/* LOCK_EX */) && pStream->xLock ){ + /* Try to acquire an exclusive lock */ + pStream->xLock(pHandle,1/* LOCK_EX */); + } + /* Perform the write operation */ + n = pStream->xWrite(pHandle,(const void *)zData,nLen); + if( n < 1 ){ + /* IO error,return FALSE */ + ph7_result_bool(pCtx,0); + }else{ + /* Total number of bytes written */ + ph7_result_int64(pCtx,n); + } + }else{ + /* Read-only stream */ + ph7_context_throw_error_format(pCtx,PH7_CTX_ERR, + "Read-only stream(%s): Cannot perform write operation", + pStream ? pStream->zName : "null_stream" + ); + ph7_result_bool(pCtx,0); + } + /* Close the handle */ + PH7_StreamCloseHandle(pStream,pHandle); + return PH7_OK; +} +/* + * array file(string $filename[,int $flags = 0[,resource $context]]) + * Reads entire file into an array. + * Parameters + * $filename + * The filename being read. + * $flags + * The optional parameter flags can be one, or more, of the following constants: + * FILE_USE_INCLUDE_PATH + * Search for the file in the include_path. + * FILE_IGNORE_NEW_LINES + * Do not add newline at the end of each array element + * FILE_SKIP_EMPTY_LINES + * Skip empty lines + * $context + * A context stream resource. + * Return + * The function returns the read data or FALSE on failure. + */ +static int PH7_builtin_file(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + const char *zFile,*zPtr,*zEnd,*zBuf; + ph7_value *pArray,*pLine; + const ph7_io_stream *pStream; + int use_include = 0; + io_private *pDev; + ph7_int64 n; + int iFlags; + int nLen; + + if( nArg < 1 || !ph7_value_is_string(apArg[0]) ){ + /* Missing/Invalid arguments,return FALSE */ + ph7_context_throw_error(pCtx,PH7_CTX_WARNING,"Expecting a file path"); + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Extract the file path */ + zFile = ph7_value_to_string(apArg[0],&nLen); + /* Point to the target IO stream device */ + pStream = PH7_VmGetStreamDevice(pCtx->pVm,&zFile,nLen); + if( pStream == 0 ){ + ph7_context_throw_error(pCtx,PH7_CTX_WARNING,"No such stream device,PH7 is returning FALSE"); + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Allocate a new IO private instance */ + pDev = (io_private *)ph7_context_alloc_chunk(pCtx,sizeof(io_private),TRUE,FALSE); + if( pDev == 0 ){ + ph7_context_throw_error(pCtx,PH7_CTX_ERR,"PH7 is running out of memory"); + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Initialize the structure */ + InitIOPrivate(pCtx->pVm,pStream,pDev); + iFlags = 0; + if( nArg > 1 ){ + iFlags = ph7_value_to_int(apArg[1]); + } + if( iFlags & 0x01 /*FILE_USE_INCLUDE_PATH*/ ){ + use_include = TRUE; + } + /* Create the array and the working value */ + pArray = ph7_context_new_array(pCtx); + pLine = ph7_context_new_scalar(pCtx); + if( pArray == 0 || pLine == 0 ){ + ph7_context_throw_error(pCtx,PH7_CTX_ERR,"PH7 is running out of memory"); + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Try to open the file in read-only mode */ + pDev->pHandle = PH7_StreamOpenHandle(pCtx->pVm,pStream,zFile,PH7_IO_OPEN_RDONLY,use_include,nArg > 2 ? apArg[2] : 0,FALSE,0); + if( pDev->pHandle == 0 ){ + ph7_context_throw_error_format(pCtx,PH7_CTX_ERR,"IO error while opening '%s'",zFile); + ph7_result_bool(pCtx,0); + /* Don't worry about freeing memory, everything will be released automatically + * as soon we return from this function. + */ + return PH7_OK; + } + /* Perform the requested operation */ + for(;;){ + /* Try to extract a line */ + n = StreamReadLine(pDev,&zBuf,-1); + if( n < 1 ){ + /* EOF or IO error */ + break; + } + /* Reset the cursor */ + ph7_value_reset_string_cursor(pLine); + /* Remove line ending if requested by the caller */ + zPtr = zBuf; + zEnd = &zBuf[n]; + if( iFlags & 0x02 /* FILE_IGNORE_NEW_LINES */ ){ + /* Ignore trailig lines */ + while( zPtr < zEnd && (zEnd[-1] == '\n' +#ifdef __WINNT__ + || zEnd[-1] == '\r' +#endif + )){ + n--; + zEnd--; + } + } + if( iFlags & 0x04 /* FILE_SKIP_EMPTY_LINES */ ){ + /* Ignore empty lines */ + while( zPtr < zEnd && (unsigned char)zPtr[0] < 0xc0 && SyisSpace(zPtr[0]) ){ + zPtr++; + } + if( zPtr >= zEnd ){ + /* Empty line */ + continue; + } + } + ph7_value_string(pLine,zBuf,(int)(zEnd-zBuf)); + /* Insert line */ + ph7_array_add_elem(pArray,0/* Automatic index assign*/,pLine); + } + /* Close the stream */ + PH7_StreamCloseHandle(pStream,pDev->pHandle); + /* Release the io_private instance */ + ReleaseIOPrivate(pCtx,pDev); + /* Return the created array */ + ph7_result_value(pCtx,pArray); + return PH7_OK; +} +/* + * bool copy(string $source,string $dest[,resource $context ] ) + * Makes a copy of the file source to dest. + * Parameters + * $source + * Path to the source file. + * $dest + * The destination path. If dest is a URL, the copy operation + * may fail if the wrapper does not support overwriting of existing files. + * $context + * A context stream resource. + * Return + * TRUE on success or FALSE on failure. + */ +static int PH7_builtin_copy(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + const ph7_io_stream *pSin,*pSout; + const char *zFile; + char zBuf[8192]; + void *pIn,*pOut; + ph7_int64 n; + int nLen; + if( nArg < 2 || !ph7_value_is_string(apArg[0]) || !ph7_value_is_string(apArg[1])){ + /* Missing/Invalid arguments,return FALSE */ + ph7_context_throw_error(pCtx,PH7_CTX_WARNING,"Expecting a source and a destination path"); + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Extract the source name */ + zFile = ph7_value_to_string(apArg[0],&nLen); + /* Point to the target IO stream device */ + pSin = PH7_VmGetStreamDevice(pCtx->pVm,&zFile,nLen); + if( pSin == 0 ){ + ph7_context_throw_error(pCtx,PH7_CTX_WARNING,"No such stream device,PH7 is returning FALSE"); + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Try to open the source file in a read-only mode */ + pIn = PH7_StreamOpenHandle(pCtx->pVm,pSin,zFile,PH7_IO_OPEN_RDONLY,FALSE,nArg > 2 ? apArg[2] : 0,FALSE,0); + if( pIn == 0 ){ + ph7_context_throw_error_format(pCtx,PH7_CTX_ERR,"IO error while opening source: '%s'",zFile); + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Extract the destination name */ + zFile = ph7_value_to_string(apArg[1],&nLen); + /* Point to the target IO stream device */ + pSout = PH7_VmGetStreamDevice(pCtx->pVm,&zFile,nLen); + if( pSout == 0 ){ + ph7_context_throw_error(pCtx,PH7_CTX_WARNING,"No such stream device,PH7 is returning FALSE"); + ph7_result_bool(pCtx,0); + PH7_StreamCloseHandle(pSin,pIn); + return PH7_OK; + } + if( pSout->xWrite == 0 ){ + ph7_context_throw_error_format(pCtx,PH7_CTX_WARNING, + "IO routine(%s) not implemented in the underlying stream(%s) device,PH7 is returning FALSE", + ph7_function_name(pCtx),pSin->zName + ); + ph7_result_bool(pCtx,0); + PH7_StreamCloseHandle(pSin,pIn); + return PH7_OK; + } + /* Try to open the destination file in a read-write mode */ + pOut = PH7_StreamOpenHandle(pCtx->pVm,pSout,zFile, + PH7_IO_OPEN_CREATE|PH7_IO_OPEN_TRUNC|PH7_IO_OPEN_RDWR,FALSE,nArg > 2 ? apArg[2] : 0,FALSE,0); + if( pOut == 0 ){ + ph7_context_throw_error_format(pCtx,PH7_CTX_ERR,"IO error while opening destination: '%s'",zFile); + ph7_result_bool(pCtx,0); + PH7_StreamCloseHandle(pSin,pIn); + return PH7_OK; + } + /* Perform the requested operation */ + for(;;){ + /* Read from source */ + n = pSin->xRead(pIn,zBuf,sizeof(zBuf)); + if( n < 1 ){ + /* EOF or IO error,break immediately */ + break; + } + /* Write to dest */ + n = pSout->xWrite(pOut,zBuf,n); + if( n < 1 ){ + /* IO error,break immediately */ + break; + } + } + /* Close the streams */ + PH7_StreamCloseHandle(pSin,pIn); + PH7_StreamCloseHandle(pSout,pOut); + /* Return TRUE */ + ph7_result_bool(pCtx,1); + return PH7_OK; +} +/* + * array fstat(resource $handle) + * Gets information about a file using an open file pointer. + * Parameters + * $handle + * The file pointer. + * Return + * Returns an array with the statistics of the file or FALSE on failure. + */ +static int PH7_builtin_fstat(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + ph7_value *pArray,*pValue; + const ph7_io_stream *pStream; + io_private *pDev; + if( nArg < 1 || !ph7_value_is_resource(apArg[0]) ){ + /* Missing/Invalid arguments,return FALSE */ + ph7_context_throw_error(pCtx,PH7_CTX_WARNING,"Expecting an IO handle"); + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Extract our private data */ + pDev = (io_private *)ph7_value_to_resource(apArg[0]); + /* Make sure we are dealing with a valid io_private instance */ + if( IO_PRIVATE_INVALID(pDev) ){ + /* Expecting an IO handle */ + ph7_context_throw_error(pCtx,PH7_CTX_WARNING,"Expecting an IO handle"); + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Point to the target IO stream device */ + pStream = pDev->pStream; + if( pStream == 0 || pStream->xStat == 0){ + ph7_context_throw_error_format(pCtx,PH7_CTX_WARNING, + "IO routine(%s) not implemented in the underlying stream(%s) device,PH7 is returning FALSE", + ph7_function_name(pCtx),pStream ? pStream->zName : "null_stream" + ); + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Create the array and the working value */ + pArray = ph7_context_new_array(pCtx); + pValue = ph7_context_new_scalar(pCtx); + if( pArray == 0 || pValue == 0 ){ + ph7_context_throw_error(pCtx,PH7_CTX_ERR,"PH7 is running out of memory"); + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Perform the requested operation */ + pStream->xStat(pDev->pHandle,pArray,pValue); + /* Return the freshly created array */ + ph7_result_value(pCtx,pArray); + /* Don't worry about freeing memory here,everything will be + * released automatically as soon we return from this function. + */ + return PH7_OK; +} +/* + * int fwrite(resource $handle,string $string[,int $length]) + * Writes the contents of string to the file stream pointed to by handle. + * Parameters + * $handle + * The file pointer. + * $string + * The string that is to be written. + * $length + * If the length argument is given, writing will stop after length bytes have been written + * or the end of string is reached, whichever comes first. + * Return + * Returns the number of bytes written, or FALSE on error. + */ +static int PH7_builtin_fwrite(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + const ph7_io_stream *pStream; + const char *zString; + io_private *pDev; + int nLen,n; + if( nArg < 2 || !ph7_value_is_resource(apArg[0]) ){ + /* Missing/Invalid arguments,return FALSE */ + ph7_context_throw_error(pCtx,PH7_CTX_WARNING,"Expecting an IO handle"); + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Extract our private data */ + pDev = (io_private *)ph7_value_to_resource(apArg[0]); + /* Make sure we are dealing with a valid io_private instance */ + if( IO_PRIVATE_INVALID(pDev) ){ + /* Expecting an IO handle */ + ph7_context_throw_error(pCtx,PH7_CTX_WARNING,"Expecting an IO handle"); + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Point to the target IO stream device */ + pStream = pDev->pStream; + if( pStream == 0 || pStream->xWrite == 0){ + ph7_context_throw_error_format(pCtx,PH7_CTX_WARNING, + "IO routine(%s) not implemented in the underlying stream(%s) device,PH7 is returning FALSE", + ph7_function_name(pCtx),pStream ? pStream->zName : "null_stream" + ); + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Extract the data to write */ + zString = ph7_value_to_string(apArg[1],&nLen); + if( nArg > 2 ){ + /* Maximum data length to write */ + n = ph7_value_to_int(apArg[2]); + if( n >= 0 && n < nLen ){ + nLen = n; + } + } + if( nLen < 1 ){ + /* Nothing to write */ + ph7_result_int(pCtx,0); + return PH7_OK; + } + /* Perform the requested operation */ + n = (int)pStream->xWrite(pDev->pHandle,(const void *)zString,nLen); + if( n < 0 ){ + /* IO error,return FALSE */ + ph7_result_bool(pCtx,0); + }else{ + /* #Bytes written */ + ph7_result_int(pCtx,n); + } + return PH7_OK; +} +/* + * bool flock(resource $handle,int $operation) + * Portable advisory file locking. + * Parameters + * $handle + * The file pointer. + * $operation + * operation is one of the following: + * LOCK_SH to acquire a shared lock (reader). + * LOCK_EX to acquire an exclusive lock (writer). + * LOCK_UN to release a lock (shared or exclusive). + * Return + * Returns TRUE on success or FALSE on failure. + */ +static int PH7_builtin_flock(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + const ph7_io_stream *pStream; + io_private *pDev; + int nLock; + int rc; + if( nArg < 2 || !ph7_value_is_resource(apArg[0]) ){ + /* Missing/Invalid arguments,return FALSE */ + ph7_context_throw_error(pCtx,PH7_CTX_WARNING,"Expecting an IO handle"); + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Extract our private data */ + pDev = (io_private *)ph7_value_to_resource(apArg[0]); + /* Make sure we are dealing with a valid io_private instance */ + if( IO_PRIVATE_INVALID(pDev) ){ + /*Expecting an IO handle */ + ph7_context_throw_error(pCtx,PH7_CTX_WARNING,"Expecting an IO handle"); + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Point to the target IO stream device */ + pStream = pDev->pStream; + if( pStream == 0 || pStream->xLock == 0){ + ph7_context_throw_error_format(pCtx,PH7_CTX_WARNING, + "IO routine(%s) not implemented in the underlying stream(%s) device,PH7 is returning FALSE", + ph7_function_name(pCtx),pStream ? pStream->zName : "null_stream" + ); + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Requested lock operation */ + nLock = ph7_value_to_int(apArg[1]); + /* Lock operation */ + rc = pStream->xLock(pDev->pHandle,nLock); + /* IO result */ + ph7_result_bool(pCtx,rc == PH7_OK); + return PH7_OK; +} +/* + * int fpassthru(resource $handle) + * Output all remaining data on a file pointer. + * Parameters + * $handle + * The file pointer. + * Return + * Total number of characters read from handle and passed through + * to the output on success or FALSE on failure. + */ +static int PH7_builtin_fpassthru(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + const ph7_io_stream *pStream; + io_private *pDev; + ph7_int64 n,nRead; + char zBuf[8192]; + int rc; + if( nArg < 1 || !ph7_value_is_resource(apArg[0]) ){ + /* Missing/Invalid arguments,return FALSE */ + ph7_context_throw_error(pCtx,PH7_CTX_WARNING,"Expecting an IO handle"); + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Extract our private data */ + pDev = (io_private *)ph7_value_to_resource(apArg[0]); + /* Make sure we are dealing with a valid io_private instance */ + if( IO_PRIVATE_INVALID(pDev) ){ + /*Expecting an IO handle */ + ph7_context_throw_error(pCtx,PH7_CTX_WARNING,"Expecting an IO handle"); + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Point to the target IO stream device */ + pStream = pDev->pStream; + if( pStream == 0 ){ + ph7_context_throw_error_format(pCtx,PH7_CTX_WARNING, + "IO routine(%s) not implemented in the underlying stream(%s) device,PH7 is returning FALSE", + ph7_function_name(pCtx),pStream ? pStream->zName : "null_stream" + ); + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Perform the requested operation */ + nRead = 0; + for(;;){ + n = StreamRead(pDev,zBuf,sizeof(zBuf)); + if( n < 1 ){ + /* Error or EOF */ + break; + } + /* Increment the read counter */ + nRead += n; + /* Output data */ + rc = ph7_context_output(pCtx,zBuf,(int)nRead /* FIXME: 64-bit issues */); + if( rc == PH7_ABORT ){ + /* Consumer callback request an operation abort */ + break; + } + } + /* Total number of bytes readen */ + ph7_result_int64(pCtx,nRead); + return PH7_OK; +} +/* CSV reader/writer private data */ +struct csv_data +{ + int delimiter; /* Delimiter. Default ',' */ + int enclosure; /* Enclosure. Default '"'*/ + io_private *pDev; /* Open stream handle */ + int iCount; /* Counter */ +}; +/* + * The following callback is used by the fputcsv() function inorder to iterate + * throw array entries and output CSV data based on the current key and it's + * associated data. + */ +static int csv_write_callback(ph7_value *pKey,ph7_value *pValue,void *pUserData) +{ + struct csv_data *pData = (struct csv_data *)pUserData; + const char *zData; + int nLen,c2; + sxu32 n; + /* Point to the raw data */ + zData = ph7_value_to_string(pValue,&nLen); + if( nLen < 1 ){ + /* Nothing to write */ + return PH7_OK; + } + if( pData->iCount > 0 ){ + /* Write the delimiter */ + pData->pDev->pStream->xWrite(pData->pDev->pHandle,(const void *)&pData->delimiter,sizeof(char)); + } + n = 1; + c2 = 0; + if( SyByteFind(zData,(sxu32)nLen,pData->delimiter,0) == SXRET_OK || + SyByteFind(zData,(sxu32)nLen,pData->enclosure,&n) == SXRET_OK ){ + c2 = 1; + if( n == 0 ){ + c2 = 2; + } + /* Write the enclosure */ + pData->pDev->pStream->xWrite(pData->pDev->pHandle,(const void *)&pData->enclosure,sizeof(char)); + if( c2 > 1 ){ + pData->pDev->pStream->xWrite(pData->pDev->pHandle,(const void *)&pData->enclosure,sizeof(char)); + } + } + /* Write the data */ + if( pData->pDev->pStream->xWrite(pData->pDev->pHandle,(const void *)zData,(ph7_int64)nLen) < 1 ){ + SXUNUSED(pKey); /* cc warning */ + return PH7_ABORT; + } + if( c2 > 0 ){ + /* Write the enclosure */ + pData->pDev->pStream->xWrite(pData->pDev->pHandle,(const void *)&pData->enclosure,sizeof(char)); + if( c2 > 1 ){ + pData->pDev->pStream->xWrite(pData->pDev->pHandle,(const void *)&pData->enclosure,sizeof(char)); + } + } + pData->iCount++; + return PH7_OK; +} +/* + * int fputcsv(resource $handle,array $fields[,string $delimiter = ','[,string $enclosure = '"' ]]) + * Format line as CSV and write to file pointer. + * Parameters + * $handle + * Open file handle. + * $fields + * An array of values. + * $delimiter + * The optional delimiter parameter sets the field delimiter (one character only). + * $enclosure + * The optional enclosure parameter sets the field enclosure (one character only). + */ +static int PH7_builtin_fputcsv(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + const ph7_io_stream *pStream; + struct csv_data sCsv; + io_private *pDev; + char *zEol; + int eolen; + if( nArg < 2 || !ph7_value_is_resource(apArg[0]) || !ph7_value_is_array(apArg[1]) ){ + /* Missing/Invalid arguments,return FALSE */ + ph7_context_throw_error(pCtx,PH7_CTX_WARNING,"Missing/Invalid arguments"); + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Extract our private data */ + pDev = (io_private *)ph7_value_to_resource(apArg[0]); + /* Make sure we are dealing with a valid io_private instance */ + if( IO_PRIVATE_INVALID(pDev) ){ + /*Expecting an IO handle */ + ph7_context_throw_error(pCtx,PH7_CTX_WARNING,"Expecting an IO handle"); + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Point to the target IO stream device */ + pStream = pDev->pStream; + if( pStream == 0 || pStream->xWrite == 0){ + ph7_context_throw_error_format(pCtx,PH7_CTX_WARNING, + "IO routine(%s) not implemented in the underlying stream(%s) device,PH7 is returning FALSE", + ph7_function_name(pCtx),pStream ? pStream->zName : "null_stream" + ); + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Set default csv separator */ + sCsv.delimiter = ','; + sCsv.enclosure = '"'; + sCsv.pDev = pDev; + sCsv.iCount = 0; + if( nArg > 2 ){ + /* User delimiter */ + const char *z; + int n; + z = ph7_value_to_string(apArg[2],&n); + if( n > 0 ){ + sCsv.delimiter = z[0]; + } + if( nArg > 3 ){ + z = ph7_value_to_string(apArg[3],&n); + if( n > 0 ){ + sCsv.enclosure = z[0]; + } + } + } + /* Iterate throw array entries and write csv data */ + ph7_array_walk(apArg[1],csv_write_callback,&sCsv); + /* Write a line ending */ +#ifdef __WINNT__ + zEol = "\r\n"; + eolen = (int)sizeof("\r\n")-1; +#else + /* Assume UNIX LF */ + zEol = "\n"; + eolen = (int)sizeof(char); +#endif + pDev->pStream->xWrite(pDev->pHandle,(const void *)zEol,eolen); + return PH7_OK; +} +/* + * fprintf,vfprintf private data. + * An instance of the following structure is passed to the formatted + * input consumer callback defined below. + */ +typedef struct fprintf_data fprintf_data; +struct fprintf_data +{ + io_private *pIO; /* IO stream */ + ph7_int64 nCount; /* Total number of bytes written */ +}; +/* + * Callback [i.e: Formatted input consumer] for the fprintf function. + */ +static int fprintfConsumer(ph7_context *pCtx,const char *zInput,int nLen,void *pUserData) +{ + fprintf_data *pFdata = (fprintf_data *)pUserData; + ph7_int64 n; + /* Write the formatted data */ + n = pFdata->pIO->pStream->xWrite(pFdata->pIO->pHandle,(const void *)zInput,nLen); + if( n < 1 ){ + SXUNUSED(pCtx); /* cc warning */ + /* IO error,abort immediately */ + return SXERR_ABORT; + } + /* Increment counter */ + pFdata->nCount += n; + return PH7_OK; +} +/* + * int fprintf(resource $handle,string $format[,mixed $args [, mixed $... ]]) + * Write a formatted string to a stream. + * Parameters + * $handle + * The file pointer. + * $format + * String format (see sprintf()). + * Return + * The length of the written string. + */ +static int PH7_builtin_fprintf(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + fprintf_data sFdata; + const char *zFormat; + io_private *pDev; + int nLen; + if( nArg < 2 || !ph7_value_is_resource(apArg[0]) || !ph7_value_is_string(apArg[1]) ){ + /* Missing/Invalid arguments,return zero */ + ph7_context_throw_error(pCtx,PH7_CTX_WARNING,"Invalid arguments"); + ph7_result_int(pCtx,0); + return PH7_OK; + } + /* Extract our private data */ + pDev = (io_private *)ph7_value_to_resource(apArg[0]); + /* Make sure we are dealing with a valid io_private instance */ + if( IO_PRIVATE_INVALID(pDev) ){ + /*Expecting an IO handle */ + ph7_context_throw_error(pCtx,PH7_CTX_WARNING,"Expecting an IO handle"); + ph7_result_int(pCtx,0); + return PH7_OK; + } + /* Point to the target IO stream device */ + if( pDev->pStream == 0 || pDev->pStream->xWrite == 0 ){ + ph7_context_throw_error_format(pCtx,PH7_CTX_WARNING, + "IO routine(%s) not implemented in the underlying stream(%s) device", + ph7_function_name(pCtx),pDev->pStream ? pDev->pStream->zName : "null_stream" + ); + ph7_result_int(pCtx,0); + return PH7_OK; + } + /* Extract the string format */ + zFormat = ph7_value_to_string(apArg[1],&nLen); + if( nLen < 1 ){ + /* Empty string,return zero */ + ph7_result_int(pCtx,0); + return PH7_OK; + } + /* Prepare our private data */ + sFdata.nCount = 0; + sFdata.pIO = pDev; + /* Format the string */ + PH7_InputFormat(fprintfConsumer,pCtx,zFormat,nLen,nArg - 1,&apArg[1],(void *)&sFdata,FALSE); + /* Return total number of bytes written */ + ph7_result_int64(pCtx,sFdata.nCount); + return PH7_OK; +} +/* + * int vfprintf(resource $handle,string $format,array $args) + * Write a formatted string to a stream. + * Parameters + * $handle + * The file pointer. + * $format + * String format (see sprintf()). + * $args + * User arguments. + * Return + * The length of the written string. + */ +static int PH7_builtin_vfprintf(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + fprintf_data sFdata; + const char *zFormat; + ph7_hashmap *pMap; + io_private *pDev; + SySet sArg; + int n,nLen; + if( nArg < 3 || !ph7_value_is_resource(apArg[0]) || !ph7_value_is_string(apArg[1]) || !ph7_value_is_array(apArg[2]) ){ + /* Missing/Invalid arguments,return zero */ + ph7_context_throw_error(pCtx,PH7_CTX_WARNING,"Invalid arguments"); + ph7_result_int(pCtx,0); + return PH7_OK; + } + /* Extract our private data */ + pDev = (io_private *)ph7_value_to_resource(apArg[0]); + /* Make sure we are dealing with a valid io_private instance */ + if( IO_PRIVATE_INVALID(pDev) ){ + /*Expecting an IO handle */ + ph7_context_throw_error(pCtx,PH7_CTX_WARNING,"Expecting an IO handle"); + ph7_result_int(pCtx,0); + return PH7_OK; + } + /* Point to the target IO stream device */ + if( pDev->pStream == 0 || pDev->pStream->xWrite == 0 ){ + ph7_context_throw_error_format(pCtx,PH7_CTX_WARNING, + "IO routine(%s) not implemented in the underlying stream(%s) device", + ph7_function_name(pCtx),pDev->pStream ? pDev->pStream->zName : "null_stream" + ); + ph7_result_int(pCtx,0); + return PH7_OK; + } + /* Extract the string format */ + zFormat = ph7_value_to_string(apArg[1],&nLen); + if( nLen < 1 ){ + /* Empty string,return zero */ + ph7_result_int(pCtx,0); + return PH7_OK; + } + /* Point to hashmap */ + pMap = (ph7_hashmap *)apArg[2]->x.pOther; + /* Extract arguments from the hashmap */ + n = PH7_HashmapValuesToSet(pMap,&sArg); + /* Prepare our private data */ + sFdata.nCount = 0; + sFdata.pIO = pDev; + /* Format the string */ + PH7_InputFormat(fprintfConsumer,pCtx,zFormat,nLen,n,(ph7_value **)SySetBasePtr(&sArg),(void *)&sFdata,TRUE); + /* Return total number of bytes written*/ + ph7_result_int64(pCtx,sFdata.nCount); + SySetRelease(&sArg); + return PH7_OK; +} +/* + * Convert open modes (string passed to the fopen() function) [i.e: 'r','w+','a',...] into PH7 flags. + * According to the PHP reference manual: + * The mode parameter specifies the type of access you require to the stream. It may be any of the following + * 'r' Open for reading only; place the file pointer at the beginning of the file. + * 'r+' Open for reading and writing; place the file pointer at the beginning of the file. + * 'w' Open for writing only; place the file pointer at the beginning of the file and truncate the file + * to zero length. If the file does not exist, attempt to create it. + * 'w+' Open for reading and writing; place the file pointer at the beginning of the file and truncate + * the file to zero length. If the file does not exist, attempt to create it. + * 'a' Open for writing only; place the file pointer at the end of the file. If the file does not + * exist, attempt to create it. + * 'a+' Open for reading and writing; place the file pointer at the end of the file. If the file does + * not exist, attempt to create it. + * 'x' Create and open for writing only; place the file pointer at the beginning of the file. If the file + * already exists, + * the fopen() call will fail by returning FALSE and generating an error of level E_WARNING. If the file + * does not exist attempt to create it. This is equivalent to specifying O_EXCL|O_CREAT flags for + * the underlying open(2) system call. + * 'x+' Create and open for reading and writing; otherwise it has the same behavior as 'x'. + * 'c' Open the file for writing only. If the file does not exist, it is created. If it exists, it is neither truncated + * (as opposed to 'w'), nor the call to this function fails (as is the case with 'x'). The file pointer + * is positioned on the beginning of the file. + * This may be useful if it's desired to get an advisory lock (see flock()) before attempting to modify the file + * as using 'w' could truncate the file before the lock was obtained (if truncation is desired, ftruncate() can + * be used after the lock is requested). + * 'c+' Open the file for reading and writing; otherwise it has the same behavior as 'c'. + */ +static int StrModeToFlags(ph7_context *pCtx,const char *zMode,int nLen) +{ + const char *zEnd = &zMode[nLen]; + int iFlag = 0; + int c; + if( nLen < 1 ){ + /* Open in a read-only mode */ + return PH7_IO_OPEN_RDONLY; + } + c = zMode[0]; + if( c == 'r' || c == 'R' ){ + /* Read-only access */ + iFlag = PH7_IO_OPEN_RDONLY; + zMode++; /* Advance */ + if( zMode < zEnd ){ + c = zMode[0]; + if( c == '+' || c == 'w' || c == 'W' ){ + /* Read+Write access */ + iFlag = PH7_IO_OPEN_RDWR; + } + } + }else if( c == 'w' || c == 'W' ){ + /* Overwrite mode. + * If the file does not exists,try to create it + */ + iFlag = PH7_IO_OPEN_WRONLY|PH7_IO_OPEN_TRUNC|PH7_IO_OPEN_CREATE; + zMode++; /* Advance */ + if( zMode < zEnd ){ + c = zMode[0]; + if( c == '+' || c == 'r' || c == 'R' ){ + /* Read+Write access */ + iFlag &= ~PH7_IO_OPEN_WRONLY; + iFlag |= PH7_IO_OPEN_RDWR; + } + } + }else if( c == 'a' || c == 'A' ){ + /* Append mode (place the file pointer at the end of the file). + * Create the file if it does not exists. + */ + iFlag = PH7_IO_OPEN_WRONLY|PH7_IO_OPEN_APPEND|PH7_IO_OPEN_CREATE; + zMode++; /* Advance */ + if( zMode < zEnd ){ + c = zMode[0]; + if( c == '+' ){ + /* Read-Write access */ + iFlag &= ~PH7_IO_OPEN_WRONLY; + iFlag |= PH7_IO_OPEN_RDWR; + } + } + }else if( c == 'x' || c == 'X' ){ + /* Exclusive access. + * If the file already exists,return immediately with a failure code. + * Otherwise create a new file. + */ + iFlag = PH7_IO_OPEN_WRONLY|PH7_IO_OPEN_EXCL; + zMode++; /* Advance */ + if( zMode < zEnd ){ + c = zMode[0]; + if( c == '+' || c == 'r' || c == 'R' ){ + /* Read-Write access */ + iFlag &= ~PH7_IO_OPEN_WRONLY; + iFlag |= PH7_IO_OPEN_RDWR; + } + } + }else if( c == 'c' || c == 'C' ){ + /* Overwrite mode.Create the file if it does not exists.*/ + iFlag = PH7_IO_OPEN_WRONLY|PH7_IO_OPEN_CREATE; + zMode++; /* Advance */ + if( zMode < zEnd ){ + c = zMode[0]; + if( c == '+' ){ + /* Read-Write access */ + iFlag &= ~PH7_IO_OPEN_WRONLY; + iFlag |= PH7_IO_OPEN_RDWR; + } + } + }else{ + /* Invalid mode. Assume a read only open */ + ph7_context_throw_error(pCtx,PH7_CTX_NOTICE,"Invalid open mode,PH7 is assuming a Read-Only open"); + iFlag = PH7_IO_OPEN_RDONLY; + } + while( zMode < zEnd ){ + c = zMode[0]; + if( c == 'b' || c == 'B' ){ + iFlag &= ~PH7_IO_OPEN_TEXT; + iFlag |= PH7_IO_OPEN_BINARY; + }else if( c == 't' || c == 'T' ){ + iFlag &= ~PH7_IO_OPEN_BINARY; + iFlag |= PH7_IO_OPEN_TEXT; + } + zMode++; + } + return iFlag; +} +/* + * Initialize the IO private structure. + */ +static void InitIOPrivate(ph7_vm *pVm,const ph7_io_stream *pStream,io_private *pOut) +{ + pOut->pStream = pStream; + SyBlobInit(&pOut->sBuffer,&pVm->sAllocator); + pOut->nOfft = 0; + /* Set the magic number */ + pOut->iMagic = IO_PRIVATE_MAGIC; +} +/* + * Release the IO private structure. + */ +static void ReleaseIOPrivate(ph7_context *pCtx,io_private *pDev) +{ + SyBlobRelease(&pDev->sBuffer); + pDev->iMagic = 0x2126; /* Invalid magic number so we can detetct misuse */ + /* Release the whole structure */ + ph7_context_free_chunk(pCtx,pDev); +} +/* + * Reset the IO private structure. + */ +static void ResetIOPrivate(io_private *pDev) +{ + SyBlobReset(&pDev->sBuffer); + pDev->nOfft = 0; +} +/* Forward declaration */ +static int is_php_stream(const ph7_io_stream *pStream); +/* + * resource fopen(string $filename,string $mode [,bool $use_include_path = false[,resource $context ]]) + * Open a file,a URL or any other IO stream. + * Parameters + * $filename + * If filename is of the form "scheme://...", it is assumed to be a URL and PHP will search + * for a protocol handler (also known as a wrapper) for that scheme. If no scheme is given + * then a regular file is assumed. + * $mode + * The mode parameter specifies the type of access you require to the stream + * See the block comment associated with the StrModeToFlags() for the supported + * modes. + * $use_include_path + * You can use the optional second parameter and set it to + * TRUE, if you want to search for the file in the include_path, too. + * $context + * A context stream resource. + * Return + * File handle on success or FALSE on failure. + */ +static int PH7_builtin_fopen(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + const ph7_io_stream *pStream; + const char *zUri,*zMode; + ph7_value *pResource; + io_private *pDev; + int iLen,imLen; + int iOpenFlags; + if( nArg < 1 || !ph7_value_is_string(apArg[0]) ){ + /* Missing/Invalid arguments,return FALSE */ + ph7_context_throw_error(pCtx,PH7_CTX_WARNING,"Expecting a file path or URL"); + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Extract the URI and the desired access mode */ + zUri = ph7_value_to_string(apArg[0],&iLen); + if( nArg > 1 ){ + zMode = ph7_value_to_string(apArg[1],&imLen); + }else{ + /* Set a default read-only mode */ + zMode = "r"; + imLen = (int)sizeof(char); + } + /* Try to extract a stream */ + pStream = PH7_VmGetStreamDevice(pCtx->pVm,&zUri,iLen); + if( pStream == 0 ){ + ph7_context_throw_error_format(pCtx,PH7_CTX_WARNING, + "No stream device is associated with the given URI(%s)",zUri); + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Allocate a new IO private instance */ + pDev = (io_private *)ph7_context_alloc_chunk(pCtx,sizeof(io_private),TRUE,FALSE); + if( pDev == 0 ){ + ph7_context_throw_error(pCtx,PH7_CTX_ERR,"PH7 is running out of memory"); + ph7_result_bool(pCtx,0); + return PH7_OK; + } + pResource = 0; + if( nArg > 3 ){ + pResource = apArg[3]; + }else if( is_php_stream(pStream) ){ + /* TICKET 1433-80: The php:// stream need a ph7_value to access the underlying + * virtual machine. + */ + pResource = apArg[0]; + } + /* Initialize the structure */ + InitIOPrivate(pCtx->pVm,pStream,pDev); + /* Convert open mode to PH7 flags */ + iOpenFlags = StrModeToFlags(pCtx,zMode,imLen); + /* Try to get a handle */ + pDev->pHandle = PH7_StreamOpenHandle(pCtx->pVm,pStream,zUri,iOpenFlags, + nArg > 2 ? ph7_value_to_bool(apArg[2]) : FALSE,pResource,FALSE,0); + if( pDev->pHandle == 0 ){ + ph7_context_throw_error_format(pCtx,PH7_CTX_ERR,"IO error while opening '%s'",zUri); + ph7_result_bool(pCtx,0); + ph7_context_free_chunk(pCtx,pDev); + return PH7_OK; + } + /* All done,return the io_private instance as a resource */ + ph7_result_resource(pCtx,pDev); + return PH7_OK; +} +/* + * bool fclose(resource $handle) + * Closes an open file pointer + * Parameters + * $handle + * The file pointer. + * Return + * TRUE on success or FALSE on failure. + */ +static int PH7_builtin_fclose(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + const ph7_io_stream *pStream; + io_private *pDev; + ph7_vm *pVm; + if( nArg < 1 || !ph7_value_is_resource(apArg[0]) ){ + /* Missing/Invalid arguments,return FALSE */ + ph7_context_throw_error(pCtx,PH7_CTX_WARNING,"Expecting an IO handle"); + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Extract our private data */ + pDev = (io_private *)ph7_value_to_resource(apArg[0]); + /* Make sure we are dealing with a valid io_private instance */ + if( IO_PRIVATE_INVALID(pDev) ){ + /*Expecting an IO handle */ + ph7_context_throw_error(pCtx,PH7_CTX_WARNING,"Expecting an IO handle"); + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Point to the target IO stream device */ + pStream = pDev->pStream; + if( pStream == 0 ){ + ph7_context_throw_error_format(pCtx,PH7_CTX_WARNING, + "IO routine(%s) not implemented in the underlying stream(%s) device,PH7 is returning FALSE", + ph7_function_name(pCtx),pStream ? pStream->zName : "null_stream" + ); + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Point to the VM that own this context */ + pVm = pCtx->pVm; + /* TICKET 1433-62: Keep the STDIN/STDOUT/STDERR handles open */ + if( pDev != pVm->pStdin && pDev != pVm->pStdout && pDev != pVm->pStderr ){ + /* Perform the requested operation */ + PH7_StreamCloseHandle(pStream,pDev->pHandle); + /* Release the IO private structure */ + ReleaseIOPrivate(pCtx,pDev); + /* Invalidate the resource handle */ + ph7_value_release(apArg[0]); + } + /* Return TRUE */ + ph7_result_bool(pCtx,1); + return PH7_OK; +} +#if !defined(PH7_DISABLE_HASH_FUNC) +/* + * MD5/SHA1 digest consumer. + */ +static int vfsHashConsumer(const void *pData,unsigned int nLen,void *pUserData) +{ + /* Append hex chunk verbatim */ + ph7_result_string((ph7_context *)pUserData,(const char *)pData,(int)nLen); + return SXRET_OK; +} +/* + * string md5_file(string $uri[,bool $raw_output = false ]) + * Calculates the md5 hash of a given file. + * Parameters + * $uri + * Target URI (file(/path/to/something) or URL(http://www.symisc.net/)) + * $raw_output + * When TRUE, returns the digest in raw binary format with a length of 16. + * Return + * Return the MD5 digest on success or FALSE on failure. + */ +static int PH7_builtin_md5_file(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + const ph7_io_stream *pStream; + unsigned char zDigest[16]; + int raw_output = FALSE; + const char *zFile; + MD5Context sCtx; + char zBuf[8192]; + void *pHandle; + ph7_int64 n; + int nLen; + if( nArg < 1 || !ph7_value_is_string(apArg[0]) ){ + /* Missing/Invalid arguments,return FALSE */ + ph7_context_throw_error(pCtx,PH7_CTX_WARNING,"Expecting a file path"); + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Extract the file path */ + zFile = ph7_value_to_string(apArg[0],&nLen); + /* Point to the target IO stream device */ + pStream = PH7_VmGetStreamDevice(pCtx->pVm,&zFile,nLen); + if( pStream == 0 ){ + ph7_context_throw_error(pCtx,PH7_CTX_WARNING,"No such stream device,PH7 is returning FALSE"); + ph7_result_bool(pCtx,0); + return PH7_OK; + } + if( nArg > 1 ){ + raw_output = ph7_value_to_bool(apArg[1]); + } + /* Try to open the file in read-only mode */ + pHandle = PH7_StreamOpenHandle(pCtx->pVm,pStream,zFile,PH7_IO_OPEN_RDONLY,FALSE,0,FALSE,0); + if( pHandle == 0 ){ + ph7_context_throw_error_format(pCtx,PH7_CTX_ERR,"IO error while opening '%s'",zFile); + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Init the MD5 context */ + MD5Init(&sCtx); + /* Perform the requested operation */ + for(;;){ + n = pStream->xRead(pHandle,zBuf,sizeof(zBuf)); + if( n < 1 ){ + /* EOF or IO error,break immediately */ + break; + } + MD5Update(&sCtx,(const unsigned char *)zBuf,(unsigned int)n); + } + /* Close the stream */ + PH7_StreamCloseHandle(pStream,pHandle); + /* Extract the digest */ + MD5Final(zDigest,&sCtx); + if( raw_output ){ + /* Output raw digest */ + ph7_result_string(pCtx,(const char *)zDigest,sizeof(zDigest)); + }else{ + /* Perform a binary to hex conversion */ + SyBinToHexConsumer((const void *)zDigest,sizeof(zDigest),vfsHashConsumer,pCtx); + } + return PH7_OK; +} +/* + * string sha1_file(string $uri[,bool $raw_output = false ]) + * Calculates the SHA1 hash of a given file. + * Parameters + * $uri + * Target URI (file(/path/to/something) or URL(http://www.symisc.net/)) + * $raw_output + * When TRUE, returns the digest in raw binary format with a length of 20. + * Return + * Return the SHA1 digest on success or FALSE on failure. + */ +static int PH7_builtin_sha1_file(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + const ph7_io_stream *pStream; + unsigned char zDigest[20]; + int raw_output = FALSE; + const char *zFile; + SHA1Context sCtx; + char zBuf[8192]; + void *pHandle; + ph7_int64 n; + int nLen; + if( nArg < 1 || !ph7_value_is_string(apArg[0]) ){ + /* Missing/Invalid arguments,return FALSE */ + ph7_context_throw_error(pCtx,PH7_CTX_WARNING,"Expecting a file path"); + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Extract the file path */ + zFile = ph7_value_to_string(apArg[0],&nLen); + /* Point to the target IO stream device */ + pStream = PH7_VmGetStreamDevice(pCtx->pVm,&zFile,nLen); + if( pStream == 0 ){ + ph7_context_throw_error(pCtx,PH7_CTX_WARNING,"No such stream device,PH7 is returning FALSE"); + ph7_result_bool(pCtx,0); + return PH7_OK; + } + if( nArg > 1 ){ + raw_output = ph7_value_to_bool(apArg[1]); + } + /* Try to open the file in read-only mode */ + pHandle = PH7_StreamOpenHandle(pCtx->pVm,pStream,zFile,PH7_IO_OPEN_RDONLY,FALSE,0,FALSE,0); + if( pHandle == 0 ){ + ph7_context_throw_error_format(pCtx,PH7_CTX_ERR,"IO error while opening '%s'",zFile); + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Init the SHA1 context */ + SHA1Init(&sCtx); + /* Perform the requested operation */ + for(;;){ + n = pStream->xRead(pHandle,zBuf,sizeof(zBuf)); + if( n < 1 ){ + /* EOF or IO error,break immediately */ + break; + } + SHA1Update(&sCtx,(const unsigned char *)zBuf,(unsigned int)n); + } + /* Close the stream */ + PH7_StreamCloseHandle(pStream,pHandle); + /* Extract the digest */ + SHA1Final(&sCtx,zDigest); + if( raw_output ){ + /* Output raw digest */ + ph7_result_string(pCtx,(const char *)zDigest,sizeof(zDigest)); + }else{ + /* Perform a binary to hex conversion */ + SyBinToHexConsumer((const void *)zDigest,sizeof(zDigest),vfsHashConsumer,pCtx); + } + return PH7_OK; +} +#endif /* PH7_DISABLE_HASH_FUNC */ +/* + * array parse_ini_file(string $filename[, bool $process_sections = false [, int $scanner_mode = INI_SCANNER_NORMAL ]] ) + * Parse a configuration file. + * Parameters + * $filename + * The filename of the ini file being parsed. + * $process_sections + * By setting the process_sections parameter to TRUE, you get a multidimensional array + * with the section names and settings included. + * The default for process_sections is FALSE. + * $scanner_mode + * Can either be INI_SCANNER_NORMAL (default) or INI_SCANNER_RAW. + * If INI_SCANNER_RAW is supplied, then option values will not be parsed. + * Return + * The settings are returned as an associative array on success. + * Otherwise is returned. + */ +static int PH7_builtin_parse_ini_file(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + const ph7_io_stream *pStream; + const char *zFile; + SyBlob sContents; + void *pHandle; + int nLen; + if( nArg < 1 || !ph7_value_is_string(apArg[0]) ){ + /* Missing/Invalid arguments,return FALSE */ + ph7_context_throw_error(pCtx,PH7_CTX_WARNING,"Expecting a file path"); + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Extract the file path */ + zFile = ph7_value_to_string(apArg[0],&nLen); + /* Point to the target IO stream device */ + pStream = PH7_VmGetStreamDevice(pCtx->pVm,&zFile,nLen); + if( pStream == 0 ){ + ph7_context_throw_error(pCtx,PH7_CTX_WARNING,"No such stream device,PH7 is returning FALSE"); + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Try to open the file in read-only mode */ + pHandle = PH7_StreamOpenHandle(pCtx->pVm,pStream,zFile,PH7_IO_OPEN_RDONLY,FALSE,0,FALSE,0); + if( pHandle == 0 ){ + ph7_context_throw_error_format(pCtx,PH7_CTX_ERR,"IO error while opening '%s'",zFile); + ph7_result_bool(pCtx,0); + return PH7_OK; + } + SyBlobInit(&sContents,&pCtx->pVm->sAllocator); + /* Read the whole file */ + PH7_StreamReadWholeFile(pHandle,pStream,&sContents); + if( SyBlobLength(&sContents) < 1 ){ + /* Empty buffer,return FALSE */ + ph7_result_bool(pCtx,0); + }else{ + /* Process the raw INI buffer */ + PH7_ParseIniString(pCtx,(const char *)SyBlobData(&sContents),SyBlobLength(&sContents), + nArg > 1 ? ph7_value_to_bool(apArg[1]) : 0); + } + /* Close the stream */ + PH7_StreamCloseHandle(pStream,pHandle); + /* Release the working buffer */ + SyBlobRelease(&sContents); + return PH7_OK; +} +/* + * Section: + * ZIP archive processing. + * Authors: + * Symisc Systems,devel@symisc.net. + * Copyright (C) Symisc Systems,http://ph7.symisc.net + * Status: + * Stable. + */ +typedef struct zip_raw_data zip_raw_data; +struct zip_raw_data +{ + int iType; /* Where the raw data is stored */ + union raw_data{ + struct mmap_data{ + void *pMap; /* Memory mapped data */ + ph7_int64 nSize; /* Map size */ + const ph7_vfs *pVfs; /* Underlying vfs */ + }mmap; + SyBlob sBlob; /* Memory buffer */ + }raw; +}; +#define ZIP_RAW_DATA_MMAPED 1 /* Memory mapped ZIP raw data */ +#define ZIP_RAW_DATA_MEMBUF 2 /* ZIP raw data stored in a dynamically + * allocated memory chunk. + */ + /* + * mixed zip_open(string $filename) + * Opens a new zip archive for reading. + * Parameters + * $filename + * The file name of the ZIP archive to open. + * Return + * A resource handle for later use with zip_read() and zip_close() or FALSE on failure. + */ +static int PH7_builtin_zip_open(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + const ph7_io_stream *pStream; + SyArchive *pArchive; + zip_raw_data *pRaw; + const char *zFile; + SyBlob *pContents; + void *pHandle; + int nLen; + sxi32 rc; + if( nArg < 1 || !ph7_value_is_string(apArg[0]) ){ + /* Missing/Invalid arguments,return FALSE */ + ph7_context_throw_error(pCtx,PH7_CTX_WARNING,"Expecting a file path"); + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Extract the file path */ + zFile = ph7_value_to_string(apArg[0],&nLen); + /* Point to the target IO stream device */ + pStream = PH7_VmGetStreamDevice(pCtx->pVm,&zFile,nLen); + if( pStream == 0 ){ + ph7_context_throw_error(pCtx,PH7_CTX_WARNING,"No such stream device,PH7 is returning FALSE"); + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Create an in-memory archive */ + pArchive = (SyArchive *)ph7_context_alloc_chunk(pCtx,sizeof(SyArchive)+sizeof(zip_raw_data),TRUE,FALSE); + if( pArchive == 0 ){ + ph7_context_throw_error(pCtx,PH7_CTX_WARNING,"PH7 is running out of memory"); + ph7_result_bool(pCtx,0); + return PH7_OK; + } + pRaw = (zip_raw_data *)&pArchive[1]; + /* Initialize the archive */ + SyArchiveInit(pArchive,&pCtx->pVm->sAllocator,0,0); + /* Extract the default stream */ + if( pStream == pCtx->pVm->pDefStream /* file:// stream*/){ + const ph7_vfs *pVfs; + /* Try to get a memory view of the whole file since ZIP files + * tends to be very big this days,this is a huge performance win. + */ + pVfs = PH7_ExportBuiltinVfs(); + if( pVfs && pVfs->xMmap ){ + rc = pVfs->xMmap(zFile,&pRaw->raw.mmap.pMap,&pRaw->raw.mmap.nSize); + if( rc == PH7_OK ){ + /* Nice,Extract the whole archive */ + rc = SyZipExtractFromBuf(pArchive,(const char *)pRaw->raw.mmap.pMap,(sxu32)pRaw->raw.mmap.nSize); + if( rc != SXRET_OK ){ + if( pVfs->xUnmap ){ + pVfs->xUnmap(pRaw->raw.mmap.pMap,pRaw->raw.mmap.nSize); + } + /* Release the allocated chunk */ + ph7_context_free_chunk(pCtx,pArchive); + /* Something goes wrong with this ZIP archive,return FALSE */ + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Archive successfully opened */ + pRaw->iType = ZIP_RAW_DATA_MMAPED; + pRaw->raw.mmap.pVfs = pVfs; + goto success; + } + } + /* FALL THROUGH */ + } + /* Try to open the file in read-only mode */ + pHandle = PH7_StreamOpenHandle(pCtx->pVm,pStream,zFile,PH7_IO_OPEN_RDONLY,FALSE,0,FALSE,0); + if( pHandle == 0 ){ + ph7_context_throw_error_format(pCtx,PH7_CTX_ERR,"IO error while opening '%s'",zFile); + ph7_result_bool(pCtx,0); + return PH7_OK; + } + pContents = &pRaw->raw.sBlob; + SyBlobInit(pContents,&pCtx->pVm->sAllocator); + /* Read the whole file */ + PH7_StreamReadWholeFile(pHandle,pStream,pContents); + /* Assume an invalid ZIP file */ + rc = SXERR_INVALID; + if( SyBlobLength(pContents) > 0 ){ + /* Extract archive entries */ + rc = SyZipExtractFromBuf(pArchive,(const char *)SyBlobData(pContents),SyBlobLength(pContents)); + } + pRaw->iType = ZIP_RAW_DATA_MEMBUF; + /* Close the stream */ + PH7_StreamCloseHandle(pStream,pHandle); + if( rc != SXRET_OK ){ + /* Release the working buffer */ + SyBlobRelease(pContents); + /* Release the allocated chunk */ + ph7_context_free_chunk(pCtx,pArchive); + /* Something goes wrong with this ZIP archive,return FALSE */ + ph7_result_bool(pCtx,0); + return PH7_OK; + } +success: + /* Reset the loop cursor */ + SyArchiveResetLoopCursor(pArchive); + /* Return the in-memory archive as a resource handle */ + ph7_result_resource(pCtx,pArchive); + return PH7_OK; +} +/* + * void zip_close(resource $zip) + * Close an in-memory ZIP archive. + * Parameters + * $zip + * A ZIP file previously opened with zip_open(). + * Return + * null. + */ +static int PH7_builtin_zip_close(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + SyArchive *pArchive; + zip_raw_data *pRaw; + if( nArg < 1 || !ph7_value_is_resource(apArg[0]) ){ + /* Missing/Invalid arguments */ + ph7_context_throw_error(pCtx,PH7_CTX_ERR,"Expecting a ZIP archive"); + return PH7_OK; + } + /* Point to the in-memory archive */ + pArchive = (SyArchive *)ph7_value_to_resource(apArg[0]); + /* Make sure we are dealing with a valid ZIP archive */ + if( SXARCH_INVALID(pArchive) ){ + ph7_context_throw_error(pCtx,PH7_CTX_ERR,"Expecting a ZIP archive"); + return PH7_OK; + } + /* Release the archive */ + SyArchiveRelease(pArchive); + pRaw = (zip_raw_data *)&pArchive[1]; + if( pRaw->iType == ZIP_RAW_DATA_MEMBUF ){ + SyBlobRelease(&pRaw->raw.sBlob); + }else{ + const ph7_vfs *pVfs = pRaw->raw.mmap.pVfs; + if( pVfs->xUnmap ){ + /* Unmap the memory view */ + pVfs->xUnmap(pRaw->raw.mmap.pMap,pRaw->raw.mmap.nSize); + } + } + /* Release the memory chunk */ + ph7_context_free_chunk(pCtx,pArchive); + return PH7_OK; +} +/* + * mixed zip_read(resource $zip) + * Reads the next entry from an in-memory ZIP archive. + * Parameters + * $zip + * A ZIP file previously opened with zip_open(). + * Return + * A directory entry resource for later use with the zip_entry_... functions + * or FALSE if there are no more entries to read, or an error code if an error occurred. + */ +static int PH7_builtin_zip_read(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + SyArchiveEntry *pNext = 0; /* cc warning */ + SyArchive *pArchive; + sxi32 rc; + if( nArg < 1 || !ph7_value_is_resource(apArg[0]) ){ + /* Missing/Invalid arguments */ + ph7_context_throw_error(pCtx,PH7_CTX_ERR,"Expecting a ZIP archive"); + /* return FALSE */ + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Point to the in-memory archive */ + pArchive = (SyArchive *)ph7_value_to_resource(apArg[0]); + /* Make sure we are dealing with a valid ZIP archive */ + if( SXARCH_INVALID(pArchive) ){ + ph7_context_throw_error(pCtx,PH7_CTX_ERR,"Expecting a ZIP archive"); + /* return FALSE */ + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Extract the next entry */ + rc = SyArchiveGetNextEntry(pArchive,&pNext); + if( rc != SXRET_OK ){ + /* No more entries in the central directory,return FALSE */ + ph7_result_bool(pCtx,0); + }else{ + /* Return as a resource handle */ + ph7_result_resource(pCtx,pNext); + /* Point to the ZIP raw data */ + pNext->pUserData = (void *)&pArchive[1]; + } + return PH7_OK; +} +/* + * bool zip_entry_open(resource $zip,resource $zip_entry[,string $mode ]) + * Open a directory entry for reading + * Parameters + * $zip + * A ZIP file previously opened with zip_open(). + * $zip_entry + * A directory entry returned by zip_read(). + * $mode + * Not used + * Return + * A directory entry resource for later use with the zip_entry_... functions + * or FALSE if there are no more entries to read, or an error code if an error occurred. + */ +static int PH7_builtin_zip_entry_open(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + SyArchiveEntry *pEntry; + SyArchive *pArchive; + if( nArg < 2 || !ph7_value_is_resource(apArg[0]) || !ph7_value_is_resource(apArg[1]) ){ + /* Missing/Invalid arguments */ + ph7_context_throw_error(pCtx,PH7_CTX_ERR,"Expecting a ZIP archive"); + /* return FALSE */ + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Point to the in-memory archive */ + pArchive = (SyArchive *)ph7_value_to_resource(apArg[0]); + /* Make sure we are dealing with a valid ZIP archive */ + if( SXARCH_INVALID(pArchive) ){ + ph7_context_throw_error(pCtx,PH7_CTX_ERR,"Expecting a ZIP archive"); + /* return FALSE */ + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Make sure we are dealing with a valid ZIP archive entry */ + pEntry = (SyArchiveEntry *)ph7_value_to_resource(apArg[1]); + if( SXARCH_ENTRY_INVALID(pEntry) ){ + ph7_context_throw_error(pCtx,PH7_CTX_ERR,"Expecting a ZIP archive entry"); + /* return FALSE */ + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* All done. Actually this function is a no-op,return TRUE */ + ph7_result_bool(pCtx,1); + return PH7_OK; +} +/* + * bool zip_entry_close(resource $zip_entry) + * Close a directory entry. + * Parameters + * $zip_entry + * A directory entry returned by zip_read(). + * Return + * Returns TRUE on success or FALSE on failure. + */ +static int PH7_builtin_zip_entry_close(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + SyArchiveEntry *pEntry; + if( nArg < 1 || !ph7_value_is_resource(apArg[0]) ){ + /* Missing/Invalid arguments */ + ph7_context_throw_error(pCtx,PH7_CTX_ERR,"Expecting a ZIP archive entry"); + /* return FALSE */ + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Make sure we are dealing with a valid ZIP archive entry */ + pEntry = (SyArchiveEntry *)ph7_value_to_resource(apArg[0]); + if( SXARCH_ENTRY_INVALID(pEntry) ){ + ph7_context_throw_error(pCtx,PH7_CTX_ERR,"Expecting a ZIP archive entry"); + /* return FALSE */ + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Reset the read cursor */ + pEntry->nReadCount = 0; + /*All done. Actually this function is a no-op,return TRUE */ + ph7_result_bool(pCtx,1); + return PH7_OK; +} +/* + * string zip_entry_name(resource $zip_entry) + * Retrieve the name of a directory entry. + * Parameters + * $zip_entry + * A directory entry returned by zip_read(). + * Return + * The name of the directory entry. + */ +static int PH7_builtin_zip_entry_name(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + SyArchiveEntry *pEntry; + SyString *pName; + if( nArg < 1 || !ph7_value_is_resource(apArg[0]) ){ + /* Missing/Invalid arguments */ + ph7_context_throw_error(pCtx,PH7_CTX_ERR,"Expecting a ZIP archive entry"); + /* return FALSE */ + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Make sure we are dealing with a valid ZIP archive entry */ + pEntry = (SyArchiveEntry *)ph7_value_to_resource(apArg[0]); + if( SXARCH_ENTRY_INVALID(pEntry) ){ + ph7_context_throw_error(pCtx,PH7_CTX_ERR,"Expecting a ZIP archive entry"); + /* return FALSE */ + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Return entry name */ + pName = &pEntry->sFileName; + ph7_result_string(pCtx,pName->zString,(int)pName->nByte); + return PH7_OK; +} +/* + * int64 zip_entry_filesize(resource $zip_entry) + * Retrieve the actual file size of a directory entry. + * Parameters + * $zip_entry + * A directory entry returned by zip_read(). + * Return + * The size of the directory entry. + */ +static int PH7_builtin_zip_entry_filesize(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + SyArchiveEntry *pEntry; + if( nArg < 1 || !ph7_value_is_resource(apArg[0]) ){ + /* Missing/Invalid arguments */ + ph7_context_throw_error(pCtx,PH7_CTX_ERR,"Expecting a ZIP archive entry"); + /* return FALSE */ + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Make sure we are dealing with a valid ZIP archive entry */ + pEntry = (SyArchiveEntry *)ph7_value_to_resource(apArg[0]); + if( SXARCH_ENTRY_INVALID(pEntry) ){ + ph7_context_throw_error(pCtx,PH7_CTX_ERR,"Expecting a ZIP archive entry"); + /* return FALSE */ + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Return entry size */ + ph7_result_int64(pCtx,(ph7_int64)pEntry->nByte); + return PH7_OK; +} +/* + * int64 zip_entry_compressedsize(resource $zip_entry) + * Retrieve the compressed size of a directory entry. + * Parameters + * $zip_entry + * A directory entry returned by zip_read(). + * Return + * The compressed size. + */ +static int PH7_builtin_zip_entry_compressedsize(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + SyArchiveEntry *pEntry; + if( nArg < 1 || !ph7_value_is_resource(apArg[0]) ){ + /* Missing/Invalid arguments */ + ph7_context_throw_error(pCtx,PH7_CTX_ERR,"Expecting a ZIP archive entry"); + /* return FALSE */ + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Make sure we are dealing with a valid ZIP archive entry */ + pEntry = (SyArchiveEntry *)ph7_value_to_resource(apArg[0]); + if( SXARCH_ENTRY_INVALID(pEntry) ){ + ph7_context_throw_error(pCtx,PH7_CTX_ERR,"Expecting a ZIP archive entry"); + /* return FALSE */ + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Return entry compressed size */ + ph7_result_int64(pCtx,(ph7_int64)pEntry->nByteCompr); + return PH7_OK; +} +/* + * string zip_entry_read(resource $zip_entry[,int $length]) + * Reads from an open directory entry. + * Parameters + * $zip_entry + * A directory entry returned by zip_read(). + * $length + * The number of bytes to return. If not specified, this function + * will attempt to read 1024 bytes. + * Return + * Returns the data read, or FALSE if the end of the file is reached. + */ +static int PH7_builtin_zip_entry_read(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + SyArchiveEntry *pEntry; + zip_raw_data *pRaw; + const char *zData; + int iLength; + if( nArg < 1 || !ph7_value_is_resource(apArg[0]) ){ + /* Missing/Invalid arguments */ + ph7_context_throw_error(pCtx,PH7_CTX_ERR,"Expecting a ZIP archive entry"); + /* return FALSE */ + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Make sure we are dealing with a valid ZIP archive entry */ + pEntry = (SyArchiveEntry *)ph7_value_to_resource(apArg[0]); + if( SXARCH_ENTRY_INVALID(pEntry) ){ + ph7_context_throw_error(pCtx,PH7_CTX_ERR,"Expecting a ZIP archive entry"); + /* return FALSE */ + ph7_result_bool(pCtx,0); + return PH7_OK; + } + zData = 0; + if( pEntry->nReadCount >= pEntry->nByteCompr ){ + /* No more data to read,return FALSE */ + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Set a default read length */ + iLength = 1024; + if( nArg > 1 ){ + iLength = ph7_value_to_int(apArg[1]); + if( iLength < 1 ){ + iLength = 1024; + } + } + if( (sxu32)iLength > pEntry->nByteCompr - pEntry->nReadCount ){ + iLength = (int)(pEntry->nByteCompr - pEntry->nReadCount); + } + /* Return the entry contents */ + pRaw = (zip_raw_data *)pEntry->pUserData; + if( pRaw->iType == ZIP_RAW_DATA_MEMBUF ){ + zData = (const char *)SyBlobDataAt(&pRaw->raw.sBlob,(pEntry->nOfft+pEntry->nReadCount)); + }else{ + const char *zMap = (const char *)pRaw->raw.mmap.pMap; + /* Memory mmaped chunk */ + zData = &zMap[pEntry->nOfft+pEntry->nReadCount]; + } + /* Increment the read counter */ + pEntry->nReadCount += iLength; + /* Return the raw data */ + ph7_result_string(pCtx,zData,iLength); + return PH7_OK; +} +/* + * bool zip_entry_reset_read_cursor(resource $zip_entry) + * Reset the read cursor of an open directory entry. + * Parameters + * $zip_entry + * A directory entry returned by zip_read(). + * Return + * TRUE on success,FALSE on failure. + * Note that this is a symisc eXtension. + */ +static int PH7_builtin_zip_entry_reset_read_cursor(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + SyArchiveEntry *pEntry; + if( nArg < 1 || !ph7_value_is_resource(apArg[0]) ){ + /* Missing/Invalid arguments */ + ph7_context_throw_error(pCtx,PH7_CTX_ERR,"Expecting a ZIP archive entry"); + /* return FALSE */ + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Make sure we are dealing with a valid ZIP archive entry */ + pEntry = (SyArchiveEntry *)ph7_value_to_resource(apArg[0]); + if( SXARCH_ENTRY_INVALID(pEntry) ){ + ph7_context_throw_error(pCtx,PH7_CTX_ERR,"Expecting a ZIP archive entry"); + /* return FALSE */ + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Reset the cursor */ + pEntry->nReadCount = 0; + /* Return TRUE */ + ph7_result_bool(pCtx,1); + return PH7_OK; +} +/* + * string zip_entry_compressionmethod(resource $zip_entry) + * Retrieve the compression method of a directory entry. + * Parameters + * $zip_entry + * A directory entry returned by zip_read(). + * Return + * The compression method on success or FALSE on failure. + */ +static int PH7_builtin_zip_entry_compressionmethod(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + SyArchiveEntry *pEntry; + if( nArg < 1 || !ph7_value_is_resource(apArg[0]) ){ + /* Missing/Invalid arguments */ + ph7_context_throw_error(pCtx,PH7_CTX_ERR,"Expecting a ZIP archive entry"); + /* return FALSE */ + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Make sure we are dealing with a valid ZIP archive entry */ + pEntry = (SyArchiveEntry *)ph7_value_to_resource(apArg[0]); + if( SXARCH_ENTRY_INVALID(pEntry) ){ + ph7_context_throw_error(pCtx,PH7_CTX_ERR,"Expecting a ZIP archive entry"); + /* return FALSE */ + ph7_result_bool(pCtx,0); + return PH7_OK; + } + switch(pEntry->nComprMeth){ + case 0: + /* No compression;entry is stored */ + ph7_result_string(pCtx,"stored",(int)sizeof("stored")-1); + break; + case 8: + /* Entry is deflated (Default compression algorithm) */ + ph7_result_string(pCtx,"deflate",(int)sizeof("deflate")-1); + break; + /* Exotic compression algorithms */ + case 1: + ph7_result_string(pCtx,"shrunk",(int)sizeof("shrunk")-1); + break; + case 2: + case 3: + case 4: + case 5: + /* Entry is reduced */ + ph7_result_string(pCtx,"reduced",(int)sizeof("reduced")-1); + break; + case 6: + /* Entry is imploded */ + ph7_result_string(pCtx,"implode",(int)sizeof("implode")-1); + break; + default: + ph7_result_string(pCtx,"unknown",(int)sizeof("unknown")-1); + break; + } + return PH7_OK; +} +#endif /* #ifndef PH7_DISABLE_BUILTIN_FUNC*/ +/* NULL VFS [i.e: a no-op VFS]*/ +static const ph7_vfs null_vfs = { + "null_vfs", + PH7_VFS_VERSION, + 0, /* int (*xChdir)(const char *) */ + 0, /* int (*xChroot)(const char *); */ + 0, /* int (*xGetcwd)(ph7_context *) */ + 0, /* int (*xMkdir)(const char *,int,int) */ + 0, /* int (*xRmdir)(const char *) */ + 0, /* int (*xIsdir)(const char *) */ + 0, /* int (*xRename)(const char *,const char *) */ + 0, /*int (*xRealpath)(const char *,ph7_context *)*/ + 0, /* int (*xSleep)(unsigned int) */ + 0, /* int (*xUnlink)(const char *) */ + 0, /* int (*xFileExists)(const char *) */ + 0, /*int (*xChmod)(const char *,int)*/ + 0, /*int (*xChown)(const char *,const char *)*/ + 0, /*int (*xChgrp)(const char *,const char *)*/ + 0, /* ph7_int64 (*xFreeSpace)(const char *) */ + 0, /* ph7_int64 (*xTotalSpace)(const char *) */ + 0, /* ph7_int64 (*xFileSize)(const char *) */ + 0, /* ph7_int64 (*xFileAtime)(const char *) */ + 0, /* ph7_int64 (*xFileMtime)(const char *) */ + 0, /* ph7_int64 (*xFileCtime)(const char *) */ + 0, /* int (*xStat)(const char *,ph7_value *,ph7_value *) */ + 0, /* int (*xlStat)(const char *,ph7_value *,ph7_value *) */ + 0, /* int (*xIsfile)(const char *) */ + 0, /* int (*xIslink)(const char *) */ + 0, /* int (*xReadable)(const char *) */ + 0, /* int (*xWritable)(const char *) */ + 0, /* int (*xExecutable)(const char *) */ + 0, /* int (*xFiletype)(const char *,ph7_context *) */ + 0, /* int (*xGetenv)(const char *,ph7_context *) */ + 0, /* int (*xSetenv)(const char *,const char *) */ + 0, /* int (*xTouch)(const char *,ph7_int64,ph7_int64) */ + 0, /* int (*xMmap)(const char *,void **,ph7_int64 *) */ + 0, /* void (*xUnmap)(void *,ph7_int64); */ + 0, /* int (*xLink)(const char *,const char *,int) */ + 0, /* int (*xUmask)(int) */ + 0, /* void (*xTempDir)(ph7_context *) */ + 0, /* unsigned int (*xProcessId)(void) */ + 0, /* int (*xUid)(void) */ + 0, /* int (*xGid)(void) */ + 0, /* void (*xUsername)(ph7_context *) */ + 0 /* int (*xExec)(const char *,ph7_context *) */ +}; +#ifndef PH7_DISABLE_BUILTIN_FUNC +#ifndef PH7_DISABLE_DISK_IO +#ifdef __WINNT__ +/* + * Windows VFS implementation for the PH7 engine. + * Authors: + * Symisc Systems,devel@symisc.net. + * Copyright (C) Symisc Systems,http://ph7.symisc.net + * Status: + * Stable. + */ +/* What follows here is code that is specific to windows systems. */ +#include +/* +** Convert a UTF-8 string to microsoft unicode (UTF-16?). +** +** Space to hold the returned string is obtained from HeapAlloc(). +** Taken from the sqlite3 source tree +** status: Public Domain +*/ +static WCHAR *utf8ToUnicode(const char *zFilename){ + int nChar; + WCHAR *zWideFilename; + + nChar = MultiByteToWideChar(CP_UTF8, 0, zFilename, -1, 0, 0); + zWideFilename = (WCHAR *)HeapAlloc(GetProcessHeap(),0,nChar*sizeof(zWideFilename[0])); + if( zWideFilename == 0 ){ + return 0; + } + nChar = MultiByteToWideChar(CP_UTF8, 0, zFilename, -1, zWideFilename, nChar); + if( nChar==0 ){ + HeapFree(GetProcessHeap(),0,zWideFilename); + return 0; + } + return zWideFilename; +} +/* +** Convert a UTF-8 filename into whatever form the underlying +** operating system wants filenames in.Space to hold the result +** is obtained from HeapAlloc() and must be freed by the calling +** function. +** Taken from the sqlite3 source tree +** status: Public Domain +*/ +static void *convertUtf8Filename(const char *zFilename){ + void *zConverted; + zConverted = utf8ToUnicode(zFilename); + return zConverted; +} +/* +** Convert microsoft unicode to UTF-8. Space to hold the returned string is +** obtained from HeapAlloc(). +** Taken from the sqlite3 source tree +** status: Public Domain +*/ +static char *unicodeToUtf8(const WCHAR *zWideFilename){ + char *zFilename; + int nByte; + + nByte = WideCharToMultiByte(CP_UTF8, 0, zWideFilename, -1, 0, 0, 0, 0); + zFilename = (char *)HeapAlloc(GetProcessHeap(),0,nByte); + if( zFilename == 0 ){ + return 0; + } + nByte = WideCharToMultiByte(CP_UTF8, 0, zWideFilename, -1, zFilename, nByte,0, 0); + if( nByte == 0 ){ + HeapFree(GetProcessHeap(),0,zFilename); + return 0; + } + return zFilename; +} +/* int (*xchdir)(const char *) */ +static int WinVfs_chdir(const char *zPath) +{ + void * pConverted; + BOOL rc; + pConverted = convertUtf8Filename(zPath); + if( pConverted == 0 ){ + return -1; + } + rc = SetCurrentDirectoryW((LPCWSTR)pConverted); + HeapFree(GetProcessHeap(),0,pConverted); + return rc ? PH7_OK : -1; +} +/* int (*xGetcwd)(ph7_context *) */ +static int WinVfs_getcwd(ph7_context *pCtx) +{ + WCHAR zDir[2048]; + char *zConverted; + DWORD rc; + /* Get the current directory */ + rc = GetCurrentDirectoryW(sizeof(zDir),zDir); + if( rc < 1 ){ + return -1; + } + zConverted = unicodeToUtf8(zDir); + if( zConverted == 0 ){ + return -1; + } + ph7_result_string(pCtx,zConverted,-1/*Compute length automatically*/); /* Will make it's own copy */ + HeapFree(GetProcessHeap(),0,zConverted); + return PH7_OK; +} +/* int (*xMkdir)(const char *,int,int) */ +static int WinVfs_mkdir(const char *zPath,int mode,int recursive) +{ + void * pConverted; + BOOL rc; + pConverted = convertUtf8Filename(zPath); + if( pConverted == 0 ){ + return -1; + } + mode= 0; /* MSVC warning */ + recursive = 0; + rc = CreateDirectoryW((LPCWSTR)pConverted,0); + HeapFree(GetProcessHeap(),0,pConverted); + return rc ? PH7_OK : -1; +} +/* int (*xRmdir)(const char *) */ +static int WinVfs_rmdir(const char *zPath) +{ + void * pConverted; + BOOL rc; + pConverted = convertUtf8Filename(zPath); + if( pConverted == 0 ){ + return -1; + } + rc = RemoveDirectoryW((LPCWSTR)pConverted); + HeapFree(GetProcessHeap(),0,pConverted); + return rc ? PH7_OK : -1; +} +/* int (*xIsdir)(const char *) */ +static int WinVfs_isdir(const char *zPath) +{ + void * pConverted; + DWORD dwAttr; + pConverted = convertUtf8Filename(zPath); + if( pConverted == 0 ){ + return -1; + } + dwAttr = GetFileAttributesW((LPCWSTR)pConverted); + HeapFree(GetProcessHeap(),0,pConverted); + if( dwAttr == INVALID_FILE_ATTRIBUTES ){ + return -1; + } + return (dwAttr & FILE_ATTRIBUTE_DIRECTORY) ? PH7_OK : -1; +} +/* int (*xRename)(const char *,const char *) */ +static int WinVfs_Rename(const char *zOld,const char *zNew) +{ + void *pOld,*pNew; + BOOL rc = 0; + pOld = convertUtf8Filename(zOld); + if( pOld == 0 ){ + return -1; + } + pNew = convertUtf8Filename(zNew); + if( pNew ){ + rc = MoveFileW((LPCWSTR)pOld,(LPCWSTR)pNew); + } + HeapFree(GetProcessHeap(),0,pOld); + if( pNew ){ + HeapFree(GetProcessHeap(),0,pNew); + } + return rc ? PH7_OK : - 1; +} +/* int (*xRealpath)(const char *,ph7_context *) */ +static int WinVfs_Realpath(const char *zPath,ph7_context *pCtx) +{ + WCHAR zTemp[2048]; + void *pPath; + char *zReal; + DWORD n; + pPath = convertUtf8Filename(zPath); + if( pPath == 0 ){ + return -1; + } + n = GetFullPathNameW((LPCWSTR)pPath,0,0,0); + if( n > 0 ){ + if( n >= sizeof(zTemp) ){ + n = sizeof(zTemp) - 1; + } + GetFullPathNameW((LPCWSTR)pPath,n,zTemp,0); + } + HeapFree(GetProcessHeap(),0,pPath); + if( !n ){ + return -1; + } + zReal = unicodeToUtf8(zTemp); + if( zReal == 0 ){ + return -1; + } + ph7_result_string(pCtx,zReal,-1); /* Will make it's own copy */ + HeapFree(GetProcessHeap(),0,zReal); + return PH7_OK; +} +/* int (*xSleep)(unsigned int) */ +static int WinVfs_Sleep(unsigned int uSec) +{ + Sleep(uSec/1000/*uSec per Millisec */); + return PH7_OK; +} +/* int (*xUnlink)(const char *) */ +static int WinVfs_unlink(const char *zPath) +{ + void * pConverted; + BOOL rc; + pConverted = convertUtf8Filename(zPath); + if( pConverted == 0 ){ + return -1; + } + rc = DeleteFileW((LPCWSTR)pConverted); + HeapFree(GetProcessHeap(),0,pConverted); + return rc ? PH7_OK : - 1; +} +/* ph7_int64 (*xFreeSpace)(const char *) */ +static ph7_int64 WinVfs_DiskFreeSpace(const char *zPath) +{ +#ifdef _WIN32_WCE + /* GetDiskFreeSpace is not supported under WINCE */ + SXUNUSED(zPath); + return 0; +#else + DWORD dwSectPerClust,dwBytesPerSect,dwFreeClusters,dwTotalClusters; + void * pConverted; + WCHAR *p; + BOOL rc; + pConverted = convertUtf8Filename(zPath); + if( pConverted == 0 ){ + return 0; + } + p = (WCHAR *)pConverted; + for(;*p;p++){ + if( *p == '\\' || *p == '/'){ + *p = '\0'; + break; + } + } + rc = GetDiskFreeSpaceW((LPCWSTR)pConverted,&dwSectPerClust,&dwBytesPerSect,&dwFreeClusters,&dwTotalClusters); + if( !rc ){ + return 0; + } + return (ph7_int64)dwFreeClusters * dwSectPerClust * dwBytesPerSect; +#endif +} +/* ph7_int64 (*xTotalSpace)(const char *) */ +static ph7_int64 WinVfs_DiskTotalSpace(const char *zPath) +{ +#ifdef _WIN32_WCE + /* GetDiskFreeSpace is not supported under WINCE */ + SXUNUSED(zPath); + return 0; +#else + DWORD dwSectPerClust,dwBytesPerSect,dwFreeClusters,dwTotalClusters; + void * pConverted; + WCHAR *p; + BOOL rc; + pConverted = convertUtf8Filename(zPath); + if( pConverted == 0 ){ + return 0; + } + p = (WCHAR *)pConverted; + for(;*p;p++){ + if( *p == '\\' || *p == '/'){ + *p = '\0'; + break; + } + } + rc = GetDiskFreeSpaceW((LPCWSTR)pConverted,&dwSectPerClust,&dwBytesPerSect,&dwFreeClusters,&dwTotalClusters); + if( !rc ){ + return 0; + } + return (ph7_int64)dwTotalClusters * dwSectPerClust * dwBytesPerSect; +#endif +} +/* int (*xFileExists)(const char *) */ +static int WinVfs_FileExists(const char *zPath) +{ + void * pConverted; + DWORD dwAttr; + pConverted = convertUtf8Filename(zPath); + if( pConverted == 0 ){ + return -1; + } + dwAttr = GetFileAttributesW((LPCWSTR)pConverted); + HeapFree(GetProcessHeap(),0,pConverted); + if( dwAttr == INVALID_FILE_ATTRIBUTES ){ + return -1; + } + return PH7_OK; +} +/* Open a file in a read-only mode */ +static HANDLE OpenReadOnly(LPCWSTR pPath) +{ + DWORD dwType = FILE_ATTRIBUTE_NORMAL | FILE_FLAG_RANDOM_ACCESS; + DWORD dwShare = FILE_SHARE_READ | FILE_SHARE_WRITE; + DWORD dwAccess = GENERIC_READ; + DWORD dwCreate = OPEN_EXISTING; + HANDLE pHandle; + pHandle = CreateFileW(pPath,dwAccess,dwShare,0,dwCreate,dwType,0); + if( pHandle == INVALID_HANDLE_VALUE){ + return 0; + } + return pHandle; +} +/* ph7_int64 (*xFileSize)(const char *) */ +static ph7_int64 WinVfs_FileSize(const char *zPath) +{ + DWORD dwLow,dwHigh; + void * pConverted; + ph7_int64 nSize; + HANDLE pHandle; + + pConverted = convertUtf8Filename(zPath); + if( pConverted == 0 ){ + return -1; + } + /* Open the file in read-only mode */ + pHandle = OpenReadOnly((LPCWSTR)pConverted); + HeapFree(GetProcessHeap(),0,pConverted); + if( pHandle ){ + dwLow = GetFileSize(pHandle,&dwHigh); + nSize = dwHigh; + nSize <<= 32; + nSize += dwLow; + CloseHandle(pHandle); + }else{ + nSize = -1; + } + return nSize; +} +#define TICKS_PER_SECOND 10000000 +#define EPOCH_DIFFERENCE 11644473600LL +/* Convert Windows timestamp to UNIX timestamp */ +static ph7_int64 convertWindowsTimeToUnixTime(LPFILETIME pTime) +{ + ph7_int64 input,temp; + input = pTime->dwHighDateTime; + input <<= 32; + input += pTime->dwLowDateTime; + temp = input / TICKS_PER_SECOND; /*convert from 100ns intervals to seconds*/ + temp = temp - EPOCH_DIFFERENCE; /*subtract number of seconds between epochs*/ + return temp; +} +/* Convert UNIX timestamp to Windows timestamp */ +static void convertUnixTimeToWindowsTime(ph7_int64 nUnixtime,LPFILETIME pOut) +{ + ph7_int64 result = EPOCH_DIFFERENCE; + result += nUnixtime; + result *= 10000000LL; + pOut->dwHighDateTime = (DWORD)(nUnixtime>>32); + pOut->dwLowDateTime = (DWORD)nUnixtime; +} +/* int (*xTouch)(const char *,ph7_int64,ph7_int64) */ +static int WinVfs_Touch(const char *zPath,ph7_int64 touch_time,ph7_int64 access_time) +{ + FILETIME sTouch,sAccess; + void *pConverted; + void *pHandle; + BOOL rc = 0; + pConverted = convertUtf8Filename(zPath); + if( pConverted == 0 ){ + return -1; + } + pHandle = OpenReadOnly((LPCWSTR)pConverted); + if( pHandle ){ + if( touch_time < 0 ){ + GetSystemTimeAsFileTime(&sTouch); + }else{ + convertUnixTimeToWindowsTime(touch_time,&sTouch); + } + if( access_time < 0 ){ + /* Use the touch time */ + sAccess = sTouch; /* Structure assignment */ + }else{ + convertUnixTimeToWindowsTime(access_time,&sAccess); + } + rc = SetFileTime(pHandle,&sTouch,&sAccess,0); + /* Close the handle */ + CloseHandle(pHandle); + } + HeapFree(GetProcessHeap(),0,pConverted); + return rc ? PH7_OK : -1; +} +/* ph7_int64 (*xFileAtime)(const char *) */ +static ph7_int64 WinVfs_FileAtime(const char *zPath) +{ + BY_HANDLE_FILE_INFORMATION sInfo; + void * pConverted; + ph7_int64 atime; + HANDLE pHandle; + pConverted = convertUtf8Filename(zPath); + if( pConverted == 0 ){ + return -1; + } + /* Open the file in read-only mode */ + pHandle = OpenReadOnly((LPCWSTR)pConverted); + if( pHandle ){ + BOOL rc; + rc = GetFileInformationByHandle(pHandle,&sInfo); + if( rc ){ + atime = convertWindowsTimeToUnixTime(&sInfo.ftLastAccessTime); + }else{ + atime = -1; + } + CloseHandle(pHandle); + }else{ + atime = -1; + } + HeapFree(GetProcessHeap(),0,pConverted); + return atime; +} +/* ph7_int64 (*xFileMtime)(const char *) */ +static ph7_int64 WinVfs_FileMtime(const char *zPath) +{ + BY_HANDLE_FILE_INFORMATION sInfo; + void * pConverted; + ph7_int64 mtime; + HANDLE pHandle; + pConverted = convertUtf8Filename(zPath); + if( pConverted == 0 ){ + return -1; + } + /* Open the file in read-only mode */ + pHandle = OpenReadOnly((LPCWSTR)pConverted); + if( pHandle ){ + BOOL rc; + rc = GetFileInformationByHandle(pHandle,&sInfo); + if( rc ){ + mtime = convertWindowsTimeToUnixTime(&sInfo.ftLastWriteTime); + }else{ + mtime = -1; + } + CloseHandle(pHandle); + }else{ + mtime = -1; + } + HeapFree(GetProcessHeap(),0,pConverted); + return mtime; +} +/* ph7_int64 (*xFileCtime)(const char *) */ +static ph7_int64 WinVfs_FileCtime(const char *zPath) +{ + BY_HANDLE_FILE_INFORMATION sInfo; + void * pConverted; + ph7_int64 ctime; + HANDLE pHandle; + pConverted = convertUtf8Filename(zPath); + if( pConverted == 0 ){ + return -1; + } + /* Open the file in read-only mode */ + pHandle = OpenReadOnly((LPCWSTR)pConverted); + if( pHandle ){ + BOOL rc; + rc = GetFileInformationByHandle(pHandle,&sInfo); + if( rc ){ + ctime = convertWindowsTimeToUnixTime(&sInfo.ftCreationTime); + }else{ + ctime = -1; + } + CloseHandle(pHandle); + }else{ + ctime = -1; + } + HeapFree(GetProcessHeap(),0,pConverted); + return ctime; +} +/* int (*xStat)(const char *,ph7_value *,ph7_value *) */ +/* int (*xlStat)(const char *,ph7_value *,ph7_value *) */ +static int WinVfs_Stat(const char *zPath,ph7_value *pArray,ph7_value *pWorker) +{ + BY_HANDLE_FILE_INFORMATION sInfo; + void *pConverted; + HANDLE pHandle; + BOOL rc; + pConverted = convertUtf8Filename(zPath); + if( pConverted == 0 ){ + return -1; + } + /* Open the file in read-only mode */ + pHandle = OpenReadOnly((LPCWSTR)pConverted); + HeapFree(GetProcessHeap(),0,pConverted); + if( pHandle == 0 ){ + return -1; + } + rc = GetFileInformationByHandle(pHandle,&sInfo); + CloseHandle(pHandle); + if( !rc ){ + return -1; + } + /* dev */ + ph7_value_int64(pWorker,(ph7_int64)sInfo.dwVolumeSerialNumber); + ph7_array_add_strkey_elem(pArray,"dev",pWorker); /* Will make it's own copy */ + /* ino */ + ph7_value_int64(pWorker,(ph7_int64)(((ph7_int64)sInfo.nFileIndexHigh << 32) | sInfo.nFileIndexLow)); + ph7_array_add_strkey_elem(pArray,"ino",pWorker); /* Will make it's own copy */ + /* mode */ + ph7_value_int(pWorker,0); + ph7_array_add_strkey_elem(pArray,"mode",pWorker); + /* nlink */ + ph7_value_int(pWorker,(int)sInfo.nNumberOfLinks); + ph7_array_add_strkey_elem(pArray,"nlink",pWorker); /* Will make it's own copy */ + /* uid,gid,rdev */ + ph7_value_int(pWorker,0); + ph7_array_add_strkey_elem(pArray,"uid",pWorker); + ph7_array_add_strkey_elem(pArray,"gid",pWorker); + ph7_array_add_strkey_elem(pArray,"rdev",pWorker); + /* size */ + ph7_value_int64(pWorker,(ph7_int64)(((ph7_int64)sInfo.nFileSizeHigh << 32) | sInfo.nFileSizeLow)); + ph7_array_add_strkey_elem(pArray,"size",pWorker); /* Will make it's own copy */ + /* atime */ + ph7_value_int64(pWorker,convertWindowsTimeToUnixTime(&sInfo.ftLastAccessTime)); + ph7_array_add_strkey_elem(pArray,"atime",pWorker); /* Will make it's own copy */ + /* mtime */ + ph7_value_int64(pWorker,convertWindowsTimeToUnixTime(&sInfo.ftLastWriteTime)); + ph7_array_add_strkey_elem(pArray,"mtime",pWorker); /* Will make it's own copy */ + /* ctime */ + ph7_value_int64(pWorker,convertWindowsTimeToUnixTime(&sInfo.ftCreationTime)); + ph7_array_add_strkey_elem(pArray,"ctime",pWorker); /* Will make it's own copy */ + /* blksize,blocks */ + ph7_value_int(pWorker,0); + ph7_array_add_strkey_elem(pArray,"blksize",pWorker); + ph7_array_add_strkey_elem(pArray,"blocks",pWorker); + return PH7_OK; +} +/* int (*xIsfile)(const char *) */ +static int WinVfs_isfile(const char *zPath) +{ + void * pConverted; + DWORD dwAttr; + pConverted = convertUtf8Filename(zPath); + if( pConverted == 0 ){ + return -1; + } + dwAttr = GetFileAttributesW((LPCWSTR)pConverted); + HeapFree(GetProcessHeap(),0,pConverted); + if( dwAttr == INVALID_FILE_ATTRIBUTES ){ + return -1; + } + return (dwAttr & (FILE_ATTRIBUTE_NORMAL|FILE_ATTRIBUTE_ARCHIVE)) ? PH7_OK : -1; +} +/* int (*xIslink)(const char *) */ +static int WinVfs_islink(const char *zPath) +{ + void * pConverted; + DWORD dwAttr; + pConverted = convertUtf8Filename(zPath); + if( pConverted == 0 ){ + return -1; + } + dwAttr = GetFileAttributesW((LPCWSTR)pConverted); + HeapFree(GetProcessHeap(),0,pConverted); + if( dwAttr == INVALID_FILE_ATTRIBUTES ){ + return -1; + } + return (dwAttr & FILE_ATTRIBUTE_REPARSE_POINT) ? PH7_OK : -1; +} +/* int (*xWritable)(const char *) */ +static int WinVfs_iswritable(const char *zPath) +{ + void * pConverted; + DWORD dwAttr; + pConverted = convertUtf8Filename(zPath); + if( pConverted == 0 ){ + return -1; + } + dwAttr = GetFileAttributesW((LPCWSTR)pConverted); + HeapFree(GetProcessHeap(),0,pConverted); + if( dwAttr == INVALID_FILE_ATTRIBUTES ){ + return -1; + } + if( (dwAttr & (FILE_ATTRIBUTE_ARCHIVE|FILE_ATTRIBUTE_NORMAL)) == 0 ){ + /* Not a regular file */ + return -1; + } + if( dwAttr & FILE_ATTRIBUTE_READONLY ){ + /* Read-only file */ + return -1; + } + /* File is writable */ + return PH7_OK; +} +/* int (*xExecutable)(const char *) */ +static int WinVfs_isexecutable(const char *zPath) +{ + void * pConverted; + DWORD dwAttr; + pConverted = convertUtf8Filename(zPath); + if( pConverted == 0 ){ + return -1; + } + dwAttr = GetFileAttributesW((LPCWSTR)pConverted); + HeapFree(GetProcessHeap(),0,pConverted); + if( dwAttr == INVALID_FILE_ATTRIBUTES ){ + return -1; + } + if( (dwAttr & FILE_ATTRIBUTE_NORMAL) == 0 ){ + /* Not a regular file */ + return -1; + } + /* File is executable */ + return PH7_OK; +} +/* int (*xFiletype)(const char *,ph7_context *) */ +static int WinVfs_Filetype(const char *zPath,ph7_context *pCtx) +{ + void * pConverted; + DWORD dwAttr; + pConverted = convertUtf8Filename(zPath); + if( pConverted == 0 ){ + /* Expand 'unknown' */ + ph7_result_string(pCtx,"unknown",sizeof("unknown")-1); + return -1; + } + dwAttr = GetFileAttributesW((LPCWSTR)pConverted); + HeapFree(GetProcessHeap(),0,pConverted); + if( dwAttr == INVALID_FILE_ATTRIBUTES ){ + /* Expand 'unknown' */ + ph7_result_string(pCtx,"unknown",sizeof("unknown")-1); + return -1; + } + if(dwAttr & (FILE_ATTRIBUTE_HIDDEN|FILE_ATTRIBUTE_NORMAL|FILE_ATTRIBUTE_ARCHIVE) ){ + ph7_result_string(pCtx,"file",sizeof("file")-1); + }else if(dwAttr & FILE_ATTRIBUTE_DIRECTORY){ + ph7_result_string(pCtx,"dir",sizeof("dir")-1); + }else if(dwAttr & FILE_ATTRIBUTE_REPARSE_POINT){ + ph7_result_string(pCtx,"link",sizeof("link")-1); + }else if(dwAttr & (FILE_ATTRIBUTE_DEVICE)){ + ph7_result_string(pCtx,"block",sizeof("block")-1); + }else{ + ph7_result_string(pCtx,"unknown",sizeof("unknown")-1); + } + return PH7_OK; +} +/* int (*xGetenv)(const char *,ph7_context *) */ +static int WinVfs_Getenv(const char *zVar,ph7_context *pCtx) +{ + char zValue[1024]; + DWORD n; + /* + * According to MSDN + * If lpBuffer is not large enough to hold the data, the return + * value is the buffer size, in characters, required to hold the + * string and its terminating null character and the contents + * of lpBuffer are undefined. + */ + n = sizeof(zValue); + SyMemcpy("Undefined",zValue,sizeof("Undefined")-1); + /* Extract the environment value */ + n = GetEnvironmentVariableA(zVar,zValue,sizeof(zValue)); + if( !n ){ + /* No such variable*/ + return -1; + } + ph7_result_string(pCtx,zValue,(int)n); + return PH7_OK; +} +/* int (*xSetenv)(const char *,const char *) */ +static int WinVfs_Setenv(const char *zName,const char *zValue) +{ + BOOL rc; + rc = SetEnvironmentVariableA(zName,zValue); + return rc ? PH7_OK : -1; +} +/* int (*xMmap)(const char *,void **,ph7_int64 *) */ +static int WinVfs_Mmap(const char *zPath,void **ppMap,ph7_int64 *pSize) +{ + DWORD dwSizeLow,dwSizeHigh; + HANDLE pHandle,pMapHandle; + void *pConverted,*pView; + + pConverted = convertUtf8Filename(zPath); + if( pConverted == 0 ){ + return -1; + } + pHandle = OpenReadOnly((LPCWSTR)pConverted); + HeapFree(GetProcessHeap(),0,pConverted); + if( pHandle == 0 ){ + return -1; + } + /* Get the file size */ + dwSizeLow = GetFileSize(pHandle,&dwSizeHigh); + /* Create the mapping */ + pMapHandle = CreateFileMappingW(pHandle,0,PAGE_READONLY,dwSizeHigh,dwSizeLow,0); + if( pMapHandle == 0 ){ + CloseHandle(pHandle); + return -1; + } + *pSize = ((ph7_int64)dwSizeHigh << 32) | dwSizeLow; + /* Obtain the view */ + pView = MapViewOfFile(pMapHandle,FILE_MAP_READ,0,0,(SIZE_T)(*pSize)); + if( pView ){ + /* Let the upper layer point to the view */ + *ppMap = pView; + } + /* Close the handle + * According to MSDN it's OK the close the HANDLES. + */ + CloseHandle(pMapHandle); + CloseHandle(pHandle); + return pView ? PH7_OK : -1; +} +/* void (*xUnmap)(void *,ph7_int64) */ +static void WinVfs_Unmap(void *pView,ph7_int64 nSize) +{ + nSize = 0; /* Compiler warning */ + UnmapViewOfFile(pView); +} +/* void (*xTempDir)(ph7_context *) */ +static void WinVfs_TempDir(ph7_context *pCtx) +{ + CHAR zTemp[1024]; + DWORD n; + n = GetTempPathA(sizeof(zTemp),zTemp); + if( n < 1 ){ + /* Assume the default windows temp directory */ + ph7_result_string(pCtx,"C:\\Windows\\Temp",-1/*Compute length automatically*/); + }else{ + ph7_result_string(pCtx,zTemp,(int)n); + } +} +/* unsigned int (*xProcessId)(void) */ +static unsigned int WinVfs_ProcessId(void) +{ + DWORD nID = 0; +#ifndef __MINGW32__ + nID = GetProcessId(GetCurrentProcess()); +#endif /* __MINGW32__ */ + return (unsigned int)nID; +} +/* void (*xUsername)(ph7_context *) */ +static void WinVfs_Username(ph7_context *pCtx) +{ + WCHAR zUser[1024]; + DWORD nByte; + BOOL rc; + nByte = sizeof(zUser); + rc = GetUserNameW(zUser,&nByte); + if( !rc ){ + /* Set a dummy name */ + ph7_result_string(pCtx,"Unknown",sizeof("Unknown")-1); + }else{ + char *zName; + zName = unicodeToUtf8(zUser); + if( zName == 0 ){ + ph7_result_string(pCtx,"Unknown",sizeof("Unknown")-1); + }else{ + ph7_result_string(pCtx,zName,-1/*Compute length automatically*/); /* Will make it's own copy */ + HeapFree(GetProcessHeap(),0,zName); + } + } + +} +/* Export the windows vfs */ +static const ph7_vfs sWinVfs = { + "Windows_vfs", + PH7_VFS_VERSION, + WinVfs_chdir, /* int (*xChdir)(const char *) */ + 0, /* int (*xChroot)(const char *); */ + WinVfs_getcwd, /* int (*xGetcwd)(ph7_context *) */ + WinVfs_mkdir, /* int (*xMkdir)(const char *,int,int) */ + WinVfs_rmdir, /* int (*xRmdir)(const char *) */ + WinVfs_isdir, /* int (*xIsdir)(const char *) */ + WinVfs_Rename, /* int (*xRename)(const char *,const char *) */ + WinVfs_Realpath, /*int (*xRealpath)(const char *,ph7_context *)*/ + WinVfs_Sleep, /* int (*xSleep)(unsigned int) */ + WinVfs_unlink, /* int (*xUnlink)(const char *) */ + WinVfs_FileExists, /* int (*xFileExists)(const char *) */ + 0, /*int (*xChmod)(const char *,int)*/ + 0, /*int (*xChown)(const char *,const char *)*/ + 0, /*int (*xChgrp)(const char *,const char *)*/ + WinVfs_DiskFreeSpace,/* ph7_int64 (*xFreeSpace)(const char *) */ + WinVfs_DiskTotalSpace,/* ph7_int64 (*xTotalSpace)(const char *) */ + WinVfs_FileSize, /* ph7_int64 (*xFileSize)(const char *) */ + WinVfs_FileAtime,/* ph7_int64 (*xFileAtime)(const char *) */ + WinVfs_FileMtime,/* ph7_int64 (*xFileMtime)(const char *) */ + WinVfs_FileCtime,/* ph7_int64 (*xFileCtime)(const char *) */ + WinVfs_Stat, /* int (*xStat)(const char *,ph7_value *,ph7_value *) */ + WinVfs_Stat, /* int (*xlStat)(const char *,ph7_value *,ph7_value *) */ + WinVfs_isfile, /* int (*xIsfile)(const char *) */ + WinVfs_islink, /* int (*xIslink)(const char *) */ + WinVfs_isfile, /* int (*xReadable)(const char *) */ + WinVfs_iswritable, /* int (*xWritable)(const char *) */ + WinVfs_isexecutable, /* int (*xExecutable)(const char *) */ + WinVfs_Filetype, /* int (*xFiletype)(const char *,ph7_context *) */ + WinVfs_Getenv, /* int (*xGetenv)(const char *,ph7_context *) */ + WinVfs_Setenv, /* int (*xSetenv)(const char *,const char *) */ + WinVfs_Touch, /* int (*xTouch)(const char *,ph7_int64,ph7_int64) */ + WinVfs_Mmap, /* int (*xMmap)(const char *,void **,ph7_int64 *) */ + WinVfs_Unmap, /* void (*xUnmap)(void *,ph7_int64); */ + 0, /* int (*xLink)(const char *,const char *,int) */ + 0, /* int (*xUmask)(int) */ + WinVfs_TempDir, /* void (*xTempDir)(ph7_context *) */ + WinVfs_ProcessId, /* unsigned int (*xProcessId)(void) */ + 0, /* int (*xUid)(void) */ + 0, /* int (*xGid)(void) */ + WinVfs_Username, /* void (*xUsername)(ph7_context *) */ + 0 /* int (*xExec)(const char *,ph7_context *) */ +}; +/* Windows file IO */ +#ifndef INVALID_SET_FILE_POINTER +# define INVALID_SET_FILE_POINTER ((DWORD)-1) +#endif +/* int (*xOpen)(const char *,int,ph7_value *,void **) */ +static int WinFile_Open(const char *zPath,int iOpenMode,ph7_value *pResource,void **ppHandle) +{ + DWORD dwType = FILE_ATTRIBUTE_NORMAL | FILE_FLAG_RANDOM_ACCESS; + DWORD dwAccess = GENERIC_READ; + DWORD dwShare,dwCreate; + void *pConverted; + HANDLE pHandle; + + pConverted = convertUtf8Filename(zPath); + if( pConverted == 0 ){ + return -1; + } + /* Set the desired flags according to the open mode */ + if( iOpenMode & PH7_IO_OPEN_CREATE ){ + /* Open existing file, or create if it doesn't exist */ + dwCreate = OPEN_ALWAYS; + if( iOpenMode & PH7_IO_OPEN_TRUNC ){ + /* If the specified file exists and is writable, the function overwrites the file */ + dwCreate = CREATE_ALWAYS; + } + }else if( iOpenMode & PH7_IO_OPEN_EXCL ){ + /* Creates a new file, only if it does not already exist. + * If the file exists, it fails. + */ + dwCreate = CREATE_NEW; + }else if( iOpenMode & PH7_IO_OPEN_TRUNC ){ + /* Opens a file and truncates it so that its size is zero bytes + * The file must exist. + */ + dwCreate = TRUNCATE_EXISTING; + }else{ + /* Opens a file, only if it exists. */ + dwCreate = OPEN_EXISTING; + } + if( iOpenMode & PH7_IO_OPEN_RDWR ){ + /* Read+Write access */ + dwAccess |= GENERIC_WRITE; + }else if( iOpenMode & PH7_IO_OPEN_WRONLY ){ + /* Write only access */ + dwAccess = GENERIC_WRITE; + } + if( iOpenMode & PH7_IO_OPEN_APPEND ){ + /* Append mode */ + dwAccess = FILE_APPEND_DATA; + } + if( iOpenMode & PH7_IO_OPEN_TEMP ){ + /* File is temporary */ + dwType = FILE_ATTRIBUTE_TEMPORARY; + } + dwShare = FILE_SHARE_READ | FILE_SHARE_WRITE; + pHandle = CreateFileW((LPCWSTR)pConverted,dwAccess,dwShare,0,dwCreate,dwType,0); + HeapFree(GetProcessHeap(),0,pConverted); + if( pHandle == INVALID_HANDLE_VALUE){ + SXUNUSED(pResource); /* MSVC warning */ + return -1; + } + /* Make the handle accessible to the upper layer */ + *ppHandle = (void *)pHandle; + return PH7_OK; +} +/* An instance of the following structure is used to record state information + * while iterating throw directory entries. + */ +typedef struct WinDir_Info WinDir_Info; +struct WinDir_Info +{ + HANDLE pDirHandle; + void *pPath; + WIN32_FIND_DATAW sInfo; + int rc; +}; +/* int (*xOpenDir)(const char *,ph7_value *,void **) */ +static int WinDir_Open(const char *zPath,ph7_value *pResource,void **ppHandle) +{ + WinDir_Info *pDirInfo; + void *pConverted; + char *zPrep; + sxu32 n; + /* Prepare the path */ + n = SyStrlen(zPath); + zPrep = (char *)HeapAlloc(GetProcessHeap(),0,n+sizeof("\\*")+4); + if( zPrep == 0 ){ + return -1; + } + SyMemcpy((const void *)zPath,zPrep,n); + zPrep[n] = '\\'; + zPrep[n+1] = '*'; + zPrep[n+2] = 0; + pConverted = convertUtf8Filename(zPrep); + HeapFree(GetProcessHeap(),0,zPrep); + if( pConverted == 0 ){ + return -1; + } + /* Allocate a new instance */ + pDirInfo = (WinDir_Info *)HeapAlloc(GetProcessHeap(),0,sizeof(WinDir_Info)); + if( pDirInfo == 0 ){ + pResource = 0; /* Compiler warning */ + return -1; + } + pDirInfo->rc = SXRET_OK; + pDirInfo->pDirHandle = FindFirstFileW((LPCWSTR)pConverted,&pDirInfo->sInfo); + if( pDirInfo->pDirHandle == INVALID_HANDLE_VALUE ){ + /* Cannot open directory */ + HeapFree(GetProcessHeap(),0,pConverted); + HeapFree(GetProcessHeap(),0,pDirInfo); + return -1; + } + /* Save the path */ + pDirInfo->pPath = pConverted; + /* Save our structure */ + *ppHandle = pDirInfo; + return PH7_OK; +} +/* void (*xCloseDir)(void *) */ +static void WinDir_Close(void *pUserData) +{ + WinDir_Info *pDirInfo = (WinDir_Info *)pUserData; + if( pDirInfo->pDirHandle != INVALID_HANDLE_VALUE ){ + FindClose(pDirInfo->pDirHandle); + } + HeapFree(GetProcessHeap(),0,pDirInfo->pPath); + HeapFree(GetProcessHeap(),0,pDirInfo); +} +/* void (*xClose)(void *); */ +static void WinFile_Close(void *pUserData) +{ + HANDLE pHandle = (HANDLE)pUserData; + CloseHandle(pHandle); +} +/* int (*xReadDir)(void *,ph7_context *) */ +static int WinDir_Read(void *pUserData,ph7_context *pCtx) +{ + WinDir_Info *pDirInfo = (WinDir_Info *)pUserData; + LPWIN32_FIND_DATAW pData; + char *zName; + BOOL rc; + sxu32 n; + if( pDirInfo->rc != SXRET_OK ){ + /* No more entry to process */ + return -1; + } + pData = &pDirInfo->sInfo; + for(;;){ + zName = unicodeToUtf8(pData->cFileName); + if( zName == 0 ){ + /* Out of memory */ + return -1; + } + n = SyStrlen(zName); + /* Ignore '.' && '..' */ + if( n > sizeof("..")-1 || zName[0] != '.' || ( n == sizeof("..")-1 && zName[1] != '.') ){ + break; + } + HeapFree(GetProcessHeap(),0,zName); + rc = FindNextFileW(pDirInfo->pDirHandle,&pDirInfo->sInfo); + if( !rc ){ + return -1; + } + } + /* Return the current file name */ + ph7_result_string(pCtx,zName,-1); + HeapFree(GetProcessHeap(),0,zName); + /* Point to the next entry */ + rc = FindNextFileW(pDirInfo->pDirHandle,&pDirInfo->sInfo); + if( !rc ){ + pDirInfo->rc = SXERR_EOF; + } + return PH7_OK; +} +/* void (*xRewindDir)(void *) */ +static void WinDir_RewindDir(void *pUserData) +{ + WinDir_Info *pDirInfo = (WinDir_Info *)pUserData; + FindClose(pDirInfo->pDirHandle); + pDirInfo->pDirHandle = FindFirstFileW((LPCWSTR)pDirInfo->pPath,&pDirInfo->sInfo); + if( pDirInfo->pDirHandle == INVALID_HANDLE_VALUE ){ + pDirInfo->rc = SXERR_EOF; + }else{ + pDirInfo->rc = SXRET_OK; + } +} +/* ph7_int64 (*xRead)(void *,void *,ph7_int64); */ +static ph7_int64 WinFile_Read(void *pOS,void *pBuffer,ph7_int64 nDatatoRead) +{ + HANDLE pHandle = (HANDLE)pOS; + DWORD nRd; + BOOL rc; + rc = ReadFile(pHandle,pBuffer,(DWORD)nDatatoRead,&nRd,0); + if( !rc ){ + /* EOF or IO error */ + return -1; + } + return (ph7_int64)nRd; +} +/* ph7_int64 (*xWrite)(void *,const void *,ph7_int64); */ +static ph7_int64 WinFile_Write(void *pOS,const void *pBuffer,ph7_int64 nWrite) +{ + const char *zData = (const char *)pBuffer; + HANDLE pHandle = (HANDLE)pOS; + ph7_int64 nCount; + DWORD nWr; + BOOL rc; + nWr = 0; + nCount = 0; + for(;;){ + if( nWrite < 1 ){ + break; + } + rc = WriteFile(pHandle,zData,(DWORD)nWrite,&nWr,0); + if( !rc ){ + /* IO error */ + break; + } + nWrite -= nWr; + nCount += nWr; + zData += nWr; + } + if( nWrite > 0 ){ + return -1; + } + return nCount; +} +/* int (*xSeek)(void *,ph7_int64,int) */ +static int WinFile_Seek(void *pUserData,ph7_int64 iOfft,int whence) +{ + HANDLE pHandle = (HANDLE)pUserData; + DWORD dwMove,dwNew; + LONG nHighOfft; + switch(whence){ + case 1:/*SEEK_CUR*/ + dwMove = FILE_CURRENT; + break; + case 2: /* SEEK_END */ + dwMove = FILE_END; + break; + case 0: /* SEEK_SET */ + default: + dwMove = FILE_BEGIN; + break; + } + nHighOfft = (LONG)(iOfft >> 32); + dwNew = SetFilePointer(pHandle,(LONG)iOfft,&nHighOfft,dwMove); + if( dwNew == INVALID_SET_FILE_POINTER ){ + return -1; + } + return PH7_OK; +} +/* int (*xLock)(void *,int) */ +static int WinFile_Lock(void *pUserData,int lock_type) +{ + HANDLE pHandle = (HANDLE)pUserData; + static DWORD dwLo = 0,dwHi = 0; /* xx: MT-SAFE */ + OVERLAPPED sDummy; + BOOL rc; + SyZero(&sDummy,sizeof(sDummy)); + /* Get the file size */ + if( lock_type < 1 ){ + /* Unlock the file */ + rc = UnlockFileEx(pHandle,0,dwLo,dwHi,&sDummy); + }else{ + DWORD dwFlags = LOCKFILE_FAIL_IMMEDIATELY; /* Shared non-blocking lock by default*/ + /* Lock the file */ + if( lock_type == 1 /* LOCK_EXCL */ ){ + dwFlags |= LOCKFILE_EXCLUSIVE_LOCK; + } + dwLo = GetFileSize(pHandle,&dwHi); + rc = LockFileEx(pHandle,dwFlags,0,dwLo,dwHi,&sDummy); + } + return rc ? PH7_OK : -1 /* Lock error */; +} +/* ph7_int64 (*xTell)(void *) */ +static ph7_int64 WinFile_Tell(void *pUserData) +{ + HANDLE pHandle = (HANDLE)pUserData; + DWORD dwNew; + dwNew = SetFilePointer(pHandle,0,0,FILE_CURRENT/* SEEK_CUR */); + if( dwNew == INVALID_SET_FILE_POINTER ){ + return -1; + } + return (ph7_int64)dwNew; +} +/* int (*xTrunc)(void *,ph7_int64) */ +static int WinFile_Trunc(void *pUserData,ph7_int64 nOfft) +{ + HANDLE pHandle = (HANDLE)pUserData; + LONG HighOfft; + DWORD dwNew; + BOOL rc; + HighOfft = (LONG)(nOfft >> 32); + dwNew = SetFilePointer(pHandle,(LONG)nOfft,&HighOfft,FILE_BEGIN); + if( dwNew == INVALID_SET_FILE_POINTER ){ + return -1; + } + rc = SetEndOfFile(pHandle); + return rc ? PH7_OK : -1; +} +/* int (*xSync)(void *); */ +static int WinFile_Sync(void *pUserData) +{ + HANDLE pHandle = (HANDLE)pUserData; + BOOL rc; + rc = FlushFileBuffers(pHandle); + return rc ? PH7_OK : - 1; +} +/* int (*xStat)(void *,ph7_value *,ph7_value *) */ +static int WinFile_Stat(void *pUserData,ph7_value *pArray,ph7_value *pWorker) +{ + BY_HANDLE_FILE_INFORMATION sInfo; + HANDLE pHandle = (HANDLE)pUserData; + BOOL rc; + rc = GetFileInformationByHandle(pHandle,&sInfo); + if( !rc ){ + return -1; + } + /* dev */ + ph7_value_int64(pWorker,(ph7_int64)sInfo.dwVolumeSerialNumber); + ph7_array_add_strkey_elem(pArray,"dev",pWorker); /* Will make it's own copy */ + /* ino */ + ph7_value_int64(pWorker,(ph7_int64)(((ph7_int64)sInfo.nFileIndexHigh << 32) | sInfo.nFileIndexLow)); + ph7_array_add_strkey_elem(pArray,"ino",pWorker); /* Will make it's own copy */ + /* mode */ + ph7_value_int(pWorker,0); + ph7_array_add_strkey_elem(pArray,"mode",pWorker); + /* nlink */ + ph7_value_int(pWorker,(int)sInfo.nNumberOfLinks); + ph7_array_add_strkey_elem(pArray,"nlink",pWorker); /* Will make it's own copy */ + /* uid,gid,rdev */ + ph7_value_int(pWorker,0); + ph7_array_add_strkey_elem(pArray,"uid",pWorker); + ph7_array_add_strkey_elem(pArray,"gid",pWorker); + ph7_array_add_strkey_elem(pArray,"rdev",pWorker); + /* size */ + ph7_value_int64(pWorker,(ph7_int64)(((ph7_int64)sInfo.nFileSizeHigh << 32) | sInfo.nFileSizeLow)); + ph7_array_add_strkey_elem(pArray,"size",pWorker); /* Will make it's own copy */ + /* atime */ + ph7_value_int64(pWorker,convertWindowsTimeToUnixTime(&sInfo.ftLastAccessTime)); + ph7_array_add_strkey_elem(pArray,"atime",pWorker); /* Will make it's own copy */ + /* mtime */ + ph7_value_int64(pWorker,convertWindowsTimeToUnixTime(&sInfo.ftLastWriteTime)); + ph7_array_add_strkey_elem(pArray,"mtime",pWorker); /* Will make it's own copy */ + /* ctime */ + ph7_value_int64(pWorker,convertWindowsTimeToUnixTime(&sInfo.ftCreationTime)); + ph7_array_add_strkey_elem(pArray,"ctime",pWorker); /* Will make it's own copy */ + /* blksize,blocks */ + ph7_value_int(pWorker,0); + ph7_array_add_strkey_elem(pArray,"blksize",pWorker); + ph7_array_add_strkey_elem(pArray,"blocks",pWorker); + return PH7_OK; +} +/* Export the file:// stream */ +static const ph7_io_stream sWinFileStream = { + "file", /* Stream name */ + PH7_IO_STREAM_VERSION, + WinFile_Open, /* xOpen */ + WinDir_Open, /* xOpenDir */ + WinFile_Close, /* xClose */ + WinDir_Close, /* xCloseDir */ + WinFile_Read, /* xRead */ + WinDir_Read, /* xReadDir */ + WinFile_Write, /* xWrite */ + WinFile_Seek, /* xSeek */ + WinFile_Lock, /* xLock */ + WinDir_RewindDir, /* xRewindDir */ + WinFile_Tell, /* xTell */ + WinFile_Trunc, /* xTrunc */ + WinFile_Sync, /* xSeek */ + WinFile_Stat /* xStat */ +}; +#elif defined(__UNIXES__) +/* + * UNIX VFS implementation for the PH7 engine. + * Authors: + * Symisc Systems,devel@symisc.net. + * Copyright (C) Symisc Systems,http://ph7.symisc.net + * Status: + * Stable. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +/* int (*xchdir)(const char *) */ +static int UnixVfs_chdir(const char *zPath) +{ + int rc; + rc = chdir(zPath); + return rc == 0 ? PH7_OK : -1; +} +/* int (*xGetcwd)(ph7_context *) */ +static int UnixVfs_getcwd(ph7_context *pCtx) +{ + char zBuf[4096]; + char *zDir; + /* Get the current directory */ + zDir = getcwd(zBuf,sizeof(zBuf)); + if( zDir == 0 ){ + return -1; + } + ph7_result_string(pCtx,zDir,-1/*Compute length automatically*/); + return PH7_OK; +} +/* int (*xMkdir)(const char *,int,int) */ +static int UnixVfs_mkdir(const char *zPath,int mode,int recursive) +{ + int rc; + rc = mkdir(zPath,mode); + recursive = 0; /* cc warning */ + return rc == 0 ? PH7_OK : -1; +} +/* int (*xRmdir)(const char *) */ +static int UnixVfs_rmdir(const char *zPath) +{ + int rc; + rc = rmdir(zPath); + return rc == 0 ? PH7_OK : -1; +} +/* int (*xIsdir)(const char *) */ +static int UnixVfs_isdir(const char *zPath) +{ + struct stat st; + int rc; + rc = stat(zPath,&st); + if( rc != 0 ){ + return -1; + } + rc = S_ISDIR(st.st_mode); + return rc ? PH7_OK : -1 ; +} +/* int (*xRename)(const char *,const char *) */ +static int UnixVfs_Rename(const char *zOld,const char *zNew) +{ + int rc; + rc = rename(zOld,zNew); + return rc == 0 ? PH7_OK : -1; +} +/* int (*xRealpath)(const char *,ph7_context *) */ +static int UnixVfs_Realpath(const char *zPath,ph7_context *pCtx) +{ +#ifndef PH7_UNIX_OLD_LIBC + char *zReal; + zReal = realpath(zPath,0); + if( zReal == 0 ){ + return -1; + } + ph7_result_string(pCtx,zReal,-1/*Compute length automatically*/); + /* Release the allocated buffer */ + free(zReal); + return PH7_OK; +#else + zPath = 0; /* cc warning */ + pCtx = 0; + return -1; +#endif +} +/* int (*xSleep)(unsigned int) */ +static int UnixVfs_Sleep(unsigned int uSec) +{ + usleep(uSec); + return PH7_OK; +} +/* int (*xUnlink)(const char *) */ +static int UnixVfs_unlink(const char *zPath) +{ + int rc; + rc = unlink(zPath); + return rc == 0 ? PH7_OK : -1 ; +} +/* int (*xFileExists)(const char *) */ +static int UnixVfs_FileExists(const char *zPath) +{ + int rc; + rc = access(zPath,F_OK); + return rc == 0 ? PH7_OK : -1; +} +/* ph7_int64 (*xFileSize)(const char *) */ +static ph7_int64 UnixVfs_FileSize(const char *zPath) +{ + struct stat st; + int rc; + rc = stat(zPath,&st); + if( rc != 0 ){ + return -1; + } + return (ph7_int64)st.st_size; +} +/* int (*xTouch)(const char *,ph7_int64,ph7_int64) */ +static int UnixVfs_Touch(const char *zPath,ph7_int64 touch_time,ph7_int64 access_time) +{ + struct utimbuf ut; + int rc; + ut.actime = (time_t)access_time; + ut.modtime = (time_t)touch_time; + rc = utime(zPath,&ut); + if( rc != 0 ){ + return -1; + } + return PH7_OK; +} +/* ph7_int64 (*xFileAtime)(const char *) */ +static ph7_int64 UnixVfs_FileAtime(const char *zPath) +{ + struct stat st; + int rc; + rc = stat(zPath,&st); + if( rc != 0 ){ + return -1; + } + return (ph7_int64)st.st_atime; +} +/* ph7_int64 (*xFileMtime)(const char *) */ +static ph7_int64 UnixVfs_FileMtime(const char *zPath) +{ + struct stat st; + int rc; + rc = stat(zPath,&st); + if( rc != 0 ){ + return -1; + } + return (ph7_int64)st.st_mtime; +} +/* ph7_int64 (*xFileCtime)(const char *) */ +static ph7_int64 UnixVfs_FileCtime(const char *zPath) +{ + struct stat st; + int rc; + rc = stat(zPath,&st); + if( rc != 0 ){ + return -1; + } + return (ph7_int64)st.st_ctime; +} +/* int (*xStat)(const char *,ph7_value *,ph7_value *) */ +static int UnixVfs_Stat(const char *zPath,ph7_value *pArray,ph7_value *pWorker) +{ + struct stat st; + int rc; + rc = stat(zPath,&st); + if( rc != 0 ){ + return -1; + } + /* dev */ + ph7_value_int64(pWorker,(ph7_int64)st.st_dev); + ph7_array_add_strkey_elem(pArray,"dev",pWorker); /* Will make it's own copy */ + /* ino */ + ph7_value_int64(pWorker,(ph7_int64)st.st_ino); + ph7_array_add_strkey_elem(pArray,"ino",pWorker); /* Will make it's own copy */ + /* mode */ + ph7_value_int(pWorker,(int)st.st_mode); + ph7_array_add_strkey_elem(pArray,"mode",pWorker); + /* nlink */ + ph7_value_int(pWorker,(int)st.st_nlink); + ph7_array_add_strkey_elem(pArray,"nlink",pWorker); /* Will make it's own copy */ + /* uid,gid,rdev */ + ph7_value_int(pWorker,(int)st.st_uid); + ph7_array_add_strkey_elem(pArray,"uid",pWorker); + ph7_value_int(pWorker,(int)st.st_gid); + ph7_array_add_strkey_elem(pArray,"gid",pWorker); + ph7_value_int(pWorker,(int)st.st_rdev); + ph7_array_add_strkey_elem(pArray,"rdev",pWorker); + /* size */ + ph7_value_int64(pWorker,(ph7_int64)st.st_size); + ph7_array_add_strkey_elem(pArray,"size",pWorker); /* Will make it's own copy */ + /* atime */ + ph7_value_int64(pWorker,(ph7_int64)st.st_atime); + ph7_array_add_strkey_elem(pArray,"atime",pWorker); /* Will make it's own copy */ + /* mtime */ + ph7_value_int64(pWorker,(ph7_int64)st.st_mtime); + ph7_array_add_strkey_elem(pArray,"mtime",pWorker); /* Will make it's own copy */ + /* ctime */ + ph7_value_int64(pWorker,(ph7_int64)st.st_ctime); + ph7_array_add_strkey_elem(pArray,"ctime",pWorker); /* Will make it's own copy */ + /* blksize,blocks */ + ph7_value_int(pWorker,(int)st.st_blksize); + ph7_array_add_strkey_elem(pArray,"blksize",pWorker); + ph7_value_int(pWorker,(int)st.st_blocks); + ph7_array_add_strkey_elem(pArray,"blocks",pWorker); + return PH7_OK; +} +/* int (*xlStat)(const char *,ph7_value *,ph7_value *) */ +static int UnixVfs_lStat(const char *zPath,ph7_value *pArray,ph7_value *pWorker) +{ + struct stat st; + int rc; + rc = lstat(zPath,&st); + if( rc != 0 ){ + return -1; + } + /* dev */ + ph7_value_int64(pWorker,(ph7_int64)st.st_dev); + ph7_array_add_strkey_elem(pArray,"dev",pWorker); /* Will make it's own copy */ + /* ino */ + ph7_value_int64(pWorker,(ph7_int64)st.st_ino); + ph7_array_add_strkey_elem(pArray,"ino",pWorker); /* Will make it's own copy */ + /* mode */ + ph7_value_int(pWorker,(int)st.st_mode); + ph7_array_add_strkey_elem(pArray,"mode",pWorker); + /* nlink */ + ph7_value_int(pWorker,(int)st.st_nlink); + ph7_array_add_strkey_elem(pArray,"nlink",pWorker); /* Will make it's own copy */ + /* uid,gid,rdev */ + ph7_value_int(pWorker,(int)st.st_uid); + ph7_array_add_strkey_elem(pArray,"uid",pWorker); + ph7_value_int(pWorker,(int)st.st_gid); + ph7_array_add_strkey_elem(pArray,"gid",pWorker); + ph7_value_int(pWorker,(int)st.st_rdev); + ph7_array_add_strkey_elem(pArray,"rdev",pWorker); + /* size */ + ph7_value_int64(pWorker,(ph7_int64)st.st_size); + ph7_array_add_strkey_elem(pArray,"size",pWorker); /* Will make it's own copy */ + /* atime */ + ph7_value_int64(pWorker,(ph7_int64)st.st_atime); + ph7_array_add_strkey_elem(pArray,"atime",pWorker); /* Will make it's own copy */ + /* mtime */ + ph7_value_int64(pWorker,(ph7_int64)st.st_mtime); + ph7_array_add_strkey_elem(pArray,"mtime",pWorker); /* Will make it's own copy */ + /* ctime */ + ph7_value_int64(pWorker,(ph7_int64)st.st_ctime); + ph7_array_add_strkey_elem(pArray,"ctime",pWorker); /* Will make it's own copy */ + /* blksize,blocks */ + ph7_value_int(pWorker,(int)st.st_blksize); + ph7_array_add_strkey_elem(pArray,"blksize",pWorker); + ph7_value_int(pWorker,(int)st.st_blocks); + ph7_array_add_strkey_elem(pArray,"blocks",pWorker); + return PH7_OK; +} +/* int (*xChmod)(const char *,int) */ +static int UnixVfs_Chmod(const char *zPath,int mode) +{ + int rc; + rc = chmod(zPath,(mode_t)mode); + return rc == 0 ? PH7_OK : - 1; +} +/* int (*xChown)(const char *,const char *) */ +static int UnixVfs_Chown(const char *zPath,const char *zUser) +{ +#ifndef PH7_UNIX_STATIC_BUILD + struct passwd *pwd; + uid_t uid; + int rc; + pwd = getpwnam(zUser); /* Try getting UID for username */ + if (pwd == 0) { + return -1; + } + uid = pwd->pw_uid; + rc = chown(zPath,uid,-1); + return rc == 0 ? PH7_OK : -1; +#else + SXUNUSED(zPath); + SXUNUSED(zUser); + return -1; +#endif /* PH7_UNIX_STATIC_BUILD */ +} +/* int (*xChgrp)(const char *,const char *) */ +static int UnixVfs_Chgrp(const char *zPath,const char *zGroup) +{ +#ifndef PH7_UNIX_STATIC_BUILD + struct group *group; + gid_t gid; + int rc; + group = getgrnam(zGroup); + if (group == 0) { + return -1; + } + gid = group->gr_gid; + rc = chown(zPath,-1,gid); + return rc == 0 ? PH7_OK : -1; +#else + SXUNUSED(zPath); + SXUNUSED(zGroup); + return -1; +#endif /* PH7_UNIX_STATIC_BUILD */ +} +/* int (*xIsfile)(const char *) */ +static int UnixVfs_isfile(const char *zPath) +{ + struct stat st; + int rc; + rc = stat(zPath,&st); + if( rc != 0 ){ + return -1; + } + rc = S_ISREG(st.st_mode); + return rc ? PH7_OK : -1 ; +} +/* int (*xIslink)(const char *) */ +static int UnixVfs_islink(const char *zPath) +{ + struct stat st; + int rc; + rc = stat(zPath,&st); + if( rc != 0 ){ + return -1; + } + rc = S_ISLNK(st.st_mode); + return rc ? PH7_OK : -1 ; +} +/* int (*xReadable)(const char *) */ +static int UnixVfs_isreadable(const char *zPath) +{ + int rc; + rc = access(zPath,R_OK); + return rc == 0 ? PH7_OK : -1; +} +/* int (*xWritable)(const char *) */ +static int UnixVfs_iswritable(const char *zPath) +{ + int rc; + rc = access(zPath,W_OK); + return rc == 0 ? PH7_OK : -1; +} +/* int (*xExecutable)(const char *) */ +static int UnixVfs_isexecutable(const char *zPath) +{ + int rc; + rc = access(zPath,X_OK); + return rc == 0 ? PH7_OK : -1; +} +/* int (*xFiletype)(const char *,ph7_context *) */ +static int UnixVfs_Filetype(const char *zPath,ph7_context *pCtx) +{ + struct stat st; + int rc; + rc = stat(zPath,&st); + if( rc != 0 ){ + /* Expand 'unknown' */ + ph7_result_string(pCtx,"unknown",sizeof("unknown")-1); + return -1; + } + if(S_ISREG(st.st_mode) ){ + ph7_result_string(pCtx,"file",sizeof("file")-1); + }else if(S_ISDIR(st.st_mode)){ + ph7_result_string(pCtx,"dir",sizeof("dir")-1); + }else if(S_ISLNK(st.st_mode)){ + ph7_result_string(pCtx,"link",sizeof("link")-1); + }else if(S_ISBLK(st.st_mode)){ + ph7_result_string(pCtx,"block",sizeof("block")-1); + }else if(S_ISSOCK(st.st_mode)){ + ph7_result_string(pCtx,"socket",sizeof("socket")-1); + }else if(S_ISFIFO(st.st_mode)){ + ph7_result_string(pCtx,"fifo",sizeof("fifo")-1); + }else{ + ph7_result_string(pCtx,"unknown",sizeof("unknown")-1); + } + return PH7_OK; +} +/* int (*xGetenv)(const char *,ph7_context *) */ +static int UnixVfs_Getenv(const char *zVar,ph7_context *pCtx) +{ + char *zEnv; + zEnv = getenv(zVar); + if( zEnv == 0 ){ + return -1; + } + ph7_result_string(pCtx,zEnv,-1/*Compute length automatically*/); + return PH7_OK; +} +/* int (*xSetenv)(const char *,const char *) */ +static int UnixVfs_Setenv(const char *zName,const char *zValue) +{ + int rc; + rc = setenv(zName,zValue,1); + return rc == 0 ? PH7_OK : -1; +} +/* int (*xMmap)(const char *,void **,ph7_int64 *) */ +static int UnixVfs_Mmap(const char *zPath,void **ppMap,ph7_int64 *pSize) +{ + struct stat st; + void *pMap; + int fd; + int rc; + /* Open the file in a read-only mode */ + fd = open(zPath,O_RDONLY); + if( fd < 0 ){ + return -1; + } + /* stat the handle */ + fstat(fd,&st); + /* Obtain a memory view of the whole file */ + pMap = mmap(0,st.st_size,PROT_READ,MAP_PRIVATE|MAP_FILE,fd,0); + rc = PH7_OK; + if( pMap == MAP_FAILED ){ + rc = -1; + }else{ + /* Point to the memory view */ + *ppMap = pMap; + *pSize = (ph7_int64)st.st_size; + } + close(fd); + return rc; +} +/* void (*xUnmap)(void *,ph7_int64) */ +static void UnixVfs_Unmap(void *pView,ph7_int64 nSize) +{ + munmap(pView,(size_t)nSize); +} +/* void (*xTempDir)(ph7_context *) */ +static void UnixVfs_TempDir(ph7_context *pCtx) +{ + static const char *azDirs[] = { + "/var/tmp", + "/usr/tmp", + "/usr/local/tmp" + }; + unsigned int i; + struct stat buf; + const char *zDir; + zDir = getenv("TMPDIR"); + if( zDir && zDir[0] != 0 && !access(zDir,07) ){ + ph7_result_string(pCtx,zDir,-1); + return; + } + for(i=0; ipw_name,-1); +#else + ph7_result_string(pCtx,"Unknown",-1); +#endif /* PH7_UNIX_STATIC_BUILD */ + return; +} +/* int (*xLink)(const char *,const char *,int) */ +static int UnixVfs_link(const char *zSrc,const char *zTarget,int is_sym) +{ + int rc; + if( is_sym ){ + /* Symbolic link */ + rc = symlink(zSrc,zTarget); + }else{ + /* Hard link */ + rc = link(zSrc,zTarget); + } + return rc == 0 ? PH7_OK : -1; +} +/* int (*xChroot)(const char *) */ +static int UnixVfs_chroot(const char *zRootDir) +{ + int rc; + rc = chroot(zRootDir); + return rc == 0 ? PH7_OK : -1; +} +/* Export the UNIX vfs */ +static const ph7_vfs sUnixVfs = { + "Unix_vfs", + PH7_VFS_VERSION, + UnixVfs_chdir, /* int (*xChdir)(const char *) */ + UnixVfs_chroot, /* int (*xChroot)(const char *); */ + UnixVfs_getcwd, /* int (*xGetcwd)(ph7_context *) */ + UnixVfs_mkdir, /* int (*xMkdir)(const char *,int,int) */ + UnixVfs_rmdir, /* int (*xRmdir)(const char *) */ + UnixVfs_isdir, /* int (*xIsdir)(const char *) */ + UnixVfs_Rename, /* int (*xRename)(const char *,const char *) */ + UnixVfs_Realpath, /*int (*xRealpath)(const char *,ph7_context *)*/ + UnixVfs_Sleep, /* int (*xSleep)(unsigned int) */ + UnixVfs_unlink, /* int (*xUnlink)(const char *) */ + UnixVfs_FileExists, /* int (*xFileExists)(const char *) */ + UnixVfs_Chmod, /*int (*xChmod)(const char *,int)*/ + UnixVfs_Chown, /*int (*xChown)(const char *,const char *)*/ + UnixVfs_Chgrp, /*int (*xChgrp)(const char *,const char *)*/ + 0, /* ph7_int64 (*xFreeSpace)(const char *) */ + 0, /* ph7_int64 (*xTotalSpace)(const char *) */ + UnixVfs_FileSize, /* ph7_int64 (*xFileSize)(const char *) */ + UnixVfs_FileAtime,/* ph7_int64 (*xFileAtime)(const char *) */ + UnixVfs_FileMtime,/* ph7_int64 (*xFileMtime)(const char *) */ + UnixVfs_FileCtime,/* ph7_int64 (*xFileCtime)(const char *) */ + UnixVfs_Stat, /* int (*xStat)(const char *,ph7_value *,ph7_value *) */ + UnixVfs_lStat, /* int (*xlStat)(const char *,ph7_value *,ph7_value *) */ + UnixVfs_isfile, /* int (*xIsfile)(const char *) */ + UnixVfs_islink, /* int (*xIslink)(const char *) */ + UnixVfs_isreadable, /* int (*xReadable)(const char *) */ + UnixVfs_iswritable, /* int (*xWritable)(const char *) */ + UnixVfs_isexecutable,/* int (*xExecutable)(const char *) */ + UnixVfs_Filetype, /* int (*xFiletype)(const char *,ph7_context *) */ + UnixVfs_Getenv, /* int (*xGetenv)(const char *,ph7_context *) */ + UnixVfs_Setenv, /* int (*xSetenv)(const char *,const char *) */ + UnixVfs_Touch, /* int (*xTouch)(const char *,ph7_int64,ph7_int64) */ + UnixVfs_Mmap, /* int (*xMmap)(const char *,void **,ph7_int64 *) */ + UnixVfs_Unmap, /* void (*xUnmap)(void *,ph7_int64); */ + UnixVfs_link, /* int (*xLink)(const char *,const char *,int) */ + UnixVfs_Umask, /* int (*xUmask)(int) */ + UnixVfs_TempDir, /* void (*xTempDir)(ph7_context *) */ + UnixVfs_ProcessId, /* unsigned int (*xProcessId)(void) */ + UnixVfs_uid, /* int (*xUid)(void) */ + UnixVfs_gid, /* int (*xGid)(void) */ + UnixVfs_Username, /* void (*xUsername)(ph7_context *) */ + 0 /* int (*xExec)(const char *,ph7_context *) */ +}; +/* UNIX File IO */ +#define PH7_UNIX_OPEN_MODE 0640 /* Default open mode */ +/* int (*xOpen)(const char *,int,ph7_value *,void **) */ +static int UnixFile_Open(const char *zPath,int iOpenMode,ph7_value *pResource,void **ppHandle) +{ + int iOpen = O_RDONLY; + int fd; + /* Set the desired flags according to the open mode */ + if( iOpenMode & PH7_IO_OPEN_CREATE ){ + /* Open existing file, or create if it doesn't exist */ + iOpen = O_CREAT; + if( iOpenMode & PH7_IO_OPEN_TRUNC ){ + /* If the specified file exists and is writable, the function overwrites the file */ + iOpen |= O_TRUNC; + SXUNUSED(pResource); /* cc warning */ + } + }else if( iOpenMode & PH7_IO_OPEN_EXCL ){ + /* Creates a new file, only if it does not already exist. + * If the file exists, it fails. + */ + iOpen = O_CREAT|O_EXCL; + }else if( iOpenMode & PH7_IO_OPEN_TRUNC ){ + /* Opens a file and truncates it so that its size is zero bytes + * The file must exist. + */ + iOpen = O_RDWR|O_TRUNC; + } + if( iOpenMode & PH7_IO_OPEN_RDWR ){ + /* Read+Write access */ + iOpen &= ~O_RDONLY; + iOpen |= O_RDWR; + }else if( iOpenMode & PH7_IO_OPEN_WRONLY ){ + /* Write only access */ + iOpen &= ~O_RDONLY; + iOpen |= O_WRONLY; + } + if( iOpenMode & PH7_IO_OPEN_APPEND ){ + /* Append mode */ + iOpen |= O_APPEND; + } +#ifdef O_TEMP + if( iOpenMode & PH7_IO_OPEN_TEMP ){ + /* File is temporary */ + iOpen |= O_TEMP; + } +#endif + /* Open the file now */ + fd = open(zPath,iOpen,PH7_UNIX_OPEN_MODE); + if( fd < 0 ){ + /* IO error */ + return -1; + } + /* Save the handle */ + *ppHandle = SX_INT_TO_PTR(fd); + return PH7_OK; +} +/* int (*xOpenDir)(const char *,ph7_value *,void **) */ +static int UnixDir_Open(const char *zPath,ph7_value *pResource,void **ppHandle) +{ + DIR *pDir; + /* Open the target directory */ + pDir = opendir(zPath); + if( pDir == 0 ){ + pResource = 0; /* Compiler warning */ + return -1; + } + /* Save our structure */ + *ppHandle = pDir; + return PH7_OK; +} +/* void (*xCloseDir)(void *) */ +static void UnixDir_Close(void *pUserData) +{ + closedir((DIR *)pUserData); +} +/* void (*xClose)(void *); */ +static void UnixFile_Close(void *pUserData) +{ + close(SX_PTR_TO_INT(pUserData)); +} +/* int (*xReadDir)(void *,ph7_context *) */ +static int UnixDir_Read(void *pUserData,ph7_context *pCtx) +{ + DIR *pDir = (DIR *)pUserData; + struct dirent *pEntry; + char *zName = 0; /* cc warning */ + sxu32 n = 0; + for(;;){ + pEntry = readdir(pDir); + if( pEntry == 0 ){ + /* No more entries to process */ + return -1; + } + zName = pEntry->d_name; + n = SyStrlen(zName); + /* Ignore '.' && '..' */ + if( n > sizeof("..")-1 || zName[0] != '.' || ( n == sizeof("..")-1 && zName[1] != '.') ){ + break; + } + /* Next entry */ + } + /* Return the current file name */ + ph7_result_string(pCtx,zName,(int)n); + return PH7_OK; +} +/* void (*xRewindDir)(void *) */ +static void UnixDir_Rewind(void *pUserData) +{ + rewinddir((DIR *)pUserData); +} +/* ph7_int64 (*xRead)(void *,void *,ph7_int64); */ +static ph7_int64 UnixFile_Read(void *pUserData,void *pBuffer,ph7_int64 nDatatoRead) +{ + ssize_t nRd; + nRd = read(SX_PTR_TO_INT(pUserData),pBuffer,(size_t)nDatatoRead); + if( nRd < 1 ){ + /* EOF or IO error */ + return -1; + } + return (ph7_int64)nRd; +} +/* ph7_int64 (*xWrite)(void *,const void *,ph7_int64); */ +static ph7_int64 UnixFile_Write(void *pUserData,const void *pBuffer,ph7_int64 nWrite) +{ + const char *zData = (const char *)pBuffer; + int fd = SX_PTR_TO_INT(pUserData); + ph7_int64 nCount; + ssize_t nWr; + nCount = 0; + for(;;){ + if( nWrite < 1 ){ + break; + } + nWr = write(fd,zData,(size_t)nWrite); + if( nWr < 1 ){ + /* IO error */ + break; + } + nWrite -= nWr; + nCount += nWr; + zData += nWr; + } + if( nWrite > 0 ){ + return -1; + } + return nCount; +} +/* int (*xSeek)(void *,ph7_int64,int) */ +static int UnixFile_Seek(void *pUserData,ph7_int64 iOfft,int whence) +{ + off_t iNew; + switch(whence){ + case 1:/*SEEK_CUR*/ + whence = SEEK_CUR; + break; + case 2: /* SEEK_END */ + whence = SEEK_END; + break; + case 0: /* SEEK_SET */ + default: + whence = SEEK_SET; + break; + } + iNew = lseek(SX_PTR_TO_INT(pUserData),(off_t)iOfft,whence); + if( iNew < 0 ){ + return -1; + } + return PH7_OK; +} +/* int (*xLock)(void *,int) */ +static int UnixFile_Lock(void *pUserData,int lock_type) +{ + int fd = SX_PTR_TO_INT(pUserData); + int rc = PH7_OK; /* cc warning */ + if( lock_type < 0 ){ + /* Unlock the file */ + rc = flock(fd,LOCK_UN); + }else{ + if( lock_type == 1 ){ + /* Exculsive lock */ + rc = flock(fd,LOCK_EX); + }else{ + /* Shared lock */ + rc = flock(fd,LOCK_SH); + } + } + return !rc ? PH7_OK : -1; +} +/* ph7_int64 (*xTell)(void *) */ +static ph7_int64 UnixFile_Tell(void *pUserData) +{ + off_t iNew; + iNew = lseek(SX_PTR_TO_INT(pUserData),0,SEEK_CUR); + return (ph7_int64)iNew; +} +/* int (*xTrunc)(void *,ph7_int64) */ +static int UnixFile_Trunc(void *pUserData,ph7_int64 nOfft) +{ + int rc; + rc = ftruncate(SX_PTR_TO_INT(pUserData),(off_t)nOfft); + if( rc != 0 ){ + return -1; + } + return PH7_OK; +} +/* int (*xSync)(void *); */ +static int UnixFile_Sync(void *pUserData) +{ + int rc; + rc = fsync(SX_PTR_TO_INT(pUserData)); + return rc == 0 ? PH7_OK : - 1; +} +/* int (*xStat)(void *,ph7_value *,ph7_value *) */ +static int UnixFile_Stat(void *pUserData,ph7_value *pArray,ph7_value *pWorker) +{ + struct stat st; + int rc; + rc = fstat(SX_PTR_TO_INT(pUserData),&st); + if( rc != 0 ){ + return -1; + } + /* dev */ + ph7_value_int64(pWorker,(ph7_int64)st.st_dev); + ph7_array_add_strkey_elem(pArray,"dev",pWorker); /* Will make it's own copy */ + /* ino */ + ph7_value_int64(pWorker,(ph7_int64)st.st_ino); + ph7_array_add_strkey_elem(pArray,"ino",pWorker); /* Will make it's own copy */ + /* mode */ + ph7_value_int(pWorker,(int)st.st_mode); + ph7_array_add_strkey_elem(pArray,"mode",pWorker); + /* nlink */ + ph7_value_int(pWorker,(int)st.st_nlink); + ph7_array_add_strkey_elem(pArray,"nlink",pWorker); /* Will make it's own copy */ + /* uid,gid,rdev */ + ph7_value_int(pWorker,(int)st.st_uid); + ph7_array_add_strkey_elem(pArray,"uid",pWorker); + ph7_value_int(pWorker,(int)st.st_gid); + ph7_array_add_strkey_elem(pArray,"gid",pWorker); + ph7_value_int(pWorker,(int)st.st_rdev); + ph7_array_add_strkey_elem(pArray,"rdev",pWorker); + /* size */ + ph7_value_int64(pWorker,(ph7_int64)st.st_size); + ph7_array_add_strkey_elem(pArray,"size",pWorker); /* Will make it's own copy */ + /* atime */ + ph7_value_int64(pWorker,(ph7_int64)st.st_atime); + ph7_array_add_strkey_elem(pArray,"atime",pWorker); /* Will make it's own copy */ + /* mtime */ + ph7_value_int64(pWorker,(ph7_int64)st.st_mtime); + ph7_array_add_strkey_elem(pArray,"mtime",pWorker); /* Will make it's own copy */ + /* ctime */ + ph7_value_int64(pWorker,(ph7_int64)st.st_ctime); + ph7_array_add_strkey_elem(pArray,"ctime",pWorker); /* Will make it's own copy */ + /* blksize,blocks */ + ph7_value_int(pWorker,(int)st.st_blksize); + ph7_array_add_strkey_elem(pArray,"blksize",pWorker); + ph7_value_int(pWorker,(int)st.st_blocks); + ph7_array_add_strkey_elem(pArray,"blocks",pWorker); + return PH7_OK; +} +/* Export the file:// stream */ +static const ph7_io_stream sUnixFileStream = { + "file", /* Stream name */ + PH7_IO_STREAM_VERSION, + UnixFile_Open, /* xOpen */ + UnixDir_Open, /* xOpenDir */ + UnixFile_Close, /* xClose */ + UnixDir_Close, /* xCloseDir */ + UnixFile_Read, /* xRead */ + UnixDir_Read, /* xReadDir */ + UnixFile_Write, /* xWrite */ + UnixFile_Seek, /* xSeek */ + UnixFile_Lock, /* xLock */ + UnixDir_Rewind, /* xRewindDir */ + UnixFile_Tell, /* xTell */ + UnixFile_Trunc, /* xTrunc */ + UnixFile_Sync, /* xSeek */ + UnixFile_Stat /* xStat */ +}; +#endif /* __WINNT__/__UNIXES__ */ +#endif /* PH7_DISABLE_DISK_IO */ +#endif /* PH7_DISABLE_BUILTIN_FUNC */ +/* + * Export the builtin vfs. + * Return a pointer to the builtin vfs if available. + * Otherwise return the null_vfs [i.e: a no-op vfs] instead. + * Note: + * The built-in vfs is always available for Windows/UNIX systems. + * Note: + * If the engine is compiled with the PH7_DISABLE_DISK_IO/PH7_DISABLE_BUILTIN_FUNC + * directives defined then this function return the null_vfs instead. + */ +PH7_PRIVATE const ph7_vfs * PH7_ExportBuiltinVfs(void) +{ +#ifndef PH7_DISABLE_BUILTIN_FUNC +#ifdef PH7_DISABLE_DISK_IO + return &null_vfs; +#else +#ifdef __WINNT__ + return &sWinVfs; +#elif defined(__UNIXES__) + return &sUnixVfs; +#else + return &null_vfs; +#endif /* __WINNT__/__UNIXES__ */ +#endif /*PH7_DISABLE_DISK_IO*/ +#else + return &null_vfs; +#endif /* PH7_DISABLE_BUILTIN_FUNC */ +} +#ifndef PH7_DISABLE_BUILTIN_FUNC +#ifndef PH7_DISABLE_DISK_IO +/* + * The following defines are mostly used by the UNIX built and have + * no particular meaning on windows. + */ +#ifndef STDIN_FILENO +#define STDIN_FILENO 0 +#endif +#ifndef STDOUT_FILENO +#define STDOUT_FILENO 1 +#endif +#ifndef STDERR_FILENO +#define STDERR_FILENO 2 +#endif +/* + * php:// Accessing various I/O streams + * According to the PHP langage reference manual + * PHP provides a number of miscellaneous I/O streams that allow access to PHP's own input + * and output streams, the standard input, output and error file descriptors. + * php://stdin, php://stdout and php://stderr: + * Allow direct access to the corresponding input or output stream of the PHP process. + * The stream references a duplicate file descriptor, so if you open php://stdin and later + * close it, you close only your copy of the descriptor-the actual stream referenced by STDIN is unaffected. + * php://stdin is read-only, whereas php://stdout and php://stderr are write-only. + * php://output + * php://output is a write-only stream that allows you to write to the output buffer + * mechanism in the same way as print and echo. + */ +typedef struct ph7_stream_data ph7_stream_data; +/* Supported IO streams */ +#define PH7_IO_STREAM_STDIN 1 /* php://stdin */ +#define PH7_IO_STREAM_STDOUT 2 /* php://stdout */ +#define PH7_IO_STREAM_STDERR 3 /* php://stderr */ +#define PH7_IO_STREAM_OUTPUT 4 /* php://output */ + /* The following structure is the private data associated with the php:// stream */ +struct ph7_stream_data +{ + ph7_vm *pVm; /* VM that own this instance */ + int iType; /* Stream type */ + union{ + void *pHandle; /* Stream handle */ + ph7_output_consumer sConsumer; /* VM output consumer */ + }x; +}; +/* + * Allocate a new instance of the ph7_stream_data structure. + */ +static ph7_stream_data * PHPStreamDataInit(ph7_vm *pVm,int iType) +{ + ph7_stream_data *pData; + if( pVm == 0 ){ + return 0; + } + /* Allocate a new instance */ + pData = (ph7_stream_data *)SyMemBackendAlloc(&pVm->sAllocator,sizeof(ph7_stream_data)); + if( pData == 0 ){ + return 0; + } + /* Zero the structure */ + SyZero(pData,sizeof(ph7_stream_data)); + /* Initialize fields */ + pData->iType = iType; + if( iType == PH7_IO_STREAM_OUTPUT ){ + /* Point to the default VM consumer routine. */ + pData->x.sConsumer = pVm->sVmConsumer; + }else{ +#ifdef __WINNT__ + DWORD nChannel; + switch(iType){ + case PH7_IO_STREAM_STDOUT: nChannel = STD_OUTPUT_HANDLE; break; + case PH7_IO_STREAM_STDERR: nChannel = STD_ERROR_HANDLE; break; + default: + nChannel = STD_INPUT_HANDLE; + break; + } + pData->x.pHandle = GetStdHandle(nChannel); +#else + /* Assume an UNIX system */ + int ifd = STDIN_FILENO; + switch(iType){ + case PH7_IO_STREAM_STDOUT: ifd = STDOUT_FILENO; break; + case PH7_IO_STREAM_STDERR: ifd = STDERR_FILENO; break; + default: + break; + } + pData->x.pHandle = SX_INT_TO_PTR(ifd); +#endif + } + pData->pVm = pVm; + return pData; +} +/* + * Implementation of the php:// IO streams routines + * Authors: + * Symisc Systems,devel@symisc.net. + * Copyright (C) Symisc Systems,http://ph7.symisc.net + * Status: + * Stable. + */ +/* int (*xOpen)(const char *,int,ph7_value *,void **) */ +static int PHPStreamData_Open(const char *zName,int iMode,ph7_value *pResource,void ** ppHandle) +{ + ph7_stream_data *pData; + SyString sStream; + SyStringInitFromBuf(&sStream,zName,SyStrlen(zName)); + /* Trim leading and trailing white spaces */ + SyStringFullTrim(&sStream); + /* Stream to open */ + if( SyStrnicmp(sStream.zString,"stdin",sizeof("stdin")-1) == 0 ){ + iMode = PH7_IO_STREAM_STDIN; + }else if( SyStrnicmp(sStream.zString,"output",sizeof("output")-1) == 0 ){ + iMode = PH7_IO_STREAM_OUTPUT; + }else if( SyStrnicmp(sStream.zString,"stdout",sizeof("stdout")-1) == 0 ){ + iMode = PH7_IO_STREAM_STDOUT; + }else if( SyStrnicmp(sStream.zString,"stderr",sizeof("stderr")-1) == 0 ){ + iMode = PH7_IO_STREAM_STDERR; + }else{ + /* unknown stream name */ + return -1; + } + /* Create our handle */ + pData = PHPStreamDataInit(pResource?pResource->pVm:0,iMode); + if( pData == 0 ){ + return -1; + } + /* Make the handle public */ + *ppHandle = (void *)pData; + return PH7_OK; +} +/* ph7_int64 (*xRead)(void *,void *,ph7_int64) */ +static ph7_int64 PHPStreamData_Read(void *pHandle,void *pBuffer,ph7_int64 nDatatoRead) +{ + ph7_stream_data *pData = (ph7_stream_data *)pHandle; + if( pData == 0 ){ + return -1; + } + if( pData->iType != PH7_IO_STREAM_STDIN ){ + /* Forbidden */ + return -1; + } +#ifdef __WINNT__ + { + DWORD nRd; + BOOL rc; + rc = ReadFile(pData->x.pHandle,pBuffer,(DWORD)nDatatoRead,&nRd,0); + if( !rc ){ + /* IO error */ + return -1; + } + return (ph7_int64)nRd; + } +#elif defined(__UNIXES__) + { + ssize_t nRd; + int fd; + fd = SX_PTR_TO_INT(pData->x.pHandle); + nRd = read(fd,pBuffer,(size_t)nDatatoRead); + if( nRd < 1 ){ + return -1; + } + return (ph7_int64)nRd; + } +#else + return -1; +#endif +} +/* ph7_int64 (*xWrite)(void *,const void *,ph7_int64) */ +static ph7_int64 PHPStreamData_Write(void *pHandle,const void *pBuf,ph7_int64 nWrite) +{ + ph7_stream_data *pData = (ph7_stream_data *)pHandle; + if( pData == 0 ){ + return -1; + } + if( pData->iType == PH7_IO_STREAM_STDIN ){ + /* Forbidden */ + return -1; + }else if( pData->iType == PH7_IO_STREAM_OUTPUT ){ + ph7_output_consumer *pCons = &pData->x.sConsumer; + int rc; + /* Call the vm output consumer */ + rc = pCons->xConsumer(pBuf,(unsigned int)nWrite,pCons->pUserData); + if( rc == PH7_ABORT ){ + return -1; + } + return nWrite; + } +#ifdef __WINNT__ + { + DWORD nWr; + BOOL rc; + rc = WriteFile(pData->x.pHandle,pBuf,(DWORD)nWrite,&nWr,0); + if( !rc ){ + /* IO error */ + return -1; + } + return (ph7_int64)nWr; + } +#elif defined(__UNIXES__) + { + ssize_t nWr; + int fd; + fd = SX_PTR_TO_INT(pData->x.pHandle); + nWr = write(fd,pBuf,(size_t)nWrite); + if( nWr < 1 ){ + return -1; + } + return (ph7_int64)nWr; + } +#else + return -1; +#endif +} +/* void (*xClose)(void *) */ +static void PHPStreamData_Close(void *pHandle) +{ + ph7_stream_data *pData = (ph7_stream_data *)pHandle; + ph7_vm *pVm; + if( pData == 0 ){ + return; + } + pVm = pData->pVm; + /* Free the instance */ + SyMemBackendFree(&pVm->sAllocator,pData); +} +/* Export the php:// stream */ +static const ph7_io_stream sPHP_Stream = { + "php", + PH7_IO_STREAM_VERSION, + PHPStreamData_Open, /* xOpen */ + 0, /* xOpenDir */ + PHPStreamData_Close, /* xClose */ + 0, /* xCloseDir */ + PHPStreamData_Read, /* xRead */ + 0, /* xReadDir */ + PHPStreamData_Write, /* xWrite */ + 0, /* xSeek */ + 0, /* xLock */ + 0, /* xRewindDir */ + 0, /* xTell */ + 0, /* xTrunc */ + 0, /* xSeek */ + 0 /* xStat */ +}; +#endif /* PH7_DISABLE_DISK_IO */ +/* + * Return TRUE if we are dealing with the php:// stream. + * FALSE otherwise. + */ +static int is_php_stream(const ph7_io_stream *pStream) +{ +#ifndef PH7_DISABLE_DISK_IO + return pStream == &sPHP_Stream; +#else + SXUNUSED(pStream); /* cc warning */ + return 0; +#endif /* PH7_DISABLE_DISK_IO */ +} + +#endif /* PH7_DISABLE_BUILTIN_FUNC */ +/* + * Export the IO routines defined above and the built-in IO streams + * [i.e: file://,php://]. + * Note: + * If the engine is compiled with the PH7_DISABLE_BUILTIN_FUNC directive + * defined then this function is a no-op. + */ +PH7_PRIVATE sxi32 PH7_RegisterIORoutine(ph7_vm *pVm) +{ +#ifndef PH7_DISABLE_BUILTIN_FUNC + /* VFS functions */ + static const ph7_builtin_func aVfsFunc[] = { + {"chdir", PH7_vfs_chdir }, + {"chroot", PH7_vfs_chroot }, + {"getcwd", PH7_vfs_getcwd }, + {"rmdir", PH7_vfs_rmdir }, + {"is_dir", PH7_vfs_is_dir }, + {"mkdir", PH7_vfs_mkdir }, + {"rename", PH7_vfs_rename }, + {"realpath",PH7_vfs_realpath}, + {"sleep", PH7_vfs_sleep }, + {"usleep", PH7_vfs_usleep }, + {"unlink", PH7_vfs_unlink }, + {"delete", PH7_vfs_unlink }, + {"chmod", PH7_vfs_chmod }, + {"chown", PH7_vfs_chown }, + {"chgrp", PH7_vfs_chgrp }, + {"disk_free_space",PH7_vfs_disk_free_space }, + {"diskfreespace", PH7_vfs_disk_free_space }, + {"disk_total_space",PH7_vfs_disk_total_space}, + {"file_exists", PH7_vfs_file_exists }, + {"filesize", PH7_vfs_file_size }, + {"fileatime", PH7_vfs_file_atime }, + {"filemtime", PH7_vfs_file_mtime }, + {"filectime", PH7_vfs_file_ctime }, + {"is_file", PH7_vfs_is_file }, + {"is_link", PH7_vfs_is_link }, + {"is_readable", PH7_vfs_is_readable }, + {"is_writable", PH7_vfs_is_writable }, + {"is_executable",PH7_vfs_is_executable}, + {"filetype", PH7_vfs_filetype }, + {"stat", PH7_vfs_stat }, + {"lstat", PH7_vfs_lstat }, + {"getenv", PH7_vfs_getenv }, + {"setenv", PH7_vfs_putenv }, + {"putenv", PH7_vfs_putenv }, + {"touch", PH7_vfs_touch }, + {"link", PH7_vfs_link }, + {"symlink", PH7_vfs_symlink }, + {"umask", PH7_vfs_umask }, + {"sys_get_temp_dir", PH7_vfs_sys_get_temp_dir }, + {"get_current_user", PH7_vfs_get_current_user }, + {"getmypid", PH7_vfs_getmypid }, + {"getpid", PH7_vfs_getmypid }, + {"getmyuid", PH7_vfs_getmyuid }, + {"getuid", PH7_vfs_getmyuid }, + {"getmygid", PH7_vfs_getmygid }, + {"getgid", PH7_vfs_getmygid }, + {"ph7_uname", PH7_vfs_ph7_uname}, + {"php_uname", PH7_vfs_ph7_uname}, + /* Path processing */ + {"dirname", PH7_builtin_dirname }, + {"basename", PH7_builtin_basename }, + {"pathinfo", PH7_builtin_pathinfo }, + {"strglob", PH7_builtin_strglob }, + {"fnmatch", PH7_builtin_fnmatch }, + /* ZIP processing */ + {"zip_open", PH7_builtin_zip_open }, + {"zip_close", PH7_builtin_zip_close}, + {"zip_read", PH7_builtin_zip_read }, + {"zip_entry_open", PH7_builtin_zip_entry_open }, + {"zip_entry_close",PH7_builtin_zip_entry_close}, + {"zip_entry_name", PH7_builtin_zip_entry_name }, + {"zip_entry_filesize", PH7_builtin_zip_entry_filesize }, + {"zip_entry_compressedsize",PH7_builtin_zip_entry_compressedsize }, + {"zip_entry_read", PH7_builtin_zip_entry_read }, + {"zip_entry_reset_read_cursor",PH7_builtin_zip_entry_reset_read_cursor}, + {"zip_entry_compressionmethod",PH7_builtin_zip_entry_compressionmethod} + }; + /* IO stream functions */ + static const ph7_builtin_func aIOFunc[] = { + {"ftruncate", PH7_builtin_ftruncate }, + {"fseek", PH7_builtin_fseek }, + {"ftell", PH7_builtin_ftell }, + {"rewind", PH7_builtin_rewind }, + {"fflush", PH7_builtin_fflush }, + {"feof", PH7_builtin_feof }, + {"fgetc", PH7_builtin_fgetc }, + {"fgets", PH7_builtin_fgets }, + {"fread", PH7_builtin_fread }, + {"fgetcsv", PH7_builtin_fgetcsv}, + {"fgetss", PH7_builtin_fgetss }, + {"readdir", PH7_builtin_readdir}, + {"rewinddir", PH7_builtin_rewinddir }, + {"closedir", PH7_builtin_closedir}, + {"opendir", PH7_builtin_opendir }, + {"readfile", PH7_builtin_readfile}, + {"file_get_contents", PH7_builtin_file_get_contents}, + {"file_put_contents", PH7_builtin_file_put_contents}, + {"file", PH7_builtin_file }, + {"copy", PH7_builtin_copy }, + {"fstat", PH7_builtin_fstat }, + {"fwrite", PH7_builtin_fwrite }, + {"fputs", PH7_builtin_fwrite }, + {"flock", PH7_builtin_flock }, + {"fclose", PH7_builtin_fclose }, + {"fopen", PH7_builtin_fopen }, + {"fpassthru", PH7_builtin_fpassthru }, + {"fputcsv", PH7_builtin_fputcsv }, + {"fprintf", PH7_builtin_fprintf }, +#if !defined(PH7_DISABLE_HASH_FUNC) + {"md5_file", PH7_builtin_md5_file}, + {"sha1_file", PH7_builtin_sha1_file}, +#endif /* PH7_DISABLE_HASH_FUNC */ + {"parse_ini_file", PH7_builtin_parse_ini_file}, + {"vfprintf", PH7_builtin_vfprintf} + }; + const ph7_io_stream *pFileStream = 0; + sxu32 n = 0; + /* Register the functions defined above */ + for( n = 0 ; n < SX_ARRAYSIZE(aVfsFunc) ; ++n ){ + ph7_create_function(&(*pVm),aVfsFunc[n].zName,aVfsFunc[n].xFunc,(void *)pVm->pEngine->pVfs); + } + for( n = 0 ; n < SX_ARRAYSIZE(aIOFunc) ; ++n ){ + ph7_create_function(&(*pVm),aIOFunc[n].zName,aIOFunc[n].xFunc,pVm); + } +#ifndef PH7_DISABLE_DISK_IO + /* Register the file stream if available */ +#ifdef __WINNT__ + pFileStream = &sWinFileStream; +#elif defined(__UNIXES__) + pFileStream = &sUnixFileStream; +#endif + /* Install the php:// stream */ + ph7_vm_config(pVm,PH7_VM_CONFIG_IO_STREAM,&sPHP_Stream); +#endif /* PH7_DISABLE_DISK_IO */ + if( pFileStream ){ + /* Install the file:// stream */ + ph7_vm_config(pVm,PH7_VM_CONFIG_IO_STREAM,pFileStream); + } +#else + SXUNUSED(pVm); /* cc warning */ +#endif /* PH7_DISABLE_BUILTIN_FUNC */ + return SXRET_OK; +} +/* + * Export the STDIN handle. + */ +PH7_PRIVATE void * PH7_ExportStdin(ph7_vm *pVm) +{ +#ifndef PH7_DISABLE_BUILTIN_FUNC +#ifndef PH7_DISABLE_DISK_IO + if( pVm->pStdin == 0 ){ + io_private *pIn; + /* Allocate an IO private instance */ + pIn = (io_private *)SyMemBackendAlloc(&pVm->sAllocator,sizeof(io_private)); + if( pIn == 0 ){ + return 0; + } + InitIOPrivate(pVm,&sPHP_Stream,pIn); + /* Initialize the handle */ + pIn->pHandle = PHPStreamDataInit(pVm,PH7_IO_STREAM_STDIN); + /* Install the STDIN stream */ + pVm->pStdin = pIn; + return pIn; + }else{ + /* NULL or STDIN */ + return pVm->pStdin; + } +#else + return 0; +#endif +#else + SXUNUSED(pVm); /* cc warning */ + return 0; +#endif +} +/* + * Export the STDOUT handle. + */ +PH7_PRIVATE void * PH7_ExportStdout(ph7_vm *pVm) +{ +#ifndef PH7_DISABLE_BUILTIN_FUNC +#ifndef PH7_DISABLE_DISK_IO + if( pVm->pStdout == 0 ){ + io_private *pOut; + /* Allocate an IO private instance */ + pOut = (io_private *)SyMemBackendAlloc(&pVm->sAllocator,sizeof(io_private)); + if( pOut == 0 ){ + return 0; + } + InitIOPrivate(pVm,&sPHP_Stream,pOut); + /* Initialize the handle */ + pOut->pHandle = PHPStreamDataInit(pVm,PH7_IO_STREAM_STDOUT); + /* Install the STDOUT stream */ + pVm->pStdout = pOut; + return pOut; + }else{ + /* NULL or STDOUT */ + return pVm->pStdout; + } +#else + return 0; +#endif +#else + SXUNUSED(pVm); /* cc warning */ + return 0; +#endif +} +/* + * Export the STDERR handle. + */ +PH7_PRIVATE void * PH7_ExportStderr(ph7_vm *pVm) +{ +#ifndef PH7_DISABLE_BUILTIN_FUNC +#ifndef PH7_DISABLE_DISK_IO + if( pVm->pStderr == 0 ){ + io_private *pErr; + /* Allocate an IO private instance */ + pErr = (io_private *)SyMemBackendAlloc(&pVm->sAllocator,sizeof(io_private)); + if( pErr == 0 ){ + return 0; + } + InitIOPrivate(pVm,&sPHP_Stream,pErr); + /* Initialize the handle */ + pErr->pHandle = PHPStreamDataInit(pVm,PH7_IO_STREAM_STDERR); + /* Install the STDERR stream */ + pVm->pStderr = pErr; + return pErr; + }else{ + /* NULL or STDERR */ + return pVm->pStderr; + } +#else + return 0; +#endif +#else + SXUNUSED(pVm); /* cc warning */ + return 0; +#endif +} + +/* + * ---------------------------------------------------------- + * File: parse.c + * MD5: 56ca16eaf7ac65c2fd5a9a8b67345f21 + * ---------------------------------------------------------- + */ +/* + * Symisc PH7: An embeddable bytecode compiler and a virtual machine for the PHP(5) programming language. + * Copyright (C) 2011-2012, Symisc Systems http://ph7.symisc.net/ + * Version 2.1.4 + * For information on licensing,redistribution of this file,and for a DISCLAIMER OF ALL WARRANTIES + * please contact Symisc Systems via: + * legal@symisc.net + * licensing@symisc.net + * contact@symisc.net + * or visit: + * http://ph7.symisc.net/ + */ + /* $SymiscID: parse.c v3.7 FreeBSD 2011-12-20 22:46 stable $ */ +#ifndef PH7_AMALGAMATION +#include "ph7int.h" +#endif +/* + * This file implement a hand-coded, thread-safe, full-reentrant and highly-efficient + * expression parser for the PH7 engine. + * Besides from the one introudced by PHP (Over 60), the PH7 engine have introduced three new + * operators. These are 'eq', 'ne' and the comma operator ','. + * The eq and ne operators are borrowed from the Perl world. They are used for strict + * string comparison. The reason why they have been implemented in the PH7 engine + * and introduced as an extension to the PHP programming language is due to the confusion + * introduced by the standard PHP comparison operators ('==' or '===') especially if you + * are comparing strings with numbers. + * Take the following example: + * var_dump( 0xFF == '255' ); // bool(true) ??? + * // use the type equal operator by adding a single space to one of the operand + * var_dump( '255 ' === '255' ); //bool(true) depending on the PHP version + * That is, if one of the operand looks like a number (either integer or float) then PHP + * will internally convert the two operands to numbers and then a numeric comparison is performed. + * This is what the PHP language reference manual says: + * If you compare a number with a string or the comparison involves numerical strings, then each + * string is converted to a number and the comparison performed numerically. + * Bummer, if you ask me,this is broken, badly broken. I mean,the programmer cannot dictate + * it's comparison rule, it's the underlying engine who decides in it's place and perform + * the internal conversion. In most cases,PHP developers wants simple string comparison and they + * are stuck to use the ugly and inefficient strcmp() function and it's variants instead. + * This is the big reason why we have introduced these two operators. + * The eq operator is used to compare two strings byte per byte. If you came from the C/C++ world + * think of this operator as a barebone implementation of the memcmp() C standard library function. + * Keep in mind that if you are comparing two ASCII strings then the capital letters and their lowercase + * letters are completely different and so this example will output false. + * var_dump('allo' eq 'Allo'); //bool(FALSE) + * The ne operator perform the opposite operation of the eq operator and is used to test for string + * inequality. This example will output true + * var_dump('allo' ne 'Allo'); //bool(TRUE) unequal strings + * The eq operator return a Boolean true if and only if the two strings are identical while the + * ne operator return a Boolean true if and only if the two strings are different. Otherwise + * a Boolean false is returned (equal strings). + * Note that the comparison is performed only if the two strings are of the same length. + * Otherwise the eq and ne operators return a Boolean false without performing any comparison + * and avoid us wasting CPU time for nothing. + * Again remember that we talk about a low level byte per byte comparison and nothing else. + * Also remember that zero length strings are always equal. + * + * Again, another powerful mechanism borrowed from the C/C++ world and introduced as an extension + * to the PHP programming language. + * A comma expression contains two operands of any type separated by a comma and has left-to-right + * associativity. The left operand is fully evaluated, possibly producing side effects, and its + * value, if there is one, is discarded. The right operand is then evaluated. The type and value + * of the result of a comma expression are those of its right operand, after the usual unary conversions. + * Any number of expressions separated by commas can form a single expression because the comma operator + * is associative. The use of the comma operator guarantees that the sub-expressions will be evaluated + * in left-to-right order, and the value of the last becomes the value of the entire expression. + * The following example assign the value 25 to the variable $a, multiply the value of $a with 2 + * and assign the result to variable $b and finally we call a test function to output the value + * of $a and $b. Keep-in mind that all theses operations are done in a single expression using + * the comma operator to create side effect. + * $a = 25,$b = $a << 1 ,test(); + * //Output the value of $a and $b + * function test(){ + * global $a,$b; + * echo "\$a = $a \$b= $b\n"; // You should see: $a = 25 $b = 50 + * } + * + * For a full discussions on these extensions, please refer to offical + * documentation(http://ph7.symisc.net/features.html) or visit the offical forums + * (http://forums.symisc.net/) if you want to share your point of view. + * + * Exprressions: According to the PHP language reference manual + * + * Expressions are the most important building stones of PHP. In PHP, almost anything you write is an expression. + * The simplest yet most accurate way to define an expression is "anything that has a value". + * The most basic forms of expressions are constants and variables. When you type "$a = 5", you're assigning + * '5' into $a. '5', obviously, has the value 5, or in other words '5' is an expression with the value of 5 + * (in this case, '5' is an integer constant). + * After this assignment, you'd expect $a's value to be 5 as well, so if you wrote $b = $a, you'd expect + * it to behave just as if you wrote $b = 5. In other words, $a is an expression with the value of 5 as well. + * If everything works right, this is exactly what will happen. + * Slightly more complex examples for expressions are functions. For instance, consider the following function: + * + * Assuming you're familiar with the concept of functions (if you're not, take a look at the chapter about functions) + * you'd assume that typing $c = foo() is essentially just like writing $c = 5, and you're right. + * Functions are expressions with the value of their return value. Since foo() returns 5, the value of the expression + * 'foo()' is 5. Usually functions don't just return a static value but compute something. + * Of course, values in PHP don't have to be integers, and very often they aren't. + * PHP supports four scalar value types: integer values, floating point values (float), string values and boolean values + * (scalar values are values that you can't 'break' into smaller pieces, unlike arrays, for instance). + * PHP also supports two composite (non-scalar) types: arrays and objects. Each of these value types can be assigned + * into variables or returned from functions. + * PHP takes expressions much further, in the same way many other languages do. PHP is an expression-oriented language + * in the sense that almost everything is an expression. Consider the example we've already dealt with, '$a = 5'. + * It's easy to see that there are two values involved here, the value of the integer constant '5', and the value + * of $a which is being updated to 5 as well. But the truth is that there's one additional value involved here + * and that's the value of the assignment itself. The assignment itself evaluates to the assigned value, in this case 5. + * In practice, it means that '$a = 5', regardless of what it does, is an expression with the value 5. Thus, writing + * something like '$b = ($a = 5)' is like writing '$a = 5; $b = 5;' (a semicolon marks the end of a statement). + * Since assignments are parsed in a right to left order, you can also write '$b = $a = 5'. + * Another good example of expression orientation is pre- and post-increment and decrement. + * Users of PHP and many other languages may be familiar with the notation of variable++ and variable--. + * These are increment and decrement operators. In PHP, like in C, there are two types of increment - pre-increment + * and post-increment. Both pre-increment and post-increment essentially increment the variable, and the effect + * on the variable is identical. The difference is with the value of the increment expression. Pre-increment, which is written + * '++$variable', evaluates to the incremented value (PHP increments the variable before reading its value, thus the name 'pre-increment'). + * Post-increment, which is written '$variable++' evaluates to the original value of $variable, before it was incremented + * (PHP increments the variable after reading its value, thus the name 'post-increment'). + * A very common type of expressions are comparison expressions. These expressions evaluate to either FALSE or TRUE. + * PHP supports > (bigger than), >= (bigger than or equal to), == (equal), != (not equal), < (smaller than) and <= (smaller than or equal to). + * The language also supports a set of strict equivalence operators: === (equal to and same type) and !== (not equal to or not same type). + * These expressions are most commonly used inside conditional execution, such as if statements. + * The last example of expressions we'll deal with here is combined operator-assignment expressions. + * You already know that if you want to increment $a by 1, you can simply write '$a++' or '++$a'. + * But what if you want to add more than one to it, for instance 3? You could write '$a++' multiple times, but this is obviously not a very + * efficient or comfortable way. A much more common practice is to write '$a = $a + 3'. '$a + 3' evaluates to the value of $a plus 3 + * and is assigned back into $a, which results in incrementing $a by 3. In PHP, as in several other languages like C, you can write + * this in a shorter way, which with time would become clearer and quicker to understand as well. Adding 3 to the current value of $a + * can be written '$a += 3'. This means exactly "take the value of $a, add 3 to it, and assign it back into $a". + * In addition to being shorter and clearer, this also results in faster execution. The value of '$a += 3', like the value of a regular + * assignment, is the assigned value. Notice that it is NOT 3, but the combined value of $a plus 3 (this is the value that's assigned into $a). + * Any two-place operator can be used in this operator-assignment mode, for example '$a -= 5' (subtract 5 from the value of $a), '$b *= 7' + * (multiply the value of $b by 7), etc. + * There is one more expression that may seem odd if you haven't seen it in other languages, the ternary conditional operator: + * + * If the value of the first subexpression is TRUE (non-zero), then the second subexpression is evaluated, and that is the result + * of the conditional expression. Otherwise, the third subexpression is evaluated, and that is the value. + */ +/* Operators associativity */ +#define EXPR_OP_ASSOC_LEFT 0x01 /* Left associative operator */ +#define EXPR_OP_ASSOC_RIGHT 0x02 /* Right associative operator */ +#define EXPR_OP_NON_ASSOC 0x04 /* Non-associative operator */ +/* + * Operators table + * This table is sorted by operators priority (highest to lowest) according + * the PHP language reference manual. + * PH7 implements all the 60 PHP operators and have introduced the eq and ne operators. + * The operators precedence table have been improved dramatically so that you can do same + * amazing things now such as array dereferencing,on the fly function call,anonymous function + * as array values,class member access on instantiation and so on. + * Refer to the following page for a full discussion on these improvements: + * http://ph7.symisc.net/features.html#improved_precedence + */ +static const ph7_expr_op aOpTable[] = { + /* Precedence 1: non-associative */ + { {"new",sizeof("new")-1}, EXPR_OP_NEW, 1, EXPR_OP_NON_ASSOC, PH7_OP_NEW }, + { {"clone",sizeof("clone")-1}, EXPR_OP_CLONE, 1, EXPR_OP_NON_ASSOC, PH7_OP_CLONE}, + /* Postfix operators */ + /* Precedence 2(Highest),left-associative */ + { {"->",sizeof(char)*2}, EXPR_OP_ARROW, 2, EXPR_OP_ASSOC_LEFT , PH7_OP_MEMBER}, + { {"::",sizeof(char)*2}, EXPR_OP_DC, 2, EXPR_OP_ASSOC_LEFT , PH7_OP_MEMBER}, + { {"[",sizeof(char)}, EXPR_OP_SUBSCRIPT, 2, EXPR_OP_ASSOC_LEFT , PH7_OP_LOAD_IDX}, + /* Precedence 3,non-associative */ + { {"++",sizeof(char)*2}, EXPR_OP_INCR, 3, EXPR_OP_NON_ASSOC , PH7_OP_INCR}, + { {"--",sizeof(char)*2}, EXPR_OP_DECR, 3, EXPR_OP_NON_ASSOC , PH7_OP_DECR}, + /* Unary operators */ + /* Precedence 4,right-associative */ + { {"-",sizeof(char)}, EXPR_OP_UMINUS, 4, EXPR_OP_ASSOC_RIGHT, PH7_OP_UMINUS }, + { {"+",sizeof(char)}, EXPR_OP_UPLUS, 4, EXPR_OP_ASSOC_RIGHT, PH7_OP_UPLUS }, + { {"~",sizeof(char)}, EXPR_OP_BITNOT, 4, EXPR_OP_ASSOC_RIGHT, PH7_OP_BITNOT }, + { {"!",sizeof(char)}, EXPR_OP_LOGNOT, 4, EXPR_OP_ASSOC_RIGHT, PH7_OP_LNOT }, + { {"@",sizeof(char)}, EXPR_OP_ALT, 4, EXPR_OP_ASSOC_RIGHT, PH7_OP_ERR_CTRL}, + /* Cast operators */ + { {"(int)", sizeof("(int)")-1 }, EXPR_OP_TYPECAST, 4, EXPR_OP_ASSOC_RIGHT, PH7_OP_CVT_INT }, + { {"(bool)", sizeof("(bool)")-1 }, EXPR_OP_TYPECAST, 4, EXPR_OP_ASSOC_RIGHT, PH7_OP_CVT_BOOL }, + { {"(string)", sizeof("(string)")-1}, EXPR_OP_TYPECAST, 4, EXPR_OP_ASSOC_RIGHT, PH7_OP_CVT_STR }, + { {"(float)", sizeof("(float)")-1 }, EXPR_OP_TYPECAST, 4, EXPR_OP_ASSOC_RIGHT, PH7_OP_CVT_REAL }, + { {"(array)", sizeof("(array)")-1 }, EXPR_OP_TYPECAST, 4, EXPR_OP_ASSOC_RIGHT, PH7_OP_CVT_ARRAY}, + { {"(object)", sizeof("(object)")-1}, EXPR_OP_TYPECAST, 4, EXPR_OP_ASSOC_RIGHT, PH7_OP_CVT_OBJ }, + { {"(unset)", sizeof("(unset)")-1 }, EXPR_OP_TYPECAST, 4, EXPR_OP_ASSOC_RIGHT, PH7_OP_CVT_NULL }, + /* Binary operators */ + /* Precedence 7,left-associative */ + { {"instanceof",sizeof("instanceof")-1}, EXPR_OP_INSTOF, 7, EXPR_OP_NON_ASSOC, PH7_OP_IS_A}, + { {"*",sizeof(char)}, EXPR_OP_MUL, 7, EXPR_OP_ASSOC_LEFT , PH7_OP_MUL}, + { {"/",sizeof(char)}, EXPR_OP_DIV, 7, EXPR_OP_ASSOC_LEFT , PH7_OP_DIV}, + { {"%",sizeof(char)}, EXPR_OP_MOD, 7, EXPR_OP_ASSOC_LEFT , PH7_OP_MOD}, + /* Precedence 8,left-associative */ + { {"+",sizeof(char)}, EXPR_OP_ADD, 8, EXPR_OP_ASSOC_LEFT, PH7_OP_ADD}, + { {"-",sizeof(char)}, EXPR_OP_SUB, 8, EXPR_OP_ASSOC_LEFT, PH7_OP_SUB}, + { {".",sizeof(char)}, EXPR_OP_DOT, 8, EXPR_OP_ASSOC_LEFT, PH7_OP_CAT}, + /* Precedence 9,left-associative */ + { {"<<",sizeof(char)*2}, EXPR_OP_SHL, 9, EXPR_OP_ASSOC_LEFT, PH7_OP_SHL}, + { {">>",sizeof(char)*2}, EXPR_OP_SHR, 9, EXPR_OP_ASSOC_LEFT, PH7_OP_SHR}, + /* Precedence 10,non-associative */ + { {"<",sizeof(char)}, EXPR_OP_LT, 10, EXPR_OP_NON_ASSOC, PH7_OP_LT}, + { {">",sizeof(char)}, EXPR_OP_GT, 10, EXPR_OP_NON_ASSOC, PH7_OP_GT}, + { {"<=",sizeof(char)*2}, EXPR_OP_LE, 10, EXPR_OP_NON_ASSOC, PH7_OP_LE}, + { {">=",sizeof(char)*2}, EXPR_OP_GE, 10, EXPR_OP_NON_ASSOC, PH7_OP_GE}, + { {"<>",sizeof(char)*2}, EXPR_OP_NE, 10, EXPR_OP_NON_ASSOC, PH7_OP_NEQ}, + /* Precedence 11,non-associative */ + { {"==",sizeof(char)*2}, EXPR_OP_EQ, 11, EXPR_OP_NON_ASSOC, PH7_OP_EQ}, + { {"!=",sizeof(char)*2}, EXPR_OP_NE, 11, EXPR_OP_NON_ASSOC, PH7_OP_NEQ}, + { {"eq",sizeof(char)*2}, EXPR_OP_SEQ, 11, EXPR_OP_NON_ASSOC, PH7_OP_SEQ}, /* IMP-0137-EQ: Symisc eXtension */ + { {"ne",sizeof(char)*2}, EXPR_OP_SNE, 11, EXPR_OP_NON_ASSOC, PH7_OP_SNE}, /* IMP-0138-NE: Symisc eXtension */ + { {"===",sizeof(char)*3}, EXPR_OP_TEQ, 11, EXPR_OP_NON_ASSOC, PH7_OP_TEQ}, + { {"!==",sizeof(char)*3}, EXPR_OP_TNE, 11, EXPR_OP_NON_ASSOC, PH7_OP_TNE}, + /* Precedence 12,left-associative */ + { {"&",sizeof(char)}, EXPR_OP_BAND, 12, EXPR_OP_ASSOC_LEFT, PH7_OP_BAND}, + /* Precedence 12,left-associative */ + { {"=&",sizeof(char)*2}, EXPR_OP_REF, 12, EXPR_OP_ASSOC_LEFT, PH7_OP_STORE_REF}, + /* Binary operators */ + /* Precedence 13,left-associative */ + { {"^",sizeof(char)}, EXPR_OP_XOR,13, EXPR_OP_ASSOC_LEFT, PH7_OP_BXOR}, + /* Precedence 14,left-associative */ + { {"|",sizeof(char)}, EXPR_OP_BOR,14, EXPR_OP_ASSOC_LEFT, PH7_OP_BOR}, + /* Precedence 15,left-associative */ + { {"&&",sizeof(char)*2}, EXPR_OP_LAND,15, EXPR_OP_ASSOC_LEFT, PH7_OP_LAND}, + /* Precedence 16,left-associative */ + { {"||",sizeof(char)*2}, EXPR_OP_LOR, 16, EXPR_OP_ASSOC_LEFT, PH7_OP_LOR}, + /* Ternary operator */ + /* Precedence 17,left-associative */ + { {"?",sizeof(char)}, EXPR_OP_QUESTY, 17, EXPR_OP_ASSOC_LEFT, 0}, + /* Combined binary operators */ + /* Precedence 18,right-associative */ + { {"=",sizeof(char)}, EXPR_OP_ASSIGN, 18, EXPR_OP_ASSOC_RIGHT, PH7_OP_STORE}, + { {"+=",sizeof(char)*2}, EXPR_OP_ADD_ASSIGN, 18, EXPR_OP_ASSOC_RIGHT, PH7_OP_ADD_STORE }, + { {"-=",sizeof(char)*2}, EXPR_OP_SUB_ASSIGN, 18, EXPR_OP_ASSOC_RIGHT, PH7_OP_SUB_STORE }, + { {".=",sizeof(char)*2}, EXPR_OP_DOT_ASSIGN, 18, EXPR_OP_ASSOC_RIGHT, PH7_OP_CAT_STORE }, + { {"*=",sizeof(char)*2}, EXPR_OP_MUL_ASSIGN, 18, EXPR_OP_ASSOC_RIGHT, PH7_OP_MUL_STORE }, + { {"/=",sizeof(char)*2}, EXPR_OP_DIV_ASSIGN, 18, EXPR_OP_ASSOC_RIGHT, PH7_OP_DIV_STORE }, + { {"%=",sizeof(char)*2}, EXPR_OP_MOD_ASSIGN, 18, EXPR_OP_ASSOC_RIGHT, PH7_OP_MOD_STORE }, + { {"&=",sizeof(char)*2}, EXPR_OP_AND_ASSIGN, 18, EXPR_OP_ASSOC_RIGHT, PH7_OP_BAND_STORE }, + { {"|=",sizeof(char)*2}, EXPR_OP_OR_ASSIGN, 18, EXPR_OP_ASSOC_RIGHT, PH7_OP_BOR_STORE }, + { {"^=",sizeof(char)*2}, EXPR_OP_XOR_ASSIGN, 18, EXPR_OP_ASSOC_RIGHT, PH7_OP_BXOR_STORE }, + { {"<<=",sizeof(char)*3}, EXPR_OP_SHL_ASSIGN, 18, EXPR_OP_ASSOC_RIGHT, PH7_OP_SHL_STORE }, + { {">>=",sizeof(char)*3}, EXPR_OP_SHR_ASSIGN, 18, EXPR_OP_ASSOC_RIGHT, PH7_OP_SHR_STORE }, + /* Precedence 19,left-associative */ + { {"and",sizeof("and")-1}, EXPR_OP_LAND, 19, EXPR_OP_ASSOC_LEFT, PH7_OP_LAND}, + /* Precedence 20,left-associative */ + { {"xor", sizeof("xor") -1}, EXPR_OP_LXOR, 20, EXPR_OP_ASSOC_LEFT, PH7_OP_LXOR}, + /* Precedence 21,left-associative */ + { {"or",sizeof("or")-1}, EXPR_OP_LOR, 21, EXPR_OP_ASSOC_LEFT, PH7_OP_LOR}, + /* Precedence 22,left-associative [Lowest operator] */ + { {",",sizeof(char)}, EXPR_OP_COMMA,22, EXPR_OP_ASSOC_LEFT, 0}, /* IMP-0139-COMMA: Symisc eXtension */ +}; +/* Function call operator need special handling */ +static const ph7_expr_op sFCallOp = {{"(",sizeof(char)}, EXPR_OP_FUNC_CALL, 2, EXPR_OP_ASSOC_LEFT , PH7_OP_CALL}; +/* + * Check if the given token is a potential operator or not. + * This function is called by the lexer each time it extract a token that may + * look like an operator. + * Return a structure [i.e: ph7_expr_op instnace ] that describe the operator on success. + * Otherwise NULL. + * Note that the function take care of handling ambiguity [i.e: whether we are dealing with + * a binary minus or unary minus.] + */ +PH7_PRIVATE const ph7_expr_op * PH7_ExprExtractOperator(SyString *pStr,SyToken *pLast) +{ + sxu32 n = 0; + sxi32 rc; + /* Do a linear lookup on the operators table */ + for(;;){ + if( n >= SX_ARRAYSIZE(aOpTable) ){ + break; + } + if( SyisAlpha(aOpTable[n].sOp.zString[0]) ){ + /* TICKET 1433-012: Alpha stream operators [i.e: and,or,xor,new...] */ + rc = SyStringCmp(pStr,&aOpTable[n].sOp,SyStrnicmp); + }else{ + rc = SyStringCmp(pStr,&aOpTable[n].sOp,SyMemcmp); + } + if( rc == 0 ){ + if( aOpTable[n].sOp.nByte != sizeof(char) || (aOpTable[n].iOp != EXPR_OP_UMINUS && aOpTable[n].iOp != EXPR_OP_UPLUS) || pLast == 0 ){ + /* There is no ambiguity here,simply return the first operator seen */ + return &aOpTable[n]; + } + /* Handle ambiguity */ + if( pLast->nType & (PH7_TK_LPAREN/*'('*/|PH7_TK_OCB/*'{'*/|PH7_TK_OSB/*'['*/|PH7_TK_COLON/*:*/|PH7_TK_COMMA/*,'*/) ){ + /* Unary opertors have prcedence here over binary operators */ + return &aOpTable[n]; + } + if( pLast->nType & PH7_TK_OP ){ + const ph7_expr_op *pOp = (const ph7_expr_op *)pLast->pUserData; + /* Ticket 1433-31: Handle the '++','--' operators case */ + if( pOp->iOp != EXPR_OP_INCR && pOp->iOp != EXPR_OP_DECR ){ + /* Unary opertors have prcedence here over binary operators */ + return &aOpTable[n]; + } + + } + } + ++n; /* Next operator in the table */ + } + /* No such operator */ + return 0; +} +/* + * Delimit a set of token stream. + * This function take care of handling the nesting level and stops when it hit + * the end of the input or the ending token is found and the nesting level is zero. + */ +PH7_PRIVATE void PH7_DelimitNestedTokens(SyToken *pIn,SyToken *pEnd,sxu32 nTokStart,sxu32 nTokEnd,SyToken **ppEnd) +{ + SyToken *pCur = pIn; + sxi32 iNest = 1; + for(;;){ + if( pCur >= pEnd ){ + break; + } + if( pCur->nType & nTokStart ){ + /* Increment nesting level */ + iNest++; + }else if( pCur->nType & nTokEnd ){ + /* Decrement nesting level */ + iNest--; + if( iNest <= 0 ){ + break; + } + } + /* Advance cursor */ + pCur++; + } + /* Point to the end of the chunk */ + *ppEnd = pCur; +} +/* + * Retrun TRUE if the given ID represent a language construct [i.e: print,echo..]. FALSE otherwise. + * Note on reserved keywords. + * According to the PHP language reference manual: + * These words have special meaning in PHP. Some of them represent things which look like + * functions, some look like constants, and so on--but they're not, really: they are language + * constructs. You cannot use any of the following words as constants, class names, function + * or method names. Using them as variable names is generally OK, but could lead to confusion. + */ +PH7_PRIVATE int PH7_IsLangConstruct(sxu32 nKeyID,sxu8 bCheckFunc) +{ + if( nKeyID == PH7_TKWRD_ECHO || nKeyID == PH7_TKWRD_PRINT || nKeyID == PH7_TKWRD_INCLUDE + || nKeyID == PH7_TKWRD_INCONCE || nKeyID == PH7_TKWRD_REQUIRE || nKeyID == PH7_TKWRD_REQONCE + ){ + return TRUE; + } + if( bCheckFunc ){ + if( nKeyID == PH7_TKWRD_ISSET || nKeyID == PH7_TKWRD_UNSET || nKeyID == PH7_TKWRD_EVAL + || nKeyID == PH7_TKWRD_EMPTY || nKeyID == PH7_TKWRD_ARRAY || nKeyID == PH7_TKWRD_LIST + || /* TICKET 1433-012 */ nKeyID == PH7_TKWRD_NEW || nKeyID == PH7_TKWRD_CLONE ){ + return TRUE; + } + } + /* Not a language construct */ + return FALSE; +} +/* + * Make sure we are dealing with a valid expression tree. + * This function check for balanced parenthesis,braces,brackets and so on. + * When errors,PH7 take care of generating the appropriate error message. + * Return SXRET_OK on success. Any other return value indicates syntax error. + */ +static sxi32 ExprVerifyNodes(ph7_gen_state *pGen,ph7_expr_node **apNode,sxi32 nNode) +{ + sxi32 iParen,iSquare,iQuesty,iBraces; + sxi32 i,rc; + + if( nNode > 0 && apNode[0]->pOp && (apNode[0]->pOp->iOp == EXPR_OP_ADD || apNode[0]->pOp->iOp == EXPR_OP_SUB) ){ + /* Fix and mark as an unary not binary plus/minus operator */ + apNode[0]->pOp = PH7_ExprExtractOperator(&apNode[0]->pStart->sData,0); + apNode[0]->pStart->pUserData = (void *)apNode[0]->pOp; + } + iParen = iSquare = iQuesty = iBraces = 0; + for( i = 0 ; i < nNode ; ++i ){ + if( apNode[i]->pStart->nType & PH7_TK_LPAREN /*'('*/){ + if( i > 0 && ( apNode[i-1]->xCode == PH7_CompileVariable || apNode[i-1]->xCode == PH7_CompileLiteral || + (apNode[i - 1]->pStart->nType & (PH7_TK_ID|PH7_TK_KEYWORD|PH7_TK_SSTR|PH7_TK_DSTR|PH7_TK_RPAREN/*')'*/|PH7_TK_CSB/*']'*/|PH7_TK_CCB/*'}'*/))) ){ + /* Ticket 1433-033: Take care to ignore alpha-stream [i.e: or,xor] operators followed by an opening parenthesis */ + if( (apNode[i - 1]->pStart->nType & PH7_TK_OP) == 0 ){ + /* We are dealing with a postfix [i.e: function call] operator + * not a simple left parenthesis. Mark the node. + */ + apNode[i]->pStart->nType |= PH7_TK_OP; + apNode[i]->pStart->pUserData = (void *)&sFCallOp; /* Function call operator */ + apNode[i]->pOp = &sFCallOp; + } + } + iParen++; + }else if( apNode[i]->pStart->nType & PH7_TK_RPAREN/*')*/){ + if( iParen <= 0 ){ + rc = PH7_GenCompileError(&(*pGen),E_ERROR,apNode[i]->pStart->nLine,"Syntax error: Unexpected token ')'"); + if( rc != SXERR_ABORT ){ + rc = SXERR_SYNTAX; + } + return rc; + } + iParen--; + }else if( apNode[i]->pStart->nType & PH7_TK_OSB /*'['*/){ + iSquare++; + }else if (apNode[i]->pStart->nType & PH7_TK_CSB /*']'*/){ + if( iSquare <= 0 ){ + rc = PH7_GenCompileError(&(*pGen),E_ERROR,apNode[i]->pStart->nLine,"Syntax error: Unexpected token ']'"); + if( rc != SXERR_ABORT ){ + rc = SXERR_SYNTAX; + } + return rc; + } + iSquare--; + }else if( apNode[i]->pStart->nType & PH7_TK_OCB /*'{'*/){ + iBraces++; + if( i > 0 && ( apNode[i - 1]->xCode == PH7_CompileVariable || (apNode[i - 1]->pStart->nType & PH7_TK_CSB/*]*/)) ){ + const ph7_expr_op *pOp,*pEnd; + int iNest = 1; + sxi32 j=i+1; + /* + * Dirty Hack: $a{'x'} == > $a['x'] + */ + apNode[i]->pStart->nType &= ~PH7_TK_OCB /*'{'*/; + apNode[i]->pStart->nType |= PH7_TK_OSB /*'['*/; + pOp = aOpTable; + pEnd = &pOp[sizeof(aOpTable)]; + while( pOp < pEnd ){ + if( pOp->iOp == EXPR_OP_SUBSCRIPT ){ + break; + } + pOp++; + } + if( pOp >= pEnd ){ + pOp = 0; + } + if( pOp ){ + apNode[i]->pOp = pOp; + apNode[i]->pStart->nType |= PH7_TK_OP; + } + iBraces--; + iSquare++; + while( j < nNode ){ + if( apNode[j]->pStart->nType & PH7_TK_OCB /*{*/){ + /* Increment nesting level */ + iNest++; + }else if( apNode[j]->pStart->nType & PH7_TK_CCB/*}*/ ){ + /* Decrement nesting level */ + iNest--; + if( iNest < 1 ){ + break; + } + } + j++; + } + if( j < nNode ){ + apNode[j]->pStart->nType &= ~PH7_TK_CCB /*'}'*/; + apNode[j]->pStart->nType |= PH7_TK_CSB /*']'*/; + } + + } + }else if (apNode[i]->pStart->nType & PH7_TK_CCB /*'}'*/){ + if( iBraces <= 0 ){ + rc = PH7_GenCompileError(&(*pGen),E_ERROR,apNode[i]->pStart->nLine,"Syntax error: Unexpected token '}'"); + if( rc != SXERR_ABORT ){ + rc = SXERR_SYNTAX; + } + return rc; + } + iBraces--; + }else if ( apNode[i]->pStart->nType & PH7_TK_COLON ){ + if( iQuesty <= 0 ){ + rc = PH7_GenCompileError(&(*pGen),E_ERROR,apNode[i]->pStart->nLine,"Syntax error: Unexpected token ':'"); + if( rc != SXERR_ABORT ){ + rc = SXERR_SYNTAX; + } + return rc; + } + iQuesty--; + }else if( apNode[i]->pStart->nType & PH7_TK_OP ){ + const ph7_expr_op *pOp = (const ph7_expr_op *)apNode[i]->pOp; + if( pOp->iOp == EXPR_OP_QUESTY ){ + iQuesty++; + }else if( i > 0 && (pOp->iOp == EXPR_OP_UMINUS || pOp->iOp == EXPR_OP_UPLUS)){ + if( apNode[i-1]->xCode == PH7_CompileVariable || apNode[i-1]->xCode == PH7_CompileLiteral ){ + sxi32 iExprOp = EXPR_OP_SUB; /* Binary minus */ + sxu32 n = 0; + if( pOp->iOp == EXPR_OP_UPLUS ){ + iExprOp = EXPR_OP_ADD; /* Binary plus */ + } + /* + * TICKET 1433-013: This is a fix around an obscure bug when the user uses + * a variable name which is an alpha-stream operator [i.e: $and,$xor,$eq..]. + */ + while( n < SX_ARRAYSIZE(aOpTable) && aOpTable[n].iOp != iExprOp ){ + ++n; + } + pOp = &aOpTable[n]; + /* Mark as binary '+' or '-',not an unary */ + apNode[i]->pOp = pOp; + apNode[i]->pStart->pUserData = (void *)pOp; + } + } + } + } + if( iParen != 0 || iSquare != 0 || iQuesty != 0 || iBraces != 0){ + rc = PH7_GenCompileError(&(*pGen),E_ERROR,apNode[0]->pStart->nLine,"Syntax error,mismatched '(','[','{' or '?'"); + if( rc != SXERR_ABORT ){ + rc = SXERR_SYNTAX; + } + return rc; + } + return SXRET_OK; +} +/* + * Collect and assemble tokens holding a namespace path [i.e: namespace\to\const] + * or a simple literal [i.e: PHP_EOL]. + */ +static void ExprAssembleLiteral(SyToken **ppCur,SyToken *pEnd) +{ + SyToken *pIn = *ppCur; + /* Jump the first literal seen */ + if( (pIn->nType & PH7_TK_NSSEP) == 0 ){ + pIn++; + } + for(;;){ + if(pIn < pEnd && (pIn->nType & PH7_TK_NSSEP) ){ + pIn++; + if(pIn < pEnd && (pIn->nType & (PH7_TK_ID|PH7_TK_KEYWORD)) ){ + pIn++; + } + }else{ + break; + } + } + /* Synchronize pointers */ + *ppCur = pIn; +} +/* + * Collect and assemble tokens holding annonymous functions/closure body. + * When errors,PH7 take care of generating the appropriate error message. + * Note on annonymous functions. + * According to the PHP language reference manual: + * Anonymous functions, also known as closures, allow the creation of functions + * which have no specified name. They are most useful as the value of callback + * parameters, but they have many other uses. + * Closures may also inherit variables from the parent scope. Any such variables + * must be declared in the function header. Inheriting variables from the parent + * scope is not the same as using global variables. Global variables exist in the global scope + * which is the same no matter what function is executing. The parent scope of a closure is the + * function in which the closure was declared (not necessarily the function it was called from). + * + * Some example: + * $greet = function($name) + * { + * printf("Hello %s\r\n", $name); + * }; + * $greet('World'); + * $greet('PHP'); + * + * $double = function($a) { + * return $a * 2; + * }; + * // This is our range of numbers + * $numbers = range(1, 5); + * // Use the Annonymous function as a callback here to + * // double the size of each element in our + * // range + * $new_numbers = array_map($double, $numbers); + * print implode(' ', $new_numbers); + */ +static sxi32 ExprAssembleAnnon(ph7_gen_state *pGen,SyToken **ppCur,SyToken *pEnd) +{ + SyToken *pIn = *ppCur; + sxu32 nLine; + sxi32 rc; + /* Jump the 'function' keyword */ + nLine = pIn->nLine; + pIn++; + if( pIn < pEnd && (pIn->nType & (PH7_TK_ID|PH7_TK_KEYWORD)) ){ + pIn++; + } + if( pIn >= pEnd || (pIn->nType & PH7_TK_LPAREN) == 0 ){ + /* Syntax error */ + rc = PH7_GenCompileError(&(*pGen),E_ERROR,nLine,"Missing opening parenthesis '(' while declaring annonymous function"); + if( rc != SXERR_ABORT ){ + rc = SXERR_SYNTAX; + } + goto Synchronize; + } + pIn++; /* Jump the leading parenthesis '(' */ + PH7_DelimitNestedTokens(pIn,pEnd,PH7_TK_LPAREN/*'('*/,PH7_TK_RPAREN/*')'*/,&pIn); + if( pIn >= pEnd || &pIn[1] >= pEnd ){ + /* Syntax error */ + rc = PH7_GenCompileError(&(*pGen),E_ERROR,nLine,"Syntax error while declaring annonymous function"); + if( rc != SXERR_ABORT ){ + rc = SXERR_SYNTAX; + } + goto Synchronize; + } + pIn++; /* Jump the trailing parenthesis */ + if( pIn->nType & PH7_TK_KEYWORD ){ + sxu32 nKey = SX_PTR_TO_INT(pIn->pUserData); + /* Check if we are dealing with a closure */ + if( nKey == PH7_TKWRD_USE ){ + pIn++; /* Jump the 'use' keyword */ + if( pIn >= pEnd || (pIn->nType & PH7_TK_LPAREN) == 0 ){ + /* Syntax error */ + rc = PH7_GenCompileError(&(*pGen),E_ERROR,nLine,"Syntax error while declaring annonymous function"); + if( rc != SXERR_ABORT ){ + rc = SXERR_SYNTAX; + } + goto Synchronize; + } + pIn++; /* Jump the leading parenthesis '(' */ + PH7_DelimitNestedTokens(pIn,pEnd,PH7_TK_LPAREN/*'('*/,PH7_TK_RPAREN/*')'*/,&pIn); + if( pIn >= pEnd || &pIn[1] >= pEnd ){ + /* Syntax error */ + rc = PH7_GenCompileError(&(*pGen),E_ERROR,nLine,"Syntax error while declaring annonymous function"); + if( rc != SXERR_ABORT ){ + rc = SXERR_SYNTAX; + } + goto Synchronize; + } + pIn++; + }else{ + /* Syntax error */ + rc = PH7_GenCompileError(&(*pGen),E_ERROR,nLine,"Syntax error while declaring annonymous function"); + if( rc != SXERR_ABORT ){ + rc = SXERR_SYNTAX; + } + goto Synchronize; + } + } + if( pIn->nType & PH7_TK_OCB /*'{'*/ ){ + pIn++; /* Jump the leading curly '{' */ + PH7_DelimitNestedTokens(pIn,pEnd,PH7_TK_OCB/*'{'*/,PH7_TK_CCB/*'}'*/,&pIn); + if( pIn < pEnd ){ + pIn++; + } + }else{ + /* Syntax error */ + rc = PH7_GenCompileError(&(*pGen),E_ERROR,nLine,"Syntax error while declaring annonymous function,missing '{'"); + if( rc == SXERR_ABORT ){ + return SXERR_ABORT; + } + } + rc = SXRET_OK; +Synchronize: + /* Synchronize pointers */ + *ppCur = pIn; + return rc; +} +/* + * Extract a single expression node from the input. + * On success store the freshly extractd node in ppNode. + * When errors,PH7 take care of generating the appropriate error message. + * An expression node can be a variable [i.e: $var],an operator [i.e: ++] + * an annonymous function [i.e: function(){ return "Hello"; }, a double/single + * quoted string, a heredoc/nowdoc,a literal [i.e: PHP_EOL],a namespace path + * [i.e: namespaces\path\to..],a array/list [i.e: array(4,5,6)] and so on. + */ +static sxi32 ExprExtractNode(ph7_gen_state *pGen,ph7_expr_node **ppNode) +{ + ph7_expr_node *pNode; + SyToken *pCur; + sxi32 rc; + /* Allocate a new node */ + pNode = (ph7_expr_node *)SyMemBackendPoolAlloc(&pGen->pVm->sAllocator,sizeof(ph7_expr_node)); + if( pNode == 0 ){ + /* If the supplied memory subsystem is so sick that we are unable to allocate + * a tiny chunk of memory, there is no much we can do here. + */ + return SXERR_MEM; + } + /* Zero the structure */ + SyZero(pNode,sizeof(ph7_expr_node)); + SySetInit(&pNode->aNodeArgs,&pGen->pVm->sAllocator,sizeof(ph7_expr_node **)); + /* Point to the head of the token stream */ + pCur = pNode->pStart = pGen->pIn; + /* Start collecting tokens */ + if( pCur->nType & PH7_TK_OP ){ + /* Point to the instance that describe this operator */ + pNode->pOp = (const ph7_expr_op *)pCur->pUserData; + /* Advance the stream cursor */ + pCur++; + }else if( pCur->nType & PH7_TK_DOLLAR ){ + /* Isolate variable */ + while( pCur < pGen->pEnd && (pCur->nType & PH7_TK_DOLLAR) ){ + pCur++; /* Variable variable */ + } + if( pCur < pGen->pEnd ){ + if (pCur->nType & (PH7_TK_ID|PH7_TK_KEYWORD) ){ + /* Variable name */ + pCur++; + }else if( pCur->nType & PH7_TK_OCB /* '{' */ ){ + pCur++; + /* Dynamic variable name,Collect until the next non nested '}' */ + PH7_DelimitNestedTokens(pCur,pGen->pEnd,PH7_TK_OCB, PH7_TK_CCB,&pCur); + if( pCur < pGen->pEnd ){ + pCur++; + }else{ + rc = PH7_GenCompileError(pGen,E_ERROR,pNode->pStart->nLine,"Syntax error: Missing closing brace '}'"); + if( rc != SXERR_ABORT ){ + rc = SXERR_SYNTAX; + } + SyMemBackendPoolFree(&pGen->pVm->sAllocator,pNode); + return rc; + } + } + } + pNode->xCode = PH7_CompileVariable; + }else if( pCur->nType & PH7_TK_KEYWORD ){ + sxu32 nKeyword = (sxu32)SX_PTR_TO_INT(pCur->pUserData); + if( nKeyword == PH7_TKWRD_ARRAY || nKeyword == PH7_TKWRD_LIST ){ + /* List/Array node */ + if( &pCur[1] >= pGen->pEnd || (pCur[1].nType & PH7_TK_LPAREN) == 0 ){ + /* Assume a literal */ + ExprAssembleLiteral(&pCur,pGen->pEnd); + pNode->xCode = PH7_CompileLiteral; + }else{ + pCur += 2; + /* Collect array/list tokens */ + PH7_DelimitNestedTokens(pCur,pGen->pEnd,PH7_TK_LPAREN /* '(' */, PH7_TK_RPAREN /* ')' */,&pCur); + if( pCur < pGen->pEnd ){ + pCur++; + }else{ + /* Syntax error */ + rc = PH7_GenCompileError(pGen,E_ERROR,pNode->pStart->nLine, + "%s: Missing closing parenthesis ')'",nKeyword == PH7_TKWRD_LIST ? "list" : "array"); + if( rc != SXERR_ABORT ){ + rc = SXERR_SYNTAX; + } + SyMemBackendPoolFree(&pGen->pVm->sAllocator,pNode); + return rc; + } + pNode->xCode = (nKeyword == PH7_TKWRD_LIST) ? PH7_CompileList : PH7_CompileArray; + if( pNode->xCode == PH7_CompileList ){ + ph7_expr_op *pOp = (pCur < pGen->pEnd) ? (ph7_expr_op *)pCur->pUserData : 0; + if( pCur >= pGen->pEnd || (pCur->nType & PH7_TK_OP) == 0 || pOp == 0 || pOp->iVmOp != PH7_OP_STORE /*'='*/){ + /* Syntax error */ + rc = PH7_GenCompileError(pGen,E_ERROR,pNode->pStart->nLine,"list(): expecting '=' after construct"); + if( rc != SXERR_ABORT ){ + rc = SXERR_SYNTAX; + } + SyMemBackendPoolFree(&pGen->pVm->sAllocator,pNode); + return rc; + } + } + } + }else if( nKeyword == PH7_TKWRD_FUNCTION ){ + /* Annonymous function */ + if( &pCur[1] >= pGen->pEnd ){ + /* Assume a literal */ + ExprAssembleLiteral(&pCur,pGen->pEnd); + pNode->xCode = PH7_CompileLiteral; + }else{ + /* Assemble annonymous functions body */ + rc = ExprAssembleAnnon(&(*pGen),&pCur,pGen->pEnd); + if( rc != SXRET_OK ){ + SyMemBackendPoolFree(&pGen->pVm->sAllocator,pNode); + return rc; + } + pNode->xCode = PH7_CompileAnnonFunc; + } + }else if( PH7_IsLangConstruct(nKeyword,FALSE) == TRUE && &pCur[1] < pGen->pEnd ){ + /* Language constructs [i.e: print,echo,die...] require special handling */ + PH7_DelimitNestedTokens(pCur,pGen->pEnd,PH7_TK_LPAREN|PH7_TK_OCB|PH7_TK_OSB, PH7_TK_RPAREN|PH7_TK_CCB|PH7_TK_CSB,&pCur); + pNode->xCode = PH7_CompileLangConstruct; + }else{ + /* Assume a literal */ + ExprAssembleLiteral(&pCur,pGen->pEnd); + pNode->xCode = PH7_CompileLiteral; + } + }else if( pCur->nType & (PH7_TK_NSSEP|PH7_TK_ID) ){ + /* Constants,function name,namespace path,class name... */ + ExprAssembleLiteral(&pCur,pGen->pEnd); + pNode->xCode = PH7_CompileLiteral; + }else{ + if( (pCur->nType & (PH7_TK_LPAREN|PH7_TK_RPAREN|PH7_TK_COMMA|PH7_TK_COLON|PH7_TK_CSB|PH7_TK_OCB|PH7_TK_CCB)) == 0 ){ + /* Point to the code generator routine */ + pNode->xCode = PH7_GetNodeHandler(pCur->nType); + if( pNode->xCode == 0 ){ + rc = PH7_GenCompileError(pGen,E_ERROR,pNode->pStart->nLine,"Syntax error: Unexpected token '%z'",&pNode->pStart->sData); + if( rc != SXERR_ABORT ){ + rc = SXERR_SYNTAX; + } + SyMemBackendPoolFree(&pGen->pVm->sAllocator,pNode); + return rc; + } + } + /* Advance the stream cursor */ + pCur++; + } + /* Point to the end of the token stream */ + pNode->pEnd = pCur; + /* Save the node for later processing */ + *ppNode = pNode; + /* Synchronize cursors */ + pGen->pIn = pCur; + return SXRET_OK; +} +/* + * Point to the next expression that should be evaluated shortly. + * The cursor stops when it hit a comma ',' or a semi-colon and the nesting + * level is zero. + */ +PH7_PRIVATE sxi32 PH7_GetNextExpr(SyToken *pStart,SyToken *pEnd,SyToken **ppNext) +{ + SyToken *pCur = pStart; + sxi32 iNest = 0; + if( pCur >= pEnd || (pCur->nType & PH7_TK_SEMI/*';'*/) ){ + /* Last expression */ + return SXERR_EOF; + } + while( pCur < pEnd ){ + if( (pCur->nType & (PH7_TK_COMMA/*','*/|PH7_TK_SEMI/*';'*/)) && iNest <= 0){ + break; + } + if( pCur->nType & (PH7_TK_LPAREN/*'('*/|PH7_TK_OSB/*'['*/|PH7_TK_OCB/*'{'*/) ){ + iNest++; + }else if( pCur->nType & (PH7_TK_RPAREN/*')'*/|PH7_TK_CSB/*']'*/|PH7_TK_CCB/*'}*/) ){ + iNest--; + } + pCur++; + } + *ppNext = pCur; + return SXRET_OK; +} +/* + * Free an expression tree. + */ +static void ExprFreeTree(ph7_gen_state *pGen,ph7_expr_node *pNode) +{ + if( pNode->pLeft ){ + /* Release the left tree */ + ExprFreeTree(&(*pGen),pNode->pLeft); + } + if( pNode->pRight ){ + /* Release the right tree */ + ExprFreeTree(&(*pGen),pNode->pRight); + } + if( pNode->pCond ){ + /* Release the conditional tree used by the ternary operator */ + ExprFreeTree(&(*pGen),pNode->pCond); + } + if( SySetUsed(&pNode->aNodeArgs) > 0 ){ + ph7_expr_node **apArg; + sxu32 n; + /* Release node arguments */ + apArg = (ph7_expr_node **)SySetBasePtr(&pNode->aNodeArgs); + for( n = 0 ; n < SySetUsed(&pNode->aNodeArgs) ; ++n ){ + ExprFreeTree(&(*pGen),apArg[n]); + } + SySetRelease(&pNode->aNodeArgs); + } + /* Finally,release this node */ + SyMemBackendPoolFree(&pGen->pVm->sAllocator,pNode); +} +/* + * Free an expression tree. + * This function is a wrapper around ExprFreeTree() defined above. + */ +PH7_PRIVATE sxi32 PH7_ExprFreeTree(ph7_gen_state *pGen,SySet *pNodeSet) +{ + ph7_expr_node **apNode; + sxu32 n; + apNode = (ph7_expr_node **)SySetBasePtr(pNodeSet); + for( n = 0 ; n < SySetUsed(pNodeSet) ; ++n ){ + if( apNode[n] ){ + ExprFreeTree(&(*pGen),apNode[n]); + } + } + return SXRET_OK; +} +/* + * Check if the given node is a modifialbe l/r-value. + * Return TRUE if modifiable.FALSE otherwise. + */ +static int ExprIsModifiableValue(ph7_expr_node *pNode,sxu8 bFunc) +{ + sxi32 iExprOp; + if( pNode->pOp == 0 ){ + return pNode->xCode == PH7_CompileVariable ? TRUE : FALSE; + } + iExprOp = pNode->pOp->iOp; + if( iExprOp == EXPR_OP_ARROW /*'->' */ || iExprOp == EXPR_OP_DC /*'::'*/ ){ + return TRUE; + } + if( iExprOp == EXPR_OP_SUBSCRIPT/*'[]'*/ ){ + if( pNode->pLeft->pOp ) { + if( pNode->pLeft->pOp->iOp != EXPR_OP_SUBSCRIPT /*'['*/ && pNode->pLeft->pOp->iOp != EXPR_OP_ARROW /*'->'*/ + && pNode->pLeft->pOp->iOp != EXPR_OP_DC /*'::'*/){ + return FALSE; + } + }else if( pNode->pLeft->xCode != PH7_CompileVariable ){ + return FALSE; + } + return TRUE; + } + if( bFunc && iExprOp == EXPR_OP_FUNC_CALL ){ + return TRUE; + } + /* Not a modifiable l or r-value */ + return FALSE; +} +/* Forward declaration */ +static sxi32 ExprMakeTree(ph7_gen_state *pGen,ph7_expr_node **apNode,sxi32 nToken); +/* Macro to check if the given node is a terminal */ +#define NODE_ISTERM(NODE) (apNode[NODE] && (!apNode[NODE]->pOp || apNode[NODE]->pLeft )) +/* + * Buid an expression tree for each given function argument. + * When errors,PH7 take care of generating the appropriate error message. + */ +static sxi32 ExprProcessFuncArguments(ph7_gen_state *pGen,ph7_expr_node *pOp,ph7_expr_node **apNode,sxi32 nToken) +{ + sxi32 iNest,iCur,iNode; + sxi32 rc; + /* Process function arguments from left to right */ + iCur = 0; + for(;;){ + if( iCur >= nToken ){ + /* No more arguments to process */ + break; + } + iNode = iCur; + iNest = 0; + while( iCur < nToken ){ + if( apNode[iCur] ){ + if( (apNode[iCur]->pStart->nType & PH7_TK_COMMA) && apNode[iCur]->pLeft == 0 && iNest <= 0 ){ + break; + }else if( apNode[iCur]->pStart->nType & (PH7_TK_LPAREN|PH7_TK_OSB|PH7_TK_OCB) ){ + iNest++; + }else if( apNode[iCur]->pStart->nType & (PH7_TK_RPAREN|PH7_TK_CCB|PH7_TK_CSB) ){ + iNest--; + } + } + iCur++; + } + if( iCur > iNode ){ + if( apNode[iNode] && (apNode[iNode]->pStart->nType & PH7_TK_AMPER /*'&'*/) && ((iCur - iNode) == 2) + && apNode[iNode+1]->xCode == PH7_CompileVariable ){ + PH7_GenCompileError(&(*pGen),E_WARNING,apNode[iNode]->pStart->nLine, + "call-time pass-by-reference is depreceated"); + ExprFreeTree(&(*pGen),apNode[iNode]); + apNode[iNode] = 0; + } + ExprMakeTree(&(*pGen),&apNode[iNode],iCur-iNode); + if( apNode[iNode] ){ + /* Put a pointer to the root of the tree in the arguments set */ + SySetPut(&pOp->aNodeArgs,(const void *)&apNode[iNode]); + }else{ + /* Empty function argument */ + rc = PH7_GenCompileError(&(*pGen),E_ERROR,pOp->pStart->nLine,"Empty function argument"); + if( rc != SXERR_ABORT ){ + rc = SXERR_SYNTAX; + } + return rc; + } + }else{ + rc = PH7_GenCompileError(&(*pGen),E_ERROR,pOp->pStart->nLine,"Missing function argument"); + if( rc != SXERR_ABORT ){ + rc = SXERR_SYNTAX; + } + return rc; + } + /* Jump trailing comma */ + if( iCur < nToken && apNode[iCur] && (apNode[iCur]->pStart->nType & PH7_TK_COMMA) ){ + iCur++; + if( iCur >= nToken ){ + /* missing function argument */ + rc = PH7_GenCompileError(&(*pGen),E_ERROR,pOp->pStart->nLine,"Missing function argument"); + if( rc != SXERR_ABORT ){ + rc = SXERR_SYNTAX; + } + return rc; + } + } + } + return SXRET_OK; +} + /* + * Create an expression tree from an array of tokens. + * If successful, the root of the tree is stored in apNode[0]. + * When errors,PH7 take care of generating the appropriate error message. + */ + static sxi32 ExprMakeTree(ph7_gen_state *pGen,ph7_expr_node **apNode,sxi32 nToken) + { + sxi32 i,iLeft,iRight; + ph7_expr_node *pNode; + sxi32 iCur; + sxi32 rc; + if( nToken <= 0 || (nToken == 1 && apNode[0]->xCode) ){ + /* TICKET 1433-17: self evaluating node */ + return SXRET_OK; + } + /* Process expressions enclosed in parenthesis first */ + for( iCur = 0 ; iCur < nToken ; ++iCur ){ + sxi32 iNest; + /* Note that, we use strict comparison here '!=' instead of the bitwise and '&' operator + * since the LPAREN token can also be an operator [i.e: Function call]. + */ + if( apNode[iCur] == 0 || apNode[iCur]->pStart->nType != PH7_TK_LPAREN ){ + continue; + } + iNest = 1; + iLeft = iCur; + /* Find the closing parenthesis */ + iCur++; + while( iCur < nToken ){ + if( apNode[iCur] ){ + if( apNode[iCur]->pStart->nType & PH7_TK_RPAREN /* ')' */){ + /* Decrement nesting level */ + iNest--; + if( iNest <= 0 ){ + break; + } + }else if( apNode[iCur]->pStart->nType & PH7_TK_LPAREN /* '(' */ ){ + /* Increment nesting level */ + iNest++; + } + } + iCur++; + } + if( iCur - iLeft > 1 ){ + /* Recurse and process this expression */ + rc = ExprMakeTree(&(*pGen),&apNode[iLeft + 1],iCur - iLeft - 1); + if( rc != SXRET_OK ){ + return rc; + } + } + /* Free the left and right nodes */ + ExprFreeTree(&(*pGen),apNode[iLeft]); + ExprFreeTree(&(*pGen),apNode[iCur]); + apNode[iLeft] = 0; + apNode[iCur] = 0; + } + /* Process expressions enclosed in braces */ + for( iCur = 0 ; iCur < nToken ; ++iCur ){ + sxi32 iNest; + /* Note that, we use strict comparison here '!=' instead of the bitwise and '&' operator + * since the OCB '{' token can also be an operator [i.e: subscripting]. + */ + if( apNode[iCur] == 0 || apNode[iCur]->pStart->nType != PH7_TK_OCB ){ + continue; + } + iNest = 1; + iLeft = iCur; + /* Find the closing parenthesis */ + iCur++; + while( iCur < nToken ){ + if( apNode[iCur] ){ + if( apNode[iCur]->pStart->nType & PH7_TK_CCB/*'}'*/){ + /* Decrement nesting level */ + iNest--; + if( iNest <= 0 ){ + break; + } + }else if( apNode[iCur]->pStart->nType & PH7_TK_OCB /*'{'*/ ){ + /* Increment nesting level */ + iNest++; + } + } + iCur++; + } + if( iCur - iLeft > 1 ){ + /* Recurse and process this expression */ + rc = ExprMakeTree(&(*pGen),&apNode[iLeft + 1],iCur - iLeft - 1); + if( rc != SXRET_OK ){ + return rc; + } + } + /* Free the left and right nodes */ + ExprFreeTree(&(*pGen),apNode[iLeft]); + ExprFreeTree(&(*pGen),apNode[iCur]); + apNode[iLeft] = 0; + apNode[iCur] = 0; + } + /* Handle postfix [i.e: function call,subscripting,member access] operators with precedence 2 */ + iLeft = -1; + for( iCur = 0 ; iCur < nToken ; ++iCur ){ + if( apNode[iCur] == 0 ){ + continue; + } + pNode = apNode[iCur]; + if( pNode->pOp && pNode->pOp->iPrec == 2 && pNode->pLeft == 0 ){ + if( pNode->pOp->iOp == EXPR_OP_FUNC_CALL ){ + /* Collect function arguments */ + sxi32 iPtr = 0; + sxi32 nFuncTok = 0; + while( nFuncTok + iCur < nToken ){ + if( apNode[nFuncTok+iCur] ){ + if( apNode[nFuncTok+iCur]->pStart->nType & PH7_TK_LPAREN /*'('*/ ){ + iPtr++; + }else if ( apNode[nFuncTok+iCur]->pStart->nType & PH7_TK_RPAREN /*')'*/){ + iPtr--; + if( iPtr <= 0 ){ + break; + } + } + } + nFuncTok++; + } + if( nFuncTok + iCur >= nToken ){ + /* Syntax error */ + rc = PH7_GenCompileError(pGen,E_ERROR,pNode->pStart->nLine,"Missing right parenthesis ')'"); + if( rc != SXERR_ABORT ){ + rc = SXERR_SYNTAX; + } + return rc; + } + if( iLeft < 0 || !NODE_ISTERM(iLeft) /*|| ( apNode[iLeft]->pOp && apNode[iLeft]->pOp->iPrec != 2)*/ ){ + /* Syntax error */ + rc = PH7_GenCompileError(pGen,E_ERROR,pNode->pStart->nLine,"Invalid function name"); + if( rc != SXERR_ABORT ){ + rc = SXERR_SYNTAX; + } + return rc; + } + if( nFuncTok > 1 ){ + /* Process function arguments */ + rc = ExprProcessFuncArguments(&(*pGen),pNode,&apNode[iCur+1],nFuncTok-1); + if( rc != SXRET_OK ){ + return rc; + } + } + /* Link the node to the tree */ + pNode->pLeft = apNode[iLeft]; + apNode[iLeft] = 0; + for( iPtr = 1; iPtr <= nFuncTok ; iPtr++ ){ + apNode[iCur+iPtr] = 0; + } + }else if (pNode->pOp->iOp == EXPR_OP_SUBSCRIPT ){ + /* Subscripting */ + sxi32 iArrTok = iCur + 1; + sxi32 iNest = 1; + if( iLeft < 0 || apNode[iLeft] == 0 || (apNode[iLeft]->pOp == 0 && (apNode[iLeft]->xCode != PH7_CompileVariable && + apNode[iLeft]->xCode != PH7_CompileSimpleString && apNode[iLeft]->xCode != PH7_CompileString ) ) || + ( apNode[iLeft]->pOp && apNode[iLeft]->pOp->iPrec != 2 /* postfix */) ){ + /* Syntax error */ + rc = PH7_GenCompileError(pGen,E_ERROR,pNode->pStart->nLine,"Invalid array name"); + if( rc != SXERR_ABORT ){ + rc = SXERR_SYNTAX; + } + return rc; + } + /* Collect index tokens */ + while( iArrTok < nToken ){ + if( apNode[iArrTok] ){ + if( apNode[iArrTok]->pOp && apNode[iArrTok]->pOp->iOp == EXPR_OP_SUBSCRIPT && apNode[iArrTok]->pLeft == 0){ + /* Increment nesting level */ + iNest++; + }else if( apNode[iArrTok]->pStart->nType & PH7_TK_CSB /*']'*/){ + /* Decrement nesting level */ + iNest--; + if( iNest <= 0 ){ + break; + } + } + } + ++iArrTok; + } + if( iArrTok > iCur + 1 ){ + /* Recurse and process this expression */ + rc = ExprMakeTree(&(*pGen),&apNode[iCur+1],iArrTok - iCur - 1); + if( rc != SXRET_OK ){ + return rc; + } + /* Link the node to it's index */ + SySetPut(&pNode->aNodeArgs,(const void *)&apNode[iCur+1]); + } + /* Link the node to the tree */ + pNode->pLeft = apNode[iLeft]; + pNode->pRight = 0; + apNode[iLeft] = 0; + for( iNest = iCur + 1 ; iNest <= iArrTok ; ++iNest ){ + apNode[iNest] = 0; + } + }else{ + /* Member access operators [i.e: '->','::'] */ + iRight = iCur + 1; + while( iRight < nToken && apNode[iRight] == 0 ){ + iRight++; + } + if( iRight >= nToken || iLeft < 0 || !NODE_ISTERM(iRight) || !NODE_ISTERM(iLeft) ){ + /* Syntax error */ + rc = PH7_GenCompileError(pGen,E_ERROR,pNode->pStart->nLine,"'%z': Missing/Invalid member name",&pNode->pOp->sOp); + if( rc != SXERR_ABORT ){ + rc = SXERR_SYNTAX; + } + return rc; + } + /* Link the node to the tree */ + pNode->pLeft = apNode[iLeft]; + if( pNode->pOp->iOp == EXPR_OP_ARROW /*'->'*/ && pNode->pLeft->pOp == 0 && + pNode->pLeft->xCode != PH7_CompileVariable ){ + /* Syntax error */ + rc = PH7_GenCompileError(pGen,E_ERROR,pNode->pStart->nLine, + "'%z': Expecting a variable as left operand",&pNode->pOp->sOp); + if( rc != SXERR_ABORT ){ + rc = SXERR_SYNTAX; + } + return rc; + } + pNode->pRight = apNode[iRight]; + apNode[iLeft] = apNode[iRight] = 0; + } + } + iLeft = iCur; + } + /* Handle left associative (new, clone) operators */ + for( iCur = 0 ; iCur < nToken ; ++iCur ){ + if( apNode[iCur] == 0 ){ + continue; + } + pNode = apNode[iCur]; + if( pNode->pOp && pNode->pOp->iPrec == 1 && pNode->pLeft == 0 ){ + SyToken *pToken; + /* Get the left node */ + iLeft = iCur + 1; + while( iLeft < nToken && apNode[iLeft] == 0 ){ + iLeft++; + } + if( iLeft >= nToken || !NODE_ISTERM(iLeft) ){ + /* Syntax error */ + rc = PH7_GenCompileError(pGen,E_ERROR,pNode->pStart->nLine,"'%z': Expecting class constructor call", + &pNode->pOp->sOp); + if( rc != SXERR_ABORT ){ + rc = SXERR_SYNTAX; + } + return rc; + } + /* Make sure the operand are of a valid type */ + if( pNode->pOp->iOp == EXPR_OP_CLONE ){ + /* Clone: + * Symisc eXtension: 'clone' accepts now as it's left operand: + * ++ function call (including annonymous) + * ++ array member + * ++ 'new' operator + * Example: + * clone $pObj; + * clone obj(); // function obj(){ return new Class(); } + * clone $a['object']; // $a = array('object' => new Class()); + */ + if( apNode[iLeft]->pOp == 0 ){ + if( apNode[iLeft]->xCode != PH7_CompileVariable ){ + pToken = apNode[iLeft]->pStart; + rc = PH7_GenCompileError(pGen,E_ERROR,pNode->pStart->nLine,"'%z': Unexpected token '%z'", + &pNode->pOp->sOp,&pToken->sData); + if( rc != SXERR_ABORT ){ + rc = SXERR_SYNTAX; + } + return rc; + } + } + }else{ + /* New */ + if( apNode[iLeft]->pOp == 0 ){ + ProcNodeConstruct xCons = apNode[iLeft]->xCode; + if( xCons != PH7_CompileVariable && xCons != PH7_CompileLiteral && xCons != PH7_CompileSimpleString){ + pToken = apNode[iLeft]->pStart; + /* Syntax error */ + rc = PH7_GenCompileError(pGen,E_ERROR,pNode->pStart->nLine, + "'%z': Unexpected token '%z', expecting literal, variable or constructor call", + &pNode->pOp->sOp,&pToken->sData); + if( rc != SXERR_ABORT ){ + rc = SXERR_SYNTAX; + } + return rc; + } + } + } + /* Link the node to the tree */ + pNode->pLeft = apNode[iLeft]; + apNode[iLeft] = 0; + pNode->pRight = 0; /* Paranoid */ + } + } + /* Handle post/pre icrement/decrement [i.e: ++/--] operators with precedence 3 */ + iLeft = -1; + for( iCur = 0 ; iCur < nToken ; ++iCur ){ + if( apNode[iCur] == 0 ){ + continue; + } + pNode = apNode[iCur]; + if( pNode->pOp && pNode->pOp->iPrec == 3 && pNode->pLeft == 0){ + if( iLeft >= 0 && ((apNode[iLeft]->pOp && apNode[iLeft]->pOp->iPrec == 2 /* Postfix */) + || apNode[iLeft]->xCode == PH7_CompileVariable) ){ + /* Link the node to the tree */ + pNode->pLeft = apNode[iLeft]; + apNode[iLeft] = 0; + } + } + iLeft = iCur; + } + iLeft = -1; + for( iCur = nToken - 1 ; iCur >= 0 ; iCur-- ){ + if( apNode[iCur] == 0 ){ + continue; + } + pNode = apNode[iCur]; + if( pNode->pOp && pNode->pOp->iPrec == 3 && pNode->pLeft == 0){ + if( iLeft < 0 || (apNode[iLeft]->pOp == 0 && apNode[iLeft]->xCode != PH7_CompileVariable) + || ( apNode[iLeft]->pOp && apNode[iLeft]->pOp->iPrec != 2 /* Postfix */) ){ + /* Syntax error */ + rc = PH7_GenCompileError(pGen,E_ERROR,pNode->pStart->nLine,"'%z' operator needs l-value",&pNode->pOp->sOp); + if( rc != SXERR_ABORT ){ + rc = SXERR_SYNTAX; + } + return rc; + } + /* Link the node to the tree */ + pNode->pLeft = apNode[iLeft]; + apNode[iLeft] = 0; + /* Mark as pre-increment/decrement node */ + pNode->iFlags |= EXPR_NODE_PRE_INCR; + } + iLeft = iCur; + } + /* Handle right associative unary and cast operators [i.e: !,(string),~...] with precedence 4*/ + iLeft = 0; + for( iCur = nToken - 1 ; iCur >= 0 ; iCur-- ){ + if( apNode[iCur] ){ + pNode = apNode[iCur]; + if( pNode->pOp && pNode->pOp->iPrec == 4 && pNode->pLeft == 0){ + if( iLeft > 0 ){ + /* Link the node to the tree */ + pNode->pLeft = apNode[iLeft]; + apNode[iLeft] = 0; + if( pNode->pLeft && pNode->pLeft->pOp && pNode->pLeft->pOp->iPrec > 4 ){ + if( pNode->pLeft->pLeft == 0 || pNode->pLeft->pRight == 0 ){ + /* Syntax error */ + rc = PH7_GenCompileError(pGen,E_ERROR,pNode->pLeft->pStart->nLine,"'%z': Missing operand",&pNode->pLeft->pOp->sOp); + if( rc != SXERR_ABORT ){ + rc = SXERR_SYNTAX; + } + return rc; + } + } + }else{ + /* Syntax error */ + rc = PH7_GenCompileError(pGen,E_ERROR,pNode->pStart->nLine,"'%z': Missing operand",&pNode->pOp->sOp); + if( rc != SXERR_ABORT ){ + rc = SXERR_SYNTAX; + } + return rc; + } + } + /* Save terminal position */ + iLeft = iCur; + } + } + /* Process left and non-associative binary operators [i.e: *,/,&&,||...]*/ + for( i = 7 ; i < 17 ; i++ ){ + iLeft = -1; + for( iCur = 0 ; iCur < nToken ; ++iCur ){ + if( apNode[iCur] == 0 ){ + continue; + } + pNode = apNode[iCur]; + if( pNode->pOp && pNode->pOp->iPrec == i && pNode->pLeft == 0 ){ + /* Get the right node */ + iRight = iCur + 1; + while( iRight < nToken && apNode[iRight] == 0 ){ + iRight++; + } + if( iRight >= nToken || iLeft < 0 || !NODE_ISTERM(iRight) || !NODE_ISTERM(iLeft) ){ + /* Syntax error */ + rc = PH7_GenCompileError(pGen,E_ERROR,pNode->pStart->nLine,"'%z': Missing/Invalid operand",&pNode->pOp->sOp); + if( rc != SXERR_ABORT ){ + rc = SXERR_SYNTAX; + } + return rc; + } + if( pNode->pOp->iOp == EXPR_OP_REF ){ + sxi32 iTmp; + /* Reference operator [i.e: '&=' ]*/ + if( ExprIsModifiableValue(apNode[iLeft],FALSE) == FALSE || (apNode[iLeft]->pOp && apNode[iLeft]->pOp->iVmOp == PH7_OP_MEMBER /*->,::*/) ){ + /* Left operand must be a modifiable l-value */ + rc = PH7_GenCompileError(pGen,E_ERROR,pNode->pStart->nLine,"'&': Left operand must be a modifiable l-value"); + if( rc != SXERR_ABORT ){ + rc = SXERR_SYNTAX; + } + return rc; + } + if( apNode[iLeft]->pOp == 0 || apNode[iLeft]->pOp->iOp != EXPR_OP_SUBSCRIPT /*$a[] =& 14*/) { + if( ExprIsModifiableValue(apNode[iRight],TRUE) == FALSE ){ + if( apNode[iRight]->pOp == 0 || (apNode[iRight]->pOp->iOp != EXPR_OP_NEW /* new */ + && apNode[iRight]->pOp->iOp != EXPR_OP_CLONE /* clone */) ){ + rc = PH7_GenCompileError(pGen,E_ERROR,pNode->pStart->nLine, + "Reference operator '&' require a variable not a constant expression as it's right operand"); + if( rc != SXERR_ABORT ){ + rc = SXERR_SYNTAX; + } + return rc; + } + } + } + /* Swap operands */ + iTmp = iRight; + iRight = iLeft; + iLeft = iTmp; + } + /* Link the node to the tree */ + pNode->pLeft = apNode[iLeft]; + pNode->pRight = apNode[iRight]; + apNode[iLeft] = apNode[iRight] = 0; + } + iLeft = iCur; + } + } + /* Handle the ternary operator. (expr1) ? (expr2) : (expr3) + * Note that we do not need a precedence loop here since + * we are dealing with a single operator. + */ + iLeft = -1; + for( iCur = 0 ; iCur < nToken ; ++iCur ){ + if( apNode[iCur] == 0 ){ + continue; + } + pNode = apNode[iCur]; + if( pNode->pOp && pNode->pOp->iOp == EXPR_OP_QUESTY && pNode->pLeft == 0 ){ + sxi32 iNest = 1; + if( iLeft < 0 || !NODE_ISTERM(iLeft) ){ + /* Missing condition */ + rc = PH7_GenCompileError(pGen,E_ERROR,pNode->pStart->nLine,"'%z': Syntax error",&pNode->pOp->sOp); + if( rc != SXERR_ABORT ){ + rc = SXERR_SYNTAX; + } + return rc; + } + /* Get the right node */ + iRight = iCur + 1; + while( iRight < nToken ){ + if( apNode[iRight] ){ + if( apNode[iRight]->pOp && apNode[iRight]->pOp->iOp == EXPR_OP_QUESTY && apNode[iRight]->pCond == 0){ + /* Increment nesting level */ + ++iNest; + }else if( apNode[iRight]->pStart->nType & PH7_TK_COLON /*:*/ ){ + /* Decrement nesting level */ + --iNest; + if( iNest <= 0 ){ + break; + } + } + } + iRight++; + } + if( iRight > iCur + 1 ){ + /* Recurse and process the then expression */ + rc = ExprMakeTree(&(*pGen),&apNode[iCur + 1],iRight - iCur - 1); + if( rc != SXRET_OK ){ + return rc; + } + /* Link the node to the tree */ + pNode->pLeft = apNode[iCur + 1]; + }else{ + rc = PH7_GenCompileError(pGen,E_ERROR,pNode->pStart->nLine,"'%z': Missing 'then' expression",&pNode->pOp->sOp); + if( rc != SXERR_ABORT ){ + rc = SXERR_SYNTAX; + } + return rc; + } + apNode[iCur + 1] = 0; + if( iRight + 1 < nToken ){ + /* Recurse and process the else expression */ + rc = ExprMakeTree(&(*pGen),&apNode[iRight + 1],nToken - iRight - 1); + if( rc != SXRET_OK ){ + return rc; + } + /* Link the node to the tree */ + pNode->pRight = apNode[iRight + 1]; + apNode[iRight + 1] = apNode[iRight] = 0; + }else{ + rc = PH7_GenCompileError(pGen,E_ERROR,pNode->pStart->nLine,"'%z': Missing 'else' expression",&pNode->pOp->sOp); + if( rc != SXERR_ABORT ){ + rc = SXERR_SYNTAX; + } + return rc; + } + /* Point to the condition */ + pNode->pCond = apNode[iLeft]; + apNode[iLeft] = 0; + break; + } + iLeft = iCur; + } + /* Process right associative binary operators [i.e: '=','+=','/='] + * Note: All right associative binary operators have precedence 18 + * so there is no need for a precedence loop here. + */ + iRight = -1; + for( iCur = nToken - 1 ; iCur >= 0 ; iCur--){ + if( apNode[iCur] == 0 ){ + continue; + } + pNode = apNode[iCur]; + if( pNode->pOp && pNode->pOp->iPrec == 18 && pNode->pLeft == 0 ){ + /* Get the left node */ + iLeft = iCur - 1; + while( iLeft >= 0 && apNode[iLeft] == 0 ){ + iLeft--; + } + if( iLeft < 0 || iRight < 0 || !NODE_ISTERM(iRight) || !NODE_ISTERM(iLeft) ){ + /* Syntax error */ + rc = PH7_GenCompileError(pGen,E_ERROR,pNode->pStart->nLine,"'%z': Missing/Invalid operand",&pNode->pOp->sOp); + if( rc != SXERR_ABORT ){ + rc = SXERR_SYNTAX; + } + return rc; + } + if( ExprIsModifiableValue(apNode[iLeft],FALSE) == FALSE ){ + if( pNode->pOp->iVmOp != PH7_OP_STORE || apNode[iLeft]->xCode != PH7_CompileList ){ + /* Left operand must be a modifiable l-value */ + rc = PH7_GenCompileError(pGen,E_ERROR,pNode->pStart->nLine, + "'%z': Left operand must be a modifiable l-value",&pNode->pOp->sOp); + if( rc != SXERR_ABORT ){ + rc = SXERR_SYNTAX; + } + return rc; + } + } + /* Link the node to the tree (Reverse) */ + pNode->pLeft = apNode[iRight]; + pNode->pRight = apNode[iLeft]; + apNode[iLeft] = apNode[iRight] = 0; + } + iRight = iCur; + } + /* Process left associative binary operators that have the lowest precedence [i.e: and,or,xor] */ + for( i = 19 ; i < 23 ; i++ ){ + iLeft = -1; + for( iCur = 0 ; iCur < nToken ; ++iCur ){ + if( apNode[iCur] == 0 ){ + continue; + } + pNode = apNode[iCur]; + if( pNode->pOp && pNode->pOp->iPrec == i && pNode->pLeft == 0 ){ + /* Get the right node */ + iRight = iCur + 1; + while( iRight < nToken && apNode[iRight] == 0 ){ + iRight++; + } + if( iRight >= nToken || iLeft < 0 || !NODE_ISTERM(iRight) || !NODE_ISTERM(iLeft) ){ + /* Syntax error */ + rc = PH7_GenCompileError(pGen,E_ERROR,pNode->pStart->nLine,"'%z': Missing/Invalid operand",&pNode->pOp->sOp); + if( rc != SXERR_ABORT ){ + rc = SXERR_SYNTAX; + } + return rc; + } + /* Link the node to the tree */ + pNode->pLeft = apNode[iLeft]; + pNode->pRight = apNode[iRight]; + apNode[iLeft] = apNode[iRight] = 0; + } + iLeft = iCur; + } + } + /* Point to the root of the expression tree */ + for( iCur = 1 ; iCur < nToken ; ++iCur ){ + if( apNode[iCur] ){ + if( (apNode[iCur]->pOp || apNode[iCur]->xCode ) && apNode[0] != 0){ + rc = PH7_GenCompileError(pGen,E_ERROR,apNode[iCur]->pStart->nLine,"Unexpected token '%z'",&apNode[iCur]->pStart->sData); + if( rc != SXERR_ABORT ){ + rc = SXERR_SYNTAX; + } + return rc; + } + apNode[0] = apNode[iCur]; + apNode[iCur] = 0; + } + } + return SXRET_OK; + } + /* + * Build an expression tree from the freshly extracted raw tokens. + * If successful, the root of the tree is stored in ppRoot. + * When errors,PH7 take care of generating the appropriate error message. + * This is the public interface used by the most code generator routines. + */ +PH7_PRIVATE sxi32 PH7_ExprMakeTree(ph7_gen_state *pGen,SySet *pExprNode,ph7_expr_node **ppRoot) +{ + ph7_expr_node **apNode; + ph7_expr_node *pNode; + sxi32 rc; + /* Reset node container */ + SySetReset(pExprNode); + pNode = 0; /* Prevent compiler warning */ + /* Extract nodes one after one until we hit the end of the input */ + while( pGen->pIn < pGen->pEnd ){ + rc = ExprExtractNode(&(*pGen),&pNode); + if( rc != SXRET_OK ){ + return rc; + } + /* Save the extracted node */ + SySetPut(pExprNode,(const void *)&pNode); + } + if( SySetUsed(pExprNode) < 1 ){ + /* Empty expression [i.e: A semi-colon;] */ + *ppRoot = 0; + return SXRET_OK; + } + apNode = (ph7_expr_node **)SySetBasePtr(pExprNode); + /* Make sure we are dealing with valid nodes */ + rc = ExprVerifyNodes(&(*pGen),apNode,(sxi32)SySetUsed(pExprNode)); + if( rc != SXRET_OK ){ + /* Don't worry about freeing memory,upper layer will + * cleanup the mess left behind. + */ + *ppRoot = 0; + return rc; + } + /* Build the tree */ + rc = ExprMakeTree(&(*pGen),apNode,(sxi32)SySetUsed(pExprNode)); + if( rc != SXRET_OK ){ + /* Something goes wrong [i.e: Syntax error] */ + *ppRoot = 0; + return rc; + } + /* Point to the root of the tree */ + *ppRoot = apNode[0]; + return SXRET_OK; +} +/* + * ---------------------------------------------------------- + * File: oo.c + * MD5: 4b7cc68a49eca23fc71ff7f103f5dfc7 + * ---------------------------------------------------------- + */ +/* + * Symisc PH7: An embeddable bytecode compiler and a virtual machine for the PHP(5) programming language. + * Copyright (C) 2011-2012, Symisc Systems http://ph7.symisc.net/ + * Version 2.1.4 + * For information on licensing,redistribution of this file,and for a DISCLAIMER OF ALL WARRANTIES + * please contact Symisc Systems via: + * legal@symisc.net + * licensing@symisc.net + * contact@symisc.net + * or visit: + * http://ph7.symisc.net/ + */ + /* $SymiscID: oo.c v1.9 FeeBSD 2012-07-17 03:44 devel $ */ +#ifndef PH7_AMALGAMATION +#include "ph7int.h" +#endif +/* + * This file implement an Object Oriented (OO) subsystem for the PH7 engine. + */ +/* + * Create an empty class. + * Return a pointer to a raw class (ph7_class instance) on success. NULL otherwise. + */ +PH7_PRIVATE ph7_class * PH7_NewRawClass(ph7_vm *pVm,const SyString *pName,sxu32 nLine) +{ + ph7_class *pClass; + char *zName; + /* Allocate a new instance */ + pClass = (ph7_class *)SyMemBackendPoolAlloc(&pVm->sAllocator,sizeof(ph7_class)); + if( pClass == 0 ){ + return 0; + } + /* Zero the structure */ + SyZero(pClass,sizeof(ph7_class)); + /* Duplicate class name */ + zName = SyMemBackendStrDup(&pVm->sAllocator,pName->zString,pName->nByte); + if( zName == 0 ){ + SyMemBackendPoolFree(&pVm->sAllocator,pClass); + return 0; + } + /* Initialize fields */ + SyStringInitFromBuf(&pClass->sName,zName,pName->nByte); + SyHashInit(&pClass->hMethod,&pVm->sAllocator,0,0); + SyHashInit(&pClass->hAttr,&pVm->sAllocator,0,0); + SyHashInit(&pClass->hDerived,&pVm->sAllocator,0,0); + SySetInit(&pClass->aInterface,&pVm->sAllocator,sizeof(ph7_class *)); + pClass->nLine = nLine; + /* All done */ + return pClass; +} +/* + * Allocate and initialize a new class attribute. + * Return a pointer to the class attribute on success. NULL otherwise. + */ +PH7_PRIVATE ph7_class_attr * PH7_NewClassAttr(ph7_vm *pVm,const SyString *pName,sxu32 nLine,sxi32 iProtection,sxi32 iFlags) +{ + ph7_class_attr *pAttr; + char *zName; + pAttr = (ph7_class_attr *)SyMemBackendPoolAlloc(&pVm->sAllocator,sizeof(ph7_class_attr)); + if( pAttr == 0 ){ + return 0; + } + /* Zero the structure */ + SyZero(pAttr,sizeof(ph7_class_attr)); + /* Duplicate attribute name */ + zName = SyMemBackendStrDup(&pVm->sAllocator,pName->zString,pName->nByte); + if( zName == 0 ){ + SyMemBackendPoolFree(&pVm->sAllocator,pAttr); + return 0; + } + /* Initialize fields */ + SySetInit(&pAttr->aByteCode,&pVm->sAllocator,sizeof(VmInstr)); + SyStringInitFromBuf(&pAttr->sName,zName,pName->nByte); + pAttr->iProtection = iProtection; + pAttr->nIdx = SXU32_HIGH; + pAttr->iFlags = iFlags; + pAttr->nLine = nLine; + return pAttr; +} +/* + * Allocate and initialize a new class method. + * Return a pointer to the class method on success. NULL otherwise + * This function associate with the newly created method an automatically generated + * random unique name. + */ +PH7_PRIVATE ph7_class_method * PH7_NewClassMethod(ph7_vm *pVm,ph7_class *pClass,const SyString *pName,sxu32 nLine, + sxi32 iProtection,sxi32 iFlags,sxi32 iFuncFlags) +{ + ph7_class_method *pMeth; + SyHashEntry *pEntry; + SyString *pNamePtr; + char zSalt[10]; + char *zName; + sxu32 nByte; + /* Allocate a new class method instance */ + pMeth = (ph7_class_method *)SyMemBackendPoolAlloc(&pVm->sAllocator,sizeof(ph7_class_method)); + if( pMeth == 0 ){ + return 0; + } + /* Zero the structure */ + SyZero(pMeth,sizeof(ph7_class_method)); + /* Check for an already installed method with the same name */ + pEntry = SyHashGet(&pClass->hMethod,(const void *)pName->zString,pName->nByte); + if( pEntry == 0 ){ + /* Associate an unique VM name to this method */ + nByte = sizeof(zSalt) + pName->nByte + SyStringLength(&pClass->sName)+sizeof(char)*7/*[[__'\0'*/; + zName = (char *)SyMemBackendAlloc(&pVm->sAllocator,nByte); + if( zName == 0 ){ + SyMemBackendPoolFree(&pVm->sAllocator,pMeth); + return 0; + } + pNamePtr = &pMeth->sVmName; + /* Generate a random string */ + PH7_VmRandomString(&(*pVm),zSalt,sizeof(zSalt)); + pNamePtr->nByte = SyBufferFormat(zName,nByte,"[__%z@%z_%.*s]",&pClass->sName,pName,sizeof(zSalt),zSalt); + pNamePtr->zString = zName; + }else{ + /* Method is condidate for 'overloading' */ + ph7_class_method *pCurrent = (ph7_class_method *)pEntry->pUserData; + pNamePtr = &pMeth->sVmName; + /* Use the same VM name */ + SyStringDupPtr(pNamePtr,&pCurrent->sVmName); + zName = (char *)pNamePtr->zString; + } + if( iProtection != PH7_CLASS_PROT_PUBLIC ){ + if( (pName->nByte == sizeof("__construct") - 1 && SyMemcmp(pName->zString,"__construct",sizeof("__construct") - 1 ) == 0) + || (pName->nByte == sizeof("__destruct") - 1 && SyMemcmp(pName->zString,"__destruct",sizeof("__destruct") - 1 ) == 0) + || SyStringCmp(pName,&pClass->sName,SyMemcmp) == 0 ){ + /* Switch to public visibility when dealing with constructor/destructor */ + iProtection = PH7_CLASS_PROT_PUBLIC; + } + } + /* Initialize method fields */ + pMeth->iProtection = iProtection; + pMeth->iFlags = iFlags; + pMeth->nLine = nLine; + PH7_VmInitFuncState(&(*pVm),&pMeth->sFunc,&zName[sizeof(char)*4/*[__@*/+SyStringLength(&pClass->sName)], + pName->nByte,iFuncFlags|VM_FUNC_CLASS_METHOD,pClass); + return pMeth; +} +/* + * Check if the given name have a class method associated with it. + * Return the desired method [i.e: ph7_class_method instance] on success. NULL otherwise. + */ +PH7_PRIVATE ph7_class_method * PH7_ClassExtractMethod(ph7_class *pClass,const char *zName,sxu32 nByte) +{ + SyHashEntry *pEntry; + /* Perform a hash lookup */ + pEntry = SyHashGet(&pClass->hMethod,(const void *)zName,nByte); + if( pEntry == 0 ){ + /* No such entry */ + return 0; + } + /* Point to the desired method */ + return (ph7_class_method *)pEntry->pUserData; +} +/* + * Check if the given name is a class attribute. + * Return the desired attribute [i.e: ph7_class_attr instance] on success.NULL otherwise. + */ +PH7_PRIVATE ph7_class_attr * PH7_ClassExtractAttribute(ph7_class *pClass,const char *zName,sxu32 nByte) +{ + SyHashEntry *pEntry; + /* Perform a hash lookup */ + pEntry = SyHashGet(&pClass->hAttr,(const void *)zName,nByte); + if( pEntry == 0 ){ + /* No such entry */ + return 0; + } + /* Point to the desierd method */ + return (ph7_class_attr *)pEntry->pUserData; +} +/* + * Install a class attribute in the corresponding container. + * Return SXRET_OK on success. Any other return value indicates failure. + */ +PH7_PRIVATE sxi32 PH7_ClassInstallAttr(ph7_class *pClass,ph7_class_attr *pAttr) +{ + SyString *pName = &pAttr->sName; + sxi32 rc; + rc = SyHashInsert(&pClass->hAttr,(const void *)pName->zString,pName->nByte,pAttr); + return rc; +} +/* + * Install a class method in the corresponding container. + * Return SXRET_OK on success. Any other return value indicates failure. + */ +PH7_PRIVATE sxi32 PH7_ClassInstallMethod(ph7_class *pClass,ph7_class_method *pMeth) +{ + SyString *pName = &pMeth->sFunc.sName; + sxi32 rc; + rc = SyHashInsert(&pClass->hMethod,(const void *)pName->zString,pName->nByte,pMeth); + return rc; +} +/* + * Perform an inheritance operation. + * According to the PHP language reference manual + * When you extend a class, the subclass inherits all of the public and protected methods + * from the parent class. Unless a class Overwrites those methods, they will retain their original + * functionality. + * This is useful for defining and abstracting functionality, and permits the implementation + * of additional functionality in similar objects without the need to reimplement all of the shared + * functionality. + * Example #1 Inheritance Example + * printItem('baz'); // Output: 'Foo: baz' + * $foo->printPHP(); // Output: 'PHP is great' + * $bar->printItem('baz'); // Output: 'Bar: baz' + * $bar->printPHP(); // Output: 'PHP is great' + * + * This function return SXRET_OK if the inheritance operation was successfully performed. + * Any other return value indicates failure and the upper layer must generate an appropriate + * error message. + */ +PH7_PRIVATE sxi32 PH7_ClassInherit(ph7_gen_state *pGen,ph7_class *pSub,ph7_class *pBase) +{ + ph7_class_method *pMeth; + ph7_class_attr *pAttr; + SyHashEntry *pEntry; + SyString *pName; + sxi32 rc; + /* Install in the derived hashtable */ + rc = SyHashInsert(&pBase->hDerived,(const void *)SyStringData(&pSub->sName),SyStringLength(&pSub->sName),pSub); + if( rc != SXRET_OK ){ + return rc; + } + /* Copy public/protected attributes from the base class */ + SyHashResetLoopCursor(&pBase->hAttr); + while((pEntry = SyHashGetNextEntry(&pBase->hAttr)) != 0 ){ + /* Make sure the private attributes are not redeclared in the subclass */ + pAttr = (ph7_class_attr *)pEntry->pUserData; + pName = &pAttr->sName; + if( (pEntry = SyHashGet(&pSub->hAttr,(const void *)pName->zString,pName->nByte)) != 0 ){ + if( pAttr->iProtection == PH7_CLASS_PROT_PRIVATE && + ((ph7_class_attr *)pEntry->pUserData)->iProtection != PH7_CLASS_PROT_PUBLIC ){ + /* Cannot redeclare private attribute */ + PH7_GenCompileError(&(*pGen),E_WARNING,((ph7_class_attr *)pEntry->pUserData)->nLine, + "Private attribute '%z::%z' redeclared inside child class '%z'", + &pBase->sName,pName,&pSub->sName); + + } + continue; + } + /* Install the attribute */ + if( pAttr->iProtection != PH7_CLASS_PROT_PRIVATE ){ + rc = SyHashInsert(&pSub->hAttr,(const void *)pName->zString,pName->nByte,pAttr); + if( rc != SXRET_OK ){ + return rc; + } + } + } + SyHashResetLoopCursor(&pBase->hMethod); + while((pEntry = SyHashGetNextEntry(&pBase->hMethod)) != 0 ){ + /* Make sure the private/final methods are not redeclared in the subclass */ + pMeth = (ph7_class_method *)pEntry->pUserData; + pName = &pMeth->sFunc.sName; + if( (pEntry = SyHashGet(&pSub->hMethod,(const void *)pName->zString,pName->nByte)) != 0 ){ + if( pMeth->iFlags & PH7_CLASS_ATTR_FINAL ){ + /* Cannot Overwrite final method */ + rc = PH7_GenCompileError(&(*pGen),E_ERROR,((ph7_class_method *)pEntry->pUserData)->nLine, + "Cannot Overwrite final method '%z:%z' inside child class '%z'", + &pBase->sName,pName,&pSub->sName); + if( rc == SXERR_ABORT ){ + return SXERR_ABORT; + } + } + continue; + }else{ + if( pMeth->iFlags & PH7_CLASS_ATTR_ABSTRACT ){ + /* Abstract method must be defined in the child class */ + PH7_GenCompileError(&(*pGen),E_WARNING,pMeth->nLine, + "Abstract method '%z:%z' must be defined inside child class '%z'", + &pBase->sName,pName,&pSub->sName); + continue; + } + } + /* Install the method */ + if( pMeth->iProtection != PH7_CLASS_PROT_PRIVATE ){ + rc = SyHashInsert(&pSub->hMethod,(const void *)pName->zString,pName->nByte,pMeth); + if( rc != SXRET_OK ){ + return rc; + } + } + } + /* Mark as subclass */ + pSub->pBase = pBase; + /* All done */ + return SXRET_OK; +} +/* + * Inherit an object interface from another object interface. + * According to the PHP language reference manual. + * Object interfaces allow you to create code which specifies which methods a class + * must implement, without having to define how these methods are handled. + * Interfaces are defined using the interface keyword, in the same way as a standard + * class, but without any of the methods having their contents defined. + * All methods declared in an interface must be public, this is the nature of an interface. + * + * This function return SXRET_OK if the interface inheritance operation was successfully performed. + * Any other return value indicates failure and the upper layer must generate an appropriate + * error message. + */ +PH7_PRIVATE sxi32 PH7_ClassInterfaceInherit(ph7_class *pSub,ph7_class *pBase) +{ + ph7_class_method *pMeth; + ph7_class_attr *pAttr; + SyHashEntry *pEntry; + SyString *pName; + sxi32 rc; + /* Install in the derived hashtable */ + SyHashInsert(&pBase->hDerived,(const void *)SyStringData(&pSub->sName),SyStringLength(&pSub->sName),pSub); + SyHashResetLoopCursor(&pBase->hAttr); + /* Copy constants */ + while((pEntry = SyHashGetNextEntry(&pBase->hAttr)) != 0 ){ + /* Make sure the constants are not redeclared in the subclass */ + pAttr = (ph7_class_attr *)pEntry->pUserData; + pName = &pAttr->sName; + if( SyHashGet(&pSub->hAttr,(const void *)pName->zString,pName->nByte) == 0 ){ + /* Install the constant in the subclass */ + rc = SyHashInsert(&pSub->hAttr,(const void *)pName->zString,pName->nByte,pAttr); + if( rc != SXRET_OK ){ + return rc; + } + } + } + SyHashResetLoopCursor(&pBase->hMethod); + /* Copy methods signature */ + while((pEntry = SyHashGetNextEntry(&pBase->hMethod)) != 0 ){ + /* Make sure the method are not redeclared in the subclass */ + pMeth = (ph7_class_method *)pEntry->pUserData; + pName = &pMeth->sFunc.sName; + if( SyHashGet(&pSub->hMethod,(const void *)pName->zString,pName->nByte) == 0 ){ + /* Install the method */ + rc = SyHashInsert(&pSub->hMethod,(const void *)pName->zString,pName->nByte,pMeth); + if( rc != SXRET_OK ){ + return rc; + } + } + } + /* Mark as subclass */ + pSub->pBase = pBase; + /* All done */ + return SXRET_OK; +} +/* + * Implements an object interface in the given main class. + * According to the PHP language reference manual. + * Object interfaces allow you to create code which specifies which methods a class + * must implement, without having to define how these methods are handled. + * Interfaces are defined using the interface keyword, in the same way as a standard + * class, but without any of the methods having their contents defined. + * All methods declared in an interface must be public, this is the nature of an interface. + * + * This function return SXRET_OK if the interface was successfully implemented. + * Any other return value indicates failure and the upper layer must generate an appropriate + * error message. + */ +PH7_PRIVATE sxi32 PH7_ClassImplement(ph7_class *pMain,ph7_class *pInterface) +{ + ph7_class_attr *pAttr; + SyHashEntry *pEntry; + SyString *pName; + sxi32 rc; + /* First off,copy all constants declared inside the interface */ + SyHashResetLoopCursor(&pInterface->hAttr); + while((pEntry = SyHashGetNextEntry(&pInterface->hAttr)) != 0 ){ + /* Point to the constant declaration */ + pAttr = (ph7_class_attr *)pEntry->pUserData; + pName = &pAttr->sName; + /* Make sure the attribute is not redeclared in the main class */ + if( SyHashGet(&pMain->hAttr,pName->zString,pName->nByte) == 0 ){ + /* Install the attribute */ + rc = SyHashInsert(&pMain->hAttr,pName->zString,pName->nByte,pAttr); + if( rc != SXRET_OK ){ + return rc; + } + } + } + /* Install in the interface container */ + SySetPut(&pMain->aInterface,(const void *)&pInterface); + /* TICKET 1433-49/1: Symisc eXtension + * A class may not implemnt all declared interface methods,so there + * is no need for a method installer loop here. + */ + return SXRET_OK; +} +/* + * Create a class instance [i.e: Object in the PHP jargon] at run-time. + * The following function is called when an object is created at run-time + * typically when the PH7_OP_NEW/PH7_OP_CLONE instructions are executed. + * Notes on object creation. + * + * According to PHP language reference manual. + * To create an instance of a class, the new keyword must be used. An object will always + * be created unless the object has a constructor defined that throws an exception on error. + * Classes should be defined before instantiation (and in some cases this is a requirement). + * If a string containing the name of a class is used with new, a new instance of that class + * will be created. If the class is in a namespace, its fully qualified name must be used when + * doing this. + * Example #3 Creating an instance + * + * In the class context, it is possible to create a new object by new self and new parent. + * When assigning an already created instance of a class to a new variable, the new variable + * will access the same instance as the object that was assigned. This behaviour is the same + * when passing instances to a function. A copy of an already created object can be made by + * cloning it. + * Example #4 Object Assignment + * var = '$assigned will have this value'; + * $instance = null; // $instance and $reference become null + * var_dump($instance); + * var_dump($reference); + * var_dump($assigned); + * ?> + * The above example will output: + * NULL + * NULL + * object(SimpleClass)#1 (1) { + * ["var"]=> + * string(30) "$assigned will have this value" + * } + * Example #5 Creating new objects + * + * The above example will output: + * bool(true) + * bool(true) + * bool(true) + * Note that Symisc Systems have introduced powerfull extension to + * OO subsystem. For example a class attribute may have any complex + * expression associated with it when declaring the attribute unlike + * the standard PHP engine which would allow a single value. + * Example: + * class myClass{ + * public $var = 25<<1+foo()/bar(); + * }; + * Refer to the official documentation for more information. + */ +static ph7_class_instance * NewClassInstance(ph7_vm *pVm,ph7_class *pClass) +{ + ph7_class_instance *pThis; + /* Allocate a new instance */ + pThis = (ph7_class_instance *)SyMemBackendPoolAlloc(&pVm->sAllocator,sizeof(ph7_class_instance)); + if( pThis == 0 ){ + return 0; + } + /* Zero the structure */ + SyZero(pThis,sizeof(ph7_class_instance)); + /* Initialize fields */ + pThis->iRef = 1; + pThis->pVm = pVm; + pThis->pClass = pClass; + SyHashInit(&pThis->hAttr,&pVm->sAllocator,0,0); + return pThis; +} +/* + * Wrapper around the NewClassInstance() function defined above. + * See the block comment above for more information. + */ +PH7_PRIVATE ph7_class_instance * PH7_NewClassInstance(ph7_vm *pVm,ph7_class *pClass) +{ + ph7_class_instance *pNew; + sxi32 rc; + pNew = NewClassInstance(&(*pVm),&(*pClass)); + if( pNew == 0 ){ + return 0; + } + /* Associate a private VM frame with this class instance */ + rc = PH7_VmCreateClassInstanceFrame(&(*pVm),pNew); + if( rc != SXRET_OK ){ + SyMemBackendPoolFree(&pVm->sAllocator,pNew); + return 0; + } + return pNew; +} +/* + * Extract the value of a class instance [i.e: Object in the PHP jargon] attribute. + * This function never fail. + */ +static ph7_value * ExtractClassAttrValue(ph7_vm *pVm,VmClassAttr *pAttr) +{ + /* Extract the value */ + ph7_value *pValue; + pValue = (ph7_value *)SySetAt(&pVm->aMemObj,pAttr->nIdx); + return pValue; +} +/* + * Perform a clone operation on a class instance [i.e: Object in the PHP jargon]. + * The following function is called when an object is cloned at run-time + * typically when the PH7_OP_CLONE instruction is executed. + * Notes on object cloning. + * + * According to PHP language reference manual. + * Creating a copy of an object with fully replicated properties is not always the wanted behavior. + * A good example of the need for copy constructors. Another example is if your object holds a reference + * to another object which it uses and when you replicate the parent object you want to create + * a new instance of this other object so that the replica has its own separate copy. + * An object copy is created by using the clone keyword (which calls the object's __clone() method if possible). + * An object's __clone() method cannot be called directly. + * $copy_of_object = clone $object; + * When an object is cloned, PHP 5 will perform a shallow copy of all of the object's properties. + * Any properties that are references to other variables, will remain references. + * Once the cloning is complete, if a __clone() method is defined, then the newly created object's __clone() method + * will be called, to allow any necessary properties that need to be changed. + * Example #1 Cloning an object + * instance = ++self::$instances; + * } + * + * public function __clone() { + * $this->instance = ++self::$instances; + * } + * } + * + * class MyCloneable + * { + * public $object1; + * public $object2; + * + * function __clone() + * { + * // Force a copy of this->object, otherwise + * // it will point to same object. + * $this->object1 = clone $this->object1; + * } + * } + * $obj = new MyCloneable(); + * $obj->object1 = new SubObject(); + * $obj->object2 = new SubObject(); + * $obj2 = clone $obj; + * print("Original Object:\n"); + * print_r($obj); + * print("Cloned Object:\n"); + * print_r($obj2); + * ?> + * The above example will output: + * Original Object: + * MyCloneable Object + * ( + * [object1] => SubObject Object + * ( + * [instance] => 1 + * ) + * + * [object2] => SubObject Object + * ( + * [instance] => 2 + * ) + * + * ) + * Cloned Object: + * MyCloneable Object + * ( + * [object1] => SubObject Object + * ( + * [instance] => 3 + * ) + * + * [object2] => SubObject Object + * ( + * [instance] => 2 + * ) + * ) + */ +PH7_PRIVATE ph7_class_instance * PH7_CloneClassInstance(ph7_class_instance *pSrc) +{ + ph7_class_instance *pClone; + ph7_class_method *pMethod; + SyHashEntry *pEntry2; + SyHashEntry *pEntry; + ph7_vm *pVm; + sxi32 rc; + /* Allocate a new instance */ + pVm = pSrc->pVm; + pClone = NewClassInstance(pVm,pSrc->pClass); + if( pClone == 0 ){ + return 0; + } + /* Associate a private VM frame with this class instance */ + rc = PH7_VmCreateClassInstanceFrame(pVm,pClone); + if( rc != SXRET_OK ){ + SyMemBackendPoolFree(&pVm->sAllocator,pClone); + return 0; + } + /* Duplicate object values */ + SyHashResetLoopCursor(&pSrc->hAttr); + SyHashResetLoopCursor(&pClone->hAttr); + while((pEntry = SyHashGetNextEntry(&pSrc->hAttr)) != 0 && (pEntry2 = SyHashGetNextEntry(&pClone->hAttr)) != 0 ){ + VmClassAttr *pSrcAttr = (VmClassAttr *)pEntry->pUserData; + VmClassAttr *pDestAttr = (VmClassAttr *)pEntry2->pUserData; + /* Duplicate non-static attribute */ + if( (pSrcAttr->pAttr->iFlags & (PH7_CLASS_ATTR_STATIC|PH7_CLASS_ATTR_CONSTANT)) == 0 ){ + ph7_value *pvSrc,*pvDest; + pvSrc = ExtractClassAttrValue(pVm,pSrcAttr); + pvDest = ExtractClassAttrValue(pVm,pDestAttr); + if( pvSrc && pvDest ){ + PH7_MemObjStore(pvSrc,pvDest); + } + } + } + /* call the __clone method on the cloned object if available */ + pMethod = PH7_ClassExtractMethod(pClone->pClass,"__clone",sizeof("__clone")-1); + if( pMethod ){ + if( pMethod->iCloneDepth < 16 ){ + pMethod->iCloneDepth++; + PH7_VmCallClassMethod(pVm,pClone,pMethod,0,0,0); + }else{ + /* Nesting limit reached */ + PH7_VmThrowError(pVm,0,PH7_CTX_ERR,"Object clone limit reached,no more call to __clone()"); + } + /* Reset the cursor */ + pMethod->iCloneDepth = 0; + } + /* Return the cloned object */ + return pClone; +} +#define CLASS_INSTANCE_DESTROYED 0x001 /* Instance is released */ +/* + * Release a class instance [i.e: Object in the PHP jargon] and invoke any defined destructor. + * This routine is invoked as soon as there are no other references to a particular + * class instance. + */ +static void PH7_ClassInstanceRelease(ph7_class_instance *pThis) +{ + ph7_class_method *pDestr; + SyHashEntry *pEntry; + ph7_class *pClass; + ph7_vm *pVm; + if( pThis->iFlags & CLASS_INSTANCE_DESTROYED ){ + /* + * Already destroyed,return immediately. + * This could happend if someone perform unset($this) in the destructor body. + */ + return; + } + /* Mark as destroyed */ + pThis->iFlags |= CLASS_INSTANCE_DESTROYED; + /* Invoke any defined destructor if available */ + pVm = pThis->pVm; + pClass = pThis->pClass; + pDestr = PH7_ClassExtractMethod(pClass,"__destruct",sizeof("__destruct")-1); + if( pDestr ){ + /* Invoke the destructor */ + pThis->iRef = 2; /* Prevent garbage collection */ + PH7_VmCallClassMethod(pVm,pThis,pDestr,0,0,0); + } + /* Release non-static attributes */ + SyHashResetLoopCursor(&pThis->hAttr); + while((pEntry = SyHashGetNextEntry(&pThis->hAttr)) != 0 ){ + VmClassAttr *pVmAttr = (VmClassAttr *)pEntry->pUserData; + if( (pVmAttr->pAttr->iFlags & (PH7_CLASS_ATTR_STATIC|PH7_CLASS_ATTR_CONSTANT)) == 0 ){ + PH7_VmUnsetMemObj(pVm,pVmAttr->nIdx,TRUE); + } + SyMemBackendPoolFree(&pVm->sAllocator,pVmAttr); + } + /* Release the whole structure */ + SyHashRelease(&pThis->hAttr); + SyMemBackendPoolFree(&pVm->sAllocator,pThis); +} +/* + * Decrement the reference count of a class instance [i.e Object in the PHP jargon]. + * If the reference count reaches zero,release the whole instance. + */ +PH7_PRIVATE void PH7_ClassInstanceUnref(ph7_class_instance *pThis) +{ + pThis->iRef--; + if( pThis->iRef < 1 ){ + /* No more reference to this instance */ + PH7_ClassInstanceRelease(&(*pThis)); + } +} +/* + * Compare two class instances [i.e: Objects in the PHP jargon] + * Note on objects comparison: + * According to the PHP langauge reference manual + * When using the comparison operator (==), object variables are compared in a simple manner + * namely: Two object instances are equal if they have the same attributes and values, and are + * instances of the same class. + * On the other hand, when using the identity operator (===), object variables are identical + * if and only if they refer to the same instance of the same class. + * An example will clarify these rules. + * Example #1 Example of object comparison + * flag = $flag; + * } + * } + * + * class OtherFlag + * { + * public $flag; + * + * function OtherFlag($flag = true) { + * $this->flag = $flag; + * } + * } + * + * $o = new Flag(); + * $p = new Flag(); + * $q = $o; + * $r = new OtherFlag(); + * + * echo "Two instances of the same class\n"; + * compareObjects($o, $p); + * echo "\nTwo references to the same instance\n"; + * compareObjects($o, $q); + * echo "\nInstances of two different classes\n"; + * compareObjects($o, $r); + * ?> + * The above example will output: + * Two instances of the same class + * o1 == o2 : TRUE + * o1 != o2 : FALSE + * o1 === o2 : FALSE + * o1 !== o2 : TRUE + * Two references to the same instance + * o1 == o2 : TRUE + * o1 != o2 : FALSE + * o1 === o2 : TRUE + * o1 !== o2 : FALSE + * Instances of two different classes + * o1 == o2 : FALSE + * o1 != o2 : TRUE + * o1 === o2 : FALSE + * o1 !== o2 : TRUE + * + * This function return 0 if the objects are equals according to the comprison rules defined above. + * Any other return values indicates difference. + */ +PH7_PRIVATE sxi32 PH7_ClassInstanceCmp(ph7_class_instance *pLeft,ph7_class_instance *pRight,int bStrict,int iNest) +{ + SyHashEntry *pEntry,*pEntry2; + ph7_value sV1,sV2; + sxi32 rc; + if( iNest > 31 ){ + /* Nesting limit reached */ + PH7_VmThrowError(pLeft->pVm,0,PH7_CTX_ERR,"Nesting limit reached: Infinite recursion?"); + return 1; + } + /* Comparison is performed only if the objects are instance of the same class */ + if( pLeft->pClass != pRight->pClass ){ + return 1; + } + if( bStrict ){ + /* + * According to the PHP language reference manual: + * when using the identity operator (===), object variables + * are identical if and only if they refer to the same instance + * of the same class. + */ + return !(pLeft == pRight); + } + /* + * Attribute comparison. + * According to the PHP reference manual: + * When using the comparison operator (==), object variables are compared + * in a simple manner, namely: Two object instances are equal if they have + * the same attributes and values, and are instances of the same class. + */ + if( pLeft == pRight ){ + /* Same instance,don't bother processing,object are equals */ + return 0; + } + SyHashResetLoopCursor(&pLeft->hAttr); + SyHashResetLoopCursor(&pRight->hAttr); + PH7_MemObjInit(pLeft->pVm,&sV1); + PH7_MemObjInit(pLeft->pVm,&sV2); + sV1.nIdx = sV2.nIdx = SXU32_HIGH; + while((pEntry = SyHashGetNextEntry(&pLeft->hAttr)) != 0 && (pEntry2 = SyHashGetNextEntry(&pRight->hAttr)) != 0 ){ + VmClassAttr *p1 = (VmClassAttr *)pEntry->pUserData; + VmClassAttr *p2 = (VmClassAttr *)pEntry2->pUserData; + /* Compare only non-static attribute */ + if( (p1->pAttr->iFlags & (PH7_CLASS_ATTR_CONSTANT|PH7_CLASS_ATTR_STATIC)) == 0 ){ + ph7_value *pL,*pR; + pL = ExtractClassAttrValue(pLeft->pVm,p1); + pR = ExtractClassAttrValue(pRight->pVm,p2); + if( pL && pR ){ + PH7_MemObjLoad(pL,&sV1); + PH7_MemObjLoad(pR,&sV2); + /* Compare the two values now */ + rc = PH7_MemObjCmp(&sV1,&sV2,bStrict,iNest+1); + PH7_MemObjRelease(&sV1); + PH7_MemObjRelease(&sV2); + if( rc != 0 ){ + /* Not equals */ + return rc; + } + } + } + } + /* Object are equals */ + return 0; +} +/* + * Dump a class instance and the store the dump in the BLOB given + * as the first argument. + * Note that only non-static/non-constants attribute are dumped. + * This function is typically invoked when the user issue a call + * to [var_dump(),var_export(),print_r(),...]. + * This function SXRET_OK on success. Any other return value including + * SXERR_LIMIT(infinite recursion) indicates failure. + */ +PH7_PRIVATE sxi32 PH7_ClassInstanceDump(SyBlob *pOut,ph7_class_instance *pThis,int ShowType,int nTab,int nDepth) +{ + SyHashEntry *pEntry; + ph7_value *pValue; + sxi32 rc; + int i; + if( nDepth > 31 ){ + static const char zInfinite[] = "Nesting limit reached: Infinite recursion?"; + /* Nesting limit reached..halt immediately*/ + SyBlobAppend(&(*pOut),zInfinite,sizeof(zInfinite)-1); + if( ShowType ){ + SyBlobAppend(&(*pOut),")",sizeof(char)); + } + return SXERR_LIMIT; + } + rc = SXRET_OK; + if( !ShowType ){ + SyBlobAppend(&(*pOut),"Object(",sizeof("Object(")-1); + } + /* Append class name */ + SyBlobFormat(&(*pOut),"%z) {",&pThis->pClass->sName); +#ifdef __WINNT__ + SyBlobAppend(&(*pOut),"\r\n",sizeof("\r\n")-1); +#else + SyBlobAppend(&(*pOut),"\n",sizeof(char)); +#endif + /* Dump object attributes */ + SyHashResetLoopCursor(&pThis->hAttr); + while((pEntry = SyHashGetNextEntry(&pThis->hAttr)) != 0){ + VmClassAttr *pVmAttr = (VmClassAttr *)pEntry->pUserData; + if((pVmAttr->pAttr->iFlags & (PH7_CLASS_ATTR_CONSTANT|PH7_CLASS_ATTR_STATIC)) == 0 ){ + /* Dump non-static/constant attribute only */ + for( i = 0 ; i < nTab ; i++ ){ + SyBlobAppend(&(*pOut)," ",sizeof(char)); + } + pValue = ExtractClassAttrValue(pThis->pVm,pVmAttr); + if( pValue ){ + SyBlobFormat(&(*pOut),"['%z'] =>",&pVmAttr->pAttr->sName); +#ifdef __WINNT__ + SyBlobAppend(&(*pOut),"\r\n",sizeof("\r\n")-1); +#else + SyBlobAppend(&(*pOut),"\n",sizeof(char)); +#endif + rc = PH7_MemObjDump(&(*pOut),pValue,ShowType,nTab+1,nDepth,0); + if( rc == SXERR_LIMIT ){ + break; + } + } + } + } + for( i = 0 ; i < nTab ; i++ ){ + SyBlobAppend(&(*pOut)," ",sizeof(char)); + } + SyBlobAppend(&(*pOut),"}",sizeof(char)); + return rc; +} +/* + * Call a magic method [i.e: __toString(),__toBool(),__Invoke()...] + * Return SXRET_OK on successfull call. Any other return value indicates failure. + * Notes on magic methods. + * According to the PHP language reference manual. + * The function names __construct(), __destruct(), __call(), __callStatic() + * __get(), __toString(), __invoke(), __clone() are magical in PHP classes. + * You cannot have functions with these names in any of your classes unless + * you want the magic functionality associated with them. + * Example of magical methods: + * __toString() + * The __toString() method allows a class to decide how it will react when it is treated like + * a string. For example, what echo $obj; will print. This method must return a string. + * Example #2 Simple example + * foo = $foo; + * } + * + * public function __toString() + * { + * return $this->foo; + * } + * } + * $class = new TestClass('Hello'); + * echo $class; + * ?> + * The above example will output: + * Hello + * + * Note that PH7 does not support all the magical method and introudces __toFloat(),__toInt() + * which have the same behaviour as __toString() but for float and integer types + * respectively. + * Refer to the official documentation for more information. + */ +PH7_PRIVATE sxi32 PH7_ClassInstanceCallMagicMethod( + ph7_vm *pVm, /* VM that own all this stuff */ + ph7_class *pClass, /* Target class */ + ph7_class_instance *pThis, /* Target object */ + const char *zMethod, /* Magic method name [i.e: __toString()]*/ + sxu32 nByte, /* zMethod length*/ + const SyString *pAttrName /* Attribute name */ + ) +{ + ph7_value *apArg[2] = { 0 , 0 }; + ph7_class_method *pMeth; + ph7_value sAttr; /* cc warning */ + sxi32 rc; + int nArg; + /* Make sure the magic method is available */ + pMeth = PH7_ClassExtractMethod(&(*pClass),zMethod,nByte); + if( pMeth == 0 ){ + /* No such method,return immediately */ + return SXERR_NOTFOUND; + } + nArg = 0; + /* Copy arguments */ + if( pAttrName ){ + PH7_MemObjInitFromString(pVm,&sAttr,pAttrName); + sAttr.nIdx = SXU32_HIGH; /* Mark as constant */ + apArg[0] = &sAttr; + nArg = 1; + } + /* Call the magic method now */ + rc = PH7_VmCallClassMethod(pVm,&(*pThis),pMeth,0,nArg,apArg); + /* Clean up */ + if( pAttrName ){ + PH7_MemObjRelease(&sAttr); + } + return rc; +} +/* + * Extract the value of a class instance [i.e: Object in the PHP jargon]. + * This function is simply a wrapper on ExtractClassAttrValue(). + */ +PH7_PRIVATE ph7_value * PH7_ClassInstanceExtractAttrValue(ph7_class_instance *pThis,VmClassAttr *pAttr) +{ + /* Extract the attribute value */ + ph7_value *pValue; + pValue = ExtractClassAttrValue(pThis->pVm,pAttr); + return pValue; +} +/* + * Convert a class instance [i.e: Object in the PHP jargon] into a hashmap [i.e: array in the PHP jargon]. + * Return SXRET_OK on success. Any other value indicates failure. + * Note on object conversion to array: + * Acccording to the PHP language reference manual + * If an object is converted to an array, the result is an array whose elements are the object's properties. + * The keys are the member variable names. + * + * The following example: + * class Test { + * public $A = 25<<1; // 50 + * public $c = rand_str(3); // Random string of length 3 + * public $d = rand() & 1023; // Random number between 0..1023 + * } + * var_dump((array) new Test()); + * Will output: + * array(3) { + * [A] => + * int(50) + * [c] => + * string(3 'aps') + * [d] => + * int(991) + * } + * You have noticed that PH7 allow class attributes [i.e: $a,$c,$d in the example above] + * have any complex expression (even function calls/Annonymous functions) as their default + * value unlike the standard PHP engine. + * This is a very powerful feature that you have to look at. + */ +PH7_PRIVATE sxi32 PH7_ClassInstanceToHashmap(ph7_class_instance *pThis,ph7_hashmap *pMap) +{ + SyHashEntry *pEntry; + SyString *pAttrName; + VmClassAttr *pAttr; + ph7_value *pValue; + ph7_value sName; + /* Reset the loop cursor */ + SyHashResetLoopCursor(&pThis->hAttr); + PH7_MemObjInitFromString(pThis->pVm,&sName,0); + while((pEntry = SyHashGetNextEntry(&pThis->hAttr)) != 0 ){ + /* Point to the current attribute */ + pAttr = (VmClassAttr *)pEntry->pUserData; + /* Extract attribute value */ + pValue = ExtractClassAttrValue(pThis->pVm,pAttr); + if( pValue ){ + /* Build attribute name */ + pAttrName = &pAttr->pAttr->sName; + PH7_MemObjStringAppend(&sName,pAttrName->zString,pAttrName->nByte); + /* Perform the insertion */ + PH7_HashmapInsert(pMap,&sName,pValue); + /* Reset the string cursor */ + SyBlobReset(&sName.sBlob); + } + } + PH7_MemObjRelease(&sName); + return SXRET_OK; +} +/* + * Iterate throw class attributes and invoke the given callback [i.e: xWalk()] for each + * retrieved attribute. + * Note that argument are passed to the callback by copy. That is,any modification to + * the attribute value in the callback body will not alter the real attribute value. + * If the callback wishes to abort processing [i.e: it's invocation] it must return + * a value different from PH7_OK. + * Refer to [ph7_object_walk()] for more information. + */ +PH7_PRIVATE sxi32 PH7_ClassInstanceWalk( + ph7_class_instance *pThis, /* Target object */ + int (*xWalk)(const char *,ph7_value *,void *), /* Walker callback */ + void *pUserData /* Last argument to xWalk() */ + ) +{ + SyHashEntry *pEntry; /* Hash entry */ + VmClassAttr *pAttr; /* Pointer to the attribute */ + ph7_value *pValue; /* Attribute value */ + ph7_value sValue; /* Copy of the attribute value */ + int rc; + /* Reset the loop cursor */ + SyHashResetLoopCursor(&pThis->hAttr); + PH7_MemObjInit(pThis->pVm,&sValue); + /* Start the walk process */ + while((pEntry = SyHashGetNextEntry(&pThis->hAttr)) != 0 ){ + /* Point to the current attribute */ + pAttr = (VmClassAttr *)pEntry->pUserData; + /* Extract attribute value */ + pValue = ExtractClassAttrValue(pThis->pVm,pAttr); + if( pValue ){ + PH7_MemObjLoad(pValue,&sValue); + /* Invoke the supplied callback */ + rc = xWalk(SyStringData(&pAttr->pAttr->sName),&sValue,pUserData); + PH7_MemObjRelease(&sValue); + if( rc != PH7_OK){ + /* User callback request an operation abort */ + return SXERR_ABORT; + } + } + } + /* All done */ + return SXRET_OK; +} +/* + * Extract a class atrribute value. + * Return a pointer to the attribute value on success. Otherwise NULL. + * Note: + * Access to static and constant attribute is not allowed. That is,the function + * will return NULL in case someone (host-application code) try to extract + * a static/constant attribute. + */ +PH7_PRIVATE ph7_value * PH7_ClassInstanceFetchAttr(ph7_class_instance *pThis,const SyString *pName) +{ + SyHashEntry *pEntry; + VmClassAttr *pAttr; + /* Query the attribute hashtable */ + pEntry = SyHashGet(&pThis->hAttr,(const void *)pName->zString,pName->nByte); + if( pEntry == 0 ){ + /* No such attribute */ + return 0; + } + /* Point to the class atrribute */ + pAttr = (VmClassAttr *)pEntry->pUserData; + /* Check if we are dealing with a static/constant attribute */ + if( pAttr->pAttr->iFlags & (PH7_CLASS_ATTR_CONSTANT|PH7_CLASS_ATTR_STATIC) ){ + /* Access is forbidden */ + return 0; + } + /* Return the attribute value */ + return ExtractClassAttrValue(pThis->pVm,pAttr); +} +/* + * ---------------------------------------------------------- + * File: memobj.c + * MD5: 02f3ad7da7ab382b1d5bf779013b4d7b + * ---------------------------------------------------------- + */ +/* + * Symisc PH7: An embeddable bytecode compiler and a virtual machine for the PHP(5) programming language. + * Copyright (C) 2011-2012, Symisc Systems http://ph7.symisc.net/ + * Version 2.1.4 + * For information on licensing,redistribution of this file,and for a DISCLAIMER OF ALL WARRANTIES + * please contact Symisc Systems via: + * legal@symisc.net + * licensing@symisc.net + * contact@symisc.net + * or visit: + * http://ph7.symisc.net/ + */ + /* $SymiscID: memobj.c v2.7 FreeBSD 2012-08-09 03:40 stable $ */ +#ifndef PH7_AMALGAMATION +#include "ph7int.h" +#endif +/* This file handle low-level stuff related to indexed memory objects [i.e: ph7_value] */ +/* + * Notes on memory objects [i.e: ph7_value]. + * Internally, the PH7 virtual machine manipulates nearly all PHP values + * [i.e: string,int,float,resource,object,bool,null..] as ph7_values structures. + * Each ph7_values struct may cache multiple representations (string, + * integer etc.) of the same value. + */ +/* + * Convert a 64-bit IEEE double into a 64-bit signed integer. + * If the double is too large, return 0x8000000000000000. + * + * Most systems appear to do this simply by assigning ariables and without + * the extra range tests. + * But there are reports that windows throws an expection if the floating + * point value is out of range. + */ +static sxi64 MemObjRealToInt(ph7_value *pObj) +{ +#ifdef PH7_OMIT_FLOATING_POINT + /* Real and 64bit integer are the same when floating point arithmetic + * is omitted from the build. + */ + return pObj->rVal; +#else + /* + ** Many compilers we encounter do not define constants for the + ** minimum and maximum 64-bit integers, or they define them + ** inconsistently. And many do not understand the "LL" notation. + ** So we define our own static constants here using nothing + ** larger than a 32-bit integer constant. + */ + static const sxi64 maxInt = LARGEST_INT64; + static const sxi64 minInt = SMALLEST_INT64; + ph7_real r = pObj->rVal; + if( r<(ph7_real)minInt ){ + return minInt; + }else if( r>(ph7_real)maxInt ){ + /* minInt is correct here - not maxInt. It turns out that assigning + ** a very large positive number to an integer results in a very large + ** negative integer. This makes no sense, but it is what x86 hardware + ** does so for compatibility we will do the same in software. */ + return minInt; + }else{ + return (sxi64)r; + } +#endif +} +/* + * Convert a raw token value typically a stream of digit [i.e: hex,octal,binary or decimal] + * to a 64-bit integer. + */ +PH7_PRIVATE sxi64 PH7_TokenValueToInt64(SyString *pVal) +{ + sxi64 iVal = 0; + if( pVal->nByte <= 0 ){ + return 0; + } + if( pVal->zString[0] == '0' ){ + sxi32 c; + if( pVal->nByte == sizeof(char) ){ + return 0; + } + c = pVal->zString[1]; + if( c == 'x' || c == 'X' ){ + /* Hex digit stream */ + SyHexStrToInt64(pVal->zString,pVal->nByte,(void *)&iVal,0); + }else if( c == 'b' || c == 'B' ){ + /* Binary digit stream */ + SyBinaryStrToInt64(pVal->zString,pVal->nByte,(void *)&iVal,0); + }else{ + /* Octal digit stream */ + SyOctalStrToInt64(pVal->zString,pVal->nByte,(void *)&iVal,0); + } + }else{ + /* Decimal digit stream */ + SyStrToInt64(pVal->zString,pVal->nByte,(void *)&iVal,0); + } + return iVal; +} +/* + * Return some kind of 64-bit integer value which is the best we can + * do at representing the value that pObj describes as a string + * representation. + */ +static sxi64 MemObjStringToInt(ph7_value *pObj) +{ + SyString sVal; + SyStringInitFromBuf(&sVal,SyBlobData(&pObj->sBlob),SyBlobLength(&pObj->sBlob)); + return PH7_TokenValueToInt64(&sVal); +} +/* + * Call a magic class method [i.e: __toString(),__toInt(),...] + * Return SXRET_OK if the magic method is available and have been + * successfully called. Any other return value indicates failure. + */ +static sxi32 MemObjCallClassCastMethod( + ph7_vm *pVm, /* VM that trigger the invocation */ + ph7_class_instance *pThis, /* Target class instance [i.e: Object] */ + const char *zMethod, /* Magic method name [i.e: __toString] */ + sxu32 nLen, /* Method name length */ + ph7_value *pResult /* OUT: Store the return value of the magic method here */ + ) +{ + ph7_class_method *pMethod; + /* Check if the method is available */ + pMethod = PH7_ClassExtractMethod(pThis->pClass,zMethod,nLen); + if( pMethod == 0 ){ + /* No such method */ + return SXERR_NOTFOUND; + } + /* Invoke the desired method */ + PH7_VmCallClassMethod(&(*pVm),&(*pThis),pMethod,&(*pResult),0,0); + /* Method successfully called,pResult should hold the return value */ + return SXRET_OK; +} +/* + * Return some kind of integer value which is the best we can + * do at representing the value that pObj describes as an integer. + * If pObj is an integer, then the value is exact. If pObj is + * a floating-point then the value returned is the integer part. + * If pObj is a string, then we make an attempt to convert it into + * a integer and return that. + * If pObj represents a NULL value, return 0. + */ +static sxi64 MemObjIntValue(ph7_value *pObj) +{ + sxi32 iFlags; + iFlags = pObj->iFlags; + if (iFlags & MEMOBJ_REAL ){ + return MemObjRealToInt(&(*pObj)); + }else if( iFlags & (MEMOBJ_INT|MEMOBJ_BOOL) ){ + return pObj->x.iVal; + }else if (iFlags & MEMOBJ_STRING) { + return MemObjStringToInt(&(*pObj)); + }else if( iFlags & MEMOBJ_NULL ){ + return 0; + }else if( iFlags & MEMOBJ_HASHMAP ){ + ph7_hashmap *pMap = (ph7_hashmap *)pObj->x.pOther; + sxu32 n = pMap->nEntry; + PH7_HashmapUnref(pMap); + /* Return total number of entries in the hashmap */ + return n; + }else if( iFlags & MEMOBJ_OBJ ){ + ph7_value sResult; + sxi64 iVal = 1; + sxi32 rc; + /* Invoke the [__toInt()] magic method if available [note that this is a symisc extension] */ + PH7_MemObjInit(pObj->pVm,&sResult); + rc = MemObjCallClassCastMethod(pObj->pVm,(ph7_class_instance *)pObj->x.pOther, + "__toInt",sizeof("__toInt")-1,&sResult); + if( rc == SXRET_OK && (sResult.iFlags & MEMOBJ_INT) ){ + /* Extract method return value */ + iVal = sResult.x.iVal; + } + PH7_ClassInstanceUnref((ph7_class_instance *)pObj->x.pOther); + PH7_MemObjRelease(&sResult); + return iVal; + }else if(iFlags & MEMOBJ_RES ){ + return pObj->x.pOther != 0; + } + /* CANT HAPPEN */ + return 0; +} +/* + * Return some kind of real value which is the best we can + * do at representing the value that pObj describes as a real. + * If pObj is a real, then the value is exact.If pObj is an + * integer then the integer is promoted to real and that value + * is returned. + * If pObj is a string, then we make an attempt to convert it + * into a real and return that. + * If pObj represents a NULL value, return 0.0 + */ +static ph7_real MemObjRealValue(ph7_value *pObj) +{ + sxi32 iFlags; + iFlags = pObj->iFlags; + if( iFlags & MEMOBJ_REAL ){ + return pObj->rVal; + }else if (iFlags & (MEMOBJ_INT|MEMOBJ_BOOL) ){ + return (ph7_real)pObj->x.iVal; + }else if (iFlags & MEMOBJ_STRING){ + SyString sString; +#ifdef PH7_OMIT_FLOATING_POINT + ph7_real rVal = 0; +#else + ph7_real rVal = 0.0; +#endif + SyStringInitFromBuf(&sString,SyBlobData(&pObj->sBlob),SyBlobLength(&pObj->sBlob)); + if( SyBlobLength(&pObj->sBlob) > 0 ){ + /* Convert as much as we can */ +#ifdef PH7_OMIT_FLOATING_POINT + rVal = MemObjStringToInt(&(*pObj)); +#else + SyStrToReal(sString.zString,sString.nByte,(void *)&rVal,0); +#endif + } + return rVal; + }else if( iFlags & MEMOBJ_NULL ){ +#ifdef PH7_OMIT_FLOATING_POINT + return 0; +#else + return 0.0; +#endif + }else if( iFlags & MEMOBJ_HASHMAP ){ + /* Return the total number of entries in the hashmap */ + ph7_hashmap *pMap = (ph7_hashmap *)pObj->x.pOther; + ph7_real n = (ph7_real)pMap->nEntry; + PH7_HashmapUnref(pMap); + return n; + }else if( iFlags & MEMOBJ_OBJ ){ + ph7_value sResult; + ph7_real rVal = 1; + sxi32 rc; + /* Invoke the [__toFloat()] magic method if available [note that this is a symisc extension] */ + PH7_MemObjInit(pObj->pVm,&sResult); + rc = MemObjCallClassCastMethod(pObj->pVm,(ph7_class_instance *)pObj->x.pOther, + "__toFloat",sizeof("__toFloat")-1,&sResult); + if( rc == SXRET_OK && (sResult.iFlags & MEMOBJ_REAL) ){ + /* Extract method return value */ + rVal = sResult.rVal; + } + PH7_ClassInstanceUnref((ph7_class_instance *)pObj->x.pOther); + PH7_MemObjRelease(&sResult); + return rVal; + }else if(iFlags & MEMOBJ_RES ){ + return (ph7_real)(pObj->x.pOther != 0); + } + /* NOT REACHED */ + return 0; +} +/* + * Return the string representation of a given ph7_value. + * This function never fail and always return SXRET_OK. + */ +static sxi32 MemObjStringValue(SyBlob *pOut,ph7_value *pObj,sxu8 bStrictBool) +{ + if( pObj->iFlags & MEMOBJ_REAL ){ + SyBlobFormat(&(*pOut),"%.15g",pObj->rVal); + }else if( pObj->iFlags & MEMOBJ_INT ){ + SyBlobFormat(&(*pOut),"%qd",pObj->x.iVal); + /* %qd (BSD quad) is equivalent to %lld in the libc printf */ + }else if( pObj->iFlags & MEMOBJ_BOOL ){ + if( pObj->x.iVal ){ + SyBlobAppend(&(*pOut),"TRUE",sizeof("TRUE")-1); + }else{ + if( !bStrictBool ){ + SyBlobAppend(&(*pOut),"FALSE",sizeof("FALSE")-1); + } + } + }else if( pObj->iFlags & MEMOBJ_HASHMAP ){ + SyBlobAppend(&(*pOut),"Array",sizeof("Array")-1); + PH7_HashmapUnref((ph7_hashmap *)pObj->x.pOther); + }else if( pObj->iFlags & MEMOBJ_OBJ ){ + ph7_value sResult; + sxi32 rc; + /* Invoke the __toString() method if available */ + PH7_MemObjInit(pObj->pVm,&sResult); + rc = MemObjCallClassCastMethod(pObj->pVm,(ph7_class_instance *)pObj->x.pOther, + "__toString",sizeof("__toString")-1,&sResult); + if( rc == SXRET_OK && (sResult.iFlags & MEMOBJ_STRING) && SyBlobLength(&sResult.sBlob) > 0){ + /* Expand method return value */ + SyBlobDup(&sResult.sBlob,pOut); + }else{ + /* Expand "Object" as requested by the PHP language reference manual */ + SyBlobAppend(&(*pOut),"Object",sizeof("Object")-1); + } + PH7_ClassInstanceUnref((ph7_class_instance *)pObj->x.pOther); + PH7_MemObjRelease(&sResult); + }else if(pObj->iFlags & MEMOBJ_RES ){ + SyBlobFormat(&(*pOut),"ResourceID_%#x",pObj->x.pOther); + } + return SXRET_OK; +} +/* + * Return some kind of boolean value which is the best we can do + * at representing the value that pObj describes as a boolean. + * When converting to boolean, the following values are considered FALSE: + * NULL + * the boolean FALSE itself. + * the integer 0 (zero). + * the real 0.0 (zero). + * the empty string,a stream of zero [i.e: "0","00","000",...] and the string + * "false". + * an array with zero elements. + */ +static sxi32 MemObjBooleanValue(ph7_value *pObj) +{ + sxi32 iFlags; + iFlags = pObj->iFlags; + if (iFlags & MEMOBJ_REAL ){ +#ifdef PH7_OMIT_FLOATING_POINT + return pObj->rVal ? 1 : 0; +#else + return pObj->rVal != 0.0 ? 1 : 0; +#endif + }else if( iFlags & MEMOBJ_INT ){ + return pObj->x.iVal ? 1 : 0; + }else if (iFlags & MEMOBJ_STRING) { + SyString sString; + SyStringInitFromBuf(&sString,SyBlobData(&pObj->sBlob),SyBlobLength(&pObj->sBlob)); + if( sString.nByte == 0 ){ + /* Empty string */ + return 0; + }else if( (sString.nByte == sizeof("true") - 1 && SyStrnicmp(sString.zString,"true",sizeof("true")-1) == 0) || + (sString.nByte == sizeof("on") - 1 && SyStrnicmp(sString.zString,"on",sizeof("on")-1) == 0) || + (sString.nByte == sizeof("yes") - 1 && SyStrnicmp(sString.zString,"yes",sizeof("yes")-1) == 0) ){ + return 1; + }else if( sString.nByte == sizeof("false") - 1 && SyStrnicmp(sString.zString,"false",sizeof("false")-1) == 0 ){ + return 0; + }else{ + const char *zIn,*zEnd; + zIn = sString.zString; + zEnd = &zIn[sString.nByte]; + while( zIn < zEnd && zIn[0] == '0' ){ + zIn++; + } + return zIn >= zEnd ? 0 : 1; + } + }else if( iFlags & MEMOBJ_NULL ){ + return 0; + }else if( iFlags & MEMOBJ_HASHMAP ){ + ph7_hashmap *pMap = (ph7_hashmap *)pObj->x.pOther; + sxu32 n = pMap->nEntry; + PH7_HashmapUnref(pMap); + return n > 0 ? TRUE : FALSE; + }else if( iFlags & MEMOBJ_OBJ ){ + ph7_value sResult; + sxi32 iVal = 1; + sxi32 rc; + /* Invoke the __toBool() method if available [note that this is a symisc extension] */ + PH7_MemObjInit(pObj->pVm,&sResult); + rc = MemObjCallClassCastMethod(pObj->pVm,(ph7_class_instance *)pObj->x.pOther, + "__toBool",sizeof("__toBool")-1,&sResult); + if( rc == SXRET_OK && (sResult.iFlags & (MEMOBJ_INT|MEMOBJ_BOOL)) ){ + /* Extract method return value */ + iVal = (sxi32)(sResult.x.iVal != 0); /* Stupid cc warning -W -Wall -O6 */ + } + PH7_ClassInstanceUnref((ph7_class_instance *)pObj->x.pOther); + PH7_MemObjRelease(&sResult); + return iVal; + }else if(iFlags & MEMOBJ_RES ){ + return pObj->x.pOther != 0; + } + /* NOT REACHED */ + return 0; +} +/* + * If the ph7_value is of type real,try to make it an integer also. + */ +static sxi32 MemObjTryIntger(ph7_value *pObj) +{ + pObj->x.iVal = MemObjRealToInt(&(*pObj)); + /* Only mark the value as an integer if + ** + ** (1) the round-trip conversion real->int->real is a no-op, and + ** (2) The integer is neither the largest nor the smallest + ** possible integer + ** + ** The second and third terms in the following conditional enforces + ** the second condition under the assumption that addition overflow causes + ** values to wrap around. On x86 hardware, the third term is always + ** true and could be omitted. But we leave it in because other + ** architectures might behave differently. + */ + if( pObj->rVal ==(ph7_real)pObj->x.iVal && pObj->x.iVal>SMALLEST_INT64 + && pObj->x.iValiFlags |= MEMOBJ_INT; + } + return SXRET_OK; +} +/* + * Convert a ph7_value to type integer.Invalidate any prior representations. + */ +PH7_PRIVATE sxi32 PH7_MemObjToInteger(ph7_value *pObj) +{ + if( (pObj->iFlags & MEMOBJ_INT) == 0 ){ + /* Preform the conversion */ + pObj->x.iVal = MemObjIntValue(&(*pObj)); + /* Invalidate any prior representations */ + SyBlobRelease(&pObj->sBlob); + MemObjSetType(pObj,MEMOBJ_INT); + } + return SXRET_OK; +} +/* + * Convert a ph7_value to type real (Try to get an integer representation also). + * Invalidate any prior representations + */ +PH7_PRIVATE sxi32 PH7_MemObjToReal(ph7_value *pObj) +{ + if((pObj->iFlags & MEMOBJ_REAL) == 0 ){ + /* Preform the conversion */ + pObj->rVal = MemObjRealValue(&(*pObj)); + /* Invalidate any prior representations */ + SyBlobRelease(&pObj->sBlob); + MemObjSetType(pObj,MEMOBJ_REAL); + /* Try to get an integer representation */ + MemObjTryIntger(&(*pObj)); + } + return SXRET_OK; +} +/* + * Convert a ph7_value to type boolean.Invalidate any prior representations. + */ +PH7_PRIVATE sxi32 PH7_MemObjToBool(ph7_value *pObj) +{ + if( (pObj->iFlags & MEMOBJ_BOOL) == 0 ){ + /* Preform the conversion */ + pObj->x.iVal = MemObjBooleanValue(&(*pObj)); + /* Invalidate any prior representations */ + SyBlobRelease(&pObj->sBlob); + MemObjSetType(pObj,MEMOBJ_BOOL); + } + return SXRET_OK; +} +/* + * Convert a ph7_value to type string.Prior representations are NOT invalidated. + */ +PH7_PRIVATE sxi32 PH7_MemObjToString(ph7_value *pObj) +{ + sxi32 rc = SXRET_OK; + if( (pObj->iFlags & MEMOBJ_STRING) == 0 ){ + /* Perform the conversion */ + SyBlobReset(&pObj->sBlob); /* Reset the internal buffer */ + rc = MemObjStringValue(&pObj->sBlob,&(*pObj),TRUE); + MemObjSetType(pObj,MEMOBJ_STRING); + } + return rc; +} +/* + * Nullify a ph7_value.In other words invalidate any prior + * representation. + */ +PH7_PRIVATE sxi32 PH7_MemObjToNull(ph7_value *pObj) +{ + return PH7_MemObjRelease(pObj); +} +/* + * Convert a ph7_value to type array.Invalidate any prior representations. + * According to the PHP language reference manual. + * For any of the types: integer, float, string, boolean converting a value + * to an array results in an array with a single element with index zero + * and the value of the scalar which was converted. + */ +PH7_PRIVATE sxi32 PH7_MemObjToHashmap(ph7_value *pObj) +{ + if( (pObj->iFlags & MEMOBJ_HASHMAP) == 0 ){ + ph7_hashmap *pMap; + /* Allocate a new hashmap instance */ + pMap = PH7_NewHashmap(pObj->pVm,0,0); + if( pMap == 0 ){ + return SXERR_MEM; + } + if( (pObj->iFlags & (MEMOBJ_NULL|MEMOBJ_RES)) == 0 ){ + /* + * According to the PHP language reference manual. + * For any of the types: integer, float, string, boolean converting a value + * to an array results in an array with a single element with index zero + * and the value of the scalar which was converted. + */ + if( pObj->iFlags & MEMOBJ_OBJ ){ + /* Object cast */ + PH7_ClassInstanceToHashmap((ph7_class_instance *)pObj->x.pOther,pMap); + }else{ + /* Insert a single element */ + PH7_HashmapInsert(pMap,0/* Automatic index assign */,&(*pObj)); + } + SyBlobRelease(&pObj->sBlob); + } + /* Invalidate any prior representation */ + MemObjSetType(pObj,MEMOBJ_HASHMAP); + pObj->x.pOther = pMap; + } + return SXRET_OK; +} +/* + * Convert a ph7_value to type object.Invalidate any prior representations. + * The new object is instantiated from the builtin stdClass(). + * The stdClass() class have a single attribute which is '$value'. This attribute + * hold a copy of the converted ph7_value. + * The internal of the stdClass is as follows: + * class stdClass{ + * public $value; + * public function __toInt(){ return (int)$this->value; } + * public function __toBool(){ return (bool)$this->value; } + * public function __toFloat(){ return (float)$this->value; } + * public function __toString(){ return (string)$this->value; } + * function __construct($v){ $this->value = $v; }" + * } + * Refer to the official documentation for more information. + */ +PH7_PRIVATE sxi32 PH7_MemObjToObject(ph7_value *pObj) +{ + if( (pObj->iFlags & MEMOBJ_OBJ) == 0 ){ + ph7_class_instance *pStd; + ph7_class_method *pCons; + ph7_class *pClass; + ph7_vm *pVm; + /* Point to the underlying VM */ + pVm = pObj->pVm; + /* Point to the stdClass() */ + pClass = PH7_VmExtractClass(pVm,"stdClass",sizeof("stdClass")-1,0,0); + if( pClass == 0 ){ + /* Can't happen,load null instead */ + PH7_MemObjRelease(pObj); + return SXRET_OK; + } + /* Instanciate a new stdClass() object */ + pStd = PH7_NewClassInstance(pVm,pClass); + if( pStd == 0 ){ + /* Out of memory */ + PH7_MemObjRelease(pObj); + return SXRET_OK; + } + /* Check if a constructor is available */ + pCons = PH7_ClassExtractMethod(pClass,"__construct",sizeof("__construct")-1); + if( pCons ){ + ph7_value *apArg[2]; + /* Invoke the constructor with one argument */ + apArg[0] = pObj; + PH7_VmCallClassMethod(pVm,pStd,pCons,0,1,apArg); + if( pStd->iRef < 1 ){ + pStd->iRef = 1; + } + } + /* Invalidate any prior representation */ + PH7_MemObjRelease(pObj); + /* Save the new instance */ + pObj->x.pOther = pStd; + MemObjSetType(pObj,MEMOBJ_OBJ); + } + return SXRET_OK; +} +/* + * Return a pointer to the appropriate convertion method associated + * with the given type. + * Note on type juggling. + * Accoding to the PHP language reference manual + * PHP does not require (or support) explicit type definition in variable + * declaration; a variable's type is determined by the context in which + * the variable is used. That is to say, if a string value is assigned + * to variable $var, $var becomes a string. If an integer value is then + * assigned to $var, it becomes an integer. + */ +PH7_PRIVATE ProcMemObjCast PH7_MemObjCastMethod(sxi32 iFlags) +{ + if( iFlags & MEMOBJ_STRING ){ + return PH7_MemObjToString; + }else if( iFlags & MEMOBJ_INT ){ + return PH7_MemObjToInteger; + }else if( iFlags & MEMOBJ_REAL ){ + return PH7_MemObjToReal; + }else if( iFlags & MEMOBJ_BOOL ){ + return PH7_MemObjToBool; + }else if( iFlags & MEMOBJ_HASHMAP ){ + return PH7_MemObjToHashmap; + }else if( iFlags & MEMOBJ_OBJ ){ + return PH7_MemObjToObject; + } + /* NULL cast */ + return PH7_MemObjToNull; +} +/* + * Check whether the ph7_value is numeric [i.e: int/float/bool] or looks + * like a numeric number [i.e: if the ph7_value is of type string.]. + * Return TRUE if numeric.FALSE otherwise. + */ +PH7_PRIVATE sxi32 PH7_MemObjIsNumeric(ph7_value *pObj) +{ + if( pObj->iFlags & ( MEMOBJ_BOOL|MEMOBJ_INT|MEMOBJ_REAL) ){ + return TRUE; + }else if( pObj->iFlags & (MEMOBJ_NULL|MEMOBJ_HASHMAP|MEMOBJ_OBJ|MEMOBJ_RES) ){ + return FALSE; + }else if( pObj->iFlags & MEMOBJ_STRING ){ + SyString sStr; + sxi32 rc; + SyStringInitFromBuf(&sStr,SyBlobData(&pObj->sBlob),SyBlobLength(&pObj->sBlob)); + if( sStr.nByte <= 0 ){ + /* Empty string */ + return FALSE; + } + /* Check if the string representation looks like a numeric number */ + rc = SyStrIsNumeric(sStr.zString,sStr.nByte,0,0); + return rc == SXRET_OK ? TRUE : FALSE; + } + /* NOT REACHED */ + return FALSE; +} +/* + * Check whether the ph7_value is empty.Return TRUE if empty. + * FALSE otherwise. + * An ph7_value is considered empty if the following are true: + * NULL value. + * Boolean FALSE. + * Integer/Float with a 0 (zero) value. + * An empty string or a stream of 0 (zero) [i.e: "0","00","000",...]. + * An empty array. + * NOTE + * OBJECT VALUE MUST NOT BE MODIFIED. + */ +PH7_PRIVATE sxi32 PH7_MemObjIsEmpty(ph7_value *pObj) +{ + if( pObj->iFlags & MEMOBJ_NULL ){ + return TRUE; + }else if( pObj->iFlags & MEMOBJ_INT ){ + return pObj->x.iVal == 0 ? TRUE : FALSE; + }else if( pObj->iFlags & MEMOBJ_REAL ){ + return pObj->rVal == (ph7_real)0 ? TRUE : FALSE; + }else if( pObj->iFlags & MEMOBJ_BOOL ){ + return !pObj->x.iVal; + }else if( pObj->iFlags & MEMOBJ_STRING ){ + if( SyBlobLength(&pObj->sBlob) <= 0 ){ + return TRUE; + }else{ + const char *zIn,*zEnd; + zIn = (const char *)SyBlobData(&pObj->sBlob); + zEnd = &zIn[SyBlobLength(&pObj->sBlob)]; + while( zIn < zEnd ){ + if( zIn[0] != '0' ){ + break; + } + zIn++; + } + return zIn >= zEnd ? TRUE : FALSE; + } + }else if( pObj->iFlags & MEMOBJ_HASHMAP ){ + ph7_hashmap *pMap = (ph7_hashmap *)pObj->x.pOther; + return pMap->nEntry == 0 ? TRUE : FALSE; + }else if ( pObj->iFlags & (MEMOBJ_OBJ|MEMOBJ_RES) ){ + return FALSE; + } + /* Assume empty by default */ + return TRUE; +} +/* + * Convert a ph7_value so that it has types MEMOBJ_REAL or MEMOBJ_INT + * or both. + * Invalidate any prior representations. Every effort is made to force + * the conversion, even if the input is a string that does not look + * completely like a number.Convert as much of the string as we can + * and ignore the rest. + */ +PH7_PRIVATE sxi32 PH7_MemObjToNumeric(ph7_value *pObj) +{ + if( pObj->iFlags & (MEMOBJ_INT|MEMOBJ_REAL|MEMOBJ_BOOL|MEMOBJ_NULL) ){ + if( pObj->iFlags & (MEMOBJ_BOOL|MEMOBJ_NULL) ){ + if( pObj->iFlags & MEMOBJ_NULL ){ + pObj->x.iVal = 0; + } + MemObjSetType(pObj,MEMOBJ_INT); + } + /* Already numeric */ + return SXRET_OK; + } + if( pObj->iFlags & MEMOBJ_STRING ){ + sxi32 rc = SXERR_INVALID; + sxu8 bReal = FALSE; + SyString sString; + SyStringInitFromBuf(&sString,SyBlobData(&pObj->sBlob),SyBlobLength(&pObj->sBlob)); + /* Check if the given string looks like a numeric number */ + if( sString.nByte > 0 ){ + rc = SyStrIsNumeric(sString.zString,sString.nByte,&bReal,0); + } + if( bReal ){ + PH7_MemObjToReal(&(*pObj)); + }else{ + if( rc != SXRET_OK ){ + /* The input does not look at all like a number,set the value to 0 */ + pObj->x.iVal = 0; + }else{ + /* Convert as much as we can */ + pObj->x.iVal = MemObjStringToInt(&(*pObj)); + } + MemObjSetType(pObj,MEMOBJ_INT); + SyBlobRelease(&pObj->sBlob); + } + }else if(pObj->iFlags & (MEMOBJ_OBJ|MEMOBJ_HASHMAP|MEMOBJ_RES)){ + PH7_MemObjToInteger(pObj); + }else{ + /* Perform a blind cast */ + PH7_MemObjToReal(&(*pObj)); + } + return SXRET_OK; +} +/* + * Try a get an integer representation of the given ph7_value. + * If the ph7_value is not of type real,this function is a no-op. + */ +PH7_PRIVATE sxi32 PH7_MemObjTryInteger(ph7_value *pObj) +{ + if( pObj->iFlags & MEMOBJ_REAL ){ + /* Work only with reals */ + MemObjTryIntger(&(*pObj)); + } + return SXRET_OK; +} +/* + * Initialize a ph7_value to the null type. + */ +PH7_PRIVATE sxi32 PH7_MemObjInit(ph7_vm *pVm,ph7_value *pObj) +{ + /* Zero the structure */ + SyZero(pObj,sizeof(ph7_value)); + /* Initialize fields */ + pObj->pVm = pVm; + SyBlobInit(&pObj->sBlob,&pVm->sAllocator); + /* Set the NULL type */ + pObj->iFlags = MEMOBJ_NULL; + return SXRET_OK; +} +/* + * Initialize a ph7_value to the integer type. + */ +PH7_PRIVATE sxi32 PH7_MemObjInitFromInt(ph7_vm *pVm,ph7_value *pObj,sxi64 iVal) +{ + /* Zero the structure */ + SyZero(pObj,sizeof(ph7_value)); + /* Initialize fields */ + pObj->pVm = pVm; + SyBlobInit(&pObj->sBlob,&pVm->sAllocator); + /* Set the desired type */ + pObj->x.iVal = iVal; + pObj->iFlags = MEMOBJ_INT; + return SXRET_OK; +} +/* + * Initialize a ph7_value to the boolean type. + */ +PH7_PRIVATE sxi32 PH7_MemObjInitFromBool(ph7_vm *pVm,ph7_value *pObj,sxi32 iVal) +{ + /* Zero the structure */ + SyZero(pObj,sizeof(ph7_value)); + /* Initialize fields */ + pObj->pVm = pVm; + SyBlobInit(&pObj->sBlob,&pVm->sAllocator); + /* Set the desired type */ + pObj->x.iVal = iVal ? 1 : 0; + pObj->iFlags = MEMOBJ_BOOL; + return SXRET_OK; +} +#if 0 +/* + * Initialize a ph7_value to the real type. + */ +PH7_PRIVATE sxi32 PH7_MemObjInitFromReal(ph7_vm *pVm,ph7_value *pObj,ph7_real rVal) +{ + /* Zero the structure */ + SyZero(pObj,sizeof(ph7_value)); + /* Initialize fields */ + pObj->pVm = pVm; + SyBlobInit(&pObj->sBlob,&pVm->sAllocator); + /* Set the desired type */ + pObj->rVal = rVal; + pObj->iFlags = MEMOBJ_REAL; + return SXRET_OK; +} +#endif +/* + * Initialize a ph7_value to the array type. + */ +PH7_PRIVATE sxi32 PH7_MemObjInitFromArray(ph7_vm *pVm,ph7_value *pObj,ph7_hashmap *pArray) +{ + /* Zero the structure */ + SyZero(pObj,sizeof(ph7_value)); + /* Initialize fields */ + pObj->pVm = pVm; + SyBlobInit(&pObj->sBlob,&pVm->sAllocator); + /* Set the desired type */ + pObj->iFlags = MEMOBJ_HASHMAP; + pObj->x.pOther = pArray; + return SXRET_OK; +} +/* + * Initialize a ph7_value to the string type. + */ +PH7_PRIVATE sxi32 PH7_MemObjInitFromString(ph7_vm *pVm,ph7_value *pObj,const SyString *pVal) +{ + /* Zero the structure */ + SyZero(pObj,sizeof(ph7_value)); + /* Initialize fields */ + pObj->pVm = pVm; + SyBlobInit(&pObj->sBlob,&pVm->sAllocator); + if( pVal ){ + /* Append contents */ + SyBlobAppend(&pObj->sBlob,(const void *)pVal->zString,pVal->nByte); + } + /* Set the desired type */ + pObj->iFlags = MEMOBJ_STRING; + return SXRET_OK; +} +/* + * Append some contents to the internal buffer of a given ph7_value. + * If the given ph7_value is not of type string,this function + * invalidate any prior representation and set the string type. + * Then a simple append operation is performed. + */ +PH7_PRIVATE sxi32 PH7_MemObjStringAppend(ph7_value *pObj,const char *zData,sxu32 nLen) +{ + sxi32 rc; + if( (pObj->iFlags & MEMOBJ_STRING) == 0 ){ + /* Invalidate any prior representation */ + PH7_MemObjRelease(pObj); + MemObjSetType(pObj,MEMOBJ_STRING); + } + /* Append contents */ + rc = SyBlobAppend(&pObj->sBlob,zData,nLen); + return rc; +} +#if 0 +/* + * Format and append some contents to the internal buffer of a given ph7_value. + * If the given ph7_value is not of type string,this function invalidate + * any prior representation and set the string type. + * Then a simple format and append operation is performed. + */ +PH7_PRIVATE sxi32 PH7_MemObjStringFormat(ph7_value *pObj,const char *zFormat,va_list ap) +{ + sxi32 rc; + if( (pObj->iFlags & MEMOBJ_STRING) == 0 ){ + /* Invalidate any prior representation */ + PH7_MemObjRelease(pObj); + MemObjSetType(pObj,MEMOBJ_STRING); + } + /* Format and append contents */ + rc = SyBlobFormatAp(&pObj->sBlob,zFormat,ap); + return rc; +} +#endif +/* + * Duplicate the contents of a ph7_value. + */ +PH7_PRIVATE sxi32 PH7_MemObjStore(ph7_value *pSrc,ph7_value *pDest) +{ + ph7_class_instance *pObj = 0; + ph7_hashmap *pMap = 0; + sxi32 rc; + if( pSrc->iFlags & MEMOBJ_HASHMAP ){ + /* Increment reference count */ + ((ph7_hashmap *)pSrc->x.pOther)->iRef++; + }else if( pSrc->iFlags & MEMOBJ_OBJ ){ + /* Increment reference count */ + ((ph7_class_instance *)pSrc->x.pOther)->iRef++; + } + if( pDest->iFlags & MEMOBJ_HASHMAP ){ + pMap = (ph7_hashmap *)pDest->x.pOther; + }else if( pDest->iFlags & MEMOBJ_OBJ ){ + pObj = (ph7_class_instance *)pDest->x.pOther; + } + SyMemcpy((const void *)&(*pSrc),&(*pDest),sizeof(ph7_value)-(sizeof(ph7_vm *)+sizeof(SyBlob)+sizeof(sxu32))); + pDest->iFlags &= ~MEMOBJ_AUX; + rc = SXRET_OK; + if( SyBlobLength(&pSrc->sBlob) > 0 ){ + SyBlobReset(&pDest->sBlob); + rc = SyBlobDup(&pSrc->sBlob,&pDest->sBlob); + }else{ + if( SyBlobLength(&pDest->sBlob) > 0 ){ + SyBlobRelease(&pDest->sBlob); + } + } + if( pMap ){ + PH7_HashmapUnref(pMap); + }else if( pObj ){ + PH7_ClassInstanceUnref(pObj); + } + return rc; +} +/* + * Duplicate the contents of a ph7_value but do not copy internal + * buffer contents,simply point to it. + */ +PH7_PRIVATE sxi32 PH7_MemObjLoad(ph7_value *pSrc,ph7_value *pDest) +{ + SyMemcpy((const void *)&(*pSrc),&(*pDest), + sizeof(ph7_value)-(sizeof(ph7_vm *)+sizeof(SyBlob)+sizeof(sxu32))); + if( pSrc->iFlags & MEMOBJ_HASHMAP ){ + /* Increment reference count */ + ((ph7_hashmap *)pSrc->x.pOther)->iRef++; + }else if( pSrc->iFlags & MEMOBJ_OBJ ){ + /* Increment reference count */ + ((ph7_class_instance *)pSrc->x.pOther)->iRef++; + } + if( SyBlobLength(&pDest->sBlob) > 0 ){ + SyBlobRelease(&pDest->sBlob); + } + if( SyBlobLength(&pSrc->sBlob) > 0 ){ + SyBlobReadOnly(&pDest->sBlob,SyBlobData(&pSrc->sBlob),SyBlobLength(&pSrc->sBlob)); + } + return SXRET_OK; +} +/* + * Invalidate any prior representation of a given ph7_value. + */ +PH7_PRIVATE sxi32 PH7_MemObjRelease(ph7_value *pObj) +{ + if( (pObj->iFlags & MEMOBJ_NULL) == 0 ){ + if( pObj->iFlags & MEMOBJ_HASHMAP ){ + PH7_HashmapUnref((ph7_hashmap *)pObj->x.pOther); + }else if( pObj->iFlags & MEMOBJ_OBJ ){ + PH7_ClassInstanceUnref((ph7_class_instance *)pObj->x.pOther); + } + /* Release the internal buffer */ + SyBlobRelease(&pObj->sBlob); + /* Invalidate any prior representation */ + pObj->iFlags = MEMOBJ_NULL; + } + return SXRET_OK; +} +/* + * Compare two ph7_values. + * Return 0 if the values are equals, > 0 if pObj1 is greater than pObj2 + * or < 0 if pObj2 is greater than pObj1. + * Type comparison table taken from the PHP language reference manual. + * Comparisons of $x with PHP functions Expression + * gettype() empty() is_null() isset() boolean : if($x) + * $x = ""; string TRUE FALSE TRUE FALSE + * $x = null NULL TRUE TRUE FALSE FALSE + * var $x; NULL TRUE TRUE FALSE FALSE + * $x is undefined NULL TRUE TRUE FALSE FALSE + * $x = array(); array TRUE FALSE TRUE FALSE + * $x = false; boolean TRUE FALSE TRUE FALSE + * $x = true; boolean FALSE FALSE TRUE TRUE + * $x = 1; integer FALSE FALSE TRUE TRUE + * $x = 42; integer FALSE FALSE TRUE TRUE + * $x = 0; integer TRUE FALSE TRUE FALSE + * $x = -1; integer FALSE FALSE TRUE TRUE + * $x = "1"; string FALSE FALSE TRUE TRUE + * $x = "0"; string TRUE FALSE TRUE FALSE + * $x = "-1"; string FALSE FALSE TRUE TRUE + * $x = "php"; string FALSE FALSE TRUE TRUE + * $x = "true"; string FALSE FALSE TRUE TRUE + * $x = "false"; string FALSE FALSE TRUE TRUE + * Loose comparisons with == + * TRUE FALSE 1 0 -1 "1" "0" "-1" NULL array() "php" "" + * TRUE TRUE FALSE TRUE FALSE TRUE TRUE FALSE TRUE FALSE FALSE TRUE FALSE + * FALSE FALSE TRUE FALSE TRUE FALSE FALSE TRUE FALSE TRUE TRUE FALSE TRUE + * 1 TRUE FALSE TRUE FALSE FALSE TRUE FALSE FALSE FALSE FALSE FALSE FALSE + * 0 FALSE TRUE FALSE TRUE FALSE FALSE TRUE FALSE TRUE FALSE TRUE TRUE + * -1 TRUE FALSE FALSE FALSE TRUE FALSE FALSE TRUE FALSE FALSE FALSE FALSE + * "1" TRUE FALSE TRUE FALSE FALSE TRUE FALSE FALSE FALSE FALSE FALSE FALSE + * "0" FALSE TRUE FALSE TRUE FALSE FALSE TRUE FALSE FALSE FALSE FALSE FALSE + * "-1" TRUE FALSE FALSE FALSE TRUE FALSE FALSE TRUE FALSE FALSE FALSE FALSE + * NULL FALSE TRUE FALSE TRUE FALSE FALSE FALSE FALSE TRUE TRUE FALSE TRUE + * array() FALSE TRUE FALSE FALSE FALSE FALSE FALSE FALSE TRUE TRUE FALSE FALSE + * "php" TRUE FALSE FALSE TRUE FALSE FALSE FALSE FALSE FALSE FALSE TRUE FALSE + * "" FALSE TRUE FALSE TRUE FALSE FALSE FALSE FALSE TRUE FALSE FALSE TRUE + * Strict comparisons with === + * TRUE FALSE 1 0 -1 "1" "0" "-1" NULL array() "php" "" + * TRUE TRUE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE + * FALSE FALSE TRUE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE + * 1 FALSE FALSE TRUE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE + * 0 FALSE FALSE FALSE TRUE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE + * -1 FALSE FALSE FALSE FALSE TRUE FALSE FALSE FALSE FALSE FALSE FALSE FALSE + * "1" FALSE FALSE FALSE FALSE FALSE TRUE FALSE FALSE FALSE FALSE FALSE FALSE + * "0" FALSE FALSE FALSE FALSE FALSE FALSE TRUE FALSE FALSE FALSE FALSE FALSE + * "-1" FALSE FALSE FALSE FALSE FALSE FALSE FALSE TRUE FALSE FALSE FALSE FALSE + * NULL FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE TRUE FALSE FALSE FALSE + * array() FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE TRUE FALSE FALSE + * "php" FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE TRUE FALSE + * "" FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE TRUE + */ +PH7_PRIVATE sxi32 PH7_MemObjCmp(ph7_value *pObj1,ph7_value *pObj2,int bStrict,int iNest) +{ + sxi32 iComb; + sxi32 rc; + if( bStrict ){ + sxi32 iF1,iF2; + /* Strict comparisons with === */ + iF1 = pObj1->iFlags&~MEMOBJ_AUX; + iF2 = pObj2->iFlags&~MEMOBJ_AUX; + if( iF1 != iF2 ){ + /* Not of the same type */ + return 1; + } + } + /* Combine flag together */ + iComb = pObj1->iFlags|pObj2->iFlags; + if( iComb & (MEMOBJ_NULL|MEMOBJ_RES|MEMOBJ_BOOL) ){ + /* Convert to boolean: Keep in mind FALSE < TRUE */ + if( (pObj1->iFlags & MEMOBJ_BOOL) == 0 ){ + PH7_MemObjToBool(pObj1); + } + if( (pObj2->iFlags & MEMOBJ_BOOL) == 0 ){ + PH7_MemObjToBool(pObj2); + } + return (sxi32)((pObj1->x.iVal != 0) - (pObj2->x.iVal != 0)); + }else if ( iComb & MEMOBJ_HASHMAP ){ + /* Hashmap aka 'array' comparison */ + if( (pObj1->iFlags & MEMOBJ_HASHMAP) == 0 ){ + /* Array is always greater */ + return -1; + } + if( (pObj2->iFlags & MEMOBJ_HASHMAP) == 0 ){ + /* Array is always greater */ + return 1; + } + /* Perform the comparison */ + rc = PH7_HashmapCmp((ph7_hashmap *)pObj1->x.pOther,(ph7_hashmap *)pObj2->x.pOther,bStrict); + return rc; + }else if(iComb & MEMOBJ_OBJ ){ + /* Object comparison */ + if( (pObj1->iFlags & MEMOBJ_OBJ) == 0 ){ + /* Object is always greater */ + return -1; + } + if( (pObj2->iFlags & MEMOBJ_OBJ) == 0 ){ + /* Object is always greater */ + return 1; + } + /* Perform the comparison */ + rc = PH7_ClassInstanceCmp((ph7_class_instance *)pObj1->x.pOther,(ph7_class_instance *)pObj2->x.pOther,bStrict,iNest); + return rc; + }else if ( iComb & MEMOBJ_STRING ){ + SyString s1,s2; + if( !bStrict ){ + /* + * According to the PHP language reference manual: + * + * If you compare a number with a string or the comparison involves numerical + * strings, then each string is converted to a number and the comparison + * performed numerically. + */ + if( PH7_MemObjIsNumeric(pObj1) ){ + /* Perform a numeric comparison */ + goto Numeric; + } + if( PH7_MemObjIsNumeric(pObj2) ){ + /* Perform a numeric comparison */ + goto Numeric; + } + } + /* Perform a strict string comparison.*/ + if( (pObj1->iFlags&MEMOBJ_STRING) == 0 ){ + PH7_MemObjToString(pObj1); + } + if( (pObj2->iFlags&MEMOBJ_STRING) == 0 ){ + PH7_MemObjToString(pObj2); + } + SyStringInitFromBuf(&s1,SyBlobData(&pObj1->sBlob),SyBlobLength(&pObj1->sBlob)); + SyStringInitFromBuf(&s2,SyBlobData(&pObj2->sBlob),SyBlobLength(&pObj2->sBlob)); + /* + * Strings are compared using memcmp(). If one value is an exact prefix of the + * other, then the shorter value is less than the longer value. + */ + rc = SyMemcmp((const void *)s1.zString,(const void *)s2.zString,SXMIN(s1.nByte,s2.nByte)); + if( rc == 0 ){ + if( s1.nByte != s2.nByte ){ + rc = s1.nByte < s2.nByte ? -1 : 1; + } + } + return rc; + }else if( iComb & (MEMOBJ_INT|MEMOBJ_REAL) ){ +Numeric: + /* Perform a numeric comparison if one of the operand is numeric(integer or real) */ + if( (pObj1->iFlags & (MEMOBJ_INT|MEMOBJ_REAL)) == 0 ){ + PH7_MemObjToNumeric(pObj1); + } + if( (pObj2->iFlags & (MEMOBJ_INT|MEMOBJ_REAL)) == 0 ){ + PH7_MemObjToNumeric(pObj2); + } + if( (pObj1->iFlags & pObj2->iFlags & MEMOBJ_INT) == 0) { + /* + * Symisc eXtension to the PHP language: + * Floating point comparison is introduced and works as expected. + */ + ph7_real r1,r2; + /* Compare as reals */ + if( (pObj1->iFlags & MEMOBJ_REAL) == 0 ){ + PH7_MemObjToReal(pObj1); + } + r1 = pObj1->rVal; + if( (pObj2->iFlags & MEMOBJ_REAL) == 0 ){ + PH7_MemObjToReal(pObj2); + } + r2 = pObj2->rVal; + if( r1 > r2 ){ + return 1; + }else if( r1 < r2 ){ + return -1; + } + return 0; + }else{ + /* Integer comparison */ + if( pObj1->x.iVal > pObj2->x.iVal ){ + return 1; + }else if( pObj1->x.iVal < pObj2->x.iVal ){ + return -1; + } + return 0; + } + } + /* NOT REACHED */ + return 0; +} +/* + * Perform an addition operation of two ph7_values. + * The reason this function is implemented here rather than 'vm.c' + * is that the '+' operator is overloaded. + * That is,the '+' operator is used for arithmetic operation and also + * used for operation on arrays [i.e: union]. When used with an array + * The + operator returns the right-hand array appended to the left-hand array. + * For keys that exist in both arrays, the elements from the left-hand array + * will be used, and the matching elements from the right-hand array will + * be ignored. + * This function take care of handling all the scenarios. + */ +PH7_PRIVATE sxi32 PH7_MemObjAdd(ph7_value *pObj1,ph7_value *pObj2,int bAddStore) +{ + if( ((pObj1->iFlags|pObj2->iFlags) & MEMOBJ_HASHMAP) == 0 ){ + /* Arithemtic operation */ + PH7_MemObjToNumeric(pObj1); + PH7_MemObjToNumeric(pObj2); + if( (pObj1->iFlags|pObj2->iFlags) & MEMOBJ_REAL ){ + /* Floating point arithmetic */ + ph7_real a,b; + if( (pObj1->iFlags & MEMOBJ_REAL) == 0 ){ + PH7_MemObjToReal(pObj1); + } + if( (pObj2->iFlags & MEMOBJ_REAL) == 0 ){ + PH7_MemObjToReal(pObj2); + } + a = pObj1->rVal; + b = pObj2->rVal; + pObj1->rVal = a+b; + MemObjSetType(pObj1,MEMOBJ_REAL); + /* Try to get an integer representation also */ + MemObjTryIntger(&(*pObj1)); + }else{ + /* Integer arithmetic */ + sxi64 a,b; + a = pObj1->x.iVal; + b = pObj2->x.iVal; + pObj1->x.iVal = a+b; + MemObjSetType(pObj1,MEMOBJ_INT); + } + }else{ + if( (pObj1->iFlags|pObj2->iFlags) & MEMOBJ_HASHMAP ){ + ph7_hashmap *pMap; + sxi32 rc; + if( bAddStore ){ + /* Do not duplicate the hashmap,use the left one since its an add&store operation. + */ + if( (pObj1->iFlags & MEMOBJ_HASHMAP) == 0 ){ + /* Force a hashmap cast */ + rc = PH7_MemObjToHashmap(pObj1); + if( rc != SXRET_OK ){ + PH7_VmThrowError(pObj1->pVm,0,PH7_CTX_ERR,"PH7 is running out of memory while creating array"); + return rc; + } + } + /* Point to the structure that describe the hashmap */ + pMap = (ph7_hashmap *)pObj1->x.pOther; + }else{ + /* Create a new hashmap */ + pMap = PH7_NewHashmap(pObj1->pVm,0,0); + if( pMap == 0){ + PH7_VmThrowError(pObj1->pVm,0,PH7_CTX_ERR,"PH7 is running out of memory while creating array"); + return SXERR_MEM; + } + } + if( !bAddStore ){ + if(pObj1->iFlags & MEMOBJ_HASHMAP ){ + /* Perform a hashmap duplication */ + PH7_HashmapDup((ph7_hashmap *)pObj1->x.pOther,pMap); + }else{ + if((pObj1->iFlags & MEMOBJ_NULL) == 0 ){ + /* Simple insertion */ + PH7_HashmapInsert(pMap,0,pObj1); + } + } + } + /* Perform the union */ + if(pObj2->iFlags & MEMOBJ_HASHMAP ){ + PH7_HashmapUnion(pMap,(ph7_hashmap *)pObj2->x.pOther); + }else{ + if((pObj2->iFlags & MEMOBJ_NULL) == 0 ){ + /* Simple insertion */ + PH7_HashmapInsert(pMap,0,pObj2); + } + } + /* Reflect the change */ + if( pObj1->iFlags & MEMOBJ_STRING ){ + SyBlobRelease(&pObj1->sBlob); + } + pObj1->x.pOther = pMap; + MemObjSetType(pObj1,MEMOBJ_HASHMAP); + } + } + return SXRET_OK; +} +/* + * Return a printable representation of the type of a given + * ph7_value. + */ +PH7_PRIVATE const char * PH7_MemObjTypeDump(ph7_value *pVal) +{ + const char *zType = ""; + if( pVal->iFlags & MEMOBJ_NULL ){ + zType = "null"; + }else if( pVal->iFlags & MEMOBJ_INT ){ + zType = "int"; + }else if( pVal->iFlags & MEMOBJ_REAL ){ + zType = "float"; + }else if( pVal->iFlags & MEMOBJ_STRING ){ + zType = "string"; + }else if( pVal->iFlags & MEMOBJ_BOOL ){ + zType = "bool"; + }else if( pVal->iFlags & MEMOBJ_HASHMAP ){ + zType = "array"; + }else if( pVal->iFlags & MEMOBJ_OBJ ){ + zType = "object"; + }else if( pVal->iFlags & MEMOBJ_RES ){ + zType = "resource"; + } + return zType; +} +/* + * Dump a ph7_value [i.e: get a printable representation of it's type and contents.]. + * Store the dump in the given blob. + */ +PH7_PRIVATE sxi32 PH7_MemObjDump( + SyBlob *pOut, /* Store the dump here */ + ph7_value *pObj, /* Dump this */ + int ShowType, /* TRUE to output value type */ + int nTab, /* # of Whitespace to insert */ + int nDepth, /* Nesting level */ + int isRef /* TRUE if referenced object */ + ) +{ + sxi32 rc = SXRET_OK; + const char *zType; + int i; + for( i = 0 ; i < nTab ; i++ ){ + SyBlobAppend(&(*pOut)," ",sizeof(char)); + } + if( ShowType ){ + if( isRef ){ + SyBlobAppend(&(*pOut),"&",sizeof(char)); + } + /* Get value type first */ + zType = PH7_MemObjTypeDump(pObj); + SyBlobAppend(&(*pOut),zType,SyStrlen(zType)); + } + if((pObj->iFlags & MEMOBJ_NULL) == 0 ){ + if ( ShowType ){ + SyBlobAppend(&(*pOut),"(",sizeof(char)); + } + if( pObj->iFlags & MEMOBJ_HASHMAP ){ + /* Dump hashmap entries */ + rc = PH7_HashmapDump(&(*pOut),(ph7_hashmap *)pObj->x.pOther,ShowType,nTab+1,nDepth+1); + }else if(pObj->iFlags & MEMOBJ_OBJ ){ + /* Dump class instance attributes */ + rc = PH7_ClassInstanceDump(&(*pOut),(ph7_class_instance *)pObj->x.pOther,ShowType,nTab+1,nDepth+1); + }else{ + SyBlob *pContents = &pObj->sBlob; + /* Get a printable representation of the contents */ + if((pObj->iFlags & MEMOBJ_STRING) == 0 ){ + MemObjStringValue(&(*pOut),&(*pObj),FALSE); + }else{ + /* Append length first */ + if( ShowType ){ + SyBlobFormat(&(*pOut),"%u '",SyBlobLength(&pObj->sBlob)); + } + if( SyBlobLength(pContents) > 0 ){ + SyBlobAppend(&(*pOut),SyBlobData(pContents),SyBlobLength(pContents)); + } + if( ShowType ){ + SyBlobAppend(&(*pOut),"'",sizeof(char)); + } + } + } + if( ShowType ){ + if( (pObj->iFlags & (MEMOBJ_HASHMAP|MEMOBJ_OBJ)) == 0 ){ + SyBlobAppend(&(*pOut),")",sizeof(char)); + } + } + } +#ifdef __WINNT__ + SyBlobAppend(&(*pOut),"\r\n",sizeof("\r\n")-1); +#else + SyBlobAppend(&(*pOut),"\n",sizeof(char)); +#endif + return rc; +} +/* + * ---------------------------------------------------------- + * File: lib.c + * MD5: 1d998050126fa9f31b5a52c757d498cb + * ---------------------------------------------------------- + */ +/* + * Symisc PH7: An embeddable bytecode compiler and a virtual machine for the PHP(5) programming language. + * Copyright (C) 2011-2012, Symisc Systems http://ph7.symisc.net/ + * Version 2.1.4 + * For information on licensing,redistribution of this file,and for a DISCLAIMER OF ALL WARRANTIES + * please contact Symisc Systems via: + * legal@symisc.net + * licensing@symisc.net + * contact@symisc.net + * or visit: + * http://ph7.symisc.net/ + */ + /* $SymiscID: lib.c v5.1 Win7 2012-08-08 04:19 stable $ */ +/* + * Symisc Run-Time API: A modern thread safe replacement of the standard libc + * Copyright (C) Symisc Systems 2007-2012, http://www.symisc.net/ + * + * The Symisc Run-Time API is an independent project developed by symisc systems + * internally as a secure replacement of the standard libc. + * The library is re-entrant,thread-safe and platform independent. + */ +#ifndef PH7_AMALGAMATION +#include "ph7int.h" +#endif +#if defined(__WINNT__) +#include +#else +#include +#endif +#if defined(PH7_ENABLE_THREADS) +/* SyRunTimeApi: sxmutex.c */ +#if defined(__WINNT__) +struct SyMutex +{ + CRITICAL_SECTION sMutex; + sxu32 nType; /* Mutex type,one of SXMUTEX_TYPE_* */ +}; +/* Preallocated static mutex */ +static SyMutex aStaticMutexes[] = { + {{0},SXMUTEX_TYPE_STATIC_1}, + {{0},SXMUTEX_TYPE_STATIC_2}, + {{0},SXMUTEX_TYPE_STATIC_3}, + {{0},SXMUTEX_TYPE_STATIC_4}, + {{0},SXMUTEX_TYPE_STATIC_5}, + {{0},SXMUTEX_TYPE_STATIC_6} +}; +static BOOL winMutexInit = FALSE; +static LONG winMutexLock = 0; + +static sxi32 WinMutexGlobaInit(void) +{ + LONG rc; + rc = InterlockedCompareExchange(&winMutexLock,1,0); + if ( rc == 0 ){ + sxu32 n; + for( n = 0 ; n < SX_ARRAYSIZE(aStaticMutexes) ; ++n ){ + InitializeCriticalSection(&aStaticMutexes[n].sMutex); + } + winMutexInit = TRUE; + }else{ + /* Someone else is doing this for us */ + while( winMutexInit == FALSE ){ + Sleep(1); + } + } + return SXRET_OK; +} +static void WinMutexGlobalRelease(void) +{ + LONG rc; + rc = InterlockedCompareExchange(&winMutexLock,0,1); + if( rc == 1 ){ + /* The first to decrement to zero does the actual global release */ + if( winMutexInit == TRUE ){ + sxu32 n; + for( n = 0 ; n < SX_ARRAYSIZE(aStaticMutexes) ; ++n ){ + DeleteCriticalSection(&aStaticMutexes[n].sMutex); + } + winMutexInit = FALSE; + } + } +} +static SyMutex * WinMutexNew(int nType) +{ + SyMutex *pMutex = 0; + if( nType == SXMUTEX_TYPE_FAST || nType == SXMUTEX_TYPE_RECURSIVE ){ + /* Allocate a new mutex */ + pMutex = (SyMutex *)HeapAlloc(GetProcessHeap(),0,sizeof(SyMutex)); + if( pMutex == 0 ){ + return 0; + } + InitializeCriticalSection(&pMutex->sMutex); + }else{ + /* Use a pre-allocated static mutex */ + if( nType > SXMUTEX_TYPE_STATIC_6 ){ + nType = SXMUTEX_TYPE_STATIC_6; + } + pMutex = &aStaticMutexes[nType - 3]; + } + pMutex->nType = nType; + return pMutex; +} +static void WinMutexRelease(SyMutex *pMutex) +{ + if( pMutex->nType == SXMUTEX_TYPE_FAST || pMutex->nType == SXMUTEX_TYPE_RECURSIVE ){ + DeleteCriticalSection(&pMutex->sMutex); + HeapFree(GetProcessHeap(),0,pMutex); + } +} +static void WinMutexEnter(SyMutex *pMutex) +{ + EnterCriticalSection(&pMutex->sMutex); +} +static sxi32 WinMutexTryEnter(SyMutex *pMutex) +{ +#ifdef _WIN32_WINNT + BOOL rc; + /* Only WindowsNT platforms */ + rc = TryEnterCriticalSection(&pMutex->sMutex); + if( rc ){ + return SXRET_OK; + }else{ + return SXERR_BUSY; + } +#else + return SXERR_NOTIMPLEMENTED; +#endif +} +static void WinMutexLeave(SyMutex *pMutex) +{ + LeaveCriticalSection(&pMutex->sMutex); +} +/* Export Windows mutex interfaces */ +static const SyMutexMethods sWinMutexMethods = { + WinMutexGlobaInit, /* xGlobalInit() */ + WinMutexGlobalRelease, /* xGlobalRelease() */ + WinMutexNew, /* xNew() */ + WinMutexRelease, /* xRelease() */ + WinMutexEnter, /* xEnter() */ + WinMutexTryEnter, /* xTryEnter() */ + WinMutexLeave /* xLeave() */ +}; +PH7_PRIVATE const SyMutexMethods * SyMutexExportMethods(void) +{ + return &sWinMutexMethods; +} +#elif defined(__UNIXES__) +#include +struct SyMutex +{ + pthread_mutex_t sMutex; + sxu32 nType; +}; +static SyMutex * UnixMutexNew(int nType) +{ + static SyMutex aStaticMutexes[] = { + {PTHREAD_MUTEX_INITIALIZER,SXMUTEX_TYPE_STATIC_1}, + {PTHREAD_MUTEX_INITIALIZER,SXMUTEX_TYPE_STATIC_2}, + {PTHREAD_MUTEX_INITIALIZER,SXMUTEX_TYPE_STATIC_3}, + {PTHREAD_MUTEX_INITIALIZER,SXMUTEX_TYPE_STATIC_4}, + {PTHREAD_MUTEX_INITIALIZER,SXMUTEX_TYPE_STATIC_5}, + {PTHREAD_MUTEX_INITIALIZER,SXMUTEX_TYPE_STATIC_6} + }; + SyMutex *pMutex; + + if( nType == SXMUTEX_TYPE_FAST || nType == SXMUTEX_TYPE_RECURSIVE ){ + pthread_mutexattr_t sRecursiveAttr; + /* Allocate a new mutex */ + pMutex = (SyMutex *)malloc(sizeof(SyMutex)); + if( pMutex == 0 ){ + return 0; + } + if( nType == SXMUTEX_TYPE_RECURSIVE ){ + pthread_mutexattr_init(&sRecursiveAttr); + pthread_mutexattr_settype(&sRecursiveAttr,PTHREAD_MUTEX_RECURSIVE); + } + pthread_mutex_init(&pMutex->sMutex,nType == SXMUTEX_TYPE_RECURSIVE ? &sRecursiveAttr : 0 ); + if( nType == SXMUTEX_TYPE_RECURSIVE ){ + pthread_mutexattr_destroy(&sRecursiveAttr); + } + }else{ + /* Use a pre-allocated static mutex */ + if( nType > SXMUTEX_TYPE_STATIC_6 ){ + nType = SXMUTEX_TYPE_STATIC_6; + } + pMutex = &aStaticMutexes[nType - 3]; + } + pMutex->nType = nType; + + return pMutex; +} +static void UnixMutexRelease(SyMutex *pMutex) +{ + if( pMutex->nType == SXMUTEX_TYPE_FAST || pMutex->nType == SXMUTEX_TYPE_RECURSIVE ){ + pthread_mutex_destroy(&pMutex->sMutex); + free(pMutex); + } +} +static void UnixMutexEnter(SyMutex *pMutex) +{ + pthread_mutex_lock(&pMutex->sMutex); +} +static void UnixMutexLeave(SyMutex *pMutex) +{ + pthread_mutex_unlock(&pMutex->sMutex); +} +/* Export pthread mutex interfaces */ +static const SyMutexMethods sPthreadMutexMethods = { + 0, /* xGlobalInit() */ + 0, /* xGlobalRelease() */ + UnixMutexNew, /* xNew() */ + UnixMutexRelease, /* xRelease() */ + UnixMutexEnter, /* xEnter() */ + 0, /* xTryEnter() */ + UnixMutexLeave /* xLeave() */ +}; +PH7_PRIVATE const SyMutexMethods * SyMutexExportMethods(void) +{ + return &sPthreadMutexMethods; +} +#else +/* Host application must register their own mutex subsystem if the target + * platform is not an UNIX-like or windows systems. + */ +struct SyMutex +{ + sxu32 nType; +}; +static SyMutex * DummyMutexNew(int nType) +{ + static SyMutex sMutex; + SXUNUSED(nType); + return &sMutex; +} +static void DummyMutexRelease(SyMutex *pMutex) +{ + SXUNUSED(pMutex); +} +static void DummyMutexEnter(SyMutex *pMutex) +{ + SXUNUSED(pMutex); +} +static void DummyMutexLeave(SyMutex *pMutex) +{ + SXUNUSED(pMutex); +} +/* Export the dummy mutex interfaces */ +static const SyMutexMethods sDummyMutexMethods = { + 0, /* xGlobalInit() */ + 0, /* xGlobalRelease() */ + DummyMutexNew, /* xNew() */ + DummyMutexRelease, /* xRelease() */ + DummyMutexEnter, /* xEnter() */ + 0, /* xTryEnter() */ + DummyMutexLeave /* xLeave() */ +}; +PH7_PRIVATE const SyMutexMethods * SyMutexExportMethods(void) +{ + return &sDummyMutexMethods; +} +#endif /* __WINNT__ */ +#endif /* PH7_ENABLE_THREADS */ +static void * SyOSHeapAlloc(sxu32 nByte) +{ + void *pNew; +#if defined(__WINNT__) + pNew = HeapAlloc(GetProcessHeap(),0,nByte); +#else + pNew = malloc((size_t)nByte); +#endif + return pNew; +} +static void * SyOSHeapRealloc(void *pOld,sxu32 nByte) +{ + void *pNew; +#if defined(__WINNT__) + pNew = HeapReAlloc(GetProcessHeap(),0,pOld,nByte); +#else + pNew = realloc(pOld,(size_t)nByte); +#endif + return pNew; +} +static void SyOSHeapFree(void *pPtr) +{ +#if defined(__WINNT__) + HeapFree(GetProcessHeap(),0,pPtr); +#else + free(pPtr); +#endif +} +/* SyRunTimeApi:sxstr.c */ +PH7_PRIVATE sxu32 SyStrlen(const char *zSrc) +{ + register const char *zIn = zSrc; +#if defined(UNTRUST) + if( zIn == 0 ){ + return 0; + } +#endif + for(;;){ + if( !zIn[0] ){ break; } zIn++; + if( !zIn[0] ){ break; } zIn++; + if( !zIn[0] ){ break; } zIn++; + if( !zIn[0] ){ break; } zIn++; + } + return (sxu32)(zIn - zSrc); +} +PH7_PRIVATE sxi32 SyByteFind(const char *zStr,sxu32 nLen,sxi32 c,sxu32 *pPos) +{ + const char *zIn = zStr; + const char *zEnd; + + zEnd = &zIn[nLen]; + for(;;){ + if( zIn >= zEnd ){ break; }if( zIn[0] == c ){ if( pPos ){ *pPos = (sxu32)(zIn - zStr); } return SXRET_OK; } zIn++; + if( zIn >= zEnd ){ break; }if( zIn[0] == c ){ if( pPos ){ *pPos = (sxu32)(zIn - zStr); } return SXRET_OK; } zIn++; + if( zIn >= zEnd ){ break; }if( zIn[0] == c ){ if( pPos ){ *pPos = (sxu32)(zIn - zStr); } return SXRET_OK; } zIn++; + if( zIn >= zEnd ){ break; }if( zIn[0] == c ){ if( pPos ){ *pPos = (sxu32)(zIn - zStr); } return SXRET_OK; } zIn++; + } + return SXERR_NOTFOUND; +} +#ifndef PH7_DISABLE_BUILTIN_FUNC +PH7_PRIVATE sxi32 SyByteFind2(const char *zStr,sxu32 nLen,sxi32 c,sxu32 *pPos) +{ + const char *zIn = zStr; + const char *zEnd; + + zEnd = &zIn[nLen - 1]; + for( ;; ){ + if( zEnd < zIn ){ break; } if( zEnd[0] == c ){ if( pPos ){ *pPos = (sxu32)(zEnd - zIn);} return SXRET_OK; } zEnd--; + if( zEnd < zIn ){ break; } if( zEnd[0] == c ){ if( pPos ){ *pPos = (sxu32)(zEnd - zIn);} return SXRET_OK; } zEnd--; + if( zEnd < zIn ){ break; } if( zEnd[0] == c ){ if( pPos ){ *pPos = (sxu32)(zEnd - zIn);} return SXRET_OK; } zEnd--; + if( zEnd < zIn ){ break; } if( zEnd[0] == c ){ if( pPos ){ *pPos = (sxu32)(zEnd - zIn);} return SXRET_OK; } zEnd--; + } + return SXERR_NOTFOUND; +} +#endif /* PH7_DISABLE_BUILTIN_FUNC */ +PH7_PRIVATE sxi32 SyByteListFind(const char *zSrc,sxu32 nLen,const char *zList,sxu32 *pFirstPos) +{ + const char *zIn = zSrc; + const char *zPtr; + const char *zEnd; + sxi32 c; + zEnd = &zSrc[nLen]; + for(;;){ + if( zIn >= zEnd ){ break; } for(zPtr = zList ; (c = zPtr[0]) != 0 ; zPtr++ ){ if( zIn[0] == c ){ if( pFirstPos ){ *pFirstPos = (sxu32)(zIn - zSrc); } return SXRET_OK; } } zIn++; + if( zIn >= zEnd ){ break; } for(zPtr = zList ; (c = zPtr[0]) != 0 ; zPtr++ ){ if( zIn[0] == c ){ if( pFirstPos ){ *pFirstPos = (sxu32)(zIn - zSrc); } return SXRET_OK; } } zIn++; + if( zIn >= zEnd ){ break; } for(zPtr = zList ; (c = zPtr[0]) != 0 ; zPtr++ ){ if( zIn[0] == c ){ if( pFirstPos ){ *pFirstPos = (sxu32)(zIn - zSrc); } return SXRET_OK; } } zIn++; + if( zIn >= zEnd ){ break; } for(zPtr = zList ; (c = zPtr[0]) != 0 ; zPtr++ ){ if( zIn[0] == c ){ if( pFirstPos ){ *pFirstPos = (sxu32)(zIn - zSrc); } return SXRET_OK; } } zIn++; + } + return SXERR_NOTFOUND; +} +#ifndef PH7_DISABLE_BUILTIN_FUNC +PH7_PRIVATE sxi32 SyStrncmp(const char *zLeft,const char *zRight,sxu32 nLen) +{ + const unsigned char *zP = (const unsigned char *)zLeft; + const unsigned char *zQ = (const unsigned char *)zRight; + + if( SX_EMPTY_STR(zP) || SX_EMPTY_STR(zQ) ){ + return SX_EMPTY_STR(zP) ? (SX_EMPTY_STR(zQ) ? 0 : -1) :1; + } + if( nLen <= 0 ){ + return 0; + } + for(;;){ + if( nLen <= 0 ){ return 0; } if( zP[0] == 0 || zQ[0] == 0 || zP[0] != zQ[0] ){ break; } zP++; zQ++; nLen--; + if( nLen <= 0 ){ return 0; } if( zP[0] == 0 || zQ[0] == 0 || zP[0] != zQ[0] ){ break; } zP++; zQ++; nLen--; + if( nLen <= 0 ){ return 0; } if( zP[0] == 0 || zQ[0] == 0 || zP[0] != zQ[0] ){ break; } zP++; zQ++; nLen--; + if( nLen <= 0 ){ return 0; } if( zP[0] == 0 || zQ[0] == 0 || zP[0] != zQ[0] ){ break; } zP++; zQ++; nLen--; + } + return (sxi32)(zP[0] - zQ[0]); +} +#endif +PH7_PRIVATE sxi32 SyStrnicmp(const char *zLeft, const char *zRight,sxu32 SLen) +{ + register unsigned char *p = (unsigned char *)zLeft; + register unsigned char *q = (unsigned char *)zRight; + + if( SX_EMPTY_STR(p) || SX_EMPTY_STR(q) ){ + return SX_EMPTY_STR(p)? SX_EMPTY_STR(q) ? 0 : -1 :1; + } + for(;;){ + if( !SLen ){ return 0; }if( !*p || !*q || SyCharToLower(*p) != SyCharToLower(*q) ){ break; }p++;q++;--SLen; + if( !SLen ){ return 0; }if( !*p || !*q || SyCharToLower(*p) != SyCharToLower(*q) ){ break; }p++;q++;--SLen; + if( !SLen ){ return 0; }if( !*p || !*q || SyCharToLower(*p) != SyCharToLower(*q) ){ break; }p++;q++;--SLen; + if( !SLen ){ return 0; }if( !*p || !*q || SyCharToLower(*p) != SyCharToLower(*q) ){ break; }p++;q++;--SLen; + + } + return (sxi32)(SyCharToLower(p[0]) - SyCharToLower(q[0])); +} +PH7_PRIVATE sxi32 SyStrnmicmp(const void *pLeft, const void *pRight,sxu32 SLen) +{ + return SyStrnicmp((const char *)pLeft,(const char *)pRight,SLen); +} +static sxu32 Systrcpy(char *zDest,sxu32 nDestLen,const char *zSrc,sxu32 nLen) +{ + unsigned char *zBuf = (unsigned char *)zDest; + unsigned char *zIn = (unsigned char *)zSrc; + unsigned char *zEnd; +#if defined(UNTRUST) + if( zSrc == (const char *)zDest ){ + return 0; + } +#endif + if( nLen <= 0 ){ + nLen = SyStrlen(zSrc); + } + zEnd = &zBuf[nDestLen - 1]; /* reserve a room for the null terminator */ + for(;;){ + if( zBuf >= zEnd || nLen == 0 ){ break;} zBuf[0] = zIn[0]; zIn++; zBuf++; nLen--; + if( zBuf >= zEnd || nLen == 0 ){ break;} zBuf[0] = zIn[0]; zIn++; zBuf++; nLen--; + if( zBuf >= zEnd || nLen == 0 ){ break;} zBuf[0] = zIn[0]; zIn++; zBuf++; nLen--; + if( zBuf >= zEnd || nLen == 0 ){ break;} zBuf[0] = zIn[0]; zIn++; zBuf++; nLen--; + } + zBuf[0] = 0; + return (sxu32)(zBuf-(unsigned char *)zDest); +} +/* SyRunTimeApi:sxmem.c */ +PH7_PRIVATE void SyZero(void *pSrc,sxu32 nSize) +{ + register unsigned char *zSrc = (unsigned char *)pSrc; + unsigned char *zEnd; +#if defined(UNTRUST) + if( zSrc == 0 || nSize <= 0 ){ + return ; + } +#endif + zEnd = &zSrc[nSize]; + for(;;){ + if( zSrc >= zEnd ){break;} zSrc[0] = 0; zSrc++; + if( zSrc >= zEnd ){break;} zSrc[0] = 0; zSrc++; + if( zSrc >= zEnd ){break;} zSrc[0] = 0; zSrc++; + if( zSrc >= zEnd ){break;} zSrc[0] = 0; zSrc++; + } +} +PH7_PRIVATE sxi32 SyMemcmp(const void *pB1,const void *pB2,sxu32 nSize) +{ + sxi32 rc; + if( nSize <= 0 ){ + return 0; + } + if( pB1 == 0 || pB2 == 0 ){ + return pB1 != 0 ? 1 : (pB2 == 0 ? 0 : -1); + } + SX_MACRO_FAST_CMP(pB1,pB2,nSize,rc); + return rc; +} +PH7_PRIVATE sxu32 SyMemcpy(const void *pSrc,void *pDest,sxu32 nLen) +{ +#if defined(UNTRUST) + if( pSrc == 0 || pDest == 0 ){ + return 0; + } +#endif + if( pSrc == (const void *)pDest ){ + return nLen; + } + SX_MACRO_FAST_MEMCPY(pSrc,pDest,nLen); + return nLen; +} +static void * MemOSAlloc(sxu32 nBytes) +{ + sxu32 *pChunk; + pChunk = (sxu32 *)SyOSHeapAlloc(nBytes + sizeof(sxu32)); + if( pChunk == 0 ){ + return 0; + } + pChunk[0] = nBytes; + return (void *)&pChunk[1]; +} +static void * MemOSRealloc(void *pOld,sxu32 nBytes) +{ + sxu32 *pOldChunk; + sxu32 *pChunk; + pOldChunk = (sxu32 *)(((char *)pOld)-sizeof(sxu32)); + if( pOldChunk[0] >= nBytes ){ + return pOld; + } + pChunk = (sxu32 *)SyOSHeapRealloc(pOldChunk,nBytes + sizeof(sxu32)); + if( pChunk == 0 ){ + return 0; + } + pChunk[0] = nBytes; + return (void *)&pChunk[1]; +} +static void MemOSFree(void *pBlock) +{ + void *pChunk; + pChunk = (void *)(((char *)pBlock)-sizeof(sxu32)); + SyOSHeapFree(pChunk); +} +static sxu32 MemOSChunkSize(void *pBlock) +{ + sxu32 *pChunk; + pChunk = (sxu32 *)(((char *)pBlock)-sizeof(sxu32)); + return pChunk[0]; +} +/* Export OS allocation methods */ +static const SyMemMethods sOSAllocMethods = { + MemOSAlloc, + MemOSRealloc, + MemOSFree, + MemOSChunkSize, + 0, + 0, + 0 +}; +static void * MemBackendAlloc(SyMemBackend *pBackend,sxu32 nByte) +{ + SyMemBlock *pBlock; + sxi32 nRetry = 0; + + /* Append an extra block so we can tracks allocated chunks and avoid memory + * leaks. + */ + nByte += sizeof(SyMemBlock); + for(;;){ + pBlock = (SyMemBlock *)pBackend->pMethods->xAlloc(nByte); + if( pBlock != 0 || pBackend->xMemError == 0 || nRetry > SXMEM_BACKEND_RETRY + || SXERR_RETRY != pBackend->xMemError(pBackend->pUserData) ){ + break; + } + nRetry++; + } + if( pBlock == 0 ){ + return 0; + } + pBlock->pNext = pBlock->pPrev = 0; + /* Link to the list of already tracked blocks */ + MACRO_LD_PUSH(pBackend->pBlocks,pBlock); +#if defined(UNTRUST) + pBlock->nGuard = SXMEM_BACKEND_MAGIC; +#endif + pBackend->nBlock++; + return (void *)&pBlock[1]; +} +PH7_PRIVATE void * SyMemBackendAlloc(SyMemBackend *pBackend,sxu32 nByte) +{ + void *pChunk; +#if defined(UNTRUST) + if( SXMEM_BACKEND_CORRUPT(pBackend) ){ + return 0; + } +#endif + if( pBackend->pMutexMethods ){ + SyMutexEnter(pBackend->pMutexMethods,pBackend->pMutex); + } + pChunk = MemBackendAlloc(&(*pBackend),nByte); + if( pBackend->pMutexMethods ){ + SyMutexLeave(pBackend->pMutexMethods,pBackend->pMutex); + } + return pChunk; +} +static void * MemBackendRealloc(SyMemBackend *pBackend,void * pOld,sxu32 nByte) +{ + SyMemBlock *pBlock,*pNew,*pPrev,*pNext; + sxu32 nRetry = 0; + + if( pOld == 0 ){ + return MemBackendAlloc(&(*pBackend),nByte); + } + pBlock = (SyMemBlock *)(((char *)pOld) - sizeof(SyMemBlock)); +#if defined(UNTRUST) + if( pBlock->nGuard != SXMEM_BACKEND_MAGIC ){ + return 0; + } +#endif + nByte += sizeof(SyMemBlock); + pPrev = pBlock->pPrev; + pNext = pBlock->pNext; + for(;;){ + pNew = (SyMemBlock *)pBackend->pMethods->xRealloc(pBlock,nByte); + if( pNew != 0 || pBackend->xMemError == 0 || nRetry > SXMEM_BACKEND_RETRY || + SXERR_RETRY != pBackend->xMemError(pBackend->pUserData) ){ + break; + } + nRetry++; + } + if( pNew == 0 ){ + return 0; + } + if( pNew != pBlock ){ + if( pPrev == 0 ){ + pBackend->pBlocks = pNew; + }else{ + pPrev->pNext = pNew; + } + if( pNext ){ + pNext->pPrev = pNew; + } +#if defined(UNTRUST) + pNew->nGuard = SXMEM_BACKEND_MAGIC; +#endif + } + return (void *)&pNew[1]; +} +PH7_PRIVATE void * SyMemBackendRealloc(SyMemBackend *pBackend,void * pOld,sxu32 nByte) +{ + void *pChunk; +#if defined(UNTRUST) + if( SXMEM_BACKEND_CORRUPT(pBackend) ){ + return 0; + } +#endif + if( pBackend->pMutexMethods ){ + SyMutexEnter(pBackend->pMutexMethods,pBackend->pMutex); + } + pChunk = MemBackendRealloc(&(*pBackend),pOld,nByte); + if( pBackend->pMutexMethods ){ + SyMutexLeave(pBackend->pMutexMethods,pBackend->pMutex); + } + return pChunk; +} +static sxi32 MemBackendFree(SyMemBackend *pBackend,void * pChunk) +{ + SyMemBlock *pBlock; + pBlock = (SyMemBlock *)(((char *)pChunk) - sizeof(SyMemBlock)); +#if defined(UNTRUST) + if( pBlock->nGuard != SXMEM_BACKEND_MAGIC ){ + return SXERR_CORRUPT; + } +#endif + /* Unlink from the list of active blocks */ + if( pBackend->nBlock > 0 ){ + /* Release the block */ +#if defined(UNTRUST) + /* Mark as stale block */ + pBlock->nGuard = 0x635B; +#endif + MACRO_LD_REMOVE(pBackend->pBlocks,pBlock); + pBackend->nBlock--; + pBackend->pMethods->xFree(pBlock); + } + return SXRET_OK; +} +PH7_PRIVATE sxi32 SyMemBackendFree(SyMemBackend *pBackend,void * pChunk) +{ + sxi32 rc; +#if defined(UNTRUST) + if( SXMEM_BACKEND_CORRUPT(pBackend) ){ + return SXERR_CORRUPT; + } +#endif + if( pChunk == 0 ){ + return SXRET_OK; + } + if( pBackend->pMutexMethods ){ + SyMutexEnter(pBackend->pMutexMethods,pBackend->pMutex); + } + rc = MemBackendFree(&(*pBackend),pChunk); + if( pBackend->pMutexMethods ){ + SyMutexLeave(pBackend->pMutexMethods,pBackend->pMutex); + } + return rc; +} +#if defined(PH7_ENABLE_THREADS) +PH7_PRIVATE sxi32 SyMemBackendMakeThreadSafe(SyMemBackend *pBackend,const SyMutexMethods *pMethods) +{ + SyMutex *pMutex; +#if defined(UNTRUST) + if( SXMEM_BACKEND_CORRUPT(pBackend) || pMethods == 0 || pMethods->xNew == 0){ + return SXERR_CORRUPT; + } +#endif + pMutex = pMethods->xNew(SXMUTEX_TYPE_FAST); + if( pMutex == 0 ){ + return SXERR_OS; + } + /* Attach the mutex to the memory backend */ + pBackend->pMutex = pMutex; + pBackend->pMutexMethods = pMethods; + return SXRET_OK; +} +PH7_PRIVATE sxi32 SyMemBackendDisbaleMutexing(SyMemBackend *pBackend) +{ +#if defined(UNTRUST) + if( SXMEM_BACKEND_CORRUPT(pBackend) ){ + return SXERR_CORRUPT; + } +#endif + if( pBackend->pMutex == 0 ){ + /* There is no mutex subsystem at all */ + return SXRET_OK; + } + SyMutexRelease(pBackend->pMutexMethods,pBackend->pMutex); + pBackend->pMutexMethods = 0; + pBackend->pMutex = 0; + return SXRET_OK; +} +#endif +/* + * Memory pool allocator + */ +#define SXMEM_POOL_MAGIC 0xDEAD +#define SXMEM_POOL_MAXALLOC (1<<(SXMEM_POOL_NBUCKETS+SXMEM_POOL_INCR)) +#define SXMEM_POOL_MINALLOC (1<<(SXMEM_POOL_INCR)) +static sxi32 MemPoolBucketAlloc(SyMemBackend *pBackend,sxu32 nBucket) +{ + char *zBucket,*zBucketEnd; + SyMemHeader *pHeader; + sxu32 nBucketSize; + + /* Allocate one big block first */ + zBucket = (char *)MemBackendAlloc(&(*pBackend),SXMEM_POOL_MAXALLOC); + if( zBucket == 0 ){ + return SXERR_MEM; + } + zBucketEnd = &zBucket[SXMEM_POOL_MAXALLOC]; + /* Divide the big block into mini bucket pool */ + nBucketSize = 1 << (nBucket + SXMEM_POOL_INCR); + pBackend->apPool[nBucket] = pHeader = (SyMemHeader *)zBucket; + for(;;){ + if( &zBucket[nBucketSize] >= zBucketEnd ){ + break; + } + pHeader->pNext = (SyMemHeader *)&zBucket[nBucketSize]; + /* Advance the cursor to the next available chunk */ + pHeader = pHeader->pNext; + zBucket += nBucketSize; + } + pHeader->pNext = 0; + + return SXRET_OK; +} +static void * MemBackendPoolAlloc(SyMemBackend *pBackend,sxu32 nByte) +{ + SyMemHeader *pBucket,*pNext; + sxu32 nBucketSize; + sxu32 nBucket; + + if( nByte + sizeof(SyMemHeader) >= SXMEM_POOL_MAXALLOC ){ + /* Allocate a big chunk directly */ + pBucket = (SyMemHeader *)MemBackendAlloc(&(*pBackend),nByte+sizeof(SyMemHeader)); + if( pBucket == 0 ){ + return 0; + } + /* Record as big block */ + pBucket->nBucket = (sxu32)(SXMEM_POOL_MAGIC << 16) | SXU16_HIGH; + return (void *)(pBucket+1); + } + /* Locate the appropriate bucket */ + nBucket = 0; + nBucketSize = SXMEM_POOL_MINALLOC; + while( nByte + sizeof(SyMemHeader) > nBucketSize ){ + nBucketSize <<= 1; + nBucket++; + } + pBucket = pBackend->apPool[nBucket]; + if( pBucket == 0 ){ + sxi32 rc; + rc = MemPoolBucketAlloc(&(*pBackend),nBucket); + if( rc != SXRET_OK ){ + return 0; + } + pBucket = pBackend->apPool[nBucket]; + } + /* Remove from the free list */ + pNext = pBucket->pNext; + pBackend->apPool[nBucket] = pNext; + /* Record bucket&magic number */ + pBucket->nBucket = (SXMEM_POOL_MAGIC << 16) | nBucket; + return (void *)&pBucket[1]; +} +PH7_PRIVATE void * SyMemBackendPoolAlloc(SyMemBackend *pBackend,sxu32 nByte) +{ + void *pChunk; +#if defined(UNTRUST) + if( SXMEM_BACKEND_CORRUPT(pBackend) ){ + return 0; + } +#endif + if( pBackend->pMutexMethods ){ + SyMutexEnter(pBackend->pMutexMethods,pBackend->pMutex); + } + pChunk = MemBackendPoolAlloc(&(*pBackend),nByte); + if( pBackend->pMutexMethods ){ + SyMutexLeave(pBackend->pMutexMethods,pBackend->pMutex); + } + return pChunk; +} +static sxi32 MemBackendPoolFree(SyMemBackend *pBackend,void * pChunk) +{ + SyMemHeader *pHeader; + sxu32 nBucket; + /* Get the corresponding bucket */ + pHeader = (SyMemHeader *)(((char *)pChunk) - sizeof(SyMemHeader)); + /* Sanity check to avoid misuse */ + if( (pHeader->nBucket >> 16) != SXMEM_POOL_MAGIC ){ + return SXERR_CORRUPT; + } + nBucket = pHeader->nBucket & 0xFFFF; + if( nBucket == SXU16_HIGH ){ + /* Free the big block */ + MemBackendFree(&(*pBackend),pHeader); + }else{ + /* Return to the free list */ + pHeader->pNext = pBackend->apPool[nBucket & 0x0f]; + pBackend->apPool[nBucket & 0x0f] = pHeader; + } + return SXRET_OK; +} +PH7_PRIVATE sxi32 SyMemBackendPoolFree(SyMemBackend *pBackend,void * pChunk) +{ + sxi32 rc; +#if defined(UNTRUST) + if( SXMEM_BACKEND_CORRUPT(pBackend) || pChunk == 0 ){ + return SXERR_CORRUPT; + } +#endif + if( pBackend->pMutexMethods ){ + SyMutexEnter(pBackend->pMutexMethods,pBackend->pMutex); + } + rc = MemBackendPoolFree(&(*pBackend),pChunk); + if( pBackend->pMutexMethods ){ + SyMutexLeave(pBackend->pMutexMethods,pBackend->pMutex); + } + return rc; +} +#if 0 +static void * MemBackendPoolRealloc(SyMemBackend *pBackend,void * pOld,sxu32 nByte) +{ + sxu32 nBucket,nBucketSize; + SyMemHeader *pHeader; + void * pNew; + + if( pOld == 0 ){ + /* Allocate a new pool */ + pNew = MemBackendPoolAlloc(&(*pBackend),nByte); + return pNew; + } + /* Get the corresponding bucket */ + pHeader = (SyMemHeader *)(((char *)pOld) - sizeof(SyMemHeader)); + /* Sanity check to avoid misuse */ + if( (pHeader->nBucket >> 16) != SXMEM_POOL_MAGIC ){ + return 0; + } + nBucket = pHeader->nBucket & 0xFFFF; + if( nBucket == SXU16_HIGH ){ + /* Big block */ + return MemBackendRealloc(&(*pBackend),pHeader,nByte); + } + nBucketSize = 1 << (nBucket + SXMEM_POOL_INCR); + if( nBucketSize >= nByte + sizeof(SyMemHeader) ){ + /* The old bucket can honor the requested size */ + return pOld; + } + /* Allocate a new pool */ + pNew = MemBackendPoolAlloc(&(*pBackend),nByte); + if( pNew == 0 ){ + return 0; + } + /* Copy the old data into the new block */ + SyMemcpy(pOld,pNew,nBucketSize); + /* Free the stale block */ + MemBackendPoolFree(&(*pBackend),pOld); + return pNew; +} +PH7_PRIVATE void * SyMemBackendPoolRealloc(SyMemBackend *pBackend,void * pOld,sxu32 nByte) +{ + void *pChunk; +#if defined(UNTRUST) + if( SXMEM_BACKEND_CORRUPT(pBackend) ){ + return 0; + } +#endif + if( pBackend->pMutexMethods ){ + SyMutexEnter(pBackend->pMutexMethods,pBackend->pMutex); + } + pChunk = MemBackendPoolRealloc(&(*pBackend),pOld,nByte); + if( pBackend->pMutexMethods ){ + SyMutexLeave(pBackend->pMutexMethods,pBackend->pMutex); + } + return pChunk; +} +#endif +PH7_PRIVATE sxi32 SyMemBackendInit(SyMemBackend *pBackend,ProcMemError xMemErr,void * pUserData) +{ +#if defined(UNTRUST) + if( pBackend == 0 ){ + return SXERR_EMPTY; + } +#endif + /* Zero the allocator first */ + SyZero(&(*pBackend),sizeof(SyMemBackend)); + pBackend->xMemError = xMemErr; + pBackend->pUserData = pUserData; + /* Switch to the OS memory allocator */ + pBackend->pMethods = &sOSAllocMethods; + if( pBackend->pMethods->xInit ){ + /* Initialize the backend */ + if( SXRET_OK != pBackend->pMethods->xInit(pBackend->pMethods->pUserData) ){ + return SXERR_ABORT; + } + } +#if defined(UNTRUST) + pBackend->nMagic = SXMEM_BACKEND_MAGIC; +#endif + return SXRET_OK; +} +PH7_PRIVATE sxi32 SyMemBackendInitFromOthers(SyMemBackend *pBackend,const SyMemMethods *pMethods,ProcMemError xMemErr,void * pUserData) +{ +#if defined(UNTRUST) + if( pBackend == 0 || pMethods == 0){ + return SXERR_EMPTY; + } +#endif + if( pMethods->xAlloc == 0 || pMethods->xRealloc == 0 || pMethods->xFree == 0 || pMethods->xChunkSize == 0 ){ + /* mandatory methods are missing */ + return SXERR_INVALID; + } + /* Zero the allocator first */ + SyZero(&(*pBackend),sizeof(SyMemBackend)); + pBackend->xMemError = xMemErr; + pBackend->pUserData = pUserData; + /* Switch to the host application memory allocator */ + pBackend->pMethods = pMethods; + if( pBackend->pMethods->xInit ){ + /* Initialize the backend */ + if( SXRET_OK != pBackend->pMethods->xInit(pBackend->pMethods->pUserData) ){ + return SXERR_ABORT; + } + } +#if defined(UNTRUST) + pBackend->nMagic = SXMEM_BACKEND_MAGIC; +#endif + return SXRET_OK; +} +PH7_PRIVATE sxi32 SyMemBackendInitFromParent(SyMemBackend *pBackend,SyMemBackend *pParent) +{ + sxu8 bInheritMutex; +#if defined(UNTRUST) + if( pBackend == 0 || SXMEM_BACKEND_CORRUPT(pParent) ){ + return SXERR_CORRUPT; + } +#endif + /* Zero the allocator first */ + SyZero(&(*pBackend),sizeof(SyMemBackend)); + pBackend->pMethods = pParent->pMethods; + pBackend->xMemError = pParent->xMemError; + pBackend->pUserData = pParent->pUserData; + bInheritMutex = pParent->pMutexMethods ? TRUE : FALSE; + if( bInheritMutex ){ + pBackend->pMutexMethods = pParent->pMutexMethods; + /* Create a private mutex */ + pBackend->pMutex = pBackend->pMutexMethods->xNew(SXMUTEX_TYPE_FAST); + if( pBackend->pMutex == 0){ + return SXERR_OS; + } + } +#if defined(UNTRUST) + pBackend->nMagic = SXMEM_BACKEND_MAGIC; +#endif + return SXRET_OK; +} +static sxi32 MemBackendRelease(SyMemBackend *pBackend) +{ + SyMemBlock *pBlock,*pNext; + + pBlock = pBackend->pBlocks; + for(;;){ + if( pBackend->nBlock == 0 ){ + break; + } + pNext = pBlock->pNext; + pBackend->pMethods->xFree(pBlock); + pBlock = pNext; + pBackend->nBlock--; + /* LOOP ONE */ + if( pBackend->nBlock == 0 ){ + break; + } + pNext = pBlock->pNext; + pBackend->pMethods->xFree(pBlock); + pBlock = pNext; + pBackend->nBlock--; + /* LOOP TWO */ + if( pBackend->nBlock == 0 ){ + break; + } + pNext = pBlock->pNext; + pBackend->pMethods->xFree(pBlock); + pBlock = pNext; + pBackend->nBlock--; + /* LOOP THREE */ + if( pBackend->nBlock == 0 ){ + break; + } + pNext = pBlock->pNext; + pBackend->pMethods->xFree(pBlock); + pBlock = pNext; + pBackend->nBlock--; + /* LOOP FOUR */ + } + if( pBackend->pMethods->xRelease ){ + pBackend->pMethods->xRelease(pBackend->pMethods->pUserData); + } + pBackend->pMethods = 0; + pBackend->pBlocks = 0; +#if defined(UNTRUST) + pBackend->nMagic = 0x2626; +#endif + return SXRET_OK; +} +PH7_PRIVATE sxi32 SyMemBackendRelease(SyMemBackend *pBackend) +{ + sxi32 rc; +#if defined(UNTRUST) + if( SXMEM_BACKEND_CORRUPT(pBackend) ){ + return SXERR_INVALID; + } +#endif + if( pBackend->pMutexMethods ){ + SyMutexEnter(pBackend->pMutexMethods,pBackend->pMutex); + } + rc = MemBackendRelease(&(*pBackend)); + if( pBackend->pMutexMethods ){ + SyMutexLeave(pBackend->pMutexMethods,pBackend->pMutex); + SyMutexRelease(pBackend->pMutexMethods,pBackend->pMutex); + } + return SXRET_OK; +} +PH7_PRIVATE void * SyMemBackendDup(SyMemBackend *pBackend,const void *pSrc,sxu32 nSize) +{ + void *pNew; +#if defined(UNTRUST) + if( pSrc == 0 || nSize <= 0 ){ + return 0; + } +#endif + pNew = SyMemBackendAlloc(&(*pBackend),nSize); + if( pNew ){ + SyMemcpy(pSrc,pNew,nSize); + } + return pNew; +} +PH7_PRIVATE char * SyMemBackendStrDup(SyMemBackend *pBackend,const char *zSrc,sxu32 nSize) +{ + char *zDest; + zDest = (char *)SyMemBackendAlloc(&(*pBackend),nSize + 1); + if( zDest ){ + Systrcpy(zDest,nSize+1,zSrc,nSize); + } + return zDest; +} +PH7_PRIVATE sxi32 SyBlobInitFromBuf(SyBlob *pBlob,void *pBuffer,sxu32 nSize) +{ +#if defined(UNTRUST) + if( pBlob == 0 || pBuffer == 0 || nSize < 1 ){ + return SXERR_EMPTY; + } +#endif + pBlob->pBlob = pBuffer; + pBlob->mByte = nSize; + pBlob->nByte = 0; + pBlob->pAllocator = 0; + pBlob->nFlags = SXBLOB_LOCKED|SXBLOB_STATIC; + return SXRET_OK; +} +PH7_PRIVATE sxi32 SyBlobInit(SyBlob *pBlob,SyMemBackend *pAllocator) +{ +#if defined(UNTRUST) + if( pBlob == 0 ){ + return SXERR_EMPTY; + } +#endif + pBlob->pBlob = 0; + pBlob->mByte = pBlob->nByte = 0; + pBlob->pAllocator = &(*pAllocator); + pBlob->nFlags = 0; + return SXRET_OK; +} +PH7_PRIVATE sxi32 SyBlobReadOnly(SyBlob *pBlob,const void *pData,sxu32 nByte) +{ +#if defined(UNTRUST) + if( pBlob == 0 ){ + return SXERR_EMPTY; + } +#endif + pBlob->pBlob = (void *)pData; + pBlob->nByte = nByte; + pBlob->mByte = 0; + pBlob->nFlags |= SXBLOB_RDONLY; + return SXRET_OK; +} +#ifndef SXBLOB_MIN_GROWTH +#define SXBLOB_MIN_GROWTH 16 +#endif +static sxi32 BlobPrepareGrow(SyBlob *pBlob,sxu32 *pByte) +{ + sxu32 nByte; + void *pNew; + nByte = *pByte; + if( pBlob->nFlags & (SXBLOB_LOCKED|SXBLOB_STATIC) ){ + if ( SyBlobFreeSpace(pBlob) < nByte ){ + *pByte = SyBlobFreeSpace(pBlob); + if( (*pByte) == 0 ){ + return SXERR_SHORT; + } + } + return SXRET_OK; + } + if( pBlob->nFlags & SXBLOB_RDONLY ){ + /* Make a copy of the read-only item */ + if( pBlob->nByte > 0 ){ + pNew = SyMemBackendDup(pBlob->pAllocator,pBlob->pBlob,pBlob->nByte); + if( pNew == 0 ){ + return SXERR_MEM; + } + pBlob->pBlob = pNew; + pBlob->mByte = pBlob->nByte; + }else{ + pBlob->pBlob = 0; + pBlob->mByte = 0; + } + /* Remove the read-only flag */ + pBlob->nFlags &= ~SXBLOB_RDONLY; + } + if( SyBlobFreeSpace(pBlob) >= nByte ){ + return SXRET_OK; + } + if( pBlob->mByte > 0 ){ + nByte = nByte + pBlob->mByte * 2 + SXBLOB_MIN_GROWTH; + }else if ( nByte < SXBLOB_MIN_GROWTH ){ + nByte = SXBLOB_MIN_GROWTH; + } + pNew = SyMemBackendRealloc(pBlob->pAllocator,pBlob->pBlob,nByte); + if( pNew == 0 ){ + return SXERR_MEM; + } + pBlob->pBlob = pNew; + pBlob->mByte = nByte; + return SXRET_OK; +} +PH7_PRIVATE sxi32 SyBlobAppend(SyBlob *pBlob,const void *pData,sxu32 nSize) +{ + sxu8 *zBlob; + sxi32 rc; + if( nSize < 1 ){ + return SXRET_OK; + } + rc = BlobPrepareGrow(&(*pBlob),&nSize); + if( SXRET_OK != rc ){ + return rc; + } + if( pData ){ + zBlob = (sxu8 *)pBlob->pBlob ; + zBlob = &zBlob[pBlob->nByte]; + pBlob->nByte += nSize; + SX_MACRO_FAST_MEMCPY(pData,zBlob,nSize); + } + return SXRET_OK; +} +PH7_PRIVATE sxi32 SyBlobNullAppend(SyBlob *pBlob) +{ + sxi32 rc; + sxu32 n; + n = pBlob->nByte; + rc = SyBlobAppend(&(*pBlob),(const void *)"\0",sizeof(char)); + if (rc == SXRET_OK ){ + pBlob->nByte = n; + } + return rc; +} +PH7_PRIVATE sxi32 SyBlobDup(SyBlob *pSrc,SyBlob *pDest) +{ + sxi32 rc = SXRET_OK; +#ifdef UNTRUST + if( pSrc == 0 || pDest == 0 ){ + return SXERR_EMPTY; + } +#endif + if( pSrc->nByte > 0 ){ + rc = SyBlobAppend(&(*pDest),pSrc->pBlob,pSrc->nByte); + } + return rc; +} +PH7_PRIVATE sxi32 SyBlobCmp(SyBlob *pLeft,SyBlob *pRight) +{ + sxi32 rc; +#ifdef UNTRUST + if( pLeft == 0 || pRight == 0 ){ + return pLeft ? 1 : -1; + } +#endif + if( pLeft->nByte != pRight->nByte ){ + /* Length differ */ + return pLeft->nByte - pRight->nByte; + } + if( pLeft->nByte == 0 ){ + return 0; + } + /* Perform a standard memcmp() operation */ + rc = SyMemcmp(pLeft->pBlob,pRight->pBlob,pLeft->nByte); + return rc; +} +PH7_PRIVATE sxi32 SyBlobReset(SyBlob *pBlob) +{ + pBlob->nByte = 0; + if( pBlob->nFlags & SXBLOB_RDONLY ){ + pBlob->pBlob = 0; + pBlob->mByte = 0; + pBlob->nFlags &= ~SXBLOB_RDONLY; + } + return SXRET_OK; +} +PH7_PRIVATE sxi32 SyBlobRelease(SyBlob *pBlob) +{ + if( (pBlob->nFlags & (SXBLOB_STATIC|SXBLOB_RDONLY)) == 0 && pBlob->mByte > 0 ){ + SyMemBackendFree(pBlob->pAllocator,pBlob->pBlob); + } + pBlob->pBlob = 0; + pBlob->nByte = pBlob->mByte = 0; + pBlob->nFlags = 0; + return SXRET_OK; +} +#ifndef PH7_DISABLE_BUILTIN_FUNC +PH7_PRIVATE sxi32 SyBlobSearch(const void *pBlob,sxu32 nLen,const void *pPattern,sxu32 pLen,sxu32 *pOfft) +{ + const char *zIn = (const char *)pBlob; + const char *zEnd; + sxi32 rc; + if( pLen > nLen ){ + return SXERR_NOTFOUND; + } + zEnd = &zIn[nLen-pLen]; + for(;;){ + if( zIn > zEnd ){break;} SX_MACRO_FAST_CMP(zIn,pPattern,pLen,rc); if( rc == 0 ){ if( pOfft ){ *pOfft = (sxu32)(zIn - (const char *)pBlob);} return SXRET_OK; } zIn++; + if( zIn > zEnd ){break;} SX_MACRO_FAST_CMP(zIn,pPattern,pLen,rc); if( rc == 0 ){ if( pOfft ){ *pOfft = (sxu32)(zIn - (const char *)pBlob);} return SXRET_OK; } zIn++; + if( zIn > zEnd ){break;} SX_MACRO_FAST_CMP(zIn,pPattern,pLen,rc); if( rc == 0 ){ if( pOfft ){ *pOfft = (sxu32)(zIn - (const char *)pBlob);} return SXRET_OK; } zIn++; + if( zIn > zEnd ){break;} SX_MACRO_FAST_CMP(zIn,pPattern,pLen,rc); if( rc == 0 ){ if( pOfft ){ *pOfft = (sxu32)(zIn - (const char *)pBlob);} return SXRET_OK; } zIn++; + } + return SXERR_NOTFOUND; +} +#endif /* PH7_DISABLE_BUILTIN_FUNC */ +/* SyRunTimeApi:sxds.c */ +PH7_PRIVATE sxi32 SySetInit(SySet *pSet,SyMemBackend *pAllocator,sxu32 ElemSize) +{ + pSet->nSize = 0 ; + pSet->nUsed = 0; + pSet->nCursor = 0; + pSet->eSize = ElemSize; + pSet->pAllocator = pAllocator; + pSet->pBase = 0; + pSet->pUserData = 0; + return SXRET_OK; +} +PH7_PRIVATE sxi32 SySetPut(SySet *pSet,const void *pItem) +{ + unsigned char *zbase; + if( pSet->nUsed >= pSet->nSize ){ + void *pNew; + if( pSet->pAllocator == 0 ){ + return SXERR_LOCKED; + } + if( pSet->nSize <= 0 ){ + pSet->nSize = 4; + } + pNew = SyMemBackendRealloc(pSet->pAllocator,pSet->pBase,pSet->eSize * pSet->nSize * 2); + if( pNew == 0 ){ + return SXERR_MEM; + } + pSet->pBase = pNew; + pSet->nSize <<= 1; + } + zbase = (unsigned char *)pSet->pBase; + SX_MACRO_FAST_MEMCPY(pItem,&zbase[pSet->nUsed * pSet->eSize],pSet->eSize); + pSet->nUsed++; + return SXRET_OK; +} +PH7_PRIVATE sxi32 SySetAlloc(SySet *pSet,sxi32 nItem) +{ + if( pSet->nSize > 0 ){ + return SXERR_LOCKED; + } + if( nItem < 8 ){ + nItem = 8; + } + pSet->pBase = SyMemBackendAlloc(pSet->pAllocator,pSet->eSize * nItem); + if( pSet->pBase == 0 ){ + return SXERR_MEM; + } + pSet->nSize = nItem; + return SXRET_OK; +} +PH7_PRIVATE sxi32 SySetReset(SySet *pSet) +{ + pSet->nUsed = 0; + pSet->nCursor = 0; + return SXRET_OK; +} +PH7_PRIVATE sxi32 SySetResetCursor(SySet *pSet) +{ + pSet->nCursor = 0; + return SXRET_OK; +} +PH7_PRIVATE sxi32 SySetGetNextEntry(SySet *pSet,void **ppEntry) +{ + register unsigned char *zSrc; + if( pSet->nCursor >= pSet->nUsed ){ + /* Reset cursor */ + pSet->nCursor = 0; + return SXERR_EOF; + } + zSrc = (unsigned char *)SySetBasePtr(pSet); + if( ppEntry ){ + *ppEntry = (void *)&zSrc[pSet->nCursor * pSet->eSize]; + } + pSet->nCursor++; + return SXRET_OK; +} +#ifndef PH7_DISABLE_BUILTIN_FUNC +PH7_PRIVATE void * SySetPeekCurrentEntry(SySet *pSet) +{ + register unsigned char *zSrc; + if( pSet->nCursor >= pSet->nUsed ){ + return 0; + } + zSrc = (unsigned char *)SySetBasePtr(pSet); + return (void *)&zSrc[pSet->nCursor * pSet->eSize]; +} +#endif /* PH7_DISABLE_BUILTIN_FUNC */ +PH7_PRIVATE sxi32 SySetTruncate(SySet *pSet,sxu32 nNewSize) +{ + if( nNewSize < pSet->nUsed ){ + pSet->nUsed = nNewSize; + } + return SXRET_OK; +} +PH7_PRIVATE sxi32 SySetRelease(SySet *pSet) +{ + sxi32 rc = SXRET_OK; + if( pSet->pAllocator && pSet->pBase ){ + rc = SyMemBackendFree(pSet->pAllocator,pSet->pBase); + } + pSet->pBase = 0; + pSet->nUsed = 0; + pSet->nCursor = 0; + return rc; +} +PH7_PRIVATE void * SySetPeek(SySet *pSet) +{ + const char *zBase; + if( pSet->nUsed <= 0 ){ + return 0; + } + zBase = (const char *)pSet->pBase; + return (void *)&zBase[(pSet->nUsed - 1) * pSet->eSize]; +} +PH7_PRIVATE void * SySetPop(SySet *pSet) +{ + const char *zBase; + void *pData; + if( pSet->nUsed <= 0 ){ + return 0; + } + zBase = (const char *)pSet->pBase; + pSet->nUsed--; + pData = (void *)&zBase[pSet->nUsed * pSet->eSize]; + return pData; +} +PH7_PRIVATE void * SySetAt(SySet *pSet,sxu32 nIdx) +{ + const char *zBase; + if( nIdx >= pSet->nUsed ){ + /* Out of range */ + return 0; + } + zBase = (const char *)pSet->pBase; + return (void *)&zBase[nIdx * pSet->eSize]; +} +/* Private hash entry */ +struct SyHashEntry_Pr +{ + const void *pKey; /* Hash key */ + sxu32 nKeyLen; /* Key length */ + void *pUserData; /* User private data */ + /* Private fields */ + sxu32 nHash; + SyHash *pHash; + SyHashEntry_Pr *pNext,*pPrev; /* Next and previous entry in the list */ + SyHashEntry_Pr *pNextCollide,*pPrevCollide; /* Collision list */ +}; +#define INVALID_HASH(H) ((H)->apBucket == 0) +/* Forward declarartion */ +static sxu32 SyBinHash(const void *pSrc,sxu32 nLen); +PH7_PRIVATE sxi32 SyHashInit(SyHash *pHash,SyMemBackend *pAllocator,ProcHash xHash,ProcCmp xCmp) +{ + SyHashEntry_Pr **apNew; +#if defined(UNTRUST) + if( pHash == 0 ){ + return SXERR_EMPTY; + } +#endif + /* Allocate a new table */ + apNew = (SyHashEntry_Pr **)SyMemBackendAlloc(&(*pAllocator),sizeof(SyHashEntry_Pr *) * SXHASH_BUCKET_SIZE); + if( apNew == 0 ){ + return SXERR_MEM; + } + SyZero((void *)apNew,sizeof(SyHashEntry_Pr *) * SXHASH_BUCKET_SIZE); + pHash->pAllocator = &(*pAllocator); + pHash->xHash = xHash ? xHash : SyBinHash; + pHash->xCmp = xCmp ? xCmp : SyMemcmp; + pHash->pCurrent = pHash->pList = 0; + pHash->nEntry = 0; + pHash->apBucket = apNew; + pHash->nBucketSize = SXHASH_BUCKET_SIZE; + return SXRET_OK; +} +PH7_PRIVATE sxi32 SyHashRelease(SyHash *pHash) +{ + SyHashEntry_Pr *pEntry,*pNext; +#if defined(UNTRUST) + if( INVALID_HASH(pHash) ){ + return SXERR_EMPTY; + } +#endif + pEntry = pHash->pList; + for(;;){ + if( pHash->nEntry == 0 ){ + break; + } + pNext = pEntry->pNext; + SyMemBackendPoolFree(pHash->pAllocator,pEntry); + pEntry = pNext; + pHash->nEntry--; + } + if( pHash->apBucket ){ + SyMemBackendFree(pHash->pAllocator,(void *)pHash->apBucket); + } + pHash->apBucket = 0; + pHash->nBucketSize = 0; + pHash->pAllocator = 0; + return SXRET_OK; +} +static SyHashEntry_Pr * HashGetEntry(SyHash *pHash,const void *pKey,sxu32 nKeyLen) +{ + SyHashEntry_Pr *pEntry; + sxu32 nHash; + + nHash = pHash->xHash(pKey,nKeyLen); + pEntry = pHash->apBucket[nHash & (pHash->nBucketSize - 1)]; + for(;;){ + if( pEntry == 0 ){ + break; + } + if( pEntry->nHash == nHash && pEntry->nKeyLen == nKeyLen && + pHash->xCmp(pEntry->pKey,pKey,nKeyLen) == 0 ){ + return pEntry; + } + pEntry = pEntry->pNextCollide; + } + /* Entry not found */ + return 0; +} +PH7_PRIVATE SyHashEntry * SyHashGet(SyHash *pHash,const void *pKey,sxu32 nKeyLen) +{ + SyHashEntry_Pr *pEntry; +#if defined(UNTRUST) + if( INVALID_HASH(pHash) ){ + return 0; + } +#endif + if( pHash->nEntry < 1 || nKeyLen < 1 ){ + /* Don't bother hashing,return immediately */ + return 0; + } + pEntry = HashGetEntry(&(*pHash),pKey,nKeyLen); + if( pEntry == 0 ){ + return 0; + } + return (SyHashEntry *)pEntry; +} +static sxi32 HashDeleteEntry(SyHash *pHash,SyHashEntry_Pr *pEntry,void **ppUserData) +{ + sxi32 rc; + if( pEntry->pPrevCollide == 0 ){ + pHash->apBucket[pEntry->nHash & (pHash->nBucketSize - 1)] = pEntry->pNextCollide; + }else{ + pEntry->pPrevCollide->pNextCollide = pEntry->pNextCollide; + } + if( pEntry->pNextCollide ){ + pEntry->pNextCollide->pPrevCollide = pEntry->pPrevCollide; + } + MACRO_LD_REMOVE(pHash->pList,pEntry); + pHash->nEntry--; + if( ppUserData ){ + /* Write a pointer to the user data */ + *ppUserData = pEntry->pUserData; + } + /* Release the entry */ + rc = SyMemBackendPoolFree(pHash->pAllocator,pEntry); + return rc; +} +PH7_PRIVATE sxi32 SyHashDeleteEntry(SyHash *pHash,const void *pKey,sxu32 nKeyLen,void **ppUserData) +{ + SyHashEntry_Pr *pEntry; + sxi32 rc; +#if defined(UNTRUST) + if( INVALID_HASH(pHash) ){ + return SXERR_CORRUPT; + } +#endif + pEntry = HashGetEntry(&(*pHash),pKey,nKeyLen); + if( pEntry == 0 ){ + return SXERR_NOTFOUND; + } + rc = HashDeleteEntry(&(*pHash),pEntry,ppUserData); + return rc; +} +PH7_PRIVATE sxi32 SyHashDeleteEntry2(SyHashEntry *pEntry) +{ + SyHashEntry_Pr *pPtr = (SyHashEntry_Pr *)pEntry; + sxi32 rc; +#if defined(UNTRUST) + if( pPtr == 0 || INVALID_HASH(pPtr->pHash) ){ + return SXERR_CORRUPT; + } +#endif + rc = HashDeleteEntry(pPtr->pHash,pPtr,0); + return rc; +} +PH7_PRIVATE sxi32 SyHashResetLoopCursor(SyHash *pHash) +{ +#if defined(UNTRUST) + if( INVALID_HASH(pHash) ){ + return SXERR_CORRUPT; + } +#endif + pHash->pCurrent = pHash->pList; + return SXRET_OK; +} +PH7_PRIVATE SyHashEntry * SyHashGetNextEntry(SyHash *pHash) +{ + SyHashEntry_Pr *pEntry; +#if defined(UNTRUST) + if( INVALID_HASH(pHash) ){ + return 0; + } +#endif + if( pHash->pCurrent == 0 || pHash->nEntry <= 0 ){ + pHash->pCurrent = pHash->pList; + return 0; + } + pEntry = pHash->pCurrent; + /* Advance the cursor */ + pHash->pCurrent = pEntry->pNext; + /* Return the current entry */ + return (SyHashEntry *)pEntry; +} +PH7_PRIVATE sxi32 SyHashForEach(SyHash *pHash,sxi32 (*xStep)(SyHashEntry *,void *),void *pUserData) +{ + SyHashEntry_Pr *pEntry; + sxi32 rc; + sxu32 n; +#if defined(UNTRUST) + if( INVALID_HASH(pHash) || xStep == 0){ + return 0; + } +#endif + pEntry = pHash->pList; + for( n = 0 ; n < pHash->nEntry ; n++ ){ + /* Invoke the callback */ + rc = xStep((SyHashEntry *)pEntry,pUserData); + if( rc != SXRET_OK ){ + return rc; + } + /* Point to the next entry */ + pEntry = pEntry->pNext; + } + return SXRET_OK; +} +static sxi32 HashGrowTable(SyHash *pHash) +{ + sxu32 nNewSize = pHash->nBucketSize * 2; + SyHashEntry_Pr *pEntry; + SyHashEntry_Pr **apNew; + sxu32 n,iBucket; + + /* Allocate a new larger table */ + apNew = (SyHashEntry_Pr **)SyMemBackendAlloc(pHash->pAllocator,nNewSize * sizeof(SyHashEntry_Pr *)); + if( apNew == 0 ){ + /* Not so fatal,simply a performance hit */ + return SXRET_OK; + } + /* Zero the new table */ + SyZero((void *)apNew,nNewSize * sizeof(SyHashEntry_Pr *)); + /* Rehash all entries */ + for( n = 0,pEntry = pHash->pList; n < pHash->nEntry ; n++ ){ + pEntry->pNextCollide = pEntry->pPrevCollide = 0; + /* Install in the new bucket */ + iBucket = pEntry->nHash & (nNewSize - 1); + pEntry->pNextCollide = apNew[iBucket]; + if( apNew[iBucket] != 0 ){ + apNew[iBucket]->pPrevCollide = pEntry; + } + apNew[iBucket] = pEntry; + /* Point to the next entry */ + pEntry = pEntry->pNext; + } + /* Release the old table and reflect the change */ + SyMemBackendFree(pHash->pAllocator,(void *)pHash->apBucket); + pHash->apBucket = apNew; + pHash->nBucketSize = nNewSize; + return SXRET_OK; +} +static sxi32 HashInsert(SyHash *pHash,SyHashEntry_Pr *pEntry) +{ + sxu32 iBucket = pEntry->nHash & (pHash->nBucketSize - 1); + /* Insert the entry in its corresponding bcuket */ + pEntry->pNextCollide = pHash->apBucket[iBucket]; + if( pHash->apBucket[iBucket] != 0 ){ + pHash->apBucket[iBucket]->pPrevCollide = pEntry; + } + pHash->apBucket[iBucket] = pEntry; + /* Link to the entry list */ + MACRO_LD_PUSH(pHash->pList,pEntry); + if( pHash->nEntry == 0 ){ + pHash->pCurrent = pHash->pList; + } + pHash->nEntry++; + return SXRET_OK; +} +PH7_PRIVATE sxi32 SyHashInsert(SyHash *pHash,const void *pKey,sxu32 nKeyLen,void *pUserData) +{ + SyHashEntry_Pr *pEntry; + sxi32 rc; +#if defined(UNTRUST) + if( INVALID_HASH(pHash) || pKey == 0 ){ + return SXERR_CORRUPT; + } +#endif + if( pHash->nEntry >= pHash->nBucketSize * SXHASH_FILL_FACTOR ){ + rc = HashGrowTable(&(*pHash)); + if( rc != SXRET_OK ){ + return rc; + } + } + /* Allocate a new hash entry */ + pEntry = (SyHashEntry_Pr *)SyMemBackendPoolAlloc(pHash->pAllocator,sizeof(SyHashEntry_Pr)); + if( pEntry == 0 ){ + return SXERR_MEM; + } + /* Zero the entry */ + SyZero(pEntry,sizeof(SyHashEntry_Pr)); + pEntry->pHash = pHash; + pEntry->pKey = pKey; + pEntry->nKeyLen = nKeyLen; + pEntry->pUserData = pUserData; + pEntry->nHash = pHash->xHash(pEntry->pKey,pEntry->nKeyLen); + /* Finally insert the entry in its corresponding bucket */ + rc = HashInsert(&(*pHash),pEntry); + return rc; +} +PH7_PRIVATE SyHashEntry * SyHashLastEntry(SyHash *pHash) +{ +#if defined(UNTRUST) + if( INVALID_HASH(pHash) ){ + return 0; + } +#endif + /* Last inserted entry */ + return (SyHashEntry *)pHash->pList; +} +/* SyRunTimeApi:sxutils.c */ +PH7_PRIVATE sxi32 SyStrIsNumeric(const char *zSrc,sxu32 nLen,sxu8 *pReal,const char **pzTail) +{ + const char *zCur,*zEnd; +#ifdef UNTRUST + if( SX_EMPTY_STR(zSrc) ){ + return SXERR_EMPTY; + } +#endif + zEnd = &zSrc[nLen]; + /* Jump leading white spaces */ + while( zSrc < zEnd && (unsigned char)zSrc[0] < 0xc0 && SyisSpace(zSrc[0]) ){ + zSrc++; + } + if( zSrc < zEnd && (zSrc[0] == '+' || zSrc[0] == '-') ){ + zSrc++; + } + zCur = zSrc; + if( pReal ){ + *pReal = FALSE; + } + for(;;){ + if( zSrc >= zEnd || (unsigned char)zSrc[0] >= 0xc0 || !SyisDigit(zSrc[0]) ){ break; } zSrc++; + if( zSrc >= zEnd || (unsigned char)zSrc[0] >= 0xc0 || !SyisDigit(zSrc[0]) ){ break; } zSrc++; + if( zSrc >= zEnd || (unsigned char)zSrc[0] >= 0xc0 || !SyisDigit(zSrc[0]) ){ break; } zSrc++; + if( zSrc >= zEnd || (unsigned char)zSrc[0] >= 0xc0 || !SyisDigit(zSrc[0]) ){ break; } zSrc++; + }; + if( zSrc < zEnd && zSrc > zCur ){ + int c = zSrc[0]; + if( c == '.' ){ + zSrc++; + if( pReal ){ + *pReal = TRUE; + } + if( pzTail ){ + while( zSrc < zEnd && (unsigned char)zSrc[0] < 0xc0 && SyisDigit(zSrc[0]) ){ + zSrc++; + } + if( zSrc < zEnd && (zSrc[0] == 'e' || zSrc[0] == 'E') ){ + zSrc++; + if( zSrc < zEnd && (zSrc[0] == '+' || zSrc[0] == '-') ){ + zSrc++; + } + while( zSrc < zEnd && (unsigned char)zSrc[0] < 0xc0 && SyisDigit(zSrc[0]) ){ + zSrc++; + } + } + } + }else if( c == 'e' || c == 'E' ){ + zSrc++; + if( pReal ){ + *pReal = TRUE; + } + if( pzTail ){ + if( zSrc < zEnd && (zSrc[0] == '+' || zSrc[0] == '-') ){ + zSrc++; + } + while( zSrc < zEnd && (unsigned char)zSrc[0] < 0xc0 && SyisDigit(zSrc[0]) ){ + zSrc++; + } + } + } + } + if( pzTail ){ + /* Point to the non numeric part */ + *pzTail = zSrc; + } + return zSrc > zCur ? SXRET_OK /* String prefix is numeric */ : SXERR_INVALID /* Not a digit stream */; +} +#define SXINT32_MIN_STR "2147483648" +#define SXINT32_MAX_STR "2147483647" +#define SXINT64_MIN_STR "9223372036854775808" +#define SXINT64_MAX_STR "9223372036854775807" +PH7_PRIVATE sxi32 SyStrToInt32(const char *zSrc,sxu32 nLen,void * pOutVal,const char **zRest) +{ + int isNeg = FALSE; + const char *zEnd; + sxi32 nVal = 0; + sxi16 i; +#if defined(UNTRUST) + if( SX_EMPTY_STR(zSrc) ){ + if( pOutVal ){ + *(sxi32 *)pOutVal = 0; + } + return SXERR_EMPTY; + } +#endif + zEnd = &zSrc[nLen]; + while(zSrc < zEnd && SyisSpace(zSrc[0]) ){ + zSrc++; + } + if( zSrc < zEnd && ( zSrc[0] == '-' || zSrc[0] == '+' ) ){ + isNeg = (zSrc[0] == '-') ? TRUE :FALSE; + zSrc++; + } + /* Skip leading zero */ + while(zSrc < zEnd && zSrc[0] == '0' ){ + zSrc++; + } + i = 10; + if( (sxu32)(zEnd-zSrc) >= 10 ){ + /* Handle overflow */ + i = SyMemcmp(zSrc,(isNeg == TRUE) ? SXINT32_MIN_STR : SXINT32_MAX_STR,nLen) <= 0 ? 10 : 9; + } + for(;;){ + if(zSrc >= zEnd || !i || !SyisDigit(zSrc[0])){ break; } nVal = nVal * 10 + ( zSrc[0] - '0' ) ; --i ; zSrc++; + if(zSrc >= zEnd || !i || !SyisDigit(zSrc[0])){ break; } nVal = nVal * 10 + ( zSrc[0] - '0' ) ; --i ; zSrc++; + if(zSrc >= zEnd || !i || !SyisDigit(zSrc[0])){ break; } nVal = nVal * 10 + ( zSrc[0] - '0' ) ; --i ; zSrc++; + if(zSrc >= zEnd || !i || !SyisDigit(zSrc[0])){ break; } nVal = nVal * 10 + ( zSrc[0] - '0' ) ; --i ; zSrc++; + } + /* Skip trailing spaces */ + while(zSrc < zEnd && SyisSpace(zSrc[0])){ + zSrc++; + } + if( zRest ){ + *zRest = (char *)zSrc; + } + if( pOutVal ){ + if( isNeg == TRUE && nVal != 0 ){ + nVal = -nVal; + } + *(sxi32 *)pOutVal = nVal; + } + return (zSrc >= zEnd) ? SXRET_OK : SXERR_SYNTAX; +} +PH7_PRIVATE sxi32 SyStrToInt64(const char *zSrc,sxu32 nLen,void * pOutVal,const char **zRest) +{ + int isNeg = FALSE; + const char *zEnd; + sxi64 nVal; + sxi16 i; +#if defined(UNTRUST) + if( SX_EMPTY_STR(zSrc) ){ + if( pOutVal ){ + *(sxi32 *)pOutVal = 0; + } + return SXERR_EMPTY; + } +#endif + zEnd = &zSrc[nLen]; + while(zSrc < zEnd && SyisSpace(zSrc[0]) ){ + zSrc++; + } + if( zSrc < zEnd && ( zSrc[0] == '-' || zSrc[0] == '+' ) ){ + isNeg = (zSrc[0] == '-') ? TRUE :FALSE; + zSrc++; + } + /* Skip leading zero */ + while(zSrc < zEnd && zSrc[0] == '0' ){ + zSrc++; + } + i = 19; + if( (sxu32)(zEnd-zSrc) >= 19 ){ + i = SyMemcmp(zSrc,isNeg ? SXINT64_MIN_STR : SXINT64_MAX_STR,19) <= 0 ? 19 : 18 ; + } + nVal = 0; + for(;;){ + if(zSrc >= zEnd || !i || !SyisDigit(zSrc[0])){ break; } nVal = nVal * 10 + ( zSrc[0] - '0' ) ; --i ; zSrc++; + if(zSrc >= zEnd || !i || !SyisDigit(zSrc[0])){ break; } nVal = nVal * 10 + ( zSrc[0] - '0' ) ; --i ; zSrc++; + if(zSrc >= zEnd || !i || !SyisDigit(zSrc[0])){ break; } nVal = nVal * 10 + ( zSrc[0] - '0' ) ; --i ; zSrc++; + if(zSrc >= zEnd || !i || !SyisDigit(zSrc[0])){ break; } nVal = nVal * 10 + ( zSrc[0] - '0' ) ; --i ; zSrc++; + } + /* Skip trailing spaces */ + while(zSrc < zEnd && SyisSpace(zSrc[0])){ + zSrc++; + } + if( zRest ){ + *zRest = (char *)zSrc; + } + if( pOutVal ){ + if( isNeg == TRUE && nVal != 0 ){ + nVal = -nVal; + } + *(sxi64 *)pOutVal = nVal; + } + return (zSrc >= zEnd) ? SXRET_OK : SXERR_SYNTAX; +} +PH7_PRIVATE sxi32 SyHexToint(sxi32 c) +{ + switch(c){ + case '0': return 0; + case '1': return 1; + case '2': return 2; + case '3': return 3; + case '4': return 4; + case '5': return 5; + case '6': return 6; + case '7': return 7; + case '8': return 8; + case '9': return 9; + case 'A': case 'a': return 10; + case 'B': case 'b': return 11; + case 'C': case 'c': return 12; + case 'D': case 'd': return 13; + case 'E': case 'e': return 14; + case 'F': case 'f': return 15; + } + return -1; +} +PH7_PRIVATE sxi32 SyHexStrToInt64(const char *zSrc,sxu32 nLen,void * pOutVal,const char **zRest) +{ + const char *zIn,*zEnd; + int isNeg = FALSE; + sxi64 nVal = 0; +#if defined(UNTRUST) + if( SX_EMPTY_STR(zSrc) ){ + if( pOutVal ){ + *(sxi32 *)pOutVal = 0; + } + return SXERR_EMPTY; + } +#endif + zEnd = &zSrc[nLen]; + while( zSrc < zEnd && SyisSpace(zSrc[0]) ){ + zSrc++; + } + if( zSrc < zEnd && ( *zSrc == '-' || *zSrc == '+' ) ){ + isNeg = (zSrc[0] == '-') ? TRUE :FALSE; + zSrc++; + } + if( zSrc < &zEnd[-2] && zSrc[0] == '0' && (zSrc[1] == 'x' || zSrc[1] == 'X') ){ + /* Bypass hex prefix */ + zSrc += sizeof(char) * 2; + } + /* Skip leading zero */ + while(zSrc < zEnd && zSrc[0] == '0' ){ + zSrc++; + } + zIn = zSrc; + for(;;){ + if(zSrc >= zEnd || !SyisHex(zSrc[0]) || (int)(zSrc-zIn) > 15) break; nVal = nVal * 16 + SyHexToint(zSrc[0]); zSrc++ ; + if(zSrc >= zEnd || !SyisHex(zSrc[0]) || (int)(zSrc-zIn) > 15) break; nVal = nVal * 16 + SyHexToint(zSrc[0]); zSrc++ ; + if(zSrc >= zEnd || !SyisHex(zSrc[0]) || (int)(zSrc-zIn) > 15) break; nVal = nVal * 16 + SyHexToint(zSrc[0]); zSrc++ ; + if(zSrc >= zEnd || !SyisHex(zSrc[0]) || (int)(zSrc-zIn) > 15) break; nVal = nVal * 16 + SyHexToint(zSrc[0]); zSrc++ ; + } + while( zSrc < zEnd && SyisSpace(zSrc[0]) ){ + zSrc++; + } + if( zRest ){ + *zRest = zSrc; + } + if( pOutVal ){ + if( isNeg == TRUE && nVal != 0 ){ + nVal = -nVal; + } + *(sxi64 *)pOutVal = nVal; + } + return zSrc >= zEnd ? SXRET_OK : SXERR_SYNTAX; +} +PH7_PRIVATE sxi32 SyOctalStrToInt64(const char *zSrc,sxu32 nLen,void * pOutVal,const char **zRest) +{ + const char *zIn,*zEnd; + int isNeg = FALSE; + sxi64 nVal = 0; + int c; +#if defined(UNTRUST) + if( SX_EMPTY_STR(zSrc) ){ + if( pOutVal ){ + *(sxi32 *)pOutVal = 0; + } + return SXERR_EMPTY; + } +#endif + zEnd = &zSrc[nLen]; + while(zSrc < zEnd && SyisSpace(zSrc[0]) ){ + zSrc++; + } + if( zSrc < zEnd && ( zSrc[0] == '-' || zSrc[0] == '+' ) ){ + isNeg = (zSrc[0] == '-') ? TRUE :FALSE; + zSrc++; + } + /* Skip leading zero */ + while(zSrc < zEnd && zSrc[0] == '0' ){ + zSrc++; + } + zIn = zSrc; + for(;;){ + if(zSrc >= zEnd || !SyisDigit(zSrc[0])){ break; } if( (c=zSrc[0]-'0') > 7 || (int)(zSrc-zIn) > 20){ break;} nVal = nVal * 8 + c; zSrc++; + if(zSrc >= zEnd || !SyisDigit(zSrc[0])){ break; } if( (c=zSrc[0]-'0') > 7 || (int)(zSrc-zIn) > 20){ break;} nVal = nVal * 8 + c; zSrc++; + if(zSrc >= zEnd || !SyisDigit(zSrc[0])){ break; } if( (c=zSrc[0]-'0') > 7 || (int)(zSrc-zIn) > 20){ break;} nVal = nVal * 8 + c; zSrc++; + if(zSrc >= zEnd || !SyisDigit(zSrc[0])){ break; } if( (c=zSrc[0]-'0') > 7 || (int)(zSrc-zIn) > 20){ break;} nVal = nVal * 8 + c; zSrc++; + } + /* Skip trailing spaces */ + while(zSrc < zEnd && SyisSpace(zSrc[0])){ + zSrc++; + } + if( zRest ){ + *zRest = zSrc; + } + if( pOutVal ){ + if( isNeg == TRUE && nVal != 0 ){ + nVal = -nVal; + } + *(sxi64 *)pOutVal = nVal; + } + return (zSrc >= zEnd) ? SXRET_OK : SXERR_SYNTAX; +} +PH7_PRIVATE sxi32 SyBinaryStrToInt64(const char *zSrc,sxu32 nLen,void * pOutVal,const char **zRest) +{ + const char *zIn,*zEnd; + int isNeg = FALSE; + sxi64 nVal = 0; + int c; +#if defined(UNTRUST) + if( SX_EMPTY_STR(zSrc) ){ + if( pOutVal ){ + *(sxi32 *)pOutVal = 0; + } + return SXERR_EMPTY; + } +#endif + zEnd = &zSrc[nLen]; + while(zSrc < zEnd && SyisSpace(zSrc[0]) ){ + zSrc++; + } + if( zSrc < zEnd && ( zSrc[0] == '-' || zSrc[0] == '+' ) ){ + isNeg = (zSrc[0] == '-') ? TRUE :FALSE; + zSrc++; + } + if( zSrc < &zEnd[-2] && zSrc[0] == '0' && (zSrc[1] == 'b' || zSrc[1] == 'B') ){ + /* Bypass binary prefix */ + zSrc += sizeof(char) * 2; + } + /* Skip leading zero */ + while(zSrc < zEnd && zSrc[0] == '0' ){ + zSrc++; + } + zIn = zSrc; + for(;;){ + if(zSrc >= zEnd || (zSrc[0] != '1' && zSrc[0] != '0') || (int)(zSrc-zIn) > 62){ break; } c = zSrc[0] - '0'; nVal = (nVal << 1) + c; zSrc++; + if(zSrc >= zEnd || (zSrc[0] != '1' && zSrc[0] != '0') || (int)(zSrc-zIn) > 62){ break; } c = zSrc[0] - '0'; nVal = (nVal << 1) + c; zSrc++; + if(zSrc >= zEnd || (zSrc[0] != '1' && zSrc[0] != '0') || (int)(zSrc-zIn) > 62){ break; } c = zSrc[0] - '0'; nVal = (nVal << 1) + c; zSrc++; + if(zSrc >= zEnd || (zSrc[0] != '1' && zSrc[0] != '0') || (int)(zSrc-zIn) > 62){ break; } c = zSrc[0] - '0'; nVal = (nVal << 1) + c; zSrc++; + } + /* Skip trailing spaces */ + while(zSrc < zEnd && SyisSpace(zSrc[0])){ + zSrc++; + } + if( zRest ){ + *zRest = zSrc; + } + if( pOutVal ){ + if( isNeg == TRUE && nVal != 0 ){ + nVal = -nVal; + } + *(sxi64 *)pOutVal = nVal; + } + return (zSrc >= zEnd) ? SXRET_OK : SXERR_SYNTAX; +} +PH7_PRIVATE sxi32 SyStrToReal(const char *zSrc,sxu32 nLen,void * pOutVal,const char **zRest) +{ +#define SXDBL_DIG 15 +#define SXDBL_MAX_EXP 308 +#define SXDBL_MIN_EXP_PLUS 307 + static const sxreal aTab[] = { + 10, + 1.0e2, + 1.0e4, + 1.0e8, + 1.0e16, + 1.0e32, + 1.0e64, + 1.0e128, + 1.0e256 + }; + sxu8 neg = FALSE; + sxreal Val = 0.0; + const char *zEnd; + sxi32 Lim,exp; + sxreal *p = 0; +#ifdef UNTRUST + if( SX_EMPTY_STR(zSrc) ){ + if( pOutVal ){ + *(sxreal *)pOutVal = 0.0; + } + return SXERR_EMPTY; + } +#endif + zEnd = &zSrc[nLen]; + while( zSrc < zEnd && SyisSpace(zSrc[0]) ){ + zSrc++; + } + if( zSrc < zEnd && (zSrc[0] == '-' || zSrc[0] == '+' ) ){ + neg = zSrc[0] == '-' ? TRUE : FALSE ; + zSrc++; + } + Lim = SXDBL_DIG ; + for(;;){ + if(zSrc >= zEnd||!Lim||!SyisDigit(zSrc[0])) break ; Val = Val * 10.0 + (zSrc[0] - '0') ; zSrc++ ; --Lim; + if(zSrc >= zEnd||!Lim||!SyisDigit(zSrc[0])) break ; Val = Val * 10.0 + (zSrc[0] - '0') ; zSrc++ ; --Lim; + if(zSrc >= zEnd||!Lim||!SyisDigit(zSrc[0])) break ; Val = Val * 10.0 + (zSrc[0] - '0') ; zSrc++ ; --Lim; + if(zSrc >= zEnd||!Lim||!SyisDigit(zSrc[0])) break ; Val = Val * 10.0 + (zSrc[0] - '0') ; zSrc++ ; --Lim; + } + if( zSrc < zEnd && ( zSrc[0] == '.' || zSrc[0] == ',' ) ){ + sxreal dec = 1.0; + zSrc++; + for(;;){ + if(zSrc >= zEnd||!Lim||!SyisDigit(zSrc[0])) break ; Val = Val * 10.0 + (zSrc[0] - '0') ; dec *= 10.0; zSrc++ ;--Lim; + if(zSrc >= zEnd||!Lim||!SyisDigit(zSrc[0])) break ; Val = Val * 10.0 + (zSrc[0] - '0') ; dec *= 10.0; zSrc++ ;--Lim; + if(zSrc >= zEnd||!Lim||!SyisDigit(zSrc[0])) break ; Val = Val * 10.0 + (zSrc[0] - '0') ; dec *= 10.0; zSrc++ ;--Lim; + if(zSrc >= zEnd||!Lim||!SyisDigit(zSrc[0])) break ; Val = Val * 10.0 + (zSrc[0] - '0') ; dec *= 10.0; zSrc++ ;--Lim; + } + Val /= dec; + } + if( neg == TRUE && Val != 0.0 ) { + Val = -Val ; + } + if( Lim <= 0 ){ + /* jump overflow digit */ + while( zSrc < zEnd ){ + if( zSrc[0] == 'e' || zSrc[0] == 'E' ){ + break; + } + zSrc++; + } + } + neg = FALSE; + if( zSrc < zEnd && ( zSrc[0] == 'e' || zSrc[0] == 'E' ) ){ + zSrc++; + if( zSrc < zEnd && ( zSrc[0] == '-' || zSrc[0] == '+') ){ + neg = zSrc[0] == '-' ? TRUE : FALSE ; + zSrc++; + } + exp = 0; + while( zSrc < zEnd && SyisDigit(zSrc[0]) && exp < SXDBL_MAX_EXP ){ + exp = exp * 10 + (zSrc[0] - '0'); + zSrc++; + } + if( neg ){ + if( exp > SXDBL_MIN_EXP_PLUS ) exp = SXDBL_MIN_EXP_PLUS ; + }else if ( exp > SXDBL_MAX_EXP ){ + exp = SXDBL_MAX_EXP; + } + for( p = (sxreal *)aTab ; exp ; exp >>= 1 , p++ ){ + if( exp & 01 ){ + if( neg ){ + Val /= *p ; + }else{ + Val *= *p; + } + } + } + } + while( zSrc < zEnd && SyisSpace(zSrc[0]) ){ + zSrc++; + } + if( zRest ){ + *zRest = zSrc; + } + if( pOutVal ){ + *(sxreal *)pOutVal = Val; + } + return zSrc >= zEnd ? SXRET_OK : SXERR_SYNTAX; +} +/* SyRunTimeApi:sxlib.c */ +static sxu32 SyBinHash(const void *pSrc,sxu32 nLen) +{ + register unsigned char *zIn = (unsigned char *)pSrc; + unsigned char *zEnd; + sxu32 nH = 5381; + zEnd = &zIn[nLen]; + for(;;){ + if( zIn >= zEnd ){ break; } nH = nH * 33 + zIn[0] ; zIn++; + if( zIn >= zEnd ){ break; } nH = nH * 33 + zIn[0] ; zIn++; + if( zIn >= zEnd ){ break; } nH = nH * 33 + zIn[0] ; zIn++; + if( zIn >= zEnd ){ break; } nH = nH * 33 + zIn[0] ; zIn++; + } + return nH; +} +PH7_PRIVATE sxu32 SyStrHash(const void *pSrc,sxu32 nLen) +{ + register unsigned char *zIn = (unsigned char *)pSrc; + unsigned char *zEnd; + sxu32 nH = 5381; + zEnd = &zIn[nLen]; + for(;;){ + if( zIn >= zEnd ){ break; } nH = nH * 33 + SyToLower(zIn[0]); zIn++; + if( zIn >= zEnd ){ break; } nH = nH * 33 + SyToLower(zIn[0]); zIn++; + if( zIn >= zEnd ){ break; } nH = nH * 33 + SyToLower(zIn[0]); zIn++; + if( zIn >= zEnd ){ break; } nH = nH * 33 + SyToLower(zIn[0]); zIn++; + } + return nH; +} +#ifndef PH7_DISABLE_BUILTIN_FUNC +PH7_PRIVATE sxi32 SyBase64Encode(const char *zSrc,sxu32 nLen,ProcConsumer xConsumer,void *pUserData) +{ + static const unsigned char zBase64[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + unsigned char *zIn = (unsigned char *)zSrc; + unsigned char z64[4]; + sxu32 i; + sxi32 rc; +#if defined(UNTRUST) + if( SX_EMPTY_STR(zSrc) || xConsumer == 0){ + return SXERR_EMPTY; + } +#endif + for(i = 0; i + 2 < nLen; i += 3){ + z64[0] = zBase64[(zIn[i] >> 2) & 0x3F]; + z64[1] = zBase64[( ((zIn[i] & 0x03) << 4) | (zIn[i+1] >> 4)) & 0x3F]; + z64[2] = zBase64[( ((zIn[i+1] & 0x0F) << 2) | (zIn[i + 2] >> 6) ) & 0x3F]; + z64[3] = zBase64[ zIn[i + 2] & 0x3F]; + + rc = xConsumer((const void *)z64,sizeof(z64),pUserData); + if( rc != SXRET_OK ){return SXERR_ABORT;} + + } + if ( i+1 < nLen ){ + z64[0] = zBase64[(zIn[i] >> 2) & 0x3F]; + z64[1] = zBase64[( ((zIn[i] & 0x03) << 4) | (zIn[i+1] >> 4)) & 0x3F]; + z64[2] = zBase64[(zIn[i+1] & 0x0F) << 2 ]; + z64[3] = '='; + + rc = xConsumer((const void *)z64,sizeof(z64),pUserData); + if( rc != SXRET_OK ){return SXERR_ABORT;} + + }else if( i < nLen ){ + z64[0] = zBase64[(zIn[i] >> 2) & 0x3F]; + z64[1] = zBase64[(zIn[i] & 0x03) << 4]; + z64[2] = '='; + z64[3] = '='; + + rc = xConsumer((const void *)z64,sizeof(z64),pUserData); + if( rc != SXRET_OK ){return SXERR_ABORT;} + } + + return SXRET_OK; +} +PH7_PRIVATE sxi32 SyBase64Decode(const char *zB64,sxu32 nLen,ProcConsumer xConsumer,void *pUserData) +{ + static const sxu32 aBase64Trans[] = { + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,62,0,0,0,63,52,53,54,55,56,57,58,59,60,61,0,0,0,0,0,0,0,0,1,2,3,4, + 5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,0,0,0,0,0,0,26,27, + 28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,0,0, + 0,0,0 + }; + sxu32 n,w,x,y,z; + sxi32 rc; + unsigned char zOut[10]; +#if defined(UNTRUST) + if( SX_EMPTY_STR(zB64) || xConsumer == 0 ){ + return SXERR_EMPTY; + } +#endif + while(nLen > 0 && zB64[nLen - 1] == '=' ){ + nLen--; + } + for( n = 0 ; n+3>4) & 0x03); + zOut[1] = ((x<<4) & 0xF0) | ((y>>2) & 0x0F); + zOut[2] = ((y<<6) & 0xC0) | (z & 0x3F); + + rc = xConsumer((const void *)zOut,sizeof(unsigned char)*3,pUserData); + if( rc != SXRET_OK ){ return SXERR_ABORT;} + } + if( n+2 < nLen ){ + w = aBase64Trans[zB64[n] & 0x7F]; + x = aBase64Trans[zB64[n+1] & 0x7F]; + y = aBase64Trans[zB64[n+2] & 0x7F]; + + zOut[0] = ((w<<2) & 0xFC) | ((x>>4) & 0x03); + zOut[1] = ((x<<4) & 0xF0) | ((y>>2) & 0x0F); + + rc = xConsumer((const void *)zOut,sizeof(unsigned char)*2,pUserData); + if( rc != SXRET_OK ){ return SXERR_ABORT;} + }else if( n+1 < nLen ){ + w = aBase64Trans[zB64[n] & 0x7F]; + x = aBase64Trans[zB64[n+1] & 0x7F]; + + zOut[0] = ((w<<2) & 0xFC) | ((x>>4) & 0x03); + + rc = xConsumer((const void *)zOut,sizeof(unsigned char)*1,pUserData); + if( rc != SXRET_OK ){ return SXERR_ABORT;} + } + return SXRET_OK; +} +#endif /* PH7_DISABLE_BUILTIN_FUNC */ +#define INVALID_LEXER(LEX) ( LEX == 0 || LEX->xTokenizer == 0 ) +PH7_PRIVATE sxi32 SyLexInit(SyLex *pLex,SySet *pSet,ProcTokenizer xTokenizer,void *pUserData) +{ + SyStream *pStream; +#if defined (UNTRUST) + if ( pLex == 0 || xTokenizer == 0 ){ + return SXERR_CORRUPT; + } +#endif + pLex->pTokenSet = 0; + /* Initialize lexer fields */ + if( pSet ){ + if ( SySetElemSize(pSet) != sizeof(SyToken) ){ + return SXERR_INVALID; + } + pLex->pTokenSet = pSet; + } + pStream = &pLex->sStream; + pLex->xTokenizer = xTokenizer; + pLex->pUserData = pUserData; + + pStream->nLine = 1; + pStream->nIgn = 0; + pStream->zText = pStream->zEnd = 0; + pStream->pSet = pSet; + return SXRET_OK; +} +PH7_PRIVATE sxi32 SyLexTokenizeInput(SyLex *pLex,const char *zInput,sxu32 nLen,void *pCtxData,ProcSort xSort,ProcCmp xCmp) +{ + const unsigned char *zCur; + SyStream *pStream; + SyToken sToken; + sxi32 rc; +#if defined (UNTRUST) + if ( INVALID_LEXER(pLex) || zInput == 0 ){ + return SXERR_CORRUPT; + } +#endif + pStream = &pLex->sStream; + /* Point to the head of the input */ + pStream->zText = pStream->zInput = (const unsigned char *)zInput; + /* Point to the end of the input */ + pStream->zEnd = &pStream->zInput[nLen]; + for(;;){ + if( pStream->zText >= pStream->zEnd ){ + /* End of the input reached */ + break; + } + zCur = pStream->zText; + /* Call the tokenizer callback */ + rc = pLex->xTokenizer(pStream,&sToken,pLex->pUserData,pCtxData); + if( rc != SXRET_OK && rc != SXERR_CONTINUE ){ + /* Tokenizer callback request an operation abort */ + if( rc == SXERR_ABORT ){ + return SXERR_ABORT; + } + break; + } + if( rc == SXERR_CONTINUE ){ + /* Request to ignore this token */ + pStream->nIgn++; + }else if( pLex->pTokenSet ){ + /* Put the token in the set */ + rc = SySetPut(pLex->pTokenSet,(const void *)&sToken); + if( rc != SXRET_OK ){ + break; + } + } + if( zCur >= pStream->zText ){ + /* Automatic advance of the stream cursor */ + pStream->zText = &zCur[1]; + } + } + if( xSort && pLex->pTokenSet ){ + SyToken *aToken = (SyToken *)SySetBasePtr(pLex->pTokenSet); + /* Sort the extrated tokens */ + if( xCmp == 0 ){ + /* Use a default comparison function */ + xCmp = SyMemcmp; + } + xSort(aToken,SySetUsed(pLex->pTokenSet),sizeof(SyToken),xCmp); + } + return SXRET_OK; +} +PH7_PRIVATE sxi32 SyLexRelease(SyLex *pLex) +{ + sxi32 rc = SXRET_OK; +#if defined (UNTRUST) + if ( INVALID_LEXER(pLex) ){ + return SXERR_CORRUPT; + } +#else + SXUNUSED(pLex); /* Prevent compiler warning */ +#endif + return rc; +} +#ifndef PH7_DISABLE_BUILTIN_FUNC +#define SAFE_HTTP(C) (SyisAlphaNum(c) || c == '_' || c == '-' || c == '$' || c == '.' ) +PH7_PRIVATE sxi32 SyUriEncode(const char *zSrc,sxu32 nLen,ProcConsumer xConsumer,void *pUserData) +{ + unsigned char *zIn = (unsigned char *)zSrc; + unsigned char zHex[3] = { '%',0,0 }; + unsigned char zOut[2]; + unsigned char *zCur,*zEnd; + sxi32 c; + sxi32 rc; +#ifdef UNTRUST + if( SX_EMPTY_STR(zSrc) || xConsumer == 0 ){ + return SXERR_EMPTY; + } +#endif + rc = SXRET_OK; + zEnd = &zIn[nLen]; zCur = zIn; + for(;;){ + if( zCur >= zEnd ){ + if( zCur != zIn ){ + rc = xConsumer(zIn,(sxu32)(zCur-zIn),pUserData); + } + break; + } + c = zCur[0]; + if( SAFE_HTTP(c) ){ + zCur++; continue; + } + if( zCur != zIn && SXRET_OK != (rc = xConsumer(zIn,(sxu32)(zCur-zIn),pUserData))){ + break; + } + if( c == ' ' ){ + zOut[0] = '+'; + rc = xConsumer((const void *)zOut,sizeof(unsigned char),pUserData); + }else{ + zHex[1] = "0123456789ABCDEF"[(c >> 4) & 0x0F]; + zHex[2] = "0123456789ABCDEF"[c & 0x0F]; + rc = xConsumer(zHex,sizeof(zHex),pUserData); + } + if( SXRET_OK != rc ){ + break; + } + zIn = &zCur[1]; zCur = zIn ; + } + return rc == SXRET_OK ? SXRET_OK : SXERR_ABORT; +} +#endif /* PH7_DISABLE_BUILTIN_FUNC */ +static sxi32 SyAsciiToHex(sxi32 c) +{ + if( c >= 'a' && c <= 'f' ){ + c += 10 - 'a'; + return c; + } + if( c >= '0' && c <= '9' ){ + c -= '0'; + return c; + } + if( c >= 'A' && c <= 'F') { + c += 10 - 'A'; + return c; + } + return 0; +} +PH7_PRIVATE sxi32 SyUriDecode(const char *zSrc,sxu32 nLen,ProcConsumer xConsumer,void *pUserData,int bUTF8) +{ + static const sxu8 Utf8Trans[] = { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x00, 0x01, 0x02, 0x03, 0x00, 0x01, 0x00, 0x00 + }; + const char *zIn = zSrc; + const char *zEnd; + const char *zCur; + sxu8 *zOutPtr; + sxu8 zOut[10]; + sxi32 c,d; + sxi32 rc; +#if defined(UNTRUST) + if( SX_EMPTY_STR(zSrc) || xConsumer == 0 ){ + return SXERR_EMPTY; + } +#endif + rc = SXRET_OK; + zEnd = &zSrc[nLen]; + zCur = zIn; + for(;;){ + while(zCur < zEnd && zCur[0] != '%' && zCur[0] != '+' ){ + zCur++; + } + if( zCur != zIn ){ + /* Consume input */ + rc = xConsumer(zIn,(unsigned int)(zCur-zIn),pUserData); + if( rc != SXRET_OK ){ + /* User consumer routine request an operation abort */ + break; + } + } + if( zCur >= zEnd ){ + rc = SXRET_OK; + break; + } + /* Decode unsafe HTTP characters */ + zOutPtr = zOut; + if( zCur[0] == '+' ){ + *zOutPtr++ = ' '; + zCur++; + }else{ + if( &zCur[2] >= zEnd ){ + rc = SXERR_OVERFLOW; + break; + } + c = (SyAsciiToHex(zCur[1]) <<4) | SyAsciiToHex(zCur[2]); + zCur += 3; + if( c < 0x000C0 ){ + *zOutPtr++ = (sxu8)c; + }else{ + c = Utf8Trans[c-0xC0]; + while( zCur[0] == '%' ){ + d = (SyAsciiToHex(zCur[1]) <<4) | SyAsciiToHex(zCur[2]); + if( (d&0xC0) != 0x80 ){ + break; + } + c = (c<<6) + (0x3f & d); + zCur += 3; + } + if( bUTF8 == FALSE ){ + *zOutPtr++ = (sxu8)c; + }else{ + SX_WRITE_UTF8(zOutPtr,c); + } + } + + } + /* Consume the decoded characters */ + rc = xConsumer((const void *)zOut,(unsigned int)(zOutPtr-zOut),pUserData); + if( rc != SXRET_OK ){ + break; + } + /* Synchronize pointers */ + zIn = zCur; + } + return rc; +} +#ifndef PH7_DISABLE_BUILTIN_FUNC +static const char *zEngDay[] = { + "Sunday","Monday","Tuesday","Wednesday", + "Thursday","Friday","Saturday" +}; +static const char *zEngMonth[] = { + "January","February","March","April", + "May","June","July","August", + "September","October","November","December" +}; +static const char * GetDay(sxi32 i) +{ + return zEngDay[ i % 7 ]; +} +static const char * GetMonth(sxi32 i) +{ + return zEngMonth[ i % 12 ]; +} +PH7_PRIVATE const char * SyTimeGetDay(sxi32 iDay) +{ + return GetDay(iDay); +} +PH7_PRIVATE const char * SyTimeGetMonth(sxi32 iMonth) +{ + return GetMonth(iMonth); +} +#endif /* PH7_DISABLE_BUILTIN_FUNC */ +/* SyRunTimeApi: sxfmt.c */ +#define SXFMT_BUFSIZ 1024 /* Conversion buffer size */ +/* +** Conversion types fall into various categories as defined by the +** following enumeration. +*/ +#define SXFMT_RADIX 1 /* Integer types.%d, %x, %o, and so forth */ +#define SXFMT_FLOAT 2 /* Floating point.%f */ +#define SXFMT_EXP 3 /* Exponentional notation.%e and %E */ +#define SXFMT_GENERIC 4 /* Floating or exponential, depending on exponent.%g */ +#define SXFMT_SIZE 5 /* Total number of characters processed so far.%n */ +#define SXFMT_STRING 6 /* Strings.%s */ +#define SXFMT_PERCENT 7 /* Percent symbol.%% */ +#define SXFMT_CHARX 8 /* Characters.%c */ +#define SXFMT_ERROR 9 /* Used to indicate no such conversion type */ +/* Extension by Symisc Systems */ +#define SXFMT_RAWSTR 13 /* %z Pointer to raw string (SyString *) */ +#define SXFMT_UNUSED 15 +/* +** Allowed values for SyFmtInfo.flags +*/ +#define SXFLAG_SIGNED 0x01 +#define SXFLAG_UNSIGNED 0x02 +/* Allowed values for SyFmtConsumer.nType */ +#define SXFMT_CONS_PROC 1 /* Consumer is a procedure */ +#define SXFMT_CONS_STR 2 /* Consumer is a managed string */ +#define SXFMT_CONS_FILE 5 /* Consumer is an open File */ +#define SXFMT_CONS_BLOB 6 /* Consumer is a BLOB */ +/* +** Each builtin conversion character (ex: the 'd' in "%d") is described +** by an instance of the following structure +*/ +typedef struct SyFmtInfo SyFmtInfo; +struct SyFmtInfo +{ + char fmttype; /* The format field code letter [i.e: 'd','s','x'] */ + sxu8 base; /* The base for radix conversion */ + int flags; /* One or more of SXFLAG_ constants below */ + sxu8 type; /* Conversion paradigm */ + char *charset; /* The character set for conversion */ + char *prefix; /* Prefix on non-zero values in alt format */ +}; +typedef struct SyFmtConsumer SyFmtConsumer; +struct SyFmtConsumer +{ + sxu32 nLen; /* Total output length */ + sxi32 nType; /* Type of the consumer see below */ + sxi32 rc; /* Consumer return value;Abort processing if rc != SXRET_OK */ + union{ + struct{ + ProcConsumer xUserConsumer; + void *pUserData; + }sFunc; + SyBlob *pBlob; + }uConsumer; +}; +#ifndef SX_OMIT_FLOATINGPOINT +static int getdigit(sxlongreal *val,int *cnt) +{ + sxlongreal d; + int digit; + + if( (*cnt)++ >= 16 ){ + return '0'; + } + digit = (int)*val; + d = digit; + *val = (*val - d)*10.0; + return digit + '0' ; +} +#endif /* SX_OMIT_FLOATINGPOINT */ +/* + * The following routine was taken from the SQLITE2 source tree and was + * extended by Symisc Systems to fit its need. + * Status: Public Domain + */ +static sxi32 InternFormat(ProcConsumer xConsumer,void *pUserData,const char *zFormat,va_list ap) +{ + /* + * The following table is searched linearly, so it is good to put the most frequently + * used conversion types first. + */ +static const SyFmtInfo aFmt[] = { + { 'd', 10, SXFLAG_SIGNED, SXFMT_RADIX, "0123456789",0 }, + { 's', 0, 0, SXFMT_STRING, 0, 0 }, + { 'c', 0, 0, SXFMT_CHARX, 0, 0 }, + { 'x', 16, 0, SXFMT_RADIX, "0123456789abcdef", "x0" }, + { 'X', 16, 0, SXFMT_RADIX, "0123456789ABCDEF", "X0" }, + /* -- Extensions by Symisc Systems -- */ + { 'z', 0, 0, SXFMT_RAWSTR, 0, 0 }, /* Pointer to a raw string (SyString *) */ + { 'B', 2, 0, SXFMT_RADIX, "01", "b0"}, + /* -- End of Extensions -- */ + { 'o', 8, 0, SXFMT_RADIX, "01234567", "0" }, + { 'u', 10, 0, SXFMT_RADIX, "0123456789", 0 }, +#ifndef SX_OMIT_FLOATINGPOINT + { 'f', 0, SXFLAG_SIGNED, SXFMT_FLOAT, 0, 0 }, + { 'e', 0, SXFLAG_SIGNED, SXFMT_EXP, "e", 0 }, + { 'E', 0, SXFLAG_SIGNED, SXFMT_EXP, "E", 0 }, + { 'g', 0, SXFLAG_SIGNED, SXFMT_GENERIC, "e", 0 }, + { 'G', 0, SXFLAG_SIGNED, SXFMT_GENERIC, "E", 0 }, +#endif + { 'i', 10, SXFLAG_SIGNED, SXFMT_RADIX,"0123456789", 0 }, + { 'n', 0, 0, SXFMT_SIZE, 0, 0 }, + { '%', 0, 0, SXFMT_PERCENT, 0, 0 }, + { 'p', 10, 0, SXFMT_RADIX, "0123456789", 0 } +}; + int c; /* Next character in the format string */ + char *bufpt; /* Pointer to the conversion buffer */ + int precision; /* Precision of the current field */ + int length; /* Length of the field */ + int idx; /* A general purpose loop counter */ + int width; /* Width of the current field */ + sxu8 flag_leftjustify; /* True if "-" flag is present */ + sxu8 flag_plussign; /* True if "+" flag is present */ + sxu8 flag_blanksign; /* True if " " flag is present */ + sxu8 flag_alternateform; /* True if "#" flag is present */ + sxu8 flag_zeropad; /* True if field width constant starts with zero */ + sxu8 flag_long; /* True if "l" flag is present */ + sxi64 longvalue; /* Value for integer types */ + const SyFmtInfo *infop; /* Pointer to the appropriate info structure */ + char buf[SXFMT_BUFSIZ]; /* Conversion buffer */ + char prefix; /* Prefix character."+" or "-" or " " or '\0'.*/ + sxu8 errorflag = 0; /* True if an error is encountered */ + sxu8 xtype; /* Conversion paradigm */ + char *zExtra; + static char spaces[] = " "; +#define etSPACESIZE ((int)sizeof(spaces)-1) +#ifndef SX_OMIT_FLOATINGPOINT + sxlongreal realvalue; /* Value for real types */ + int exp; /* exponent of real numbers */ + double rounder; /* Used for rounding floating point values */ + sxu8 flag_dp; /* True if decimal point should be shown */ + sxu8 flag_rtz; /* True if trailing zeros should be removed */ + sxu8 flag_exp; /* True to force display of the exponent */ + int nsd; /* Number of significant digits returned */ +#endif + int rc; + + length = 0; + bufpt = 0; + for(; (c=(*zFormat))!=0; ++zFormat){ + if( c!='%' ){ + unsigned int amt; + bufpt = (char *)zFormat; + amt = 1; + while( (c=(*++zFormat))!='%' && c!=0 ) amt++; + rc = xConsumer((const void *)bufpt,amt,pUserData); + if( rc != SXRET_OK ){ + return SXERR_ABORT; /* Consumer routine request an operation abort */ + } + if( c==0 ){ + return errorflag > 0 ? SXERR_FORMAT : SXRET_OK; + } + } + if( (c=(*++zFormat))==0 ){ + errorflag = 1; + rc = xConsumer("%",sizeof("%")-1,pUserData); + if( rc != SXRET_OK ){ + return SXERR_ABORT; /* Consumer routine request an operation abort */ + } + return errorflag > 0 ? SXERR_FORMAT : SXRET_OK; + } + /* Find out what flags are present */ + flag_leftjustify = flag_plussign = flag_blanksign = + flag_alternateform = flag_zeropad = 0; + do{ + switch( c ){ + case '-': flag_leftjustify = 1; c = 0; break; + case '+': flag_plussign = 1; c = 0; break; + case ' ': flag_blanksign = 1; c = 0; break; + case '#': flag_alternateform = 1; c = 0; break; + case '0': flag_zeropad = 1; c = 0; break; + default: break; + } + }while( c==0 && (c=(*++zFormat))!=0 ); + /* Get the field width */ + width = 0; + if( c=='*' ){ + width = va_arg(ap,int); + if( width<0 ){ + flag_leftjustify = 1; + width = -width; + } + c = *++zFormat; + }else{ + while( c>='0' && c<='9' ){ + width = width*10 + c - '0'; + c = *++zFormat; + } + } + if( width > SXFMT_BUFSIZ-10 ){ + width = SXFMT_BUFSIZ-10; + } + /* Get the precision */ + precision = -1; + if( c=='.' ){ + precision = 0; + c = *++zFormat; + if( c=='*' ){ + precision = va_arg(ap,int); + if( precision<0 ) precision = -precision; + c = *++zFormat; + }else{ + while( c>='0' && c<='9' ){ + precision = precision*10 + c - '0'; + c = *++zFormat; + } + } + } + /* Get the conversion type modifier */ + flag_long = 0; + if( c=='l' || c == 'q' /* BSD quad (expect a 64-bit integer) */ ){ + flag_long = (c == 'q') ? 2 : 1; + c = *++zFormat; + if( c == 'l' ){ + /* Standard printf emulation 'lld' (expect a 64bit integer) */ + flag_long = 2; + } + } + /* Fetch the info entry for the field */ + infop = 0; + xtype = SXFMT_ERROR; + for(idx=0; idx< (int)SX_ARRAYSIZE(aFmt); idx++){ + if( c==aFmt[idx].fmttype ){ + infop = &aFmt[idx]; + xtype = infop->type; + break; + } + } + zExtra = 0; + + /* + ** At this point, variables are initialized as follows: + ** + ** flag_alternateform TRUE if a '#' is present. + ** flag_plussign TRUE if a '+' is present. + ** flag_leftjustify TRUE if a '-' is present or if the + ** field width was negative. + ** flag_zeropad TRUE if the width began with 0. + ** flag_long TRUE if the letter 'l' (ell) or 'q'(BSD quad) prefixed + ** the conversion character. + ** flag_blanksign TRUE if a ' ' is present. + ** width The specified field width.This is + ** always non-negative.Zero is the default. + ** precision The specified precision.The default + ** is -1. + ** xtype The class of the conversion. + ** infop Pointer to the appropriate info struct. + */ + switch( xtype ){ + case SXFMT_RADIX: + if( flag_long > 0 ){ + if( flag_long > 1 ){ + /* BSD quad: expect a 64-bit integer */ + longvalue = va_arg(ap,sxi64); + }else{ + longvalue = va_arg(ap,sxlong); + } + }else{ + if( infop->flags & SXFLAG_SIGNED ){ + longvalue = va_arg(ap,sxi32); + }else{ + longvalue = va_arg(ap,sxu32); + } + } + /* Limit the precision to prevent overflowing buf[] during conversion */ + if( precision>SXFMT_BUFSIZ-40 ) precision = SXFMT_BUFSIZ-40; +#if 1 + /* For the format %#x, the value zero is printed "0" not "0x0". + ** I think this is stupid.*/ + if( longvalue==0 ) flag_alternateform = 0; +#else + /* More sensible: turn off the prefix for octal (to prevent "00"), + ** but leave the prefix for hex.*/ + if( longvalue==0 && infop->base==8 ) flag_alternateform = 0; +#endif + if( infop->flags & SXFLAG_SIGNED ){ + if( longvalue<0 ){ + longvalue = -longvalue; + /* Ticket 1433-003 */ + if( longvalue < 0 ){ + /* Overflow */ + longvalue= 0x7FFFFFFFFFFFFFFF; + } + prefix = '-'; + }else if( flag_plussign ) prefix = '+'; + else if( flag_blanksign ) prefix = ' '; + else prefix = 0; + }else{ + if( longvalue<0 ){ + longvalue = -longvalue; + /* Ticket 1433-003 */ + if( longvalue < 0 ){ + /* Overflow */ + longvalue= 0x7FFFFFFFFFFFFFFF; + } + } + prefix = 0; + } + if( flag_zeropad && precisioncharset; + base = infop->base; + do{ /* Convert to ascii */ + *(--bufpt) = cset[longvalue%base]; + longvalue = longvalue/base; + }while( longvalue>0 ); + } + length = &buf[SXFMT_BUFSIZ-1]-bufpt; + for(idx=precision-length; idx>0; idx--){ + *(--bufpt) = '0'; /* Zero pad */ + } + if( prefix ) *(--bufpt) = prefix; /* Add sign */ + if( flag_alternateform && infop->prefix ){ /* Add "0" or "0x" */ + char *pre, x; + pre = infop->prefix; + if( *bufpt!=pre[0] ){ + for(pre=infop->prefix; (x=(*pre))!=0; pre++) *(--bufpt) = x; + } + } + length = &buf[SXFMT_BUFSIZ-1]-bufpt; + break; + case SXFMT_FLOAT: + case SXFMT_EXP: + case SXFMT_GENERIC: +#ifndef SX_OMIT_FLOATINGPOINT + realvalue = va_arg(ap,double); + if( precision<0 ) precision = 6; /* Set default precision */ + if( precision>SXFMT_BUFSIZ-40) precision = SXFMT_BUFSIZ-40; + if( realvalue<0.0 ){ + realvalue = -realvalue; + prefix = '-'; + }else{ + if( flag_plussign ) prefix = '+'; + else if( flag_blanksign ) prefix = ' '; + else prefix = 0; + } + if( infop->type==SXFMT_GENERIC && precision>0 ) precision--; + rounder = 0.0; +#if 0 + /* Rounding works like BSD when the constant 0.4999 is used.Wierd! */ + for(idx=precision, rounder=0.4999; idx>0; idx--, rounder*=0.1); +#else + /* It makes more sense to use 0.5 */ + for(idx=precision, rounder=0.5; idx>0; idx--, rounder*=0.1); +#endif + if( infop->type==SXFMT_FLOAT ) realvalue += rounder; + /* Normalize realvalue to within 10.0 > realvalue >= 1.0 */ + exp = 0; + if( realvalue>0.0 ){ + while( realvalue>=1e8 && exp<=350 ){ realvalue *= 1e-8; exp+=8; } + while( realvalue>=10.0 && exp<=350 ){ realvalue *= 0.1; exp++; } + while( realvalue<1e-8 && exp>=-350 ){ realvalue *= 1e8; exp-=8; } + while( realvalue<1.0 && exp>=-350 ){ realvalue *= 10.0; exp--; } + if( exp>350 || exp<-350 ){ + bufpt = "NaN"; + length = 3; + break; + } + } + bufpt = buf; + /* + ** If the field type is etGENERIC, then convert to either etEXP + ** or etFLOAT, as appropriate. + */ + flag_exp = xtype==SXFMT_EXP; + if( xtype!=SXFMT_FLOAT ){ + realvalue += rounder; + if( realvalue>=10.0 ){ realvalue *= 0.1; exp++; } + } + if( xtype==SXFMT_GENERIC ){ + flag_rtz = !flag_alternateform; + if( exp<-4 || exp>precision ){ + xtype = SXFMT_EXP; + }else{ + precision = precision - exp; + xtype = SXFMT_FLOAT; + } + }else{ + flag_rtz = 0; + } + /* + ** The "exp+precision" test causes output to be of type etEXP if + ** the precision is too large to fit in buf[]. + */ + nsd = 0; + if( xtype==SXFMT_FLOAT && exp+precision0 || flag_alternateform); + if( prefix ) *(bufpt++) = prefix; /* Sign */ + if( exp<0 ) *(bufpt++) = '0'; /* Digits before "." */ + else for(; exp>=0; exp--) *(bufpt++) = (char)getdigit(&realvalue,&nsd); + if( flag_dp ) *(bufpt++) = '.'; /* The decimal point */ + for(exp++; exp<0 && precision>0; precision--, exp++){ + *(bufpt++) = '0'; + } + while( (precision--)>0 ) *(bufpt++) = (char)getdigit(&realvalue,&nsd); + *(bufpt--) = 0; /* Null terminate */ + if( flag_rtz && flag_dp ){ /* Remove trailing zeros and "." */ + while( bufpt>=buf && *bufpt=='0' ) *(bufpt--) = 0; + if( bufpt>=buf && *bufpt=='.' ) *(bufpt--) = 0; + } + bufpt++; /* point to next free slot */ + }else{ /* etEXP or etGENERIC */ + flag_dp = (precision>0 || flag_alternateform); + if( prefix ) *(bufpt++) = prefix; /* Sign */ + *(bufpt++) = (char)getdigit(&realvalue,&nsd); /* First digit */ + if( flag_dp ) *(bufpt++) = '.'; /* Decimal point */ + while( (precision--)>0 ) *(bufpt++) = (char)getdigit(&realvalue,&nsd); + bufpt--; /* point to last digit */ + if( flag_rtz && flag_dp ){ /* Remove tail zeros */ + while( bufpt>=buf && *bufpt=='0' ) *(bufpt--) = 0; + if( bufpt>=buf && *bufpt=='.' ) *(bufpt--) = 0; + } + bufpt++; /* point to next free slot */ + if( exp || flag_exp ){ + *(bufpt++) = infop->charset[0]; + if( exp<0 ){ *(bufpt++) = '-'; exp = -exp; } /* sign of exp */ + else { *(bufpt++) = '+'; } + if( exp>=100 ){ + *(bufpt++) = (char)((exp/100)+'0'); /* 100's digit */ + exp %= 100; + } + *(bufpt++) = (char)(exp/10+'0'); /* 10's digit */ + *(bufpt++) = (char)(exp%10+'0'); /* 1's digit */ + } + } + /* The converted number is in buf[] and zero terminated.Output it. + ** Note that the number is in the usual order, not reversed as with + ** integer conversions.*/ + length = bufpt-buf; + bufpt = buf; + + /* Special case: Add leading zeros if the flag_zeropad flag is + ** set and we are not left justified */ + if( flag_zeropad && !flag_leftjustify && length < width){ + int i; + int nPad = width - length; + for(i=width; i>=nPad; i--){ + bufpt[i] = bufpt[i-nPad]; + } + i = prefix!=0; + while( nPad-- ) bufpt[i++] = '0'; + length = width; + } +#else + bufpt = " "; + length = (int)sizeof(" ") - 1; +#endif /* SX_OMIT_FLOATINGPOINT */ + break; + case SXFMT_SIZE:{ + int *pSize = va_arg(ap,int *); + *pSize = ((SyFmtConsumer *)pUserData)->nLen; + length = width = 0; + } + break; + case SXFMT_PERCENT: + buf[0] = '%'; + bufpt = buf; + length = 1; + break; + case SXFMT_CHARX: + c = va_arg(ap,int); + buf[0] = (char)c; + /* Limit the precision to prevent overflowing buf[] during conversion */ + if( precision>SXFMT_BUFSIZ-40 ) precision = SXFMT_BUFSIZ-40; + if( precision>=0 ){ + for(idx=1; idx=0 && precisionzString == 0 ){ + bufpt = " "; + length = (int)sizeof(char); + break; + } + bufpt = (char *)pStr->zString; + length = (int)pStr->nByte; + break; + } + case SXFMT_ERROR: + buf[0] = '?'; + bufpt = buf; + length = (int)sizeof(char); + if( c==0 ) zFormat--; + break; + }/* End switch over the format type */ + /* + ** The text of the conversion is pointed to by "bufpt" and is + ** "length" characters long.The field width is "width".Do + ** the output. + */ + if( !flag_leftjustify ){ + register int nspace; + nspace = width-length; + if( nspace>0 ){ + while( nspace>=etSPACESIZE ){ + rc = xConsumer(spaces,etSPACESIZE,pUserData); + if( rc != SXRET_OK ){ + return SXERR_ABORT; /* Consumer routine request an operation abort */ + } + nspace -= etSPACESIZE; + } + if( nspace>0 ){ + rc = xConsumer(spaces,(unsigned int)nspace,pUserData); + if( rc != SXRET_OK ){ + return SXERR_ABORT; /* Consumer routine request an operation abort */ + } + } + } + } + if( length>0 ){ + rc = xConsumer(bufpt,(unsigned int)length,pUserData); + if( rc != SXRET_OK ){ + return SXERR_ABORT; /* Consumer routine request an operation abort */ + } + } + if( flag_leftjustify ){ + register int nspace; + nspace = width-length; + if( nspace>0 ){ + while( nspace>=etSPACESIZE ){ + rc = xConsumer(spaces,etSPACESIZE,pUserData); + if( rc != SXRET_OK ){ + return SXERR_ABORT; /* Consumer routine request an operation abort */ + } + nspace -= etSPACESIZE; + } + if( nspace>0 ){ + rc = xConsumer(spaces,(unsigned int)nspace,pUserData); + if( rc != SXRET_OK ){ + return SXERR_ABORT; /* Consumer routine request an operation abort */ + } + } + } + } + }/* End for loop over the format string */ + return errorflag ? SXERR_FORMAT : SXRET_OK; +} +static sxi32 FormatConsumer(const void *pSrc,unsigned int nLen,void *pData) +{ + SyFmtConsumer *pConsumer = (SyFmtConsumer *)pData; + sxi32 rc = SXERR_ABORT; + switch(pConsumer->nType){ + case SXFMT_CONS_PROC: + /* User callback */ + rc = pConsumer->uConsumer.sFunc.xUserConsumer(pSrc,nLen,pConsumer->uConsumer.sFunc.pUserData); + break; + case SXFMT_CONS_BLOB: + /* Blob consumer */ + rc = SyBlobAppend(pConsumer->uConsumer.pBlob,pSrc,(sxu32)nLen); + break; + default: + /* Unknown consumer */ + break; + } + /* Update total number of bytes consumed so far */ + pConsumer->nLen += nLen; + pConsumer->rc = rc; + return rc; +} +static sxi32 FormatMount(sxi32 nType,void *pConsumer,ProcConsumer xUserCons,void *pUserData,sxu32 *pOutLen,const char *zFormat,va_list ap) +{ + SyFmtConsumer sCons; + sCons.nType = nType; + sCons.rc = SXRET_OK; + sCons.nLen = 0; + if( pOutLen ){ + *pOutLen = 0; + } + switch(nType){ + case SXFMT_CONS_PROC: +#if defined(UNTRUST) + if( xUserCons == 0 ){ + return SXERR_EMPTY; + } +#endif + sCons.uConsumer.sFunc.xUserConsumer = xUserCons; + sCons.uConsumer.sFunc.pUserData = pUserData; + break; + case SXFMT_CONS_BLOB: + sCons.uConsumer.pBlob = (SyBlob *)pConsumer; + break; + default: + return SXERR_UNKNOWN; + } + InternFormat(FormatConsumer,&sCons,zFormat,ap); + if( pOutLen ){ + *pOutLen = sCons.nLen; + } + return sCons.rc; +} +PH7_PRIVATE sxi32 SyProcFormat(ProcConsumer xConsumer,void *pData,const char *zFormat,...) +{ + va_list ap; + sxi32 rc; +#if defined(UNTRUST) + if( SX_EMPTY_STR(zFormat) ){ + return SXERR_EMPTY; + } +#endif + va_start(ap,zFormat); + rc = FormatMount(SXFMT_CONS_PROC,0,xConsumer,pData,0,zFormat,ap); + va_end(ap); + return rc; +} +PH7_PRIVATE sxu32 SyBlobFormat(SyBlob *pBlob,const char *zFormat,...) +{ + va_list ap; + sxu32 n; +#if defined(UNTRUST) + if( SX_EMPTY_STR(zFormat) ){ + return 0; + } +#endif + va_start(ap,zFormat); + FormatMount(SXFMT_CONS_BLOB,&(*pBlob),0,0,&n,zFormat,ap); + va_end(ap); + return n; +} +PH7_PRIVATE sxu32 SyBlobFormatAp(SyBlob *pBlob,const char *zFormat,va_list ap) +{ + sxu32 n = 0; /* cc warning */ +#if defined(UNTRUST) + if( SX_EMPTY_STR(zFormat) ){ + return 0; + } +#endif + FormatMount(SXFMT_CONS_BLOB,&(*pBlob),0,0,&n,zFormat,ap); + return n; +} +PH7_PRIVATE sxu32 SyBufferFormat(char *zBuf,sxu32 nLen,const char *zFormat,...) +{ + SyBlob sBlob; + va_list ap; + sxu32 n; +#if defined(UNTRUST) + if( SX_EMPTY_STR(zFormat) ){ + return 0; + } +#endif + if( SXRET_OK != SyBlobInitFromBuf(&sBlob,zBuf,nLen - 1) ){ + return 0; + } + va_start(ap,zFormat); + FormatMount(SXFMT_CONS_BLOB,&sBlob,0,0,0,zFormat,ap); + va_end(ap); + n = SyBlobLength(&sBlob); + /* Append the null terminator */ + sBlob.mByte++; + SyBlobAppend(&sBlob,"\0",sizeof(char)); + return n; +} +#ifndef PH7_DISABLE_BUILTIN_FUNC +/* +* Symisc XML Parser Engine (UTF-8) SAX(Event Driven) API +* @author Mrad Chems Eddine +* @started 08/03/2010 21:32 FreeBSD +* @finished 07/04/2010 23:24 Win32[VS8] +*/ +/* + * An XML raw text,CDATA,tag name is parsed out and stored + * in an instance of the following structure. + */ +typedef struct SyXMLRawStrNS SyXMLRawStrNS; +struct SyXMLRawStrNS +{ + /* Public field [Must match the SyXMLRawStr fields ] */ + const char *zString; /* Raw text [UTF-8 ENCODED EXCEPT CDATA] [NOT NULL TERMINATED] */ + sxu32 nByte; /* Text length */ + sxu32 nLine; /* Line number this text occurs */ + /* Private fields */ + SySet sNSset; /* Namespace entries */ +}; +/* + * Lexer token codes + * The following set of constants are the token value recognized + * by the lexer when processing XML input. + */ +#define SXML_TOK_INVALID 0xFFFF /* Invalid Token */ +#define SXML_TOK_COMMENT 0x01 /* Comment */ +#define SXML_TOK_PI 0x02 /* Processing instruction */ +#define SXML_TOK_DOCTYPE 0x04 /* Doctype directive */ +#define SXML_TOK_RAW 0x08 /* Raw text */ +#define SXML_TOK_START_TAG 0x10 /* Starting tag */ +#define SXML_TOK_CDATA 0x20 /* CDATA */ +#define SXML_TOK_END_TAG 0x40 /* Ending tag */ +#define SXML_TOK_START_END 0x80 /* Tag */ +#define SXML_TOK_SPACE 0x100 /* Spaces (including new lines) */ +#define IS_XML_DIRTY(c) \ + ( c == '<' || c == '$'|| c == '"' || c == '\''|| c == '&'|| c == '(' || c == ')' || c == '*' ||\ + c == '%' || c == '#' || c == '|' || c == '/'|| c == '~' || c == '{' || c == '}' ||\ + c == '[' || c == ']' || c == '\\'|| c == ';'||c == '^' || c == '`' ) +/* Tokenize an entire XML input */ +static sxi32 XML_Tokenize(SyStream *pStream,SyToken *pToken,void *pUserData,void *pUnused2) +{ + SyXMLParser *pParse = (SyXMLParser *)pUserData; + SyString *pStr; + sxi32 rc; + int c; + /* Jump leading white spaces */ + while( pStream->zText < pStream->zEnd && pStream->zText[0] < 0xc0 && SyisSpace(pStream->zText[0]) ){ + /* Advance the stream cursor */ + if( pStream->zText[0] == '\n' ){ + /* Increment line counter */ + pStream->nLine++; + } + pStream->zText++; + } + if( pStream->zText >= pStream->zEnd ){ + SXUNUSED(pUnused2); + /* End of input reached */ + return SXERR_EOF; + } + /* Record token starting position and line */ + pToken->nLine = pStream->nLine; + pToken->pUserData = 0; + pStr = &pToken->sData; + SyStringInitFromBuf(pStr,pStream->zText,0); + /* Extract the current token */ + c = pStream->zText[0]; + if( c == '<' ){ + pStream->zText++; + pStr->zString++; + if( pStream->zText >= pStream->zEnd ){ + if( pParse->xError ){ + rc = pParse->xError("Illegal syntax,expecting valid start name character",SXML_ERROR_SYNTAX,pToken,pParse->pUserData); + if( rc == SXERR_ABORT ){ + return SXERR_ABORT; + } + } + /* End of input reached */ + return SXERR_EOF; + } + c = pStream->zText[0]; + if( c == '?' ){ + /* Processing instruction */ + pStream->zText++; + pStr->zString++; + pToken->nType = SXML_TOK_PI; + while( XLEX_IN_LEN(pStream) >= sizeof("?>")-1 && + SyMemcmp((const void *)pStream->zText,"?>",sizeof("?>")-1) != 0 ){ + if( pStream->zText[0] == '\n' ){ + /* Increment line counter */ + pStream->nLine++; + } + pStream->zText++; + } + /* Record token length */ + pStr->nByte = (sxu32)((const char *)pStream->zText-pStr->zString); + if( XLEX_IN_LEN(pStream) < sizeof("?>")-1 ){ + if( pParse->xError ){ + rc = pParse->xError("End of input found,but processing instruction was not found",SXML_ERROR_UNCLOSED_TOKEN,pToken,pParse->pUserData); + if( rc == SXERR_ABORT ){ + return SXERR_ABORT; + } + } + return SXERR_EOF; + } + pStream->zText += sizeof("?>")-1; + }else if( c == '!' ){ + pStream->zText++; + if( XLEX_IN_LEN(pStream) >= sizeof("--")-1 && pStream->zText[0] == '-' && pStream->zText[1] == '-' ){ + /* Comment */ + pStream->zText += sizeof("--") - 1; + while( XLEX_IN_LEN(pStream) >= sizeof("-->")-1 && + SyMemcmp((const void *)pStream->zText,"-->",sizeof("-->")-1) != 0 ){ + if( pStream->zText[0] == '\n' ){ + /* Increment line counter */ + pStream->nLine++; + } + pStream->zText++; + } + pStream->zText += sizeof("-->")-1; + /* Tell the lexer to ignore this token */ + return SXERR_CONTINUE; + } + if( XLEX_IN_LEN(pStream) >= sizeof("[CDATA[") - 1 && SyMemcmp((const void *)pStream->zText,"[CDATA[",sizeof("[CDATA[")-1) == 0 ){ + /* CDATA */ + pStream->zText += sizeof("[CDATA[") - 1; + pStr->zString = (const char *)pStream->zText; + while( XLEX_IN_LEN(pStream) >= sizeof("]]>")-1 && + SyMemcmp((const void *)pStream->zText,"]]>",sizeof("]]>")-1) != 0 ){ + if( pStream->zText[0] == '\n' ){ + /* Increment line counter */ + pStream->nLine++; + } + pStream->zText++; + } + /* Record token type and length */ + pStr->nByte = (sxu32)((const char *)pStream->zText-pStr->zString); + pToken->nType = SXML_TOK_CDATA; + if( XLEX_IN_LEN(pStream) < sizeof("]]>")-1 ){ + if( pParse->xError ){ + rc = pParse->xError("End of input found,but ]]> was not found",SXML_ERROR_UNCLOSED_TOKEN,pToken,pParse->pUserData); + if( rc == SXERR_ABORT ){ + return SXERR_ABORT; + } + } + return SXERR_EOF; + } + pStream->zText += sizeof("]]>")-1; + return SXRET_OK; + } + if( XLEX_IN_LEN(pStream) >= sizeof("DOCTYPE") - 1 && SyMemcmp((const void *)pStream->zText,"DOCTYPE",sizeof("DOCTYPE")-1) == 0 ){ + SyString sDelim = { ">" , sizeof(char) }; /* Default delimiter */ + int c = 0; + /* DOCTYPE */ + pStream->zText += sizeof("DOCTYPE") - 1; + pStr->zString = (const char *)pStream->zText; + /* Check for element declaration */ + while( pStream->zText < pStream->zEnd && pStream->zText[0] != '\n' ){ + if( pStream->zText[0] >= 0xc0 || !SyisSpace(pStream->zText[0]) ){ + c = pStream->zText[0]; + if( c == '>' ){ + break; + } + } + pStream->zText++; + } + if( c == '[' ){ + /* Change the delimiter */ + SyStringInitFromBuf(&sDelim,"]>",sizeof("]>")-1); + } + if( c != '>' ){ + while( XLEX_IN_LEN(pStream) >= sDelim.nByte && + SyMemcmp((const void *)pStream->zText,sDelim.zString,sDelim.nByte) != 0 ){ + if( pStream->zText[0] == '\n' ){ + /* Increment line counter */ + pStream->nLine++; + } + pStream->zText++; + } + } + /* Record token type and length */ + pStr->nByte = (sxu32)((const char *)pStream->zText-pStr->zString); + pToken->nType = SXML_TOK_DOCTYPE; + if( XLEX_IN_LEN(pStream) < sDelim.nByte ){ + if( pParse->xError ){ + rc = pParse->xError("End of input found,but ]> or > was not found",SXML_ERROR_UNCLOSED_TOKEN,pToken,pParse->pUserData); + if( rc == SXERR_ABORT ){ + return SXERR_ABORT; + } + } + return SXERR_EOF; + } + pStream->zText += sDelim.nByte; + return SXRET_OK; + } + }else{ + int c; + c = pStream->zText[0]; + rc = SXRET_OK; + pToken->nType = SXML_TOK_START_TAG; + if( c == '/' ){ + /* End tag */ + pToken->nType = SXML_TOK_END_TAG; + pStream->zText++; + pStr->zString++; + if( pStream->zText >= pStream->zEnd ){ + if( pParse->xError ){ + rc = pParse->xError("Illegal syntax,expecting valid start name character",SXML_ERROR_SYNTAX,pToken,pParse->pUserData); + if( rc == SXERR_ABORT ){ + return SXERR_ABORT; + } + } + return SXERR_EOF; + } + c = pStream->zText[0]; + } + if( c == '>' ){ + /*<>*/ + if( pParse->xError ){ + rc = pParse->xError("Illegal syntax,expecting valid start name character",SXML_ERROR_SYNTAX,pToken,pParse->pUserData); + if( rc == SXERR_ABORT ){ + return SXERR_ABORT; + } + } + /* Ignore the token */ + return SXERR_CONTINUE; + } + if( c < 0xc0 && (SyisSpace(c) || SyisDigit(c) || c == '.' || c == '-' ||IS_XML_DIRTY(c) ) ){ + if( pParse->xError ){ + rc = pParse->xError("Illegal syntax,expecting valid start name character",SXML_ERROR_SYNTAX,pToken,pParse->pUserData); + if( rc == SXERR_ABORT ){ + return SXERR_ABORT; + } + } + rc = SXERR_INVALID; + } + pStream->zText++; + /* Delimit the tag */ + while( pStream->zText < pStream->zEnd && pStream->zText[0] != '>' ){ + c = pStream->zText[0]; + if( c >= 0xc0 ){ + /* UTF-8 stream */ + pStream->zText++; + SX_JMP_UTF8(pStream->zText,pStream->zEnd); + }else{ + if( c == '/' && &pStream->zText[1] < pStream->zEnd && pStream->zText[1] == '>' ){ + pStream->zText++; + if( pToken->nType != SXML_TOK_START_TAG ){ + if( pParse->xError ){ + rc = pParse->xError("Unexpected closing tag,expecting '>'", + SXML_ERROR_SYNTAX,pToken,pParse->pUserData); + if( rc == SXERR_ABORT ){ + return SXERR_ABORT; + } + } + /* Ignore the token */ + rc = SXERR_INVALID; + }else{ + pToken->nType = SXML_TOK_START_END; + } + break; + } + if( pStream->zText[0] == '\n' ){ + /* Increment line counter */ + pStream->nLine++; + } + /* Advance the stream cursor */ + pStream->zText++; + } + } + if( rc != SXRET_OK ){ + /* Tell the lexer to ignore this token */ + return SXERR_CONTINUE; + } + /* Record token length */ + pStr->nByte = (sxu32)((const char *)pStream->zText-pStr->zString); + if( pToken->nType == SXML_TOK_START_END && pStr->nByte > 0){ + pStr->nByte -= sizeof(char); + } + if ( pStream->zText < pStream->zEnd ){ + pStream->zText++; + }else{ + if( pParse->xError ){ + rc = pParse->xError("End of input found,but closing tag '>' was not found",SXML_ERROR_UNCLOSED_TOKEN,pToken,pParse->pUserData); + if( rc == SXERR_ABORT ){ + return SXERR_ABORT; + } + } + } + } + }else{ + /* Raw input */ + while( pStream->zText < pStream->zEnd ){ + c = pStream->zText[0]; + if( c < 0xc0 ){ + if( c == '<' ){ + break; + }else if( c == '\n' ){ + /* Increment line counter */ + pStream->nLine++; + } + /* Advance the stream cursor */ + pStream->zText++; + }else{ + /* UTF-8 stream */ + pStream->zText++; + SX_JMP_UTF8(pStream->zText,pStream->zEnd); + } + } + /* Record token type,length */ + pToken->nType = SXML_TOK_RAW; + pStr->nByte = (sxu32)((const char *)pStream->zText-pStr->zString); + } + /* Return to the lexer */ + return SXRET_OK; +} +static int XMLCheckDuplicateAttr(SyXMLRawStr *aSet,sxu32 nEntry,SyXMLRawStr *pEntry) +{ + sxu32 n; + for( n = 0 ; n < nEntry ; n += 2 ){ + SyXMLRawStr *pAttr = &aSet[n]; + if( pAttr->nByte == pEntry->nByte && SyMemcmp(pAttr->zString,pEntry->zString,pEntry->nByte) == 0 ){ + /* Attribute found */ + return 1; + } + } + /* No duplicates */ + return 0; +} +static sxi32 XMLProcessNamesSpace(SyXMLParser *pParse,SyXMLRawStrNS *pTag,SyToken *pToken,SySet *pAttr) +{ + SyXMLRawStr *pPrefix,*pUri; /* Namespace prefix/URI */ + SyHashEntry *pEntry; + SyXMLRawStr *pDup; + sxi32 rc; + /* Extract the URI first */ + pUri = (SyXMLRawStr *)SySetPeek(pAttr); + /* Extract the prefix */ + pPrefix = (SyXMLRawStr *)SySetAt(pAttr,SySetUsed(pAttr) - 2); + /* Prefix name */ + if( pPrefix->nByte == sizeof("xmlns")-1 ){ + /* Default namespace */ + pPrefix->nByte = 0; + pPrefix->zString = ""; /* Empty string */ + }else{ + pPrefix->nByte -= sizeof("xmlns")-1; + pPrefix->zString += sizeof("xmlns")-1; + if( pPrefix->zString[0] != ':' ){ + return SXRET_OK; + } + pPrefix->nByte--; + pPrefix->zString++; + if( pPrefix->nByte < 1 ){ + if( pParse->xError ){ + rc = pParse->xError("Invalid namespace name",SXML_ERROR_SYNTAX,pToken,pParse->pUserData); + if( rc == SXERR_ABORT ){ + return SXERR_ABORT; + } + } + /* POP the last insertred two entries */ + (void)SySetPop(pAttr); + (void)SySetPop(pAttr); + return SXERR_SYNTAX; + } + } + /* Invoke the namespace callback if available */ + if( pParse->xNameSpace ){ + rc = pParse->xNameSpace(pPrefix,pUri,pParse->pUserData); + if( rc == SXERR_ABORT ){ + /* User callback request an operation abort */ + return SXERR_ABORT; + } + } + /* Duplicate structure */ + pDup = (SyXMLRawStr *)SyMemBackendAlloc(pParse->pAllocator,sizeof(SyXMLRawStr)); + if( pDup == 0 ){ + if( pParse->xError ){ + pParse->xError("Out of memory",SXML_ERROR_NO_MEMORY,pToken,pParse->pUserData); + } + /* Abort processing immediately */ + return SXERR_ABORT; + } + *pDup = *pUri; /* Structure assignement */ + /* Save the namespace */ + if( pPrefix->nByte == 0 ){ + pPrefix->zString = "Default"; + pPrefix->nByte = sizeof("Default")-1; + } + SyHashInsert(&pParse->hns,(const void *)pPrefix->zString,pPrefix->nByte,pDup); + /* Peek the last inserted entry */ + pEntry = SyHashLastEntry(&pParse->hns); + /* Store in the corresponding tag container*/ + SySetPut(&pTag->sNSset,(const void *)&pEntry); + /* POP the last insertred two entries */ + (void)SySetPop(pAttr); + (void)SySetPop(pAttr); + return SXRET_OK; +} +static sxi32 XMLProcessStartTag(SyXMLParser *pParse,SyToken *pToken,SyXMLRawStrNS *pTag,SySet *pAttrSet,SySet *pTagStack) +{ + SyString *pIn = &pToken->sData; + const char *zIn,*zCur,*zEnd; + SyXMLRawStr sEntry; + sxi32 rc; + int c; + /* Reset the working set */ + SySetReset(pAttrSet); + /* Delimit the raw tag */ + zIn = pIn->zString; + zEnd = &zIn[pIn->nByte]; + while( zIn < zEnd && (unsigned char)zIn[0] < 0xc0 && SyisSpace(zIn[0]) ){ + zIn++; + } + /* Isolate tag name */ + sEntry.nLine = pTag->nLine = pToken->nLine; + zCur = zIn; + while( zIn < zEnd ){ + if( (unsigned char)zIn[0] >= 0xc0 ){ + /* UTF-8 stream */ + zIn++; + SX_JMP_UTF8(zIn,zEnd); + }else if( SyisSpace(zIn[0])){ + break; + }else{ + if( IS_XML_DIRTY(zIn[0]) ){ + if( pParse->xError ){ + rc = pParse->xError("Illegal character in XML name",SXML_ERROR_SYNTAX,pToken,pParse->pUserData); + if( rc == SXERR_ABORT ){ + return SXERR_ABORT; + } + } + } + zIn++; + } + } + if( zCur >= zIn ){ + if( pParse->xError ){ + rc = pParse->xError("Invalid XML name",SXML_ERROR_SYNTAX,pToken,pParse->pUserData); + if( rc == SXERR_ABORT ){ + return SXERR_ABORT; + } + } + return SXERR_SYNTAX; + } + pTag->zString = zCur; + pTag->nByte = (sxu32)(zIn-zCur); + /* Process tag attribute */ + for(;;){ + int is_ns = 0; + while( zIn < zEnd && (unsigned char)zIn[0] < 0xc0 && SyisSpace(zIn[0]) ){ + zIn++; + } + if( zIn >= zEnd ){ + break; + } + zCur = zIn; + while( zIn < zEnd && zIn[0] != '=' ){ + if( (unsigned char)zIn[0] >= 0xc0 ){ + /* UTF-8 stream */ + zIn++; + SX_JMP_UTF8(zIn,zEnd); + }else if( SyisSpace(zIn[0]) ){ + break; + }else{ + zIn++; + } + } + if( zCur >= zIn ){ + if( pParse->xError ){ + rc = pParse->xError("Missing attribute name",SXML_ERROR_SYNTAX,pToken,pParse->pUserData); + if( rc == SXERR_ABORT ){ + return SXERR_ABORT; + } + } + return SXERR_SYNTAX; + } + /* Store attribute name */ + sEntry.zString = zCur; + sEntry.nByte = (sxu32)(zIn-zCur); + if( (pParse->nFlags & SXML_ENABLE_NAMESPACE) && sEntry.nByte >= sizeof("xmlns") - 1 && + SyMemcmp(sEntry.zString,"xmlns",sizeof("xmlns") - 1) == 0 ){ + is_ns = 1; + } + while( zIn < zEnd && (unsigned char)zIn[0] < 0xc0 && SyisSpace(zIn[0]) ){ + zIn++; + } + if( zIn >= zEnd || zIn[0] != '=' ){ + if( pParse->xError ){ + rc = pParse->xError("Missing attribute value",SXML_ERROR_SYNTAX,pToken,pParse->pUserData); + if( rc == SXERR_ABORT ){ + return SXERR_ABORT; + } + } + return SXERR_SYNTAX; + } + while( sEntry.nByte > 0 && (unsigned char)zCur[sEntry.nByte - 1] < 0xc0 + && SyisSpace(zCur[sEntry.nByte - 1])){ + sEntry.nByte--; + } + /* Check for duplicates first */ + if( XMLCheckDuplicateAttr((SyXMLRawStr *)SySetBasePtr(pAttrSet),SySetUsed(pAttrSet),&sEntry) ){ + if( pParse->xError ){ + rc = pParse->xError("Duplicate attribute",SXML_ERROR_DUPLICATE_ATTRIBUTE,pToken,pParse->pUserData); + if( rc == SXERR_ABORT ){ + return SXERR_ABORT; + } + } + return SXERR_SYNTAX; + } + if( SXRET_OK != SySetPut(pAttrSet,(const void *)&sEntry) ){ + return SXERR_ABORT; + } + /* Extract attribute value */ + zIn++; /* Jump the trailing '=' */ + while( zIn < zEnd && (unsigned char)zIn[0] < 0xc0 && SyisSpace(zIn[0]) ){ + zIn++; + } + if( zIn >= zEnd ){ + if( pParse->xError ){ + rc = pParse->xError("Missing attribute value",SXML_ERROR_SYNTAX,pToken,pParse->pUserData); + if( rc == SXERR_ABORT ){ + return SXERR_ABORT; + } + } + (void)SySetPop(pAttrSet); + return SXERR_SYNTAX; + } + if( zIn[0] != '\'' && zIn[0] != '"' ){ + if( pParse->xError ){ + rc = pParse->xError("Missing quotes on attribute value",SXML_ERROR_SYNTAX,pToken,pParse->pUserData); + if( rc == SXERR_ABORT ){ + return SXERR_ABORT; + } + } + (void)SySetPop(pAttrSet); + return SXERR_SYNTAX; + } + c = zIn[0]; + zIn++; + zCur = zIn; + while( zIn < zEnd && zIn[0] != c ){ + zIn++; + } + if( zIn >= zEnd ){ + if( pParse->xError ){ + rc = pParse->xError("Missing quotes on attribute value",SXML_ERROR_SYNTAX,pToken,pParse->pUserData); + if( rc == SXERR_ABORT ){ + return SXERR_ABORT; + } + } + (void)SySetPop(pAttrSet); + return SXERR_SYNTAX; + } + /* Store attribute value */ + sEntry.zString = zCur; + sEntry.nByte = (sxu32)(zIn-zCur); + if( SXRET_OK != SySetPut(pAttrSet,(const void *)&sEntry) ){ + return SXERR_ABORT; + } + zIn++; + if( is_ns ){ + /* Process namespace declaration */ + XMLProcessNamesSpace(pParse,pTag,pToken,pAttrSet); + } + } + /* Store in the tag stack */ + if( pToken->nType == SXML_TOK_START_TAG ){ + rc = SySetPut(pTagStack,(const void *)pTag); + } + return SXRET_OK; +} +static void XMLExtactPI(SyToken *pToken,SyXMLRawStr *pTarget,SyXMLRawStr *pData,int *pXML) +{ + SyString *pIn = &pToken->sData; + const char *zIn,*zCur,*zEnd; + + pTarget->nLine = pData->nLine = pToken->nLine; + /* Nullify the entries first */ + pTarget->zString = pData->zString = 0; + /* Ignore leading and traing white spaces */ + SyStringFullTrim(pIn); + /* Delimit the raw PI */ + zIn = pIn->zString; + zEnd = &zIn[pIn->nByte]; + if( pXML ){ + *pXML = 0; + } + /* Extract the target */ + zCur = zIn; + while( zIn < zEnd ){ + if( (unsigned char)zIn[0] >= 0xc0 ){ + /* UTF-8 stream */ + zIn++; + SX_JMP_UTF8(zIn,zEnd); + }else if( SyisSpace(zIn[0])){ + break; + }else{ + zIn++; + } + } + if( zIn > zCur ){ + pTarget->zString = zCur; + pTarget->nByte = (sxu32)(zIn-zCur); + if( pXML && pTarget->nByte == sizeof("xml")-1 && SyStrnicmp(pTarget->zString,"xml",sizeof("xml")-1) == 0 ){ + *pXML = 1; + } + } + /* Extract the PI data */ + while( zIn < zEnd && (unsigned char)zIn[0] < 0xc0 && SyisSpace(zIn[0]) ){ + zIn++; + } + if( zIn < zEnd ){ + pData->zString = zIn; + pData->nByte = (sxu32)(zEnd-zIn); + } +} +static sxi32 XMLExtractEndTag(SyXMLParser *pParse,SyToken *pToken,SyXMLRawStrNS *pOut) +{ + SyString *pIn = &pToken->sData; + const char *zEnd = &pIn->zString[pIn->nByte]; + const char *zIn = pIn->zString; + /* Ignore leading white spaces */ + while( zIn < zEnd && (unsigned char)zIn[0] < 0xc0 && SyisSpace(zIn[0]) ){ + zIn++; + } + pOut->nLine = pToken->nLine; + pOut->zString = zIn; + pOut->nByte = (sxu32)(zEnd-zIn); + /* Ignore trailing white spaces */ + while( pOut->nByte > 0 && (unsigned char)pOut->zString[pOut->nByte - 1] < 0xc0 + && SyisSpace(pOut->zString[pOut->nByte - 1]) ){ + pOut->nByte--; + } + if( pOut->nByte < 1 ){ + if( pParse->xError ){ + sxi32 rc; + rc = pParse->xError("Invalid end tag name",SXML_ERROR_INVALID_TOKEN,pToken,pParse->pUserData); + if( rc == SXERR_ABORT ){ + return SXERR_ABORT; + } + } + return SXERR_SYNTAX; + } + return SXRET_OK; +} +static void TokenToXMLString(SyToken *pTok,SyXMLRawStrNS *pOut) +{ + /* Remove leading and trailing white spaces first */ + SyStringFullTrim(&pTok->sData); + pOut->zString = SyStringData(&pTok->sData); + pOut->nByte = SyStringLength(&pTok->sData); +} +static sxi32 XMLExtractNS(SyXMLParser *pParse,SyToken *pToken,SyXMLRawStrNS *pTag,SyXMLRawStr *pnsUri) +{ + SyXMLRawStr *pUri,sPrefix; + SyHashEntry *pEntry; + sxu32 nOfft; + sxi32 rc; + /* Extract a prefix if available */ + rc = SyByteFind(pTag->zString,pTag->nByte,':',&nOfft); + if( rc != SXRET_OK ){ + /* Check if there is a default namespace */ + pEntry = SyHashGet(&pParse->hns,"Default",sizeof("Default")-1); + if( pEntry ){ + /* Extract the ns URI */ + pUri = (SyXMLRawStr *)pEntry->pUserData; + /* Save the ns URI */ + pnsUri->zString = pUri->zString; + pnsUri->nByte = pUri->nByte; + } + return SXRET_OK; + } + if( nOfft < 1 ){ + if( pParse->xError ){ + rc = pParse->xError("Empty prefix is not allowed according to XML namespace specification", + SXML_ERROR_SYNTAX,pToken,pParse->pUserData); + if( rc == SXERR_ABORT ){ + return SXERR_ABORT; + } + } + return SXERR_SYNTAX; + } + sPrefix.zString = pTag->zString; + sPrefix.nByte = nOfft; + sPrefix.nLine = pTag->nLine; + pTag->zString += nOfft + 1; + pTag->nByte -= nOfft; + if( pTag->nByte < 1 ){ + if( pParse->xError ){ + rc = pParse->xError("Missing tag name",SXML_ERROR_SYNTAX,pToken,pParse->pUserData); + if( rc == SXERR_ABORT ){ + return SXERR_ABORT; + } + } + return SXERR_SYNTAX; + } + /* Check if the prefix is already registered */ + pEntry = SyHashGet(&pParse->hns,sPrefix.zString,sPrefix.nByte); + if( pEntry == 0 ){ + if( pParse->xError ){ + rc = pParse->xError("Namespace prefix is not defined",SXML_ERROR_SYNTAX, + pToken,pParse->pUserData); + if( rc == SXERR_ABORT ){ + return SXERR_ABORT; + } + } + return SXERR_SYNTAX; + } + /* Extract the ns URI */ + pUri = (SyXMLRawStr *)pEntry->pUserData; + /* Save the ns URI */ + pnsUri->zString = pUri->zString; + pnsUri->nByte = pUri->nByte; + /* All done */ + return SXRET_OK; +} +static sxi32 XMLnsUnlink(SyXMLParser *pParse,SyXMLRawStrNS *pLast,SyToken *pToken) +{ + SyHashEntry **apEntry,*pEntry; + void *pUserData; + sxu32 n; + /* Release namespace entries */ + apEntry = (SyHashEntry **)SySetBasePtr(&pLast->sNSset); + for( n = 0 ; n < SySetUsed(&pLast->sNSset) ; ++n ){ + pEntry = apEntry[n]; + /* Invoke the end namespace declaration callback */ + if( pParse->xNameSpaceEnd && (pParse->nFlags & SXML_ENABLE_NAMESPACE) && pToken ){ + SyXMLRawStr sPrefix; + sxi32 rc; + sPrefix.zString = (const char *)pEntry->pKey; + sPrefix.nByte = pEntry->nKeyLen; + sPrefix.nLine = pToken->nLine; + rc = pParse->xNameSpaceEnd(&sPrefix,pParse->pUserData); + if( rc == SXERR_ABORT ){ + return SXERR_ABORT; + } + } + pUserData = pEntry->pUserData; + /* Remove from the namespace hashtable */ + SyHashDeleteEntry2(pEntry); + SyMemBackendFree(pParse->pAllocator,pUserData); + } + SySetRelease(&pLast->sNSset); + return SXRET_OK; +} +/* Process XML tokens */ +static sxi32 ProcessXML(SyXMLParser *pParse,SySet *pTagStack,SySet *pWorker) +{ + SySet *pTokenSet = &pParse->sToken; + SyXMLRawStrNS sEntry; + SyXMLRawStr sNs; + SyToken *pToken; + int bGotTag; + sxi32 rc; + /* Initialize fields */ + bGotTag = 0; + /* Start processing */ + if( pParse->xStartDoc && (SXERR_ABORT == pParse->xStartDoc(pParse->pUserData)) ){ + /* User callback request an operation abort */ + return SXERR_ABORT; + } + /* Reset the loop cursor */ + SySetResetCursor(pTokenSet); + /* Extract the current token */ + while( SXRET_OK == (SySetGetNextEntry(&(*pTokenSet),(void **)&pToken)) ){ + SyZero(&sEntry,sizeof(SyXMLRawStrNS)); + SyZero(&sNs,sizeof(SyXMLRawStr)); + SySetInit(&sEntry.sNSset,pParse->pAllocator,sizeof(SyHashEntry *)); + sEntry.nLine = sNs.nLine = pToken->nLine; + switch(pToken->nType){ + case SXML_TOK_DOCTYPE: + if( SySetUsed(pTagStack) > 1 || bGotTag ){ + if( pParse->xError ){ + rc = pParse->xError("DOCTYPE must be declared first",SXML_ERROR_MISPLACED_XML_PI,pToken,pParse->pUserData); + if( rc == SXERR_ABORT ){ + return SXERR_ABORT; + } + } + break; + } + /* Invoke the supplied callback if any */ + if( pParse->xDoctype ){ + TokenToXMLString(pToken,&sEntry); + rc = pParse->xDoctype((SyXMLRawStr *)&sEntry,pParse->pUserData); + if( rc == SXERR_ABORT ){ + return SXERR_ABORT; + } + } + break; + case SXML_TOK_CDATA: + if( SySetUsed(pTagStack) < 1 ){ + if( pParse->xError ){ + rc = pParse->xError("CDATA without matching tag",SXML_ERROR_TAG_MISMATCH,pToken,pParse->pUserData); + if( rc == SXERR_ABORT ){ + return SXERR_ABORT; + } + } + } + /* Invoke the supplied callback if any */ + if( pParse->xRaw ){ + TokenToXMLString(pToken,&sEntry); + rc = pParse->xRaw((SyXMLRawStr *)&sEntry,pParse->pUserData); + if( rc == SXERR_ABORT ){ + return SXERR_ABORT; + } + } + break; + case SXML_TOK_PI:{ + SyXMLRawStr sTarget,sData; + int isXML = 0; + /* Extract the target and data */ + XMLExtactPI(pToken,&sTarget,&sData,&isXML); + if( isXML && SySetCursor(pTokenSet) - 1 > 0 ){ + if( pParse->xError ){ + rc = pParse->xError("Unexpected XML declaration. The XML declaration must be the first node in the document", + SXML_ERROR_MISPLACED_XML_PI,pToken,pParse->pUserData); + if( rc == SXERR_ABORT ){ + return SXERR_ABORT; + } + } + }else if( pParse->xPi ){ + /* Invoke the supplied callback*/ + rc = pParse->xPi(&sTarget,&sData,pParse->pUserData); + if( rc == SXERR_ABORT ){ + return SXERR_ABORT; + } + } + break; + } + case SXML_TOK_RAW: + if( SySetUsed(pTagStack) < 1 ){ + if( pParse->xError ){ + rc = pParse->xError("Text (Raw data) without matching tag",SXML_ERROR_TAG_MISMATCH,pToken,pParse->pUserData); + if( rc == SXERR_ABORT ){ + return SXERR_ABORT; + } + } + break; + } + /* Invoke the supplied callback if any */ + if( pParse->xRaw ){ + TokenToXMLString(pToken,&sEntry); + rc = pParse->xRaw((SyXMLRawStr *)&sEntry,pParse->pUserData); + if( rc == SXERR_ABORT ){ + return SXERR_ABORT; + } + } + break; + case SXML_TOK_END_TAG:{ + SyXMLRawStrNS *pLast = 0; /* cc warning */ + if( SySetUsed(pTagStack) < 1 ){ + if( pParse->xError ){ + rc = pParse->xError("Unexpected closing tag",SXML_ERROR_TAG_MISMATCH,pToken,pParse->pUserData); + if( rc == SXERR_ABORT ){ + return SXERR_ABORT; + } + } + break; + } + rc = XMLExtractEndTag(pParse,pToken,&sEntry); + if( rc == SXRET_OK ){ + /* Extract the last inserted entry */ + pLast = (SyXMLRawStrNS *)SySetPeek(pTagStack); + if( pLast == 0 || pLast->nByte != sEntry.nByte || + SyMemcmp(pLast->zString,sEntry.zString,sEntry.nByte) != 0 ){ + if( pParse->xError ){ + rc = pParse->xError("Unexpected closing tag",SXML_ERROR_TAG_MISMATCH,pToken,pParse->pUserData); + if( rc == SXERR_ABORT ){ + return SXERR_ABORT; + } + } + }else{ + /* Invoke the supllied callback if any */ + if( pParse->xEndTag ){ + rc = SXRET_OK; + if( pParse->nFlags & SXML_ENABLE_NAMESPACE ){ + /* Extract namespace URI */ + rc = XMLExtractNS(pParse,pToken,&sEntry,&sNs); + if( rc == SXERR_ABORT ){ + return SXERR_ABORT; + } + } + if( rc == SXRET_OK ){ + rc = pParse->xEndTag((SyXMLRawStr *)&sEntry,&sNs,pParse->pUserData); + if( rc == SXERR_ABORT ){ + return SXERR_ABORT; + } + } + } + } + }else if( rc == SXERR_ABORT ){ + return SXERR_ABORT; + } + if( pLast ){ + rc = XMLnsUnlink(pParse,pLast,pToken); + (void)SySetPop(pTagStack); + if( rc == SXERR_ABORT ){ + return SXERR_ABORT; + } + } + break; + } + case SXML_TOK_START_TAG: + case SXML_TOK_START_END: + if( SySetUsed(pTagStack) < 1 && bGotTag ){ + if( pParse->xError ){ + rc = pParse->xError("XML document cannot contain multiple root level elements documents", + SXML_ERROR_SYNTAX,pToken,pParse->pUserData); + if( rc == SXERR_ABORT ){ + return SXERR_ABORT; + } + } + break; + } + bGotTag = 1; + /* Extract the tag and it's supplied attribute */ + rc = XMLProcessStartTag(pParse,pToken,&sEntry,pWorker,pTagStack); + if( rc == SXRET_OK ){ + if( pParse->nFlags & SXML_ENABLE_NAMESPACE ){ + /* Extract namespace URI */ + rc = XMLExtractNS(pParse,pToken,&sEntry,&sNs); + } + } + if( rc == SXRET_OK ){ + /* Invoke the supplied callback */ + if( pParse->xStartTag ){ + rc = pParse->xStartTag((SyXMLRawStr *)&sEntry,&sNs,SySetUsed(pWorker), + (SyXMLRawStr *)SySetBasePtr(pWorker),pParse->pUserData); + if( rc == SXERR_ABORT ){ + return SXERR_ABORT; + } + } + if( pToken->nType == SXML_TOK_START_END ){ + if ( pParse->xEndTag ){ + rc = pParse->xEndTag((SyXMLRawStr *)&sEntry,&sNs,pParse->pUserData); + if( rc == SXERR_ABORT ){ + return SXERR_ABORT; + } + } + rc = XMLnsUnlink(pParse,&sEntry,pToken); + if( rc == SXERR_ABORT ){ + return SXERR_ABORT; + } + } + }else if( rc == SXERR_ABORT ){ + /* Abort processing immediately */ + return SXERR_ABORT; + } + break; + default: + /* Can't happen */ + break; + } + } + if( SySetUsed(pTagStack) > 0 && pParse->xError){ + pParse->xError("Missing closing tag",SXML_ERROR_SYNTAX, + (SyToken *)SySetPeek(&pParse->sToken),pParse->pUserData); + } + if( pParse->xEndDoc ){ + pParse->xEndDoc(pParse->pUserData); + } + return SXRET_OK; +} +PH7_PRIVATE sxi32 SyXMLParserInit(SyXMLParser *pParser,SyMemBackend *pAllocator,sxi32 iFlags) +{ + /* Zero the structure first */ + SyZero(pParser,sizeof(SyXMLParser)); + /* Initilaize fields */ + SySetInit(&pParser->sToken,pAllocator,sizeof(SyToken)); + SyLexInit(&pParser->sLex,&pParser->sToken,XML_Tokenize,pParser); + SyHashInit(&pParser->hns,pAllocator,0,0); + pParser->pAllocator = pAllocator; + pParser->nFlags = iFlags; + return SXRET_OK; +} +PH7_PRIVATE sxi32 SyXMLParserSetEventHandler(SyXMLParser *pParser, + void *pUserData, + ProcXMLStartTagHandler xStartTag, + ProcXMLTextHandler xRaw, + ProcXMLSyntaxErrorHandler xErr, + ProcXMLStartDocument xStartDoc, + ProcXMLEndTagHandler xEndTag, + ProcXMLPIHandler xPi, + ProcXMLEndDocument xEndDoc, + ProcXMLDoctypeHandler xDoctype, + ProcXMLNameSpaceStart xNameSpace, + ProcXMLNameSpaceEnd xNameSpaceEnd + ){ + /* Install user callbacks */ + if( xErr ){ + pParser->xError = xErr; + } + if( xStartDoc ){ + pParser->xStartDoc = xStartDoc; + } + if( xStartTag ){ + pParser->xStartTag = xStartTag; + } + if( xRaw ){ + pParser->xRaw = xRaw; + } + if( xEndTag ){ + pParser->xEndTag = xEndTag; + } + if( xPi ){ + pParser->xPi = xPi; + } + if( xEndDoc ){ + pParser->xEndDoc = xEndDoc; + } + if( xDoctype ){ + pParser->xDoctype = xDoctype; + } + if( xNameSpace ){ + pParser->xNameSpace = xNameSpace; + } + if( xNameSpaceEnd ){ + pParser->xNameSpaceEnd = xNameSpaceEnd; + } + pParser->pUserData = pUserData; + return SXRET_OK; +} +/* Process an XML chunk */ +PH7_PRIVATE sxi32 SyXMLProcess(SyXMLParser *pParser,const char *zInput,sxu32 nByte) +{ + SySet sTagStack; + SySet sWorker; + sxi32 rc; + /* Initialize working sets */ + SySetInit(&sWorker,pParser->pAllocator,sizeof(SyXMLRawStr)); /* Tag container */ + SySetInit(&sTagStack,pParser->pAllocator,sizeof(SyXMLRawStrNS)); /* Tag stack */ + /* Tokenize the entire input */ + rc = SyLexTokenizeInput(&pParser->sLex,zInput,nByte,0,0,0); + if( rc == SXERR_ABORT ){ + /* Tokenize callback request an operation abort */ + return SXERR_ABORT; + } + if( SySetUsed(&pParser->sToken) < 1 ){ + /* Nothing to process [i.e: white spaces] */ + rc = SXRET_OK; + }else{ + /* Process XML Tokens */ + rc = ProcessXML(&(*pParser),&sTagStack,&sWorker); + if( pParser->nFlags & SXML_ENABLE_NAMESPACE ){ + if( SySetUsed(&sTagStack) > 0 ){ + SyXMLRawStrNS *pEntry; + SyHashEntry **apEntry; + sxu32 n; + SySetResetCursor(&sTagStack); + while( SySetGetNextEntry(&sTagStack,(void **)&pEntry) == SXRET_OK ){ + /* Release namespace entries */ + apEntry = (SyHashEntry **)SySetBasePtr(&pEntry->sNSset); + for( n = 0 ; n < SySetUsed(&pEntry->sNSset) ; ++n ){ + SyMemBackendFree(pParser->pAllocator,apEntry[n]->pUserData); + } + SySetRelease(&pEntry->sNSset); + } + } + } + } + /* Clean-up the mess left behind */ + SySetRelease(&sWorker); + SySetRelease(&sTagStack); + /* Processing result */ + return rc; +} +PH7_PRIVATE sxi32 SyXMLParserRelease(SyXMLParser *pParser) +{ + SyLexRelease(&pParser->sLex); + SySetRelease(&pParser->sToken); + SyHashRelease(&pParser->hns); + return SXRET_OK; +} +/* + * Zip File Format: + * + * Byte order: Little-endian + * + * [Local file header + Compressed data [+ Extended local header]?]* + * [Central directory]* + * [End of central directory record] + * + * Local file header:* + * Offset Length Contents + * 0 4 bytes Local file header signature (0x04034b50) + * 4 2 bytes Version needed to extract + * 6 2 bytes General purpose bit flag + * 8 2 bytes Compression method + * 10 2 bytes Last mod file time + * 12 2 bytes Last mod file date + * 14 4 bytes CRC-32 + * 18 4 bytes Compressed size (n) + * 22 4 bytes Uncompressed size + * 26 2 bytes Filename length (f) + * 28 2 bytes Extra field length (e) + * 30 (f)bytes Filename + * (e)bytes Extra field + * (n)bytes Compressed data + * + * Extended local header:* + * Offset Length Contents + * 0 4 bytes Extended Local file header signature (0x08074b50) + * 4 4 bytes CRC-32 + * 8 4 bytes Compressed size + * 12 4 bytes Uncompressed size + * + * Extra field:?(if any) + * Offset Length Contents + * 0 2 bytes Header ID (0x001 until 0xfb4a) see extended appnote from Info-zip + * 2 2 bytes Data size (g) + * (g) bytes (g) bytes of extra field + * + * Central directory:* + * Offset Length Contents + * 0 4 bytes Central file header signature (0x02014b50) + * 4 2 bytes Version made by + * 6 2 bytes Version needed to extract + * 8 2 bytes General purpose bit flag + * 10 2 bytes Compression method + * 12 2 bytes Last mod file time + * 14 2 bytes Last mod file date + * 16 4 bytes CRC-32 + * 20 4 bytes Compressed size + * 24 4 bytes Uncompressed size + * 28 2 bytes Filename length (f) + * 30 2 bytes Extra field length (e) + * 32 2 bytes File comment length (c) + * 34 2 bytes Disk number start + * 36 2 bytes Internal file attributes + * 38 4 bytes External file attributes + * 42 4 bytes Relative offset of local header + * 46 (f)bytes Filename + * (e)bytes Extra field + * (c)bytes File comment + * + * End of central directory record: + * Offset Length Contents + * 0 4 bytes End of central dir signature (0x06054b50) + * 4 2 bytes Number of this disk + * 6 2 bytes Number of the disk with the start of the central directory + * 8 2 bytes Total number of entries in the central dir on this disk + * 10 2 bytes Total number of entries in the central dir + * 12 4 bytes Size of the central directory + * 16 4 bytes Offset of start of central directory with respect to the starting disk number + * 20 2 bytes zipfile comment length (c) + * 22 (c)bytes zipfile comment + * + * compression method: (2 bytes) + * 0 - The file is stored (no compression) + * 1 - The file is Shrunk + * 2 - The file is Reduced with compression factor 1 + * 3 - The file is Reduced with compression factor 2 + * 4 - The file is Reduced with compression factor 3 + * 5 - The file is Reduced with compression factor 4 + * 6 - The file is Imploded + * 7 - Reserved for Tokenizing compression algorithm + * 8 - The file is Deflated + */ + +#define SXMAKE_ZIP_WORKBUF (SXU16_HIGH/2) /* 32KB Initial working buffer size */ +#define SXMAKE_ZIP_EXTRACT_VER 0x000a /* Version needed to extract */ +#define SXMAKE_ZIP_VER 0x003 /* Version made by */ + +#define SXZIP_CENTRAL_MAGIC 0x02014b50 +#define SXZIP_END_CENTRAL_MAGIC 0x06054b50 +#define SXZIP_LOCAL_MAGIC 0x04034b50 +/*#define SXZIP_CRC32_START 0xdebb20e3*/ + +#define SXZIP_LOCAL_HDRSZ 30 /* Local header size */ +#define SXZIP_LOCAL_EXT_HDRZ 16 /* Extended local header(footer) size */ +#define SXZIP_CENTRAL_HDRSZ 46 /* Central directory header size */ +#define SXZIP_END_CENTRAL_HDRSZ 22 /* End of central directory header size */ + +#define SXARCHIVE_HASH_SIZE 64 /* Starting hash table size(MUST BE POWER OF 2)*/ +static sxi32 SyLittleEndianUnpack32(sxu32 *uNB,const unsigned char *buf,sxu32 Len) +{ + if( Len < sizeof(sxu32) ){ + return SXERR_SHORT; + } + *uNB = buf[0] + (buf[1] << 8) + (buf[2] << 16) + (buf[3] << 24); + return SXRET_OK; +} +static sxi32 SyLittleEndianUnpack16(sxu16 *pOut,const unsigned char *zBuf,sxu32 nLen) +{ + if( nLen < sizeof(sxu16) ){ + return SXERR_SHORT; + } + *pOut = zBuf[0] + (zBuf[1] <<8); + + return SXRET_OK; +} +static sxi32 SyDosTimeFormat(sxu32 nDosDate,Sytm *pOut) +{ + sxu16 nDate; + sxu16 nTime; + nDate = nDosDate >> 16; + nTime = nDosDate & 0xFFFF; + pOut->tm_isdst = 0; + pOut->tm_year = 1980 + (nDate >> 9); + pOut->tm_mon = (nDate % (1<<9))>>5; + pOut->tm_mday = (nDate % (1<<9))&0x1F; + pOut->tm_hour = nTime >> 11; + pOut->tm_min = (nTime % (1<<11)) >> 5; + pOut->tm_sec = ((nTime % (1<<11))& 0x1F )<<1; + return SXRET_OK; +} +/* + * Archive hashtable manager + */ +static sxi32 ArchiveHashGetEntry(SyArchive *pArch,const char *zName,sxu32 nLen,SyArchiveEntry **ppEntry) +{ + SyArchiveEntry *pBucketEntry; + SyString sEntry; + sxu32 nHash; + + nHash = pArch->xHash(zName,nLen); + pBucketEntry = pArch->apHash[nHash & (pArch->nSize - 1)]; + + SyStringInitFromBuf(&sEntry,zName,nLen); + + for(;;){ + if( pBucketEntry == 0 ){ + break; + } + if( nHash == pBucketEntry->nHash && pArch->xCmp(&sEntry,&pBucketEntry->sFileName) == 0 ){ + if( ppEntry ){ + *ppEntry = pBucketEntry; + } + return SXRET_OK; + } + pBucketEntry = pBucketEntry->pNextHash; + } + return SXERR_NOTFOUND; +} +static void ArchiveHashBucketInstall(SyArchiveEntry **apTable,sxu32 nBucket,SyArchiveEntry *pEntry) +{ + pEntry->pNextHash = apTable[nBucket]; + if( apTable[nBucket] != 0 ){ + apTable[nBucket]->pPrevHash = pEntry; + } + apTable[nBucket] = pEntry; +} +static sxi32 ArchiveHashGrowTable(SyArchive *pArch) +{ + sxu32 nNewSize = pArch->nSize * 2; + SyArchiveEntry **apNew; + SyArchiveEntry *pEntry; + sxu32 n; + + /* Allocate a new table */ + apNew = (SyArchiveEntry **)SyMemBackendAlloc(pArch->pAllocator,nNewSize * sizeof(SyArchiveEntry *)); + if( apNew == 0 ){ + return SXRET_OK; /* Not so fatal,simply a performance hit */ + } + SyZero(apNew,nNewSize * sizeof(SyArchiveEntry *)); + /* Rehash old entries */ + for( n = 0 , pEntry = pArch->pList ; n < pArch->nLoaded ; n++ , pEntry = pEntry->pNext ){ + pEntry->pNextHash = pEntry->pPrevHash = 0; + ArchiveHashBucketInstall(apNew,pEntry->nHash & (nNewSize - 1),pEntry); + } + /* Release the old table */ + SyMemBackendFree(pArch->pAllocator,pArch->apHash); + pArch->apHash = apNew; + pArch->nSize = nNewSize; + + return SXRET_OK; +} +static sxi32 ArchiveHashInstallEntry(SyArchive *pArch,SyArchiveEntry *pEntry) +{ + if( pArch->nLoaded > pArch->nSize * 3 ){ + ArchiveHashGrowTable(&(*pArch)); + } + pEntry->nHash = pArch->xHash(SyStringData(&pEntry->sFileName),SyStringLength(&pEntry->sFileName)); + /* Install the entry in its bucket */ + ArchiveHashBucketInstall(pArch->apHash,pEntry->nHash & (pArch->nSize - 1),pEntry); + MACRO_LD_PUSH(pArch->pList,pEntry); + pArch->nLoaded++; + + return SXRET_OK; +} + /* + * Parse the End of central directory and report status + */ + static sxi32 ParseEndOfCentralDirectory(SyArchive *pArch,const unsigned char *zBuf) + { + sxu32 nMagic = 0; /* cc -O6 warning */ + sxi32 rc; + + /* Sanity check */ + rc = SyLittleEndianUnpack32(&nMagic,zBuf,sizeof(sxu32)); + if( /* rc != SXRET_OK || */nMagic != SXZIP_END_CENTRAL_MAGIC ){ + return SXERR_CORRUPT; + } + /* # of entries */ + rc = SyLittleEndianUnpack16((sxu16 *)&pArch->nEntry,&zBuf[8],sizeof(sxu16)); + if( /* rc != SXRET_OK || */ pArch->nEntry > SXI16_HIGH /* SXU16_HIGH */ ){ + return SXERR_CORRUPT; + } + /* Size of central directory */ + rc = SyLittleEndianUnpack32(&pArch->nCentralSize,&zBuf[12],sizeof(sxu32)); + if( /*rc != SXRET_OK ||*/ pArch->nCentralSize > SXI32_HIGH ){ + return SXERR_CORRUPT; + } + /* Starting offset of central directory */ + rc = SyLittleEndianUnpack32(&pArch->nCentralOfft,&zBuf[16],sizeof(sxu32)); + if( /*rc != SXRET_OK ||*/ pArch->nCentralSize > SXI32_HIGH ){ + return SXERR_CORRUPT; + } + + return SXRET_OK; + } + /* + * Fill the zip entry with the appropriate information from the central directory + */ +static sxi32 GetCentralDirectoryEntry(SyArchive *pArch,SyArchiveEntry *pEntry,const unsigned char *zCentral,sxu32 *pNextOffset) + { + SyString *pName = &pEntry->sFileName; /* File name */ + sxu16 nDosDate,nDosTime; + sxu16 nComment = 0 ; + sxu32 nMagic = 0; /* cc -O6 warning */ + sxi32 rc; + nDosDate = nDosTime = 0; /* cc -O6 warning */ + SXUNUSED(pArch); + /* Sanity check */ + rc = SyLittleEndianUnpack32(&nMagic,zCentral,sizeof(sxu32)); + if( /* rc != SXRET_OK || */ nMagic != SXZIP_CENTRAL_MAGIC ){ + rc = SXERR_CORRUPT; + /* + * Try to recover by examing the next central directory record. + * Dont worry here,there is no risk of an infinite loop since + * the buffer size is delimited. + */ + + /* pName->nByte = 0; nComment = 0; pName->nExtra = 0 */ + goto update; + } + /* + * entry name length + */ + SyLittleEndianUnpack16((sxu16 *)&pName->nByte,&zCentral[28],sizeof(sxu16)); + if( pName->nByte > SXI16_HIGH /* SXU16_HIGH */){ + rc = SXERR_BIG; + goto update; + } + /* Extra information */ + SyLittleEndianUnpack16(&pEntry->nExtra,&zCentral[30],sizeof(sxu16)); + /* Comment length */ + SyLittleEndianUnpack16(&nComment,&zCentral[32],sizeof(sxu16)); + /* Compression method 0 == stored / 8 == deflated */ + rc = SyLittleEndianUnpack16(&pEntry->nComprMeth,&zCentral[10],sizeof(sxu16)); + /* DOS Timestamp */ + SyLittleEndianUnpack16(&nDosTime,&zCentral[12],sizeof(sxu16)); + SyLittleEndianUnpack16(&nDosDate,&zCentral[14],sizeof(sxu16)); + SyDosTimeFormat((nDosDate << 16 | nDosTime),&pEntry->sFmt); + /* Little hack to fix month index */ + pEntry->sFmt.tm_mon--; + /* CRC32 */ + rc = SyLittleEndianUnpack32(&pEntry->nCrc,&zCentral[16],sizeof(sxu32)); + /* Content size before compression */ + rc = SyLittleEndianUnpack32(&pEntry->nByte,&zCentral[24],sizeof(sxu32)); + if( pEntry->nByte > SXI32_HIGH ){ + rc = SXERR_BIG; + goto update; + } + /* + * Content size after compression. + * Note that if the file is stored pEntry->nByte should be equal to pEntry->nByteCompr + */ + rc = SyLittleEndianUnpack32(&pEntry->nByteCompr,&zCentral[20],sizeof(sxu32)); + if( pEntry->nByteCompr > SXI32_HIGH ){ + rc = SXERR_BIG; + goto update; + } + /* Finally grab the contents offset */ + SyLittleEndianUnpack32(&pEntry->nOfft,&zCentral[42],sizeof(sxu32)); + if( pEntry->nOfft > SXI32_HIGH ){ + rc = SXERR_BIG; + goto update; + } + rc = SXRET_OK; +update: + /* Update the offset to point to the next central directory record */ + *pNextOffset = SXZIP_CENTRAL_HDRSZ + pName->nByte + pEntry->nExtra + nComment; + return rc; /* Report failure or success */ +} +static sxi32 ZipFixOffset(SyArchiveEntry *pEntry,void *pSrc) +{ + sxu16 nExtra,nNameLen; + unsigned char *zHdr; + nExtra = nNameLen = 0; + zHdr = (unsigned char *)pSrc; + zHdr = &zHdr[pEntry->nOfft]; + if( SyMemcmp(zHdr,"PK\003\004",sizeof(sxu32)) != 0 ){ + return SXERR_CORRUPT; + } + SyLittleEndianUnpack16(&nNameLen,&zHdr[26],sizeof(sxu16)); + SyLittleEndianUnpack16(&nExtra,&zHdr[28],sizeof(sxu16)); + /* Fix contents offset */ + pEntry->nOfft += SXZIP_LOCAL_HDRSZ + nExtra + nNameLen; + return SXRET_OK; +} +/* + * Extract all valid entries from the central directory + */ +static sxi32 ZipExtract(SyArchive *pArch,const unsigned char *zCentral,sxu32 nLen,void *pSrc) +{ + SyArchiveEntry *pEntry,*pDup; + const unsigned char *zEnd ; /* End of central directory */ + sxu32 nIncr,nOfft; /* Central Offset */ + SyString *pName; /* Entry name */ + char *zName; + sxi32 rc; + + nOfft = nIncr = 0; + zEnd = &zCentral[nLen]; + + for(;;){ + if( &zCentral[nOfft] >= zEnd ){ + break; + } + /* Add a new entry */ + pEntry = (SyArchiveEntry *)SyMemBackendPoolAlloc(pArch->pAllocator,sizeof(SyArchiveEntry)); + if( pEntry == 0 ){ + break; + } + SyZero(pEntry,sizeof(SyArchiveEntry)); + pEntry->nMagic = SXARCH_MAGIC; + nIncr = 0; + rc = GetCentralDirectoryEntry(&(*pArch),pEntry,&zCentral[nOfft],&nIncr); + if( rc == SXRET_OK ){ + /* Fix the starting record offset so we can access entry contents correctly */ + rc = ZipFixOffset(pEntry,pSrc); + } + if(rc != SXRET_OK ){ + sxu32 nJmp = 0; + SyMemBackendPoolFree(pArch->pAllocator,pEntry); + /* Try to recover by brute-forcing for a valid central directory record */ + if( SXRET_OK == SyBlobSearch((const void *)&zCentral[nOfft + nIncr],(sxu32)(zEnd - &zCentral[nOfft + nIncr]), + (const void *)"PK\001\002",sizeof(sxu32),&nJmp)){ + nOfft += nIncr + nJmp; /* Check next entry */ + continue; + } + break; /* Giving up,archive is hopelessly corrupted */ + } + pName = &pEntry->sFileName; + pName->zString = (const char *)&zCentral[nOfft + SXZIP_CENTRAL_HDRSZ]; + if( pName->nByte <= 0 || ( pEntry->nByte <= 0 && pName->zString[pName->nByte - 1] != '/') ){ + /* Ignore zero length records (except folders) and records without names */ + SyMemBackendPoolFree(pArch->pAllocator,pEntry); + nOfft += nIncr; /* Check next entry */ + continue; + } + zName = SyMemBackendStrDup(pArch->pAllocator,pName->zString,pName->nByte); + if( zName == 0 ){ + SyMemBackendPoolFree(pArch->pAllocator,pEntry); + nOfft += nIncr; /* Check next entry */ + continue; + } + pName->zString = (const char *)zName; + /* Check for duplicates */ + rc = ArchiveHashGetEntry(&(*pArch),pName->zString,pName->nByte,&pDup); + if( rc == SXRET_OK ){ + /* Another entry with the same name exists ; link them together */ + pEntry->pNextName = pDup->pNextName; + pDup->pNextName = pEntry; + pDup->nDup++; + }else{ + /* Insert in hashtable */ + ArchiveHashInstallEntry(pArch,pEntry); + } + nOfft += nIncr; /* Check next record */ + } + pArch->pCursor = pArch->pList; + + return pArch->nLoaded > 0 ? SXRET_OK : SXERR_EMPTY; +} +PH7_PRIVATE sxi32 SyZipExtractFromBuf(SyArchive *pArch,const char *zBuf,sxu32 nLen) + { + const unsigned char *zCentral,*zEnd; + sxi32 rc; +#if defined(UNTRUST) + if( SXARCH_INVALID(pArch) || zBuf == 0 ){ + return SXERR_INVALID; + } +#endif + /* The miminal size of a zip archive: + * LOCAL_HDR_SZ + CENTRAL_HDR_SZ + END_OF_CENTRAL_HDR_SZ + * 30 46 22 + */ + if( nLen < SXZIP_LOCAL_HDRSZ + SXZIP_CENTRAL_HDRSZ + SXZIP_END_CENTRAL_HDRSZ ){ + return SXERR_CORRUPT; /* Don't bother processing return immediately */ + } + + zEnd = (unsigned char *)&zBuf[nLen - SXZIP_END_CENTRAL_HDRSZ]; + /* Find the end of central directory */ + while( ((sxu32)((unsigned char *)&zBuf[nLen] - zEnd) < (SXZIP_END_CENTRAL_HDRSZ + SXI16_HIGH)) && + zEnd > (unsigned char *)zBuf && SyMemcmp(zEnd,"PK\005\006",sizeof(sxu32)) != 0 ){ + zEnd--; + } + /* Parse the end of central directory */ + rc = ParseEndOfCentralDirectory(&(*pArch),zEnd); + if( rc != SXRET_OK ){ + return rc; + } + + /* Find the starting offset of the central directory */ + zCentral = &zEnd[-(sxi32)pArch->nCentralSize]; + if( zCentral <= (unsigned char *)zBuf || SyMemcmp(zCentral,"PK\001\002",sizeof(sxu32)) != 0 ){ + if( pArch->nCentralOfft >= nLen ){ + /* Corrupted central directory offset */ + return SXERR_CORRUPT; + } + zCentral = (unsigned char *)&zBuf[pArch->nCentralOfft]; + if( SyMemcmp(zCentral,"PK\001\002",sizeof(sxu32)) != 0 ){ + /* Corrupted zip archive */ + return SXERR_CORRUPT; + } + /* Fall thru and extract all valid entries from the central directory */ + } + rc = ZipExtract(&(*pArch),zCentral,(sxu32)(zEnd - zCentral),(void *)zBuf); + return rc; + } +/* + * Default comparison function. + */ + static sxi32 ArchiveHashCmp(const SyString *pStr1,const SyString *pStr2) + { + sxi32 rc; + rc = SyStringCmp(pStr1,pStr2,SyMemcmp); + return rc; + } +PH7_PRIVATE sxi32 SyArchiveInit(SyArchive *pArch,SyMemBackend *pAllocator,ProcHash xHash,ProcRawStrCmp xCmp) + { + SyArchiveEntry **apHash; +#if defined(UNTRUST) + if( pArch == 0 ){ + return SXERR_EMPTY; + } +#endif + SyZero(pArch,sizeof(SyArchive)); + /* Allocate a new hashtable */ + apHash = (SyArchiveEntry **)SyMemBackendAlloc(&(*pAllocator),SXARCHIVE_HASH_SIZE * sizeof(SyArchiveEntry *)); + if( apHash == 0){ + return SXERR_MEM; + } + SyZero(apHash,SXARCHIVE_HASH_SIZE * sizeof(SyArchiveEntry *)); + pArch->apHash = apHash; + pArch->xHash = xHash ? xHash : SyBinHash; + pArch->xCmp = xCmp ? xCmp : ArchiveHashCmp; + pArch->nSize = SXARCHIVE_HASH_SIZE; + pArch->pAllocator = &(*pAllocator); + pArch->nMagic = SXARCH_MAGIC; + return SXRET_OK; + } + static sxi32 ArchiveReleaseEntry(SyMemBackend *pAllocator,SyArchiveEntry *pEntry) + { + SyArchiveEntry *pDup = pEntry->pNextName; + SyArchiveEntry *pNextDup; + + /* Release duplicates first since there are not stored in the hashtable */ + for(;;){ + if( pEntry->nDup == 0 ){ + break; + } + pNextDup = pDup->pNextName; + pDup->nMagic = 0x2661; + SyMemBackendFree(pAllocator,(void *)SyStringData(&pDup->sFileName)); + SyMemBackendPoolFree(pAllocator,pDup); + pDup = pNextDup; + pEntry->nDup--; + } + pEntry->nMagic = 0x2661; + SyMemBackendFree(pAllocator,(void *)SyStringData(&pEntry->sFileName)); + SyMemBackendPoolFree(pAllocator,pEntry); + return SXRET_OK; + } +PH7_PRIVATE sxi32 SyArchiveRelease(SyArchive *pArch) + { + SyArchiveEntry *pEntry,*pNext; + pEntry = pArch->pList; + for(;;){ + if( pArch->nLoaded < 1 ){ + break; + } + pNext = pEntry->pNext; + MACRO_LD_REMOVE(pArch->pList,pEntry); + ArchiveReleaseEntry(pArch->pAllocator,pEntry); + pEntry = pNext; + pArch->nLoaded--; + } + SyMemBackendFree(pArch->pAllocator,pArch->apHash); + pArch->pCursor = 0; + pArch->nMagic = 0x2626; + return SXRET_OK; + } + PH7_PRIVATE sxi32 SyArchiveResetLoopCursor(SyArchive *pArch) + { + pArch->pCursor = pArch->pList; + return SXRET_OK; + } + PH7_PRIVATE sxi32 SyArchiveGetNextEntry(SyArchive *pArch,SyArchiveEntry **ppEntry) + { + SyArchiveEntry *pNext; + if( pArch->pCursor == 0 ){ + /* Rewind the cursor */ + pArch->pCursor = pArch->pList; + return SXERR_EOF; + } + *ppEntry = pArch->pCursor; + pNext = pArch->pCursor->pNext; + /* Advance the cursor to the next entry */ + pArch->pCursor = pNext; + return SXRET_OK; + } +#endif /* PH7_DISABLE_BUILTIN_FUNC */ +/* + * Psuedo Random Number Generator (PRNG) + * @authors: SQLite authors + * @status: Public Domain + * NOTE: + * Nothing in this file or anywhere else in the library does any kind of + * encryption.The RC4 algorithm is being used as a PRNG (pseudo-random + * number generator) not as an encryption device. + */ +#define SXPRNG_MAGIC 0x13C4 +#ifdef __UNIXES__ +#include +#include +#include +#include +#include +#include +#include +#endif +static sxi32 SyOSUtilRandomSeed(void *pBuf,sxu32 nLen,void *pUnused) +{ + char *zBuf = (char *)pBuf; +#ifdef __WINNT__ + DWORD nProcessID; /* Yes,keep it uninitialized when compiling using the MinGW32 builds tools */ +#elif defined(__UNIXES__) + pid_t pid; + int fd; +#else + char zGarbage[128]; /* Yes,keep this buffer uninitialized */ +#endif + SXUNUSED(pUnused); +#ifdef __WINNT__ +#ifndef __MINGW32__ + nProcessID = GetProcessId(GetCurrentProcess()); +#endif + SyMemcpy((const void *)&nProcessID,zBuf,SXMIN(nLen,sizeof(DWORD))); + if( (sxu32)(&zBuf[nLen] - &zBuf[sizeof(DWORD)]) >= sizeof(SYSTEMTIME) ){ + GetSystemTime((LPSYSTEMTIME)&zBuf[sizeof(DWORD)]); + } +#elif defined(__UNIXES__) + fd = open("/dev/urandom",O_RDONLY); + if (fd >= 0 ){ + if( read(fd,zBuf,nLen) > 0 ){ + close(fd); + return SXRET_OK; + } + /* FALL THRU */ + } + close(fd); + pid = getpid(); + SyMemcpy((const void *)&pid,zBuf,SXMIN(nLen,sizeof(pid_t))); + if( &zBuf[nLen] - &zBuf[sizeof(pid_t)] >= (int)sizeof(struct timeval) ){ + gettimeofday((struct timeval *)&zBuf[sizeof(pid_t)],0); + } +#else + /* Fill with uninitialized data */ + SyMemcpy(zGarbage,zBuf,SXMIN(nLen,sizeof(zGarbage))); +#endif + return SXRET_OK; +} +PH7_PRIVATE sxi32 SyRandomnessInit(SyPRNGCtx *pCtx,ProcRandomSeed xSeed,void * pUserData) +{ + char zSeed[256]; + sxu8 t; + sxi32 rc; + sxu32 i; + if( pCtx->nMagic == SXPRNG_MAGIC ){ + return SXRET_OK; /* Already initialized */ + } + /* Initialize the state of the random number generator once, + ** the first time this routine is called.The seed value does + ** not need to contain a lot of randomness since we are not + ** trying to do secure encryption or anything like that... + */ + if( xSeed == 0 ){ + xSeed = SyOSUtilRandomSeed; + } + rc = xSeed(zSeed,sizeof(zSeed),pUserData); + if( rc != SXRET_OK ){ + return rc; + } + pCtx->i = pCtx->j = 0; + for(i=0; i < SX_ARRAYSIZE(pCtx->s) ; i++){ + pCtx->s[i] = (unsigned char)i; + } + for(i=0; i < sizeof(zSeed) ; i++){ + pCtx->j += pCtx->s[i] + zSeed[i]; + t = pCtx->s[pCtx->j]; + pCtx->s[pCtx->j] = pCtx->s[i]; + pCtx->s[i] = t; + } + pCtx->nMagic = SXPRNG_MAGIC; + + return SXRET_OK; +} +/* + * Get a single 8-bit random value using the RC4 PRNG. + */ +static sxu8 randomByte(SyPRNGCtx *pCtx) +{ + sxu8 t; + + /* Generate and return single random byte */ + pCtx->i++; + t = pCtx->s[pCtx->i]; + pCtx->j += t; + pCtx->s[pCtx->i] = pCtx->s[pCtx->j]; + pCtx->s[pCtx->j] = t; + t += pCtx->s[pCtx->i]; + return pCtx->s[t]; +} +PH7_PRIVATE sxi32 SyRandomness(SyPRNGCtx *pCtx,void *pBuf,sxu32 nLen) +{ + unsigned char *zBuf = (unsigned char *)pBuf; + unsigned char *zEnd = &zBuf[nLen]; +#if defined(UNTRUST) + if( pCtx == 0 || pBuf == 0 || nLen <= 0 ){ + return SXERR_EMPTY; + } +#endif + if(pCtx->nMagic != SXPRNG_MAGIC ){ + return SXERR_CORRUPT; + } + for(;;){ + if( zBuf >= zEnd ){break;} zBuf[0] = randomByte(pCtx); zBuf++; + if( zBuf >= zEnd ){break;} zBuf[0] = randomByte(pCtx); zBuf++; + if( zBuf >= zEnd ){break;} zBuf[0] = randomByte(pCtx); zBuf++; + if( zBuf >= zEnd ){break;} zBuf[0] = randomByte(pCtx); zBuf++; + } + return SXRET_OK; +} +#ifndef PH7_DISABLE_BUILTIN_FUNC +#ifndef PH7_DISABLE_HASH_FUNC +/* SyRunTimeApi: sxhash.c */ +/* + * This code implements the MD5 message-digest algorithm. + * The algorithm is due to Ron Rivest.This code was + * written by Colin Plumb in 1993, no copyright is claimed. + * This code is in the public domain; do with it what you wish. + * + * Equivalent code is available from RSA Data Security, Inc. + * This code has been tested against that, and is equivalent, + * except that you don't need to include two pages of legalese + * with every copy. + * + * To compute the message digest of a chunk of bytes, declare an + * MD5Context structure, pass it to MD5Init, call MD5Update as + * needed on buffers full of bytes, and then call MD5Final, which + * will fill a supplied 16-byte array with the digest. + */ +#define SX_MD5_BINSZ 16 +#define SX_MD5_HEXSZ 32 +/* + * Note: this code is harmless on little-endian machines. + */ +static void byteReverse (unsigned char *buf, unsigned longs) +{ + sxu32 t; + do { + t = (sxu32)((unsigned)buf[3]<<8 | buf[2]) << 16 | + ((unsigned)buf[1]<<8 | buf[0]); + *(sxu32*)buf = t; + buf += 4; + } while (--longs); +} +/* The four core functions - F1 is optimized somewhat */ + +/* #define F1(x, y, z) (x & y | ~x & z) */ +#ifdef F1 +#undef F1 +#endif +#ifdef F2 +#undef F2 +#endif +#ifdef F3 +#undef F3 +#endif +#ifdef F4 +#undef F4 +#endif + +#define F1(x, y, z) (z ^ (x & (y ^ z))) +#define F2(x, y, z) F1(z, x, y) +#define F3(x, y, z) (x ^ y ^ z) +#define F4(x, y, z) (y ^ (x | ~z)) + +/* This is the central step in the MD5 algorithm.*/ +#define SX_MD5STEP(f, w, x, y, z, data, s) \ + ( w += f(x, y, z) + data, w = w<>(32-s), w += x ) + +/* + * The core of the MD5 algorithm, this alters an existing MD5 hash to + * reflect the addition of 16 longwords of new data.MD5Update blocks + * the data and converts bytes into longwords for this routine. + */ +static void MD5Transform(sxu32 buf[4], const sxu32 in[16]) +{ + register sxu32 a, b, c, d; + + a = buf[0]; + b = buf[1]; + c = buf[2]; + d = buf[3]; + + SX_MD5STEP(F1, a, b, c, d, in[ 0]+0xd76aa478, 7); + SX_MD5STEP(F1, d, a, b, c, in[ 1]+0xe8c7b756, 12); + SX_MD5STEP(F1, c, d, a, b, in[ 2]+0x242070db, 17); + SX_MD5STEP(F1, b, c, d, a, in[ 3]+0xc1bdceee, 22); + SX_MD5STEP(F1, a, b, c, d, in[ 4]+0xf57c0faf, 7); + SX_MD5STEP(F1, d, a, b, c, in[ 5]+0x4787c62a, 12); + SX_MD5STEP(F1, c, d, a, b, in[ 6]+0xa8304613, 17); + SX_MD5STEP(F1, b, c, d, a, in[ 7]+0xfd469501, 22); + SX_MD5STEP(F1, a, b, c, d, in[ 8]+0x698098d8, 7); + SX_MD5STEP(F1, d, a, b, c, in[ 9]+0x8b44f7af, 12); + SX_MD5STEP(F1, c, d, a, b, in[10]+0xffff5bb1, 17); + SX_MD5STEP(F1, b, c, d, a, in[11]+0x895cd7be, 22); + SX_MD5STEP(F1, a, b, c, d, in[12]+0x6b901122, 7); + SX_MD5STEP(F1, d, a, b, c, in[13]+0xfd987193, 12); + SX_MD5STEP(F1, c, d, a, b, in[14]+0xa679438e, 17); + SX_MD5STEP(F1, b, c, d, a, in[15]+0x49b40821, 22); + + SX_MD5STEP(F2, a, b, c, d, in[ 1]+0xf61e2562, 5); + SX_MD5STEP(F2, d, a, b, c, in[ 6]+0xc040b340, 9); + SX_MD5STEP(F2, c, d, a, b, in[11]+0x265e5a51, 14); + SX_MD5STEP(F2, b, c, d, a, in[ 0]+0xe9b6c7aa, 20); + SX_MD5STEP(F2, a, b, c, d, in[ 5]+0xd62f105d, 5); + SX_MD5STEP(F2, d, a, b, c, in[10]+0x02441453, 9); + SX_MD5STEP(F2, c, d, a, b, in[15]+0xd8a1e681, 14); + SX_MD5STEP(F2, b, c, d, a, in[ 4]+0xe7d3fbc8, 20); + SX_MD5STEP(F2, a, b, c, d, in[ 9]+0x21e1cde6, 5); + SX_MD5STEP(F2, d, a, b, c, in[14]+0xc33707d6, 9); + SX_MD5STEP(F2, c, d, a, b, in[ 3]+0xf4d50d87, 14); + SX_MD5STEP(F2, b, c, d, a, in[ 8]+0x455a14ed, 20); + SX_MD5STEP(F2, a, b, c, d, in[13]+0xa9e3e905, 5); + SX_MD5STEP(F2, d, a, b, c, in[ 2]+0xfcefa3f8, 9); + SX_MD5STEP(F2, c, d, a, b, in[ 7]+0x676f02d9, 14); + SX_MD5STEP(F2, b, c, d, a, in[12]+0x8d2a4c8a, 20); + + SX_MD5STEP(F3, a, b, c, d, in[ 5]+0xfffa3942, 4); + SX_MD5STEP(F3, d, a, b, c, in[ 8]+0x8771f681, 11); + SX_MD5STEP(F3, c, d, a, b, in[11]+0x6d9d6122, 16); + SX_MD5STEP(F3, b, c, d, a, in[14]+0xfde5380c, 23); + SX_MD5STEP(F3, a, b, c, d, in[ 1]+0xa4beea44, 4); + SX_MD5STEP(F3, d, a, b, c, in[ 4]+0x4bdecfa9, 11); + SX_MD5STEP(F3, c, d, a, b, in[ 7]+0xf6bb4b60, 16); + SX_MD5STEP(F3, b, c, d, a, in[10]+0xbebfbc70, 23); + SX_MD5STEP(F3, a, b, c, d, in[13]+0x289b7ec6, 4); + SX_MD5STEP(F3, d, a, b, c, in[ 0]+0xeaa127fa, 11); + SX_MD5STEP(F3, c, d, a, b, in[ 3]+0xd4ef3085, 16); + SX_MD5STEP(F3, b, c, d, a, in[ 6]+0x04881d05, 23); + SX_MD5STEP(F3, a, b, c, d, in[ 9]+0xd9d4d039, 4); + SX_MD5STEP(F3, d, a, b, c, in[12]+0xe6db99e5, 11); + SX_MD5STEP(F3, c, d, a, b, in[15]+0x1fa27cf8, 16); + SX_MD5STEP(F3, b, c, d, a, in[ 2]+0xc4ac5665, 23); + + SX_MD5STEP(F4, a, b, c, d, in[ 0]+0xf4292244, 6); + SX_MD5STEP(F4, d, a, b, c, in[ 7]+0x432aff97, 10); + SX_MD5STEP(F4, c, d, a, b, in[14]+0xab9423a7, 15); + SX_MD5STEP(F4, b, c, d, a, in[ 5]+0xfc93a039, 21); + SX_MD5STEP(F4, a, b, c, d, in[12]+0x655b59c3, 6); + SX_MD5STEP(F4, d, a, b, c, in[ 3]+0x8f0ccc92, 10); + SX_MD5STEP(F4, c, d, a, b, in[10]+0xffeff47d, 15); + SX_MD5STEP(F4, b, c, d, a, in[ 1]+0x85845dd1, 21); + SX_MD5STEP(F4, a, b, c, d, in[ 8]+0x6fa87e4f, 6); + SX_MD5STEP(F4, d, a, b, c, in[15]+0xfe2ce6e0, 10); + SX_MD5STEP(F4, c, d, a, b, in[ 6]+0xa3014314, 15); + SX_MD5STEP(F4, b, c, d, a, in[13]+0x4e0811a1, 21); + SX_MD5STEP(F4, a, b, c, d, in[ 4]+0xf7537e82, 6); + SX_MD5STEP(F4, d, a, b, c, in[11]+0xbd3af235, 10); + SX_MD5STEP(F4, c, d, a, b, in[ 2]+0x2ad7d2bb, 15); + SX_MD5STEP(F4, b, c, d, a, in[ 9]+0xeb86d391, 21); + + buf[0] += a; + buf[1] += b; + buf[2] += c; + buf[3] += d; +} +/* + * Update context to reflect the concatenation of another buffer full + * of bytes. + */ +PH7_PRIVATE void MD5Update(MD5Context *ctx, const unsigned char *buf, unsigned int len) +{ + sxu32 t; + + /* Update bitcount */ + t = ctx->bits[0]; + if ((ctx->bits[0] = t + ((sxu32)len << 3)) < t) + ctx->bits[1]++; /* Carry from low to high */ + ctx->bits[1] += len >> 29; + t = (t >> 3) & 0x3f; /* Bytes already in shsInfo->data */ + /* Handle any leading odd-sized chunks */ + if ( t ) { + unsigned char *p = (unsigned char *)ctx->in + t; + + t = 64-t; + if (len < t) { + SyMemcpy(buf,p,len); + return; + } + SyMemcpy(buf,p,t); + byteReverse(ctx->in, 16); + MD5Transform(ctx->buf, (sxu32*)ctx->in); + buf += t; + len -= t; + } + /* Process data in 64-byte chunks */ + while (len >= 64) { + SyMemcpy(buf,ctx->in,64); + byteReverse(ctx->in, 16); + MD5Transform(ctx->buf, (sxu32*)ctx->in); + buf += 64; + len -= 64; + } + /* Handle any remaining bytes of data.*/ + SyMemcpy(buf,ctx->in,len); +} +/* + * Final wrapup - pad to 64-byte boundary with the bit pattern + * 1 0* (64-bit count of bits processed, MSB-first) + */ +PH7_PRIVATE void MD5Final(unsigned char digest[16], MD5Context *ctx){ + unsigned count; + unsigned char *p; + + /* Compute number of bytes mod 64 */ + count = (ctx->bits[0] >> 3) & 0x3F; + + /* Set the first char of padding to 0x80.This is safe since there is + always at least one byte free */ + p = ctx->in + count; + *p++ = 0x80; + + /* Bytes of padding needed to make 64 bytes */ + count = 64 - 1 - count; + + /* Pad out to 56 mod 64 */ + if (count < 8) { + /* Two lots of padding: Pad the first block to 64 bytes */ + SyZero(p,count); + byteReverse(ctx->in, 16); + MD5Transform(ctx->buf, (sxu32*)ctx->in); + + /* Now fill the next block with 56 bytes */ + SyZero(ctx->in,56); + } else { + /* Pad block to 56 bytes */ + SyZero(p,count-8); + } + byteReverse(ctx->in, 14); + + /* Append length in bits and transform */ + ((sxu32*)ctx->in)[ 14 ] = ctx->bits[0]; + ((sxu32*)ctx->in)[ 15 ] = ctx->bits[1]; + + MD5Transform(ctx->buf, (sxu32*)ctx->in); + byteReverse((unsigned char *)ctx->buf, 4); + SyMemcpy(ctx->buf,digest,0x10); + SyZero(ctx,sizeof(ctx)); /* In case it's sensitive */ +} +#undef F1 +#undef F2 +#undef F3 +#undef F4 +PH7_PRIVATE sxi32 MD5Init(MD5Context *pCtx) +{ + pCtx->buf[0] = 0x67452301; + pCtx->buf[1] = 0xefcdab89; + pCtx->buf[2] = 0x98badcfe; + pCtx->buf[3] = 0x10325476; + pCtx->bits[0] = 0; + pCtx->bits[1] = 0; + + return SXRET_OK; +} +PH7_PRIVATE sxi32 SyMD5Compute(const void *pIn,sxu32 nLen,unsigned char zDigest[16]) +{ + MD5Context sCtx; + MD5Init(&sCtx); + MD5Update(&sCtx,(const unsigned char *)pIn,nLen); + MD5Final(zDigest,&sCtx); + return SXRET_OK; +} +/* + * SHA-1 in C + * By Steve Reid + * Status: Public Domain + */ +/* + * blk0() and blk() perform the initial expand. + * I got the idea of expanding during the round function from SSLeay + * + * blk0le() for little-endian and blk0be() for big-endian. + */ +#if __GNUC__ && (defined(__i386__) || defined(__x86_64__)) +/* + * GCC by itself only generates left rotates. Use right rotates if + * possible to be kinder to dinky implementations with iterative rotate + * instructions. + */ +#define SHA_ROT(op, x, k) \ + ({ unsigned int y; asm(op " %1,%0" : "=r" (y) : "I" (k), "0" (x)); y; }) +#define rol(x,k) SHA_ROT("roll", x, k) +#define ror(x,k) SHA_ROT("rorl", x, k) + +#else +/* Generic C equivalent */ +#define SHA_ROT(x,l,r) ((x) << (l) | (x) >> (r)) +#define rol(x,k) SHA_ROT(x,k,32-(k)) +#define ror(x,k) SHA_ROT(x,32-(k),k) +#endif + +#define blk0le(i) (block[i] = (ror(block[i],8)&0xFF00FF00) \ + |(rol(block[i],8)&0x00FF00FF)) +#define blk0be(i) block[i] +#define blk(i) (block[i&15] = rol(block[(i+13)&15]^block[(i+8)&15] \ + ^block[(i+2)&15]^block[i&15],1)) + +/* + * (R0+R1), R2, R3, R4 are the different operations (rounds) used in SHA1 + * + * Rl0() for little-endian and Rb0() for big-endian. Endianness is + * determined at run-time. + */ +#define Rl0(v,w,x,y,z,i) \ + z+=((w&(x^y))^y)+blk0le(i)+0x5A827999+rol(v,5);w=ror(w,2); +#define Rb0(v,w,x,y,z,i) \ + z+=((w&(x^y))^y)+blk0be(i)+0x5A827999+rol(v,5);w=ror(w,2); +#define R1(v,w,x,y,z,i) \ + z+=((w&(x^y))^y)+blk(i)+0x5A827999+rol(v,5);w=ror(w,2); +#define R2(v,w,x,y,z,i) \ + z+=(w^x^y)+blk(i)+0x6ED9EBA1+rol(v,5);w=ror(w,2); +#define R3(v,w,x,y,z,i) \ + z+=(((w|x)&y)|(w&x))+blk(i)+0x8F1BBCDC+rol(v,5);w=ror(w,2); +#define R4(v,w,x,y,z,i) \ + z+=(w^x^y)+blk(i)+0xCA62C1D6+rol(v,5);w=ror(w,2); + +/* + * Hash a single 512-bit block. This is the core of the algorithm. + */ +#define a qq[0] +#define b qq[1] +#define c qq[2] +#define d qq[3] +#define e qq[4] + +static void SHA1Transform(unsigned int state[5], const unsigned char buffer[64]) +{ + unsigned int qq[5]; /* a, b, c, d, e; */ + static int one = 1; + unsigned int block[16]; + SyMemcpy(buffer,(void *)block,64); + SyMemcpy(state,qq,5*sizeof(unsigned int)); + + /* Copy context->state[] to working vars */ + /* + a = state[0]; + b = state[1]; + c = state[2]; + d = state[3]; + e = state[4]; + */ + + /* 4 rounds of 20 operations each. Loop unrolled. */ + if( 1 == *(unsigned char*)&one ){ + Rl0(a,b,c,d,e, 0); Rl0(e,a,b,c,d, 1); Rl0(d,e,a,b,c, 2); Rl0(c,d,e,a,b, 3); + Rl0(b,c,d,e,a, 4); Rl0(a,b,c,d,e, 5); Rl0(e,a,b,c,d, 6); Rl0(d,e,a,b,c, 7); + Rl0(c,d,e,a,b, 8); Rl0(b,c,d,e,a, 9); Rl0(a,b,c,d,e,10); Rl0(e,a,b,c,d,11); + Rl0(d,e,a,b,c,12); Rl0(c,d,e,a,b,13); Rl0(b,c,d,e,a,14); Rl0(a,b,c,d,e,15); + }else{ + Rb0(a,b,c,d,e, 0); Rb0(e,a,b,c,d, 1); Rb0(d,e,a,b,c, 2); Rb0(c,d,e,a,b, 3); + Rb0(b,c,d,e,a, 4); Rb0(a,b,c,d,e, 5); Rb0(e,a,b,c,d, 6); Rb0(d,e,a,b,c, 7); + Rb0(c,d,e,a,b, 8); Rb0(b,c,d,e,a, 9); Rb0(a,b,c,d,e,10); Rb0(e,a,b,c,d,11); + Rb0(d,e,a,b,c,12); Rb0(c,d,e,a,b,13); Rb0(b,c,d,e,a,14); Rb0(a,b,c,d,e,15); + } + R1(e,a,b,c,d,16); R1(d,e,a,b,c,17); R1(c,d,e,a,b,18); R1(b,c,d,e,a,19); + R2(a,b,c,d,e,20); R2(e,a,b,c,d,21); R2(d,e,a,b,c,22); R2(c,d,e,a,b,23); + R2(b,c,d,e,a,24); R2(a,b,c,d,e,25); R2(e,a,b,c,d,26); R2(d,e,a,b,c,27); + R2(c,d,e,a,b,28); R2(b,c,d,e,a,29); R2(a,b,c,d,e,30); R2(e,a,b,c,d,31); + R2(d,e,a,b,c,32); R2(c,d,e,a,b,33); R2(b,c,d,e,a,34); R2(a,b,c,d,e,35); + R2(e,a,b,c,d,36); R2(d,e,a,b,c,37); R2(c,d,e,a,b,38); R2(b,c,d,e,a,39); + R3(a,b,c,d,e,40); R3(e,a,b,c,d,41); R3(d,e,a,b,c,42); R3(c,d,e,a,b,43); + R3(b,c,d,e,a,44); R3(a,b,c,d,e,45); R3(e,a,b,c,d,46); R3(d,e,a,b,c,47); + R3(c,d,e,a,b,48); R3(b,c,d,e,a,49); R3(a,b,c,d,e,50); R3(e,a,b,c,d,51); + R3(d,e,a,b,c,52); R3(c,d,e,a,b,53); R3(b,c,d,e,a,54); R3(a,b,c,d,e,55); + R3(e,a,b,c,d,56); R3(d,e,a,b,c,57); R3(c,d,e,a,b,58); R3(b,c,d,e,a,59); + R4(a,b,c,d,e,60); R4(e,a,b,c,d,61); R4(d,e,a,b,c,62); R4(c,d,e,a,b,63); + R4(b,c,d,e,a,64); R4(a,b,c,d,e,65); R4(e,a,b,c,d,66); R4(d,e,a,b,c,67); + R4(c,d,e,a,b,68); R4(b,c,d,e,a,69); R4(a,b,c,d,e,70); R4(e,a,b,c,d,71); + R4(d,e,a,b,c,72); R4(c,d,e,a,b,73); R4(b,c,d,e,a,74); R4(a,b,c,d,e,75); + R4(e,a,b,c,d,76); R4(d,e,a,b,c,77); R4(c,d,e,a,b,78); R4(b,c,d,e,a,79); + + /* Add the working vars back into context.state[] */ + state[0] += a; + state[1] += b; + state[2] += c; + state[3] += d; + state[4] += e; +} +#undef a +#undef b +#undef c +#undef d +#undef e +/* + * SHA1Init - Initialize new context + */ +PH7_PRIVATE void SHA1Init(SHA1Context *context){ + /* SHA1 initialization constants */ + context->state[0] = 0x67452301; + context->state[1] = 0xEFCDAB89; + context->state[2] = 0x98BADCFE; + context->state[3] = 0x10325476; + context->state[4] = 0xC3D2E1F0; + context->count[0] = context->count[1] = 0; +} +/* + * Run your data through this. + */ +PH7_PRIVATE void SHA1Update(SHA1Context *context,const unsigned char *data,unsigned int len){ + unsigned int i, j; + + j = context->count[0]; + if ((context->count[0] += len << 3) < j) + context->count[1] += (len>>29)+1; + j = (j >> 3) & 63; + if ((j + len) > 63) { + (void)SyMemcpy(data,&context->buffer[j], (i = 64-j)); + SHA1Transform(context->state, context->buffer); + for ( ; i + 63 < len; i += 64) + SHA1Transform(context->state, &data[i]); + j = 0; + } else { + i = 0; + } + (void)SyMemcpy(&data[i],&context->buffer[j],len - i); +} +/* + * Add padding and return the message digest. + */ +PH7_PRIVATE void SHA1Final(SHA1Context *context, unsigned char digest[20]){ + unsigned int i; + unsigned char finalcount[8]; + + for (i = 0; i < 8; i++) { + finalcount[i] = (unsigned char)((context->count[(i >= 4 ? 0 : 1)] + >> ((3-(i & 3)) * 8) ) & 255); /* Endian independent */ + } + SHA1Update(context, (const unsigned char *)"\200", 1); + while ((context->count[0] & 504) != 448) + SHA1Update(context, (const unsigned char *)"\0", 1); + SHA1Update(context, finalcount, 8); /* Should cause a SHA1Transform() */ + + if (digest) { + for (i = 0; i < 20; i++) + digest[i] = (unsigned char) + ((context->state[i>>2] >> ((3-(i & 3)) * 8) ) & 255); + } +} +#undef Rl0 +#undef Rb0 +#undef R1 +#undef R2 +#undef R3 +#undef R4 + +PH7_PRIVATE sxi32 SySha1Compute(const void *pIn,sxu32 nLen,unsigned char zDigest[20]) +{ + SHA1Context sCtx; + SHA1Init(&sCtx); + SHA1Update(&sCtx,(const unsigned char *)pIn,nLen); + SHA1Final(&sCtx,zDigest); + return SXRET_OK; +} +static const sxu32 crc32_table[] = { + 0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, + 0x076dc419, 0x706af48f, 0xe963a535, 0x9e6495a3, + 0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988, + 0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91, + 0x1db71064, 0x6ab020f2, 0xf3b97148, 0x84be41de, + 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7, + 0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, + 0x14015c4f, 0x63066cd9, 0xfa0f3d63, 0x8d080df5, + 0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172, + 0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b, + 0x35b5a8fa, 0x42b2986c, 0xdbbbc9d6, 0xacbcf940, + 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59, + 0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116, + 0x21b4f4b5, 0x56b3c423, 0xcfba9599, 0xb8bda50f, + 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924, + 0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d, + 0x76dc4190, 0x01db7106, 0x98d220bc, 0xefd5102a, + 0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433, + 0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818, + 0x7f6a0dbb, 0x086d3d2d, 0x91646c97, 0xe6635c01, + 0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e, + 0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457, + 0x65b0d9c6, 0x12b7e950, 0x8bbeb8ea, 0xfcb9887c, + 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65, + 0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2, + 0x4adfa541, 0x3dd895d7, 0xa4d1c46d, 0xd3d6f4fb, + 0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0, + 0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9, + 0x5005713c, 0x270241aa, 0xbe0b1010, 0xc90c2086, + 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f, + 0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, + 0x59b33d17, 0x2eb40d81, 0xb7bd5c3b, 0xc0ba6cad, + 0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a, + 0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683, + 0xe3630b12, 0x94643b84, 0x0d6d6a3e, 0x7a6a5aa8, + 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1, + 0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe, + 0xf762575d, 0x806567cb, 0x196c3671, 0x6e6b06e7, + 0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc, + 0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5, + 0xd6d6a3e8, 0xa1d1937e, 0x38d8c2c4, 0x4fdff252, + 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b, + 0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60, + 0xdf60efc3, 0xa867df55, 0x316e8eef, 0x4669be79, + 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236, + 0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f, + 0xc5ba3bbe, 0xb2bd0b28, 0x2bb45a92, 0x5cb36a04, + 0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d, + 0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a, + 0x9c0906a9, 0xeb0e363f, 0x72076785, 0x05005713, + 0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38, + 0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21, + 0x86d3d2d4, 0xf1d4e242, 0x68ddb3f8, 0x1fda836e, + 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777, + 0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c, + 0x8f659eff, 0xf862ae69, 0x616bffd3, 0x166ccf45, + 0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2, + 0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db, + 0xaed16a4a, 0xd9d65adc, 0x40df0b66, 0x37d83bf0, + 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9, + 0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, + 0xbad03605, 0xcdd70693, 0x54de5729, 0x23d967bf, + 0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94, + 0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d, +}; +#define CRC32C(c,d) (c = ( crc32_table[(c ^ (d)) & 0xFF] ^ (c>>8) ) ) +static sxu32 SyCrc32Update(sxu32 crc32,const void *pSrc,sxu32 nLen) +{ + register unsigned char *zIn = (unsigned char *)pSrc; + unsigned char *zEnd; + if( zIn == 0 ){ + return crc32; + } + zEnd = &zIn[nLen]; + for(;;){ + if(zIn >= zEnd ){ break; } CRC32C(crc32,zIn[0]); zIn++; + if(zIn >= zEnd ){ break; } CRC32C(crc32,zIn[0]); zIn++; + if(zIn >= zEnd ){ break; } CRC32C(crc32,zIn[0]); zIn++; + if(zIn >= zEnd ){ break; } CRC32C(crc32,zIn[0]); zIn++; + } + + return crc32; +} +PH7_PRIVATE sxu32 SyCrc32(const void *pSrc,sxu32 nLen) +{ + return SyCrc32Update(SXU32_HIGH,pSrc,nLen); +} +#endif /* PH7_DISABLE_HASH_FUNC */ +#endif /* PH7_DISABLE_BUILTIN_FUNC */ +#ifndef PH7_DISABLE_BUILTIN_FUNC +PH7_PRIVATE sxi32 SyBinToHexConsumer(const void *pIn,sxu32 nLen,ProcConsumer xConsumer,void *pConsumerData) +{ + static const unsigned char zHexTab[] = "0123456789abcdef"; + const unsigned char *zIn,*zEnd; + unsigned char zOut[3]; + sxi32 rc; +#if defined(UNTRUST) + if( pIn == 0 || xConsumer == 0 ){ + return SXERR_EMPTY; + } +#endif + zIn = (const unsigned char *)pIn; + zEnd = &zIn[nLen]; + for(;;){ + if( zIn >= zEnd ){ + break; + } + zOut[0] = zHexTab[zIn[0] >> 4]; zOut[1] = zHexTab[zIn[0] & 0x0F]; + rc = xConsumer((const void *)zOut,sizeof(char)*2,pConsumerData); + if( rc != SXRET_OK ){ + return rc; + } + zIn++; + } + return SXRET_OK; +} +#endif /* PH7_DISABLE_BUILTIN_FUNC */ +/* + * ---------------------------------------------------------- + * File: lex.c + * MD5: c218c13068ed53acb1154762f9e6fd13 + * ---------------------------------------------------------- + */ +/* + * Symisc PH7: An embeddable bytecode compiler and a virtual machine for the PHP(5) programming language. + * Copyright (C) 2011-2012, Symisc Systems http://ph7.symisc.net/ + * Version 2.1.4 + * For information on licensing,redistribution of this file,and for a DISCLAIMER OF ALL WARRANTIES + * please contact Symisc Systems via: + * legal@symisc.net + * licensing@symisc.net + * contact@symisc.net + * or visit: + * http://ph7.symisc.net/ + */ + /* $SymiscID: lex.c v2.8 Ubuntu-linux 2012-07-13 01:21 stable $ */ +#ifndef PH7_AMALGAMATION +#include "ph7int.h" +#endif +/* + * This file implement an efficient hand-coded,thread-safe and full-reentrant + * lexical analyzer/Tokenizer for the PH7 engine. + */ +/* Forward declaration */ +static sxu32 KeywordCode(const char *z, int n); +static sxi32 LexExtractHeredoc(SyStream *pStream,SyToken *pToken); +/* + * Tokenize a raw PHP input. + * Get a single low-level token from the input file. Update the stream pointer so that + * it points to the first character beyond the extracted token. + */ +static sxi32 TokenizePHP(SyStream *pStream,SyToken *pToken,void *pUserData,void *pCtxData) +{ + SyString *pStr; + sxi32 rc; + /* Ignore leading white spaces */ + while( pStream->zText < pStream->zEnd && pStream->zText[0] < 0xc0 && SyisSpace(pStream->zText[0]) ){ + /* Advance the stream cursor */ + if( pStream->zText[0] == '\n' ){ + /* Update line counter */ + pStream->nLine++; + } + pStream->zText++; + } + if( pStream->zText >= pStream->zEnd ){ + /* End of input reached */ + return SXERR_EOF; + } + /* Record token starting position and line */ + pToken->nLine = pStream->nLine; + pToken->pUserData = 0; + pStr = &pToken->sData; + SyStringInitFromBuf(pStr,pStream->zText,0); + if( pStream->zText[0] >= 0xc0 || SyisAlpha(pStream->zText[0]) || pStream->zText[0] == '_' ){ + /* The following code fragment is taken verbatim from the xPP source tree. + * xPP is a modern embeddable macro processor with advanced features useful for + * application seeking for a production quality,ready to use macro processor. + * xPP is a widely used library developed and maintened by Symisc Systems. + * You can reach the xPP home page by following this link: + * http://xpp.symisc.net/ + */ + const unsigned char *zIn; + sxu32 nKeyword; + /* Isolate UTF-8 or alphanumeric stream */ + if( pStream->zText[0] < 0xc0 ){ + pStream->zText++; + } + for(;;){ + zIn = pStream->zText; + if( zIn[0] >= 0xc0 ){ + zIn++; + /* UTF-8 stream */ + while( zIn < pStream->zEnd && ((zIn[0] & 0xc0) == 0x80) ){ + zIn++; + } + } + /* Skip alphanumeric stream */ + while( zIn < pStream->zEnd && zIn[0] < 0xc0 && (SyisAlphaNum(zIn[0]) || zIn[0] == '_') ){ + zIn++; + } + if( zIn == pStream->zText ){ + /* Not an UTF-8 or alphanumeric stream */ + break; + } + /* Synchronize pointers */ + pStream->zText = zIn; + } + /* Record token length */ + pStr->nByte = (sxu32)((const char *)pStream->zText-pStr->zString); + nKeyword = KeywordCode(pStr->zString,(int)pStr->nByte); + if( nKeyword != PH7_TK_ID ){ + if( nKeyword & + (PH7_TKWRD_NEW|PH7_TKWRD_CLONE|PH7_TKWRD_AND|PH7_TKWRD_XOR|PH7_TKWRD_OR|PH7_TKWRD_INSTANCEOF|PH7_TKWRD_SEQ|PH7_TKWRD_SNE) ){ + /* Alpha stream operators [i.e: new,clone,and,instanceof,eq,ne,or,xor],save the operator instance for later processing */ + pToken->pUserData = (void *)PH7_ExprExtractOperator(pStr,0); + /* Mark as an operator */ + pToken->nType = PH7_TK_ID|PH7_TK_OP; + }else{ + /* We are dealing with a keyword [i.e: while,foreach,class...],save the keyword ID */ + pToken->nType = PH7_TK_KEYWORD; + pToken->pUserData = SX_INT_TO_PTR(nKeyword); + } + }else{ + /* A simple identifier */ + pToken->nType = PH7_TK_ID; + } + }else{ + sxi32 c; + /* Non-alpha stream */ + if( pStream->zText[0] == '#' || + ( pStream->zText[0] == '/' && &pStream->zText[1] < pStream->zEnd && pStream->zText[1] == '/') ){ + pStream->zText++; + /* Inline comments */ + while( pStream->zText < pStream->zEnd && pStream->zText[0] != '\n' ){ + pStream->zText++; + } + /* Tell the upper-layer to ignore this token */ + return SXERR_CONTINUE; + }else if( pStream->zText[0] == '/' && &pStream->zText[1] < pStream->zEnd && pStream->zText[1] == '*' ){ + pStream->zText += 2; + /* Block comment */ + while( pStream->zText < pStream->zEnd ){ + if( pStream->zText[0] == '*' ){ + if( &pStream->zText[1] >= pStream->zEnd || pStream->zText[1] == '/' ){ + break; + } + } + if( pStream->zText[0] == '\n' ){ + pStream->nLine++; + } + pStream->zText++; + } + pStream->zText += 2; + /* Tell the upper-layer to ignore this token */ + return SXERR_CONTINUE; + }else if( SyisDigit(pStream->zText[0]) ){ + pStream->zText++; + /* Decimal digit stream */ + while( pStream->zText < pStream->zEnd && pStream->zText[0] < 0xc0 && SyisDigit(pStream->zText[0]) ){ + pStream->zText++; + } + /* Mark the token as integer until we encounter a real number */ + pToken->nType = PH7_TK_INTEGER; + if( pStream->zText < pStream->zEnd ){ + c = pStream->zText[0]; + if( c == '.' ){ + /* Real number */ + pStream->zText++; + while( pStream->zText < pStream->zEnd && pStream->zText[0] < 0xc0 && SyisDigit(pStream->zText[0]) ){ + pStream->zText++; + } + if( pStream->zText < pStream->zEnd ){ + c = pStream->zText[0]; + if( c=='e' || c=='E' ){ + pStream->zText++; + if( pStream->zText < pStream->zEnd ){ + c = pStream->zText[0]; + if( (c =='+' || c=='-') && &pStream->zText[1] < pStream->zEnd && + pStream->zText[1] < 0xc0 && SyisDigit(pStream->zText[1]) ){ + pStream->zText++; + } + while( pStream->zText < pStream->zEnd && pStream->zText[0] < 0xc0 && SyisDigit(pStream->zText[0]) ){ + pStream->zText++; + } + } + } + } + pToken->nType = PH7_TK_REAL; + }else if( c=='e' || c=='E' ){ + SXUNUSED(pUserData); /* Prevent compiler warning */ + SXUNUSED(pCtxData); + pStream->zText++; + if( pStream->zText < pStream->zEnd ){ + c = pStream->zText[0]; + if( (c =='+' || c=='-') && &pStream->zText[1] < pStream->zEnd && + pStream->zText[1] < 0xc0 && SyisDigit(pStream->zText[1]) ){ + pStream->zText++; + } + while( pStream->zText < pStream->zEnd && pStream->zText[0] < 0xc0 && SyisDigit(pStream->zText[0]) ){ + pStream->zText++; + } + } + pToken->nType = PH7_TK_REAL; + }else if( c == 'x' || c == 'X' ){ + /* Hex digit stream */ + pStream->zText++; + while( pStream->zText < pStream->zEnd && pStream->zText[0] < 0xc0 && SyisHex(pStream->zText[0]) ){ + pStream->zText++; + } + }else if(c == 'b' || c == 'B' ){ + /* Binary digit stream */ + pStream->zText++; + while( pStream->zText < pStream->zEnd && (pStream->zText[0] == '0' || pStream->zText[0] == '1') ){ + pStream->zText++; + } + } + } + /* Record token length */ + pStr->nByte = (sxu32)((const char *)pStream->zText-pStr->zString); + return SXRET_OK; + } + c = pStream->zText[0]; + pStream->zText++; /* Advance the stream cursor */ + /* Assume we are dealing with an operator*/ + pToken->nType = PH7_TK_OP; + switch(c){ + case '$': pToken->nType = PH7_TK_DOLLAR; break; + case '{': pToken->nType = PH7_TK_OCB; break; + case '}': pToken->nType = PH7_TK_CCB; break; + case '(': pToken->nType = PH7_TK_LPAREN; break; + case '[': pToken->nType |= PH7_TK_OSB; break; /* Bitwise operation here,since the square bracket token '[' + * is a potential operator [i.e: subscripting] */ + case ']': pToken->nType = PH7_TK_CSB; break; + case ')': { + SySet *pTokSet = pStream->pSet; + /* Assemble type cast operators [i.e: (int),(float),(bool)...] */ + if( pTokSet->nUsed >= 2 ){ + SyToken *pTmp; + /* Peek the last recongnized token */ + pTmp = (SyToken *)SySetPeek(pTokSet); + if( pTmp->nType & PH7_TK_KEYWORD ){ + sxi32 nID = SX_PTR_TO_INT(pTmp->pUserData); + if( (sxu32)nID & (PH7_TKWRD_ARRAY|PH7_TKWRD_INT|PH7_TKWRD_FLOAT|PH7_TKWRD_STRING|PH7_TKWRD_OBJECT|PH7_TKWRD_BOOL|PH7_TKWRD_UNSET) ){ + pTmp = (SyToken *)SySetAt(pTokSet,pTokSet->nUsed - 2); + if( pTmp->nType & PH7_TK_LPAREN ){ + /* Merge the three tokens '(' 'TYPE' ')' into a single one */ + const char * zTypeCast = "(int)"; + if( nID & PH7_TKWRD_FLOAT ){ + zTypeCast = "(float)"; + }else if( nID & PH7_TKWRD_BOOL ){ + zTypeCast = "(bool)"; + }else if( nID & PH7_TKWRD_STRING ){ + zTypeCast = "(string)"; + }else if( nID & PH7_TKWRD_ARRAY ){ + zTypeCast = "(array)"; + }else if( nID & PH7_TKWRD_OBJECT ){ + zTypeCast = "(object)"; + }else if( nID & PH7_TKWRD_UNSET ){ + zTypeCast = "(unset)"; + } + /* Reflect the change */ + pToken->nType = PH7_TK_OP; + SyStringInitFromBuf(&pToken->sData,zTypeCast,SyStrlen(zTypeCast)); + /* Save the instance associated with the type cast operator */ + pToken->pUserData = (void *)PH7_ExprExtractOperator(&pToken->sData,0); + /* Remove the two previous tokens */ + pTokSet->nUsed -= 2; + return SXRET_OK; + } + } + } + } + pToken->nType = PH7_TK_RPAREN; + break; + } + case '\'':{ + /* Single quoted string */ + pStr->zString++; + while( pStream->zText < pStream->zEnd ){ + if( pStream->zText[0] == '\'' ){ + if( pStream->zText[-1] != '\\' ){ + break; + }else{ + const unsigned char *zPtr = &pStream->zText[-2]; + sxi32 i = 1; + while( zPtr > pStream->zInput && zPtr[0] == '\\' ){ + zPtr--; + i++; + } + if((i&1)==0){ + break; + } + } + } + if( pStream->zText[0] == '\n' ){ + pStream->nLine++; + } + pStream->zText++; + } + /* Record token length and type */ + pStr->nByte = (sxu32)((const char *)pStream->zText-pStr->zString); + pToken->nType = PH7_TK_SSTR; + /* Jump the trailing single quote */ + pStream->zText++; + return SXRET_OK; + } + case '"':{ + sxi32 iNest; + /* Double quoted string */ + pStr->zString++; + while( pStream->zText < pStream->zEnd ){ + if( pStream->zText[0] == '{' && &pStream->zText[1] < pStream->zEnd && pStream->zText[1] == '$'){ + iNest = 1; + pStream->zText++; + /* TICKET 1433-40: Hnadle braces'{}' in double quoted string where everything is allowed */ + while(pStream->zText < pStream->zEnd ){ + if( pStream->zText[0] == '{' ){ + iNest++; + }else if (pStream->zText[0] == '}' ){ + iNest--; + if( iNest <= 0 ){ + pStream->zText++; + break; + } + }else if( pStream->zText[0] == '\n' ){ + pStream->nLine++; + } + pStream->zText++; + } + if( pStream->zText >= pStream->zEnd ){ + break; + } + } + if( pStream->zText[0] == '"' ){ + if( pStream->zText[-1] != '\\' ){ + break; + }else{ + const unsigned char *zPtr = &pStream->zText[-2]; + sxi32 i = 1; + while( zPtr > pStream->zInput && zPtr[0] == '\\' ){ + zPtr--; + i++; + } + if((i&1)==0){ + break; + } + } + } + if( pStream->zText[0] == '\n' ){ + pStream->nLine++; + } + pStream->zText++; + } + /* Record token length and type */ + pStr->nByte = (sxu32)((const char *)pStream->zText-pStr->zString); + pToken->nType = PH7_TK_DSTR; + /* Jump the trailing quote */ + pStream->zText++; + return SXRET_OK; + } + case '`':{ + /* Backtick quoted string */ + pStr->zString++; + while( pStream->zText < pStream->zEnd ){ + if( pStream->zText[0] == '`' && pStream->zText[-1] != '\\' ){ + break; + } + if( pStream->zText[0] == '\n' ){ + pStream->nLine++; + } + pStream->zText++; + } + /* Record token length and type */ + pStr->nByte = (sxu32)((const char *)pStream->zText-pStr->zString); + pToken->nType = PH7_TK_BSTR; + /* Jump the trailing backtick */ + pStream->zText++; + return SXRET_OK; + } + case '\\': pToken->nType = PH7_TK_NSSEP; break; + case ':': + if( pStream->zText < pStream->zEnd && pStream->zText[0] == ':' ){ + /* Current operator: '::' */ + pStream->zText++; + }else{ + pToken->nType = PH7_TK_COLON; /* Single colon */ + } + break; + case ',': pToken->nType |= PH7_TK_COMMA; break; /* Comma is also an operator */ + case ';': pToken->nType = PH7_TK_SEMI; break; + /* Handle combined operators [i.e: +=,===,!=== ...] */ + case '=': + pToken->nType |= PH7_TK_EQUAL; + if( pStream->zText < pStream->zEnd ){ + if( pStream->zText[0] == '=' ){ + pToken->nType &= ~PH7_TK_EQUAL; + /* Current operator: == */ + pStream->zText++; + if( pStream->zText < pStream->zEnd && pStream->zText[0] == '=' ){ + /* Current operator: === */ + pStream->zText++; + } + }else if( pStream->zText[0] == '>' ){ + /* Array operator: => */ + pToken->nType = PH7_TK_ARRAY_OP; + pStream->zText++; + }else{ + /* TICKET 1433-0010: Reference operator '=&' */ + const unsigned char *zCur = pStream->zText; + sxu32 nLine = 0; + while( zCur < pStream->zEnd && zCur[0] < 0xc0 && SyisSpace(zCur[0]) ){ + if( zCur[0] == '\n' ){ + nLine++; + } + zCur++; + } + if( zCur < pStream->zEnd && zCur[0] == '&' ){ + /* Current operator: =& */ + pToken->nType &= ~PH7_TK_EQUAL; + SyStringInitFromBuf(pStr,"=&",sizeof("=&")-1); + /* Update token stream */ + pStream->zText = &zCur[1]; + pStream->nLine += nLine; + } + } + } + break; + case '!': + if( pStream->zText < pStream->zEnd && pStream->zText[0] == '=' ){ + /* Current operator: != */ + pStream->zText++; + if( pStream->zText < pStream->zEnd && pStream->zText[0] == '=' ){ + /* Current operator: !== */ + pStream->zText++; + } + } + break; + case '&': + pToken->nType |= PH7_TK_AMPER; + if( pStream->zText < pStream->zEnd ){ + if( pStream->zText[0] == '&' ){ + pToken->nType &= ~PH7_TK_AMPER; + /* Current operator: && */ + pStream->zText++; + }else if( pStream->zText[0] == '=' ){ + pToken->nType &= ~PH7_TK_AMPER; + /* Current operator: &= */ + pStream->zText++; + } + } + break; + case '|': + if( pStream->zText < pStream->zEnd ){ + if( pStream->zText[0] == '|' ){ + /* Current operator: || */ + pStream->zText++; + }else if( pStream->zText[0] == '=' ){ + /* Current operator: |= */ + pStream->zText++; + } + } + break; + case '+': + if( pStream->zText < pStream->zEnd ){ + if( pStream->zText[0] == '+' ){ + /* Current operator: ++ */ + pStream->zText++; + }else if( pStream->zText[0] == '=' ){ + /* Current operator: += */ + pStream->zText++; + } + } + break; + case '-': + if( pStream->zText < pStream->zEnd ){ + if( pStream->zText[0] == '-' ){ + /* Current operator: -- */ + pStream->zText++; + }else if( pStream->zText[0] == '=' ){ + /* Current operator: -= */ + pStream->zText++; + }else if( pStream->zText[0] == '>' ){ + /* Current operator: -> */ + pStream->zText++; + } + } + break; + case '*': + if( pStream->zText < pStream->zEnd && pStream->zText[0] == '=' ){ + /* Current operator: *= */ + pStream->zText++; + } + break; + case '/': + if( pStream->zText < pStream->zEnd && pStream->zText[0] == '=' ){ + /* Current operator: /= */ + pStream->zText++; + } + break; + case '%': + if( pStream->zText < pStream->zEnd && pStream->zText[0] == '=' ){ + /* Current operator: %= */ + pStream->zText++; + } + break; + case '^': + if( pStream->zText < pStream->zEnd && pStream->zText[0] == '=' ){ + /* Current operator: ^= */ + pStream->zText++; + } + break; + case '.': + if( pStream->zText < pStream->zEnd && pStream->zText[0] == '=' ){ + /* Current operator: .= */ + pStream->zText++; + } + break; + case '<': + if( pStream->zText < pStream->zEnd ){ + if( pStream->zText[0] == '<' ){ + /* Current operator: << */ + pStream->zText++; + if( pStream->zText < pStream->zEnd ){ + if( pStream->zText[0] == '=' ){ + /* Current operator: <<= */ + pStream->zText++; + }else if( pStream->zText[0] == '<' ){ + /* Current Token: <<< */ + pStream->zText++; + /* This may be the beginning of a Heredoc/Nowdoc string,try to delimit it */ + rc = LexExtractHeredoc(&(*pStream),&(*pToken)); + if( rc == SXRET_OK ){ + /* Here/Now doc successfuly extracted */ + return SXRET_OK; + } + } + } + }else if( pStream->zText[0] == '>' ){ + /* Current operator: <> */ + pStream->zText++; + }else if( pStream->zText[0] == '=' ){ + /* Current operator: <= */ + pStream->zText++; + } + } + break; + case '>': + if( pStream->zText < pStream->zEnd ){ + if( pStream->zText[0] == '>' ){ + /* Current operator: >> */ + pStream->zText++; + if( pStream->zText < pStream->zEnd && pStream->zText[0] == '=' ){ + /* Current operator: >>= */ + pStream->zText++; + } + }else if( pStream->zText[0] == '=' ){ + /* Current operator: >= */ + pStream->zText++; + } + } + break; + default: + break; + } + if( pStr->nByte <= 0 ){ + /* Record token length */ + pStr->nByte = (sxu32)((const char *)pStream->zText-pStr->zString); + } + if( pToken->nType & PH7_TK_OP ){ + const ph7_expr_op *pOp; + /* Check if the extracted token is an operator */ + pOp = PH7_ExprExtractOperator(pStr,(SyToken *)SySetPeek(pStream->pSet)); + if( pOp == 0 ){ + /* Not an operator */ + pToken->nType &= ~PH7_TK_OP; + if( pToken->nType <= 0 ){ + pToken->nType = PH7_TK_OTHER; + } + }else{ + /* Save the instance associated with this operator for later processing */ + pToken->pUserData = (void *)pOp; + } + } + } + /* Tell the upper-layer to save the extracted token for later processing */ + return SXRET_OK; +} +/***** This file contains automatically generated code ****** +** +** The code in this file has been automatically generated by +** +** $Header: /sqlite/sqlite/tool/mkkeywordhash.c +** +** Sligthly modified by Chems mrad for the PH7 engine. +** +** The code in this file implements a function that determines whether +** or not a given identifier is really a PHP keyword. The same thing +** might be implemented more directly using a hand-written hash table. +** But by using this automatically generated code, the size of the code +** is substantially reduced. This is important for embedded applications +** on platforms with limited memory. +*/ +/* Hash score: 103 */ +static sxu32 KeywordCode(const char *z, int n){ + /* zText[] encodes 532 bytes of keywords in 333 bytes */ + /* extendswitchprintegerequire_oncenddeclareturnamespacechobject */ + /* hrowbooleandefaultrycaselfinalistaticlonewconstringlobaluse */ + /* lseifloatvarrayANDIEchoUSECHOabstractclasscontinuendifunction */ + /* diendwhilevaldoexitgotoimplementsinclude_oncemptyinstanceof */ + /* interfacendforeachissetparentprivateprotectedpublicatchunset */ + /* xorARRAYASArrayEXITUNSETXORbreak */ + static const char zText[332] = { + 'e','x','t','e','n','d','s','w','i','t','c','h','p','r','i','n','t','e', + 'g','e','r','e','q','u','i','r','e','_','o','n','c','e','n','d','d','e', + 'c','l','a','r','e','t','u','r','n','a','m','e','s','p','a','c','e','c', + 'h','o','b','j','e','c','t','h','r','o','w','b','o','o','l','e','a','n', + 'd','e','f','a','u','l','t','r','y','c','a','s','e','l','f','i','n','a', + 'l','i','s','t','a','t','i','c','l','o','n','e','w','c','o','n','s','t', + 'r','i','n','g','l','o','b','a','l','u','s','e','l','s','e','i','f','l', + 'o','a','t','v','a','r','r','a','y','A','N','D','I','E','c','h','o','U', + 'S','E','C','H','O','a','b','s','t','r','a','c','t','c','l','a','s','s', + 'c','o','n','t','i','n','u','e','n','d','i','f','u','n','c','t','i','o', + 'n','d','i','e','n','d','w','h','i','l','e','v','a','l','d','o','e','x', + 'i','t','g','o','t','o','i','m','p','l','e','m','e','n','t','s','i','n', + 'c','l','u','d','e','_','o','n','c','e','m','p','t','y','i','n','s','t', + 'a','n','c','e','o','f','i','n','t','e','r','f','a','c','e','n','d','f', + 'o','r','e','a','c','h','i','s','s','e','t','p','a','r','e','n','t','p', + 'r','i','v','a','t','e','p','r','o','t','e','c','t','e','d','p','u','b', + 'l','i','c','a','t','c','h','u','n','s','e','t','x','o','r','A','R','R', + 'A','Y','A','S','A','r','r','a','y','E','X','I','T','U','N','S','E','T', + 'X','O','R','b','r','e','a','k' + }; + static const unsigned char aHash[151] = { + 0, 0, 4, 83, 0, 61, 39, 12, 0, 33, 77, 0, 48, + 0, 2, 65, 67, 0, 0, 0, 47, 0, 0, 40, 0, 15, + 74, 0, 51, 0, 76, 0, 0, 20, 0, 0, 0, 50, 0, + 80, 34, 0, 36, 0, 0, 64, 16, 0, 0, 17, 0, 1, + 19, 84, 66, 0, 43, 45, 78, 0, 0, 53, 56, 0, 0, + 0, 23, 49, 0, 0, 13, 31, 54, 7, 0, 0, 25, 0, + 72, 14, 0, 71, 0, 38, 6, 0, 0, 0, 73, 0, 0, + 3, 0, 41, 5, 52, 57, 32, 0, 60, 63, 0, 69, 82, + 30, 0, 79, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 81, 0, 0, + 62, 0, 11, 0, 0, 58, 0, 0, 0, 0, 59, 75, 0, + 0, 0, 0, 0, 0, 35, 27, 0 + }; + static const unsigned char aNext[84] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 8, 0, 0, 0, 10, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 28, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 44, 0, 18, 0, 0, 0, 0, 0, + 0, 46, 0, 29, 0, 0, 0, 22, 0, 0, 0, 0, 26, + 0, 21, 24, 0, 0, 68, 0, 0, 9, 37, 0, 0, 0, + 42, 0, 0, 0, 70, 55 + }; + static const unsigned char aLen[84] = { + 7, 9, 6, 5, 7, 12, 7, 2, 10, 7, 6, 9, 4, + 6, 5, 7, 4, 3, 7, 3, 4, 4, 5, 4, 6, 5, + 2, 3, 5, 6, 6, 3, 6, 4, 2, 5, 3, 5, 3, + 3, 4, 3, 4, 8, 5, 2, 8, 5, 8, 3, 8, 5, + 4, 2, 4, 4, 10, 12, 7, 5, 10, 9, 3, 6, 10, + 3, 7, 2, 5, 6, 7, 9, 6, 5, 5, 3, 5, 2, + 5, 4, 5, 3, 2, 5 + }; + static const sxu16 aOffset[84] = { + 0, 3, 6, 12, 14, 20, 20, 21, 31, 34, 39, 44, 52, + 55, 60, 65, 65, 70, 72, 78, 81, 83, 86, 90, 92, 97, + 100, 100, 103, 106, 111, 117, 119, 119, 123, 124, 129, 130, 135, + 137, 139, 143, 145, 149, 157, 159, 162, 169, 173, 181, 183, 186, + 190, 194, 196, 200, 204, 214, 214, 225, 230, 240, 240, 248, 248, + 251, 251, 252, 258, 263, 269, 276, 285, 290, 295, 300, 303, 308, + 310, 315, 319, 324, 325, 327 + }; + static const sxu32 aCode[84] = { + PH7_TKWRD_EXTENDS, PH7_TKWRD_ENDSWITCH, PH7_TKWRD_SWITCH, PH7_TKWRD_PRINT, PH7_TKWRD_INT, + PH7_TKWRD_REQONCE, PH7_TKWRD_REQUIRE, PH7_TKWRD_SEQ, PH7_TKWRD_ENDDEC, PH7_TKWRD_DECLARE, + PH7_TKWRD_RETURN, PH7_TKWRD_NAMESPACE, PH7_TKWRD_ECHO, PH7_TKWRD_OBJECT, PH7_TKWRD_THROW, + PH7_TKWRD_BOOL, PH7_TKWRD_BOOL, PH7_TKWRD_AND, PH7_TKWRD_DEFAULT, PH7_TKWRD_TRY, + PH7_TKWRD_CASE, PH7_TKWRD_SELF, PH7_TKWRD_FINAL, PH7_TKWRD_LIST, PH7_TKWRD_STATIC, + PH7_TKWRD_CLONE, PH7_TKWRD_SNE, PH7_TKWRD_NEW, PH7_TKWRD_CONST, PH7_TKWRD_STRING, + PH7_TKWRD_GLOBAL, PH7_TKWRD_USE, PH7_TKWRD_ELIF, PH7_TKWRD_ELSE, PH7_TKWRD_IF, + PH7_TKWRD_FLOAT, PH7_TKWRD_VAR, PH7_TKWRD_ARRAY, PH7_TKWRD_AND, PH7_TKWRD_DIE, + PH7_TKWRD_ECHO, PH7_TKWRD_USE, PH7_TKWRD_ECHO, PH7_TKWRD_ABSTRACT, PH7_TKWRD_CLASS, + PH7_TKWRD_AS, PH7_TKWRD_CONTINUE, PH7_TKWRD_ENDIF, PH7_TKWRD_FUNCTION, PH7_TKWRD_DIE, + PH7_TKWRD_ENDWHILE, PH7_TKWRD_WHILE, PH7_TKWRD_EVAL, PH7_TKWRD_DO, PH7_TKWRD_EXIT, + PH7_TKWRD_GOTO, PH7_TKWRD_IMPLEMENTS, PH7_TKWRD_INCONCE, PH7_TKWRD_INCLUDE, PH7_TKWRD_EMPTY, + PH7_TKWRD_INSTANCEOF,PH7_TKWRD_INTERFACE, PH7_TKWRD_INT, PH7_TKWRD_ENDFOR, PH7_TKWRD_END4EACH, + PH7_TKWRD_FOR, PH7_TKWRD_FOREACH, PH7_TKWRD_OR, PH7_TKWRD_ISSET, PH7_TKWRD_PARENT, + PH7_TKWRD_PRIVATE, PH7_TKWRD_PROTECTED, PH7_TKWRD_PUBLIC, PH7_TKWRD_CATCH, PH7_TKWRD_UNSET, + PH7_TKWRD_XOR, PH7_TKWRD_ARRAY, PH7_TKWRD_AS, PH7_TKWRD_ARRAY, PH7_TKWRD_EXIT, + PH7_TKWRD_UNSET, PH7_TKWRD_XOR, PH7_TKWRD_OR, PH7_TKWRD_BREAK + }; + int h, i; + if( n<2 ) return PH7_TK_ID; + h = (((int)z[0]*4) ^ ((int)z[n-1]*3) ^ n) % 151; + for(i=((int)aHash[h])-1; i>=0; i=((int)aNext[i])-1){ + if( (int)aLen[i]==n && SyMemcmp(&zText[aOffset[i]],z,n)==0 ){ + /* PH7_TKWRD_EXTENDS */ + /* PH7_TKWRD_ENDSWITCH */ + /* PH7_TKWRD_SWITCH */ + /* PH7_TKWRD_PRINT */ + /* PH7_TKWRD_INT */ + /* PH7_TKWRD_REQONCE */ + /* PH7_TKWRD_REQUIRE */ + /* PH7_TKWRD_SEQ */ + /* PH7_TKWRD_ENDDEC */ + /* PH7_TKWRD_DECLARE */ + /* PH7_TKWRD_RETURN */ + /* PH7_TKWRD_NAMESPACE */ + /* PH7_TKWRD_ECHO */ + /* PH7_TKWRD_OBJECT */ + /* PH7_TKWRD_THROW */ + /* PH7_TKWRD_BOOL */ + /* PH7_TKWRD_BOOL */ + /* PH7_TKWRD_AND */ + /* PH7_TKWRD_DEFAULT */ + /* PH7_TKWRD_TRY */ + /* PH7_TKWRD_CASE */ + /* PH7_TKWRD_SELF */ + /* PH7_TKWRD_FINAL */ + /* PH7_TKWRD_LIST */ + /* PH7_TKWRD_STATIC */ + /* PH7_TKWRD_CLONE */ + /* PH7_TKWRD_SNE */ + /* PH7_TKWRD_NEW */ + /* PH7_TKWRD_CONST */ + /* PH7_TKWRD_STRING */ + /* PH7_TKWRD_GLOBAL */ + /* PH7_TKWRD_USE */ + /* PH7_TKWRD_ELIF */ + /* PH7_TKWRD_ELSE */ + /* PH7_TKWRD_IF */ + /* PH7_TKWRD_FLOAT */ + /* PH7_TKWRD_VAR */ + /* PH7_TKWRD_ARRAY */ + /* PH7_TKWRD_AND */ + /* PH7_TKWRD_DIE */ + /* PH7_TKWRD_ECHO */ + /* PH7_TKWRD_USE */ + /* PH7_TKWRD_ECHO */ + /* PH7_TKWRD_ABSTRACT */ + /* PH7_TKWRD_CLASS */ + /* PH7_TKWRD_AS */ + /* PH7_TKWRD_CONTINUE */ + /* PH7_TKWRD_ENDIF */ + /* PH7_TKWRD_FUNCTION */ + /* PH7_TKWRD_DIE */ + /* PH7_TKWRD_ENDWHILE */ + /* PH7_TKWRD_WHILE */ + /* PH7_TKWRD_EVAL */ + /* PH7_TKWRD_DO */ + /* PH7_TKWRD_EXIT */ + /* PH7_TKWRD_GOTO */ + /* PH7_TKWRD_IMPLEMENTS */ + /* PH7_TKWRD_INCONCE */ + /* PH7_TKWRD_INCLUDE */ + /* PH7_TKWRD_EMPTY */ + /* PH7_TKWRD_INSTANCEOF */ + /* PH7_TKWRD_INTERFACE */ + /* PH7_TKWRD_INT */ + /* PH7_TKWRD_ENDFOR */ + /* PH7_TKWRD_END4EACH */ + /* PH7_TKWRD_FOR */ + /* PH7_TKWRD_FOREACH */ + /* PH7_TKWRD_OR */ + /* PH7_TKWRD_ISSET */ + /* PH7_TKWRD_PARENT */ + /* PH7_TKWRD_PRIVATE */ + /* PH7_TKWRD_PROTECTED */ + /* PH7_TKWRD_PUBLIC */ + /* PH7_TKWRD_CATCH */ + /* PH7_TKWRD_UNSET */ + /* PH7_TKWRD_XOR */ + /* PH7_TKWRD_ARRAY */ + /* PH7_TKWRD_AS */ + /* PH7_TKWRD_ARRAY */ + /* PH7_TKWRD_EXIT */ + /* PH7_TKWRD_UNSET */ + /* PH7_TKWRD_XOR */ + /* PH7_TKWRD_OR */ + /* PH7_TKWRD_BREAK */ + return aCode[i]; + } + } + return PH7_TK_ID; +} +/* --- End of Automatically generated code --- */ +/* + * Extract a heredoc/nowdoc text from a raw PHP input. + * According to the PHP language reference manual: + * A third way to delimit strings is the heredoc syntax: <<<. After this operator, an identifier + * is provided, then a newline. The string itself follows, and then the same identifier again + * to close the quotation. + * The closing identifier must begin in the first column of the line. Also, the identifier must + * follow the same naming rules as any other label in PHP: it must contain only alphanumeric + * characters and underscores, and must start with a non-digit character or underscore. + * Heredoc text behaves just like a double-quoted string, without the double quotes. + * This means that quotes in a heredoc do not need to be escaped, but the escape codes listed + * above can still be used. Variables are expanded, but the same care must be taken when expressing + * complex variables inside a heredoc as with strings. + * Nowdocs are to single-quoted strings what heredocs are to double-quoted strings. + * A nowdoc is specified similarly to a heredoc, but no parsing is done inside a nowdoc. + * The construct is ideal for embedding PHP code or other large blocks of text without the need + * for escaping. It shares some features in common with the SGML construct, in that + * it declares a block of text which is not for parsing. + * A nowdoc is identified with the same <<< sequence used for heredocs, but the identifier which follows + * is enclosed in single quotes, e.g. <<<'EOT'. All the rules for heredoc identifiers also apply to nowdoc + * identifiers, especially those regarding the appearance of the closing identifier. + * Symisc Extension: + * The closing delimiter can now start with a digit or undersocre or it can be an UTF-8 stream. + * Example: + * <<<123 + * HEREDOC Here + * 123 + * or + * <<<___ + * HEREDOC Here + * ___ + */ +static sxi32 LexExtractHeredoc(SyStream *pStream,SyToken *pToken) +{ + const unsigned char *zIn = pStream->zText; + const unsigned char *zEnd = pStream->zEnd; + const unsigned char *zPtr; + sxu8 bNowDoc = FALSE; + SyString sDelim; + SyString sStr; + /* Jump leading white spaces */ + while( zIn < zEnd && zIn[0] < 0xc0 && SyisSpace(zIn[0]) && zIn[0] != '\n' ){ + zIn++; + } + if( zIn >= zEnd ){ + /* A simple symbol,return immediately */ + return SXERR_CONTINUE; + } + if( zIn[0] == '\'' || zIn[0] == '"' ){ + /* Make sure we are dealing with a nowdoc */ + bNowDoc = zIn[0] == '\'' ? TRUE : FALSE; + zIn++; + } + if( zIn[0] < 0xc0 && !SyisAlphaNum(zIn[0]) && zIn[0] != '_' ){ + /* Invalid delimiter,return immediately */ + return SXERR_CONTINUE; + } + /* Isolate the identifier */ + sDelim.zString = (const char *)zIn; + for(;;){ + zPtr = zIn; + /* Skip alphanumeric stream */ + while( zPtr < zEnd && zPtr[0] < 0xc0 && (SyisAlphaNum(zPtr[0]) || zPtr[0] == '_') ){ + zPtr++; + } + if( zPtr < zEnd && zPtr[0] >= 0xc0 ){ + zPtr++; + /* UTF-8 stream */ + while( zPtr < zEnd && ((zPtr[0] & 0xc0) == 0x80) ){ + zPtr++; + } + } + if( zPtr == zIn ){ + /* Not an UTF-8 or alphanumeric stream */ + break; + } + /* Synchronize pointers */ + zIn = zPtr; + } + /* Get the identifier length */ + sDelim.nByte = (sxu32)((const char *)zIn-sDelim.zString); + if( zIn[0] == '"' || (bNowDoc && zIn[0] == '\'') ){ + /* Jump the trailing single quote */ + zIn++; + } + /* Jump trailing white spaces */ + while( zIn < zEnd && zIn[0] < 0xc0 && SyisSpace(zIn[0]) && zIn[0] != '\n' ){ + zIn++; + } + if( sDelim.nByte <= 0 || zIn >= zEnd || zIn[0] != '\n' ){ + /* Invalid syntax */ + return SXERR_CONTINUE; + } + pStream->nLine++; /* Increment line counter */ + zIn++; + /* Isolate the delimited string */ + sStr.zString = (const char *)zIn; + /* Go and found the closing delimiter */ + for(;;){ + /* Synchronize with the next line */ + while( zIn < zEnd && zIn[0] != '\n' ){ + zIn++; + } + if( zIn >= zEnd ){ + /* End of the input reached, break immediately */ + pStream->zText = pStream->zEnd; + break; + } + pStream->nLine++; /* Increment line counter */ + zIn++; + if( (sxu32)(zEnd - zIn) >= sDelim.nByte && SyMemcmp((const void *)sDelim.zString,(const void *)zIn,sDelim.nByte) == 0 ){ + zPtr = &zIn[sDelim.nByte]; + while( zPtr < zEnd && zPtr[0] < 0xc0 && SyisSpace(zPtr[0]) && zPtr[0] != '\n' ){ + zPtr++; + } + if( zPtr >= zEnd ){ + /* End of input */ + pStream->zText = zPtr; + break; + } + if( zPtr[0] == ';' ){ + const unsigned char *zCur = zPtr; + zPtr++; + while( zPtr < zEnd && zPtr[0] < 0xc0 && SyisSpace(zPtr[0]) && zPtr[0] != '\n' ){ + zPtr++; + } + if( zPtr >= zEnd || zPtr[0] == '\n' ){ + /* Closing delimiter found,break immediately */ + pStream->zText = zCur; /* Keep the semi-colon */ + break; + } + }else if( zPtr[0] == '\n' ){ + /* Closing delimiter found,break immediately */ + pStream->zText = zPtr; /* Synchronize with the stream cursor */ + break; + } + /* Synchronize pointers and continue searching */ + zIn = zPtr; + } + } /* For(;;) */ + /* Get the delimited string length */ + sStr.nByte = (sxu32)((const char *)zIn-sStr.zString); + /* Record token type and length */ + pToken->nType = bNowDoc ? PH7_TK_NOWDOC : PH7_TK_HEREDOC; + SyStringDupPtr(&pToken->sData,&sStr); + /* Remove trailing white spaces */ + SyStringRightTrim(&pToken->sData); + /* All done */ + return SXRET_OK; +} +/* + * Tokenize a raw PHP input. + * This is the public tokenizer called by most code generator routines. + */ +PH7_PRIVATE sxi32 PH7_TokenizePHP(const char *zInput,sxu32 nLen,sxu32 nLineStart,SySet *pOut) +{ + SyLex sLexer; + sxi32 rc; + /* Initialize the lexer */ + rc = SyLexInit(&sLexer,&(*pOut),TokenizePHP,0); + if( rc != SXRET_OK ){ + return rc; + } + sLexer.sStream.nLine = nLineStart; + /* Tokenize input */ + rc = SyLexTokenizeInput(&sLexer,zInput,nLen,0,0,0); + /* Release the lexer */ + SyLexRelease(&sLexer); + /* Tokenization result */ + return rc; +} +/* + * High level public tokenizer. + * Tokenize the input into PHP tokens and raw tokens [i.e: HTML,XML,Raw text...]. + * According to the PHP language reference manual + * When PHP parses a file, it looks for opening and closing tags, which tell PHP + * to start and stop interpreting the code between them. Parsing in this manner allows + * PHP to be embedded in all sorts of different documents, as everything outside of a pair + * of opening and closing tags is ignored by the PHP parser. Most of the time you will see + * PHP embedded in HTML documents, as in this example. + * + *

This will also be ignored.

+ * You can also use more advanced structures: + * Example #1 Advanced escaping + * + * This is true. + * + * This is false. + * + * This works as expected, because when PHP hits the ?> closing tags, it simply starts outputting + * whatever it finds (except for an immediately following newline - see instruction separation ) until it hits + * another opening tag. The example given here is contrived, of course, but for outputting large blocks of text + * dropping out of PHP parsing mode is generally more efficient than sending all of the text through echo() or print(). + * There are four different pairs of opening and closing tags which can be used in PHP. Three of those, + * and are always available. The other two are short tags and ASP style + * tags, and can be turned on and off from the php.ini configuration file. As such, while some people find short tags + * and ASP style tags convenient, they are less portable, and generally not recommended. + * Note: + * Also note that if you are embedding PHP within XML or XHTML you will need to use the tags to remain + * compliant with standards. + * Example #2 PHP Opening and Closing Tags + * 1. + * 2. + * + * 3. + * This is a shortcut for "" + */ +PH7_PRIVATE sxi32 PH7_TokenizeRawText(const char *zInput,sxu32 nLen,SySet *pOut) +{ + const char *zEnd = &zInput[nLen]; + const char *zIn = zInput; + const char *zCur,*zCurEnd; + SyString sCtag = { 0, 0 }; /* Closing tag */ + SyToken sToken; + SyString sDoc; + sxu32 nLine; + sxi32 iNest; + sxi32 rc; + /* Tokenize the input into PHP tokens and raw tokens */ + nLine = 1; + zCur = zCurEnd = 0; /* Prevent compiler warning */ + sToken.pUserData = 0; + iNest = 0; + sDoc.nByte = 0; + sDoc.zString = ""; /* cc warning */ + for(;;){ + if( zIn >= zEnd ){ + /* End of input reached */ + break; + } + sToken.nLine = nLine; + zCur = zIn; + zCurEnd = 0; + while( zIn < zEnd ){ + if( zIn[0] == '<' ){ + const char *zTmp = zIn; /* End of raw input marker */ + zIn++; + if( zIn < zEnd ){ + if( zIn[0] == '?' ){ + zIn++; + if( (sxu32)(zEnd - zIn) >= sizeof("php")-1 && SyStrnicmp(zIn,"php",sizeof("php")-1) == 0 ){ + /* opening tag: ' */ + SyStringInitFromBuf(&sCtag,"?>",sizeof("?>")-1); + zCurEnd = zTmp; + break; + } + } + }else{ + if( zIn[0] == '\n' ){ + nLine++; + } + zIn++; + } + } /* While(zIn < zEnd) */ + if( zCurEnd == 0 ){ + zCurEnd = zIn; + } + /* Save the raw token */ + SyStringInitFromBuf(&sToken.sData,zCur,zCurEnd - zCur); + sToken.nType = PH7_TOKEN_RAW; + rc = SySetPut(&(*pOut),(const void *)&sToken); + if( rc != SXRET_OK ){ + return rc; + } + if( zIn >= zEnd ){ + break; + } + /* Ignore leading white space */ + while( zIn < zEnd && (unsigned char)zIn[0] < 0xc0 && SyisSpace(zIn[0]) ){ + if( zIn[0] == '\n' ){ + nLine++; + } + zIn++; + } + /* Delimit the PHP chunk */ + sToken.nLine = nLine; + zCur = zIn; + while( (sxu32)(zEnd - zIn) >= sCtag.nByte ){ + const char *zPtr; + if( SyMemcmp(zIn,sCtag.zString,sCtag.nByte) == 0 && iNest < 1 ){ + break; + } + for(;;){ + if( zIn[0] != '/' || (zIn[1] != '*' && zIn[1] != '/') /* && sCtag.nByte >= 2 */ ){ + break; + } + zIn += 2; + if( zIn[-1] == '/' ){ + /* Inline comment */ + while( zIn < zEnd && zIn[0] != '\n' ){ + zIn++; + } + if( zIn >= zEnd ){ + zIn--; + } + }else{ + /* Block comment */ + while( (sxu32)(zEnd-zIn) >= sizeof("*/") - 1 ){ + if( zIn[0] == '*' && zIn[1] == '/' ){ + zIn += 2; + break; + } + if( zIn[0] == '\n' ){ + nLine++; + } + zIn++; + } + } + } + if( zIn[0] == '\n' ){ + nLine++; + if( iNest > 0 ){ + zIn++; + while( zIn < zEnd && (unsigned char)zIn[0] < 0xc0 && SyisSpace(zIn[0]) && zIn[0] != '\n' ){ + zIn++; + } + zPtr = zIn; + while( zIn < zEnd ){ + if( (unsigned char)zIn[0] >= 0xc0 ){ + /* UTF-8 stream */ + zIn++; + SX_JMP_UTF8(zIn,zEnd); + }else if( !SyisAlphaNum(zIn[0]) && zIn[0] != '_' ){ + break; + }else{ + zIn++; + } + } + if( (sxu32)(zIn - zPtr) == sDoc.nByte && SyMemcmp(sDoc.zString,zPtr,sDoc.nByte) == 0 ){ + iNest = 0; + } + continue; + } + }else if ( (sxu32)(zEnd - zIn) >= sizeof("<<<") && zIn[0] == '<' && zIn[1] == '<' && zIn[2] == '<' && iNest < 1){ + zIn += sizeof("<<<")-1; + while( zIn < zEnd && (unsigned char)zIn[0] < 0xc0 && SyisSpace(zIn[0]) && zIn[0] != '\n' ){ + zIn++; + } + if( zIn[0] == '"' || zIn[0] == '\'' ){ + zIn++; + } + zPtr = zIn; + while( zIn < zEnd ){ + if( (unsigned char)zIn[0] >= 0xc0 ){ + /* UTF-8 stream */ + zIn++; + SX_JMP_UTF8(zIn,zEnd); + }else if( !SyisAlphaNum(zIn[0]) && zIn[0] != '_' ){ + break; + }else{ + zIn++; + } + } + SyStringInitFromBuf(&sDoc,zPtr,zIn-zPtr); + SyStringFullTrim(&sDoc); + if( sDoc.nByte > 0 ){ + iNest++; + } + continue; + } + zIn++; + + if ( zIn >= zEnd ) + break; + } + if( (sxu32)(zEnd - zIn) < sCtag.nByte ){ + zIn = zEnd; + } + if( zCur < zIn ){ + /* Save the PHP chunk for later processing */ + sToken.nType = PH7_TOKEN_PHP; + SyStringInitFromBuf(&sToken.sData,zCur,zIn-zCur); + SyStringRightTrim(&sToken.sData); /* Trim trailing white spaces */ + rc = SySetPut(&(*pOut),(const void *)&sToken); + if( rc != SXRET_OK ){ + return rc; + } + } + if( zIn < zEnd ){ + /* Jump the trailing closing tag */ + zIn += sCtag.nByte; + } + } /* For(;;) */ + + return SXRET_OK; +} + +/* + * ---------------------------------------------------------- + * File: hashmap.c + * MD5: cf4287c2602a9c97df208364cb9be084 + * ---------------------------------------------------------- + */ +/* + * Symisc PH7: An embeddable bytecode compiler and a virtual machine for the PHP(5) programming language. + * Copyright (C) 2011-2012, Symisc Systems http://ph7.symisc.net/ + * Version 2.1.4 + * For information on licensing,redistribution of this file,and for a DISCLAIMER OF ALL WARRANTIES + * please contact Symisc Systems via: + * legal@symisc.net + * licensing@symisc.net + * contact@symisc.net + * or visit: + * http://ph7.symisc.net/ + */ + /* $SymiscID: hashmap.c v3.5 FreeBSD 2012-08-07 08:29 stable $ */ +#ifndef PH7_AMALGAMATION +#include "ph7int.h" +#endif +/* This file implement generic hashmaps known as 'array' in the PHP world */ +/* Allowed node types */ +#define HASHMAP_INT_NODE 1 /* Node with an int [i.e: 64-bit integer] key */ +#define HASHMAP_BLOB_NODE 2 /* Node with a string/BLOB key */ +/* Node control flags */ +#define HASHMAP_NODE_FOREIGN_OBJ 0x001 /* Node hold a reference to a foreign ph7_value + * [i.e: array(&var)/$a[] =& $var ] + */ +/* + * Default hash function for int [i.e; 64-bit integer] keys. + */ +static sxu32 IntHash(sxi64 iKey) +{ + return (sxu32)(iKey ^ (iKey << 8) ^ (iKey >> 8)); +} +/* + * Default hash function for string/BLOB keys. + */ +static sxu32 BinHash(const void *pSrc,sxu32 nLen) +{ + register unsigned char *zIn = (unsigned char *)pSrc; + unsigned char *zEnd; + sxu32 nH = 5381; + zEnd = &zIn[nLen]; + for(;;){ + if( zIn >= zEnd ){ break; } nH = nH * 33 + zIn[0] ; zIn++; + if( zIn >= zEnd ){ break; } nH = nH * 33 + zIn[0] ; zIn++; + if( zIn >= zEnd ){ break; } nH = nH * 33 + zIn[0] ; zIn++; + if( zIn >= zEnd ){ break; } nH = nH * 33 + zIn[0] ; zIn++; + } + return nH; +} +/* + * Return the total number of entries in a given hashmap. + * If bRecurisve is set to TRUE then recurse on hashmap entries. + * If the nesting limit is reached,this function abort immediately. + */ +static sxi64 HashmapCount(ph7_hashmap *pMap,int bRecursive,int iRecCount) +{ + sxi64 iCount = 0; + if( !bRecursive ){ + iCount = pMap->nEntry; + }else{ + /* Recursive hashmap walk */ + ph7_hashmap_node *pEntry = pMap->pLast; + ph7_value *pElem; + sxu32 n = 0; + for(;;){ + if( n >= pMap->nEntry ){ + break; + } + /* Point to the element value */ + pElem = (ph7_value *)SySetAt(&pMap->pVm->aMemObj,pEntry->nValIdx); + if( pElem ){ + if( pElem->iFlags & MEMOBJ_HASHMAP ){ + if( iRecCount > 31 ){ + /* Nesting limit reached */ + return iCount; + } + /* Recurse */ + iRecCount++; + iCount += HashmapCount((ph7_hashmap *)pElem->x.pOther,TRUE,iRecCount); + iRecCount--; + } + } + /* Point to the next entry */ + pEntry = pEntry->pNext; + ++n; + } + /* Update count */ + iCount += pMap->nEntry; + } + return iCount; +} +/* + * Allocate a new hashmap node with a 64-bit integer key. + * If something goes wrong [i.e: out of memory],this function return NULL. + * Otherwise a fresh [ph7_hashmap_node] instance is returned. + */ +static ph7_hashmap_node * HashmapNewIntNode(ph7_hashmap *pMap,sxi64 iKey,sxu32 nHash,sxu32 nValIdx) +{ + ph7_hashmap_node *pNode; + /* Allocate a new node */ + pNode = (ph7_hashmap_node *)SyMemBackendPoolAlloc(&pMap->pVm->sAllocator,sizeof(ph7_hashmap_node)); + if( pNode == 0 ){ + return 0; + } + /* Zero the stucture */ + SyZero(pNode,sizeof(ph7_hashmap_node)); + /* Fill in the structure */ + pNode->pMap = &(*pMap); + pNode->iType = HASHMAP_INT_NODE; + pNode->nHash = nHash; + pNode->xKey.iKey = iKey; + pNode->nValIdx = nValIdx; + return pNode; +} +/* + * Allocate a new hashmap node with a BLOB key. + * If something goes wrong [i.e: out of memory],this function return NULL. + * Otherwise a fresh [ph7_hashmap_node] instance is returned. + */ +static ph7_hashmap_node * HashmapNewBlobNode(ph7_hashmap *pMap,const void *pKey,sxu32 nKeyLen,sxu32 nHash,sxu32 nValIdx) +{ + ph7_hashmap_node *pNode; + /* Allocate a new node */ + pNode = (ph7_hashmap_node *)SyMemBackendPoolAlloc(&pMap->pVm->sAllocator,sizeof(ph7_hashmap_node)); + if( pNode == 0 ){ + return 0; + } + /* Zero the stucture */ + SyZero(pNode,sizeof(ph7_hashmap_node)); + /* Fill in the structure */ + pNode->pMap = &(*pMap); + pNode->iType = HASHMAP_BLOB_NODE; + pNode->nHash = nHash; + SyBlobInit(&pNode->xKey.sKey,&pMap->pVm->sAllocator); + SyBlobAppend(&pNode->xKey.sKey,pKey,nKeyLen); + pNode->nValIdx = nValIdx; + return pNode; +} +/* + * link a hashmap node to the given bucket index (last argument to this function). + */ +static void HashmapNodeLink(ph7_hashmap *pMap,ph7_hashmap_node *pNode,sxu32 nBucketIdx) +{ + /* Link */ + if( pMap->apBucket[nBucketIdx] != 0 ){ + pNode->pNextCollide = pMap->apBucket[nBucketIdx]; + pMap->apBucket[nBucketIdx]->pPrevCollide = pNode; + } + pMap->apBucket[nBucketIdx] = pNode; + /* Link to the map list */ + if( pMap->pFirst == 0 ){ + pMap->pFirst = pMap->pLast = pNode; + /* Point to the first inserted node */ + pMap->pCur = pNode; + }else{ + MACRO_LD_PUSH(pMap->pLast,pNode); + } + ++pMap->nEntry; +} +/* + * Unlink a node from the hashmap. + * If the node count reaches zero then release the whole hash-bucket. + */ +PH7_PRIVATE void PH7_HashmapUnlinkNode(ph7_hashmap_node *pNode,int bRestore) +{ + ph7_hashmap *pMap = pNode->pMap; + ph7_vm *pVm = pMap->pVm; + /* Unlink from the corresponding bucket */ + if( pNode->pPrevCollide == 0 ){ + pMap->apBucket[pNode->nHash & (pMap->nSize - 1)] = pNode->pNextCollide; + }else{ + pNode->pPrevCollide->pNextCollide = pNode->pNextCollide; + } + if( pNode->pNextCollide ){ + pNode->pNextCollide->pPrevCollide = pNode->pPrevCollide; + } + if( pMap->pFirst == pNode ){ + pMap->pFirst = pNode->pPrev; + } + if( pMap->pCur == pNode ){ + /* Advance the node cursor */ + pMap->pCur = pMap->pCur->pPrev; /* Reverse link */ + } + /* Unlink from the map list */ + MACRO_LD_REMOVE(pMap->pLast,pNode); + if( bRestore ){ + /* Remove the ph7_value associated with this node from the reference table */ + PH7_VmRefObjRemove(pVm,pNode->nValIdx,0,pNode); + /* Restore to the freelist */ + if( (pNode->iFlags & HASHMAP_NODE_FOREIGN_OBJ) == 0 ){ + PH7_VmUnsetMemObj(pVm,pNode->nValIdx,FALSE); + } + } + if( pNode->iType == HASHMAP_BLOB_NODE ){ + SyBlobRelease(&pNode->xKey.sKey); + } + SyMemBackendPoolFree(&pVm->sAllocator,pNode); + pMap->nEntry--; + if( pMap->nEntry < 1 && pMap != pVm->pGlobal ){ + /* Free the hash-bucket */ + SyMemBackendFree(&pVm->sAllocator,pMap->apBucket); + pMap->apBucket = 0; + pMap->nSize = 0; + pMap->pFirst = pMap->pLast = pMap->pCur = 0; + } +} +#define HASHMAP_FILL_FACTOR 3 +/* + * Grow the hash-table and rehash all entries. + */ +static sxi32 HashmapGrowBucket(ph7_hashmap *pMap) +{ + if( pMap->nEntry >= pMap->nSize * HASHMAP_FILL_FACTOR ){ + ph7_hashmap_node **apOld = pMap->apBucket; + ph7_hashmap_node *pEntry,**apNew; + sxu32 nNew = pMap->nSize << 1; + sxu32 nBucket; + sxu32 n; + if( nNew < 1 ){ + nNew = 16; + } + /* Allocate a new bucket */ + apNew = (ph7_hashmap_node **)SyMemBackendAlloc(&pMap->pVm->sAllocator,nNew * sizeof(ph7_hashmap_node *)); + if( apNew == 0 ){ + if( pMap->nSize < 1 ){ + return SXERR_MEM; /* Fatal */ + } + /* Not so fatal here,simply a performance hit */ + return SXRET_OK; + } + /* Zero the table */ + SyZero((void *)apNew,nNew * sizeof(ph7_hashmap_node *)); + /* Reflect the change */ + pMap->apBucket = apNew; + pMap->nSize = nNew; + if( apOld == 0 ){ + /* First allocated table [i.e: no entry],return immediately */ + return SXRET_OK; + } + /* Rehash old entries */ + pEntry = pMap->pFirst; + n = 0; + for( ;; ){ + if( n >= pMap->nEntry ){ + break; + } + /* Clear the old collision link */ + pEntry->pNextCollide = pEntry->pPrevCollide = 0; + /* Link to the new bucket */ + nBucket = pEntry->nHash & (nNew - 1); + if( pMap->apBucket[nBucket] != 0 ){ + pEntry->pNextCollide = pMap->apBucket[nBucket]; + pMap->apBucket[nBucket]->pPrevCollide = pEntry; + } + pMap->apBucket[nBucket] = pEntry; + /* Point to the next entry */ + pEntry = pEntry->pPrev; /* Reverse link */ + n++; + } + /* Free the old table */ + SyMemBackendFree(&pMap->pVm->sAllocator,(void *)apOld); + } + return SXRET_OK; +} +/* + * Insert a 64-bit integer key and it's associated value (if any) in the given + * hashmap. + */ +static sxi32 HashmapInsertIntKey(ph7_hashmap *pMap,sxi64 iKey,ph7_value *pValue,sxu32 nRefIdx,int isForeign) +{ + ph7_hashmap_node *pNode; + sxu32 nIdx; + sxu32 nHash; + sxi32 rc; + if( !isForeign ){ + ph7_value *pObj; + /* Reserve a ph7_value for the value */ + pObj = PH7_ReserveMemObj(pMap->pVm); + if( pObj == 0 ){ + return SXERR_MEM; + } + if( pValue ){ + /* Duplicate the value */ + PH7_MemObjStore(pValue,pObj); + } + nIdx = pObj->nIdx; + }else{ + nIdx = nRefIdx; + } + /* Hash the key */ + nHash = pMap->xIntHash(iKey); + /* Allocate a new int node */ + pNode = HashmapNewIntNode(&(*pMap),iKey,nHash,nIdx); + if( pNode == 0 ){ + return SXERR_MEM; + } + if( isForeign ){ + /* Mark as a foregin entry */ + pNode->iFlags |= HASHMAP_NODE_FOREIGN_OBJ; + } + /* Make sure the bucket is big enough to hold the new entry */ + rc = HashmapGrowBucket(&(*pMap)); + if( rc != SXRET_OK ){ + SyMemBackendPoolFree(&pMap->pVm->sAllocator,pNode); + return rc; + } + /* Perform the insertion */ + HashmapNodeLink(&(*pMap),pNode,nHash & (pMap->nSize - 1)); + /* Install in the reference table */ + PH7_VmRefObjInstall(pMap->pVm,nIdx,0,pNode,0); + /* All done */ + return SXRET_OK; +} +/* + * Insert a BLOB key and it's associated value (if any) in the given + * hashmap. + */ +static sxi32 HashmapInsertBlobKey(ph7_hashmap *pMap,const void *pKey,sxu32 nKeyLen,ph7_value *pValue,sxu32 nRefIdx,int isForeign) +{ + ph7_hashmap_node *pNode; + sxu32 nHash; + sxu32 nIdx; + sxi32 rc; + if( !isForeign ){ + ph7_value *pObj; + /* Reserve a ph7_value for the value */ + pObj = PH7_ReserveMemObj(pMap->pVm); + if( pObj == 0 ){ + return SXERR_MEM; + } + if( pValue ){ + /* Duplicate the value */ + PH7_MemObjStore(pValue,pObj); + } + nIdx = pObj->nIdx; + }else{ + nIdx = nRefIdx; + } + /* Hash the key */ + nHash = pMap->xBlobHash(pKey,nKeyLen); + /* Allocate a new blob node */ + pNode = HashmapNewBlobNode(&(*pMap),pKey,nKeyLen,nHash,nIdx); + if( pNode == 0 ){ + return SXERR_MEM; + } + if( isForeign ){ + /* Mark as a foregin entry */ + pNode->iFlags |= HASHMAP_NODE_FOREIGN_OBJ; + } + /* Make sure the bucket is big enough to hold the new entry */ + rc = HashmapGrowBucket(&(*pMap)); + if( rc != SXRET_OK ){ + SyMemBackendPoolFree(&pMap->pVm->sAllocator,pNode); + return rc; + } + /* Perform the insertion */ + HashmapNodeLink(&(*pMap),pNode,nHash & (pMap->nSize - 1)); + /* Install in the reference table */ + PH7_VmRefObjInstall(pMap->pVm,nIdx,0,pNode,0); + /* All done */ + return SXRET_OK; +} +/* + * Check if a given 64-bit integer key exists in the given hashmap. + * Write a pointer to the target node on success. Otherwise + * SXERR_NOTFOUND is returned on failure. + */ +static sxi32 HashmapLookupIntKey( + ph7_hashmap *pMap, /* Target hashmap */ + sxi64 iKey, /* lookup key */ + ph7_hashmap_node **ppNode /* OUT: target node on success */ + ) +{ + ph7_hashmap_node *pNode; + sxu32 nHash; + if( pMap->nEntry < 1 ){ + /* Don't bother hashing,there is no entry anyway */ + return SXERR_NOTFOUND; + } + /* Hash the key first */ + nHash = pMap->xIntHash(iKey); + /* Point to the appropriate bucket */ + pNode = pMap->apBucket[nHash & (pMap->nSize - 1)]; + /* Perform the lookup */ + for(;;){ + if( pNode == 0 ){ + break; + } + if( pNode->iType == HASHMAP_INT_NODE + && pNode->nHash == nHash + && pNode->xKey.iKey == iKey ){ + /* Node found */ + if( ppNode ){ + *ppNode = pNode; + } + return SXRET_OK; + } + /* Follow the collision link */ + pNode = pNode->pNextCollide; + } + /* No such entry */ + return SXERR_NOTFOUND; +} +/* + * Check if a given BLOB key exists in the given hashmap. + * Write a pointer to the target node on success. Otherwise + * SXERR_NOTFOUND is returned on failure. + */ +static sxi32 HashmapLookupBlobKey( + ph7_hashmap *pMap, /* Target hashmap */ + const void *pKey, /* Lookup key */ + sxu32 nKeyLen, /* Key length in bytes */ + ph7_hashmap_node **ppNode /* OUT: target node on success */ + ) +{ + ph7_hashmap_node *pNode; + sxu32 nHash; + if( pMap->nEntry < 1 ){ + /* Don't bother hashing,there is no entry anyway */ + return SXERR_NOTFOUND; + } + /* Hash the key first */ + nHash = pMap->xBlobHash(pKey,nKeyLen); + /* Point to the appropriate bucket */ + pNode = pMap->apBucket[nHash & (pMap->nSize - 1)]; + /* Perform the lookup */ + for(;;){ + if( pNode == 0 ){ + break; + } + if( pNode->iType == HASHMAP_BLOB_NODE + && pNode->nHash == nHash + && SyBlobLength(&pNode->xKey.sKey) == nKeyLen + && SyMemcmp(SyBlobData(&pNode->xKey.sKey),pKey,nKeyLen) == 0 ){ + /* Node found */ + if( ppNode ){ + *ppNode = pNode; + } + return SXRET_OK; + } + /* Follow the collision link */ + pNode = pNode->pNextCollide; + } + /* No such entry */ + return SXERR_NOTFOUND; +} +/* + * Check if the given BLOB key looks like a decimal number. + * Retrurn TRUE on success.FALSE otherwise. + */ +static int HashmapIsIntKey(SyBlob *pKey) +{ + const char *zIn = (const char *)SyBlobData(pKey); + const char *zEnd = &zIn[SyBlobLength(pKey)]; + if( (int)(zEnd-zIn) > 1 && zIn[0] == '0' ){ + /* Octal not decimal number */ + return FALSE; + } + if( (zIn[0] == '-' || zIn[0] == '+') && &zIn[1] < zEnd ){ + zIn++; + } + for(;;){ + if( zIn >= zEnd ){ + return TRUE; + } + if( (unsigned char)zIn[0] >= 0xc0 /* UTF-8 stream */ || !SyisDigit(zIn[0]) ){ + break; + } + zIn++; + } + /* Key does not look like a decimal number */ + return FALSE; +} +/* + * Check if a given key exists in the given hashmap. + * Write a pointer to the target node on success. + * Otherwise SXERR_NOTFOUND is returned on failure. + */ +static sxi32 HashmapLookup( + ph7_hashmap *pMap, /* Target hashmap */ + ph7_value *pKey, /* Lookup key */ + ph7_hashmap_node **ppNode /* OUT: target node on success */ + ) +{ + ph7_hashmap_node *pNode = 0; /* cc -O6 warning */ + sxi32 rc; + if( pKey->iFlags & (MEMOBJ_STRING|MEMOBJ_HASHMAP|MEMOBJ_OBJ|MEMOBJ_RES) ){ + if( (pKey->iFlags & MEMOBJ_STRING) == 0 ){ + /* Force a string cast */ + PH7_MemObjToString(&(*pKey)); + } + if( SyBlobLength(&pKey->sBlob) > 0 && !HashmapIsIntKey(&pKey->sBlob) ){ + /* Perform a blob lookup */ + rc = HashmapLookupBlobKey(&(*pMap),SyBlobData(&pKey->sBlob),SyBlobLength(&pKey->sBlob),&pNode); + goto result; + } + } + /* Perform an int lookup */ + if((pKey->iFlags & MEMOBJ_INT) == 0 ){ + /* Force an integer cast */ + PH7_MemObjToInteger(pKey); + } + /* Perform an int lookup */ + rc = HashmapLookupIntKey(&(*pMap),pKey->x.iVal,&pNode); +result: + if( rc == SXRET_OK ){ + /* Node found */ + if( ppNode ){ + *ppNode = pNode; + } + return SXRET_OK; + } + /* No such entry */ + return SXERR_NOTFOUND; +} +/* + * Insert a given key and it's associated value (if any) in the given + * hashmap. + * If a node with the given key already exists in the database + * then this function overwrite the old value. + */ +static sxi32 HashmapInsert( + ph7_hashmap *pMap, /* Target hashmap */ + ph7_value *pKey, /* Lookup key */ + ph7_value *pVal /* Node value */ + ) +{ + ph7_hashmap_node *pNode = 0; + sxi32 rc = SXRET_OK; + if( pKey && pKey->iFlags & (MEMOBJ_STRING|MEMOBJ_HASHMAP|MEMOBJ_OBJ|MEMOBJ_RES) ){ + if( (pKey->iFlags & MEMOBJ_STRING) == 0 ){ + /* Force a string cast */ + PH7_MemObjToString(&(*pKey)); + } + if( SyBlobLength(&pKey->sBlob) < 1 || HashmapIsIntKey(&pKey->sBlob) ){ + if(SyBlobLength(&pKey->sBlob) < 1){ + /* Automatic index assign */ + pKey = 0; + } + goto IntKey; + } + if( SXRET_OK == HashmapLookupBlobKey(&(*pMap),SyBlobData(&pKey->sBlob), + SyBlobLength(&pKey->sBlob),&pNode) ){ + /* Overwrite the old value */ + ph7_value *pElem; + pElem = (ph7_value *)SySetAt(&pMap->pVm->aMemObj,pNode->nValIdx); + if( pElem ){ + if( pVal ){ + PH7_MemObjStore(pVal,pElem); + }else{ + /* Nullify the entry */ + PH7_MemObjToNull(pElem); + } + } + return SXRET_OK; + } + if( pMap == pMap->pVm->pGlobal ){ + /* Forbidden */ + PH7_VmThrowError(pMap->pVm,0,PH7_CTX_NOTICE,"$GLOBALS is a read-only array,insertion is forbidden"); + return SXRET_OK; + } + /* Perform a blob-key insertion */ + rc = HashmapInsertBlobKey(&(*pMap),SyBlobData(&pKey->sBlob),SyBlobLength(&pKey->sBlob),&(*pVal),0,FALSE); + return rc; + } +IntKey: + if( pKey ){ + if((pKey->iFlags & MEMOBJ_INT) == 0 ){ + /* Force an integer cast */ + PH7_MemObjToInteger(pKey); + } + if( SXRET_OK == HashmapLookupIntKey(&(*pMap),pKey->x.iVal,&pNode) ){ + /* Overwrite the old value */ + ph7_value *pElem; + pElem = (ph7_value *)SySetAt(&pMap->pVm->aMemObj,pNode->nValIdx); + if( pElem ){ + if( pVal ){ + PH7_MemObjStore(pVal,pElem); + }else{ + /* Nullify the entry */ + PH7_MemObjToNull(pElem); + } + } + return SXRET_OK; + } + if( pMap == pMap->pVm->pGlobal ){ + /* Forbidden */ + PH7_VmThrowError(pMap->pVm,0,PH7_CTX_NOTICE,"$GLOBALS is a read-only array,insertion is forbidden"); + return SXRET_OK; + } + /* Perform a 64-bit-int-key insertion */ + rc = HashmapInsertIntKey(&(*pMap),pKey->x.iVal,&(*pVal),0,FALSE); + if( rc == SXRET_OK ){ + if( pKey->x.iVal >= pMap->iNextIdx ){ + /* Increment the automatic index */ + pMap->iNextIdx = pKey->x.iVal + 1; + /* Make sure the automatic index is not reserved */ + while( SXRET_OK == HashmapLookupIntKey(&(*pMap),pMap->iNextIdx,0) ){ + pMap->iNextIdx++; + } + } + } + }else{ + if( pMap == pMap->pVm->pGlobal ){ + /* Forbidden */ + PH7_VmThrowError(pMap->pVm,0,PH7_CTX_NOTICE,"$GLOBALS is a read-only array,insertion is forbidden"); + return SXRET_OK; + } + /* Assign an automatic index */ + rc = HashmapInsertIntKey(&(*pMap),pMap->iNextIdx,&(*pVal),0,FALSE); + if( rc == SXRET_OK ){ + ++pMap->iNextIdx; + } + } + /* Insertion result */ + return rc; +} +/* + * Insert a given key and it's associated value (foreign index) in the given + * hashmap. + * This is insertion by reference so be careful to mark the node + * with the HASHMAP_NODE_FOREIGN_OBJ flag being set. + * The insertion by reference is triggered when the following + * expression is encountered. + * $var = 10; + * $a = array(&var); + * OR + * $a[] =& $var; + * That is,$var is a foreign ph7_value and the $a array have no control + * over it's contents. + * Note that the node that hold the foreign ph7_value is automatically + * removed when the foreign ph7_value is unset. + * Example: + * $var = 10; + * $a[] =& $var; + * echo count($a).PHP_EOL; //1 + * //Unset the foreign ph7_value now + * unset($var); + * echo count($a); //0 + * Note that this is a PH7 eXtension. + * Refer to the official documentation for more information. + * If a node with the given key already exists in the database + * then this function overwrite the old value. + */ +static sxi32 HashmapInsertByRef( + ph7_hashmap *pMap, /* Target hashmap */ + ph7_value *pKey, /* Lookup key */ + sxu32 nRefIdx /* Foreign ph7_value index */ + ) +{ + ph7_hashmap_node *pNode = 0; + sxi32 rc = SXRET_OK; + if( pKey && pKey->iFlags & (MEMOBJ_STRING|MEMOBJ_HASHMAP|MEMOBJ_OBJ|MEMOBJ_RES) ){ + if( (pKey->iFlags & MEMOBJ_STRING) == 0 ){ + /* Force a string cast */ + PH7_MemObjToString(&(*pKey)); + } + if( SyBlobLength(&pKey->sBlob) < 1 || HashmapIsIntKey(&pKey->sBlob) ){ + if(SyBlobLength(&pKey->sBlob) < 1){ + /* Automatic index assign */ + pKey = 0; + } + goto IntKey; + } + if( SXRET_OK == HashmapLookupBlobKey(&(*pMap),SyBlobData(&pKey->sBlob), + SyBlobLength(&pKey->sBlob),&pNode) ){ + /* Overwrite */ + PH7_VmRefObjRemove(pMap->pVm,pNode->nValIdx,0,pNode); + pNode->nValIdx = nRefIdx; + /* Install in the reference table */ + PH7_VmRefObjInstall(pMap->pVm,nRefIdx,0,pNode,0); + return SXRET_OK; + } + /* Perform a blob-key insertion */ + rc = HashmapInsertBlobKey(&(*pMap),SyBlobData(&pKey->sBlob),SyBlobLength(&pKey->sBlob),0,nRefIdx,TRUE); + return rc; + } +IntKey: + if( pKey ){ + if((pKey->iFlags & MEMOBJ_INT) == 0 ){ + /* Force an integer cast */ + PH7_MemObjToInteger(pKey); + } + if( SXRET_OK == HashmapLookupIntKey(&(*pMap),pKey->x.iVal,&pNode) ){ + /* Overwrite */ + PH7_VmRefObjRemove(pMap->pVm,pNode->nValIdx,0,pNode); + pNode->nValIdx = nRefIdx; + /* Install in the reference table */ + PH7_VmRefObjInstall(pMap->pVm,nRefIdx,0,pNode,0); + return SXRET_OK; + } + /* Perform a 64-bit-int-key insertion */ + rc = HashmapInsertIntKey(&(*pMap),pKey->x.iVal,0,nRefIdx,TRUE); + if( rc == SXRET_OK ){ + if( pKey->x.iVal >= pMap->iNextIdx ){ + /* Increment the automatic index */ + pMap->iNextIdx = pKey->x.iVal + 1; + /* Make sure the automatic index is not reserved */ + while( SXRET_OK == HashmapLookupIntKey(&(*pMap),pMap->iNextIdx,0) ){ + pMap->iNextIdx++; + } + } + } + }else{ + /* Assign an automatic index */ + rc = HashmapInsertIntKey(&(*pMap),pMap->iNextIdx,0,nRefIdx,TRUE); + if( rc == SXRET_OK ){ + ++pMap->iNextIdx; + } + } + /* Insertion result */ + return rc; +} +/* + * Extract node value. + */ +static ph7_value * HashmapExtractNodeValue(ph7_hashmap_node *pNode) +{ + /* Point to the desired object */ + ph7_value *pObj; + pObj = (ph7_value *)SySetAt(&pNode->pMap->pVm->aMemObj,pNode->nValIdx); + return pObj; +} +/* + * Insert a node in the given hashmap. + * If a node with the given key already exists in the database + * then this function overwrite the old value. + */ +static sxi32 HashmapInsertNode(ph7_hashmap *pMap,ph7_hashmap_node *pNode,int bPreserve) +{ + ph7_value *pObj; + sxi32 rc; + /* Extract the node value */ + pObj = HashmapExtractNodeValue(&(*pNode)); + if( pObj == 0 ){ + return SXERR_EMPTY; + } + /* Preserve key */ + if( pNode->iType == HASHMAP_INT_NODE){ + /* Int64 key */ + if( !bPreserve ){ + /* Assign an automatic index */ + rc = HashmapInsert(&(*pMap),0,pObj); + }else{ + rc = HashmapInsertIntKey(&(*pMap),pNode->xKey.iKey,pObj,0,FALSE); + } + }else{ + /* Blob key */ + rc = HashmapInsertBlobKey(&(*pMap),SyBlobData(&pNode->xKey.sKey), + SyBlobLength(&pNode->xKey.sKey),pObj,0,FALSE); + } + return rc; +} +/* + * Compare two node values. + * Return 0 if the node values are equals, > 0 if pLeft is greater than pRight + * or < 0 if pRight is greater than pLeft. + * For a full description on ph7_values comparison,refer to the implementation + * of the [PH7_MemObjCmp()] function defined in memobj.c or the official + * documenation. + */ +static sxi32 HashmapNodeCmp(ph7_hashmap_node *pLeft,ph7_hashmap_node *pRight,int bStrict) +{ + ph7_value sObj1,sObj2; + sxi32 rc; + if( pLeft == pRight ){ + /* + * Same node.Refer to the sort() implementation defined + * below for more information on this sceanario. + */ + return 0; + } + /* Do the comparison */ + PH7_MemObjInit(pLeft->pMap->pVm,&sObj1); + PH7_MemObjInit(pLeft->pMap->pVm,&sObj2); + PH7_HashmapExtractNodeValue(pLeft,&sObj1,FALSE); + PH7_HashmapExtractNodeValue(pRight,&sObj2,FALSE); + rc = PH7_MemObjCmp(&sObj1,&sObj2,bStrict,0); + PH7_MemObjRelease(&sObj1); + PH7_MemObjRelease(&sObj2); + return rc; +} +/* + * Rehash a node with a 64-bit integer key. + * Refer to [merge_sort(),array_shift()] implementations for more information. + */ +static void HashmapRehashIntNode(ph7_hashmap_node *pEntry) +{ + ph7_hashmap *pMap = pEntry->pMap; + sxu32 nBucket; + /* Remove old collision links */ + if( pEntry->pPrevCollide ){ + pEntry->pPrevCollide->pNextCollide = pEntry->pNextCollide; + }else{ + pMap->apBucket[pEntry->nHash & (pMap->nSize - 1)] = pEntry->pNextCollide; + } + if( pEntry->pNextCollide ){ + pEntry->pNextCollide->pPrevCollide = pEntry->pPrevCollide; + } + pEntry->pNextCollide = pEntry->pPrevCollide = 0; + /* Compute the new hash */ + pEntry->nHash = pMap->xIntHash(pMap->iNextIdx); + pEntry->xKey.iKey = pMap->iNextIdx; + nBucket = pEntry->nHash & (pMap->nSize - 1); + /* Link to the new bucket */ + pEntry->pNextCollide = pMap->apBucket[nBucket]; + if( pMap->apBucket[nBucket] ){ + pMap->apBucket[nBucket]->pPrevCollide = pEntry; + } + pEntry->pNextCollide = pMap->apBucket[nBucket]; + pMap->apBucket[nBucket] = pEntry; + /* Increment the automatic index */ + pMap->iNextIdx++; +} +/* + * Perform a linear search on a given hashmap. + * Write a pointer to the target node on success. + * Otherwise SXERR_NOTFOUND is returned on failure. + * Refer to [array_intersect(),array_diff(),in_array(),...] implementations + * for more information. + */ +static int HashmapFindValue( + ph7_hashmap *pMap, /* Target hashmap */ + ph7_value *pNeedle, /* Lookup key */ + ph7_hashmap_node **ppNode, /* OUT: target node on success */ + int bStrict /* TRUE for strict comparison */ + ) +{ + ph7_hashmap_node *pEntry; + ph7_value sVal,*pVal; + ph7_value sNeedle; + sxi32 rc; + sxu32 n; + /* Perform a linear search since we cannot sort the hashmap based on values */ + pEntry = pMap->pFirst; + n = pMap->nEntry; + PH7_MemObjInit(pMap->pVm,&sVal); + PH7_MemObjInit(pMap->pVm,&sNeedle); + for(;;){ + if( n < 1 ){ + break; + } + /* Extract node value */ + pVal = HashmapExtractNodeValue(pEntry); + if( pVal ){ + if( (pVal->iFlags|pNeedle->iFlags) & MEMOBJ_NULL ){ + sxi32 iF1 = pVal->iFlags&~MEMOBJ_AUX; + sxi32 iF2 = pNeedle->iFlags&~MEMOBJ_AUX; + if( iF1 == iF2 ){ + /* NULL values are equals */ + if( ppNode ){ + *ppNode = pEntry; + } + return SXRET_OK; + } + }else{ + /* Duplicate value */ + PH7_MemObjLoad(pVal,&sVal); + PH7_MemObjLoad(pNeedle,&sNeedle); + rc = PH7_MemObjCmp(&sNeedle,&sVal,bStrict,0); + PH7_MemObjRelease(&sVal); + PH7_MemObjRelease(&sNeedle); + if( rc == 0 ){ + if( ppNode ){ + *ppNode = pEntry; + } + /* Match found*/ + return SXRET_OK; + } + } + } + /* Point to the next entry */ + pEntry = pEntry->pPrev; /* Reverse link */ + n--; + } + /* No such entry */ + return SXERR_NOTFOUND; +} +/* + * Perform a linear search on a given hashmap but use an user-defined callback + * for values comparison. + * Write a pointer to the target node on success. + * Otherwise SXERR_NOTFOUND is returned on failure. + * Refer to [array_uintersect(),array_udiff()...] implementations + * for more information. + */ +static int HashmapFindValueByCallback( + ph7_hashmap *pMap, /* Target hashmap */ + ph7_value *pNeedle, /* Lookup key */ + ph7_value *pCallback, /* User defined callback */ + ph7_hashmap_node **ppNode /* OUT: target node on success */ + ) +{ + ph7_hashmap_node *pEntry; + ph7_value sResult,*pVal; + ph7_value *apArg[2]; /* Callback arguments */ + sxi32 rc; + sxu32 n; + /* Perform a linear search since we cannot sort the array based on values */ + pEntry = pMap->pFirst; + n = pMap->nEntry; + /* Store callback result here */ + PH7_MemObjInit(pMap->pVm,&sResult); + /* First argument to the callback */ + apArg[0] = pNeedle; + for(;;){ + if( n < 1 ){ + break; + } + /* Extract node value */ + pVal = HashmapExtractNodeValue(pEntry); + if( pVal ){ + /* Invoke the user callback */ + apArg[1] = pVal; /* Second argument to the callback */ + rc = PH7_VmCallUserFunction(pMap->pVm,pCallback,2,apArg,&sResult); + if( rc == SXRET_OK ){ + /* Extract callback result */ + if( (sResult.iFlags & MEMOBJ_INT) == 0 ){ + /* Perform an int cast */ + PH7_MemObjToInteger(&sResult); + } + rc = (sxi32)sResult.x.iVal; + PH7_MemObjRelease(&sResult); + if( rc == 0 ){ + /* Match found*/ + if( ppNode ){ + *ppNode = pEntry; + } + return SXRET_OK; + } + } + } + /* Point to the next entry */ + pEntry = pEntry->pPrev; /* Reverse link */ + n--; + } + /* No such entry */ + return SXERR_NOTFOUND; +} +/* + * Compare two hashmaps. + * Return 0 if the hashmaps are equals.Any other value indicates inequality. + * Note on array comparison operators. + * According to the PHP language reference manual. + * Array Operators Example Name Result + * $a + $b Union Union of $a and $b. + * $a == $b Equality TRUE if $a and $b have the same key/value pairs. + * $a === $b Identity TRUE if $a and $b have the same key/value pairs in the same + * order and of the same types. + * $a != $b Inequality TRUE if $a is not equal to $b. + * $a <> $b Inequality TRUE if $a is not equal to $b. + * $a !== $b Non-identity TRUE if $a is not identical to $b. + * The + operator returns the right-hand array appended to the left-hand array; + * For keys that exist in both arrays, the elements from the left-hand array will be used + * and the matching elements from the right-hand array will be ignored. + * "apple", "b" => "banana"); + * $b = array("a" => "pear", "b" => "strawberry", "c" => "cherry"); + * $c = $a + $b; // Union of $a and $b + * echo "Union of \$a and \$b: \n"; + * var_dump($c); + * $c = $b + $a; // Union of $b and $a + * echo "Union of \$b and \$a: \n"; + * var_dump($c); + * ?> + * When executed, this script will print the following: + * Union of $a and $b: + * array(3) { + * ["a"]=> + * string(5) "apple" + * ["b"]=> + * string(6) "banana" + * ["c"]=> + * string(6) "cherry" + * } + * Union of $b and $a: + * array(3) { + * ["a"]=> + * string(4) "pear" + * ["b"]=> + * string(10) "strawberry" + * ["c"]=> + * string(6) "cherry" + * } + * Elements of arrays are equal for the comparison if they have the same key and value. + */ +PH7_PRIVATE sxi32 PH7_HashmapCmp( + ph7_hashmap *pLeft, /* Left hashmap */ + ph7_hashmap *pRight, /* Right hashmap */ + int bStrict /* TRUE for strict comparison */ + ) +{ + ph7_hashmap_node *pLe,*pRe; + sxi32 rc; + sxu32 n; + if( pLeft == pRight ){ + /* Same hashmap instance. This can easily happen since hashmaps are passed by reference. + * Unlike the zend engine. + */ + return 0; + } + if( pLeft->nEntry != pRight->nEntry ){ + /* Must have the same number of entries */ + return pLeft->nEntry > pRight->nEntry ? 1 : -1; + } + /* Point to the first inserted entry of the left hashmap */ + pLe = pLeft->pFirst; + pRe = 0; /* cc warning */ + /* Perform the comparison */ + n = pLeft->nEntry; + for(;;){ + if( n < 1 ){ + break; + } + if( pLe->iType == HASHMAP_INT_NODE){ + /* Int key */ + rc = HashmapLookupIntKey(&(*pRight),pLe->xKey.iKey,&pRe); + }else{ + SyBlob *pKey = &pLe->xKey.sKey; + /* Blob key */ + rc = HashmapLookupBlobKey(&(*pRight),SyBlobData(pKey),SyBlobLength(pKey),&pRe); + } + if( rc != SXRET_OK ){ + /* No such entry in the right side */ + return 1; + } + rc = 0; + if( bStrict ){ + /* Make sure,the keys are of the same type */ + if( pLe->iType != pRe->iType ){ + rc = 1; + } + } + if( !rc ){ + /* Compare nodes */ + rc = HashmapNodeCmp(pLe,pRe,bStrict); + } + if( rc != 0 ){ + /* Nodes key/value differ */ + return rc; + } + /* Point to the next entry */ + pLe = pLe->pPrev; /* Reverse link */ + n--; + } + return 0; /* Hashmaps are equals */ +} +/* + * Merge two hashmaps. + * Note on the merge process + * According to the PHP language reference manual. + * Merges the elements of two arrays together so that the values of one are appended + * to the end of the previous one. It returns the resulting array (pDest). + * If the input arrays have the same string keys, then the later value for that key + * will overwrite the previous one. If, however, the arrays contain numeric keys + * the later value will not overwrite the original value, but will be appended. + * Values in the input array with numeric keys will be renumbered with incrementing + * keys starting from zero in the result array. + */ +static sxi32 HashmapMerge(ph7_hashmap *pSrc,ph7_hashmap *pDest) +{ + ph7_hashmap_node *pEntry; + ph7_value sKey,*pVal; + sxi32 rc; + sxu32 n; + if( pSrc == pDest ){ + /* Same map. This can easily happen since hashmaps are passed by reference. + * Unlike the zend engine. + */ + return SXRET_OK; + } + /* Point to the first inserted entry in the source */ + pEntry = pSrc->pFirst; + /* Perform the merge */ + for( n = 0 ; n < pSrc->nEntry ; ++n ){ + /* Extract the node value */ + pVal = HashmapExtractNodeValue(pEntry); + if( pEntry->iType == HASHMAP_BLOB_NODE ){ + /* Blob key insertion */ + PH7_MemObjInitFromString(pDest->pVm,&sKey,0); + PH7_MemObjStringAppend(&sKey,(const char *)SyBlobData(&pEntry->xKey.sKey),SyBlobLength(&pEntry->xKey.sKey)); + rc = PH7_HashmapInsert(&(*pDest),&sKey,pVal); + PH7_MemObjRelease(&sKey); + }else{ + rc = HashmapInsert(&(*pDest),0/* Automatic index assign */,pVal); + } + if( rc != SXRET_OK ){ + return rc; + } + /* Point to the next entry */ + pEntry = pEntry->pPrev; /* Reverse link */ + } + return SXRET_OK; +} +/* + * Overwrite entries with the same key. + * Refer to the [array_replace()] implementation for more information. + * According to the PHP language reference manual. + * array_replace() replaces the values of the first array with the same values + * from all the following arrays. If a key from the first array exists in the second + * array, its value will be replaced by the value from the second array. If the key + * exists in the second array, and not the first, it will be created in the first array. + * If a key only exists in the first array, it will be left as is. If several arrays + * are passed for replacement, they will be processed in order, the later arrays + * overwriting the previous values. + * array_replace() is not recursive : it will replace values in the first array + * by whatever type is in the second array. + */ +static sxi32 HashmapOverwrite(ph7_hashmap *pSrc,ph7_hashmap *pDest) +{ + ph7_hashmap_node *pEntry; + ph7_value sKey,*pVal; + sxi32 rc; + sxu32 n; + if( pSrc == pDest ){ + /* Same map. This can easily happen since hashmaps are passed by reference. + * Unlike the zend engine. + */ + return SXRET_OK; + } + /* Point to the first inserted entry in the source */ + pEntry = pSrc->pFirst; + /* Perform the merge */ + for( n = 0 ; n < pSrc->nEntry ; ++n ){ + /* Extract the node value */ + pVal = HashmapExtractNodeValue(pEntry); + if( pEntry->iType == HASHMAP_BLOB_NODE ){ + /* Blob key insertion */ + PH7_MemObjInitFromString(pDest->pVm,&sKey,0); + PH7_MemObjStringAppend(&sKey,(const char *)SyBlobData(&pEntry->xKey.sKey),SyBlobLength(&pEntry->xKey.sKey)); + }else{ + /* Int key insertion */ + PH7_MemObjInitFromInt(pDest->pVm,&sKey,pEntry->xKey.iKey); + } + rc = PH7_HashmapInsert(&(*pDest),&sKey,pVal); + PH7_MemObjRelease(&sKey); + if( rc != SXRET_OK ){ + return rc; + } + /* Point to the next entry */ + pEntry = pEntry->pPrev; /* Reverse link */ + } + return SXRET_OK; +} +/* + * Duplicate the contents of a hashmap. Store the copy in pDest. + * Refer to the [array_pad(),array_copy(),...] implementation for more information. + */ +PH7_PRIVATE sxi32 PH7_HashmapDup(ph7_hashmap *pSrc,ph7_hashmap *pDest) +{ + ph7_hashmap_node *pEntry; + ph7_value sKey,*pVal; + sxi32 rc; + sxu32 n; + if( pSrc == pDest ){ + /* Same map. This can easily happen since hashmaps are passed by reference. + * Unlike the zend engine. + */ + return SXRET_OK; + } + /* Point to the first inserted entry in the source */ + pEntry = pSrc->pFirst; + /* Perform the duplication */ + for( n = 0 ; n < pSrc->nEntry ; ++n ){ + /* Extract the node value */ + pVal = HashmapExtractNodeValue(pEntry); + if( pEntry->iType == HASHMAP_BLOB_NODE ){ + /* Blob key insertion */ + PH7_MemObjInitFromString(pDest->pVm,&sKey,0); + PH7_MemObjStringAppend(&sKey,(const char *)SyBlobData(&pEntry->xKey.sKey),SyBlobLength(&pEntry->xKey.sKey)); + rc = PH7_HashmapInsert(&(*pDest),&sKey,pVal); + PH7_MemObjRelease(&sKey); + }else{ + /* Int key insertion */ + rc = HashmapInsertIntKey(&(*pDest),pEntry->xKey.iKey,pVal,0,FALSE); + } + if( rc != SXRET_OK ){ + return rc; + } + /* Point to the next entry */ + pEntry = pEntry->pPrev; /* Reverse link */ + } + return SXRET_OK; +} +/* + * Perform the union of two hashmaps. + * This operation is performed only if the user uses the '+' operator + * with a variable holding an array as follows: + * "apple", "b" => "banana"); + * $b = array("a" => "pear", "b" => "strawberry", "c" => "cherry"); + * $c = $a + $b; // Union of $a and $b + * echo "Union of \$a and \$b: \n"; + * var_dump($c); + * $c = $b + $a; // Union of $b and $a + * echo "Union of \$b and \$a: \n"; + * var_dump($c); + * ?> + * When executed, this script will print the following: + * Union of $a and $b: + * array(3) { + * ["a"]=> + * string(5) "apple" + * ["b"]=> + * string(6) "banana" + * ["c"]=> + * string(6) "cherry" + * } + * Union of $b and $a: + * array(3) { + * ["a"]=> + * string(4) "pear" + * ["b"]=> + * string(10) "strawberry" + * ["c"]=> + * string(6) "cherry" + * } + * The + operator returns the right-hand array appended to the left-hand array; + * For keys that exist in both arrays, the elements from the left-hand array will be used + * and the matching elements from the right-hand array will be ignored. + */ +PH7_PRIVATE sxi32 PH7_HashmapUnion(ph7_hashmap *pLeft,ph7_hashmap *pRight) +{ + ph7_hashmap_node *pEntry; + sxi32 rc = SXRET_OK; + ph7_value *pObj; + sxu32 n; + if( pLeft == pRight ){ + /* Same map. This can easily happen since hashmaps are passed by reference. + * Unlike the zend engine. + */ + return SXRET_OK; + } + /* Perform the union */ + pEntry = pRight->pFirst; + for(n = 0 ; n < pRight->nEntry ; ++n ){ + /* Make sure the given key does not exists in the left array */ + if( pEntry->iType == HASHMAP_BLOB_NODE ){ + /* BLOB key */ + if( SXRET_OK != + HashmapLookupBlobKey(&(*pLeft),SyBlobData(&pEntry->xKey.sKey),SyBlobLength(&pEntry->xKey.sKey),0) ){ + pObj = HashmapExtractNodeValue(pEntry); + if( pObj ){ + /* Perform the insertion */ + rc = HashmapInsertBlobKey(&(*pLeft),SyBlobData(&pEntry->xKey.sKey),SyBlobLength(&pEntry->xKey.sKey), + pObj,0,FALSE); + if( rc != SXRET_OK ){ + return rc; + } + } + } + }else{ + /* INT key */ + if( SXRET_OK != HashmapLookupIntKey(&(*pLeft),pEntry->xKey.iKey,0) ){ + pObj = HashmapExtractNodeValue(pEntry); + if( pObj ){ + /* Perform the insertion */ + rc = HashmapInsertIntKey(&(*pLeft),pEntry->xKey.iKey,pObj,0,FALSE); + if( rc != SXRET_OK ){ + return rc; + } + } + } + } + /* Point to the next entry */ + pEntry = pEntry->pPrev; /* Reverse link */ + } + return SXRET_OK; +} +/* + * Allocate a new hashmap. + * Return a pointer to the freshly allocated hashmap on success.NULL otherwise. + */ +PH7_PRIVATE ph7_hashmap * PH7_NewHashmap( + ph7_vm *pVm, /* VM that trigger the hashmap creation */ + sxu32 (*xIntHash)(sxi64), /* Hash function for int keys.NULL otherwise*/ + sxu32 (*xBlobHash)(const void *,sxu32) /* Hash function for BLOB keys.NULL otherwise */ + ) +{ + ph7_hashmap *pMap; + /* Allocate a new instance */ + pMap = (ph7_hashmap *)SyMemBackendPoolAlloc(&pVm->sAllocator,sizeof(ph7_hashmap)); + if( pMap == 0 ){ + return 0; + } + /* Zero the structure */ + SyZero(pMap,sizeof(ph7_hashmap)); + /* Fill in the structure */ + pMap->pVm = &(*pVm); + pMap->iRef = 1; + /* Default hash functions */ + pMap->xIntHash = xIntHash ? xIntHash : IntHash; + pMap->xBlobHash = xBlobHash ? xBlobHash : BinHash; + return pMap; +} +/* + * Install superglobals in the given virtual machine. + * Note on superglobals. + * According to the PHP language reference manual. + * Superglobals are built-in variables that are always available in all scopes. +* Description +* Several predefined variables in PHP are "superglobals", which means they +* are available in all scopes throughout a script. There is no need to do +* global $variable; to access them within functions or methods. +* These superglobal variables are: +* $GLOBALS +* $_SERVER +* $_GET +* $_POST +* $_FILES +* $_COOKIE +* $_SESSION +* $_REQUEST +* $_ENV +*/ +PH7_PRIVATE sxi32 PH7_HashmapCreateSuper(ph7_vm *pVm) +{ + static const char * azSuper[] = { + "_SERVER", /* $_SERVER */ + "_GET", /* $_GET */ + "_POST", /* $_POST */ + "_FILES", /* $_FILES */ + "_COOKIE", /* $_COOKIE */ + "_SESSION", /* $_SESSION */ + "_REQUEST", /* $_REQUEST */ + "_ENV", /* $_ENV */ + "_HEADER", /* $_HEADER */ + "argv" /* $argv */ + }; + ph7_hashmap *pMap; + ph7_value *pObj; + SyString *pFile; + sxi32 rc; + sxu32 n; + /* Allocate a new hashmap for the $GLOBALS array */ + pMap = PH7_NewHashmap(&(*pVm),0,0); + if( pMap == 0 ){ + return SXERR_MEM; + } + pVm->pGlobal = pMap; + /* Reserve a ph7_value for the $GLOBALS array*/ + pObj = PH7_ReserveMemObj(&(*pVm)); + if( pObj == 0 ){ + return SXERR_MEM; + } + PH7_MemObjInitFromArray(&(*pVm),pObj,pMap); + /* Record object index */ + pVm->nGlobalIdx = pObj->nIdx; + /* Install the special $GLOBALS array */ + rc = SyHashInsert(&pVm->hSuper,(const void *)"GLOBALS",sizeof("GLOBALS")-1,SX_INT_TO_PTR(pVm->nGlobalIdx)); + if( rc != SXRET_OK ){ + return rc; + } + /* Install superglobals now */ + for( n = 0 ; n < SX_ARRAYSIZE(azSuper) ; n++ ){ + ph7_value *pSuper; + /* Request an empty array */ + pSuper = ph7_new_array(&(*pVm)); + if( pSuper == 0 ){ + return SXERR_MEM; + } + /* Install */ + rc = ph7_vm_config(&(*pVm),PH7_VM_CONFIG_CREATE_SUPER,azSuper[n]/* Super-global name*/,pSuper/* Super-global value */); + if( rc != SXRET_OK ){ + return rc; + } + /* Release the value now it have been installed */ + ph7_release_value(&(*pVm),pSuper); + } + /* Set some $_SERVER entries */ + pFile = (SyString *)SySetPeek(&pVm->aFiles); + /* + * 'SCRIPT_FILENAME' + * The absolute pathname of the currently executing script. + */ + ph7_vm_config(pVm,PH7_VM_CONFIG_SERVER_ATTR, + "SCRIPT_FILENAME", + pFile ? pFile->zString : ":Memory:", + pFile ? pFile->nByte : sizeof(":Memory:") - 1 + ); + /* All done,all super-global are installed now */ + return SXRET_OK; +} +/* + * Release a hashmap. + */ +PH7_PRIVATE sxi32 PH7_HashmapRelease(ph7_hashmap *pMap,int FreeDS) +{ + ph7_hashmap_node *pEntry,*pNext; + ph7_vm *pVm = pMap->pVm; + sxu32 n; + if( pMap == pVm->pGlobal ){ + /* Cannot delete the $GLOBALS array */ + PH7_VmThrowError(pMap->pVm,0,PH7_CTX_NOTICE,"$GLOBALS is a read-only array,deletion is forbidden"); + return SXRET_OK; + } + /* Start the release process */ + n = 0; + pEntry = pMap->pFirst; + for(;;){ + if( n >= pMap->nEntry ){ + break; + } + pNext = pEntry->pPrev; /* Reverse link */ + /* Remove the reference from the foreign table */ + PH7_VmRefObjRemove(pVm,pEntry->nValIdx,0,pEntry); + if( (pEntry->iFlags & HASHMAP_NODE_FOREIGN_OBJ) == 0 ){ + /* Restore the ph7_value to the free list */ + PH7_VmUnsetMemObj(pVm,pEntry->nValIdx,FALSE); + } + /* Release the node */ + if( pEntry->iType == HASHMAP_BLOB_NODE ){ + SyBlobRelease(&pEntry->xKey.sKey); + } + SyMemBackendPoolFree(&pVm->sAllocator,pEntry); + /* Point to the next entry */ + pEntry = pNext; + n++; + } + if( pMap->nEntry > 0 ){ + /* Release the hash bucket */ + SyMemBackendFree(&pVm->sAllocator,pMap->apBucket); + } + if( FreeDS ){ + /* Free the whole instance */ + SyMemBackendPoolFree(&pVm->sAllocator,pMap); + }else{ + /* Keep the instance but reset it's fields */ + pMap->apBucket = 0; + pMap->iNextIdx = 0; + pMap->nEntry = pMap->nSize = 0; + pMap->pFirst = pMap->pLast = pMap->pCur = 0; + } + return SXRET_OK; +} +/* + * Decrement the reference count of a given hashmap. + * If the count reaches zero which mean no more variables + * are pointing to this hashmap,then release the whole instance. + */ +PH7_PRIVATE void PH7_HashmapUnref(ph7_hashmap *pMap) +{ + ph7_vm *pVm = pMap->pVm; + /* TICKET 1432-49: $GLOBALS is not subject to garbage collection */ + pMap->iRef--; + if( pMap->iRef < 1 && pMap != pVm->pGlobal){ + PH7_HashmapRelease(pMap,TRUE); + } +} +/* + * Check if a given key exists in the given hashmap. + * Write a pointer to the target node on success. + * Otherwise SXERR_NOTFOUND is returned on failure. + */ +PH7_PRIVATE sxi32 PH7_HashmapLookup( + ph7_hashmap *pMap, /* Target hashmap */ + ph7_value *pKey, /* Lookup key */ + ph7_hashmap_node **ppNode /* OUT: Target node on success */ + ) +{ + sxi32 rc; + if( pMap->nEntry < 1 ){ + /* TICKET 1433-25: Don't bother hashing,the hashmap is empty anyway. + */ + return SXERR_NOTFOUND; + } + rc = HashmapLookup(&(*pMap),&(*pKey),ppNode); + return rc; +} +/* + * Insert a given key and it's associated value (if any) in the given + * hashmap. + * If a node with the given key already exists in the database + * then this function overwrite the old value. + */ +PH7_PRIVATE sxi32 PH7_HashmapInsert( + ph7_hashmap *pMap, /* Target hashmap */ + ph7_value *pKey, /* Lookup key */ + ph7_value *pVal /* Node value.NULL otherwise */ + ) +{ + sxi32 rc; + if( pVal && (pVal->iFlags & MEMOBJ_HASHMAP) && (ph7_hashmap *)pVal->x.pOther == pMap->pVm->pGlobal ){ + /* + * TICKET 1433-35: Insertion in the $GLOBALS array is forbidden. + */ + PH7_VmThrowError(pMap->pVm,0,PH7_CTX_ERR,"$GLOBALS is a read-only array,insertion is forbidden"); + return SXRET_OK; + } + rc = HashmapInsert(&(*pMap),&(*pKey),&(*pVal)); + return rc; +} +/* + * Insert a given key and it's associated value (foreign index) in the given + * hashmap. + * This is insertion by reference so be careful to mark the node + * with the HASHMAP_NODE_FOREIGN_OBJ flag being set. + * The insertion by reference is triggered when the following + * expression is encountered. + * $var = 10; + * $a = array(&var); + * OR + * $a[] =& $var; + * That is,$var is a foreign ph7_value and the $a array have no control + * over it's contents. + * Note that the node that hold the foreign ph7_value is automatically + * removed when the foreign ph7_value is unset. + * Example: + * $var = 10; + * $a[] =& $var; + * echo count($a).PHP_EOL; //1 + * //Unset the foreign ph7_value now + * unset($var); + * echo count($a); //0 + * Note that this is a PH7 eXtension. + * Refer to the official documentation for more information. + * If a node with the given key already exists in the database + * then this function overwrite the old value. + */ +PH7_PRIVATE sxi32 PH7_HashmapInsertByRef( + ph7_hashmap *pMap, /* Target hashmap */ + ph7_value *pKey, /* Lookup key */ + sxu32 nRefIdx /* Foreign ph7_value index */ + ) +{ + sxi32 rc; + if( nRefIdx == pMap->pVm->nGlobalIdx ){ + /* + * TICKET 1433-35: Insertion in the $GLOBALS array is forbidden. + */ + PH7_VmThrowError(pMap->pVm,0,PH7_CTX_ERR,"$GLOBALS is a read-only array,insertion is forbidden"); + return SXRET_OK; + } + rc = HashmapInsertByRef(&(*pMap),&(*pKey),nRefIdx); + return rc; +} +/* + * Reset the node cursor of a given hashmap. + */ +PH7_PRIVATE void PH7_HashmapResetLoopCursor(ph7_hashmap *pMap) +{ + /* Reset the loop cursor */ + pMap->pCur = pMap->pFirst; +} +/* + * Return a pointer to the node currently pointed by the node cursor. + * If the cursor reaches the end of the list,then this function + * return NULL. + * Note that the node cursor is automatically advanced by this function. + */ +PH7_PRIVATE ph7_hashmap_node * PH7_HashmapGetNextEntry(ph7_hashmap *pMap) +{ + ph7_hashmap_node *pCur = pMap->pCur; + if( pCur == 0 ){ + /* End of the list,return null */ + return 0; + } + /* Advance the node cursor */ + pMap->pCur = pCur->pPrev; /* Reverse link */ + return pCur; +} +/* + * Extract a node value. + */ +PH7_PRIVATE void PH7_HashmapExtractNodeValue(ph7_hashmap_node *pNode,ph7_value *pValue,int bStore) +{ + ph7_value *pEntry = HashmapExtractNodeValue(pNode); + if( pEntry ){ + if( bStore ){ + PH7_MemObjStore(pEntry,pValue); + }else{ + PH7_MemObjLoad(pEntry,pValue); + } + }else{ + PH7_MemObjRelease(pValue); + } +} +/* + * Extract a node key. + */ +PH7_PRIVATE void PH7_HashmapExtractNodeKey(ph7_hashmap_node *pNode,ph7_value *pKey) +{ + /* Fill with the current key */ + if( pNode->iType == HASHMAP_INT_NODE ){ + if( SyBlobLength(&pKey->sBlob) > 0 ){ + SyBlobRelease(&pKey->sBlob); + } + pKey->x.iVal = pNode->xKey.iKey; + MemObjSetType(pKey,MEMOBJ_INT); + }else{ + SyBlobReset(&pKey->sBlob); + SyBlobAppend(&pKey->sBlob,SyBlobData(&pNode->xKey.sKey),SyBlobLength(&pNode->xKey.sKey)); + MemObjSetType(pKey,MEMOBJ_STRING); + } +} +#ifndef PH7_DISABLE_BUILTIN_FUNC +/* + * Store the address of nodes value in the given container. + * Refer to the [vfprintf(),vprintf(),vsprintf()] implementations + * defined in 'builtin.c' for more information. + */ +PH7_PRIVATE int PH7_HashmapValuesToSet(ph7_hashmap *pMap,SySet *pOut) +{ + ph7_hashmap_node *pEntry = pMap->pFirst; + ph7_value *pValue; + sxu32 n; + /* Initialize the container */ + SySetInit(pOut,&pMap->pVm->sAllocator,sizeof(ph7_value *)); + for(n = 0 ; n < pMap->nEntry ; n++ ){ + /* Extract node value */ + pValue = HashmapExtractNodeValue(pEntry); + if( pValue ){ + SySetPut(pOut,(const void *)&pValue); + } + /* Point to the next entry */ + pEntry = pEntry->pPrev; /* Reverse link */ + } + /* Total inserted entries */ + return (int)SySetUsed(pOut); +} +#endif /* PH7_DISABLE_BUILTIN_FUNC */ +/* + * Merge sort. + * The merge sort implementation is based on the one found in the SQLite3 source tree. + * Status: Public domain + */ +/* Node comparison callback signature */ +typedef sxi32 (*ProcNodeCmp)(ph7_hashmap_node *,ph7_hashmap_node *,void *); +/* +** Inputs: +** a: A sorted, null-terminated linked list. (May be null). +** b: A sorted, null-terminated linked list. (May be null). +** cmp: A pointer to the comparison function. +** +** Return Value: +** A pointer to the head of a sorted list containing the elements +** of both a and b. +** +** Side effects: +** The "next","prev" pointers for elements in the lists a and b are +** changed. +*/ +static ph7_hashmap_node * HashmapNodeMerge(ph7_hashmap_node *pA,ph7_hashmap_node *pB,ProcNodeCmp xCmp,void *pCmpData) +{ + ph7_hashmap_node result,*pTail; + /* Prevent compiler warning */ + result.pNext = result.pPrev = 0; + pTail = &result; + while( pA && pB ){ + if( xCmp(pA,pB,pCmpData) < 0 ){ + pTail->pPrev = pA; + pA->pNext = pTail; + pTail = pA; + pA = pA->pPrev; + }else{ + pTail->pPrev = pB; + pB->pNext = pTail; + pTail = pB; + pB = pB->pPrev; + } + } + if( pA ){ + pTail->pPrev = pA; + pA->pNext = pTail; + }else if( pB ){ + pTail->pPrev = pB; + pB->pNext = pTail; + }else{ + pTail->pPrev = pTail->pNext = 0; + } + return result.pPrev; +} +/* +** Inputs: +** Map: Input hashmap +** cmp: A comparison function. +** +** Return Value: +** Sorted hashmap. +** +** Side effects: +** The "next" pointers for elements in list are changed. +*/ +#define N_SORT_BUCKET 32 +static sxi32 HashmapMergeSort(ph7_hashmap *pMap,ProcNodeCmp xCmp,void *pCmpData) +{ + ph7_hashmap_node *a[N_SORT_BUCKET], *p,*pIn; + sxu32 i; + SyZero(a,sizeof(a)); + /* Point to the first inserted entry */ + pIn = pMap->pFirst; + while( pIn ){ + p = pIn; + pIn = p->pPrev; + p->pPrev = 0; + for(i=0; ipNext = 0; + /* Reflect the change */ + pMap->pFirst = p; + /* Reset the loop cursor */ + pMap->pCur = pMap->pFirst; + return SXRET_OK; +} +/* + * Node comparison callback. + * used-by: [sort(),asort(),...] + */ +static sxi32 HashmapCmpCallback1(ph7_hashmap_node *pA,ph7_hashmap_node *pB,void *pCmpData) +{ + ph7_value sA,sB; + sxi32 iFlags; + int rc; + if( pCmpData == 0 ){ + /* Perform a standard comparison */ + rc = HashmapNodeCmp(pA,pB,FALSE); + return rc; + } + iFlags = SX_PTR_TO_INT(pCmpData); + /* Duplicate node values */ + PH7_MemObjInit(pA->pMap->pVm,&sA); + PH7_MemObjInit(pA->pMap->pVm,&sB); + PH7_HashmapExtractNodeValue(pA,&sA,FALSE); + PH7_HashmapExtractNodeValue(pB,&sB,FALSE); + if( iFlags == 5 ){ + /* String cast */ + if( (sA.iFlags & MEMOBJ_STRING) == 0 ){ + PH7_MemObjToString(&sA); + } + if( (sB.iFlags & MEMOBJ_STRING) == 0 ){ + PH7_MemObjToString(&sB); + } + }else{ + /* Numeric cast */ + PH7_MemObjToNumeric(&sA); + PH7_MemObjToNumeric(&sB); + } + /* Perform the comparison */ + rc = PH7_MemObjCmp(&sA,&sB,FALSE,0); + PH7_MemObjRelease(&sA); + PH7_MemObjRelease(&sB); + return rc; +} +/* + * Node comparison callback: Compare nodes by keys only. + * used-by: [ksort()] + */ +static sxi32 HashmapCmpCallback2(ph7_hashmap_node *pA,ph7_hashmap_node *pB,void *pCmpData) +{ + sxi32 rc; + SXUNUSED(pCmpData); /* cc warning */ + if( pA->iType == HASHMAP_BLOB_NODE && pB->iType == HASHMAP_BLOB_NODE ){ + /* Perform a string comparison */ + rc = SyBlobCmp(&pA->xKey.sKey,&pB->xKey.sKey); + }else{ + SyString sStr; + sxi64 iA,iB; + /* Perform a numeric comparison */ + if( pA->iType == HASHMAP_BLOB_NODE ){ + /* Cast to 64-bit integer */ + SyStringInitFromBuf(&sStr,SyBlobData(&pA->xKey.sKey),SyBlobLength(&pA->xKey.sKey)); + if( sStr.nByte < 1 ){ + iA = 0; + }else{ + SyStrToInt64(sStr.zString,sStr.nByte,(void *)&iA,0); + } + }else{ + iA = pA->xKey.iKey; + } + if( pB->iType == HASHMAP_BLOB_NODE ){ + /* Cast to 64-bit integer */ + SyStringInitFromBuf(&sStr,SyBlobData(&pB->xKey.sKey),SyBlobLength(&pB->xKey.sKey)); + if( sStr.nByte < 1 ){ + iB = 0; + }else{ + SyStrToInt64(sStr.zString,sStr.nByte,(void *)&iB,0); + } + }else{ + iB = pB->xKey.iKey; + } + rc = (sxi32)(iA-iB); + } + /* Comparison result */ + return rc; +} +/* + * Node comparison callback. + * Used by: [rsort(),arsort()]; + */ +static sxi32 HashmapCmpCallback3(ph7_hashmap_node *pA,ph7_hashmap_node *pB,void *pCmpData) +{ + ph7_value sA,sB; + sxi32 iFlags; + int rc; + if( pCmpData == 0 ){ + /* Perform a standard comparison */ + rc = HashmapNodeCmp(pA,pB,FALSE); + return -rc; + } + iFlags = SX_PTR_TO_INT(pCmpData); + /* Duplicate node values */ + PH7_MemObjInit(pA->pMap->pVm,&sA); + PH7_MemObjInit(pA->pMap->pVm,&sB); + PH7_HashmapExtractNodeValue(pA,&sA,FALSE); + PH7_HashmapExtractNodeValue(pB,&sB,FALSE); + if( iFlags == 5 ){ + /* String cast */ + if( (sA.iFlags & MEMOBJ_STRING) == 0 ){ + PH7_MemObjToString(&sA); + } + if( (sB.iFlags & MEMOBJ_STRING) == 0 ){ + PH7_MemObjToString(&sB); + } + }else{ + /* Numeric cast */ + PH7_MemObjToNumeric(&sA); + PH7_MemObjToNumeric(&sB); + } + /* Perform the comparison */ + rc = PH7_MemObjCmp(&sA,&sB,FALSE,0); + PH7_MemObjRelease(&sA); + PH7_MemObjRelease(&sB); + return -rc; +} +/* + * Node comparison callback: Invoke an user-defined callback for the purpose of node comparison. + * used-by: [usort(),uasort()] + */ +static sxi32 HashmapCmpCallback4(ph7_hashmap_node *pA,ph7_hashmap_node *pB,void *pCmpData) +{ + ph7_value sResult,*pCallback; + ph7_value *pV1,*pV2; + ph7_value *apArg[2]; /* Callback arguments */ + sxi32 rc; + /* Point to the desired callback */ + pCallback = (ph7_value *)pCmpData; + /* initialize the result value */ + PH7_MemObjInit(pA->pMap->pVm,&sResult); + /* Extract nodes values */ + pV1 = HashmapExtractNodeValue(pA); + pV2 = HashmapExtractNodeValue(pB); + apArg[0] = pV1; + apArg[1] = pV2; + /* Invoke the callback */ + rc = PH7_VmCallUserFunction(pA->pMap->pVm,pCallback,2,apArg,&sResult); + if( rc != SXRET_OK ){ + /* An error occured while calling user defined function [i.e: not defined] */ + rc = -1; /* Set a dummy result */ + }else{ + /* Extract callback result */ + if((sResult.iFlags & MEMOBJ_INT) == 0 ){ + /* Perform an int cast */ + PH7_MemObjToInteger(&sResult); + } + rc = (sxi32)sResult.x.iVal; + } + PH7_MemObjRelease(&sResult); + /* Callback result */ + return rc; +} +/* + * Node comparison callback: Compare nodes by keys only. + * used-by: [krsort()] + */ +static sxi32 HashmapCmpCallback5(ph7_hashmap_node *pA,ph7_hashmap_node *pB,void *pCmpData) +{ + sxi32 rc; + SXUNUSED(pCmpData); /* cc warning */ + if( pA->iType == HASHMAP_BLOB_NODE && pB->iType == HASHMAP_BLOB_NODE ){ + /* Perform a string comparison */ + rc = SyBlobCmp(&pA->xKey.sKey,&pB->xKey.sKey); + }else{ + SyString sStr; + sxi64 iA,iB; + /* Perform a numeric comparison */ + if( pA->iType == HASHMAP_BLOB_NODE ){ + /* Cast to 64-bit integer */ + SyStringInitFromBuf(&sStr,SyBlobData(&pA->xKey.sKey),SyBlobLength(&pA->xKey.sKey)); + if( sStr.nByte < 1 ){ + iA = 0; + }else{ + SyStrToInt64(sStr.zString,sStr.nByte,(void *)&iA,0); + } + }else{ + iA = pA->xKey.iKey; + } + if( pB->iType == HASHMAP_BLOB_NODE ){ + /* Cast to 64-bit integer */ + SyStringInitFromBuf(&sStr,SyBlobData(&pB->xKey.sKey),SyBlobLength(&pB->xKey.sKey)); + if( sStr.nByte < 1 ){ + iB = 0; + }else{ + SyStrToInt64(sStr.zString,sStr.nByte,(void *)&iB,0); + } + }else{ + iB = pB->xKey.iKey; + } + rc = (sxi32)(iA-iB); + } + return -rc; /* Reverse result */ +} +/* + * Node comparison callback: Invoke an user-defined callback for the purpose of node comparison. + * used-by: [uksort()] + */ +static sxi32 HashmapCmpCallback6(ph7_hashmap_node *pA,ph7_hashmap_node *pB,void *pCmpData) +{ + ph7_value sResult,*pCallback; + ph7_value *apArg[2]; /* Callback arguments */ + ph7_value sK1,sK2; + sxi32 rc; + /* Point to the desired callback */ + pCallback = (ph7_value *)pCmpData; + /* initialize the result value */ + PH7_MemObjInit(pA->pMap->pVm,&sResult); + PH7_MemObjInit(pA->pMap->pVm,&sK1); + PH7_MemObjInit(pA->pMap->pVm,&sK2); + /* Extract nodes keys */ + PH7_HashmapExtractNodeKey(pA,&sK1); + PH7_HashmapExtractNodeKey(pB,&sK2); + apArg[0] = &sK1; + apArg[1] = &sK2; + /* Mark keys as constants */ + sK1.nIdx = SXU32_HIGH; + sK2.nIdx = SXU32_HIGH; + /* Invoke the callback */ + rc = PH7_VmCallUserFunction(pA->pMap->pVm,pCallback,2,apArg,&sResult); + if( rc != SXRET_OK ){ + /* An error occured while calling user defined function [i.e: not defined] */ + rc = -1; /* Set a dummy result */ + }else{ + /* Extract callback result */ + if((sResult.iFlags & MEMOBJ_INT) == 0 ){ + /* Perform an int cast */ + PH7_MemObjToInteger(&sResult); + } + rc = (sxi32)sResult.x.iVal; + } + PH7_MemObjRelease(&sResult); + PH7_MemObjRelease(&sK1); + PH7_MemObjRelease(&sK2); + /* Callback result */ + return rc; +} +/* + * Node comparison callback: Random node comparison. + * used-by: [shuffle()] + */ +static sxi32 HashmapCmpCallback7(ph7_hashmap_node *pA,ph7_hashmap_node *pB,void *pCmpData) +{ + sxu32 n; + SXUNUSED(pB); /* cc warning */ + SXUNUSED(pCmpData); + /* Grab a random number */ + n = PH7_VmRandomNum(pA->pMap->pVm); + /* if the random number is odd then the first node 'pA' is greater then + * the second node 'pB'. Otherwise the reverse is assumed. + */ + return n&1 ? 1 : -1; +} +/* + * Rehash all nodes keys after a merge-sort have been applied. + * Used by [sort(),usort() and rsort()]. + */ +static void HashmapSortRehash(ph7_hashmap *pMap) +{ + ph7_hashmap_node *p,*pLast; + sxu32 i; + /* Rehash all entries */ + pLast = p = pMap->pFirst; + pMap->iNextIdx = 0; /* Reset the automatic index */ + i = 0; + for( ;; ){ + if( i >= pMap->nEntry ){ + pMap->pLast = pLast; /* Fix the last link broken by the merge-sort */ + break; + } + if( p->iType == HASHMAP_BLOB_NODE ){ + /* Do not maintain index association as requested by the PHP specification */ + SyBlobRelease(&p->xKey.sKey); + /* Change key type */ + p->iType = HASHMAP_INT_NODE; + } + HashmapRehashIntNode(p); + /* Point to the next entry */ + i++; + pLast = p; + p = p->pPrev; /* Reverse link */ + } +} +/* + * Array functions implementation. + * Authors: + * Symisc Systems,devel@symisc.net. + * Copyright (C) Symisc Systems,http://ph7.symisc.net + * Status: + * Stable. + */ +/* + * bool sort(array &$array[,int $sort_flags = SORT_REGULAR ] ) + * Sort an array. + * Parameters + * $array + * The input array. + * $sort_flags + * The optional second parameter sort_flags may be used to modify the sorting behavior using these values: + * Sorting type flags: + * SORT_REGULAR - compare items normally (don't change types) + * SORT_NUMERIC - compare items numerically + * SORT_STRING - compare items as strings + * Return + * TRUE on success or FALSE on failure. + * + */ +static int ph7_hashmap_sort(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + ph7_hashmap *pMap; + /* Make sure we are dealing with a valid hashmap */ + if( nArg < 1 || !ph7_value_is_array(apArg[0]) ){ + /* Missing/Invalid arguments,return FALSE */ + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Point to the internal representation of the input hashmap */ + pMap = (ph7_hashmap *)apArg[0]->x.pOther; + if( pMap->nEntry > 1 ){ + sxi32 iCmpFlags = 0; + if( nArg > 1 ){ + /* Extract comparison flags */ + iCmpFlags = ph7_value_to_int(apArg[1]); + if( iCmpFlags == 3 /* SORT_REGULAR */ ){ + iCmpFlags = 0; /* Standard comparison */ + } + } + /* Do the merge sort */ + HashmapMergeSort(pMap,HashmapCmpCallback1,SX_INT_TO_PTR(iCmpFlags)); + /* Rehash [Do not maintain index association as requested by the PHP specification] */ + HashmapSortRehash(pMap); + } + /* All done,return TRUE */ + ph7_result_bool(pCtx,1); + return PH7_OK; +} +/* + * bool asort(array &$array[,int $sort_flags = SORT_REGULAR ] ) + * Sort an array and maintain index association. + * Parameters + * $array + * The input array. + * $sort_flags + * The optional second parameter sort_flags may be used to modify the sorting behavior using these values: + * Sorting type flags: + * SORT_REGULAR - compare items normally (don't change types) + * SORT_NUMERIC - compare items numerically + * SORT_STRING - compare items as strings + * Return + * TRUE on success or FALSE on failure. + */ +static int ph7_hashmap_asort(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + ph7_hashmap *pMap; + /* Make sure we are dealing with a valid hashmap */ + if( nArg < 1 || !ph7_value_is_array(apArg[0]) ){ + /* Missing/Invalid arguments,return FALSE */ + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Point to the internal representation of the input hashmap */ + pMap = (ph7_hashmap *)apArg[0]->x.pOther; + if( pMap->nEntry > 1 ){ + sxi32 iCmpFlags = 0; + if( nArg > 1 ){ + /* Extract comparison flags */ + iCmpFlags = ph7_value_to_int(apArg[1]); + if( iCmpFlags == 3 /* SORT_REGULAR */ ){ + iCmpFlags = 0; /* Standard comparison */ + } + } + /* Do the merge sort */ + HashmapMergeSort(pMap,HashmapCmpCallback1,SX_INT_TO_PTR(iCmpFlags)); + /* Fix the last link broken by the merge */ + while(pMap->pLast->pPrev){ + pMap->pLast = pMap->pLast->pPrev; + } + } + /* All done,return TRUE */ + ph7_result_bool(pCtx,1); + return PH7_OK; +} +/* + * bool arsort(array &$array[,int $sort_flags = SORT_REGULAR ] ) + * Sort an array in reverse order and maintain index association. + * Parameters + * $array + * The input array. + * $sort_flags + * The optional second parameter sort_flags may be used to modify the sorting behavior using these values: + * Sorting type flags: + * SORT_REGULAR - compare items normally (don't change types) + * SORT_NUMERIC - compare items numerically + * SORT_STRING - compare items as strings + * Return + * TRUE on success or FALSE on failure. + */ +static int ph7_hashmap_arsort(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + ph7_hashmap *pMap; + /* Make sure we are dealing with a valid hashmap */ + if( nArg < 1 || !ph7_value_is_array(apArg[0]) ){ + /* Missing/Invalid arguments,return FALSE */ + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Point to the internal representation of the input hashmap */ + pMap = (ph7_hashmap *)apArg[0]->x.pOther; + if( pMap->nEntry > 1 ){ + sxi32 iCmpFlags = 0; + if( nArg > 1 ){ + /* Extract comparison flags */ + iCmpFlags = ph7_value_to_int(apArg[1]); + if( iCmpFlags == 3 /* SORT_REGULAR */ ){ + iCmpFlags = 0; /* Standard comparison */ + } + } + /* Do the merge sort */ + HashmapMergeSort(pMap,HashmapCmpCallback3,SX_INT_TO_PTR(iCmpFlags)); + /* Fix the last link broken by the merge */ + while(pMap->pLast->pPrev){ + pMap->pLast = pMap->pLast->pPrev; + } + } + /* All done,return TRUE */ + ph7_result_bool(pCtx,1); + return PH7_OK; +} +/* + * bool ksort(array &$array[,int $sort_flags = SORT_REGULAR ] ) + * Sort an array by key. + * Parameters + * $array + * The input array. + * $sort_flags + * The optional second parameter sort_flags may be used to modify the sorting behavior using these values: + * Sorting type flags: + * SORT_REGULAR - compare items normally (don't change types) + * SORT_NUMERIC - compare items numerically + * SORT_STRING - compare items as strings + * Return + * TRUE on success or FALSE on failure. + */ +static int ph7_hashmap_ksort(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + ph7_hashmap *pMap; + /* Make sure we are dealing with a valid hashmap */ + if( nArg < 1 || !ph7_value_is_array(apArg[0]) ){ + /* Missing/Invalid arguments,return FALSE */ + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Point to the internal representation of the input hashmap */ + pMap = (ph7_hashmap *)apArg[0]->x.pOther; + if( pMap->nEntry > 1 ){ + sxi32 iCmpFlags = 0; + if( nArg > 1 ){ + /* Extract comparison flags */ + iCmpFlags = ph7_value_to_int(apArg[1]); + if( iCmpFlags == 3 /* SORT_REGULAR */ ){ + iCmpFlags = 0; /* Standard comparison */ + } + } + /* Do the merge sort */ + HashmapMergeSort(pMap,HashmapCmpCallback2,SX_INT_TO_PTR(iCmpFlags)); + /* Fix the last link broken by the merge */ + while(pMap->pLast->pPrev){ + pMap->pLast = pMap->pLast->pPrev; + } + } + /* All done,return TRUE */ + ph7_result_bool(pCtx,1); + return PH7_OK; +} +/* + * bool krsort(array &$array[,int $sort_flags = SORT_REGULAR ] ) + * Sort an array by key in reverse order. + * Parameters + * $array + * The input array. + * $sort_flags + * The optional second parameter sort_flags may be used to modify the sorting behavior using these values: + * Sorting type flags: + * SORT_REGULAR - compare items normally (don't change types) + * SORT_NUMERIC - compare items numerically + * SORT_STRING - compare items as strings + * Return + * TRUE on success or FALSE on failure. + */ +static int ph7_hashmap_krsort(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + ph7_hashmap *pMap; + /* Make sure we are dealing with a valid hashmap */ + if( nArg < 1 || !ph7_value_is_array(apArg[0]) ){ + /* Missing/Invalid arguments,return FALSE */ + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Point to the internal representation of the input hashmap */ + pMap = (ph7_hashmap *)apArg[0]->x.pOther; + if( pMap->nEntry > 1 ){ + sxi32 iCmpFlags = 0; + if( nArg > 1 ){ + /* Extract comparison flags */ + iCmpFlags = ph7_value_to_int(apArg[1]); + if( iCmpFlags == 3 /* SORT_REGULAR */ ){ + iCmpFlags = 0; /* Standard comparison */ + } + } + /* Do the merge sort */ + HashmapMergeSort(pMap,HashmapCmpCallback5,SX_INT_TO_PTR(iCmpFlags)); + /* Fix the last link broken by the merge */ + while(pMap->pLast->pPrev){ + pMap->pLast = pMap->pLast->pPrev; + } + } + /* All done,return TRUE */ + ph7_result_bool(pCtx,1); + return PH7_OK; +} +/* + * bool rsort(array &$array[,int $sort_flags = SORT_REGULAR ] ) + * Sort an array in reverse order. + * Parameters + * $array + * The input array. + * $sort_flags + * The optional second parameter sort_flags may be used to modify the sorting behavior using these values: + * Sorting type flags: + * SORT_REGULAR - compare items normally (don't change types) + * SORT_NUMERIC - compare items numerically + * SORT_STRING - compare items as strings + * Return + * TRUE on success or FALSE on failure. + */ +static int ph7_hashmap_rsort(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + ph7_hashmap *pMap; + /* Make sure we are dealing with a valid hashmap */ + if( nArg < 1 || !ph7_value_is_array(apArg[0]) ){ + /* Missing/Invalid arguments,return FALSE */ + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Point to the internal representation of the input hashmap */ + pMap = (ph7_hashmap *)apArg[0]->x.pOther; + if( pMap->nEntry > 1 ){ + sxi32 iCmpFlags = 0; + if( nArg > 1 ){ + /* Extract comparison flags */ + iCmpFlags = ph7_value_to_int(apArg[1]); + if( iCmpFlags == 3 /* SORT_REGULAR */ ){ + iCmpFlags = 0; /* Standard comparison */ + } + } + /* Do the merge sort */ + HashmapMergeSort(pMap,HashmapCmpCallback3,SX_INT_TO_PTR(iCmpFlags)); + /* Rehash [Do not maintain index association as requested by the PHP specification] */ + HashmapSortRehash(pMap); + } + /* All done,return TRUE */ + ph7_result_bool(pCtx,1); + return PH7_OK; +} +/* + * bool usort(array &$array,callable $cmp_function) + * Sort an array by values using a user-defined comparison function. + * Parameters + * $array + * The input array. + * $cmp_function + * The comparison function must return an integer less than, equal to, or greater + * than zero if the first argument is considered to be respectively less than, equal + * to, or greater than the second. + * int callback ( mixed $a, mixed $b ) + * Return + * TRUE on success or FALSE on failure. + */ +static int ph7_hashmap_usort(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + ph7_hashmap *pMap; + /* Make sure we are dealing with a valid hashmap */ + if( nArg < 1 || !ph7_value_is_array(apArg[0]) ){ + /* Missing/Invalid arguments,return FALSE */ + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Point to the internal representation of the input hashmap */ + pMap = (ph7_hashmap *)apArg[0]->x.pOther; + if( pMap->nEntry > 1 ){ + ph7_value *pCallback = 0; + ProcNodeCmp xCmp; + xCmp = HashmapCmpCallback4; /* User-defined function as the comparison callback */ + if( nArg > 1 && ph7_value_is_callable(apArg[1]) ){ + /* Point to the desired callback */ + pCallback = apArg[1]; + }else{ + /* Use the default comparison function */ + xCmp = HashmapCmpCallback1; + } + /* Do the merge sort */ + HashmapMergeSort(pMap,xCmp,pCallback); + /* Rehash [Do not maintain index association as requested by the PHP specification] */ + HashmapSortRehash(pMap); + } + /* All done,return TRUE */ + ph7_result_bool(pCtx,1); + return PH7_OK; +} +/* + * bool uasort(array &$array,callable $cmp_function) + * Sort an array by values using a user-defined comparison function + * and maintain index association. + * Parameters + * $array + * The input array. + * $cmp_function + * The comparison function must return an integer less than, equal to, or greater + * than zero if the first argument is considered to be respectively less than, equal + * to, or greater than the second. + * int callback ( mixed $a, mixed $b ) + * Return + * TRUE on success or FALSE on failure. + */ +static int ph7_hashmap_uasort(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + ph7_hashmap *pMap; + /* Make sure we are dealing with a valid hashmap */ + if( nArg < 1 || !ph7_value_is_array(apArg[0]) ){ + /* Missing/Invalid arguments,return FALSE */ + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Point to the internal representation of the input hashmap */ + pMap = (ph7_hashmap *)apArg[0]->x.pOther; + if( pMap->nEntry > 1 ){ + ph7_value *pCallback = 0; + ProcNodeCmp xCmp; + xCmp = HashmapCmpCallback4; /* User-defined function as the comparison callback */ + if( nArg > 1 && ph7_value_is_callable(apArg[1]) ){ + /* Point to the desired callback */ + pCallback = apArg[1]; + }else{ + /* Use the default comparison function */ + xCmp = HashmapCmpCallback1; + } + /* Do the merge sort */ + HashmapMergeSort(pMap,xCmp,pCallback); + /* Fix the last link broken by the merge */ + while(pMap->pLast->pPrev){ + pMap->pLast = pMap->pLast->pPrev; + } + } + /* All done,return TRUE */ + ph7_result_bool(pCtx,1); + return PH7_OK; +} +/* + * bool uksort(array &$array,callable $cmp_function) + * Sort an array by keys using a user-defined comparison + * function and maintain index association. + * Parameters + * $array + * The input array. + * $cmp_function + * The comparison function must return an integer less than, equal to, or greater + * than zero if the first argument is considered to be respectively less than, equal + * to, or greater than the second. + * int callback ( mixed $a, mixed $b ) + * Return + * TRUE on success or FALSE on failure. + */ +static int ph7_hashmap_uksort(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + ph7_hashmap *pMap; + /* Make sure we are dealing with a valid hashmap */ + if( nArg < 1 || !ph7_value_is_array(apArg[0]) ){ + /* Missing/Invalid arguments,return FALSE */ + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Point to the internal representation of the input hashmap */ + pMap = (ph7_hashmap *)apArg[0]->x.pOther; + if( pMap->nEntry > 1 ){ + ph7_value *pCallback = 0; + ProcNodeCmp xCmp; + xCmp = HashmapCmpCallback6; /* User-defined function as the comparison callback */ + if( nArg > 1 && ph7_value_is_callable(apArg[1]) ){ + /* Point to the desired callback */ + pCallback = apArg[1]; + }else{ + /* Use the default comparison function */ + xCmp = HashmapCmpCallback2; + } + /* Do the merge sort */ + HashmapMergeSort(pMap,xCmp,pCallback); + /* Fix the last link broken by the merge */ + while(pMap->pLast->pPrev){ + pMap->pLast = pMap->pLast->pPrev; + } + } + /* All done,return TRUE */ + ph7_result_bool(pCtx,1); + return PH7_OK; +} +/* + * bool shuffle(array &$array) + * shuffles (randomizes the order of the elements in) an array. + * Parameters + * $array + * The input array. + * Return + * TRUE on success or FALSE on failure. + * + */ +static int ph7_hashmap_shuffle(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + ph7_hashmap *pMap; + /* Make sure we are dealing with a valid hashmap */ + if( nArg < 1 || !ph7_value_is_array(apArg[0]) ){ + /* Missing/Invalid arguments,return FALSE */ + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Point to the internal representation of the input hashmap */ + pMap = (ph7_hashmap *)apArg[0]->x.pOther; + if( pMap->nEntry > 1 ){ + /* Do the merge sort */ + HashmapMergeSort(pMap,HashmapCmpCallback7,0); + /* Fix the last link broken by the merge */ + while(pMap->pLast->pPrev){ + pMap->pLast = pMap->pLast->pPrev; + } + } + /* All done,return TRUE */ + ph7_result_bool(pCtx,1); + return PH7_OK; +} +/* + * int count(array $var [, int $mode = COUNT_NORMAL ]) + * Count all elements in an array, or something in an object. + * Parameters + * $var + * The array or the object. + * $mode + * If the optional mode parameter is set to COUNT_RECURSIVE (or 1), count() + * will recursively count the array. This is particularly useful for counting + * all the elements of a multidimensional array. count() does not detect infinite + * recursion. + * Return + * Returns the number of elements in the array. + */ +static int ph7_hashmap_count(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + int bRecursive = FALSE; + sxi64 iCount; + if( nArg < 1 ){ + /* Missing arguments,return 0 */ + ph7_result_int(pCtx,0); + return PH7_OK; + } + if( !ph7_value_is_array(apArg[0]) ){ + /* TICKET 1433-19: Handle objects */ + int res = !ph7_value_is_null(apArg[0]); + ph7_result_int(pCtx,res); + return PH7_OK; + } + if( nArg > 1 ){ + /* Recursive count? */ + bRecursive = ph7_value_to_int(apArg[1]) == 1 /* COUNT_RECURSIVE */; + } + /* Count */ + iCount = HashmapCount((ph7_hashmap *)apArg[0]->x.pOther,bRecursive,0); + ph7_result_int64(pCtx,iCount); + return PH7_OK; +} +/* + * bool array_key_exists(value $key,array $search) + * Checks if the given key or index exists in the array. + * Parameters + * $key + * Value to check. + * $search + * An array with keys to check. + * Return + * TRUE on success or FALSE on failure. + */ +static int ph7_hashmap_key_exists(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + sxi32 rc; + if( nArg < 2 ){ + /* Missing arguments,return FALSE */ + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Make sure we are dealing with a valid hashmap */ + if( !ph7_value_is_array(apArg[1]) ){ + /* Invalid argument,return FALSE */ + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Perform the lookup */ + rc = PH7_HashmapLookup((ph7_hashmap *)apArg[1]->x.pOther,apArg[0],0); + /* lookup result */ + ph7_result_bool(pCtx,rc == SXRET_OK ? 1 : 0); + return PH7_OK; +} +/* + * value array_pop(array $array) + * POP the last inserted element from the array. + * Parameter + * The array to get the value from. + * Return + * Poped value or NULL on failure. + */ +static int ph7_hashmap_pop(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + ph7_hashmap *pMap; + if( nArg < 1 ){ + /* Missing arguments,return null */ + ph7_result_null(pCtx); + return PH7_OK; + } + /* Make sure we are dealing with a valid hashmap */ + if( !ph7_value_is_array(apArg[0]) ){ + /* Invalid argument,return null */ + ph7_result_null(pCtx); + return PH7_OK; + } + pMap = (ph7_hashmap *)apArg[0]->x.pOther; + if( pMap->nEntry < 1 ){ + /* Noting to pop,return NULL */ + ph7_result_null(pCtx); + }else{ + ph7_hashmap_node *pLast = pMap->pLast; + ph7_value *pObj; + pObj = HashmapExtractNodeValue(pLast); + if( pObj ){ + /* Node value */ + ph7_result_value(pCtx,pObj); + /* Unlink the node */ + PH7_HashmapUnlinkNode(pLast,TRUE); + }else{ + ph7_result_null(pCtx); + } + /* Reset the cursor */ + pMap->pCur = pMap->pFirst; + } + return PH7_OK; +} +/* + * int array_push($array,$var,...) + * Push one or more elements onto the end of array. (Stack insertion) + * Parameters + * array + * The input array. + * var + * On or more value to push. + * Return + * New array count (including old items). + */ +static int ph7_hashmap_push(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + ph7_hashmap *pMap; + sxi32 rc; + int i; + if( nArg < 1 ){ + /* Missing arguments,return 0 */ + ph7_result_int(pCtx,0); + return PH7_OK; + } + /* Make sure we are dealing with a valid hashmap */ + if( !ph7_value_is_array(apArg[0]) ){ + /* Invalid argument,return 0 */ + ph7_result_int(pCtx,0); + return PH7_OK; + } + /* Point to the internal representation of the input hashmap */ + pMap = (ph7_hashmap *)apArg[0]->x.pOther; + /* Start pushing given values */ + for( i = 1 ; i < nArg ; ++i ){ + rc = PH7_HashmapInsert(pMap,0,apArg[i]); + if( rc != SXRET_OK ){ + break; + } + } + /* Return the new count */ + ph7_result_int64(pCtx,(sxi64)pMap->nEntry); + return PH7_OK; +} +/* + * value array_shift(array $array) + * Shift an element off the beginning of array. + * Parameter + * The array to get the value from. + * Return + * Shifted value or NULL on failure. + */ +static int ph7_hashmap_shift(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + ph7_hashmap *pMap; + if( nArg < 1 ){ + /* Missing arguments,return null */ + ph7_result_null(pCtx); + return PH7_OK; + } + /* Make sure we are dealing with a valid hashmap */ + if( !ph7_value_is_array(apArg[0]) ){ + /* Invalid argument,return null */ + ph7_result_null(pCtx); + return PH7_OK; + } + /* Point to the internal representation of the hashmap */ + pMap = (ph7_hashmap *)apArg[0]->x.pOther; + if( pMap->nEntry < 1 ){ + /* Empty hashmap,return NULL */ + ph7_result_null(pCtx); + }else{ + ph7_hashmap_node *pEntry = pMap->pFirst; + ph7_value *pObj; + sxu32 n; + pObj = HashmapExtractNodeValue(pEntry); + if( pObj ){ + /* Node value */ + ph7_result_value(pCtx,pObj); + /* Unlink the first node */ + PH7_HashmapUnlinkNode(pEntry,TRUE); + }else{ + ph7_result_null(pCtx); + } + /* Rehash all int keys */ + n = pMap->nEntry; + pEntry = pMap->pFirst; + pMap->iNextIdx = 0; /* Reset the automatic index */ + for(;;){ + if( n < 1 ){ + break; + } + if( pEntry->iType == HASHMAP_INT_NODE ){ + HashmapRehashIntNode(pEntry); + } + /* Point to the next entry */ + pEntry = pEntry->pPrev; /* Reverse link */ + n--; + } + /* Reset the cursor */ + pMap->pCur = pMap->pFirst; + } + return PH7_OK; +} +/* + * Extract the node cursor value. + */ +static sxi32 HashmapCurrentValue(ph7_context *pCtx,ph7_hashmap *pMap,int iDirection) +{ + ph7_hashmap_node *pCur = pMap->pCur; + ph7_value *pVal; + if( pCur == 0 ){ + /* Cursor does not point to anything,return FALSE */ + ph7_result_bool(pCtx,0); + return PH7_OK; + } + if( iDirection != 0 ){ + if( iDirection > 0 ){ + /* Point to the next entry */ + pMap->pCur = pCur->pPrev; /* Reverse link */ + pCur = pMap->pCur; + }else{ + /* Point to the previous entry */ + pMap->pCur = pCur->pNext; /* Reverse link */ + pCur = pMap->pCur; + } + if( pCur == 0 ){ + /* End of input reached,return FALSE */ + ph7_result_bool(pCtx,0); + return PH7_OK; + } + } + /* Point to the desired element */ + pVal = HashmapExtractNodeValue(pCur); + if( pVal ){ + ph7_result_value(pCtx,pVal); + }else{ + ph7_result_bool(pCtx,0); + } + return PH7_OK; +} +/* + * value current(array $array) + * Return the current element in an array. + * Parameter + * $input: The input array. + * Return + * The current() function simply returns the value of the array element that's currently + * being pointed to by the internal pointer. It does not move the pointer in any way. + * If the internal pointer points beyond the end of the elements list or the array + * is empty, current() returns FALSE. + */ +static int ph7_hashmap_current(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + if( nArg < 1 ){ + /* Missing arguments,return FALSE */ + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Make sure we are dealing with a valid hashmap */ + if( !ph7_value_is_array(apArg[0]) ){ + /* Invalid argument,return FALSE */ + ph7_result_bool(pCtx,0); + return PH7_OK; + } + HashmapCurrentValue(&(*pCtx),(ph7_hashmap *)apArg[0]->x.pOther,0); + return PH7_OK; +} +/* + * value next(array $input) + * Advance the internal array pointer of an array. + * Parameter + * $input: The input array. + * Return + * next() behaves like current(), with one difference. It advances the internal array + * pointer one place forward before returning the element value. That means it returns + * the next array value and advances the internal array pointer by one. + */ +static int ph7_hashmap_next(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + if( nArg < 1 ){ + /* Missing arguments,return FALSE */ + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Make sure we are dealing with a valid hashmap */ + if( !ph7_value_is_array(apArg[0]) ){ + /* Invalid argument,return FALSE */ + ph7_result_bool(pCtx,0); + return PH7_OK; + } + HashmapCurrentValue(&(*pCtx),(ph7_hashmap *)apArg[0]->x.pOther,1); + return PH7_OK; +} +/* + * value prev(array $input) + * Rewind the internal array pointer. + * Parameter + * $input: The input array. + * Return + * Returns the array value in the previous place that's pointed + * to by the internal array pointer, or FALSE if there are no more + * elements. + */ +static int ph7_hashmap_prev(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + if( nArg < 1 ){ + /* Missing arguments,return FALSE */ + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Make sure we are dealing with a valid hashmap */ + if( !ph7_value_is_array(apArg[0]) ){ + /* Invalid argument,return FALSE */ + ph7_result_bool(pCtx,0); + return PH7_OK; + } + HashmapCurrentValue(&(*pCtx),(ph7_hashmap *)apArg[0]->x.pOther,-1); + return PH7_OK; +} +/* + * value end(array $input) + * Set the internal pointer of an array to its last element. + * Parameter + * $input: The input array. + * Return + * Returns the value of the last element or FALSE for empty array. + */ +static int ph7_hashmap_end(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + ph7_hashmap *pMap; + if( nArg < 1 ){ + /* Missing arguments,return FALSE */ + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Make sure we are dealing with a valid hashmap */ + if( !ph7_value_is_array(apArg[0]) ){ + /* Invalid argument,return FALSE */ + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Point to the internal representation of the input hashmap */ + pMap = (ph7_hashmap *)apArg[0]->x.pOther; + /* Point to the last node */ + pMap->pCur = pMap->pLast; + /* Return the last node value */ + HashmapCurrentValue(&(*pCtx),pMap,0); + return PH7_OK; +} +/* + * value reset(array $array ) + * Set the internal pointer of an array to its first element. + * Parameter + * $input: The input array. + * Return + * Returns the value of the first array element,or FALSE if the array is empty. + */ +static int ph7_hashmap_reset(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + ph7_hashmap *pMap; + if( nArg < 1 ){ + /* Missing arguments,return FALSE */ + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Make sure we are dealing with a valid hashmap */ + if( !ph7_value_is_array(apArg[0]) ){ + /* Invalid argument,return FALSE */ + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Point to the internal representation of the input hashmap */ + pMap = (ph7_hashmap *)apArg[0]->x.pOther; + /* Point to the first node */ + pMap->pCur = pMap->pFirst; + /* Return the last node value if available */ + HashmapCurrentValue(&(*pCtx),pMap,0); + return PH7_OK; +} +/* + * value key(array $array) + * Fetch a key from an array + * Parameter + * $input + * The input array. + * Return + * The key() function simply returns the key of the array element that's currently + * being pointed to by the internal pointer. It does not move the pointer in any way. + * If the internal pointer points beyond the end of the elements list or the array + * is empty, key() returns NULL. + */ +static int ph7_hashmap_simple_key(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + ph7_hashmap_node *pCur; + ph7_hashmap *pMap; + if( nArg < 1 ){ + /* Missing arguments,return NULL */ + ph7_result_null(pCtx); + return PH7_OK; + } + /* Make sure we are dealing with a valid hashmap */ + if( !ph7_value_is_array(apArg[0]) ){ + /* Invalid argument,return NULL */ + ph7_result_null(pCtx); + return PH7_OK; + } + pMap = (ph7_hashmap *)apArg[0]->x.pOther; + pCur = pMap->pCur; + if( pCur == 0 ){ + /* Cursor does not point to anything,return NULL */ + ph7_result_null(pCtx); + return PH7_OK; + } + if( pCur->iType == HASHMAP_INT_NODE){ + /* Key is integer */ + ph7_result_int64(pCtx,pCur->xKey.iKey); + }else{ + /* Key is blob */ + ph7_result_string(pCtx, + (const char *)SyBlobData(&pCur->xKey.sKey),(int)SyBlobLength(&pCur->xKey.sKey)); + } + return PH7_OK; +} +/* + * array each(array $input) + * Return the current key and value pair from an array and advance the array cursor. + * Parameter + * $input + * The input array. + * Return + * Returns the current key and value pair from the array array. This pair is returned + * in a four-element array, with the keys 0, 1, key, and value. Elements 0 and key + * contain the key name of the array element, and 1 and value contain the data. + * If the internal pointer for the array points past the end of the array contents + * each() returns FALSE. + */ +static int ph7_hashmap_each(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + ph7_hashmap_node *pCur; + ph7_hashmap *pMap; + ph7_value *pArray; + ph7_value *pVal; + ph7_value sKey; + if( nArg < 1 ){ + /* Missing arguments,return FALSE */ + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Make sure we are dealing with a valid hashmap */ + if( !ph7_value_is_array(apArg[0]) ){ + /* Invalid argument,return FALSE */ + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Point to the internal representation that describe the input hashmap */ + pMap = (ph7_hashmap *)apArg[0]->x.pOther; + if( pMap->pCur == 0 ){ + /* Cursor does not point to anything,return FALSE */ + ph7_result_bool(pCtx,0); + return PH7_OK; + } + pCur = pMap->pCur; + /* Create a new array */ + pArray = ph7_context_new_array(pCtx); + if( pArray == 0 ){ + ph7_result_bool(pCtx,0); + return PH7_OK; + } + pVal = HashmapExtractNodeValue(pCur); + /* Insert the current value */ + ph7_array_add_intkey_elem(pArray,1,pVal); + ph7_array_add_strkey_elem(pArray,"value",pVal); + /* Make the key */ + if( pCur->iType == HASHMAP_INT_NODE ){ + PH7_MemObjInitFromInt(pMap->pVm,&sKey,pCur->xKey.iKey); + }else{ + PH7_MemObjInitFromString(pMap->pVm,&sKey,0); + PH7_MemObjStringAppend(&sKey,(const char *)SyBlobData(&pCur->xKey.sKey),SyBlobLength(&pCur->xKey.sKey)); + } + /* Insert the current key */ + ph7_array_add_intkey_elem(pArray,0,&sKey); + ph7_array_add_strkey_elem(pArray,"key",&sKey); + PH7_MemObjRelease(&sKey); + /* Advance the cursor */ + pMap->pCur = pCur->pPrev; /* Reverse link */ + /* Return the current entry */ + ph7_result_value(pCtx,pArray); + return PH7_OK; +} +/* + * array range(int $start,int $limit,int $step) + * Create an array containing a range of elements + * Parameter + * start + * First value of the sequence. + * limit + * The sequence is ended upon reaching the limit value. + * step + * If a step value is given, it will be used as the increment between elements in the sequence. + * step should be given as a positive number. If not specified, step will default to 1. + * Return + * An array of elements from start to limit, inclusive. + * NOTE: + * Only 32/64 bit integer key is supported. + */ +static int ph7_hashmap_range(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + ph7_value *pValue,*pArray; + sxi64 iOfft,iLimit; + int iStep = 1; + + iOfft = iLimit = 0; /* cc -O6 */ + if( nArg > 0 ){ + /* Extract the offset */ + iOfft = ph7_value_to_int64(apArg[0]); + if( nArg > 1 ){ + /* Extract the limit */ + iLimit = ph7_value_to_int64(apArg[1]); + if( nArg > 2 ){ + /* Extract the increment */ + iStep = ph7_value_to_int(apArg[2]); + if( iStep < 1 ){ + /* Only positive number are allowed */ + iStep = 1; + } + } + } + } + /* Element container */ + pValue = ph7_context_new_scalar(pCtx); + /* Create the new array */ + pArray = ph7_context_new_array(pCtx); + if( pArray == 0 ){ + ph7_result_null(pCtx); + return PH7_OK; + } + /* Start filling */ + while( iOfft <= iLimit ){ + ph7_value_int64(pValue,iOfft); + /* Perform the insertion */ + ph7_array_add_elem(pArray,0/* Automatic index assign*/,pValue); + /* Increment */ + iOfft += iStep; + } + /* Return the new array */ + ph7_result_value(pCtx,pArray); + /* Dont'worry about freeing 'pValue',it will be released automatically + * by the virtual machine as soon we return from this foreign function. + */ + return PH7_OK; +} +/* + * array array_values(array $input) + * Returns all the values from the input array and indexes numerically the array. + * Parameters + * input: The input array. + * Return + * An indexed array of values or NULL on failure. + */ +static int ph7_hashmap_values(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + ph7_hashmap_node *pNode; + ph7_hashmap *pMap; + ph7_value *pArray; + ph7_value *pObj; + sxu32 n; + if( nArg < 1 ){ + /* Missing arguments,return NULL */ + ph7_result_null(pCtx); + return PH7_OK; + } + /* Make sure we are dealing with a valid hashmap */ + if( !ph7_value_is_array(apArg[0]) ){ + /* Invalid argument,return NULL */ + ph7_result_null(pCtx); + return PH7_OK; + } + /* Point to the internal representation that describe the input hashmap */ + pMap = (ph7_hashmap *)apArg[0]->x.pOther; + /* Create a new array */ + pArray = ph7_context_new_array(pCtx); + if( pArray == 0 ){ + ph7_result_null(pCtx); + return PH7_OK; + } + /* Perform the requested operation */ + pNode = pMap->pFirst; + for( n = 0 ; n < pMap->nEntry ; ++n ){ + pObj = HashmapExtractNodeValue(pNode); + if( pObj ){ + /* perform the insertion */ + ph7_array_add_elem(pArray,0/* Automatic index assign */,pObj); + } + /* Point to the next entry */ + pNode = pNode->pPrev; /* Reverse link */ + } + /* return the new array */ + ph7_result_value(pCtx,pArray); + return PH7_OK; +} +/* + * array array_keys(array $input [, val $search_value [, bool $strict = false ]] ) + * Return all the keys or a subset of the keys of an array. + * Parameters + * $input + * An array containing keys to return. + * $search_value + * If specified, then only keys containing these values are returned. + * $strict + * Determines if strict comparison (===) should be used during the search. + * Return + * An array of all the keys in input or NULL on failure. + */ +static int ph7_hashmap_keys(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + ph7_hashmap_node *pNode; + ph7_hashmap *pMap; + ph7_value *pArray; + ph7_value sObj; + ph7_value sVal; + SyString sKey; + int bStrict; + sxi32 rc; + sxu32 n; + if( nArg < 1 ){ + /* Missing arguments,return NULL */ + ph7_result_null(pCtx); + return PH7_OK; + } + /* Make sure we are dealing with a valid hashmap */ + if( !ph7_value_is_array(apArg[0]) ){ + /* Invalid argument,return NULL */ + ph7_result_null(pCtx); + return PH7_OK; + } + /* Point to the internal representation of the input hashmap */ + pMap = (ph7_hashmap *)apArg[0]->x.pOther; + /* Create a new array */ + pArray = ph7_context_new_array(pCtx); + if( pArray == 0 ){ + ph7_result_null(pCtx); + return PH7_OK; + } + bStrict = FALSE; + if( nArg > 2 && ph7_value_is_bool(apArg[2]) ){ + bStrict = ph7_value_to_bool(apArg[2]); + } + /* Perform the requested operation */ + pNode = pMap->pFirst; + PH7_MemObjInit(pMap->pVm,&sVal); + for( n = 0 ; n < pMap->nEntry ; ++n ){ + if( pNode->iType == HASHMAP_INT_NODE ){ + PH7_MemObjInitFromInt(pMap->pVm,&sObj,pNode->xKey.iKey); + }else{ + SyStringInitFromBuf(&sKey,SyBlobData(&pNode->xKey.sKey),SyBlobLength(&pNode->xKey.sKey)); + PH7_MemObjInitFromString(pMap->pVm,&sObj,&sKey); + } + rc = 0; + if( nArg > 1 ){ + ph7_value *pValue = HashmapExtractNodeValue(pNode); + if( pValue ){ + PH7_MemObjLoad(pValue,&sVal); + /* Filter key */ + rc = ph7_value_compare(&sVal,apArg[1],bStrict); + PH7_MemObjRelease(pValue); + } + } + if( rc == 0 ){ + /* Perform the insertion */ + ph7_array_add_elem(pArray,0,&sObj); + } + PH7_MemObjRelease(&sObj); + /* Point to the next entry */ + pNode = pNode->pPrev; /* Reverse link */ + } + /* return the new array */ + ph7_result_value(pCtx,pArray); + return PH7_OK; +} +/* + * bool array_same(array $arr1,array $arr2) + * Return TRUE if the given arrays are the same instance. + * This function is useful under PH7 since arrays are passed + * by reference unlike the zend engine which use pass by values. + * Parameters + * $arr1 + * First array + * $arr2 + * Second array + * Return + * TRUE if the arrays are the same instance.FALSE otherwise. + * Note + * This function is a symisc eXtension. + */ +static int ph7_hashmap_same(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + ph7_hashmap *p1,*p2; + int rc; + if( nArg < 2 || !ph7_value_is_array(apArg[0]) || !ph7_value_is_array(apArg[1]) ){ + /* Missing or invalid arguments,return FALSE*/ + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Point to the hashmaps */ + p1 = (ph7_hashmap *)apArg[0]->x.pOther; + p2 = (ph7_hashmap *)apArg[1]->x.pOther; + rc = (p1 == p2); + /* Same instance? */ + ph7_result_bool(pCtx,rc); + return PH7_OK; +} +/* + * array array_merge(array $array1,...) + * Merge one or more arrays. + * Parameters + * $array1 + * Initial array to merge. + * ... + * More array to merge. + * Return + * The resulting array. + */ +static int ph7_hashmap_merge(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + ph7_hashmap *pMap,*pSrc; + ph7_value *pArray; + int i; + if( nArg < 1 ){ + /* Missing arguments,return NULL */ + ph7_result_null(pCtx); + return PH7_OK; + } + /* Create a new array */ + pArray = ph7_context_new_array(pCtx); + if( pArray == 0 ){ + ph7_result_null(pCtx); + return PH7_OK; + } + /* Point to the internal representation of the hashmap */ + pMap = (ph7_hashmap *)pArray->x.pOther; + /* Start merging */ + for( i = 0 ; i < nArg ; i++ ){ + /* Make sure we are dealing with a valid hashmap */ + if( !ph7_value_is_array(apArg[i]) ){ + /* Insert scalar value */ + ph7_array_add_elem(pArray,0,apArg[i]); + }else{ + pSrc = (ph7_hashmap *)apArg[i]->x.pOther; + /* Merge the two hashmaps */ + HashmapMerge(pSrc,pMap); + } + } + /* Return the freshly created array */ + ph7_result_value(pCtx,pArray); + return PH7_OK; +} +/* + * array array_copy(array $source) + * Make a blind copy of the target array. + * Parameters + * $source + * Target array + * Return + * Copy of the target array on success.NULL otherwise. + * Note + * This function is a symisc eXtension. + */ +static int ph7_hashmap_copy(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + ph7_hashmap *pMap; + ph7_value *pArray; + if( nArg < 1 ){ + /* Missing arguments,return NULL */ + ph7_result_null(pCtx); + return PH7_OK; + } + /* Create a new array */ + pArray = ph7_context_new_array(pCtx); + if( pArray == 0 ){ + ph7_result_null(pCtx); + return PH7_OK; + } + /* Point to the internal representation of the hashmap */ + pMap = (ph7_hashmap *)pArray->x.pOther; + if( ph7_value_is_array(apArg[0])){ + /* Point to the internal representation of the source */ + ph7_hashmap *pSrc = (ph7_hashmap *)apArg[0]->x.pOther; + /* Perform the copy */ + PH7_HashmapDup(pSrc,pMap); + }else{ + /* Simple insertion */ + PH7_HashmapInsert(pMap,0/* Automatic index assign*/,apArg[0]); + } + /* Return the duplicated array */ + ph7_result_value(pCtx,pArray); + return PH7_OK; +} +/* + * bool array_erase(array $source) + * Remove all elements from a given array. + * Parameters + * $source + * Target array + * Return + * TRUE on success.FALSE otherwise. + * Note + * This function is a symisc eXtension. + */ +static int ph7_hashmap_erase(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + ph7_hashmap *pMap; + if( nArg < 1 ){ + /* Missing arguments */ + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Point to the target hashmap */ + pMap = (ph7_hashmap *)apArg[0]->x.pOther; + /* Erase */ + PH7_HashmapRelease(pMap,FALSE); + return PH7_OK; +} +/* + * array array_slice(array $array,int $offset [,int $length [, bool $preserve_keys = false ]]) + * Extract a slice of the array. + * Parameters + * $array + * The input array. + * $offset + * If offset is non-negative, the sequence will start at that offset in the array. + * If offset is negative, the sequence will start that far from the end of the array. + * $length (optional) + * If length is given and is positive, then the sequence will have that many elements + * in it. If length is given and is negative then the sequence will stop that many + * elements from the end of the array. If it is omitted, then the sequence will have + * everything from offset up until the end of the array. + * $preserve_keys (optional) + * Note that array_slice() will reorder and reset the array indices by default. + * You can change this behaviour by setting preserve_keys to TRUE. + * Return + * The new slice. + */ +static int ph7_hashmap_slice(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + ph7_hashmap *pMap,*pSrc; + ph7_hashmap_node *pCur; + ph7_value *pArray; + int iLength,iOfft; + int bPreserve; + sxi32 rc; + if( nArg < 2 || !ph7_value_is_array(apArg[0]) ){ + /* Missing/Invalid arguments,return NULL */ + ph7_result_null(pCtx); + return PH7_OK; + } + /* Point the internal representation of the target array */ + pSrc = (ph7_hashmap *)apArg[0]->x.pOther; + bPreserve = FALSE; + /* Get the offset */ + iOfft = ph7_value_to_int(apArg[1]); + if( iOfft < 0 ){ + iOfft = (int)pSrc->nEntry + iOfft; + } + if( iOfft < 0 || iOfft > (int)pSrc->nEntry ){ + /* Invalid offset,return the last entry */ + iOfft = (int)pSrc->nEntry - 1; + } + /* Get the length */ + iLength = (int)pSrc->nEntry - iOfft; + if( nArg > 2 ){ + iLength = ph7_value_to_int(apArg[2]); + if( iLength < 0 ){ + iLength = ((int)pSrc->nEntry + iLength) - iOfft; + } + if( iLength < 0 || iOfft + iLength >= (int)pSrc->nEntry ){ + iLength = (int)pSrc->nEntry - iOfft; + } + if( nArg > 3 && ph7_value_is_bool(apArg[3]) ){ + bPreserve = ph7_value_to_bool(apArg[3]); + } + } + /* Create a new array */ + pArray = ph7_context_new_array(pCtx); + if( pArray == 0 ){ + ph7_result_null(pCtx); + return PH7_OK; + } + if( iLength < 1 ){ + /* Don't bother processing,return the empty array */ + ph7_result_value(pCtx,pArray); + return PH7_OK; + } + /* Point to the desired entry */ + pCur = pSrc->pFirst; + for(;;){ + if( iOfft < 1 ){ + break; + } + /* Point to the next entry */ + pCur = pCur->pPrev; /* Reverse link */ + iOfft--; + } + /* Point to the internal representation of the hashmap */ + pMap = (ph7_hashmap *)pArray->x.pOther; + for(;;){ + if( iLength < 1 ){ + break; + } + rc = HashmapInsertNode(pMap,pCur,bPreserve); + if( rc != SXRET_OK ){ + break; + } + /* Point to the next entry */ + pCur = pCur->pPrev; /* Reverse link */ + iLength--; + } + /* Return the freshly created array */ + ph7_result_value(pCtx,pArray); + return PH7_OK; +} +/* + * array array_splice(array $array,int $offset [,int $length [,value $replacement ]]) + * Remove a portion of the array and replace it with something else. + * Parameters + * $array + * The input array. + * $offset + * If offset is positive then the start of removed portion is at that offset from + * the beginning of the input array. If offset is negative then it starts that far + * from the end of the input array. + * $length (optional) + * If length is omitted, removes everything from offset to the end of the array. + * If length is specified and is positive, then that many elements will be removed. + * If length is specified and is negative then the end of the removed portion will + * be that many elements from the end of the array. + * $replacement (optional) + * If replacement array is specified, then the removed elements are replaced + * with elements from this array. + * If offset and length are such that nothing is removed, then the elements + * from the replacement array are inserted in the place specified by the offset. + * Note that keys in replacement array are not preserved. + * If replacement is just one element it is not necessary to put array() around + * it, unless the element is an array itself, an object or NULL. + * Return + * A new array consisting of the extracted elements. + */ +static int ph7_hashmap_splice(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + ph7_hashmap_node *pCur,*pPrev,*pRnode; + ph7_value *pArray,*pRvalue,*pOld; + ph7_hashmap *pMap,*pSrc,*pRep; + int iLength,iOfft; + sxi32 rc; + if( nArg < 2 || !ph7_value_is_array(apArg[0]) ){ + /* Missing/Invalid arguments,return NULL */ + ph7_result_null(pCtx); + return PH7_OK; + } + /* Point the internal representation of the target array */ + pSrc = (ph7_hashmap *)apArg[0]->x.pOther; + /* Get the offset */ + iOfft = ph7_value_to_int(apArg[1]); + if( iOfft < 0 ){ + iOfft = (int)pSrc->nEntry + iOfft; + } + if( iOfft < 0 || iOfft > (int)pSrc->nEntry ){ + /* Invalid offset,remove the last entry */ + iOfft = (int)pSrc->nEntry - 1; + } + /* Get the length */ + iLength = (int)pSrc->nEntry - iOfft; + if( nArg > 2 ){ + iLength = ph7_value_to_int(apArg[2]); + if( iLength < 0 ){ + iLength = ((int)pSrc->nEntry + iLength) - iOfft; + } + if( iLength < 0 || iOfft + iLength >= (int)pSrc->nEntry ){ + iLength = (int)pSrc->nEntry - iOfft; + } + } + /* Create a new array */ + pArray = ph7_context_new_array(pCtx); + if( pArray == 0 ){ + ph7_result_null(pCtx); + return PH7_OK; + } + if( iLength < 1 ){ + /* Don't bother processing,return the empty array */ + ph7_result_value(pCtx,pArray); + return PH7_OK; + } + /* Point to the desired entry */ + pCur = pSrc->pFirst; + for(;;){ + if( iOfft < 1 ){ + break; + } + /* Point to the next entry */ + pCur = pCur->pPrev; /* Reverse link */ + iOfft--; + } + pRep = 0; + if( nArg > 3 ){ + if( !ph7_value_is_array(apArg[3]) ){ + /* Perform an array cast */ + PH7_MemObjToHashmap(apArg[3]); + if(ph7_value_is_array(apArg[3])){ + pRep = (ph7_hashmap *)apArg[3]->x.pOther; + } + }else{ + pRep = (ph7_hashmap *)apArg[3]->x.pOther; + } + if( pRep ){ + /* Reset the loop cursor */ + pRep->pCur = pRep->pFirst; + } + } + /* Point to the internal representation of the hashmap */ + pMap = (ph7_hashmap *)pArray->x.pOther; + for(;;){ + if( iLength < 1 ){ + break; + } + pPrev = pCur->pPrev; + rc = HashmapInsertNode(pMap,pCur,FALSE); + if( pRep && (pRnode = PH7_HashmapGetNextEntry(pRep)) != 0 ){ + /* Extract node value */ + pRvalue = HashmapExtractNodeValue(pRnode); + /* Replace the old node */ + pOld = HashmapExtractNodeValue(pCur); + if( pRvalue && pOld ){ + PH7_MemObjStore(pRvalue,pOld); + } + }else{ + /* Unlink the node from the source hashmap */ + PH7_HashmapUnlinkNode(pCur,TRUE); + } + if( rc != SXRET_OK ){ + break; + } + /* Point to the next entry */ + pCur = pPrev; /* Reverse link */ + iLength--; + } + if( pRep ){ + while((pRnode = PH7_HashmapGetNextEntry(pRep)) != 0 ){ + HashmapInsertNode(pSrc,pRnode,FALSE); + } + } + /* Return the freshly created array */ + ph7_result_value(pCtx,pArray); + return PH7_OK; +} +/* + * bool in_array(value $needle,array $haystack[,bool $strict = FALSE ]) + * Checks if a value exists in an array. + * Parameters + * $needle + * The searched value. + * Note: + * If needle is a string, the comparison is done in a case-sensitive manner. + * $haystack + * The target array. + * $strict + * If the third parameter strict is set to TRUE then the in_array() function + * will also check the types of the needle in the haystack. + */ +static int ph7_hashmap_in_array(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + ph7_value *pNeedle; + int bStrict; + int rc; + if( nArg < 2 ){ + /* Missing argument,return FALSE */ + ph7_result_bool(pCtx,0); + return PH7_OK; + } + pNeedle = apArg[0]; + bStrict = 0; + if( nArg > 2 ){ + bStrict = ph7_value_to_bool(apArg[2]); + } + if( !ph7_value_is_array(apArg[1]) ){ + /* haystack must be an array,perform a standard comparison */ + rc = ph7_value_compare(pNeedle,apArg[1],bStrict); + /* Set the comparison result */ + ph7_result_bool(pCtx,rc == 0); + return PH7_OK; + } + /* Perform the lookup */ + rc = HashmapFindValue((ph7_hashmap *)apArg[1]->x.pOther,pNeedle,0,bStrict); + /* Lookup result */ + ph7_result_bool(pCtx,rc == SXRET_OK); + return PH7_OK; +} +/* + * value array_search(value $needle,array $haystack[,bool $strict = false ]) + * Searches the array for a given value and returns the corresponding key if successful. + * Parameters + * $needle + * The searched value. + * $haystack + * The array. + * $strict + * If the third parameter strict is set to TRUE then the array_search() function + * will search for identical elements in the haystack. This means it will also check + * the types of the needle in the haystack, and objects must be the same instance. + * Return + * Returns the key for needle if it is found in the array, FALSE otherwise. + */ +static int ph7_hashmap_search(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + ph7_hashmap_node *pEntry; + ph7_value *pVal,sNeedle; + ph7_hashmap *pMap; + ph7_value sVal; + int bStrict; + sxu32 n; + int rc; + if( nArg < 2 ){ + /* Missing argument,return FALSE*/ + ph7_result_bool(pCtx,0); + return PH7_OK; + } + bStrict = FALSE; + if( !ph7_value_is_array(apArg[1]) ){ + /* hasystack must be an array,return FALSE */ + ph7_result_bool(pCtx,0); + return PH7_OK; + } + if( nArg > 2 && ph7_value_is_bool(apArg[2]) ){ + bStrict = ph7_value_to_bool(apArg[2]); + } + /* Point to the internal representation of the internal hashmap */ + pMap = (ph7_hashmap *)apArg[1]->x.pOther; + /* Perform a linear search since we cannot sort the hashmap based on values */ + PH7_MemObjInit(pMap->pVm,&sVal); + PH7_MemObjInit(pMap->pVm,&sNeedle); + pEntry = pMap->pFirst; + n = pMap->nEntry; + for(;;){ + if( !n ){ + break; + } + /* Extract node value */ + pVal = HashmapExtractNodeValue(pEntry); + if( pVal ){ + /* Make a copy of the vuurent values since the comparison routine + * can change their type. + */ + PH7_MemObjLoad(pVal,&sVal); + PH7_MemObjLoad(apArg[0],&sNeedle); + rc = PH7_MemObjCmp(&sNeedle,&sVal,bStrict,0); + PH7_MemObjRelease(&sVal); + PH7_MemObjRelease(&sNeedle); + if( rc == 0 ){ + /* Match found,return key */ + if( pEntry->iType == HASHMAP_INT_NODE){ + /* INT key */ + ph7_result_int64(pCtx,pEntry->xKey.iKey); + }else{ + SyBlob *pKey = &pEntry->xKey.sKey; + /* Blob key */ + ph7_result_string(pCtx,(const char *)SyBlobData(pKey),(int)SyBlobLength(pKey)); + } + return PH7_OK; + } + } + /* Point to the next entry */ + pEntry = pEntry->pPrev; /* Reverse link */ + n--; + } + /* No such value,return FALSE */ + ph7_result_bool(pCtx,0); + return PH7_OK; +} +/* + * array array_diff(array $array1,array $array2,...) + * Computes the difference of arrays. + * Parameters + * $array1 + * The array to compare from + * $array2 + * An array to compare against + * $... + * More arrays to compare against + * Return + * Returns an array containing all the entries from array1 that + * are not present in any of the other arrays. + */ +static int ph7_hashmap_diff(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + ph7_hashmap_node *pEntry; + ph7_hashmap *pSrc,*pMap; + ph7_value *pArray; + ph7_value *pVal; + sxi32 rc; + sxu32 n; + int i; + if( nArg < 1 || !ph7_value_is_array(apArg[0]) ){ + /* Missing arguments,return NULL */ + ph7_result_null(pCtx); + return PH7_OK; + } + if( nArg == 1 ){ + /* Return the first array since we cannot perform a diff */ + ph7_result_value(pCtx,apArg[0]); + return PH7_OK; + } + /* Create a new array */ + pArray = ph7_context_new_array(pCtx); + if( pArray == 0 ){ + ph7_result_null(pCtx); + return PH7_OK; + } + /* Point to the internal representation of the source hashmap */ + pSrc = (ph7_hashmap *)apArg[0]->x.pOther; + /* Perform the diff */ + pEntry = pSrc->pFirst; + n = pSrc->nEntry; + for(;;){ + if( n < 1 ){ + break; + } + /* Extract the node value */ + pVal = HashmapExtractNodeValue(pEntry); + if( pVal ){ + for( i = 1 ; i < nArg ; i++ ){ + if( !ph7_value_is_array(apArg[i])) { + /* ignore */ + continue; + } + /* Point to the internal representation of the hashmap */ + pMap = (ph7_hashmap *)apArg[i]->x.pOther; + /* Perform the lookup */ + rc = HashmapFindValue(pMap,pVal,0,TRUE); + if( rc == SXRET_OK ){ + /* Value exist */ + break; + } + } + if( i >= nArg ){ + /* Perform the insertion */ + HashmapInsertNode((ph7_hashmap *)pArray->x.pOther,pEntry,TRUE); + } + } + /* Point to the next entry */ + pEntry = pEntry->pPrev; /* Reverse link */ + n--; + } + /* Return the freshly created array */ + ph7_result_value(pCtx,pArray); + return PH7_OK; +} +/* + * array array_udiff(array $array1,array $array2,...,$callback) + * Computes the difference of arrays by using a callback function for data comparison. + * Parameters + * $array1 + * The array to compare from + * $array2 + * An array to compare against + * $... + * More arrays to compare against. + * $callback + * The callback comparison function. + * The comparison function must return an integer less than, equal to, or greater than zero + * if the first argument is considered to be respectively less than, equal to, or greater + * than the second. + * int callback ( mixed $a, mixed $b ) + * Return + * Returns an array containing all the entries from array1 that + * are not present in any of the other arrays. + */ +static int ph7_hashmap_udiff(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + ph7_hashmap_node *pEntry; + ph7_hashmap *pSrc,*pMap; + ph7_value *pCallback; + ph7_value *pArray; + ph7_value *pVal; + sxi32 rc; + sxu32 n; + int i; + if( nArg < 2 || !ph7_value_is_array(apArg[0]) ){ + /* Missing/Invalid arguments,return NULL */ + ph7_result_null(pCtx); + return PH7_OK; + } + /* Point to the callback */ + pCallback = apArg[nArg - 1]; + if( nArg == 2 ){ + /* Return the first array since we cannot perform a diff */ + ph7_result_value(pCtx,apArg[0]); + return PH7_OK; + } + /* Create a new array */ + pArray = ph7_context_new_array(pCtx); + if( pArray == 0 ){ + ph7_result_null(pCtx); + return PH7_OK; + } + /* Point to the internal representation of the source hashmap */ + pSrc = (ph7_hashmap *)apArg[0]->x.pOther; + /* Perform the diff */ + pEntry = pSrc->pFirst; + n = pSrc->nEntry; + for(;;){ + if( n < 1 ){ + break; + } + /* Extract the node value */ + pVal = HashmapExtractNodeValue(pEntry); + if( pVal ){ + for( i = 1 ; i < nArg - 1; i++ ){ + if( !ph7_value_is_array(apArg[i])) { + /* ignore */ + continue; + } + /* Point to the internal representation of the hashmap */ + pMap = (ph7_hashmap *)apArg[i]->x.pOther; + /* Perform the lookup */ + rc = HashmapFindValueByCallback(pMap,pVal,pCallback,0); + if( rc == SXRET_OK ){ + /* Value exist */ + break; + } + } + if( i >= (nArg - 1)){ + /* Perform the insertion */ + HashmapInsertNode((ph7_hashmap *)pArray->x.pOther,pEntry,TRUE); + } + } + /* Point to the next entry */ + pEntry = pEntry->pPrev; /* Reverse link */ + n--; + } + /* Return the freshly created array */ + ph7_result_value(pCtx,pArray); + return PH7_OK; +} +/* + * array array_diff_assoc(array $array1,array $array2,...) + * Computes the difference of arrays with additional index check. + * Parameters + * $array1 + * The array to compare from + * $array2 + * An array to compare against + * $... + * More arrays to compare against + * Return + * Returns an array containing all the entries from array1 that + * are not present in any of the other arrays. + */ +static int ph7_hashmap_diff_assoc(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + ph7_hashmap_node *pN1,*pN2,*pEntry; + ph7_hashmap *pSrc,*pMap; + ph7_value *pArray; + ph7_value *pVal; + sxi32 rc; + sxu32 n; + int i; + if( nArg < 1 || !ph7_value_is_array(apArg[0]) ){ + /* Missing arguments,return NULL */ + ph7_result_null(pCtx); + return PH7_OK; + } + if( nArg == 1 ){ + /* Return the first array since we cannot perform a diff */ + ph7_result_value(pCtx,apArg[0]); + return PH7_OK; + } + /* Create a new array */ + pArray = ph7_context_new_array(pCtx); + if( pArray == 0 ){ + ph7_result_null(pCtx); + return PH7_OK; + } + /* Point to the internal representation of the source hashmap */ + pSrc = (ph7_hashmap *)apArg[0]->x.pOther; + /* Perform the diff */ + pEntry = pSrc->pFirst; + n = pSrc->nEntry; + pN1 = pN2 = 0; + for(;;){ + if( n < 1 ){ + break; + } + for( i = 1 ; i < nArg ; i++ ){ + if( !ph7_value_is_array(apArg[i])) { + /* ignore */ + continue; + } + /* Point to the internal representation of the hashmap */ + pMap = (ph7_hashmap *)apArg[i]->x.pOther; + /* Perform a key lookup first */ + if( pEntry->iType == HASHMAP_INT_NODE ){ + rc = HashmapLookupIntKey(pMap,pEntry->xKey.iKey,&pN1); + }else{ + rc = HashmapLookupBlobKey(pMap,SyBlobData(&pEntry->xKey.sKey),SyBlobLength(&pEntry->xKey.sKey),&pN1); + } + if( rc != SXRET_OK ){ + /* No such key,break immediately */ + break; + } + /* Extract node value */ + pVal = HashmapExtractNodeValue(pEntry); + if( pVal ){ + /* Perform the lookup */ + rc = HashmapFindValue(pMap,pVal,&pN2,TRUE); + if( rc != SXRET_OK || pN1 != pN2 ){ + /* Value does not exist */ + break; + } + } + } + if( i < nArg ){ + /* Perform the insertion */ + HashmapInsertNode((ph7_hashmap *)pArray->x.pOther,pEntry,TRUE); + } + /* Point to the next entry */ + pEntry = pEntry->pPrev; /* Reverse link */ + n--; + } + /* Return the freshly created array */ + ph7_result_value(pCtx,pArray); + return PH7_OK; +} +/* + * array array_diff_uassoc(array $array1,array $array2,...,callback $key_compare_func) + * Computes the difference of arrays with additional index check which is performed + * by a user supplied callback function. + * Parameters + * $array1 + * The array to compare from + * $array2 + * An array to compare against + * $... + * More arrays to compare against. + * $key_compare_func + * Callback function to use. The callback function must return an integer + * less than, equal to, or greater than zero if the first argument is considered + * to be respectively less than, equal to, or greater than the second. + * Return + * Returns an array containing all the entries from array1 that + * are not present in any of the other arrays. + */ +static int ph7_hashmap_diff_uassoc(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + ph7_hashmap_node *pN1,*pN2,*pEntry; + ph7_hashmap *pSrc,*pMap; + ph7_value *pCallback; + ph7_value *pArray; + ph7_value *pVal; + sxi32 rc; + sxu32 n; + int i; + + if( nArg < 2 || !ph7_value_is_array(apArg[0]) ){ + /* Missing/Invalid arguments,return NULL */ + ph7_result_null(pCtx); + return PH7_OK; + } + /* Point to the callback */ + pCallback = apArg[nArg - 1]; + if( nArg == 2 ){ + /* Return the first array since we cannot perform a diff */ + ph7_result_value(pCtx,apArg[0]); + return PH7_OK; + } + /* Create a new array */ + pArray = ph7_context_new_array(pCtx); + if( pArray == 0 ){ + ph7_result_null(pCtx); + return PH7_OK; + } + /* Point to the internal representation of the source hashmap */ + pSrc = (ph7_hashmap *)apArg[0]->x.pOther; + /* Perform the diff */ + pEntry = pSrc->pFirst; + n = pSrc->nEntry; + pN1 = pN2 = 0; /* cc warning */ + for(;;){ + if( n < 1 ){ + break; + } + for( i = 1 ; i < nArg - 1; i++ ){ + if( !ph7_value_is_array(apArg[i])) { + /* ignore */ + continue; + } + /* Point to the internal representation of the hashmap */ + pMap = (ph7_hashmap *)apArg[i]->x.pOther; + /* Perform a key lookup first */ + if( pEntry->iType == HASHMAP_INT_NODE ){ + rc = HashmapLookupIntKey(pMap,pEntry->xKey.iKey,&pN1); + }else{ + rc = HashmapLookupBlobKey(pMap,SyBlobData(&pEntry->xKey.sKey),SyBlobLength(&pEntry->xKey.sKey),&pN1); + } + if( rc != SXRET_OK ){ + /* No such key,break immediately */ + break; + } + /* Extract node value */ + pVal = HashmapExtractNodeValue(pEntry); + if( pVal ){ + /* Invoke the user callback */ + rc = HashmapFindValueByCallback(pMap,pVal,pCallback,&pN2); + if( rc != SXRET_OK || pN1 != pN2 ){ + /* Value does not exist */ + break; + } + } + } + if( i < (nArg-1) ){ + /* Perform the insertion */ + HashmapInsertNode((ph7_hashmap *)pArray->x.pOther,pEntry,TRUE); + } + /* Point to the next entry */ + pEntry = pEntry->pPrev; /* Reverse link */ + n--; + } + /* Return the freshly created array */ + ph7_result_value(pCtx,pArray); + return PH7_OK; +} +/* + * array array_diff_key(array $array1 ,array $array2,...) + * Computes the difference of arrays using keys for comparison. + * Parameters + * $array1 + * The array to compare from + * $array2 + * An array to compare against + * $... + * More arrays to compare against + * Return + * Returns an array containing all the entries from array1 whose keys are not present + * in any of the other arrays. + * Note that NULL is returned on failure. + */ +static int ph7_hashmap_diff_key(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + ph7_hashmap_node *pEntry; + ph7_hashmap *pSrc,*pMap; + ph7_value *pArray; + sxi32 rc; + sxu32 n; + int i; + if( nArg < 1 || !ph7_value_is_array(apArg[0]) ){ + /* Missing arguments,return NULL */ + ph7_result_null(pCtx); + return PH7_OK; + } + if( nArg == 1 ){ + /* Return the first array since we cannot perform a diff */ + ph7_result_value(pCtx,apArg[0]); + return PH7_OK; + } + /* Create a new array */ + pArray = ph7_context_new_array(pCtx); + if( pArray == 0 ){ + ph7_result_null(pCtx); + return PH7_OK; + } + /* Point to the internal representation of the main hashmap */ + pSrc = (ph7_hashmap *)apArg[0]->x.pOther; + /* Perfrom the diff */ + pEntry = pSrc->pFirst; + n = pSrc->nEntry; + for(;;){ + if( n < 1 ){ + break; + } + for( i = 1 ; i < nArg ; i++ ){ + if( !ph7_value_is_array(apArg[i])) { + /* ignore */ + continue; + } + pMap = (ph7_hashmap *)apArg[i]->x.pOther; + if( pEntry->iType == HASHMAP_BLOB_NODE ){ + SyBlob *pKey = &pEntry->xKey.sKey; + /* Blob lookup */ + rc = HashmapLookupBlobKey(pMap,SyBlobData(pKey),SyBlobLength(pKey),0); + }else{ + /* Int lookup */ + rc = HashmapLookupIntKey(pMap,pEntry->xKey.iKey,0); + } + if( rc == SXRET_OK ){ + /* Key exists,break immediately */ + break; + } + } + if( i >= nArg ){ + /* Perform the insertion */ + HashmapInsertNode((ph7_hashmap *)pArray->x.pOther,pEntry,TRUE); + } + /* Point to the next entry */ + pEntry = pEntry->pPrev; /* Reverse link */ + n--; + } + /* Return the freshly created array */ + ph7_result_value(pCtx,pArray); + return PH7_OK; +} +/* + * array array_intersect(array $array1 ,array $array2,...) + * Computes the intersection of arrays. + * Parameters + * $array1 + * The array to compare from + * $array2 + * An array to compare against + * $... + * More arrays to compare against + * Return + * Returns an array containing all of the values in array1 whose values exist + * in all of the parameters. . + * Note that NULL is returned on failure. + */ +static int ph7_hashmap_intersect(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + ph7_hashmap_node *pEntry; + ph7_hashmap *pSrc,*pMap; + ph7_value *pArray; + ph7_value *pVal; + sxi32 rc; + sxu32 n; + int i; + if( nArg < 1 || !ph7_value_is_array(apArg[0]) ){ + /* Missing arguments,return NULL */ + ph7_result_null(pCtx); + return PH7_OK; + } + if( nArg == 1 ){ + /* Return the first array since we cannot perform a diff */ + ph7_result_value(pCtx,apArg[0]); + return PH7_OK; + } + /* Create a new array */ + pArray = ph7_context_new_array(pCtx); + if( pArray == 0 ){ + ph7_result_null(pCtx); + return PH7_OK; + } + /* Point to the internal representation of the source hashmap */ + pSrc = (ph7_hashmap *)apArg[0]->x.pOther; + /* Perform the intersection */ + pEntry = pSrc->pFirst; + n = pSrc->nEntry; + for(;;){ + if( n < 1 ){ + break; + } + /* Extract the node value */ + pVal = HashmapExtractNodeValue(pEntry); + if( pVal ){ + for( i = 1 ; i < nArg ; i++ ){ + if( !ph7_value_is_array(apArg[i])) { + /* ignore */ + continue; + } + /* Point to the internal representation of the hashmap */ + pMap = (ph7_hashmap *)apArg[i]->x.pOther; + /* Perform the lookup */ + rc = HashmapFindValue(pMap,pVal,0,TRUE); + if( rc != SXRET_OK ){ + /* Value does not exist */ + break; + } + } + if( i >= nArg ){ + /* Perform the insertion */ + HashmapInsertNode((ph7_hashmap *)pArray->x.pOther,pEntry,TRUE); + } + } + /* Point to the next entry */ + pEntry = pEntry->pPrev; /* Reverse link */ + n--; + } + /* Return the freshly created array */ + ph7_result_value(pCtx,pArray); + return PH7_OK; +} +/* + * array array_intersect_assoc(array $array1 ,array $array2,...) + * Computes the intersection of arrays. + * Parameters + * $array1 + * The array to compare from + * $array2 + * An array to compare against + * $... + * More arrays to compare against + * Return + * Returns an array containing all of the values in array1 whose values exist + * in all of the parameters. . + * Note that NULL is returned on failure. + */ +static int ph7_hashmap_intersect_assoc(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + ph7_hashmap_node *pEntry,*pN1,*pN2; + ph7_hashmap *pSrc,*pMap; + ph7_value *pArray; + ph7_value *pVal; + sxi32 rc; + sxu32 n; + int i; + if( nArg < 1 || !ph7_value_is_array(apArg[0]) ){ + /* Missing arguments,return NULL */ + ph7_result_null(pCtx); + return PH7_OK; + } + if( nArg == 1 ){ + /* Return the first array since we cannot perform a diff */ + ph7_result_value(pCtx,apArg[0]); + return PH7_OK; + } + /* Create a new array */ + pArray = ph7_context_new_array(pCtx); + if( pArray == 0 ){ + ph7_result_null(pCtx); + return PH7_OK; + } + /* Point to the internal representation of the source hashmap */ + pSrc = (ph7_hashmap *)apArg[0]->x.pOther; + /* Perform the intersection */ + pEntry = pSrc->pFirst; + n = pSrc->nEntry; + pN1 = pN2 = 0; /* cc warning */ + for(;;){ + if( n < 1 ){ + break; + } + /* Extract the node value */ + pVal = HashmapExtractNodeValue(pEntry); + if( pVal ){ + for( i = 1 ; i < nArg ; i++ ){ + if( !ph7_value_is_array(apArg[i])) { + /* ignore */ + continue; + } + /* Point to the internal representation of the hashmap */ + pMap = (ph7_hashmap *)apArg[i]->x.pOther; + /* Perform a key lookup first */ + if( pEntry->iType == HASHMAP_INT_NODE ){ + rc = HashmapLookupIntKey(pMap,pEntry->xKey.iKey,&pN1); + }else{ + rc = HashmapLookupBlobKey(pMap,SyBlobData(&pEntry->xKey.sKey),SyBlobLength(&pEntry->xKey.sKey),&pN1); + } + if( rc != SXRET_OK ){ + /* No such key,break immediately */ + break; + } + /* Perform the lookup */ + rc = HashmapFindValue(pMap,pVal,&pN2,TRUE); + if( rc != SXRET_OK || pN1 != pN2 ){ + /* Value does not exist */ + break; + } + } + if( i >= nArg ){ + /* Perform the insertion */ + HashmapInsertNode((ph7_hashmap *)pArray->x.pOther,pEntry,TRUE); + } + } + /* Point to the next entry */ + pEntry = pEntry->pPrev; /* Reverse link */ + n--; + } + /* Return the freshly created array */ + ph7_result_value(pCtx,pArray); + return PH7_OK; +} +/* + * array array_intersect_key(array $array1 ,array $array2,...) + * Computes the intersection of arrays using keys for comparison. + * Parameters + * $array1 + * The array to compare from + * $array2 + * An array to compare against + * $... + * More arrays to compare against + * Return + * Returns an associative array containing all the entries of array1 which + * have keys that are present in all arguments. + * Note that NULL is returned on failure. + */ +static int ph7_hashmap_intersect_key(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + ph7_hashmap_node *pEntry; + ph7_hashmap *pSrc,*pMap; + ph7_value *pArray; + sxi32 rc; + sxu32 n; + int i; + if( nArg < 1 || !ph7_value_is_array(apArg[0]) ){ + /* Missing arguments,return NULL */ + ph7_result_null(pCtx); + return PH7_OK; + } + if( nArg == 1 ){ + /* Return the first array since we cannot perform a diff */ + ph7_result_value(pCtx,apArg[0]); + return PH7_OK; + } + /* Create a new array */ + pArray = ph7_context_new_array(pCtx); + if( pArray == 0 ){ + ph7_result_null(pCtx); + return PH7_OK; + } + /* Point to the internal representation of the main hashmap */ + pSrc = (ph7_hashmap *)apArg[0]->x.pOther; + /* Perfrom the intersection */ + pEntry = pSrc->pFirst; + n = pSrc->nEntry; + for(;;){ + if( n < 1 ){ + break; + } + for( i = 1 ; i < nArg ; i++ ){ + if( !ph7_value_is_array(apArg[i])) { + /* ignore */ + continue; + } + pMap = (ph7_hashmap *)apArg[i]->x.pOther; + if( pEntry->iType == HASHMAP_BLOB_NODE ){ + SyBlob *pKey = &pEntry->xKey.sKey; + /* Blob lookup */ + rc = HashmapLookupBlobKey(pMap,SyBlobData(pKey),SyBlobLength(pKey),0); + }else{ + /* Int key */ + rc = HashmapLookupIntKey(pMap,pEntry->xKey.iKey,0); + } + if( rc != SXRET_OK ){ + /* Key does not exists,break immediately */ + break; + } + } + if( i >= nArg ){ + /* Perform the insertion */ + HashmapInsertNode((ph7_hashmap *)pArray->x.pOther,pEntry,TRUE); + } + /* Point to the next entry */ + pEntry = pEntry->pPrev; /* Reverse link */ + n--; + } + /* Return the freshly created array */ + ph7_result_value(pCtx,pArray); + return PH7_OK; +} +/* + * array array_uintersect(array $array1 ,array $array2,...,$callback) + * Computes the intersection of arrays. + * Parameters + * $array1 + * The array to compare from + * $array2 + * An array to compare against + * $... + * More arrays to compare against + * $callback + * The callback comparison function. + * The comparison function must return an integer less than, equal to, or greater than zero + * if the first argument is considered to be respectively less than, equal to, or greater + * than the second. + * int callback ( mixed $a, mixed $b ) + * Return + * Returns an array containing all of the values in array1 whose values exist + * in all of the parameters. . + * Note that NULL is returned on failure. + */ +static int ph7_hashmap_uintersect(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + ph7_hashmap_node *pEntry; + ph7_hashmap *pSrc,*pMap; + ph7_value *pCallback; + ph7_value *pArray; + ph7_value *pVal; + sxi32 rc; + sxu32 n; + int i; + + if( nArg < 2 || !ph7_value_is_array(apArg[0]) ){ + /* Missing/Invalid arguments,return NULL */ + ph7_result_null(pCtx); + return PH7_OK; + } + /* Point to the callback */ + pCallback = apArg[nArg - 1]; + if( nArg == 2 ){ + /* Return the first array since we cannot perform a diff */ + ph7_result_value(pCtx,apArg[0]); + return PH7_OK; + } + /* Create a new array */ + pArray = ph7_context_new_array(pCtx); + if( pArray == 0 ){ + ph7_result_null(pCtx); + return PH7_OK; + } + /* Point to the internal representation of the source hashmap */ + pSrc = (ph7_hashmap *)apArg[0]->x.pOther; + /* Perform the intersection */ + pEntry = pSrc->pFirst; + n = pSrc->nEntry; + for(;;){ + if( n < 1 ){ + break; + } + /* Extract the node value */ + pVal = HashmapExtractNodeValue(pEntry); + if( pVal ){ + for( i = 1 ; i < nArg - 1; i++ ){ + if( !ph7_value_is_array(apArg[i])) { + /* ignore */ + continue; + } + /* Point to the internal representation of the hashmap */ + pMap = (ph7_hashmap *)apArg[i]->x.pOther; + /* Perform the lookup */ + rc = HashmapFindValueByCallback(pMap,pVal,pCallback,0); + if( rc != SXRET_OK ){ + /* Value does not exist */ + break; + } + } + if( i >= (nArg-1) ){ + /* Perform the insertion */ + HashmapInsertNode((ph7_hashmap *)pArray->x.pOther,pEntry,TRUE); + } + } + /* Point to the next entry */ + pEntry = pEntry->pPrev; /* Reverse link */ + n--; + } + /* Return the freshly created array */ + ph7_result_value(pCtx,pArray); + return PH7_OK; +} +/* + * array array_fill(int $start_index,int $num,var $value) + * Fill an array with values. + * Parameters + * $start_index + * The first index of the returned array. + * $num + * Number of elements to insert. + * $value + * Value to use for filling. + * Return + * The filled array or null on failure. + */ +static int ph7_hashmap_fill(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + ph7_value *pArray; + int i,nEntry; + if( nArg < 3 ){ + /* Missing arguments,return NULL */ + ph7_result_null(pCtx); + return PH7_OK; + } + /* Create a new array */ + pArray = ph7_context_new_array(pCtx); + if( pArray == 0 ){ + ph7_result_null(pCtx); + return PH7_OK; + } + /* Total number of entries to insert */ + nEntry = ph7_value_to_int(apArg[1]); + /* Insert the first entry alone because it have it's own key */ + ph7_array_add_intkey_elem(pArray,ph7_value_to_int(apArg[0]),apArg[2]); + /* Repeat insertion of the desired value */ + for( i = 1 ; i < nEntry ; i++ ){ + ph7_array_add_elem(pArray,0/*Automatic index assign */,apArg[2]); + } + /* Return the filled array */ + ph7_result_value(pCtx,pArray); + return PH7_OK; +} +/* + * array array_fill_keys(array $input,var $value) + * Fill an array with values, specifying keys. + * Parameters + * $input + * Array of values that will be used as key. + * $value + * Value to use for filling. + * Return + * The filled array or null on failure. + */ +static int ph7_hashmap_fill_keys(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + ph7_hashmap_node *pEntry; + ph7_hashmap *pSrc; + ph7_value *pArray; + sxu32 n; + if( nArg < 2 ){ + /* Missing arguments,return NULL */ + ph7_result_null(pCtx); + return PH7_OK; + } + /* Make sure we are dealing with a valid hashmap */ + if( !ph7_value_is_array(apArg[0]) ){ + /* Invalid argument,return NULL */ + ph7_result_null(pCtx); + return PH7_OK; + } + /* Point to the internal representation of the input hashmap */ + pSrc = (ph7_hashmap *)apArg[0]->x.pOther; + /* Create a new array */ + pArray = ph7_context_new_array(pCtx); + if( pArray == 0 ){ + ph7_result_null(pCtx); + return PH7_OK; + } + /* Perform the requested operation */ + pEntry = pSrc->pFirst; + for( n = 0 ; n < pSrc->nEntry ; n++ ){ + ph7_array_add_elem(pArray,HashmapExtractNodeValue(pEntry),apArg[1]); + /* Point to the next entry */ + pEntry = pEntry->pPrev; /* Reverse link */ + } + /* Return the filled array */ + ph7_result_value(pCtx,pArray); + return PH7_OK; +} +/* + * array array_combine(array $keys,array $values) + * Creates an array by using one array for keys and another for its values. + * Parameters + * $keys + * Array of keys to be used. + * $values + * Array of values to be used. + * Return + * Returns the combined array. Otherwise FALSE if the number of elements + * for each array isn't equal or if one of the given arguments is + * not an array. + */ +static int ph7_hashmap_combine(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + ph7_hashmap_node *pKe,*pVe; + ph7_hashmap *pKey,*pValue; + ph7_value *pArray; + sxu32 n; + if( nArg < 2 ){ + /* Missing arguments,return FALSE */ + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Make sure we are dealing with a valid hashmap */ + if( !ph7_value_is_array(apArg[0]) || !ph7_value_is_array(apArg[1]) ){ + /* Invalid argument,return FALSE */ + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Point to the internal representation of the input hashmaps */ + pKey = (ph7_hashmap *)apArg[0]->x.pOther; + pValue = (ph7_hashmap *)apArg[1]->x.pOther; + if( pKey->nEntry != pValue->nEntry ){ + /* Array length differs,return FALSE */ + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Create a new array */ + pArray = ph7_context_new_array(pCtx); + if( pArray == 0 ){ + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Perform the requested operation */ + pKe = pKey->pFirst; + pVe = pValue->pFirst; + for( n = 0 ; n < pKey->nEntry ; n++ ){ + ph7_array_add_elem(pArray,HashmapExtractNodeValue(pKe),HashmapExtractNodeValue(pVe)); + /* Point to the next entry */ + pKe = pKe->pPrev; /* Reverse link */ + pVe = pVe->pPrev; + } + /* Return the filled array */ + ph7_result_value(pCtx,pArray); + return PH7_OK; +} +/* + * array array_reverse(array $array [,bool $preserve_keys = false ]) + * Return an array with elements in reverse order. + * Parameters + * $array + * The input array. + * $preserve_keys (optional) + * If set to TRUE keys are preserved. + * Return + * The reversed array. + */ +static int ph7_hashmap_reverse(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + ph7_hashmap_node *pEntry; + ph7_hashmap *pSrc; + ph7_value *pArray; + int bPreserve; + sxu32 n; + if( nArg < 1 ){ + /* Missing arguments,return NULL */ + ph7_result_null(pCtx); + return PH7_OK; + } + /* Make sure we are dealing with a valid hashmap */ + if( !ph7_value_is_array(apArg[0]) ){ + /* Invalid argument,return NULL */ + ph7_result_null(pCtx); + return PH7_OK; + } + bPreserve = FALSE; + if( nArg > 1 && ph7_value_is_bool(apArg[1]) ){ + bPreserve = ph7_value_to_bool(apArg[1]); + } + /* Point to the internal representation of the input hashmap */ + pSrc = (ph7_hashmap *)apArg[0]->x.pOther; + /* Create a new array */ + pArray = ph7_context_new_array(pCtx); + if( pArray == 0 ){ + ph7_result_null(pCtx); + return PH7_OK; + } + /* Perform the requested operation */ + pEntry = pSrc->pLast; + for( n = 0 ; n < pSrc->nEntry ; n++ ){ + HashmapInsertNode((ph7_hashmap *)pArray->x.pOther,pEntry,bPreserve); + /* Point to the previous entry */ + pEntry = pEntry->pNext; /* Reverse link */ + } + ph7_result_value(pCtx,pArray); + return PH7_OK; +} +/* + * array array_unique(array $array[,int $sort_flags = SORT_STRING ]) + * Removes duplicate values from an array + * Parameter + * $array + * The input array. + * $sort_flags + * The optional second parameter sort_flags may be used to modify the sorting behavior using these values: + * Sorting type flags: + * SORT_REGULAR - compare items normally (don't change types) + * SORT_NUMERIC - compare items numerically + * SORT_STRING - compare items as strings + * SORT_LOCALE_STRING - compare items as + * Return + * Filtered array or NULL on failure. + */ +static int ph7_hashmap_unique(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + ph7_hashmap_node *pEntry; + ph7_value *pNeedle; + ph7_hashmap *pSrc; + ph7_value *pArray; + int bStrict; + sxi32 rc; + sxu32 n; + if( nArg < 1 ){ + /* Missing arguments,return NULL */ + ph7_result_null(pCtx); + return PH7_OK; + } + /* Make sure we are dealing with a valid hashmap */ + if( !ph7_value_is_array(apArg[0]) ){ + /* Invalid argument,return NULL */ + ph7_result_null(pCtx); + return PH7_OK; + } + bStrict = FALSE; + if( nArg > 1 ){ + bStrict = ph7_value_to_int(apArg[1]) == 3 /* SORT_REGULAR */ ? 1 : 0; + } + /* Point to the internal representation of the input hashmap */ + pSrc = (ph7_hashmap *)apArg[0]->x.pOther; + /* Create a new array */ + pArray = ph7_context_new_array(pCtx); + if( pArray == 0 ){ + ph7_result_null(pCtx); + return PH7_OK; + } + /* Perform the requested operation */ + pEntry = pSrc->pFirst; + for( n = 0 ; n < pSrc->nEntry ; n++ ){ + pNeedle = HashmapExtractNodeValue(pEntry); + rc = SXERR_NOTFOUND; + if( pNeedle ){ + rc = HashmapFindValue((ph7_hashmap *)pArray->x.pOther,pNeedle,0,bStrict); + } + if( rc != SXRET_OK ){ + /* Perform the insertion */ + HashmapInsertNode((ph7_hashmap *)pArray->x.pOther,pEntry,TRUE); + } + /* Point to the next entry */ + pEntry = pEntry->pPrev; /* Reverse link */ + } + /* Return the freshly created array */ + ph7_result_value(pCtx,pArray); + return PH7_OK; +} +/* + * array array_flip(array $input) + * Exchanges all keys with their associated values in an array. + * Parameter + * $input + * Input array. + * Return + * The flipped array on success or NULL on failure. + */ +static int ph7_hashmap_flip(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + ph7_hashmap_node *pEntry; + ph7_hashmap *pSrc; + ph7_value *pArray; + ph7_value *pKey; + ph7_value sVal; + sxu32 n; + if( nArg < 1 ){ + /* Missing arguments,return NULL */ + ph7_result_null(pCtx); + return PH7_OK; + } + /* Make sure we are dealing with a valid hashmap */ + if( !ph7_value_is_array(apArg[0]) ){ + /* Invalid argument,return NULL */ + ph7_result_null(pCtx); + return PH7_OK; + } + /* Point to the internal representation of the input hashmap */ + pSrc = (ph7_hashmap *)apArg[0]->x.pOther; + /* Create a new array */ + pArray = ph7_context_new_array(pCtx); + if( pArray == 0 ){ + ph7_result_null(pCtx); + return PH7_OK; + } + /* Start processing */ + pEntry = pSrc->pFirst; + for( n = 0 ; n < pSrc->nEntry ; n++ ){ + /* Extract the node value */ + pKey = HashmapExtractNodeValue(pEntry); + if( pKey && (pKey->iFlags & MEMOBJ_NULL) == 0){ + /* Prepare the value for insertion */ + if( pEntry->iType == HASHMAP_INT_NODE ){ + PH7_MemObjInitFromInt(pSrc->pVm,&sVal,pEntry->xKey.iKey); + }else{ + SyString sStr; + SyStringInitFromBuf(&sStr,SyBlobData(&pEntry->xKey.sKey),SyBlobLength(&pEntry->xKey.sKey)); + PH7_MemObjInitFromString(pSrc->pVm,&sVal,&sStr); + } + /* Perform the insertion */ + ph7_array_add_elem(pArray,pKey,&sVal); + /* Safely release the value because each inserted entry + * have it's own private copy of the value. + */ + PH7_MemObjRelease(&sVal); + } + /* Point to the next entry */ + pEntry = pEntry->pPrev; /* Reverse link */ + } + /* Return the freshly created array */ + ph7_result_value(pCtx,pArray); + return PH7_OK; +} +/* + * number array_sum(array $array ) + * Calculate the sum of values in an array. + * Parameters + * $array: The input array. + * Return + * Returns the sum of values as an integer or float. + */ +static void DoubleSum(ph7_context *pCtx,ph7_hashmap *pMap) +{ + ph7_hashmap_node *pEntry; + ph7_value *pObj; + double dSum = 0; + sxu32 n; + pEntry = pMap->pFirst; + for( n = 0 ; n < pMap->nEntry ; n++ ){ + pObj = HashmapExtractNodeValue(pEntry); + if( pObj && (pObj->iFlags & (MEMOBJ_NULL|MEMOBJ_HASHMAP|MEMOBJ_OBJ|MEMOBJ_RES)) == 0){ + if( pObj->iFlags & MEMOBJ_REAL ){ + dSum += pObj->rVal; + }else if( pObj->iFlags & (MEMOBJ_INT|MEMOBJ_BOOL) ){ + dSum += (double)pObj->x.iVal; + }else if( pObj->iFlags & MEMOBJ_STRING ){ + if( SyBlobLength(&pObj->sBlob) > 0 ){ + double dv = 0; + SyStrToReal((const char *)SyBlobData(&pObj->sBlob),SyBlobLength(&pObj->sBlob),(void *)&dv,0); + dSum += dv; + } + } + } + /* Point to the next entry */ + pEntry = pEntry->pPrev; /* Reverse link */ + } + /* Return sum */ + ph7_result_double(pCtx,dSum); +} +static void Int64Sum(ph7_context *pCtx,ph7_hashmap *pMap) +{ + ph7_hashmap_node *pEntry; + ph7_value *pObj; + sxi64 nSum = 0; + sxu32 n; + pEntry = pMap->pFirst; + for( n = 0 ; n < pMap->nEntry ; n++ ){ + pObj = HashmapExtractNodeValue(pEntry); + if( pObj && (pObj->iFlags & (MEMOBJ_NULL|MEMOBJ_HASHMAP|MEMOBJ_OBJ|MEMOBJ_RES)) == 0){ + if( pObj->iFlags & MEMOBJ_REAL ){ + nSum += (sxi64)pObj->rVal; + }else if( pObj->iFlags & (MEMOBJ_INT|MEMOBJ_BOOL) ){ + nSum += pObj->x.iVal; + }else if( pObj->iFlags & MEMOBJ_STRING ){ + if( SyBlobLength(&pObj->sBlob) > 0 ){ + sxi64 nv = 0; + SyStrToInt64((const char *)SyBlobData(&pObj->sBlob),SyBlobLength(&pObj->sBlob),(void *)&nv,0); + nSum += nv; + } + } + } + /* Point to the next entry */ + pEntry = pEntry->pPrev; /* Reverse link */ + } + /* Return sum */ + ph7_result_int64(pCtx,nSum); +} +/* number array_sum(array $array ) + * (See block-coment above) + */ +static int ph7_hashmap_sum(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + ph7_hashmap *pMap; + ph7_value *pObj; + if( nArg < 1 ){ + /* Missing arguments,return 0 */ + ph7_result_int(pCtx,0); + return PH7_OK; + } + /* Make sure we are dealing with a valid hashmap */ + if( !ph7_value_is_array(apArg[0]) ){ + /* Invalid argument,return 0 */ + ph7_result_int(pCtx,0); + return PH7_OK; + } + pMap = (ph7_hashmap *)apArg[0]->x.pOther; + if( pMap->nEntry < 1 ){ + /* Nothing to compute,return 0 */ + ph7_result_int(pCtx,0); + return PH7_OK; + } + /* If the first element is of type float,then perform floating + * point computaion.Otherwise switch to int64 computaion. + */ + pObj = HashmapExtractNodeValue(pMap->pFirst); + if( pObj == 0 ){ + ph7_result_int(pCtx,0); + return PH7_OK; + } + if( pObj->iFlags & MEMOBJ_REAL ){ + DoubleSum(pCtx,pMap); + }else{ + Int64Sum(pCtx,pMap); + } + return PH7_OK; +} +/* + * number array_product(array $array ) + * Calculate the product of values in an array. + * Parameters + * $array: The input array. + * Return + * Returns the product of values as an integer or float. + */ +static void DoubleProd(ph7_context *pCtx,ph7_hashmap *pMap) +{ + ph7_hashmap_node *pEntry; + ph7_value *pObj; + double dProd; + sxu32 n; + pEntry = pMap->pFirst; + dProd = 1; + for( n = 0 ; n < pMap->nEntry ; n++ ){ + pObj = HashmapExtractNodeValue(pEntry); + if( pObj && (pObj->iFlags & (MEMOBJ_NULL|MEMOBJ_HASHMAP|MEMOBJ_OBJ|MEMOBJ_RES)) == 0){ + if( pObj->iFlags & MEMOBJ_REAL ){ + dProd *= pObj->rVal; + }else if( pObj->iFlags & (MEMOBJ_INT|MEMOBJ_BOOL) ){ + dProd *= (double)pObj->x.iVal; + }else if( pObj->iFlags & MEMOBJ_STRING ){ + if( SyBlobLength(&pObj->sBlob) > 0 ){ + double dv = 0; + SyStrToReal((const char *)SyBlobData(&pObj->sBlob),SyBlobLength(&pObj->sBlob),(void *)&dv,0); + dProd *= dv; + } + } + } + /* Point to the next entry */ + pEntry = pEntry->pPrev; /* Reverse link */ + } + /* Return product */ + ph7_result_double(pCtx,dProd); +} +static void Int64Prod(ph7_context *pCtx,ph7_hashmap *pMap) +{ + ph7_hashmap_node *pEntry; + ph7_value *pObj; + sxi64 nProd; + sxu32 n; + pEntry = pMap->pFirst; + nProd = 1; + for( n = 0 ; n < pMap->nEntry ; n++ ){ + pObj = HashmapExtractNodeValue(pEntry); + if( pObj && (pObj->iFlags & (MEMOBJ_NULL|MEMOBJ_HASHMAP|MEMOBJ_OBJ|MEMOBJ_RES)) == 0){ + if( pObj->iFlags & MEMOBJ_REAL ){ + nProd *= (sxi64)pObj->rVal; + }else if( pObj->iFlags & (MEMOBJ_INT|MEMOBJ_BOOL) ){ + nProd *= pObj->x.iVal; + }else if( pObj->iFlags & MEMOBJ_STRING ){ + if( SyBlobLength(&pObj->sBlob) > 0 ){ + sxi64 nv = 0; + SyStrToInt64((const char *)SyBlobData(&pObj->sBlob),SyBlobLength(&pObj->sBlob),(void *)&nv,0); + nProd *= nv; + } + } + } + /* Point to the next entry */ + pEntry = pEntry->pPrev; /* Reverse link */ + } + /* Return product */ + ph7_result_int64(pCtx,nProd); +} +/* number array_product(array $array ) + * (See block-block comment above) + */ +static int ph7_hashmap_product(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + ph7_hashmap *pMap; + ph7_value *pObj; + if( nArg < 1 ){ + /* Missing arguments,return 0 */ + ph7_result_int(pCtx,0); + return PH7_OK; + } + /* Make sure we are dealing with a valid hashmap */ + if( !ph7_value_is_array(apArg[0]) ){ + /* Invalid argument,return 0 */ + ph7_result_int(pCtx,0); + return PH7_OK; + } + pMap = (ph7_hashmap *)apArg[0]->x.pOther; + if( pMap->nEntry < 1 ){ + /* Nothing to compute,return 0 */ + ph7_result_int(pCtx,0); + return PH7_OK; + } + /* If the first element is of type float,then perform floating + * point computaion.Otherwise switch to int64 computaion. + */ + pObj = HashmapExtractNodeValue(pMap->pFirst); + if( pObj == 0 ){ + ph7_result_int(pCtx,0); + return PH7_OK; + } + if( pObj->iFlags & MEMOBJ_REAL ){ + DoubleProd(pCtx,pMap); + }else{ + Int64Prod(pCtx,pMap); + } + return PH7_OK; +} +/* + * value array_rand(array $input[,int $num_req = 1 ]) + * Pick one or more random entries out of an array. + * Parameters + * $input + * The input array. + * $num_req + * Specifies how many entries you want to pick. + * Return + * If you are picking only one entry, array_rand() returns the key for a random entry. + * Otherwise, it returns an array of keys for the random entries. + * NULL is returned on failure. + */ +static int ph7_hashmap_rand(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + ph7_hashmap_node *pNode; + ph7_hashmap *pMap; + int nItem = 1; + if( nArg < 1 ){ + /* Missing argument,return NULL */ + ph7_result_null(pCtx); + return PH7_OK; + } + /* Make sure we are dealing with an array */ + if( !ph7_value_is_array(apArg[0]) ){ + ph7_result_null(pCtx); + return PH7_OK; + } + /* Point to the internal representation of the input hashmap */ + pMap = (ph7_hashmap *)apArg[0]->x.pOther; + if(pMap->nEntry < 1 ){ + /* Empty hashmap,return NULL */ + ph7_result_null(pCtx); + return PH7_OK; + } + if( nArg > 1 ){ + nItem = ph7_value_to_int(apArg[1]); + } + if( nItem < 2 ){ + sxu32 nEntry; + /* Select a random number */ + nEntry = PH7_VmRandomNum(pMap->pVm) % pMap->nEntry; + /* Extract the desired entry. + * Note that we perform a linear lookup here (later version must change this) + */ + if( nEntry > pMap->nEntry / 2 ){ + pNode = pMap->pLast; + nEntry = pMap->nEntry - nEntry; + if( nEntry > 1 ){ + for(;;){ + if( nEntry == 0 ){ + break; + } + /* Point to the previous entry */ + pNode = pNode->pNext; /* Reverse link */ + nEntry--; + } + } + }else{ + pNode = pMap->pFirst; + for(;;){ + if( nEntry == 0 ){ + break; + } + /* Point to the next entry */ + pNode = pNode->pPrev; /* Reverse link */ + nEntry--; + } + } + if( pNode->iType == HASHMAP_INT_NODE ){ + /* Int key */ + ph7_result_int64(pCtx,pNode->xKey.iKey); + }else{ + /* Blob key */ + ph7_result_string(pCtx,(const char *)SyBlobData(&pNode->xKey.sKey),(int)SyBlobLength(&pNode->xKey.sKey)); + } + }else{ + ph7_value sKey,*pArray; + ph7_hashmap *pDest; + /* Create a new array */ + pArray = ph7_context_new_array(pCtx); + if( pArray == 0 ){ + ph7_result_null(pCtx); + return PH7_OK; + } + /* Point to the internal representation of the hashmap */ + pDest = (ph7_hashmap *)pArray->x.pOther; + PH7_MemObjInit(pDest->pVm,&sKey); + /* Copy the first n items */ + pNode = pMap->pFirst; + if( nItem > (int)pMap->nEntry ){ + nItem = (int)pMap->nEntry; + } + while( nItem > 0){ + PH7_HashmapExtractNodeKey(pNode,&sKey); + PH7_HashmapInsert(pDest,0/* Automatic index assign*/,&sKey); + PH7_MemObjRelease(&sKey); + /* Point to the next entry */ + pNode = pNode->pPrev; /* Reverse link */ + nItem--; + } + /* Shuffle the array */ + HashmapMergeSort(pDest,HashmapCmpCallback7,0); + /* Rehash node */ + HashmapSortRehash(pDest); + /* Return the random array */ + ph7_result_value(pCtx,pArray); + } + return PH7_OK; +} +/* + * array array_chunk (array $input,int $size [,bool $preserve_keys = false ]) + * Split an array into chunks. + * Parameters + * $input + * The array to work on + * $size + * The size of each chunk + * $preserve_keys + * When set to TRUE keys will be preserved. Default is FALSE which will reindex + * the chunk numerically. + * Return + * Returns a multidimensional numerically indexed array, starting with + * zero, with each dimension containing size elements. + */ +static int ph7_hashmap_chunk(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + ph7_value *pArray,*pChunk; + ph7_hashmap_node *pEntry; + ph7_hashmap *pMap; + int bPreserve; + sxu32 nChunk; + sxu32 nSize; + sxu32 n; + if( nArg < 2 || !ph7_value_is_array(apArg[0]) ){ + /* Invalid arguments,return NULL */ + ph7_result_null(pCtx); + return PH7_OK; + } + /* Create a new array */ + pArray = ph7_context_new_array(pCtx); + if( pArray == 0 ){ + ph7_result_null(pCtx); + return PH7_OK; + } + /* Point to the internal representation of the input hashmap */ + pMap = (ph7_hashmap *)apArg[0]->x.pOther; + /* Extract the chunk size */ + nSize = (sxu32)ph7_value_to_int(apArg[1]); + if( nSize < 1 ){ + ph7_result_null(pCtx); + return PH7_OK; + } + if( nSize >= pMap->nEntry ){ + /* Return the whole array */ + ph7_array_add_elem(pArray,0,apArg[0]); + ph7_result_value(pCtx,pArray); + return PH7_OK; + } + bPreserve = 0; + if( nArg > 2 ){ + bPreserve = ph7_value_to_bool(apArg[2]); + } + /* Start processing */ + pEntry = pMap->pFirst; + nChunk = 0; + pChunk = 0; + n = pMap->nEntry; + for( ;; ){ + if( n < 1 ){ + if( nChunk > 0 ){ + /* Insert the last chunk */ + ph7_array_add_elem(pArray,0,pChunk); /* Will have it's own copy */ + } + break; + } + if( nChunk < 1 ){ + if( pChunk ){ + /* Put the first chunk */ + ph7_array_add_elem(pArray,0,pChunk); /* Will have it's own copy */ + } + /* Create a new dimension */ + pChunk = ph7_context_new_array(pCtx); /* Don't worry about freeing memory here,everything + * will be automatically released as soon we return + * from this function */ + if( pChunk == 0 ){ + break; + } + nChunk = nSize; + } + /* Insert the entry */ + HashmapInsertNode((ph7_hashmap *)pChunk->x.pOther,pEntry,bPreserve); + /* Point to the next entry */ + pEntry = pEntry->pPrev; /* Reverse link */ + nChunk--; + n--; + } + /* Return the multidimensional array */ + ph7_result_value(pCtx,pArray); + return PH7_OK; +} +/* + * array array_pad(array $input,int $pad_size,value $pad_value) + * Pad array to the specified length with a value. + * $input + * Initial array of values to pad. + * $pad_size + * New size of the array. + * $pad_value + * Value to pad if input is less than pad_size. + */ +static int ph7_hashmap_pad(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + ph7_hashmap *pMap; + ph7_value *pArray; + int nEntry; + if( nArg < 3 || !ph7_value_is_array(apArg[0]) ){ + /* Invalid arguments,return NULL */ + ph7_result_null(pCtx); + return PH7_OK; + } + /* Create a new array */ + pArray = ph7_context_new_array(pCtx); + if( pArray == 0 ){ + ph7_result_null(pCtx); + return PH7_OK; + } + /* Point to the internal representation of the input hashmap */ + pMap = (ph7_hashmap *)apArg[0]->x.pOther; + /* Extract the total number of desired entry to insert */ + nEntry = ph7_value_to_int(apArg[1]); + if( nEntry < 0 ){ + nEntry = -nEntry; + if( nEntry > 1048576 ){ + nEntry = 1048576; /* Limit imposed by PHP */ + } + if( nEntry > (int)pMap->nEntry ){ + nEntry -= (int)pMap->nEntry; + /* Insert given items first */ + while( nEntry > 0 ){ + ph7_array_add_elem(pArray,0,apArg[2]); + nEntry--; + } + /* Merge the two arrays */ + HashmapMerge(pMap,(ph7_hashmap *)pArray->x.pOther); + }else{ + PH7_HashmapDup(pMap,(ph7_hashmap *)pArray->x.pOther); + } + }else if( nEntry > 0 ){ + if( nEntry > 1048576 ){ + nEntry = 1048576; /* Limit imposed by PHP */ + } + if( nEntry > (int)pMap->nEntry ){ + nEntry -= (int)pMap->nEntry; + /* Merge the two arrays first */ + HashmapMerge(pMap,(ph7_hashmap *)pArray->x.pOther); + /* Insert given items */ + while( nEntry > 0 ){ + ph7_array_add_elem(pArray,0,apArg[2]); + nEntry--; + } + }else{ + PH7_HashmapDup(pMap,(ph7_hashmap *)pArray->x.pOther); + } + } + /* Return the new array */ + ph7_result_value(pCtx,pArray); + return PH7_OK; +} +/* + * array array_replace(array &$array,array &$array1,...) + * Replaces elements from passed arrays into the first array. + * Parameters + * $array + * The array in which elements are replaced. + * $array1 + * The array from which elements will be extracted. + * .... + * More arrays from which elements will be extracted. + * Values from later arrays overwrite the previous values. + * Return + * Returns an array, or NULL if an error occurs. + */ +static int ph7_hashmap_replace(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + ph7_hashmap *pMap; + ph7_value *pArray; + int i; + if( nArg < 1 ){ + /* Invalid arguments,return NULL */ + ph7_result_null(pCtx); + return PH7_OK; + } + /* Create a new array */ + pArray = ph7_context_new_array(pCtx); + if( pArray == 0 ){ + ph7_result_null(pCtx); + return PH7_OK; + } + /* Perform the requested operation */ + for( i = 0 ; i < nArg ; i++ ){ + if( !ph7_value_is_array(apArg[i]) ){ + continue; + } + /* Point to the internal representation of the input hashmap */ + pMap = (ph7_hashmap *)apArg[i]->x.pOther; + HashmapOverwrite(pMap,(ph7_hashmap *)pArray->x.pOther); + } + /* Return the new array */ + ph7_result_value(pCtx,pArray); + return PH7_OK; +} +/* + * array array_filter(array $input [,callback $callback ]) + * Filters elements of an array using a callback function. + * Parameters + * $input + * The array to iterate over + * $callback + * The callback function to use + * If no callback is supplied, all entries of input equal to FALSE (see converting to boolean) + * will be removed. + * Return + * The filtered array. + */ +static int ph7_hashmap_filter(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + ph7_hashmap_node *pEntry; + ph7_hashmap *pMap; + ph7_value *pArray; + ph7_value sResult; /* Callback result */ + ph7_value *pValue; + sxi32 rc; + int keep; + sxu32 n; + if( nArg < 1 || !ph7_value_is_array(apArg[0]) ){ + /* Invalid arguments,return NULL */ + ph7_result_null(pCtx); + return PH7_OK; + } + /* Create a new array */ + pArray = ph7_context_new_array(pCtx); + if( pArray == 0 ){ + ph7_result_null(pCtx); + return PH7_OK; + } + /* Point to the internal representation of the input hashmap */ + pMap = (ph7_hashmap *)apArg[0]->x.pOther; + pEntry = pMap->pFirst; + PH7_MemObjInit(pMap->pVm,&sResult); + sResult.nIdx = SXU32_HIGH; /* Mark as constant */ + /* Perform the requested operation */ + for( n = 0 ; n < pMap->nEntry ; n++ ){ + /* Extract node value */ + pValue = HashmapExtractNodeValue(pEntry); + if( nArg > 1 && pValue ){ + /* Invoke the given callback */ + keep = FALSE; + rc = PH7_VmCallUserFunction(pMap->pVm,apArg[1],1,&pValue,&sResult); + if( rc == SXRET_OK ){ + /* Perform a boolean cast */ + keep = ph7_value_to_bool(&sResult); + } + PH7_MemObjRelease(&sResult); + }else{ + /* No available callback,check for empty item */ + keep = !PH7_MemObjIsEmpty(pValue); + } + if( keep ){ + /* Perform the insertion,now the callback returned true */ + HashmapInsertNode((ph7_hashmap *)pArray->x.pOther,pEntry,TRUE); + } + /* Point to the next entry */ + pEntry = pEntry->pPrev; /* Reverse link */ + } + ph7_result_value(pCtx,pArray); + return PH7_OK; +} +/* + * array array_map(callback $callback,array $arr1) + * Applies the callback to the elements of the given arrays. + * Parameters + * $callback + * Callback function to run for each element in each array. + * $arr1 + * An array to run through the callback function. + * Return + * Returns an array containing all the elements of arr1 after applying + * the callback function to each one. + * NOTE: + * array_map() passes only a single value to the callback. + */ +static int ph7_hashmap_map(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + ph7_value *pArray,*pValue,sKey,sResult; + ph7_hashmap_node *pEntry; + ph7_hashmap *pMap; + sxu32 n; + if( nArg < 2 || !ph7_value_is_array(apArg[1]) ){ + /* Invalid arguments,return NULL */ + ph7_result_null(pCtx); + return PH7_OK; + } + /* Create a new array */ + pArray = ph7_context_new_array(pCtx); + if( pArray == 0 ){ + ph7_result_null(pCtx); + return PH7_OK; + } + /* Point to the internal representation of the input hashmap */ + pMap = (ph7_hashmap *)apArg[1]->x.pOther; + PH7_MemObjInit(pMap->pVm,&sResult); + PH7_MemObjInit(pMap->pVm,&sKey); + sResult.nIdx = SXU32_HIGH; /* Mark as constant */ + sKey.nIdx = SXU32_HIGH; /* Mark as constant */ + /* Perform the requested operation */ + pEntry = pMap->pFirst; + for( n = 0 ; n < pMap->nEntry ; n++ ){ + /* Extrcat the node value */ + pValue = HashmapExtractNodeValue(pEntry); + if( pValue ){ + sxi32 rc; + /* Invoke the supplied callback */ + rc = PH7_VmCallUserFunction(pMap->pVm,apArg[0],1,&pValue,&sResult); + /* Extract the node key */ + PH7_HashmapExtractNodeKey(pEntry,&sKey); + if( rc != SXRET_OK ){ + /* An error occured while invoking the supplied callback [i.e: not defined] */ + ph7_array_add_elem(pArray,&sKey,pValue); /* Keep the same value */ + }else{ + /* Insert the callback return value */ + ph7_array_add_elem(pArray,&sKey,&sResult); + } + PH7_MemObjRelease(&sKey); + PH7_MemObjRelease(&sResult); + } + /* Point to the next entry */ + pEntry = pEntry->pPrev; /* Reverse link */ + } + ph7_result_value(pCtx,pArray); + return PH7_OK; +} +/* + * value array_reduce(array $input,callback $function[, value $initial = NULL ]) + * Iteratively reduce the array to a single value using a callback function. + * Parameters + * $input + * The input array. + * $function + * The callback function. + * $initial + * If the optional initial is available, it will be used at the beginning + * of the process, or as a final result in case the array is empty. + * Return + * Returns the resulting value. + * If the array is empty and initial is not passed, array_reduce() returns NULL. + */ +static int ph7_hashmap_reduce(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + ph7_hashmap_node *pEntry; + ph7_hashmap *pMap; + ph7_value *pValue; + ph7_value sResult; + sxu32 n; + if( nArg < 2 || !ph7_value_is_array(apArg[0]) ){ + /* Invalid/Missing arguments,return NULL */ + ph7_result_null(pCtx); + return PH7_OK; + } + /* Point to the internal representation of the input hashmap */ + pMap = (ph7_hashmap *)apArg[0]->x.pOther; + /* Assume a NULL initial value */ + PH7_MemObjInit(pMap->pVm,&sResult); + sResult.nIdx = SXU32_HIGH; /* Mark as constant */ + if( nArg > 2 ){ + /* Set the initial value */ + PH7_MemObjLoad(apArg[2],&sResult); + } + /* Perform the requested operation */ + pEntry = pMap->pFirst; + for( n = 0 ; n < pMap->nEntry ; n++ ){ + /* Extract the node value */ + pValue = HashmapExtractNodeValue(pEntry); + /* Invoke the supplied callback */ + PH7_VmCallUserFunctionAp(pMap->pVm,apArg[1],&sResult,&sResult,pValue,0); + /* Point to the next entry */ + pEntry = pEntry->pPrev; /* Reverse link */ + } + ph7_result_value(pCtx,&sResult); /* Will make it's own copy */ + PH7_MemObjRelease(&sResult); + return PH7_OK; +} +/* + * bool array_walk(array &$array,callback $funcname [, value $userdata ] ) + * Apply a user function to every member of an array. + * Parameters + * $array + * The input array. + * $funcname + * Typically, funcname takes on two parameters.The array parameter's value being + * the first, and the key/index second. + * Note: + * If funcname needs to be working with the actual values of the array,specify the first + * parameter of funcname as a reference. Then, any changes made to those elements will + * be made in the original array itself. + * $userdata + * If the optional userdata parameter is supplied, it will be passed as the third parameter + * to the callback funcname. + * Return + * Returns TRUE on success or FALSE on failure. + */ +static int ph7_hashmap_walk(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + ph7_value *pValue,*pUserData,sKey; + ph7_hashmap_node *pEntry; + ph7_hashmap *pMap; + sxi32 rc; + sxu32 n; + if( nArg < 2 || !ph7_value_is_array(apArg[0]) ){ + /* Invalid/Missing arguments,return FALSE */ + ph7_result_bool(pCtx,0); + return PH7_OK; + } + pUserData = nArg > 2 ? apArg[2] : 0; + /* Point to the internal representation of the input hashmap */ + pMap = (ph7_hashmap *)apArg[0]->x.pOther; + PH7_MemObjInit(pMap->pVm,&sKey); + sKey.nIdx = SXU32_HIGH; /* Mark as constant */ + /* Perform the desired operation */ + pEntry = pMap->pFirst; + for( n = 0 ; n < pMap->nEntry ; n++ ){ + /* Extract the node value */ + pValue = HashmapExtractNodeValue(pEntry); + if( pValue ){ + /* Extract the entry key */ + PH7_HashmapExtractNodeKey(pEntry,&sKey); + /* Invoke the supplied callback */ + rc = PH7_VmCallUserFunctionAp(pMap->pVm,apArg[1],0,pValue,&sKey,pUserData,0); + PH7_MemObjRelease(&sKey); + if( rc != SXRET_OK ){ + /* An error occured while invoking the supplied callback [i.e: not defined] */ + ph7_result_bool(pCtx,0); /* return FALSE */ + return PH7_OK; + } + } + /* Point to the next entry */ + pEntry = pEntry->pPrev; /* Reverse link */ + } + /* All done,return TRUE */ + ph7_result_bool(pCtx,1); + return PH7_OK; +} +/* + * Apply a user function to every member of an array.(Recurse on array's). + * Refer to the [array_walk_recursive()] implementation for more information. + */ +static int HashmapWalkRecursive( + ph7_hashmap *pMap, /* Target hashmap */ + ph7_value *pCallback, /* User callback */ + ph7_value *pUserData, /* Callback private data */ + int iNest /* Nesting level */ + ) +{ + ph7_hashmap_node *pEntry; + ph7_value *pValue,sKey; + sxi32 rc; + sxu32 n; + /* Iterate throw hashmap entries */ + PH7_MemObjInit(pMap->pVm,&sKey); + sKey.nIdx = SXU32_HIGH; /* Mark as constant */ + pEntry = pMap->pFirst; + for( n = 0 ; n < pMap->nEntry ; n++ ){ + /* Extract the node value */ + pValue = HashmapExtractNodeValue(pEntry); + if( pValue ){ + if( pValue->iFlags & MEMOBJ_HASHMAP ){ + if( iNest < 32 ){ + /* Recurse */ + iNest++; + HashmapWalkRecursive((ph7_hashmap *)pValue->x.pOther,pCallback,pUserData,iNest); + iNest--; + } + }else{ + /* Extract the node key */ + PH7_HashmapExtractNodeKey(pEntry,&sKey); + /* Invoke the supplied callback */ + rc = PH7_VmCallUserFunctionAp(pMap->pVm,pCallback,0,pValue,&sKey,pUserData,0); + PH7_MemObjRelease(&sKey); + if( rc != SXRET_OK ){ + return rc; + } + } + } + /* Point to the next entry */ + pEntry = pEntry->pPrev; /* Reverse link */ + } + return SXRET_OK; +} +/* + * bool array_walk_recursive(array &$array,callback $funcname [, value $userdata ] ) + * Apply a user function recursively to every member of an array. + * Parameters + * $array + * The input array. + * $funcname + * Typically, funcname takes on two parameters.The array parameter's value being + * the first, and the key/index second. + * Note: + * If funcname needs to be working with the actual values of the array,specify the first + * parameter of funcname as a reference. Then, any changes made to those elements will + * be made in the original array itself. + * $userdata + * If the optional userdata parameter is supplied, it will be passed as the third parameter + * to the callback funcname. + * Return + * Returns TRUE on success or FALSE on failure. + */ +static int ph7_hashmap_walk_recursive(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + ph7_hashmap *pMap; + sxi32 rc; + if( nArg < 2 || !ph7_value_is_array(apArg[0]) ){ + /* Invalid/Missing arguments,return FALSE */ + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Point to the internal representation of the input hashmap */ + pMap = (ph7_hashmap *)apArg[0]->x.pOther; + /* Perform the desired operation */ + rc = HashmapWalkRecursive(pMap,apArg[1],nArg > 2 ? apArg[2] : 0,0); + /* All done */ + ph7_result_bool(pCtx,rc == SXRET_OK); + return PH7_OK; +} +/* + * Table of hashmap functions. + */ +static const ph7_builtin_func aHashmapFunc[] = { + {"count", ph7_hashmap_count }, + {"sizeof", ph7_hashmap_count }, + {"array_key_exists", ph7_hashmap_key_exists }, + {"array_pop", ph7_hashmap_pop }, + {"array_push", ph7_hashmap_push }, + {"array_shift", ph7_hashmap_shift }, + {"array_product", ph7_hashmap_product }, + {"array_sum", ph7_hashmap_sum }, + {"array_keys", ph7_hashmap_keys }, + {"array_values", ph7_hashmap_values }, + {"array_same", ph7_hashmap_same }, /* Symisc eXtension */ + {"array_merge", ph7_hashmap_merge }, + {"array_slice", ph7_hashmap_slice }, + {"array_splice", ph7_hashmap_splice }, + {"array_search", ph7_hashmap_search }, + {"array_diff", ph7_hashmap_diff }, + {"array_udiff", ph7_hashmap_udiff }, + {"array_diff_assoc", ph7_hashmap_diff_assoc }, + {"array_diff_uassoc", ph7_hashmap_diff_uassoc }, + {"array_diff_key", ph7_hashmap_diff_key }, + {"array_intersect", ph7_hashmap_intersect}, + {"array_intersect_assoc", ph7_hashmap_intersect_assoc}, + {"array_uintersect", ph7_hashmap_uintersect}, + {"array_intersect_key", ph7_hashmap_intersect_key}, + {"array_copy", ph7_hashmap_copy }, + {"array_erase", ph7_hashmap_erase }, + {"array_fill", ph7_hashmap_fill }, + {"array_fill_keys", ph7_hashmap_fill_keys}, + {"array_combine", ph7_hashmap_combine }, + {"array_reverse", ph7_hashmap_reverse }, + {"array_unique", ph7_hashmap_unique }, + {"array_flip", ph7_hashmap_flip }, + {"array_rand", ph7_hashmap_rand }, + {"array_chunk", ph7_hashmap_chunk }, + {"array_pad", ph7_hashmap_pad }, + {"array_replace", ph7_hashmap_replace }, + {"array_filter", ph7_hashmap_filter }, + {"array_map", ph7_hashmap_map }, + {"array_reduce", ph7_hashmap_reduce }, + {"array_walk", ph7_hashmap_walk }, + {"array_walk_recursive", ph7_hashmap_walk_recursive }, + {"in_array", ph7_hashmap_in_array}, + {"sort", ph7_hashmap_sort }, + {"asort", ph7_hashmap_asort }, + {"arsort", ph7_hashmap_arsort }, + {"ksort", ph7_hashmap_ksort }, + {"krsort", ph7_hashmap_krsort }, + {"rsort", ph7_hashmap_rsort }, + {"usort", ph7_hashmap_usort }, + {"uasort", ph7_hashmap_uasort }, + {"uksort", ph7_hashmap_uksort }, + {"shuffle", ph7_hashmap_shuffle }, + {"range", ph7_hashmap_range }, + {"current", ph7_hashmap_current }, + {"each", ph7_hashmap_each }, + {"pos", ph7_hashmap_current }, + {"next", ph7_hashmap_next }, + {"prev", ph7_hashmap_prev }, + {"end", ph7_hashmap_end }, + {"reset", ph7_hashmap_reset }, + {"key", ph7_hashmap_simple_key } +}; +/* + * Register the built-in hashmap functions defined above. + */ +PH7_PRIVATE void PH7_RegisterHashmapFunctions(ph7_vm *pVm) +{ + sxu32 n; + for( n = 0 ; n < SX_ARRAYSIZE(aHashmapFunc) ; n++ ){ + ph7_create_function(&(*pVm),aHashmapFunc[n].zName,aHashmapFunc[n].xFunc,0); + } +} +/* + * Dump a hashmap instance and it's entries and the store the dump in + * the BLOB given as the first argument. + * This function is typically invoked when the user issue a call to + * [var_dump(),var_export(),print_r(),...] + * This function SXRET_OK on success. Any other return value including + * SXERR_LIMIT(infinite recursion) indicates failure. + */ +PH7_PRIVATE sxi32 PH7_HashmapDump(SyBlob *pOut,ph7_hashmap *pMap,int ShowType,int nTab,int nDepth) +{ + ph7_hashmap_node *pEntry; + ph7_value *pObj; + sxu32 n = 0; + int isRef; + sxi32 rc; + int i; + if( nDepth > 31 ){ + static const char zInfinite[] = "Nesting limit reached: Infinite recursion?"; + /* Nesting limit reached */ + SyBlobAppend(&(*pOut),zInfinite,sizeof(zInfinite)-1); + if( ShowType ){ + SyBlobAppend(&(*pOut),")",sizeof(char)); + } + return SXERR_LIMIT; + } + /* Point to the first inserted entry */ + pEntry = pMap->pFirst; + rc = SXRET_OK; + if( !ShowType ){ + SyBlobAppend(&(*pOut),"Array(",sizeof("Array(")-1); + } + /* Total entries */ + SyBlobFormat(&(*pOut),"%u) {",pMap->nEntry); +#ifdef __WINNT__ + SyBlobAppend(&(*pOut),"\r\n",sizeof("\r\n")-1); +#else + SyBlobAppend(&(*pOut),"\n",sizeof(char)); +#endif + for(;;){ + if( n >= pMap->nEntry ){ + break; + } + for( i = 0 ; i < nTab ; i++ ){ + SyBlobAppend(&(*pOut)," ",sizeof(char)); + } + /* Dump key */ + if( pEntry->iType == HASHMAP_INT_NODE){ + SyBlobFormat(&(*pOut),"[%qd] =>",pEntry->xKey.iKey); + }else{ + SyBlobFormat(&(*pOut),"[%.*s] =>", + SyBlobLength(&pEntry->xKey.sKey),SyBlobData(&pEntry->xKey.sKey)); + } +#ifdef __WINNT__ + SyBlobAppend(&(*pOut),"\r\n",sizeof("\r\n")-1); +#else + SyBlobAppend(&(*pOut),"\n",sizeof(char)); +#endif + /* Dump node value */ + pObj = HashmapExtractNodeValue(pEntry); + isRef = 0; + if( pObj ){ + if( pEntry->iFlags & HASHMAP_NODE_FOREIGN_OBJ ){ + /* Referenced object */ + isRef = 1; + } + rc = PH7_MemObjDump(&(*pOut),pObj,ShowType,nTab+1,nDepth,isRef); + if( rc == SXERR_LIMIT ){ + break; + } + } + /* Point to the next entry */ + n++; + pEntry = pEntry->pPrev; /* Reverse link */ + } + for( i = 0 ; i < nTab ; i++ ){ + SyBlobAppend(&(*pOut)," ",sizeof(char)); + } + SyBlobAppend(&(*pOut),"}",sizeof(char)); + return rc; +} +/* + * Iterate throw hashmap entries and invoke the given callback [i.e: xWalk()] for each + * retrieved entry. + * Note that argument are passed to the callback by copy. That is,any modification to + * the entry value in the callback body will not alter the real value. + * If the callback wishes to abort processing [i.e: it's invocation] it must return + * a value different from PH7_OK. + * Refer to [ph7_array_walk()] for more information. + */ +PH7_PRIVATE sxi32 PH7_HashmapWalk( + ph7_hashmap *pMap, /* Target hashmap */ + int (*xWalk)(ph7_value *,ph7_value *,void *), /* Walker callback */ + void *pUserData /* Last argument to xWalk() */ + ) +{ + ph7_hashmap_node *pEntry; + ph7_value sKey,sValue; + sxi32 rc; + sxu32 n; + /* Initialize walker parameter */ + rc = SXRET_OK; + PH7_MemObjInit(pMap->pVm,&sKey); + PH7_MemObjInit(pMap->pVm,&sValue); + n = pMap->nEntry; + pEntry = pMap->pFirst; + /* Start the iteration process */ + for(;;){ + if( n < 1 ){ + break; + } + /* Extract a copy of the key and a copy the current value */ + PH7_HashmapExtractNodeKey(pEntry,&sKey); + PH7_HashmapExtractNodeValue(pEntry,&sValue,FALSE); + /* Invoke the user callback */ + rc = xWalk(&sKey,&sValue,pUserData); + /* Release the copy of the key and the value */ + PH7_MemObjRelease(&sKey); + PH7_MemObjRelease(&sValue); + if( rc != PH7_OK ){ + /* Callback request an operation abort */ + return SXERR_ABORT; + } + /* Point to the next entry */ + pEntry = pEntry->pPrev; /* Reverse link */ + n--; + } + /* All done */ + return SXRET_OK; +} +/* + * ---------------------------------------------------------- + * File: constant.c + * MD5: 9cf62714d3cc5de3825c4eebc8378bb7 + * ---------------------------------------------------------- + */ +/* + * Symisc PH7: An embeddable bytecode compiler and a virtual machine for the PHP(5) programming language. + * Copyright (C) 2011-2012, Symisc Systems http://ph7.symisc.net/ + * Version 2.1.4 + * For information on licensing,redistribution of this file,and for a DISCLAIMER OF ALL WARRANTIES + * please contact Symisc Systems via: + * legal@symisc.net + * licensing@symisc.net + * contact@symisc.net + * or visit: + * http://ph7.symisc.net/ + */ + /* $SymiscID: constant.c v1.1 Win7 2012-08-07 08:22 devel $ */ +#ifndef PH7_AMALGAMATION +#include "ph7int.h" +#endif +/* This file implement built-in constants for the PH7 engine. */ +/* + * PH7_VERSION + * __PH7__ + * Expand the current version of the PH7 engine. + */ +static void PH7_VER_Const(ph7_value *pVal,void *pUnused) +{ + SXUNUSED(pUnused); + ph7_value_string(pVal,ph7_lib_signature(),-1/*Compute length automatically*/); +} +#ifdef __WINNT__ +#include +#elif defined(__UNIXES__) +#include +#endif +/* + * PHP_OS + * Expand the name of the host Operating System. + */ +static void PH7_OS_Const(ph7_value *pVal,void *pUnused) +{ +#if defined(__WINNT__) + ph7_value_string(pVal,"WINNT",(int)sizeof("WINNT")-1); +#elif defined(__UNIXES__) + struct utsname sInfo; + if( uname(&sInfo) != 0 ){ + ph7_value_string(pVal,"Unix",(int)sizeof("Unix")-1); + }else{ + ph7_value_string(pVal,sInfo.sysname,-1); + } +#else + ph7_value_string(pVal,"Host OS",(int)sizeof("Host OS")-1); +#endif + SXUNUSED(pUnused); +} +/* + * PHP_EOL + * Expand the correct 'End Of Line' symbol for this platform. + */ +static void PH7_EOL_Const(ph7_value *pVal,void *pUnused) +{ + SXUNUSED(pUnused); +#ifdef __WINNT__ + ph7_value_string(pVal,"\r\n",(int)sizeof("\r\n")-1); +#else + ph7_value_string(pVal,"\n",(int)sizeof(char)); +#endif +} +/* + * PHP_INT_MAX + * Expand the largest integer supported. + * Note that PH7 deals with 64-bit integer for all platforms. + */ +static void PH7_INTMAX_Const(ph7_value *pVal,void *pUnused) +{ + SXUNUSED(pUnused); + ph7_value_int64(pVal,SXI64_HIGH); +} +/* + * PHP_INT_SIZE + * Expand the size in bytes of a 64-bit integer. + */ +static void PH7_INTSIZE_Const(ph7_value *pVal,void *pUnused) +{ + SXUNUSED(pUnused); + ph7_value_int64(pVal,sizeof(sxi64)); +} +/* + * DIRECTORY_SEPARATOR. + * Expand the directory separator character. + */ +static void PH7_DIRSEP_Const(ph7_value *pVal,void *pUnused) +{ + SXUNUSED(pUnused); +#ifdef __WINNT__ + ph7_value_string(pVal,"\\",(int)sizeof(char)); +#else + ph7_value_string(pVal,"/",(int)sizeof(char)); +#endif +} +/* + * PATH_SEPARATOR. + * Expand the path separator character. + */ +static void PH7_PATHSEP_Const(ph7_value *pVal,void *pUnused) +{ + SXUNUSED(pUnused); +#ifdef __WINNT__ + ph7_value_string(pVal,";",(int)sizeof(char)); +#else + ph7_value_string(pVal,":",(int)sizeof(char)); +#endif +} +#ifndef __WINNT__ +#include +#endif +/* + * __TIME__ + * Expand the current time (GMT). + */ +static void PH7_TIME_Const(ph7_value *pVal,void *pUnused) +{ + Sytm sTm; +#ifdef __WINNT__ + SYSTEMTIME sOS; + GetSystemTime(&sOS); + SYSTEMTIME_TO_SYTM(&sOS,&sTm); +#else + struct tm *pTm; + time_t t; + time(&t); + pTm = gmtime(&t); + STRUCT_TM_TO_SYTM(pTm,&sTm); +#endif + SXUNUSED(pUnused); /* cc warning */ + /* Expand */ + ph7_value_string_format(pVal,"%02d:%02d:%02d",sTm.tm_hour,sTm.tm_min,sTm.tm_sec); +} +/* + * __DATE__ + * Expand the current date in the ISO-8601 format. + */ +static void PH7_DATE_Const(ph7_value *pVal,void *pUnused) +{ + Sytm sTm; +#ifdef __WINNT__ + SYSTEMTIME sOS; + GetSystemTime(&sOS); + SYSTEMTIME_TO_SYTM(&sOS,&sTm); +#else + struct tm *pTm; + time_t t; + time(&t); + pTm = gmtime(&t); + STRUCT_TM_TO_SYTM(pTm,&sTm); +#endif + SXUNUSED(pUnused); /* cc warning */ + /* Expand */ + ph7_value_string_format(pVal,"%04d-%02d-%02d",sTm.tm_year,sTm.tm_mon+1,sTm.tm_mday); +} +/* + * __FILE__ + * Path of the processed script. + */ +static void PH7_FILE_Const(ph7_value *pVal,void *pUserData) +{ + ph7_vm *pVm = (ph7_vm *)pUserData; + SyString *pFile; + /* Peek the top entry */ + pFile = (SyString *)SySetPeek(&pVm->aFiles); + if( pFile == 0 ){ + /* Expand the magic word: ":MEMORY:" */ + ph7_value_string(pVal,":MEMORY:",(int)sizeof(":MEMORY:")-1); + }else{ + ph7_value_string(pVal,pFile->zString,pFile->nByte); + } +} +/* + * __DIR__ + * Directory holding the processed script. + */ +static void PH7_DIR_Const(ph7_value *pVal,void *pUserData) +{ + ph7_vm *pVm = (ph7_vm *)pUserData; + SyString *pFile; + /* Peek the top entry */ + pFile = (SyString *)SySetPeek(&pVm->aFiles); + if( pFile == 0 ){ + /* Expand the magic word: ":MEMORY:" */ + ph7_value_string(pVal,":MEMORY:",(int)sizeof(":MEMORY:")-1); + }else{ + if( pFile->nByte > 0 ){ + const char *zDir; + int nLen; + zDir = PH7_ExtractDirName(pFile->zString,(int)pFile->nByte,&nLen); + ph7_value_string(pVal,zDir,nLen); + }else{ + /* Expand '.' as the current directory*/ + ph7_value_string(pVal,".",(int)sizeof(char)); + } + } +} +/* + * PHP_SHLIB_SUFFIX + * Expand shared library suffix. + */ +static void PH7_PHP_SHLIB_SUFFIX_Const(ph7_value *pVal,void *pUserData) +{ +#ifdef __WINNT__ + ph7_value_string(pVal,"dll",(int)sizeof("dll")-1); +#else + ph7_value_string(pVal,"so",(int)sizeof("so")-1); +#endif + SXUNUSED(pUserData); /* cc warning */ +} +/* + * E_ERROR + * Expands 1 + */ +static void PH7_E_ERROR_Const(ph7_value *pVal,void *pUserData) +{ + ph7_value_int(pVal,1); + SXUNUSED(pUserData); +} +/* + * E_WARNING + * Expands 2 + */ +static void PH7_E_WARNING_Const(ph7_value *pVal,void *pUserData) +{ + ph7_value_int(pVal,2); + SXUNUSED(pUserData); +} +/* + * E_PARSE + * Expands 4 + */ +static void PH7_E_PARSE_Const(ph7_value *pVal,void *pUserData) +{ + ph7_value_int(pVal,4); + SXUNUSED(pUserData); +} +/* + * E_NOTICE + * Expands 8 + */ +static void PH7_E_NOTICE_Const(ph7_value *pVal,void *pUserData) +{ + ph7_value_int(pVal,8); + SXUNUSED(pUserData); +} +/* + * E_CORE_ERROR + * Expands 16 + */ +static void PH7_E_CORE_ERROR_Const(ph7_value *pVal,void *pUserData) +{ + ph7_value_int(pVal,16); + SXUNUSED(pUserData); +} +/* + * E_CORE_WARNING + * Expands 32 + */ +static void PH7_E_CORE_WARNING_Const(ph7_value *pVal,void *pUserData) +{ + ph7_value_int(pVal,32); + SXUNUSED(pUserData); +} +/* + * E_COMPILE_ERROR + * Expands 64 + */ +static void PH7_E_COMPILE_ERROR_Const(ph7_value *pVal,void *pUserData) +{ + ph7_value_int(pVal,64); + SXUNUSED(pUserData); +} +/* + * E_COMPILE_WARNING + * Expands 128 + */ +static void PH7_E_COMPILE_WARNING_Const(ph7_value *pVal,void *pUserData) +{ + ph7_value_int(pVal,128); + SXUNUSED(pUserData); +} +/* + * E_USER_ERROR + * Expands 256 + */ +static void PH7_E_USER_ERROR_Const(ph7_value *pVal,void *pUserData) +{ + ph7_value_int(pVal,256); + SXUNUSED(pUserData); +} +/* + * E_USER_WARNING + * Expands 512 + */ +static void PH7_E_USER_WARNING_Const(ph7_value *pVal,void *pUserData) +{ + ph7_value_int(pVal,512); + SXUNUSED(pUserData); +} +/* + * E_USER_NOTICE + * Expands 1024 + */ +static void PH7_E_USER_NOTICE_Const(ph7_value *pVal,void *pUserData) +{ + ph7_value_int(pVal,1024); + SXUNUSED(pUserData); +} +/* + * E_STRICT + * Expands 2048 + */ +static void PH7_E_STRICT_Const(ph7_value *pVal,void *pUserData) +{ + ph7_value_int(pVal,2048); + SXUNUSED(pUserData); +} +/* + * E_RECOVERABLE_ERROR + * Expands 4096 + */ +static void PH7_E_RECOVERABLE_ERROR_Const(ph7_value *pVal,void *pUserData) +{ + ph7_value_int(pVal,4096); + SXUNUSED(pUserData); +} +/* + * E_DEPRECATED + * Expands 8192 + */ +static void PH7_E_DEPRECATED_Const(ph7_value *pVal,void *pUserData) +{ + ph7_value_int(pVal,8192); + SXUNUSED(pUserData); +} +/* + * E_USER_DEPRECATED + * Expands 16384. + */ +static void PH7_E_USER_DEPRECATED_Const(ph7_value *pVal,void *pUserData) +{ + ph7_value_int(pVal,16384); + SXUNUSED(pUserData); +} +/* + * E_ALL + * Expands 32767 + */ +static void PH7_E_ALL_Const(ph7_value *pVal,void *pUserData) +{ + ph7_value_int(pVal,32767); + SXUNUSED(pUserData); +} +/* + * CASE_LOWER + * Expands 0. + */ +static void PH7_CASE_LOWER_Const(ph7_value *pVal,void *pUserData) +{ + ph7_value_int(pVal,0); + SXUNUSED(pUserData); +} +/* + * CASE_UPPER + * Expands 1. + */ +static void PH7_CASE_UPPER_Const(ph7_value *pVal,void *pUserData) +{ + ph7_value_int(pVal,1); + SXUNUSED(pUserData); +} +/* + * STR_PAD_LEFT + * Expands 0. + */ +static void PH7_STR_PAD_LEFT_Const(ph7_value *pVal,void *pUserData) +{ + ph7_value_int(pVal,0); + SXUNUSED(pUserData); +} +/* + * STR_PAD_RIGHT + * Expands 1. + */ +static void PH7_STR_PAD_RIGHT_Const(ph7_value *pVal,void *pUserData) +{ + ph7_value_int(pVal,1); + SXUNUSED(pUserData); +} +/* + * STR_PAD_BOTH + * Expands 2. + */ +static void PH7_STR_PAD_BOTH_Const(ph7_value *pVal,void *pUserData) +{ + ph7_value_int(pVal,2); + SXUNUSED(pUserData); +} +/* + * COUNT_NORMAL + * Expands 0 + */ +static void PH7_COUNT_NORMAL_Const(ph7_value *pVal,void *pUserData) +{ + ph7_value_int(pVal,0); + SXUNUSED(pUserData); +} +/* + * COUNT_RECURSIVE + * Expands 1. + */ +static void PH7_COUNT_RECURSIVE_Const(ph7_value *pVal,void *pUserData) +{ + ph7_value_int(pVal,1); + SXUNUSED(pUserData); +} +/* + * SORT_ASC + * Expands 1. + */ +static void PH7_SORT_ASC_Const(ph7_value *pVal,void *pUserData) +{ + ph7_value_int(pVal,1); + SXUNUSED(pUserData); +} +/* + * SORT_DESC + * Expands 2. + */ +static void PH7_SORT_DESC_Const(ph7_value *pVal,void *pUserData) +{ + ph7_value_int(pVal,2); + SXUNUSED(pUserData); +} +/* + * SORT_REGULAR + * Expands 3. + */ +static void PH7_SORT_REG_Const(ph7_value *pVal,void *pUserData) +{ + ph7_value_int(pVal,3); + SXUNUSED(pUserData); +} +/* + * SORT_NUMERIC + * Expands 4. + */ +static void PH7_SORT_NUMERIC_Const(ph7_value *pVal,void *pUserData) +{ + ph7_value_int(pVal,4); + SXUNUSED(pUserData); +} +/* + * SORT_STRING + * Expands 5. + */ +static void PH7_SORT_STRING_Const(ph7_value *pVal,void *pUserData) +{ + ph7_value_int(pVal,5); + SXUNUSED(pUserData); +} +/* + * PHP_ROUND_HALF_UP + * Expands 1. + */ +static void PH7_PHP_ROUND_HALF_UP_Const(ph7_value *pVal,void *pUserData) +{ + ph7_value_int(pVal,1); + SXUNUSED(pUserData); +} +/* + * SPHP_ROUND_HALF_DOWN + * Expands 2. + */ +static void PH7_PHP_ROUND_HALF_DOWN_Const(ph7_value *pVal,void *pUserData) +{ + ph7_value_int(pVal,2); + SXUNUSED(pUserData); +} +/* + * PHP_ROUND_HALF_EVEN + * Expands 3. + */ +static void PH7_PHP_ROUND_HALF_EVEN_Const(ph7_value *pVal,void *pUserData) +{ + ph7_value_int(pVal,3); + SXUNUSED(pUserData); +} +/* + * PHP_ROUND_HALF_ODD + * Expands 4. + */ +static void PH7_PHP_ROUND_HALF_ODD_Const(ph7_value *pVal,void *pUserData) +{ + ph7_value_int(pVal,4); + SXUNUSED(pUserData); +} +/* + * DEBUG_BACKTRACE_PROVIDE_OBJECT + * Expand 0x01 + * NOTE: + * The expanded value must be a power of two. + */ +static void PH7_DBPO_Const(ph7_value *pVal,void *pUserData) +{ + ph7_value_int(pVal,0x01); /* MUST BE A POWER OF TWO */ + SXUNUSED(pUserData); +} +/* + * DEBUG_BACKTRACE_IGNORE_ARGS + * Expand 0x02 + * NOTE: + * The expanded value must be a power of two. + */ +static void PH7_DBIA_Const(ph7_value *pVal,void *pUserData) +{ + ph7_value_int(pVal,0x02); /* MUST BE A POWER OF TWO */ + SXUNUSED(pUserData); +} +#ifdef PH7_ENABLE_MATH_FUNC +/* + * M_PI + * Expand the value of pi. + */ +static void PH7_M_PI_Const(ph7_value *pVal,void *pUserData) +{ + SXUNUSED(pUserData); /* cc warning */ + ph7_value_double(pVal,PH7_PI); +} +/* + * M_E + * Expand 2.7182818284590452354 + */ +static void PH7_M_E_Const(ph7_value *pVal,void *pUserData) +{ + SXUNUSED(pUserData); /* cc warning */ + ph7_value_double(pVal,2.7182818284590452354); +} +/* + * M_LOG2E + * Expand 2.7182818284590452354 + */ +static void PH7_M_LOG2E_Const(ph7_value *pVal,void *pUserData) +{ + SXUNUSED(pUserData); /* cc warning */ + ph7_value_double(pVal,1.4426950408889634074); +} +/* + * M_LOG10E + * Expand 0.4342944819032518276 + */ +static void PH7_M_LOG10E_Const(ph7_value *pVal,void *pUserData) +{ + SXUNUSED(pUserData); /* cc warning */ + ph7_value_double(pVal,0.4342944819032518276); +} +/* + * M_LN2 + * Expand 0.69314718055994530942 + */ +static void PH7_M_LN2_Const(ph7_value *pVal,void *pUserData) +{ + SXUNUSED(pUserData); /* cc warning */ + ph7_value_double(pVal,0.69314718055994530942); +} +/* + * M_LN10 + * Expand 2.30258509299404568402 + */ +static void PH7_M_LN10_Const(ph7_value *pVal,void *pUserData) +{ + SXUNUSED(pUserData); /* cc warning */ + ph7_value_double(pVal,2.30258509299404568402); +} +/* + * M_PI_2 + * Expand 1.57079632679489661923 + */ +static void PH7_M_PI_2_Const(ph7_value *pVal,void *pUserData) +{ + SXUNUSED(pUserData); /* cc warning */ + ph7_value_double(pVal,1.57079632679489661923); +} +/* + * M_PI_4 + * Expand 0.78539816339744830962 + */ +static void PH7_M_PI_4_Const(ph7_value *pVal,void *pUserData) +{ + SXUNUSED(pUserData); /* cc warning */ + ph7_value_double(pVal,0.78539816339744830962); +} +/* + * M_1_PI + * Expand 0.31830988618379067154 + */ +static void PH7_M_1_PI_Const(ph7_value *pVal,void *pUserData) +{ + SXUNUSED(pUserData); /* cc warning */ + ph7_value_double(pVal,0.31830988618379067154); +} +/* + * M_2_PI + * Expand 0.63661977236758134308 + */ +static void PH7_M_2_PI_Const(ph7_value *pVal,void *pUserData) +{ + SXUNUSED(pUserData); /* cc warning */ + ph7_value_double(pVal,0.63661977236758134308); +} +/* + * M_SQRTPI + * Expand 1.77245385090551602729 + */ +static void PH7_M_SQRTPI_Const(ph7_value *pVal,void *pUserData) +{ + SXUNUSED(pUserData); /* cc warning */ + ph7_value_double(pVal,1.77245385090551602729); +} +/* + * M_2_SQRTPI + * Expand 1.12837916709551257390 + */ +static void PH7_M_2_SQRTPI_Const(ph7_value *pVal,void *pUserData) +{ + SXUNUSED(pUserData); /* cc warning */ + ph7_value_double(pVal,1.12837916709551257390); +} +/* + * M_SQRT2 + * Expand 1.41421356237309504880 + */ +static void PH7_M_SQRT2_Const(ph7_value *pVal,void *pUserData) +{ + SXUNUSED(pUserData); /* cc warning */ + ph7_value_double(pVal,1.41421356237309504880); +} +/* + * M_SQRT3 + * Expand 1.73205080756887729352 + */ +static void PH7_M_SQRT3_Const(ph7_value *pVal,void *pUserData) +{ + SXUNUSED(pUserData); /* cc warning */ + ph7_value_double(pVal,1.73205080756887729352); +} +/* + * M_SQRT1_2 + * Expand 0.70710678118654752440 + */ +static void PH7_M_SQRT1_2_Const(ph7_value *pVal,void *pUserData) +{ + SXUNUSED(pUserData); /* cc warning */ + ph7_value_double(pVal,0.70710678118654752440); +} +/* + * M_LNPI + * Expand 1.14472988584940017414 + */ +static void PH7_M_LNPI_Const(ph7_value *pVal,void *pUserData) +{ + SXUNUSED(pUserData); /* cc warning */ + ph7_value_double(pVal,1.14472988584940017414); +} +/* + * M_EULER + * Expand 0.57721566490153286061 + */ +static void PH7_M_EULER_Const(ph7_value *pVal,void *pUserData) +{ + SXUNUSED(pUserData); /* cc warning */ + ph7_value_double(pVal,0.57721566490153286061); +} +#endif /* PH7_DISABLE_BUILTIN_MATH */ +/* + * DATE_ATOM + * Expand Atom (example: 2005-08-15T15:52:01+00:00) + */ +static void PH7_DATE_ATOM_Const(ph7_value *pVal,void *pUserData) +{ + SXUNUSED(pUserData); /* cc warning */ + ph7_value_string(pVal,"Y-m-d\\TH:i:sP",-1/*Compute length automatically*/); +} +/* + * DATE_COOKIE + * HTTP Cookies (example: Monday, 15-Aug-05 15:52:01 UTC) + */ +static void PH7_DATE_COOKIE_Const(ph7_value *pVal,void *pUserData) +{ + SXUNUSED(pUserData); /* cc warning */ + ph7_value_string(pVal,"l, d-M-y H:i:s T",-1/*Compute length automatically*/); +} +/* + * DATE_ISO8601 + * ISO-8601 (example: 2005-08-15T15:52:01+0000) + */ +static void PH7_DATE_ISO8601_Const(ph7_value *pVal,void *pUserData) +{ + SXUNUSED(pUserData); /* cc warning */ + ph7_value_string(pVal,"Y-m-d\\TH:i:sO",-1/*Compute length automatically*/); +} +/* + * DATE_RFC822 + * RFC 822 (example: Mon, 15 Aug 05 15:52:01 +0000) + */ +static void PH7_DATE_RFC822_Const(ph7_value *pVal,void *pUserData) +{ + SXUNUSED(pUserData); /* cc warning */ + ph7_value_string(pVal,"D, d M y H:i:s O",-1/*Compute length automatically*/); +} +/* + * DATE_RFC850 + * RFC 850 (example: Monday, 15-Aug-05 15:52:01 UTC) + */ +static void PH7_DATE_RFC850_Const(ph7_value *pVal,void *pUserData) +{ + SXUNUSED(pUserData); /* cc warning */ + ph7_value_string(pVal,"l, d-M-y H:i:s T",-1/*Compute length automatically*/); +} +/* + * DATE_RFC1036 + * RFC 1123 (example: Mon, 15 Aug 2005 15:52:01 +0000) + */ +static void PH7_DATE_RFC1036_Const(ph7_value *pVal,void *pUserData) +{ + SXUNUSED(pUserData); /* cc warning */ + ph7_value_string(pVal,"D, d M y H:i:s O",-1/*Compute length automatically*/); +} +/* + * DATE_RFC1123 + * RFC 1123 (example: Mon, 15 Aug 2005 15:52:01 +0000) + */ +static void PH7_DATE_RFC1123_Const(ph7_value *pVal,void *pUserData) +{ + SXUNUSED(pUserData); /* cc warning */ + ph7_value_string(pVal,"D, d M Y H:i:s O",-1/*Compute length automatically*/); +} +/* + * DATE_RFC2822 + * RFC 2822 (Mon, 15 Aug 2005 15:52:01 +0000) + */ +static void PH7_DATE_RFC2822_Const(ph7_value *pVal,void *pUserData) +{ + SXUNUSED(pUserData); /* cc warning */ + ph7_value_string(pVal,"D, d M Y H:i:s O",-1/*Compute length automatically*/); +} +/* + * DATE_RSS + * RSS (Mon, 15 Aug 2005 15:52:01 +0000) + */ +static void PH7_DATE_RSS_Const(ph7_value *pVal,void *pUserData) +{ + SXUNUSED(pUserData); /* cc warning */ + ph7_value_string(pVal,"D, d M Y H:i:s O",-1/*Compute length automatically*/); +} +/* + * DATE_W3C + * World Wide Web Consortium (example: 2005-08-15T15:52:01+00:00) + */ +static void PH7_DATE_W3C_Const(ph7_value *pVal,void *pUserData) +{ + SXUNUSED(pUserData); /* cc warning */ + ph7_value_string(pVal,"Y-m-d\\TH:i:sP",-1/*Compute length automatically*/); +} +/* + * ENT_COMPAT + * Expand 0x01 (Must be a power of two) + */ +static void PH7_ENT_COMPAT_Const(ph7_value *pVal,void *pUserData) +{ + SXUNUSED(pUserData); /* cc warning */ + ph7_value_int(pVal,0x01); +} +/* + * ENT_QUOTES + * Expand 0x02 (Must be a power of two) + */ +static void PH7_ENT_QUOTES_Const(ph7_value *pVal,void *pUserData) +{ + SXUNUSED(pUserData); /* cc warning */ + ph7_value_int(pVal,0x02); +} +/* + * ENT_NOQUOTES + * Expand 0x04 (Must be a power of two) + */ +static void PH7_ENT_NOQUOTES_Const(ph7_value *pVal,void *pUserData) +{ + SXUNUSED(pUserData); /* cc warning */ + ph7_value_int(pVal,0x04); +} +/* + * ENT_IGNORE + * Expand 0x08 (Must be a power of two) + */ +static void PH7_ENT_IGNORE_Const(ph7_value *pVal,void *pUserData) +{ + SXUNUSED(pUserData); /* cc warning */ + ph7_value_int(pVal,0x08); +} +/* + * ENT_SUBSTITUTE + * Expand 0x10 (Must be a power of two) + */ +static void PH7_ENT_SUBSTITUTE_Const(ph7_value *pVal,void *pUserData) +{ + SXUNUSED(pUserData); /* cc warning */ + ph7_value_int(pVal,0x10); +} +/* + * ENT_DISALLOWED + * Expand 0x20 (Must be a power of two) + */ +static void PH7_ENT_DISALLOWED_Const(ph7_value *pVal,void *pUserData) +{ + SXUNUSED(pUserData); /* cc warning */ + ph7_value_int(pVal,0x20); +} +/* + * ENT_HTML401 + * Expand 0x40 (Must be a power of two) + */ +static void PH7_ENT_HTML401_Const(ph7_value *pVal,void *pUserData) +{ + SXUNUSED(pUserData); /* cc warning */ + ph7_value_int(pVal,0x40); +} +/* + * ENT_XML1 + * Expand 0x80 (Must be a power of two) + */ +static void PH7_ENT_XML1_Const(ph7_value *pVal,void *pUserData) +{ + SXUNUSED(pUserData); /* cc warning */ + ph7_value_int(pVal,0x80); +} +/* + * ENT_XHTML + * Expand 0x100 (Must be a power of two) + */ +static void PH7_ENT_XHTML_Const(ph7_value *pVal,void *pUserData) +{ + SXUNUSED(pUserData); /* cc warning */ + ph7_value_int(pVal,0x100); +} +/* + * ENT_HTML5 + * Expand 0x200 (Must be a power of two) + */ +static void PH7_ENT_HTML5_Const(ph7_value *pVal,void *pUserData) +{ + SXUNUSED(pUserData); /* cc warning */ + ph7_value_int(pVal,0x200); +} +/* + * ISO-8859-1 + * ISO_8859_1 + * Expand 1 + */ +static void PH7_ISO88591_Const(ph7_value *pVal,void *pUserData) +{ + SXUNUSED(pUserData); /* cc warning */ + ph7_value_int(pVal,1); +} +/* + * UTF-8 + * UTF8 + * Expand 2 + */ +static void PH7_UTF8_Const(ph7_value *pVal,void *pUserData) +{ + SXUNUSED(pUserData); /* cc warning */ + ph7_value_int(pVal,1); +} +/* + * HTML_ENTITIES + * Expand 1 + */ +static void PH7_HTML_ENTITIES_Const(ph7_value *pVal,void *pUserData) +{ + SXUNUSED(pUserData); /* cc warning */ + ph7_value_int(pVal,1); +} +/* + * HTML_SPECIALCHARS + * Expand 2 + */ +static void PH7_HTML_SPECIALCHARS_Const(ph7_value *pVal,void *pUserData) +{ + SXUNUSED(pUserData); /* cc warning */ + ph7_value_int(pVal,2); +} +/* + * PHP_URL_SCHEME. + * Expand 1 + */ +static void PH7_PHP_URL_SCHEME_Const(ph7_value *pVal,void *pUserData) +{ + SXUNUSED(pUserData); /* cc warning */ + ph7_value_int(pVal,1); +} +/* + * PHP_URL_HOST. + * Expand 2 + */ +static void PH7_PHP_URL_HOST_Const(ph7_value *pVal,void *pUserData) +{ + SXUNUSED(pUserData); /* cc warning */ + ph7_value_int(pVal,2); +} +/* + * PHP_URL_PORT. + * Expand 3 + */ +static void PH7_PHP_URL_PORT_Const(ph7_value *pVal,void *pUserData) +{ + SXUNUSED(pUserData); /* cc warning */ + ph7_value_int(pVal,3); +} +/* + * PHP_URL_USER. + * Expand 4 + */ +static void PH7_PHP_URL_USER_Const(ph7_value *pVal,void *pUserData) +{ + SXUNUSED(pUserData); /* cc warning */ + ph7_value_int(pVal,4); +} +/* + * PHP_URL_PASS. + * Expand 5 + */ +static void PH7_PHP_URL_PASS_Const(ph7_value *pVal,void *pUserData) +{ + SXUNUSED(pUserData); /* cc warning */ + ph7_value_int(pVal,5); +} +/* + * PHP_URL_PATH. + * Expand 6 + */ +static void PH7_PHP_URL_PATH_Const(ph7_value *pVal,void *pUserData) +{ + SXUNUSED(pUserData); /* cc warning */ + ph7_value_int(pVal,6); +} +/* + * PHP_URL_QUERY. + * Expand 7 + */ +static void PH7_PHP_URL_QUERY_Const(ph7_value *pVal,void *pUserData) +{ + SXUNUSED(pUserData); /* cc warning */ + ph7_value_int(pVal,7); +} +/* + * PHP_URL_FRAGMENT. + * Expand 8 + */ +static void PH7_PHP_URL_FRAGMENT_Const(ph7_value *pVal,void *pUserData) +{ + SXUNUSED(pUserData); /* cc warning */ + ph7_value_int(pVal,8); +} +/* + * PHP_QUERY_RFC1738 + * Expand 1 + */ +static void PH7_PHP_QUERY_RFC1738_Const(ph7_value *pVal,void *pUserData) +{ + SXUNUSED(pUserData); /* cc warning */ + ph7_value_int(pVal,1); +} +/* + * PHP_QUERY_RFC3986 + * Expand 1 + */ +static void PH7_PHP_QUERY_RFC3986_Const(ph7_value *pVal,void *pUserData) +{ + SXUNUSED(pUserData); /* cc warning */ + ph7_value_int(pVal,2); +} +/* + * FNM_NOESCAPE + * Expand 0x01 (Must be a power of two) + */ +static void PH7_FNM_NOESCAPE_Const(ph7_value *pVal,void *pUserData) +{ + SXUNUSED(pUserData); /* cc warning */ + ph7_value_int(pVal,0x01); +} +/* + * FNM_PATHNAME + * Expand 0x02 (Must be a power of two) + */ +static void PH7_FNM_PATHNAME_Const(ph7_value *pVal,void *pUserData) +{ + SXUNUSED(pUserData); /* cc warning */ + ph7_value_int(pVal,0x02); +} +/* + * FNM_PERIOD + * Expand 0x04 (Must be a power of two) + */ +static void PH7_FNM_PERIOD_Const(ph7_value *pVal,void *pUserData) +{ + SXUNUSED(pUserData); /* cc warning */ + ph7_value_int(pVal,0x04); +} +/* + * FNM_CASEFOLD + * Expand 0x08 (Must be a power of two) + */ +static void PH7_FNM_CASEFOLD_Const(ph7_value *pVal,void *pUserData) +{ + SXUNUSED(pUserData); /* cc warning */ + ph7_value_int(pVal,0x08); +} +/* + * PATHINFO_DIRNAME + * Expand 1. + */ +static void PH7_PATHINFO_DIRNAME_Const(ph7_value *pVal,void *pUserData) +{ + SXUNUSED(pUserData); /* cc warning */ + ph7_value_int(pVal,1); +} +/* + * PATHINFO_BASENAME + * Expand 2. + */ +static void PH7_PATHINFO_BASENAME_Const(ph7_value *pVal,void *pUserData) +{ + SXUNUSED(pUserData); /* cc warning */ + ph7_value_int(pVal,2); +} +/* + * PATHINFO_EXTENSION + * Expand 3. + */ +static void PH7_PATHINFO_EXTENSION_Const(ph7_value *pVal,void *pUserData) +{ + SXUNUSED(pUserData); /* cc warning */ + ph7_value_int(pVal,3); +} +/* + * PATHINFO_FILENAME + * Expand 4. + */ +static void PH7_PATHINFO_FILENAME_Const(ph7_value *pVal,void *pUserData) +{ + SXUNUSED(pUserData); /* cc warning */ + ph7_value_int(pVal,4); +} +/* + * ASSERT_ACTIVE. + * Expand the value of PH7_ASSERT_ACTIVE defined in ph7Int.h + */ +static void PH7_ASSERT_ACTIVE_Const(ph7_value *pVal,void *pUserData) +{ + SXUNUSED(pUserData); /* cc warning */ + ph7_value_int(pVal,PH7_ASSERT_DISABLE); +} +/* + * ASSERT_WARNING. + * Expand the value of PH7_ASSERT_WARNING defined in ph7Int.h + */ +static void PH7_ASSERT_WARNING_Const(ph7_value *pVal,void *pUserData) +{ + SXUNUSED(pUserData); /* cc warning */ + ph7_value_int(pVal,PH7_ASSERT_WARNING); +} +/* + * ASSERT_BAIL. + * Expand the value of PH7_ASSERT_BAIL defined in ph7Int.h + */ +static void PH7_ASSERT_BAIL_Const(ph7_value *pVal,void *pUserData) +{ + SXUNUSED(pUserData); /* cc warning */ + ph7_value_int(pVal,PH7_ASSERT_BAIL); +} +/* + * ASSERT_QUIET_EVAL. + * Expand the value of PH7_ASSERT_QUIET_EVAL defined in ph7Int.h + */ +static void PH7_ASSERT_QUIET_EVAL_Const(ph7_value *pVal,void *pUserData) +{ + SXUNUSED(pUserData); /* cc warning */ + ph7_value_int(pVal,PH7_ASSERT_QUIET_EVAL); +} +/* + * ASSERT_CALLBACK. + * Expand the value of PH7_ASSERT_CALLBACK defined in ph7Int.h + */ +static void PH7_ASSERT_CALLBACK_Const(ph7_value *pVal,void *pUserData) +{ + SXUNUSED(pUserData); /* cc warning */ + ph7_value_int(pVal,PH7_ASSERT_CALLBACK); +} +/* + * SEEK_SET. + * Expand 0 + */ +static void PH7_SEEK_SET_Const(ph7_value *pVal,void *pUserData) +{ + SXUNUSED(pUserData); /* cc warning */ + ph7_value_int(pVal,0); +} +/* + * SEEK_CUR. + * Expand 1 + */ +static void PH7_SEEK_CUR_Const(ph7_value *pVal,void *pUserData) +{ + SXUNUSED(pUserData); /* cc warning */ + ph7_value_int(pVal,1); +} +/* + * SEEK_END. + * Expand 2 + */ +static void PH7_SEEK_END_Const(ph7_value *pVal,void *pUserData) +{ + SXUNUSED(pUserData); /* cc warning */ + ph7_value_int(pVal,2); +} +/* + * LOCK_SH. + * Expand 2 + */ +static void PH7_LOCK_SH_Const(ph7_value *pVal,void *pUserData) +{ + SXUNUSED(pUserData); /* cc warning */ + ph7_value_int(pVal,1); +} +/* + * LOCK_NB. + * Expand 5 + */ +static void PH7_LOCK_NB_Const(ph7_value *pVal,void *pUserData) +{ + SXUNUSED(pUserData); /* cc warning */ + ph7_value_int(pVal,5); +} +/* + * LOCK_EX. + * Expand 0x01 (MUST BE A POWER OF TWO) + */ +static void PH7_LOCK_EX_Const(ph7_value *pVal,void *pUserData) +{ + SXUNUSED(pUserData); /* cc warning */ + ph7_value_int(pVal,0x01); +} +/* + * LOCK_UN. + * Expand 0 + */ +static void PH7_LOCK_UN_Const(ph7_value *pVal,void *pUserData) +{ + SXUNUSED(pUserData); /* cc warning */ + ph7_value_int(pVal,0); +} +/* + * FILE_USE_INCLUDE_PATH + * Expand 0x01 (Must be a power of two) + */ +static void PH7_FILE_USE_INCLUDE_PATH_Const(ph7_value *pVal,void *pUserData) +{ + SXUNUSED(pUserData); /* cc warning */ + ph7_value_int(pVal,0x1); +} +/* + * FILE_IGNORE_NEW_LINES + * Expand 0x02 (Must be a power of two) + */ +static void PH7_FILE_IGNORE_NEW_LINES_Const(ph7_value *pVal,void *pUserData) +{ + SXUNUSED(pUserData); /* cc warning */ + ph7_value_int(pVal,0x2); +} +/* + * FILE_SKIP_EMPTY_LINES + * Expand 0x04 (Must be a power of two) + */ +static void PH7_FILE_SKIP_EMPTY_LINES_Const(ph7_value *pVal,void *pUserData) +{ + SXUNUSED(pUserData); /* cc warning */ + ph7_value_int(pVal,0x4); +} +/* + * FILE_APPEND + * Expand 0x08 (Must be a power of two) + */ +static void PH7_FILE_APPEND_Const(ph7_value *pVal,void *pUserData) +{ + SXUNUSED(pUserData); /* cc warning */ + ph7_value_int(pVal,0x08); +} +/* + * SCANDIR_SORT_ASCENDING + * Expand 0 + */ +static void PH7_SCANDIR_SORT_ASCENDING_Const(ph7_value *pVal,void *pUserData) +{ + SXUNUSED(pUserData); /* cc warning */ + ph7_value_int(pVal,0); +} +/* + * SCANDIR_SORT_DESCENDING + * Expand 1 + */ +static void PH7_SCANDIR_SORT_DESCENDING_Const(ph7_value *pVal,void *pUserData) +{ + SXUNUSED(pUserData); /* cc warning */ + ph7_value_int(pVal,1); +} +/* + * SCANDIR_SORT_NONE + * Expand 2 + */ +static void PH7_SCANDIR_SORT_NONE_Const(ph7_value *pVal,void *pUserData) +{ + SXUNUSED(pUserData); /* cc warning */ + ph7_value_int(pVal,2); +} +/* + * GLOB_MARK + * Expand 0x01 (must be a power of two) + */ +static void PH7_GLOB_MARK_Const(ph7_value *pVal,void *pUserData) +{ + SXUNUSED(pUserData); /* cc warning */ + ph7_value_int(pVal,0x01); +} +/* + * GLOB_NOSORT + * Expand 0x02 (must be a power of two) + */ +static void PH7_GLOB_NOSORT_Const(ph7_value *pVal,void *pUserData) +{ + SXUNUSED(pUserData); /* cc warning */ + ph7_value_int(pVal,0x02); +} +/* + * GLOB_NOCHECK + * Expand 0x04 (must be a power of two) + */ +static void PH7_GLOB_NOCHECK_Const(ph7_value *pVal,void *pUserData) +{ + SXUNUSED(pUserData); /* cc warning */ + ph7_value_int(pVal,0x04); +} +/* + * GLOB_NOESCAPE + * Expand 0x08 (must be a power of two) + */ +static void PH7_GLOB_NOESCAPE_Const(ph7_value *pVal,void *pUserData) +{ + SXUNUSED(pUserData); /* cc warning */ + ph7_value_int(pVal,0x08); +} +/* + * GLOB_BRACE + * Expand 0x10 (must be a power of two) + */ +static void PH7_GLOB_BRACE_Const(ph7_value *pVal,void *pUserData) +{ + SXUNUSED(pUserData); /* cc warning */ + ph7_value_int(pVal,0x10); +} +/* + * GLOB_ONLYDIR + * Expand 0x20 (must be a power of two) + */ +static void PH7_GLOB_ONLYDIR_Const(ph7_value *pVal,void *pUserData) +{ + SXUNUSED(pUserData); /* cc warning */ + ph7_value_int(pVal,0x20); +} +/* + * GLOB_ERR + * Expand 0x40 (must be a power of two) + */ +static void PH7_GLOB_ERR_Const(ph7_value *pVal,void *pUserData) +{ + SXUNUSED(pUserData); /* cc warning */ + ph7_value_int(pVal,0x40); +} +/* + * STDIN + * Expand the STDIN handle as a resource. + */ +static void PH7_STDIN_Const(ph7_value *pVal,void *pUserData) +{ + ph7_vm *pVm = (ph7_vm *)pUserData; + void *pResource; + pResource = PH7_ExportStdin(pVm); + ph7_value_resource(pVal,pResource); +} +/* + * STDOUT + * Expand the STDOUT handle as a resource. + */ +static void PH7_STDOUT_Const(ph7_value *pVal,void *pUserData) +{ + ph7_vm *pVm = (ph7_vm *)pUserData; + void *pResource; + pResource = PH7_ExportStdout(pVm); + ph7_value_resource(pVal,pResource); +} +/* + * STDERR + * Expand the STDERR handle as a resource. + */ +static void PH7_STDERR_Const(ph7_value *pVal,void *pUserData) +{ + ph7_vm *pVm = (ph7_vm *)pUserData; + void *pResource; + pResource = PH7_ExportStderr(pVm); + ph7_value_resource(pVal,pResource); +} +/* + * INI_SCANNER_NORMAL + * Expand 1 + */ +static void PH7_INI_SCANNER_NORMAL_Const(ph7_value *pVal,void *pUserData) +{ + SXUNUSED(pUserData); /* cc warning */ + ph7_value_int(pVal,1); +} +/* + * INI_SCANNER_RAW + * Expand 2 + */ +static void PH7_INI_SCANNER_RAW_Const(ph7_value *pVal,void *pUserData) +{ + SXUNUSED(pUserData); /* cc warning */ + ph7_value_int(pVal,2); +} +/* + * EXTR_OVERWRITE + * Expand 0x01 (Must be a power of two) + */ +static void PH7_EXTR_OVERWRITE_Const(ph7_value *pVal,void *pUserData) +{ + SXUNUSED(pUserData); /* cc warning */ + ph7_value_int(pVal,0x1); +} +/* + * EXTR_SKIP + * Expand 0x02 (Must be a power of two) + */ +static void PH7_EXTR_SKIP_Const(ph7_value *pVal,void *pUserData) +{ + SXUNUSED(pUserData); /* cc warning */ + ph7_value_int(pVal,0x2); +} +/* + * EXTR_PREFIX_SAME + * Expand 0x04 (Must be a power of two) + */ +static void PH7_EXTR_PREFIX_SAME_Const(ph7_value *pVal,void *pUserData) +{ + SXUNUSED(pUserData); /* cc warning */ + ph7_value_int(pVal,0x4); +} +/* + * EXTR_PREFIX_ALL + * Expand 0x08 (Must be a power of two) + */ +static void PH7_EXTR_PREFIX_ALL_Const(ph7_value *pVal,void *pUserData) +{ + SXUNUSED(pUserData); /* cc warning */ + ph7_value_int(pVal,0x8); +} +/* + * EXTR_PREFIX_INVALID + * Expand 0x10 (Must be a power of two) + */ +static void PH7_EXTR_PREFIX_INVALID_Const(ph7_value *pVal,void *pUserData) +{ + SXUNUSED(pUserData); /* cc warning */ + ph7_value_int(pVal,0x10); +} +/* + * EXTR_IF_EXISTS + * Expand 0x20 (Must be a power of two) + */ +static void PH7_EXTR_IF_EXISTS_Const(ph7_value *pVal,void *pUserData) +{ + SXUNUSED(pUserData); /* cc warning */ + ph7_value_int(pVal,0x20); +} +/* + * EXTR_PREFIX_IF_EXISTS + * Expand 0x40 (Must be a power of two) + */ +static void PH7_EXTR_PREFIX_IF_EXISTS_Const(ph7_value *pVal,void *pUserData) +{ + SXUNUSED(pUserData); /* cc warning */ + ph7_value_int(pVal,0x40); +} +#ifndef PH7_DISABLE_BUILTIN_FUNC +/* + * XML_ERROR_NONE + * Expand the value of SXML_ERROR_NO_MEMORY defined in ph7Int.h + */ +static void PH7_XML_ERROR_NONE_Const(ph7_value *pVal,void *pUserData) +{ + SXUNUSED(pUserData); /* cc warning */ + ph7_value_int(pVal,SXML_ERROR_NO_MEMORY); +} +/* + * XML_ERROR_NO_MEMORY + * Expand the value of SXML_ERROR_NONE defined in ph7Int.h + */ +static void PH7_XML_ERROR_NO_MEMORY_Const(ph7_value *pVal,void *pUserData) +{ + SXUNUSED(pUserData); /* cc warning */ + ph7_value_int(pVal,SXML_ERROR_NO_MEMORY); +} +/* + * XML_ERROR_SYNTAX + * Expand the value of SXML_ERROR_SYNTAX defined in ph7Int.h + */ +static void PH7_XML_ERROR_SYNTAX_Const(ph7_value *pVal,void *pUserData) +{ + SXUNUSED(pUserData); /* cc warning */ + ph7_value_int(pVal,SXML_ERROR_SYNTAX); +} +/* + * XML_ERROR_NO_ELEMENTS + * Expand the value of SXML_ERROR_NO_ELEMENTS defined in ph7Int.h + */ +static void PH7_XML_ERROR_NO_ELEMENTS_Const(ph7_value *pVal,void *pUserData) +{ + SXUNUSED(pUserData); /* cc warning */ + ph7_value_int(pVal,SXML_ERROR_NO_ELEMENTS); +} +/* + * XML_ERROR_INVALID_TOKEN + * Expand the value of SXML_ERROR_INVALID_TOKEN defined in ph7Int.h + */ +static void PH7_XML_ERROR_INVALID_TOKEN_Const(ph7_value *pVal,void *pUserData) +{ + SXUNUSED(pUserData); /* cc warning */ + ph7_value_int(pVal,SXML_ERROR_INVALID_TOKEN); +} +/* + * XML_ERROR_UNCLOSED_TOKEN + * Expand the value of SXML_ERROR_UNCLOSED_TOKEN defined in ph7Int.h + */ +static void PH7_XML_ERROR_UNCLOSED_TOKEN_Const(ph7_value *pVal,void *pUserData) +{ + SXUNUSED(pUserData); /* cc warning */ + ph7_value_int(pVal,SXML_ERROR_UNCLOSED_TOKEN); +} +/* + * XML_ERROR_PARTIAL_CHAR + * Expand the value of SXML_ERROR_PARTIAL_CHAR defined in ph7Int.h + */ +static void PH7_XML_ERROR_PARTIAL_CHAR_Const(ph7_value *pVal,void *pUserData) +{ + SXUNUSED(pUserData); /* cc warning */ + ph7_value_int(pVal,SXML_ERROR_PARTIAL_CHAR); +} +/* + * XML_ERROR_TAG_MISMATCH + * Expand the value of SXML_ERROR_TAG_MISMATCH defined in ph7Int.h + */ +static void PH7_XML_ERROR_TAG_MISMATCH_Const(ph7_value *pVal,void *pUserData) +{ + SXUNUSED(pUserData); /* cc warning */ + ph7_value_int(pVal,SXML_ERROR_TAG_MISMATCH); +} +/* + * XML_ERROR_DUPLICATE_ATTRIBUTE + * Expand the value of SXML_ERROR_DUPLICATE_ATTRIBUTE defined in ph7Int.h + */ +static void PH7_XML_ERROR_DUPLICATE_ATTRIBUTE_Const(ph7_value *pVal,void *pUserData) +{ + SXUNUSED(pUserData); /* cc warning */ + ph7_value_int(pVal,SXML_ERROR_DUPLICATE_ATTRIBUTE); +} +/* + * XML_ERROR_JUNK_AFTER_DOC_ELEMENT + * Expand the value of SXML_ERROR_JUNK_AFTER_DOC_ELEMENT defined in ph7Int.h + */ +static void PH7_XML_ERROR_JUNK_AFTER_DOC_ELEMENT_Const(ph7_value *pVal,void *pUserData) +{ + SXUNUSED(pUserData); /* cc warning */ + ph7_value_int(pVal,SXML_ERROR_JUNK_AFTER_DOC_ELEMENT); +} +/* + * XML_ERROR_PARAM_ENTITY_REF + * Expand the value of SXML_ERROR_PARAM_ENTITY_REF defined in ph7Int.h + */ +static void PH7_XML_ERROR_PARAM_ENTITY_REF_Const(ph7_value *pVal,void *pUserData) +{ + SXUNUSED(pUserData); /* cc warning */ + ph7_value_int(pVal,SXML_ERROR_PARAM_ENTITY_REF); +} +/* + * XML_ERROR_UNDEFINED_ENTITY + * Expand the value of SXML_ERROR_UNDEFINED_ENTITY defined in ph7Int.h + */ +static void PH7_XML_ERROR_UNDEFINED_ENTITY_Const(ph7_value *pVal,void *pUserData) +{ + SXUNUSED(pUserData); /* cc warning */ + ph7_value_int(pVal,SXML_ERROR_UNDEFINED_ENTITY); +} +/* + * XML_ERROR_RECURSIVE_ENTITY_REF + * Expand the value of SXML_ERROR_RECURSIVE_ENTITY_REF defined in ph7Int.h + */ +static void PH7_XML_ERROR_RECURSIVE_ENTITY_REF_Const(ph7_value *pVal,void *pUserData) +{ + SXUNUSED(pUserData); /* cc warning */ + ph7_value_int(pVal,SXML_ERROR_RECURSIVE_ENTITY_REF); +} +/* + * XML_ERROR_ASYNC_ENTITY + * Expand the value of SXML_ERROR_ASYNC_ENTITY defined in ph7Int.h + */ +static void PH7_XML_ERROR_ASYNC_ENTITY_Const(ph7_value *pVal,void *pUserData) +{ + SXUNUSED(pUserData); /* cc warning */ + ph7_value_int(pVal,SXML_ERROR_ASYNC_ENTITY); +} +/* + * XML_ERROR_BAD_CHAR_REF + * Expand the value of SXML_ERROR_BAD_CHAR_REF defined in ph7Int.h + */ +static void PH7_XML_ERROR_BAD_CHAR_REF_Const(ph7_value *pVal,void *pUserData) +{ + SXUNUSED(pUserData); /* cc warning */ + ph7_value_int(pVal,SXML_ERROR_BAD_CHAR_REF); +} +/* + * XML_ERROR_BINARY_ENTITY_REF + * Expand the value of SXML_ERROR_BINARY_ENTITY_REF defined in ph7Int.h + */ +static void PH7_XML_ERROR_BINARY_ENTITY_REF_Const(ph7_value *pVal,void *pUserData) +{ + SXUNUSED(pUserData); /* cc warning */ + ph7_value_int(pVal,SXML_ERROR_BINARY_ENTITY_REF); +} +/* + * XML_ERROR_ATTRIBUTE_EXTERNAL_ENTITY_REF + * Expand the value of SXML_ERROR_ATTRIBUTE_EXTERNAL_ENTITY_REF defined in ph7Int.h + */ +static void PH7_XML_ERROR_ATTRIBUTE_EXTERNAL_ENTITY_REF_Const(ph7_value *pVal,void *pUserData) +{ + SXUNUSED(pUserData); /* cc warning */ + ph7_value_int(pVal,SXML_ERROR_ATTRIBUTE_EXTERNAL_ENTITY_REF); +} +/* + * XML_ERROR_MISPLACED_XML_PI + * Expand the value of SXML_ERROR_MISPLACED_XML_PI defined in ph7Int.h + */ +static void PH7_XML_ERROR_MISPLACED_XML_PI_Const(ph7_value *pVal,void *pUserData) +{ + SXUNUSED(pUserData); /* cc warning */ + ph7_value_int(pVal,SXML_ERROR_MISPLACED_XML_PI); +} +/* + * XML_ERROR_UNKNOWN_ENCODING + * Expand the value of SXML_ERROR_UNKNOWN_ENCODING defined in ph7Int.h + */ +static void PH7_XML_ERROR_UNKNOWN_ENCODING_Const(ph7_value *pVal,void *pUserData) +{ + SXUNUSED(pUserData); /* cc warning */ + ph7_value_int(pVal,SXML_ERROR_UNKNOWN_ENCODING); +} +/* + * XML_ERROR_INCORRECT_ENCODING + * Expand the value of SXML_ERROR_INCORRECT_ENCODING defined in ph7Int.h + */ +static void PH7_XML_ERROR_INCORRECT_ENCODING_Const(ph7_value *pVal,void *pUserData) +{ + SXUNUSED(pUserData); /* cc warning */ + ph7_value_int(pVal,SXML_ERROR_INCORRECT_ENCODING); +} +/* + * XML_ERROR_UNCLOSED_CDATA_SECTION + * Expand the value of SXML_ERROR_UNCLOSED_CDATA_SECTION defined in ph7Int.h + */ +static void PH7_XML_ERROR_UNCLOSED_CDATA_SECTION_Const(ph7_value *pVal,void *pUserData) +{ + SXUNUSED(pUserData); /* cc warning */ + ph7_value_int(pVal,SXML_ERROR_UNCLOSED_CDATA_SECTION); +} +/* + * XML_ERROR_EXTERNAL_ENTITY_HANDLING + * Expand the value of SXML_ERROR_EXTERNAL_ENTITY_HANDLING defined in ph7Int.h + */ +static void PH7_XML_ERROR_EXTERNAL_ENTITY_HANDLING_Const(ph7_value *pVal,void *pUserData) +{ + SXUNUSED(pUserData); /* cc warning */ + ph7_value_int(pVal,SXML_ERROR_EXTERNAL_ENTITY_HANDLING); +} +/* + * XML_OPTION_CASE_FOLDING + * Expand the value of SXML_OPTION_CASE_FOLDING defined in ph7Int.h. + */ +static void PH7_XML_OPTION_CASE_FOLDING_Const(ph7_value *pVal,void *pUserData) +{ + SXUNUSED(pUserData); /* cc warning */ + ph7_value_int(pVal,SXML_OPTION_CASE_FOLDING); +} +/* + * XML_OPTION_TARGET_ENCODING + * Expand the value of SXML_OPTION_TARGET_ENCODING defined in ph7Int.h. + */ +static void PH7_XML_OPTION_TARGET_ENCODING_Const(ph7_value *pVal,void *pUserData) +{ + SXUNUSED(pUserData); /* cc warning */ + ph7_value_int(pVal,SXML_OPTION_TARGET_ENCODING); +} +/* + * XML_OPTION_SKIP_TAGSTART + * Expand the value of SXML_OPTION_SKIP_TAGSTART defined in ph7Int.h. + */ +static void PH7_XML_OPTION_SKIP_TAGSTART_Const(ph7_value *pVal,void *pUserData) +{ + SXUNUSED(pUserData); /* cc warning */ + ph7_value_int(pVal,SXML_OPTION_SKIP_TAGSTART); +} +/* + * XML_OPTION_SKIP_WHITE + * Expand the value of SXML_OPTION_SKIP_TAGSTART defined in ph7Int.h. + */ +static void PH7_XML_OPTION_SKIP_WHITE_Const(ph7_value *pVal,void *pUserData) +{ + SXUNUSED(pUserData); /* cc warning */ + ph7_value_int(pVal,SXML_OPTION_SKIP_WHITE); +} +/* + * XML_SAX_IMPL. + * Expand the name of the underlying XML engine. + */ +static void PH7_XML_SAX_IMP_Const(ph7_value *pVal,void *pUserData) +{ + SXUNUSED(pUserData); /* cc warning */ + ph7_value_string(pVal,"Symisc XML engine",(int)sizeof("Symisc XML engine")-1); +} +#endif /* PH7_DISABLE_BUILTIN_FUNC */ +/* + * JSON_HEX_TAG. + * Expand the value of JSON_HEX_TAG defined in ph7Int.h. + */ +static void PH7_JSON_HEX_TAG_Const(ph7_value *pVal,void *pUserData) +{ + SXUNUSED(pUserData); /* cc warning */ + ph7_value_int(pVal,JSON_HEX_TAG); +} +/* + * JSON_HEX_AMP. + * Expand the value of JSON_HEX_AMP defined in ph7Int.h. + */ +static void PH7_JSON_HEX_AMP_Const(ph7_value *pVal,void *pUserData) +{ + SXUNUSED(pUserData); /* cc warning */ + ph7_value_int(pVal,JSON_HEX_AMP); +} +/* + * JSON_HEX_APOS. + * Expand the value of JSON_HEX_APOS defined in ph7Int.h. + */ +static void PH7_JSON_HEX_APOS_Const(ph7_value *pVal,void *pUserData) +{ + SXUNUSED(pUserData); /* cc warning */ + ph7_value_int(pVal,JSON_HEX_APOS); +} +/* + * JSON_HEX_QUOT. + * Expand the value of JSON_HEX_QUOT defined in ph7Int.h. + */ +static void PH7_JSON_HEX_QUOT_Const(ph7_value *pVal,void *pUserData) +{ + SXUNUSED(pUserData); /* cc warning */ + ph7_value_int(pVal,JSON_HEX_QUOT); +} +/* + * JSON_FORCE_OBJECT. + * Expand the value of JSON_FORCE_OBJECT defined in ph7Int.h. + */ +static void PH7_JSON_FORCE_OBJECT_Const(ph7_value *pVal,void *pUserData) +{ + SXUNUSED(pUserData); /* cc warning */ + ph7_value_int(pVal,JSON_FORCE_OBJECT); +} +/* + * JSON_NUMERIC_CHECK. + * Expand the value of JSON_NUMERIC_CHECK defined in ph7Int.h. + */ +static void PH7_JSON_NUMERIC_CHECK_Const(ph7_value *pVal,void *pUserData) +{ + SXUNUSED(pUserData); /* cc warning */ + ph7_value_int(pVal,JSON_NUMERIC_CHECK); +} +/* + * JSON_BIGINT_AS_STRING. + * Expand the value of JSON_BIGINT_AS_STRING defined in ph7Int.h. + */ +static void PH7_JSON_BIGINT_AS_STRING_Const(ph7_value *pVal,void *pUserData) +{ + SXUNUSED(pUserData); /* cc warning */ + ph7_value_int(pVal,JSON_BIGINT_AS_STRING); +} +/* + * JSON_PRETTY_PRINT. + * Expand the value of JSON_PRETTY_PRINT defined in ph7Int.h. + */ +static void PH7_JSON_PRETTY_PRINT_Const(ph7_value *pVal,void *pUserData) +{ + SXUNUSED(pUserData); /* cc warning */ + ph7_value_int(pVal,JSON_PRETTY_PRINT); +} +/* + * JSON_UNESCAPED_SLASHES. + * Expand the value of JSON_UNESCAPED_SLASHES defined in ph7Int.h. + */ +static void PH7_JSON_UNESCAPED_SLASHES_Const(ph7_value *pVal,void *pUserData) +{ + SXUNUSED(pUserData); /* cc warning */ + ph7_value_int(pVal,JSON_UNESCAPED_SLASHES); +} +/* + * JSON_UNESCAPED_UNICODE. + * Expand the value of JSON_UNESCAPED_UNICODE defined in ph7Int.h. + */ +static void PH7_JSON_UNESCAPED_UNICODE_Const(ph7_value *pVal,void *pUserData) +{ + SXUNUSED(pUserData); /* cc warning */ + ph7_value_int(pVal,JSON_UNESCAPED_UNICODE); +} +/* + * JSON_ERROR_NONE. + * Expand the value of JSON_ERROR_NONE defined in ph7Int.h. + */ +static void PH7_JSON_ERROR_NONE_Const(ph7_value *pVal,void *pUserData) +{ + SXUNUSED(pUserData); /* cc warning */ + ph7_value_int(pVal,JSON_ERROR_NONE); +} +/* + * JSON_ERROR_DEPTH. + * Expand the value of JSON_ERROR_DEPTH defined in ph7Int.h. + */ +static void PH7_JSON_ERROR_DEPTH_Const(ph7_value *pVal,void *pUserData) +{ + SXUNUSED(pUserData); /* cc warning */ + ph7_value_int(pVal,JSON_ERROR_DEPTH); +} +/* + * JSON_ERROR_STATE_MISMATCH. + * Expand the value of JSON_ERROR_STATE_MISMATCH defined in ph7Int.h. + */ +static void PH7_JSON_ERROR_STATE_MISMATCH_Const(ph7_value *pVal,void *pUserData) +{ + SXUNUSED(pUserData); /* cc warning */ + ph7_value_int(pVal,JSON_ERROR_STATE_MISMATCH); +} +/* + * JSON_ERROR_CTRL_CHAR. + * Expand the value of JSON_ERROR_CTRL_CHAR defined in ph7Int.h. + */ +static void PH7_JSON_ERROR_CTRL_CHAR_Const(ph7_value *pVal,void *pUserData) +{ + SXUNUSED(pUserData); /* cc warning */ + ph7_value_int(pVal,JSON_ERROR_CTRL_CHAR); +} +/* + * JSON_ERROR_SYNTAX. + * Expand the value of JSON_ERROR_SYNTAX defined in ph7Int.h. + */ +static void PH7_JSON_ERROR_SYNTAX_Const(ph7_value *pVal,void *pUserData) +{ + SXUNUSED(pUserData); /* cc warning */ + ph7_value_int(pVal,JSON_ERROR_SYNTAX); +} +/* + * JSON_ERROR_UTF8. + * Expand the value of JSON_ERROR_UTF8 defined in ph7Int.h. + */ +static void PH7_JSON_ERROR_UTF8_Const(ph7_value *pVal,void *pUserData) +{ + SXUNUSED(pUserData); /* cc warning */ + ph7_value_int(pVal,JSON_ERROR_UTF8); +} +/* + * static + * Expand the name of the current class. 'static' otherwise. + */ +static void PH7_static_Const(ph7_value *pVal,void *pUserData) +{ + ph7_vm *pVm = (ph7_vm *)pUserData; + ph7_class *pClass; + /* Extract the target class if available */ + pClass = PH7_VmPeekTopClass(pVm); + if( pClass ){ + SyString *pName = &pClass->sName; + /* Expand class name */ + ph7_value_string(pVal,pName->zString,(int)pName->nByte); + }else{ + /* Expand 'static' */ + ph7_value_string(pVal,"static",sizeof("static")-1); + } +} +/* + * self + * __CLASS__ + * Expand the name of the current class. NULL otherwise. + */ +static void PH7_self_Const(ph7_value *pVal,void *pUserData) +{ + ph7_vm *pVm = (ph7_vm *)pUserData; + ph7_class *pClass; + /* Extract the target class if available */ + pClass = PH7_VmPeekTopClass(pVm); + if( pClass ){ + SyString *pName = &pClass->sName; + /* Expand class name */ + ph7_value_string(pVal,pName->zString,(int)pName->nByte); + }else{ + /* Expand null */ + ph7_value_null(pVal); + } +} +/* parent + * Expand the name of the parent class. NULL otherwise. + */ +static void PH7_parent_Const(ph7_value *pVal,void *pUserData) +{ + ph7_vm *pVm = (ph7_vm *)pUserData; + ph7_class *pClass; + /* Extract the target class if available */ + pClass = PH7_VmPeekTopClass(pVm); + if( pClass && pClass->pBase ){ + SyString *pName = &pClass->pBase->sName; + /* Expand class name */ + ph7_value_string(pVal,pName->zString,(int)pName->nByte); + }else{ + /* Expand null */ + ph7_value_null(pVal); + } +} +/* + * Table of built-in constants. + */ +static const ph7_builtin_constant aBuiltIn[] = { + {"PH7_VERSION", PH7_VER_Const }, + {"PH7_ENGINE", PH7_VER_Const }, + {"__PH7__", PH7_VER_Const }, + {"PHP_OS", PH7_OS_Const }, + {"PHP_EOL", PH7_EOL_Const }, + {"PHP_INT_MAX", PH7_INTMAX_Const }, + {"MAXINT", PH7_INTMAX_Const }, + {"PHP_INT_SIZE", PH7_INTSIZE_Const }, + {"PATH_SEPARATOR", PH7_PATHSEP_Const }, + {"DIRECTORY_SEPARATOR", PH7_DIRSEP_Const }, + {"DIR_SEP", PH7_DIRSEP_Const }, + {"__TIME__", PH7_TIME_Const }, + {"__DATE__", PH7_DATE_Const }, + {"__FILE__", PH7_FILE_Const }, + {"__DIR__", PH7_DIR_Const }, + {"PHP_SHLIB_SUFFIX", PH7_PHP_SHLIB_SUFFIX_Const }, + {"E_ERROR", PH7_E_ERROR_Const }, + {"E_WARNING", PH7_E_WARNING_Const}, + {"E_PARSE", PH7_E_PARSE_Const }, + {"E_NOTICE", PH7_E_NOTICE_Const }, + {"E_CORE_ERROR", PH7_E_CORE_ERROR_Const }, + {"E_CORE_WARNING", PH7_E_CORE_WARNING_Const }, + {"E_COMPILE_ERROR", PH7_E_COMPILE_ERROR_Const }, + {"E_COMPILE_WARNING", PH7_E_COMPILE_WARNING_Const }, + {"E_USER_ERROR", PH7_E_USER_ERROR_Const }, + {"E_USER_WARNING", PH7_E_USER_WARNING_Const }, + {"E_USER_NOTICE ", PH7_E_USER_NOTICE_Const }, + {"E_STRICT", PH7_E_STRICT_Const }, + {"E_RECOVERABLE_ERROR", PH7_E_RECOVERABLE_ERROR_Const }, + {"E_DEPRECATED", PH7_E_DEPRECATED_Const }, + {"E_USER_DEPRECATED", PH7_E_USER_DEPRECATED_Const }, + {"E_ALL", PH7_E_ALL_Const }, + {"CASE_LOWER", PH7_CASE_LOWER_Const }, + {"CASE_UPPER", PH7_CASE_UPPER_Const }, + {"STR_PAD_LEFT", PH7_STR_PAD_LEFT_Const }, + {"STR_PAD_RIGHT", PH7_STR_PAD_RIGHT_Const}, + {"STR_PAD_BOTH", PH7_STR_PAD_BOTH_Const }, + {"COUNT_NORMAL", PH7_COUNT_NORMAL_Const }, + {"COUNT_RECURSIVE", PH7_COUNT_RECURSIVE_Const }, + {"SORT_ASC", PH7_SORT_ASC_Const }, + {"SORT_DESC", PH7_SORT_DESC_Const }, + {"SORT_REGULAR", PH7_SORT_REG_Const }, + {"SORT_NUMERIC", PH7_SORT_NUMERIC_Const }, + {"SORT_STRING", PH7_SORT_STRING_Const }, + {"PHP_ROUND_HALF_DOWN", PH7_PHP_ROUND_HALF_DOWN_Const }, + {"PHP_ROUND_HALF_EVEN", PH7_PHP_ROUND_HALF_EVEN_Const }, + {"PHP_ROUND_HALF_UP", PH7_PHP_ROUND_HALF_UP_Const }, + {"PHP_ROUND_HALF_ODD", PH7_PHP_ROUND_HALF_ODD_Const }, + {"DEBUG_BACKTRACE_IGNORE_ARGS", PH7_DBIA_Const }, + {"DEBUG_BACKTRACE_PROVIDE_OBJECT",PH7_DBPO_Const}, +#ifdef PH7_ENABLE_MATH_FUNC + {"M_PI", PH7_M_PI_Const }, + {"M_E", PH7_M_E_Const }, + {"M_LOG2E", PH7_M_LOG2E_Const }, + {"M_LOG10E", PH7_M_LOG10E_Const }, + {"M_LN2", PH7_M_LN2_Const }, + {"M_LN10", PH7_M_LN10_Const }, + {"M_PI_2", PH7_M_PI_2_Const }, + {"M_PI_4", PH7_M_PI_4_Const }, + {"M_1_PI", PH7_M_1_PI_Const }, + {"M_2_PI", PH7_M_2_PI_Const }, + {"M_SQRTPI", PH7_M_SQRTPI_Const }, + {"M_2_SQRTPI", PH7_M_2_SQRTPI_Const }, + {"M_SQRT2", PH7_M_SQRT2_Const }, + {"M_SQRT3", PH7_M_SQRT3_Const }, + {"M_SQRT1_2", PH7_M_SQRT1_2_Const }, + {"M_LNPI", PH7_M_LNPI_Const }, + {"M_EULER", PH7_M_EULER_Const }, +#endif /* PH7_ENABLE_MATH_FUNC */ + {"DATE_ATOM", PH7_DATE_ATOM_Const }, + {"DATE_COOKIE", PH7_DATE_COOKIE_Const }, + {"DATE_ISO8601", PH7_DATE_ISO8601_Const }, + {"DATE_RFC822", PH7_DATE_RFC822_Const }, + {"DATE_RFC850", PH7_DATE_RFC850_Const }, + {"DATE_RFC1036", PH7_DATE_RFC1036_Const }, + {"DATE_RFC1123", PH7_DATE_RFC1123_Const }, + {"DATE_RFC2822", PH7_DATE_RFC2822_Const }, + {"DATE_RFC3339", PH7_DATE_ATOM_Const }, + {"DATE_RSS", PH7_DATE_RSS_Const }, + {"DATE_W3C", PH7_DATE_W3C_Const }, + {"ENT_COMPAT", PH7_ENT_COMPAT_Const }, + {"ENT_QUOTES", PH7_ENT_QUOTES_Const }, + {"ENT_NOQUOTES", PH7_ENT_NOQUOTES_Const }, + {"ENT_IGNORE", PH7_ENT_IGNORE_Const }, + {"ENT_SUBSTITUTE", PH7_ENT_SUBSTITUTE_Const}, + {"ENT_DISALLOWED", PH7_ENT_DISALLOWED_Const}, + {"ENT_HTML401", PH7_ENT_HTML401_Const }, + {"ENT_XML1", PH7_ENT_XML1_Const }, + {"ENT_XHTML", PH7_ENT_XHTML_Const }, + {"ENT_HTML5", PH7_ENT_HTML5_Const }, + {"ISO-8859-1", PH7_ISO88591_Const }, + {"ISO_8859_1", PH7_ISO88591_Const }, + {"UTF-8", PH7_UTF8_Const }, + {"UTF8", PH7_UTF8_Const }, + {"HTML_ENTITIES", PH7_HTML_ENTITIES_Const}, + {"HTML_SPECIALCHARS", PH7_HTML_SPECIALCHARS_Const }, + {"PHP_URL_SCHEME", PH7_PHP_URL_SCHEME_Const}, + {"PHP_URL_HOST", PH7_PHP_URL_HOST_Const}, + {"PHP_URL_PORT", PH7_PHP_URL_PORT_Const}, + {"PHP_URL_USER", PH7_PHP_URL_USER_Const}, + {"PHP_URL_PASS", PH7_PHP_URL_PASS_Const}, + {"PHP_URL_PATH", PH7_PHP_URL_PATH_Const}, + {"PHP_URL_QUERY", PH7_PHP_URL_QUERY_Const}, + {"PHP_URL_FRAGMENT", PH7_PHP_URL_FRAGMENT_Const}, + {"PHP_QUERY_RFC1738", PH7_PHP_QUERY_RFC1738_Const}, + {"PHP_QUERY_RFC3986", PH7_PHP_QUERY_RFC3986_Const}, + {"FNM_NOESCAPE", PH7_FNM_NOESCAPE_Const }, + {"FNM_PATHNAME", PH7_FNM_PATHNAME_Const }, + {"FNM_PERIOD", PH7_FNM_PERIOD_Const }, + {"FNM_CASEFOLD", PH7_FNM_CASEFOLD_Const }, + {"PATHINFO_DIRNAME", PH7_PATHINFO_DIRNAME_Const }, + {"PATHINFO_BASENAME", PH7_PATHINFO_BASENAME_Const }, + {"PATHINFO_EXTENSION", PH7_PATHINFO_EXTENSION_Const}, + {"PATHINFO_FILENAME", PH7_PATHINFO_FILENAME_Const }, + {"ASSERT_ACTIVE", PH7_ASSERT_ACTIVE_Const }, + {"ASSERT_WARNING", PH7_ASSERT_WARNING_Const }, + {"ASSERT_BAIL", PH7_ASSERT_BAIL_Const }, + {"ASSERT_QUIET_EVAL", PH7_ASSERT_QUIET_EVAL_Const }, + {"ASSERT_CALLBACK", PH7_ASSERT_CALLBACK_Const }, + {"SEEK_SET", PH7_SEEK_SET_Const }, + {"SEEK_CUR", PH7_SEEK_CUR_Const }, + {"SEEK_END", PH7_SEEK_END_Const }, + {"LOCK_EX", PH7_LOCK_EX_Const }, + {"LOCK_SH", PH7_LOCK_SH_Const }, + {"LOCK_NB", PH7_LOCK_NB_Const }, + {"LOCK_UN", PH7_LOCK_UN_Const }, + {"FILE_USE_INCLUDE_PATH", PH7_FILE_USE_INCLUDE_PATH_Const}, + {"FILE_IGNORE_NEW_LINES", PH7_FILE_IGNORE_NEW_LINES_Const}, + {"FILE_SKIP_EMPTY_LINES", PH7_FILE_SKIP_EMPTY_LINES_Const}, + {"FILE_APPEND", PH7_FILE_APPEND_Const }, + {"SCANDIR_SORT_ASCENDING", PH7_SCANDIR_SORT_ASCENDING_Const }, + {"SCANDIR_SORT_DESCENDING",PH7_SCANDIR_SORT_DESCENDING_Const }, + {"SCANDIR_SORT_NONE", PH7_SCANDIR_SORT_NONE_Const }, + {"GLOB_MARK", PH7_GLOB_MARK_Const }, + {"GLOB_NOSORT", PH7_GLOB_NOSORT_Const }, + {"GLOB_NOCHECK", PH7_GLOB_NOCHECK_Const }, + {"GLOB_NOESCAPE", PH7_GLOB_NOESCAPE_Const}, + {"GLOB_BRACE", PH7_GLOB_BRACE_Const }, + {"GLOB_ONLYDIR", PH7_GLOB_ONLYDIR_Const }, + {"GLOB_ERR", PH7_GLOB_ERR_Const }, + {"STDIN", PH7_STDIN_Const }, + {"stdin", PH7_STDIN_Const }, + {"STDOUT", PH7_STDOUT_Const }, + {"stdout", PH7_STDOUT_Const }, + {"STDERR", PH7_STDERR_Const }, + {"stderr", PH7_STDERR_Const }, + {"INI_SCANNER_NORMAL", PH7_INI_SCANNER_NORMAL_Const }, + {"INI_SCANNER_RAW", PH7_INI_SCANNER_RAW_Const }, + {"EXTR_OVERWRITE", PH7_EXTR_OVERWRITE_Const }, + {"EXTR_SKIP", PH7_EXTR_SKIP_Const }, + {"EXTR_PREFIX_SAME", PH7_EXTR_PREFIX_SAME_Const }, + {"EXTR_PREFIX_ALL", PH7_EXTR_PREFIX_ALL_Const }, + {"EXTR_PREFIX_INVALID", PH7_EXTR_PREFIX_INVALID_Const }, + {"EXTR_IF_EXISTS", PH7_EXTR_IF_EXISTS_Const }, + {"EXTR_PREFIX_IF_EXISTS",PH7_EXTR_PREFIX_IF_EXISTS_Const}, +#ifndef PH7_DISABLE_BUILTIN_FUNC + {"XML_ERROR_NONE", PH7_XML_ERROR_NONE_Const}, + {"XML_ERROR_NO_MEMORY", PH7_XML_ERROR_NO_MEMORY_Const}, + {"XML_ERROR_SYNTAX", PH7_XML_ERROR_SYNTAX_Const}, + {"XML_ERROR_NO_ELEMENTS",PH7_XML_ERROR_NO_ELEMENTS_Const}, + {"XML_ERROR_INVALID_TOKEN", PH7_XML_ERROR_INVALID_TOKEN_Const}, + {"XML_ERROR_UNCLOSED_TOKEN",PH7_XML_ERROR_UNCLOSED_TOKEN_Const}, + {"XML_ERROR_PARTIAL_CHAR", PH7_XML_ERROR_PARTIAL_CHAR_Const}, + {"XML_ERROR_TAG_MISMATCH", PH7_XML_ERROR_TAG_MISMATCH_Const}, + {"XML_ERROR_DUPLICATE_ATTRIBUTE", PH7_XML_ERROR_DUPLICATE_ATTRIBUTE_Const}, + {"XML_ERROR_JUNK_AFTER_DOC_ELEMENT",PH7_XML_ERROR_JUNK_AFTER_DOC_ELEMENT_Const}, + {"XML_ERROR_PARAM_ENTITY_REF", PH7_XML_ERROR_PARAM_ENTITY_REF_Const}, + {"XML_ERROR_UNDEFINED_ENTITY", PH7_XML_ERROR_UNDEFINED_ENTITY_Const}, + {"XML_ERROR_RECURSIVE_ENTITY_REF", PH7_XML_ERROR_RECURSIVE_ENTITY_REF_Const}, + {"XML_ERROR_ASYNC_ENTITY", PH7_XML_ERROR_ASYNC_ENTITY_Const}, + {"XML_ERROR_BAD_CHAR_REF", PH7_XML_ERROR_BAD_CHAR_REF_Const}, + {"XML_ERROR_BINARY_ENTITY_REF", PH7_XML_ERROR_BINARY_ENTITY_REF_Const}, + {"XML_ERROR_ATTRIBUTE_EXTERNAL_ENTITY_REF", PH7_XML_ERROR_ATTRIBUTE_EXTERNAL_ENTITY_REF_Const}, + {"XML_ERROR_MISPLACED_XML_PI", PH7_XML_ERROR_MISPLACED_XML_PI_Const}, + {"XML_ERROR_UNKNOWN_ENCODING", PH7_XML_ERROR_UNKNOWN_ENCODING_Const}, + {"XML_ERROR_INCORRECT_ENCODING", PH7_XML_ERROR_INCORRECT_ENCODING_Const}, + {"XML_ERROR_UNCLOSED_CDATA_SECTION", PH7_XML_ERROR_UNCLOSED_CDATA_SECTION_Const}, + {"XML_ERROR_EXTERNAL_ENTITY_HANDLING",PH7_XML_ERROR_EXTERNAL_ENTITY_HANDLING_Const}, + {"XML_OPTION_CASE_FOLDING", PH7_XML_OPTION_CASE_FOLDING_Const}, + {"XML_OPTION_TARGET_ENCODING", PH7_XML_OPTION_TARGET_ENCODING_Const}, + {"XML_OPTION_SKIP_TAGSTART", PH7_XML_OPTION_SKIP_TAGSTART_Const}, + {"XML_OPTION_SKIP_WHITE", PH7_XML_OPTION_SKIP_WHITE_Const}, + {"XML_SAX_IMPL", PH7_XML_SAX_IMP_Const}, +#endif /* PH7_DISABLE_BUILTIN_FUNC */ + {"JSON_HEX_TAG", PH7_JSON_HEX_TAG_Const}, + {"JSON_HEX_AMP", PH7_JSON_HEX_AMP_Const}, + {"JSON_HEX_APOS", PH7_JSON_HEX_APOS_Const}, + {"JSON_HEX_QUOT", PH7_JSON_HEX_QUOT_Const}, + {"JSON_FORCE_OBJECT", PH7_JSON_FORCE_OBJECT_Const}, + {"JSON_NUMERIC_CHECK", PH7_JSON_NUMERIC_CHECK_Const}, + {"JSON_BIGINT_AS_STRING", PH7_JSON_BIGINT_AS_STRING_Const}, + {"JSON_PRETTY_PRINT", PH7_JSON_PRETTY_PRINT_Const}, + {"JSON_UNESCAPED_SLASHES", PH7_JSON_UNESCAPED_SLASHES_Const}, + {"JSON_UNESCAPED_UNICODE", PH7_JSON_UNESCAPED_UNICODE_Const}, + {"JSON_ERROR_NONE", PH7_JSON_ERROR_NONE_Const}, + {"JSON_ERROR_DEPTH", PH7_JSON_ERROR_DEPTH_Const}, + {"JSON_ERROR_STATE_MISMATCH", PH7_JSON_ERROR_STATE_MISMATCH_Const}, + {"JSON_ERROR_CTRL_CHAR", PH7_JSON_ERROR_CTRL_CHAR_Const}, + {"JSON_ERROR_SYNTAX", PH7_JSON_ERROR_SYNTAX_Const}, + {"JSON_ERROR_UTF8", PH7_JSON_ERROR_UTF8_Const}, + {"static", PH7_static_Const }, + {"self", PH7_self_Const }, + {"__CLASS__", PH7_self_Const }, + {"parent", PH7_parent_Const } +}; +/* + * Register the built-in constants defined above. + */ +PH7_PRIVATE void PH7_RegisterBuiltInConstant(ph7_vm *pVm) +{ + sxu32 n; + /* + * Note that all built-in constants have access to the ph7 virtual machine + * that trigger the constant invocation as their private data. + */ + for( n = 0 ; n < SX_ARRAYSIZE(aBuiltIn) ; ++n ){ + ph7_create_constant(&(*pVm),aBuiltIn[n].zName,aBuiltIn[n].xExpand,&(*pVm)); + } +} +/* + * ---------------------------------------------------------- + * File: compile.c + * MD5: 85c9bc2bcbb35e9f704442f7b8f3b993 + * ---------------------------------------------------------- + */ +/* + * Symisc PH7: An embeddable bytecode compiler and a virtual machine for the PHP(5) programming language. + * Copyright (C) 2011-2012, Symisc Systems http://ph7.symisc.net/ + * Version 2.1.4 + * For information on licensing,redistribution of this file,and for a DISCLAIMER OF ALL WARRANTIES + * please contact Symisc Systems via: + * legal@symisc.net + * licensing@symisc.net + * contact@symisc.net + * or visit: + * http://ph7.symisc.net/ + */ + /* $SymiscID: compile.c v6.0 Win7 2012-08-18 05:11 stable $ */ +#ifndef PH7_AMALGAMATION +#include "ph7int.h" +#endif +/* + * This file implement a thread-safe and full-reentrant compiler for the PH7 engine. + * That is, routines defined in this file takes a stream of tokens and output + * PH7 bytecode instructions. + */ +/* Forward declaration */ +typedef struct LangConstruct LangConstruct; +typedef struct JumpFixup JumpFixup; +typedef struct Label Label; +/* Block [i.e: set of statements] control flags */ +#define GEN_BLOCK_LOOP 0x001 /* Loop block [i.e: for,while,...] */ +#define GEN_BLOCK_PROTECTED 0x002 /* Protected block */ +#define GEN_BLOCK_COND 0x004 /* Conditional block [i.e: if(condition){} ]*/ +#define GEN_BLOCK_FUNC 0x008 /* Function body */ +#define GEN_BLOCK_GLOBAL 0x010 /* Global block (always set)*/ +#define GEN_BLOC_NESTED_FUNC 0x020 /* Nested function body */ +#define GEN_BLOCK_EXPR 0x040 /* Expression */ +#define GEN_BLOCK_STD 0x080 /* Standard block */ +#define GEN_BLOCK_EXCEPTION 0x100 /* Exception block [i.e: try{ } }*/ +#define GEN_BLOCK_SWITCH 0x200 /* Switch statement */ +/* + * Each label seen in the input is recorded in an instance + * of the following structure. + * A label is a target point [i.e: a jump destination] that is specified + * by an identifier followed by a colon. + * Example + * LABEL: + * echo "hello\n"; + */ +struct Label +{ + ph7_vm_func *pFunc; /* Compiled function where the label was declared.NULL otherwise */ + sxu32 nJumpDest; /* Jump destination */ + SyString sName; /* Label name */ + sxu32 nLine; /* Line number this label occurs */ + sxu8 bRef; /* True if the label was referenced */ +}; +/* + * Compilation of some PHP constructs such as if, for, while, the logical or + * (||) and logical and (&&) operators in expressions requires the + * generation of forward jumps. + * Since the destination PC target of these jumps isn't known when the jumps + * are emitted, we record each forward jump in an instance of the following + * structure. Those jumps are fixed later when the jump destination is resolved. + */ +struct JumpFixup +{ + sxi32 nJumpType; /* Jump type. Either TRUE jump, FALSE jump or Unconditional jump */ + sxu32 nInstrIdx; /* Instruction index to fix later when the jump destination is resolved. */ + /* The following fields are only used by the goto statement */ + SyString sLabel; /* Label name */ + ph7_vm_func *pFunc; /* Compiled function inside which the goto was emitted. NULL otherwise */ + sxu32 nLine; /* Track line number */ +}; +/* + * Each language construct is represented by an instance + * of the following structure. + */ +struct LangConstruct +{ + sxu32 nID; /* Language construct ID [i.e: PH7_TKWRD_WHILE,PH7_TKWRD_FOR,PH7_TKWRD_IF...] */ + ProcLangConstruct xConstruct; /* C function implementing the language construct */ +}; +/* Compilation flags */ +#define PH7_COMPILE_SINGLE_STMT 0x001 /* Compile a single statement */ +/* Token stream synchronization macros */ +#define SWAP_TOKEN_STREAM(GEN,START,END)\ + pTmp = GEN->pEnd;\ + pGen->pIn = START;\ + pGen->pEnd = END +#define UPDATE_TOKEN_STREAM(GEN)\ + if( GEN->pIn < pTmp ){\ + GEN->pIn++;\ + }\ + GEN->pEnd = pTmp +#define SWAP_DELIMITER(GEN,START,END)\ + pTmpIn = GEN->pIn;\ + pTmpEnd = GEN->pEnd;\ + GEN->pIn = START;\ + GEN->pEnd = END +#define RE_SWAP_DELIMITER(GEN)\ + GEN->pIn = pTmpIn;\ + GEN->pEnd = pTmpEnd +/* Flags related to expression compilation */ +#define EXPR_FLAG_LOAD_IDX_STORE 0x001 /* Set the iP2 flag when dealing with the LOAD_IDX instruction */ +#define EXPR_FLAG_RDONLY_LOAD 0x002 /* Read-only load, refer to the 'PH7_OP_LOAD' VM instruction for more information */ +#define EXPR_FLAG_COMMA_STATEMENT 0x004 /* Treat comma expression as a single statement (used by class attributes) */ +/* Forward declaration */ +static sxi32 PH7_CompileExpr(ph7_gen_state *pGen,sxi32 iFlags,sxi32 (*xTreeValidator)(ph7_gen_state *,ph7_expr_node *)); +/* + * Local utility routines used in the code generation phase. + */ +/* + * Check if the given name refer to a valid label. + * Return SXRET_OK and write a pointer to that label on success. + * Any other return value indicates no such label. + */ +static sxi32 GenStateGetLabel(ph7_gen_state *pGen,SyString *pName,Label **ppOut) +{ + Label *aLabel; + sxu32 n; + /* Perform a linear scan on the label table */ + aLabel = (Label *)SySetBasePtr(&pGen->aLabel); + for( n = 0 ; n < SySetUsed(&pGen->aLabel) ; ++n ){ + if( SyStringCmp(&aLabel[n].sName,pName,SyMemcmp) == 0 ){ + /* Jump destination found */ + aLabel[n].bRef = TRUE; + if( ppOut ){ + *ppOut = &aLabel[n]; + } + return SXRET_OK; + } + } + /* No such destination */ + return SXERR_NOTFOUND; +} +/* + * Fetch a block that correspond to the given criteria from the stack of + * compiled blocks. + * Return a pointer to that block on success. NULL otherwise. + */ +static GenBlock * GenStateFetchBlock(GenBlock *pCurrent,sxi32 iBlockType,sxi32 iCount) +{ + GenBlock *pBlock = pCurrent; + for(;;){ + if( pBlock->iFlags & iBlockType ){ + iCount--; /* Decrement nesting level */ + if( iCount < 1 ){ + /* Block meet with the desired criteria */ + return pBlock; + } + } + /* Point to the upper block */ + pBlock = pBlock->pParent; + if( pBlock == 0 || (pBlock->iFlags & (GEN_BLOCK_PROTECTED|GEN_BLOCK_FUNC)) ){ + /* Forbidden */ + break; + } + } + /* No such block */ + return 0; +} +/* + * Initialize a freshly allocated block instance. + */ +static void GenStateInitBlock( + ph7_gen_state *pGen, /* Code generator state */ + GenBlock *pBlock, /* Target block */ + sxi32 iType, /* Block type [i.e: loop, conditional, function body, etc.]*/ + sxu32 nFirstInstr, /* First instruction to compile */ + void *pUserData /* Upper layer private data */ + ) +{ + /* Initialize block fields */ + pBlock->nFirstInstr = nFirstInstr; + pBlock->pUserData = pUserData; + pBlock->pGen = pGen; + pBlock->iFlags = iType; + pBlock->pParent = 0; + SySetInit(&pBlock->aJumpFix,&pGen->pVm->sAllocator,sizeof(JumpFixup)); + SySetInit(&pBlock->aPostContFix,&pGen->pVm->sAllocator,sizeof(JumpFixup)); +} +/* + * Allocate a new block instance. + * Return SXRET_OK and write a pointer to the new instantiated block + * on success.Otherwise generate a compile-time error and abort + * processing on failure. + */ +static sxi32 GenStateEnterBlock( + ph7_gen_state *pGen, /* Code generator state */ + sxi32 iType, /* Block type [i.e: loop, conditional, function body, etc.]*/ + sxu32 nFirstInstr, /* First instruction to compile */ + void *pUserData, /* Upper layer private data */ + GenBlock **ppBlock /* OUT: instantiated block */ + ) +{ + GenBlock *pBlock; + /* Allocate a new block instance */ + pBlock = (GenBlock *)SyMemBackendPoolAlloc(&pGen->pVm->sAllocator,sizeof(GenBlock)); + if( pBlock == 0 ){ + /* If the supplied memory subsystem is so sick that we are unable to allocate + * a tiny chunk of memory, there is no much we can do here. + */ + PH7_GenCompileError(&(*pGen),E_ERROR,1,"Fatal, PH7 engine is running out-of-memory"); + /* Abort processing immediately */ + return SXERR_ABORT; + } + /* Zero the structure */ + SyZero(pBlock,sizeof(GenBlock)); + GenStateInitBlock(&(*pGen),pBlock,iType,nFirstInstr,pUserData); + /* Link to the parent block */ + pBlock->pParent = pGen->pCurrent; + /* Mark as the current block */ + pGen->pCurrent = pBlock; + if( ppBlock ){ + /* Write a pointer to the new instance */ + *ppBlock = pBlock; + } + return SXRET_OK; +} +/* + * Release block fields without freeing the whole instance. + */ +static void GenStateReleaseBlock(GenBlock *pBlock) +{ + SySetRelease(&pBlock->aPostContFix); + SySetRelease(&pBlock->aJumpFix); +} +/* + * Release a block. + */ +static void GenStateFreeBlock(GenBlock *pBlock) +{ + ph7_gen_state *pGen = pBlock->pGen; + GenStateReleaseBlock(&(*pBlock)); + /* Free the instance */ + SyMemBackendPoolFree(&pGen->pVm->sAllocator,pBlock); +} +/* + * POP and release a block from the stack of compiled blocks. + */ +static sxi32 GenStateLeaveBlock(ph7_gen_state *pGen,GenBlock **ppBlock) +{ + GenBlock *pBlock = pGen->pCurrent; + if( pBlock == 0 ){ + /* No more block to pop */ + return SXERR_EMPTY; + } + /* Point to the upper block */ + pGen->pCurrent = pBlock->pParent; + if( ppBlock ){ + /* Write a pointer to the popped block */ + *ppBlock = pBlock; + }else{ + /* Safely release the block */ + GenStateFreeBlock(&(*pBlock)); + } + return SXRET_OK; +} +/* + * Emit a forward jump. + * Notes on forward jumps + * Compilation of some PHP constructs such as if,for,while and the logical or + * (||) and logical and (&&) operators in expressions requires the + * generation of forward jumps. + * Since the destination PC target of these jumps isn't known when the jumps + * are emitted, we record each forward jump in an instance of the following + * structure. Those jumps are fixed later when the jump destination is resolved. + */ +static sxi32 GenStateNewJumpFixup(GenBlock *pBlock,sxi32 nJumpType,sxu32 nInstrIdx) +{ + JumpFixup sJumpFix; + sxi32 rc; + /* Init the JumpFixup structure */ + sJumpFix.nJumpType = nJumpType; + sJumpFix.nInstrIdx = nInstrIdx; + /* Insert in the jump fixup table */ + rc = SySetPut(&pBlock->aJumpFix,(const void *)&sJumpFix); + return rc; +} +/* + * Fix a forward jump now the jump destination is resolved. + * Return the total number of fixed jumps. + * Notes on forward jumps: + * Compilation of some PHP constructs such as if,for,while and the logical or + * (||) and logical and (&&) operators in expressions requires the + * generation of forward jumps. + * Since the destination PC target of these jumps isn't known when the jumps + * are emitted, we record each forward jump in an instance of the following + * structure.Those jumps are fixed later when the jump destination is resolved. + */ +static sxu32 GenStateFixJumps(GenBlock *pBlock,sxi32 nJumpType,sxu32 nJumpDest) +{ + JumpFixup *aFix; + VmInstr *pInstr; + sxu32 nFixed; + sxu32 n; + /* Point to the jump fixup table */ + aFix = (JumpFixup *)SySetBasePtr(&pBlock->aJumpFix); + /* Fix the desired jumps */ + for( nFixed = n = 0 ; n < SySetUsed(&pBlock->aJumpFix) ; ++n ){ + if( aFix[n].nJumpType < 0 ){ + /* Already fixed */ + continue; + } + if( nJumpType > 0 && aFix[n].nJumpType != nJumpType ){ + /* Not of our interest */ + continue; + } + /* Point to the instruction to fix */ + pInstr = PH7_VmGetInstr(pBlock->pGen->pVm,aFix[n].nInstrIdx); + if( pInstr ){ + pInstr->iP2 = nJumpDest; + nFixed++; + /* Mark as fixed */ + aFix[n].nJumpType = -1; + } + } + /* Total number of fixed jumps */ + return nFixed; +} +/* + * Fix a 'goto' now the jump destination is resolved. + * The goto statement can be used to jump to another section + * in the program. + * Refer to the routine responsible of compiling the goto + * statement for more information. + */ +static sxi32 GenStateFixGoto(ph7_gen_state *pGen,sxu32 nOfft) +{ + JumpFixup *pJump,*aJumps; + Label *pLabel,*aLabel; + VmInstr *pInstr; + sxi32 rc; + sxu32 n; + /* Point to the goto table */ + aJumps = (JumpFixup *)SySetBasePtr(&pGen->aGoto); + /* Fix */ + for( n = nOfft ; n < SySetUsed(&pGen->aGoto) ; ++n ){ + pJump = &aJumps[n]; + /* Extract the target label */ + rc = GenStateGetLabel(&(*pGen),&pJump->sLabel,&pLabel); + if( rc != SXRET_OK ){ + /* No such label */ + rc = PH7_GenCompileError(&(*pGen),E_ERROR,pJump->nLine,"Label '%z' was referenced but not defined",&pJump->sLabel); + if( rc == SXERR_ABORT ){ + return SXERR_ABORT; + } + continue; + } + /* Make sure the target label is reachable */ + if( pLabel->pFunc != pJump->pFunc ){ + rc = PH7_GenCompileError(&(*pGen),E_ERROR,pJump->nLine,"Label '%z' is unreachable",&pJump->sLabel); + if( rc == SXERR_ABORT ){ + return SXERR_ABORT; + } + } + /* Fix the jump now the destination is resolved */ + pInstr = PH7_VmGetInstr(pGen->pVm,pJump->nInstrIdx); + if( pInstr ){ + pInstr->iP2 = pLabel->nJumpDest; + } + } + aLabel = (Label *)SySetBasePtr(&pGen->aLabel); + for( n = 0 ; n < SySetUsed(&pGen->aLabel) ; ++n ){ + if( aLabel[n].bRef == FALSE ){ + /* Emit a warning */ + PH7_GenCompileError(&(*pGen),E_WARNING,aLabel[n].nLine, + "Label '%z' is defined but not referenced",&aLabel[n].sName); + } + } + return SXRET_OK; +} +/* + * Check if a given token value is installed in the literal table. + */ +static sxi32 GenStateFindLiteral(ph7_gen_state *pGen,const SyString *pValue,sxu32 *pIdx) +{ + SyHashEntry *pEntry; + pEntry = SyHashGet(&pGen->hLiteral,(const void *)pValue->zString,pValue->nByte); + if( pEntry == 0 ){ + return SXERR_NOTFOUND; + } + *pIdx = (sxu32)SX_PTR_TO_INT(pEntry->pUserData); + return SXRET_OK; +} +/* + * Install a given constant index in the literal table. + * In order to be installed, the ph7_value must be of type string. + */ +static sxi32 GenStateInstallLiteral(ph7_gen_state *pGen,ph7_value *pObj,sxu32 nIdx) +{ + if( SyBlobLength(&pObj->sBlob) > 0 ){ + SyHashInsert(&pGen->hLiteral,SyBlobData(&pObj->sBlob),SyBlobLength(&pObj->sBlob),SX_INT_TO_PTR(nIdx)); + } + return SXRET_OK; +} +/* + * Reserve a room for a numeric constant [i.e: 64-bit integer or real number] + * in the constant table. + */ +static ph7_value * GenStateInstallNumLiteral(ph7_gen_state *pGen,sxu32 *pIdx) +{ + ph7_value *pObj; + sxu32 nIdx = 0; /* cc warning */ + /* Reserve a new constant */ + pObj = PH7_ReserveConstObj(pGen->pVm,&nIdx); + if( pObj == 0 ){ + PH7_GenCompileError(&(*pGen),E_ERROR,1,"PH7 engine is running out of memory"); + return 0; + } + *pIdx = nIdx; + /* TODO(chems): Create a numeric table (64bit int keys) same as + * the constant string iterals table [optimization purposes]. + */ + return pObj; +} +/* + * Implementation of the PHP language constructs. + */ +/* Forward declaration */ +static sxi32 GenStateCompileChunk(ph7_gen_state *pGen,sxi32 iFlags); +/* + * Compile a numeric [i.e: integer or real] literal. + * Notes on the integer type. + * According to the PHP language reference manual + * Integers can be specified in decimal (base 10), hexadecimal (base 16), octal (base 8) + * or binary (base 2) notation, optionally preceded by a sign (- or +). + * To use octal notation, precede the number with a 0 (zero). To use hexadecimal + * notation precede the number with 0x. To use binary notation precede the number with 0b. + * Symisc eXtension to the integer type. + * PH7 introduced platform-independant 64-bit integer unlike the standard PHP engine + * where the size of an integer is platform-dependent.That is,the size of an integer + * is 8 bytes and the maximum integer size is 0x7FFFFFFFFFFFFFFF for all platforms + * [i.e: either 32bit or 64bit]. + * For more information on this powerfull extension please refer to the official + * documentation. + */ +static sxi32 PH7_CompileNumLiteral(ph7_gen_state *pGen,sxi32 iCompileFlag) +{ + SyToken *pToken = pGen->pIn; /* Raw token */ + sxu32 nIdx = 0; + if( pToken->nType & PH7_TK_INTEGER ){ + ph7_value *pObj; + sxi64 iValue; + iValue = PH7_TokenValueToInt64(&pToken->sData); + pObj = GenStateInstallNumLiteral(&(*pGen),&nIdx); + if( pObj == 0 ){ + SXUNUSED(iCompileFlag); /* cc warning */ + return SXERR_ABORT; + } + PH7_MemObjInitFromInt(pGen->pVm,pObj,iValue); + }else{ + /* Real number */ + ph7_value *pObj; + /* Reserve a new constant */ + pObj = PH7_ReserveConstObj(pGen->pVm,&nIdx); + if( pObj == 0 ){ + PH7_GenCompileError(&(*pGen),E_ERROR,1,"PH7 engine is running out of memory"); + return SXERR_ABORT; + } + PH7_MemObjInitFromString(pGen->pVm,pObj,&pToken->sData); + PH7_MemObjToReal(pObj); + } + /* Emit the load constant instruction */ + PH7_VmEmitInstr(pGen->pVm,PH7_OP_LOADC,0,nIdx,0,0); + /* Node successfully compiled */ + return SXRET_OK; +} +/* + * Compile a single quoted string. + * According to the PHP language reference manual: + * + * The simplest way to specify a string is to enclose it in single quotes (the character ' ). + * To specify a literal single quote, escape it with a backslash (\). To specify a literal + * backslash, double it (\\). All other instances of backslash will be treated as a literal + * backslash: this means that the other escape sequences you might be used to, such as \r + * or \n, will be output literally as specified rather than having any special meaning. + * + */ +PH7_PRIVATE sxi32 PH7_CompileSimpleString(ph7_gen_state *pGen,sxi32 iCompileFlag) +{ + SyString *pStr = &pGen->pIn->sData; /* Constant string literal */ + const char *zIn,*zCur,*zEnd; + ph7_value *pObj; + sxu32 nIdx; + nIdx = 0; /* Prevent compiler warning */ + /* Delimit the string */ + zIn = pStr->zString; + zEnd = &zIn[pStr->nByte]; + if( zIn >= zEnd ){ + /* Empty string,load NULL */ + PH7_VmEmitInstr(pGen->pVm,PH7_OP_LOADC,0,0,0,0); + return SXRET_OK; + } + if( SXRET_OK == GenStateFindLiteral(&(*pGen),pStr,&nIdx) ){ + /* Already processed,emit the load constant instruction + * and return. + */ + PH7_VmEmitInstr(pGen->pVm,PH7_OP_LOADC,0,nIdx,0,0); + return SXRET_OK; + } + /* Reserve a new constant */ + pObj = PH7_ReserveConstObj(pGen->pVm,&nIdx); + if( pObj == 0 ){ + PH7_GenCompileError(&(*pGen),E_ERROR,1,"PH7 engine is running out of memory"); + SXUNUSED(iCompileFlag); /* cc warning */ + return SXERR_ABORT; + } + PH7_MemObjInitFromString(pGen->pVm,pObj,0); + /* Compile the node */ + for(;;){ + if( zIn >= zEnd ){ + /* End of input */ + break; + } + zCur = zIn; + while( zIn < zEnd && zIn[0] != '\\' ){ + zIn++; + } + if( zIn > zCur ){ + /* Append raw contents*/ + PH7_MemObjStringAppend(pObj,zCur,(sxu32)(zIn-zCur)); + } + zIn++; + if( zIn < zEnd ){ + if( zIn[0] == '\\' ){ + /* A literal backslash */ + PH7_MemObjStringAppend(pObj,"\\",sizeof(char)); + }else if( zIn[0] == '\'' ){ + /* A single quote */ + PH7_MemObjStringAppend(pObj,"'",sizeof(char)); + }else{ + /* verbatim copy */ + zIn--; + PH7_MemObjStringAppend(pObj,zIn,sizeof(char)*2); + zIn++; + } + } + /* Advance the stream cursor */ + zIn++; + } + /* Emit the load constant instruction */ + PH7_VmEmitInstr(pGen->pVm,PH7_OP_LOADC,0,nIdx,0,0); + if( pStr->nByte < 1024 ){ + /* Install in the literal table */ + GenStateInstallLiteral(pGen,pObj,nIdx); + } + /* Node successfully compiled */ + return SXRET_OK; +} +/* + * Compile a nowdoc string. + * According to the PHP language reference manual: + * + * Nowdocs are to single-quoted strings what heredocs are to double-quoted strings. + * A nowdoc is specified similarly to a heredoc, but no parsing is done inside a nowdoc. + * The construct is ideal for embedding PHP code or other large blocks of text without the + * need for escaping. It shares some features in common with the SGML + * construct, in that it declares a block of text which is not for parsing. + * A nowdoc is identified with the same <<< sequence used for heredocs, but the identifier + * which follows is enclosed in single quotes, e.g. <<<'EOT'. All the rules for heredoc + * identifiers also apply to nowdoc identifiers, especially those regarding the appearance + * of the closing identifier. + */ +static sxi32 PH7_CompileNowDoc(ph7_gen_state *pGen,sxi32 iCompileFlag) +{ + SyString *pStr = &pGen->pIn->sData; /* Constant string literal */ + ph7_value *pObj; + sxu32 nIdx; + nIdx = 0; /* Prevent compiler warning */ + if( pStr->nByte <= 0 ){ + /* Empty string,load NULL */ + PH7_VmEmitInstr(pGen->pVm,PH7_OP_LOADC,0,0,0,0); + return SXRET_OK; + } + /* Reserve a new constant */ + pObj = PH7_ReserveConstObj(pGen->pVm,&nIdx); + if( pObj == 0 ){ + PH7_GenCompileError(&(*pGen),E_ERROR,pGen->pIn->nLine,"PH7 engine is running out of memory"); + SXUNUSED(iCompileFlag); /* cc warning */ + return SXERR_ABORT; + } + /* No processing is done here, simply a memcpy() operation */ + PH7_MemObjInitFromString(pGen->pVm,pObj,pStr); + /* Emit the load constant instruction */ + PH7_VmEmitInstr(pGen->pVm,PH7_OP_LOADC,0,nIdx,0,0); + /* Node successfully compiled */ + return SXRET_OK; +} +/* + * Process variable expression [i.e: "$var","${var}"] embedded in a double quoted/heredoc string. + * According to the PHP language reference manual + * When a string is specified in double quotes or with heredoc,variables are parsed within it. + * There are two types of syntax: a simple one and a complex one. The simple syntax is the most + * common and convenient. It provides a way to embed a variable, an array value, or an object + * property in a string with a minimum of effort. + * Simple syntax + * If a dollar sign ($) is encountered, the parser will greedily take as many tokens as possible + * to form a valid variable name. Enclose the variable name in curly braces to explicitly specify + * the end of the name. + * Similarly, an array index or an object property can be parsed. With array indices, the closing + * square bracket (]) marks the end of the index. The same rules apply to object properties + * as to simple variables. + * Complex (curly) syntax + * This isn't called complex because the syntax is complex, but because it allows for the use + * of complex expressions. + * Any scalar variable, array element or object property with a string representation can be + * included via this syntax. Simply write the expression the same way as it would appear outside + * the string, and then wrap it in { and }. Since { can not be escaped, this syntax will only + * be recognised when the $ immediately follows the {. Use {\$ to get a literal {$ + */ +static sxi32 GenStateProcessStringExpression( + ph7_gen_state *pGen, /* Code generator state */ + sxu32 nLine, /* Line number */ + const char *zIn, /* Raw expression */ + const char *zEnd /* End of the expression */ + ) +{ + SyToken *pTmpIn,*pTmpEnd; + SySet sToken; + sxi32 rc; + /* Initialize the token set */ + SySetInit(&sToken,&pGen->pVm->sAllocator,sizeof(SyToken)); + /* Preallocate some slots */ + SySetAlloc(&sToken,0x08); + /* Tokenize the text */ + PH7_TokenizePHP(zIn,(sxu32)(zEnd-zIn),nLine,&sToken); + /* Swap delimiter */ + pTmpIn = pGen->pIn; + pTmpEnd = pGen->pEnd; + pGen->pIn = (SyToken *)SySetBasePtr(&sToken); + pGen->pEnd = &pGen->pIn[SySetUsed(&sToken)]; + /* Compile the expression */ + rc = PH7_CompileExpr(&(*pGen),0,0); + /* Restore token stream */ + pGen->pIn = pTmpIn; + pGen->pEnd = pTmpEnd; + /* Release the token set */ + SySetRelease(&sToken); + /* Compilation result */ + return rc; +} +/* + * Reserve a new constant for a double quoted/heredoc string. + */ +static ph7_value * GenStateNewStrObj(ph7_gen_state *pGen,sxi32 *pCount) +{ + ph7_value *pConstObj; + sxu32 nIdx = 0; + /* Reserve a new constant */ + pConstObj = PH7_ReserveConstObj(pGen->pVm,&nIdx); + if( pConstObj == 0 ){ + PH7_GenCompileError(&(*pGen),E_ERROR,pGen->pIn->nLine,"PH7 engine is running out of memory"); + return 0; + } + (*pCount)++; + PH7_MemObjInitFromString(pGen->pVm,pConstObj,0); + /* Emit the load constant instruction */ + PH7_VmEmitInstr(pGen->pVm,PH7_OP_LOADC,0,nIdx,0,0); + return pConstObj; +} +/* + * Compile a double quoted/heredoc string. + * According to the PHP language reference manual + * Heredoc + * A third way to delimit strings is the heredoc syntax: <<<. After this operator, an identifier + * is provided, then a newline. The string itself follows, and then the same identifier again + * to close the quotation. + * The closing identifier must begin in the first column of the line. Also, the identifier must + * follow the same naming rules as any other label in PHP: it must contain only alphanumeric + * characters and underscores, and must start with a non-digit character or underscore. + * Warning + * It is very important to note that the line with the closing identifier must contain + * no other characters, except possibly a semicolon (;). That means especially that the identifier + * may not be indented, and there may not be any spaces or tabs before or after the semicolon. + * It's also important to realize that the first character before the closing identifier must + * be a newline as defined by the local operating system. This is \n on UNIX systems, including Mac OS X. + * The closing delimiter (possibly followed by a semicolon) must also be followed by a newline. + * If this rule is broken and the closing identifier is not "clean", it will not be considered a closing + * identifier, and PHP will continue looking for one. If a proper closing identifier is not found before + * the end of the current file, a parse error will result at the last line. + * Heredocs can not be used for initializing class properties. + * Double quoted + * If the string is enclosed in double-quotes ("), PHP will interpret more escape sequences for special characters: + * Escaped characters Sequence Meaning + * \n linefeed (LF or 0x0A (10) in ASCII) + * \r carriage return (CR or 0x0D (13) in ASCII) + * \t horizontal tab (HT or 0x09 (9) in ASCII) + * \v vertical tab (VT or 0x0B (11) in ASCII) + * \f form feed (FF or 0x0C (12) in ASCII) + * \\ backslash + * \$ dollar sign + * \" double-quote + * \[0-7]{1,3} the sequence of characters matching the regular expression is a character in octal notation + * \x[0-9A-Fa-f]{1,2} the sequence of characters matching the regular expression is a character in hexadecimal notation + * As in single quoted strings, escaping any other character will result in the backslash being printed too. + * The most important feature of double-quoted strings is the fact that variable names will be expanded. + * See string parsing for details. + */ +static sxi32 GenStateCompileString(ph7_gen_state *pGen) +{ + SyString *pStr = &pGen->pIn->sData; /* Raw token value */ + const char *zIn,*zCur,*zEnd; + ph7_value *pObj = 0; + sxi32 iCons; + sxi32 rc; + /* Delimit the string */ + zIn = pStr->zString; + zEnd = &zIn[pStr->nByte]; + if( zIn >= zEnd ){ + /* Empty string,load NULL */ + PH7_VmEmitInstr(pGen->pVm,PH7_OP_LOADC,0,0,0,0); + return SXRET_OK; + } + zCur = 0; + /* Compile the node */ + iCons = 0; + for(;;){ + zCur = zIn; + while( zIn < zEnd && zIn[0] != '\\' ){ + if( zIn[0] == '{' && &zIn[1] < zEnd && zIn[1] == '$' ){ + break; + }else if(zIn[0] == '$' && &zIn[1] < zEnd && + (((unsigned char)zIn[1] >= 0xc0 || SyisAlpha(zIn[1]) || zIn[1] == '{' || zIn[1] == '_')) ){ + break; + } + zIn++; + } + if( zIn > zCur ){ + if( pObj == 0 ){ + pObj = GenStateNewStrObj(&(*pGen),&iCons); + if( pObj == 0 ){ + return SXERR_ABORT; + } + } + PH7_MemObjStringAppend(pObj,zCur,(sxu32)(zIn-zCur)); + } + if( zIn >= zEnd ){ + break; + } + if( zIn[0] == '\\' ){ + const char *zPtr = 0; + sxu32 n; + zIn++; + if( zIn >= zEnd ){ + break; + } + if( pObj == 0 ){ + pObj = GenStateNewStrObj(&(*pGen),&iCons); + if( pObj == 0 ){ + return SXERR_ABORT; + } + } + n = sizeof(char); /* size of conversion */ + switch( zIn[0] ){ + case '$': + /* Dollar sign */ + PH7_MemObjStringAppend(pObj,"$",sizeof(char)); + break; + case '\\': + /* A literal backslash */ + PH7_MemObjStringAppend(pObj,"\\",sizeof(char)); + break; + case 'a': + /* The "alert" character (BEL)[ctrl+g] ASCII code 7 */ + PH7_MemObjStringAppend(pObj,"\a",sizeof(char)); + break; + case 'b': + /* Backspace (BS)[ctrl+h] ASCII code 8 */ + PH7_MemObjStringAppend(pObj,"\b",sizeof(char)); + break; + case 'f': + /* Form-feed (FF)[ctrl+l] ASCII code 12 */ + PH7_MemObjStringAppend(pObj,"\f",sizeof(char)); + break; + case 'n': + /* Line feed(new line) (LF)[ctrl+j] ASCII code 10 */ + PH7_MemObjStringAppend(pObj,"\n",sizeof(char)); + break; + case 'r': + /* Carriage return (CR)[ctrl+m] ASCII code 13 */ + PH7_MemObjStringAppend(pObj,"\r",sizeof(char)); + break; + case 't': + /* Horizontal tab (HT)[ctrl+i] ASCII code 9 */ + PH7_MemObjStringAppend(pObj,"\t",sizeof(char)); + break; + case 'v': + /* Vertical tab(VT)[ctrl+k] ASCII code 11 */ + PH7_MemObjStringAppend(pObj,"\v",sizeof(char)); + break; + case '\'': + /* Single quote */ + PH7_MemObjStringAppend(pObj,"'",sizeof(char)); + break; + case '"': + /* Double quote */ + PH7_MemObjStringAppend(pObj,"\"",sizeof(char)); + break; + case '0': + /* NUL byte */ + PH7_MemObjStringAppend(pObj,"\0",sizeof(char)); + break; + case 'x': + if((unsigned char)zIn[1] < 0xc0 && SyisHex(zIn[1]) ){ + int c; + /* Hex digit */ + c = SyHexToint(zIn[1]) << 4; + if( &zIn[2] < zEnd ){ + c += SyHexToint(zIn[2]); + } + /* Output char */ + PH7_MemObjStringAppend(pObj,(const char *)&c,sizeof(char)); + n += sizeof(char) * 2; + }else{ + /* Output literal character */ + PH7_MemObjStringAppend(pObj,"x",sizeof(char)); + } + break; + case 'o': + if( &zIn[1] < zEnd && (unsigned char)zIn[1] < 0xc0 && SyisDigit(zIn[1]) && (zIn[1] - '0') < 8 ){ + /* Octal digit stream */ + int c; + c = 0; + zIn++; + for( zPtr = zIn ; zPtr < &zIn[3*sizeof(char)] ; zPtr++ ){ + if( zPtr >= zEnd || (unsigned char)zPtr[0] >= 0xc0 || !SyisDigit(zPtr[0]) || (zPtr[0] - '0') > 7 ){ + break; + } + c = c * 8 + (zPtr[0] - '0'); + } + if ( c > 0 ){ + PH7_MemObjStringAppend(pObj,(const char *)&c,sizeof(char)); + } + n = (sxu32)(zPtr-zIn); + }else{ + /* Output literal character */ + PH7_MemObjStringAppend(pObj,"o",sizeof(char)); + } + break; + default: + /* Output without a slash */ + PH7_MemObjStringAppend(pObj,zIn,sizeof(char)); + break; + } + /* Advance the stream cursor */ + zIn += n; + continue; + } + if( zIn[0] == '{' ){ + /* Curly syntax */ + const char *zExpr; + sxi32 iNest = 1; + zIn++; + zExpr = zIn; + /* Synchronize with the next closing curly braces */ + while( zIn < zEnd ){ + if( zIn[0] == '{' ){ + /* Increment nesting level */ + iNest++; + }else if(zIn[0] == '}' ){ + /* Decrement nesting level */ + iNest--; + if( iNest <= 0 ){ + break; + } + } + zIn++; + } + /* Process the expression */ + rc = GenStateProcessStringExpression(&(*pGen),pGen->pIn->nLine,zExpr,zIn); + if( rc == SXERR_ABORT ){ + return SXERR_ABORT; + } + if( rc != SXERR_EMPTY ){ + ++iCons; + } + if( zIn < zEnd ){ + /* Jump the trailing curly */ + zIn++; + } + }else{ + /* Simple syntax */ + const char *zExpr = zIn; + /* Assemble variable name */ + for(;;){ + /* Jump leading dollars */ + while( zIn < zEnd && zIn[0] == '$' ){ + zIn++; + } + for(;;){ + while( zIn < zEnd && (unsigned char)zIn[0] < 0xc0 && (SyisAlphaNum(zIn[0]) || zIn[0] == '_' ) ){ + zIn++; + } + if((unsigned char)zIn[0] >= 0xc0 ){ + /* UTF-8 stream */ + zIn++; + while( zIn < zEnd && (((unsigned char)zIn[0] & 0xc0) == 0x80) ){ + zIn++; + } + continue; + } + break; + } + if( zIn >= zEnd ){ + break; + } + if( zIn[0] == '[' ){ + sxi32 iSquare = 1; + zIn++; + while( zIn < zEnd ){ + if( zIn[0] == '[' ){ + iSquare++; + }else if (zIn[0] == ']' ){ + iSquare--; + if( iSquare <= 0 ){ + break; + } + } + zIn++; + } + if( zIn < zEnd ){ + zIn++; + } + break; + }else if(zIn[0] == '{' ){ + sxi32 iCurly = 1; + zIn++; + while( zIn < zEnd ){ + if( zIn[0] == '{' ){ + iCurly++; + }else if (zIn[0] == '}' ){ + iCurly--; + if( iCurly <= 0 ){ + break; + } + } + zIn++; + } + if( zIn < zEnd ){ + zIn++; + } + break; + }else if( zIn[0] == '-' && &zIn[1] < zEnd && zIn[1] == '>' ){ + /* Member access operator '->' */ + zIn += 2; + }else if(zIn[0] == ':' && &zIn[1] < zEnd && zIn[1] == ':'){ + /* Static member access operator '::' */ + zIn += 2; + }else{ + break; + } + } + /* Process the expression */ + rc = GenStateProcessStringExpression(&(*pGen),pGen->pIn->nLine,zExpr,zIn); + if( rc == SXERR_ABORT ){ + return SXERR_ABORT; + } + if( rc != SXERR_EMPTY ){ + ++iCons; + } + } + /* Invalidate the previously used constant */ + pObj = 0; + }/*for(;;)*/ + if( iCons > 1 ){ + /* Concatenate all compiled constants */ + PH7_VmEmitInstr(pGen->pVm,PH7_OP_CAT,iCons,0,0,0); + } + /* Node successfully compiled */ + return SXRET_OK; +} +/* + * Compile a double quoted string. + * See the block-comment above for more information. + */ +PH7_PRIVATE sxi32 PH7_CompileString(ph7_gen_state *pGen,sxi32 iCompileFlag) +{ + sxi32 rc; + rc = GenStateCompileString(&(*pGen)); + SXUNUSED(iCompileFlag); /* cc warning */ + /* Compilation result */ + return rc; +} +/* + * Compile a Heredoc string. + * See the block-comment above for more information. + */ +static sxi32 PH7_CompileHereDoc(ph7_gen_state *pGen,sxi32 iCompileFlag) +{ + sxi32 rc; + rc = GenStateCompileString(&(*pGen)); + SXUNUSED(iCompileFlag); /* cc warning */ + /* Compilation result */ + return SXRET_OK; +} +/* + * Compile an array entry whether it is a key or a value. + * Notes on array entries. + * According to the PHP language reference manual + * An array can be created by the array() language construct. + * It takes as parameters any number of comma-separated key => value pairs. + * array( key => value + * , ... + * ) + * A key may be either an integer or a string. If a key is the standard representation + * of an integer, it will be interpreted as such (i.e. "8" will be interpreted as 8, while + * "08" will be interpreted as "08"). Floats in key are truncated to integer. + * The indexed and associative array types are the same type in PHP, which can both + * contain integer and string indices. + * A value can be any PHP type. + * If a key is not specified for a value, the maximum of the integer indices is taken + * and the new key will be that value plus 1. If a key that already has an assigned value + * is specified, that value will be overwritten. + */ +static sxi32 GenStateCompileArrayEntry( + ph7_gen_state *pGen, /* Code generator state */ + SyToken *pIn, /* Token stream */ + SyToken *pEnd, /* End of the token stream */ + sxi32 iFlags, /* Compilation flags */ + sxi32 (*xValidator)(ph7_gen_state *,ph7_expr_node *) /* Expression tree validator callback */ + ) +{ + SyToken *pTmpIn,*pTmpEnd; + sxi32 rc; + /* Swap token stream */ + SWAP_DELIMITER(pGen,pIn,pEnd); + /* Compile the expression*/ + rc = PH7_CompileExpr(&(*pGen),iFlags,xValidator); + /* Restore token stream */ + RE_SWAP_DELIMITER(pGen); + return rc; +} +/* + * Expression tree validator callback for the 'array' language construct. + * Return SXRET_OK if the tree is valid. Any other return value indicates + * an invalid expression tree and this function will generate the appropriate + * error message. + * See the routine responible of compiling the array language construct + * for more inforation. + */ +static sxi32 GenStateArrayNodeValidator(ph7_gen_state *pGen,ph7_expr_node *pRoot) +{ + sxi32 rc = SXRET_OK; + if( pRoot->pOp ){ + if( pRoot->pOp->iOp != EXPR_OP_SUBSCRIPT /* $a[] */ && + pRoot->pOp->iOp != EXPR_OP_FUNC_CALL /* function() [Symisc extension: i.e: array(&foo())] */ + && pRoot->pOp->iOp != EXPR_OP_ARROW /* -> */ && pRoot->pOp->iOp != EXPR_OP_DC /* :: */){ + /* Unexpected expression */ + rc = PH7_GenCompileError(&(*pGen),E_ERROR,pRoot->pStart? pRoot->pStart->nLine : 0, + "array(): Expecting a variable/array member/function call after reference operator '&'"); + if( rc != SXERR_ABORT ){ + rc = SXERR_INVALID; + } + } + }else if( pRoot->xCode != PH7_CompileVariable ){ + /* Unexpected expression */ + rc = PH7_GenCompileError(&(*pGen),E_ERROR,pRoot->pStart? pRoot->pStart->nLine : 0, + "array(): Expecting a variable after reference operator '&'"); + if( rc != SXERR_ABORT ){ + rc = SXERR_INVALID; + } + } + return rc; +} +/* + * Compile the 'array' language construct. + * According to the PHP language reference manual + * An array in PHP is actually an ordered map. A map is a type that associates + * values to keys. This type is optimized for several different uses; it can + * be treated as an array, list (vector), hash table (an implementation of a map) + * dictionary, collection, stack, queue, and probably more. As array values can be + * other arrays, trees and multidimensional arrays are also possible. + */ +PH7_PRIVATE sxi32 PH7_CompileArray(ph7_gen_state *pGen,sxi32 iCompileFlag) +{ + sxi32 (*xValidator)(ph7_gen_state *,ph7_expr_node *); /* Expression tree validator callback */ + SyToken *pKey,*pCur; + sxi32 iEmitRef = 0; + sxi32 nPair = 0; + sxi32 iNest; + sxi32 rc; + /* Jump the 'array' keyword,the leading left parenthesis and the trailing parenthesis. + */ + pGen->pIn += 2; + pGen->pEnd--; + xValidator = 0; + SXUNUSED(iCompileFlag); /* cc warning */ + for(;;){ + /* Jump leading commas */ + while( pGen->pIn < pGen->pEnd && (pGen->pIn->nType & PH7_TK_COMMA) ){ + pGen->pIn++; + } + pCur = pGen->pIn; + if( SXRET_OK != PH7_GetNextExpr(pGen->pIn,pGen->pEnd,&pGen->pIn) ){ + /* No more entry to process */ + break; + } + if( pCur >= pGen->pIn ){ + continue; + } + /* Compile the key if available */ + pKey = pCur; + iNest = 0; + while( pCur < pGen->pIn ){ + if( (pCur->nType & PH7_TK_ARRAY_OP) && iNest <= 0 ){ + break; + } + if( pCur->nType & PH7_TK_LPAREN /*'('*/ ){ + iNest++; + }else if( pCur->nType & PH7_TK_RPAREN /*')'*/ ){ + /* Don't worry about mismatched parenthesis here,the expression + * parser will shortly detect any syntax error. + */ + iNest--; + } + pCur++; + } + rc = SXERR_EMPTY; + if( pCur < pGen->pIn ){ + if( &pCur[1] >= pGen->pIn ){ + /* Missing value */ + rc = PH7_GenCompileError(&(*pGen),E_ERROR,pCur->nLine,"array(): Missing entry value"); + if( rc == SXERR_ABORT ){ + return SXERR_ABORT; + } + return SXRET_OK; + } + /* Compile the expression holding the key */ + rc = GenStateCompileArrayEntry(&(*pGen),pKey,pCur, + EXPR_FLAG_RDONLY_LOAD/*Do not create the variable if inexistant*/,0); + if( rc == SXERR_ABORT ){ + return SXERR_ABORT; + } + pCur++; /* Jump the '=>' operator */ + }else if( pKey == pCur ){ + /* Key is omitted,emit a warning */ + PH7_GenCompileError(&(*pGen),E_WARNING,pCur->nLine,"array(): Missing entry key"); + pCur++; /* Jump the '=>' operator */ + }else{ + /* Reset back the cursor and point to the entry value */ + pCur = pKey; + } + if( rc == SXERR_EMPTY ){ + /* No available key,load NULL */ + PH7_VmEmitInstr(pGen->pVm,PH7_OP_LOADC,0,0 /* nil index */,0,0); + } + if( pCur->nType & PH7_TK_AMPER /*'&'*/){ + /* Insertion by reference, [i.e: $a = array(&$x);] */ + xValidator = GenStateArrayNodeValidator; /* Only variable are allowed */ + iEmitRef = 1; + pCur++; /* Jump the '&' token */ + if( pCur >= pGen->pIn ){ + /* Missing value */ + rc = PH7_GenCompileError(&(*pGen),E_ERROR,pCur->nLine,"array(): Missing referenced variable"); + if( rc == SXERR_ABORT ){ + return SXERR_ABORT; + } + return SXRET_OK; + } + } + /* Compile indice value */ + rc = GenStateCompileArrayEntry(&(*pGen),pCur,pGen->pIn,EXPR_FLAG_RDONLY_LOAD/*Do not create the variable if inexistant*/,xValidator); + if( rc == SXERR_ABORT ){ + return SXERR_ABORT; + } + if( iEmitRef ){ + /* Emit the load reference instruction */ + PH7_VmEmitInstr(pGen->pVm,PH7_OP_LOAD_REF,0,0,0,0); + } + xValidator = 0; + iEmitRef = 0; + nPair++; + } + /* Emit the load map instruction */ + PH7_VmEmitInstr(pGen->pVm,PH7_OP_LOAD_MAP,nPair * 2,0,0,0); + /* Node successfully compiled */ + return SXRET_OK; +} +/* + * Expression tree validator callback for the 'list' language construct. + * Return SXRET_OK if the tree is valid. Any other return value indicates + * an invalid expression tree and this function will generate the appropriate + * error message. + * See the routine responible of compiling the list language construct + * for more inforation. + */ +static sxi32 GenStateListNodeValidator(ph7_gen_state *pGen,ph7_expr_node *pRoot) +{ + sxi32 rc = SXRET_OK; + if( pRoot->pOp ){ + if( pRoot->pOp->iOp != EXPR_OP_SUBSCRIPT /* $a[] */ && pRoot->pOp->iOp != EXPR_OP_ARROW /* -> */ + && pRoot->pOp->iOp != EXPR_OP_DC /* :: */ ){ + /* Unexpected expression */ + rc = PH7_GenCompileError(&(*pGen),E_ERROR,pRoot->pStart? pRoot->pStart->nLine : 0, + "list(): Expecting a variable not an expression"); + if( rc != SXERR_ABORT ){ + rc = SXERR_INVALID; + } + } + }else if( pRoot->xCode != PH7_CompileVariable ){ + /* Unexpected expression */ + rc = PH7_GenCompileError(&(*pGen),E_ERROR,pRoot->pStart? pRoot->pStart->nLine : 0, + "list(): Expecting a variable not an expression"); + if( rc != SXERR_ABORT ){ + rc = SXERR_INVALID; + } + } + return rc; +} +/* + * Compile the 'list' language construct. + * According to the PHP language reference + * list(): Assign variables as if they were an array. + * list() is used to assign a list of variables in one operation. + * Description + * array list (mixed $varname [, mixed $... ] ) + * Like array(), this is not really a function, but a language construct. + * list() is used to assign a list of variables in one operation. + * Parameters + * $varname: A variable. + * Return Values + * The assigned array. + */ +PH7_PRIVATE sxi32 PH7_CompileList(ph7_gen_state *pGen,sxi32 iCompileFlag) +{ + SyToken *pNext; + sxi32 nExpr; + sxi32 rc; + nExpr = 0; + /* Jump the 'list' keyword,the leading left parenthesis and the trailing parenthesis */ + pGen->pIn += 2; + pGen->pEnd--; + SXUNUSED(iCompileFlag); /* cc warning */ + while( SXRET_OK == PH7_GetNextExpr(pGen->pIn,pGen->pEnd,&pNext) ){ + if( pGen->pIn < pNext ){ + /* Compile the expression holding the variable */ + rc = GenStateCompileArrayEntry(&(*pGen),pGen->pIn,pNext,EXPR_FLAG_LOAD_IDX_STORE,GenStateListNodeValidator); + if( rc != SXRET_OK ){ + /* Do not bother compiling this expression, it's broken anyway */ + return SXRET_OK; + } + }else{ + /* Empty entry,load NULL */ + PH7_VmEmitInstr(pGen->pVm,PH7_OP_LOADC,0,0/* NULL index */,0,0); + } + nExpr++; + /* Advance the stream cursor */ + pGen->pIn = &pNext[1]; + } + /* Emit the LOAD_LIST instruction */ + PH7_VmEmitInstr(pGen->pVm,PH7_OP_LOAD_LIST,nExpr,0,0,0); + /* Node successfully compiled */ + return SXRET_OK; +} +/* Forward declaration */ +static sxi32 GenStateCompileFunc(ph7_gen_state *pGen,SyString *pName,sxi32 iFlags,int bHandleClosure,ph7_vm_func **ppFunc); +/* + * Compile an annoynmous function or a closure. + * According to the PHP language reference + * Anonymous functions, also known as closures, allow the creation of functions + * which have no specified name. They are most useful as the value of callback + * parameters, but they have many other uses. Closures can also be used as + * the values of variables; Assigning a closure to a variable uses the same + * syntax as any other assignment, including the trailing semicolon: + * Example Anonymous function variable assignment example + * + * Note that the implementation of annoynmous function and closure under + * PH7 is completely different from the one used by the zend engine. + */ +PH7_PRIVATE sxi32 PH7_CompileAnnonFunc(ph7_gen_state *pGen,sxi32 iCompileFlag) +{ + ph7_vm_func *pAnnonFunc; /* Annonymous function body */ + char zName[512]; /* Unique lambda name */ + static int iCnt = 1; /* There is no worry about thread-safety here,because only + * one thread is allowed to compile the script. + */ + ph7_value *pObj; + SyString sName; + sxu32 nIdx; + sxu32 nLen; + sxi32 rc; + + pGen->pIn++; /* Jump the 'function' keyword */ + if( pGen->pIn->nType & (PH7_TK_ID|PH7_TK_KEYWORD) ){ + pGen->pIn++; + } + /* Reserve a constant for the lambda */ + pObj = PH7_ReserveConstObj(pGen->pVm,&nIdx); + if( pObj == 0 ){ + PH7_GenCompileError(&(*pGen),E_ERROR,1,"Fatal, PH7 engine is running out of memory"); + SXUNUSED(iCompileFlag); /* cc warning */ + return SXERR_ABORT; + } + /* Generate a unique name */ + nLen = SyBufferFormat(zName,sizeof(zName),"[lambda_%d]",iCnt++); + /* Make sure the generated name is unique */ + while( SyHashGet(&pGen->pVm->hFunction,zName,nLen) != 0 && nLen < sizeof(zName) - 2 ){ + nLen = SyBufferFormat(zName,sizeof(zName),"[lambda_%d]",iCnt++); + } + SyStringInitFromBuf(&sName,zName,nLen); + PH7_MemObjInitFromString(pGen->pVm,pObj,&sName); + /* Compile the lambda body */ + rc = GenStateCompileFunc(&(*pGen),&sName,0,TRUE,&pAnnonFunc); + if( rc == SXERR_ABORT ){ + return SXERR_ABORT; + } + if( pAnnonFunc->iFlags & VM_FUNC_CLOSURE ){ + /* Emit the load closure instruction */ + PH7_VmEmitInstr(pGen->pVm,PH7_OP_LOAD_CLOSURE,0,0,pAnnonFunc,0); + }else{ + /* Emit the load constant instruction */ + PH7_VmEmitInstr(pGen->pVm,PH7_OP_LOADC,0,nIdx,0,0); + } + /* Node successfully compiled */ + return SXRET_OK; +} +/* + * Compile a backtick quoted string. + */ +static sxi32 PH7_CompileBacktic(ph7_gen_state *pGen,sxi32 iCompileFlag) +{ + /* TICKET 1433-40: This construct is disabled in the current release of the PH7 engine. + * If you want this feature,please contact symisc systems via contact@symisc.net + */ + PH7_GenCompileError(&(*pGen),E_NOTICE,pGen->pIn->nLine, + "Command line invocation is disabled in the current release of the PH7(%s) engine", + ph7_lib_version() + ); + /* Load NULL */ + PH7_VmEmitInstr(pGen->pVm,PH7_OP_LOADC,0,0,0,0); + SXUNUSED(iCompileFlag); /* cc warning */ + /* Node successfully compiled */ + return SXRET_OK; +} +/* + * Compile a function [i.e: die(),exit(),include(),...] which is a langauge + * construct. + */ +PH7_PRIVATE sxi32 PH7_CompileLangConstruct(ph7_gen_state *pGen,sxi32 iCompileFlag) +{ + SyString *pName; + sxu32 nKeyID; + sxi32 rc; + /* Name of the language construct [i.e: echo,die...]*/ + pName = &pGen->pIn->sData; + nKeyID = (sxu32)SX_PTR_TO_INT(pGen->pIn->pUserData); + pGen->pIn++; /* Jump the language construct keyword */ + if( nKeyID == PH7_TKWRD_ECHO ){ + SyToken *pTmp,*pNext = 0; + /* Compile arguments one after one */ + pTmp = pGen->pEnd; + /* Symisc eXtension to the PHP programming language: + * 'echo' can be used in the context of a function which + * mean that the following expression is valid: + * fopen('file.txt','r') or echo "IO error"; + */ + PH7_VmEmitInstr(pGen->pVm,PH7_OP_LOADC,0,1 /* Boolean true index */,0,0); + while( SXRET_OK == PH7_GetNextExpr(pGen->pIn,pTmp,&pNext) ){ + if( pGen->pIn < pNext ){ + pGen->pEnd = pNext; + rc = PH7_CompileExpr(&(*pGen),EXPR_FLAG_RDONLY_LOAD/* Do not create variable if inexistant */,0); + if( rc == SXERR_ABORT ){ + return SXERR_ABORT; + } + if( rc != SXERR_EMPTY ){ + /* Ticket 1433-008: Optimization #1: Consume input directly + * without the overhead of a function call. + * This is a very powerful optimization that improve + * performance greatly. + */ + PH7_VmEmitInstr(pGen->pVm,PH7_OP_CONSUME,1,0,0,0); + } + } + /* Jump trailing commas */ + while( pNext < pTmp && (pNext->nType & PH7_TK_COMMA) ){ + pNext++; + } + pGen->pIn = pNext; + } + /* Restore token stream */ + pGen->pEnd = pTmp; + }else{ + sxi32 nArg = 0; + sxu32 nIdx = 0; + rc = PH7_CompileExpr(&(*pGen),EXPR_FLAG_RDONLY_LOAD/* Do not create variable if inexistant */,0); + if( rc == SXERR_ABORT ){ + return SXERR_ABORT; + }else if(rc != SXERR_EMPTY ){ + nArg = 1; + } + if( SXRET_OK != GenStateFindLiteral(&(*pGen),pName,&nIdx) ){ + ph7_value *pObj; + /* Emit the call instruction */ + pObj = PH7_ReserveConstObj(pGen->pVm,&nIdx); + if( pObj == 0 ){ + PH7_GenCompileError(&(*pGen),E_ERROR,1,"Fatal, PH7 engine is running out of memory"); + SXUNUSED(iCompileFlag); /* cc warning */ + return SXERR_ABORT; + } + PH7_MemObjInitFromString(pGen->pVm,pObj,pName); + /* Install in the literal table */ + GenStateInstallLiteral(&(*pGen),pObj,nIdx); + } + /* Emit the call instruction */ + PH7_VmEmitInstr(pGen->pVm,PH7_OP_LOADC,0,nIdx,0,0); + PH7_VmEmitInstr(pGen->pVm,PH7_OP_CALL,nArg,0,0,0); + } + /* Node successfully compiled */ + return SXRET_OK; +} +/* + * Compile a node holding a variable declaration. + * According to the PHP language reference + * Variables in PHP are represented by a dollar sign followed by the name of the variable. + * The variable name is case-sensitive. + * Variable names follow the same rules as other labels in PHP. A valid variable name starts + * with a letter or underscore, followed by any number of letters, numbers, or underscores. + * As a regular expression, it would be expressed thus: '[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*' + * Note: For our purposes here, a letter is a-z, A-Z, and the bytes from 127 through 255 (0x7f-0xff). + * Note: $this is a special variable that can't be assigned. + * By default, variables are always assigned by value. That is to say, when you assign an expression + * to a variable, the entire value of the original expression is copied into the destination variable. + * This means, for instance, that after assigning one variable's value to another, changing one of those + * variables will have no effect on the other. For more information on this kind of assignment, see + * the chapter on Expressions. + * PHP also offers another way to assign values to variables: assign by reference. This means that + * the new variable simply references (in other words, "becomes an alias for" or "points to") the original + * variable. Changes to the new variable affect the original, and vice versa. + * To assign by reference, simply prepend an ampersand (&) to the beginning of the variable which + * is being assigned (the source variable). + */ +PH7_PRIVATE sxi32 PH7_CompileVariable(ph7_gen_state *pGen,sxi32 iCompileFlag) +{ + sxu32 nLine = pGen->pIn->nLine; + sxi32 iVv; + sxi32 iP1; + void *p3; + sxi32 rc; + iVv = -1; /* Variable variable counter */ + while( pGen->pIn < pGen->pEnd && (pGen->pIn->nType & PH7_TK_DOLLAR) ){ + pGen->pIn++; + iVv++; + } + if( pGen->pIn >= pGen->pEnd || (pGen->pIn->nType & (PH7_TK_ID|PH7_TK_KEYWORD|PH7_TK_OCB/*'{'*/)) == 0 ){ + /* Invalid variable name */ + rc = PH7_GenCompileError(pGen,E_ERROR,nLine,"Invalid variable name"); + if( rc == SXERR_ABORT ){ + /* Error count limit reached,abort immediately */ + return SXERR_ABORT; + } + return SXRET_OK; + } + p3 = 0; + if( pGen->pIn->nType & PH7_TK_OCB/*'{'*/ ){ + /* Dynamic variable creation */ + pGen->pIn++; /* Jump the open curly */ + pGen->pEnd--; /* Ignore the trailing curly */ + if( pGen->pIn >= pGen->pEnd ){ + /* Empty expression */ + PH7_GenCompileError(&(*pGen),E_ERROR,nLine,"Invalid variable name"); + return SXRET_OK; + } + /* Compile the expression holding the variable name */ + rc = PH7_CompileExpr(&(*pGen),0,0); + if( rc == SXERR_ABORT ){ + return SXERR_ABORT; + }else if( rc == SXERR_EMPTY ){ + PH7_GenCompileError(&(*pGen),E_ERROR,nLine,"Missing variable name"); + return SXRET_OK; + } + }else{ + SyHashEntry *pEntry; + SyString *pName; + char *zName = 0; + /* Extract variable name */ + pName = &pGen->pIn->sData; + /* Advance the stream cursor */ + pGen->pIn++; + pEntry = SyHashGet(&pGen->hVar,(const void *)pName->zString,pName->nByte); + if( pEntry == 0 ){ + /* Duplicate name */ + zName = SyMemBackendStrDup(&pGen->pVm->sAllocator,pName->zString,pName->nByte); + if( zName == 0 ){ + PH7_GenCompileError(pGen,E_ERROR,nLine,"Fatal, PH7 engine is running out of memory"); + return SXERR_ABORT; + } + /* Install in the hashtable */ + SyHashInsert(&pGen->hVar,zName,pName->nByte,zName); + }else{ + /* Name already available */ + zName = (char *)pEntry->pUserData; + } + p3 = (void *)zName; + } + iP1 = 0; + if( iCompileFlag & EXPR_FLAG_RDONLY_LOAD ){ + if( (iCompileFlag & EXPR_FLAG_LOAD_IDX_STORE) == 0 ){ + /* Read-only load.In other words do not create the variable if inexistant */ + iP1 = 1; + } + } + /* Emit the load instruction */ + PH7_VmEmitInstr(pGen->pVm,PH7_OP_LOAD,iP1,0,p3,0); + while( iVv > 0 ){ + PH7_VmEmitInstr(pGen->pVm,PH7_OP_LOAD,iP1,0,0,0); + iVv--; + } + /* Node successfully compiled */ + return SXRET_OK; +} +/* + * Load a literal. + */ +static sxi32 GenStateLoadLiteral(ph7_gen_state *pGen) +{ + SyToken *pToken = pGen->pIn; + ph7_value *pObj; + SyString *pStr; + sxu32 nIdx; + /* Extract token value */ + pStr = &pToken->sData; + /* Deal with the reserved literals [i.e: null,false,true,...] first */ + if( pStr->nByte == sizeof("NULL") - 1 ){ + if( SyStrnicmp(pStr->zString,"null",sizeof("NULL")-1) == 0 ){ + /* NULL constant are always indexed at 0 */ + PH7_VmEmitInstr(pGen->pVm,PH7_OP_LOADC,0,0,0,0); + return SXRET_OK; + }else if( SyStrnicmp(pStr->zString,"true",sizeof("TRUE")-1) == 0 ){ + /* TRUE constant are always indexed at 1 */ + PH7_VmEmitInstr(pGen->pVm,PH7_OP_LOADC,0,1,0,0); + return SXRET_OK; + } + }else if (pStr->nByte == sizeof("FALSE") - 1 && + SyStrnicmp(pStr->zString,"false",sizeof("FALSE")-1) == 0 ){ + /* FALSE constant are always indexed at 2 */ + PH7_VmEmitInstr(pGen->pVm,PH7_OP_LOADC,0,2,0,0); + return SXRET_OK; + }else if(pStr->nByte == sizeof("__LINE__") - 1 && + SyMemcmp(pStr->zString,"__LINE__",sizeof("__LINE__")-1) == 0 ){ + /* TICKET 1433-004: __LINE__ constant must be resolved at compile time,not run time */ + pObj = PH7_ReserveConstObj(pGen->pVm,&nIdx); + if( pObj == 0 ){ + PH7_GenCompileError(pGen,E_ERROR,pToken->nLine,"Fatal, PH7 engine is running out of memory"); + return SXERR_ABORT; + } + PH7_MemObjInitFromInt(pGen->pVm,pObj,pToken->nLine); + /* Emit the load constant instruction */ + PH7_VmEmitInstr(pGen->pVm,PH7_OP_LOADC,0,nIdx,0,0); + return SXRET_OK; + }else if( (pStr->nByte == sizeof("__FUNCTION__") - 1 && + SyMemcmp(pStr->zString,"__FUNCTION__",sizeof("__FUNCTION__")-1) == 0) || + (pStr->nByte == sizeof("__METHOD__") - 1 && + SyMemcmp(pStr->zString,"__METHOD__",sizeof("__METHOD__")-1) == 0) ){ + GenBlock *pBlock = pGen->pCurrent; + /* TICKET 1433-004: __FUNCTION__/__METHOD__ constants must be resolved at compile time,not run time */ + while( pBlock && (pBlock->iFlags & GEN_BLOCK_FUNC) == 0 ){ + /* Point to the upper block */ + pBlock = pBlock->pParent; + } + if( pBlock == 0 ){ + /* Called in the global scope,load NULL */ + PH7_VmEmitInstr(pGen->pVm,PH7_OP_LOADC,0,0,0,0); + }else{ + /* Extract the target function/method */ + ph7_vm_func *pFunc = (ph7_vm_func *)pBlock->pUserData; + if( pStr->zString[2] == 'M' /* METHOD */ && (pFunc->iFlags & VM_FUNC_CLASS_METHOD) == 0 ){ + /* Not a class method,Load null */ + PH7_VmEmitInstr(pGen->pVm,PH7_OP_LOADC,0,0,0,0); + }else{ + pObj = PH7_ReserveConstObj(pGen->pVm,&nIdx); + if( pObj == 0 ){ + PH7_GenCompileError(pGen,E_ERROR,pToken->nLine,"Fatal, PH7 engine is running out of memory"); + return SXERR_ABORT; + } + PH7_MemObjInitFromString(pGen->pVm,pObj,&pFunc->sName); + /* Emit the load constant instruction */ + PH7_VmEmitInstr(pGen->pVm,PH7_OP_LOADC,0,nIdx,0,0); + } + } + return SXRET_OK; + } + /* Query literal table */ + if( SXRET_OK != GenStateFindLiteral(&(*pGen),&pToken->sData,&nIdx) ){ + ph7_value *pObj; + /* Unknown literal,install it in the literal table */ + pObj = PH7_ReserveConstObj(pGen->pVm,&nIdx); + if( pObj == 0 ){ + PH7_GenCompileError(&(*pGen),E_ERROR,1,"PH7 engine is running out of memory"); + return SXERR_ABORT; + } + PH7_MemObjInitFromString(pGen->pVm,pObj,&pToken->sData); + GenStateInstallLiteral(&(*pGen),pObj,nIdx); + } + /* Emit the load constant instruction */ + PH7_VmEmitInstr(pGen->pVm,PH7_OP_LOADC,1,nIdx,0,0); + return SXRET_OK; +} +/* + * Resolve a namespace path or simply load a literal: + * As of this version namespace support is disabled. If you need + * a working version that implement namespace,please contact + * symisc systems via contact@symisc.net + */ +static sxi32 GenStateResolveNamespaceLiteral(ph7_gen_state *pGen) +{ + int emit = 0; + sxi32 rc; + while( pGen->pIn < &pGen->pEnd[-1] ){ + /* Emit a warning */ + if( !emit ){ + PH7_GenCompileError(&(*pGen),E_WARNING,pGen->pIn->nLine, + "Namespace support is disabled in the current release of the PH7(%s) engine", + ph7_lib_version() + ); + emit = 1; + } + pGen->pIn++; /* Ignore the token */ + } + /* Load literal */ + rc = GenStateLoadLiteral(&(*pGen)); + return rc; +} +/* + * Compile a literal which is an identifier(name) for a simple value. + */ +PH7_PRIVATE sxi32 PH7_CompileLiteral(ph7_gen_state *pGen,sxi32 iCompileFlag) +{ + sxi32 rc; + rc = GenStateResolveNamespaceLiteral(&(*pGen)); + if( rc != SXRET_OK ){ + SXUNUSED(iCompileFlag); /* cc warning */ + return rc; + } + /* Node successfully compiled */ + return SXRET_OK; +} +/* + * Recover from a compile-time error. In other words synchronize + * the token stream cursor with the first semi-colon seen. + */ +static sxi32 PH7_ErrorRecover(ph7_gen_state *pGen) +{ + /* Synchronize with the next-semi-colon and avoid compiling this erroneous statement */ + while( pGen->pIn < pGen->pEnd && (pGen->pIn->nType & PH7_TK_SEMI /*';'*/) == 0){ + pGen->pIn++; + } + return SXRET_OK; +} +/* + * Check if the given identifier name is reserved or not. + * Return TRUE if reserved.FALSE otherwise. + */ +static int GenStateIsReservedConstant(SyString *pName) +{ + if( pName->nByte == sizeof("null") - 1 ){ + if( SyStrnicmp(pName->zString,"null",sizeof("null")-1) == 0 ){ + return TRUE; + }else if( SyStrnicmp(pName->zString,"true",sizeof("true")-1) == 0 ){ + return TRUE; + } + }else if( pName->nByte == sizeof("false") - 1 ){ + if( SyStrnicmp(pName->zString,"false",sizeof("false")-1) == 0 ){ + return TRUE; + } + } + /* Not a reserved constant */ + return FALSE; +} +/* + * Compile the 'const' statement. + * According to the PHP language reference + * A constant is an identifier (name) for a simple value. As the name suggests, that value + * cannot change during the execution of the script (except for magic constants, which aren't actually constants). + * A constant is case-sensitive by default. By convention, constant identifiers are always uppercase. + * The name of a constant follows the same rules as any label in PHP. A valid constant name starts + * with a letter or underscore, followed by any number of letters, numbers, or underscores. + * As a regular expression it would be expressed thusly: [a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]* + * Syntax + * You can define a constant by using the define()-function or by using the const keyword outside + * a class definition. Once a constant is defined, it can never be changed or undefined. + * You can get the value of a constant by simply specifying its name. Unlike with variables + * you should not prepend a constant with a $. You can also use the function constant() to read + * a constant's value if you wish to obtain the constant's name dynamically. Use get_defined_constants() + * to get a list of all defined constants. + * + * Symisc eXtension. + * PH7 allow any complex expression to be associated with the constant while the zend engine + * would allow only simple scalar value. + * Example + * const HELLO = "Welcome "." guest ".rand_str(3); //Valid under PH7/Generate error using the zend engine + * Refer to the official documentation for more information on this feature. + */ +static sxi32 PH7_CompileConstant(ph7_gen_state *pGen) +{ + SySet *pConsCode,*pInstrContainer; + sxu32 nLine = pGen->pIn->nLine; + SyString *pName; + sxi32 rc; + pGen->pIn++; /* Jump the 'const' keyword */ + if( pGen->pIn >= pGen->pEnd || (pGen->pIn->nType & (PH7_TK_SSTR|PH7_TK_DSTR|PH7_TK_ID|PH7_TK_KEYWORD)) == 0 ){ + /* Invalid constant name */ + rc = PH7_GenCompileError(pGen,E_ERROR,nLine,"const: Invalid constant name"); + if( rc == SXERR_ABORT ){ + /* Error count limit reached,abort immediately */ + return SXERR_ABORT; + } + goto Synchronize; + } + /* Peek constant name */ + pName = &pGen->pIn->sData; + /* Make sure the constant name isn't reserved */ + if( GenStateIsReservedConstant(pName) ){ + /* Reserved constant */ + rc = PH7_GenCompileError(pGen,E_ERROR,nLine,"const: Cannot redeclare a reserved constant '%z'",pName); + if( rc == SXERR_ABORT ){ + /* Error count limit reached,abort immediately */ + return SXERR_ABORT; + } + goto Synchronize; + } + pGen->pIn++; + if(pGen->pIn >= pGen->pEnd || (pGen->pIn->nType & PH7_TK_EQUAL /* '=' */) == 0 ){ + /* Invalid statement*/ + rc = PH7_GenCompileError(pGen,E_ERROR,nLine,"const: Expected '=' after constant name"); + if( rc == SXERR_ABORT ){ + /* Error count limit reached,abort immediately */ + return SXERR_ABORT; + } + goto Synchronize; + } + pGen->pIn++; /*Jump the equal sign */ + /* Allocate a new constant value container */ + pConsCode = (SySet *)SyMemBackendPoolAlloc(&pGen->pVm->sAllocator,sizeof(SySet)); + if( pConsCode == 0 ){ + PH7_GenCompileError(pGen,E_ERROR,nLine,"Fatal, PH7 engine is running out of memory"); + return SXERR_ABORT; + } + SySetInit(pConsCode,&pGen->pVm->sAllocator,sizeof(VmInstr)); + /* Swap bytecode container */ + pInstrContainer = PH7_VmGetByteCodeContainer(pGen->pVm); + PH7_VmSetByteCodeContainer(pGen->pVm,pConsCode); + /* Compile constant value */ + rc = PH7_CompileExpr(&(*pGen),0,0); + /* Emit the done instruction */ + PH7_VmEmitInstr(pGen->pVm,PH7_OP_DONE,(rc != SXERR_EMPTY ? 1 : 0),0,0,0); + PH7_VmSetByteCodeContainer(pGen->pVm,pInstrContainer); + if( rc == SXERR_ABORT ){ + /* Don't worry about freeing memory, everything will be released shortly */ + return SXERR_ABORT; + } + SySetSetUserData(pConsCode,pGen->pVm); + /* Register the constant */ + rc = PH7_VmRegisterConstant(pGen->pVm,pName,PH7_VmExpandConstantValue,pConsCode); + if( rc != SXRET_OK ){ + SySetRelease(pConsCode); + SyMemBackendPoolFree(&pGen->pVm->sAllocator,pConsCode); + } + return SXRET_OK; +Synchronize: + /* Synchronize with the next-semi-colon and avoid compiling this erroneous statement */ + while(pGen->pIn < pGen->pEnd && (pGen->pIn->nType & PH7_TK_SEMI) == 0 ){ + pGen->pIn++; + } + return SXRET_OK; +} +/* + * Compile the 'continue' statement. + * According to the PHP language reference + * continue is used within looping structures to skip the rest of the current loop iteration + * and continue execution at the condition evaluation and then the beginning of the next + * iteration. + * Note: Note that in PHP the switch statement is considered a looping structure for + * the purposes of continue. + * continue accepts an optional numeric argument which tells it how many levels + * of enclosing loops it should skip to the end of. + * Note: + * continue 0; and continue 1; is the same as running continue;. + */ +static sxi32 PH7_CompileContinue(ph7_gen_state *pGen) +{ + GenBlock *pLoop; /* Target loop */ + sxi32 iLevel; /* How many nesting loop to skip */ + sxu32 nLine; + sxi32 rc; + nLine = pGen->pIn->nLine; + iLevel = 0; + /* Jump the 'continue' keyword */ + pGen->pIn++; + if( pGen->pIn < pGen->pEnd && (pGen->pIn->nType & PH7_TK_NUM) ){ + /* optional numeric argument which tells us how many levels + * of enclosing loops we should skip to the end of. + */ + iLevel = (sxi32)PH7_TokenValueToInt64(&pGen->pIn->sData); + if( iLevel < 2 ){ + iLevel = 0; + } + pGen->pIn++; /* Jump the optional numeric argument */ + } + /* Point to the target loop */ + pLoop = GenStateFetchBlock(pGen->pCurrent,GEN_BLOCK_LOOP,iLevel); + if( pLoop == 0 ){ + /* Illegal continue */ + rc = PH7_GenCompileError(pGen,E_ERROR,nLine,"A 'continue' statement may only be used within a loop or switch"); + if( rc == SXERR_ABORT ){ + /* Error count limit reached,abort immediately */ + return SXERR_ABORT; + } + }else{ + sxu32 nInstrIdx = 0; + if( pLoop->iFlags & GEN_BLOCK_SWITCH ){ + /* According to the PHP language reference manual + * Note that unlike some other languages, the continue statement applies to switch + * and acts similar to break. If you have a switch inside a loop and wish to continue + * to the next iteration of the outer loop, use continue 2. + */ + rc = PH7_VmEmitInstr(pGen->pVm,PH7_OP_JMP,0,0,0,&nInstrIdx); + if( rc == SXRET_OK ){ + GenStateNewJumpFixup(pLoop,PH7_OP_JMP,nInstrIdx); + } + }else{ + /* Emit the unconditional jump to the beginning of the target loop */ + PH7_VmEmitInstr(pGen->pVm,PH7_OP_JMP,0,pLoop->nFirstInstr,0,&nInstrIdx); + if( pLoop->bPostContinue == TRUE ){ + JumpFixup sJumpFix; + /* Post-continue */ + sJumpFix.nJumpType = PH7_OP_JMP; + sJumpFix.nInstrIdx = nInstrIdx; + SySetPut(&pLoop->aPostContFix,(const void *)&sJumpFix); + } + } + } + if( pGen->pIn < pGen->pEnd && (pGen->pIn->nType & PH7_TK_SEMI) == 0 ){ + /* Not so fatal,emit a warning only */ + PH7_GenCompileError(&(*pGen),E_WARNING,pGen->pIn->nLine,"Expected semi-colon ';' after 'continue' statement"); + } + /* Statement successfully compiled */ + return SXRET_OK; +} +/* + * Compile the 'break' statement. + * According to the PHP language reference + * break ends execution of the current for, foreach, while, do-while or switch + * structure. + * break accepts an optional numeric argument which tells it how many nested + * enclosing structures are to be broken out of. + */ +static sxi32 PH7_CompileBreak(ph7_gen_state *pGen) +{ + GenBlock *pLoop; /* Target loop */ + sxi32 iLevel; /* How many nesting loop to skip */ + sxu32 nLine; + sxi32 rc; + nLine = pGen->pIn->nLine; + iLevel = 0; + /* Jump the 'break' keyword */ + pGen->pIn++; + if( pGen->pIn < pGen->pEnd && (pGen->pIn->nType & PH7_TK_NUM) ){ + /* optional numeric argument which tells us how many levels + * of enclosing loops we should skip to the end of. + */ + iLevel = (sxi32)PH7_TokenValueToInt64(&pGen->pIn->sData); + if( iLevel < 2 ){ + iLevel = 0; + } + pGen->pIn++; /* Jump the optional numeric argument */ + } + /* Extract the target loop */ + pLoop = GenStateFetchBlock(pGen->pCurrent,GEN_BLOCK_LOOP,iLevel); + if( pLoop == 0 ){ + /* Illegal break */ + rc = PH7_GenCompileError(pGen,E_ERROR,pGen->pIn->nLine,"A 'break' statement may only be used within a loop or switch"); + if( rc == SXERR_ABORT ){ + /* Error count limit reached,abort immediately */ + return SXERR_ABORT; + } + }else{ + sxu32 nInstrIdx; + rc = PH7_VmEmitInstr(pGen->pVm,PH7_OP_JMP,0,0,0,&nInstrIdx); + if( rc == SXRET_OK ){ + /* Fix the jump later when the jump destination is resolved */ + GenStateNewJumpFixup(pLoop,PH7_OP_JMP,nInstrIdx); + } + } + if( pGen->pIn < pGen->pEnd && (pGen->pIn->nType & PH7_TK_SEMI) == 0 ){ + /* Not so fatal,emit a warning only */ + PH7_GenCompileError(&(*pGen),E_WARNING,pGen->pIn->nLine,"Expected semi-colon ';' after 'break' statement"); + } + /* Statement successfully compiled */ + return SXRET_OK; +} +/* + * Compile or record a label. + * A label is a target point that is specified by an identifier followed by a colon. + * Example + * goto LABEL; + * echo 'Foo'; + * LABEL: + * echo 'Bar'; + */ +static sxi32 PH7_CompileLabel(ph7_gen_state *pGen) +{ + GenBlock *pBlock; + Label sLabel; + /* Make sure the label does not occur inside a loop or a try{}catch(); block */ + pBlock = GenStateFetchBlock(pGen->pCurrent,GEN_BLOCK_LOOP|GEN_BLOCK_EXCEPTION,0); + if( pBlock ){ + sxi32 rc; + rc = PH7_GenCompileError(&(*pGen),E_ERROR,pGen->pIn->nLine, + "Label '%z' inside loop or try/catch block is disallowed",&pGen->pIn->sData); + if( rc == SXERR_ABORT ){ + return SXERR_ABORT; + } + }else{ + SyString *pTarget = &pGen->pIn->sData; + char *zDup; + /* Initialize label fields */ + sLabel.nJumpDest = PH7_VmInstrLength(pGen->pVm); + /* Duplicate label name */ + zDup = SyMemBackendStrDup(&pGen->pVm->sAllocator,pTarget->zString,pTarget->nByte); + if( zDup == 0 ){ + PH7_GenCompileError(&(*pGen),E_ERROR,pGen->pIn->nLine,"Fatal, PH7 is running out of memory"); + return SXERR_ABORT; + } + SyStringInitFromBuf(&sLabel.sName,zDup,pTarget->nByte); + sLabel.bRef = FALSE; + sLabel.nLine = pGen->pIn->nLine; + pBlock = pGen->pCurrent; + while( pBlock ){ + if( pBlock->iFlags & (GEN_BLOCK_FUNC|GEN_BLOCK_EXCEPTION) ){ + break; + } + /* Point to the upper block */ + pBlock = pBlock->pParent; + } + if( pBlock ){ + sLabel.pFunc = (ph7_vm_func *)pBlock->pUserData; + }else{ + sLabel.pFunc = 0; + } + /* Insert in label set */ + SySetPut(&pGen->aLabel,(const void *)&sLabel); + } + pGen->pIn += 2; /* Jump the label name and the semi-colon*/ + return SXRET_OK; +} +/* + * Compile the so hated 'goto' statement. + * You've probably been taught that gotos are bad, but this sort + * of rewriting happens all the time, in fact every time you run + * a compiler it has to do this. + * According to the PHP language reference manual + * The goto operator can be used to jump to another section in the program. + * The target point is specified by a label followed by a colon, and the instruction + * is given as goto followed by the desired target label. This is not a full unrestricted goto. + * The target label must be within the same file and context, meaning that you cannot jump out + * of a function or method, nor can you jump into one. You also cannot jump into any sort of loop + * or switch structure. You may jump out of these, and a common use is to use a goto in place + * of a multi-level break + */ +static sxi32 PH7_CompileGoto(ph7_gen_state *pGen) +{ + JumpFixup sJump; + sxi32 rc; + pGen->pIn++; /* Jump the 'goto' keyword */ + if( pGen->pIn >= pGen->pEnd ){ + /* Missing label */ + rc = PH7_GenCompileError(pGen,E_ERROR,pGen->pIn->nLine,"goto: expecting a 'label_name'"); + if( rc == SXERR_ABORT ){ + /* Error count limit reached,abort immediately */ + return SXERR_ABORT; + } + return SXRET_OK; + } + if( (pGen->pIn->nType & (PH7_TK_KEYWORD|PH7_TK_ID)) == 0 ){ + rc = PH7_GenCompileError(pGen,E_ERROR,pGen->pIn->nLine,"goto: Invalid label name: '%z'",&pGen->pIn->sData); + if( rc == SXERR_ABORT ){ + /* Error count limit reached,abort immediately */ + return SXERR_ABORT; + } + }else{ + SyString *pTarget = &pGen->pIn->sData; + GenBlock *pBlock; + char *zDup; + /* Prepare the jump destination */ + sJump.nJumpType = PH7_OP_JMP; + sJump.nLine = pGen->pIn->nLine; + /* Duplicate label name */ + zDup = SyMemBackendStrDup(&pGen->pVm->sAllocator,pTarget->zString,pTarget->nByte); + if( zDup == 0 ){ + PH7_GenCompileError(&(*pGen),E_ERROR,pGen->pIn->nLine,"Fatal, PH7 is running out of memory"); + return SXERR_ABORT; + } + SyStringInitFromBuf(&sJump.sLabel,zDup,pTarget->nByte); + pBlock = pGen->pCurrent; + while( pBlock ){ + if( pBlock->iFlags & (GEN_BLOCK_FUNC|GEN_BLOCK_EXCEPTION) ){ + break; + } + /* Point to the upper block */ + pBlock = pBlock->pParent; + } + if( pBlock && pBlock->iFlags & GEN_BLOCK_EXCEPTION ){ + rc = PH7_GenCompileError(pGen,E_ERROR,pGen->pIn->nLine,"goto inside try/catch block is disallowed"); + if( rc == SXERR_ABORT ){ + /* Error count limit reached,abort immediately */ + return SXERR_ABORT; + } + } + if( pBlock && (pBlock->iFlags & GEN_BLOCK_FUNC)){ + sJump.pFunc = (ph7_vm_func *)pBlock->pUserData; + }else{ + sJump.pFunc = 0; + } + /* Emit the unconditional jump */ + if( SXRET_OK == PH7_VmEmitInstr(pGen->pVm,PH7_OP_JMP,0,0,0,&sJump.nInstrIdx) ){ + SySetPut(&pGen->aGoto,(const void *)&sJump); + } + } + pGen->pIn++; /* Jump the label name */ + if( pGen->pIn < pGen->pEnd && (pGen->pIn->nType & PH7_TK_SEMI) == 0 ){ + PH7_GenCompileError(&(*pGen),E_ERROR,pGen->pIn->nLine,"Expected semi-colon ';' after 'goto' statement"); + } + /* Statement successfully compiled */ + return SXRET_OK; +} +/* + * Point to the next PHP chunk that will be processed shortly. + * Return SXRET_OK on success. Any other return value indicates + * failure. + */ +static sxi32 GenStateNextChunk(ph7_gen_state *pGen) +{ + ph7_value *pRawObj; /* Raw chunk [i.e: HTML,XML...] */ + sxu32 nRawObj; + sxu32 nObjIdx; + /* Consume raw chunks verbatim without any processing until we get + * a PHP block. + */ +Consume: + nRawObj = nObjIdx = 0; + while( pGen->pRawIn < pGen->pRawEnd && pGen->pRawIn->nType != PH7_TOKEN_PHP ){ + pRawObj = PH7_ReserveConstObj(pGen->pVm,&nObjIdx); + if( pRawObj == 0 ){ + PH7_GenCompileError(pGen,E_ERROR,1,"Fatal, PH7 engine is running out of memory"); + return SXERR_ABORT; + } + /* Mark as constant and emit the load constant instruction */ + PH7_MemObjInitFromString(pGen->pVm,pRawObj,&pGen->pRawIn->sData); + PH7_VmEmitInstr(pGen->pVm,PH7_OP_LOADC,0,nObjIdx,0,0); + ++nRawObj; + pGen->pRawIn++; /* Next chunk */ + } + if( nRawObj > 0 ){ + /* Emit the consume instruction */ + PH7_VmEmitInstr(pGen->pVm,PH7_OP_CONSUME,nRawObj,0,0,0); + } + if( pGen->pRawIn < pGen->pRawEnd ){ + SySet *pTokenSet = pGen->pTokenSet; + /* Reset the token set */ + SySetReset(pTokenSet); + /* Tokenize input */ + PH7_TokenizePHP(SyStringData(&pGen->pRawIn->sData),SyStringLength(&pGen->pRawIn->sData), + pGen->pRawIn->nLine,pTokenSet); + /* Point to the fresh token stream */ + pGen->pIn = (SyToken *)SySetBasePtr(pTokenSet); + pGen->pEnd = &pGen->pIn[SySetUsed(pTokenSet)]; + /* Advance the stream cursor */ + pGen->pRawIn++; + /* TICKET 1433-011 */ + if( pGen->pIn < pGen->pEnd && ( pGen->pIn->nType & PH7_TK_EQUAL ) ){ + static const sxu32 nKeyID = PH7_TKWRD_ECHO; + sxi32 rc; + /* Refer to TICKET 1433-009 */ + pGen->pIn->nType = PH7_TK_KEYWORD; + pGen->pIn->pUserData = SX_INT_TO_PTR(nKeyID); + SyStringInitFromBuf(&pGen->pIn->sData,"echo",sizeof("echo")-1); + rc = PH7_CompileExpr(pGen,0,0); + if( rc == SXERR_ABORT ){ + return SXERR_ABORT; + }else if( rc != SXERR_EMPTY ){ + PH7_VmEmitInstr(pGen->pVm,PH7_OP_POP,1,0,0,0); + } + goto Consume; + } + }else{ + /* No more chunks to process */ + pGen->pIn = pGen->pEnd; + return SXERR_EOF; + } + return SXRET_OK; +} +/* + * Compile a PHP block. + * A block is simply one or more PHP statements and expressions to compile + * optionally delimited by braces {}. + * Return SXRET_OK on success. Any other return value indicates failure + * and this function takes care of generating the appropriate error + * message. + */ +static sxi32 PH7_CompileBlock( + ph7_gen_state *pGen, /* Code generator state */ + sxi32 nKeywordEnd /* EOF-keyword [i.e: endif;endfor;...]. 0 (zero) otherwise */ + ) +{ + sxi32 rc; + if( pGen->pIn->nType & PH7_TK_OCB /* '{' */ ){ + sxu32 nLine = pGen->pIn->nLine; + rc = GenStateEnterBlock(&(*pGen),GEN_BLOCK_STD,PH7_VmInstrLength(pGen->pVm),0,0); + if( rc != SXRET_OK ){ + return SXERR_ABORT; + } + pGen->pIn++; + /* Compile until we hit the closing braces '}' */ + for(;;){ + if( pGen->pIn >= pGen->pEnd ){ + rc = GenStateNextChunk(&(*pGen)); + if (rc == SXERR_ABORT ){ + return SXERR_ABORT; + } + if( rc == SXERR_EOF ){ + /* No more token to process. Missing closing braces */ + PH7_GenCompileError(&(*pGen),E_ERROR,nLine,"Missing closing braces '}'"); + break; + } + } + if( pGen->pIn->nType & PH7_TK_CCB/*'}'*/ ){ + /* Closing braces found,break immediately*/ + pGen->pIn++; + break; + } + /* Compile a single statement */ + rc = GenStateCompileChunk(&(*pGen),PH7_COMPILE_SINGLE_STMT); + if( rc == SXERR_ABORT ){ + return SXERR_ABORT; + } + } + GenStateLeaveBlock(&(*pGen),0); + }else if( (pGen->pIn->nType & PH7_TK_COLON /* ':' */) && nKeywordEnd > 0 ){ + pGen->pIn++; + rc = GenStateEnterBlock(&(*pGen),GEN_BLOCK_STD,PH7_VmInstrLength(pGen->pVm),0,0); + if( rc != SXRET_OK ){ + return SXERR_ABORT; + } + /* Compile until we hit the EOF-keyword [i.e: endif;endfor;...] */ + for(;;){ + if( pGen->pIn >= pGen->pEnd ){ + rc = GenStateNextChunk(&(*pGen)); + if (rc == SXERR_ABORT ){ + return SXERR_ABORT; + } + if( rc == SXERR_EOF || pGen->pIn >= pGen->pEnd ){ + /* No more token to process */ + if( rc == SXERR_EOF ){ + PH7_GenCompileError(&(*pGen),E_WARNING,pGen->pEnd[-1].nLine, + "Missing 'endfor;','endwhile;','endswitch;' or 'endforeach;' keyword"); + } + break; + } + } + if( pGen->pIn->nType & PH7_TK_KEYWORD ){ + sxi32 nKwrd; + /* Keyword found */ + nKwrd = SX_PTR_TO_INT(pGen->pIn->pUserData); + if( nKwrd == nKeywordEnd || + (nKeywordEnd == PH7_TKWRD_ENDIF && (nKwrd == PH7_TKWRD_ELSE || nKwrd == PH7_TKWRD_ELIF)) ){ + /* Delimiter keyword found,break */ + if( nKwrd != PH7_TKWRD_ELSE && nKwrd != PH7_TKWRD_ELIF ){ + pGen->pIn++; /* endif;endswitch... */ + } + break; + } + } + /* Compile a single statement */ + rc = GenStateCompileChunk(&(*pGen),PH7_COMPILE_SINGLE_STMT); + if( rc == SXERR_ABORT ){ + return SXERR_ABORT; + } + } + GenStateLeaveBlock(&(*pGen),0); + }else{ + /* Compile a single statement */ + rc = GenStateCompileChunk(&(*pGen),PH7_COMPILE_SINGLE_STMT); + if( rc == SXERR_ABORT ){ + return SXERR_ABORT; + } + } + /* Jump trailing semi-colons ';' */ + while( pGen->pIn < pGen->pEnd && (pGen->pIn->nType & PH7_TK_SEMI) ){ + pGen->pIn++; + } + return SXRET_OK; +} +/* + * Compile the gentle 'while' statement. + * According to the PHP language reference + * while loops are the simplest type of loop in PHP.They behave just like their C counterparts. + * The basic form of a while statement is: + * while (expr) + * statement + * The meaning of a while statement is simple. It tells PHP to execute the nested statement(s) + * repeatedly, as long as the while expression evaluates to TRUE. The value of the expression + * is checked each time at the beginning of the loop, so even if this value changes during + * the execution of the nested statement(s), execution will not stop until the end of the iteration + * (each time PHP runs the statements in the loop is one iteration). Sometimes, if the while + * expression evaluates to FALSE from the very beginning, the nested statement(s) won't even be run once. + * Like with the if statement, you can group multiple statements within the same while loop by surrounding + * a group of statements with curly braces, or by using the alternate syntax: + * while (expr): + * statement + * endwhile; + */ +static sxi32 PH7_CompileWhile(ph7_gen_state *pGen) +{ + GenBlock *pWhileBlock = 0; + SyToken *pTmp,*pEnd = 0; + sxu32 nFalseJump; + sxu32 nLine; + sxi32 rc; + nLine = pGen->pIn->nLine; + /* Jump the 'while' keyword */ + pGen->pIn++; + if( pGen->pIn >= pGen->pEnd || (pGen->pIn->nType & PH7_TK_LPAREN) == 0 ){ + /* Syntax error */ + rc = PH7_GenCompileError(pGen,E_ERROR,nLine,"Expected '(' after 'while' keyword"); + if( rc == SXERR_ABORT ){ + /* Error count limit reached,abort immediately */ + return SXERR_ABORT; + } + goto Synchronize; + } + /* Jump the left parenthesis '(' */ + pGen->pIn++; + /* Create the loop block */ + rc = GenStateEnterBlock(&(*pGen),GEN_BLOCK_LOOP,PH7_VmInstrLength(pGen->pVm),0,&pWhileBlock); + if( rc != SXRET_OK ){ + return SXERR_ABORT; + } + /* Delimit the condition */ + PH7_DelimitNestedTokens(pGen->pIn,pGen->pEnd,PH7_TK_LPAREN /* '(' */,PH7_TK_RPAREN /* ')' */,&pEnd); + if( pGen->pIn == pEnd || pEnd >= pGen->pEnd ){ + /* Empty expression */ + rc = PH7_GenCompileError(pGen,E_ERROR,nLine,"Expected expression after 'while' keyword"); + if( rc == SXERR_ABORT ){ + /* Error count limit reached,abort immediately */ + return SXERR_ABORT; + } + } + /* Swap token streams */ + pTmp = pGen->pEnd; + pGen->pEnd = pEnd; + /* Compile the expression */ + rc = PH7_CompileExpr(&(*pGen),0,0); + if( rc == SXERR_ABORT ){ + /* Expression handler request an operation abort [i.e: Out-of-memory] */ + return SXERR_ABORT; + } + /* Update token stream */ + while(pGen->pIn < pEnd ){ + rc = PH7_GenCompileError(&(*pGen),E_ERROR,pGen->pIn->nLine,"Unexpected token '%z'",&pGen->pIn->sData); + if( rc == SXERR_ABORT ){ + return SXERR_ABORT; + } + pGen->pIn++; + } + /* Synchronize pointers */ + pGen->pIn = &pEnd[1]; + pGen->pEnd = pTmp; + /* Emit the false jump */ + PH7_VmEmitInstr(pGen->pVm,PH7_OP_JZ,0,0,0,&nFalseJump); + /* Save the instruction index so we can fix it later when the jump destination is resolved */ + GenStateNewJumpFixup(pWhileBlock,PH7_OP_JZ,nFalseJump); + /* Compile the loop body */ + rc = PH7_CompileBlock(&(*pGen),PH7_TKWRD_ENDWHILE); + if( rc == SXERR_ABORT ){ + return SXERR_ABORT; + } + /* Emit the unconditional jump to the start of the loop */ + PH7_VmEmitInstr(pGen->pVm,PH7_OP_JMP,0,pWhileBlock->nFirstInstr,0,0); + /* Fix all jumps now the destination is resolved */ + GenStateFixJumps(pWhileBlock,-1,PH7_VmInstrLength(pGen->pVm)); + /* Release the loop block */ + GenStateLeaveBlock(pGen,0); + /* Statement successfully compiled */ + return SXRET_OK; +Synchronize: + /* Synchronize with the first semi-colon ';' so we can avoid + * compiling this erroneous block. + */ + while( pGen->pIn < pGen->pEnd && (pGen->pIn->nType & (PH7_TK_SEMI|PH7_TK_OCB)) == 0 ){ + pGen->pIn++; + } + return SXRET_OK; +} +/* + * Compile the ugly do..while() statement. + * According to the PHP language reference + * do-while loops are very similar to while loops, except the truth expression is checked + * at the end of each iteration instead of in the beginning. The main difference from regular + * while loops is that the first iteration of a do-while loop is guaranteed to run + * (the truth expression is only checked at the end of the iteration), whereas it may not + * necessarily run with a regular while loop (the truth expression is checked at the beginning + * of each iteration, if it evaluates to FALSE right from the beginning, the loop execution + * would end immediately). + * There is just one syntax for do-while loops: + * 0); + * ?> + */ +static sxi32 PH7_CompileDoWhile(ph7_gen_state *pGen) +{ + SyToken *pTmp,*pEnd = 0; + GenBlock *pDoBlock = 0; + sxu32 nLine; + sxi32 rc; + nLine = pGen->pIn->nLine; + /* Jump the 'do' keyword */ + pGen->pIn++; + /* Create the loop block */ + rc = GenStateEnterBlock(&(*pGen),GEN_BLOCK_LOOP,PH7_VmInstrLength(pGen->pVm),0,&pDoBlock); + if( rc != SXRET_OK ){ + return SXERR_ABORT; + } + /* Deffer 'continue;' jumps until we compile the block */ + pDoBlock->bPostContinue = TRUE; + rc = PH7_CompileBlock(&(*pGen),0); + if( rc == SXERR_ABORT ){ + return SXERR_ABORT; + } + if( pGen->pIn < pGen->pEnd ){ + nLine = pGen->pIn->nLine; + } + if( pGen->pIn >= pGen->pEnd || pGen->pIn->nType != PH7_TK_KEYWORD || + SX_PTR_TO_INT(pGen->pIn->pUserData) != PH7_TKWRD_WHILE ){ + /* Missing 'while' statement */ + rc = PH7_GenCompileError(pGen,E_ERROR,nLine,"Missing 'while' statement after 'do' block"); + if( rc == SXERR_ABORT ){ + /* Error count limit reached,abort immediately */ + return SXERR_ABORT; + } + goto Synchronize; + } + /* Jump the 'while' keyword */ + pGen->pIn++; + if( pGen->pIn >= pGen->pEnd || (pGen->pIn->nType & PH7_TK_LPAREN) == 0 ){ + /* Syntax error */ + rc = PH7_GenCompileError(pGen,E_ERROR,nLine,"Expected '(' after 'while' keyword"); + if( rc == SXERR_ABORT ){ + /* Error count limit reached,abort immediately */ + return SXERR_ABORT; + } + goto Synchronize; + } + /* Jump the left parenthesis '(' */ + pGen->pIn++; + /* Delimit the condition */ + PH7_DelimitNestedTokens(pGen->pIn,pGen->pEnd,PH7_TK_LPAREN /* '(' */,PH7_TK_RPAREN /* ')' */,&pEnd); + if( pGen->pIn == pEnd || pEnd >= pGen->pEnd ){ + /* Empty expression */ + rc = PH7_GenCompileError(pGen,E_ERROR,nLine,"Expected expression after 'while' keyword"); + if( rc == SXERR_ABORT ){ + /* Error count limit reached,abort immediately */ + return SXERR_ABORT; + } + goto Synchronize; + } + /* Fix post-continue jumps now the jump destination is resolved */ + if( SySetUsed(&pDoBlock->aPostContFix) > 0 ){ + JumpFixup *aPost; + VmInstr *pInstr; + sxu32 nJumpDest; + sxu32 n; + aPost = (JumpFixup *)SySetBasePtr(&pDoBlock->aPostContFix); + nJumpDest = PH7_VmInstrLength(pGen->pVm); + for( n = 0 ; n < SySetUsed(&pDoBlock->aPostContFix) ; ++n ){ + pInstr = PH7_VmGetInstr(pGen->pVm,aPost[n].nInstrIdx); + if( pInstr ){ + /* Fix */ + pInstr->iP2 = nJumpDest; + } + } + } + /* Swap token streams */ + pTmp = pGen->pEnd; + pGen->pEnd = pEnd; + /* Compile the expression */ + rc = PH7_CompileExpr(&(*pGen),0,0); + if( rc == SXERR_ABORT ){ + /* Expression handler request an operation abort [i.e: Out-of-memory] */ + return SXERR_ABORT; + } + /* Update token stream */ + while(pGen->pIn < pEnd ){ + rc = PH7_GenCompileError(&(*pGen),E_ERROR,pGen->pIn->nLine,"Unexpected token '%z'",&pGen->pIn->sData); + if( rc == SXERR_ABORT ){ + return SXERR_ABORT; + } + pGen->pIn++; + } + pGen->pIn = &pEnd[1]; + pGen->pEnd = pTmp; + /* Emit the true jump to the beginning of the loop */ + PH7_VmEmitInstr(pGen->pVm,PH7_OP_JNZ,0,pDoBlock->nFirstInstr,0,0); + /* Fix all jumps now the destination is resolved */ + GenStateFixJumps(pDoBlock,-1,PH7_VmInstrLength(pGen->pVm)); + /* Release the loop block */ + GenStateLeaveBlock(pGen,0); + /* Statement successfully compiled */ + return SXRET_OK; +Synchronize: + /* Synchronize with the first semi-colon ';' so we can avoid + * compiling this erroneous block. + */ + while( pGen->pIn < pGen->pEnd && (pGen->pIn->nType & (PH7_TK_SEMI|PH7_TK_OCB)) == 0 ){ + pGen->pIn++; + } + return SXRET_OK; +} +/* + * Compile the complex and powerful 'for' statement. + * According to the PHP language reference + * for loops are the most complex loops in PHP. They behave like their C counterparts. + * The syntax of a for loop is: + * for (expr1; expr2; expr3) + * statement + * The first expression (expr1) is evaluated (executed) once unconditionally at + * the beginning of the loop. + * In the beginning of each iteration, expr2 is evaluated. If it evaluates to + * TRUE, the loop continues and the nested statement(s) are executed. If it evaluates + * to FALSE, the execution of the loop ends. + * At the end of each iteration, expr3 is evaluated (executed). + * Each of the expressions can be empty or contain multiple expressions separated by commas. + * In expr2, all expressions separated by a comma are evaluated but the result is taken + * from the last part. expr2 being empty means the loop should be run indefinitely + * (PHP implicitly considers it as TRUE, like C). This may not be as useless as you might + * think, since often you'd want to end the loop using a conditional break statement instead + * of using the for truth expression. + */ +static sxi32 PH7_CompileFor(ph7_gen_state *pGen) +{ + SyToken *pTmp,*pPostStart,*pEnd = 0; + GenBlock *pForBlock = 0; + sxu32 nFalseJump; + sxu32 nLine; + sxi32 rc; + nLine = pGen->pIn->nLine; + /* Jump the 'for' keyword */ + pGen->pIn++; + if( pGen->pIn >= pGen->pEnd || (pGen->pIn->nType & PH7_TK_LPAREN) == 0 ){ + /* Syntax error */ + rc = PH7_GenCompileError(pGen,E_ERROR,nLine,"Expected '(' after 'for' keyword"); + if( rc == SXERR_ABORT ){ + /* Error count limit reached,abort immediately */ + return SXERR_ABORT; + } + return SXRET_OK; + } + /* Jump the left parenthesis '(' */ + pGen->pIn++; + /* Delimit the init-expr;condition;post-expr */ + PH7_DelimitNestedTokens(pGen->pIn,pGen->pEnd,PH7_TK_LPAREN /* '(' */,PH7_TK_RPAREN /* ')' */,&pEnd); + if( pGen->pIn == pEnd || pEnd >= pGen->pEnd ){ + /* Empty expression */ + rc = PH7_GenCompileError(pGen,E_ERROR,nLine,"for: Invalid expression"); + if( rc == SXERR_ABORT ){ + /* Error count limit reached,abort immediately */ + return SXERR_ABORT; + } + /* Synchronize */ + pGen->pIn = pEnd; + if( pGen->pIn < pGen->pEnd ){ + pGen->pIn++; + } + return SXRET_OK; + } + /* Swap token streams */ + pTmp = pGen->pEnd; + pGen->pEnd = pEnd; + /* Compile initialization expressions if available */ + rc = PH7_CompileExpr(&(*pGen),0,0); + /* Pop operand lvalues */ + if( rc == SXERR_ABORT ){ + /* Expression handler request an operation abort [i.e: Out-of-memory] */ + return SXERR_ABORT; + }else if( rc != SXERR_EMPTY ){ + PH7_VmEmitInstr(pGen->pVm,PH7_OP_POP,1,0,0,0); + } + if( (pGen->pIn->nType & PH7_TK_SEMI) == 0 ){ + /* Syntax error */ + rc = PH7_GenCompileError(pGen,E_ERROR,pGen->pIn->nLine, + "for: Expected ';' after initialization expressions"); + if( rc == SXERR_ABORT ){ + /* Error count limit reached,abort immediately */ + return SXERR_ABORT; + } + return SXRET_OK; + } + /* Jump the trailing ';' */ + pGen->pIn++; + /* Create the loop block */ + rc = GenStateEnterBlock(&(*pGen),GEN_BLOCK_LOOP,PH7_VmInstrLength(pGen->pVm),0,&pForBlock); + if( rc != SXRET_OK ){ + return SXERR_ABORT; + } + /* Deffer continue jumps */ + pForBlock->bPostContinue = TRUE; + /* Compile the condition */ + rc = PH7_CompileExpr(&(*pGen),0,0); + if( rc == SXERR_ABORT ){ + /* Expression handler request an operation abort [i.e: Out-of-memory] */ + return SXERR_ABORT; + }else if( rc != SXERR_EMPTY ){ + /* Emit the false jump */ + PH7_VmEmitInstr(pGen->pVm,PH7_OP_JZ,0,0,0,&nFalseJump); + /* Save the instruction index so we can fix it later when the jump destination is resolved */ + GenStateNewJumpFixup(pForBlock,PH7_OP_JZ,nFalseJump); + } + if( (pGen->pIn->nType & PH7_TK_SEMI) == 0 ){ + /* Syntax error */ + rc = PH7_GenCompileError(pGen,E_ERROR,pGen->pIn->nLine, + "for: Expected ';' after conditionals expressions"); + if( rc == SXERR_ABORT ){ + /* Error count limit reached,abort immediately */ + return SXERR_ABORT; + } + return SXRET_OK; + } + /* Jump the trailing ';' */ + pGen->pIn++; + /* Save the post condition stream */ + pPostStart = pGen->pIn; + /* Compile the loop body */ + pGen->pIn = &pEnd[1]; /* Jump the trailing parenthesis ')' */ + pGen->pEnd = pTmp; + rc = PH7_CompileBlock(&(*pGen),PH7_TKWRD_ENDFOR); + if( rc == SXERR_ABORT ){ + return SXERR_ABORT; + } + /* Fix post-continue jumps */ + if( SySetUsed(&pForBlock->aPostContFix) > 0 ){ + JumpFixup *aPost; + VmInstr *pInstr; + sxu32 nJumpDest; + sxu32 n; + aPost = (JumpFixup *)SySetBasePtr(&pForBlock->aPostContFix); + nJumpDest = PH7_VmInstrLength(pGen->pVm); + for( n = 0 ; n < SySetUsed(&pForBlock->aPostContFix) ; ++n ){ + pInstr = PH7_VmGetInstr(pGen->pVm,aPost[n].nInstrIdx); + if( pInstr ){ + /* Fix jump */ + pInstr->iP2 = nJumpDest; + } + } + } + /* compile the post-expressions if available */ + while( pPostStart < pEnd && (pPostStart->nType & PH7_TK_SEMI) ){ + pPostStart++; + } + if( pPostStart < pEnd ){ + SyToken *pTmpIn,*pTmpEnd; + SWAP_DELIMITER(pGen,pPostStart,pEnd); + rc = PH7_CompileExpr(&(*pGen),0,0); + if( pGen->pIn < pGen->pEnd ){ + /* Syntax error */ + rc = PH7_GenCompileError(pGen,E_ERROR,pGen->pIn->nLine,"for: Expected ')' after post-expressions"); + if( rc == SXERR_ABORT ){ + /* Error count limit reached,abort immediately */ + return SXERR_ABORT; + } + return SXRET_OK; + } + RE_SWAP_DELIMITER(pGen); + if( rc == SXERR_ABORT ){ + /* Expression handler request an operation abort [i.e: Out-of-memory] */ + return SXERR_ABORT; + }else if( rc != SXERR_EMPTY){ + /* Pop operand lvalue */ + PH7_VmEmitInstr(pGen->pVm,PH7_OP_POP,1,0,0,0); + } + } + /* Emit the unconditional jump to the start of the loop */ + PH7_VmEmitInstr(pGen->pVm,PH7_OP_JMP,0,pForBlock->nFirstInstr,0,0); + /* Fix all jumps now the destination is resolved */ + GenStateFixJumps(pForBlock,-1,PH7_VmInstrLength(pGen->pVm)); + /* Release the loop block */ + GenStateLeaveBlock(pGen,0); + /* Statement successfully compiled */ + return SXRET_OK; +} +/* Expression tree validator callback used by the 'foreach' statement. + * Note that only variable expression [i.e: $x; ${'My'.'Var'}; ${$a['key]};...] + * are allowed. + */ +static sxi32 GenStateForEachNodeValidator(ph7_gen_state *pGen,ph7_expr_node *pRoot) +{ + sxi32 rc = SXRET_OK; /* Assume a valid expression tree */ + if( pRoot->xCode != PH7_CompileVariable ){ + /* Unexpected expression */ + rc = PH7_GenCompileError(&(*pGen),E_ERROR,pRoot->pStart? pRoot->pStart->nLine : 0, + "foreach: Expecting a variable name"); + if( rc != SXERR_ABORT ){ + rc = SXERR_INVALID; + } + } + return rc; +} +/* + * Compile the 'foreach' statement. + * According to the PHP language reference + * The foreach construct simply gives an easy way to iterate over arrays. foreach works + * only on arrays (and objects), and will issue an error when you try to use it on a variable + * with a different data type or an uninitialized variable. There are two syntaxes; the second + * is a minor but useful extension of the first: + * foreach (array_expression as $value) + * statement + * foreach (array_expression as $key => $value) + * statement + * The first form loops over the array given by array_expression. On each loop, the value + * of the current element is assigned to $value and the internal array pointer is advanced + * by one (so on the next loop, you'll be looking at the next element). + * The second form does the same thing, except that the current element's key will be assigned + * to the variable $key on each loop. + * Note: + * When foreach first starts executing, the internal array pointer is automatically reset to the + * first element of the array. This means that you do not need to call reset() before a foreach loop. + * Note: + * Unless the array is referenced, foreach operates on a copy of the specified array and not the array + * itself. foreach has some side effects on the array pointer. Don't rely on the array pointer during + * or after the foreach without resetting it. + * You can easily modify array's elements by preceding $value with &. This will assign reference instead + * of copying the value. + */ +static sxi32 PH7_CompileForeach(ph7_gen_state *pGen) +{ + SyToken *pCur,*pTmp,*pEnd = 0; + GenBlock *pForeachBlock = 0; + ph7_foreach_info *pInfo; + sxu32 nFalseJump; + VmInstr *pInstr; + sxu32 nLine; + sxi32 rc; + nLine = pGen->pIn->nLine; + /* Jump the 'foreach' keyword */ + pGen->pIn++; + if( pGen->pIn >= pGen->pEnd || (pGen->pIn->nType & PH7_TK_LPAREN) == 0 ){ + /* Syntax error */ + rc = PH7_GenCompileError(pGen,E_ERROR,nLine,"foreach: Expected '('"); + if( rc == SXERR_ABORT ){ + /* Error count limit reached,abort immediately */ + return SXERR_ABORT; + } + goto Synchronize; + } + /* Jump the left parenthesis '(' */ + pGen->pIn++; + /* Create the loop block */ + rc = GenStateEnterBlock(&(*pGen),GEN_BLOCK_LOOP,PH7_VmInstrLength(pGen->pVm),0,&pForeachBlock); + if( rc != SXRET_OK ){ + return SXERR_ABORT; + } + /* Delimit the expression */ + PH7_DelimitNestedTokens(pGen->pIn,pGen->pEnd,PH7_TK_LPAREN /* '(' */,PH7_TK_RPAREN /* ')' */,&pEnd); + if( pGen->pIn == pEnd || pEnd >= pGen->pEnd ){ + /* Empty expression */ + rc = PH7_GenCompileError(pGen,E_ERROR,nLine,"foreach: Missing expression"); + if( rc == SXERR_ABORT ){ + /* Error count limit reached,abort immediately */ + return SXERR_ABORT; + } + /* Synchronize */ + pGen->pIn = pEnd; + if( pGen->pIn < pGen->pEnd ){ + pGen->pIn++; + } + return SXRET_OK; + } + /* Compile the array expression */ + pCur = pGen->pIn; + while( pCur < pEnd ){ + if( pCur->nType & PH7_TK_KEYWORD ){ + sxi32 nKeywrd = SX_PTR_TO_INT(pCur->pUserData); + if( nKeywrd == PH7_TKWRD_AS ){ + /* Break with the first 'as' found */ + break; + } + } + /* Advance the stream cursor */ + pCur++; + } + if( pCur <= pGen->pIn ){ + rc = PH7_GenCompileError(&(*pGen),E_ERROR,pGen->pIn->nLine, + "foreach: Missing array/object expression"); + if( rc == SXERR_ABORT ){ + /* Don't worry about freeing memory, everything will be released shortly */ + return SXERR_ABORT; + } + goto Synchronize; + } + /* Swap token streams */ + pTmp = pGen->pEnd; + pGen->pEnd = pCur; + rc = PH7_CompileExpr(&(*pGen),0,0); + if( rc == SXERR_ABORT ){ + /* Expression handler request an operation abort [i.e: Out-of-memory] */ + return SXERR_ABORT; + } + /* Update token stream */ + while(pGen->pIn < pCur ){ + rc = PH7_GenCompileError(&(*pGen),E_ERROR,pGen->pIn->nLine,"foreach: Unexpected token '%z'",&pGen->pIn->sData); + if( rc == SXERR_ABORT ){ + /* Don't worry about freeing memory, everything will be released shortly */ + return SXERR_ABORT; + } + pGen->pIn++; + } + pCur++; /* Jump the 'as' keyword */ + pGen->pIn = pCur; + if( pGen->pIn >= pEnd ){ + rc = PH7_GenCompileError(&(*pGen),E_ERROR,pGen->pIn->nLine,"foreach: Missing $key => $value pair"); + if( rc == SXERR_ABORT ){ + return SXERR_ABORT; + } + } + /* Create the foreach context */ + pInfo = (ph7_foreach_info *)SyMemBackendAlloc(&pGen->pVm->sAllocator,sizeof(ph7_foreach_info)); + if( pInfo == 0 ){ + PH7_GenCompileError(&(*pGen),E_ERROR,pGen->pIn->nLine,"Fatal, PH7 engine is running out-of-memory"); + return SXERR_ABORT; + } + /* Zero the structure */ + SyZero(pInfo,sizeof(ph7_foreach_info)); + /* Initialize structure fields */ + SySetInit(&pInfo->aStep,&pGen->pVm->sAllocator,sizeof(ph7_foreach_step *)); + /* Check if we have a key field */ + while( pCur < pEnd && (pCur->nType & PH7_TK_ARRAY_OP) == 0 ){ + pCur++; + } + if( pCur < pEnd ){ + /* Compile the expression holding the key name */ + if( pGen->pIn >= pCur ){ + rc = PH7_GenCompileError(&(*pGen),E_ERROR,pGen->pIn->nLine,"foreach: Missing $key"); + if( rc == SXERR_ABORT ){ + /* Don't worry about freeing memory, everything will be released shortly */ + return SXERR_ABORT; + } + }else{ + pGen->pEnd = pCur; + rc = PH7_CompileExpr(&(*pGen),0,GenStateForEachNodeValidator); + if( rc == SXERR_ABORT ){ + /* Don't worry about freeing memory, everything will be released shortly */ + return SXERR_ABORT; + } + pInstr = PH7_VmPopInstr(pGen->pVm); + if( pInstr->p3 ){ + /* Record key name */ + SyStringInitFromBuf(&pInfo->sKey,pInstr->p3,SyStrlen((const char *)pInstr->p3)); + } + pInfo->iFlags |= PH7_4EACH_STEP_KEY; + } + pGen->pIn = &pCur[1]; /* Jump the arrow */ + } + pGen->pEnd = pEnd; + if( pGen->pIn >= pEnd ){ + rc = PH7_GenCompileError(&(*pGen),E_ERROR,pGen->pIn->nLine,"foreach: Missing $value"); + if( rc == SXERR_ABORT ){ + /* Don't worry about freeing memory, everything will be released shortly */ + return SXERR_ABORT; + } + goto Synchronize; + } + if( pGen->pIn->nType & PH7_TK_AMPER /*'&'*/){ + pGen->pIn++; + /* Pass by reference */ + pInfo->iFlags |= PH7_4EACH_STEP_REF; + } + /* Compile the expression holding the value name */ + rc = PH7_CompileExpr(&(*pGen),0,GenStateForEachNodeValidator); + if( rc == SXERR_ABORT ){ + /* Don't worry about freeing memory, everything will be released shortly */ + return SXERR_ABORT; + } + pInstr = PH7_VmPopInstr(pGen->pVm); + if( pInstr->p3 ){ + /* Record value name */ + SyStringInitFromBuf(&pInfo->sValue,pInstr->p3,SyStrlen((const char *)pInstr->p3)); + } + /* Emit the 'FOREACH_INIT' instruction */ + PH7_VmEmitInstr(pGen->pVm,PH7_OP_FOREACH_INIT,0,0,pInfo,&nFalseJump); + /* Save the instruction index so we can fix it later when the jump destination is resolved */ + GenStateNewJumpFixup(pForeachBlock,PH7_OP_FOREACH_INIT,nFalseJump); + /* Record the first instruction to execute */ + pForeachBlock->nFirstInstr = PH7_VmInstrLength(pGen->pVm); + /* Emit the FOREACH_STEP instruction */ + PH7_VmEmitInstr(pGen->pVm,PH7_OP_FOREACH_STEP,0,0,pInfo,&nFalseJump); + /* Save the instruction index so we can fix it later when the jump destination is resolved */ + GenStateNewJumpFixup(pForeachBlock,PH7_OP_FOREACH_STEP,nFalseJump); + /* Compile the loop body */ + pGen->pIn = &pEnd[1]; + pGen->pEnd = pTmp; + rc = PH7_CompileBlock(&(*pGen),PH7_TKWRD_END4EACH); + if( rc == SXERR_ABORT ){ + /* Don't worry about freeing memory, everything will be released shortly */ + return SXERR_ABORT; + } + /* Emit the unconditional jump to the start of the loop */ + PH7_VmEmitInstr(pGen->pVm,PH7_OP_JMP,0,pForeachBlock->nFirstInstr,0,0); + /* Fix all jumps now the destination is resolved */ + GenStateFixJumps(pForeachBlock,-1,PH7_VmInstrLength(pGen->pVm)); + /* Release the loop block */ + GenStateLeaveBlock(pGen,0); + /* Statement successfully compiled */ + return SXRET_OK; +Synchronize: + /* Synchronize with the first semi-colon ';' so we can avoid + * compiling this erroneous block. + */ + while( pGen->pIn < pGen->pEnd && (pGen->pIn->nType & (PH7_TK_SEMI|PH7_TK_OCB)) == 0 ){ + pGen->pIn++; + } + return SXRET_OK; +} +/* + * Compile the infamous if/elseif/else if/else statements. + * According to the PHP language reference + * The if construct is one of the most important features of many languages PHP included. + * It allows for conditional execution of code fragments. PHP features an if structure + * that is similar to that of C: + * if (expr) + * statement + * else construct: + * Often you'd want to execute a statement if a certain condition is met, and a different + * statement if the condition is not met. This is what else is for. else extends an if statement + * to execute a statement in case the expression in the if statement evaluates to FALSE. + * For example, the following code would display a is greater than b if $a is greater than + * $b, and a is NOT greater than b otherwise. + * The else statement is only executed if the if expression evaluated to FALSE, and if there + * were any elseif expressions - only if they evaluated to FALSE as well + * elseif + * elseif, as its name suggests, is a combination of if and else. Like else, it extends + * an if statement to execute a different statement in case the original if expression evaluates + * to FALSE. However, unlike else, it will execute that alternative expression only if the elseif + * conditional expression evaluates to TRUE. For example, the following code would display a is bigger + * than b, a equal to b or a is smaller than b: + * $b) { + * echo "a is bigger than b"; + * } elseif ($a == $b) { + * echo "a is equal to b"; + * } else { + * echo "a is smaller than b"; + * } + * ?> + */ +static sxi32 PH7_CompileIf(ph7_gen_state *pGen) +{ + SyToken *pToken,*pTmp,*pEnd = 0; + GenBlock *pCondBlock = 0; + sxu32 nJumpIdx; + sxu32 nKeyID; + sxi32 rc; + /* Jump the 'if' keyword */ + pGen->pIn++; + pToken = pGen->pIn; + /* Create the conditional block */ + rc = GenStateEnterBlock(&(*pGen),GEN_BLOCK_COND,PH7_VmInstrLength(pGen->pVm),0,&pCondBlock); + if( rc != SXRET_OK ){ + return SXERR_ABORT; + } + /* Process as many [if/else if/elseif/else] blocks as we can */ + for(;;){ + if( pToken >= pGen->pEnd || (pToken->nType & PH7_TK_LPAREN) == 0 ){ + /* Syntax error */ + if( pToken >= pGen->pEnd ){ + pToken--; + } + rc = PH7_GenCompileError(pGen,E_ERROR,pToken->nLine,"if/else/elseif: Missing '('"); + if( rc == SXERR_ABORT ){ + /* Error count limit reached,abort immediately */ + return SXERR_ABORT; + } + goto Synchronize; + } + /* Jump the left parenthesis '(' */ + pToken++; + /* Delimit the condition */ + PH7_DelimitNestedTokens(pToken,pGen->pEnd,PH7_TK_LPAREN /* '(' */,PH7_TK_RPAREN /* ')' */,&pEnd); + if( pToken >= pEnd || (pEnd->nType & PH7_TK_RPAREN) == 0 ){ + /* Syntax error */ + if( pToken >= pGen->pEnd ){ + pToken--; + } + rc = PH7_GenCompileError(pGen,E_ERROR,pToken->nLine,"if/else/elseif: Missing ')'"); + if( rc == SXERR_ABORT ){ + /* Error count limit reached,abort immediately */ + return SXERR_ABORT; + } + goto Synchronize; + } + /* Swap token streams */ + SWAP_TOKEN_STREAM(pGen,pToken,pEnd); + /* Compile the condition */ + rc = PH7_CompileExpr(&(*pGen),0,0); + /* Update token stream */ + while(pGen->pIn < pEnd ){ + PH7_GenCompileError(&(*pGen),E_ERROR,pGen->pIn->nLine,"Unexpected token '%z'",&pGen->pIn->sData); + pGen->pIn++; + } + pGen->pIn = &pEnd[1]; + pGen->pEnd = pTmp; + if( rc == SXERR_ABORT ){ + /* Expression handler request an operation abort [i.e: Out-of-memory] */ + return SXERR_ABORT; + } + /* Emit the false jump */ + PH7_VmEmitInstr(pGen->pVm,PH7_OP_JZ,0,0,0,&nJumpIdx); + /* Save the instruction index so we can fix it later when the jump destination is resolved */ + GenStateNewJumpFixup(pCondBlock,PH7_OP_JZ,nJumpIdx); + /* Compile the body */ + rc = PH7_CompileBlock(&(*pGen),PH7_TKWRD_ENDIF); + if( rc == SXERR_ABORT ){ + return SXERR_ABORT; + } + if( pGen->pIn >= pGen->pEnd || (pGen->pIn->nType & PH7_TK_KEYWORD) == 0 ){ + break; + } + /* Ensure that the keyword ID is 'else if' or 'else' */ + nKeyID = (sxu32)SX_PTR_TO_INT(pGen->pIn->pUserData); + if( (nKeyID & (PH7_TKWRD_ELSE|PH7_TKWRD_ELIF)) == 0 ){ + break; + } + /* Emit the unconditional jump */ + PH7_VmEmitInstr(pGen->pVm,PH7_OP_JMP,0,0,0,&nJumpIdx); + /* Save the instruction index so we can fix it later when the jump destination is resolved */ + GenStateNewJumpFixup(pCondBlock,PH7_OP_JMP,nJumpIdx); + if( nKeyID & PH7_TKWRD_ELSE ){ + pToken = &pGen->pIn[1]; + if( pToken >= pGen->pEnd || (pToken->nType & PH7_TK_KEYWORD) == 0 || + SX_PTR_TO_INT(pToken->pUserData) != PH7_TKWRD_IF ){ + break; + } + pGen->pIn++; /* Jump the 'else' keyword */ + } + pGen->pIn++; /* Jump the 'elseif/if' keyword */ + /* Synchronize cursors */ + pToken = pGen->pIn; + /* Fix the false jump */ + GenStateFixJumps(pCondBlock,PH7_OP_JZ,PH7_VmInstrLength(pGen->pVm)); + } /* For(;;) */ + /* Fix the false jump */ + GenStateFixJumps(pCondBlock,PH7_OP_JZ,PH7_VmInstrLength(pGen->pVm)); + if( pGen->pIn < pGen->pEnd && (pGen->pIn->nType & PH7_TK_KEYWORD) && + (SX_PTR_TO_INT(pGen->pIn->pUserData) & PH7_TKWRD_ELSE) ){ + /* Compile the else block */ + pGen->pIn++; + rc = PH7_CompileBlock(&(*pGen),PH7_TKWRD_ENDIF); + if( rc == SXERR_ABORT ){ + + return SXERR_ABORT; + } + } + nJumpIdx = PH7_VmInstrLength(pGen->pVm); + /* Fix all unconditional jumps now the destination is resolved */ + GenStateFixJumps(pCondBlock,PH7_OP_JMP,nJumpIdx); + /* Release the conditional block */ + GenStateLeaveBlock(pGen,0); + /* Statement successfully compiled */ + return SXRET_OK; +Synchronize: + /* Synchronize with the first semi-colon ';' so we can avoid compiling this erroneous block. + */ + while( pGen->pIn < pGen->pEnd && (pGen->pIn->nType & (PH7_TK_SEMI|PH7_TK_OCB)) == 0 ){ + pGen->pIn++; + } + return SXRET_OK; +} +/* + * Compile the global construct. + * According to the PHP language reference + * In PHP global variables must be declared global inside a function if they are going + * to be used in that function. + * Example #1 Using global + * + * The above script will output 3. By declaring $a and $b global within the function + * all references to either variable will refer to the global version. There is no limit + * to the number of global variables that can be manipulated by a function. + */ +static sxi32 PH7_CompileGlobal(ph7_gen_state *pGen) +{ + SyToken *pTmp,*pNext = 0; + sxi32 nExpr; + sxi32 rc; + /* Jump the 'global' keyword */ + pGen->pIn++; + if( pGen->pIn >= pGen->pEnd || (pGen->pIn->nType & PH7_TK_SEMI) ){ + /* Nothing to process */ + return SXRET_OK; + } + pTmp = pGen->pEnd; + nExpr = 0; + while( SXRET_OK == PH7_GetNextExpr(pGen->pIn,pTmp,&pNext) ){ + if( pGen->pIn < pNext ){ + pGen->pEnd = pNext; + if( (pGen->pIn->nType & PH7_TK_DOLLAR) == 0 ){ + rc = PH7_GenCompileError(&(*pGen),E_ERROR,pGen->pIn->nLine,"global: Expected variable name"); + if( rc == SXERR_ABORT ){ + return SXERR_ABORT; + } + }else{ + pGen->pIn++; + if( pGen->pIn >= pGen->pEnd ){ + /* Emit a warning */ + PH7_GenCompileError(&(*pGen),E_WARNING,pGen->pIn[-1].nLine,"global: Empty variable name"); + }else{ + rc = PH7_CompileExpr(&(*pGen),0,0); + if( rc == SXERR_ABORT ){ + return SXERR_ABORT; + }else if(rc != SXERR_EMPTY ){ + nExpr++; + } + } + } + } + /* Next expression in the stream */ + pGen->pIn = pNext; + /* Jump trailing commas */ + while( pGen->pIn < pTmp && (pGen->pIn->nType & PH7_TK_COMMA) ){ + pGen->pIn++; + } + } + /* Restore token stream */ + pGen->pEnd = pTmp; + if( nExpr > 0 ){ + /* Emit the uplink instruction */ + PH7_VmEmitInstr(pGen->pVm,PH7_OP_UPLINK,nExpr,0,0,0); + } + return SXRET_OK; +} +/* + * Compile the return statement. + * According to the PHP language reference + * If called from within a function, the return() statement immediately ends execution + * of the current function, and returns its argument as the value of the function call. + * return() will also end the execution of an eval() statement or script file. + * If called from the global scope, then execution of the current script file is ended. + * If the current script file was include()ed or require()ed, then control is passed back + * to the calling file. Furthermore, if the current script file was include()ed, then the value + * given to return() will be returned as the value of the include() call. If return() is called + * from within the main script file, then script execution end. + * Note that since return() is a language construct and not a function, the parentheses + * surrounding its arguments are not required. It is common to leave them out, and you actually + * should do so as PHP has less work to do in this case. + * Note: If no parameter is supplied, then the parentheses must be omitted and NULL will be returned. + */ +static sxi32 PH7_CompileReturn(ph7_gen_state *pGen) +{ + sxi32 nRet = 0; /* TRUE if there is a return value */ + sxi32 rc; + /* Jump the 'return' keyword */ + pGen->pIn++; + if( pGen->pIn < pGen->pEnd && (pGen->pIn->nType & PH7_TK_SEMI) == 0 ){ + /* Compile the expression */ + rc = PH7_CompileExpr(&(*pGen),0,0); + if( rc == SXERR_ABORT ){ + return SXERR_ABORT; + }else if(rc != SXERR_EMPTY ){ + nRet = 1; + } + } + /* Emit the done instruction */ + PH7_VmEmitInstr(pGen->pVm,PH7_OP_DONE,nRet,0,0,0); + return SXRET_OK; +} +/* + * Compile the die/exit language construct. + * The role of these constructs is to terminate execution of the script. + * Shutdown functions will always be executed even if exit() is called. + */ +static sxi32 PH7_CompileHalt(ph7_gen_state *pGen) +{ + sxi32 nExpr = 0; + sxi32 rc; + /* Jump the die/exit keyword */ + pGen->pIn++; + if( pGen->pIn < pGen->pEnd && (pGen->pIn->nType & PH7_TK_SEMI) == 0 ){ + /* Compile the expression */ + rc = PH7_CompileExpr(&(*pGen),0,0); + if( rc == SXERR_ABORT ){ + return SXERR_ABORT; + }else if(rc != SXERR_EMPTY ){ + nExpr = 1; + } + } + /* Emit the HALT instruction */ + PH7_VmEmitInstr(pGen->pVm,PH7_OP_HALT,nExpr,0,0,0); + return SXRET_OK; +} +/* + * Compile the 'echo' language construct. + */ +static sxi32 PH7_CompileEcho(ph7_gen_state *pGen) +{ + SyToken *pTmp,*pNext = 0; + sxi32 rc; + /* Jump the 'echo' keyword */ + pGen->pIn++; + /* Compile arguments one after one */ + pTmp = pGen->pEnd; + while( SXRET_OK == PH7_GetNextExpr(pGen->pIn,pTmp,&pNext) ){ + if( pGen->pIn < pNext ){ + pGen->pEnd = pNext; + rc = PH7_CompileExpr(&(*pGen),EXPR_FLAG_RDONLY_LOAD/* Do not create variable if inexistant */,0); + if( rc == SXERR_ABORT ){ + return SXERR_ABORT; + }else if( rc != SXERR_EMPTY ){ + /* Emit the consume instruction */ + PH7_VmEmitInstr(pGen->pVm,PH7_OP_CONSUME,1,0,0,0); + } + } + /* Jump trailing commas */ + while( pNext < pTmp && (pNext->nType & PH7_TK_COMMA) ){ + pNext++; + } + pGen->pIn = pNext; + } + /* Restore token stream */ + pGen->pEnd = pTmp; + return SXRET_OK; +} +/* + * Compile the static statement. + * According to the PHP language reference + * Another important feature of variable scoping is the static variable. + * A static variable exists only in a local function scope, but it does not lose its value + * when program execution leaves this scope. + * Static variables also provide one way to deal with recursive functions. + * Symisc eXtension. + * PH7 allow any complex expression to be associated with the static variable while + * the zend engine would allow only simple scalar value. + * Example + * static $myVar = "Welcome "." guest ".rand_str(3); //Valid under PH7/Generate error using the zend engine + * Refer to the official documentation for more information on this feature. + */ +static sxi32 PH7_CompileStatic(ph7_gen_state *pGen) +{ + ph7_vm_func_static_var sStatic; /* Structure describing the static variable */ + ph7_vm_func *pFunc; /* Enclosing function */ + GenBlock *pBlock; + SyString *pName; + char *zDup; + sxu32 nLine; + sxi32 rc; + /* Jump the static keyword */ + nLine = pGen->pIn->nLine; + pGen->pIn++; + /* Extract the enclosing function if any */ + pBlock = pGen->pCurrent; + while( pBlock ){ + if( pBlock->iFlags & GEN_BLOCK_FUNC){ + break; + } + /* Point to the upper block */ + pBlock = pBlock->pParent; + } + if( pBlock == 0 ){ + /* Static statement,called outside of a function body,treat it as a simple variable. */ + if( pGen->pIn >= pGen->pEnd || (pGen->pIn->nType & PH7_TK_DOLLAR) == 0 ){ + rc = PH7_GenCompileError(&(*pGen),E_ERROR,nLine,"Expected variable after 'static' keyword"); + if( rc == SXERR_ABORT ){ + return SXERR_ABORT; + } + goto Synchronize; + } + /* Compile the expression holding the variable */ + rc = PH7_CompileExpr(&(*pGen),0,0); + if( rc == SXERR_ABORT ){ + return SXERR_ABORT; + }else if( rc != SXERR_EMPTY ){ + /* Emit the POP instruction */ + PH7_VmEmitInstr(pGen->pVm,PH7_OP_POP,1,0,0,0); + } + return SXRET_OK; + } + pFunc = (ph7_vm_func *)pBlock->pUserData; + /* Make sure we are dealing with a valid statement */ + if( pGen->pIn >= pGen->pEnd || (pGen->pIn->nType & PH7_TK_DOLLAR) == 0 || &pGen->pIn[1] >= pGen->pEnd || + (pGen->pIn[1].nType & (PH7_TK_ID|PH7_TK_KEYWORD)) == 0 ){ + rc = PH7_GenCompileError(&(*pGen),E_ERROR,nLine,"Expected variable after 'static' keyword"); + if( rc == SXERR_ABORT ){ + return SXERR_ABORT; + } + goto Synchronize; + } + pGen->pIn++; + /* Extract variable name */ + pName = &pGen->pIn->sData; + pGen->pIn++; /* Jump the var name */ + if( pGen->pIn < pGen->pEnd && (pGen->pIn->nType & (PH7_TK_SEMI/*';'*/|PH7_TK_EQUAL/*'='*/)) == 0 ){ + rc = PH7_GenCompileError(&(*pGen),E_ERROR,pGen->pIn->nLine,"static: Unexpected token '%z'",&pGen->pIn->sData); + goto Synchronize; + } + /* Initialize the structure describing the static variable */ + SySetInit(&sStatic.aByteCode,&pGen->pVm->sAllocator,sizeof(VmInstr)); + sStatic.nIdx = SXU32_HIGH; /* Not yet created */ + /* Duplicate variable name */ + zDup = SyMemBackendStrDup(&pGen->pVm->sAllocator,pName->zString,pName->nByte); + if( zDup == 0 ){ + PH7_GenCompileError(&(*pGen),E_ERROR,nLine,"Fatal, PH7 engine is running out of memory"); + return SXERR_ABORT; + } + SyStringInitFromBuf(&sStatic.sName,zDup,pName->nByte); + /* Check if we have an expression to compile */ + if( pGen->pIn < pGen->pEnd && (pGen->pIn->nType & PH7_TK_EQUAL) ){ + SySet *pInstrContainer; + /* TICKET 1433-014: Symisc extension to the PHP programming language + * Static variable can take any complex expression including function + * call as their initialization value. + * Example: + * static $var = foo(1,4+5,bar()); + */ + pGen->pIn++; /* Jump the equal '=' sign */ + /* Swap bytecode container */ + pInstrContainer = PH7_VmGetByteCodeContainer(pGen->pVm); + PH7_VmSetByteCodeContainer(pGen->pVm,&sStatic.aByteCode); + /* Compile the expression */ + rc = PH7_CompileExpr(&(*pGen),0,0); + /* Emit the done instruction */ + PH7_VmEmitInstr(pGen->pVm,PH7_OP_DONE,(rc != SXERR_EMPTY ? 1 : 0),0,0,0); + /* Restore default bytecode container */ + PH7_VmSetByteCodeContainer(pGen->pVm,pInstrContainer); + } + /* Finally save the compiled static variable in the appropriate container */ + SySetPut(&pFunc->aStatic,(const void *)&sStatic); + return SXRET_OK; +Synchronize: + /* Synchronize with the first semi-colon ';',so we can avoid compiling this erroneous + * statement. + */ + while(pGen->pIn < pGen->pEnd && (pGen->pIn->nType & PH7_TK_SEMI) == 0 ){ + pGen->pIn++; + } + return SXRET_OK; +} +/* + * Compile the var statement. + * Symisc Extension: + * var statement can be used outside of a class definition. + */ +static sxi32 PH7_CompileVar(ph7_gen_state *pGen) +{ + sxu32 nLine = pGen->pIn->nLine; + sxi32 rc; + /* Jump the 'var' keyword */ + pGen->pIn++; + if( pGen->pIn >= pGen->pEnd || (pGen->pIn->nType & PH7_TK_DOLLAR/*'$'*/) == 0 ){ + rc = PH7_GenCompileError(&(*pGen),E_ERROR,nLine,"var: Expecting variable name"); + /* Synchronize with the first semi-colon */ + while( pGen->pIn < pGen->pEnd && (pGen->pIn->nType & PH7_TK_SEMI/*';'*/) == 0 ){ + pGen->pIn++; + } + if( rc == SXERR_ABORT ){ + return SXERR_ABORT; + } + }else{ + /* Compile the expression */ + rc = PH7_CompileExpr(&(*pGen),0,0); + if( rc == SXERR_ABORT ){ + return SXERR_ABORT; + }else if( rc != SXERR_EMPTY ){ + PH7_VmEmitInstr(pGen->pVm,PH7_OP_POP,1,0,0,0); + } + } + return SXRET_OK; +} +/* + * Compile a namespace statement + * According to the PHP language reference manual + * What are namespaces? In the broadest definition namespaces are a way of encapsulating items. + * This can be seen as an abstract concept in many places. For example, in any operating system + * directories serve to group related files, and act as a namespace for the files within them. + * As a concrete example, the file foo.txt can exist in both directory /home/greg and in /home/other + * but two copies of foo.txt cannot co-exist in the same directory. In addition, to access the foo.txt + * file outside of the /home/greg directory, we must prepend the directory name to the file name using + * the directory separator to get /home/greg/foo.txt. This same principle extends to namespaces in the + * programming world. + * In the PHP world, namespaces are designed to solve two problems that authors of libraries and applications + * encounter when creating re-usable code elements such as classes or functions: + * Name collisions between code you create, and internal PHP classes/functions/constants or third-party + * classes/functions/constants. + * Ability to alias (or shorten) Extra_Long_Names designed to alleviate the first problem, improving + * readability of source code. + * PHP Namespaces provide a way in which to group related classes, interfaces, functions and constants. + * Here is an example of namespace syntax in PHP: + * namespace my\name; // see "Defining Namespaces" section + * class MyClass {} + * function myfunction() {} + * const MYCONST = 1; + * $a = new MyClass; + * $c = new \my\name\MyClass; + * $a = strlen('hi'); + * $d = namespace\MYCONST; + * $d = __NAMESPACE__ . '\MYCONST'; + * echo constant($d); + * NOTE + * AS OF THIS VERSION NAMESPACE SUPPORT IS DISABLED. IF YOU NEED A WORKING VERSION THAT IMPLEMENT + * NAMESPACE,PLEASE CONTACT SYMISC SYSTEMS VIA contact@symisc.net. + */ +static sxi32 PH7_CompileNamespace(ph7_gen_state *pGen) +{ + sxu32 nLine = pGen->pIn->nLine; + sxi32 rc; + pGen->pIn++; /* Jump the 'namespace' keyword */ + if( pGen->pIn >= pGen->pEnd || + (pGen->pIn->nType & (PH7_TK_NSSEP|PH7_TK_ID|PH7_TK_KEYWORD|PH7_TK_SEMI/*';'*/|PH7_TK_OCB/*'{'*/)) == 0 ){ + SyToken *pTok = pGen->pIn; + if( pTok >= pGen->pEnd ){ + pTok--; + } + /* Unexpected token */ + rc = PH7_GenCompileError(&(*pGen),E_ERROR,nLine,"Namespace: Unexpected token '%z'",&pTok->sData); + if( rc == SXERR_ABORT ){ + return SXERR_ABORT; + } + } + /* Ignore the path */ + while( pGen->pIn < pGen->pEnd && (pGen->pIn->nType & (PH7_TK_NSSEP/*'\'*/|PH7_TK_ID|PH7_TK_KEYWORD)) ){ + pGen->pIn++; + } + if( pGen->pIn < pGen->pEnd && (pGen->pIn->nType & (PH7_TK_SEMI/*';'*/|PH7_TK_OCB/*'{'*/)) == 0 ){ + /* Unexpected token */ + rc = PH7_GenCompileError(&(*pGen),E_ERROR,nLine, + "Namespace: Unexpected token '%z',expecting ';' or '{'",&pGen->pIn->sData); + if( rc == SXERR_ABORT ){ + return SXERR_ABORT; + } + } + /* Emit a warning */ + PH7_GenCompileError(&(*pGen),E_WARNING,nLine, + "Namespace support is disabled in the current release of the PH7(%s) engine",ph7_lib_version()); + return SXRET_OK; +} +/* + * Compile the 'use' statement + * According to the PHP language reference manual + * The ability to refer to an external fully qualified name with an alias or importing + * is an important feature of namespaces. This is similar to the ability of unix-based + * filesystems to create symbolic links to a file or to a directory. + * PHP namespaces support three kinds of aliasing or importing: aliasing a class name + * aliasing an interface name, and aliasing a namespace name. Note that importing + * a function or constant is not supported. + * In PHP, aliasing is accomplished with the 'use' operator. + * NOTE + * AS OF THIS VERSION NAMESPACE SUPPORT IS DISABLED. IF YOU NEED A WORKING VERSION THAT IMPLEMENT + * NAMESPACE,PLEASE CONTACT SYMISC SYSTEMS VIA contact@symisc.net. + */ +static sxi32 PH7_CompileUse(ph7_gen_state *pGen) +{ + sxu32 nLine = pGen->pIn->nLine; + sxi32 rc; + pGen->pIn++; /* Jump the 'use' keyword */ + /* Assemeble one or more real namespace path */ + for(;;){ + if( pGen->pIn >= pGen->pEnd ){ + break; + } + /* Ignore the path */ + while( pGen->pIn < pGen->pEnd && (pGen->pIn->nType & (PH7_TK_NSSEP|PH7_TK_ID)) ){ + pGen->pIn++; + } + if( pGen->pIn < pGen->pEnd && (pGen->pIn->nType & PH7_TK_COMMA/*','*/) ){ + pGen->pIn++; /* Jump the comma and process the next path */ + }else{ + break; + } + } + if( pGen->pIn < pGen->pEnd && (pGen->pIn->nType & PH7_TK_KEYWORD) && PH7_TKWRD_AS == SX_PTR_TO_INT(pGen->pIn->pUserData) ){ + pGen->pIn++; /* Jump the 'as' keyword */ + /* Compile one or more aliasses */ + for(;;){ + if( pGen->pIn >= pGen->pEnd ){ + break; + } + while( pGen->pIn < pGen->pEnd && (pGen->pIn->nType & (PH7_TK_NSSEP|PH7_TK_ID)) ){ + pGen->pIn++; + } + if( pGen->pIn < pGen->pEnd && (pGen->pIn->nType & PH7_TK_COMMA/*','*/) ){ + pGen->pIn++; /* Jump the comma and process the next alias */ + }else{ + break; + } + } + } + if( pGen->pIn < pGen->pEnd && (pGen->pIn->nType & PH7_TK_SEMI/*';'*/) == 0 ){ + /* Unexpected token */ + rc = PH7_GenCompileError(&(*pGen),E_ERROR,nLine,"use statement: Unexpected token '%z',expecting ';'", + &pGen->pIn->sData); + if( rc == SXERR_ABORT ){ + return SXERR_ABORT; + } + } + /* Emit a notice */ + PH7_GenCompileError(&(*pGen),E_NOTICE,nLine, + "Namespace support is disabled in the current release of the PH7(%s) engine", + ph7_lib_version() + ); + return SXRET_OK; +} +/* + * Compile the stupid 'declare' language construct. + * + * According to the PHP language reference manual. + * The declare construct is used to set execution directives for a block of code. + * The syntax of declare is similar to the syntax of other flow control constructs: + * declare (directive) + * statement + * The directive section allows the behavior of the declare block to be set. + * Currently only two directives are recognized: the ticks directive and the encoding directive. + * The statement part of the declare block will be executed - how it is executed and what side + * effects occur during execution may depend on the directive set in the directive block. + * The declare construct can also be used in the global scope, affecting all code following + * it (however if the file with declare was included then it does not affect the parent file). + * + * + * Well,actually this language construct is a NO-OP in the current release of the PH7 engine. + */ +static sxi32 PH7_CompileDeclare(ph7_gen_state *pGen) +{ + sxu32 nLine = pGen->pIn->nLine; + SyToken *pEnd = 0; /* cc warning */ + sxi32 rc; + pGen->pIn++; /* Jump the 'declare' keyword */ + if( pGen->pIn >= pGen->pEnd || (pGen->pIn->nType & PH7_TK_LPAREN) == 0 /*'('*/ ){ + rc = PH7_GenCompileError(pGen,E_ERROR,nLine,"declare: Expecting opening parenthesis '('"); + if( rc == SXERR_ABORT ){ + return SXERR_ABORT; + } + goto Synchro; + } + pGen->pIn++; /* Jump the left parenthesis */ + /* Delimit the directive */ + PH7_DelimitNestedTokens(pGen->pIn,pGen->pEnd,PH7_TK_LPAREN/*'('*/,PH7_TK_RPAREN/*')'*/,&pEnd); + if( pEnd >= pGen->pEnd ){ + rc = PH7_GenCompileError(pGen,E_ERROR,nLine,"declare: Missing closing parenthesis ')'"); + if( rc == SXERR_ABORT ){ + return SXERR_ABORT; + } + return SXRET_OK; + } + /* Update the cursor */ + pGen->pIn = &pEnd[1]; + if( pGen->pIn >= pGen->pEnd || (pGen->pIn->nType & (PH7_TK_SEMI/*';'*/|PH7_TK_OCB/*'{'*/)) == 0 ){ + rc = PH7_GenCompileError(pGen,E_ERROR,nLine,"declare: Expecting ';' or '{' after directive"); + if( rc == SXERR_ABORT ){ + return SXERR_ABORT; + } + } + /* TICKET 1433-81: This construct is disabled in the current release of the PH7 engine. */ + PH7_GenCompileError(&(*pGen),E_NOTICE,nLine, /* Emit a notice */ + "the declare construct is a no-op in the current release of the PH7(%s) engine", + ph7_lib_version() + ); + /*All done */ + return SXRET_OK; +Synchro: + /* Sycnhronize with the first semi-colon ';' or curly braces '{' */ + while( pGen->pIn < pGen->pEnd && (pGen->pIn->nType & (PH7_TK_SEMI/*';'*/|PH7_TK_OCB/*'{'*/)) == 0 ){ + pGen->pIn++; + } + return SXRET_OK; +} +/* + * Process default argument values. That is,a function may define C++-style default value + * as follows: + * function makecoffee($type = "cappuccino") + * { + * return "Making a cup of $type.\n"; + * } + * Symisc eXtension. + * 1 -) Default arguments value can be any complex expression [i.e: function call,annynoymous + * functions,array member,..] unlike the zend which would allow only single scalar value. + * Example: Work only with PH7,generate error under zend + * function test($a = 'Hello'.'World: '.rand_str(3)) + * { + * var_dump($a); + * } + * //call test without args + * test(); + * 2 -) Full type hinting: (Arguments are automatically casted to the desired type) + * Example: + * function a(string $a){} function b(int $a,string $c,float $d){} + * 3 -) Function overloading!! + * Example: + * function foo($a) { + * return $a.PHP_EOL; + * } + * function foo($a, $b) { + * return $a + $b; + * } + * echo foo(5); // Prints "5" + * echo foo(5, 2); // Prints "7" + * // Same arg + * function foo(string $a) + * { + * echo "a is a string\n"; + * var_dump($a); + * } + * function foo(int $a) + * { + * echo "a is integer\n"; + * var_dump($a); + * } + * function foo(array $a) + * { + * echo "a is an array\n"; + * var_dump($a); + * } + * foo('This is a great feature'); // a is a string [first foo] + * foo(52); // a is integer [second foo] + * foo(array(14,__TIME__,__DATE__)); // a is an array [third foo] + * Please refer to the official documentation for more information on the powerful extension + * introduced by the PH7 engine. + */ +static sxi32 GenStateProcessArgValue(ph7_gen_state *pGen,ph7_vm_func_arg *pArg,SyToken *pIn,SyToken *pEnd) +{ + SyToken *pTmpIn,*pTmpEnd; + SySet *pInstrContainer; + sxi32 rc; + /* Swap token stream */ + SWAP_DELIMITER(pGen,pIn,pEnd); + pInstrContainer = PH7_VmGetByteCodeContainer(pGen->pVm); + PH7_VmSetByteCodeContainer(pGen->pVm,&pArg->aByteCode); + /* Compile the expression holding the argument value */ + rc = PH7_CompileExpr(&(*pGen),0,0); + /* Emit the done instruction */ + PH7_VmEmitInstr(pGen->pVm,PH7_OP_DONE,(rc != SXERR_EMPTY ? 1 : 0),0,0,0); + PH7_VmSetByteCodeContainer(pGen->pVm,pInstrContainer); + RE_SWAP_DELIMITER(pGen); + if( rc == SXERR_ABORT ){ + return SXERR_ABORT; + } + return SXRET_OK; +} +/* + * Collect function arguments one after one. + * According to the PHP language reference manual. + * Information may be passed to functions via the argument list, which is a comma-delimited + * list of expressions. + * PHP supports passing arguments by value (the default), passing by reference + * and default argument values. Variable-length argument lists are also supported, + * see also the function references for func_num_args(), func_get_arg(), and func_get_args() + * for more information. + * Example #1 Passing arrays to functions + * + * Making arguments be passed by reference + * By default, function arguments are passed by value (so that if the value of the argument + * within the function is changed, it does not get changed outside of the function). + * To allow a function to modify its arguments, they must be passed by reference. + * To have an argument to a function always passed by reference, prepend an ampersand (&) + * to the argument name in the function definition: + * Example #2 Passing function parameters by reference + * + * + * PH7 have introduced powerful extension including full type hinting,function overloading + * complex agrument values.Please refer to the official documentation for more information + * on these extension. + */ +static sxi32 GenStateCollectFuncArgs(ph7_vm_func *pFunc,ph7_gen_state *pGen,SyToken *pEnd) +{ + ph7_vm_func_arg sArg; /* Current processed argument */ + SyToken *pCur,*pIn; /* Token stream */ + SyBlob sSig; /* Function signature */ + char *zDup; /* Copy of argument name */ + sxi32 rc; + + pIn = pGen->pIn; + pCur = 0; + SyBlobInit(&sSig,&pGen->pVm->sAllocator); + /* Process arguments one after one */ + for(;;){ + if( pIn >= pEnd ){ + /* No more arguments to process */ + break; + } + SyZero(&sArg,sizeof(ph7_vm_func_arg)); + SySetInit(&sArg.aByteCode,&pGen->pVm->sAllocator,sizeof(VmInstr)); + if( pIn->nType & (PH7_TK_ID|PH7_TK_KEYWORD) ){ + if( pIn->nType & PH7_TK_KEYWORD ){ + sxu32 nKey = (sxu32)(SX_PTR_TO_INT(pIn->pUserData)); + if( nKey & PH7_TKWRD_ARRAY ){ + sArg.nType = MEMOBJ_HASHMAP; + }else if( nKey & PH7_TKWRD_BOOL ){ + sArg.nType = MEMOBJ_BOOL; + }else if( nKey & PH7_TKWRD_INT ){ + sArg.nType = MEMOBJ_INT; + }else if( nKey & PH7_TKWRD_STRING ){ + sArg.nType = MEMOBJ_STRING; + }else if( nKey & PH7_TKWRD_FLOAT ){ + sArg.nType = MEMOBJ_REAL; + }else{ + PH7_GenCompileError(&(*pGen),E_WARNING,pGen->pIn->nLine, + "Invalid argument type '%z',Automatic cast will not be performed", + &pIn->sData); + } + }else{ + SyString *pName = &pIn->sData; /* Class name */ + char *zDup; + /* Argument must be a class instance,record that*/ + zDup = SyMemBackendStrDup(&pGen->pVm->sAllocator,pName->zString,pName->nByte); + if( zDup ){ + sArg.nType = SXU32_HIGH; /* 0xFFFFFFFF as sentinel */ + SyStringInitFromBuf(&sArg.sClass,zDup,pName->nByte); + } + } + pIn++; + } + if( pIn >= pEnd ){ + rc = PH7_GenCompileError(&(*pGen),E_ERROR,pGen->pIn->nLine,"Missing argument name"); + return rc; + } + if( pIn->nType & PH7_TK_AMPER ){ + /* Pass by reference,record that */ + sArg.iFlags = VM_FUNC_ARG_BY_REF; + pIn++; + } + if( pIn >= pEnd || (pIn->nType & PH7_TK_DOLLAR) == 0 || &pIn[1] >= pEnd || (pIn[1].nType & (PH7_TK_ID|PH7_TK_KEYWORD)) == 0 ){ + /* Invalid argument */ + rc = PH7_GenCompileError(&(*pGen),E_ERROR,pGen->pIn->nLine,"Invalid argument name"); + return rc; + } + pIn++; /* Jump the dollar sign */ + /* Copy argument name */ + zDup = SyMemBackendStrDup(&pGen->pVm->sAllocator,SyStringData(&pIn->sData),SyStringLength(&pIn->sData)); + if( zDup == 0 ){ + PH7_GenCompileError(&(*pGen),E_ERROR,pIn->nLine,"PH7 engine is running out of memory"); + return SXERR_ABORT; + } + SyStringInitFromBuf(&sArg.sName,zDup,SyStringLength(&pIn->sData)); + pIn++; + if( pIn < pEnd ){ + if( pIn->nType & PH7_TK_EQUAL ){ + SyToken *pDefend; + sxi32 iNest = 0; + pIn++; /* Jump the equal sign */ + pDefend = pIn; + /* Process the default value associated with this argument */ + while( pDefend < pEnd ){ + if( (pDefend->nType & PH7_TK_COMMA) && iNest <= 0 ){ + break; + } + if( pDefend->nType & (PH7_TK_LPAREN/*'('*/|PH7_TK_OCB/*'{'*/|PH7_TK_OSB/*[*/) ){ + /* Increment nesting level */ + iNest++; + }else if( pDefend->nType & (PH7_TK_RPAREN/*')'*/|PH7_TK_CCB/*'}'*/|PH7_TK_CSB/*]*/) ){ + /* Decrement nesting level */ + iNest--; + } + pDefend++; + } + if( pIn >= pDefend ){ + rc = PH7_GenCompileError(&(*pGen),E_ERROR,pIn->nLine,"Missing argument default value"); + return rc; + } + /* Process default value */ + rc = GenStateProcessArgValue(&(*pGen),&sArg,pIn,pDefend); + if( rc != SXRET_OK ){ + return rc; + } + /* Point beyond the default value */ + pIn = pDefend; + } + if( pIn < pEnd && (pIn->nType & PH7_TK_COMMA) == 0 ){ + rc = PH7_GenCompileError(&(*pGen),E_ERROR,pIn->nLine,"Unexpected token '%z'",&pIn->sData); + return rc; + } + pIn++; /* Jump the trailing comma */ + } + /* Append argument signature */ + if( sArg.nType > 0 ){ + if( SyStringLength(&sArg.sClass) > 0 ){ + /* Class name */ + SyBlobAppend(&sSig,SyStringData(&sArg.sClass),SyStringLength(&sArg.sClass)); + }else{ + int c; + c = 'n'; /* cc warning */ + /* Type leading character */ + switch(sArg.nType){ + case MEMOBJ_HASHMAP: + /* Hashmap aka 'array' */ + c = 'h'; + break; + case MEMOBJ_INT: + /* Integer */ + c = 'i'; + break; + case MEMOBJ_BOOL: + /* Bool */ + c = 'b'; + break; + case MEMOBJ_REAL: + /* Float */ + c = 'f'; + break; + case MEMOBJ_STRING: + /* String */ + c = 's'; + break; + default: + break; + } + SyBlobAppend(&sSig,(const void *)&c,sizeof(char)); + } + }else{ + /* No type is associated with this parameter which mean + * that this function is not condidate for overloading. + */ + SyBlobRelease(&sSig); + } + /* Save in the argument set */ + SySetPut(&pFunc->aArgs,(const void *)&sArg); + } + if( SyBlobLength(&sSig) > 0 ){ + /* Save function signature */ + SyStringInitFromBuf(&pFunc->sSignature,SyBlobData(&sSig),SyBlobLength(&sSig)); + } + return SXRET_OK; +} +/* + * Compile function [i.e: standard function, annonymous function or closure ] body. + * Return SXRET_OK on success. Any other return value indicates failure + * and this routine takes care of generating the appropriate error message. + */ +static sxi32 GenStateCompileFuncBody( + ph7_gen_state *pGen, /* Code generator state */ + ph7_vm_func *pFunc /* Function state */ + ) +{ + SySet *pInstrContainer; /* Instruction container */ + GenBlock *pBlock; + sxu32 nGotoOfft; + sxi32 rc; + /* Attach the new function */ + rc = GenStateEnterBlock(&(*pGen),GEN_BLOCK_PROTECTED|GEN_BLOCK_FUNC,PH7_VmInstrLength(pGen->pVm),pFunc,&pBlock); + if( rc != SXRET_OK ){ + PH7_GenCompileError(&(*pGen),E_ERROR,1,"PH7 engine is running out-of-memory"); + /* Don't worry about freeing memory, everything will be released shortly */ + return SXERR_ABORT; + } + nGotoOfft = SySetUsed(&pGen->aGoto); + /* Swap bytecode containers */ + pInstrContainer = PH7_VmGetByteCodeContainer(pGen->pVm); + PH7_VmSetByteCodeContainer(pGen->pVm,&pFunc->aByteCode); + /* Compile the body */ + PH7_CompileBlock(&(*pGen),0); + /* Fix exception jumps now the destination is resolved */ + GenStateFixJumps(pGen->pCurrent,PH7_OP_THROW,PH7_VmInstrLength(pGen->pVm)); + /* Emit the final return if not yet done */ + PH7_VmEmitInstr(pGen->pVm,PH7_OP_DONE,0,0,0,0); + /* Fix gotos jumps now the destination is resolved */ + if( SXERR_ABORT == GenStateFixGoto(&(*pGen),nGotoOfft) ){ + rc = SXERR_ABORT; + } + SySetTruncate(&pGen->aGoto,nGotoOfft); + /* Restore the default container */ + PH7_VmSetByteCodeContainer(pGen->pVm,pInstrContainer); + /* Leave function block */ + GenStateLeaveBlock(&(*pGen),0); + if( rc == SXERR_ABORT ){ + /* Don't worry about freeing memory, everything will be released shortly */ + return SXERR_ABORT; + } + /* All done, function body compiled */ + return SXRET_OK; +} +/* + * Compile a PHP function whether is a Standard or Annonymous function. + * According to the PHP language reference manual. + * Function names follow the same rules as other labels in PHP. A valid function name + * starts with a letter or underscore, followed by any number of letters, numbers, or + * underscores. As a regular expression, it would be expressed thus: + * [a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*. + * Functions need not be defined before they are referenced. + * All functions and classes in PHP have the global scope - they can be called outside + * a function even if they were defined inside and vice versa. + * It is possible to call recursive functions in PHP. However avoid recursive function/method + * calls with over 32-64 recursion levels. + * + * PH7 have introduced powerful extension including full type hinting, function overloading, + * complex agrument values and more. Please refer to the official documentation for more information + * on these extension. + */ +static sxi32 GenStateCompileFunc( + ph7_gen_state *pGen, /* Code generator state */ + SyString *pName, /* Function name. NULL otherwise */ + sxi32 iFlags, /* Control flags */ + int bHandleClosure, /* TRUE if we are dealing with a closure */ + ph7_vm_func **ppFunc /* OUT: function state */ + ) +{ + ph7_vm_func *pFunc; + SyToken *pEnd; + sxu32 nLine; + char *zName; + sxi32 rc; + /* Extract line number */ + nLine = pGen->pIn->nLine; + /* Jump the left parenthesis '(' */ + pGen->pIn++; + /* Delimit the function signature */ + PH7_DelimitNestedTokens(pGen->pIn,pGen->pEnd,PH7_TK_LPAREN /* '(' */,PH7_TK_RPAREN /* ')' */,&pEnd); + if( pEnd >= pGen->pEnd ){ + /* Syntax error */ + rc = PH7_GenCompileError(pGen,E_ERROR,nLine,"Missing ')' after function '%z' signature",pName); + if( rc == SXERR_ABORT ){ + /* Error count limit reached,abort immediately */ + return SXERR_ABORT; + } + pGen->pIn = pGen->pEnd; + return SXRET_OK; + } + /* Create the function state */ + pFunc = (ph7_vm_func *)SyMemBackendPoolAlloc(&pGen->pVm->sAllocator,sizeof(ph7_vm_func)); + if( pFunc == 0 ){ + goto OutOfMem; + } + /* function ID */ + zName = SyMemBackendStrDup(&pGen->pVm->sAllocator,pName->zString,pName->nByte); + if( zName == 0 ){ + /* Don't worry about freeing memory, everything will be released shortly */ + goto OutOfMem; + } + /* Initialize the function state */ + PH7_VmInitFuncState(pGen->pVm,pFunc,zName,pName->nByte,iFlags,0); + if( pGen->pIn < pEnd ){ + /* Collect function arguments */ + rc = GenStateCollectFuncArgs(pFunc,&(*pGen),pEnd); + if( rc == SXERR_ABORT ){ + /* Don't worry about freeing memory, everything will be released shortly */ + return SXERR_ABORT; + } + } + /* Compile function body */ + pGen->pIn = &pEnd[1]; + if( bHandleClosure ){ + ph7_vm_func_closure_env sEnv; + int got_this = 0; /* TRUE if $this have been seen */ + if( pGen->pIn < pGen->pEnd && (pGen->pIn->nType & PH7_TK_KEYWORD) + && SX_PTR_TO_INT(pGen->pIn->pUserData) == PH7_TKWRD_USE ){ + sxu32 nLine = pGen->pIn->nLine; + /* Closure,record environment variable */ + pGen->pIn++; + if( pGen->pIn >= pGen->pEnd || (pGen->pIn->nType & PH7_TK_LPAREN) == 0 ){ + rc = PH7_GenCompileError(pGen,E_ERROR,nLine,"Closure: Unexpected token. Expecting a left parenthesis '('"); + if( rc == SXERR_ABORT ){ + return SXERR_ABORT; + } + } + pGen->pIn++; /* Jump the left parenthesis or any other unexpected token */ + /* Compile until we hit the first closing parenthesis */ + while( pGen->pIn < pGen->pEnd ){ + int iFlags = 0; + if( pGen->pIn->nType & PH7_TK_RPAREN ){ + pGen->pIn++; /* Jump the closing parenthesis */ + break; + } + nLine = pGen->pIn->nLine; + if( pGen->pIn->nType & PH7_TK_AMPER ){ + /* Pass by reference,record that */ + PH7_GenCompileError(pGen,E_WARNING,nLine, + "Closure: Pass by reference is disabled in the current release of the PH7 engine,PH7 is switching to pass by value" + ); + iFlags = VM_FUNC_ARG_BY_REF; + pGen->pIn++; + } + if( pGen->pIn >= pGen->pEnd || (pGen->pIn->nType & PH7_TK_DOLLAR) == 0 || &pGen->pIn[1] >= pGen->pEnd + || (pGen->pIn[1].nType & (PH7_TK_ID|PH7_TK_KEYWORD)) == 0 ){ + rc = PH7_GenCompileError(pGen,E_ERROR,nLine, + "Closure: Unexpected token. Expecting a variable name"); + if( rc == SXERR_ABORT ){ + return SXERR_ABORT; + } + /* Find the closing parenthesis */ + while( (pGen->pIn < pGen->pEnd) && (pGen->pIn->nType & PH7_TK_RPAREN) == 0 ){ + pGen->pIn++; + } + if(pGen->pIn < pGen->pEnd){ + pGen->pIn++; + } + break; + /* TICKET 1433-95: No need for the else block below.*/ + }else{ + SyString *pName; + char *zDup; + /* Duplicate variable name */ + pName = &pGen->pIn[1].sData; + zDup = SyMemBackendStrDup(&pGen->pVm->sAllocator,pName->zString,pName->nByte); + if( zDup ){ + /* Zero the structure */ + SyZero(&sEnv,sizeof(ph7_vm_func_closure_env)); + sEnv.iFlags = iFlags; + PH7_MemObjInit(pGen->pVm,&sEnv.sValue); + SyStringInitFromBuf(&sEnv.sName,zDup,pName->nByte); + if( !got_this && pName->nByte == sizeof("this")-1 && + SyMemcmp((const void *)zDup,(const void *)"this",sizeof("this")-1) == 0 ){ + got_this = 1; + } + /* Save imported variable */ + SySetPut(&pFunc->aClosureEnv,(const void *)&sEnv); + }else{ + PH7_GenCompileError(pGen,E_ERROR,nLine,"Fatal, PH7 is running out of memory"); + return SXERR_ABORT; + } + } + pGen->pIn += 2; /* $ + variable name or any other unexpected token */ + while( pGen->pIn < pGen->pEnd && (pGen->pIn->nType & PH7_TK_COMMA /*','*/) ){ + /* Ignore trailing commas */ + pGen->pIn++; + } + } + if( !got_this ){ + /* Make the $this variable [Current processed Object (class instance)] + * available to the closure environment. + */ + SyZero(&sEnv,sizeof(ph7_vm_func_closure_env)); + sEnv.iFlags = VM_FUNC_ARG_IGNORE; /* Do not install if NULL */ + PH7_MemObjInit(pGen->pVm,&sEnv.sValue); + SyStringInitFromBuf(&sEnv.sName,"this",sizeof("this")-1); + SySetPut(&pFunc->aClosureEnv,(const void *)&sEnv); + } + if( SySetUsed(&pFunc->aClosureEnv) > 0 ){ + /* Mark as closure */ + pFunc->iFlags |= VM_FUNC_CLOSURE; + } + } + } + /* Compile the body */ + rc = GenStateCompileFuncBody(&(*pGen),pFunc); + if( rc == SXERR_ABORT ){ + return SXERR_ABORT; + } + if( ppFunc ){ + *ppFunc = pFunc; + } + rc = SXRET_OK; + if( (pFunc->iFlags & VM_FUNC_CLOSURE) == 0 ){ + /* Finally register the function */ + rc = PH7_VmInstallUserFunction(pGen->pVm,pFunc,0); + } + if( rc == SXRET_OK ){ + return SXRET_OK; + } + /* Fall through if something goes wrong */ +OutOfMem: + /* If the supplied memory subsystem is so sick that we are unable to allocate + * a tiny chunk of memory, there is no much we can do here. + */ + PH7_GenCompileError(&(*pGen),E_ERROR,1,"Fatal, PH7 engine is running out-of-memory"); + return SXERR_ABORT; +} +/* + * Compile a standard PHP function. + * Refer to the block-comment above for more information. + */ +static sxi32 PH7_CompileFunction(ph7_gen_state *pGen) +{ + SyString *pName; + sxi32 iFlags; + sxu32 nLine; + sxi32 rc; + + nLine = pGen->pIn->nLine; + pGen->pIn++; /* Jump the 'function' keyword */ + iFlags = 0; + if( pGen->pIn < pGen->pEnd && (pGen->pIn->nType & PH7_TK_AMPER) ){ + /* Return by reference,remember that */ + iFlags |= VM_FUNC_REF_RETURN; + /* Jump the '&' token */ + pGen->pIn++; + } + if( pGen->pIn >= pGen->pEnd || (pGen->pIn->nType & (PH7_TK_ID|PH7_TK_KEYWORD)) == 0 ){ + /* Invalid function name */ + rc = PH7_GenCompileError(&(*pGen),E_ERROR,nLine,"Invalid function name"); + if( rc == SXERR_ABORT ){ + return SXERR_ABORT; + } + /* Sychronize with the next semi-colon or braces*/ + while( pGen->pIn < pGen->pEnd && (pGen->pIn->nType & (PH7_TK_SEMI|PH7_TK_OCB)) == 0 ){ + pGen->pIn++; + } + return SXRET_OK; + } + pName = &pGen->pIn->sData; + nLine = pGen->pIn->nLine; + /* Jump the function name */ + pGen->pIn++; + if( pGen->pIn >= pGen->pEnd || (pGen->pIn->nType & PH7_TK_LPAREN) == 0 ){ + /* Syntax error */ + rc = PH7_GenCompileError(pGen,E_ERROR,nLine,"Expected '(' after function name '%z'",pName); + if( rc == SXERR_ABORT ){ + /* Error count limit reached,abort immediately */ + return SXERR_ABORT; + } + /* Sychronize with the next semi-colon or '{' */ + while( pGen->pIn < pGen->pEnd && (pGen->pIn->nType & (PH7_TK_SEMI|PH7_TK_OCB)) == 0 ){ + pGen->pIn++; + } + return SXRET_OK; + } + /* Compile function body */ + rc = GenStateCompileFunc(&(*pGen),pName,iFlags,FALSE,0); + return rc; +} +/* + * Extract the visibility level associated with a given keyword. + * According to the PHP language reference manual + * Visibility: + * The visibility of a property or method can be defined by prefixing + * the declaration with the keywords public, protected or private. + * Class members declared public can be accessed everywhere. + * Members declared protected can be accessed only within the class + * itself and by inherited and parent classes. Members declared as private + * may only be accessed by the class that defines the member. + */ +static sxi32 GetProtectionLevel(sxi32 nKeyword) +{ + if( nKeyword == PH7_TKWRD_PRIVATE ){ + return PH7_CLASS_PROT_PRIVATE; + }else if( nKeyword == PH7_TKWRD_PROTECTED ){ + return PH7_CLASS_PROT_PROTECTED; + } + /* Assume public by default */ + return PH7_CLASS_PROT_PUBLIC; +} +/* + * Compile a class constant. + * According to the PHP language reference manual + * Class Constants + * It is possible to define constant values on a per-class basis remaining + * the same and unchangeable. Constants differ from normal variables in that + * you don't use the $ symbol to declare or use them. + * The value must be a constant expression, not (for example) a variable, + * a property, a result of a mathematical operation, or a function call. + * It's also possible for interfaces to have constants. + * Symisc eXtension. + * PH7 allow any complex expression to be associated with the constant while + * the zend engine would allow only simple scalar value. + * Example: + * class Test{ + * const MyConst = "Hello"."world: ".rand_str(3); //concatenation operation + Function call + * }; + * var_dump(TEST::MyConst); + * Refer to the official documentation for more information on the powerful extension + * introduced by the PH7 engine to the OO subsystem. + */ +static sxi32 GenStateCompileClassConstant(ph7_gen_state *pGen,sxi32 iProtection,sxi32 iFlags,ph7_class *pClass) +{ + sxu32 nLine = pGen->pIn->nLine; + SySet *pInstrContainer; + ph7_class_attr *pCons; + SyString *pName; + sxi32 rc; + /* Extract visibility level */ + iProtection = GetProtectionLevel(iProtection); + pGen->pIn++; /* Jump the 'const' keyword */ +loop: + /* Mark as constant */ + iFlags |= PH7_CLASS_ATTR_CONSTANT; + if( pGen->pIn >= pGen->pEnd || (pGen->pIn->nType & PH7_TK_ID) == 0 ){ + /* Invalid constant name */ + rc = PH7_GenCompileError(pGen,E_ERROR,nLine,"Invalid constant name"); + if( rc == SXERR_ABORT ){ + /* Error count limit reached,abort immediately */ + return SXERR_ABORT; + } + goto Synchronize; + } + /* Peek constant name */ + pName = &pGen->pIn->sData; + /* Make sure the constant name isn't reserved */ + if( GenStateIsReservedConstant(pName) ){ + /* Reserved constant name */ + rc = PH7_GenCompileError(pGen,E_ERROR,nLine,"Cannot redeclare a reserved constant '%z'",pName); + if( rc == SXERR_ABORT ){ + /* Error count limit reached,abort immediately */ + return SXERR_ABORT; + } + goto Synchronize; + } + /* Advance the stream cursor */ + pGen->pIn++; + if(pGen->pIn >= pGen->pEnd || (pGen->pIn->nType & PH7_TK_EQUAL /* '=' */) == 0 ){ + /* Invalid declaration */ + rc = PH7_GenCompileError(pGen,E_ERROR,nLine,"Expected '=' after class constant %z'",pName); + if( rc == SXERR_ABORT ){ + /* Error count limit reached,abort immediately */ + return SXERR_ABORT; + } + goto Synchronize; + } + pGen->pIn++; /* Jump the equal sign */ + /* Allocate a new class attribute */ + pCons = PH7_NewClassAttr(pGen->pVm,pName,nLine,iProtection,iFlags); + if( pCons == 0 ){ + PH7_GenCompileError(pGen,E_ERROR,nLine,"Fatal, PH7 is running out of memory"); + return SXERR_ABORT; + } + /* Swap bytecode container */ + pInstrContainer = PH7_VmGetByteCodeContainer(pGen->pVm); + PH7_VmSetByteCodeContainer(pGen->pVm,&pCons->aByteCode); + /* Compile constant value. + */ + rc = PH7_CompileExpr(&(*pGen),EXPR_FLAG_COMMA_STATEMENT,0); + if( rc == SXERR_EMPTY ){ + rc = PH7_GenCompileError(pGen,E_ERROR,nLine,"Empty constant '%z' value",pName); + if( rc == SXERR_ABORT ){ + return SXERR_ABORT; + } + } + /* Emit the done instruction */ + PH7_VmEmitInstr(pGen->pVm,PH7_OP_DONE,1,0,0,0); + PH7_VmSetByteCodeContainer(pGen->pVm,pInstrContainer); + if( rc == SXERR_ABORT ){ + /* Don't worry about freeing memory, everything will be released shortly */ + return SXERR_ABORT; + } + /* All done,install the constant */ + rc = PH7_ClassInstallAttr(pClass,pCons); + if( rc != SXRET_OK ){ + PH7_GenCompileError(pGen,E_ERROR,nLine,"Fatal, PH7 is running out of memory"); + return SXERR_ABORT; + } + if( pGen->pIn < pGen->pEnd && (pGen->pIn->nType & PH7_TK_COMMA /*','*/) ){ + /* Multiple constants declarations [i.e: const min=-1,max = 10] */ + pGen->pIn++; /* Jump the comma */ + if( pGen->pIn >= pGen->pEnd || (pGen->pIn->nType & PH7_TK_ID) == 0 ){ + SyToken *pTok = pGen->pIn; + if( pTok >= pGen->pEnd ){ + pTok--; + } + rc = PH7_GenCompileError(pGen,E_ERROR,pGen->pIn->nLine, + "Unexpected token '%z',expecting constant declaration inside class '%z'", + &pTok->sData,&pClass->sName); + if( rc == SXERR_ABORT ){ + return SXERR_ABORT; + } + }else{ + if( pGen->pIn->nType & PH7_TK_ID ){ + goto loop; + } + } + } + return SXRET_OK; +Synchronize: + /* Synchronize with the first semi-colon */ + while(pGen->pIn < pGen->pEnd && ((pGen->pIn->nType & PH7_TK_SEMI/*';'*/) == 0) ){ + pGen->pIn++; + } + return SXERR_CORRUPT; +} +/* + * complie a class attribute or Properties in the PHP jargon. + * According to the PHP language reference manual + * Properties + * Class member variables are called "properties". You may also see them referred + * to using other terms such as "attributes" or "fields", but for the purposes + * of this reference we will use "properties". They are defined by using one + * of the keywords public, protected, or private, followed by a normal variable + * declaration. This declaration may include an initialization, but this initialization + * must be a constant value--that is, it must be able to be evaluated at compile time + * and must not depend on run-time information in order to be evaluated. + * Symisc eXtension. + * PH7 allow any complex expression to be associated with the attribute while + * the zend engine would allow only simple scalar value. + * Example: + * class Test{ + * public static $myVar = "Hello"."world: ".rand_str(3); //concatenation operation + Function call + * }; + * var_dump(TEST::myVar); + * Refer to the official documentation for more information on the powerful extension + * introduced by the PH7 engine to the OO subsystem. + */ +static sxi32 GenStateCompileClassAttr(ph7_gen_state *pGen,sxi32 iProtection,sxi32 iFlags,ph7_class *pClass) +{ + sxu32 nLine = pGen->pIn->nLine; + ph7_class_attr *pAttr; + SyString *pName; + sxi32 rc; + /* Extract visibility level */ + iProtection = GetProtectionLevel(iProtection); +loop: + pGen->pIn++; /* Jump the dollar sign */ + if( pGen->pIn >= pGen->pEnd || (pGen->pIn->nType & (PH7_TK_KEYWORD|PH7_TK_ID)) == 0 ){ + /* Invalid attribute name */ + rc = PH7_GenCompileError(pGen,E_ERROR,nLine,"Invalid attribute name"); + if( rc == SXERR_ABORT ){ + /* Error count limit reached,abort immediately */ + return SXERR_ABORT; + } + goto Synchronize; + } + /* Peek attribute name */ + pName = &pGen->pIn->sData; + /* Advance the stream cursor */ + pGen->pIn++; + if(pGen->pIn >= pGen->pEnd || (pGen->pIn->nType & (PH7_TK_EQUAL/*'='*/|PH7_TK_SEMI/*';'*/|PH7_TK_COMMA/*','*/)) == 0 ){ + /* Invalid declaration */ + rc = PH7_GenCompileError(pGen,E_ERROR,nLine,"Expected '=' or ';' after attribute name '%z'",pName); + if( rc == SXERR_ABORT ){ + /* Error count limit reached,abort immediately */ + return SXERR_ABORT; + } + goto Synchronize; + } + /* Allocate a new class attribute */ + pAttr = PH7_NewClassAttr(pGen->pVm,pName,nLine,iProtection,iFlags); + if( pAttr == 0 ){ + PH7_GenCompileError(pGen,E_ERROR,nLine,"Fatal, PH7 engine is running out of memory"); + return SXERR_ABORT; + } + if( pGen->pIn->nType & PH7_TK_EQUAL /*'='*/ ){ + SySet *pInstrContainer; + pGen->pIn++; /*Jump the equal sign */ + /* Swap bytecode container */ + pInstrContainer = PH7_VmGetByteCodeContainer(pGen->pVm); + PH7_VmSetByteCodeContainer(pGen->pVm,&pAttr->aByteCode); + /* Compile attribute value. + */ + rc = PH7_CompileExpr(&(*pGen),EXPR_FLAG_COMMA_STATEMENT,0); + if( rc == SXERR_EMPTY ){ + rc = PH7_GenCompileError(pGen,E_ERROR,nLine,"Attribute '%z': Missing default value",pName); + if( rc == SXERR_ABORT ){ + return SXERR_ABORT; + } + } + /* Emit the done instruction */ + PH7_VmEmitInstr(pGen->pVm,PH7_OP_DONE,1,0,0,0); + PH7_VmSetByteCodeContainer(pGen->pVm,pInstrContainer); + } + /* All done,install the attribute */ + rc = PH7_ClassInstallAttr(pClass,pAttr); + if( rc != SXRET_OK ){ + PH7_GenCompileError(pGen,E_ERROR,nLine,"Fatal, PH7 is running out of memory"); + return SXERR_ABORT; + } + if( pGen->pIn < pGen->pEnd && (pGen->pIn->nType & PH7_TK_COMMA /*','*/) ){ + /* Multiple attribute declarations [i.e: public $var1,$var2=5<<1,$var3] */ + pGen->pIn++; /* Jump the comma */ + if( pGen->pIn >= pGen->pEnd || (pGen->pIn->nType & PH7_TK_DOLLAR/*'$'*/) == 0 ){ + SyToken *pTok = pGen->pIn; + if( pTok >= pGen->pEnd ){ + pTok--; + } + rc = PH7_GenCompileError(pGen,E_ERROR,pGen->pIn->nLine, + "Unexpected token '%z',expecting attribute declaration inside class '%z'", + &pTok->sData,&pClass->sName); + if( rc == SXERR_ABORT ){ + return SXERR_ABORT; + } + }else{ + if( pGen->pIn->nType & PH7_TK_DOLLAR ){ + goto loop; + } + } + } + return SXRET_OK; +Synchronize: + /* Synchronize with the first semi-colon */ + while(pGen->pIn < pGen->pEnd && ((pGen->pIn->nType & PH7_TK_SEMI/*';'*/) == 0) ){ + pGen->pIn++; + } + return SXERR_CORRUPT; +} +/* + * Compile a class method. + * + * Refer to the official documentation for more information + * on the powerful extension introduced by the PH7 engine + * to the OO subsystem such as full type hinting,method + * overloading and many more. + */ +static sxi32 GenStateCompileClassMethod( + ph7_gen_state *pGen, /* Code generator state */ + sxi32 iProtection, /* Visibility level */ + sxi32 iFlags, /* Configuration flags */ + int doBody, /* TRUE to process method body */ + ph7_class *pClass /* Class this method belongs */ + ) +{ + sxu32 nLine = pGen->pIn->nLine; + ph7_class_method *pMeth; + sxi32 iFuncFlags; + SyString *pName; + SyToken *pEnd; + sxi32 rc; + /* Extract visibility level */ + iProtection = GetProtectionLevel(iProtection); + pGen->pIn++; /* Jump the 'function' keyword */ + iFuncFlags = 0; + if( pGen->pIn >= pGen->pEnd ){ + /* Invalid method name */ + rc = PH7_GenCompileError(pGen,E_ERROR,nLine,"Invalid method name"); + if( rc == SXERR_ABORT ){ + /* Error count limit reached,abort immediately */ + return SXERR_ABORT; + } + goto Synchronize; + } + if( pGen->pIn < pGen->pEnd && (pGen->pIn->nType & PH7_TK_AMPER) ){ + /* Return by reference,remember that */ + iFuncFlags |= VM_FUNC_REF_RETURN; + /* Jump the '&' token */ + pGen->pIn++; + } + if( pGen->pIn >= pGen->pEnd || (pGen->pIn->nType & (PH7_TK_ID)) == 0 ){ + /* Invalid method name */ + rc = PH7_GenCompileError(&(*pGen),E_ERROR,nLine,"Invalid method name"); + if( rc == SXERR_ABORT ){ + return SXERR_ABORT; + } + goto Synchronize; + } + /* Peek method name */ + pName = &pGen->pIn->sData; + nLine = pGen->pIn->nLine; + /* Jump the method name */ + pGen->pIn++; + if( iFlags & PH7_CLASS_ATTR_ABSTRACT ){ + /* Abstract method */ + if( iProtection == PH7_CLASS_PROT_PRIVATE ){ + rc = PH7_GenCompileError(pGen,E_ERROR,nLine, + "Access type for abstract method '%z::%z' cannot be 'private'", + &pClass->sName,pName); + if( rc == SXERR_ABORT ){ + return SXERR_ABORT; + } + } + /* Assemble method signature only */ + doBody = FALSE; + } + if( pGen->pIn >= pGen->pEnd || (pGen->pIn->nType & PH7_TK_LPAREN) == 0 ){ + /* Syntax error */ + rc = PH7_GenCompileError(pGen,E_ERROR,nLine,"Expected '(' after method name '%z'",pName); + if( rc == SXERR_ABORT ){ + /* Error count limit reached,abort immediately */ + return SXERR_ABORT; + } + goto Synchronize; + } + /* Allocate a new class_method instance */ + pMeth = PH7_NewClassMethod(pGen->pVm,pClass,pName,nLine,iProtection,iFlags,iFuncFlags); + if( pMeth == 0 ){ + PH7_GenCompileError(&(*pGen),E_ERROR,nLine,"Fatal, PH7 is running out of memory"); + return SXERR_ABORT; + } + /* Jump the left parenthesis '(' */ + pGen->pIn++; + pEnd = 0; /* cc warning */ + /* Delimit the method signature */ + PH7_DelimitNestedTokens(pGen->pIn,pGen->pEnd,PH7_TK_LPAREN /* '(' */,PH7_TK_RPAREN /* ')' */,&pEnd); + if( pEnd >= pGen->pEnd ){ + /* Syntax error */ + rc = PH7_GenCompileError(pGen,E_ERROR,nLine,"Missing ')' after method '%z' declaration",pName); + if( rc == SXERR_ABORT ){ + /* Error count limit reached,abort immediately */ + return SXERR_ABORT; + } + goto Synchronize; + } + if( pGen->pIn < pEnd ){ + /* Collect method arguments */ + rc = GenStateCollectFuncArgs(&pMeth->sFunc,&(*pGen),pEnd); + if( rc == SXERR_ABORT ){ + return SXERR_ABORT; + } + } + /* Point beyond method signature */ + pGen->pIn = &pEnd[1]; + if( doBody ){ + /* Compile method body */ + rc = GenStateCompileFuncBody(&(*pGen),&pMeth->sFunc); + if( rc == SXERR_ABORT ){ + return SXERR_ABORT; + } + }else{ + /* Only method signature is allowed */ + if( pGen->pIn < pGen->pEnd && (pGen->pIn->nType & PH7_TK_SEMI /* ';'*/) == 0 ){ + rc = PH7_GenCompileError(pGen,E_ERROR,pGen->pIn->nLine, + "Expected ';' after method signature '%z'",pName); + if( rc == SXERR_ABORT ){ + /* Error count limit reached,abort immediately */ + return SXERR_ABORT; + } + return SXERR_CORRUPT; + } + } + /* All done,install the method */ + rc = PH7_ClassInstallMethod(pClass,pMeth); + if( rc != SXRET_OK ){ + PH7_GenCompileError(pGen,E_ERROR,nLine,"Fatal, PH7 is running out of memory"); + return SXERR_ABORT; + } + return SXRET_OK; +Synchronize: + /* Synchronize with the first semi-colon */ + while(pGen->pIn < pGen->pEnd && ((pGen->pIn->nType & PH7_TK_SEMI/*';'*/) == 0) ){ + pGen->pIn++; + } + return SXERR_CORRUPT; +} +/* + * Compile an object interface. + * According to the PHP language reference manual + * Object Interfaces: + * Object interfaces allow you to create code which specifies which methods + * a class must implement, without having to define how these methods are handled. + * Interfaces are defined using the interface keyword, in the same way as a standard + * class, but without any of the methods having their contents defined. + * All methods declared in an interface must be public, this is the nature of an interface. + */ +static sxi32 PH7_CompileClassInterface(ph7_gen_state *pGen) +{ + sxu32 nLine = pGen->pIn->nLine; + ph7_class *pClass,*pBase; + SyToken *pEnd,*pTmp; + SyString *pName; + sxi32 nKwrd; + sxi32 rc; + /* Jump the 'interface' keyword */ + pGen->pIn++; + /* Extract interface name */ + pName = &pGen->pIn->sData; + /* Advance the stream cursor */ + pGen->pIn++; + /* Obtain a raw class */ + pClass = PH7_NewRawClass(pGen->pVm,pName,nLine); + if( pClass == 0 ){ + PH7_GenCompileError(pGen,E_ERROR,nLine,"Fatal, PH7 is running out of memory"); + return SXERR_ABORT; + } + /* Mark as an interface */ + pClass->iFlags = PH7_CLASS_INTERFACE; + /* Assume no base class is given */ + pBase = 0; + if( pGen->pIn < pGen->pEnd && (pGen->pIn->nType & PH7_TK_KEYWORD) ){ + nKwrd = SX_PTR_TO_INT(pGen->pIn->pUserData); + if( nKwrd == PH7_TKWRD_EXTENDS /* interface b extends a */ ){ + SyString *pBaseName; + /* Extract base interface */ + pGen->pIn++; + if( pGen->pIn >= pGen->pEnd || (pGen->pIn->nType & PH7_TK_ID) == 0 ){ + /* Syntax error */ + rc = PH7_GenCompileError(pGen,E_ERROR,nLine, + "Expected 'interface_name' after 'extends' keyword inside interface '%z'", + pName); + SyMemBackendPoolFree(&pGen->pVm->sAllocator,pClass); + if( rc == SXERR_ABORT ){ + /* Error count limit reached,abort immediately */ + return SXERR_ABORT; + } + return SXRET_OK; + } + pBaseName = &pGen->pIn->sData; + pBase = PH7_VmExtractClass(pGen->pVm,pBaseName->zString,pBaseName->nByte,FALSE,0); + /* Only interfaces is allowed */ + while( pBase && (pBase->iFlags & PH7_CLASS_INTERFACE) == 0 ){ + pBase = pBase->pNextName; + } + if( pBase == 0 ){ + /* Inexistant interface */ + rc = PH7_GenCompileError(pGen,E_ERROR,pGen->pIn->nLine,"Inexistant base interface '%z'",pBaseName); + if( rc == SXERR_ABORT ){ + /* Error count limit reached,abort immediately */ + return SXERR_ABORT; + } + } + /* Advance the stream cursor */ + pGen->pIn++; + } + } + if( pGen->pIn >= pGen->pEnd || (pGen->pIn->nType & PH7_TK_OCB /*'{'*/) == 0 ){ + /* Syntax error */ + rc = PH7_GenCompileError(pGen,E_ERROR,nLine,"Expected '{' after interface '%z' definition",pName); + SyMemBackendPoolFree(&pGen->pVm->sAllocator,pClass); + if( rc == SXERR_ABORT ){ + /* Error count limit reached,abort immediately */ + return SXERR_ABORT; + } + return SXRET_OK; + } + pGen->pIn++; /* Jump the leading curly brace */ + pEnd = 0; /* cc warning */ + /* Delimit the interface body */ + PH7_DelimitNestedTokens(pGen->pIn,pGen->pEnd,PH7_TK_OCB/*'{'*/,PH7_TK_CCB/*'}'*/,&pEnd); + if( pEnd >= pGen->pEnd ){ + /* Syntax error */ + rc = PH7_GenCompileError(pGen,E_ERROR,nLine,"Missing '}' after interface '%z' definition",pName); + SyMemBackendPoolFree(&pGen->pVm->sAllocator,pClass); + if( rc == SXERR_ABORT ){ + /* Error count limit reached,abort immediately */ + return SXERR_ABORT; + } + return SXRET_OK; + } + /* Swap token stream */ + pTmp = pGen->pEnd; + pGen->pEnd = pEnd; + /* Start the parse process + * Note (According to the PHP reference manual): + * Only constants and function signatures(without body) are allowed. + * Only 'public' visibility is allowed. + */ + for(;;){ + /* Jump leading/trailing semi-colons */ + while( pGen->pIn < pGen->pEnd && (pGen->pIn->nType & PH7_TK_SEMI/*';'*/) ){ + pGen->pIn++; + } + if( pGen->pIn >= pGen->pEnd ){ + /* End of interface body */ + break; + } + if( (pGen->pIn->nType & PH7_TK_KEYWORD) == 0 ){ + rc = PH7_GenCompileError(pGen,E_ERROR,pGen->pIn->nLine, + "Unexpected token '%z'.Expecting method signature or constant declaration inside interface '%z'", + &pGen->pIn->sData,pName); + if( rc == SXERR_ABORT ){ + /* Error count limit reached,abort immediately */ + return SXERR_ABORT; + } + goto done; + } + /* Extract the current keyword */ + nKwrd = SX_PTR_TO_INT(pGen->pIn->pUserData); + if( nKwrd == PH7_TKWRD_PRIVATE || nKwrd == PH7_TKWRD_PROTECTED ){ + /* Emit a warning and switch to public visibility */ + PH7_GenCompileError(&(*pGen),E_WARNING,pGen->pIn->nLine,"interface: Access type must be public"); + nKwrd = PH7_TKWRD_PUBLIC; + } + if( nKwrd != PH7_TKWRD_PUBLIC && nKwrd != PH7_TKWRD_FUNCTION && nKwrd != PH7_TKWRD_CONST && nKwrd != PH7_TKWRD_STATIC ){ + rc = PH7_GenCompileError(pGen,E_ERROR,pGen->pIn->nLine, + "Expecting method signature or constant declaration inside interface '%z'",pName); + if( rc == SXERR_ABORT ){ + /* Error count limit reached,abort immediately */ + return SXERR_ABORT; + } + goto done; + } + if( nKwrd == PH7_TKWRD_PUBLIC ){ + /* Advance the stream cursor */ + pGen->pIn++; + if( pGen->pIn >= pGen->pEnd || (pGen->pIn->nType & PH7_TK_KEYWORD) == 0 ){ + rc = PH7_GenCompileError(pGen,E_ERROR,pGen->pIn->nLine, + "Expecting method signature inside interface '%z'",pName); + if( rc == SXERR_ABORT ){ + /* Error count limit reached,abort immediately */ + return SXERR_ABORT; + } + goto done; + } + nKwrd = SX_PTR_TO_INT(pGen->pIn->pUserData); + if( nKwrd != PH7_TKWRD_FUNCTION && nKwrd != PH7_TKWRD_CONST && nKwrd != PH7_TKWRD_STATIC ){ + rc = PH7_GenCompileError(pGen,E_ERROR,pGen->pIn->nLine, + "Expecting method signature or constant declaration inside interface '%z'",pName); + if( rc == SXERR_ABORT ){ + /* Error count limit reached,abort immediately */ + return SXERR_ABORT; + } + goto done; + } + } + if( nKwrd == PH7_TKWRD_CONST ){ + /* Parse constant */ + rc = GenStateCompileClassConstant(&(*pGen),0,0,pClass); + if( rc != SXRET_OK ){ + if( rc == SXERR_ABORT ){ + return SXERR_ABORT; + } + goto done; + } + }else{ + sxi32 iFlags = 0; + if( nKwrd == PH7_TKWRD_STATIC ){ + /* Static method,record that */ + iFlags |= PH7_CLASS_ATTR_STATIC; + /* Advance the stream cursor */ + pGen->pIn++; + if( pGen->pIn >= pGen->pEnd || (pGen->pIn->nType & PH7_TK_KEYWORD) == 0 + || SX_PTR_TO_INT(pGen->pIn->pUserData) != PH7_TKWRD_FUNCTION ){ + rc = PH7_GenCompileError(pGen,E_ERROR,pGen->pIn->nLine, + "Expecting method signature inside interface '%z'",pName); + if( rc == SXERR_ABORT ){ + /* Error count limit reached,abort immediately */ + return SXERR_ABORT; + } + goto done; + } + } + /* Process method signature */ + rc = GenStateCompileClassMethod(&(*pGen),0,FALSE/* Only method signature*/,iFlags,pClass); + if( rc != SXRET_OK ){ + if( rc == SXERR_ABORT ){ + return SXERR_ABORT; + } + goto done; + } + } + } + /* Install the interface */ + rc = PH7_VmInstallClass(pGen->pVm,pClass); + if( rc == SXRET_OK && pBase ){ + /* Inherit from the base interface */ + rc = PH7_ClassInterfaceInherit(pClass,pBase); + } + if( rc != SXRET_OK ){ + PH7_GenCompileError(pGen,E_ERROR,nLine,"Fatal, PH7 is running out of memory"); + return SXERR_ABORT; + } +done: + /* Point beyond the interface body */ + pGen->pIn = &pEnd[1]; + pGen->pEnd = pTmp; + return PH7_OK; +} +/* + * Compile a user-defined class. + * According to the PHP language reference manual + * class + * Basic class definitions begin with the keyword class, followed by a class + * name, followed by a pair of curly braces which enclose the definitions + * of the properties and methods belonging to the class. + * The class name can be any valid label which is a not a PHP reserved word. + * A valid class name starts with a letter or underscore, followed by any number + * of letters, numbers, or underscores. As a regular expression, it would be expressed + * thus: [a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*. + * A class may contain its own constants, variables (called "properties"), and functions + * (called "methods"). + */ +static sxi32 GenStateCompileClass(ph7_gen_state *pGen,sxi32 iFlags) +{ + sxu32 nLine = pGen->pIn->nLine; + ph7_class *pClass,*pBase; + SyToken *pEnd,*pTmp; + sxi32 iProtection; + SySet aInterfaces; + sxi32 iAttrflags; + SyString *pName; + sxi32 nKwrd; + sxi32 rc; + /* Jump the 'class' keyword */ + pGen->pIn++; + if( pGen->pIn >= pGen->pEnd || (pGen->pIn->nType & PH7_TK_ID) == 0 ){ + /* Syntax error */ + rc = PH7_GenCompileError(pGen,E_ERROR,nLine,"Invalid class name"); + if( rc == SXERR_ABORT ){ + /* Error count limit reached,abort immediately */ + return SXERR_ABORT; + } + /* Synchronize with the first semi-colon or curly braces */ + while( pGen->pIn < pGen->pEnd && (pGen->pIn->nType & (PH7_TK_OCB/*'{'*/|PH7_TK_SEMI/*';'*/)) == 0 ){ + pGen->pIn++; + } + return SXRET_OK; + } + /* Extract class name */ + pName = &pGen->pIn->sData; + /* Advance the stream cursor */ + pGen->pIn++; + /* Obtain a raw class */ + pClass = PH7_NewRawClass(pGen->pVm,pName,nLine); + if( pClass == 0 ){ + PH7_GenCompileError(pGen,E_ERROR,nLine,"Fatal, PH7 is running out of memory"); + return SXERR_ABORT; + } + /* implemented interfaces container */ + SySetInit(&aInterfaces,&pGen->pVm->sAllocator,sizeof(ph7_class *)); + /* Assume a standalone class */ + pBase = 0; + if( pGen->pIn < pGen->pEnd && (pGen->pIn->nType & PH7_TK_KEYWORD) ){ + SyString *pBaseName; + nKwrd = SX_PTR_TO_INT(pGen->pIn->pUserData); + if( nKwrd == PH7_TKWRD_EXTENDS /* class b extends a */ ){ + pGen->pIn++; /* Advance the stream cursor */ + if( pGen->pIn >= pGen->pEnd || (pGen->pIn->nType & PH7_TK_ID) == 0 ){ + /* Syntax error */ + rc = PH7_GenCompileError(pGen,E_ERROR,nLine, + "Expected 'class_name' after 'extends' keyword inside class '%z'", + pName); + SyMemBackendPoolFree(&pGen->pVm->sAllocator,pClass); + if( rc == SXERR_ABORT ){ + /* Error count limit reached,abort immediately */ + return SXERR_ABORT; + } + return SXRET_OK; + } + /* Extract base class name */ + pBaseName = &pGen->pIn->sData; + /* Perform the query */ + pBase = PH7_VmExtractClass(pGen->pVm,pBaseName->zString,pBaseName->nByte,FALSE,0); + /* Interfaces are not allowed */ + while( pBase && (pBase->iFlags & PH7_CLASS_INTERFACE) ){ + pBase = pBase->pNextName; + } + if( pBase == 0 ){ + /* Inexistant base class */ + rc = PH7_GenCompileError(pGen,E_ERROR,pGen->pIn->nLine,"Inexistant base class '%z'",pBaseName); + if( rc == SXERR_ABORT ){ + /* Error count limit reached,abort immediately */ + return SXERR_ABORT; + } + }else{ + if( pBase->iFlags & PH7_CLASS_FINAL ){ + rc = PH7_GenCompileError(pGen,E_ERROR,nLine, + "Class '%z' may not inherit from final class '%z'",pName,&pBase->sName); + if( rc == SXERR_ABORT ){ + /* Error count limit reached,abort immediately */ + return SXERR_ABORT; + } + } + } + /* Advance the stream cursor */ + pGen->pIn++; + } + if (pGen->pIn < pGen->pEnd && (pGen->pIn->nType & PH7_TK_KEYWORD) && SX_PTR_TO_INT(pGen->pIn->pUserData) == PH7_TKWRD_IMPLEMENTS ){ + ph7_class *pInterface; + SyString *pIntName; + /* Interface implementation */ + pGen->pIn++; /* Advance the stream cursor */ + for(;;){ + if( pGen->pIn >= pGen->pEnd || (pGen->pIn->nType & PH7_TK_ID) == 0 ){ + /* Syntax error */ + rc = PH7_GenCompileError(pGen,E_ERROR,nLine, + "Expected 'interface_name' after 'implements' keyword inside class '%z' declaration", + pName); + if( rc == SXERR_ABORT ){ + /* Error count limit reached,abort immediately */ + return SXERR_ABORT; + } + break; + } + /* Extract interface name */ + pIntName = &pGen->pIn->sData; + /* Make sure the interface is already defined */ + pInterface = PH7_VmExtractClass(pGen->pVm,pIntName->zString,pIntName->nByte,FALSE,0); + /* Only interfaces are allowed */ + while( pInterface && (pInterface->iFlags & PH7_CLASS_INTERFACE) == 0 ){ + pInterface = pInterface->pNextName; + } + if( pInterface == 0 ){ + /* Inexistant interface */ + rc = PH7_GenCompileError(pGen,E_ERROR,pGen->pIn->nLine,"Inexistant base interface '%z'",pIntName); + if( rc == SXERR_ABORT ){ + /* Error count limit reached,abort immediately */ + return SXERR_ABORT; + } + }else{ + /* Register interface */ + SySetPut(&aInterfaces,(const void *)&pInterface); + } + /* Advance the stream cursor */ + pGen->pIn++; + if( pGen->pIn >= pGen->pEnd || (pGen->pIn->nType & PH7_TK_COMMA) == 0 ){ + break; + } + pGen->pIn++;/* Jump the comma */ + } + } + } + if( pGen->pIn >= pGen->pEnd || (pGen->pIn->nType & PH7_TK_OCB /*'{'*/) == 0 ){ + /* Syntax error */ + rc = PH7_GenCompileError(pGen,E_ERROR,nLine,"Expected '{' after class '%z' declaration",pName); + SyMemBackendPoolFree(&pGen->pVm->sAllocator,pClass); + if( rc == SXERR_ABORT ){ + /* Error count limit reached,abort immediately */ + return SXERR_ABORT; + } + return SXRET_OK; + } + pGen->pIn++; /* Jump the leading curly brace */ + pEnd = 0; /* cc warning */ + /* Delimit the class body */ + PH7_DelimitNestedTokens(pGen->pIn,pGen->pEnd,PH7_TK_OCB/*'{'*/,PH7_TK_CCB/*'}'*/,&pEnd); + if( pEnd >= pGen->pEnd ){ + /* Syntax error */ + rc = PH7_GenCompileError(pGen,E_ERROR,nLine,"Missing closing braces'}' after class '%z' definition",pName); + SyMemBackendPoolFree(&pGen->pVm->sAllocator,pClass); + if( rc == SXERR_ABORT ){ + /* Error count limit reached,abort immediately */ + return SXERR_ABORT; + } + return SXRET_OK; + } + /* Swap token stream */ + pTmp = pGen->pEnd; + pGen->pEnd = pEnd; + /* Set the inherited flags */ + pClass->iFlags = iFlags; + /* Start the parse process */ + for(;;){ + /* Jump leading/trailing semi-colons */ + while( pGen->pIn < pGen->pEnd && (pGen->pIn->nType & PH7_TK_SEMI/*';'*/) ){ + pGen->pIn++; + } + if( pGen->pIn >= pGen->pEnd ){ + /* End of class body */ + break; + } + if( (pGen->pIn->nType & (PH7_TK_KEYWORD|PH7_TK_DOLLAR)) == 0 ){ + rc = PH7_GenCompileError(pGen,E_ERROR,pGen->pIn->nLine, + "Unexpected token '%z'. Expecting attribute declaration inside class '%z'", + &pGen->pIn->sData,pName); + if( rc == SXERR_ABORT ){ + /* Error count limit reached,abort immediately */ + return SXERR_ABORT; + } + goto done; + } + /* Assume public visibility */ + iProtection = PH7_TKWRD_PUBLIC; + iAttrflags = 0; + if( pGen->pIn->nType & PH7_TK_KEYWORD ){ + /* Extract the current keyword */ + nKwrd = SX_PTR_TO_INT(pGen->pIn->pUserData); + if( nKwrd == PH7_TKWRD_PUBLIC || nKwrd == PH7_TKWRD_PRIVATE || nKwrd == PH7_TKWRD_PROTECTED ){ + iProtection = nKwrd; + pGen->pIn++; /* Jump the visibility token */ + if( pGen->pIn >= pGen->pEnd || (pGen->pIn->nType & (PH7_TK_KEYWORD|PH7_TK_DOLLAR)) == 0 ){ + rc = PH7_GenCompileError(pGen,E_ERROR,pGen->pIn->nLine, + "Unexpected token '%z'. Expecting attribute declaration inside class '%z'", + &pGen->pIn->sData,pName); + if( rc == SXERR_ABORT ){ + /* Error count limit reached,abort immediately */ + return SXERR_ABORT; + } + goto done; + } + if( pGen->pIn->nType & PH7_TK_DOLLAR ){ + /* Attribute declaration */ + rc = GenStateCompileClassAttr(&(*pGen),iProtection,iAttrflags,pClass); + if( rc != SXRET_OK ){ + if( rc == SXERR_ABORT ){ + return SXERR_ABORT; + } + goto done; + } + continue; + } + /* Extract the keyword */ + nKwrd = SX_PTR_TO_INT(pGen->pIn->pUserData); + } + if( nKwrd == PH7_TKWRD_CONST ){ + /* Process constant declaration */ + rc = GenStateCompileClassConstant(&(*pGen),iProtection,iAttrflags,pClass); + if( rc != SXRET_OK ){ + if( rc == SXERR_ABORT ){ + return SXERR_ABORT; + } + goto done; + } + }else{ + if( nKwrd == PH7_TKWRD_STATIC ){ + /* Static method or attribute,record that */ + iAttrflags |= PH7_CLASS_ATTR_STATIC; + pGen->pIn++; /* Jump the static keyword */ + if( pGen->pIn < pGen->pEnd && (pGen->pIn->nType & PH7_TK_KEYWORD) ){ + /* Extract the keyword */ + nKwrd = SX_PTR_TO_INT(pGen->pIn->pUserData); + if( nKwrd == PH7_TKWRD_PUBLIC || nKwrd == PH7_TKWRD_PRIVATE || nKwrd == PH7_TKWRD_PROTECTED ){ + iProtection = nKwrd; + pGen->pIn++; /* Jump the visibility token */ + } + } + if( pGen->pIn >= pGen->pEnd || (pGen->pIn->nType & (PH7_TK_KEYWORD|PH7_TK_DOLLAR)) == 0 ){ + rc = PH7_GenCompileError(pGen,E_ERROR,pGen->pIn->nLine, + "Unexpected token '%z',Expecting method,attribute or constant declaration inside class '%z'", + &pGen->pIn->sData,pName); + if( rc == SXERR_ABORT ){ + /* Error count limit reached,abort immediately */ + return SXERR_ABORT; + } + goto done; + } + if( pGen->pIn->nType & PH7_TK_DOLLAR ){ + /* Attribute declaration */ + rc = GenStateCompileClassAttr(&(*pGen),iProtection,iAttrflags,pClass); + if( rc != SXRET_OK ){ + if( rc == SXERR_ABORT ){ + return SXERR_ABORT; + } + goto done; + } + continue; + } + /* Extract the keyword */ + nKwrd = SX_PTR_TO_INT(pGen->pIn->pUserData); + }else if( nKwrd == PH7_TKWRD_ABSTRACT ){ + /* Abstract method,record that */ + iAttrflags |= PH7_CLASS_ATTR_ABSTRACT; + /* Mark the whole class as abstract */ + pClass->iFlags |= PH7_CLASS_ABSTRACT; + /* Advance the stream cursor */ + pGen->pIn++; + if( pGen->pIn < pGen->pEnd && (pGen->pIn->nType & PH7_TK_KEYWORD) ){ + nKwrd = SX_PTR_TO_INT(pGen->pIn->pUserData); + if( nKwrd == PH7_TKWRD_PUBLIC || nKwrd == PH7_TKWRD_PRIVATE || nKwrd == PH7_TKWRD_PROTECTED ){ + iProtection = nKwrd; + pGen->pIn++; /* Jump the visibility token */ + } + } + if( pGen->pIn < pGen->pEnd && (pGen->pIn->nType & PH7_TK_KEYWORD) && + SX_PTR_TO_INT(pGen->pIn->pUserData) == PH7_TKWRD_STATIC ){ + /* Static method */ + iAttrflags |= PH7_CLASS_ATTR_STATIC; + pGen->pIn++; /* Jump the static keyword */ + } + if( pGen->pIn >= pGen->pEnd || (pGen->pIn->nType & PH7_TK_KEYWORD) == 0 || + SX_PTR_TO_INT(pGen->pIn->pUserData) != PH7_TKWRD_FUNCTION ){ + rc = PH7_GenCompileError(pGen,E_ERROR,pGen->pIn->nLine, + "Unexpected token '%z',Expecting method declaration after 'abstract' keyword inside class '%z'", + &pGen->pIn->sData,pName); + if( rc == SXERR_ABORT ){ + /* Error count limit reached,abort immediately */ + return SXERR_ABORT; + } + goto done; + } + nKwrd = PH7_TKWRD_FUNCTION; + }else if( nKwrd == PH7_TKWRD_FINAL ){ + /* final method ,record that */ + iAttrflags |= PH7_CLASS_ATTR_FINAL; + pGen->pIn++; /* Jump the final keyword */ + if( pGen->pIn < pGen->pEnd && (pGen->pIn->nType & PH7_TK_KEYWORD) ){ + /* Extract the keyword */ + nKwrd = SX_PTR_TO_INT(pGen->pIn->pUserData); + if( nKwrd == PH7_TKWRD_PUBLIC || nKwrd == PH7_TKWRD_PRIVATE || nKwrd == PH7_TKWRD_PROTECTED ){ + iProtection = nKwrd; + pGen->pIn++; /* Jump the visibility token */ + } + } + if( pGen->pIn < pGen->pEnd && (pGen->pIn->nType & PH7_TK_KEYWORD) && + SX_PTR_TO_INT(pGen->pIn->pUserData) == PH7_TKWRD_STATIC ){ + /* Static method */ + iAttrflags |= PH7_CLASS_ATTR_STATIC; + pGen->pIn++; /* Jump the static keyword */ + } + if( pGen->pIn >= pGen->pEnd || (pGen->pIn->nType & PH7_TK_KEYWORD) == 0 || + SX_PTR_TO_INT(pGen->pIn->pUserData) != PH7_TKWRD_FUNCTION ){ + rc = PH7_GenCompileError(pGen,E_ERROR,pGen->pIn->nLine, + "Unexpected token '%z',Expecting method declaration after 'final' keyword inside class '%z'", + &pGen->pIn->sData,pName); + if( rc == SXERR_ABORT ){ + /* Error count limit reached,abort immediately */ + return SXERR_ABORT; + } + goto done; + } + nKwrd = PH7_TKWRD_FUNCTION; + } + if( nKwrd != PH7_TKWRD_FUNCTION && nKwrd != PH7_TKWRD_VAR ){ + rc = PH7_GenCompileError(pGen,E_ERROR,pGen->pIn->nLine, + "Unexpected token '%z',Expecting method declaration inside class '%z'", + &pGen->pIn->sData,pName); + if( rc == SXERR_ABORT ){ + /* Error count limit reached,abort immediately */ + return SXERR_ABORT; + } + goto done; + } + if( nKwrd == PH7_TKWRD_VAR ){ + pGen->pIn++; /* Jump the 'var' keyword */ + if( pGen->pIn >= pGen->pEnd || (pGen->pIn->nType & PH7_TK_DOLLAR/*'$'*/) == 0){ + rc = PH7_GenCompileError(pGen,E_ERROR,pGen->pIn->nLine, + "Expecting attribute declaration after 'var' keyword"); + if( rc == SXERR_ABORT ){ + /* Error count limit reached,abort immediately */ + return SXERR_ABORT; + } + goto done; + } + /* Attribute declaration */ + rc = GenStateCompileClassAttr(&(*pGen),iProtection,iAttrflags,pClass); + }else{ + /* Process method declaration */ + rc = GenStateCompileClassMethod(&(*pGen),iProtection,iAttrflags,TRUE,pClass); + } + if( rc != SXRET_OK ){ + if( rc == SXERR_ABORT ){ + return SXERR_ABORT; + } + goto done; + } + } + }else{ + /* Attribute declaration */ + rc = GenStateCompileClassAttr(&(*pGen),iProtection,iAttrflags,pClass); + if( rc != SXRET_OK ){ + if( rc == SXERR_ABORT ){ + return SXERR_ABORT; + } + goto done; + } + } + } + /* Install the class */ + rc = PH7_VmInstallClass(pGen->pVm,pClass); + if( rc == SXRET_OK ){ + ph7_class **apInterface; + sxu32 n; + if( pBase ){ + /* Inherit from base class and mark as a subclass */ + rc = PH7_ClassInherit(&(*pGen),pClass,pBase); + } + apInterface = (ph7_class **)SySetBasePtr(&aInterfaces); + for( n = 0 ; n < SySetUsed(&aInterfaces) ; n++ ){ + /* Implements one or more interface */ + rc = PH7_ClassImplement(pClass,apInterface[n]); + if( rc != SXRET_OK ){ + break; + } + } + } + SySetRelease(&aInterfaces); + if( rc != SXRET_OK ){ + PH7_GenCompileError(pGen,E_ERROR,nLine,"Fatal, PH7 is running out of memory"); + return SXERR_ABORT; + } +done: + /* Point beyond the class body */ + pGen->pIn = &pEnd[1]; + pGen->pEnd = pTmp; + return PH7_OK; +} +/* + * Compile a user-defined abstract class. + * According to the PHP language reference manual + * PHP 5 introduces abstract classes and methods. Classes defined as abstract + * may not be instantiated, and any class that contains at least one abstract + * method must also be abstract. Methods defined as abstract simply declare + * the method's signature - they cannot define the implementation. + * When inheriting from an abstract class, all methods marked abstract in the parent's + * class declaration must be defined by the child; additionally, these methods must be + * defined with the same (or a less restricted) visibility. For example, if the abstract + * method is defined as protected, the function implementation must be defined as either + * protected or public, but not private. Furthermore the signatures of the methods must + * match, i.e. the type hints and the number of required arguments must be the same. + * This also applies to constructors as of PHP 5.4. Before 5.4 constructor signatures + * could differ. + */ +static sxi32 PH7_CompileAbstractClass(ph7_gen_state *pGen) +{ + sxi32 rc; + pGen->pIn++; /* Jump the 'abstract' keyword */ + rc = GenStateCompileClass(&(*pGen),PH7_CLASS_ABSTRACT); + return rc; +} +/* + * Compile a user-defined final class. + * According to the PHP language reference manual + * PHP 5 introduces the final keyword, which prevents child classes from overriding + * a method by prefixing the definition with final. If the class itself is being defined + * final then it cannot be extended. + */ +static sxi32 PH7_CompileFinalClass(ph7_gen_state *pGen) +{ + sxi32 rc; + pGen->pIn++; /* Jump the 'final' keyword */ + rc = GenStateCompileClass(&(*pGen),PH7_CLASS_FINAL); + return rc; +} +/* + * Compile a user-defined class. + * According to the PHP language reference manual + * Basic class definitions begin with the keyword class, followed + * by a class name, followed by a pair of curly braces which enclose + * the definitions of the properties and methods belonging to the class. + * A class may contain its own constants, variables (called "properties") + * and functions (called "methods"). + */ +static sxi32 PH7_CompileClass(ph7_gen_state *pGen) +{ + sxi32 rc; + rc = GenStateCompileClass(&(*pGen),0); + return rc; +} +/* + * Exception handling. + * According to the PHP language reference manual + * An exception can be thrown, and caught ("catched") within PHP. Code may be surrounded + * in a try block, to facilitate the catching of potential exceptions. Each try must have + * at least one corresponding catch block. Multiple catch blocks can be used to catch + * different classes of exceptions. Normal execution (when no exception is thrown within + * the try block, or when a catch matching the thrown exception's class is not present) + * will continue after that last catch block defined in sequence. Exceptions can be thrown + * (or re-thrown) within a catch block. + * When an exception is thrown, code following the statement will not be executed, and PHP + * will attempt to find the first matching catch block. If an exception is not caught, a PHP + * Fatal Error will be issued with an "Uncaught Exception ..." message, unless a handler has + * been defined with set_exception_handler(). + * The thrown object must be an instance of the Exception class or a subclass of Exception. + * Trying to throw an object that is not will result in a PHP Fatal Error. + */ +/* + * Expression tree validator callback associated with the 'throw' statement. + * Return SXRET_OK if the tree form a valid expression.Any other error + * indicates failure. + */ +static sxi32 GenStateThrowNodeValidator(ph7_gen_state *pGen,ph7_expr_node *pRoot) +{ + sxi32 rc = SXRET_OK; + if( pRoot->pOp ){ + if( pRoot->pOp->iOp != EXPR_OP_SUBSCRIPT /* $a[] */ && pRoot->pOp->iOp != EXPR_OP_NEW /* new Exception() */ + && pRoot->pOp->iOp != EXPR_OP_ARROW /* -> */ && pRoot->pOp->iOp != EXPR_OP_DC /* :: */){ + /* Unexpected expression */ + rc = PH7_GenCompileError(&(*pGen),E_ERROR,pRoot->pStart? pRoot->pStart->nLine : 0, + "throw: Expecting an exception class instance"); + if( rc != SXERR_ABORT ){ + rc = SXERR_INVALID; + } + } + }else if( pRoot->xCode != PH7_CompileVariable ){ + /* Unexpected expression */ + rc = PH7_GenCompileError(&(*pGen),E_ERROR,pRoot->pStart? pRoot->pStart->nLine : 0, + "throw: Expecting an exception class instance"); + if( rc != SXERR_ABORT ){ + rc = SXERR_INVALID; + } + } + return rc; +} +/* + * Compile a 'throw' statement. + * throw: This is how you trigger an exception. + * Each "throw" block must have at least one "catch" block associated with it. + */ +static sxi32 PH7_CompileThrow(ph7_gen_state *pGen) +{ + sxu32 nLine = pGen->pIn->nLine; + GenBlock *pBlock; + sxu32 nIdx; + sxi32 rc; + pGen->pIn++; /* Jump the 'throw' keyword */ + /* Compile the expression */ + rc = PH7_CompileExpr(&(*pGen),0,GenStateThrowNodeValidator); + if( rc == SXERR_EMPTY ){ + rc = PH7_GenCompileError(&(*pGen),E_ERROR,nLine,"throw: Expecting an exception class instance"); + if( rc == SXERR_ABORT ){ + return SXERR_ABORT; + } + return SXRET_OK; + } + pBlock = pGen->pCurrent; + /* Point to the top most function or try block and emit the forward jump */ + while(pBlock->pParent){ + if( pBlock->iFlags & (GEN_BLOCK_EXCEPTION|GEN_BLOCK_FUNC) ){ + break; + } + /* Point to the parent block */ + pBlock = pBlock->pParent; + } + /* Emit the throw instruction */ + PH7_VmEmitInstr(pGen->pVm,PH7_OP_THROW,0,0,0,&nIdx); + /* Emit the jump */ + GenStateNewJumpFixup(pBlock,PH7_OP_THROW,nIdx); + return SXRET_OK; +} +/* + * Compile a 'catch' block. + * Catch: A "catch" block retrieves an exception and creates + * an object containing the exception information. + */ +static sxi32 PH7_CompileCatch(ph7_gen_state *pGen,ph7_exception *pException) +{ + sxu32 nLine = pGen->pIn->nLine; + ph7_exception_block sCatch; + SySet *pInstrContainer; + GenBlock *pCatch; + SyToken *pToken; + SyString *pName; + char *zDup; + sxi32 rc; + pGen->pIn++; /* Jump the 'catch' keyword */ + /* Zero the structure */ + SyZero(&sCatch,sizeof(ph7_exception_block)); + /* Initialize fields */ + SySetInit(&sCatch.sByteCode,&pException->pVm->sAllocator,sizeof(VmInstr)); + if( pGen->pIn >= pGen->pEnd || (pGen->pIn->nType & PH7_TK_LPAREN) == 0 /*(*/ || + &pGen->pIn[1] >= pGen->pEnd || (pGen->pIn[1].nType & (PH7_TK_ID|PH7_TK_KEYWORD)) == 0 ){ + /* Unexpected token,break immediately */ + pToken = pGen->pIn; + if( pToken >= pGen->pEnd ){ + pToken--; + } + rc = PH7_GenCompileError(pGen,E_ERROR,pToken->nLine, + "Catch: Unexpected token '%z',excpecting class name",&pToken->sData); + if( rc == SXERR_ABORT ){ + return SXERR_ABORT; + } + return SXERR_INVALID; + } + /* Extract the exception class */ + pGen->pIn++; /* Jump the left parenthesis '(' */ + /* Duplicate class name */ + pName = &pGen->pIn->sData; + zDup = SyMemBackendStrDup(&pGen->pVm->sAllocator,pName->zString,pName->nByte); + if( zDup == 0 ){ + goto Mem; + } + SyStringInitFromBuf(&sCatch.sClass,zDup,pName->nByte); + pGen->pIn++; + if( pGen->pIn >= pGen->pEnd || (pGen->pIn->nType & PH7_TK_DOLLAR) == 0 /*$*/ || + &pGen->pIn[1] >= pGen->pEnd || (pGen->pIn[1].nType & (PH7_TK_ID|PH7_TK_KEYWORD)) == 0 ){ + /* Unexpected token,break immediately */ + pToken = pGen->pIn; + if( pToken >= pGen->pEnd ){ + pToken--; + } + rc = PH7_GenCompileError(pGen,E_ERROR,pToken->nLine, + "Catch: Unexpected token '%z',expecting variable name",&pToken->sData); + if( rc == SXERR_ABORT ){ + return SXERR_ABORT; + } + return SXERR_INVALID; + } + pGen->pIn++; /* Jump the dollar sign */ + /* Duplicate instance name */ + pName = &pGen->pIn->sData; + zDup = SyMemBackendStrDup(&pGen->pVm->sAllocator,pName->zString,pName->nByte); + if( zDup == 0 ){ + goto Mem; + } + SyStringInitFromBuf(&sCatch.sThis,zDup,pName->nByte); + pGen->pIn++; + if( pGen->pIn >= pGen->pEnd || (pGen->pIn->nType & PH7_TK_RPAREN) == 0 /*)*/ ){ + /* Unexpected token,break immediately */ + pToken = pGen->pIn; + if( pToken >= pGen->pEnd ){ + pToken--; + } + rc = PH7_GenCompileError(pGen,E_ERROR,pToken->nLine, + "Catch: Unexpected token '%z',expecting right parenthesis ')'",&pToken->sData); + if( rc == SXERR_ABORT ){ + return SXERR_ABORT; + } + return SXERR_INVALID; + } + /* Compile the block */ + pGen->pIn++; /* Jump the right parenthesis */ + /* Create the catch block */ + rc = GenStateEnterBlock(&(*pGen),GEN_BLOCK_EXCEPTION,PH7_VmInstrLength(pGen->pVm),0,&pCatch); + if( rc != SXRET_OK ){ + return SXERR_ABORT; + } + /* Swap bytecode container */ + pInstrContainer = PH7_VmGetByteCodeContainer(pGen->pVm); + PH7_VmSetByteCodeContainer(pGen->pVm,&sCatch.sByteCode); + /* Compile the block */ + PH7_CompileBlock(&(*pGen),0); + /* Fix forward jumps now the destination is resolved */ + GenStateFixJumps(pCatch,-1,PH7_VmInstrLength(pGen->pVm)); + /* Emit the DONE instruction */ + PH7_VmEmitInstr(pGen->pVm,PH7_OP_DONE,0,0,0,0); + /* Leave the block */ + GenStateLeaveBlock(&(*pGen),0); + /* Restore the default container */ + PH7_VmSetByteCodeContainer(pGen->pVm,pInstrContainer); + /* Install the catch block */ + rc = SySetPut(&pException->sEntry,(const void *)&sCatch); + if( rc != SXRET_OK ){ + goto Mem; + } + return SXRET_OK; +Mem: + PH7_GenCompileError(&(*pGen),E_ERROR,nLine,"Fatal, PH7 engine is running out of memory"); + return SXERR_ABORT; +} +/* + * Compile a 'try' block. + * A function using an exception should be in a "try" block. + * If the exception does not trigger, the code will continue + * as normal. However if the exception triggers, an exception + * is "thrown". + */ +static sxi32 PH7_CompileTry(ph7_gen_state *pGen) +{ + ph7_exception *pException; + GenBlock *pTry; + sxu32 nJmpIdx; + sxi32 rc; + /* Create the exception container */ + pException = (ph7_exception *)SyMemBackendAlloc(&pGen->pVm->sAllocator,sizeof(ph7_exception)); + if( pException == 0 ){ + PH7_GenCompileError(&(*pGen),E_ERROR, + pGen->pIn->nLine,"Fatal, PH7 engine is running out of memory"); + return SXERR_ABORT; + } + /* Zero the structure */ + SyZero(pException,sizeof(ph7_exception)); + /* Initialize fields */ + SySetInit(&pException->sEntry,&pGen->pVm->sAllocator,sizeof(ph7_exception_block)); + pException->pVm = pGen->pVm; + /* Create the try block */ + rc = GenStateEnterBlock(&(*pGen),GEN_BLOCK_EXCEPTION,PH7_VmInstrLength(pGen->pVm),0,&pTry); + if( rc != SXRET_OK ){ + return SXERR_ABORT; + } + /* Emit the 'LOAD_EXCEPTION' instruction */ + PH7_VmEmitInstr(pGen->pVm,PH7_OP_LOAD_EXCEPTION,0,0,pException,&nJmpIdx); + /* Fix the jump later when the destination is resolved */ + GenStateNewJumpFixup(pTry,PH7_OP_LOAD_EXCEPTION,nJmpIdx); + pGen->pIn++; /* Jump the 'try' keyword */ + /* Compile the block */ + rc = PH7_CompileBlock(&(*pGen),0); + if( rc == SXERR_ABORT ){ + return SXERR_ABORT; + } + /* Fix forward jumps now the destination is resolved */ + GenStateFixJumps(pTry,-1,PH7_VmInstrLength(pGen->pVm)); + /* Emit the 'POP_EXCEPTION' instruction */ + PH7_VmEmitInstr(pGen->pVm,PH7_OP_POP_EXCEPTION,0,0,pException,0); + /* Leave the block */ + GenStateLeaveBlock(&(*pGen),0); + /* Compile the catch block */ + if( pGen->pIn >= pGen->pEnd || (pGen->pIn->nType & PH7_TK_KEYWORD) == 0 || + SX_PTR_TO_INT(pGen->pIn->pUserData) != PH7_TKWRD_CATCH ){ + SyToken *pTok = pGen->pIn; + if( pTok >= pGen->pEnd ){ + pTok--; /* Point back */ + } + /* Unexpected token */ + rc = PH7_GenCompileError(&(*pGen),E_ERROR,pTok->nLine, + "Try: Unexpected token '%z',expecting 'catch' block",&pTok->sData); + if( rc == SXERR_ABORT ){ + return SXERR_ABORT; + } + return SXRET_OK; + } + /* Compile one or more catch blocks */ + for(;;){ + if( pGen->pIn >= pGen->pEnd || (pGen->pIn->nType & PH7_TK_KEYWORD) == 0 + || SX_PTR_TO_INT(pGen->pIn->pUserData) != PH7_TKWRD_CATCH ){ + /* No more blocks */ + break; + } + /* Compile the catch block */ + rc = PH7_CompileCatch(&(*pGen),pException); + if( rc == SXERR_ABORT ){ + return SXERR_ABORT; + } + } + return SXRET_OK; +} +/* + * Compile a switch block. + * (See block-comment below for more information) + */ +static sxi32 GenStateCompileSwitchBlock(ph7_gen_state *pGen,sxu32 iTokenDelim,sxu32 *pBlockStart) +{ + sxi32 rc = SXRET_OK; + while( pGen->pIn < pGen->pEnd && (pGen->pIn->nType & (PH7_TK_SEMI/*';'*/|PH7_TK_COLON/*':'*/)) == 0 ){ + /* Unexpected token */ + rc = PH7_GenCompileError(&(*pGen),E_ERROR,pGen->pIn->nLine,"Unexpected token '%z'",&pGen->pIn->sData); + if( rc == SXERR_ABORT ){ + return SXERR_ABORT; + } + pGen->pIn++; + } + pGen->pIn++; + /* First instruction to execute in this block. */ + *pBlockStart = PH7_VmInstrLength(pGen->pVm); + /* Compile the block until we hit a case/default/endswitch keyword + * or the '}' token */ + for(;;){ + if( pGen->pIn >= pGen->pEnd ){ + /* No more input to process */ + break; + } + rc = SXRET_OK; + if( (pGen->pIn->nType & PH7_TK_KEYWORD) == 0 ){ + if( pGen->pIn->nType & PH7_TK_CCB /*'}' */ ){ + if( iTokenDelim != PH7_TK_CCB ){ + /* Unexpected token */ + rc = PH7_GenCompileError(&(*pGen),E_ERROR,pGen->pIn->nLine,"Unexpected token '%z'", + &pGen->pIn->sData); + if( rc == SXERR_ABORT ){ + return SXERR_ABORT; + } + /* FALL THROUGH */ + } + rc = SXERR_EOF; + break; + } + }else{ + sxi32 nKwrd; + /* Extract the keyword */ + nKwrd = SX_PTR_TO_INT(pGen->pIn->pUserData); + if( nKwrd == PH7_TKWRD_CASE || nKwrd == PH7_TKWRD_DEFAULT ){ + break; + } + if( nKwrd == PH7_TKWRD_ENDSWITCH /* endswitch; */){ + if( iTokenDelim != PH7_TK_KEYWORD ){ + /* Unexpected token */ + rc = PH7_GenCompileError(&(*pGen),E_ERROR,pGen->pIn->nLine,"Unexpected token '%z'", + &pGen->pIn->sData); + if( rc == SXERR_ABORT ){ + return SXERR_ABORT; + } + /* FALL THROUGH */ + } + /* Block compiled */ + break; + } + } + /* Compile block */ + rc = PH7_CompileBlock(&(*pGen),0); + if( rc == SXERR_ABORT ){ + return SXERR_ABORT; + } + } + return rc; +} +/* + * Compile a case eXpression. + * (See block-comment below for more information) + */ +static sxi32 GenStateCompileCaseExpr(ph7_gen_state *pGen,ph7_case_expr *pExpr) +{ + SySet *pInstrContainer; + SyToken *pEnd,*pTmp; + sxi32 iNest = 0; + sxi32 rc; + /* Delimit the expression */ + pEnd = pGen->pIn; + while( pEnd < pGen->pEnd ){ + if( pEnd->nType & PH7_TK_LPAREN /*(*/ ){ + /* Increment nesting level */ + iNest++; + }else if( pEnd->nType & PH7_TK_RPAREN /*)*/ ){ + /* Decrement nesting level */ + iNest--; + }else if( pEnd->nType & (PH7_TK_SEMI/*';'*/|PH7_TK_COLON/*;'*/) && iNest < 1 ){ + break; + } + pEnd++; + } + if( pGen->pIn >= pEnd ){ + rc = PH7_GenCompileError(pGen,E_ERROR,pGen->pIn->nLine,"Empty case expression"); + if( rc == SXERR_ABORT ){ + /* Error count limit reached,abort immediately */ + return SXERR_ABORT; + } + } + /* Swap token stream */ + pTmp = pGen->pEnd; + pGen->pEnd = pEnd; + pInstrContainer = PH7_VmGetByteCodeContainer(pGen->pVm); + PH7_VmSetByteCodeContainer(pGen->pVm,&pExpr->aByteCode); + rc = PH7_CompileExpr(&(*pGen),0,0); + /* Emit the done instruction */ + PH7_VmEmitInstr(pGen->pVm,PH7_OP_DONE,(rc != SXERR_EMPTY ? 1 : 0),0,0,0); + PH7_VmSetByteCodeContainer(pGen->pVm,pInstrContainer); + /* Update token stream */ + pGen->pIn = pEnd; + pGen->pEnd = pTmp; + if( rc == SXERR_ABORT ){ + return SXERR_ABORT; + } + return SXRET_OK; +} +/* + * Compile the smart switch statement. + * According to the PHP language reference manual + * The switch statement is similar to a series of IF statements on the same expression. + * In many occasions, you may want to compare the same variable (or expression) with many + * different values, and execute a different piece of code depending on which value it equals to. + * This is exactly what the switch statement is for. + * Note: Note that unlike some other languages, the continue statement applies to switch and acts + * similar to break. If you have a switch inside a loop and wish to continue to the next iteration + * of the outer loop, use continue 2. + * Note that switch/case does loose comparision. + * It is important to understand how the switch statement is executed in order to avoid mistakes. + * The switch statement executes line by line (actually, statement by statement). + * In the beginning, no code is executed. Only when a case statement is found with a value that + * matches the value of the switch expression does PHP begin to execute the statements. + * PHP continues to execute the statements until the end of the switch block, or the first time + * it sees a break statement. If you don't write a break statement at the end of a case's statement list. + * In a switch statement, the condition is evaluated only once and the result is compared to each + * case statement. In an elseif statement, the condition is evaluated again. If your condition + * is more complicated than a simple compare and/or is in a tight loop, a switch may be faster. + * The statement list for a case can also be empty, which simply passes control into the statement + * list for the next case. + * The case expression may be any expression that evaluates to a simple type, that is, integer + * or floating-point numbers and strings. + */ +static sxi32 PH7_CompileSwitch(ph7_gen_state *pGen) +{ + GenBlock *pSwitchBlock; + SyToken *pTmp,*pEnd; + ph7_switch *pSwitch; + sxu32 nToken; + sxu32 nLine; + sxi32 rc; + nLine = pGen->pIn->nLine; + /* Jump the 'switch' keyword */ + pGen->pIn++; + if( pGen->pIn >= pGen->pEnd || (pGen->pIn->nType & PH7_TK_LPAREN) == 0 ){ + /* Syntax error */ + rc = PH7_GenCompileError(pGen,E_ERROR,nLine,"Expected '(' after 'switch' keyword"); + if( rc == SXERR_ABORT ){ + /* Error count limit reached,abort immediately */ + return SXERR_ABORT; + } + goto Synchronize; + } + /* Jump the left parenthesis '(' */ + pGen->pIn++; + pEnd = 0; /* cc warning */ + /* Create the loop block */ + rc = GenStateEnterBlock(&(*pGen),GEN_BLOCK_LOOP|GEN_BLOCK_SWITCH, + PH7_VmInstrLength(pGen->pVm),0,&pSwitchBlock); + if( rc != SXRET_OK ){ + return SXERR_ABORT; + } + /* Delimit the condition */ + PH7_DelimitNestedTokens(pGen->pIn,pGen->pEnd,PH7_TK_LPAREN /* '(' */,PH7_TK_RPAREN /* ')' */,&pEnd); + if( pGen->pIn == pEnd || pEnd >= pGen->pEnd ){ + /* Empty expression */ + rc = PH7_GenCompileError(pGen,E_ERROR,nLine,"Expected expression after 'switch' keyword"); + if( rc == SXERR_ABORT ){ + /* Error count limit reached,abort immediately */ + return SXERR_ABORT; + } + } + /* Swap token streams */ + pTmp = pGen->pEnd; + pGen->pEnd = pEnd; + /* Compile the expression */ + rc = PH7_CompileExpr(&(*pGen),0,0); + if( rc == SXERR_ABORT ){ + /* Expression handler request an operation abort [i.e: Out-of-memory] */ + return SXERR_ABORT; + } + /* Update token stream */ + while(pGen->pIn < pEnd ){ + rc = PH7_GenCompileError(&(*pGen),E_ERROR,pGen->pIn->nLine, + "Switch: Unexpected token '%z'",&pGen->pIn->sData); + if( rc == SXERR_ABORT ){ + return SXERR_ABORT; + } + pGen->pIn++; + } + pGen->pIn = &pEnd[1]; + pGen->pEnd = pTmp; + if( pGen->pIn >= pGen->pEnd || &pGen->pIn[1] >= pGen->pEnd || + (pGen->pIn->nType & (PH7_TK_OCB/*'{'*/|PH7_TK_COLON/*:*/)) == 0 ){ + pTmp = pGen->pIn; + if( pTmp >= pGen->pEnd ){ + pTmp--; + } + /* Unexpected token */ + rc = PH7_GenCompileError(&(*pGen),E_ERROR,pTmp->nLine,"Switch: Unexpected token '%z'",&pTmp->sData); + if( rc == SXERR_ABORT ){ + return SXERR_ABORT; + } + goto Synchronize; + } + /* Set the delimiter token */ + if( pGen->pIn->nType & PH7_TK_COLON ){ + nToken = PH7_TK_KEYWORD; + /* Stop compilation when the 'endswitch;' keyword is seen */ + }else{ + nToken = PH7_TK_CCB; /* '}' */ + } + pGen->pIn++; /* Jump the leading curly braces/colons */ + /* Create the switch blocks container */ + pSwitch = (ph7_switch *)SyMemBackendAlloc(&pGen->pVm->sAllocator,sizeof(ph7_switch)); + if( pSwitch == 0 ){ + /* Abort compilation */ + PH7_GenCompileError(&(*pGen),E_ERROR,pGen->pIn->nLine,"Fatal, PH7 is running out of memory"); + return SXERR_ABORT; + } + /* Zero the structure */ + SyZero(pSwitch,sizeof(ph7_switch)); + /* Initialize fields */ + SySetInit(&pSwitch->aCaseExpr,&pGen->pVm->sAllocator,sizeof(ph7_case_expr)); + /* Emit the switch instruction */ + PH7_VmEmitInstr(pGen->pVm,PH7_OP_SWITCH,0,0,pSwitch,0); + /* Compile case blocks */ + for(;;){ + sxu32 nKwrd; + if( pGen->pIn >= pGen->pEnd ){ + /* No more input to process */ + break; + } + if( (pGen->pIn->nType & PH7_TK_KEYWORD) == 0 ){ + if( nToken != PH7_TK_CCB || (pGen->pIn->nType & PH7_TK_CCB /*}*/) == 0 ){ + /* Unexpected token */ + rc = PH7_GenCompileError(&(*pGen),E_ERROR,pGen->pIn->nLine,"Switch: Unexpected token '%z'", + &pGen->pIn->sData); + if( rc == SXERR_ABORT ){ + return SXERR_ABORT; + } + /* FALL THROUGH */ + } + /* Block compiled */ + break; + } + /* Extract the keyword */ + nKwrd = SX_PTR_TO_INT(pGen->pIn->pUserData); + if( nKwrd == PH7_TKWRD_ENDSWITCH /* endswitch; */){ + if( nToken != PH7_TK_KEYWORD ){ + /* Unexpected token */ + rc = PH7_GenCompileError(&(*pGen),E_ERROR,pGen->pIn->nLine,"Switch: Unexpected token '%z'", + &pGen->pIn->sData); + if( rc == SXERR_ABORT ){ + return SXERR_ABORT; + } + /* FALL THROUGH */ + } + /* Block compiled */ + break; + } + if( nKwrd == PH7_TKWRD_DEFAULT ){ + /* + * Accroding to the PHP language reference manual + * A special case is the default case. This case matches anything + * that wasn't matched by the other cases. + */ + if( pSwitch->nDefault > 0 ){ + /* Default case already compiled */ + rc = PH7_GenCompileError(&(*pGen),E_WARNING,pGen->pIn->nLine,"Switch: 'default' case already compiled"); + if( rc == SXERR_ABORT ){ + return SXERR_ABORT; + } + } + pGen->pIn++; /* Jump the 'default' keyword */ + /* Compile the default block */ + rc = GenStateCompileSwitchBlock(pGen,nToken,&pSwitch->nDefault); + if( rc == SXERR_ABORT){ + return SXERR_ABORT; + }else if( rc == SXERR_EOF ){ + break; + } + }else if( nKwrd == PH7_TKWRD_CASE ){ + ph7_case_expr sCase; + /* Standard case block */ + pGen->pIn++; /* Jump the 'case' keyword */ + /* initialize the structure */ + SySetInit(&sCase.aByteCode,&pGen->pVm->sAllocator,sizeof(VmInstr)); + /* Compile the case expression */ + rc = GenStateCompileCaseExpr(pGen,&sCase); + if( rc == SXERR_ABORT ){ + return SXERR_ABORT; + } + /* Compile the case block */ + rc = GenStateCompileSwitchBlock(pGen,nToken,&sCase.nStart); + /* Insert in the switch container */ + SySetPut(&pSwitch->aCaseExpr,(const void *)&sCase); + if( rc == SXERR_ABORT){ + return SXERR_ABORT; + }else if( rc == SXERR_EOF ){ + break; + } + }else{ + /* Unexpected token */ + rc = PH7_GenCompileError(&(*pGen),E_ERROR,pGen->pIn->nLine,"Switch: Unexpected token '%z'", + &pGen->pIn->sData); + if( rc == SXERR_ABORT ){ + return SXERR_ABORT; + } + break; + } + } + /* Fix all jumps now the destination is resolved */ + pSwitch->nOut = PH7_VmInstrLength(pGen->pVm); + GenStateFixJumps(pSwitchBlock,-1,PH7_VmInstrLength(pGen->pVm)); + /* Release the loop block */ + GenStateLeaveBlock(pGen,0); + if( pGen->pIn < pGen->pEnd ){ + /* Jump the trailing curly braces or the endswitch keyword*/ + pGen->pIn++; + } + /* Statement successfully compiled */ + return SXRET_OK; +Synchronize: + /* Synchronize with the first semi-colon */ + while( pGen->pIn < pGen->pEnd && (pGen->pIn->nType & PH7_TK_SEMI) == 0 ){ + pGen->pIn++; + } + return SXRET_OK; +} +/* + * Generate bytecode for a given expression tree. + * If something goes wrong while generating bytecode + * for the expression tree (A very unlikely scenario) + * this function takes care of generating the appropriate + * error message. + */ +static sxi32 GenStateEmitExprCode( + ph7_gen_state *pGen, /* Code generator state */ + ph7_expr_node *pNode, /* Root of the expression tree */ + sxi32 iFlags /* Control flags */ + ) +{ + VmInstr *pInstr; + sxu32 nJmpIdx; + sxi32 iP1 = 0; + sxu32 iP2 = 0; + void *p3 = 0; + sxi32 iVmOp; + sxi32 rc; + if( pNode->xCode ){ + SyToken *pTmpIn,*pTmpEnd; + /* Compile node */ + SWAP_DELIMITER(pGen,pNode->pStart,pNode->pEnd); + rc = pNode->xCode(&(*pGen),iFlags); + RE_SWAP_DELIMITER(pGen); + return rc; + } + if( pNode->pOp == 0 ){ + PH7_GenCompileError(&(*pGen),E_ERROR,pNode->pStart->nLine, + "Invalid expression node,PH7 is aborting compilation"); + return SXERR_ABORT; + } + iVmOp = pNode->pOp->iVmOp; + if( pNode->pOp->iOp == EXPR_OP_QUESTY ){ + sxu32 nJz,nJmp; + /* Ternary operator require special handling */ + /* Phase#1: Compile the condition */ + rc = GenStateEmitExprCode(&(*pGen),pNode->pCond,iFlags); + if( rc != SXRET_OK ){ + return rc; + } + nJz = nJmp = 0; /* cc -O6 warning */ + /* Phase#2: Emit the false jump */ + PH7_VmEmitInstr(pGen->pVm,PH7_OP_JZ,0,0,0,&nJz); + if( pNode->pLeft ){ + /* Phase#3: Compile the 'then' expression */ + rc = GenStateEmitExprCode(&(*pGen),pNode->pLeft,iFlags); + if( rc != SXRET_OK ){ + return rc; + } + } + /* Phase#4: Emit the unconditional jump */ + PH7_VmEmitInstr(pGen->pVm,PH7_OP_JMP,0,0,0,&nJmp); + /* Phase#5: Fix the false jump now the jump destination is resolved. */ + pInstr = PH7_VmGetInstr(pGen->pVm,nJz); + if( pInstr ){ + pInstr->iP2 = PH7_VmInstrLength(pGen->pVm); + } + /* Phase#6: Compile the 'else' expression */ + if( pNode->pRight ){ + rc = GenStateEmitExprCode(&(*pGen),pNode->pRight,iFlags); + if( rc != SXRET_OK ){ + return rc; + } + } + if( nJmp > 0 ){ + /* Phase#7: Fix the unconditional jump */ + pInstr = PH7_VmGetInstr(pGen->pVm,nJmp); + if( pInstr ){ + pInstr->iP2 = PH7_VmInstrLength(pGen->pVm); + } + } + /* All done */ + return SXRET_OK; + } + /* Generate code for the left tree */ + if( pNode->pLeft ){ + if( iVmOp == PH7_OP_CALL ){ + ph7_expr_node **apNode; + sxi32 n; + /* Recurse and generate bytecodes for function arguments */ + apNode = (ph7_expr_node **)SySetBasePtr(&pNode->aNodeArgs); + /* Read-only load */ + iFlags |= EXPR_FLAG_RDONLY_LOAD; + for( n = 0 ; n < (sxi32)SySetUsed(&pNode->aNodeArgs) ; ++n ){ + rc = GenStateEmitExprCode(&(*pGen),apNode[n],iFlags&~EXPR_FLAG_LOAD_IDX_STORE); + if( rc != SXRET_OK ){ + return rc; + } + } + /* Total number of given arguments */ + iP1 = (sxi32)SySetUsed(&pNode->aNodeArgs); + /* Remove stale flags now */ + iFlags &= ~EXPR_FLAG_RDONLY_LOAD; + } + rc = GenStateEmitExprCode(&(*pGen),pNode->pLeft,iFlags); + if( rc != SXRET_OK ){ + return rc; + } + if( iVmOp == PH7_OP_CALL ){ + pInstr = PH7_VmPeekInstr(pGen->pVm); + if( pInstr ){ + if ( pInstr->iOp == PH7_OP_LOADC ){ + /* Prevent constant expansion */ + pInstr->iP1 = 0; + }else if( pInstr->iOp == PH7_OP_MEMBER /* $a->b(1,2,3) */ || pInstr->iOp == PH7_OP_NEW ){ + /* Method call,flag that */ + pInstr->iP2 = 1; + } + } + }else if( iVmOp == PH7_OP_LOAD_IDX ){ + ph7_expr_node **apNode; + sxi32 n; + /* Recurse and generate bytecodes for array index */ + apNode = (ph7_expr_node **)SySetBasePtr(&pNode->aNodeArgs); + for( n = 0 ; n < (sxi32)SySetUsed(&pNode->aNodeArgs) ; ++n ){ + rc = GenStateEmitExprCode(&(*pGen),apNode[n],iFlags&~EXPR_FLAG_LOAD_IDX_STORE); + if( rc != SXRET_OK ){ + return rc; + } + } + if( SySetUsed(&pNode->aNodeArgs) > 0 ){ + iP1 = 1; /* Node have an index associated with it */ + } + if( iFlags & EXPR_FLAG_LOAD_IDX_STORE ){ + /* Create an empty entry when the desired index is not found */ + iP2 = 1; + } + }else if( pNode->pOp->iOp == EXPR_OP_COMMA ){ + /* POP the left node */ + PH7_VmEmitInstr(pGen->pVm,PH7_OP_POP,1,0,0,0); + } + } + rc = SXRET_OK; + nJmpIdx = 0; + /* Generate code for the right tree */ + if( pNode->pRight ){ + if( iVmOp == PH7_OP_LAND ){ + /* Emit the false jump so we can short-circuit the logical and */ + PH7_VmEmitInstr(pGen->pVm,PH7_OP_JZ,1/* Keep the value on the stack */,0,0,&nJmpIdx); + }else if (iVmOp == PH7_OP_LOR ){ + /* Emit the true jump so we can short-circuit the logical or*/ + PH7_VmEmitInstr(pGen->pVm,PH7_OP_JNZ,1/* Keep the value on the stack */,0,0,&nJmpIdx); + }else if( pNode->pOp->iPrec == 18 /* Combined binary operators [i.e: =,'.=','+=',*=' ...] precedence */ ){ + iFlags |= EXPR_FLAG_LOAD_IDX_STORE; + } + rc = GenStateEmitExprCode(&(*pGen),pNode->pRight,iFlags); + if( iVmOp == PH7_OP_STORE ){ + pInstr = PH7_VmPeekInstr(pGen->pVm); + if( pInstr ){ + if( pInstr->iOp == PH7_OP_LOAD_LIST ){ + /* Hide the STORE instruction */ + iVmOp = 0; + }else if(pInstr->iOp == PH7_OP_MEMBER ){ + /* Perform a member store operation [i.e: $this->x = 50] */ + iP2 = 1; + }else{ + if( pInstr->iOp == PH7_OP_LOAD_IDX ){ + /* Transform the STORE instruction to STORE_IDX instruction */ + iVmOp = PH7_OP_STORE_IDX; + iP1 = pInstr->iP1; + }else{ + p3 = pInstr->p3; + } + /* POP the last dynamic load instruction */ + (void)PH7_VmPopInstr(pGen->pVm); + } + } + }else if( iVmOp == PH7_OP_STORE_REF ){ + pInstr = PH7_VmPopInstr(pGen->pVm); + if( pInstr ){ + if( pInstr->iOp == PH7_OP_LOAD_IDX ){ + /* Array insertion by reference [i.e: $pArray[] =& $some_var; ] + * We have to convert the STORE_REF instruction into STORE_IDX_REF + */ + iVmOp = PH7_OP_STORE_IDX_REF; + iP1 = pInstr->iP1; + iP2 = pInstr->iP2; + p3 = pInstr->p3; + }else{ + p3 = pInstr->p3; + } + } + } + } + if( iVmOp > 0 ){ + if( iVmOp == PH7_OP_INCR || iVmOp == PH7_OP_DECR ){ + if( pNode->iFlags & EXPR_NODE_PRE_INCR ){ + /* Pre-increment/decrement operator [i.e: ++$i,--$j ] */ + iP1 = 1; + } + }else if( iVmOp == PH7_OP_NEW ){ + pInstr = PH7_VmPeekInstr(pGen->pVm); + if( pInstr && pInstr->iOp == PH7_OP_CALL ){ + VmInstr *pPrev; + pPrev = PH7_VmPeekNextInstr(pGen->pVm); + if( pPrev == 0 || pPrev->iOp != PH7_OP_MEMBER ){ + /* Pop the call instruction */ + iP1 = pInstr->iP1; + (void)PH7_VmPopInstr(pGen->pVm); + } + } + }else if( iVmOp == PH7_OP_MEMBER){ + if( pNode->pOp->iOp == EXPR_OP_DC /* '::' */){ + /* Static member access,remember that */ + iP1 = 1; + pInstr = PH7_VmPeekInstr(pGen->pVm); + if( pInstr && pInstr->iOp == PH7_OP_LOAD ){ + p3 = pInstr->p3; + (void)PH7_VmPopInstr(pGen->pVm); + } + } + } + /* Finally,emit the VM instruction associated with this operator */ + PH7_VmEmitInstr(pGen->pVm,iVmOp,iP1,iP2,p3,0); + if( nJmpIdx > 0 ){ + /* Fix short-circuited jumps now the destination is resolved */ + pInstr = PH7_VmGetInstr(pGen->pVm,nJmpIdx); + if( pInstr ){ + pInstr->iP2 = PH7_VmInstrLength(pGen->pVm); + } + } + } + return rc; +} +/* + * Compile a PHP expression. + * According to the PHP language reference manual: + * Expressions are the most important building stones of PHP. + * In PHP, almost anything you write is an expression. + * The simplest yet most accurate way to define an expression + * is "anything that has a value". + * If something goes wrong while compiling the expression,this + * function takes care of generating the appropriate error + * message. + */ +static sxi32 PH7_CompileExpr( + ph7_gen_state *pGen, /* Code generator state */ + sxi32 iFlags, /* Control flags */ + sxi32 (*xTreeValidator)(ph7_gen_state *,ph7_expr_node *) /* Node validator callback.NULL otherwise */ + ) +{ + ph7_expr_node *pRoot; + SySet sExprNode; + SyToken *pEnd; + sxi32 nExpr; + sxi32 iNest; + sxi32 rc; + /* Initialize worker variables */ + nExpr = 0; + pRoot = 0; + SySetInit(&sExprNode,&pGen->pVm->sAllocator,sizeof(ph7_expr_node *)); + SySetAlloc(&sExprNode,0x10); + rc = SXRET_OK; + /* Delimit the expression */ + pEnd = pGen->pIn; + iNest = 0; + while( pEnd < pGen->pEnd ){ + if( pEnd->nType & PH7_TK_OCB /* '{' */ ){ + /* Ticket 1433-30: Annonymous/Closure functions body */ + iNest++; + }else if(pEnd->nType & PH7_TK_CCB /* '}' */ ){ + iNest--; + }else if( pEnd->nType & PH7_TK_SEMI /* ';' */ ){ + if( iNest <= 0 ){ + break; + } + } + pEnd++; + } + if( iFlags & EXPR_FLAG_COMMA_STATEMENT ){ + SyToken *pEnd2 = pGen->pIn; + iNest = 0; + /* Stop at the first comma */ + while( pEnd2 < pEnd ){ + if( pEnd2->nType & (PH7_TK_OCB/*'{'*/|PH7_TK_OSB/*'['*/|PH7_TK_LPAREN/*'('*/) ){ + iNest++; + }else if(pEnd2->nType & (PH7_TK_CCB/*'}'*/|PH7_TK_CSB/*']'*/|PH7_TK_RPAREN/*')'*/)){ + iNest--; + }else if( pEnd2->nType & PH7_TK_COMMA /*','*/ ){ + if( iNest <= 0 ){ + break; + } + } + pEnd2++; + } + if( pEnd2 pGen->pIn ){ + SyToken *pTmp = pGen->pEnd; + /* Swap delimiter */ + pGen->pEnd = pEnd; + /* Try to get an expression tree */ + rc = PH7_ExprMakeTree(&(*pGen),&sExprNode,&pRoot); + if( rc == SXRET_OK && pRoot ){ + rc = SXRET_OK; + if( xTreeValidator ){ + /* Call the upper layer validator callback */ + rc = xTreeValidator(&(*pGen),pRoot); + } + if( rc != SXERR_ABORT ){ + /* Generate code for the given tree */ + rc = GenStateEmitExprCode(&(*pGen),pRoot,iFlags); + } + nExpr = 1; + } + /* Release the whole tree */ + PH7_ExprFreeTree(&(*pGen),&sExprNode); + /* Synchronize token stream */ + pGen->pEnd = pTmp; + pGen->pIn = pEnd; + if( rc == SXERR_ABORT ){ + SySetRelease(&sExprNode); + return SXERR_ABORT; + } + } + SySetRelease(&sExprNode); + return nExpr > 0 ? SXRET_OK : SXERR_EMPTY; +} +/* + * Return a pointer to the node construct handler associated + * with a given node type [i.e: string,integer,float,...]. + */ +PH7_PRIVATE ProcNodeConstruct PH7_GetNodeHandler(sxu32 nNodeType) +{ + if( nNodeType & PH7_TK_NUM ){ + /* Numeric literal: Either real or integer */ + return PH7_CompileNumLiteral; + }else if( nNodeType & PH7_TK_DSTR ){ + /* Double quoted string */ + return PH7_CompileString; + }else if( nNodeType & PH7_TK_SSTR ){ + /* Single quoted string */ + return PH7_CompileSimpleString; + }else if( nNodeType & PH7_TK_HEREDOC ){ + /* Heredoc */ + return PH7_CompileHereDoc; + }else if( nNodeType & PH7_TK_NOWDOC ){ + /* Nowdoc */ + return PH7_CompileNowDoc; + }else if( nNodeType & PH7_TK_BSTR ){ + /* Backtick quoted string */ + return PH7_CompileBacktic; + } + return 0; +} +/* + * PHP Language construct table. + */ +static const LangConstruct aLangConstruct[] = { + { PH7_TKWRD_ECHO, PH7_CompileEcho }, /* echo language construct */ + { PH7_TKWRD_IF, PH7_CompileIf }, /* if statement */ + { PH7_TKWRD_FOR, PH7_CompileFor }, /* for statement */ + { PH7_TKWRD_WHILE, PH7_CompileWhile }, /* while statement */ + { PH7_TKWRD_FOREACH, PH7_CompileForeach }, /* foreach statement */ + { PH7_TKWRD_FUNCTION, PH7_CompileFunction }, /* function statement */ + { PH7_TKWRD_CONTINUE, PH7_CompileContinue }, /* continue statement */ + { PH7_TKWRD_BREAK, PH7_CompileBreak }, /* break statement */ + { PH7_TKWRD_RETURN, PH7_CompileReturn }, /* return statement */ + { PH7_TKWRD_SWITCH, PH7_CompileSwitch }, /* Switch statement */ + { PH7_TKWRD_DO, PH7_CompileDoWhile }, /* do{ }while(); statement */ + { PH7_TKWRD_GLOBAL, PH7_CompileGlobal }, /* global statement */ + { PH7_TKWRD_STATIC, PH7_CompileStatic }, /* static statement */ + { PH7_TKWRD_DIE, PH7_CompileHalt }, /* die language construct */ + { PH7_TKWRD_EXIT, PH7_CompileHalt }, /* exit language construct */ + { PH7_TKWRD_TRY, PH7_CompileTry }, /* try statement */ + { PH7_TKWRD_THROW, PH7_CompileThrow }, /* throw statement */ + { PH7_TKWRD_GOTO, PH7_CompileGoto }, /* goto statement */ + { PH7_TKWRD_CONST, PH7_CompileConstant }, /* const statement */ + { PH7_TKWRD_VAR, PH7_CompileVar }, /* var statement */ + { PH7_TKWRD_NAMESPACE, PH7_CompileNamespace }, /* namespace statement */ + { PH7_TKWRD_USE, PH7_CompileUse }, /* use statement */ + { PH7_TKWRD_DECLARE, PH7_CompileDeclare } /* declare statement */ +}; +/* + * Return a pointer to the statement handler routine associated + * with a given PHP keyword [i.e: if,for,while,...]. + */ +static ProcLangConstruct GenStateGetStatementHandler( + sxu32 nKeywordID, /* Keyword ID*/ + SyToken *pLookahed /* Look-ahead token */ + ) +{ + sxu32 n = 0; + for(;;){ + if( n >= SX_ARRAYSIZE(aLangConstruct) ){ + break; + } + if( aLangConstruct[n].nID == nKeywordID ){ + if( nKeywordID == PH7_TKWRD_STATIC && pLookahed && (pLookahed->nType & PH7_TK_OP)){ + const ph7_expr_op *pOp = (const ph7_expr_op *)pLookahed->pUserData; + if( pOp && pOp->iOp == EXPR_OP_DC /*::*/){ + /* 'static' (class context),return null */ + return 0; + } + } + /* Return a pointer to the handler. + */ + return aLangConstruct[n].xConstruct; + } + n++; + } + if( pLookahed ){ + if(nKeywordID == PH7_TKWRD_INTERFACE && (pLookahed->nType & PH7_TK_ID) ){ + return PH7_CompileClassInterface; + }else if(nKeywordID == PH7_TKWRD_CLASS && (pLookahed->nType & PH7_TK_ID) ){ + return PH7_CompileClass; + }else if( nKeywordID == PH7_TKWRD_ABSTRACT && (pLookahed->nType & PH7_TK_KEYWORD) + && SX_PTR_TO_INT(pLookahed->pUserData) == PH7_TKWRD_CLASS ){ + return PH7_CompileAbstractClass; + }else if( nKeywordID == PH7_TKWRD_FINAL && (pLookahed->nType & PH7_TK_KEYWORD) + && SX_PTR_TO_INT(pLookahed->pUserData) == PH7_TKWRD_CLASS ){ + return PH7_CompileFinalClass; + } + } + /* Not a language construct */ + return 0; +} +/* + * Check if the given keyword is in fact a PHP language construct. + * Return TRUE on success. FALSE otheriwse. + */ +static int GenStateisLangConstruct(sxu32 nKeyword) +{ + int rc; + rc = PH7_IsLangConstruct(nKeyword,TRUE); + if( rc == FALSE ){ + if( nKeyword == PH7_TKWRD_SELF || nKeyword == PH7_TKWRD_PARENT || nKeyword == PH7_TKWRD_STATIC + /*|| nKeyword == PH7_TKWRD_CLASS || nKeyword == PH7_TKWRD_FINAL || nKeyword == PH7_TKWRD_EXTENDS + || nKeyword == PH7_TKWRD_ABSTRACT || nKeyword == PH7_TKWRD_INTERFACE + || nKeyword == PH7_TKWRD_PUBLIC || nKeyword == PH7_TKWRD_PROTECTED + || nKeyword == PH7_TKWRD_PRIVATE || nKeyword == PH7_TKWRD_IMPLEMENTS + */ + ){ + rc = TRUE; + } + } + return rc; +} +/* + * Compile a PHP chunk. + * If something goes wrong while compiling the PHP chunk,this function + * takes care of generating the appropriate error message. + */ +static sxi32 GenStateCompileChunk( + ph7_gen_state *pGen, /* Code generator state */ + sxi32 iFlags /* Compile flags */ + ) +{ + ProcLangConstruct xCons; + sxi32 rc; + rc = SXRET_OK; /* Prevent compiler warning */ + for(;;){ + if( pGen->pIn >= pGen->pEnd ){ + /* No more input to process */ + break; + } + if( pGen->pIn->nType & PH7_TK_OCB /* '{' */ ){ + /* Compile block */ + rc = PH7_CompileBlock(&(*pGen),0); + if( rc == SXERR_ABORT ){ + break; + } + }else{ + xCons = 0; + if( pGen->pIn->nType & PH7_TK_KEYWORD ){ + sxu32 nKeyword = (sxu32)SX_PTR_TO_INT(pGen->pIn->pUserData); + /* Try to extract a language construct handler */ + xCons = GenStateGetStatementHandler(nKeyword,(&pGen->pIn[1] < pGen->pEnd) ? &pGen->pIn[1] : 0); + if( xCons == 0 && GenStateisLangConstruct(nKeyword) == FALSE ){ + rc = PH7_GenCompileError(pGen,E_ERROR,pGen->pIn->nLine, + "Syntax error: Unexpected keyword '%z'", + &pGen->pIn->sData); + if( rc == SXERR_ABORT ){ + break; + } + /* Synchronize with the first semi-colon and avoid compiling + * this erroneous statement. + */ + xCons = PH7_ErrorRecover; + } + }else if( (pGen->pIn->nType & PH7_TK_ID) && (&pGen->pIn[1] < pGen->pEnd) + && (pGen->pIn[1].nType & PH7_TK_COLON /*':'*/) ){ + /* Label found [i.e: Out: ],point to the routine responsible of compiling it */ + xCons = PH7_CompileLabel; + } + if( xCons == 0 ){ + /* Assume an expression an try to compile it */ + rc = PH7_CompileExpr(&(*pGen),0,0); + if( rc != SXERR_EMPTY ){ + /* Pop l-value */ + PH7_VmEmitInstr(pGen->pVm,PH7_OP_POP,1,0,0,0); + } + }else{ + /* Go compile the sucker */ + rc = xCons(&(*pGen)); + } + if( rc == SXERR_ABORT ){ + /* Request to abort compilation */ + break; + } + } + /* Ignore trailing semi-colons ';' */ + while( pGen->pIn < pGen->pEnd && (pGen->pIn->nType & PH7_TK_SEMI) ){ + pGen->pIn++; + } + if( iFlags & PH7_COMPILE_SINGLE_STMT ){ + /* Compile a single statement and return */ + break; + } + /* LOOP ONE */ + /* LOOP TWO */ + /* LOOP THREE */ + /* LOOP FOUR */ + } + /* Return compilation status */ + return rc; +} +/* + * Compile a Raw PHP chunk. + * If something goes wrong while compiling the PHP chunk,this function + * takes care of generating the appropriate error message. + */ +static sxi32 PH7_CompilePHP( + ph7_gen_state *pGen, /* Code generator state */ + SySet *pTokenSet, /* Token set */ + int is_expr /* TRUE if we are dealing with a simple expression */ + ) +{ + SyToken *pScript = pGen->pRawIn; /* Script to compile */ + sxi32 rc; + /* Reset the token set */ + SySetReset(&(*pTokenSet)); + /* Mark as the default token set */ + pGen->pTokenSet = &(*pTokenSet); + /* Advance the stream cursor */ + pGen->pRawIn++; + /* Tokenize the PHP chunk first */ + PH7_TokenizePHP(SyStringData(&pScript->sData),SyStringLength(&pScript->sData),pScript->nLine,&(*pTokenSet)); + /* Point to the head and tail of the token stream. */ + pGen->pIn = (SyToken *)SySetBasePtr(pTokenSet); + pGen->pEnd = &pGen->pIn[SySetUsed(pTokenSet)]; + if( is_expr ){ + rc = SXERR_EMPTY; + if( pGen->pIn < pGen->pEnd ){ + /* A simple expression,compile it */ + rc = PH7_CompileExpr(pGen,0,0); + } + /* Emit the DONE instruction */ + PH7_VmEmitInstr(pGen->pVm,PH7_OP_DONE,(rc != SXERR_EMPTY ? 1 : 0),0,0,0); + return SXRET_OK; + } + if( pGen->pIn < pGen->pEnd && ( pGen->pIn->nType & PH7_TK_EQUAL ) ){ + static const sxu32 nKeyID = PH7_TKWRD_ECHO; + /* + * Shortcut syntax for the 'echo' language construct. + * According to the PHP reference manual: + * echo() also has a shortcut syntax, where you can + * immediately follow + * the opening tag with an equals sign as follows: + * is the same as + * Symisc extension: + * This short syntax works with all PHP opening + * tags unlike the default PHP engine that handle + * only short tag. + */ + /* Ticket 1433-009: Emulate the 'echo' call */ + pGen->pIn->nType = PH7_TK_KEYWORD; + pGen->pIn->pUserData = SX_INT_TO_PTR(nKeyID); + SyStringInitFromBuf(&pGen->pIn->sData,"echo",sizeof("echo")-1); + rc = PH7_CompileExpr(pGen,0,0); + if( rc != SXERR_EMPTY ){ + PH7_VmEmitInstr(pGen->pVm,PH7_OP_POP,1,0,0,0); + } + return SXRET_OK; + } + /* Compile the PHP chunk */ + rc = GenStateCompileChunk(pGen,0); + /* Fix exceptions jumps */ + GenStateFixJumps(pGen->pCurrent,PH7_OP_THROW,PH7_VmInstrLength(pGen->pVm)); + /* Fix gotos now, the jump destination is resolved */ + if( SXERR_ABORT == GenStateFixGoto(&(*pGen),0) ){ + rc = SXERR_ABORT; + } + /* Reset container */ + SySetReset(&pGen->aGoto); + SySetReset(&pGen->aLabel); + /* Compilation result */ + return rc; +} +/* + * Compile a raw chunk. The raw chunk can contain PHP code embedded + * in HTML, XML and so on. This function handle all the stuff. + * This is the only compile interface exported from this file. + */ +PH7_PRIVATE sxi32 PH7_CompileScript( + ph7_vm *pVm, /* Generate PH7 byte-codes for this Virtual Machine */ + SyString *pScript, /* Script to compile */ + sxi32 iFlags /* Compile flags */ + ) +{ + SySet aPhpToken,aRawToken; + ph7_gen_state *pCodeGen; + ph7_value *pRawObj; + sxu32 nObjIdx; + sxi32 nRawObj; + int is_expr; + sxi32 rc; + if( pScript->nByte < 1 ){ + /* Nothing to compile */ + return PH7_OK; + } + /* Initialize the tokens containers */ + SySetInit(&aRawToken,&pVm->sAllocator,sizeof(SyToken)); + SySetInit(&aPhpToken,&pVm->sAllocator,sizeof(SyToken)); + SySetAlloc(&aPhpToken,0xc0); + is_expr = 0; + if( iFlags & PH7_PHP_ONLY ){ + SyToken sTmp; + /* PHP only: -*/ + sTmp.nLine = 1; + sTmp.nType = PH7_TOKEN_PHP; + sTmp.pUserData = 0; + SyStringDupPtr(&sTmp.sData,pScript); + SySetPut(&aRawToken,(const void *)&sTmp); + if( iFlags & PH7_PHP_EXPR ){ + /* A simple PHP expression */ + is_expr = 1; + } + }else{ + /* Tokenize raw text */ + SySetAlloc(&aRawToken,32); + PH7_TokenizeRawText(pScript->zString,pScript->nByte,&aRawToken); + } + pCodeGen = &pVm->sCodeGen; + /* Process high-level tokens */ + pCodeGen->pRawIn = (SyToken *)SySetBasePtr(&aRawToken); + pCodeGen->pRawEnd = &pCodeGen->pRawIn[SySetUsed(&aRawToken)]; + rc = PH7_OK; + if( is_expr ){ + /* Compile the expression */ + rc = PH7_CompilePHP(pCodeGen,&aPhpToken,TRUE); + goto cleanup; + } + nObjIdx = 0; + /* Start the compilation process */ + for(;;){ + if( pCodeGen->pRawIn >= pCodeGen->pRawEnd ){ + break; /* No more tokens to process */ + } + if( pCodeGen->pRawIn->nType & PH7_TOKEN_PHP ){ + /* Compile the PHP chunk */ + rc = PH7_CompilePHP(pCodeGen,&aPhpToken,FALSE); + if( rc == SXERR_ABORT ){ + break; + } + continue; + } + /* Raw chunk: [i.e: HTML, XML, etc.] */ + nRawObj = 0; + while( (pCodeGen->pRawIn < pCodeGen->pRawEnd) && (pCodeGen->pRawIn->nType != PH7_TOKEN_PHP) ){ + /* Consume the raw chunk without any processing */ + pRawObj = PH7_ReserveConstObj(&(*pVm),&nObjIdx); + if( pRawObj == 0 ){ + rc = SXERR_MEM; + break; + } + /* Mark as constant and emit the load constant instruction */ + PH7_MemObjInitFromString(pVm,pRawObj,&pCodeGen->pRawIn->sData); + PH7_VmEmitInstr(&(*pVm),PH7_OP_LOADC,0,nObjIdx,0,0); + ++nRawObj; + pCodeGen->pRawIn++; /* Next chunk */ + } + if( nRawObj > 0 ){ + /* Emit the consume instruction */ + PH7_VmEmitInstr(&(*pVm),PH7_OP_CONSUME,nRawObj,0,0,0); + } + } +cleanup: + SySetRelease(&aRawToken); + SySetRelease(&aPhpToken); + return rc; +} +/* + * Utility routines.Initialize the code generator. + */ +PH7_PRIVATE sxi32 PH7_InitCodeGenerator( + ph7_vm *pVm, /* Target VM */ + ProcConsumer xErr, /* Error log consumer callabck */ + void *pErrData /* Last argument to xErr() */ + ) +{ + ph7_gen_state *pGen = &pVm->sCodeGen; + /* Zero the structure */ + SyZero(pGen,sizeof(ph7_gen_state)); + /* Initial state */ + pGen->pVm = &(*pVm); + pGen->xErr = xErr; + pGen->pErrData = pErrData; + SySetInit(&pGen->aLabel,&pVm->sAllocator,sizeof(Label)); + SySetInit(&pGen->aGoto,&pVm->sAllocator,sizeof(JumpFixup)); + SyHashInit(&pGen->hLiteral,&pVm->sAllocator,0,0); + SyHashInit(&pGen->hVar,&pVm->sAllocator,0,0); + /* Error log buffer */ + SyBlobInit(&pGen->sErrBuf,&pVm->sAllocator); + /* General purpose working buffer */ + SyBlobInit(&pGen->sWorker,&pVm->sAllocator); + /* Create the global scope */ + GenStateInitBlock(pGen,&pGen->sGlobal,GEN_BLOCK_GLOBAL,PH7_VmInstrLength(&(*pVm)),0); + /* Point to the global scope */ + pGen->pCurrent = &pGen->sGlobal; + return SXRET_OK; +} +/* + * Utility routines. Reset the code generator to it's initial state. + */ +PH7_PRIVATE sxi32 PH7_ResetCodeGenerator( + ph7_vm *pVm, /* Target VM */ + ProcConsumer xErr, /* Error log consumer callabck */ + void *pErrData /* Last argument to xErr() */ + ) +{ + ph7_gen_state *pGen = &pVm->sCodeGen; + GenBlock *pBlock,*pParent; + /* Reset state */ + SySetReset(&pGen->aLabel); + SySetReset(&pGen->aGoto); + SyBlobRelease(&pGen->sErrBuf); + SyBlobRelease(&pGen->sWorker); + /* Point to the global scope */ + pBlock = pGen->pCurrent; + while( pBlock->pParent != 0 ){ + pParent = pBlock->pParent; + GenStateFreeBlock(pBlock); + pBlock = pParent; + } + pGen->xErr = xErr; + pGen->pErrData = pErrData; + pGen->pCurrent = &pGen->sGlobal; + pGen->pRawIn = pGen->pRawEnd = 0; + pGen->pIn = pGen->pEnd = 0; + pGen->nErr = 0; + return SXRET_OK; +} +/* + * Generate a compile-time error message. + * If the error count limit is reached (usually 15 error message) + * this function return SXERR_ABORT.In that case upper-layers must + * abort compilation immediately. + */ +PH7_PRIVATE sxi32 PH7_GenCompileError(ph7_gen_state *pGen,sxi32 nErrType,sxu32 nLine,const char *zFormat,...) +{ + SyBlob *pWorker = &pGen->sErrBuf; + const char *zErr = "Error"; + SyString *pFile; + va_list ap; + sxi32 rc; + /* Reset the working buffer */ + SyBlobReset(pWorker); + /* Peek the processed file path if available */ + pFile = (SyString *)SySetPeek(&pGen->pVm->aFiles); + if( pFile && pGen->xErr ){ + /* Append file name */ + SyBlobAppend(pWorker,pFile->zString,pFile->nByte); + SyBlobAppend(pWorker,(const void *)": ",sizeof(": ")-1); + } + if( nErrType == E_ERROR ){ + /* Increment the error counter */ + pGen->nErr++; + if( pGen->nErr > 15 ){ + /* Error count limit reached */ + if( pGen->xErr ){ + SyBlobFormat(pWorker,"%u Error count limit reached,PH7 is aborting compilation\n",nLine); + if( SyBlobLength(pWorker) > 0 ){ + /* Consume the generated error message */ + pGen->xErr(SyBlobData(pWorker),SyBlobLength(pWorker),pGen->pErrData); + } + } + /* Abort immediately */ + return SXERR_ABORT; + } + } + if( pGen->xErr == 0 ){ + /* No available error consumer,return immediately */ + return SXRET_OK; + } + switch(nErrType){ + case E_WARNING: zErr = "Warning"; break; + case E_PARSE: zErr = "Parse error"; break; + case E_NOTICE: zErr = "Notice"; break; + case E_USER_ERROR: zErr = "User error"; break; + case E_USER_WARNING: zErr = "User warning"; break; + case E_USER_NOTICE: zErr = "User notice"; break; + default: + break; + } + rc = SXRET_OK; + /* Format the error message */ + SyBlobFormat(pWorker,"%u %s: ",nLine,zErr); + va_start(ap,zFormat); + SyBlobFormatAp(pWorker,zFormat,ap); + va_end(ap); + /* Append a new line */ + SyBlobAppend(pWorker,(const void *)"\n",sizeof(char)); + if( SyBlobLength(pWorker) > 0 ){ + /* Consume the generated error message */ + pGen->xErr(SyBlobData(pWorker),SyBlobLength(pWorker),pGen->pErrData); + } + return rc; +} +/* + * ---------------------------------------------------------- + * File: builtin.c + * MD5: 243e3ae4de6382dfa13bd461b136240b + * ---------------------------------------------------------- + */ +/* + * Symisc PH7: An embeddable bytecode compiler and a virtual machine for the PHP(5) programming language. + * Copyright (C) 2011-2012, Symisc Systems http://ph7.symisc.net/ + * Version 2.1.4 + * For information on licensing,redistribution of this file,and for a DISCLAIMER OF ALL WARRANTIES + * please contact Symisc Systems via: + * legal@symisc.net + * licensing@symisc.net + * contact@symisc.net + * or visit: + * http://ph7.symisc.net/ + */ + /* $SymiscID: builtin.c v1.0 FreeBSD 2012-08-06 08:39 devel $ */ +#ifndef PH7_AMALGAMATION +#include "ph7int.h" +#endif +/* This file implement built-in 'foreign' functions for the PH7 engine */ +/* + * Section: + * Variable handling Functions. + * Authors: + * Symisc Systems,devel@symisc.net. + * Copyright (C) Symisc Systems,http://ph7.symisc.net + * Status: + * Stable. + */ +/* + * bool is_bool($var) + * Finds out whether a variable is a boolean. + * Parameters + * $var: The variable being evaluated. + * Return + * TRUE if var is a boolean. False otherwise. + */ +static int PH7_builtin_is_bool(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + int res = 0; /* Assume false by default */ + if( nArg > 0 ){ + res = ph7_value_is_bool(apArg[0]); + } + /* Query result */ + ph7_result_bool(pCtx,res); + return PH7_OK; +} +/* + * bool is_float($var) + * bool is_real($var) + * bool is_double($var) + * Finds out whether a variable is a float. + * Parameters + * $var: The variable being evaluated. + * Return + * TRUE if var is a float. False otherwise. + */ +static int PH7_builtin_is_float(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + int res = 0; /* Assume false by default */ + if( nArg > 0 ){ + res = ph7_value_is_float(apArg[0]); + } + /* Query result */ + ph7_result_bool(pCtx,res); + return PH7_OK; +} +/* + * bool is_int($var) + * bool is_integer($var) + * bool is_long($var) + * Finds out whether a variable is an integer. + * Parameters + * $var: The variable being evaluated. + * Return + * TRUE if var is an integer. False otherwise. + */ +static int PH7_builtin_is_int(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + int res = 0; /* Assume false by default */ + if( nArg > 0 ){ + res = ph7_value_is_int(apArg[0]); + } + /* Query result */ + ph7_result_bool(pCtx,res); + return PH7_OK; +} +/* + * bool is_string($var) + * Finds out whether a variable is a string. + * Parameters + * $var: The variable being evaluated. + * Return + * TRUE if var is string. False otherwise. + */ +static int PH7_builtin_is_string(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + int res = 0; /* Assume false by default */ + if( nArg > 0 ){ + res = ph7_value_is_string(apArg[0]); + } + /* Query result */ + ph7_result_bool(pCtx,res); + return PH7_OK; +} +/* + * bool is_null($var) + * Finds out whether a variable is NULL. + * Parameters + * $var: The variable being evaluated. + * Return + * TRUE if var is NULL. False otherwise. + */ +static int PH7_builtin_is_null(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + int res = 0; /* Assume false by default */ + if( nArg > 0 ){ + res = ph7_value_is_null(apArg[0]); + } + /* Query result */ + ph7_result_bool(pCtx,res); + return PH7_OK; +} +/* + * bool is_numeric($var) + * Find out whether a variable is NULL. + * Parameters + * $var: The variable being evaluated. + * Return + * True if var is numeric. False otherwise. + */ +static int PH7_builtin_is_numeric(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + int res = 0; /* Assume false by default */ + if( nArg > 0 ){ + res = ph7_value_is_numeric(apArg[0]); + } + /* Query result */ + ph7_result_bool(pCtx,res); + return PH7_OK; +} +/* + * bool is_scalar($var) + * Find out whether a variable is a scalar. + * Parameters + * $var: The variable being evaluated. + * Return + * True if var is scalar. False otherwise. + */ +static int PH7_builtin_is_scalar(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + int res = 0; /* Assume false by default */ + if( nArg > 0 ){ + res = ph7_value_is_scalar(apArg[0]); + } + /* Query result */ + ph7_result_bool(pCtx,res); + return PH7_OK; +} +/* + * bool is_array($var) + * Find out whether a variable is an array. + * Parameters + * $var: The variable being evaluated. + * Return + * True if var is an array. False otherwise. + */ +static int PH7_builtin_is_array(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + int res = 0; /* Assume false by default */ + if( nArg > 0 ){ + res = ph7_value_is_array(apArg[0]); + } + /* Query result */ + ph7_result_bool(pCtx,res); + return PH7_OK; +} +/* + * bool is_object($var) + * Find out whether a variable is an object. + * Parameters + * $var: The variable being evaluated. + * Return + * True if var is an object. False otherwise. + */ +static int PH7_builtin_is_object(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + int res = 0; /* Assume false by default */ + if( nArg > 0 ){ + res = ph7_value_is_object(apArg[0]); + } + /* Query result */ + ph7_result_bool(pCtx,res); + return PH7_OK; +} +/* + * bool is_resource($var) + * Find out whether a variable is a resource. + * Parameters + * $var: The variable being evaluated. + * Return + * True if a resource. False otherwise. + */ +static int PH7_builtin_is_resource(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + int res = 0; /* Assume false by default */ + if( nArg > 0 ){ + res = ph7_value_is_resource(apArg[0]); + } + ph7_result_bool(pCtx,res); + return PH7_OK; +} +/* + * float floatval($var) + * Get float value of a variable. + * Parameter + * $var: The variable being processed. + * Return + * the float value of a variable. + */ +static int PH7_builtin_floatval(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + if( nArg < 1 ){ + /* return 0.0 */ + ph7_result_double(pCtx,0); + }else{ + double dval; + /* Perform the cast */ + dval = ph7_value_to_double(apArg[0]); + ph7_result_double(pCtx,dval); + } + return PH7_OK; +} +/* + * int intval($var) + * Get integer value of a variable. + * Parameter + * $var: The variable being processed. + * Return + * the int value of a variable. + */ +static int PH7_builtin_intval(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + if( nArg < 1 ){ + /* return 0 */ + ph7_result_int(pCtx,0); + }else{ + sxi64 iVal; + /* Perform the cast */ + iVal = ph7_value_to_int64(apArg[0]); + ph7_result_int64(pCtx,iVal); + } + return PH7_OK; +} +/* + * string strval($var) + * Get the string representation of a variable. + * Parameter + * $var: The variable being processed. + * Return + * the string value of a variable. + */ +static int PH7_builtin_strval(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + if( nArg < 1 ){ + /* return NULL */ + ph7_result_null(pCtx); + }else{ + const char *zVal; + int iLen = 0; /* cc -O6 warning */ + /* Perform the cast */ + zVal = ph7_value_to_string(apArg[0],&iLen); + ph7_result_string(pCtx,zVal,iLen); + } + return PH7_OK; +} +/* + * bool empty($var) + * Determine whether a variable is empty. + * Parameters + * $var: The variable being checked. + * Return + * 0 if var has a non-empty and non-zero value.1 otherwise. + */ +static int PH7_builtin_empty(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + int res = 1; /* Assume empty by default */ + if( nArg > 0 ){ + res = ph7_value_is_empty(apArg[0]); + } + ph7_result_bool(pCtx,res); + return PH7_OK; + +} +#ifndef PH7_DISABLE_BUILTIN_FUNC +#ifdef PH7_ENABLE_MATH_FUNC +/* + * Section: + * Math Functions. + * Authors: + * Symisc Systems,devel@symisc.net. + * Copyright (C) Symisc Systems,http://ph7.symisc.net + * Status: + * Stable. + */ +#include /* abs */ +#include +/* + * float sqrt(float $arg ) + * Square root of the given number. + * Parameter + * The number to process. + * Return + * The square root of arg or the special value Nan of failure. + */ +static int PH7_builtin_sqrt(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + double r,x; + if( nArg < 1 ){ + /* Missing argument,return 0 */ + ph7_result_int(pCtx,0); + return PH7_OK; + } + x = ph7_value_to_double(apArg[0]); + /* Perform the requested operation */ + r = sqrt(x); + /* store the result back */ + ph7_result_double(pCtx,r); + return PH7_OK; +} +/* + * float exp(float $arg ) + * Calculates the exponent of e. + * Parameter + * The number to process. + * Return + * 'e' raised to the power of arg. + */ +static int PH7_builtin_exp(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + double r,x; + if( nArg < 1 ){ + /* Missing argument,return 0 */ + ph7_result_int(pCtx,0); + return PH7_OK; + } + x = ph7_value_to_double(apArg[0]); + /* Perform the requested operation */ + r = exp(x); + /* store the result back */ + ph7_result_double(pCtx,r); + return PH7_OK; +} +/* + * float floor(float $arg ) + * Round fractions down. + * Parameter + * The number to process. + * Return + * Returns the next lowest integer value by rounding down value if necessary. + */ +static int PH7_builtin_floor(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + double r,x; + if( nArg < 1 ){ + /* Missing argument,return 0 */ + ph7_result_int(pCtx,0); + return PH7_OK; + } + x = ph7_value_to_double(apArg[0]); + /* Perform the requested operation */ + r = floor(x); + /* store the result back */ + ph7_result_double(pCtx,r); + return PH7_OK; +} +/* + * float cos(float $arg ) + * Cosine. + * Parameter + * The number to process. + * Return + * The cosine of arg. + */ +static int PH7_builtin_cos(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + double r,x; + if( nArg < 1 ){ + /* Missing argument,return 0 */ + ph7_result_int(pCtx,0); + return PH7_OK; + } + x = ph7_value_to_double(apArg[0]); + /* Perform the requested operation */ + r = cos(x); + /* store the result back */ + ph7_result_double(pCtx,r); + return PH7_OK; +} +/* + * float acos(float $arg ) + * Arc cosine. + * Parameter + * The number to process. + * Return + * The arc cosine of arg. + */ +static int PH7_builtin_acos(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + double r,x; + if( nArg < 1 ){ + /* Missing argument,return 0 */ + ph7_result_int(pCtx,0); + return PH7_OK; + } + x = ph7_value_to_double(apArg[0]); + /* Perform the requested operation */ + r = acos(x); + /* store the result back */ + ph7_result_double(pCtx,r); + return PH7_OK; +} +/* + * float cosh(float $arg ) + * Hyperbolic cosine. + * Parameter + * The number to process. + * Return + * The hyperbolic cosine of arg. + */ +static int PH7_builtin_cosh(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + double r,x; + if( nArg < 1 ){ + /* Missing argument,return 0 */ + ph7_result_int(pCtx,0); + return PH7_OK; + } + x = ph7_value_to_double(apArg[0]); + /* Perform the requested operation */ + r = cosh(x); + /* store the result back */ + ph7_result_double(pCtx,r); + return PH7_OK; +} +/* + * float sin(float $arg ) + * Sine. + * Parameter + * The number to process. + * Return + * The sine of arg. + */ +static int PH7_builtin_sin(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + double r,x; + if( nArg < 1 ){ + /* Missing argument,return 0 */ + ph7_result_int(pCtx,0); + return PH7_OK; + } + x = ph7_value_to_double(apArg[0]); + /* Perform the requested operation */ + r = sin(x); + /* store the result back */ + ph7_result_double(pCtx,r); + return PH7_OK; +} +/* + * float asin(float $arg ) + * Arc sine. + * Parameter + * The number to process. + * Return + * The arc sine of arg. + */ +static int PH7_builtin_asin(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + double r,x; + if( nArg < 1 ){ + /* Missing argument,return 0 */ + ph7_result_int(pCtx,0); + return PH7_OK; + } + x = ph7_value_to_double(apArg[0]); + /* Perform the requested operation */ + r = asin(x); + /* store the result back */ + ph7_result_double(pCtx,r); + return PH7_OK; +} +/* + * float sinh(float $arg ) + * Hyperbolic sine. + * Parameter + * The number to process. + * Return + * The hyperbolic sine of arg. + */ +static int PH7_builtin_sinh(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + double r,x; + if( nArg < 1 ){ + /* Missing argument,return 0 */ + ph7_result_int(pCtx,0); + return PH7_OK; + } + x = ph7_value_to_double(apArg[0]); + /* Perform the requested operation */ + r = sinh(x); + /* store the result back */ + ph7_result_double(pCtx,r); + return PH7_OK; +} +/* + * float ceil(float $arg ) + * Round fractions up. + * Parameter + * The number to process. + * Return + * The next highest integer value by rounding up value if necessary. + */ +static int PH7_builtin_ceil(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + double r,x; + if( nArg < 1 ){ + /* Missing argument,return 0 */ + ph7_result_int(pCtx,0); + return PH7_OK; + } + x = ph7_value_to_double(apArg[0]); + /* Perform the requested operation */ + r = ceil(x); + /* store the result back */ + ph7_result_double(pCtx,r); + return PH7_OK; +} +/* + * float tan(float $arg ) + * Tangent. + * Parameter + * The number to process. + * Return + * The tangent of arg. + */ +static int PH7_builtin_tan(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + double r,x; + if( nArg < 1 ){ + /* Missing argument,return 0 */ + ph7_result_int(pCtx,0); + return PH7_OK; + } + x = ph7_value_to_double(apArg[0]); + /* Perform the requested operation */ + r = tan(x); + /* store the result back */ + ph7_result_double(pCtx,r); + return PH7_OK; +} +/* + * float atan(float $arg ) + * Arc tangent. + * Parameter + * The number to process. + * Return + * The arc tangent of arg. + */ +static int PH7_builtin_atan(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + double r,x; + if( nArg < 1 ){ + /* Missing argument,return 0 */ + ph7_result_int(pCtx,0); + return PH7_OK; + } + x = ph7_value_to_double(apArg[0]); + /* Perform the requested operation */ + r = atan(x); + /* store the result back */ + ph7_result_double(pCtx,r); + return PH7_OK; +} +/* + * float tanh(float $arg ) + * Hyperbolic tangent. + * Parameter + * The number to process. + * Return + * The Hyperbolic tangent of arg. + */ +static int PH7_builtin_tanh(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + double r,x; + if( nArg < 1 ){ + /* Missing argument,return 0 */ + ph7_result_int(pCtx,0); + return PH7_OK; + } + x = ph7_value_to_double(apArg[0]); + /* Perform the requested operation */ + r = tanh(x); + /* store the result back */ + ph7_result_double(pCtx,r); + return PH7_OK; +} +/* + * float atan2(float $y,float $x) + * Arc tangent of two variable. + * Parameter + * $y = Dividend parameter. + * $x = Divisor parameter. + * Return + * The arc tangent of y/x in radian. + */ +static int PH7_builtin_atan2(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + double r,x,y; + if( nArg < 2 ){ + /* Missing arguments,return 0 */ + ph7_result_int(pCtx,0); + return PH7_OK; + } + y = ph7_value_to_double(apArg[0]); + x = ph7_value_to_double(apArg[1]); + /* Perform the requested operation */ + r = atan2(y,x); + /* store the result back */ + ph7_result_double(pCtx,r); + return PH7_OK; +} +/* + * float/int64 abs(float/int64 $arg ) + * Absolute value. + * Parameter + * The number to process. + * Return + * The absolute value of number. + */ +static int PH7_builtin_abs(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + int is_float; + if( nArg < 1 ){ + /* Missing argument,return 0 */ + ph7_result_int(pCtx,0); + return PH7_OK; + } + is_float = ph7_value_is_float(apArg[0]); + if( is_float ){ + double r,x; + x = ph7_value_to_double(apArg[0]); + /* Perform the requested operation */ + r = fabs(x); + ph7_result_double(pCtx,r); + }else{ + int r,x; + x = ph7_value_to_int(apArg[0]); + /* Perform the requested operation */ + r = abs(x); + ph7_result_int(pCtx,r); + } + return PH7_OK; +} +/* + * float log(float $arg,[int/float $base]) + * Natural logarithm. + * Parameter + * $arg: The number to process. + * $base: The optional logarithmic base to use. (only base-10 is supported) + * Return + * The logarithm of arg to base, if given, or the natural logarithm. + * Note: + * only Natural log and base-10 log are supported. + */ +static int PH7_builtin_log(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + double r,x; + if( nArg < 1 ){ + /* Missing argument,return 0 */ + ph7_result_int(pCtx,0); + return PH7_OK; + } + x = ph7_value_to_double(apArg[0]); + /* Perform the requested operation */ + if( nArg == 2 && ph7_value_is_numeric(apArg[1]) && ph7_value_to_int(apArg[1]) == 10 ){ + /* Base-10 log */ + r = log10(x); + }else{ + r = log(x); + } + /* store the result back */ + ph7_result_double(pCtx,r); + return PH7_OK; +} +/* + * float log10(float $arg ) + * Base-10 logarithm. + * Parameter + * The number to process. + * Return + * The Base-10 logarithm of the given number. + */ +static int PH7_builtin_log10(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + double r,x; + if( nArg < 1 ){ + /* Missing argument,return 0 */ + ph7_result_int(pCtx,0); + return PH7_OK; + } + x = ph7_value_to_double(apArg[0]); + /* Perform the requested operation */ + r = log10(x); + /* store the result back */ + ph7_result_double(pCtx,r); + return PH7_OK; +} +/* + * number pow(number $base,number $exp) + * Exponential expression. + * Parameter + * base + * The base to use. + * exp + * The exponent. + * Return + * base raised to the power of exp. + * If the result can be represented as integer it will be returned + * as type integer, else it will be returned as type float. + */ +static int PH7_builtin_pow(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + double r,x,y; + if( nArg < 1 ){ + /* Missing argument,return 0 */ + ph7_result_int(pCtx,0); + return PH7_OK; + } + x = ph7_value_to_double(apArg[0]); + y = ph7_value_to_double(apArg[1]); + /* Perform the requested operation */ + r = pow(x,y); + ph7_result_double(pCtx,r); + return PH7_OK; +} +/* + * float pi(void) + * Returns an approximation of pi. + * Note + * you can use the M_PI constant which yields identical results to pi(). + * Return + * The value of pi as float. + */ +static int PH7_builtin_pi(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + SXUNUSED(nArg); /* cc warning */ + SXUNUSED(apArg); + ph7_result_double(pCtx,PH7_PI); + return PH7_OK; +} +/* + * float fmod(float $x,float $y) + * Returns the floating point remainder (modulo) of the division of the arguments. + * Parameters + * $x + * The dividend + * $y + * The divisor + * Return + * The floating point remainder of x/y. + */ +static int PH7_builtin_fmod(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + double x,y,r; + if( nArg < 2 ){ + /* Missing arguments */ + ph7_result_double(pCtx,0); + return PH7_OK; + } + /* Extract given arguments */ + x = ph7_value_to_double(apArg[0]); + y = ph7_value_to_double(apArg[1]); + /* Perform the requested operation */ + r = fmod(x,y); + /* Processing result */ + ph7_result_double(pCtx,r); + return PH7_OK; +} +/* + * float hypot(float $x,float $y) + * Calculate the length of the hypotenuse of a right-angle triangle . + * Parameters + * $x + * Length of first side + * $y + * Length of first side + * Return + * Calculated length of the hypotenuse. + */ +static int PH7_builtin_hypot(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + double x,y,r; + if( nArg < 2 ){ + /* Missing arguments */ + ph7_result_double(pCtx,0); + return PH7_OK; + } + /* Extract given arguments */ + x = ph7_value_to_double(apArg[0]); + y = ph7_value_to_double(apArg[1]); + /* Perform the requested operation */ + r = hypot(x,y); + /* Processing result */ + ph7_result_double(pCtx,r); + return PH7_OK; +} +#endif /* PH7_ENABLE_MATH_FUNC */ +/* + * float round ( float $val [, int $precision = 0 [, int $mode = PHP_ROUND_HALF_UP ]] ) + * Exponential expression. + * Parameter + * $val + * The value to round. + * $precision + * The optional number of decimal digits to round to. + * $mode + * One of PHP_ROUND_HALF_UP, PHP_ROUND_HALF_DOWN, PHP_ROUND_HALF_EVEN, or PHP_ROUND_HALF_ODD. + * (not supported). + * Return + * The rounded value. + */ +static int PH7_builtin_round(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + int n = 0; + double r; + if( nArg < 1 ){ + /* Missing argument,return 0 */ + ph7_result_int(pCtx,0); + return PH7_OK; + } + /* Extract the precision if available */ + if( nArg > 1 ){ + n = ph7_value_to_int(apArg[1]); + if( n>30 ){ + n = 30; + } + if( n<0 ){ + n = 0; + } + } + r = ph7_value_to_double(apArg[0]); + /* If Y==0 and X will fit in a 64-bit int, + * handle the rounding directly.Otherwise + * use our own cutsom printf [i.e:SyBufferFormat()]. + */ + if( n==0 && r>=0 && r= 0xc0 ){ + /* UTF-8 stream */ + zString++; + while( zString < zEnd && (((unsigned char)zString[0] & 0xc0) == 0x80) ){ + zString++; + } + }else{ + if( SyisHex(zString[0]) ){ + break; + } + /* Ignore */ + zString++; + } + } + if( zString < zEnd ){ + /* Cast */ + SyHexStrToInt64(zString,(sxu32)(zEnd-zString),(void *)&iVal,0); + } + }else{ + /* Extract as a 64-bit integer */ + iVal = ph7_value_to_int64(apArg[0]); + } + /* Return the number */ + ph7_result_int64(pCtx,iVal); + return PH7_OK; +} +/* + * int64 bindec(string $bin_string) + * Binary to decimal. + * Parameters + * $bin_string + * The binary string to convert + * Return + * Returns the decimal equivalent of the binary number represented by the binary_string argument. + */ +static int PH7_builtin_bindec(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + const char *zString; + ph7_int64 iVal; + int nLen; + if( nArg < 1 ){ + /* Missing arguments,return -1 */ + ph7_result_int(pCtx,-1); + return PH7_OK; + } + iVal = 0; + if( ph7_value_is_string(apArg[0]) ){ + /* Extract the given string */ + zString = ph7_value_to_string(apArg[0],&nLen); + if( nLen > 0 ){ + /* Perform a binary cast */ + SyBinaryStrToInt64(zString,(sxu32)nLen,(void *)&iVal,0); + } + }else{ + /* Extract as a 64-bit integer */ + iVal = ph7_value_to_int64(apArg[0]); + } + /* Return the number */ + ph7_result_int64(pCtx,iVal); + return PH7_OK; +} +/* + * int64 octdec(string $oct_string) + * Octal to decimal. + * Parameters + * $oct_string + * The octal string to convert + * Return + * Returns the decimal equivalent of the octal number represented by the octal_string argument. + */ +static int PH7_builtin_octdec(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + const char *zString; + ph7_int64 iVal; + int nLen; + if( nArg < 1 ){ + /* Missing arguments,return -1 */ + ph7_result_int(pCtx,-1); + return PH7_OK; + } + iVal = 0; + if( ph7_value_is_string(apArg[0]) ){ + /* Extract the given string */ + zString = ph7_value_to_string(apArg[0],&nLen); + if( nLen > 0 ){ + /* Perform the cast */ + SyOctalStrToInt64(zString,(sxu32)nLen,(void *)&iVal,0); + } + }else{ + /* Extract as a 64-bit integer */ + iVal = ph7_value_to_int64(apArg[0]); + } + /* Return the number */ + ph7_result_int64(pCtx,iVal); + return PH7_OK; +} +/* + * srand([int $seed]) + * mt_srand([int $seed]) + * Seed the random number generator. + * Parameters + * $seed + * Optional seed value + * Return + * null. + * Note: + * THIS FUNCTION IS A NO-OP. + * THE PH7 PRNG IS AUTOMATICALLY SEEDED WHEN THE VM IS CREATED. + */ +static int PH7_builtin_srand(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + SXUNUSED(nArg); + SXUNUSED(apArg); + ph7_result_null(pCtx); + return PH7_OK; +} +/* + * string base_convert(string $number,int $frombase,int $tobase) + * Convert a number between arbitrary bases. + * Parameters + * $number + * The number to convert + * $frombase + * The base number is in + * $tobase + * The base to convert number to + * Return + * Number converted to base tobase + */ +static int PH7_builtin_base_convert(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + int nLen,iFbase,iTobase; + const char *zNum; + ph7_int64 iNum; + if( nArg < 3 ){ + /* Return the empty string*/ + ph7_result_string(pCtx,"",0); + return PH7_OK; + } + /* Base numbers */ + iFbase = ph7_value_to_int(apArg[1]); + iTobase = ph7_value_to_int(apArg[2]); + if( ph7_value_is_string(apArg[0]) ){ + /* Extract the target number */ + zNum = ph7_value_to_string(apArg[0],&nLen); + if( nLen < 1 ){ + /* Return the empty string*/ + ph7_result_string(pCtx,"",0); + return PH7_OK; + } + /* Base conversion */ + switch(iFbase){ + case 16: + /* Hex */ + SyHexStrToInt64(zNum,(sxu32)nLen,(void *)&iNum,0); + break; + case 8: + /* Octal */ + SyOctalStrToInt64(zNum,(sxu32)nLen,(void *)&iNum,0); + break; + case 2: + /* Binary */ + SyBinaryStrToInt64(zNum,(sxu32)nLen,(void *)&iNum,0); + break; + default: + /* Decimal */ + SyStrToInt64(zNum,(sxu32)nLen,(void *)&iNum,0); + break; + } + }else{ + iNum = ph7_value_to_int64(apArg[0]); + } + switch(iTobase){ + case 16: + /* Hex */ + ph7_result_string_format(pCtx,"%qx",iNum); /* Quad hex */ + break; + case 8: + /* Octal */ + ph7_result_string_format(pCtx,"%qo",iNum); /* Quad octal */ + break; + case 2: + /* Binary */ + ph7_result_string_format(pCtx,"%qB",iNum); /* Quad binary */ + break; + default: + /* Decimal */ + ph7_result_string_format(pCtx,"%qd",iNum); /* Quad decimal */ + break; + } + return PH7_OK; +} +/* + * Section: + * String handling Functions. + * Authors: + * Symisc Systems,devel@symisc.net. + * Copyright (C) Symisc Systems,http://ph7.symisc.net + * Status: + * Stable. + */ +/* + * string substr(string $string,int $start[, int $length ]) + * Return part of a string. + * Parameters + * $string + * The input string. Must be one character or longer. + * $start + * If start is non-negative, the returned string will start at the start'th position + * in string, counting from zero. For instance, in the string 'abcdef', the character + * at position 0 is 'a', the character at position 2 is 'c', and so forth. + * If start is negative, the returned string will start at the start'th character + * from the end of string. + * If string is less than or equal to start characters long, FALSE will be returned. + * $length + * If length is given and is positive, the string returned will contain at most length + * characters beginning from start (depending on the length of string). + * If length is given and is negative, then that many characters will be omitted from + * the end of string (after the start position has been calculated when a start is negative). + * If start denotes the position of this truncation or beyond, false will be returned. + * If length is given and is 0, FALSE or NULL an empty string will be returned. + * If length is omitted, the substring starting from start until the end of the string + * will be returned. + * Return + * Returns the extracted part of string, or FALSE on failure or an empty string. + */ +static int PH7_builtin_substr(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + const char *zSource,*zOfft; + int nOfft,nLen,nSrcLen; + if( nArg < 2 ){ + /* return FALSE */ + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Extract the target string */ + zSource = ph7_value_to_string(apArg[0],&nSrcLen); + if( nSrcLen < 1 ){ + /* Empty string,return FALSE */ + ph7_result_bool(pCtx,0); + return PH7_OK; + } + nLen = nSrcLen; /* cc warning */ + /* Extract the offset */ + nOfft = ph7_value_to_int(apArg[1]); + if( nOfft < 0 ){ + zOfft = &zSource[nSrcLen+nOfft]; + if( zOfft < zSource ){ + /* Invalid offset */ + ph7_result_bool(pCtx,0); + return PH7_OK; + } + nLen = (int)(&zSource[nSrcLen]-zOfft); + nOfft = (int)(zOfft-zSource); + }else if( nOfft >= nSrcLen ){ + /* Invalid offset */ + ph7_result_bool(pCtx,0); + return PH7_OK; + }else{ + zOfft = &zSource[nOfft]; + nLen = nSrcLen - nOfft; + } + if( nArg > 2 ){ + /* Extract the length */ + nLen = ph7_value_to_int(apArg[2]); + if( nLen == 0 ){ + /* Invalid length,return an empty string */ + ph7_result_string(pCtx,"",0); + return PH7_OK; + }else if( nLen < 0 ){ + nLen = nSrcLen + nLen - nOfft; + if( nLen < 1 ){ + /* Invalid length */ + nLen = nSrcLen - nOfft; + } + } + if( nLen + nOfft > nSrcLen ){ + /* Invalid length */ + nLen = nSrcLen - nOfft; + } + } + /* Return the substring */ + ph7_result_string(pCtx,zOfft,nLen); + return PH7_OK; +} +/* + * int substr_compare(string $main_str,string $str ,int $offset[,int $length[,bool $case_insensitivity = false ]]) + * Binary safe comparison of two strings from an offset, up to length characters. + * Parameters + * $main_str + * The main string being compared. + * $str + * The secondary string being compared. + * $offset + * The start position for the comparison. If negative, it starts counting from + * the end of the string. + * $length + * The length of the comparison. The default value is the largest of the length + * of the str compared to the length of main_str less the offset. + * $case_insensitivity + * If case_insensitivity is TRUE, comparison is case insensitive. + * Return + * Returns < 0 if main_str from position offset is less than str, > 0 if it is greater than + * str, and 0 if they are equal. If offset is equal to or greater than the length of main_str + * or length is set and is less than 1, substr_compare() prints a warning and returns FALSE. + */ +static int PH7_builtin_substr_compare(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + const char *zSource,*zOfft,*zSub; + int nOfft,nLen,nSrcLen,nSublen; + int iCase = 0; + int rc; + if( nArg < 3 ){ + /* Missing arguments,return FALSE */ + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Extract the target string */ + zSource = ph7_value_to_string(apArg[0],&nSrcLen); + if( nSrcLen < 1 ){ + /* Empty string,return FALSE */ + ph7_result_bool(pCtx,0); + return PH7_OK; + } + nLen = nSrcLen; /* cc warning */ + /* Extract the substring */ + zSub = ph7_value_to_string(apArg[1],&nSublen); + if( nSublen < 1 || nSublen > nSrcLen){ + /* Empty string,return FALSE */ + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Extract the offset */ + nOfft = ph7_value_to_int(apArg[2]); + if( nOfft < 0 ){ + zOfft = &zSource[nSrcLen+nOfft]; + if( zOfft < zSource ){ + /* Invalid offset */ + ph7_result_bool(pCtx,0); + return PH7_OK; + } + nLen = (int)(&zSource[nSrcLen]-zOfft); + nOfft = (int)(zOfft-zSource); + }else if( nOfft >= nSrcLen ){ + /* Invalid offset */ + ph7_result_bool(pCtx,0); + return PH7_OK; + }else{ + zOfft = &zSource[nOfft]; + nLen = nSrcLen - nOfft; + } + if( nArg > 3 ){ + /* Extract the length */ + nLen = ph7_value_to_int(apArg[3]); + if( nLen < 1 ){ + /* Invalid length */ + ph7_result_int(pCtx,1); + return PH7_OK; + }else if( nLen + nOfft > nSrcLen ){ + /* Invalid length */ + nLen = nSrcLen - nOfft; + } + if( nArg > 4 ){ + /* Case-sensitive or not */ + iCase = ph7_value_to_bool(apArg[4]); + } + } + /* Perform the comparison */ + if( iCase ){ + rc = SyStrnicmp(zOfft,zSub,(sxu32)nLen); + }else{ + rc = SyStrncmp(zOfft,zSub,(sxu32)nLen); + } + /* Comparison result */ + ph7_result_int(pCtx,rc); + return PH7_OK; +} +/* + * int substr_count(string $haystack,string $needle[,int $offset = 0 [,int $length ]]) + * Count the number of substring occurrences. + * Parameters + * $haystack + * The string to search in + * $needle + * The substring to search for + * $offset + * The offset where to start counting + * $length (NOT USED) + * The maximum length after the specified offset to search for the substring. + * It outputs a warning if the offset plus the length is greater than the haystack length. + * Return + * Toral number of substring occurrences. + */ +static int PH7_builtin_substr_count(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + const char *zText,*zPattern,*zEnd; + int nTextlen,nPatlen; + int iCount = 0; + sxu32 nOfft; + sxi32 rc; + if( nArg < 2 ){ + /* Missing arguments */ + ph7_result_int(pCtx,0); + return PH7_OK; + } + /* Point to the haystack */ + zText = ph7_value_to_string(apArg[0],&nTextlen); + /* Point to the neddle */ + zPattern = ph7_value_to_string(apArg[1],&nPatlen); + if( nTextlen < 1 || nPatlen < 1 || nPatlen > nTextlen ){ + /* NOOP,return zero */ + ph7_result_int(pCtx,0); + return PH7_OK; + } + if( nArg > 2 ){ + int nOfft; + /* Extract the offset */ + nOfft = ph7_value_to_int(apArg[2]); + if( nOfft < 0 || nOfft > nTextlen ){ + /* Invalid offset,return zero */ + ph7_result_int(pCtx,0); + return PH7_OK; + } + /* Point to the desired offset */ + zText = &zText[nOfft]; + /* Adjust length */ + nTextlen -= nOfft; + } + /* Point to the end of the string */ + zEnd = &zText[nTextlen]; + if( nArg > 3 ){ + int nLen; + /* Extract the length */ + nLen = ph7_value_to_int(apArg[3]); + if( nLen < 0 || nLen > nTextlen ){ + /* Invalid length,return 0 */ + ph7_result_int(pCtx,0); + return PH7_OK; + } + /* Adjust pointer */ + nTextlen = nLen; + zEnd = &zText[nTextlen]; + } + /* Perform the search */ + for(;;){ + rc = SyBlobSearch((const void *)zText,(sxu32)(zEnd-zText),(const void *)zPattern,nPatlen,&nOfft); + if( rc != SXRET_OK ){ + /* Pattern not found,break immediately */ + break; + } + /* Increment counter and update the offset */ + iCount++; + zText += nOfft + nPatlen; + if( zText >= zEnd ){ + break; + } + } + /* Pattern count */ + ph7_result_int(pCtx,iCount); + return PH7_OK; +} +/* + * string chunk_split(string $body[,int $chunklen = 76 [, string $end = "\r\n" ]]) + * Split a string into smaller chunks. + * Parameters + * $body + * The string to be chunked. + * $chunklen + * The chunk length. + * $end + * The line ending sequence. + * Return + * The chunked string or NULL on failure. + */ +static int PH7_builtin_chunk_split(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + const char *zIn,*zEnd,*zSep = "\r\n"; + int nSepLen,nChunkLen,nLen; + if( nArg < 1 || !ph7_value_is_string(apArg[0]) ){ + /* Nothing to split,return null */ + ph7_result_null(pCtx); + return PH7_OK; + } + /* initialize/Extract arguments */ + nSepLen = (int)sizeof("\r\n") - 1; + nChunkLen = 76; + zIn = ph7_value_to_string(apArg[0],&nLen); + zEnd = &zIn[nLen]; + if( nArg > 1 ){ + /* Chunk length */ + nChunkLen = ph7_value_to_int(apArg[1]); + if( nChunkLen < 1 ){ + /* Switch back to the default length */ + nChunkLen = 76; + } + if( nArg > 2 ){ + /* Separator */ + zSep = ph7_value_to_string(apArg[2],&nSepLen); + if( nSepLen < 1 ){ + /* Switch back to the default separator */ + zSep = "\r\n"; + nSepLen = (int)sizeof("\r\n") - 1; + } + } + } + /* Perform the requested operation */ + if( nChunkLen > nLen ){ + /* Nothing to split,return the string and the separator */ + ph7_result_string_format(pCtx,"%.*s%.*s",nLen,zIn,nSepLen,zSep); + return PH7_OK; + } + while( zIn < zEnd ){ + if( nChunkLen > (int)(zEnd-zIn) ){ + nChunkLen = (int)(zEnd - zIn); + } + /* Append the chunk and the separator */ + ph7_result_string_format(pCtx,"%.*s%.*s",nChunkLen,zIn,nSepLen,zSep); + /* Point beyond the chunk */ + zIn += nChunkLen; + } + return PH7_OK; +} +/* + * string addslashes(string $str) + * Quote string with slashes. + * Returns a string with backslashes before characters that need + * to be quoted in database queries etc. These characters are single + * quote ('), double quote ("), backslash (\) and NUL (the NULL byte). + * Parameter + * str: The string to be escaped. + * Return + * Returns the escaped string + */ +static int PH7_builtin_addslashes(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + const char *zCur,*zIn,*zEnd; + int nLen; + if( nArg < 1 ){ + /* Nothing to process,retun NULL */ + ph7_result_null(pCtx); + return PH7_OK; + } + /* Extract the string to process */ + zIn = ph7_value_to_string(apArg[0],&nLen); + if( nLen < 1 ){ + /* Return the empty string */ + ph7_result_string(pCtx,"",0); + return PH7_OK; + } + zEnd = &zIn[nLen]; + zCur = 0; /* cc warning */ + for(;;){ + if( zIn >= zEnd ){ + /* No more input */ + break; + } + zCur = zIn; + while( zIn < zEnd && zIn[0] != '\'' && zIn[0] != '"' && zIn[0] != '\\' ){ + zIn++; + } + if( zIn > zCur ){ + /* Append raw contents */ + ph7_result_string(pCtx,zCur,(int)(zIn-zCur)); + } + if( zIn < zEnd ){ + int c = zIn[0]; + ph7_result_string_format(pCtx,"\\%c",c); + } + zIn++; + } + return PH7_OK; +} +/* + * Check if the given character is present in the given mask. + * Return TRUE if present. FALSE otherwise. + */ +static int cSlashCheckMask(int c,const char *zMask,int nLen) +{ + const char *zEnd = &zMask[nLen]; + while( zMask < zEnd ){ + if( zMask[0] == c ){ + /* Character present,return TRUE */ + return 1; + } + /* Advance the pointer */ + zMask++; + } + /* Not present */ + return 0; +} +/* + * string addcslashes(string $str,string $charlist) + * Quote string with slashes in a C style. + * Parameter + * $str: + * The string to be escaped. + * $charlist: + * A list of characters to be escaped. If charlist contains characters \n, \r etc. + * they are converted in C-like style, while other non-alphanumeric characters + * with ASCII codes lower than 32 and higher than 126 converted to octal representation. + * Return + * Returns the escaped string. + * Note: + * Range characters [i.e: 'A..Z'] is not implemented in the current release. + */ +static int PH7_builtin_addcslashes(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + const char *zCur,*zIn,*zEnd,*zMask; + int nLen,nMask; + if( nArg < 1 ){ + /* Nothing to process,retun NULL */ + ph7_result_null(pCtx); + return PH7_OK; + } + /* Extract the string to process */ + zIn = ph7_value_to_string(apArg[0],&nLen); + if( nLen < 1 || nArg < 2 ){ + /* Return the string untouched */ + ph7_result_string(pCtx,zIn,nLen); + return PH7_OK; + } + /* Extract the desired mask */ + zMask = ph7_value_to_string(apArg[1],&nMask); + zEnd = &zIn[nLen]; + zCur = 0; /* cc warning */ + for(;;){ + if( zIn >= zEnd ){ + /* No more input */ + break; + } + zCur = zIn; + while( zIn < zEnd && !cSlashCheckMask(zIn[0],zMask,nMask) ){ + zIn++; + } + if( zIn > zCur ){ + /* Append raw contents */ + ph7_result_string(pCtx,zCur,(int)(zIn-zCur)); + } + if( zIn < zEnd ){ + int c = zIn[0]; + if( c > 126 || (c < 32 && (!SyisAlphaNum(c)/*EBCDIC*/ && !SyisSpace(c))) ){ + /* Convert to octal */ + ph7_result_string_format(pCtx,"\\%o",c); + }else{ + ph7_result_string_format(pCtx,"\\%c",c); + } + } + zIn++; + } + return PH7_OK; +} +/* + * string quotemeta(string $str) + * Quote meta characters. + * Parameter + * $str: + * The string to be escaped. + * Return + * Returns the escaped string. +*/ +static int PH7_builtin_quotemeta(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + const char *zCur,*zIn,*zEnd; + int nLen; + if( nArg < 1 ){ + /* Nothing to process,retun NULL */ + ph7_result_null(pCtx); + return PH7_OK; + } + /* Extract the string to process */ + zIn = ph7_value_to_string(apArg[0],&nLen); + if( nLen < 1 ){ + /* Return the empty string */ + ph7_result_string(pCtx,"",0); + return PH7_OK; + } + zEnd = &zIn[nLen]; + zCur = 0; /* cc warning */ + for(;;){ + if( zIn >= zEnd ){ + /* No more input */ + break; + } + zCur = zIn; + while( zIn < zEnd && !cSlashCheckMask(zIn[0],".\\+*?[^]($)",(int)sizeof(".\\+*?[^]($)")-1) ){ + zIn++; + } + if( zIn > zCur ){ + /* Append raw contents */ + ph7_result_string(pCtx,zCur,(int)(zIn-zCur)); + } + if( zIn < zEnd ){ + int c = zIn[0]; + ph7_result_string_format(pCtx,"\\%c",c); + } + zIn++; + } + return PH7_OK; +} +/* + * string stripslashes(string $str) + * Un-quotes a quoted string. + * Returns a string with backslashes before characters that need + * to be quoted in database queries etc. These characters are single + * quote ('), double quote ("), backslash (\) and NUL (the NULL byte). + * Parameter + * $str + * The input string. + * Return + * Returns a string with backslashes stripped off. + */ +static int PH7_builtin_stripslashes(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + const char *zCur,*zIn,*zEnd; + int nLen; + if( nArg < 1 ){ + /* Nothing to process,retun NULL */ + ph7_result_null(pCtx); + return PH7_OK; + } + /* Extract the string to process */ + zIn = ph7_value_to_string(apArg[0],&nLen); + if( zIn == 0 ){ + ph7_result_null(pCtx); + return PH7_OK; + } + zEnd = &zIn[nLen]; + zCur = 0; /* cc warning */ + /* Encode the string */ + for(;;){ + if( zIn >= zEnd ){ + /* No more input */ + break; + } + zCur = zIn; + while( zIn < zEnd && zIn[0] != '\\' ){ + zIn++; + } + if( zIn > zCur ){ + /* Append raw contents */ + ph7_result_string(pCtx,zCur,(int)(zIn-zCur)); + } + if( &zIn[1] < zEnd ){ + int c = zIn[1]; + if( c == '\'' || c == '"' || c == '\\' ){ + /* Ignore the backslash */ + zIn++; + } + }else{ + break; + } + } + return PH7_OK; +} +/* + * string htmlspecialchars(string $string [, int $flags = ENT_COMPAT | ENT_HTML401 [, string $charset]]) + * HTML escaping of special characters. + * The translations performed are: + * '&' (ampersand) ==> '&' + * '"' (double quote) ==> '"' when ENT_NOQUOTES is not set. + * "'" (single quote) ==> ''' only when ENT_QUOTES is set. + * '<' (less than) ==> '<' + * '>' (greater than) ==> '>' + * Parameters + * $string + * The string being converted. + * $flags + * A bitmask of one or more of the following flags, which specify how to handle quotes. + * The default is ENT_COMPAT | ENT_HTML401. + * ENT_COMPAT Will convert double-quotes and leave single-quotes alone. + * ENT_QUOTES Will convert both double and single quotes. + * ENT_NOQUOTES Will leave both double and single quotes unconverted. + * ENT_IGNORE Silently discard invalid code unit sequences instead of returning an empty string. + * $charset + * Defines character set used in conversion. The default character set is ISO-8859-1. (Not used) + * Return + * The escaped string or NULL on failure. + */ +static int PH7_builtin_htmlspecialchars(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + const char *zCur,*zIn,*zEnd; + int iFlags = 0x01|0x40; /* ENT_COMPAT | ENT_HTML401 */ + int nLen,c; + if( nArg < 1 || !ph7_value_is_string(apArg[0]) ){ + /* Missing/Invalid arguments,return NULL */ + ph7_result_null(pCtx); + return PH7_OK; + } + /* Extract the target string */ + zIn = ph7_value_to_string(apArg[0],&nLen); + zEnd = &zIn[nLen]; + /* Extract the flags if available */ + if( nArg > 1 ){ + iFlags = ph7_value_to_int(apArg[1]); + if( iFlags < 0 ){ + iFlags = 0x01|0x40; + } + } + /* Perform the requested operation */ + for(;;){ + if( zIn >= zEnd ){ + break; + } + zCur = zIn; + while( zIn < zEnd && zIn[0] != '&' && zIn[0] != '\'' && zIn[0] != '"' && zIn[0] != '<' && zIn[0] != '>' ){ + zIn++; + } + if( zCur < zIn ){ + /* Append the raw string verbatim */ + ph7_result_string(pCtx,zCur,(int)(zIn-zCur)); + } + if( zIn >= zEnd ){ + break; + } + c = zIn[0]; + if( c == '&' ){ + /* Expand '&' */ + ph7_result_string(pCtx,"&",(int)sizeof("&")-1); + }else if( c == '<' ){ + /* Expand '<' */ + ph7_result_string(pCtx,"<",(int)sizeof("<")-1); + }else if( c == '>' ){ + /* Expand '>' */ + ph7_result_string(pCtx,">",(int)sizeof(">")-1); + }else if( c == '\'' ){ + if( iFlags & 0x02 /*ENT_QUOTES*/ ){ + /* Expand ''' */ + ph7_result_string(pCtx,"'",(int)sizeof("'")-1); + }else{ + /* Leave the single quote untouched */ + ph7_result_string(pCtx,"'",(int)sizeof(char)); + } + }else if( c == '"' ){ + if( (iFlags & 0x04) == 0 /*ENT_NOQUOTES*/ ){ + /* Expand '"' */ + ph7_result_string(pCtx,""",(int)sizeof(""")-1); + }else{ + /* Leave the double quote untouched */ + ph7_result_string(pCtx,"\"",(int)sizeof(char)); + } + } + /* Ignore the unsafe HTML character */ + zIn++; + } + return PH7_OK; +} +/* + * string htmlspecialchars_decode(string $string[,int $quote_style = ENT_COMPAT ]) + * Unescape HTML entities. + * Parameters + * $string + * The string to decode + * $quote_style + * The quote style. One of the following constants: + * ENT_COMPAT Will convert double-quotes and leave single-quotes alone (default) + * ENT_QUOTES Will convert both double and single quotes + * ENT_NOQUOTES Will leave both double and single quotes unconverted + * Return + * The unescaped string or NULL on failure. + */ +static int PH7_builtin_htmlspecialchars_decode(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + const char *zCur,*zIn,*zEnd; + int iFlags = 0x01; /* ENT_COMPAT */ + int nLen,nJump; + if( nArg < 1 || !ph7_value_is_string(apArg[0]) ){ + /* Missing/Invalid arguments,return NULL */ + ph7_result_null(pCtx); + return PH7_OK; + } + /* Extract the target string */ + zIn = ph7_value_to_string(apArg[0],&nLen); + zEnd = &zIn[nLen]; + /* Extract the flags if available */ + if( nArg > 1 ){ + iFlags = ph7_value_to_int(apArg[1]); + if( iFlags < 0 ){ + iFlags = 0x01; + } + } + /* Perform the requested operation */ + for(;;){ + if( zIn >= zEnd ){ + break; + } + zCur = zIn; + while( zIn < zEnd && zIn[0] != '&' ){ + zIn++; + } + if( zCur < zIn ){ + /* Append the raw string verbatim */ + ph7_result_string(pCtx,zCur,(int)(zIn-zCur)); + } + nLen = (int)(zEnd-zIn); + nJump = (int)sizeof(char); + if( nLen >= (int)sizeof("&")-1 && SyStrnicmp(zIn,"&",sizeof("&")-1) == 0 ){ + /* & ==> '&' */ + ph7_result_string(pCtx,"&",(int)sizeof(char)); + nJump = (int)sizeof("&")-1; + }else if( nLen >= (int)sizeof("<")-1 && SyStrnicmp(zIn,"<",sizeof("<")-1) == 0 ){ + /* < ==> < */ + ph7_result_string(pCtx,"<",(int)sizeof(char)); + nJump = (int)sizeof("<")-1; + }else if( nLen >= (int)sizeof(">")-1 && SyStrnicmp(zIn,">",sizeof(">")-1) == 0 ){ + /* > ==> '>' */ + ph7_result_string(pCtx,">",(int)sizeof(char)); + nJump = (int)sizeof(">")-1; + }else if( nLen >= (int)sizeof(""")-1 && SyStrnicmp(zIn,""",sizeof(""")-1) == 0 ){ + /* " ==> '"' */ + if( (iFlags & 0x04) == 0 /*ENT_NOQUOTES*/ ){ + ph7_result_string(pCtx,"\"",(int)sizeof(char)); + }else{ + /* Leave untouched */ + ph7_result_string(pCtx,""",(int)sizeof(""")-1); + } + nJump = (int)sizeof(""")-1; + }else if( nLen >= (int)sizeof("'")-1 && SyStrnicmp(zIn,"'",sizeof("'")-1) == 0 ){ + /* ' ==> ''' */ + if( iFlags & 0x02 /*ENT_QUOTES*/ ){ + /* Expand ''' */ + ph7_result_string(pCtx,"'",(int)sizeof(char)); + }else{ + /* Leave untouched */ + ph7_result_string(pCtx,"'",(int)sizeof("'")-1); + } + nJump = (int)sizeof("'")-1; + }else if( nLen >= (int)sizeof(char) ){ + /* expand '&' */ + ph7_result_string(pCtx,"&",(int)sizeof(char)); + }else{ + /* No more input to process */ + break; + } + zIn += nJump; + } + return PH7_OK; +} +/* HTML encoding/Decoding table + * Source: Symisc RunTime API.[chm@symisc.net] + */ +static const char *azHtmlEscape[] = { + "<","<",">",">","&","&",""","\"","'","'", + "!","!","$","$","#","#","%","%","(","(", + ")",")","{","{","}","}","=","=","+","+", + "?","?","[","[","]","]","@","@",",","," + }; +/* + * array get_html_translation_table(void) + * Returns the translation table used by htmlspecialchars() and htmlentities(). + * Parameters + * None + * Return + * The translation table as an array or NULL on failure. + */ +static int PH7_builtin_get_html_translation_table(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + ph7_value *pArray,*pValue; + sxu32 n; + /* Element value */ + pValue = ph7_context_new_scalar(pCtx); + if( pValue == 0 ){ + SXUNUSED(nArg); /* cc warning */ + SXUNUSED(apArg); + /* Return NULL */ + ph7_result_null(pCtx); + return PH7_OK; + } + /* Create a new array */ + pArray = ph7_context_new_array(pCtx); + if( pArray == 0 ){ + /* Return NULL */ + ph7_result_null(pCtx); + return PH7_OK; + } + /* Make the table */ + for( n = 0 ; n < SX_ARRAYSIZE(azHtmlEscape) ; n += 2 ){ + /* Prepare the value */ + ph7_value_string(pValue,azHtmlEscape[n],-1 /* Compute length automatically */); + /* Insert the value */ + ph7_array_add_strkey_elem(pArray,azHtmlEscape[n+1],pValue); + /* Reset the string cursor */ + ph7_value_reset_string_cursor(pValue); + } + /* + * Return the array. + * Don't worry about freeing memory, everything will be automatically + * released upon we return from this function. + */ + ph7_result_value(pCtx,pArray); + return PH7_OK; +} +/* + * string htmlentities( string $string [, int $flags = ENT_COMPAT | ENT_HTML401]); + * Convert all applicable characters to HTML entities + * Parameters + * $string + * The input string. + * $flags + * A bitmask of one or more of the flags (see block-comment on PH7_builtin_htmlspecialchars()) + * Return + * The encoded string. + */ +static int PH7_builtin_htmlentities(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + int iFlags = 0x01; /* ENT_COMPAT */ + const char *zIn,*zEnd; + int nLen,c; + sxu32 n; + if( nArg < 1 || !ph7_value_is_string(apArg[0]) ){ + /* Missing/Invalid arguments,return NULL */ + ph7_result_null(pCtx); + return PH7_OK; + } + /* Extract the target string */ + zIn = ph7_value_to_string(apArg[0],&nLen); + zEnd = &zIn[nLen]; + /* Extract the flags if available */ + if( nArg > 1 ){ + iFlags = ph7_value_to_int(apArg[1]); + if( iFlags < 0 ){ + iFlags = 0x01; + } + } + /* Perform the requested operation */ + for(;;){ + if( zIn >= zEnd ){ + /* No more input to process */ + break; + } + c = zIn[0]; + /* Perform a linear lookup on the decoding table */ + for( n = 0 ; n < SX_ARRAYSIZE(azHtmlEscape) ; n += 2 ){ + if( azHtmlEscape[n+1][0] == c ){ + /* Got one */ + break; + } + } + if( n < SX_ARRAYSIZE(azHtmlEscape) ){ + /* Output the safe sequence [i.e: '<' ==> '<"] */ + if( c == '"' && (iFlags & 0x04) /*ENT_NOQUOTES*/ ){ + /* Expand the double quote verbatim */ + ph7_result_string(pCtx,(const char *)&c,(int)sizeof(char)); + }else if(c == '\'' && ((iFlags & 0x02 /*ENT_QUOTES*/) == 0 || (iFlags & 0x04) /*ENT_NOQUOTES*/) ){ + /* expand single quote verbatim */ + ph7_result_string(pCtx,(const char *)&c,(int)sizeof(char)); + }else{ + ph7_result_string(pCtx,azHtmlEscape[n],-1/*Compute length automatically */); + } + }else{ + /* Output character verbatim */ + ph7_result_string(pCtx,(const char *)&c,(int)sizeof(char)); + } + zIn++; + } + return PH7_OK; +} +/* + * string html_entity_decode(string $string [, int $quote_style = ENT_COMPAT [, string $charset = 'UTF-8' ]]) + * Perform the reverse operation of html_entity_decode(). + * Parameters + * $string + * The input string. + * $flags + * A bitmask of one or more of the flags (see comment on PH7_builtin_htmlspecialchars()) + * Return + * The decoded string. + */ +static int PH7_builtin_html_entity_decode(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + const char *zCur,*zIn,*zEnd; + int iFlags = 0x01; /* ENT_COMPAT */ + int nLen; + sxu32 n; + if( nArg < 1 || !ph7_value_is_string(apArg[0]) ){ + /* Missing/Invalid arguments,return NULL */ + ph7_result_null(pCtx); + return PH7_OK; + } + /* Extract the target string */ + zIn = ph7_value_to_string(apArg[0],&nLen); + zEnd = &zIn[nLen]; + /* Extract the flags if available */ + if( nArg > 1 ){ + iFlags = ph7_value_to_int(apArg[1]); + if( iFlags < 0 ){ + iFlags = 0x01; + } + } + /* Perform the requested operation */ + for(;;){ + if( zIn >= zEnd ){ + /* No more input to process */ + break; + } + zCur = zIn; + while( zIn < zEnd && zIn[0] != '&' ){ + zIn++; + } + if( zCur < zIn ){ + /* Append raw string verbatim */ + ph7_result_string(pCtx,zCur,(int)(zIn-zCur)); + } + if( zIn >= zEnd ){ + break; + } + nLen = (int)(zEnd-zIn); + /* Find an encoded sequence */ + for(n = 0 ; n < SX_ARRAYSIZE(azHtmlEscape) ; n += 2 ){ + int iLen = (int)SyStrlen(azHtmlEscape[n]); + if( nLen >= iLen && SyStrnicmp(zIn,azHtmlEscape[n],(sxu32)iLen) == 0 ){ + /* Got one */ + zIn += iLen; + break; + } + } + if( n < SX_ARRAYSIZE(azHtmlEscape) ){ + int c = azHtmlEscape[n+1][0]; + /* Output the decoded character */ + if( c == '\'' && ((iFlags & 0x02) == 0 /*ENT_QUOTES*/|| (iFlags & 0x04) /*ENT_NOQUOTES*/) ){ + /* Do not process single quotes */ + ph7_result_string(pCtx,azHtmlEscape[n],-1); + }else if( c == '"' && (iFlags & 0x04) /*ENT_NOQUOTES*/ ){ + /* Do not process double quotes */ + ph7_result_string(pCtx,azHtmlEscape[n],-1); + }else{ + ph7_result_string(pCtx,azHtmlEscape[n+1],-1); /* Compute length automatically */ + } + }else{ + /* Append '&' */ + ph7_result_string(pCtx,"&",(int)sizeof(char)); + zIn++; + } + } + return PH7_OK; +} +/* + * int strlen($string) + * return the length of the given string. + * Parameter + * string: The string being measured for length. + * Return + * length of the given string. + */ +static int PH7_builtin_strlen(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + int iLen = 0; + if( nArg > 0 ){ + ph7_value_to_string(apArg[0],&iLen); + } + /* String length */ + ph7_result_int(pCtx,iLen); + return PH7_OK; +} +/* + * int strcmp(string $str1,string $str2) + * Perform a binary safe string comparison. + * Parameter + * str1: The first string + * str2: The second string + * Return + * Returns < 0 if str1 is less than str2; > 0 if str1 is greater + * than str2, and 0 if they are equal. + */ +static int PH7_builtin_strcmp(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + const char *z1,*z2; + int n1,n2; + int res; + if( nArg < 2 ){ + res = nArg == 0 ? 0 : 1; + ph7_result_int(pCtx,res); + return PH7_OK; + } + /* Perform the comparison */ + z1 = ph7_value_to_string(apArg[0],&n1); + z2 = ph7_value_to_string(apArg[1],&n2); + res = SyStrncmp(z1,z2,(sxu32)(SXMAX(n1,n2))); + /* Comparison result */ + ph7_result_int(pCtx,res); + return PH7_OK; +} +/* + * int strncmp(string $str1,string $str2,int n) + * Perform a binary safe string comparison of the first n characters. + * Parameter + * str1: The first string + * str2: The second string + * Return + * Returns < 0 if str1 is less than str2; > 0 if str1 is greater + * than str2, and 0 if they are equal. + */ +static int PH7_builtin_strncmp(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + const char *z1,*z2; + int res; + int n; + if( nArg < 3 ){ + /* Perform a standard comparison */ + return PH7_builtin_strcmp(pCtx,nArg,apArg); + } + /* Desired comparison length */ + n = ph7_value_to_int(apArg[2]); + if( n < 0 ){ + /* Invalid length */ + ph7_result_int(pCtx,-1); + return PH7_OK; + } + /* Perform the comparison */ + z1 = ph7_value_to_string(apArg[0],0); + z2 = ph7_value_to_string(apArg[1],0); + res = SyStrncmp(z1,z2,(sxu32)n); + /* Comparison result */ + ph7_result_int(pCtx,res); + return PH7_OK; +} +/* + * int strcasecmp(string $str1,string $str2,int n) + * Perform a binary safe case-insensitive string comparison. + * Parameter + * str1: The first string + * str2: The second string + * Return + * Returns < 0 if str1 is less than str2; > 0 if str1 is greater + * than str2, and 0 if they are equal. + */ +static int PH7_builtin_strcasecmp(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + const char *z1,*z2; + int n1,n2; + int res; + if( nArg < 2 ){ + res = nArg == 0 ? 0 : 1; + ph7_result_int(pCtx,res); + return PH7_OK; + } + /* Perform the comparison */ + z1 = ph7_value_to_string(apArg[0],&n1); + z2 = ph7_value_to_string(apArg[1],&n2); + res = SyStrnicmp(z1,z2,(sxu32)(SXMAX(n1,n2))); + /* Comparison result */ + ph7_result_int(pCtx,res); + return PH7_OK; +} +/* + * int strncasecmp(string $str1,string $str2,int n) + * Perform a binary safe case-insensitive string comparison of the first n characters. + * Parameter + * $str1: The first string + * $str2: The second string + * $len: The length of strings to be used in the comparison. + * Return + * Returns < 0 if str1 is less than str2; > 0 if str1 is greater + * than str2, and 0 if they are equal. + */ +static int PH7_builtin_strncasecmp(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + const char *z1,*z2; + int res; + int n; + if( nArg < 3 ){ + /* Perform a standard comparison */ + return PH7_builtin_strcasecmp(pCtx,nArg,apArg); + } + /* Desired comparison length */ + n = ph7_value_to_int(apArg[2]); + if( n < 0 ){ + /* Invalid length */ + ph7_result_int(pCtx,-1); + return PH7_OK; + } + /* Perform the comparison */ + z1 = ph7_value_to_string(apArg[0],0); + z2 = ph7_value_to_string(apArg[1],0); + res = SyStrnicmp(z1,z2,(sxu32)n); + /* Comparison result */ + ph7_result_int(pCtx,res); + return PH7_OK; +} +/* + * Implode context [i.e: it's private data]. + * A pointer to the following structure is forwarded + * verbatim to the array walker callback defined below. + */ +struct implode_data { + ph7_context *pCtx; /* Call context */ + int bRecursive; /* TRUE if recursive implode [this is a symisc eXtension] */ + const char *zSep; /* Arguments separator if any */ + int nSeplen; /* Separator length */ + int bFirst; /* TRUE if first call */ + int nRecCount; /* Recursion count to avoid infinite loop */ +}; +/* + * Implode walker callback for the [ph7_array_walk()] interface. + * The following routine is invoked for each array entry passed + * to the implode() function. + */ +static int implode_callback(ph7_value *pKey,ph7_value *pValue,void *pUserData) +{ + struct implode_data *pData = (struct implode_data *)pUserData; + const char *zData; + int nLen; + if( pData->bRecursive && ph7_value_is_array(pValue) && pData->nRecCount < 32 ){ + if( pData->nSeplen > 0 ){ + if( !pData->bFirst ){ + /* append the separator first */ + ph7_result_string(pData->pCtx,pData->zSep,pData->nSeplen); + }else{ + pData->bFirst = 0; + } + } + /* Recurse */ + pData->bFirst = 1; + pData->nRecCount++; + PH7_HashmapWalk((ph7_hashmap *)pValue->x.pOther,implode_callback,pData); + pData->nRecCount--; + return PH7_OK; + } + /* Extract the string representation of the entry value */ + zData = ph7_value_to_string(pValue,&nLen); + if( nLen > 0 ){ + if( pData->nSeplen > 0 ){ + if( !pData->bFirst ){ + /* append the separator first */ + ph7_result_string(pData->pCtx,pData->zSep,pData->nSeplen); + }else{ + pData->bFirst = 0; + } + } + ph7_result_string(pData->pCtx,zData,nLen); + }else{ + SXUNUSED(pKey); /* cc warning */ + } + return PH7_OK; +} +/* + * string implode(string $glue,array $pieces,...) + * string implode(array $pieces,...) + * Join array elements with a string. + * $glue + * Defaults to an empty string. This is not the preferred usage of implode() as glue + * would be the second parameter and thus, the bad prototype would be used. + * $pieces + * The array of strings to implode. + * Return + * Returns a string containing a string representation of all the array elements in the same + * order, with the glue string between each element. + */ +static int PH7_builtin_implode(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + struct implode_data imp_data; + int i = 1; + if( nArg < 1 ){ + /* Missing argument,return NULL */ + ph7_result_null(pCtx); + return PH7_OK; + } + /* Prepare the implode context */ + imp_data.pCtx = pCtx; + imp_data.bRecursive = 0; + imp_data.bFirst = 1; + imp_data.nRecCount = 0; + if( !ph7_value_is_array(apArg[0]) ){ + imp_data.zSep = ph7_value_to_string(apArg[0],&imp_data.nSeplen); + }else{ + imp_data.zSep = 0; + imp_data.nSeplen = 0; + i = 0; + } + ph7_result_string(pCtx,"",0); /* Set an empty stirng */ + /* Start the 'join' process */ + while( i < nArg ){ + if( ph7_value_is_array(apArg[i]) ){ + /* Iterate throw array entries */ + ph7_array_walk(apArg[i],implode_callback,&imp_data); + }else{ + const char *zData; + int nLen; + /* Extract the string representation of the ph7 value */ + zData = ph7_value_to_string(apArg[i],&nLen); + if( nLen > 0 ){ + if( imp_data.nSeplen > 0 ){ + if( !imp_data.bFirst ){ + /* append the separator first */ + ph7_result_string(pCtx,imp_data.zSep,imp_data.nSeplen); + }else{ + imp_data.bFirst = 0; + } + } + ph7_result_string(pCtx,zData,nLen); + } + } + i++; + } + return PH7_OK; +} +/* + * Symisc eXtension: + * string implode_recursive(string $glue,array $pieces,...) + * Purpose + * Same as implode() but recurse on arrays. + * Example: + * $a = array('usr',array('home','dean')); + * echo implode_recursive("/",$a); + * Will output + * usr/home/dean. + * While the standard implode would produce. + * usr/Array. + * Parameter + * Refer to implode(). + * Return + * Refer to implode(). + */ +static int PH7_builtin_implode_recursive(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + struct implode_data imp_data; + int i = 1; + if( nArg < 1 ){ + /* Missing argument,return NULL */ + ph7_result_null(pCtx); + return PH7_OK; + } + /* Prepare the implode context */ + imp_data.pCtx = pCtx; + imp_data.bRecursive = 1; + imp_data.bFirst = 1; + imp_data.nRecCount = 0; + if( !ph7_value_is_array(apArg[0]) ){ + imp_data.zSep = ph7_value_to_string(apArg[0],&imp_data.nSeplen); + }else{ + imp_data.zSep = 0; + imp_data.nSeplen = 0; + i = 0; + } + ph7_result_string(pCtx,"",0); /* Set an empty stirng */ + /* Start the 'join' process */ + while( i < nArg ){ + if( ph7_value_is_array(apArg[i]) ){ + /* Iterate throw array entries */ + ph7_array_walk(apArg[i],implode_callback,&imp_data); + }else{ + const char *zData; + int nLen; + /* Extract the string representation of the ph7 value */ + zData = ph7_value_to_string(apArg[i],&nLen); + if( nLen > 0 ){ + if( imp_data.nSeplen > 0 ){ + if( !imp_data.bFirst ){ + /* append the separator first */ + ph7_result_string(pCtx,imp_data.zSep,imp_data.nSeplen); + }else{ + imp_data.bFirst = 0; + } + } + ph7_result_string(pCtx,zData,nLen); + } + } + i++; + } + return PH7_OK; +} +/* + * array explode(string $delimiter,string $string[,int $limit ]) + * Returns an array of strings, each of which is a substring of string + * formed by splitting it on boundaries formed by the string delimiter. + * Parameters + * $delimiter + * The boundary string. + * $string + * The input string. + * $limit + * If limit is set and positive, the returned array will contain a maximum + * of limit elements with the last element containing the rest of string. + * If the limit parameter is negative, all fields except the last -limit are returned. + * If the limit parameter is zero, then this is treated as 1. + * Returns + * Returns an array of strings created by splitting the string parameter + * on boundaries formed by the delimiter. + * If delimiter is an empty string (""), explode() will return FALSE. + * If delimiter contains a value that is not contained in string and a negative + * limit is used, then an empty array will be returned, otherwise an array containing string + * will be returned. + * NOTE: + * Negative limit is not supported. + */ +static int PH7_builtin_explode(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + const char *zDelim,*zString,*zCur,*zEnd; + int nDelim,nStrlen,iLimit; + ph7_value *pArray; + ph7_value *pValue; + sxu32 nOfft; + sxi32 rc; + if( nArg < 2 ){ + /* Missing arguments,return FALSE */ + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Extract the delimiter */ + zDelim = ph7_value_to_string(apArg[0],&nDelim); + if( nDelim < 1 ){ + /* Empty delimiter,return FALSE */ + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Extract the string */ + zString = ph7_value_to_string(apArg[1],&nStrlen); + if( nStrlen < 1 ){ + /* Empty delimiter,return FALSE */ + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Point to the end of the string */ + zEnd = &zString[nStrlen]; + /* Create the array */ + pArray = ph7_context_new_array(pCtx); + pValue = ph7_context_new_scalar(pCtx); + if( pArray == 0 || pValue == 0 ){ + /* Out of memory,return FALSE */ + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Set a defualt limit */ + iLimit = SXI32_HIGH; + if( nArg > 2 ){ + iLimit = ph7_value_to_int(apArg[2]); + if( iLimit < 0 ){ + iLimit = -iLimit; + } + if( iLimit == 0 ){ + iLimit = 1; + } + iLimit--; + } + /* Start exploding */ + for(;;){ + if( zString >= zEnd ){ + /* No more entry to process */ + break; + } + rc = SyBlobSearch(zString,(sxu32)(zEnd-zString),zDelim,nDelim,&nOfft); + if( rc != SXRET_OK || iLimit <= (int)ph7_array_count(pArray) ){ + /* Limit reached,insert the rest of the string and break */ + if( zEnd > zString ){ + ph7_value_string(pValue,zString,(int)(zEnd-zString)); + ph7_array_add_elem(pArray,0/* Automatic index assign*/,pValue); + } + break; + } + /* Point to the desired offset */ + zCur = &zString[nOfft]; + if( zCur > zString ){ + /* Perform the store operation */ + ph7_value_string(pValue,zString,(int)(zCur-zString)); + ph7_array_add_elem(pArray,0/* Automatic index assign*/,pValue); + } + /* Point beyond the delimiter */ + zString = &zCur[nDelim]; + /* Reset the cursor */ + ph7_value_reset_string_cursor(pValue); + } + /* Return the freshly created array */ + ph7_result_value(pCtx,pArray); + /* NOTE that every allocated ph7_value will be automatically + * released as soon we return from this foregin function. + */ + return PH7_OK; +} +/* + * string trim(string $str[,string $charlist ]) + * Strip whitespace (or other characters) from the beginning and end of a string. + * Parameters + * $str + * The string that will be trimmed. + * $charlist + * Optionally, the stripped characters can also be specified using the charlist parameter. + * Simply list all characters that you want to be stripped. + * With .. you can specify a range of characters. + * Returns. + * Thr processed string. + * NOTE: + * RANGE CHARACTERS [I.E: 'a'..'z'] are not supported. + */ +static int PH7_builtin_trim(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + const char *zString; + int nLen; + if( nArg < 1 ){ + /* Missing arguments,return null */ + ph7_result_null(pCtx); + return PH7_OK; + } + /* Extract the target string */ + zString = ph7_value_to_string(apArg[0],&nLen); + if( nLen < 1 ){ + /* Empty string,return */ + ph7_result_string(pCtx,"",0); + return PH7_OK; + } + /* Start the trim process */ + if( nArg < 2 ){ + SyString sStr; + /* Remove white spaces and NUL bytes */ + SyStringInitFromBuf(&sStr,zString,nLen); + SyStringFullTrimSafe(&sStr); + ph7_result_string(pCtx,sStr.zString,(int)sStr.nByte); + }else{ + /* Char list */ + const char *zList; + int nListlen; + zList = ph7_value_to_string(apArg[1],&nListlen); + if( nListlen < 1 ){ + /* Return the string unchanged */ + ph7_result_string(pCtx,zString,nLen); + }else{ + const char *zEnd = &zString[nLen]; + const char *zCur = zString; + const char *zPtr; + int i; + /* Left trim */ + for(;;){ + if( zCur >= zEnd ){ + break; + } + zPtr = zCur; + for( i = 0 ; i < nListlen ; i++ ){ + if( zCur < zEnd && zCur[0] == zList[i] ){ + zCur++; + } + } + if( zCur == zPtr ){ + /* No match,break immediately */ + break; + } + } + /* Right trim */ + zEnd--; + for(;;){ + if( zEnd <= zCur ){ + break; + } + zPtr = zEnd; + for( i = 0 ; i < nListlen ; i++ ){ + if( zEnd > zCur && zEnd[0] == zList[i] ){ + zEnd--; + } + } + if( zEnd == zPtr ){ + break; + } + } + if( zCur >= zEnd ){ + /* Return the empty string */ + ph7_result_string(pCtx,"",0); + }else{ + zEnd++; + ph7_result_string(pCtx,zCur,(int)(zEnd-zCur)); + } + } + } + return PH7_OK; +} +/* + * string rtrim(string $str[,string $charlist ]) + * Strip whitespace (or other characters) from the end of a string. + * Parameters + * $str + * The string that will be trimmed. + * $charlist + * Optionally, the stripped characters can also be specified using the charlist parameter. + * Simply list all characters that you want to be stripped. + * With .. you can specify a range of characters. + * Returns. + * Thr processed string. + * NOTE: + * RANGE CHARACTERS [I.E: 'a'..'z'] are not supported. + */ +static int PH7_builtin_rtrim(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + const char *zString; + int nLen; + if( nArg < 1 ){ + /* Missing arguments,return null */ + ph7_result_null(pCtx); + return PH7_OK; + } + /* Extract the target string */ + zString = ph7_value_to_string(apArg[0],&nLen); + if( nLen < 1 ){ + /* Empty string,return */ + ph7_result_string(pCtx,"",0); + return PH7_OK; + } + /* Start the trim process */ + if( nArg < 2 ){ + SyString sStr; + /* Remove white spaces and NUL bytes*/ + SyStringInitFromBuf(&sStr,zString,nLen); + SyStringRightTrimSafe(&sStr); + ph7_result_string(pCtx,sStr.zString,(int)sStr.nByte); + }else{ + /* Char list */ + const char *zList; + int nListlen; + zList = ph7_value_to_string(apArg[1],&nListlen); + if( nListlen < 1 ){ + /* Return the string unchanged */ + ph7_result_string(pCtx,zString,nLen); + }else{ + const char *zEnd = &zString[nLen - 1]; + const char *zCur = zString; + const char *zPtr; + int i; + /* Right trim */ + for(;;){ + if( zEnd <= zCur ){ + break; + } + zPtr = zEnd; + for( i = 0 ; i < nListlen ; i++ ){ + if( zEnd > zCur && zEnd[0] == zList[i] ){ + zEnd--; + } + } + if( zEnd == zPtr ){ + break; + } + } + if( zEnd <= zCur ){ + /* Return the empty string */ + ph7_result_string(pCtx,"",0); + }else{ + zEnd++; + ph7_result_string(pCtx,zCur,(int)(zEnd-zCur)); + } + } + } + return PH7_OK; +} +/* + * string ltrim(string $str[,string $charlist ]) + * Strip whitespace (or other characters) from the beginning and end of a string. + * Parameters + * $str + * The string that will be trimmed. + * $charlist + * Optionally, the stripped characters can also be specified using the charlist parameter. + * Simply list all characters that you want to be stripped. + * With .. you can specify a range of characters. + * Returns. + * Thr processed string. + * NOTE: + * RANGE CHARACTERS [I.E: 'a'..'z'] are not supported. + */ +static int PH7_builtin_ltrim(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + const char *zString; + int nLen; + if( nArg < 1 ){ + /* Missing arguments,return null */ + ph7_result_null(pCtx); + return PH7_OK; + } + /* Extract the target string */ + zString = ph7_value_to_string(apArg[0],&nLen); + if( nLen < 1 ){ + /* Empty string,return */ + ph7_result_string(pCtx,"",0); + return PH7_OK; + } + /* Start the trim process */ + if( nArg < 2 ){ + SyString sStr; + /* Remove white spaces and NUL byte */ + SyStringInitFromBuf(&sStr,zString,nLen); + SyStringLeftTrimSafe(&sStr); + ph7_result_string(pCtx,sStr.zString,(int)sStr.nByte); + }else{ + /* Char list */ + const char *zList; + int nListlen; + zList = ph7_value_to_string(apArg[1],&nListlen); + if( nListlen < 1 ){ + /* Return the string unchanged */ + ph7_result_string(pCtx,zString,nLen); + }else{ + const char *zEnd = &zString[nLen]; + const char *zCur = zString; + const char *zPtr; + int i; + /* Left trim */ + for(;;){ + if( zCur >= zEnd ){ + break; + } + zPtr = zCur; + for( i = 0 ; i < nListlen ; i++ ){ + if( zCur < zEnd && zCur[0] == zList[i] ){ + zCur++; + } + } + if( zCur == zPtr ){ + /* No match,break immediately */ + break; + } + } + if( zCur >= zEnd ){ + /* Return the empty string */ + ph7_result_string(pCtx,"",0); + }else{ + ph7_result_string(pCtx,zCur,(int)(zEnd-zCur)); + } + } + } + return PH7_OK; +} +/* + * string strtolower(string $str) + * Make a string lowercase. + * Parameters + * $str + * The input string. + * Returns. + * The lowercased string. + */ +static int PH7_builtin_strtolower(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + const char *zString,*zCur,*zEnd; + int nLen; + if( nArg < 1 ){ + /* Missing arguments,return null */ + ph7_result_null(pCtx); + return PH7_OK; + } + /* Extract the target string */ + zString = ph7_value_to_string(apArg[0],&nLen); + if( nLen < 1 ){ + /* Empty string,return */ + ph7_result_string(pCtx,"",0); + return PH7_OK; + } + /* Perform the requested operation */ + zEnd = &zString[nLen]; + for(;;){ + if( zString >= zEnd ){ + /* No more input,break immediately */ + break; + } + if( (unsigned char)zString[0] >= 0xc0 ){ + /* UTF-8 stream,output verbatim */ + zCur = zString; + zString++; + while( zString < zEnd && ((unsigned char)zString[0] & 0xc0) == 0x80){ + zString++; + } + /* Append UTF-8 stream */ + ph7_result_string(pCtx,zCur,(int)(zString-zCur)); + }else{ + int c = zString[0]; + if( SyisUpper(c) ){ + c = SyToLower(zString[0]); + } + /* Append character */ + ph7_result_string(pCtx,(const char *)&c,(int)sizeof(char)); + /* Advance the cursor */ + zString++; + } + } + return PH7_OK; +} +/* + * string strtolower(string $str) + * Make a string uppercase. + * Parameters + * $str + * The input string. + * Returns. + * The uppercased string. + */ +static int PH7_builtin_strtoupper(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + const char *zString,*zCur,*zEnd; + int nLen; + if( nArg < 1 ){ + /* Missing arguments,return null */ + ph7_result_null(pCtx); + return PH7_OK; + } + /* Extract the target string */ + zString = ph7_value_to_string(apArg[0],&nLen); + if( nLen < 1 ){ + /* Empty string,return */ + ph7_result_string(pCtx,"",0); + return PH7_OK; + } + /* Perform the requested operation */ + zEnd = &zString[nLen]; + for(;;){ + if( zString >= zEnd ){ + /* No more input,break immediately */ + break; + } + if( (unsigned char)zString[0] >= 0xc0 ){ + /* UTF-8 stream,output verbatim */ + zCur = zString; + zString++; + while( zString < zEnd && ((unsigned char)zString[0] & 0xc0) == 0x80){ + zString++; + } + /* Append UTF-8 stream */ + ph7_result_string(pCtx,zCur,(int)(zString-zCur)); + }else{ + int c = zString[0]; + if( SyisLower(c) ){ + c = SyToUpper(zString[0]); + } + /* Append character */ + ph7_result_string(pCtx,(const char *)&c,(int)sizeof(char)); + /* Advance the cursor */ + zString++; + } + } + return PH7_OK; +} +/* + * string ucfirst(string $str) + * Returns a string with the first character of str capitalized, if that + * character is alphabetic. + * Parameters + * $str + * The input string. + * Returns. + * The processed string. + */ +static int PH7_builtin_ucfirst(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + const char *zString,*zEnd; + int nLen,c; + if( nArg < 1 ){ + /* Missing arguments,return null */ + ph7_result_null(pCtx); + return PH7_OK; + } + /* Extract the target string */ + zString = ph7_value_to_string(apArg[0],&nLen); + if( nLen < 1 ){ + /* Empty string,return */ + ph7_result_string(pCtx,"",0); + return PH7_OK; + } + /* Perform the requested operation */ + zEnd = &zString[nLen]; + c = zString[0]; + if( SyisLower(c) ){ + c = SyToUpper(c); + } + /* Append the first character */ + ph7_result_string(pCtx,(const char *)&c,(int)sizeof(char)); + zString++; + if( zString < zEnd ){ + /* Append the rest of the input verbatim */ + ph7_result_string(pCtx,zString,(int)(zEnd-zString)); + } + return PH7_OK; +} +/* + * string lcfirst(string $str) + * Make a string's first character lowercase. + * Parameters + * $str + * The input string. + * Returns. + * The processed string. + */ +static int PH7_builtin_lcfirst(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + const char *zString,*zEnd; + int nLen,c; + if( nArg < 1 ){ + /* Missing arguments,return null */ + ph7_result_null(pCtx); + return PH7_OK; + } + /* Extract the target string */ + zString = ph7_value_to_string(apArg[0],&nLen); + if( nLen < 1 ){ + /* Empty string,return */ + ph7_result_string(pCtx,"",0); + return PH7_OK; + } + /* Perform the requested operation */ + zEnd = &zString[nLen]; + c = zString[0]; + if( SyisUpper(c) ){ + c = SyToLower(c); + } + /* Append the first character */ + ph7_result_string(pCtx,(const char *)&c,(int)sizeof(char)); + zString++; + if( zString < zEnd ){ + /* Append the rest of the input verbatim */ + ph7_result_string(pCtx,zString,(int)(zEnd-zString)); + } + return PH7_OK; +} +/* + * int ord(string $string) + * Returns the ASCII value of the first character of string. + * Parameters + * $str + * The input string. + * Returns. + * The ASCII value as an integer. + */ +static int PH7_builtin_ord(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + const char *zString; + int nLen,c; + if( nArg < 1 ){ + /* Missing arguments,return -1 */ + ph7_result_int(pCtx,-1); + return PH7_OK; + } + /* Extract the target string */ + zString = ph7_value_to_string(apArg[0],&nLen); + if( nLen < 1 ){ + /* Empty string,return -1 */ + ph7_result_int(pCtx,-1); + return PH7_OK; + } + /* Extract the ASCII value of the first character */ + c = zString[0]; + /* Return that value */ + ph7_result_int(pCtx,c); + return PH7_OK; +} +/* + * string chr(int $ascii) + * Returns a one-character string containing the character specified by ascii. + * Parameters + * $ascii + * The ascii code. + * Returns. + * The specified character. + */ +static int PH7_builtin_chr(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + int c; + if( nArg < 1 ){ + /* Missing arguments,return null */ + ph7_result_null(pCtx); + return PH7_OK; + } + /* Extract the ASCII value */ + c = ph7_value_to_int(apArg[0]); + /* Return the specified character */ + ph7_result_string(pCtx,(const char *)&c,(int)sizeof(char)); + return PH7_OK; +} +/* + * Binary to hex consumer callback. + * This callback is the default consumer used by the hash functions + * [i.e: bin2hex(),md5(),sha1(),md5_file() ... ] defined below. + */ +static int HashConsumer(const void *pData,unsigned int nLen,void *pUserData) +{ + /* Append hex chunk verbatim */ + ph7_result_string((ph7_context *)pUserData,(const char *)pData,(int)nLen); + return SXRET_OK; +} +/* + * string bin2hex(string $str) + * Convert binary data into hexadecimal representation. + * Parameters + * $str + * The input string. + * Returns. + * Returns the hexadecimal representation of the given string. + */ +static int PH7_builtin_bin2hex(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + const char *zString; + int nLen; + if( nArg < 1 ){ + /* Missing arguments,return null */ + ph7_result_null(pCtx); + return PH7_OK; + } + /* Extract the target string */ + zString = ph7_value_to_string(apArg[0],&nLen); + if( nLen < 1 ){ + /* Empty string,return */ + ph7_result_string(pCtx,"",0); + return PH7_OK; + } + /* Perform the requested operation */ + SyBinToHexConsumer((const void *)zString,(sxu32)nLen,HashConsumer,pCtx); + return PH7_OK; +} +/* Search callback signature */ +typedef sxi32 (*ProcStringMatch)(const void *,sxu32,const void *,sxu32,sxu32 *); +/* + * Case-insensitive pattern match. + * Brute force is the default search method used here. + * This is due to the fact that brute-forcing works quite + * well for short/medium texts on modern hardware. + */ +static sxi32 iPatternMatch(const void *pText,sxu32 nLen,const void *pPattern,sxu32 iPatLen,sxu32 *pOfft) +{ + const char *zpIn = (const char *)pPattern; + const char *zIn = (const char *)pText; + const char *zpEnd = &zpIn[iPatLen]; + const char *zEnd = &zIn[nLen]; + const char *zPtr,*zPtr2; + int c,d; + if( iPatLen > nLen ){ + /* Don't bother processing */ + return SXERR_NOTFOUND; + } + for(;;){ + if( zIn >= zEnd ){ + break; + } + c = SyToLower(zIn[0]); + d = SyToLower(zpIn[0]); + if( c == d ){ + zPtr = &zIn[1]; + zPtr2 = &zpIn[1]; + for(;;){ + if( zPtr2 >= zpEnd ){ + /* Pattern found */ + if( pOfft ){ *pOfft = (sxu32)(zIn-(const char *)pText); } + return SXRET_OK; + } + if( zPtr >= zEnd ){ + break; + } + c = SyToLower(zPtr[0]); + d = SyToLower(zPtr2[0]); + if( c != d ){ + break; + } + zPtr++; zPtr2++; + } + } + zIn++; + } + /* Pattern not found */ + return SXERR_NOTFOUND; +} +/* + * string strstr(string $haystack,string $needle[,bool $before_needle = false ]) + * Find the first occurrence of a string. + * Parameters + * $haystack + * The input string. + * $needle + * Search pattern (must be a string). + * $before_needle + * If TRUE, strstr() returns the part of the haystack before the first occurrence + * of the needle (excluding the needle). + * Return + * Returns the portion of string, or FALSE if needle is not found. + */ +static int PH7_builtin_strstr(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + ProcStringMatch xPatternMatch = SyBlobSearch; /* Case-sensitive pattern match */ + const char *zBlob,*zPattern; + int nLen,nPatLen; + sxu32 nOfft; + sxi32 rc; + if( nArg < 2 ){ + /* Missing arguments,return FALSE */ + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Extract the needle and the haystack */ + zBlob = ph7_value_to_string(apArg[0],&nLen); + zPattern = ph7_value_to_string(apArg[1],&nPatLen); + nOfft = 0; /* cc warning */ + if( nLen > 0 && nPatLen > 0 ){ + int before = 0; + /* Perform the lookup */ + rc = xPatternMatch(zBlob,(sxu32)nLen,zPattern,(sxu32)nPatLen,&nOfft); + if( rc != SXRET_OK ){ + /* Pattern not found,return FALSE */ + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Return the portion of the string */ + if( nArg > 2 ){ + before = ph7_value_to_int(apArg[2]); + } + if( before ){ + ph7_result_string(pCtx,zBlob,(int)(&zBlob[nOfft]-zBlob)); + }else{ + ph7_result_string(pCtx,&zBlob[nOfft],(int)(&zBlob[nLen]-&zBlob[nOfft])); + } + }else{ + ph7_result_bool(pCtx,0); + } + return PH7_OK; +} +/* + * string stristr(string $haystack,string $needle[,bool $before_needle = false ]) + * Case-insensitive strstr(). + * Parameters + * $haystack + * The input string. + * $needle + * Search pattern (must be a string). + * $before_needle + * If TRUE, strstr() returns the part of the haystack before the first occurrence + * of the needle (excluding the needle). + * Return + * Returns the portion of string, or FALSE if needle is not found. + */ +static int PH7_builtin_stristr(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + ProcStringMatch xPatternMatch = iPatternMatch; /* Case-insensitive pattern match */ + const char *zBlob,*zPattern; + int nLen,nPatLen; + sxu32 nOfft; + sxi32 rc; + if( nArg < 2 ){ + /* Missing arguments,return FALSE */ + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Extract the needle and the haystack */ + zBlob = ph7_value_to_string(apArg[0],&nLen); + zPattern = ph7_value_to_string(apArg[1],&nPatLen); + nOfft = 0; /* cc warning */ + if( nLen > 0 && nPatLen > 0 ){ + int before = 0; + /* Perform the lookup */ + rc = xPatternMatch(zBlob,(sxu32)nLen,zPattern,(sxu32)nPatLen,&nOfft); + if( rc != SXRET_OK ){ + /* Pattern not found,return FALSE */ + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Return the portion of the string */ + if( nArg > 2 ){ + before = ph7_value_to_int(apArg[2]); + } + if( before ){ + ph7_result_string(pCtx,zBlob,(int)(&zBlob[nOfft]-zBlob)); + }else{ + ph7_result_string(pCtx,&zBlob[nOfft],(int)(&zBlob[nLen]-&zBlob[nOfft])); + } + }else{ + ph7_result_bool(pCtx,0); + } + return PH7_OK; +} +/* + * int strpos(string $haystack,string $needle [,int $offset = 0 ] ) + * Returns the numeric position of the first occurrence of needle in the haystack string. + * Parameters + * $haystack + * The input string. + * $needle + * Search pattern (must be a string). + * $offset + * This optional offset parameter allows you to specify which character in haystack + * to start searching. The position returned is still relative to the beginning + * of haystack. + * Return + * Returns the position as an integer.If needle is not found, strpos() will return FALSE. + */ +static int PH7_builtin_strpos(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + ProcStringMatch xPatternMatch = SyBlobSearch; /* Case-sensitive pattern match */ + const char *zBlob,*zPattern; + int nLen,nPatLen,nStart; + sxu32 nOfft; + sxi32 rc; + if( nArg < 2 ){ + /* Missing arguments,return FALSE */ + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Extract the needle and the haystack */ + zBlob = ph7_value_to_string(apArg[0],&nLen); + zPattern = ph7_value_to_string(apArg[1],&nPatLen); + nOfft = 0; /* cc warning */ + nStart = 0; + /* Peek the starting offset if available */ + if( nArg > 2 ){ + nStart = ph7_value_to_int(apArg[2]); + if( nStart < 0 ){ + nStart = -nStart; + } + if( nStart >= nLen ){ + /* Invalid offset */ + nStart = 0; + }else{ + zBlob += nStart; + nLen -= nStart; + } + } + if( nLen > 0 && nPatLen > 0 ){ + /* Perform the lookup */ + rc = xPatternMatch(zBlob,(sxu32)nLen,zPattern,(sxu32)nPatLen,&nOfft); + if( rc != SXRET_OK ){ + /* Pattern not found,return FALSE */ + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Return the pattern position */ + ph7_result_int64(pCtx,(ph7_int64)(nOfft+nStart)); + }else{ + ph7_result_bool(pCtx,0); + } + return PH7_OK; +} +/* + * int stripos(string $haystack,string $needle [,int $offset = 0 ] ) + * Case-insensitive strpos. + * Parameters + * $haystack + * The input string. + * $needle + * Search pattern (must be a string). + * $offset + * This optional offset parameter allows you to specify which character in haystack + * to start searching. The position returned is still relative to the beginning + * of haystack. + * Return + * Returns the position as an integer.If needle is not found, strpos() will return FALSE. + */ +static int PH7_builtin_stripos(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + ProcStringMatch xPatternMatch = iPatternMatch; /* Case-insensitive pattern match */ + const char *zBlob,*zPattern; + int nLen,nPatLen,nStart; + sxu32 nOfft; + sxi32 rc; + if( nArg < 2 ){ + /* Missing arguments,return FALSE */ + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Extract the needle and the haystack */ + zBlob = ph7_value_to_string(apArg[0],&nLen); + zPattern = ph7_value_to_string(apArg[1],&nPatLen); + nOfft = 0; /* cc warning */ + nStart = 0; + /* Peek the starting offset if available */ + if( nArg > 2 ){ + nStart = ph7_value_to_int(apArg[2]); + if( nStart < 0 ){ + nStart = -nStart; + } + if( nStart >= nLen ){ + /* Invalid offset */ + nStart = 0; + }else{ + zBlob += nStart; + nLen -= nStart; + } + } + if( nLen > 0 && nPatLen > 0 ){ + /* Perform the lookup */ + rc = xPatternMatch(zBlob,(sxu32)nLen,zPattern,(sxu32)nPatLen,&nOfft); + if( rc != SXRET_OK ){ + /* Pattern not found,return FALSE */ + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Return the pattern position */ + ph7_result_int64(pCtx,(ph7_int64)(nOfft+nStart)); + }else{ + ph7_result_bool(pCtx,0); + } + return PH7_OK; +} +/* + * int strrpos(string $haystack,string $needle [,int $offset = 0 ] ) + * Find the numeric position of the last occurrence of needle in the haystack string. + * Parameters + * $haystack + * The input string. + * $needle + * Search pattern (must be a string). + * $offset + * If specified, search will start this number of characters counted from the beginning + * of the string. If the value is negative, search will instead start from that many + * characters from the end of the string, searching backwards. + * Return + * Returns the position as an integer.If needle is not found, strrpos() will return FALSE. + */ +static int PH7_builtin_strrpos(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + const char *zStart,*zBlob,*zPattern,*zPtr,*zEnd; + ProcStringMatch xPatternMatch = SyBlobSearch; /* Case-sensitive pattern match */ + int nLen,nPatLen; + sxu32 nOfft; + sxi32 rc; + if( nArg < 2 ){ + /* Missing arguments,return FALSE */ + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Extract the needle and the haystack */ + zBlob = ph7_value_to_string(apArg[0],&nLen); + zPattern = ph7_value_to_string(apArg[1],&nPatLen); + /* Point to the end of the pattern */ + zPtr = &zBlob[nLen - 1]; + zEnd = &zBlob[nLen]; + /* Save the starting posistion */ + zStart = zBlob; + nOfft = 0; /* cc warning */ + /* Peek the starting offset if available */ + if( nArg > 2 ){ + int nStart; + nStart = ph7_value_to_int(apArg[2]); + if( nStart < 0 ){ + nStart = -nStart; + if( nStart >= nLen ){ + /* Invalid offset */ + ph7_result_bool(pCtx,0); + return PH7_OK; + }else{ + nLen -= nStart; + zPtr = &zBlob[nLen - 1]; + zEnd = &zBlob[nLen]; + } + }else{ + if( nStart >= nLen ){ + /* Invalid offset */ + ph7_result_bool(pCtx,0); + return PH7_OK; + }else{ + zBlob += nStart; + nLen -= nStart; + } + } + } + if( nLen > 0 && nPatLen > 0 ){ + /* Perform the lookup */ + for(;;){ + if( zBlob >= zPtr ){ + break; + } + rc = xPatternMatch((const void *)zPtr,(sxu32)(zEnd-zPtr),(const void *)zPattern,(sxu32)nPatLen,&nOfft); + if( rc == SXRET_OK ){ + /* Pattern found,return it's position */ + ph7_result_int64(pCtx,(ph7_int64)(&zPtr[nOfft] - zStart)); + return PH7_OK; + } + zPtr--; + } + /* Pattern not found,return FALSE */ + ph7_result_bool(pCtx,0); + }else{ + ph7_result_bool(pCtx,0); + } + return PH7_OK; +} +/* + * int strripos(string $haystack,string $needle [,int $offset = 0 ] ) + * Case-insensitive strrpos. + * Parameters + * $haystack + * The input string. + * $needle + * Search pattern (must be a string). + * $offset + * If specified, search will start this number of characters counted from the beginning + * of the string. If the value is negative, search will instead start from that many + * characters from the end of the string, searching backwards. + * Return + * Returns the position as an integer.If needle is not found, strrpos() will return FALSE. + */ +static int PH7_builtin_strripos(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + const char *zStart,*zBlob,*zPattern,*zPtr,*zEnd; + ProcStringMatch xPatternMatch = iPatternMatch; /* Case-insensitive pattern match */ + int nLen,nPatLen; + sxu32 nOfft; + sxi32 rc; + if( nArg < 2 ){ + /* Missing arguments,return FALSE */ + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Extract the needle and the haystack */ + zBlob = ph7_value_to_string(apArg[0],&nLen); + zPattern = ph7_value_to_string(apArg[1],&nPatLen); + /* Point to the end of the pattern */ + zPtr = &zBlob[nLen - 1]; + zEnd = &zBlob[nLen]; + /* Save the starting posistion */ + zStart = zBlob; + nOfft = 0; /* cc warning */ + /* Peek the starting offset if available */ + if( nArg > 2 ){ + int nStart; + nStart = ph7_value_to_int(apArg[2]); + if( nStart < 0 ){ + nStart = -nStart; + if( nStart >= nLen ){ + /* Invalid offset */ + ph7_result_bool(pCtx,0); + return PH7_OK; + }else{ + nLen -= nStart; + zPtr = &zBlob[nLen - 1]; + zEnd = &zBlob[nLen]; + } + }else{ + if( nStart >= nLen ){ + /* Invalid offset */ + ph7_result_bool(pCtx,0); + return PH7_OK; + }else{ + zBlob += nStart; + nLen -= nStart; + } + } + } + if( nLen > 0 && nPatLen > 0 ){ + /* Perform the lookup */ + for(;;){ + if( zBlob >= zPtr ){ + break; + } + rc = xPatternMatch((const void *)zPtr,(sxu32)(zEnd-zPtr),(const void *)zPattern,(sxu32)nPatLen,&nOfft); + if( rc == SXRET_OK ){ + /* Pattern found,return it's position */ + ph7_result_int64(pCtx,(ph7_int64)(&zPtr[nOfft] - zStart)); + return PH7_OK; + } + zPtr--; + } + /* Pattern not found,return FALSE */ + ph7_result_bool(pCtx,0); + }else{ + ph7_result_bool(pCtx,0); + } + return PH7_OK; +} +/* + * int strrchr(string $haystack,mixed $needle) + * Find the last occurrence of a character in a string. + * Parameters + * $haystack + * The input string. + * $needle + * If needle contains more than one character, only the first is used. + * This behavior is different from that of strstr(). + * If needle is not a string, it is converted to an integer and applied + * as the ordinal value of a character. + * Return + * This function returns the portion of string, or FALSE if needle is not found. + */ +static int PH7_builtin_strrchr(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + const char *zBlob; + int nLen,c; + if( nArg < 2 ){ + /* Missing arguments,return FALSE */ + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Extract the haystack */ + zBlob = ph7_value_to_string(apArg[0],&nLen); + c = 0; /* cc warning */ + if( nLen > 0 ){ + sxu32 nOfft; + sxi32 rc; + if( ph7_value_is_string(apArg[1]) ){ + const char *zPattern; + zPattern = ph7_value_to_string(apArg[1],0); /* Never fail,so there is no need to check + * for NULL pointer. + */ + c = zPattern[0]; + }else{ + /* Int cast */ + c = ph7_value_to_int(apArg[1]); + } + /* Perform the lookup */ + rc = SyByteFind2(zBlob,(sxu32)nLen,c,&nOfft); + if( rc != SXRET_OK ){ + /* No such entry,return FALSE */ + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Return the string portion */ + ph7_result_string(pCtx,&zBlob[nOfft],(int)(&zBlob[nLen]-&zBlob[nOfft])); + }else{ + ph7_result_bool(pCtx,0); + } + return PH7_OK; +} +/* + * string strrev(string $string) + * Reverse a string. + * Parameters + * $string + * String to be reversed. + * Return + * The reversed string. + */ +static int PH7_builtin_strrev(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + const char *zIn,*zEnd; + int nLen,c; + if( nArg < 1 ){ + /* Missing arguments,return NULL */ + ph7_result_null(pCtx); + return PH7_OK; + } + /* Extract the target string */ + zIn = ph7_value_to_string(apArg[0],&nLen); + if( nLen < 1 ){ + /* Empty string Return null */ + ph7_result_null(pCtx); + return PH7_OK; + } + /* Perform the requested operation */ + zEnd = &zIn[nLen - 1]; + for(;;){ + if( zEnd < zIn ){ + /* No more input to process */ + break; + } + /* Append current character */ + c = zEnd[0]; + ph7_result_string(pCtx,(const char *)&c,(int)sizeof(char)); + zEnd--; + } + return PH7_OK; +} +/* + * string ucwords(string $string) + * Uppercase the first character of each word in a string. + * The definition of a word is any string of characters that is immediately after + * a whitespace (These are: space, form-feed, newline, carriage return, horizontal tab, and vertical tab). + * Parameters + * $string + * The input string. + * Return + * The modified string.. + */ +static int PH7_builtin_ucwords(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + const char *zIn,*zCur,*zEnd; + int nLen,c; + if( nArg < 1 ){ + /* Missing arguments,return NULL */ + ph7_result_null(pCtx); + return PH7_OK; + } + /* Extract the target string */ + zIn = ph7_value_to_string(apArg[0],&nLen); + if( nLen < 1 ){ + /* Empty string Return null */ + ph7_result_null(pCtx); + return PH7_OK; + } + /* Perform the requested operation */ + zEnd = &zIn[nLen]; + for(;;){ + /* Jump leading white spaces */ + zCur = zIn; + while( zIn < zEnd && (unsigned char)zIn[0] < 0x80 && SyisSpace(zIn[0]) ){ + zIn++; + } + if( zCur < zIn ){ + /* Append white space stream */ + ph7_result_string(pCtx,zCur,(int)(zIn-zCur)); + } + if( zIn >= zEnd ){ + /* No more input to process */ + break; + } + c = zIn[0]; + if( c < 0x80 && SyisLower(c) ){ + c = SyToUpper(c); + } + /* Append the upper-cased character */ + ph7_result_string(pCtx,(const char *)&c,(int)sizeof(char)); + zIn++; + zCur = zIn; + /* Append the word varbatim */ + while( zIn < zEnd ){ + if( (unsigned char)zIn[0] >= 0xc0 ){ + /* UTF-8 stream */ + zIn++; + SX_JMP_UTF8(zIn,zEnd); + }else if( !SyisSpace(zIn[0]) ){ + zIn++; + }else{ + break; + } + } + if( zCur < zIn ){ + ph7_result_string(pCtx,zCur,(int)(zIn-zCur)); + } + } + return PH7_OK; +} +/* + * string str_repeat(string $input,int $multiplier) + * Returns input repeated multiplier times. + * Parameters + * $string + * String to be repeated. + * $multiplier + * Number of time the input string should be repeated. + * multiplier has to be greater than or equal to 0. If the multiplier is set + * to 0, the function will return an empty string. + * Return + * The repeated string. + */ +static int PH7_builtin_str_repeat(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + const char *zIn; + int nLen,nMul; + int rc; + if( nArg < 2 ){ + /* Missing arguments,return NULL */ + ph7_result_null(pCtx); + return PH7_OK; + } + /* Extract the target string */ + zIn = ph7_value_to_string(apArg[0],&nLen); + if( nLen < 1 ){ + /* Empty string.Return null */ + ph7_result_null(pCtx); + return PH7_OK; + } + /* Extract the multiplier */ + nMul = ph7_value_to_int(apArg[1]); + if( nMul < 1 ){ + /* Return the empty string */ + ph7_result_string(pCtx,"",0); + return PH7_OK; + } + /* Perform the requested operation */ + for(;;){ + if( !nMul ){ + break; + } + /* Append the copy */ + rc = ph7_result_string(pCtx,zIn,nLen); + if( rc != PH7_OK ){ + /* Out of memory,break immediately */ + break; + } + nMul--; + } + return PH7_OK; +} +/* + * string nl2br(string $string[,bool $is_xhtml = true ]) + * Inserts HTML line breaks before all newlines in a string. + * Parameters + * $string + * The input string. + * $is_xhtml + * Whenever to use XHTML compatible line breaks or not. + * Return + * The processed string. + */ +static int PH7_builtin_nl2br(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + const char *zIn,*zCur,*zEnd; + int is_xhtml = 0; + int nLen; + if( nArg < 1 ){ + /* Missing arguments,return the empty string */ + ph7_result_string(pCtx,"",0); + return PH7_OK; + } + /* Extract the target string */ + zIn = ph7_value_to_string(apArg[0],&nLen); + if( nLen < 1 ){ + /* Empty string,return null */ + ph7_result_null(pCtx); + return PH7_OK; + } + if( nArg > 1 ){ + is_xhtml = ph7_value_to_bool(apArg[1]); + } + zEnd = &zIn[nLen]; + /* Perform the requested operation */ + for(;;){ + zCur = zIn; + /* Delimit the string */ + while( zIn < zEnd && (zIn[0] != '\n'&& zIn[0] != '\r') ){ + zIn++; + } + if( zCur < zIn ){ + /* Output chunk verbatim */ + ph7_result_string(pCtx,zCur,(int)(zIn-zCur)); + } + if( zIn >= zEnd ){ + /* No more input to process */ + break; + } + /* Output the HTML line break */ + if( is_xhtml ){ + ph7_result_string(pCtx,"
",(int)sizeof("
")-1); + }else{ + ph7_result_string(pCtx,"
",(int)sizeof("
")-1); + } + zCur = zIn; + /* Append trailing line */ + while( zIn < zEnd && (zIn[0] == '\n' || zIn[0] == '\r') ){ + zIn++; + } + if( zCur < zIn ){ + /* Output chunk verbatim */ + ph7_result_string(pCtx,zCur,(int)(zIn-zCur)); + } + } + return PH7_OK; +} +/* + * Format a given string and invoke the given callback on each processed chunk. + * According to the PHP reference manual. + * The format string is composed of zero or more directives: ordinary characters + * (excluding %) that are copied directly to the result, and conversion + * specifications, each of which results in fetching its own parameter. + * This applies to both sprintf() and printf(). + * Each conversion specification consists of a percent sign (%), followed by one + * or more of these elements, in order: + * An optional sign specifier that forces a sign (- or +) to be used on a number. + * By default, only the - sign is used on a number if it's negative. This specifier forces + * positive numbers to have the + sign attached as well. + * An optional padding specifier that says what character will be used for padding + * the results to the right string size. This may be a space character or a 0 (zero character). + * The default is to pad with spaces. An alternate padding character can be specified by prefixing + * it with a single quote ('). See the examples below. + * An optional alignment specifier that says if the result should be left-justified or right-justified. + * The default is right-justified; a - character here will make it left-justified. + * An optional number, a width specifier that says how many characters (minimum) this conversion + * should result in. + * An optional precision specifier in the form of a period (`.') followed by an optional decimal + * digit string that says how many decimal digits should be displayed for floating-point numbers. + * When using this specifier on a string, it acts as a cutoff point, setting a maximum character + * limit to the string. + * A type specifier that says what type the argument data should be treated as. Possible types: + * % - a literal percent character. No argument is required. + * b - the argument is treated as an integer, and presented as a binary number. + * c - the argument is treated as an integer, and presented as the character with that ASCII value. + * d - the argument is treated as an integer, and presented as a (signed) decimal number. + * e - the argument is treated as scientific notation (e.g. 1.2e+2). The precision specifier stands + * for the number of digits after the decimal point. + * E - like %e but uses uppercase letter (e.g. 1.2E+2). + * u - the argument is treated as an integer, and presented as an unsigned decimal number. + * f - the argument is treated as a float, and presented as a floating-point number (locale aware). + * F - the argument is treated as a float, and presented as a floating-point number (non-locale aware). + * g - shorter of %e and %f. + * G - shorter of %E and %f. + * o - the argument is treated as an integer, and presented as an octal number. + * s - the argument is treated as and presented as a string. + * x - the argument is treated as an integer and presented as a hexadecimal number (with lowercase letters). + * X - the argument is treated as an integer and presented as a hexadecimal number (with uppercase letters). + */ +/* + * This implementation is based on the one found in the SQLite3 source tree. + */ +#define PH7_FMT_BUFSIZ 1024 /* Conversion buffer size */ +/* +** Conversion types fall into various categories as defined by the +** following enumeration. +*/ +#define PH7_FMT_RADIX 1 /* Integer types.%d, %x, %o, and so forth */ +#define PH7_FMT_FLOAT 2 /* Floating point.%f */ +#define PH7_FMT_EXP 3 /* Exponentional notation.%e and %E */ +#define PH7_FMT_GENERIC 4 /* Floating or exponential, depending on exponent.%g */ +#define PH7_FMT_SIZE 5 /* Total number of characters processed so far.%n */ +#define PH7_FMT_STRING 6 /* Strings.%s */ +#define PH7_FMT_PERCENT 7 /* Percent symbol.%% */ +#define PH7_FMT_CHARX 8 /* Characters.%c */ +#define PH7_FMT_ERROR 9 /* Used to indicate no such conversion type */ +/* +** Allowed values for ph7_fmt_info.flags +*/ +#define PH7_FMT_FLAG_SIGNED 0x01 +#define PH7_FMT_FLAG_UNSIGNED 0x02 +/* +** Each builtin conversion character (ex: the 'd' in "%d") is described +** by an instance of the following structure +*/ +typedef struct ph7_fmt_info ph7_fmt_info; +struct ph7_fmt_info +{ + char fmttype; /* The format field code letter [i.e: 'd','s','x'] */ + sxu8 base; /* The base for radix conversion */ + int flags; /* One or more of PH7_FMT_FLAG_ constants below */ + sxu8 type; /* Conversion paradigm */ + char *charset; /* The character set for conversion */ + char *prefix; /* Prefix on non-zero values in alt format */ +}; +#ifndef PH7_OMIT_FLOATING_POINT +/* +** "*val" is a double such that 0.1 <= *val < 10.0 +** Return the ascii code for the leading digit of *val, then +** multiply "*val" by 10.0 to renormalize. +** +** Example: +** input: *val = 3.14159 +** output: *val = 1.4159 function return = '3' +** +** The counter *cnt is incremented each time. After counter exceeds +** 16 (the number of significant digits in a 64-bit float) '0' is +** always returned. +*/ +static int vxGetdigit(sxlongreal *val,int *cnt) +{ + sxlongreal d; + int digit; + + if( (*cnt)++ >= 16 ){ + return '0'; + } + digit = (int)*val; + d = digit; + *val = (*val - d)*10.0; + return digit + '0' ; +} +#endif /* PH7_OMIT_FLOATING_POINT */ +/* + * The following table is searched linearly, so it is good to put the most frequently + * used conversion types first. + */ +static const ph7_fmt_info aFmt[] = { + { 'd', 10, PH7_FMT_FLAG_SIGNED, PH7_FMT_RADIX, "0123456789",0 }, + { 's', 0, 0, PH7_FMT_STRING, 0, 0 }, + { 'c', 0, 0, PH7_FMT_CHARX, 0, 0 }, + { 'x', 16, 0, PH7_FMT_RADIX, "0123456789abcdef", "x0" }, + { 'X', 16, 0, PH7_FMT_RADIX, "0123456789ABCDEF", "X0" }, + { 'b', 2, 0, PH7_FMT_RADIX, "01", "b0"}, + { 'o', 8, 0, PH7_FMT_RADIX, "01234567", "0" }, + { 'u', 10, 0, PH7_FMT_RADIX, "0123456789", 0 }, + { 'f', 0, PH7_FMT_FLAG_SIGNED, PH7_FMT_FLOAT, 0, 0 }, + { 'F', 0, PH7_FMT_FLAG_SIGNED, PH7_FMT_FLOAT, 0, 0 }, + { 'e', 0, PH7_FMT_FLAG_SIGNED, PH7_FMT_EXP, "e", 0 }, + { 'E', 0, PH7_FMT_FLAG_SIGNED, PH7_FMT_EXP, "E", 0 }, + { 'g', 0, PH7_FMT_FLAG_SIGNED, PH7_FMT_GENERIC, "e", 0 }, + { 'G', 0, PH7_FMT_FLAG_SIGNED, PH7_FMT_GENERIC, "E", 0 }, + { '%', 0, 0, PH7_FMT_PERCENT, 0, 0 } +}; +/* + * Format a given string. + * The root program. All variations call this core. + * INPUTS: + * xConsumer This is a pointer to a function taking four arguments + * 1. A pointer to the call context. + * 2. A pointer to the list of characters to be output + * (Note, this list is NOT null terminated.) + * 3. An integer number of characters to be output. + * (Note: This number might be zero.) + * 4. Upper layer private data. + * zIn This is the format string, as in the usual print. + * apArg This is a pointer to a list of arguments. + */ +PH7_PRIVATE sxi32 PH7_InputFormat( + int (*xConsumer)(ph7_context *,const char *,int,void *), /* Format consumer */ + ph7_context *pCtx, /* call context */ + const char *zIn, /* Format string */ + int nByte, /* Format string length */ + int nArg, /* Total argument of the given arguments */ + ph7_value **apArg, /* User arguments */ + void *pUserData, /* Last argument to xConsumer() */ + int vf /* TRUE if called from vfprintf,vsprintf context */ + ) +{ + char spaces[] = " "; +#define etSPACESIZE ((int)sizeof(spaces)-1) + const char *zCur,*zEnd = &zIn[nByte]; + char *zBuf,zWorker[PH7_FMT_BUFSIZ]; /* Working buffer */ + const ph7_fmt_info *pInfo; /* Pointer to the appropriate info structure */ + int flag_alternateform; /* True if "#" flag is present */ + int flag_leftjustify; /* True if "-" flag is present */ + int flag_blanksign; /* True if " " flag is present */ + int flag_plussign; /* True if "+" flag is present */ + int flag_zeropad; /* True if field width constant starts with zero */ + ph7_value *pArg; /* Current processed argument */ + ph7_int64 iVal; + int precision; /* Precision of the current field */ + char *zExtra; + int c,rc,n; + int length; /* Length of the field */ + int prefix; + sxu8 xtype; /* Conversion paradigm */ + int width; /* Width of the current field */ + int idx; + n = (vf == TRUE) ? 0 : 1; +#define NEXT_ARG ( n < nArg ? apArg[n++] : 0 ) + /* Start the format process */ + for(;;){ + zCur = zIn; + while( zIn < zEnd && zIn[0] != '%' ){ + zIn++; + } + if( zCur < zIn ){ + /* Consume chunk verbatim */ + rc = xConsumer(pCtx,zCur,(int)(zIn-zCur),pUserData); + if( rc == SXERR_ABORT ){ + /* Callback request an operation abort */ + break; + } + } + if( zIn >= zEnd ){ + /* No more input to process,break immediately */ + break; + } + /* Find out what flags are present */ + flag_leftjustify = flag_plussign = flag_blanksign = + flag_alternateform = flag_zeropad = 0; + zIn++; /* Jump the precent sign */ + do{ + c = zIn[0]; + switch( c ){ + case '-': flag_leftjustify = 1; c = 0; break; + case '+': flag_plussign = 1; c = 0; break; + case ' ': flag_blanksign = 1; c = 0; break; + case '#': flag_alternateform = 1; c = 0; break; + case '0': flag_zeropad = 1; c = 0; break; + case '\'': + zIn++; + if( zIn < zEnd ){ + /* An alternate padding character can be specified by prefixing it with a single quote (') */ + c = zIn[0]; + for(idx = 0 ; idx < etSPACESIZE ; ++idx ){ + spaces[idx] = (char)c; + } + c = 0; + } + break; + default: break; + } + }while( c==0 && (zIn++ < zEnd) ); + /* Get the field width */ + width = 0; + while( zIn < zEnd && ( zIn[0] >='0' && zIn[0] <='9') ){ + width = width*10 + (zIn[0] - '0'); + zIn++; + } + if( zIn < zEnd && zIn[0] == '$' ){ + /* Position specifer */ + if( width > 0 ){ + n = width; + if( vf && n > 0 ){ + n--; + } + } + zIn++; + width = 0; + if( zIn < zEnd && zIn[0] == '0' ){ + flag_zeropad = 1; + zIn++; + } + while( zIn < zEnd && ( zIn[0] >='0' && zIn[0] <='9') ){ + width = width*10 + (zIn[0] - '0'); + zIn++; + } + } + if( width > PH7_FMT_BUFSIZ-10 ){ + width = PH7_FMT_BUFSIZ-10; + } + /* Get the precision */ + precision = -1; + if( zIn < zEnd && zIn[0] == '.' ){ + precision = 0; + zIn++; + while( zIn < zEnd && ( zIn[0] >='0' && zIn[0] <='9') ){ + precision = precision*10 + (zIn[0] - '0'); + zIn++; + } + } + if( zIn >= zEnd ){ + /* No more input */ + break; + } + /* Fetch the info entry for the field */ + pInfo = 0; + xtype = PH7_FMT_ERROR; + c = zIn[0]; + zIn++; /* Jump the format specifer */ + for(idx=0; idx< (int)SX_ARRAYSIZE(aFmt); idx++){ + if( c==aFmt[idx].fmttype ){ + pInfo = &aFmt[idx]; + xtype = pInfo->type; + break; + } + } + zBuf = zWorker; /* Point to the working buffer */ + length = 0; + zExtra = 0; + /* + ** At this point, variables are initialized as follows: + ** + ** flag_alternateform TRUE if a '#' is present. + ** flag_plussign TRUE if a '+' is present. + ** flag_leftjustify TRUE if a '-' is present or if the + ** field width was negative. + ** flag_zeropad TRUE if the width began with 0. + ** the conversion character. + ** flag_blanksign TRUE if a ' ' is present. + ** width The specified field width. This is + ** always non-negative. Zero is the default. + ** precision The specified precision. The default + ** is -1. + */ + switch(xtype){ + case PH7_FMT_PERCENT: + /* A literal percent character */ + zWorker[0] = '%'; + length = (int)sizeof(char); + break; + case PH7_FMT_CHARX: + /* The argument is treated as an integer, and presented as the character + * with that ASCII value + */ + pArg = NEXT_ARG; + if( pArg == 0 ){ + c = 0; + }else{ + c = ph7_value_to_int(pArg); + } + /* NUL byte is an acceptable value */ + zWorker[0] = (char)c; + length = (int)sizeof(char); + break; + case PH7_FMT_STRING: + /* the argument is treated as and presented as a string */ + pArg = NEXT_ARG; + if( pArg == 0 ){ + length = 0; + }else{ + zBuf = (char *)ph7_value_to_string(pArg,&length); + } + if( length < 1 ){ + zBuf = " "; + length = (int)sizeof(char); + } + if( precision>=0 && precisionPH7_FMT_BUFSIZ-40 ){ + precision = PH7_FMT_BUFSIZ-40; + } +#if 1 + /* For the format %#x, the value zero is printed "0" not "0x0". + ** I think this is stupid.*/ + if( iVal==0 ) flag_alternateform = 0; +#else + /* More sensible: turn off the prefix for octal (to prevent "00"), + ** but leave the prefix for hex.*/ + if( iVal==0 && pInfo->base==8 ) flag_alternateform = 0; +#endif + if( pInfo->flags & PH7_FMT_FLAG_SIGNED ){ + if( iVal<0 ){ + iVal = -iVal; + /* Ticket 1433-003 */ + if( iVal < 0 ){ + /* Overflow */ + iVal= 0x7FFFFFFFFFFFFFFF; + } + prefix = '-'; + }else if( flag_plussign ) prefix = '+'; + else if( flag_blanksign ) prefix = ' '; + else prefix = 0; + }else{ + if( iVal<0 ){ + iVal = -iVal; + /* Ticket 1433-003 */ + if( iVal < 0 ){ + /* Overflow */ + iVal= 0x7FFFFFFFFFFFFFFF; + } + } + prefix = 0; + } + if( flag_zeropad && precisioncharset; + base = pInfo->base; + do{ /* Convert to ascii */ + *(--zBuf) = cset[iVal%base]; + iVal = iVal/base; + }while( iVal>0 ); + } + length = &zWorker[PH7_FMT_BUFSIZ-1]-zBuf; + for(idx=precision-length; idx>0; idx--){ + *(--zBuf) = '0'; /* Zero pad */ + } + if( prefix ) *(--zBuf) = (char)prefix; /* Add sign */ + if( flag_alternateform && pInfo->prefix ){ /* Add "0" or "0x" */ + char *pre, x; + pre = pInfo->prefix; + if( *zBuf!=pre[0] ){ + for(pre=pInfo->prefix; (x=(*pre))!=0; pre++) *(--zBuf) = x; + } + } + length = &zWorker[PH7_FMT_BUFSIZ-1]-zBuf; + break; + case PH7_FMT_FLOAT: + case PH7_FMT_EXP: + case PH7_FMT_GENERIC:{ +#ifndef PH7_OMIT_FLOATING_POINT + long double realvalue; + int exp; /* exponent of real numbers */ + double rounder; /* Used for rounding floating point values */ + int flag_dp; /* True if decimal point should be shown */ + int flag_rtz; /* True if trailing zeros should be removed */ + int flag_exp; /* True to force display of the exponent */ + int nsd; /* Number of significant digits returned */ + pArg = NEXT_ARG; + if( pArg == 0 ){ + realvalue = 0; + }else{ + realvalue = ph7_value_to_double(pArg); + } + if( precision<0 ) precision = 6; /* Set default precision */ + if( precision>PH7_FMT_BUFSIZ-40) precision = PH7_FMT_BUFSIZ-40; + if( realvalue<0.0 ){ + realvalue = -realvalue; + prefix = '-'; + }else{ + if( flag_plussign ) prefix = '+'; + else if( flag_blanksign ) prefix = ' '; + else prefix = 0; + } + if( pInfo->type==PH7_FMT_GENERIC && precision>0 ) precision--; + rounder = 0.0; +#if 0 + /* Rounding works like BSD when the constant 0.4999 is used.Wierd! */ + for(idx=precision, rounder=0.4999; idx>0; idx--, rounder*=0.1); +#else + /* It makes more sense to use 0.5 */ + for(idx=precision, rounder=0.5; idx>0; idx--, rounder*=0.1); +#endif + if( pInfo->type==PH7_FMT_FLOAT ) realvalue += rounder; + /* Normalize realvalue to within 10.0 > realvalue >= 1.0 */ + exp = 0; + if( realvalue>0.0 ){ + while( realvalue>=1e8 && exp<=350 ){ realvalue *= 1e-8; exp+=8; } + while( realvalue>=10.0 && exp<=350 ){ realvalue *= 0.1; exp++; } + while( realvalue<1e-8 && exp>=-350 ){ realvalue *= 1e8; exp-=8; } + while( realvalue<1.0 && exp>=-350 ){ realvalue *= 10.0; exp--; } + if( exp>350 || exp<-350 ){ + zBuf = "NaN"; + length = 3; + break; + } + } + zBuf = zWorker; + /* + ** If the field type is etGENERIC, then convert to either etEXP + ** or etFLOAT, as appropriate. + */ + flag_exp = xtype==PH7_FMT_EXP; + if( xtype!=PH7_FMT_FLOAT ){ + realvalue += rounder; + if( realvalue>=10.0 ){ realvalue *= 0.1; exp++; } + } + if( xtype==PH7_FMT_GENERIC ){ + flag_rtz = !flag_alternateform; + if( exp<-4 || exp>precision ){ + xtype = PH7_FMT_EXP; + }else{ + precision = precision - exp; + xtype = PH7_FMT_FLOAT; + } + }else{ + flag_rtz = 0; + } + /* + ** The "exp+precision" test causes output to be of type etEXP if + ** the precision is too large to fit in buf[]. + */ + nsd = 0; + if( xtype==PH7_FMT_FLOAT && exp+precision0 || flag_alternateform); + if( prefix ) *(zBuf++) = (char)prefix; /* Sign */ + if( exp<0 ) *(zBuf++) = '0'; /* Digits before "." */ + else for(; exp>=0; exp--) *(zBuf++) = (char)vxGetdigit(&realvalue,&nsd); + if( flag_dp ) *(zBuf++) = '.'; /* The decimal point */ + for(exp++; exp<0 && precision>0; precision--, exp++){ + *(zBuf++) = '0'; + } + while( (precision--)>0 ) *(zBuf++) = (char)vxGetdigit(&realvalue,&nsd); + *(zBuf--) = 0; /* Null terminate */ + if( flag_rtz && flag_dp ){ /* Remove trailing zeros and "." */ + while( zBuf>=zWorker && *zBuf=='0' ) *(zBuf--) = 0; + if( zBuf>=zWorker && *zBuf=='.' ) *(zBuf--) = 0; + } + zBuf++; /* point to next free slot */ + }else{ /* etEXP or etGENERIC */ + flag_dp = (precision>0 || flag_alternateform); + if( prefix ) *(zBuf++) = (char)prefix; /* Sign */ + *(zBuf++) = (char)vxGetdigit(&realvalue,&nsd); /* First digit */ + if( flag_dp ) *(zBuf++) = '.'; /* Decimal point */ + while( (precision--)>0 ) *(zBuf++) = (char)vxGetdigit(&realvalue,&nsd); + zBuf--; /* point to last digit */ + if( flag_rtz && flag_dp ){ /* Remove tail zeros */ + while( zBuf>=zWorker && *zBuf=='0' ) *(zBuf--) = 0; + if( zBuf>=zWorker && *zBuf=='.' ) *(zBuf--) = 0; + } + zBuf++; /* point to next free slot */ + if( exp || flag_exp ){ + *(zBuf++) = pInfo->charset[0]; + if( exp<0 ){ *(zBuf++) = '-'; exp = -exp; } /* sign of exp */ + else { *(zBuf++) = '+'; } + if( exp>=100 ){ + *(zBuf++) = (char)((exp/100)+'0'); /* 100's digit */ + exp %= 100; + } + *(zBuf++) = (char)(exp/10+'0'); /* 10's digit */ + *(zBuf++) = (char)(exp%10+'0'); /* 1's digit */ + } + } + /* The converted number is in buf[] and zero terminated.Output it. + ** Note that the number is in the usual order, not reversed as with + ** integer conversions.*/ + length = (int)(zBuf-zWorker); + zBuf = zWorker; + /* Special case: Add leading zeros if the flag_zeropad flag is + ** set and we are not left justified */ + if( flag_zeropad && !flag_leftjustify && length < width){ + int i; + int nPad = width - length; + for(i=width; i>=nPad; i--){ + zBuf[i] = zBuf[i-nPad]; + } + i = prefix!=0; + while( nPad-- ) zBuf[i++] = '0'; + length = width; + } +#else + zBuf = " "; + length = (int)sizeof(char); +#endif /* PH7_OMIT_FLOATING_POINT */ + break; + } + default: + /* Invalid format specifer */ + zWorker[0] = '?'; + length = (int)sizeof(char); + break; + } + /* + ** The text of the conversion is pointed to by "zBuf" and is + ** "length" characters long.The field width is "width".Do + ** the output. + */ + if( !flag_leftjustify ){ + register int nspace; + nspace = width-length; + if( nspace>0 ){ + while( nspace>=etSPACESIZE ){ + rc = xConsumer(pCtx,spaces,etSPACESIZE,pUserData); + if( rc != SXRET_OK ){ + return SXERR_ABORT; /* Consumer routine request an operation abort */ + } + nspace -= etSPACESIZE; + } + if( nspace>0 ){ + rc = xConsumer(pCtx,spaces,(unsigned int)nspace,pUserData); + if( rc != SXRET_OK ){ + return SXERR_ABORT; /* Consumer routine request an operation abort */ + } + } + } + } + if( length>0 ){ + rc = xConsumer(pCtx,zBuf,(unsigned int)length,pUserData); + if( rc != SXRET_OK ){ + return SXERR_ABORT; /* Consumer routine request an operation abort */ + } + } + if( flag_leftjustify ){ + register int nspace; + nspace = width-length; + if( nspace>0 ){ + while( nspace>=etSPACESIZE ){ + rc = xConsumer(pCtx,spaces,etSPACESIZE,pUserData); + if( rc != SXRET_OK ){ + return SXERR_ABORT; /* Consumer routine request an operation abort */ + } + nspace -= etSPACESIZE; + } + if( nspace>0 ){ + rc = xConsumer(pCtx,spaces,(unsigned int)nspace,pUserData); + if( rc != SXRET_OK ){ + return SXERR_ABORT; /* Consumer routine request an operation abort */ + } + } + } + } + }/* for(;;) */ + return SXRET_OK; +} +/* + * Callback [i.e: Formatted input consumer] of the sprintf function. + */ +static int sprintfConsumer(ph7_context *pCtx,const char *zInput,int nLen,void *pUserData) +{ + /* Consume directly */ + ph7_result_string(pCtx,zInput,nLen); + SXUNUSED(pUserData); /* cc warning */ + return PH7_OK; +} +/* + * string sprintf(string $format[,mixed $args [, mixed $... ]]) + * Return a formatted string. + * Parameters + * $format + * The format string (see block comment above) + * Return + * A string produced according to the formatting string format. + */ +static int PH7_builtin_sprintf(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + const char *zFormat; + int nLen; + if( nArg < 1 || !ph7_value_is_string(apArg[0]) ){ + /* Missing/Invalid arguments,return the empty string */ + ph7_result_string(pCtx,"",0); + return PH7_OK; + } + /* Extract the string format */ + zFormat = ph7_value_to_string(apArg[0],&nLen); + if( nLen < 1 ){ + /* Empty string */ + ph7_result_string(pCtx,"",0); + return PH7_OK; + } + /* Format the string */ + PH7_InputFormat(sprintfConsumer,pCtx,zFormat,nLen,nArg,apArg,0,FALSE); + return PH7_OK; +} +/* + * Callback [i.e: Formatted input consumer] of the printf function. + */ +static int printfConsumer(ph7_context *pCtx,const char *zInput,int nLen,void *pUserData) +{ + ph7_int64 *pCounter = (ph7_int64 *)pUserData; + /* Call the VM output consumer directly */ + ph7_context_output(pCtx,zInput,nLen); + /* Increment counter */ + *pCounter += nLen; + return PH7_OK; +} +/* + * int64 printf(string $format[,mixed $args[,mixed $... ]]) + * Output a formatted string. + * Parameters + * $format + * See sprintf() for a description of format. + * Return + * The length of the outputted string. + */ +static int PH7_builtin_printf(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + ph7_int64 nCounter = 0; + const char *zFormat; + int nLen; + if( nArg < 1 || !ph7_value_is_string(apArg[0]) ){ + /* Missing/Invalid arguments,return 0 */ + ph7_result_int(pCtx,0); + return PH7_OK; + } + /* Extract the string format */ + zFormat = ph7_value_to_string(apArg[0],&nLen); + if( nLen < 1 ){ + /* Empty string */ + ph7_result_int(pCtx,0); + return PH7_OK; + } + /* Format the string */ + PH7_InputFormat(printfConsumer,pCtx,zFormat,nLen,nArg,apArg,(void *)&nCounter,FALSE); + /* Return the length of the outputted string */ + ph7_result_int64(pCtx,nCounter); + return PH7_OK; +} +/* + * int vprintf(string $format,array $args) + * Output a formatted string. + * Parameters + * $format + * See sprintf() for a description of format. + * Return + * The length of the outputted string. + */ +static int PH7_builtin_vprintf(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + ph7_int64 nCounter = 0; + const char *zFormat; + ph7_hashmap *pMap; + SySet sArg; + int nLen,n; + if( nArg < 2 || !ph7_value_is_string(apArg[0]) || !ph7_value_is_array(apArg[1]) ){ + /* Missing/Invalid arguments,return 0 */ + ph7_result_int(pCtx,0); + return PH7_OK; + } + /* Extract the string format */ + zFormat = ph7_value_to_string(apArg[0],&nLen); + if( nLen < 1 ){ + /* Empty string */ + ph7_result_int(pCtx,0); + return PH7_OK; + } + /* Point to the hashmap */ + pMap = (ph7_hashmap *)apArg[1]->x.pOther; + /* Extract arguments from the hashmap */ + n = PH7_HashmapValuesToSet(pMap,&sArg); + /* Format the string */ + PH7_InputFormat(printfConsumer,pCtx,zFormat,nLen,n,(ph7_value **)SySetBasePtr(&sArg),(void *)&nCounter,TRUE); + /* Return the length of the outputted string */ + ph7_result_int64(pCtx,nCounter); + /* Release the container */ + SySetRelease(&sArg); + return PH7_OK; +} +/* + * int vsprintf(string $format,array $args) + * Output a formatted string. + * Parameters + * $format + * See sprintf() for a description of format. + * Return + * A string produced according to the formatting string format. + */ +static int PH7_builtin_vsprintf(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + const char *zFormat; + ph7_hashmap *pMap; + SySet sArg; + int nLen,n; + if( nArg < 2 || !ph7_value_is_string(apArg[0]) || !ph7_value_is_array(apArg[1]) ){ + /* Missing/Invalid arguments,return the empty string */ + ph7_result_string(pCtx,"",0); + return PH7_OK; + } + /* Extract the string format */ + zFormat = ph7_value_to_string(apArg[0],&nLen); + if( nLen < 1 ){ + /* Empty string */ + ph7_result_string(pCtx,"",0); + return PH7_OK; + } + /* Point to hashmap */ + pMap = (ph7_hashmap *)apArg[1]->x.pOther; + /* Extract arguments from the hashmap */ + n = PH7_HashmapValuesToSet(pMap,&sArg); + /* Format the string */ + PH7_InputFormat(sprintfConsumer,pCtx,zFormat,nLen,n,(ph7_value **)SySetBasePtr(&sArg),0,TRUE); + /* Release the container */ + SySetRelease(&sArg); + return PH7_OK; +} +/* + * Symisc eXtension. + * string size_format(int64 $size) + * Return a smart string represenation of the given size [i.e: 64-bit integer] + * Example: + * echo size_format(1*1024*1024*1024);// 1GB + * echo size_format(512*1024*1024); // 512 MB + * echo size_format(file_size(/path/to/my/file_8192)); //8KB + * Parameter + * $size + * Entity size in bytes. + * Return + * Formatted string representation of the given size. + */ +static int PH7_builtin_size_format(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + /*Kilo*/ /*Mega*/ /*Giga*/ /*Tera*/ /*Peta*/ /*Exa*/ /*Zeta*/ + static const char zUnit[] = {"KMGTPEZ"}; + sxi32 nRest,i_32; + ph7_int64 iSize; + int c = -1; /* index in zUnit[] */ + + if( nArg < 1 ){ + /* Missing argument,return the empty string */ + ph7_result_string(pCtx,"",0); + return PH7_OK; + } + /* Extract the given size */ + iSize = ph7_value_to_int64(apArg[0]); + if( iSize < 100 /* Bytes */ ){ + /* Don't bother formatting,return immediately */ + ph7_result_string(pCtx,"0.1 KB",(int)sizeof("0.1 KB")-1); + return PH7_OK; + } + for(;;){ + nRest = (sxi32)(iSize & 0x3FF); + iSize >>= 10; + c++; + if( (iSize & (~0 ^ 1023)) == 0 ){ + break; + } + } + nRest /= 100; + if( nRest > 9 ){ + nRest = 9; + } + if( iSize > 999 ){ + c++; + nRest = 9; + iSize = 0; + } + i_32 = (sxi32)iSize; + /* Format */ + ph7_result_string_format(pCtx,"%d.%d %cB",i_32,nRest,zUnit[c]); + return PH7_OK; +} +#if !defined(PH7_DISABLE_HASH_FUNC) +/* + * string md5(string $str[,bool $raw_output = false]) + * Calculate the md5 hash of a string. + * Parameter + * $str + * Input string + * $raw_output + * If the optional raw_output is set to TRUE, then the md5 digest + * is instead returned in raw binary format with a length of 16. + * Return + * MD5 Hash as a 32-character hexadecimal string. + */ +static int PH7_builtin_md5(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + unsigned char zDigest[16]; + int raw_output = FALSE; + const void *pIn; + int nLen; + if( nArg < 1 ){ + /* Missing arguments,return the empty string */ + ph7_result_string(pCtx,"",0); + return PH7_OK; + } + /* Extract the input string */ + pIn = (const void *)ph7_value_to_string(apArg[0],&nLen); + if( nLen < 1 ){ + /* Empty string */ + ph7_result_string(pCtx,"",0); + return PH7_OK; + } + if( nArg > 1 && ph7_value_is_bool(apArg[1])){ + raw_output = ph7_value_to_bool(apArg[1]); + } + /* Compute the MD5 digest */ + SyMD5Compute(pIn,(sxu32)nLen,zDigest); + if( raw_output ){ + /* Output raw digest */ + ph7_result_string(pCtx,(const char *)zDigest,(int)sizeof(zDigest)); + }else{ + /* Perform a binary to hex conversion */ + SyBinToHexConsumer((const void *)zDigest,sizeof(zDigest),HashConsumer,pCtx); + } + return PH7_OK; +} +/* + * string sha1(string $str[,bool $raw_output = false]) + * Calculate the sha1 hash of a string. + * Parameter + * $str + * Input string + * $raw_output + * If the optional raw_output is set to TRUE, then the md5 digest + * is instead returned in raw binary format with a length of 16. + * Return + * SHA1 Hash as a 40-character hexadecimal string. + */ +static int PH7_builtin_sha1(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + unsigned char zDigest[20]; + int raw_output = FALSE; + const void *pIn; + int nLen; + if( nArg < 1 ){ + /* Missing arguments,return the empty string */ + ph7_result_string(pCtx,"",0); + return PH7_OK; + } + /* Extract the input string */ + pIn = (const void *)ph7_value_to_string(apArg[0],&nLen); + if( nLen < 1 ){ + /* Empty string */ + ph7_result_string(pCtx,"",0); + return PH7_OK; + } + if( nArg > 1 && ph7_value_is_bool(apArg[1])){ + raw_output = ph7_value_to_bool(apArg[1]); + } + /* Compute the SHA1 digest */ + SySha1Compute(pIn,(sxu32)nLen,zDigest); + if( raw_output ){ + /* Output raw digest */ + ph7_result_string(pCtx,(const char *)zDigest,(int)sizeof(zDigest)); + }else{ + /* Perform a binary to hex conversion */ + SyBinToHexConsumer((const void *)zDigest,sizeof(zDigest),HashConsumer,pCtx); + } + return PH7_OK; +} +/* + * int64 crc32(string $str) + * Calculates the crc32 polynomial of a strin. + * Parameter + * $str + * Input string + * Return + * CRC32 checksum of the given input (64-bit integer). + */ +static int PH7_builtin_crc32(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + const void *pIn; + sxu32 nCRC; + int nLen; + if( nArg < 1 ){ + /* Missing arguments,return 0 */ + ph7_result_int(pCtx,0); + return PH7_OK; + } + /* Extract the input string */ + pIn = (const void *)ph7_value_to_string(apArg[0],&nLen); + if( nLen < 1 ){ + /* Empty string */ + ph7_result_int(pCtx,0); + return PH7_OK; + } + /* Calculate the sum */ + nCRC = SyCrc32(pIn,(sxu32)nLen); + /* Return the CRC32 as 64-bit integer */ + ph7_result_int64(pCtx,(ph7_int64)nCRC^ 0xFFFFFFFF); + return PH7_OK; +} +#endif /* PH7_DISABLE_HASH_FUNC */ +/* + * Parse a CSV string and invoke the supplied callback for each processed xhunk. + */ +PH7_PRIVATE sxi32 PH7_ProcessCsv( + const char *zInput, /* Raw input */ + int nByte, /* Input length */ + int delim, /* Delimiter */ + int encl, /* Enclosure */ + int escape, /* Escape character */ + sxi32 (*xConsumer)(const char *,int,void *), /* User callback */ + void *pUserData /* Last argument to xConsumer() */ + ) +{ + const char *zEnd = &zInput[nByte]; + const char *zIn = zInput; + const char *zPtr; + int isEnc; + /* Start processing */ + for(;;){ + if( zIn >= zEnd ){ + /* No more input to process */ + break; + } + isEnc = 0; + zPtr = zIn; + /* Find the first delimiter */ + while( zIn < zEnd ){ + if( zIn[0] == delim && !isEnc){ + /* Delimiter found,break imediately */ + break; + }else if( zIn[0] == encl ){ + /* Inside enclosure? */ + isEnc = !isEnc; + }else if( zIn[0] == escape ){ + /* Escape sequence */ + zIn++; + } + /* Advance the cursor */ + zIn++; + } + if( zIn > zPtr ){ + int nByte = (int)(zIn-zPtr); + sxi32 rc; + /* Invoke the supllied callback */ + if( zPtr[0] == encl ){ + zPtr++; + nByte-=2; + } + if( nByte > 0 ){ + rc = xConsumer(zPtr,nByte,pUserData); + if( rc == SXERR_ABORT ){ + /* User callback request an operation abort */ + break; + } + } + } + /* Ignore trailing delimiter */ + while( zIn < zEnd && zIn[0] == delim ){ + zIn++; + } + } + return SXRET_OK; +} +/* + * Default consumer callback for the CSV parsing routine defined above. + * All the processed input is insereted into an array passed as the last + * argument to this callback. + */ +PH7_PRIVATE sxi32 PH7_CsvConsumer(const char *zToken,int nTokenLen,void *pUserData) +{ + ph7_value *pArray = (ph7_value *)pUserData; + ph7_value sEntry; + SyString sToken; + /* Insert the token in the given array */ + SyStringInitFromBuf(&sToken,zToken,nTokenLen); + /* Remove trailing and leading white spcaces and null bytes */ + SyStringFullTrimSafe(&sToken); + if( sToken.nByte < 1){ + return SXRET_OK; + } + PH7_MemObjInitFromString(pArray->pVm,&sEntry,&sToken); + ph7_array_add_elem(pArray,0,&sEntry); + PH7_MemObjRelease(&sEntry); + return SXRET_OK; +} +/* + * array str_getcsv(string $input[,string $delimiter = ','[,string $enclosure = '"' [,string $escape='\\']]]) + * Parse a CSV string into an array. + * Parameters + * $input + * The string to parse. + * $delimiter + * Set the field delimiter (one character only). + * $enclosure + * Set the field enclosure character (one character only). + * $escape + * Set the escape character (one character only). Defaults as a backslash (\) + * Return + * An indexed array containing the CSV fields or NULL on failure. + */ +static int PH7_builtin_str_getcsv(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + const char *zInput,*zPtr; + ph7_value *pArray; + int delim = ','; /* Delimiter */ + int encl = '"' ; /* Enclosure */ + int escape = '\\'; /* Escape character */ + int nLen; + if( nArg < 1 || !ph7_value_is_string(apArg[0]) ){ + /* Missing/Invalid arguments,return NULL */ + ph7_result_null(pCtx); + return PH7_OK; + } + /* Extract the raw input */ + zInput = ph7_value_to_string(apArg[0],&nLen); + if( nArg > 1 ){ + int i; + if( ph7_value_is_string(apArg[1]) ){ + /* Extract the delimiter */ + zPtr = ph7_value_to_string(apArg[1],&i); + if( i > 0 ){ + delim = zPtr[0]; + } + } + if( nArg > 2 ){ + if( ph7_value_is_string(apArg[2]) ){ + /* Extract the enclosure */ + zPtr = ph7_value_to_string(apArg[2],&i); + if( i > 0 ){ + encl = zPtr[0]; + } + } + if( nArg > 3 ){ + if( ph7_value_is_string(apArg[3]) ){ + /* Extract the escape character */ + zPtr = ph7_value_to_string(apArg[3],&i); + if( i > 0 ){ + escape = zPtr[0]; + } + } + } + } + } + /* Create our array */ + pArray = ph7_context_new_array(pCtx); + if( pArray == 0 ){ + ph7_context_throw_error(pCtx,PH7_CTX_ERR,"PH7 is running out of memory"); + ph7_result_null(pCtx); + return PH7_OK; + } + /* Parse the raw input */ + PH7_ProcessCsv(zInput,nLen,delim,encl,escape,PH7_CsvConsumer,pArray); + /* Return the freshly created array */ + ph7_result_value(pCtx,pArray); + return PH7_OK; +} +/* + * Extract a tag name from a raw HTML input and insert it in the given + * container. + * Refer to [strip_tags()]. + */ +static sxi32 AddTag(SySet *pSet,const char *zTag,int nByte) +{ + const char *zEnd = &zTag[nByte]; + const char *zPtr; + SyString sEntry; + /* Strip tags */ + for(;;){ + while( zTag < zEnd && (zTag[0] == '<' || zTag[0] == '/' || zTag[0] == '?' + || zTag[0] == '!' || zTag[0] == '-' || ((unsigned char)zTag[0] < 0xc0 && SyisSpace(zTag[0]))) ){ + zTag++; + } + if( zTag >= zEnd ){ + break; + } + zPtr = zTag; + /* Delimit the tag */ + while(zTag < zEnd ){ + if( (unsigned char)zTag[0] >= 0xc0 ){ + /* UTF-8 stream */ + zTag++; + SX_JMP_UTF8(zTag,zEnd); + }else if( !SyisAlphaNum(zTag[0]) ){ + break; + }else{ + zTag++; + } + } + if( zTag > zPtr ){ + /* Perform the insertion */ + SyStringInitFromBuf(&sEntry,zPtr,(int)(zTag-zPtr)); + SyStringFullTrim(&sEntry); + SySetPut(pSet,(const void *)&sEntry); + } + /* Jump the trailing '>' */ + zTag++; + } + return SXRET_OK; +} +/* + * Check if the given HTML tag name is present in the given container. + * Return SXRET_OK if present.SXERR_NOTFOUND otherwise. + * Refer to [strip_tags()]. + */ +static sxi32 FindTag(SySet *pSet,const char *zTag,int nByte) +{ + if( SySetUsed(pSet) > 0 ){ + const char *zCur,*zEnd = &zTag[nByte]; + SyString sTag; + while( zTag < zEnd && (zTag[0] == '<' || zTag[0] == '/' || zTag[0] == '?' || + ((unsigned char)zTag[0] < 0xc0 && SyisSpace(zTag[0]))) ){ + zTag++; + } + /* Delimit the tag */ + zCur = zTag; + while(zTag < zEnd ){ + if( (unsigned char)zTag[0] >= 0xc0 ){ + /* UTF-8 stream */ + zTag++; + SX_JMP_UTF8(zTag,zEnd); + }else if( !SyisAlphaNum(zTag[0]) ){ + break; + }else{ + zTag++; + } + } + SyStringInitFromBuf(&sTag,zCur,zTag-zCur); + /* Trim leading white spaces and null bytes */ + SyStringLeftTrimSafe(&sTag); + if( sTag.nByte > 0 ){ + SyString *aEntry,*pEntry; + sxi32 rc; + sxu32 n; + /* Perform the lookup */ + aEntry = (SyString *)SySetBasePtr(pSet); + for( n = 0 ; n < SySetUsed(pSet) ; ++n ){ + pEntry = &aEntry[n]; + /* Do the comparison */ + rc = SyStringCmp(pEntry,&sTag,SyStrnicmp); + if( !rc ){ + return SXRET_OK; + } + } + } + } + /* No such tag */ + return SXERR_NOTFOUND; +} +/* + * This function tries to return a string [i.e: in the call context result buffer] + * with all NUL bytes,HTML and PHP tags stripped from a given string. + * Refer to [strip_tags()]. + */ +PH7_PRIVATE sxi32 PH7_StripTagsFromString(ph7_context *pCtx,const char *zIn,int nByte,const char *zTaglist,int nTaglen) +{ + const char *zEnd = &zIn[nByte]; + const char *zPtr,*zTag; + SySet sSet; + /* initialize the set of allowed tags */ + SySetInit(&sSet,&pCtx->pVm->sAllocator,sizeof(SyString)); + if( nTaglen > 0 ){ + /* Set of allowed tags */ + AddTag(&sSet,zTaglist,nTaglen); + } + /* Set the empty string */ + ph7_result_string(pCtx,"",0); + /* Start processing */ + for(;;){ + if(zIn >= zEnd){ + /* No more input to process */ + break; + } + zPtr = zIn; + /* Find a tag */ + while( zIn < zEnd && zIn[0] != '<' && zIn[0] != 0 /* NUL byte */ ){ + zIn++; + } + if( zIn > zPtr ){ + /* Consume raw input */ + ph7_result_string(pCtx,zPtr,(int)(zIn-zPtr)); + } + /* Ignore trailing null bytes */ + while( zIn < zEnd && zIn[0] == 0 ){ + zIn++; + } + if(zIn >= zEnd){ + /* No more input to process */ + break; + } + if( zIn[0] == '<' ){ + sxi32 rc; + zTag = zIn++; + /* Delimit the tag */ + while( zIn < zEnd && zIn[0] != '>' ){ + zIn++; + } + if( zIn < zEnd ){ + zIn++; /* Ignore the trailing closing tag */ + } + /* Query the set */ + rc = FindTag(&sSet,zTag,(int)(zIn-zTag)); + if( rc == SXRET_OK ){ + /* Keep the tag */ + ph7_result_string(pCtx,zTag,(int)(zIn-zTag)); + } + } + } + /* Cleanup */ + SySetRelease(&sSet); + return SXRET_OK; +} +/* + * string strip_tags(string $str[,string $allowable_tags]) + * Strip HTML and PHP tags from a string. + * Parameters + * $str + * The input string. + * $allowable_tags + * You can use the optional second parameter to specify tags which should not be stripped. + * Return + * Returns the stripped string. + */ +static int PH7_builtin_strip_tags(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + const char *zTaglist = 0; + const char *zString; + int nTaglen = 0; + int nLen; + if( nArg < 1 || !ph7_value_is_string(apArg[0]) ){ + /* Missing/Invalid arguments,return the empty string */ + ph7_result_string(pCtx,"",0); + return PH7_OK; + } + /* Point to the raw string */ + zString = ph7_value_to_string(apArg[0],&nLen); + if( nArg > 1 && ph7_value_is_string(apArg[1]) ){ + /* Allowed tag */ + zTaglist = ph7_value_to_string(apArg[1],&nTaglen); + } + /* Process input */ + PH7_StripTagsFromString(pCtx,zString,nLen,zTaglist,nTaglen); + return PH7_OK; +} +/* + * string str_shuffle(string $str) + * Randomly shuffles a string. + * Parameters + * $str + * The input string. + * Return + * Returns the shuffled string. + */ +static int PH7_builtin_str_shuffle(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + const char *zString; + int nLen,i,c; + sxu32 iR; + if( nArg < 1 ){ + /* Missing arguments,return the empty string */ + ph7_result_string(pCtx,"",0); + return PH7_OK; + } + /* Extract the target string */ + zString = ph7_value_to_string(apArg[0],&nLen); + if( nLen < 1 ){ + /* Nothing to shuffle */ + ph7_result_string(pCtx,"",0); + return PH7_OK; + } + /* Shuffle the string */ + for( i = 0 ; i < nLen ; ++i ){ + /* Generate a random number first */ + iR = ph7_context_random_num(pCtx); + /* Extract a random offset */ + c = zString[iR % nLen]; + /* Append it */ + ph7_result_string(pCtx,(const char *)&c,(int)sizeof(char)); + } + return PH7_OK; +} +/* + * array str_split(string $string[,int $split_length = 1 ]) + * Convert a string to an array. + * Parameters + * $str + * The input string. + * $split_length + * Maximum length of the chunk. + * Return + * If the optional split_length parameter is specified, the returned array + * will be broken down into chunks with each being split_length in length, otherwise + * each chunk will be one character in length. FALSE is returned if split_length is less than 1. + * If the split_length length exceeds the length of string, the entire string is returned + * as the first (and only) array element. + */ +static int PH7_builtin_str_split(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + const char *zString,*zEnd; + ph7_value *pArray,*pValue; + int split_len; + int nLen; + if( nArg < 1 ){ + /* Missing arguments,return FALSE */ + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Point to the target string */ + zString = ph7_value_to_string(apArg[0],&nLen); + if( nLen < 1 ){ + /* Nothing to process,return FALSE */ + ph7_result_bool(pCtx,0); + return PH7_OK; + } + split_len = (int)sizeof(char); + if( nArg > 1 ){ + /* Split length */ + split_len = ph7_value_to_int(apArg[1]); + if( split_len < 1 ){ + /* Invalid length,return FALSE */ + ph7_result_bool(pCtx,0); + return PH7_OK; + } + if( split_len > nLen ){ + split_len = nLen; + } + } + /* Create the array and the scalar value */ + pArray = ph7_context_new_array(pCtx); + /*Chunk value */ + pValue = ph7_context_new_scalar(pCtx); + if( pValue == 0 || pArray == 0 ){ + /* Return FALSE */ + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Point to the end of the string */ + zEnd = &zString[nLen]; + /* Perform the requested operation */ + for(;;){ + int nMax; + if( zString >= zEnd ){ + /* No more input to process */ + break; + } + nMax = (int)(zEnd-zString); + if( nMax < split_len ){ + split_len = nMax; + } + /* Copy the current chunk */ + ph7_value_string(pValue,zString,split_len); + /* Insert it */ + ph7_array_add_elem(pArray,0,pValue); /* Will make it's own copy */ + /* reset the string cursor */ + ph7_value_reset_string_cursor(pValue); + /* Update position */ + zString += split_len; + } + /* + * Return the array. + * Don't worry about freeing memory, everything will be automatically released + * upon we return from this function. + */ + ph7_result_value(pCtx,pArray); + return PH7_OK; +} +/* + * Tokenize a raw string and extract the first non-space token. + * Refer to [strspn()]. + */ +static sxi32 ExtractNonSpaceToken(const char **pzIn,const char *zEnd,SyString *pOut) +{ + const char *zIn = *pzIn; + const char *zPtr; + /* Ignore leading white spaces */ + while( zIn < zEnd && (unsigned char)zIn[0] < 0xc0 && SyisSpace(zIn[0]) ){ + zIn++; + } + if( zIn >= zEnd ){ + /* End of input */ + return SXERR_EOF; + } + zPtr = zIn; + /* Extract the token */ + while( zIn < zEnd && (unsigned char)zIn[0] < 0xc0 && !SyisSpace(zIn[0]) ){ + zIn++; + } + SyStringInitFromBuf(pOut,zPtr,zIn-zPtr); + /* Synchronize pointers */ + *pzIn = zIn; + /* Return to the caller */ + return SXRET_OK; +} +/* + * Check if the given string contains only characters from the given mask. + * return the longest match. + * Refer to [strspn()]. + */ +static int LongestStringMask(const char *zString,int nLen,const char *zMask,int nMaskLen) +{ + const char *zEnd = &zString[nLen]; + const char *zIn = zString; + int i,c; + for(;;){ + if( zString >= zEnd ){ + break; + } + /* Extract current character */ + c = zString[0]; + /* Perform the lookup */ + for( i = 0 ; i < nMaskLen ; i++ ){ + if( c == zMask[i] ){ + /* Character found */ + break; + } + } + if( i >= nMaskLen ){ + /* Character not in the current mask,break immediately */ + break; + } + /* Advance cursor */ + zString++; + } + /* Longest match */ + return (int)(zString-zIn); +} +/* + * Do the reverse operation of the previous function [i.e: LongestStringMask()]. + * Refer to [strcspn()]. + */ +static int LongestStringMask2(const char *zString,int nLen,const char *zMask,int nMaskLen) +{ + const char *zEnd = &zString[nLen]; + const char *zIn = zString; + int i,c; + for(;;){ + if( zString >= zEnd ){ + break; + } + /* Extract current character */ + c = zString[0]; + /* Perform the lookup */ + for( i = 0 ; i < nMaskLen ; i++ ){ + if( c == zMask[i] ){ + break; + } + } + if( i < nMaskLen ){ + /* Character in the current mask,break immediately */ + break; + } + /* Advance cursor */ + zString++; + } + /* Longest match */ + return (int)(zString-zIn); +} +/* + * int strspn(string $str,string $mask[,int $start[,int $length]]) + * Finds the length of the initial segment of a string consisting entirely + * of characters contained within a given mask. + * Parameters + * $str + * The input string. + * $mask + * The list of allowable characters. + * $start + * The position in subject to start searching. + * If start is given and is non-negative, then strspn() will begin examining + * subject at the start'th position. For instance, in the string 'abcdef', the character + * at position 0 is 'a', the character at position 2 is 'c', and so forth. + * If start is given and is negative, then strspn() will begin examining subject at the + * start'th position from the end of subject. + * $length + * The length of the segment from subject to examine. + * If length is given and is non-negative, then subject will be examined for length + * characters after the starting position. + * If lengthis given and is negative, then subject will be examined from the starting + * position up to length characters from the end of subject. + * Return + * Returns the length of the initial segment of subject which consists entirely of characters + * in mask. + */ +static int PH7_builtin_strspn(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + const char *zString,*zMask,*zEnd; + int iMasklen,iLen; + SyString sToken; + int iCount = 0; + int rc; + if( nArg < 2 ){ + /* Missing agruments,return zero */ + ph7_result_int(pCtx,0); + return PH7_OK; + } + /* Extract the target string */ + zString = ph7_value_to_string(apArg[0],&iLen); + /* Extract the mask */ + zMask = ph7_value_to_string(apArg[1],&iMasklen); + if( iLen < 1 || iMasklen < 1 ){ + /* Nothing to process,return zero */ + ph7_result_int(pCtx,0); + return PH7_OK; + } + if( nArg > 2 ){ + int nOfft; + /* Extract the offset */ + nOfft = ph7_value_to_int(apArg[2]); + if( nOfft < 0 ){ + const char *zBase = &zString[iLen + nOfft]; + if( zBase > zString ){ + iLen = (int)(&zString[iLen]-zBase); + zString = zBase; + }else{ + /* Invalid offset */ + ph7_result_int(pCtx,0); + return PH7_OK; + } + }else{ + if( nOfft >= iLen ){ + /* Invalid offset */ + ph7_result_int(pCtx,0); + return PH7_OK; + }else{ + /* Update offset */ + zString += nOfft; + iLen -= nOfft; + } + } + if( nArg > 3 ){ + int iUserlen; + /* Extract the desired length */ + iUserlen = ph7_value_to_int(apArg[3]); + if( iUserlen > 0 && iUserlen < iLen ){ + iLen = iUserlen; + } + } + } + /* Point to the end of the string */ + zEnd = &zString[iLen]; + /* Extract the first non-space token */ + rc = ExtractNonSpaceToken(&zString,zEnd,&sToken); + if( rc == SXRET_OK && sToken.nByte > 0 ){ + /* Compare against the current mask */ + iCount = LongestStringMask(sToken.zString,(int)sToken.nByte,zMask,iMasklen); + } + /* Longest match */ + ph7_result_int(pCtx,iCount); + return PH7_OK; +} +/* + * int strcspn(string $str,string $mask[,int $start[,int $length]]) + * Find length of initial segment not matching mask. + * Parameters + * $str + * The input string. + * $mask + * The list of not allowed characters. + * $start + * The position in subject to start searching. + * If start is given and is non-negative, then strspn() will begin examining + * subject at the start'th position. For instance, in the string 'abcdef', the character + * at position 0 is 'a', the character at position 2 is 'c', and so forth. + * If start is given and is negative, then strspn() will begin examining subject at the + * start'th position from the end of subject. + * $length + * The length of the segment from subject to examine. + * If length is given and is non-negative, then subject will be examined for length + * characters after the starting position. + * If lengthis given and is negative, then subject will be examined from the starting + * position up to length characters from the end of subject. + * Return + * Returns the length of the segment as an integer. + */ +static int PH7_builtin_strcspn(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + const char *zString,*zMask,*zEnd; + int iMasklen,iLen; + SyString sToken; + int iCount = 0; + int rc; + if( nArg < 2 ){ + /* Missing agruments,return zero */ + ph7_result_int(pCtx,0); + return PH7_OK; + } + /* Extract the target string */ + zString = ph7_value_to_string(apArg[0],&iLen); + /* Extract the mask */ + zMask = ph7_value_to_string(apArg[1],&iMasklen); + if( iLen < 1 ){ + /* Nothing to process,return zero */ + ph7_result_int(pCtx,0); + return PH7_OK; + } + if( iMasklen < 1 ){ + /* No given mask,return the string length */ + ph7_result_int(pCtx,iLen); + return PH7_OK; + } + if( nArg > 2 ){ + int nOfft; + /* Extract the offset */ + nOfft = ph7_value_to_int(apArg[2]); + if( nOfft < 0 ){ + const char *zBase = &zString[iLen + nOfft]; + if( zBase > zString ){ + iLen = (int)(&zString[iLen]-zBase); + zString = zBase; + }else{ + /* Invalid offset */ + ph7_result_int(pCtx,0); + return PH7_OK; + } + }else{ + if( nOfft >= iLen ){ + /* Invalid offset */ + ph7_result_int(pCtx,0); + return PH7_OK; + }else{ + /* Update offset */ + zString += nOfft; + iLen -= nOfft; + } + } + if( nArg > 3 ){ + int iUserlen; + /* Extract the desired length */ + iUserlen = ph7_value_to_int(apArg[3]); + if( iUserlen > 0 && iUserlen < iLen ){ + iLen = iUserlen; + } + } + } + /* Point to the end of the string */ + zEnd = &zString[iLen]; + /* Extract the first non-space token */ + rc = ExtractNonSpaceToken(&zString,zEnd,&sToken); + if( rc == SXRET_OK && sToken.nByte > 0 ){ + /* Compare against the current mask */ + iCount = LongestStringMask2(sToken.zString,(int)sToken.nByte,zMask,iMasklen); + } + /* Longest match */ + ph7_result_int(pCtx,iCount); + return PH7_OK; +} +/* + * string strpbrk(string $haystack,string $char_list) + * Search a string for any of a set of characters. + * Parameters + * $haystack + * The string where char_list is looked for. + * $char_list + * This parameter is case sensitive. + * Return + * Returns a string starting from the character found, or FALSE if it is not found. + */ +static int PH7_builtin_strpbrk(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + const char *zString,*zList,*zEnd; + int iLen,iListLen,i,c; + sxu32 nOfft,nMax; + sxi32 rc; + if( nArg < 2 ){ + /* Missing arguments,return FALSE */ + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Extract the haystack and the char list */ + zString = ph7_value_to_string(apArg[0],&iLen); + zList = ph7_value_to_string(apArg[1],&iListLen); + if( iLen < 1 ){ + /* Nothing to process,return FALSE */ + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Point to the end of the string */ + zEnd = &zString[iLen]; + nOfft = nMax = SXU32_HIGH; + /* perform the requested operation */ + for( i = 0 ; i < iListLen ; i++ ){ + c = zList[i]; + rc = SyByteFind(zString,(sxu32)iLen,c,&nMax); + if( rc == SXRET_OK ){ + if( nMax < nOfft ){ + nOfft = nMax; + } + } + } + if( nOfft == SXU32_HIGH ){ + /* No such substring,return FALSE */ + ph7_result_bool(pCtx,0); + }else{ + /* Return the substring */ + ph7_result_string(pCtx,&zString[nOfft],(int)(zEnd-&zString[nOfft])); + } + return PH7_OK; +} +/* + * string soundex(string $str) + * Calculate the soundex key of a string. + * Parameters + * $str + * The input string. + * Return + * Returns the soundex key as a string. + * Note: + * This implementation is based on the one found in the SQLite3 + * source tree. + */ +static int PH7_builtin_soundex(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + const unsigned char *zIn; + char zResult[8]; + int i, j; + static const unsigned char iCode[] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 1, 2, 3, 0, 1, 2, 0, 0, 2, 2, 4, 5, 5, 0, + 1, 2, 6, 2, 3, 0, 1, 0, 2, 0, 2, 0, 0, 0, 0, 0, + 0, 0, 1, 2, 3, 0, 1, 2, 0, 0, 2, 2, 4, 5, 5, 0, + 1, 2, 6, 2, 3, 0, 1, 0, 2, 0, 2, 0, 0, 0, 0, 0, + }; + if( nArg < 1 ){ + /* Missing arguments,return the empty string */ + ph7_result_string(pCtx,"",0); + return PH7_OK; + } + zIn = (unsigned char *)ph7_value_to_string(apArg[0],0); + for(i=0; zIn[i] && zIn[i] < 0xc0 && !SyisAlpha(zIn[i]); i++){} + if( zIn[i] ){ + unsigned char prevcode = iCode[zIn[i]&0x7f]; + zResult[0] = (char)SyToUpper(zIn[i]); + for(j=1; j<4 && zIn[i]; i++){ + int code = iCode[zIn[i]&0x7f]; + if( code>0 ){ + if( code!=prevcode ){ + prevcode = (unsigned char)code; + zResult[j++] = (char)code + '0'; + } + }else{ + prevcode = 0; + } + } + while( j<4 ){ + zResult[j++] = '0'; + } + ph7_result_string(pCtx,zResult,4); + }else{ + ph7_result_string(pCtx,"?000",4); + } + return PH7_OK; +} +/* + * string wordwrap(string $str[,int $width = 75[,string $break = "\n"]]) + * Wraps a string to a given number of characters. + * Parameters + * $str + * The input string. + * $width + * The column width. + * $break + * The line is broken using the optional break parameter. + * Return + * Returns the given string wrapped at the specified column. + */ +static int PH7_builtin_wordwrap(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + const char *zIn,*zEnd,*zBreak; + int iLen,iBreaklen,iChunk; + if( nArg < 1 ){ + /* Missing arguments,return the empty string */ + ph7_result_string(pCtx,"",0); + return PH7_OK; + } + /* Extract the input string */ + zIn = ph7_value_to_string(apArg[0],&iLen); + if( iLen < 1 ){ + /* Nothing to process,return the empty string */ + ph7_result_string(pCtx,"",0); + return PH7_OK; + } + /* Chunk length */ + iChunk = 75; + iBreaklen = 0; + zBreak = ""; /* cc warning */ + if( nArg > 1 ){ + iChunk = ph7_value_to_int(apArg[1]); + if( iChunk < 1 ){ + iChunk = 75; + } + if( nArg > 2 ){ + zBreak = ph7_value_to_string(apArg[2],&iBreaklen); + } + } + if( iBreaklen < 1 ){ + /* Set a default column break */ +#ifdef __WINNT__ + zBreak = "\r\n"; + iBreaklen = (int)sizeof("\r\n")-1; +#else + zBreak = "\n"; + iBreaklen = (int)sizeof(char); +#endif + } + /* Perform the requested operation */ + zEnd = &zIn[iLen]; + for(;;){ + int nMax; + if( zIn >= zEnd ){ + /* No more input to process */ + break; + } + nMax = (int)(zEnd-zIn); + if( iChunk > nMax ){ + iChunk = nMax; + } + /* Append the column first */ + ph7_result_string(pCtx,zIn,iChunk); /* Will make it's own copy */ + /* Advance the cursor */ + zIn += iChunk; + if( zIn < zEnd ){ + /* Append the line break */ + ph7_result_string(pCtx,zBreak,iBreaklen); + } + } + return PH7_OK; +} +/* + * Check if the given character is a member of the given mask. + * Return TRUE on success. FALSE otherwise. + * Refer to [strtok()]. + */ +static int CheckMask(int c,const char *zMask,int nMasklen,int *pOfft) +{ + int i; + for( i = 0 ; i < nMasklen ; ++i ){ + if( c == zMask[i] ){ + if( pOfft ){ + *pOfft = i; + } + return TRUE; + } + } + return FALSE; +} +/* + * Extract a single token from the input stream. + * Refer to [strtok()]. + */ +static sxi32 ExtractToken(const char **pzIn,const char *zEnd,const char *zMask,int nMasklen,SyString *pOut) +{ + const char *zIn = *pzIn; + const char *zPtr; + /* Ignore leading delimiter */ + while( zIn < zEnd && (unsigned char)zIn[0] < 0xc0 && CheckMask(zIn[0],zMask,nMasklen,0) ){ + zIn++; + } + if( zIn >= zEnd ){ + /* End of input */ + return SXERR_EOF; + } + zPtr = zIn; + /* Extract the token */ + while( zIn < zEnd ){ + if( (unsigned char)zIn[0] >= 0xc0 ){ + /* UTF-8 stream */ + zIn++; + SX_JMP_UTF8(zIn,zEnd); + }else{ + if( CheckMask(zIn[0],zMask,nMasklen,0) ){ + break; + } + zIn++; + } + } + SyStringInitFromBuf(pOut,zPtr,zIn-zPtr); + /* Update the cursor */ + *pzIn = zIn; + /* Return to the caller */ + return SXRET_OK; +} +/* strtok auxiliary private data */ +typedef struct strtok_aux_data strtok_aux_data; +struct strtok_aux_data +{ + const char *zDup; /* Complete duplicate of the input */ + const char *zIn; /* Current input stream */ + const char *zEnd; /* End of input */ +}; +/* + * string strtok(string $str,string $token) + * string strtok(string $token) + * strtok() splits a string (str) into smaller strings (tokens), with each token + * being delimited by any character from token. That is, if you have a string like + * "This is an example string" you could tokenize this string into its individual + * words by using the space character as the token. + * Note that only the first call to strtok uses the string argument. Every subsequent + * call to strtok only needs the token to use, as it keeps track of where it is in + * the current string. To start over, or to tokenize a new string you simply call strtok + * with the string argument again to initialize it. Note that you may put multiple tokens + * in the token parameter. The string will be tokenized when any one of the characters in + * the argument are found. + * Parameters + * $str + * The string being split up into smaller strings (tokens). + * $token + * The delimiter used when splitting up str. + * Return + * Current token or FALSE on EOF. + */ +static int PH7_builtin_strtok(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + strtok_aux_data *pAux; + const char *zMask; + SyString sToken; + int nMasklen; + sxi32 rc; + if( nArg < 2 ){ + /* Extract top aux data */ + pAux = (strtok_aux_data *)ph7_context_peek_aux_data(pCtx); + if( pAux == 0 ){ + /* No aux data,return FALSE */ + ph7_result_bool(pCtx,0); + return PH7_OK; + } + nMasklen = 0; + zMask = ""; /* cc warning */ + if( nArg > 0 ){ + /* Extract the mask */ + zMask = ph7_value_to_string(apArg[0],&nMasklen); + } + if( nMasklen < 1 ){ + /* Invalid mask,return FALSE */ + ph7_context_free_chunk(pCtx,(void *)pAux->zDup); + ph7_context_free_chunk(pCtx,pAux); + (void)ph7_context_pop_aux_data(pCtx); + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Extract the token */ + rc = ExtractToken(&pAux->zIn,pAux->zEnd,zMask,nMasklen,&sToken); + if( rc != SXRET_OK ){ + /* EOF ,discard the aux data */ + ph7_context_free_chunk(pCtx,(void *)pAux->zDup); + ph7_context_free_chunk(pCtx,pAux); + (void)ph7_context_pop_aux_data(pCtx); + ph7_result_bool(pCtx,0); + }else{ + /* Return the extracted token */ + ph7_result_string(pCtx,sToken.zString,(int)sToken.nByte); + } + }else{ + const char *zInput,*zCur; + char *zDup; + int nLen; + /* Extract the raw input */ + zCur = zInput = ph7_value_to_string(apArg[0],&nLen); + if( nLen < 1 ){ + /* Empty input,return FALSE */ + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Extract the mask */ + zMask = ph7_value_to_string(apArg[1],&nMasklen); + if( nMasklen < 1 ){ + /* Set a default mask */ +#define TOK_MASK " \n\t\r\f" + zMask = TOK_MASK; + nMasklen = (int)sizeof(TOK_MASK) - 1; +#undef TOK_MASK + } + /* Extract a single token */ + rc = ExtractToken(&zInput,&zInput[nLen],zMask,nMasklen,&sToken); + if( rc != SXRET_OK ){ + /* Empty input */ + ph7_result_bool(pCtx,0); + return PH7_OK; + }else{ + /* Return the extracted token */ + ph7_result_string(pCtx,sToken.zString,(int)sToken.nByte); + } + /* Create our auxilliary data and copy the input */ + pAux = (strtok_aux_data *)ph7_context_alloc_chunk(pCtx,sizeof(strtok_aux_data),TRUE,FALSE); + if( pAux ){ + nLen -= (int)(zInput-zCur); + if( nLen < 1 ){ + ph7_context_free_chunk(pCtx,pAux); + return PH7_OK; + } + /* Duplicate input */ + zDup = (char *)ph7_context_alloc_chunk(pCtx,(unsigned int)(nLen+1),TRUE,FALSE); + if( zDup ){ + SyMemcpy(zInput,zDup,(sxu32)nLen); + /* Register the aux data */ + pAux->zDup = pAux->zIn = zDup; + pAux->zEnd = &zDup[nLen]; + ph7_context_push_aux_data(pCtx,pAux); + } + } + } + return PH7_OK; +} +/* + * string str_pad(string $input,int $pad_length[,string $pad_string = " " [,int $pad_type = STR_PAD_RIGHT]]) + * Pad a string to a certain length with another string + * Parameters + * $input + * The input string. + * $pad_length + * If the value of pad_length is negative, less than, or equal to the length of the input + * string, no padding takes place. + * $pad_string + * Note: + * The pad_string WIIL NOT BE truncated if the required number of padding characters can't be evenly + * divided by the pad_string's length. + * $pad_type + * Optional argument pad_type can be STR_PAD_RIGHT, STR_PAD_LEFT, or STR_PAD_BOTH. If pad_type + * is not specified it is assumed to be STR_PAD_RIGHT. + * Return + * The padded string. + */ +static int PH7_builtin_str_pad(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + int iLen,iPadlen,iType,i,iDiv,iStrpad,iRealPad,jPad; + const char *zIn,*zPad; + if( nArg < 2 ){ + /* Missing arguments,return the empty string */ + ph7_result_string(pCtx,"",0); + return PH7_OK; + } + /* Extract the target string */ + zIn = ph7_value_to_string(apArg[0],&iLen); + /* Padding length */ + iRealPad = iPadlen = ph7_value_to_int(apArg[1]); + if( iPadlen > 0 ){ + iPadlen -= iLen; + } + if( iPadlen < 1 ){ + /* Return the string verbatim */ + ph7_result_string(pCtx,zIn,iLen); + return PH7_OK; + } + zPad = " "; /* Whitespace padding */ + iStrpad = (int)sizeof(char); + iType = 1 ; /* STR_PAD_RIGHT */ + if( nArg > 2 ){ + /* Padding string */ + zPad = ph7_value_to_string(apArg[2],&iStrpad); + if( iStrpad < 1 ){ + /* Empty string */ + zPad = " "; /* Whitespace padding */ + iStrpad = (int)sizeof(char); + } + if( nArg > 3 ){ + /* Padd type */ + iType = ph7_value_to_int(apArg[3]); + if( iType != 0 /* STR_PAD_LEFT */ && iType != 2 /* STR_PAD_BOTH */ ){ + iType = 1 ; /* STR_PAD_RIGHT */ + } + } + } + iDiv = 1; + if( iType == 2 ){ + iDiv = 2; /* STR_PAD_BOTH */ + } + /* Perform the requested operation */ + if( iType == 0 /* STR_PAD_LEFT */ || iType == 2 /* STR_PAD_BOTH */ ){ + jPad = iStrpad; + for( i = 0 ; i < iPadlen/iDiv ; i += jPad ){ + /* Padding */ + if( (int)ph7_context_result_buf_length(pCtx) + iLen + jPad >= iRealPad ){ + break; + } + ph7_result_string(pCtx,zPad,jPad); + } + if( iType == 0 /* STR_PAD_LEFT */ ){ + while( (int)ph7_context_result_buf_length(pCtx) + iLen < iRealPad ){ + jPad = iRealPad - (iLen + (int)ph7_context_result_buf_length(pCtx) ); + if( jPad > iStrpad ){ + jPad = iStrpad; + } + if( jPad < 1){ + break; + } + ph7_result_string(pCtx,zPad,jPad); + } + } + } + if( iLen > 0 ){ + /* Append the input string */ + ph7_result_string(pCtx,zIn,iLen); + } + if( iType == 1 /* STR_PAD_RIGHT */ || iType == 2 /* STR_PAD_BOTH */ ){ + for( i = 0 ; i < iPadlen/iDiv ; i += iStrpad ){ + /* Padding */ + if( (int)ph7_context_result_buf_length(pCtx) + iStrpad >= iRealPad ){ + break; + } + ph7_result_string(pCtx,zPad,iStrpad); + } + while( (int)ph7_context_result_buf_length(pCtx) < iRealPad ){ + jPad = iRealPad - (int)ph7_context_result_buf_length(pCtx); + if( jPad > iStrpad ){ + jPad = iStrpad; + } + if( jPad < 1){ + break; + } + ph7_result_string(pCtx,zPad,jPad); + } + } + return PH7_OK; +} +/* + * String replacement private data. + */ +typedef struct str_replace_data str_replace_data; +struct str_replace_data +{ + /* The following two fields are only used by the strtr function */ + SyBlob *pWorker; /* Working buffer */ + ProcStringMatch xMatch; /* Pattern match routine */ + /* The following two fields are only used by the str_replace function */ + SySet *pCollector; /* Argument collector*/ + ph7_context *pCtx; /* Call context */ +}; +/* + * Remove a substring. + */ +#define STRDEL(SRC,SLEN,OFFT,ILEN){\ + for(;;){\ + if( OFFT + ILEN >= SLEN ) break; SRC[OFFT] = SRC[OFFT+ILEN]; ++OFFT;\ + }\ +} +/* + * Shift right and insert algorithm. + */ +#define SHIFTRANDINSERT(SRC,LEN,OFFT,ENTRY,ELEN){\ + sxu32 INLEN = LEN - OFFT;\ + for(;;){\ + if( LEN > 0 ){ LEN--; } if(INLEN < 1 ) break; SRC[LEN + ELEN] = SRC[LEN] ; --INLEN; \ + }\ + for(;;){\ + if(ELEN < 1)break; SRC[OFFT] = ENTRY[0]; OFFT++; ENTRY++; --ELEN;\ + }\ +} +/* + * Replace all occurrences of the search string at offset (nOfft) with the given + * replacement string [i.e: zReplace]. + */ +static int StringReplace(SyBlob *pWorker,sxu32 nOfft,int nLen,const char *zReplace,int nReplen) +{ + char *zInput = (char *)SyBlobData(pWorker); + sxu32 n,m; + n = SyBlobLength(pWorker); + m = nOfft; + /* Delete the old entry */ + STRDEL(zInput,n,m,nLen); + SyBlobLength(pWorker) -= nLen; + if( nReplen > 0 ){ + sxi32 iRep = nReplen; + sxi32 rc; + /* + * Make sure the working buffer is big enough to hold the replacement + * string. + */ + rc = SyBlobAppend(pWorker,0/* Grow without an append operation*/,(sxu32)nReplen); + if( rc != SXRET_OK ){ + /* Simply ignore any memory failure problem */ + return SXRET_OK; + } + /* Perform the insertion now */ + zInput = (char *)SyBlobData(pWorker); + n = SyBlobLength(pWorker); + SHIFTRANDINSERT(zInput,n,nOfft,zReplace,iRep); + SyBlobLength(pWorker) += nReplen; + } + return SXRET_OK; +} +/* + * String replacement walker callback. + * The following callback is invoked for each array entry that hold + * the replace string. + * Refer to the strtr() implementation for more information. + */ +static int StringReplaceWalker(ph7_value *pKey,ph7_value *pData,void *pUserData) +{ + str_replace_data *pRepData = (str_replace_data *)pUserData; + const char *zTarget,*zReplace; + SyBlob *pWorker; + int tLen,nLen; + sxu32 nOfft; + sxi32 rc; + /* Point to the working buffer */ + pWorker = pRepData->pWorker; + if( !ph7_value_is_string(pKey) ){ + /* Target and replace must be a string */ + return PH7_OK; + } + /* Extract the target and the replace */ + zTarget = ph7_value_to_string(pKey,&tLen); + if( tLen < 1 ){ + /* Empty target,return immediately */ + return PH7_OK; + } + /* Perform a pattern search */ + rc = pRepData->xMatch(SyBlobData(pWorker),SyBlobLength(pWorker),(const void *)zTarget,(sxu32)tLen,&nOfft); + if( rc != SXRET_OK ){ + /* Pattern not found */ + return PH7_OK; + } + /* Extract the replace string */ + zReplace = ph7_value_to_string(pData,&nLen); + /* Perform the replace process */ + StringReplace(pWorker,nOfft,tLen,zReplace,nLen); + /* All done */ + return PH7_OK; +} +/* + * The following walker callback is invoked by the str_rplace() function inorder + * to collect search/replace string. + * This callback is invoked only if the given argument is of type array. + */ +static int StrReplaceWalker(ph7_value *pKey,ph7_value *pData,void *pUserData) +{ + str_replace_data *pRep = (str_replace_data *)pUserData; + SyString sWorker; + const char *zIn; + int nByte; + /* Extract a string representation of the given argument */ + zIn = ph7_value_to_string(pData,&nByte); + SyStringInitFromBuf(&sWorker,0,0); + if( nByte > 0 ){ + char *zDup; + /* Duplicate the chunk */ + zDup = (char *)ph7_context_alloc_chunk(pRep->pCtx,(unsigned int)nByte,FALSE, + TRUE /* Release the chunk automatically,upon this context is destroyd */ + ); + if( zDup == 0 ){ + /* Ignore any memory failure problem */ + ph7_context_throw_error(pRep->pCtx,PH7_CTX_ERR,"PH7 is running out of memory"); + return PH7_OK; + } + SyMemcpy(zIn,zDup,(sxu32)nByte); + /* Save the chunk */ + SyStringInitFromBuf(&sWorker,zDup,nByte); + } + /* Save for later processing */ + SySetPut(pRep->pCollector,(const void *)&sWorker); + /* All done */ + SXUNUSED(pKey); /* cc warning */ + return PH7_OK; +} +/* + * mixed str_replace(mixed $search,mixed $replace,mixed $subject[,int &$count ]) + * mixed str_ireplace(mixed $search,mixed $replace,mixed $subject[,int &$count ]) + * Replace all occurrences of the search string with the replacement string. + * Parameters + * If search and replace are arrays, then str_replace() takes a value from each + * array and uses them to search and replace on subject. If replace has fewer values + * than search, then an empty string is used for the rest of replacement values. + * If search is an array and replace is a string, then this replacement string is used + * for every value of search. The converse would not make sense, though. + * If search or replace are arrays, their elements are processed first to last. + * $search + * The value being searched for, otherwise known as the needle. An array may be used + * to designate multiple needles. + * $replace + * The replacement value that replaces found search values. An array may be used + * to designate multiple replacements. + * $subject + * The string or array being searched and replaced on, otherwise known as the haystack. + * If subject is an array, then the search and replace is performed with every entry + * of subject, and the return value is an array as well. + * $count (Not used) + * If passed, this will be set to the number of replacements performed. + * Return + * This function returns a string or an array with the replaced values. + */ +static int PH7_builtin_str_replace(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + SyString sTemp,*pSearch,*pReplace; + ProcStringMatch xMatch; + const char *zIn,*zFunc; + str_replace_data sRep; + SyBlob sWorker; + SySet sReplace; + SySet sSearch; + int rep_str; + int nByte; + sxi32 rc; + if( nArg < 3 ){ + /* Missing/Invalid arguments,return null */ + ph7_result_null(pCtx); + return PH7_OK; + } + /* Initialize fields */ + SySetInit(&sSearch,&pCtx->pVm->sAllocator,sizeof(SyString)); + SySetInit(&sReplace,&pCtx->pVm->sAllocator,sizeof(SyString)); + SyBlobInit(&sWorker,&pCtx->pVm->sAllocator); + SyZero(&sRep,sizeof(str_replace_data)); + sRep.pCtx = pCtx; + sRep.pCollector = &sSearch; + rep_str = 0; + /* Extract the subject */ + zIn = ph7_value_to_string(apArg[2],&nByte); + if( nByte < 1 ){ + /* Nothing to replace,return the empty string */ + ph7_result_string(pCtx,"",0); + return PH7_OK; + } + /* Copy the subject */ + SyBlobAppend(&sWorker,(const void *)zIn,(sxu32)nByte); + /* Search string */ + if( ph7_value_is_array(apArg[0]) ){ + /* Collect search string */ + ph7_array_walk(apArg[0],StrReplaceWalker,&sRep); + }else{ + /* Single pattern */ + zIn = ph7_value_to_string(apArg[0],&nByte); + if( nByte < 1 ){ + /* Return the subject untouched since no search string is available */ + ph7_result_value(pCtx,apArg[2]/* Subject as thrird argument*/); + return PH7_OK; + } + SyStringInitFromBuf(&sTemp,zIn,nByte); + /* Save for later processing */ + SySetPut(&sSearch,(const void *)&sTemp); + } + /* Replace string */ + if( ph7_value_is_array(apArg[1]) ){ + /* Collect replace string */ + sRep.pCollector = &sReplace; + ph7_array_walk(apArg[1],StrReplaceWalker,&sRep); + }else{ + /* Single needle */ + zIn = ph7_value_to_string(apArg[1],&nByte); + rep_str = 1; + SyStringInitFromBuf(&sTemp,zIn,nByte); + /* Save for later processing */ + SySetPut(&sReplace,(const void *)&sTemp); + } + /* Reset loop cursors */ + SySetResetCursor(&sSearch); + SySetResetCursor(&sReplace); + pReplace = pSearch = 0; /* cc warning */ + SyStringInitFromBuf(&sTemp,"",0); + /* Extract function name */ + zFunc = ph7_function_name(pCtx); + /* Set the default pattern match routine */ + xMatch = SyBlobSearch; + if( SyStrncmp(zFunc,"str_ireplace",sizeof("str_ireplace") - 1) == 0 ){ + /* Case insensitive pattern match */ + xMatch = iPatternMatch; + } + /* Start the replace process */ + while( SXRET_OK == SySetGetNextEntry(&sSearch,(void **)&pSearch) ){ + sxu32 nCount,nOfft; + if( pSearch->nByte < 1 ){ + /* Empty string,ignore */ + continue; + } + /* Extract the replace string */ + if( rep_str ){ + pReplace = (SyString *)SySetPeek(&sReplace); + }else{ + if( SXRET_OK != SySetGetNextEntry(&sReplace,(void **)&pReplace) ){ + /* Sepecial case when 'replace set' has fewer values than the search set. + * An empty string is used for the rest of replacement values + */ + pReplace = 0; + } + } + if( pReplace == 0 ){ + /* Use an empty string instead */ + pReplace = &sTemp; + } + nOfft = nCount = 0; + for(;;){ + if( nCount >= SyBlobLength(&sWorker) ){ + break; + } + /* Perform a pattern lookup */ + rc = xMatch(SyBlobDataAt(&sWorker,nCount),SyBlobLength(&sWorker) - nCount,(const void *)pSearch->zString, + pSearch->nByte,&nOfft); + if( rc != SXRET_OK ){ + /* Pattern not found */ + break; + } + /* Perform the replace operation */ + StringReplace(&sWorker,nCount+nOfft,(int)pSearch->nByte,pReplace->zString,(int)pReplace->nByte); + /* Increment offset counter */ + nCount += nOfft + pReplace->nByte; + } + } + /* All done,clean-up the mess left behind */ + ph7_result_string(pCtx,(const char *)SyBlobData(&sWorker),(int)SyBlobLength(&sWorker)); + SySetRelease(&sSearch); + SySetRelease(&sReplace); + SyBlobRelease(&sWorker); + return PH7_OK; +} +/* + * string strtr(string $str,string $from,string $to) + * string strtr(string $str,array $replace_pairs) + * Translate characters or replace substrings. + * Parameters + * $str + * The string being translated. + * $from + * The string being translated to to. + * $to + * The string replacing from. + * $replace_pairs + * The replace_pairs parameter may be used instead of to and + * from, in which case it's an array in the form array('from' => 'to', ...). + * Return + * The translated string. + * If replace_pairs contains a key which is an empty string (""), FALSE will be returned. + */ +static int PH7_builtin_strtr(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + const char *zIn; + int nLen; + if( nArg < 1 ){ + /* Nothing to replace,return FALSE */ + ph7_result_bool(pCtx,0); + return PH7_OK; + } + zIn = ph7_value_to_string(apArg[0],&nLen); + if( nLen < 1 || nArg < 2 ){ + /* Invalid arguments */ + ph7_result_string(pCtx,zIn,nLen); + return PH7_OK; + } + if( nArg == 2 && ph7_value_is_array(apArg[1]) ){ + str_replace_data sRepData; + SyBlob sWorker; + /* Initilaize the working buffer */ + SyBlobInit(&sWorker,&pCtx->pVm->sAllocator); + /* Copy raw string */ + SyBlobAppend(&sWorker,(const void *)zIn,(sxu32)nLen); + /* Init our replace data instance */ + sRepData.pWorker = &sWorker; + sRepData.xMatch = SyBlobSearch; + /* Iterate throw array entries and perform the replace operation.*/ + ph7_array_walk(apArg[1],StringReplaceWalker,&sRepData); + /* All done, return the result string */ + ph7_result_string(pCtx,(const char *)SyBlobData(&sWorker), + (int)SyBlobLength(&sWorker)); /* Will make it's own copy */ + /* Clean-up */ + SyBlobRelease(&sWorker); + }else{ + int i,flen,tlen,c,iOfft; + const char *zFrom,*zTo; + if( nArg < 3 ){ + /* Nothing to replace */ + ph7_result_string(pCtx,zIn,nLen); + return PH7_OK; + } + /* Extract given arguments */ + zFrom = ph7_value_to_string(apArg[1],&flen); + zTo = ph7_value_to_string(apArg[2],&tlen); + if( flen < 1 || tlen < 1 ){ + /* Nothing to replace */ + ph7_result_string(pCtx,zIn,nLen); + return PH7_OK; + } + /* Start the replace process */ + for( i = 0 ; i < nLen ; ++i ){ + c = zIn[i]; + if( CheckMask(c,zFrom,flen,&iOfft) ){ + if ( iOfft < tlen ){ + c = zTo[iOfft]; + } + } + ph7_result_string(pCtx,(const char *)&c,(int)sizeof(char)); + + } + } + return PH7_OK; +} +/* + * Parse an INI string. + * According to wikipedia + * The INI file format is an informal standard for configuration files for some platforms or software. + * INI files are simple text files with a basic structure composed of "sections" and "properties". + * Format +* Properties +* The basic element contained in an INI file is the property. Every property has a name and a value +* delimited by an equals sign (=). The name appears to the left of the equals sign. +* Example: +* name=value +* Sections +* Properties may be grouped into arbitrarily named sections. The section name appears on a line by itself +* in square brackets ([ and ]). All properties after the section declaration are associated with that section. +* There is no explicit "end of section" delimiter; sections end at the next section declaration +* or the end of the file. Sections may not be nested. +* Example: +* [section] +* Comments +* Semicolons (;) at the beginning of the line indicate a comment. Comment lines are ignored. +* This function return an array holding parsed values on success.FALSE otherwise. +*/ +PH7_PRIVATE sxi32 PH7_ParseIniString(ph7_context *pCtx,const char *zIn,sxu32 nByte,int bProcessSection) +{ + ph7_value *pCur,*pArray,*pSection,*pWorker,*pValue; + const char *zCur,*zEnd = &zIn[nByte]; + SyHashEntry *pEntry; + SyString sEntry; + SyHash sHash; + int c; + /* Create an empty array and worker variables */ + pArray = ph7_context_new_array(pCtx); + pWorker = ph7_context_new_scalar(pCtx); + pValue = ph7_context_new_scalar(pCtx); + if( pArray == 0 || pWorker == 0 || pValue == 0){ + /* Out of memory */ + ph7_context_throw_error(pCtx,PH7_CTX_ERR,"PH7 is running out of memory"); + /* Return FALSE */ + ph7_result_bool(pCtx,0); + return PH7_OK; + } + SyHashInit(&sHash,&pCtx->pVm->sAllocator,0,0); + pCur = pArray; + /* Start the parse process */ + for(;;){ + /* Ignore leading white spaces */ + while( zIn < zEnd && (unsigned char)zIn[0] < 0xc0 && SyisSpace(zIn[0])){ + zIn++; + } + if( zIn >= zEnd ){ + /* No more input to process */ + break; + } + if( zIn[0] == ';' || zIn[0] == '#' ){ + /* Comment til the end of line */ + zIn++; + while(zIn < zEnd && zIn[0] != '\n' ){ + zIn++; + } + continue; + } + /* Reset the string cursor of the working variable */ + ph7_value_reset_string_cursor(pWorker); + if( zIn[0] == '[' ){ + /* Section: Extract the section name */ + zIn++; + zCur = zIn; + while( zIn < zEnd && zIn[0] != ']' ){ + zIn++; + } + if( zIn > zCur && bProcessSection ){ + /* Save the section name */ + SyStringInitFromBuf(&sEntry,zCur,(int)(zIn-zCur)); + SyStringFullTrim(&sEntry); + ph7_value_string(pWorker,sEntry.zString,(int)sEntry.nByte); + if( sEntry.nByte > 0 ){ + /* Associate an array with the section */ + pSection = ph7_context_new_array(pCtx); + if( pSection ){ + ph7_array_add_elem(pArray,pWorker/*Section name*/,pSection); + pCur = pSection; + } + } + } + zIn++; /* Trailing square brackets ']' */ + }else{ + ph7_value *pOldCur; + int is_array; + int iLen; + /* Properties */ + is_array = 0; + zCur = zIn; + iLen = 0; /* cc warning */ + pOldCur = pCur; + while( zIn < zEnd && zIn[0] != '=' ){ + if( zIn[0] == '[' && !is_array ){ + /* Array */ + iLen = (int)(zIn-zCur); + is_array = 1; + if( iLen > 0 ){ + ph7_value *pvArr = 0; /* cc warning */ + /* Query the hashtable */ + SyStringInitFromBuf(&sEntry,zCur,iLen); + SyStringFullTrim(&sEntry); + pEntry = SyHashGet(&sHash,(const void *)sEntry.zString,sEntry.nByte); + if( pEntry ){ + pvArr = (ph7_value *)SyHashEntryGetUserData(pEntry); + }else{ + /* Create an empty array */ + pvArr = ph7_context_new_array(pCtx); + if( pvArr ){ + /* Save the entry */ + SyHashInsert(&sHash,(const void *)sEntry.zString,sEntry.nByte,pvArr); + /* Insert the entry */ + ph7_value_reset_string_cursor(pWorker); + ph7_value_string(pWorker,sEntry.zString,(int)sEntry.nByte); + ph7_array_add_elem(pCur,pWorker,pvArr); + ph7_value_reset_string_cursor(pWorker); + } + } + if( pvArr ){ + pCur = pvArr; + } + } + while ( zIn < zEnd && zIn[0] != ']' ){ + zIn++; + } + } + zIn++; + } + if( !is_array ){ + iLen = (int)(zIn-zCur); + } + /* Trim the key */ + SyStringInitFromBuf(&sEntry,zCur,iLen); + SyStringFullTrim(&sEntry); + if( sEntry.nByte > 0 ){ + if( !is_array ){ + /* Save the key name */ + ph7_value_string(pWorker,sEntry.zString,(int)sEntry.nByte); + } + /* extract key value */ + ph7_value_reset_string_cursor(pValue); + zIn++; /* '=' */ + while( zIn < zEnd && (unsigned char)zIn[0] < 0xc0 && SyisSpace(zIn[0]) ){ + zIn++; + } + if( zIn < zEnd ){ + zCur = zIn; + c = zIn[0]; + if( c == '"' || c == '\'' ){ + zIn++; + /* Delimit the value */ + while( zIn < zEnd ){ + if ( zIn[0] == c && zIn[-1] != '\\' ){ + break; + } + zIn++; + } + if( zIn < zEnd ){ + zIn++; + } + }else{ + while( zIn < zEnd ){ + if( zIn[0] == '\n' ){ + if( zIn[-1] != '\\' ){ + break; + } + }else if( zIn[0] == ';' || zIn[0] == '#' ){ + /* Inline comments */ + break; + } + zIn++; + } + } + /* Trim the value */ + SyStringInitFromBuf(&sEntry,zCur,(int)(zIn-zCur)); + SyStringFullTrim(&sEntry); + if( c == '"' || c == '\'' ){ + SyStringTrimLeadingChar(&sEntry,c); + SyStringTrimTrailingChar(&sEntry,c); + } + if( sEntry.nByte > 0 ){ + ph7_value_string(pValue,sEntry.zString,(int)sEntry.nByte); + } + /* Insert the key and it's value */ + ph7_array_add_elem(pCur,is_array ? 0 /*Automatic index assign */: pWorker,pValue); + } + }else{ + while( zIn < zEnd && (unsigned char)zIn[0] < 0xc0 && ( SyisSpace(zIn[0]) || zIn[0] == '=' ) ){ + zIn++; + } + } + pCur = pOldCur; + } + } + SyHashRelease(&sHash); + /* Return the parse of the INI string */ + ph7_result_value(pCtx,pArray); + return SXRET_OK; +} +/* + * array parse_ini_string(string $ini[,bool $process_sections = false[,int $scanner_mode = INI_SCANNER_NORMAL ]]) + * Parse a configuration string. + * Parameters + * $ini + * The contents of the ini file being parsed. + * $process_sections + * By setting the process_sections parameter to TRUE, you get a multidimensional array, with the section names + * and settings included. The default for process_sections is FALSE. + * $scanner_mode (Not used) + * Can either be INI_SCANNER_NORMAL (default) or INI_SCANNER_RAW. If INI_SCANNER_RAW is supplied + * then option values will not be parsed. + * Return + * The settings are returned as an associative array on success, and FALSE on failure. + */ +static int PH7_builtin_parse_ini_string(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + const char *zIni; + int nByte; + if( nArg < 1 || !ph7_value_is_string(apArg[0]) ){ + /* Missing/Invalid arguments,return FALSE*/ + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Extract the raw INI buffer */ + zIni = ph7_value_to_string(apArg[0],&nByte); + /* Process the INI buffer*/ + PH7_ParseIniString(pCtx,zIni,(sxu32)nByte,(nArg > 1) ? ph7_value_to_bool(apArg[1]) : 0); + return PH7_OK; +} +/* + * Ctype Functions. + * Authors: + * Symisc Systems,devel@symisc.net. + * Copyright (C) Symisc Systems,http://ph7.symisc.net + * Status: + * Stable. + */ +/* + * bool ctype_alnum(string $text) + * Checks if all of the characters in the provided string, text, are alphanumeric. + * Parameters + * $text + * The tested string. + * Return + * TRUE if every character in text is either a letter or a digit, FALSE otherwise. + */ +static int PH7_builtin_ctype_alnum(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + const unsigned char *zIn,*zEnd; + int nLen; + if( nArg < 1 ){ + /* Missing arguments,return FALSE */ + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Extract the target string */ + zIn = (const unsigned char *)ph7_value_to_string(apArg[0],&nLen); + zEnd = &zIn[nLen]; + if( nLen < 1 ){ + /* Empty string,return FALSE */ + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Perform the requested operation */ + for(;;){ + if( zIn >= zEnd ){ + /* If we reach the end of the string,then the test succeeded. */ + ph7_result_bool(pCtx,1); + return PH7_OK; + } + if( !SyisAlphaNum(zIn[0]) ){ + break; + } + /* Point to the next character */ + zIn++; + } + /* The test failed,return FALSE */ + ph7_result_bool(pCtx,0); + return PH7_OK; +} +/* + * bool ctype_alpha(string $text) + * Checks if all of the characters in the provided string, text, are alphabetic. + * Parameters + * $text + * The tested string. + * Return + * TRUE if every character in text is a letter from the current locale, FALSE otherwise. + */ +static int PH7_builtin_ctype_alpha(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + const unsigned char *zIn,*zEnd; + int nLen; + if( nArg < 1 ){ + /* Missing arguments,return FALSE */ + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Extract the target string */ + zIn = (const unsigned char *)ph7_value_to_string(apArg[0],&nLen); + zEnd = &zIn[nLen]; + if( nLen < 1 ){ + /* Empty string,return FALSE */ + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Perform the requested operation */ + for(;;){ + if( zIn >= zEnd ){ + /* If we reach the end of the string,then the test succeeded. */ + ph7_result_bool(pCtx,1); + return PH7_OK; + } + if( !SyisAlpha(zIn[0]) ){ + break; + } + /* Point to the next character */ + zIn++; + } + /* The test failed,return FALSE */ + ph7_result_bool(pCtx,0); + return PH7_OK; +} +/* + * bool ctype_cntrl(string $text) + * Checks if all of the characters in the provided string, text, are control characters. + * Parameters + * $text + * The tested string. + * Return + * TRUE if every character in text is a control characters,FALSE otherwise. + */ +static int PH7_builtin_ctype_cntrl(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + const unsigned char *zIn,*zEnd; + int nLen; + if( nArg < 1 ){ + /* Missing arguments,return FALSE */ + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Extract the target string */ + zIn = (const unsigned char *)ph7_value_to_string(apArg[0],&nLen); + zEnd = &zIn[nLen]; + if( nLen < 1 ){ + /* Empty string,return FALSE */ + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Perform the requested operation */ + for(;;){ + if( zIn >= zEnd ){ + /* If we reach the end of the string,then the test succeeded. */ + ph7_result_bool(pCtx,1); + return PH7_OK; + } + if( zIn[0] >= 0xc0 ){ + /* UTF-8 stream */ + break; + } + if( !SyisCtrl(zIn[0]) ){ + break; + } + /* Point to the next character */ + zIn++; + } + /* The test failed,return FALSE */ + ph7_result_bool(pCtx,0); + return PH7_OK; +} +/* + * bool ctype_digit(string $text) + * Checks if all of the characters in the provided string, text, are numerical. + * Parameters + * $text + * The tested string. + * Return + * TRUE if every character in the string text is a decimal digit, FALSE otherwise. + */ +static int PH7_builtin_ctype_digit(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + const unsigned char *zIn,*zEnd; + int nLen; + if( nArg < 1 ){ + /* Missing arguments,return FALSE */ + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Extract the target string */ + zIn = (const unsigned char *)ph7_value_to_string(apArg[0],&nLen); + zEnd = &zIn[nLen]; + if( nLen < 1 ){ + /* Empty string,return FALSE */ + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Perform the requested operation */ + for(;;){ + if( zIn >= zEnd ){ + /* If we reach the end of the string,then the test succeeded. */ + ph7_result_bool(pCtx,1); + return PH7_OK; + } + if( zIn[0] >= 0xc0 ){ + /* UTF-8 stream */ + break; + } + if( !SyisDigit(zIn[0]) ){ + break; + } + /* Point to the next character */ + zIn++; + } + /* The test failed,return FALSE */ + ph7_result_bool(pCtx,0); + return PH7_OK; +} +/* + * bool ctype_xdigit(string $text) + * Check for character(s) representing a hexadecimal digit. + * Parameters + * $text + * The tested string. + * Return + * Returns TRUE if every character in text is a hexadecimal 'digit', that is + * a decimal digit or a character from [A-Fa-f] , FALSE otherwise. + */ +static int PH7_builtin_ctype_xdigit(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + const unsigned char *zIn,*zEnd; + int nLen; + if( nArg < 1 ){ + /* Missing arguments,return FALSE */ + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Extract the target string */ + zIn = (const unsigned char *)ph7_value_to_string(apArg[0],&nLen); + zEnd = &zIn[nLen]; + if( nLen < 1 ){ + /* Empty string,return FALSE */ + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Perform the requested operation */ + for(;;){ + if( zIn >= zEnd ){ + /* If we reach the end of the string,then the test succeeded. */ + ph7_result_bool(pCtx,1); + return PH7_OK; + } + if( zIn[0] >= 0xc0 ){ + /* UTF-8 stream */ + break; + } + if( !SyisHex(zIn[0]) ){ + break; + } + /* Point to the next character */ + zIn++; + } + /* The test failed,return FALSE */ + ph7_result_bool(pCtx,0); + return PH7_OK; +} +/* + * bool ctype_graph(string $text) + * Checks if all of the characters in the provided string, text, creates visible output. + * Parameters + * $text + * The tested string. + * Return + * Returns TRUE if every character in text is printable and actually creates visible output + * (no white space), FALSE otherwise. + */ +static int PH7_builtin_ctype_graph(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + const unsigned char *zIn,*zEnd; + int nLen; + if( nArg < 1 ){ + /* Missing arguments,return FALSE */ + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Extract the target string */ + zIn = (const unsigned char *)ph7_value_to_string(apArg[0],&nLen); + zEnd = &zIn[nLen]; + if( nLen < 1 ){ + /* Empty string,return FALSE */ + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Perform the requested operation */ + for(;;){ + if( zIn >= zEnd ){ + /* If we reach the end of the string,then the test succeeded. */ + ph7_result_bool(pCtx,1); + return PH7_OK; + } + if( zIn[0] >= 0xc0 ){ + /* UTF-8 stream */ + break; + } + if( !SyisGraph(zIn[0]) ){ + break; + } + /* Point to the next character */ + zIn++; + } + /* The test failed,return FALSE */ + ph7_result_bool(pCtx,0); + return PH7_OK; +} +/* + * bool ctype_print(string $text) + * Checks if all of the characters in the provided string, text, are printable. + * Parameters + * $text + * The tested string. + * Return + * Returns TRUE if every character in text will actually create output (including blanks). + * Returns FALSE if text contains control characters or characters that do not have any output + * or control function at all. + */ +static int PH7_builtin_ctype_print(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + const unsigned char *zIn,*zEnd; + int nLen; + if( nArg < 1 ){ + /* Missing arguments,return FALSE */ + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Extract the target string */ + zIn = (const unsigned char *)ph7_value_to_string(apArg[0],&nLen); + zEnd = &zIn[nLen]; + if( nLen < 1 ){ + /* Empty string,return FALSE */ + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Perform the requested operation */ + for(;;){ + if( zIn >= zEnd ){ + /* If we reach the end of the string,then the test succeeded. */ + ph7_result_bool(pCtx,1); + return PH7_OK; + } + if( zIn[0] >= 0xc0 ){ + /* UTF-8 stream */ + break; + } + if( !SyisPrint(zIn[0]) ){ + break; + } + /* Point to the next character */ + zIn++; + } + /* The test failed,return FALSE */ + ph7_result_bool(pCtx,0); + return PH7_OK; +} +/* + * bool ctype_punct(string $text) + * Checks if all of the characters in the provided string, text, are punctuation character. + * Parameters + * $text + * The tested string. + * Return + * Returns TRUE if every character in text is printable, but neither letter + * digit or blank, FALSE otherwise. + */ +static int PH7_builtin_ctype_punct(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + const unsigned char *zIn,*zEnd; + int nLen; + if( nArg < 1 ){ + /* Missing arguments,return FALSE */ + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Extract the target string */ + zIn = (const unsigned char *)ph7_value_to_string(apArg[0],&nLen); + zEnd = &zIn[nLen]; + if( nLen < 1 ){ + /* Empty string,return FALSE */ + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Perform the requested operation */ + for(;;){ + if( zIn >= zEnd ){ + /* If we reach the end of the string,then the test succeeded. */ + ph7_result_bool(pCtx,1); + return PH7_OK; + } + if( zIn[0] >= 0xc0 ){ + /* UTF-8 stream */ + break; + } + if( !SyisPunct(zIn[0]) ){ + break; + } + /* Point to the next character */ + zIn++; + } + /* The test failed,return FALSE */ + ph7_result_bool(pCtx,0); + return PH7_OK; +} +/* + * bool ctype_space(string $text) + * Checks if all of the characters in the provided string, text, creates whitespace. + * Parameters + * $text + * The tested string. + * Return + * Returns TRUE if every character in text creates some sort of white space, FALSE otherwise. + * Besides the blank character this also includes tab, vertical tab, line feed, carriage return + * and form feed characters. + */ +static int PH7_builtin_ctype_space(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + const unsigned char *zIn,*zEnd; + int nLen; + if( nArg < 1 ){ + /* Missing arguments,return FALSE */ + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Extract the target string */ + zIn = (const unsigned char *)ph7_value_to_string(apArg[0],&nLen); + zEnd = &zIn[nLen]; + if( nLen < 1 ){ + /* Empty string,return FALSE */ + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Perform the requested operation */ + for(;;){ + if( zIn >= zEnd ){ + /* If we reach the end of the string,then the test succeeded. */ + ph7_result_bool(pCtx,1); + return PH7_OK; + } + if( zIn[0] >= 0xc0 ){ + /* UTF-8 stream */ + break; + } + if( !SyisSpace(zIn[0]) ){ + break; + } + /* Point to the next character */ + zIn++; + } + /* The test failed,return FALSE */ + ph7_result_bool(pCtx,0); + return PH7_OK; +} +/* + * bool ctype_lower(string $text) + * Checks if all of the characters in the provided string, text, are lowercase letters. + * Parameters + * $text + * The tested string. + * Return + * Returns TRUE if every character in text is a lowercase letter in the current locale. + */ +static int PH7_builtin_ctype_lower(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + const unsigned char *zIn,*zEnd; + int nLen; + if( nArg < 1 ){ + /* Missing arguments,return FALSE */ + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Extract the target string */ + zIn = (const unsigned char *)ph7_value_to_string(apArg[0],&nLen); + zEnd = &zIn[nLen]; + if( nLen < 1 ){ + /* Empty string,return FALSE */ + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Perform the requested operation */ + for(;;){ + if( zIn >= zEnd ){ + /* If we reach the end of the string,then the test succeeded. */ + ph7_result_bool(pCtx,1); + return PH7_OK; + } + if( !SyisLower(zIn[0]) ){ + break; + } + /* Point to the next character */ + zIn++; + } + /* The test failed,return FALSE */ + ph7_result_bool(pCtx,0); + return PH7_OK; +} +/* + * bool ctype_upper(string $text) + * Checks if all of the characters in the provided string, text, are uppercase letters. + * Parameters + * $text + * The tested string. + * Return + * Returns TRUE if every character in text is a uppercase letter in the current locale. + */ +static int PH7_builtin_ctype_upper(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + const unsigned char *zIn,*zEnd; + int nLen; + if( nArg < 1 ){ + /* Missing arguments,return FALSE */ + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Extract the target string */ + zIn = (const unsigned char *)ph7_value_to_string(apArg[0],&nLen); + zEnd = &zIn[nLen]; + if( nLen < 1 ){ + /* Empty string,return FALSE */ + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Perform the requested operation */ + for(;;){ + if( zIn >= zEnd ){ + /* If we reach the end of the string,then the test succeeded. */ + ph7_result_bool(pCtx,1); + return PH7_OK; + } + if( !SyisUpper(zIn[0]) ){ + break; + } + /* Point to the next character */ + zIn++; + } + /* The test failed,return FALSE */ + ph7_result_bool(pCtx,0); + return PH7_OK; +} +/* + * Date/Time functions + * Authors: + * Symisc Systems,devel@symisc.net. + * Copyright (C) Symisc Systems,http://ph7.symisc.net + * Status: + * Devel. + */ +#include +#ifdef __WINNT__ +/* GetSystemTime() */ +#include +#ifdef _WIN32_WCE +/* +** WindowsCE does not have a localtime() function. So create a +** substitute. +** Taken from the SQLite3 source tree. +** Status: Public domain +*/ +struct tm *__cdecl localtime(const time_t *t) +{ + static struct tm y; + FILETIME uTm, lTm; + SYSTEMTIME pTm; + ph7_int64 t64; + t64 = *t; + t64 = (t64 + 11644473600)*10000000; + uTm.dwLowDateTime = (DWORD)(t64 & 0xFFFFFFFF); + uTm.dwHighDateTime= (DWORD)(t64 >> 32); + FileTimeToLocalFileTime(&uTm,&lTm); + FileTimeToSystemTime(&lTm,&pTm); + y.tm_year = pTm.wYear - 1900; + y.tm_mon = pTm.wMonth - 1; + y.tm_wday = pTm.wDayOfWeek; + y.tm_mday = pTm.wDay; + y.tm_hour = pTm.wHour; + y.tm_min = pTm.wMinute; + y.tm_sec = pTm.wSecond; + return &y; +} +#endif /*_WIN32_WCE */ +#elif defined(__UNIXES__) +#include +#endif /* __WINNT__*/ + /* + * int64 time(void) + * Current Unix timestamp + * Parameters + * None. + * Return + * Returns the current time measured in the number of seconds + * since the Unix Epoch (January 1 1970 00:00:00 GMT). + */ +static int PH7_builtin_time(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + time_t tt; + SXUNUSED(nArg); /* cc warning */ + SXUNUSED(apArg); + /* Extract the current time */ + time(&tt); + /* Return as 64-bit integer */ + ph7_result_int64(pCtx,(ph7_int64)tt); + return PH7_OK; +} +/* + * string/float microtime([ bool $get_as_float = false ]) + * microtime() returns the current Unix timestamp with microseconds. + * Parameters + * $get_as_float + * If used and set to TRUE, microtime() will return a float instead of a string + * as described in the return values section below. + * Return + * By default, microtime() returns a string in the form "msec sec", where sec + * is the current time measured in the number of seconds since the Unix + * epoch (0:00:00 January 1, 1970 GMT), and msec is the number of microseconds + * that have elapsed since sec expressed in seconds. + * If get_as_float is set to TRUE, then microtime() returns a float, which represents + * the current time in seconds since the Unix epoch accurate to the nearest microsecond. + */ +static int PH7_builtin_microtime(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + int bFloat = 0; + sytime sTime; +#if defined(__UNIXES__) + struct timeval tv; + gettimeofday(&tv,0); + sTime.tm_sec = (long)tv.tv_sec; + sTime.tm_usec = (long)tv.tv_usec; +#else + time_t tt; + time(&tt); + sTime.tm_sec = (long)tt; + sTime.tm_usec = (long)(tt%SX_USEC_PER_SEC); +#endif /* __UNIXES__ */ + if( nArg > 0 ){ + bFloat = ph7_value_to_bool(apArg[0]); + } + if( bFloat ){ + /* Return as float */ + ph7_result_double(pCtx,(double)sTime.tm_sec); + }else{ + /* Return as string */ + ph7_result_string_format(pCtx,"%ld %ld",sTime.tm_usec,sTime.tm_sec); + } + return PH7_OK; +} +/* + * array getdate ([ int $timestamp = time() ]) + * Get date/time information. + * Parameter + * $timestamp: The optional timestamp parameter is an integer Unix timestamp + * that defaults to the current local time if a timestamp is not given. + * In other words, it defaults to the value of time(). + * Returns + * Returns an associative array of information related to the timestamp. + * Elements from the returned associative array are as follows: + * KEY VALUE + * --------- ------- + * "seconds" Numeric representation of seconds 0 to 59 + * "minutes" Numeric representation of minutes 0 to 59 + * "hours" Numeric representation of hours 0 to 23 + * "mday" Numeric representation of the day of the month 1 to 31 + * "wday" Numeric representation of the day of the week 0 (for Sunday) through 6 (for Saturday) + * "mon" Numeric representation of a month 1 through 12 + * "year" A full numeric representation of a year, 4 digits Examples: 1999 or 2003 + * "yday" Numeric representation of the day of the year 0 through 365 + * "weekday" A full textual representation of the day of the week Sunday through Saturday + * "month" A full textual representation of a month, such as January or March January through December + * 0 Seconds since the Unix Epoch, similar to the values returned by time() and used by date(). + * NOTE: + * NULL is returned on failure. + */ +static int PH7_builtin_getdate(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + ph7_value *pValue,*pArray; + Sytm sTm; + if( nArg < 1 ){ +#ifdef __WINNT__ + SYSTEMTIME sOS; + GetSystemTime(&sOS); + SYSTEMTIME_TO_SYTM(&sOS,&sTm); +#else + struct tm *pTm; + time_t t; + time(&t); + pTm = localtime(&t); + STRUCT_TM_TO_SYTM(pTm,&sTm); +#endif + }else{ + /* Use the given timestamp */ + time_t t; + struct tm *pTm; +#ifdef __WINNT__ +#ifdef _MSC_VER +#if _MSC_VER >= 1400 /* Visual Studio 2005 and up */ +#pragma warning(disable:4996) /* _CRT_SECURE...*/ +#endif +#endif +#endif + if( ph7_value_is_int(apArg[0]) ){ + t = (time_t)ph7_value_to_int64(apArg[0]); + pTm = localtime(&t); + if( pTm == 0 ){ + time(&t); + } + }else{ + time(&t); + } + pTm = localtime(&t); + STRUCT_TM_TO_SYTM(pTm,&sTm); + } + /* Element value */ + pValue = ph7_context_new_scalar(pCtx); + if( pValue == 0 ){ + /* Return NULL */ + ph7_result_null(pCtx); + return PH7_OK; + } + /* Create a new array */ + pArray = ph7_context_new_array(pCtx); + if( pArray == 0 ){ + /* Return NULL */ + ph7_result_null(pCtx); + return PH7_OK; + } + /* Fill the array */ + /* Seconds */ + ph7_value_int(pValue,sTm.tm_sec); + ph7_array_add_strkey_elem(pArray,"seconds",pValue); + /* Minutes */ + ph7_value_int(pValue,sTm.tm_min); + ph7_array_add_strkey_elem(pArray,"minutes",pValue); + /* Hours */ + ph7_value_int(pValue,sTm.tm_hour); + ph7_array_add_strkey_elem(pArray,"hours",pValue); + /* mday */ + ph7_value_int(pValue,sTm.tm_mday); + ph7_array_add_strkey_elem(pArray,"mday",pValue); + /* wday */ + ph7_value_int(pValue,sTm.tm_wday); + ph7_array_add_strkey_elem(pArray,"wday",pValue); + /* mon */ + ph7_value_int(pValue,sTm.tm_mon+1); + ph7_array_add_strkey_elem(pArray,"mon",pValue); + /* year */ + ph7_value_int(pValue,sTm.tm_year); + ph7_array_add_strkey_elem(pArray,"year",pValue); + /* yday */ + ph7_value_int(pValue,sTm.tm_yday); + ph7_array_add_strkey_elem(pArray,"yday",pValue); + /* Weekday */ + ph7_value_string(pValue,SyTimeGetDay(sTm.tm_wday),-1); + ph7_array_add_strkey_elem(pArray,"weekday",pValue); + /* Month */ + ph7_value_reset_string_cursor(pValue); + ph7_value_string(pValue,SyTimeGetMonth(sTm.tm_mon),-1); + ph7_array_add_strkey_elem(pArray,"month",pValue); + /* Seconds since the epoch */ + ph7_value_int64(pValue,(ph7_int64)time(0)); + ph7_array_add_intkey_elem(pArray,0 /* Index zero */,pValue); + /* Return the freshly created array */ + ph7_result_value(pCtx,pArray); + return PH7_OK; +} +/* + * mixed gettimeofday([ bool $return_float = false ] ) + * Returns an associative array containing the data returned from the system call. + * Parameters + * $return_float + * When set to TRUE, a float instead of an array is returned. + * Return + * By default an array is returned. If return_float is set, then + * a float is returned. + */ +static int PH7_builtin_gettimeofday(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + int bFloat = 0; + sytime sTime; +#if defined(__UNIXES__) + struct timeval tv; + gettimeofday(&tv,0); + sTime.tm_sec = (long)tv.tv_sec; + sTime.tm_usec = (long)tv.tv_usec; +#else + time_t tt; + time(&tt); + sTime.tm_sec = (long)tt; + sTime.tm_usec = (long)(tt%SX_USEC_PER_SEC); +#endif /* __UNIXES__ */ + if( nArg > 0 ){ + bFloat = ph7_value_to_bool(apArg[0]); + } + if( bFloat ){ + /* Return as float */ + ph7_result_double(pCtx,(double)sTime.tm_sec); + }else{ + /* Return an associative array */ + ph7_value *pValue,*pArray; + /* Create a new array */ + pArray = ph7_context_new_array(pCtx); + /* Element value */ + pValue = ph7_context_new_scalar(pCtx); + if( pValue == 0 || pArray == 0 ){ + /* Return NULL */ + ph7_result_null(pCtx); + return PH7_OK; + } + /* Fill the array */ + /* sec */ + ph7_value_int64(pValue,sTime.tm_sec); + ph7_array_add_strkey_elem(pArray,"sec",pValue); + /* usec */ + ph7_value_int64(pValue,sTime.tm_usec); + ph7_array_add_strkey_elem(pArray,"usec",pValue); + /* Return the array */ + ph7_result_value(pCtx,pArray); + } + return PH7_OK; +} +/* Check if the given year is leap or not */ +#define IS_LEAP_YEAR(YEAR) (YEAR % 400 ? ( YEAR % 100 ? ( YEAR % 4 ? 0 : 1 ) : 0 ) : 1) +/* ISO-8601 numeric representation of the day of the week */ +static const int aISO8601[] = { 7 /* Sunday */,1 /* Monday */,2,3,4,5,6 }; +/* + * Format a given date string. + * Supported format: (Taken from PHP online docs) + * character Description + * d Day of the month + * D A textual representation of a days + * j Day of the month without leading zeros + * l A full textual representation of the day of the week + * N ISO-8601 numeric representation of the day of the week + * w Numeric representation of the day of the week + * z The day of the year (starting from 0) + * F A full textual representation of a month, such as January or March + * m Numeric representation of a month, with leading zeros 01 through 12 + * M A short textual representation of a month, three letters Jan through Dec + * n Numeric representation of a month, without leading zeros 1 through 12 + * t Number of days in the given month 28 through 31 + * L Whether it's a leap year 1 if it is a leap year, 0 otherwise. + * o ISO-8601 year number. This has the same value as Y, except that if the ISO week number + * (W) belongs to the previous or next year, that year is used instead. (added in PHP 5.1.0) Examples: 1999 or 2003 + * Y A full numeric representation of a year, 4 digits Examples: 1999 or 2003 + * y A two digit representation of a year Examples: 99 or 03 + * a Lowercase Ante meridiem and Post meridiem am or pm + * A Uppercase Ante meridiem and Post meridiem AM or PM + * g 12-hour format of an hour without leading zeros 1 through 12 + * G 24-hour format of an hour without leading zeros 0 through 23 + * h 12-hour format of an hour with leading zeros 01 through 12 + * H 24-hour format of an hour with leading zeros 00 through 23 + * i Minutes with leading zeros 00 to 59 + * s Seconds, with leading zeros 00 through 59 + * u Microseconds Example: 654321 + * e Timezone identifier Examples: UTC, GMT, Atlantic/Azores + * I (capital i) Whether or not the date is in daylight saving time 1 if Daylight Saving Time, 0 otherwise. + * r RFC 2822 formatted date Example: Thu, 21 Dec 2000 16:01:07 +0200 + * U Seconds since the Unix Epoch (January 1 1970 00:00:00 GMT) + * S English ordinal suffix for the day of the month, 2 characters + * O Difference to Greenwich time (GMT) in hours + * Z Timezone offset in seconds. The offset for timezones west of UTC is always negative, and for those + * east of UTC is always positive. + * c ISO 8601 date + */ +static sxi32 DateFormat(ph7_context *pCtx,const char *zIn,int nLen,Sytm *pTm) +{ + const char *zEnd = &zIn[nLen]; + const char *zCur; + /* Start the format process */ + for(;;){ + if( zIn >= zEnd ){ + /* No more input to process */ + break; + } + switch(zIn[0]){ + case 'd': + /* Day of the month, 2 digits with leading zeros */ + ph7_result_string_format(pCtx,"%02d",pTm->tm_mday); + break; + case 'D': + /*A textual representation of a day, three letters*/ + zCur = SyTimeGetDay(pTm->tm_wday); + ph7_result_string(pCtx,zCur,3); + break; + case 'j': + /* Day of the month without leading zeros */ + ph7_result_string_format(pCtx,"%d",pTm->tm_mday); + break; + case 'l': + /* A full textual representation of the day of the week */ + zCur = SyTimeGetDay(pTm->tm_wday); + ph7_result_string(pCtx,zCur,-1/*Compute length automatically*/); + break; + case 'N':{ + /* ISO-8601 numeric representation of the day of the week */ + ph7_result_string_format(pCtx,"%d",aISO8601[pTm->tm_wday % 7 ]); + break; + } + case 'w': + /*Numeric representation of the day of the week*/ + ph7_result_string_format(pCtx,"%d",pTm->tm_wday); + break; + case 'z': + /*The day of the year*/ + ph7_result_string_format(pCtx,"%d",pTm->tm_yday); + break; + case 'F': + /*A full textual representation of a month, such as January or March*/ + zCur = SyTimeGetMonth(pTm->tm_mon); + ph7_result_string(pCtx,zCur,-1/*Compute length automatically*/); + break; + case 'm': + /*Numeric representation of a month, with leading zeros*/ + ph7_result_string_format(pCtx,"%02d",pTm->tm_mon + 1); + break; + case 'M': + /*A short textual representation of a month, three letters*/ + zCur = SyTimeGetMonth(pTm->tm_mon); + ph7_result_string(pCtx,zCur,3); + break; + case 'n': + /*Numeric representation of a month, without leading zeros*/ + ph7_result_string_format(pCtx,"%d",pTm->tm_mon + 1); + break; + case 't':{ + static const int aMonDays[] = {31,29,31,30,31,30,31,31,30,31,30,31 }; + int nDays = aMonDays[pTm->tm_mon % 12 ]; + if( pTm->tm_mon == 1 /* 'February' */ && !IS_LEAP_YEAR(pTm->tm_year) ){ + nDays = 28; + } + /*Number of days in the given month*/ + ph7_result_string_format(pCtx,"%d",nDays); + break; + } + case 'L':{ + int isLeap = IS_LEAP_YEAR(pTm->tm_year); + /* Whether it's a leap year */ + ph7_result_string_format(pCtx,"%d",isLeap); + break; + } + case 'o': + /* ISO-8601 year number.*/ + ph7_result_string_format(pCtx,"%4d",pTm->tm_year); + break; + case 'Y': + /* A full numeric representation of a year, 4 digits */ + ph7_result_string_format(pCtx,"%4d",pTm->tm_year); + break; + case 'y': + /*A two digit representation of a year*/ + ph7_result_string_format(pCtx,"%02d",pTm->tm_year%100); + break; + case 'a': + /* Lowercase Ante meridiem and Post meridiem */ + ph7_result_string(pCtx,pTm->tm_hour > 12 ? "pm" : "am",2); + break; + case 'A': + /* Uppercase Ante meridiem and Post meridiem */ + ph7_result_string(pCtx,pTm->tm_hour > 12 ? "PM" : "AM",2); + break; + case 'g': + /* 12-hour format of an hour without leading zeros*/ + ph7_result_string_format(pCtx,"%d",1+(pTm->tm_hour%12)); + break; + case 'G': + /* 24-hour format of an hour without leading zeros */ + ph7_result_string_format(pCtx,"%d",pTm->tm_hour); + break; + case 'h': + /* 12-hour format of an hour with leading zeros */ + ph7_result_string_format(pCtx,"%02d",1+(pTm->tm_hour%12)); + break; + case 'H': + /* 24-hour format of an hour with leading zeros */ + ph7_result_string_format(pCtx,"%02d",pTm->tm_hour); + break; + case 'i': + /* Minutes with leading zeros */ + ph7_result_string_format(pCtx,"%02d",pTm->tm_min); + break; + case 's': + /* second with leading zeros */ + ph7_result_string_format(pCtx,"%02d",pTm->tm_sec); + break; + case 'u': + /* Microseconds */ + ph7_result_string_format(pCtx,"%u",pTm->tm_sec * SX_USEC_PER_SEC); + break; + case 'S':{ + /* English ordinal suffix for the day of the month, 2 characters */ + static const char zSuffix[] = "thstndrdthththththth"; + int v = pTm->tm_mday; + ph7_result_string(pCtx,&zSuffix[2 * (int)(v / 10 % 10 != 1 ? v % 10 : 0)],(int)sizeof(char) * 2); + break; + } + case 'e': + /* Timezone identifier */ + zCur = pTm->tm_zone; + if( zCur == 0 ){ + /* Assume GMT */ + zCur = "GMT"; + } + ph7_result_string(pCtx,zCur,-1); + break; + case 'I': + /* Whether or not the date is in daylight saving time */ +#ifdef __WINNT__ +#ifdef _MSC_VER +#ifndef _WIN32_WCE + _get_daylight(&pTm->tm_isdst); +#endif +#endif +#endif + ph7_result_string_format(pCtx,"%d",pTm->tm_isdst == 1); + break; + case 'r': + /* RFC 2822 formatted date Example: Thu, 21 Dec 2000 16:01:07 */ + ph7_result_string_format(pCtx,"%.3s, %02d %.3s %4d %02d:%02d:%02d", + SyTimeGetDay(pTm->tm_wday), + pTm->tm_mday, + SyTimeGetMonth(pTm->tm_mon), + pTm->tm_year, + pTm->tm_hour, + pTm->tm_min, + pTm->tm_sec + ); + break; + case 'U':{ + time_t tt; + /* Seconds since the Unix Epoch */ + time(&tt); + ph7_result_string_format(pCtx,"%u",(unsigned int)tt); + break; + } + case 'O': + case 'P': + /* Difference to Greenwich time (GMT) in hours */ + ph7_result_string_format(pCtx,"%+05d",pTm->tm_gmtoff); + break; + case 'Z': + /* Timezone offset in seconds. The offset for timezones west of UTC + * is always negative, and for those east of UTC is always positive. + */ + ph7_result_string_format(pCtx,"%+05d",pTm->tm_gmtoff); + break; + case 'c': + /* ISO 8601 date */ + ph7_result_string_format(pCtx,"%4d-%02d-%02dT%02d:%02d:%02d%+05d", + pTm->tm_year, + pTm->tm_mon+1, + pTm->tm_mday, + pTm->tm_hour, + pTm->tm_min, + pTm->tm_sec, + pTm->tm_gmtoff + ); + break; + case '\\': + zIn++; + /* Expand verbatim */ + if( zIn < zEnd ){ + ph7_result_string(pCtx,zIn,(int)sizeof(char)); + } + break; + default: + /* Unknown format specifer,expand verbatim */ + ph7_result_string(pCtx,zIn,(int)sizeof(char)); + break; + } + /* Point to the next character */ + zIn++; + } + return SXRET_OK; +} +/* + * PH7 implementation of the strftime() function. + * The following formats are supported: + * %a An abbreviated textual representation of the day + * %A A full textual representation of the day + * %d Two-digit day of the month (with leading zeros) + * %e Day of the month, with a space preceding single digits. + * %j Day of the year, 3 digits with leading zeros + * %u ISO-8601 numeric representation of the day of the week 1 (for Monday) though 7 (for Sunday) + * %w Numeric representation of the day of the week 0 (for Sunday) through 6 (for Saturday) + * %U Week number of the given year, starting with the first Sunday as the first week + * %V ISO-8601:1988 week number of the given year, starting with the first week of the year with at least + * 4 weekdays, with Monday being the start of the week. + * %W A numeric representation of the week of the year + * %b Abbreviated month name, based on the locale + * %B Full month name, based on the locale + * %h Abbreviated month name, based on the locale (an alias of %b) + * %m Two digit representation of the month + * %C Two digit representation of the century (year divided by 100, truncated to an integer) + * %g Two digit representation of the year going by ISO-8601:1988 standards (see %V) + * %G The full four-digit version of %g + * %y Two digit representation of the year + * %Y Four digit representation for the year + * %H Two digit representation of the hour in 24-hour format + * %I Two digit representation of the hour in 12-hour format + * %l (lower-case 'L') Hour in 12-hour format, with a space preceeding single digits + * %M Two digit representation of the minute + * %p UPPER-CASE 'AM' or 'PM' based on the given time + * %P lower-case 'am' or 'pm' based on the given time + * %r Same as "%I:%M:%S %p" + * %R Same as "%H:%M" + * %S Two digit representation of the second + * %T Same as "%H:%M:%S" + * %X Preferred time representation based on locale, without the date + * %z Either the time zone offset from UTC or the abbreviation + * %Z The time zone offset/abbreviation option NOT given by %z + * %c Preferred date and time stamp based on local + * %D Same as "%m/%d/%y" + * %F Same as "%Y-%m-%d" + * %s Unix Epoch Time timestamp (same as the time() function) + * %x Preferred date representation based on locale, without the time + * %n A newline character ("\n") + * %t A Tab character ("\t") + * %% A literal percentage character ("%") + */ +static int PH7_Strftime( + ph7_context *pCtx, /* Call context */ + const char *zIn, /* Input string */ + int nLen, /* Input length */ + Sytm *pTm /* Parse of the given time */ + ) +{ + const char *zCur,*zEnd = &zIn[nLen]; + int c; + /* Start the format process */ + for(;;){ + zCur = zIn; + while(zIn < zEnd && zIn[0] != '%' ){ + zIn++; + } + if( zIn > zCur ){ + /* Consume input verbatim */ + ph7_result_string(pCtx,zCur,(int)(zIn-zCur)); + } + zIn++; /* Jump the percent sign */ + if( zIn >= zEnd ){ + /* No more input to process */ + break; + } + c = zIn[0]; + /* Act according to the current specifer */ + switch(c){ + case '%': + /* A literal percentage character ("%") */ + ph7_result_string(pCtx,"%",(int)sizeof(char)); + break; + case 't': + /* A Tab character */ + ph7_result_string(pCtx,"\t",(int)sizeof(char)); + break; + case 'n': + /* A newline character */ + ph7_result_string(pCtx,"\n",(int)sizeof(char)); + break; + case 'a': + /* An abbreviated textual representation of the day */ + ph7_result_string(pCtx,SyTimeGetDay(pTm->tm_wday),(int)sizeof(char)*3); + break; + case 'A': + /* A full textual representation of the day */ + ph7_result_string(pCtx,SyTimeGetDay(pTm->tm_wday),-1/*Compute length automatically*/); + break; + case 'e': + /* Day of the month, 2 digits with leading space for single digit*/ + ph7_result_string_format(pCtx,"%2d",pTm->tm_mday); + break; + case 'd': + /* Two-digit day of the month (with leading zeros) */ + ph7_result_string_format(pCtx,"%02d",pTm->tm_mon+1); + break; + case 'j': + /*The day of the year,3 digits with leading zeros*/ + ph7_result_string_format(pCtx,"%03d",pTm->tm_yday); + break; + case 'u': + /* ISO-8601 numeric representation of the day of the week */ + ph7_result_string_format(pCtx,"%d",aISO8601[pTm->tm_wday % 7 ]); + break; + case 'w': + /* Numeric representation of the day of the week */ + ph7_result_string_format(pCtx,"%d",pTm->tm_wday); + break; + case 'b': + case 'h': + /*A short textual representation of a month, three letters (Not based on locale)*/ + ph7_result_string(pCtx,SyTimeGetMonth(pTm->tm_mon),(int)sizeof(char)*3); + break; + case 'B': + /* Full month name (Not based on locale) */ + ph7_result_string(pCtx,SyTimeGetMonth(pTm->tm_mon),-1/*Compute length automatically*/); + break; + case 'm': + /*Numeric representation of a month, with leading zeros*/ + ph7_result_string_format(pCtx,"%02d",pTm->tm_mon + 1); + break; + case 'C': + /* Two digit representation of the century */ + ph7_result_string_format(pCtx,"%2d",pTm->tm_year/100); + break; + case 'y': + case 'g': + /* Two digit representation of the year */ + ph7_result_string_format(pCtx,"%2d",pTm->tm_year%100); + break; + case 'Y': + case 'G': + /* Four digit representation of the year */ + ph7_result_string_format(pCtx,"%4d",pTm->tm_year); + break; + case 'I': + /* 12-hour format of an hour with leading zeros */ + ph7_result_string_format(pCtx,"%02d",1+(pTm->tm_hour%12)); + break; + case 'l': + /* 12-hour format of an hour with leading space */ + ph7_result_string_format(pCtx,"%2d",1+(pTm->tm_hour%12)); + break; + case 'H': + /* 24-hour format of an hour with leading zeros */ + ph7_result_string_format(pCtx,"%02d",pTm->tm_hour); + break; + case 'M': + /* Minutes with leading zeros */ + ph7_result_string_format(pCtx,"%02d",pTm->tm_min); + break; + case 'S': + /* Seconds with leading zeros */ + ph7_result_string_format(pCtx,"%02d",pTm->tm_sec); + break; + case 'z': + case 'Z': + /* Timezone identifier */ + zCur = pTm->tm_zone; + if( zCur == 0 ){ + /* Assume GMT */ + zCur = "GMT"; + } + ph7_result_string(pCtx,zCur,-1); + break; + case 'T': + case 'X': + /* Same as "%H:%M:%S" */ + ph7_result_string_format(pCtx,"%02d:%02d:%02d",pTm->tm_hour,pTm->tm_min,pTm->tm_sec); + break; + case 'R': + /* Same as "%H:%M" */ + ph7_result_string_format(pCtx,"%02d:%02d",pTm->tm_hour,pTm->tm_min); + break; + case 'P': + /* Lowercase Ante meridiem and Post meridiem */ + ph7_result_string(pCtx,pTm->tm_hour > 12 ? "pm" : "am",(int)sizeof(char)*2); + break; + case 'p': + /* Uppercase Ante meridiem and Post meridiem */ + ph7_result_string(pCtx,pTm->tm_hour > 12 ? "PM" : "AM",(int)sizeof(char)*2); + break; + case 'r': + /* Same as "%I:%M:%S %p" */ + ph7_result_string_format(pCtx,"%02d:%02d:%02d %s", + 1+(pTm->tm_hour%12), + pTm->tm_min, + pTm->tm_sec, + pTm->tm_hour > 12 ? "PM" : "AM" + ); + break; + case 'D': + case 'x': + /* Same as "%m/%d/%y" */ + ph7_result_string_format(pCtx,"%02d/%02d/%02d", + pTm->tm_mon+1, + pTm->tm_mday, + pTm->tm_year%100 + ); + break; + case 'F': + /* Same as "%Y-%m-%d" */ + ph7_result_string_format(pCtx,"%d-%02d-%02d", + pTm->tm_year, + pTm->tm_mon+1, + pTm->tm_mday + ); + break; + case 'c': + ph7_result_string_format(pCtx,"%d-%02d-%02d %02d:%02d:%02d", + pTm->tm_year, + pTm->tm_mon+1, + pTm->tm_mday, + pTm->tm_hour, + pTm->tm_min, + pTm->tm_sec + ); + break; + case 's':{ + time_t tt; + /* Seconds since the Unix Epoch */ + time(&tt); + ph7_result_string_format(pCtx,"%u",(unsigned int)tt); + break; + } + default: + /* unknown specifer,simply ignore*/ + break; + } + /* Advance the cursor */ + zIn++; + } + return SXRET_OK; +} +/* + * string date(string $format [, int $timestamp = time() ] ) + * Returns a string formatted according to the given format string using + * the given integer timestamp or the current time if no timestamp is given. + * In other words, timestamp is optional and defaults to the value of time(). + * Parameters + * $format + * The format of the outputted date string (See code above) + * $timestamp + * The optional timestamp parameter is an integer Unix timestamp + * that defaults to the current local time if a timestamp is not given. + * In other words, it defaults to the value of time(). + * Return + * A formatted date string. If a non-numeric value is used for timestamp, FALSE is returned. + */ +static int PH7_builtin_date(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + const char *zFormat; + int nLen; + Sytm sTm; + if( nArg < 1 || !ph7_value_is_string(apArg[0]) ){ + /* Missing/Invalid argument,return FALSE */ + ph7_result_bool(pCtx,0); + return PH7_OK; + } + zFormat = ph7_value_to_string(apArg[0],&nLen); + if( nLen < 1 ){ + /* Don't bother processing return the empty string */ + ph7_result_string(pCtx,"",0); + } + if( nArg < 2 ){ +#ifdef __WINNT__ + SYSTEMTIME sOS; + GetSystemTime(&sOS); + SYSTEMTIME_TO_SYTM(&sOS,&sTm); +#else + struct tm *pTm; + time_t t; + time(&t); + pTm = localtime(&t); + STRUCT_TM_TO_SYTM(pTm,&sTm); +#endif + }else{ + /* Use the given timestamp */ + time_t t; + struct tm *pTm; + if( ph7_value_is_int(apArg[1]) ){ + t = (time_t)ph7_value_to_int64(apArg[1]); + pTm = localtime(&t); + if( pTm == 0 ){ + time(&t); + } + }else{ + time(&t); + } + pTm = localtime(&t); + STRUCT_TM_TO_SYTM(pTm,&sTm); + } + /* Format the given string */ + DateFormat(pCtx,zFormat,nLen,&sTm); + return PH7_OK; +} +/* + * string strftime(string $format [, int $timestamp = time() ] ) + * Format a local time/date (PLATFORM INDEPENDANT IMPLEENTATION NOT BASED ON LOCALE) + * Parameters + * $format + * The format of the outputted date string (See code above) + * $timestamp + * The optional timestamp parameter is an integer Unix timestamp + * that defaults to the current local time if a timestamp is not given. + * In other words, it defaults to the value of time(). + * Return + * Returns a string formatted according format using the given timestamp + * or the current local time if no timestamp is given. + */ +static int PH7_builtin_strftime(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + const char *zFormat; + int nLen; + Sytm sTm; + if( nArg < 1 || !ph7_value_is_string(apArg[0]) ){ + /* Missing/Invalid argument,return FALSE */ + ph7_result_bool(pCtx,0); + return PH7_OK; + } + zFormat = ph7_value_to_string(apArg[0],&nLen); + if( nLen < 1 ){ + /* Don't bother processing return FALSE */ + ph7_result_bool(pCtx,0); + } + if( nArg < 2 ){ +#ifdef __WINNT__ + SYSTEMTIME sOS; + GetSystemTime(&sOS); + SYSTEMTIME_TO_SYTM(&sOS,&sTm); +#else + struct tm *pTm; + time_t t; + time(&t); + pTm = localtime(&t); + STRUCT_TM_TO_SYTM(pTm,&sTm); +#endif + }else{ + /* Use the given timestamp */ + time_t t; + struct tm *pTm; + if( ph7_value_is_int(apArg[1]) ){ + t = (time_t)ph7_value_to_int64(apArg[1]); + pTm = localtime(&t); + if( pTm == 0 ){ + time(&t); + } + }else{ + time(&t); + } + pTm = localtime(&t); + STRUCT_TM_TO_SYTM(pTm,&sTm); + } + /* Format the given string */ + PH7_Strftime(pCtx,zFormat,nLen,&sTm); + if( ph7_context_result_buf_length(pCtx) < 1 ){ + /* Nothing was formatted,return FALSE */ + ph7_result_bool(pCtx,0); + } + return PH7_OK; +} +/* + * string gmdate(string $format [, int $timestamp = time() ] ) + * Identical to the date() function except that the time returned + * is Greenwich Mean Time (GMT). + * Parameters + * $format + * The format of the outputted date string (See code above) + * $timestamp + * The optional timestamp parameter is an integer Unix timestamp + * that defaults to the current local time if a timestamp is not given. + * In other words, it defaults to the value of time(). + * Return + * A formatted date string. If a non-numeric value is used for timestamp, FALSE is returned. + */ +static int PH7_builtin_gmdate(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + const char *zFormat; + int nLen; + Sytm sTm; + if( nArg < 1 || !ph7_value_is_string(apArg[0]) ){ + /* Missing/Invalid argument,return FALSE */ + ph7_result_bool(pCtx,0); + return PH7_OK; + } + zFormat = ph7_value_to_string(apArg[0],&nLen); + if( nLen < 1 ){ + /* Don't bother processing return the empty string */ + ph7_result_string(pCtx,"",0); + } + if( nArg < 2 ){ +#ifdef __WINNT__ + SYSTEMTIME sOS; + GetSystemTime(&sOS); + SYSTEMTIME_TO_SYTM(&sOS,&sTm); +#else + struct tm *pTm; + time_t t; + time(&t); + pTm = gmtime(&t); + STRUCT_TM_TO_SYTM(pTm,&sTm); +#endif + }else{ + /* Use the given timestamp */ + time_t t; + struct tm *pTm; + if( ph7_value_is_int(apArg[1]) ){ + t = (time_t)ph7_value_to_int64(apArg[1]); + pTm = gmtime(&t); + if( pTm == 0 ){ + time(&t); + } + }else{ + time(&t); + } + pTm = gmtime(&t); + STRUCT_TM_TO_SYTM(pTm,&sTm); + } + /* Format the given string */ + DateFormat(pCtx,zFormat,nLen,&sTm); + return PH7_OK; +} +/* + * array localtime([ int $timestamp = time() [, bool $is_associative = false ]]) + * Return the local time. + * Parameter + * $timestamp: The optional timestamp parameter is an integer Unix timestamp + * that defaults to the current local time if a timestamp is not given. + * In other words, it defaults to the value of time(). + * $is_associative + * If set to FALSE or not supplied then the array is returned as a regular, numerically + * indexed array. If the argument is set to TRUE then localtime() returns an associative + * array containing all the different elements of the structure returned by the C function + * call to localtime. The names of the different keys of the associative array are as follows: + * "tm_sec" - seconds, 0 to 59 + * "tm_min" - minutes, 0 to 59 + * "tm_hour" - hours, 0 to 23 + * "tm_mday" - day of the month, 1 to 31 + * "tm_mon" - month of the year, 0 (Jan) to 11 (Dec) + * "tm_year" - years since 1900 + * "tm_wday" - day of the week, 0 (Sun) to 6 (Sat) + * "tm_yday" - day of the year, 0 to 365 + * "tm_isdst" - is daylight savings time in effect? Positive if yes, 0 if not, negative if unknown. + * Returns + * An associative array of information related to the timestamp. + */ +static int PH7_builtin_localtime(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + ph7_value *pValue,*pArray; + int isAssoc = 0; + Sytm sTm; + if( nArg < 1 ){ +#ifdef __WINNT__ + SYSTEMTIME sOS; + GetSystemTime(&sOS); /* TODO(chems): GMT not local */ + SYSTEMTIME_TO_SYTM(&sOS,&sTm); +#else + struct tm *pTm; + time_t t; + time(&t); + pTm = localtime(&t); + STRUCT_TM_TO_SYTM(pTm,&sTm); +#endif + }else{ + /* Use the given timestamp */ + time_t t; + struct tm *pTm; + if( ph7_value_is_int(apArg[0]) ){ + t = (time_t)ph7_value_to_int64(apArg[0]); + pTm = localtime(&t); + if( pTm == 0 ){ + time(&t); + } + }else{ + time(&t); + } + pTm = localtime(&t); + STRUCT_TM_TO_SYTM(pTm,&sTm); + } + /* Element value */ + pValue = ph7_context_new_scalar(pCtx); + if( pValue == 0 ){ + /* Return NULL */ + ph7_result_null(pCtx); + return PH7_OK; + } + /* Create a new array */ + pArray = ph7_context_new_array(pCtx); + if( pArray == 0 ){ + /* Return NULL */ + ph7_result_null(pCtx); + return PH7_OK; + } + if( nArg > 1 ){ + isAssoc = ph7_value_to_bool(apArg[1]); + } + /* Fill the array */ + /* Seconds */ + ph7_value_int(pValue,sTm.tm_sec); + if( isAssoc ){ + ph7_array_add_strkey_elem(pArray,"tm_sec",pValue); + }else{ + ph7_array_add_elem(pArray,0/* Automatic index */,pValue); + } + /* Minutes */ + ph7_value_int(pValue,sTm.tm_min); + if( isAssoc ){ + ph7_array_add_strkey_elem(pArray,"tm_min",pValue); + }else{ + ph7_array_add_elem(pArray,0/* Automatic index */,pValue); + } + /* Hours */ + ph7_value_int(pValue,sTm.tm_hour); + if( isAssoc ){ + ph7_array_add_strkey_elem(pArray,"tm_hour",pValue); + }else{ + ph7_array_add_elem(pArray,0/* Automatic index */,pValue); + } + /* mday */ + ph7_value_int(pValue,sTm.tm_mday); + if( isAssoc ){ + ph7_array_add_strkey_elem(pArray,"tm_mday",pValue); + }else{ + ph7_array_add_elem(pArray,0/* Automatic index */,pValue); + } + /* mon */ + ph7_value_int(pValue,sTm.tm_mon); + if( isAssoc ){ + ph7_array_add_strkey_elem(pArray,"tm_mon",pValue); + }else{ + ph7_array_add_elem(pArray,0/* Automatic index */,pValue); + } + /* year since 1900 */ + ph7_value_int(pValue,sTm.tm_year-1900); + if( isAssoc ){ + ph7_array_add_strkey_elem(pArray,"tm_year",pValue); + }else{ + ph7_array_add_elem(pArray,0/* Automatic index */,pValue); + } + /* wday */ + ph7_value_int(pValue,sTm.tm_wday); + if( isAssoc ){ + ph7_array_add_strkey_elem(pArray,"tm_wday",pValue); + }else{ + ph7_array_add_elem(pArray,0/* Automatic index */,pValue); + } + /* yday */ + ph7_value_int(pValue,sTm.tm_yday); + if( isAssoc ){ + ph7_array_add_strkey_elem(pArray,"tm_yday",pValue); + }else{ + ph7_array_add_elem(pArray,0/* Automatic index */,pValue); + } + /* isdst */ +#ifdef __WINNT__ +#ifdef _MSC_VER +#ifndef _WIN32_WCE + _get_daylight(&sTm.tm_isdst); +#endif +#endif +#endif + ph7_value_int(pValue,sTm.tm_isdst); + if( isAssoc ){ + ph7_array_add_strkey_elem(pArray,"tm_isdst",pValue); + }else{ + ph7_array_add_elem(pArray,0/* Automatic index */,pValue); + } + /* Return the array */ + ph7_result_value(pCtx,pArray); + return PH7_OK; +} +/* + * int idate(string $format [, int $timestamp = time() ]) + * Returns a number formatted according to the given format string + * using the given integer timestamp or the current local time if + * no timestamp is given. In other words, timestamp is optional and defaults + * to the value of time(). + * Unlike the function date(), idate() accepts just one char in the format + * parameter. + * $Parameters + * Supported format + * d Day of the month + * h Hour (12 hour format) + * H Hour (24 hour format) + * i Minutes + * I (uppercase i)1 if DST is activated, 0 otherwise + * L (uppercase l) returns 1 for leap year, 0 otherwise + * m Month number + * s Seconds + * t Days in current month + * U Seconds since the Unix Epoch - January 1 1970 00:00:00 UTC - this is the same as time() + * w Day of the week (0 on Sunday) + * W ISO-8601 week number of year, weeks starting on Monday + * y Year (1 or 2 digits - check note below) + * Y Year (4 digits) + * z Day of the year + * Z Timezone offset in seconds + * $timestamp + * The optional timestamp parameter is an integer Unix timestamp that defaults + * to the current local time if a timestamp is not given. In other words, it defaults + * to the value of time(). + * Return + * An integer. + */ +static int PH7_builtin_idate(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + const char *zFormat; + ph7_int64 iVal = 0; + int nLen; + Sytm sTm; + if( nArg < 1 || !ph7_value_is_string(apArg[0]) ){ + /* Missing/Invalid argument,return -1 */ + ph7_result_int(pCtx,-1); + return PH7_OK; + } + zFormat = ph7_value_to_string(apArg[0],&nLen); + if( nLen < 1 ){ + /* Don't bother processing return -1*/ + ph7_result_int(pCtx,-1); + } + if( nArg < 2 ){ +#ifdef __WINNT__ + SYSTEMTIME sOS; + GetSystemTime(&sOS); + SYSTEMTIME_TO_SYTM(&sOS,&sTm); +#else + struct tm *pTm; + time_t t; + time(&t); + pTm = localtime(&t); + STRUCT_TM_TO_SYTM(pTm,&sTm); +#endif + }else{ + /* Use the given timestamp */ + time_t t; + struct tm *pTm; + if( ph7_value_is_int(apArg[1]) ){ + t = (time_t)ph7_value_to_int64(apArg[1]); + pTm = localtime(&t); + if( pTm == 0 ){ + time(&t); + } + }else{ + time(&t); + } + pTm = localtime(&t); + STRUCT_TM_TO_SYTM(pTm,&sTm); + } + /* Perform the requested operation */ + switch(zFormat[0]){ + case 'd': + /* Day of the month */ + iVal = sTm.tm_mday; + break; + case 'h': + /* Hour (12 hour format)*/ + iVal = 1 + (sTm.tm_hour % 12); + break; + case 'H': + /* Hour (24 hour format)*/ + iVal = sTm.tm_hour; + break; + case 'i': + /*Minutes*/ + iVal = sTm.tm_min; + break; + case 'I': + /* returns 1 if DST is activated, 0 otherwise */ +#ifdef __WINNT__ +#ifdef _MSC_VER +#ifndef _WIN32_WCE + _get_daylight(&sTm.tm_isdst); +#endif +#endif +#endif + iVal = sTm.tm_isdst; + break; + case 'L': + /* returns 1 for leap year, 0 otherwise */ + iVal = IS_LEAP_YEAR(sTm.tm_year); + break; + case 'm': + /* Month number*/ + iVal = sTm.tm_mon; + break; + case 's': + /*Seconds*/ + iVal = sTm.tm_sec; + break; + case 't':{ + /*Days in current month*/ + static const int aMonDays[] = {31,29,31,30,31,30,31,31,30,31,30,31 }; + int nDays = aMonDays[sTm.tm_mon % 12 ]; + if( sTm.tm_mon == 1 /* 'February' */ && !IS_LEAP_YEAR(sTm.tm_year) ){ + nDays = 28; + } + iVal = nDays; + break; + } + case 'U': + /*Seconds since the Unix Epoch*/ + iVal = (ph7_int64)time(0); + break; + case 'w': + /* Day of the week (0 on Sunday) */ + iVal = sTm.tm_wday; + break; + case 'W': { + /* ISO-8601 week number of year, weeks starting on Monday */ + static const int aISO8601[] = { 7 /* Sunday */,1 /* Monday */,2,3,4,5,6 }; + iVal = aISO8601[sTm.tm_wday % 7 ]; + break; + } + case 'y': + /* Year (2 digits) */ + iVal = sTm.tm_year % 100; + break; + case 'Y': + /* Year (4 digits) */ + iVal = sTm.tm_year; + break; + case 'z': + /* Day of the year */ + iVal = sTm.tm_yday; + break; + case 'Z': + /*Timezone offset in seconds*/ + iVal = sTm.tm_gmtoff; + break; + default: + /* unknown format,throw a warning */ + ph7_context_throw_error(pCtx,PH7_CTX_WARNING,"Unknown date format token"); + break; + } + /* Return the time value */ + ph7_result_int64(pCtx,iVal); + return PH7_OK; +} +/* + * int mktime/gmmktime([ int $hour = date("H") [, int $minute = date("i") [, int $second = date("s") + * [, int $month = date("n") [, int $day = date("j") [, int $year = date("Y") [, int $is_dst = -1 ]]]]]]] ) + * Returns the Unix timestamp corresponding to the arguments given. This timestamp is a 64bit integer + * containing the number of seconds between the Unix Epoch (January 1 1970 00:00:00 GMT) and the time + * specified. + * Arguments may be left out in order from right to left; any arguments thus omitted will be set to + * the current value according to the local date and time. + * Parameters + * $hour + * The number of the hour relevant to the start of the day determined by month, day and year. + * Negative values reference the hour before midnight of the day in question. Values greater + * than 23 reference the appropriate hour in the following day(s). + * $minute + * The number of the minute relevant to the start of the hour. Negative values reference + * the minute in the previous hour. Values greater than 59 reference the appropriate minute + * in the following hour(s). + * $second + * The number of seconds relevant to the start of the minute. Negative values reference + * the second in the previous minute. Values greater than 59 reference the appropriate + * second in the following minute(s). + * $month + * The number of the month relevant to the end of the previous year. Values 1 to 12 reference + * the normal calendar months of the year in question. Values less than 1 (including negative values) + * reference the months in the previous year in reverse order, so 0 is December, -1 is November)... + * $day + * The number of the day relevant to the end of the previous month. Values 1 to 28, 29, 30 or 31 + * (depending upon the month) reference the normal days in the relevant month. Values less than 1 + * (including negative values) reference the days in the previous month, so 0 is the last day + * of the previous month, -1 is the day before that, etc. Values greater than the number of days + * in the relevant month reference the appropriate day in the following month(s). + * $year + * The number of the year, may be a two or four digit value, with values between 0-69 mapping + * to 2000-2069 and 70-100 to 1970-2000. On systems where time_t is a 32bit signed integer, as + * most common today, the valid range for year is somewhere between 1901 and 2038. + * $is_dst + * This parameter can be set to 1 if the time is during daylight savings time (DST), 0 if it is not, + * or -1 (the default) if it is unknown whether the time is within daylight savings time or not. + * Return + * mktime() returns the Unix timestamp of the arguments given. + * If the arguments are invalid, the function returns FALSE + */ +static int PH7_builtin_mktime(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + const char *zFunction; + ph7_int64 iVal = 0; + struct tm *pTm; + time_t t; + /* Extract function name */ + zFunction = ph7_function_name(pCtx); + /* Get the current time */ + time(&t); + if( zFunction[0] == 'g' /* gmmktime */ ){ + pTm = gmtime(&t); + }else{ + /* localtime */ + pTm = localtime(&t); + } + if( nArg > 0 ){ + int iVal; + /* Hour */ + iVal = ph7_value_to_int(apArg[0]); + pTm->tm_hour = iVal; + if( nArg > 1 ){ + /* Minutes */ + iVal = ph7_value_to_int(apArg[1]); + pTm->tm_min = iVal; + if( nArg > 2 ){ + /* Seconds */ + iVal = ph7_value_to_int(apArg[2]); + pTm->tm_sec = iVal; + if( nArg > 3 ){ + /* Month */ + iVal = ph7_value_to_int(apArg[3]); + pTm->tm_mon = iVal - 1; + if( nArg > 4 ){ + /* mday */ + iVal = ph7_value_to_int(apArg[4]); + pTm->tm_mday = iVal; + if( nArg > 5 ){ + /* Year */ + iVal = ph7_value_to_int(apArg[5]); + if( iVal > 1900 ){ + iVal -= 1900; + } + pTm->tm_year = iVal; + if( nArg > 6 ){ + /* is_dst */ + iVal = ph7_value_to_bool(apArg[6]); + pTm->tm_isdst = iVal; + } + } + } + } + } + } + } + /* Make the time */ + iVal = (ph7_int64)mktime(pTm); + /* Return the timesatmp as a 64bit integer */ + ph7_result_int64(pCtx,iVal); + return PH7_OK; +} +/* + * Section: + * URL handling Functions. + * Authors: + * Symisc Systems,devel@symisc.net. + * Copyright (C) Symisc Systems,http://ph7.symisc.net + * Status: + * Stable. + */ +/* + * Output consumer callback for the standard Symisc routines. + * [i.e: SyBase64Encode(),SyBase64Decode(),SyUriEncode(),...]. + */ +static int Consumer(const void *pData,unsigned int nLen,void *pUserData) +{ + /* Store in the call context result buffer */ + ph7_result_string((ph7_context *)pUserData,(const char *)pData,(int)nLen); + return SXRET_OK; +} +/* + * string base64_encode(string $data) + * string convert_uuencode(string $data) + * Encodes data with MIME base64 + * Parameter + * $data + * Data to encode + * Return + * Encoded data or FALSE on failure. + */ +static int PH7_builtin_base64_encode(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + const char *zIn; + int nLen; + if( nArg < 1 ){ + /* Missing arguments,return FALSE */ + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Extract the input string */ + zIn = ph7_value_to_string(apArg[0],&nLen); + if( nLen < 1 ){ + /* Nothing to process,return FALSE */ + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Perform the BASE64 encoding */ + SyBase64Encode(zIn,(sxu32)nLen,Consumer,pCtx); + return PH7_OK; +} +/* + * string base64_decode(string $data) + * string convert_uudecode(string $data) + * Decodes data encoded with MIME base64 + * Parameter + * $data + * Encoded data. + * Return + * Returns the original data or FALSE on failure. + */ +static int PH7_builtin_base64_decode(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + const char *zIn; + int nLen; + if( nArg < 1 ){ + /* Missing arguments,return FALSE */ + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Extract the input string */ + zIn = ph7_value_to_string(apArg[0],&nLen); + if( nLen < 1 ){ + /* Nothing to process,return FALSE */ + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Perform the BASE64 decoding */ + SyBase64Decode(zIn,(sxu32)nLen,Consumer,pCtx); + return PH7_OK; +} +/* + * string urlencode(string $str) + * URL encoding + * Parameter + * $data + * Input string. + * Return + * Returns a string in which all non-alphanumeric characters except -_. have + * been replaced with a percent (%) sign followed by two hex digits and spaces + * encoded as plus (+) signs. + */ +static int PH7_builtin_urlencode(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + const char *zIn; + int nLen; + if( nArg < 1 ){ + /* Missing arguments,return FALSE */ + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Extract the input string */ + zIn = ph7_value_to_string(apArg[0],&nLen); + if( nLen < 1 ){ + /* Nothing to process,return FALSE */ + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Perform the URL encoding */ + SyUriEncode(zIn,(sxu32)nLen,Consumer,pCtx); + return PH7_OK; +} +/* + * string urldecode(string $str) + * Decodes any %## encoding in the given string. + * Plus symbols ('+') are decoded to a space character. + * Parameter + * $data + * Input string. + * Return + * Decoded URL or FALSE on failure. + */ +static int PH7_builtin_urldecode(ph7_context *pCtx,int nArg,ph7_value **apArg) +{ + const char *zIn; + int nLen; + if( nArg < 1 ){ + /* Missing arguments,return FALSE */ + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Extract the input string */ + zIn = ph7_value_to_string(apArg[0],&nLen); + if( nLen < 1 ){ + /* Nothing to process,return FALSE */ + ph7_result_bool(pCtx,0); + return PH7_OK; + } + /* Perform the URL decoding */ + SyUriDecode(zIn,(sxu32)nLen,Consumer,pCtx,TRUE); + return PH7_OK; +} +#endif /* PH7_DISABLE_BUILTIN_FUNC */ +/* Table of the built-in functions */ +static const ph7_builtin_func aBuiltInFunc[] = { + /* Variable handling functions */ + { "is_bool" , PH7_builtin_is_bool }, + { "is_float" , PH7_builtin_is_float }, + { "is_real" , PH7_builtin_is_float }, + { "is_double" , PH7_builtin_is_float }, + { "is_int" , PH7_builtin_is_int }, + { "is_integer" , PH7_builtin_is_int }, + { "is_long" , PH7_builtin_is_int }, + { "is_string" , PH7_builtin_is_string }, + { "is_null" , PH7_builtin_is_null }, + { "is_numeric" , PH7_builtin_is_numeric }, + { "is_scalar" , PH7_builtin_is_scalar }, + { "is_array" , PH7_builtin_is_array }, + { "is_object" , PH7_builtin_is_object }, + { "is_resource", PH7_builtin_is_resource }, + { "douleval" , PH7_builtin_floatval }, + { "floatval" , PH7_builtin_floatval }, + { "intval" , PH7_builtin_intval }, + { "strval" , PH7_builtin_strval }, + { "empty" , PH7_builtin_empty }, +#ifndef PH7_DISABLE_BUILTIN_FUNC +#ifdef PH7_ENABLE_MATH_FUNC + /* Math functions */ + { "abs" , PH7_builtin_abs }, + { "sqrt" , PH7_builtin_sqrt }, + { "exp" , PH7_builtin_exp }, + { "floor", PH7_builtin_floor }, + { "cos" , PH7_builtin_cos }, + { "sin" , PH7_builtin_sin }, + { "acos" , PH7_builtin_acos }, + { "asin" , PH7_builtin_asin }, + { "cosh" , PH7_builtin_cosh }, + { "sinh" , PH7_builtin_sinh }, + { "ceil" , PH7_builtin_ceil }, + { "tan" , PH7_builtin_tan }, + { "tanh" , PH7_builtin_tanh }, + { "atan" , PH7_builtin_atan }, + { "atan2", PH7_builtin_atan2 }, + { "log" , PH7_builtin_log }, + { "log10" , PH7_builtin_log10 }, + { "pow" , PH7_builtin_pow }, + { "pi", PH7_builtin_pi }, + { "fmod", PH7_builtin_fmod }, + { "hypot", PH7_builtin_hypot }, +#endif /* PH7_ENABLE_MATH_FUNC */ + { "round", PH7_builtin_round }, + { "dechex", PH7_builtin_dechex }, + { "decoct", PH7_builtin_decoct }, + { "decbin", PH7_builtin_decbin }, + { "hexdec", PH7_builtin_hexdec }, + { "bindec", PH7_builtin_bindec }, + { "octdec", PH7_builtin_octdec }, + { "srand", PH7_builtin_srand }, + { "mt_srand",PH7_builtin_srand }, + { "base_convert", PH7_builtin_base_convert }, + /* String handling functions */ + { "substr", PH7_builtin_substr }, + { "substr_compare", PH7_builtin_substr_compare }, + { "substr_count", PH7_builtin_substr_count }, + { "chunk_split", PH7_builtin_chunk_split}, + { "addslashes" , PH7_builtin_addslashes }, + { "addcslashes", PH7_builtin_addcslashes}, + { "quotemeta", PH7_builtin_quotemeta }, + { "stripslashes", PH7_builtin_stripslashes }, + { "htmlspecialchars",PH7_builtin_htmlspecialchars }, + { "htmlspecialchars_decode", PH7_builtin_htmlspecialchars_decode }, + { "get_html_translation_table",PH7_builtin_get_html_translation_table }, + { "htmlentities",PH7_builtin_htmlentities}, + { "html_entity_decode", PH7_builtin_html_entity_decode}, + { "strlen" , PH7_builtin_strlen }, + { "strcmp" , PH7_builtin_strcmp }, + { "strcoll" , PH7_builtin_strcmp }, + { "strncmp" , PH7_builtin_strncmp }, + { "strcasecmp" , PH7_builtin_strcasecmp }, + { "strncasecmp", PH7_builtin_strncasecmp}, + { "implode" , PH7_builtin_implode }, + { "join" , PH7_builtin_implode }, + { "implode_recursive" , PH7_builtin_implode_recursive }, + { "join_recursive" , PH7_builtin_implode_recursive }, + { "explode" , PH7_builtin_explode }, + { "trim" , PH7_builtin_trim }, + { "rtrim" , PH7_builtin_rtrim }, + { "chop" , PH7_builtin_rtrim }, + { "ltrim" , PH7_builtin_ltrim }, + { "strtolower", PH7_builtin_strtolower }, + { "mb_strtolower",PH7_builtin_strtolower }, /* Only UTF-8 encoding is supported */ + { "strtoupper", PH7_builtin_strtoupper }, + { "mb_strtoupper",PH7_builtin_strtoupper }, /* Only UTF-8 encoding is supported */ + { "ucfirst", PH7_builtin_ucfirst }, + { "lcfirst", PH7_builtin_lcfirst }, + { "ord", PH7_builtin_ord }, + { "chr", PH7_builtin_chr }, + { "bin2hex", PH7_builtin_bin2hex }, + { "strstr", PH7_builtin_strstr }, + { "stristr", PH7_builtin_stristr }, + { "strchr", PH7_builtin_strstr }, + { "strpos", PH7_builtin_strpos }, + { "stripos", PH7_builtin_stripos }, + { "strrpos", PH7_builtin_strrpos }, + { "strripos", PH7_builtin_strripos }, + { "strrchr", PH7_builtin_strrchr }, + { "strrev", PH7_builtin_strrev }, + { "ucwords", PH7_builtin_ucwords }, + { "str_repeat", PH7_builtin_str_repeat }, + { "nl2br", PH7_builtin_nl2br }, + { "sprintf", PH7_builtin_sprintf }, + { "printf", PH7_builtin_printf }, + { "vprintf", PH7_builtin_vprintf }, + { "vsprintf", PH7_builtin_vsprintf }, + { "size_format", PH7_builtin_size_format}, +#if !defined(PH7_DISABLE_HASH_FUNC) + { "md5", PH7_builtin_md5 }, + { "sha1", PH7_builtin_sha1 }, + { "crc32", PH7_builtin_crc32 }, +#endif /* PH7_DISABLE_HASH_FUNC */ + { "str_getcsv", PH7_builtin_str_getcsv }, + { "strip_tags", PH7_builtin_strip_tags }, + { "str_shuffle", PH7_builtin_str_shuffle}, + { "str_split", PH7_builtin_str_split }, + { "strspn", PH7_builtin_strspn }, + { "strcspn", PH7_builtin_strcspn }, + { "strpbrk", PH7_builtin_strpbrk }, + { "soundex", PH7_builtin_soundex }, + { "wordwrap", PH7_builtin_wordwrap }, + { "strtok", PH7_builtin_strtok }, + { "str_pad", PH7_builtin_str_pad }, + { "str_replace", PH7_builtin_str_replace}, + { "str_ireplace", PH7_builtin_str_replace}, + { "strtr", PH7_builtin_strtr }, + { "parse_ini_string", PH7_builtin_parse_ini_string}, + /* Ctype functions */ + { "ctype_alnum", PH7_builtin_ctype_alnum }, + { "ctype_alpha", PH7_builtin_ctype_alpha }, + { "ctype_cntrl", PH7_builtin_ctype_cntrl }, + { "ctype_digit", PH7_builtin_ctype_digit }, + { "ctype_xdigit",PH7_builtin_ctype_xdigit}, + { "ctype_graph", PH7_builtin_ctype_graph }, + { "ctype_print", PH7_builtin_ctype_print }, + { "ctype_punct", PH7_builtin_ctype_punct }, + { "ctype_space", PH7_builtin_ctype_space }, + { "ctype_lower", PH7_builtin_ctype_lower }, + { "ctype_upper", PH7_builtin_ctype_upper }, + /* Time functions */ + { "time" , PH7_builtin_time }, + { "microtime", PH7_builtin_microtime }, + { "getdate" , PH7_builtin_getdate }, + { "gettimeofday",PH7_builtin_gettimeofday }, + { "date", PH7_builtin_date }, + { "strftime", PH7_builtin_strftime }, + { "idate", PH7_builtin_idate }, + { "gmdate", PH7_builtin_gmdate }, + { "localtime", PH7_builtin_localtime }, + { "mktime", PH7_builtin_mktime }, + { "gmmktime", PH7_builtin_mktime }, + /* URL functions */ + { "base64_encode",PH7_builtin_base64_encode }, + { "base64_decode",PH7_builtin_base64_decode }, + { "convert_uuencode",PH7_builtin_base64_encode }, + { "convert_uudecode",PH7_builtin_base64_decode }, + { "urlencode", PH7_builtin_urlencode }, + { "urldecode", PH7_builtin_urldecode }, + { "rawurlencode", PH7_builtin_urlencode }, + { "rawurldecode", PH7_builtin_urldecode }, +#endif /* PH7_DISABLE_BUILTIN_FUNC */ +}; +/* + * Register the built-in functions defined above,the array functions + * defined in hashmap.c and the IO functions defined in vfs.c. + */ +PH7_PRIVATE void PH7_RegisterBuiltInFunction(ph7_vm *pVm) +{ + sxu32 n; + for( n = 0 ; n < SX_ARRAYSIZE(aBuiltInFunc) ; ++n ){ + ph7_create_function(&(*pVm),aBuiltInFunc[n].zName,aBuiltInFunc[n].xFunc,0); + } + /* Register hashmap functions [i.e: array_merge(),sort(),count(),array_diff(),...] */ + PH7_RegisterHashmapFunctions(&(*pVm)); + /* Register IO functions [i.e: fread(),fwrite(),chdir(),mkdir(),file(),...] */ + PH7_RegisterIORoutine(&(*pVm)); +} + +/* + * ---------------------------------------------------------- + * File: api.c + * MD5: ec37aefad456de49a24c8f73f45f8c84 + * ---------------------------------------------------------- + */ +/* + * Symisc PH7: An embeddable bytecode compiler and a virtual machine for the PHP(5) programming language. + * Copyright (C) 2011-2012, Symisc Systems http://ph7.symisc.net/ + * Version 2.1.4 + * For information on licensing,redistribution of this file,and for a DISCLAIMER OF ALL WARRANTIES + * please contact Symisc Systems via: + * legal@symisc.net + * licensing@symisc.net + * contact@symisc.net + * or visit: + * http://ph7.symisc.net/ + */ + /* $SymiscID: api.c v2.0 FreeBSD 2012-08-18 06:54 stable $ */ +#ifndef PH7_AMALGAMATION +#include "ph7int.h" +#endif +/* This file implement the public interfaces presented to host-applications. + * Routines in other files are for internal use by PH7 and should not be + * accessed by users of the library. + */ +#define PH7_ENGINE_MAGIC 0xF874BCD7 +#define PH7_ENGINE_MISUSE(ENGINE) (ENGINE == 0 || ENGINE->nMagic != PH7_ENGINE_MAGIC) +#define PH7_VM_MISUSE(VM) (VM == 0 || VM->nMagic == PH7_VM_STALE) +/* If another thread have released a working instance,the following macros + * evaluates to true. These macros are only used when the library + * is built with threading support enabled which is not the case in + * the default built. + */ +#define PH7_THRD_ENGINE_RELEASE(ENGINE) (ENGINE->nMagic != PH7_ENGINE_MAGIC) +#define PH7_THRD_VM_RELEASE(VM) (VM->nMagic == PH7_VM_STALE) +/* IMPLEMENTATION: ph7@embedded@symisc 311-12-32 */ +/* + * All global variables are collected in the structure named "sMPGlobal". + * That way it is clear in the code when we are using static variable because + * its name start with sMPGlobal. + */ +static struct Global_Data +{ + SyMemBackend sAllocator; /* Global low level memory allocator */ +#if defined(PH7_ENABLE_THREADS) + const SyMutexMethods *pMutexMethods; /* Mutex methods */ + SyMutex *pMutex; /* Global mutex */ + sxu32 nThreadingLevel; /* Threading level: 0 == Single threaded/1 == Multi-Threaded + * The threading level can be set using the [ph7_lib_config()] + * interface with a configuration verb set to + * PH7_LIB_CONFIG_THREAD_LEVEL_SINGLE or + * PH7_LIB_CONFIG_THREAD_LEVEL_MULTI + */ +#endif + const ph7_vfs *pVfs; /* Underlying virtual file system */ + sxi32 nEngine; /* Total number of active engines */ + ph7 *pEngines; /* List of active engine */ + sxu32 nMagic; /* Sanity check against library misuse */ +}sMPGlobal = { + {0,0,0,0,0,0,0,0,{0}}, +#if defined(PH7_ENABLE_THREADS) + 0, + 0, + 0, +#endif + 0, + 0, + 0, + 0 +}; +#define PH7_LIB_MAGIC 0xEA1495BA +#define PH7_LIB_MISUSE (sMPGlobal.nMagic != PH7_LIB_MAGIC) +/* + * Supported threading level. + * These options have meaning only when the library is compiled with multi-threading + * support.That is,the PH7_ENABLE_THREADS compile time directive must be defined + * when PH7 is built. + * PH7_THREAD_LEVEL_SINGLE: + * In this mode,mutexing is disabled and the library can only be used by a single thread. + * PH7_THREAD_LEVEL_MULTI + * In this mode, all mutexes including the recursive mutexes on [ph7] objects + * are enabled so that the application is free to share the same engine + * between different threads at the same time. + */ +#define PH7_THREAD_LEVEL_SINGLE 1 +#define PH7_THREAD_LEVEL_MULTI 2 +/* + * Configure a running PH7 engine instance. + * return PH7_OK on success.Any other return + * value indicates failure. + * Refer to [ph7_config()]. + */ +static sxi32 EngineConfig(ph7 *pEngine,sxi32 nOp,va_list ap) +{ + ph7_conf *pConf = &pEngine->xConf; + int rc = PH7_OK; + /* Perform the requested operation */ + switch(nOp){ + case PH7_CONFIG_ERR_OUTPUT: { + ProcConsumer xConsumer = va_arg(ap,ProcConsumer); + void *pUserData = va_arg(ap,void *); + /* Compile time error consumer routine */ + if( xConsumer == 0 ){ + rc = PH7_CORRUPT; + break; + } + /* Install the error consumer */ + pConf->xErr = xConsumer; + pConf->pErrData = pUserData; + break; + } + case PH7_CONFIG_ERR_LOG:{ + /* Extract compile-time error log if any */ + const char **pzPtr = va_arg(ap,const char **); + int *pLen = va_arg(ap,int *); + if( pzPtr == 0 ){ + rc = PH7_CORRUPT; + break; + } + /* NULL terminate the error-log buffer */ + SyBlobNullAppend(&pConf->sErrConsumer); + /* Point to the error-log buffer */ + *pzPtr = (const char *)SyBlobData(&pConf->sErrConsumer); + if( pLen ){ + if( SyBlobLength(&pConf->sErrConsumer) > 1 /* NULL '\0' terminator */ ){ + *pLen = (int)SyBlobLength(&pConf->sErrConsumer); + }else{ + *pLen = 0; + } + } + break; + } + case PH7_CONFIG_ERR_ABORT: + /* Reserved for future use */ + break; + default: + /* Unknown configuration verb */ + rc = PH7_CORRUPT; + break; + } /* Switch() */ + return rc; +} +/* + * Configure the PH7 library. + * return PH7_OK on success.Any other return value + * indicates failure. + * Refer to [ph7_lib_config()]. + */ +static sxi32 PH7CoreConfigure(sxi32 nOp,va_list ap) +{ + int rc = PH7_OK; + switch(nOp){ + case PH7_LIB_CONFIG_VFS:{ + /* Install a virtual file system */ + const ph7_vfs *pVfs = va_arg(ap,const ph7_vfs *); + sMPGlobal.pVfs = pVfs; + break; + } + case PH7_LIB_CONFIG_USER_MALLOC: { + /* Use an alternative low-level memory allocation routines */ + const SyMemMethods *pMethods = va_arg(ap,const SyMemMethods *); + /* Save the memory failure callback (if available) */ + ProcMemError xMemErr = sMPGlobal.sAllocator.xMemError; + void *pMemErr = sMPGlobal.sAllocator.pUserData; + if( pMethods == 0 ){ + /* Use the built-in memory allocation subsystem */ + rc = SyMemBackendInit(&sMPGlobal.sAllocator,xMemErr,pMemErr); + }else{ + rc = SyMemBackendInitFromOthers(&sMPGlobal.sAllocator,pMethods,xMemErr,pMemErr); + } + break; + } + case PH7_LIB_CONFIG_MEM_ERR_CALLBACK: { + /* Memory failure callback */ + ProcMemError xMemErr = va_arg(ap,ProcMemError); + void *pUserData = va_arg(ap,void *); + sMPGlobal.sAllocator.xMemError = xMemErr; + sMPGlobal.sAllocator.pUserData = pUserData; + break; + } + case PH7_LIB_CONFIG_USER_MUTEX: { +#if defined(PH7_ENABLE_THREADS) + /* Use an alternative low-level mutex subsystem */ + const SyMutexMethods *pMethods = va_arg(ap,const SyMutexMethods *); +#if defined (UNTRUST) + if( pMethods == 0 ){ + rc = PH7_CORRUPT; + } +#endif + /* Sanity check */ + if( pMethods->xEnter == 0 || pMethods->xLeave == 0 || pMethods->xNew == 0){ + /* At least three criticial callbacks xEnter(),xLeave() and xNew() must be supplied */ + rc = PH7_CORRUPT; + break; + } + if( sMPGlobal.pMutexMethods ){ + /* Overwrite the previous mutex subsystem */ + SyMutexRelease(sMPGlobal.pMutexMethods,sMPGlobal.pMutex); + if( sMPGlobal.pMutexMethods->xGlobalRelease ){ + sMPGlobal.pMutexMethods->xGlobalRelease(); + } + sMPGlobal.pMutex = 0; + } + /* Initialize and install the new mutex subsystem */ + if( pMethods->xGlobalInit ){ + rc = pMethods->xGlobalInit(); + if ( rc != PH7_OK ){ + break; + } + } + /* Create the global mutex */ + sMPGlobal.pMutex = pMethods->xNew(SXMUTEX_TYPE_FAST); + if( sMPGlobal.pMutex == 0 ){ + /* + * If the supplied mutex subsystem is so sick that we are unable to + * create a single mutex,there is no much we can do here. + */ + if( pMethods->xGlobalRelease ){ + pMethods->xGlobalRelease(); + } + rc = PH7_CORRUPT; + break; + } + sMPGlobal.pMutexMethods = pMethods; + if( sMPGlobal.nThreadingLevel == 0 ){ + /* Set a default threading level */ + sMPGlobal.nThreadingLevel = PH7_THREAD_LEVEL_MULTI; + } +#endif + break; + } + case PH7_LIB_CONFIG_THREAD_LEVEL_SINGLE: +#if defined(PH7_ENABLE_THREADS) + /* Single thread mode(Only one thread is allowed to play with the library) */ + sMPGlobal.nThreadingLevel = PH7_THREAD_LEVEL_SINGLE; +#endif + break; + case PH7_LIB_CONFIG_THREAD_LEVEL_MULTI: +#if defined(PH7_ENABLE_THREADS) + /* Multi-threading mode (library is thread safe and PH7 engines and virtual machines + * may be shared between multiple threads). + */ + sMPGlobal.nThreadingLevel = PH7_THREAD_LEVEL_MULTI; +#endif + break; + default: + /* Unknown configuration option */ + rc = PH7_CORRUPT; + break; + } + return rc; +} +/* + * [CAPIREF: ph7_lib_config()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +int ph7_lib_config(int nConfigOp,...) +{ + va_list ap; + int rc; + + if( sMPGlobal.nMagic == PH7_LIB_MAGIC ){ + /* Library is already initialized,this operation is forbidden */ + return PH7_LOOKED; + } + va_start(ap,nConfigOp); + rc = PH7CoreConfigure(nConfigOp,ap); + va_end(ap); + return rc; +} +/* + * Global library initialization + * Refer to [ph7_lib_init()] + * This routine must be called to initialize the memory allocation subsystem,the mutex + * subsystem prior to doing any serious work with the library.The first thread to call + * this routine does the initialization process and set the magic number so no body later + * can re-initialize the library.If subsequent threads call this routine before the first + * thread have finished the initialization process, then the subsequent threads must block + * until the initialization process is done. + */ +static sxi32 PH7CoreInitialize(void) +{ + const ph7_vfs *pVfs; /* Built-in vfs */ +#if defined(PH7_ENABLE_THREADS) + const SyMutexMethods *pMutexMethods = 0; + SyMutex *pMaster = 0; +#endif + int rc; + /* + * If the library is already initialized,then a call to this routine + * is a no-op. + */ + if( sMPGlobal.nMagic == PH7_LIB_MAGIC ){ + return PH7_OK; /* Already initialized */ + } + /* Point to the built-in vfs */ + pVfs = PH7_ExportBuiltinVfs(); + /* Install it */ + ph7_lib_config(PH7_LIB_CONFIG_VFS,pVfs); +#if defined(PH7_ENABLE_THREADS) + if( sMPGlobal.nThreadingLevel != PH7_THREAD_LEVEL_SINGLE ){ + pMutexMethods = sMPGlobal.pMutexMethods; + if( pMutexMethods == 0 ){ + /* Use the built-in mutex subsystem */ + pMutexMethods = SyMutexExportMethods(); + if( pMutexMethods == 0 ){ + return PH7_CORRUPT; /* Can't happen */ + } + /* Install the mutex subsystem */ + rc = ph7_lib_config(PH7_LIB_CONFIG_USER_MUTEX,pMutexMethods); + if( rc != PH7_OK ){ + return rc; + } + } + /* Obtain a static mutex so we can initialize the library without calling malloc() */ + pMaster = SyMutexNew(pMutexMethods,SXMUTEX_TYPE_STATIC_1); + if( pMaster == 0 ){ + return PH7_CORRUPT; /* Can't happen */ + } + } + /* Lock the master mutex */ + rc = PH7_OK; + SyMutexEnter(pMutexMethods,pMaster); /* NO-OP if sMPGlobal.nThreadingLevel == PH7_THREAD_LEVEL_SINGLE */ + if( sMPGlobal.nMagic != PH7_LIB_MAGIC ){ +#endif + if( sMPGlobal.sAllocator.pMethods == 0 ){ + /* Install a memory subsystem */ + rc = ph7_lib_config(PH7_LIB_CONFIG_USER_MALLOC,0); /* zero mean use the built-in memory backend */ + if( rc != PH7_OK ){ + /* If we are unable to initialize the memory backend,there is no much we can do here.*/ + goto End; + } + } +#if defined(PH7_ENABLE_THREADS) + if( sMPGlobal.nThreadingLevel > PH7_THREAD_LEVEL_SINGLE ){ + /* Protect the memory allocation subsystem */ + rc = SyMemBackendMakeThreadSafe(&sMPGlobal.sAllocator,sMPGlobal.pMutexMethods); + if( rc != PH7_OK ){ + goto End; + } + } +#endif + /* Our library is initialized,set the magic number */ + sMPGlobal.nMagic = PH7_LIB_MAGIC; + rc = PH7_OK; +#if defined(PH7_ENABLE_THREADS) + } /* sMPGlobal.nMagic != PH7_LIB_MAGIC */ +#endif +End: +#if defined(PH7_ENABLE_THREADS) + /* Unlock the master mutex */ + SyMutexLeave(pMutexMethods,pMaster); /* NO-OP if sMPGlobal.nThreadingLevel == PH7_THREAD_LEVEL_SINGLE */ +#endif + return rc; +} +/* + * [CAPIREF: ph7_lib_init()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +int ph7_lib_init(void) +{ + int rc; + rc = PH7CoreInitialize(); + return rc; +} +/* + * Release an active PH7 engine and it's associated active virtual machines. + */ +static sxi32 EngineRelease(ph7 *pEngine) +{ + ph7_vm *pVm,*pNext; + /* Release all active VM */ + pVm = pEngine->pVms; + for(;;){ + if( pEngine->iVm <= 0 ){ + break; + } + pNext = pVm->pNext; + PH7_VmRelease(pVm); + pVm = pNext; + pEngine->iVm--; + } + /* Set a dummy magic number */ + pEngine->nMagic = 0x7635; + /* Release the private memory subsystem */ + SyMemBackendRelease(&pEngine->sAllocator); + return PH7_OK; +} +/* + * Release all resources consumed by the library. + * If PH7 is already shut down when this routine + * is invoked then this routine is a harmless no-op. + * Note: This call is not thread safe. + * Refer to [ph7_lib_shutdown()]. + */ +static void PH7CoreShutdown(void) +{ + ph7 *pEngine,*pNext; + /* Release all active engines first */ + pEngine = sMPGlobal.pEngines; + for(;;){ + if( sMPGlobal.nEngine < 1 ){ + break; + } + pNext = pEngine->pNext; + EngineRelease(pEngine); + pEngine = pNext; + sMPGlobal.nEngine--; + } +#if defined(PH7_ENABLE_THREADS) + /* Release the mutex subsystem */ + if( sMPGlobal.pMutexMethods ){ + if( sMPGlobal.pMutex ){ + SyMutexRelease(sMPGlobal.pMutexMethods,sMPGlobal.pMutex); + sMPGlobal.pMutex = 0; + } + if( sMPGlobal.pMutexMethods->xGlobalRelease ){ + sMPGlobal.pMutexMethods->xGlobalRelease(); + } + sMPGlobal.pMutexMethods = 0; + } + sMPGlobal.nThreadingLevel = 0; +#endif + if( sMPGlobal.sAllocator.pMethods ){ + /* Release the memory backend */ + SyMemBackendRelease(&sMPGlobal.sAllocator); + } + sMPGlobal.nMagic = 0x1928; +} +/* + * [CAPIREF: ph7_lib_shutdown()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +int ph7_lib_shutdown(void) +{ + if( sMPGlobal.nMagic != PH7_LIB_MAGIC ){ + /* Already shut */ + return PH7_OK; + } + PH7CoreShutdown(); + return PH7_OK; +} +/* + * [CAPIREF: ph7_lib_is_threadsafe()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +int ph7_lib_is_threadsafe(void) +{ + if( sMPGlobal.nMagic != PH7_LIB_MAGIC ){ + return 0; + } +#if defined(PH7_ENABLE_THREADS) + if( sMPGlobal.nThreadingLevel > PH7_THREAD_LEVEL_SINGLE ){ + /* Muli-threading support is enabled */ + return 1; + }else{ + /* Single-threading */ + return 0; + } +#else + return 0; +#endif +} +/* + * [CAPIREF: ph7_lib_version()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +const char * ph7_lib_version(void) +{ + return PH7_VERSION; +} +/* + * [CAPIREF: ph7_lib_signature()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +const char * ph7_lib_signature(void) +{ + return PH7_SIG; +} +/* + * [CAPIREF: ph7_lib_ident()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +const char * ph7_lib_ident(void) +{ + return PH7_IDENT; +} +/* + * [CAPIREF: ph7_lib_copyright()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +const char * ph7_lib_copyright(void) +{ + return PH7_COPYRIGHT; +} +/* + * [CAPIREF: ph7_config()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +int ph7_config(ph7 *pEngine,int nConfigOp,...) +{ + va_list ap; + int rc; + if( PH7_ENGINE_MISUSE(pEngine) ){ + return PH7_CORRUPT; + } +#if defined(PH7_ENABLE_THREADS) + /* Acquire engine mutex */ + SyMutexEnter(sMPGlobal.pMutexMethods,pEngine->pMutex); /* NO-OP if sMPGlobal.nThreadingLevel != PH7_THREAD_LEVEL_MULTI */ + if( sMPGlobal.nThreadingLevel > PH7_THREAD_LEVEL_SINGLE && + PH7_THRD_ENGINE_RELEASE(pEngine) ){ + return PH7_ABORT; /* Another thread have released this instance */ + } +#endif + va_start(ap,nConfigOp); + rc = EngineConfig(&(*pEngine),nConfigOp,ap); + va_end(ap); +#if defined(PH7_ENABLE_THREADS) + /* Leave engine mutex */ + SyMutexLeave(sMPGlobal.pMutexMethods,pEngine->pMutex); /* NO-OP if sMPGlobal.nThreadingLevel != PH7_THREAD_LEVEL_MULTI */ +#endif + return rc; +} +/* + * [CAPIREF: ph7_init()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +int ph7_init(ph7 **ppEngine) +{ + ph7 *pEngine; + int rc; +#if defined(UNTRUST) + if( ppEngine == 0 ){ + return PH7_CORRUPT; + } +#endif + *ppEngine = 0; + /* One-time automatic library initialization */ + rc = PH7CoreInitialize(); + if( rc != PH7_OK ){ + return rc; + } + /* Allocate a new engine */ + pEngine = (ph7 *)SyMemBackendPoolAlloc(&sMPGlobal.sAllocator,sizeof(ph7)); + if( pEngine == 0 ){ + return PH7_NOMEM; + } + /* Zero the structure */ + SyZero(pEngine,sizeof(ph7)); + /* Initialize engine fields */ + pEngine->nMagic = PH7_ENGINE_MAGIC; + rc = SyMemBackendInitFromParent(&pEngine->sAllocator,&sMPGlobal.sAllocator); + if( rc != PH7_OK ){ + goto Release; + } +#if defined(PH7_ENABLE_THREADS) + SyMemBackendDisbaleMutexing(&pEngine->sAllocator); +#endif + /* Default configuration */ + SyBlobInit(&pEngine->xConf.sErrConsumer,&pEngine->sAllocator); + /* Install a default compile-time error consumer routine */ + ph7_config(pEngine,PH7_CONFIG_ERR_OUTPUT,PH7_VmBlobConsumer,&pEngine->xConf.sErrConsumer); + /* Built-in vfs */ + pEngine->pVfs = sMPGlobal.pVfs; +#if defined(PH7_ENABLE_THREADS) + if( sMPGlobal.nThreadingLevel > PH7_THREAD_LEVEL_SINGLE ){ + /* Associate a recursive mutex with this instance */ + pEngine->pMutex = SyMutexNew(sMPGlobal.pMutexMethods,SXMUTEX_TYPE_RECURSIVE); + if( pEngine->pMutex == 0 ){ + rc = PH7_NOMEM; + goto Release; + } + } +#endif + /* Link to the list of active engines */ +#if defined(PH7_ENABLE_THREADS) + /* Enter the global mutex */ + SyMutexEnter(sMPGlobal.pMutexMethods,sMPGlobal.pMutex); /* NO-OP if sMPGlobal.nThreadingLevel == PH7_THREAD_LEVEL_SINGLE */ +#endif + MACRO_LD_PUSH(sMPGlobal.pEngines,pEngine); + sMPGlobal.nEngine++; +#if defined(PH7_ENABLE_THREADS) + /* Leave the global mutex */ + SyMutexLeave(sMPGlobal.pMutexMethods,sMPGlobal.pMutex); /* NO-OP if sMPGlobal.nThreadingLevel == PH7_THREAD_LEVEL_SINGLE */ +#endif + /* Write a pointer to the new instance */ + *ppEngine = pEngine; + return PH7_OK; +Release: + SyMemBackendRelease(&pEngine->sAllocator); + SyMemBackendPoolFree(&sMPGlobal.sAllocator,pEngine); + return rc; +} +/* + * [CAPIREF: ph7_release()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +int ph7_release(ph7 *pEngine) +{ + int rc; + if( PH7_ENGINE_MISUSE(pEngine) ){ + return PH7_CORRUPT; + } +#if defined(PH7_ENABLE_THREADS) + /* Acquire engine mutex */ + SyMutexEnter(sMPGlobal.pMutexMethods,pEngine->pMutex); /* NO-OP if sMPGlobal.nThreadingLevel != PH7_THREAD_LEVEL_MULTI */ + if( sMPGlobal.nThreadingLevel > PH7_THREAD_LEVEL_SINGLE && + PH7_THRD_ENGINE_RELEASE(pEngine) ){ + return PH7_ABORT; /* Another thread have released this instance */ + } +#endif + /* Release the engine */ + rc = EngineRelease(&(*pEngine)); +#if defined(PH7_ENABLE_THREADS) + /* Leave engine mutex */ + SyMutexLeave(sMPGlobal.pMutexMethods,pEngine->pMutex); /* NO-OP if sMPGlobal.nThreadingLevel != PH7_THREAD_LEVEL_MULTI */ + /* Release engine mutex */ + SyMutexRelease(sMPGlobal.pMutexMethods,pEngine->pMutex) /* NO-OP if sMPGlobal.nThreadingLevel != PH7_THREAD_LEVEL_MULTI */ +#endif +#if defined(PH7_ENABLE_THREADS) + /* Enter the global mutex */ + SyMutexEnter(sMPGlobal.pMutexMethods,sMPGlobal.pMutex); /* NO-OP if sMPGlobal.nThreadingLevel == PH7_THREAD_LEVEL_SINGLE */ +#endif + /* Unlink from the list of active engines */ + MACRO_LD_REMOVE(sMPGlobal.pEngines,pEngine); + sMPGlobal.nEngine--; +#if defined(PH7_ENABLE_THREADS) + /* Leave the global mutex */ + SyMutexLeave(sMPGlobal.pMutexMethods,sMPGlobal.pMutex); /* NO-OP if sMPGlobal.nThreadingLevel == PH7_THREAD_LEVEL_SINGLE */ +#endif + /* Release the memory chunk allocated to this engine */ + SyMemBackendPoolFree(&sMPGlobal.sAllocator,pEngine); + return rc; +} +/* + * Compile a raw PHP script. + * To execute a PHP code, it must first be compiled into a byte-code program using this routine. + * If something goes wrong [i.e: compile-time error], your error log [i.e: error consumer callback] + * should display the appropriate error message and this function set ppVm to null and return + * an error code that is different from PH7_OK. Otherwise when the script is successfully compiled + * ppVm should hold the PH7 byte-code and it's safe to call [ph7_vm_exec(), ph7_vm_reset(), etc.]. + * This API does not actually evaluate the PHP code. It merely compile and prepares the PHP script + * for evaluation. + */ +static sxi32 ProcessScript( + ph7 *pEngine, /* Running PH7 engine */ + ph7_vm **ppVm, /* OUT: A pointer to the virtual machine */ + SyString *pScript, /* Raw PHP script to compile */ + sxi32 iFlags, /* Compile-time flags */ + const char *zFilePath /* File path if script come from a file. NULL otherwise */ + ) +{ + ph7_vm *pVm; + int rc; + /* Allocate a new virtual machine */ + pVm = (ph7_vm *)SyMemBackendPoolAlloc(&pEngine->sAllocator,sizeof(ph7_vm)); + if( pVm == 0 ){ + /* If the supplied memory subsystem is so sick that we are unable to allocate + * a tiny chunk of memory, there is no much we can do here. */ + if( ppVm ){ + *ppVm = 0; + } + return PH7_NOMEM; + } + if( iFlags < 0 ){ + /* Default compile-time flags */ + iFlags = 0; + } + /* Initialize the Virtual Machine */ + rc = PH7_VmInit(pVm,&(*pEngine)); + if( rc != PH7_OK ){ + SyMemBackendPoolFree(&pEngine->sAllocator,pVm); + if( ppVm ){ + *ppVm = 0; + } + return PH7_VM_ERR; + } + if( zFilePath ){ + /* Push processed file path */ + PH7_VmPushFilePath(pVm,zFilePath,-1,TRUE,0); + } + /* Reset the error message consumer */ + SyBlobReset(&pEngine->xConf.sErrConsumer); + /* Compile the script */ + PH7_CompileScript(pVm,&(*pScript),iFlags); + if( pVm->sCodeGen.nErr > 0 || pVm == 0){ + sxu32 nErr = pVm->sCodeGen.nErr; + /* Compilation error or null ppVm pointer,release this VM */ + SyMemBackendRelease(&pVm->sAllocator); + SyMemBackendPoolFree(&pEngine->sAllocator,pVm); + if( ppVm ){ + *ppVm = 0; + } + return nErr > 0 ? PH7_COMPILE_ERR : PH7_OK; + } + /* Prepare the virtual machine for bytecode execution */ + rc = PH7_VmMakeReady(pVm); + if( rc != PH7_OK ){ + goto Release; + } + /* Install local import path which is the current directory */ + ph7_vm_config(pVm,PH7_VM_CONFIG_IMPORT_PATH,"./"); +#if defined(PH7_ENABLE_THREADS) + if( sMPGlobal.nThreadingLevel > PH7_THREAD_LEVEL_SINGLE ){ + /* Associate a recursive mutex with this instance */ + pVm->pMutex = SyMutexNew(sMPGlobal.pMutexMethods,SXMUTEX_TYPE_RECURSIVE); + if( pVm->pMutex == 0 ){ + goto Release; + } + } +#endif + /* Script successfully compiled,link to the list of active virtual machines */ + MACRO_LD_PUSH(pEngine->pVms,pVm); + pEngine->iVm++; + /* Point to the freshly created VM */ + *ppVm = pVm; + /* Ready to execute PH7 bytecode */ + return PH7_OK; +Release: + SyMemBackendRelease(&pVm->sAllocator); + SyMemBackendPoolFree(&pEngine->sAllocator,pVm); + *ppVm = 0; + return PH7_VM_ERR; +} +/* + * [CAPIREF: ph7_compile()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +int ph7_compile(ph7 *pEngine,const char *zSource,int nLen,ph7_vm **ppOutVm) +{ + SyString sScript; + int rc; + if( PH7_ENGINE_MISUSE(pEngine) || zSource == 0){ + return PH7_CORRUPT; + } + if( nLen < 0 ){ + /* Compute input length automatically */ + nLen = (int)SyStrlen(zSource); + } + SyStringInitFromBuf(&sScript,zSource,nLen); +#if defined(PH7_ENABLE_THREADS) + /* Acquire engine mutex */ + SyMutexEnter(sMPGlobal.pMutexMethods,pEngine->pMutex); /* NO-OP if sMPGlobal.nThreadingLevel != PH7_THREAD_LEVEL_MULTI */ + if( sMPGlobal.nThreadingLevel > PH7_THREAD_LEVEL_SINGLE && + PH7_THRD_ENGINE_RELEASE(pEngine) ){ + return PH7_ABORT; /* Another thread have released this instance */ + } +#endif + /* Compile the script */ + rc = ProcessScript(&(*pEngine),ppOutVm,&sScript,0,0); +#if defined(PH7_ENABLE_THREADS) + /* Leave engine mutex */ + SyMutexLeave(sMPGlobal.pMutexMethods,pEngine->pMutex); /* NO-OP if sMPGlobal.nThreadingLevel != PH7_THREAD_LEVEL_MULTI */ +#endif + /* Compilation result */ + return rc; +} +/* + * [CAPIREF: ph7_compile_v2()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +int ph7_compile_v2(ph7 *pEngine,const char *zSource,int nLen,ph7_vm **ppOutVm,int iFlags) +{ + SyString sScript; + int rc; + if( PH7_ENGINE_MISUSE(pEngine) || zSource == 0){ + return PH7_CORRUPT; + } + if( nLen < 0 ){ + /* Compute input length automatically */ + nLen = (int)SyStrlen(zSource); + } + SyStringInitFromBuf(&sScript,zSource,nLen); +#if defined(PH7_ENABLE_THREADS) + /* Acquire engine mutex */ + SyMutexEnter(sMPGlobal.pMutexMethods,pEngine->pMutex); /* NO-OP if sMPGlobal.nThreadingLevel != PH7_THREAD_LEVEL_MULTI */ + if( sMPGlobal.nThreadingLevel > PH7_THREAD_LEVEL_SINGLE && + PH7_THRD_ENGINE_RELEASE(pEngine) ){ + return PH7_ABORT; /* Another thread have released this instance */ + } +#endif + /* Compile the script */ + rc = ProcessScript(&(*pEngine),ppOutVm,&sScript,iFlags,0); +#if defined(PH7_ENABLE_THREADS) + /* Leave engine mutex */ + SyMutexLeave(sMPGlobal.pMutexMethods,pEngine->pMutex); /* NO-OP if sMPGlobal.nThreadingLevel != PH7_THREAD_LEVEL_MULTI */ +#endif + /* Compilation result */ + return rc; +} +/* + * [CAPIREF: ph7_compile_file()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +int ph7_compile_file(ph7 *pEngine,const char *zFilePath,ph7_vm **ppOutVm,int iFlags) +{ + const ph7_vfs *pVfs; + int rc; + if( ppOutVm ){ + *ppOutVm = 0; + } + rc = PH7_OK; /* cc warning */ + if( PH7_ENGINE_MISUSE(pEngine) || SX_EMPTY_STR(zFilePath) ){ + return PH7_CORRUPT; + } +#if defined(PH7_ENABLE_THREADS) + /* Acquire engine mutex */ + SyMutexEnter(sMPGlobal.pMutexMethods,pEngine->pMutex); /* NO-OP if sMPGlobal.nThreadingLevel != PH7_THREAD_LEVEL_MULTI */ + if( sMPGlobal.nThreadingLevel > PH7_THREAD_LEVEL_SINGLE && + PH7_THRD_ENGINE_RELEASE(pEngine) ){ + return PH7_ABORT; /* Another thread have released this instance */ + } +#endif + /* + * Check if the underlying vfs implement the memory map + * [i.e: mmap() under UNIX/MapViewOfFile() under windows] function. + */ + pVfs = pEngine->pVfs; + if( pVfs == 0 || pVfs->xMmap == 0 ){ + /* Memory map routine not implemented */ + rc = PH7_IO_ERR; + }else{ + void *pMapView = 0; /* cc warning */ + ph7_int64 nSize = 0; /* cc warning */ + SyString sScript; + /* Try to get a memory view of the whole file */ + rc = pVfs->xMmap(zFilePath,&pMapView,&nSize); + if( rc != PH7_OK ){ + /* Assume an IO error */ + rc = PH7_IO_ERR; + }else{ + /* Compile the file */ + SyStringInitFromBuf(&sScript,pMapView,nSize); + rc = ProcessScript(&(*pEngine),ppOutVm,&sScript,iFlags,zFilePath); + /* Release the memory view of the whole file */ + if( pVfs->xUnmap ){ + pVfs->xUnmap(pMapView,nSize); + } + } + } +#if defined(PH7_ENABLE_THREADS) + /* Leave engine mutex */ + SyMutexLeave(sMPGlobal.pMutexMethods,pEngine->pMutex); /* NO-OP if sMPGlobal.nThreadingLevel != PH7_THREAD_LEVEL_MULTI */ +#endif + /* Compilation result */ + return rc; +} +/* + * [CAPIREF: ph7_vm_dump_v2()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +int ph7_vm_dump_v2(ph7_vm *pVm,int (*xConsumer)(const void *,unsigned int,void *),void *pUserData) +{ + int rc; + /* Ticket 1433-002: NULL VM is harmless operation */ + if ( PH7_VM_MISUSE(pVm) ){ + return PH7_CORRUPT; + } +#ifdef UNTRUST + if( xConsumer == 0 ){ + return PH7_CORRUPT; + } +#endif + /* Dump VM instructions */ + rc = PH7_VmDump(&(*pVm),xConsumer,pUserData); + return rc; +} +/* + * [CAPIREF: ph7_vm_config()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +int ph7_vm_config(ph7_vm *pVm,int iConfigOp,...) +{ + va_list ap; + int rc; + /* Ticket 1433-002: NULL VM is harmless operation */ + if ( PH7_VM_MISUSE(pVm) ){ + return PH7_CORRUPT; + } +#if defined(PH7_ENABLE_THREADS) + /* Acquire VM mutex */ + SyMutexEnter(sMPGlobal.pMutexMethods,pVm->pMutex); /* NO-OP if sMPGlobal.nThreadingLevel != PH7_THREAD_LEVEL_MULTI */ + if( sMPGlobal.nThreadingLevel > PH7_THREAD_LEVEL_SINGLE && + PH7_THRD_VM_RELEASE(pVm) ){ + return PH7_ABORT; /* Another thread have released this instance */ + } +#endif + /* Confiugure the virtual machine */ + va_start(ap,iConfigOp); + rc = PH7_VmConfigure(&(*pVm),iConfigOp,ap); + va_end(ap); +#if defined(PH7_ENABLE_THREADS) + /* Leave VM mutex */ + SyMutexLeave(sMPGlobal.pMutexMethods,pVm->pMutex); /* NO-OP if sMPGlobal.nThreadingLevel != PH7_THREAD_LEVEL_MULTI */ +#endif + return rc; +} +/* + * [CAPIREF: ph7_vm_exec()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +int ph7_vm_exec(ph7_vm *pVm,int *pExitStatus) +{ + int rc; + /* Ticket 1433-002: NULL VM is harmless operation */ + if ( PH7_VM_MISUSE(pVm) ){ + return PH7_CORRUPT; + } +#if defined(PH7_ENABLE_THREADS) + /* Acquire VM mutex */ + SyMutexEnter(sMPGlobal.pMutexMethods,pVm->pMutex); /* NO-OP if sMPGlobal.nThreadingLevel != PH7_THREAD_LEVEL_MULTI */ + if( sMPGlobal.nThreadingLevel > PH7_THREAD_LEVEL_SINGLE && + PH7_THRD_VM_RELEASE(pVm) ){ + return PH7_ABORT; /* Another thread have released this instance */ + } +#endif + /* Execute PH7 byte-code */ + rc = PH7_VmByteCodeExec(&(*pVm)); + if( pExitStatus ){ + /* Exit status */ + *pExitStatus = pVm->iExitStatus; + } +#if defined(PH7_ENABLE_THREADS) + /* Leave VM mutex */ + SyMutexLeave(sMPGlobal.pMutexMethods,pVm->pMutex); /* NO-OP if sMPGlobal.nThreadingLevel != PH7_THREAD_LEVEL_MULTI */ +#endif + /* Execution result */ + return rc; +} +/* + * [CAPIREF: ph7_vm_reset()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +int ph7_vm_reset(ph7_vm *pVm) +{ + int rc; + /* Ticket 1433-002: NULL VM is harmless operation */ + if ( PH7_VM_MISUSE(pVm) ){ + return PH7_CORRUPT; + } +#if defined(PH7_ENABLE_THREADS) + /* Acquire VM mutex */ + SyMutexEnter(sMPGlobal.pMutexMethods,pVm->pMutex); /* NO-OP if sMPGlobal.nThreadingLevel != PH7_THREAD_LEVEL_MULTI */ + if( sMPGlobal.nThreadingLevel > PH7_THREAD_LEVEL_SINGLE && + PH7_THRD_VM_RELEASE(pVm) ){ + return PH7_ABORT; /* Another thread have released this instance */ + } +#endif + rc = PH7_VmReset(&(*pVm)); +#if defined(PH7_ENABLE_THREADS) + /* Leave VM mutex */ + SyMutexLeave(sMPGlobal.pMutexMethods,pVm->pMutex); /* NO-OP if sMPGlobal.nThreadingLevel != PH7_THREAD_LEVEL_MULTI */ +#endif + return rc; +} +/* + * [CAPIREF: ph7_vm_release()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +int ph7_vm_release(ph7_vm *pVm) +{ + ph7 *pEngine; + int rc; + /* Ticket 1433-002: NULL VM is harmless operation */ + if ( PH7_VM_MISUSE(pVm) ){ + return PH7_CORRUPT; + } +#if defined(PH7_ENABLE_THREADS) + /* Acquire VM mutex */ + SyMutexEnter(sMPGlobal.pMutexMethods,pVm->pMutex); /* NO-OP if sMPGlobal.nThreadingLevel != PH7_THREAD_LEVEL_MULTI */ + if( sMPGlobal.nThreadingLevel > PH7_THREAD_LEVEL_SINGLE && + PH7_THRD_VM_RELEASE(pVm) ){ + return PH7_ABORT; /* Another thread have released this instance */ + } +#endif + pEngine = pVm->pEngine; + rc = PH7_VmRelease(&(*pVm)); +#if defined(PH7_ENABLE_THREADS) + /* Leave VM mutex */ + SyMutexLeave(sMPGlobal.pMutexMethods,pVm->pMutex); /* NO-OP if sMPGlobal.nThreadingLevel != PH7_THREAD_LEVEL_MULTI */ +#endif + if( rc == PH7_OK ){ + /* Unlink from the list of active VM */ +#if defined(PH7_ENABLE_THREADS) + /* Acquire engine mutex */ + SyMutexEnter(sMPGlobal.pMutexMethods,pEngine->pMutex); /* NO-OP if sMPGlobal.nThreadingLevel != PH7_THREAD_LEVEL_MULTI */ + if( sMPGlobal.nThreadingLevel > PH7_THREAD_LEVEL_SINGLE && + PH7_THRD_ENGINE_RELEASE(pEngine) ){ + return PH7_ABORT; /* Another thread have released this instance */ + } +#endif + MACRO_LD_REMOVE(pEngine->pVms,pVm); + pEngine->iVm--; + /* Release the memory chunk allocated to this VM */ + SyMemBackendPoolFree(&pEngine->sAllocator,pVm); +#if defined(PH7_ENABLE_THREADS) + /* Leave engine mutex */ + SyMutexLeave(sMPGlobal.pMutexMethods,pEngine->pMutex); /* NO-OP if sMPGlobal.nThreadingLevel != PH7_THREAD_LEVEL_MULTI */ +#endif + } + return rc; +} +/* + * [CAPIREF: ph7_create_function()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +int ph7_create_function(ph7_vm *pVm,const char *zName,int (*xFunc)(ph7_context *,int,ph7_value **),void *pUserData) +{ + SyString sName; + int rc; + /* Ticket 1433-002: NULL VM is harmless operation */ + if ( PH7_VM_MISUSE(pVm) ){ + return PH7_CORRUPT; + } + SyStringInitFromBuf(&sName,zName,SyStrlen(zName)); + /* Remove leading and trailing white spaces */ + SyStringFullTrim(&sName); + /* Ticket 1433-003: NULL values are not allowed */ + if( sName.nByte < 1 || xFunc == 0 ){ + return PH7_CORRUPT; + } +#if defined(PH7_ENABLE_THREADS) + /* Acquire VM mutex */ + SyMutexEnter(sMPGlobal.pMutexMethods,pVm->pMutex); /* NO-OP if sMPGlobal.nThreadingLevel != PH7_THREAD_LEVEL_MULTI */ + if( sMPGlobal.nThreadingLevel > PH7_THREAD_LEVEL_SINGLE && + PH7_THRD_VM_RELEASE(pVm) ){ + return PH7_ABORT; /* Another thread have released this instance */ + } +#endif + /* Install the foreign function */ + rc = PH7_VmInstallForeignFunction(&(*pVm),&sName,xFunc,pUserData); +#if defined(PH7_ENABLE_THREADS) + /* Leave VM mutex */ + SyMutexLeave(sMPGlobal.pMutexMethods,pVm->pMutex); /* NO-OP if sMPGlobal.nThreadingLevel != PH7_THREAD_LEVEL_MULTI */ +#endif + return rc; +} +/* + * [CAPIREF: ph7_delete_function()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +int ph7_delete_function(ph7_vm *pVm,const char *zName) +{ + ph7_user_func *pFunc = 0; + int rc; + /* Ticket 1433-002: NULL VM is harmless operation */ + if ( PH7_VM_MISUSE(pVm) ){ + return PH7_CORRUPT; + } +#if defined(PH7_ENABLE_THREADS) + /* Acquire VM mutex */ + SyMutexEnter(sMPGlobal.pMutexMethods,pVm->pMutex); /* NO-OP if sMPGlobal.nThreadingLevel != PH7_THREAD_LEVEL_MULTI */ + if( sMPGlobal.nThreadingLevel > PH7_THREAD_LEVEL_SINGLE && + PH7_THRD_VM_RELEASE(pVm) ){ + return PH7_ABORT; /* Another thread have released this instance */ + } +#endif + /* Perform the deletion */ + rc = SyHashDeleteEntry(&pVm->hHostFunction,(const void *)zName,SyStrlen(zName),(void **)&pFunc); + if( rc == PH7_OK ){ + /* Release internal fields */ + SySetRelease(&pFunc->aAux); + SyMemBackendFree(&pVm->sAllocator,(void *)SyStringData(&pFunc->sName)); + SyMemBackendPoolFree(&pVm->sAllocator,pFunc); + } +#if defined(PH7_ENABLE_THREADS) + /* Leave VM mutex */ + SyMutexLeave(sMPGlobal.pMutexMethods,pVm->pMutex); /* NO-OP if sMPGlobal.nThreadingLevel != PH7_THREAD_LEVEL_MULTI */ +#endif + return rc; +} +/* + * [CAPIREF: ph7_create_constant()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +int ph7_create_constant(ph7_vm *pVm,const char *zName,void (*xExpand)(ph7_value *,void *),void *pUserData) +{ + SyString sName; + int rc; + /* Ticket 1433-002: NULL VM is harmless operation */ + if ( PH7_VM_MISUSE(pVm) ){ + return PH7_CORRUPT; + } + SyStringInitFromBuf(&sName,zName,SyStrlen(zName)); + /* Remove leading and trailing white spaces */ + SyStringFullTrim(&sName); + if( sName.nByte < 1 ){ + /* Empty constant name */ + return PH7_CORRUPT; + } + /* TICKET 1433-003: NULL pointer harmless operation */ + if( xExpand == 0 ){ + return PH7_CORRUPT; + } +#if defined(PH7_ENABLE_THREADS) + /* Acquire VM mutex */ + SyMutexEnter(sMPGlobal.pMutexMethods,pVm->pMutex); /* NO-OP if sMPGlobal.nThreadingLevel != PH7_THREAD_LEVEL_MULTI */ + if( sMPGlobal.nThreadingLevel > PH7_THREAD_LEVEL_SINGLE && + PH7_THRD_VM_RELEASE(pVm) ){ + return PH7_ABORT; /* Another thread have released this instance */ + } +#endif + /* Perform the registration */ + rc = PH7_VmRegisterConstant(&(*pVm),&sName,xExpand,pUserData); +#if defined(PH7_ENABLE_THREADS) + /* Leave VM mutex */ + SyMutexLeave(sMPGlobal.pMutexMethods,pVm->pMutex); /* NO-OP if sMPGlobal.nThreadingLevel != PH7_THREAD_LEVEL_MULTI */ +#endif + return rc; +} +/* + * [CAPIREF: ph7_delete_constant()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +int ph7_delete_constant(ph7_vm *pVm,const char *zName) +{ + ph7_constant *pCons; + int rc; + /* Ticket 1433-002: NULL VM is harmless operation */ + if ( PH7_VM_MISUSE(pVm) ){ + return PH7_CORRUPT; + } +#if defined(PH7_ENABLE_THREADS) + /* Acquire VM mutex */ + SyMutexEnter(sMPGlobal.pMutexMethods,pVm->pMutex); /* NO-OP if sMPGlobal.nThreadingLevel != PH7_THREAD_LEVEL_MULTI */ + if( sMPGlobal.nThreadingLevel > PH7_THREAD_LEVEL_SINGLE && + PH7_THRD_VM_RELEASE(pVm) ){ + return PH7_ABORT; /* Another thread have released this instance */ + } +#endif + /* Query the constant hashtable */ + rc = SyHashDeleteEntry(&pVm->hConstant,(const void *)zName,SyStrlen(zName),(void **)&pCons); + if( rc == PH7_OK ){ + /* Perform the deletion */ + SyMemBackendFree(&pVm->sAllocator,(void *)SyStringData(&pCons->sName)); + SyMemBackendPoolFree(&pVm->sAllocator,pCons); + } +#if defined(PH7_ENABLE_THREADS) + /* Leave VM mutex */ + SyMutexLeave(sMPGlobal.pMutexMethods,pVm->pMutex); /* NO-OP if sMPGlobal.nThreadingLevel != PH7_THREAD_LEVEL_MULTI */ +#endif + return rc; +} +/* + * [CAPIREF: ph7_new_scalar()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +ph7_value * ph7_new_scalar(ph7_vm *pVm) +{ + ph7_value *pObj; + /* Ticket 1433-002: NULL VM is harmless operation */ + if ( PH7_VM_MISUSE(pVm) ){ + return 0; + } + /* Allocate a new scalar variable */ + pObj = (ph7_value *)SyMemBackendPoolAlloc(&pVm->sAllocator,sizeof(ph7_value)); + if( pObj == 0 ){ + return 0; + } + /* Nullify the new scalar */ + PH7_MemObjInit(pVm,pObj); + return pObj; +} +/* + * [CAPIREF: ph7_new_array()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +ph7_value * ph7_new_array(ph7_vm *pVm) +{ + ph7_hashmap *pMap; + ph7_value *pObj; + /* Ticket 1433-002: NULL VM is harmless operation */ + if ( PH7_VM_MISUSE(pVm) ){ + return 0; + } + /* Create a new hashmap first */ + pMap = PH7_NewHashmap(&(*pVm),0,0); + if( pMap == 0 ){ + return 0; + } + /* Associate a new ph7_value with this hashmap */ + pObj = (ph7_value *)SyMemBackendPoolAlloc(&pVm->sAllocator,sizeof(ph7_value)); + if( pObj == 0 ){ + PH7_HashmapRelease(pMap,TRUE); + return 0; + } + PH7_MemObjInitFromArray(pVm,pObj,pMap); + return pObj; +} +/* + * [CAPIREF: ph7_release_value()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +int ph7_release_value(ph7_vm *pVm,ph7_value *pValue) +{ + /* Ticket 1433-002: NULL VM is harmless operation */ + if ( PH7_VM_MISUSE(pVm) ){ + return PH7_CORRUPT; + } + if( pValue ){ + /* Release the value */ + PH7_MemObjRelease(pValue); + SyMemBackendPoolFree(&pVm->sAllocator,pValue); + } + return PH7_OK; +} +/* + * [CAPIREF: ph7_value_to_int()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +int ph7_value_to_int(ph7_value *pValue) +{ + int rc; + rc = PH7_MemObjToInteger(pValue); + if( rc != PH7_OK ){ + return 0; + } + return (int)pValue->x.iVal; +} +/* + * [CAPIREF: ph7_value_to_bool()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +int ph7_value_to_bool(ph7_value *pValue) +{ + int rc; + rc = PH7_MemObjToBool(pValue); + if( rc != PH7_OK ){ + return 0; + } + return (int)pValue->x.iVal; +} +/* + * [CAPIREF: ph7_value_to_int64()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +ph7_int64 ph7_value_to_int64(ph7_value *pValue) +{ + int rc; + rc = PH7_MemObjToInteger(pValue); + if( rc != PH7_OK ){ + return 0; + } + return pValue->x.iVal; +} +/* + * [CAPIREF: ph7_value_to_double()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +double ph7_value_to_double(ph7_value *pValue) +{ + int rc; + rc = PH7_MemObjToReal(pValue); + if( rc != PH7_OK ){ + return (double)0; + } + return (double)pValue->rVal; +} +/* + * [CAPIREF: ph7_value_to_string()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +const char * ph7_value_to_string(ph7_value *pValue,int *pLen) +{ + PH7_MemObjToString(pValue); + if( SyBlobLength(&pValue->sBlob) > 0 ){ + SyBlobNullAppend(&pValue->sBlob); + if( pLen ){ + *pLen = (int)SyBlobLength(&pValue->sBlob); + } + return (const char *)SyBlobData(&pValue->sBlob); + }else{ + /* Return the empty string */ + if( pLen ){ + *pLen = 0; + } + return ""; + } +} +/* + * [CAPIREF: ph7_value_to_resource()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +void * ph7_value_to_resource(ph7_value *pValue) +{ + if( (pValue->iFlags & MEMOBJ_RES) == 0 ){ + /* Not a resource,return NULL */ + return 0; + } + return pValue->x.pOther; +} +/* + * [CAPIREF: ph7_value_compare()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +int ph7_value_compare(ph7_value *pLeft,ph7_value *pRight,int bStrict) +{ + int rc; + if( pLeft == 0 || pRight == 0 ){ + /* TICKET 1433-24: NULL values is harmless operation */ + return 1; + } + /* Perform the comparison */ + rc = PH7_MemObjCmp(&(*pLeft),&(*pRight),bStrict,0); + /* Comparison result */ + return rc; +} +/* + * [CAPIREF: ph7_result_int()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +int ph7_result_int(ph7_context *pCtx,int iValue) +{ + return ph7_value_int(pCtx->pRet,iValue); +} +/* + * [CAPIREF: ph7_result_int64()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +int ph7_result_int64(ph7_context *pCtx,ph7_int64 iValue) +{ + return ph7_value_int64(pCtx->pRet,iValue); +} +/* + * [CAPIREF: ph7_result_bool()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +int ph7_result_bool(ph7_context *pCtx,int iBool) +{ + return ph7_value_bool(pCtx->pRet,iBool); +} +/* + * [CAPIREF: ph7_result_double()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +int ph7_result_double(ph7_context *pCtx,double Value) +{ + return ph7_value_double(pCtx->pRet,Value); +} +/* + * [CAPIREF: ph7_result_null()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +int ph7_result_null(ph7_context *pCtx) +{ + /* Invalidate any prior representation and set the NULL flag */ + PH7_MemObjRelease(pCtx->pRet); + return PH7_OK; +} +/* + * [CAPIREF: ph7_result_string()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +int ph7_result_string(ph7_context *pCtx,const char *zString,int nLen) +{ + return ph7_value_string(pCtx->pRet,zString,nLen); +} +/* + * [CAPIREF: ph7_result_string_format()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +int ph7_result_string_format(ph7_context *pCtx,const char *zFormat,...) +{ + ph7_value *p; + va_list ap; + int rc; + p = pCtx->pRet; + if( (p->iFlags & MEMOBJ_STRING) == 0 ){ + /* Invalidate any prior representation */ + PH7_MemObjRelease(p); + MemObjSetType(p,MEMOBJ_STRING); + } + /* Format the given string */ + va_start(ap,zFormat); + rc = SyBlobFormatAp(&p->sBlob,zFormat,ap); + va_end(ap); + return rc; +} +/* + * [CAPIREF: ph7_result_value()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +int ph7_result_value(ph7_context *pCtx,ph7_value *pValue) +{ + int rc = PH7_OK; + if( pValue == 0 ){ + PH7_MemObjRelease(pCtx->pRet); + }else{ + rc = PH7_MemObjStore(pValue,pCtx->pRet); + } + return rc; +} +/* + * [CAPIREF: ph7_result_resource()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +int ph7_result_resource(ph7_context *pCtx,void *pUserData) +{ + return ph7_value_resource(pCtx->pRet,pUserData); +} +/* + * [CAPIREF: ph7_context_new_scalar()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +ph7_value * ph7_context_new_scalar(ph7_context *pCtx) +{ + ph7_value *pVal; + pVal = ph7_new_scalar(pCtx->pVm); + if( pVal ){ + /* Record value address so it can be freed automatically + * when the calling function returns. + */ + SySetPut(&pCtx->sVar,(const void *)&pVal); + } + return pVal; +} +/* + * [CAPIREF: ph7_context_new_array()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +ph7_value * ph7_context_new_array(ph7_context *pCtx) +{ + ph7_value *pVal; + pVal = ph7_new_array(pCtx->pVm); + if( pVal ){ + /* Record value address so it can be freed automatically + * when the calling function returns. + */ + SySetPut(&pCtx->sVar,(const void *)&pVal); + } + return pVal; +} +/* + * [CAPIREF: ph7_context_release_value()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +void ph7_context_release_value(ph7_context *pCtx,ph7_value *pValue) +{ + PH7_VmReleaseContextValue(&(*pCtx),pValue); +} +/* + * [CAPIREF: ph7_context_alloc_chunk()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +void * ph7_context_alloc_chunk(ph7_context *pCtx,unsigned int nByte,int ZeroChunk,int AutoRelease) +{ + void *pChunk; + pChunk = SyMemBackendAlloc(&pCtx->pVm->sAllocator,nByte); + if( pChunk ){ + if( ZeroChunk ){ + /* Zero the memory chunk */ + SyZero(pChunk,nByte); + } + if( AutoRelease ){ + ph7_aux_data sAux; + /* Track the chunk so that it can be released automatically + * upon this context is destroyed. + */ + sAux.pAuxData = pChunk; + SySetPut(&pCtx->sChunk,(const void *)&sAux); + } + } + return pChunk; +} +/* + * Check if the given chunk address is registered in the call context + * chunk container. + * Return TRUE if registered.FALSE otherwise. + * Refer to [ph7_context_realloc_chunk(),ph7_context_free_chunk()]. + */ +static ph7_aux_data * ContextFindChunk(ph7_context *pCtx,void *pChunk) +{ + ph7_aux_data *aAux,*pAux; + sxu32 n; + if( SySetUsed(&pCtx->sChunk) < 1 ){ + /* Don't bother processing,the container is empty */ + return 0; + } + /* Perform the lookup */ + aAux = (ph7_aux_data *)SySetBasePtr(&pCtx->sChunk); + for( n = 0; n < SySetUsed(&pCtx->sChunk) ; ++n ){ + pAux = &aAux[n]; + if( pAux->pAuxData == pChunk ){ + /* Chunk found */ + return pAux; + } + } + /* No such allocated chunk */ + return 0; +} +/* + * [CAPIREF: ph7_context_realloc_chunk()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +void * ph7_context_realloc_chunk(ph7_context *pCtx,void *pChunk,unsigned int nByte) +{ + ph7_aux_data *pAux; + void *pNew; + pNew = SyMemBackendRealloc(&pCtx->pVm->sAllocator,pChunk,nByte); + if( pNew ){ + pAux = ContextFindChunk(pCtx,pChunk); + if( pAux ){ + pAux->pAuxData = pNew; + } + } + return pNew; +} +/* + * [CAPIREF: ph7_context_free_chunk()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +void ph7_context_free_chunk(ph7_context *pCtx,void *pChunk) +{ + ph7_aux_data *pAux; + if( pChunk == 0 ){ + /* TICKET-1433-93: NULL chunk is a harmless operation */ + return; + } + pAux = ContextFindChunk(pCtx,pChunk); + if( pAux ){ + /* Mark as destroyed */ + pAux->pAuxData = 0; + } + SyMemBackendFree(&pCtx->pVm->sAllocator,pChunk); +} +/* + * [CAPIREF: ph7_array_fetch()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +ph7_value * ph7_array_fetch(ph7_value *pArray,const char *zKey,int nByte) +{ + ph7_hashmap_node *pNode; + ph7_value *pValue; + ph7_value skey; + int rc; + /* Make sure we are dealing with a valid hashmap */ + if( (pArray->iFlags & MEMOBJ_HASHMAP) == 0 ){ + return 0; + } + if( nByte < 0 ){ + nByte = (int)SyStrlen(zKey); + } + /* Convert the key to a ph7_value */ + PH7_MemObjInit(pArray->pVm,&skey); + PH7_MemObjStringAppend(&skey,zKey,(sxu32)nByte); + /* Perform the lookup */ + rc = PH7_HashmapLookup((ph7_hashmap *)pArray->x.pOther,&skey,&pNode); + PH7_MemObjRelease(&skey); + if( rc != PH7_OK ){ + /* No such entry */ + return 0; + } + /* Extract the target value */ + pValue = (ph7_value *)SySetAt(&pArray->pVm->aMemObj,pNode->nValIdx); + return pValue; +} +/* + * [CAPIREF: ph7_array_walk()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +int ph7_array_walk(ph7_value *pArray,int (*xWalk)(ph7_value *pValue,ph7_value *,void *),void *pUserData) +{ + int rc; + if( xWalk == 0 ){ + return PH7_CORRUPT; + } + /* Make sure we are dealing with a valid hashmap */ + if( (pArray->iFlags & MEMOBJ_HASHMAP) == 0 ){ + return PH7_CORRUPT; + } + /* Start the walk process */ + rc = PH7_HashmapWalk((ph7_hashmap *)pArray->x.pOther,xWalk,pUserData); + return rc != PH7_OK ? PH7_ABORT /* User callback request an operation abort*/ : PH7_OK; +} +/* + * [CAPIREF: ph7_array_add_elem()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +int ph7_array_add_elem(ph7_value *pArray,ph7_value *pKey,ph7_value *pValue) +{ + int rc; + /* Make sure we are dealing with a valid hashmap */ + if( (pArray->iFlags & MEMOBJ_HASHMAP) == 0 ){ + return PH7_CORRUPT; + } + /* Perform the insertion */ + rc = PH7_HashmapInsert((ph7_hashmap *)pArray->x.pOther,&(*pKey),&(*pValue)); + return rc; +} +/* + * [CAPIREF: ph7_array_add_strkey_elem()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +int ph7_array_add_strkey_elem(ph7_value *pArray,const char *zKey,ph7_value *pValue) +{ + int rc; + /* Make sure we are dealing with a valid hashmap */ + if( (pArray->iFlags & MEMOBJ_HASHMAP) == 0 ){ + return PH7_CORRUPT; + } + /* Perform the insertion */ + if( SX_EMPTY_STR(zKey) ){ + /* Empty key,assign an automatic index */ + rc = PH7_HashmapInsert((ph7_hashmap *)pArray->x.pOther,0,&(*pValue)); + }else{ + ph7_value sKey; + PH7_MemObjInitFromString(pArray->pVm,&sKey,0); + PH7_MemObjStringAppend(&sKey,zKey,(sxu32)SyStrlen(zKey)); + rc = PH7_HashmapInsert((ph7_hashmap *)pArray->x.pOther,&sKey,&(*pValue)); + PH7_MemObjRelease(&sKey); + } + return rc; +} +/* + * [CAPIREF: ph7_array_add_intkey_elem()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +int ph7_array_add_intkey_elem(ph7_value *pArray,int iKey,ph7_value *pValue) +{ + ph7_value sKey; + int rc; + /* Make sure we are dealing with a valid hashmap */ + if( (pArray->iFlags & MEMOBJ_HASHMAP) == 0 ){ + return PH7_CORRUPT; + } + PH7_MemObjInitFromInt(pArray->pVm,&sKey,iKey); + /* Perform the insertion */ + rc = PH7_HashmapInsert((ph7_hashmap *)pArray->x.pOther,&sKey,&(*pValue)); + PH7_MemObjRelease(&sKey); + return rc; +} +/* + * [CAPIREF: ph7_array_count()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +unsigned int ph7_array_count(ph7_value *pArray) +{ + ph7_hashmap *pMap; + /* Make sure we are dealing with a valid hashmap */ + if( (pArray->iFlags & MEMOBJ_HASHMAP) == 0 ){ + return 0; + } + /* Point to the internal representation of the hashmap */ + pMap = (ph7_hashmap *)pArray->x.pOther; + return pMap->nEntry; +} +/* + * [CAPIREF: ph7_object_walk()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +int ph7_object_walk(ph7_value *pObject,int (*xWalk)(const char *,ph7_value *,void *),void *pUserData) +{ + int rc; + if( xWalk == 0 ){ + return PH7_CORRUPT; + } + /* Make sure we are dealing with a valid class instance */ + if( (pObject->iFlags & MEMOBJ_OBJ) == 0 ){ + return PH7_CORRUPT; + } + /* Start the walk process */ + rc = PH7_ClassInstanceWalk((ph7_class_instance *)pObject->x.pOther,xWalk,pUserData); + return rc != PH7_OK ? PH7_ABORT /* User callback request an operation abort*/ : PH7_OK; +} +/* + * [CAPIREF: ph7_object_fetch_attr()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +ph7_value * ph7_object_fetch_attr(ph7_value *pObject,const char *zAttr) +{ + ph7_value *pValue; + SyString sAttr; + /* Make sure we are dealing with a valid class instance */ + if( (pObject->iFlags & MEMOBJ_OBJ) == 0 || zAttr == 0 ){ + return 0; + } + SyStringInitFromBuf(&sAttr,zAttr,SyStrlen(zAttr)); + /* Extract the attribute value if available. + */ + pValue = PH7_ClassInstanceFetchAttr((ph7_class_instance *)pObject->x.pOther,&sAttr); + return pValue; +} +/* + * [CAPIREF: ph7_object_get_class_name()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +const char * ph7_object_get_class_name(ph7_value *pObject,int *pLength) +{ + ph7_class *pClass; + if( pLength ){ + *pLength = 0; + } + /* Make sure we are dealing with a valid class instance */ + if( (pObject->iFlags & MEMOBJ_OBJ) == 0 ){ + return 0; + } + /* Point to the class */ + pClass = ((ph7_class_instance *)pObject->x.pOther)->pClass; + /* Return the class name */ + if( pLength ){ + *pLength = (int)SyStringLength(&pClass->sName); + } + return SyStringData(&pClass->sName); +} +/* + * [CAPIREF: ph7_context_output()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +int ph7_context_output(ph7_context *pCtx,const char *zString,int nLen) +{ + SyString sData; + int rc; + if( nLen < 0 ){ + nLen = (int)SyStrlen(zString); + } + SyStringInitFromBuf(&sData,zString,nLen); + rc = PH7_VmOutputConsume(pCtx->pVm,&sData); + return rc; +} +/* + * [CAPIREF: ph7_context_output_format()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +int ph7_context_output_format(ph7_context *pCtx,const char *zFormat,...) +{ + va_list ap; + int rc; + va_start(ap,zFormat); + rc = PH7_VmOutputConsumeAp(pCtx->pVm,zFormat,ap); + va_end(ap); + return rc; +} +/* + * [CAPIREF: ph7_context_throw_error()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +int ph7_context_throw_error(ph7_context *pCtx,int iErr,const char *zErr) +{ + int rc = PH7_OK; + if( zErr ){ + rc = PH7_VmThrowError(pCtx->pVm,&pCtx->pFunc->sName,iErr,zErr); + } + return rc; +} +/* + * [CAPIREF: ph7_context_throw_error_format()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +int ph7_context_throw_error_format(ph7_context *pCtx,int iErr,const char *zFormat,...) +{ + va_list ap; + int rc; + if( zFormat == 0){ + return PH7_OK; + } + va_start(ap,zFormat); + rc = PH7_VmThrowErrorAp(pCtx->pVm,&pCtx->pFunc->sName,iErr,zFormat,ap); + va_end(ap); + return rc; +} +/* + * [CAPIREF: ph7_context_random_num()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +unsigned int ph7_context_random_num(ph7_context *pCtx) +{ + sxu32 n; + n = PH7_VmRandomNum(pCtx->pVm); + return n; +} +/* + * [CAPIREF: ph7_context_random_string()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +int ph7_context_random_string(ph7_context *pCtx,char *zBuf,int nBuflen) +{ + if( nBuflen < 3 ){ + return PH7_CORRUPT; + } + PH7_VmRandomString(pCtx->pVm,zBuf,nBuflen); + return PH7_OK; +} +/* + * IMP-12-07-2012 02:10 Experimantal public API. + * + * ph7_vm * ph7_context_get_vm(ph7_context *pCtx) + * { + * return pCtx->pVm; + * } + */ +/* + * [CAPIREF: ph7_context_user_data()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +void * ph7_context_user_data(ph7_context *pCtx) +{ + return pCtx->pFunc->pUserData; +} +/* + * [CAPIREF: ph7_context_push_aux_data()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +int ph7_context_push_aux_data(ph7_context *pCtx,void *pUserData) +{ + ph7_aux_data sAux; + int rc; + sAux.pAuxData = pUserData; + rc = SySetPut(&pCtx->pFunc->aAux,(const void *)&sAux); + return rc; +} +/* + * [CAPIREF: ph7_context_peek_aux_data()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +void * ph7_context_peek_aux_data(ph7_context *pCtx) +{ + ph7_aux_data *pAux; + pAux = (ph7_aux_data *)SySetPeek(&pCtx->pFunc->aAux); + return pAux ? pAux->pAuxData : 0; +} +/* + * [CAPIREF: ph7_context_pop_aux_data()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +void * ph7_context_pop_aux_data(ph7_context *pCtx) +{ + ph7_aux_data *pAux; + pAux = (ph7_aux_data *)SySetPop(&pCtx->pFunc->aAux); + return pAux ? pAux->pAuxData : 0; +} +/* + * [CAPIREF: ph7_context_result_buf_length()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +unsigned int ph7_context_result_buf_length(ph7_context *pCtx) +{ + return SyBlobLength(&pCtx->pRet->sBlob); +} +/* + * [CAPIREF: ph7_function_name()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +const char * ph7_function_name(ph7_context *pCtx) +{ + SyString *pName; + pName = &pCtx->pFunc->sName; + return pName->zString; +} +/* + * [CAPIREF: ph7_value_int()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +int ph7_value_int(ph7_value *pVal,int iValue) +{ + /* Invalidate any prior representation */ + PH7_MemObjRelease(pVal); + pVal->x.iVal = (ph7_int64)iValue; + MemObjSetType(pVal,MEMOBJ_INT); + return PH7_OK; +} +/* + * [CAPIREF: ph7_value_int64()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +int ph7_value_int64(ph7_value *pVal,ph7_int64 iValue) +{ + /* Invalidate any prior representation */ + PH7_MemObjRelease(pVal); + pVal->x.iVal = iValue; + MemObjSetType(pVal,MEMOBJ_INT); + return PH7_OK; +} +/* + * [CAPIREF: ph7_value_bool()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +int ph7_value_bool(ph7_value *pVal,int iBool) +{ + /* Invalidate any prior representation */ + PH7_MemObjRelease(pVal); + pVal->x.iVal = iBool ? 1 : 0; + MemObjSetType(pVal,MEMOBJ_BOOL); + return PH7_OK; +} +/* + * [CAPIREF: ph7_value_null()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +int ph7_value_null(ph7_value *pVal) +{ + /* Invalidate any prior representation and set the NULL flag */ + PH7_MemObjRelease(pVal); + return PH7_OK; +} +/* + * [CAPIREF: ph7_value_double()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +int ph7_value_double(ph7_value *pVal,double Value) +{ + /* Invalidate any prior representation */ + PH7_MemObjRelease(pVal); + pVal->rVal = (ph7_real)Value; + MemObjSetType(pVal,MEMOBJ_REAL); + /* Try to get an integer representation also */ + PH7_MemObjTryInteger(pVal); + return PH7_OK; +} +/* + * [CAPIREF: ph7_value_string()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +int ph7_value_string(ph7_value *pVal,const char *zString,int nLen) +{ + if((pVal->iFlags & MEMOBJ_STRING) == 0 ){ + /* Invalidate any prior representation */ + PH7_MemObjRelease(pVal); + MemObjSetType(pVal,MEMOBJ_STRING); + } + if( zString ){ + if( nLen < 0 ){ + /* Compute length automatically */ + nLen = (int)SyStrlen(zString); + } + SyBlobAppend(&pVal->sBlob,(const void *)zString,(sxu32)nLen); + } + return PH7_OK; +} +/* + * [CAPIREF: ph7_value_string_format()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +int ph7_value_string_format(ph7_value *pVal,const char *zFormat,...) +{ + va_list ap; + int rc; + if((pVal->iFlags & MEMOBJ_STRING) == 0 ){ + /* Invalidate any prior representation */ + PH7_MemObjRelease(pVal); + MemObjSetType(pVal,MEMOBJ_STRING); + } + va_start(ap,zFormat); + rc = SyBlobFormatAp(&pVal->sBlob,zFormat,ap); + va_end(ap); + return PH7_OK; +} +/* + * [CAPIREF: ph7_value_reset_string_cursor()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +int ph7_value_reset_string_cursor(ph7_value *pVal) +{ + /* Reset the string cursor */ + SyBlobReset(&pVal->sBlob); + return PH7_OK; +} +/* + * [CAPIREF: ph7_value_resource()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +int ph7_value_resource(ph7_value *pVal,void *pUserData) +{ + /* Invalidate any prior representation */ + PH7_MemObjRelease(pVal); + /* Reflect the new type */ + pVal->x.pOther = pUserData; + MemObjSetType(pVal,MEMOBJ_RES); + return PH7_OK; +} +/* + * [CAPIREF: ph7_value_release()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +int ph7_value_release(ph7_value *pVal) +{ + PH7_MemObjRelease(pVal); + return PH7_OK; +} +/* + * [CAPIREF: ph7_value_is_int()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +int ph7_value_is_int(ph7_value *pVal) +{ + return (pVal->iFlags & MEMOBJ_INT) ? TRUE : FALSE; +} +/* + * [CAPIREF: ph7_value_is_float()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +int ph7_value_is_float(ph7_value *pVal) +{ + return (pVal->iFlags & MEMOBJ_REAL) ? TRUE : FALSE; +} +/* + * [CAPIREF: ph7_value_is_bool()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +int ph7_value_is_bool(ph7_value *pVal) +{ + return (pVal->iFlags & MEMOBJ_BOOL) ? TRUE : FALSE; +} +/* + * [CAPIREF: ph7_value_is_string()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +int ph7_value_is_string(ph7_value *pVal) +{ + return (pVal->iFlags & MEMOBJ_STRING) ? TRUE : FALSE; +} +/* + * [CAPIREF: ph7_value_is_null()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +int ph7_value_is_null(ph7_value *pVal) +{ + return (pVal->iFlags & MEMOBJ_NULL) ? TRUE : FALSE; +} +/* + * [CAPIREF: ph7_value_is_numeric()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +int ph7_value_is_numeric(ph7_value *pVal) +{ + int rc; + rc = PH7_MemObjIsNumeric(pVal); + return rc; +} +/* + * [CAPIREF: ph7_value_is_callable()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +int ph7_value_is_callable(ph7_value *pVal) +{ + int rc; + rc = PH7_VmIsCallable(pVal->pVm,pVal,FALSE); + return rc; +} +/* + * [CAPIREF: ph7_value_is_scalar()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +int ph7_value_is_scalar(ph7_value *pVal) +{ + return (pVal->iFlags & MEMOBJ_SCALAR) ? TRUE : FALSE; +} +/* + * [CAPIREF: ph7_value_is_array()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +int ph7_value_is_array(ph7_value *pVal) +{ + return (pVal->iFlags & MEMOBJ_HASHMAP) ? TRUE : FALSE; +} +/* + * [CAPIREF: ph7_value_is_object()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +int ph7_value_is_object(ph7_value *pVal) +{ + return (pVal->iFlags & MEMOBJ_OBJ) ? TRUE : FALSE; +} +/* + * [CAPIREF: ph7_value_is_resource()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +int ph7_value_is_resource(ph7_value *pVal) +{ + return (pVal->iFlags & MEMOBJ_RES) ? TRUE : FALSE; +} +/* + * [CAPIREF: ph7_value_is_empty()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +int ph7_value_is_empty(ph7_value *pVal) +{ + int rc; + rc = PH7_MemObjIsEmpty(pVal); + return rc; +} +/* END-OF-IMPLEMENTATION: ph7@embedded@symisc 345-09-46 */ +/* + * Symisc PH7: An embeddable bytecode compiler and a virtual machine for the PHP(5) programming language. + * Copyright (C) 2011-2012,Symisc Systems http://ph7.symisc.net/ + * Version 2.1.4 + * For information on licensing,redistribution of this file,and for a DISCLAIMER OF ALL WARRANTIES + * please contact Symisc Systems via: + * legal@symisc.net + * licensing@symisc.net + * contact@symisc.net + * or visit: + * http://ph7.symisc.net/ + */ +/* + * Copyright (C) 2011, 2012 Symisc Systems. 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. Redistributions in any form must be accompanied by information on + * how to obtain complete source code for the PH7 engine and any + * accompanying software that uses the PH7 engine software. + * The source code must either be included in the distribution + * or be available for no more than the cost of distribution plus + * a nominal fee, and must be freely redistributable under reasonable + * conditions. For an executable file, complete source code means + * the source code for all modules it contains.It does not include + * source code for modules or files that typically accompany the major + * components of the operating system on which the executable file runs. + * + * THIS SOFTWARE IS PROVIDED BY SYMISC SYSTEMS ``AS IS'' AND ANY EXPRESS + * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR + * NON-INFRINGEMENT, ARE DISCLAIMED. IN NO EVENT SHALL SYMISC SYSTEMS + * 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. + */ diff --git a/src/ph7/ph7.h b/src/ph7/ph7.h new file mode 100644 index 000000000..6c172b636 --- /dev/null +++ b/src/ph7/ph7.h @@ -0,0 +1,714 @@ +/* This file was automatically generated. Do not edit (except for compile time directive)! */ +#ifndef _PH7_H_ +#define _PH7_H_ +/* + * Symisc PH7: An embeddable bytecode compiler and a virtual machine for the PHP(5) programming language. + * Copyright (C) 2011-2012, Symisc Systems http://ph7.symisc.net/ + * Version 2.1.4 + * For information on licensing,redistribution of this file,and for a DISCLAIMER OF ALL WARRANTIES + * please contact Symisc Systems via: + * legal@symisc.net + * licensing@symisc.net + * contact@symisc.net + * or visit: + * http://ph7.symisc.net/ + */ +/* + * Copyright (C) 2011, 2012 Symisc Systems. 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. Redistributions in any form must be accompanied by information on + * how to obtain complete source code for the PH7 engine and any + * accompanying software that uses the PH7 engine software. + * The source code must either be included in the distribution + * or be available for no more than the cost of distribution plus + * a nominal fee, and must be freely redistributable under reasonable + * conditions. For an executable file, complete source code means + * the source code for all modules it contains.It does not include + * source code for modules or files that typically accompany the major + * components of the operating system on which the executable file runs. + * + * THIS SOFTWARE IS PROVIDED BY SYMISC SYSTEMS ``AS IS'' AND ANY EXPRESS + * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR + * NON-INFRINGEMENT, ARE DISCLAIMED. IN NO EVENT SHALL SYMISC SYSTEMS + * 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. + */ + /* $SymiscID: ph7.h v2.1 UNIX|WIN32/64 2012-09-15 09:43 stable $ */ +#include /* needed for the definition of va_list */ +/* + * Compile time engine version, signature, identification in the symisc source tree + * and copyright notice. + * Each macro have an equivalent C interface associated with it that provide the same + * information but are associated with the library instead of the header file. + * Refer to [ph7_lib_version()], [ph7_lib_signature()], [ph7_lib_ident()] and + * [ph7_lib_copyright()] for more information. + */ +/* + * The PH7_VERSION C preprocessor macroevaluates to a string literal + * that is the ph7 version in the format "X.Y.Z" where X is the major + * version number and Y is the minor version number and Z is the release + * number. + */ +#define PH7_VERSION "2.1.4" +/* + * The PH7_VERSION_NUMBER C preprocessor macro resolves to an integer + * with the value (X*1000000 + Y*1000 + Z) where X, Y, and Z are the same + * numbers used in [PH7_VERSION]. + */ +#define PH7_VERSION_NUMBER 2001004 +/* + * The PH7_SIG C preprocessor macro evaluates to a string + * literal which is the public signature of the ph7 engine. + * This signature could be included for example in a host-application + * generated Server MIME header as follows: + * Server: YourWebServer/x.x PH7/x.x.x \r\n + */ +#define PH7_SIG "PH7/2.1.4" +/* + * PH7 identification in the Symisc source tree: + * Each particular check-in of a particular software released + * by symisc systems have an unique identifier associated with it. + * This macro hold the one associated with ph7. + */ +#define PH7_IDENT "ph7:c193f4d8a6b90ee60f9afad11840f1010054fdf9" +/* + * Copyright notice. + * If you have any questions about the licensing situation,please + * visit http://ph7.symisc.net/licensing.html + * or contact Symisc Systems via: + * legal@symisc.net + * licensing@symisc.net + * contact@symisc.net + */ +#define PH7_COPYRIGHT "Copyright (C) Symisc Systems 2011-2012, http://ph7.symisc.net/" +/* Make sure we can call this stuff from C++ */ +#ifdef __cplusplus +extern "C" { +#endif +/* Forward declaration to public objects */ +typedef struct ph7_io_stream ph7_io_stream; +typedef struct ph7_context ph7_context; +typedef struct ph7_value ph7_value; +typedef struct ph7_vfs ph7_vfs; +typedef struct ph7_vm ph7_vm; +typedef struct ph7 ph7; +/* + * ------------------------------ + * Compile time directives + * ------------------------------ + * For most purposes, PH7 can be built just fine using the default compilation options. + * However, if required, the compile-time options documented below can be used to omit + * PH7 features (resulting in a smaller compiled library size) or to change the default + * values of some parameters. + * Every effort has been made to ensure that the various combinations of compilation + * options work harmoniously and produce a working library. + * + * PH7_ENABLE_THREADS + * This option controls whether or not code is included in PH7 to enable it to operate + * safely in a multithreaded environment. The default is not. That is,all mutexing code + * is omitted and it is unsafe to use PH7 in a multithreaded program. When compiled + * with the PH7_ENABLE_THREADS directive enabled, PH7 can be used in a multithreaded + * program and it's safe to share the same virtual machine and engine instance between + * two or more threads. + * The value of PH7_ENABLE_THREADS can be determined at run-time using the + * ph7_lib_is_threadsafe() interface.When PH7 has been compiled with PH7_ENABLE_THREAD + * then the threading mode can be altered at run-time using the ph7_lib_config() + * interface together with one of these verbs: + * PH7_LIB_CONFIG_THREAD_LEVEL_SINGLE + * PH7_LIB_CONFIG_THREAD_LEVEL_MULTI + * Also note,platforms others than Windows and UNIX systems must install their own + * mutex subsystem via ph7_lib_config() with a configuration verb set to + * PH7_LIB_CONFIG_USER_MUTEX. Otherwise the library is not threadsafe. + * Note that you must link PH7 with the POSIX threads library under UNIX-like systems + * (i.e: -lpthread).Otherwise you will get a link time error. + * Options To Omit/Enable Features: + * The following options can be used to reduce the size of the compiled library + * by omitting optional features. This is probably only useful in embedded systems + * where space is especially tight, as even with all features included the PH7 library + * is relatively small. Don't forget to tell your compiler to optimize for binary + * size! (the -Os option if using GCC). + * Telling your compiler to optimize for size usually has a much larger impact + * on library footprint than employing any of these compile-time options. + * PH7_DISABLE_BUILTIN_FUNC + * PH7 come with more than 460 built-in functions suitable for most purposes ranging + * from string/XML/INI processing to ZIP extracting, Base64 encoding/decoding and so on. + * If this directive is enabled, all the built-in functions are omitted from the build. + * Note that language construct functions such as is_int(), is_string(), func_get_arg(), + * define(), etc. are not omitted from the build and are not affected by this directive. + * PH7_ENABLE_MATH_FUNC + * If this directive is enabled, built-in math functions such as sqrt(),abs(), + * log(), ceil(), etc. are included in the build. Note that you may need to link + * PH7 with the math library in same linux/BSD flavor (i.e: -lm).Otherwise you + * will get a link time error. + * PH7_DISABLE_DISK_IO + * If this directive is enabled, built-in Virtual File System functions such as + * chdir(), mkdir(), chroot(), unlink(), delete(), etc. are omitted from the build. + * PH7_DISABLE_HASH_IO + * If this directive is enabled, built-in hash functions such as md5(), sha1(), + * md5_file(), crc32(), etc. are omitted from the build. + * PH7_OMIT_FLOATING_POINT + * This option is used to omit floating-point number support from the PH7 library + * if compiling for a processor that lacks floating point support. When specified + * the library will substitute 64-bit integer arithmetic for floating-point which + * mean that 25.e-3 and 25 are equals and are of type integer. + */ +/* Symisc public definitions */ +#if !defined(SYMISC_STANDARD_DEFS) +#define SYMISC_STANDARD_DEFS +#if defined (_WIN32) || defined (WIN32) || defined(__MINGW32__) || defined (_MSC_VER) || defined (_WIN32_WCE) +/* Windows Systems */ +#if !defined(__WINNT__) +#define __WINNT__ +#endif +#else +/* + * By default we will assume that we are compiling on a UNIX systems. + * Otherwise the OS_OTHER directive must be defined. + */ +#if !defined(OS_OTHER) +#if !defined(__UNIXES__) +#define __UNIXES__ +#endif /* __UNIXES__ */ +#else +#endif /* OS_OTHER */ +#endif /* __WINNT__/__UNIXES__ */ +#if defined(_MSC_VER) || defined(__BORLANDC__) +typedef signed __int64 sxi64; /* 64 bits(8 bytes) signed int64 */ +typedef unsigned __int64 sxu64; /* 64 bits(8 bytes) unsigned int64 */ +#else +typedef signed long long int sxi64; /* 64 bits(8 bytes) signed int64 */ +typedef unsigned long long int sxu64; /* 64 bits(8 bytes) unsigned int64 */ +#endif /* _MSC_VER */ +/* Signature of the consumer routine */ +typedef int (*ProcConsumer)(const void *,unsigned int,void *); +/* Forward reference */ +typedef struct SyMutexMethods SyMutexMethods; +typedef struct SyMemMethods SyMemMethods; +typedef struct SyString SyString; +typedef struct syiovec syiovec; +typedef struct SyMutex SyMutex; +typedef struct Sytm Sytm; +/* Scatter and gather array. */ +struct syiovec +{ +#if defined (__WINNT__) + /* Same fields type and offset as WSABUF structure defined one winsock2 header */ + unsigned long nLen; + char *pBase; +#else + void *pBase; + unsigned long nLen; +#endif +}; +struct SyString +{ + const char *zString; /* Raw string (may not be null terminated) */ + unsigned int nByte; /* Raw string length */ +}; +/* Time structure. */ +struct Sytm +{ + int tm_sec; /* seconds (0 - 60) */ + int tm_min; /* minutes (0 - 59) */ + int tm_hour; /* hours (0 - 23) */ + int tm_mday; /* day of month (1 - 31) */ + int tm_mon; /* month of year (0 - 11) */ + int tm_year; /* year + 1900 */ + int tm_wday; /* day of week (Sunday = 0) */ + int tm_yday; /* day of year (0 - 365) */ + int tm_isdst; /* is summer time in effect? */ + char *tm_zone; /* abbreviation of timezone name */ + long tm_gmtoff; /* offset from UTC in seconds */ +}; +/* Convert a tm structure (struct tm *) found in to a Sytm structure */ +#define STRUCT_TM_TO_SYTM(pTM,pSYTM) \ + (pSYTM)->tm_hour = (pTM)->tm_hour;\ + (pSYTM)->tm_min = (pTM)->tm_min;\ + (pSYTM)->tm_sec = (pTM)->tm_sec;\ + (pSYTM)->tm_mon = (pTM)->tm_mon;\ + (pSYTM)->tm_mday = (pTM)->tm_mday;\ + (pSYTM)->tm_year = (pTM)->tm_year + 1900;\ + (pSYTM)->tm_yday = (pTM)->tm_yday;\ + (pSYTM)->tm_wday = (pTM)->tm_wday;\ + (pSYTM)->tm_isdst = (pTM)->tm_isdst;\ + (pSYTM)->tm_gmtoff = 0;\ + (pSYTM)->tm_zone = 0; + +/* Convert a SYSTEMTIME structure (LPSYSTEMTIME: Windows Systems only ) to a Sytm structure */ +#define SYSTEMTIME_TO_SYTM(pSYSTIME,pSYTM) \ + (pSYTM)->tm_hour = (pSYSTIME)->wHour;\ + (pSYTM)->tm_min = (pSYSTIME)->wMinute;\ + (pSYTM)->tm_sec = (pSYSTIME)->wSecond;\ + (pSYTM)->tm_mon = (pSYSTIME)->wMonth - 1;\ + (pSYTM)->tm_mday = (pSYSTIME)->wDay;\ + (pSYTM)->tm_year = (pSYSTIME)->wYear;\ + (pSYTM)->tm_yday = 0;\ + (pSYTM)->tm_wday = (pSYSTIME)->wDayOfWeek;\ + (pSYTM)->tm_gmtoff = 0;\ + (pSYTM)->tm_isdst = -1;\ + (pSYTM)->tm_zone = 0; + +/* Dynamic memory allocation methods. */ +struct SyMemMethods +{ + void * (*xAlloc)(unsigned int); /* [Required:] Allocate a memory chunk */ + void * (*xRealloc)(void *,unsigned int); /* [Required:] Re-allocate a memory chunk */ + void (*xFree)(void *); /* [Required:] Release a memory chunk */ + unsigned int (*xChunkSize)(void *); /* [Optional:] Return chunk size */ + int (*xInit)(void *); /* [Optional:] Initialization callback */ + void (*xRelease)(void *); /* [Optional:] Release callback */ + void *pUserData; /* [Optional:] First argument to xInit() and xRelease() */ +}; +/* Out of memory callback signature. */ +typedef int (*ProcMemError)(void *); +/* Mutex methods. */ +struct SyMutexMethods +{ + int (*xGlobalInit)(void); /* [Optional:] Global mutex initialization */ + void (*xGlobalRelease)(void); /* [Optional:] Global Release callback () */ + SyMutex * (*xNew)(int); /* [Required:] Request a new mutex */ + void (*xRelease)(SyMutex *); /* [Optional:] Release a mutex */ + void (*xEnter)(SyMutex *); /* [Required:] Enter mutex */ + int (*xTryEnter)(SyMutex *); /* [Optional:] Try to enter a mutex */ + void (*xLeave)(SyMutex *); /* [Required:] Leave a locked mutex */ +}; +#if defined (_MSC_VER) || defined (__MINGW32__) || defined (__GNUC__) && defined (__declspec) +#define SX_APIIMPORT __declspec(dllimport) +#define SX_APIEXPORT __declspec(dllexport) +#else +#define SX_APIIMPORT +#define SX_APIEXPORT +#endif +/* Standard return values from Symisc public interfaces */ +#define SXRET_OK 0 /* Not an error */ +#define SXERR_MEM (-1) /* Out of memory */ +#define SXERR_IO (-2) /* IO error */ +#define SXERR_EMPTY (-3) /* Empty field */ +#define SXERR_LOCKED (-4) /* Locked operation */ +#define SXERR_ORANGE (-5) /* Out of range value */ +#define SXERR_NOTFOUND (-6) /* Item not found */ +#define SXERR_LIMIT (-7) /* Limit reached */ +#define SXERR_MORE (-8) /* Need more input */ +#define SXERR_INVALID (-9) /* Invalid parameter */ +#define SXERR_ABORT (-10) /* User callback request an operation abort */ +#define SXERR_EXISTS (-11) /* Item exists */ +#define SXERR_SYNTAX (-12) /* Syntax error */ +#define SXERR_UNKNOWN (-13) /* Unknown error */ +#define SXERR_BUSY (-14) /* Busy operation */ +#define SXERR_OVERFLOW (-15) /* Stack or buffer overflow */ +#define SXERR_WILLBLOCK (-16) /* Operation will block */ +#define SXERR_NOTIMPLEMENTED (-17) /* Operation not implemented */ +#define SXERR_EOF (-18) /* End of input */ +#define SXERR_PERM (-19) /* Permission error */ +#define SXERR_NOOP (-20) /* No-op */ +#define SXERR_FORMAT (-21) /* Invalid format */ +#define SXERR_NEXT (-22) /* Not an error */ +#define SXERR_OS (-23) /* System call return an error */ +#define SXERR_CORRUPT (-24) /* Corrupted pointer */ +#define SXERR_CONTINUE (-25) /* Not an error: Operation in progress */ +#define SXERR_NOMATCH (-26) /* No match */ +#define SXERR_RESET (-27) /* Operation reset */ +#define SXERR_DONE (-28) /* Not an error */ +#define SXERR_SHORT (-29) /* Buffer too short */ +#define SXERR_PATH (-30) /* Path error */ +#define SXERR_TIMEOUT (-31) /* Timeout */ +#define SXERR_BIG (-32) /* Too big for processing */ +#define SXERR_RETRY (-33) /* Retry your call */ +#define SXERR_IGNORE (-63) /* Ignore */ +#endif /* SYMISC_PUBLIC_DEFS */ +/* Standard PH7 return values */ +#define PH7_OK SXRET_OK /* Successful result */ +/* beginning-of-error-codes */ +#define PH7_NOMEM SXERR_MEM /* Out of memory */ +#define PH7_ABORT SXERR_ABORT /* Foreign Function request operation abort/Another thread have released this instance */ +#define PH7_IO_ERR SXERR_IO /* IO error */ +#define PH7_CORRUPT SXERR_CORRUPT /* Corrupt pointer/Unknown configuration option */ +#define PH7_LOOKED SXERR_LOCKED /* Forbidden Operation */ +#define PH7_COMPILE_ERR (-70) /* Compilation error */ +#define PH7_VM_ERR (-71) /* Virtual machine error */ +/* end-of-error-codes */ +/* + * If compiling for a processor that lacks floating point + * support, substitute integer for floating-point. + */ +#ifdef PH7_OMIT_FLOATING_POINT +typedef sxi64 ph7_real; +#else +typedef double ph7_real; +#endif +typedef sxi64 ph7_int64; +#define PH7_APIEXPORT SX_APIEXPORT +/* + * Engine Configuration Commands. + * + * The following set of constants are the available configuration verbs that can + * be used by the host-application to configure the PH7 engine. + * These constants must be passed as the second argument to the [ph7_config()] + * interface. + * Each options require a variable number of arguments. + * The [ph7_config()] interface will return PH7_OK on success, any other + * return value indicates failure. + * For a full discussion on the configuration verbs and their expected + * parameters, please refer to this page: + * http://ph7.symisc.net/c_api_func.html#ph7_config + */ +#define PH7_CONFIG_ERR_OUTPUT 1 /* TWO ARGUMENTS: int (*xConsumer)(const void *pOut,unsigned int nLen,void *pUserData),void *pUserData */ +#define PH7_CONFIG_ERR_ABORT 2 /* RESERVED FOR FUTURE USE */ +#define PH7_CONFIG_ERR_LOG 3 /* TWO ARGUMENTS: const char **pzBuf,int *pLen */ +/* + * Virtual Machine Configuration Commands. + * + * The following set of constants are the available configuration verbs that can + * be used by the host-application to configure the PH7 Virtual machine. + * These constants must be passed as the second argument to the [ph7_vm_config()] + * interface. + * Each options require a variable number of arguments. + * The [ph7_vm_config()] interface will return PH7_OK on success, any other return + * value indicates failure. + * There are many options but the most importants are: PH7_VM_CONFIG_OUTPUT which install + * a VM output consumer callback, PH7_VM_CONFIG_HTTP_REQUEST which parse and register + * a HTTP request and PH7_VM_CONFIG_ARGV_ENTRY which populate the $argv array. + * For a full discussion on the configuration verbs and their expected parameters, please + * refer to this page: + * http://ph7.symisc.net/c_api_func.html#ph7_vm_config + */ +#define PH7_VM_CONFIG_OUTPUT 1 /* TWO ARGUMENTS: int (*xConsumer)(const void *pOut,unsigned int nLen,void *pUserData),void *pUserData */ +#define PH7_VM_CONFIG_IMPORT_PATH 3 /* ONE ARGUMENT: const char *zIncludePath */ +#define PH7_VM_CONFIG_ERR_REPORT 4 /* NO ARGUMENTS: Report all run-time errors in the VM output */ +#define PH7_VM_CONFIG_RECURSION_DEPTH 5 /* ONE ARGUMENT: int nMaxDepth */ +#define PH7_VM_OUTPUT_LENGTH 6 /* ONE ARGUMENT: unsigned int *pLength */ +#define PH7_VM_CONFIG_CREATE_SUPER 7 /* TWO ARGUMENTS: const char *zName,ph7_value *pValue */ +#define PH7_VM_CONFIG_CREATE_VAR 8 /* TWO ARGUMENTS: const char *zName,ph7_value *pValue */ +#define PH7_VM_CONFIG_HTTP_REQUEST 9 /* TWO ARGUMENTS: const char *zRawRequest,int nRequestLength */ +#define PH7_VM_CONFIG_SERVER_ATTR 10 /* THREE ARGUMENTS: const char *zKey,const char *zValue,int nLen */ +#define PH7_VM_CONFIG_ENV_ATTR 11 /* THREE ARGUMENTS: const char *zKey,const char *zValue,int nLen */ +#define PH7_VM_CONFIG_SESSION_ATTR 12 /* THREE ARGUMENTS: const char *zKey,const char *zValue,int nLen */ +#define PH7_VM_CONFIG_POST_ATTR 13 /* THREE ARGUMENTS: const char *zKey,const char *zValue,int nLen */ +#define PH7_VM_CONFIG_GET_ATTR 14 /* THREE ARGUMENTS: const char *zKey,const char *zValue,int nLen */ +#define PH7_VM_CONFIG_COOKIE_ATTR 15 /* THREE ARGUMENTS: const char *zKey,const char *zValue,int nLen */ +#define PH7_VM_CONFIG_HEADER_ATTR 16 /* THREE ARGUMENTS: const char *zKey,const char *zValue,int nLen */ +#define PH7_VM_CONFIG_EXEC_VALUE 17 /* ONE ARGUMENT: ph7_value **ppValue */ +#define PH7_VM_CONFIG_IO_STREAM 18 /* ONE ARGUMENT: const ph7_io_stream *pStream */ +#define PH7_VM_CONFIG_ARGV_ENTRY 19 /* ONE ARGUMENT: const char *zValue */ +#define PH7_VM_CONFIG_EXTRACT_OUTPUT 20 /* TWO ARGUMENTS: const void **ppOut,unsigned int *pOutputLen */ +#define PH7_VM_CONFIG_ERR_LOG_HANDLER 21 /* ONE ARGUMENT: void (*xErrLog)(const char *,int,const char *,const char *) */ +/* + * Global Library Configuration Commands. + * + * The following set of constants are the available configuration verbs that can + * be used by the host-application to configure the whole library. + * These constants must be passed as the first argument to the [ph7_lib_config()] + * interface. + * Each options require a variable number of arguments. + * The [ph7_lib_config()] interface will return PH7_OK on success, any other return + * value indicates failure. + * Notes: + * The default configuration is recommended for most applications and so the call to + * [ph7_lib_config()] is usually not necessary. It is provided to support rare + * applications with unusual needs. + * The [ph7_lib_config()] interface is not threadsafe. The application must insure that + * no other [ph7_*()] interfaces are invoked by other threads while [ph7_lib_config()] + * is running. Furthermore, [ph7_lib_config()] may only be invoked prior to library + * initialization using [ph7_lib_init()] or [ph7_init()] or after shutdown + * by [ph7_lib_shutdown()]. If [ph7_lib_config()] is called after [ph7_lib_init()] + * or [ph7_init()] and before [ph7_lib_shutdown()] then it will return PH7_LOCKED. + * Refer to the official documentation for more information on the configuration verbs + * and their expected parameters. + * For a full discussion on the configuration verbs and their expected parameters,please + * refer to this page: + * http://ph7.symisc.net/c_api_func.html#Global_Library_Management_Interfaces + */ +#define PH7_LIB_CONFIG_USER_MALLOC 1 /* ONE ARGUMENT: const SyMemMethods *pMemMethods */ +#define PH7_LIB_CONFIG_MEM_ERR_CALLBACK 2 /* TWO ARGUMENTS: int (*xMemError)(void *),void *pUserData */ +#define PH7_LIB_CONFIG_USER_MUTEX 3 /* ONE ARGUMENT: const SyMutexMethods *pMutexMethods */ +#define PH7_LIB_CONFIG_THREAD_LEVEL_SINGLE 4 /* NO ARGUMENTS */ +#define PH7_LIB_CONFIG_THREAD_LEVEL_MULTI 5 /* NO ARGUMENTS */ +#define PH7_LIB_CONFIG_VFS 6 /* ONE ARGUMENT: const ph7_vfs *pVfs */ +/* + * Compile-time flags. + * The new compile interfaces [ph7_compile_v2()] and [ph7_compile_file()] takes + * as their last argument zero or more combination of compile time flags. + * These flags are used to control the behavior of the PH7 compiler while + * processing the input. + * Refer to the official documentation for additional information. + */ +#define PH7_PHP_ONLY 0x01 /* If this flag is set then the code to compile is assumed + * to be plain PHP only. That is, there is no need to delimit + * the PHP code using the standard tags such as or . + * Everything will pass through the PH7 compiler. + */ +#define PH7_PHP_EXPR 0x02 /* This flag is reserved for future use. */ +/* + * Call Context Error Message Serverity Level. + * + * The following constans are the allowed severity level that can + * passed as the second argument to the [ph7_context_throw_error()] or + * [ph7_context_throw_error_format()] interfaces. + * Refer to the official documentation for additional information. + */ +#define PH7_CTX_ERR 1 /* Call context error such as unexpected number of arguments,invalid types and so on. */ +#define PH7_CTX_WARNING 2 /* Call context Warning */ +#define PH7_CTX_NOTICE 3 /* Call context Notice */ +/* Current VFS structure version*/ +#define PH7_VFS_VERSION 2 +/* + * PH7 Virtual File System (VFS). + * + * An instance of the ph7_vfs object defines the interface between the PH7 core + * and the underlying operating system. The "vfs" in the name of the object stands + * for "virtual file system". The vfs is used to implement PHP system functions + * such as mkdir(), chdir(), stat(), get_user_name() and many more. + * The value of the iVersion field is initially 2 but may be larger in future versions + * of PH7. + * Additional fields may be appended to this object when the iVersion value is increased. + * Only a single vfs can be registered within the PH7 core. Vfs registration is done + * using the ph7_lib_config() interface with a configuration verb set to PH7_LIB_CONFIG_VFS. + * Note that Windows and UNIX (Linux, FreeBSD, Solaris, Mac OS X, etc.) users does not have to + * worry about registering and installing a vfs since PH7 come with a built-in vfs for these + * platforms which implement most the methods defined below. + * Host-application running on exotic systems (ie: Other than Windows and UNIX systems) must + * register their own vfs in order to be able to use and call PHP system function. + * Also note that the ph7_compile_file() interface depend on the xMmap() method of the underlying + * vfs which mean that this method must be available (Always the case using the built-in VFS) + * in order to use this interface. + * Developers wishing to implement the vfs methods can contact symisc systems to obtain + * the PH7 VFS C/C++ Specification manual. + */ +struct ph7_vfs +{ + const char *zName; /* Underlying VFS name [i.e: FreeBSD/Linux/Windows...] */ + int iVersion; /* Current VFS structure version [default 2] */ + /* Directory functions */ + int (*xChdir)(const char *); /* Change directory */ + int (*xChroot)(const char *); /* Change the root directory */ + int (*xGetcwd)(ph7_context *); /* Get the current working directory */ + int (*xMkdir)(const char *,int,int); /* Make directory */ + int (*xRmdir)(const char *); /* Remove directory */ + int (*xIsdir)(const char *); /* Tells whether the filename is a directory */ + int (*xRename)(const char *,const char *); /* Renames a file or directory */ + int (*xRealpath)(const char *,ph7_context *); /* Return canonicalized absolute pathname*/ + /* Systems functions */ + int (*xSleep)(unsigned int); /* Delay execution in microseconds */ + int (*xUnlink)(const char *); /* Deletes a file */ + int (*xFileExists)(const char *); /* Checks whether a file or directory exists */ + int (*xChmod)(const char *,int); /* Changes file mode */ + int (*xChown)(const char *,const char *); /* Changes file owner */ + int (*xChgrp)(const char *,const char *); /* Changes file group */ + ph7_int64 (*xFreeSpace)(const char *); /* Available space on filesystem or disk partition */ + ph7_int64 (*xTotalSpace)(const char *); /* Total space on filesystem or disk partition */ + ph7_int64 (*xFileSize)(const char *); /* Gets file size */ + ph7_int64 (*xFileAtime)(const char *); /* Gets last access time of file */ + ph7_int64 (*xFileMtime)(const char *); /* Gets file modification time */ + ph7_int64 (*xFileCtime)(const char *); /* Gets inode change time of file */ + int (*xStat)(const char *,ph7_value *,ph7_value *); /* Gives information about a file */ + int (*xlStat)(const char *,ph7_value *,ph7_value *); /* Gives information about a file */ + int (*xIsfile)(const char *); /* Tells whether the filename is a regular file */ + int (*xIslink)(const char *); /* Tells whether the filename is a symbolic link */ + int (*xReadable)(const char *); /* Tells whether a file exists and is readable */ + int (*xWritable)(const char *); /* Tells whether the filename is writable */ + int (*xExecutable)(const char *); /* Tells whether the filename is executable */ + int (*xFiletype)(const char *,ph7_context *); /* Gets file type [i.e: fifo,dir,file..] */ + int (*xGetenv)(const char *,ph7_context *); /* Gets the value of an environment variable */ + int (*xSetenv)(const char *,const char *); /* Sets the value of an environment variable */ + int (*xTouch)(const char *,ph7_int64,ph7_int64); /* Sets access and modification time of file */ + int (*xMmap)(const char *,void **,ph7_int64 *); /* Read-only memory map of the whole file */ + void (*xUnmap)(void *,ph7_int64); /* Unmap a memory view */ + int (*xLink)(const char *,const char *,int); /* Create hard or symbolic link */ + int (*xUmask)(int); /* Change the current umask */ + void (*xTempDir)(ph7_context *); /* Get path of the temporary directory */ + unsigned int (*xProcessId)(void); /* Get running process ID */ + int (*xUid)(void); /* user ID of the process */ + int (*xGid)(void); /* group ID of the process */ + void (*xUsername)(ph7_context *); /* Running username */ + int (*xExec)(const char *,ph7_context *); /* Execute an external program */ +}; +/* Current PH7 IO stream structure version. */ +#define PH7_IO_STREAM_VERSION 1 +/* + * Possible open mode flags that can be passed to the xOpen() routine + * of the underlying IO stream device . + * Refer to the PH7 IO Stream C/C++ specification manual (http://ph7.symisc.net/io_stream_spec.html) + * for additional information. + */ +#define PH7_IO_OPEN_RDONLY 0x001 /* Read-only open */ +#define PH7_IO_OPEN_WRONLY 0x002 /* Write-only open */ +#define PH7_IO_OPEN_RDWR 0x004 /* Read-write open. */ +#define PH7_IO_OPEN_CREATE 0x008 /* If the file does not exist it will be created */ +#define PH7_IO_OPEN_TRUNC 0x010 /* Truncate the file to zero length */ +#define PH7_IO_OPEN_APPEND 0x020 /* Append mode.The file offset is positioned at the end of the file */ +#define PH7_IO_OPEN_EXCL 0x040 /* Ensure that this call creates the file,the file must not exist before */ +#define PH7_IO_OPEN_BINARY 0x080 /* Simple hint: Data is binary */ +#define PH7_IO_OPEN_TEMP 0x100 /* Simple hint: Temporary file */ +#define PH7_IO_OPEN_TEXT 0x200 /* Simple hint: Data is textual */ +/* + * PH7 IO Stream Device. + * + * An instance of the ph7_io_stream object defines the interface between the PH7 core + * and the underlying stream device. + * A stream is a smart mechanism for generalizing file, network, data compression + * and other IO operations which share a common set of functions using an abstracted + * unified interface. + * A stream device is additional code which tells the stream how to handle specific + * protocols/encodings. For example, the http device knows how to translate a URL + * into an HTTP/1.1 request for a file on a remote server. + * PH7 come with two built-in IO streams device: + * The file:// stream which perform very efficient disk IO and the php:// stream + * which is a special stream that allow access various I/O streams (See the PHP official + * documentation for more information on this stream). + * A stream is referenced as: scheme://target + * scheme(string) - The name of the wrapper to be used. Examples include: file,http,https,ftp, + * ftps, compress.zlib, compress.bz2, and php. If no wrapper is specified,the function default + * is used (typically file://). + * target - Depends on the device used. For filesystem related streams this is typically a path + * and filename of the desired file.For network related streams this is typically a hostname,often + * with a path appended. + * IO stream devices are registered using a call to ph7_vm_config() with a configuration verb + * set to PH7_VM_CONFIG_IO_STREAM. + * Currently the PH7 development team is working on the implementation of the http:// and ftp:// + * IO stream protocols. These devices will be available in the next major release of the PH7 engine. + * Developers wishing to implement their own IO stream devices must understand and follow + * The PH7 IO Stream C/C++ specification manual (http://ph7.symisc.net/io_stream_spec.html). + */ +struct ph7_io_stream +{ + const char *zName; /* Underlying stream name [i.e: file/http/zip/php,..] */ + int iVersion; /* IO stream structure version [default 1]*/ + int (*xOpen)(const char *,int,ph7_value *,void **); /* Open handle*/ + int (*xOpenDir)(const char *,ph7_value *,void **); /* Open directory handle */ + void (*xClose)(void *); /* Close file handle */ + void (*xCloseDir)(void *); /* Close directory handle */ + ph7_int64 (*xRead)(void *,void *,ph7_int64); /* Read from the open stream */ + int (*xReadDir)(void *,ph7_context *); /* Read entry from directory handle */ + ph7_int64 (*xWrite)(void *,const void *,ph7_int64); /* Write to the open stream */ + int (*xSeek)(void *,ph7_int64,int); /* Seek on the open stream */ + int (*xLock)(void *,int); /* Lock/Unlock the open stream */ + void (*xRewindDir)(void *); /* Rewind directory handle */ + ph7_int64 (*xTell)(void *); /* Current position of the stream read/write pointer */ + int (*xTrunc)(void *,ph7_int64); /* Truncates the open stream to a given length */ + int (*xSync)(void *); /* Flush open stream data */ + int (*xStat)(void *,ph7_value *,ph7_value *); /* Stat an open stream handle */ +}; +/* + * C-API-REF: Please refer to the official documentation for interfaces + * purpose and expected parameters. + */ +/* Engine Handling Interfaces */ +PH7_APIEXPORT int ph7_init(ph7 **ppEngine); +PH7_APIEXPORT int ph7_config(ph7 *pEngine,int nConfigOp,...); +PH7_APIEXPORT int ph7_release(ph7 *pEngine); +/* Compile Interfaces */ +PH7_APIEXPORT int ph7_compile(ph7 *pEngine,const char *zSource,int nLen,ph7_vm **ppOutVm); +PH7_APIEXPORT int ph7_compile_v2(ph7 *pEngine,const char *zSource,int nLen,ph7_vm **ppOutVm,int iFlags); +PH7_APIEXPORT int ph7_compile_file(ph7 *pEngine,const char *zFilePath,ph7_vm **ppOutVm,int iFlags); +/* Virtual Machine Handling Interfaces */ +PH7_APIEXPORT int ph7_vm_config(ph7_vm *pVm,int iConfigOp,...); +PH7_APIEXPORT int ph7_vm_exec(ph7_vm *pVm,int *pExitStatus); +PH7_APIEXPORT int ph7_vm_reset(ph7_vm *pVm); +PH7_APIEXPORT int ph7_vm_release(ph7_vm *pVm); +PH7_APIEXPORT int ph7_vm_dump_v2(ph7_vm *pVm,int (*xConsumer)(const void *,unsigned int,void *),void *pUserData); +/* In-process Extending Interfaces */ +PH7_APIEXPORT int ph7_create_function(ph7_vm *pVm,const char *zName,int (*xFunc)(ph7_context *,int,ph7_value **),void *pUserData); +PH7_APIEXPORT int ph7_delete_function(ph7_vm *pVm,const char *zName); +PH7_APIEXPORT int ph7_create_constant(ph7_vm *pVm,const char *zName,void (*xExpand)(ph7_value *,void *),void *pUserData); +PH7_APIEXPORT int ph7_delete_constant(ph7_vm *pVm,const char *zName); +/* Foreign Function Parameter Values */ +PH7_APIEXPORT int ph7_value_to_int(ph7_value *pValue); +PH7_APIEXPORT int ph7_value_to_bool(ph7_value *pValue); +PH7_APIEXPORT ph7_int64 ph7_value_to_int64(ph7_value *pValue); +PH7_APIEXPORT double ph7_value_to_double(ph7_value *pValue); +PH7_APIEXPORT const char * ph7_value_to_string(ph7_value *pValue,int *pLen); +PH7_APIEXPORT void * ph7_value_to_resource(ph7_value *pValue); +PH7_APIEXPORT int ph7_value_compare(ph7_value *pLeft,ph7_value *pRight,int bStrict); +/* Setting The Result Of A Foreign Function */ +PH7_APIEXPORT int ph7_result_int(ph7_context *pCtx,int iValue); +PH7_APIEXPORT int ph7_result_int64(ph7_context *pCtx,ph7_int64 iValue); +PH7_APIEXPORT int ph7_result_bool(ph7_context *pCtx,int iBool); +PH7_APIEXPORT int ph7_result_double(ph7_context *pCtx,double Value); +PH7_APIEXPORT int ph7_result_null(ph7_context *pCtx); +PH7_APIEXPORT int ph7_result_string(ph7_context *pCtx,const char *zString,int nLen); +PH7_APIEXPORT int ph7_result_string_format(ph7_context *pCtx,const char *zFormat,...); +PH7_APIEXPORT int ph7_result_value(ph7_context *pCtx,ph7_value *pValue); +PH7_APIEXPORT int ph7_result_resource(ph7_context *pCtx,void *pUserData); +/* Call Context Handling Interfaces */ +PH7_APIEXPORT int ph7_context_output(ph7_context *pCtx,const char *zString,int nLen); +PH7_APIEXPORT int ph7_context_output_format(ph7_context *pCtx,const char *zFormat,...); +PH7_APIEXPORT int ph7_context_throw_error(ph7_context *pCtx,int iErr,const char *zErr); +PH7_APIEXPORT int ph7_context_throw_error_format(ph7_context *pCtx,int iErr,const char *zFormat,...); +PH7_APIEXPORT unsigned int ph7_context_random_num(ph7_context *pCtx); +PH7_APIEXPORT int ph7_context_random_string(ph7_context *pCtx,char *zBuf,int nBuflen); +PH7_APIEXPORT void * ph7_context_user_data(ph7_context *pCtx); +PH7_APIEXPORT int ph7_context_push_aux_data(ph7_context *pCtx,void *pUserData); +PH7_APIEXPORT void * ph7_context_peek_aux_data(ph7_context *pCtx); +PH7_APIEXPORT void * ph7_context_pop_aux_data(ph7_context *pCtx); +PH7_APIEXPORT unsigned int ph7_context_result_buf_length(ph7_context *pCtx); +PH7_APIEXPORT const char * ph7_function_name(ph7_context *pCtx); +/* Call Context Memory Management Interfaces */ +PH7_APIEXPORT void * ph7_context_alloc_chunk(ph7_context *pCtx,unsigned int nByte,int ZeroChunk,int AutoRelease); +PH7_APIEXPORT void * ph7_context_realloc_chunk(ph7_context *pCtx,void *pChunk,unsigned int nByte); +PH7_APIEXPORT void ph7_context_free_chunk(ph7_context *pCtx,void *pChunk); +/* On Demand Dynamically Typed Value Object allocation interfaces */ +PH7_APIEXPORT ph7_value * ph7_new_scalar(ph7_vm *pVm); +PH7_APIEXPORT ph7_value * ph7_new_array(ph7_vm *pVm); +PH7_APIEXPORT int ph7_release_value(ph7_vm *pVm,ph7_value *pValue); +PH7_APIEXPORT ph7_value * ph7_context_new_scalar(ph7_context *pCtx); +PH7_APIEXPORT ph7_value * ph7_context_new_array(ph7_context *pCtx); +PH7_APIEXPORT void ph7_context_release_value(ph7_context *pCtx,ph7_value *pValue); +/* Dynamically Typed Value Object Management Interfaces */ +PH7_APIEXPORT int ph7_value_int(ph7_value *pVal,int iValue); +PH7_APIEXPORT int ph7_value_int64(ph7_value *pVal,ph7_int64 iValue); +PH7_APIEXPORT int ph7_value_bool(ph7_value *pVal,int iBool); +PH7_APIEXPORT int ph7_value_null(ph7_value *pVal); +PH7_APIEXPORT int ph7_value_double(ph7_value *pVal,double Value); +PH7_APIEXPORT int ph7_value_string(ph7_value *pVal,const char *zString,int nLen); +PH7_APIEXPORT int ph7_value_string_format(ph7_value *pVal,const char *zFormat,...); +PH7_APIEXPORT int ph7_value_reset_string_cursor(ph7_value *pVal); +PH7_APIEXPORT int ph7_value_resource(ph7_value *pVal,void *pUserData); +PH7_APIEXPORT int ph7_value_release(ph7_value *pVal); +PH7_APIEXPORT ph7_value * ph7_array_fetch(ph7_value *pArray,const char *zKey,int nByte); +PH7_APIEXPORT int ph7_array_walk(ph7_value *pArray,int (*xWalk)(ph7_value *,ph7_value *,void *),void *pUserData); +PH7_APIEXPORT int ph7_array_add_elem(ph7_value *pArray,ph7_value *pKey,ph7_value *pValue); +PH7_APIEXPORT int ph7_array_add_strkey_elem(ph7_value *pArray,const char *zKey,ph7_value *pValue); +PH7_APIEXPORT int ph7_array_add_intkey_elem(ph7_value *pArray,int iKey,ph7_value *pValue); +PH7_APIEXPORT unsigned int ph7_array_count(ph7_value *pArray); +PH7_APIEXPORT int ph7_object_walk(ph7_value *pObject,int (*xWalk)(const char *,ph7_value *,void *),void *pUserData); +PH7_APIEXPORT ph7_value * ph7_object_fetch_attr(ph7_value *pObject,const char *zAttr); +PH7_APIEXPORT const char * ph7_object_get_class_name(ph7_value *pObject,int *pLength); +PH7_APIEXPORT int ph7_value_is_int(ph7_value *pVal); +PH7_APIEXPORT int ph7_value_is_float(ph7_value *pVal); +PH7_APIEXPORT int ph7_value_is_bool(ph7_value *pVal); +PH7_APIEXPORT int ph7_value_is_string(ph7_value *pVal); +PH7_APIEXPORT int ph7_value_is_null(ph7_value *pVal); +PH7_APIEXPORT int ph7_value_is_numeric(ph7_value *pVal); +PH7_APIEXPORT int ph7_value_is_callable(ph7_value *pVal); +PH7_APIEXPORT int ph7_value_is_scalar(ph7_value *pVal); +PH7_APIEXPORT int ph7_value_is_array(ph7_value *pVal); +PH7_APIEXPORT int ph7_value_is_object(ph7_value *pVal); +PH7_APIEXPORT int ph7_value_is_resource(ph7_value *pVal); +PH7_APIEXPORT int ph7_value_is_empty(ph7_value *pVal); +/* Global Library Management Interfaces */ +PH7_APIEXPORT int ph7_lib_init(void); +PH7_APIEXPORT int ph7_lib_config(int nConfigOp,...); +PH7_APIEXPORT int ph7_lib_shutdown(void); +PH7_APIEXPORT int ph7_lib_is_threadsafe(void); +PH7_APIEXPORT const char * ph7_lib_version(void); +PH7_APIEXPORT const char * ph7_lib_signature(void); +PH7_APIEXPORT const char * ph7_lib_ident(void); +PH7_APIEXPORT const char * ph7_lib_copyright(void); +#ifdef __cplusplus +} +#endif /* __cplusplus */ +#endif /* _PH7_H_ */ From cf5f3ef4640df4b0f9941b6b79e84e9a951ff656 Mon Sep 17 00:00:00 2001 From: DL6ER Date: Mon, 1 Jun 2020 09:44:49 +0200 Subject: [PATCH 0157/1669] gcc-9 optimizations. Signed-off-by: DL6ER --- src/api/ph7.c | 40 ++++++++++++++++++++++++---------------- src/api/ph7.h | 1 + 2 files changed, 25 insertions(+), 16 deletions(-) diff --git a/src/api/ph7.c b/src/api/ph7.c index 7d03028ae..7083c0736 100644 --- a/src/api/ph7.c +++ b/src/api/ph7.c @@ -44,8 +44,10 @@ static char *webroot_with_home_and_scripts = NULL; */ static int Output_Consumer(const void *pOutput, unsigned int nOutputLen, void *pUserData /* Unused */) { - logg("PH7 error:"); - logg("%.*s", nOutputLen, (const char*)pOutput); + // Log error message, strip trailing newline character if any + if(((const char*)pOutput)[nOutputLen-1] == '\n') + nOutputLen--; + logg("PH7 error: %.*s", nOutputLen, (const char*)pOutput); return PH7_OK; } @@ -61,16 +63,19 @@ int ph7_handler(struct mg_connection *conn, void *cbdata) // Build full path of PHP script on our machine const size_t webroot_len = strlen(httpsettings.webroot); - const size_t local_uri_len = strlen(req_info->local_uri+1); + const size_t local_uri_len = strlen(req_info->local_uri + 1u); // +1 to skip the initial '/' char full_path[webroot_len + local_uri_len + 2]; - strncpy(full_path, httpsettings.webroot, webroot_len); + strcpy(full_path, httpsettings.webroot); full_path[webroot_len] = '/'; - strncpy(full_path + webroot_len + 1u, req_info->local_uri + 1, local_uri_len); + strncpy(full_path + webroot_len + 1u, req_info->local_uri + 1u, local_uri_len); full_path[webroot_len + local_uri_len + 1u] = '\0'; if(config.debug & DEBUG_API) logg("Full path of PHP script: %s", full_path); - /* Now,it's time to compile our PHP file */ + // Compile PHP script into byte-code + // This usually takes only 1-2 msec even for long scripts + // (measrued on a Raspberry Pi 3), so there is little + // point in buffering the compiled script somewhere rc = ph7_compile_file( pEngine, /* PH7 Engine */ full_path, /* Path to the PHP file to compile */ @@ -100,15 +105,19 @@ int ph7_handler(struct mg_connection *conn, void *cbdata) { logg("Compile error (%d)", rc); + mg_printf(conn, "HTTP/1.1 200 OK\r\nContent-Type: text/html\r\n\r\n" + "PHP compilation error, check %s for further details.", + FTLfiles.log); + /* Extract error log */ - const char *zErrLog; - int niLen; + const char *zErrLog = NULL; + int niLen = 0; ph7_config(pEngine, PH7_CONFIG_ERR_LOG, &zErrLog, &niLen); if( niLen > 0 ){ /* zErrLog is null terminated */ - logg("PH7 error: %s", zErrLog); + logg("PH7 compile error: %s", zErrLog); } - return 0; + return 1; } } @@ -132,7 +141,6 @@ int ph7_handler(struct mg_connection *conn, void *cbdata) { mg_printf(conn, "HTTP/1.1 200 OK\r\nContent-Type: text/html\r\n\r\n"); mg_write(conn, pOut, nLen); - logg("Output length: %u", nLen); } #if 0 @@ -182,17 +190,17 @@ void init_ph7(void) const size_t webroot_len = strlen(httpsettings.webroot); const size_t webhome_len = strlen(httpsettings.webhome); webroot_with_home = calloc(webroot_len+webhome_len+1, sizeof(char)); - strncpy(webroot_with_home, httpsettings.webroot, webroot_len); - strncpy(webroot_with_home + webroot_len, httpsettings.webhome, webhome_len); + strcpy(webroot_with_home, httpsettings.webroot); + strcpy(webroot_with_home + webroot_len, httpsettings.webhome); webroot_with_home[webroot_len + webhome_len] = '\0'; // var/www/html/admin/scripts/pi-hole/php (may be different due to user configuration) const char scripts_dir[] = "/scripts/pi-hole/php"; + size_t scripts_dir_len = sizeof(scripts_dir); size_t webroot_with_home_len = strlen(webroot_with_home); - size_t scripts_dir_len = strlen(scripts_dir); webroot_with_home_and_scripts = calloc(webroot_with_home_len+scripts_dir_len+1, sizeof(char)); - strncpy(webroot_with_home_and_scripts, webroot_with_home, webroot_with_home_len); - strncpy(webroot_with_home_and_scripts + webroot_with_home_len, scripts_dir, scripts_dir_len); + strcpy(webroot_with_home_and_scripts, webroot_with_home); + strcpy(webroot_with_home_and_scripts + webroot_with_home_len, scripts_dir); webroot_with_home_and_scripts[webroot_with_home_len + scripts_dir_len] = '\0'; } diff --git a/src/api/ph7.h b/src/api/ph7.h index 4e7f877d2..1a799c77e 100644 --- a/src/api/ph7.h +++ b/src/api/ph7.h @@ -14,4 +14,5 @@ void init_ph7(void); void ph7_terminate(void); int ph7_handler(struct mg_connection *conn, void *cbdata); + #endif // PH7_H \ No newline at end of file From 67a0395390df1842b9ce3d3cebfc60851ee7b65b Mon Sep 17 00:00:00 2001 From: DL6ER Date: Mon, 1 Jun 2020 12:06:35 +0200 Subject: [PATCH 0158/1669] Redirect PH7 errors into pihole-FTL.log (instead of showing in the browser output) and define gethostname() PHP function. Signed-off-by: DL6ER --- src/CMakeLists.txt | 3 + src/api/CMakeLists.txt | 5 -- src/api/auth.c | 4 +- src/api/dns.c | 5 +- src/api/ftl.c | 4 +- src/api/routes.c | 4 +- src/api/settings.c | 4 +- src/api/stats.c | 4 +- src/api/stats_database.c | 4 +- src/api/version.c | 4 +- src/dnsmasq_interface.c | 2 +- src/main.c | 2 +- src/ph7/ph7.c | 19 ++--- src/webserver/CMakeLists.txt | 26 +++++++ src/{api => webserver}/http-common.c | 1 - src/{api => webserver}/http-common.h | 0 src/{api => webserver}/json_macros.h | 0 src/{api => webserver}/ph7.c | 104 +++++++++++---------------- src/{api => webserver}/ph7.h | 1 - src/webserver/ph7_ext/CMakeLists.txt | 19 +++++ src/webserver/ph7_ext/extensions.h | 27 +++++++ src/webserver/ph7_ext/gethostname.c | 38 ++++++++++ src/{api => webserver}/webserver.c | 2 +- src/{api => webserver}/webserver.h | 0 24 files changed, 181 insertions(+), 101 deletions(-) create mode 100644 src/webserver/CMakeLists.txt rename src/{api => webserver}/http-common.c (99%) rename src/{api => webserver}/http-common.h (100%) rename src/{api => webserver}/json_macros.h (100%) rename src/{api => webserver}/ph7.c (68%) rename src/{api => webserver}/ph7.h (99%) create mode 100644 src/webserver/ph7_ext/CMakeLists.txt create mode 100644 src/webserver/ph7_ext/extensions.h create mode 100644 src/webserver/ph7_ext/gethostname.c rename src/{api => webserver}/webserver.c (99%) rename src/{api => webserver}/webserver.h (100%) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 78dc5ede1..fa15dd675 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -156,6 +156,8 @@ add_custom_target( add_executable(pihole-FTL ${sources} $ + $ + $ $ $ $ @@ -197,6 +199,7 @@ install(TARGETS pihole-FTL install(CODE "execute_process(COMMAND ${SETCAP} CAP_NET_BIND_SERVICE,CAP_NET_RAW,CAP_NET_ADMIN+eip ${CMAKE_INSTALL_PREFIX}/bin/pihole-FTL)") add_subdirectory(api) +add_subdirectory(webserver) add_subdirectory(civetweb) add_subdirectory(cJSON) add_subdirectory(ph7) diff --git a/src/api/CMakeLists.txt b/src/api/CMakeLists.txt index b5e4ce0fe..37fb46d95 100644 --- a/src/api/CMakeLists.txt +++ b/src/api/CMakeLists.txt @@ -14,17 +14,12 @@ set(sources dns.h ftl.c ftl.h - http-common.c - http-common.h - json_macros.h routes.c routes.h settings.c stats_database.c stats.c version.c - webserver.c - ph7.c ) add_library(api OBJECT ${sources}) diff --git a/src/api/auth.c b/src/api/auth.c index a41f203a9..49bada035 100644 --- a/src/api/auth.c +++ b/src/api/auth.c @@ -9,9 +9,9 @@ * Please see LICENSE file for your rights under this license. */ #include "FTL.h" -#include "http-common.h" +#include "../webserver/http-common.h" +#include "../webserver/json_macros.h" #include "routes.h" -#include "json_macros.h" #include "log.h" #include "config.h" // read_setupVarsconf() diff --git a/src/api/dns.c b/src/api/dns.c index 314c70979..aea834c8b 100644 --- a/src/api/dns.c +++ b/src/api/dns.c @@ -12,11 +12,10 @@ // counters #include "shmem.h" #include "dns.h" -#include "http-common.h" +#include "../webserver/http-common.h" +#include "../webserver/json_macros.h" #include "routes.h" -#include "json_macros.h" #include "database/gravity-db.h" -#include "api/http-common.h" #include "log.h" // {s,g}et_blockingstatus() #include "setupVars.h" diff --git a/src/api/ftl.c b/src/api/ftl.c index e936cfff9..a3b208d9a 100644 --- a/src/api/ftl.c +++ b/src/api/ftl.c @@ -9,10 +9,10 @@ * Please see LICENSE file for your rights under this license. */ #include "FTL.h" -#include "http-common.h" +#include "../webserver/http-common.h" +#include "../webserver/json_macros.h" #include "routes.h" #include "ftl.h" -#include "json_macros.h" #include "datastructure.h" // get_FTL_version() #include "log.h" diff --git a/src/api/routes.c b/src/api/routes.c index 8e7cc8fca..f6b7fa89b 100644 --- a/src/api/routes.c +++ b/src/api/routes.c @@ -11,8 +11,8 @@ #include "../FTL.h" // struct mg_connection #include "../civetweb/civetweb.h" -#include "http-common.h" -#include "json_macros.h" +#include "../webserver/http-common.h" +#include "../webserver/json_macros.h" #include "routes.h" #include "../shmem.h" diff --git a/src/api/settings.c b/src/api/settings.c index a102c3ab9..89971b531 100644 --- a/src/api/settings.c +++ b/src/api/settings.c @@ -9,9 +9,9 @@ * Please see LICENSE file for your rights under this license. */ #include "FTL.h" -#include "http-common.h" +#include "../webserver/http-common.h" +#include "../webserver/json_macros.h" #include "routes.h" -#include "json_macros.h" // get_FTL_db_filesize() #include "files.h" // get_sqlite3_version() diff --git a/src/api/stats.c b/src/api/stats.c index f5db08b02..e5dd1bfbe 100644 --- a/src/api/stats.c +++ b/src/api/stats.c @@ -9,9 +9,9 @@ * Please see LICENSE file for your rights under this license. */ #include "FTL.h" -#include "http-common.h" +#include "../webserver/http-common.h" +#include "../webserver/json_macros.h" #include "routes.h" -#include "json_macros.h" #include "shmem.h" #include "datastructure.h" // read_setupVarsconf() diff --git a/src/api/stats_database.c b/src/api/stats_database.c index eea8caf48..2ab8b2cfe 100644 --- a/src/api/stats_database.c +++ b/src/api/stats_database.c @@ -9,9 +9,9 @@ * Please see LICENSE file for your rights under this license. */ #include "FTL.h" -#include "http-common.h" +#include "../webserver/http-common.h" +#include "../webserver/json_macros.h" #include "routes.h" -#include "json_macros.h" #include "shmem.h" #include "datastructure.h" // logg() diff --git a/src/api/version.c b/src/api/version.c index 558f5d386..87a374a4c 100644 --- a/src/api/version.c +++ b/src/api/version.c @@ -9,9 +9,9 @@ * Please see LICENSE file for your rights under this license. */ #include "FTL.h" -#include "http-common.h" +#include "../webserver/http-common.h" +#include "../webserver/json_macros.h" #include "routes.h" -#include "json_macros.h" // get_FTL_version() #include "log.h" #include "version.h" diff --git a/src/dnsmasq_interface.c b/src/dnsmasq_interface.c index b0695f0b8..261272edb 100644 --- a/src/dnsmasq_interface.c +++ b/src/dnsmasq_interface.c @@ -33,7 +33,7 @@ // global variable daemonmode #include "args.h" // http_init() -#include "api/webserver.h" +#include "webserver/webserver.h" // add_to_dnsmasq_log_buffer() #include "api/ftl.h" diff --git a/src/main.c b/src/main.c index 34f7de2ea..a64e92592 100644 --- a/src/main.c +++ b/src/main.c @@ -24,7 +24,7 @@ #include "database/gravity-db.h" #include "timers.h" // http_terminate() -#include "api/webserver.h" +#include "webserver/webserver.h" char * username; bool needGC = false; diff --git a/src/ph7/ph7.c b/src/ph7/ph7.c index 6460b28d5..6160dd924 100644 --- a/src/ph7/ph7.c +++ b/src/ph7/ph7.c @@ -125,6 +125,9 @@ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ +/******************************* Pi-hole modification ******************************/ +extern void logg(const char* format, ...) __attribute__ ((format (gnu_printf, 1, 2))); +/***********************************************************************************/ /* $SymiscID: ph7.h v2.1 UNIX|WIN32/64 2012-09-15 09:43 stable $ */ #include /* needed for the definition of va_list */ /* @@ -5001,24 +5004,14 @@ static sxi32 VmThrowException(ph7_vm *pVm,ph7_class_instance *pThis); /* * Consume a generated run-time error message by invoking the VM output * consumer callback. + * ATTENTION: MODIFIED BY PI-HOLE */ static sxi32 VmCallErrorHandler(ph7_vm *pVm,SyBlob *pMsg) { ph7_output_consumer *pCons = &pVm->sVmConsumer; - sxi32 rc = SXRET_OK; - /* Append a new line */ -#ifdef __WINNT__ - SyBlobAppend(pMsg,"\r\n",sizeof("\r\n")-1); -#else - SyBlobAppend(pMsg,"\n",sizeof(char)); -#endif /* Invoke the output consumer callback */ - rc = pCons->xConsumer(SyBlobData(pMsg),SyBlobLength(pMsg),pCons->pUserData); - if( pCons->xConsumer != VmObConsumer ){ - /* Increment output length */ - pVm->nOutputLen += SyBlobLength(pMsg); - } - return rc; + logg("PH7 Error: %.*s", SyBlobLength(pMsg), SyBlobData(pMsg)); + return SXRET_OK; } /* * Throw a run-time error and invoke the supplied VM output consumer callback. diff --git a/src/webserver/CMakeLists.txt b/src/webserver/CMakeLists.txt new file mode 100644 index 000000000..f7c627fc7 --- /dev/null +++ b/src/webserver/CMakeLists.txt @@ -0,0 +1,26 @@ +# Pi-hole: A black hole for Internet advertisements +# (c) 2020 Pi-hole, LLC (https://pi-hole.net) +# Network-wide ad blocking via your own hardware. +# +# FTL Engine +# /src/webserver/CMakeList.txt +# +# This file is copyright under the latest version of the EUPL. +# Please see LICENSE file for your rights under this license. + +set(sources + http-common.c + http-common.h + webserver.c + webserver.h + ph7.c + ph7.h + json_macros.h + ) + +add_library(webserver OBJECT ${sources}) +add_dependencies(webserver gen_version) +target_compile_options(webserver PRIVATE ${EXTRAWARN}) +target_include_directories(webserver PRIVATE ${PROJECT_SOURCE_DIR}/src) + +add_subdirectory(ph7_ext) \ No newline at end of file diff --git a/src/api/http-common.c b/src/webserver/http-common.c similarity index 99% rename from src/api/http-common.c rename to src/webserver/http-common.c index af2b2790f..a1d708b14 100644 --- a/src/api/http-common.c +++ b/src/webserver/http-common.c @@ -9,7 +9,6 @@ * Please see LICENSE file for your rights under this license. */ #include "FTL.h" -#include "routes.h" #include "http-common.h" #include "../config.h" #include "../log.h" diff --git a/src/api/http-common.h b/src/webserver/http-common.h similarity index 100% rename from src/api/http-common.h rename to src/webserver/http-common.h diff --git a/src/api/json_macros.h b/src/webserver/json_macros.h similarity index 100% rename from src/api/json_macros.h rename to src/webserver/json_macros.h diff --git a/src/api/ph7.c b/src/webserver/ph7.c similarity index 68% rename from src/api/ph7.c rename to src/webserver/ph7.c index 7083c0736..ae043ade6 100644 --- a/src/api/ph7.c +++ b/src/webserver/ph7.c @@ -1,5 +1,5 @@ /* Pi-hole: A black hole for Internet advertisements -* (c) 2019 Pi-hole, LLC (https://pi-hole.net) +* (c) 2020 Pi-hole, LLC (https://pi-hole.net) * Network-wide ad blocking via your own hardware. * * FTL Engine @@ -27,6 +27,10 @@ // open #include +// Pi-hole PH7 extensions +#define PH7_CORE +#include "ph7_ext/extensions.h" + // PH7 virtual machine engine static ph7 *pEngine; /* PH7 engine */ static ph7_vm *pVm; /* Compiled PHP program */ @@ -34,15 +38,8 @@ static ph7_vm *pVm; /* Compiled PHP program */ static char *webroot_with_home = NULL; static char *webroot_with_home_and_scripts = NULL; -/* - * VM output consumer callback. - * Each time the virtual machine generates some outputs, the following - * function gets called by the underlying virtual machine to consume - * the generated output. - * This function is registered later via a call to ph7_vm_config() - * with a configuration verb set to: PH7_VM_CONFIG_OUTPUT. - */ -static int Output_Consumer(const void *pOutput, unsigned int nOutputLen, void *pUserData /* Unused */) +static int PH7_error_report(const void *pOutput, unsigned int nOutputLen, + void *pUserData /* Unused */) { // Log error message, strip trailing newline character if any if(((const char*)pOutput)[nOutputLen-1] == '\n') @@ -53,10 +50,7 @@ static int Output_Consumer(const void *pOutput, unsigned int nOutputLen, void *p int ph7_handler(struct mg_connection *conn, void *cbdata) { - int rc; - const void *pOut; - unsigned int nLen; /* Handler may access the request info using mg_get_request_info */ const struct mg_request_info * req_info = mg_get_request_info(conn); @@ -73,14 +67,13 @@ int ph7_handler(struct mg_connection *conn, void *cbdata) logg("Full path of PHP script: %s", full_path); // Compile PHP script into byte-code - // This usually takes only 1-2 msec even for long scripts - // (measrued on a Raspberry Pi 3), so there is little - // point in buffering the compiled script somewhere + // This usually takes only 1-2 msec even for larger scripts on a Raspberry + // Pi 3, so there is little point in buffering the compiled script rc = ph7_compile_file( - pEngine, /* PH7 Engine */ + pEngine, /* PH7 Engine */ full_path, /* Path to the PHP file to compile */ - &pVm, /* OUT: Compiled PHP program */ - 0 /* IN: Compile flags */ + &pVm, /* OUT: Compiled PHP program */ + 0 /* IN: Compile flags */ ); /* Report script run-time errors */ @@ -93,13 +86,16 @@ int ph7_handler(struct mg_connection *conn, void *cbdata) if( rc != PH7_OK ){ /* Compile error */ if( rc == PH7_IO_ERR ) { - logg("IO error while opening the target file"); + logg("IO error while opening the target file (%s)", full_path); + // Fall back to HTTP server to handle the 404 event return 0; } else if( rc == PH7_VM_ERR ) { logg("VM initialization error"); - return 0; + // Mark file as processes - this prevents the HTTP server + // from printing the raw PHP source code to the user + return 1; } else { @@ -117,50 +113,46 @@ int ph7_handler(struct mg_connection *conn, void *cbdata) /* zErrLog is null terminated */ logg("PH7 compile error: %s", zErrLog); } + // Mark file as processes - this prevents the HTTP server + // from printing the raw PHP source code to the user return 1; } } + // Register Pi-hole's PH7 extensions (defined in subdirectory "ph7_ext/") + for(unsigned int i = 0; i < sizeof(aFunc)/sizeof(aFunc[0]); i++ ) + { + rc = ph7_create_function(pVm, aFunc[i].zName, aFunc[i].xProc, NULL /* NULL: No private data */); + if( rc != PH7_OK ){ + logg("Error while registering foreign function %s()", aFunc[i].zName); + } + } + + // Execute virtual machine rc = ph7_vm_exec(pVm,0); if( rc != PH7_OK ) { logg("VM execution error"); - return 0; + // Mark file as processes - this prevents the HTTP server + // from printing the raw PHP source code to the user + return 1; } - /* - * Now we have our script compiled,it's time to configure our VM. - * We will install the VM output consumer callback defined above - * so that we can consume the VM output and redirect it to STDOUT. - */ - - /* Extract the output */ + // Extract and send the output (if any) + const void *pOut = NULL; + unsigned int nLen = 0u; rc = ph7_vm_config(pVm, PH7_VM_CONFIG_EXTRACT_OUTPUT, &pOut, &nLen); - if(nLen > 0) { mg_printf(conn, "HTTP/1.1 200 OK\r\nContent-Type: text/html\r\n\r\n"); mg_write(conn, pOut, nLen); } -#if 0 - const char *zErrLog; - int niLen; - /* Extract error log */ - ph7_config( - pEngine, - PH7_CONFIG_ERR_LOG, - &zErrLog, /* First arg*/ - &niLen /* Second arg */ - ); - - if( niLen > 0 ){ - logg("%s", zErrLog); /* Output*/ - } -#endif - + // Reset and release the virtual machine ph7_vm_reset(pVm); + ph7_vm_release(pVm); + // Processed the file return 1; } @@ -172,24 +164,15 @@ void init_ph7(void) return; } - /* Set an error log consumer callback. This callback [Output_Consumer()] will - * redirect all compile-time error messages to STDOUT. - */ - ph7_config(pEngine,PH7_VM_CONFIG_OUTPUT, - Output_Consumer, // Error log consumer - 0 // NULL: Callback Private data - ); -/* - ph7_config(pEngine,PH7_CONFIG_ERR_OUTPUT, - Output_Consumer, // Error log consumer - 0 // NULL: Callback Private data - );*/ + // Set an error log consumer callback. This callback will + // receive all compile-time error messages to + ph7_config(pEngine,PH7_VM_CONFIG_OUTPUT, PH7_error_report, NULL /* NULL: No private data */); // Prepare include paths // var/www/html/admin (may be different due to user configuration) const size_t webroot_len = strlen(httpsettings.webroot); const size_t webhome_len = strlen(httpsettings.webhome); - webroot_with_home = calloc(webroot_len+webhome_len+1, sizeof(char)); + webroot_with_home = calloc(webroot_len + webhome_len + 1u, sizeof(char)); strcpy(webroot_with_home, httpsettings.webroot); strcpy(webroot_with_home + webroot_len, httpsettings.webhome); webroot_with_home[webroot_len + webhome_len] = '\0'; @@ -198,7 +181,7 @@ void init_ph7(void) const char scripts_dir[] = "/scripts/pi-hole/php"; size_t scripts_dir_len = sizeof(scripts_dir); size_t webroot_with_home_len = strlen(webroot_with_home); - webroot_with_home_and_scripts = calloc(webroot_with_home_len+scripts_dir_len+1, sizeof(char)); + webroot_with_home_and_scripts = calloc(webroot_with_home_len + scripts_dir_len + 1u, sizeof(char)); strcpy(webroot_with_home_and_scripts, webroot_with_home); strcpy(webroot_with_home_and_scripts + webroot_with_home_len, scripts_dir); webroot_with_home_and_scripts[webroot_with_home_len + scripts_dir_len] = '\0'; @@ -206,7 +189,6 @@ void init_ph7(void) void ph7_terminate(void) { - ph7_vm_release(pVm); ph7_release(pEngine); free(webroot_with_home); free(webroot_with_home_and_scripts); diff --git a/src/api/ph7.h b/src/webserver/ph7.h similarity index 99% rename from src/api/ph7.h rename to src/webserver/ph7.h index 1a799c77e..4e7f877d2 100644 --- a/src/api/ph7.h +++ b/src/webserver/ph7.h @@ -14,5 +14,4 @@ void init_ph7(void); void ph7_terminate(void); int ph7_handler(struct mg_connection *conn, void *cbdata); - #endif // PH7_H \ No newline at end of file diff --git a/src/webserver/ph7_ext/CMakeLists.txt b/src/webserver/ph7_ext/CMakeLists.txt new file mode 100644 index 000000000..5fbc36059 --- /dev/null +++ b/src/webserver/ph7_ext/CMakeLists.txt @@ -0,0 +1,19 @@ +# Pi-hole: A black hole for Internet advertisements +# (c) 2020 Pi-hole, LLC (https://pi-hole.net) +# Network-wide ad blocking via your own hardware. +# +# FTL Engine +# /src/webserver/CMakeList.txt +# +# This file is copyright under the latest version of the EUPL. +# Please see LICENSE file for your rights under this license. + +set(sources + extensions.h + gethostname.c + ) + +add_library(ph7_ext OBJECT ${sources}) +add_dependencies(ph7_ext gen_version) +target_compile_options(ph7_ext PRIVATE ${EXTRAWARN}) +target_include_directories(ph7_ext PRIVATE ${PROJECT_SOURCE_DIR}/src) diff --git a/src/webserver/ph7_ext/extensions.h b/src/webserver/ph7_ext/extensions.h new file mode 100644 index 000000000..35adf5896 --- /dev/null +++ b/src/webserver/ph7_ext/extensions.h @@ -0,0 +1,27 @@ +/* Pi-hole: A black hole for Internet advertisements +* (c) 2020 Pi-hole, LLC (https://pi-hole.net) +* Network-wide ad blocking via your own hardware. +* +* FTL Engine +* PH7 extension prototypes +* +* This file is copyright under the latest version of the EUPL. +* Please see LICENSE file for your rights under this license. */ +#ifndef PH7_EXT_H +#define PH7_EXT_H + +// Function prototypes +int gethostname_impl(ph7_context *pCtx, int argc, ph7_value **argv); + +#ifdef PH7_CORE // Include this section only in ../ph7.c +// Container for the foreign functions defined above. +// These functions will be registered later using a call +// to [ph7_create_function()]. +static const struct foreign_func { + const char *zName; /* Name of the foreign function*/ + int (*xProc)(ph7_context *, int, ph7_value **); /* Pointer to the C function performing the computation*/ +}aFunc[] = { + {"gethostname", gethostname_impl} +}; +#endif // PH7_CORE +#endif // PH7_EXT_H \ No newline at end of file diff --git a/src/webserver/ph7_ext/gethostname.c b/src/webserver/ph7_ext/gethostname.c new file mode 100644 index 000000000..47fbd6d68 --- /dev/null +++ b/src/webserver/ph7_ext/gethostname.c @@ -0,0 +1,38 @@ +/* Pi-hole: A black hole for Internet advertisements +* (c) 2020 Pi-hole, LLC (https://pi-hole.net) +* Network-wide ad blocking via your own hardware. +* +* FTL Engine +* PH7 extension: gethostname() +* +* This file is copyright under the latest version of the EUPL. +* Please see LICENSE file for your rights under this license. */ + +#include "../../ph7/ph7.h" +#include "extensions.h" +#include +#include + +int gethostname_impl(ph7_context *pCtx, int argc, ph7_value **argv) +{ + // We do not accept input arguments here + if(argc != 0) + { + // Invalid argument,throw a warning and return FALSE. + ph7_context_throw_error(pCtx, PH7_CTX_WARNING, "No arguments allowed"); + ph7_result_bool(pCtx, 0); + return PH7_OK; + } + + // Get host name + char name[256]; + if(gethostname(name, sizeof(name)) != 0) + { + strcpy(name, "N/A"); + } + + ph7_result_string(pCtx, name, strlen(name)); + + /* All done */ + return PH7_OK; +} diff --git a/src/api/webserver.c b/src/webserver/webserver.c similarity index 99% rename from src/api/webserver.c rename to src/webserver/webserver.c index f93eca95e..a036b8b05 100644 --- a/src/api/webserver.c +++ b/src/webserver/webserver.c @@ -9,7 +9,7 @@ * Please see LICENSE file for your rights under this license. */ #include "FTL.h" -#include "routes.h" +#include "../api/routes.h" // send_http() #include "http-common.h" // struct httpsettings diff --git a/src/api/webserver.h b/src/webserver/webserver.h similarity index 100% rename from src/api/webserver.h rename to src/webserver/webserver.h From 73be307c77c03681dd547673f6617725f8141692 Mon Sep 17 00:00:00 2001 From: DL6ER Date: Tue, 2 Jun 2020 15:19:07 +0200 Subject: [PATCH 0159/1669] Pass HTTP head to PH7 so it can extract header variables (, , , ...) Signed-off-by: DL6ER --- src/civetweb/civetweb.c | 6 +++++- src/civetweb/civetweb.h | 1 + src/webserver/ph7.c | 24 +++++++++++++++--------- src/webserver/webserver.c | 2 +- 4 files changed, 22 insertions(+), 11 deletions(-) diff --git a/src/civetweb/civetweb.c b/src/civetweb/civetweb.c index a9ed6eba7..d34a7a247 100644 --- a/src/civetweb/civetweb.c +++ b/src/civetweb/civetweb.c @@ -4900,6 +4900,7 @@ void my_set_cookie_header(struct mg_connection *conn, { conn->cookie_header = mg_strdup(cookie_header); } + /********************************************************************************************/ int @@ -10639,6 +10640,10 @@ parse_http_request(char *buf, int len, struct mg_request_info *ri) NULL; ri->num_headers = 0; + /******************** Pi-hole modification ********************/ + strncpy(ri->raw_http_head, buf, sizeof(ri->raw_http_head)); + /**************************************************************/ + /* RFC says that all initial whitespaces should be ingored */ /* This included all leading \r and \n (isspace) */ /* See table: http://www.cplusplus.com/reference/cctype/ */ @@ -18289,7 +18294,6 @@ consume_socket(struct mg_context *ctx, struct socket *sp, int thread_index) return !ctx->stop_flag; } - /* Master thread adds accepted socket to a queue */ static void produce_socket(struct mg_context *ctx, const struct socket *sp) diff --git a/src/civetweb/civetweb.h b/src/civetweb/civetweb.h index 2525e409b..7ed1177d3 100644 --- a/src/civetweb/civetweb.h +++ b/src/civetweb/civetweb.h @@ -174,6 +174,7 @@ struct mg_request_info { const char *acceptedWebSocketSubprotocol; /* websocket subprotocol, * accepted during handshake */ + char raw_http_head[16384]; // <---- Pi-hole addition }; diff --git a/src/webserver/ph7.c b/src/webserver/ph7.c index ae043ade6..36c1531bb 100644 --- a/src/webserver/ph7.c +++ b/src/webserver/ph7.c @@ -53,7 +53,7 @@ int ph7_handler(struct mg_connection *conn, void *cbdata) int rc; /* Handler may access the request info using mg_get_request_info */ - const struct mg_request_info * req_info = mg_get_request_info(conn); + const struct mg_request_info *req_info = mg_get_request_info(conn); // Build full path of PHP script on our machine const size_t webroot_len = strlen(httpsettings.webroot); @@ -76,14 +76,8 @@ int ph7_handler(struct mg_connection *conn, void *cbdata) 0 /* IN: Compile flags */ ); - /* Report script run-time errors */ - ph7_vm_config(pVm, PH7_VM_CONFIG_ERR_REPORT); - - /* Configure include paths */ - ph7_vm_config(pVm, PH7_VM_CONFIG_IMPORT_PATH, webroot_with_home); - ph7_vm_config(pVm, PH7_VM_CONFIG_IMPORT_PATH, webroot_with_home_and_scripts); - - if( rc != PH7_OK ){ /* Compile error */ + if( rc != PH7_OK ) // Compile error + { if( rc == PH7_IO_ERR ) { logg("IO error while opening the target file (%s)", full_path); @@ -119,6 +113,18 @@ int ph7_handler(struct mg_connection *conn, void *cbdata) } } + // Pass raw HTTP request head to PH7 so it can decode the queries and + // fill the appropriate arrays such as $_GET, $_POST, $_REQUEST, + // $_SERVER, etc. Length -1 means PH7 computes the buffer length itself + ph7_vm_config(pVm, PH7_VM_CONFIG_HTTP_REQUEST, req_info->raw_http_head, -1); + + /* Report script run-time errors */ + ph7_vm_config(pVm, PH7_VM_CONFIG_ERR_REPORT); + + /* Configure include paths */ + ph7_vm_config(pVm, PH7_VM_CONFIG_IMPORT_PATH, webroot_with_home); + ph7_vm_config(pVm, PH7_VM_CONFIG_IMPORT_PATH, webroot_with_home_and_scripts); + // Register Pi-hole's PH7 extensions (defined in subdirectory "ph7_ext/") for(unsigned int i = 0; i < sizeof(aFunc)/sizeof(aFunc[0]); i++ ) { diff --git a/src/webserver/webserver.c b/src/webserver/webserver.c index a036b8b05..35ace8882 100644 --- a/src/webserver/webserver.c +++ b/src/webserver/webserver.c @@ -123,7 +123,7 @@ void http_init(void) free(api_path); } - // Register PHP request handler + // Initialize PH7 engine and register PHP request handler init_ph7(); mg_set_request_handler(ctx, "**.php$", ph7_handler, 0); } From f326d2bd7dcdc0a79a33af0e0804d205870441ab Mon Sep 17 00:00:00 2001 From: DL6ER Date: Mon, 15 Jun 2020 21:03:04 +0200 Subject: [PATCH 0160/1669] Add two new log files for HTTP infos (incl. access logs if in API debug mode) and PH7 engine errors and messages. Signed-off-by: DL6ER --- src/config.c | 6 ++++ src/config.h | 2 ++ src/log.c | 64 +++++++++++++++++++++++++++++++++++++-- src/log.h | 3 ++ src/ph7/CMakeLists.txt | 2 +- src/webserver/ph7.c | 48 +++++++++++++++++------------ src/webserver/webserver.c | 11 ++++--- 7 files changed, 107 insertions(+), 29 deletions(-) diff --git a/src/config.c b/src/config.c index ae72feba4..354486c31 100644 --- a/src/config.c +++ b/src/config.c @@ -448,6 +448,12 @@ void read_FTLconf(void) else logg(" API_PRETTY_JSON: Disabled. Compact API output."); + // API_ERROR_LOG + getpath(fp, "API_ERROR_LOG", "/var/log/pihole/PH7.log", &httpsettings.log_error); + + // API_INFO_LOG + getpath(fp, "API_INFO_LOG", "/var/log/pihole/HTTP_info.log", &httpsettings.log_info); + // Read DEBUG_... setting from pihole-FTL.conf // This option should be the last one as it causes // some rather verbose output into the log when diff --git a/src/config.h b/src/config.h index ed52edf81..83c58cf9d 100644 --- a/src/config.h +++ b/src/config.h @@ -56,6 +56,8 @@ typedef struct { typedef struct httpsettings { char *webroot; char *webhome; + char *log_info; + char *log_error; const char *acl; bool api_auth_for_localhost; bool prettyJSON; diff --git a/src/log.c b/src/log.c index e7a6d1a61..59c14a446 100644 --- a/src/log.c +++ b/src/log.c @@ -24,7 +24,7 @@ static pthread_mutex_t lock; static FILE *logfile = NULL; -static void close_FTL_log(void) +static void close_log(void) { if(logfile != NULL) fclose(logfile); @@ -56,10 +56,30 @@ void open_FTL_log(const bool test) // Return failure exit(EXIT_FAILURE); } - close_FTL_log(); + close_log(); } } +static void open_web_log(const enum web_code code) +{ + // Open the log file in append/create mode + char *file = NULL; + switch (code) + { + case HTTP_INFO: + file = httpsettings.log_info; + break; + case PH7_ERROR: + file = httpsettings.log_error; + break; + default: + file = httpsettings.log_error; + break; + } + + logfile = fopen(file, "a+"); +} + void get_timestr(char *timestring, const time_t timein) { struct tm tm; @@ -114,7 +134,45 @@ void __attribute__ ((format (gnu_printf, 1, 2))) logg(const char *format, ...) } // Close log file - close_FTL_log(); + close_log(); + + pthread_mutex_unlock(&lock); +} + + +void __attribute__ ((format (gnu_printf, 2, 3))) logg_web(enum web_code code, const char *format, ...) +{ + char timestring[84] = ""; + va_list args; + + pthread_mutex_lock(&lock); + + get_timestr(timestring, time(NULL)); + + // Get and log PID of current process to avoid ambiguities when more than one + // pihole-FTL instance is logging into the same file + const long pid = (long)getpid(); + + // Open log file + open_web_log(code); + + // Write to log file + if(logfile != NULL) + { + fprintf(logfile, "[%s %ld] ", timestring, pid); + va_start(args, format); + vfprintf(logfile, format, args); + va_end(args); + fputc('\n',logfile); + } + else if(!daemonmode) + { + printf("!!! WARNING: Writing to web log file failed!\n"); + syslog(LOG_ERR, "Writing to web log file failed!"); + } + + // Close log file + close_log(); pthread_mutex_unlock(&lock); } diff --git a/src/log.h b/src/log.h index ad4f8e9c1..4a1ec3892 100644 --- a/src/log.h +++ b/src/log.h @@ -13,6 +13,8 @@ #include #include +enum web_code { HTTP_INFO, PH7_ERROR }; + void open_FTL_log(const bool test); void logg(const char* format, ...) __attribute__ ((format (gnu_printf, 1, 2))); void log_counter_info(void); @@ -20,5 +22,6 @@ void format_memory_size(char *prefix, unsigned long long int bytes, double *form char *get_FTL_version(void) __attribute__ ((malloc)); void log_FTL_version(bool crashreport); void get_timestr(char *timestring, const time_t timein); +void logg_web(enum web_code code, const char* format, ...) __attribute__ ((format (gnu_printf, 2, 3))); #endif //LOG_H diff --git a/src/ph7/CMakeLists.txt b/src/ph7/CMakeLists.txt index 45f6336e5..d811f3b13 100644 --- a/src/ph7/CMakeLists.txt +++ b/src/ph7/CMakeLists.txt @@ -13,5 +13,5 @@ set(sources ) add_library(ph7 OBJECT ${sources}) -target_compile_options(ph7 PRIVATE -Wno-unused-but-set-parameter -Wno-array-bounds) +target_compile_options(ph7 PRIVATE -Wno-unused-but-set-parameter -Wno-array-bounds -DPH7_ENABLE_THREADS) target_include_directories(ph7 PRIVATE ${PROJECT_SOURCE_DIR}/src) diff --git a/src/webserver/ph7.c b/src/webserver/ph7.c index 36c1531bb..928ab6cf2 100644 --- a/src/webserver/ph7.c +++ b/src/webserver/ph7.c @@ -38,16 +38,6 @@ static ph7_vm *pVm; /* Compiled PHP program */ static char *webroot_with_home = NULL; static char *webroot_with_home_and_scripts = NULL; -static int PH7_error_report(const void *pOutput, unsigned int nOutputLen, - void *pUserData /* Unused */) -{ - // Log error message, strip trailing newline character if any - if(((const char*)pOutput)[nOutputLen-1] == '\n') - nOutputLen--; - logg("PH7 error: %.*s", nOutputLen, (const char*)pOutput); - return PH7_OK; -} - int ph7_handler(struct mg_connection *conn, void *cbdata) { int rc; @@ -80,20 +70,20 @@ int ph7_handler(struct mg_connection *conn, void *cbdata) { if( rc == PH7_IO_ERR ) { - logg("IO error while opening the target file (%s)", full_path); + logg_web(PH7_ERROR, "%s: IO error while opening the target file", full_path); // Fall back to HTTP server to handle the 404 event return 0; } else if( rc == PH7_VM_ERR ) { - logg("VM initialization error"); + logg_web(PH7_ERROR, "%s: VM initialization error", full_path); // Mark file as processes - this prevents the HTTP server // from printing the raw PHP source code to the user return 1; } else { - logg("Compile error (%d)", rc); + logg_web(PH7_ERROR, "%s: Compile error (%d)", full_path, rc); mg_printf(conn, "HTTP/1.1 200 OK\r\nContent-Type: text/html\r\n\r\n" "PHP compilation error, check %s for further details.", @@ -105,7 +95,7 @@ int ph7_handler(struct mg_connection *conn, void *cbdata) ph7_config(pEngine, PH7_CONFIG_ERR_LOG, &zErrLog, &niLen); if( niLen > 0 ){ /* zErrLog is null terminated */ - logg("PH7 compile error: %s", zErrLog); + logg_web(PH7_ERROR, " ---> %s", zErrLog); } // Mark file as processes - this prevents the HTTP server // from printing the raw PHP source code to the user @@ -130,7 +120,8 @@ int ph7_handler(struct mg_connection *conn, void *cbdata) { rc = ph7_create_function(pVm, aFunc[i].zName, aFunc[i].xProc, NULL /* NULL: No private data */); if( rc != PH7_OK ){ - logg("Error while registering foreign function %s()", aFunc[i].zName); + logg_web(PH7_ERROR, "%s: Error while registering foreign function %s()", + full_path, aFunc[i].zName); } } @@ -138,7 +129,7 @@ int ph7_handler(struct mg_connection *conn, void *cbdata) rc = ph7_vm_exec(pVm,0); if( rc != PH7_OK ) { - logg("VM execution error"); + logg_web(PH7_ERROR, "%s: VM execution error", full_path); // Mark file as processes - this prevents the HTTP server // from printing the raw PHP source code to the user return 1; @@ -162,20 +153,37 @@ int ph7_handler(struct mg_connection *conn, void *cbdata) return 1; } +static int PH7_error_report(const void *pOutput, unsigned int nOutputLen, + void *pUserData /* Unused */) +{ + // Log error message, strip trailing newline character if any + if(((const char*)pOutput)[nOutputLen-1] == '\n') + nOutputLen--; + logg_web(PH7_ERROR, "PH7 error: %.*s", nOutputLen, (const char*)pOutput); + return PH7_OK; +} + void init_ph7(void) { if(ph7_init(&pEngine) != PH7_OK ) { - logg("Error while allocating a new PH7 engine instance"); + logg_web(PH7_ERROR, "Error while initializing a new PH7 engine instance"); return; } + // This should never happen, check nonetheless + if(!ph7_lib_is_threadsafe()) + { + logg("FATAL: Recompile FTL with PH7 set to multi-thread mode!"); + exit(EXIT_FAILURE); + } + // Set an error log consumer callback. This callback will // receive all compile-time error messages to - ph7_config(pEngine,PH7_VM_CONFIG_OUTPUT, PH7_error_report, NULL /* NULL: No private data */); + ph7_config(pEngine, PH7_VM_CONFIG_OUTPUT, PH7_error_report, NULL /* NULL: No private data */); // Prepare include paths - // var/www/html/admin (may be different due to user configuration) + // /var/www/html/admin (may be different due to user configuration) const size_t webroot_len = strlen(httpsettings.webroot); const size_t webhome_len = strlen(httpsettings.webhome); webroot_with_home = calloc(webroot_len + webhome_len + 1u, sizeof(char)); @@ -183,7 +191,7 @@ void init_ph7(void) strcpy(webroot_with_home + webroot_len, httpsettings.webhome); webroot_with_home[webroot_len + webhome_len] = '\0'; - // var/www/html/admin/scripts/pi-hole/php (may be different due to user configuration) + // /var/www/html/admin/scripts/pi-hole/php (may be different due to user configuration) const char scripts_dir[] = "/scripts/pi-hole/php"; size_t scripts_dir_len = sizeof(scripts_dir); size_t webroot_with_home_len = strlen(webroot_with_home); diff --git a/src/webserver/webserver.c b/src/webserver/webserver.c index 35ace8882..c2a196579 100644 --- a/src/webserver/webserver.c +++ b/src/webserver/webserver.c @@ -30,7 +30,7 @@ static int print_simple(struct mg_connection *conn, void *input) static int log_http_message(const struct mg_connection *conn, const char *message) { - logg("HTTP info: %s", message); + logg_web(HTTP_INFO, "HTTP info: %s", message); return 1; } @@ -38,14 +38,14 @@ static int log_http_access(const struct mg_connection *conn, const char *message { // Only log when in API debugging mode if(config.debug & DEBUG_API) - logg("HTTP access: %s", message); + logg_web(HTTP_INFO, "ACCESS: %s", message); return 1; } void http_init(void) { - logg("Initializing HTTP server on port %s", httpsettings.port); + logg_web(HTTP_INFO, "Initializing HTTP server on port %s", httpsettings.port); /* Initialize the library */ unsigned int features = MG_FEATURES_FILES | @@ -53,9 +53,10 @@ void http_init(void) MG_FEATURES_IPV6 | MG_FEATURES_CACHE | MG_FEATURES_STATS; + if(mg_init_library(features) == 0) { - logg("Initializing HTTP library failed!"); + logg_web(HTTP_INFO, "Initializing HTTP library failed!"); return; } @@ -116,7 +117,7 @@ void http_init(void) { if(config.debug & DEBUG_API) { - logg("Installing API handler at %s", api_path); + logg_web(HTTP_INFO, "Installing API handler at %s", api_path); } mg_set_request_handler(ctx, api_path, api_handler, NULL); // The request handler URI got duplicated From dc915c2a6008ec3efd806ffec1f1f39047de3402 Mon Sep 17 00:00:00 2001 From: DL6ER Date: Mon, 15 Jun 2020 21:03:37 +0200 Subject: [PATCH 0161/1669] Disable directory listings. Signed-off-by: DL6ER --- src/webserver/webserver.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/webserver/webserver.c b/src/webserver/webserver.c index c2a196579..bcc238b28 100644 --- a/src/webserver/webserver.c +++ b/src/webserver/webserver.c @@ -84,6 +84,7 @@ void http_init(void) "document_root", httpsettings.webroot, "listening_ports", httpsettings.port, "decode_url", "no", + "enable_directory_listing", "no", "num_threads", "4", "access_control_list", httpsettings.acl, "additional_header", "Content-Security-Policy: default-src 'self' 'unsafe-inline';\r\n" From c3d2c87800027158345d3825e268bfdf0ffcef18 Mon Sep 17 00:00:00 2001 From: DL6ER Date: Mon, 15 Jun 2020 21:05:09 +0200 Subject: [PATCH 0162/1669] Add request handler for directories (try /index.php) Signed-off-by: DL6ER --- src/webserver/ph7.c | 26 ++++++++++++++++++++------ src/webserver/webserver.c | 1 + 2 files changed, 21 insertions(+), 6 deletions(-) diff --git a/src/webserver/ph7.c b/src/webserver/ph7.c index 928ab6cf2..b092b82f8 100644 --- a/src/webserver/ph7.c +++ b/src/webserver/ph7.c @@ -44,17 +44,31 @@ int ph7_handler(struct mg_connection *conn, void *cbdata) /* Handler may access the request info using mg_get_request_info */ const struct mg_request_info *req_info = mg_get_request_info(conn); + const char *local_uri = req_info->local_uri + 1u; // Build full path of PHP script on our machine const size_t webroot_len = strlen(httpsettings.webroot); - const size_t local_uri_len = strlen(req_info->local_uri + 1u); // +1 to skip the initial '/' - char full_path[webroot_len + local_uri_len + 2]; - strcpy(full_path, httpsettings.webroot); + const size_t local_uri_len = strlen(local_uri); // +1 to skip the initial '/' + size_t buffer_len = webroot_len + local_uri_len + 2; + + // Check if we can serve an index.php file when the user is looking for a directory + bool append_index = false; + if(local_uri[local_uri_len - 1u] == '/') + { + append_index = true; + buffer_len += 11; // strlen("/index.php") + } + + char full_path[buffer_len]; + strncpy(full_path, httpsettings.webroot, webroot_len); full_path[webroot_len] = '/'; - strncpy(full_path + webroot_len + 1u, req_info->local_uri + 1u, local_uri_len); + strncpy(full_path + webroot_len + 1u, local_uri, local_uri_len); full_path[webroot_len + local_uri_len + 1u] = '\0'; - if(config.debug & DEBUG_API) - logg("Full path of PHP script: %s", full_path); + if(append_index) + { + strcpy(full_path + webroot_len + local_uri_len, "/index.php"); + full_path[webroot_len + local_uri_len + 11u] = '\0'; + } // Compile PHP script into byte-code // This usually takes only 1-2 msec even for larger scripts on a Raspberry diff --git a/src/webserver/webserver.c b/src/webserver/webserver.c index bcc238b28..748fc2d5e 100644 --- a/src/webserver/webserver.c +++ b/src/webserver/webserver.c @@ -127,6 +127,7 @@ void http_init(void) // Initialize PH7 engine and register PHP request handler init_ph7(); + mg_set_request_handler(ctx, "**/$", ph7_handler, 0); mg_set_request_handler(ctx, "**.php$", ph7_handler, 0); } From 2709cdbd17ab69714695922a6838a63240917cf9 Mon Sep 17 00:00:00 2001 From: DL6ER Date: Mon, 15 Jun 2020 21:06:33 +0200 Subject: [PATCH 0163/1669] Simplify error report messages. Signed-off-by: DL6ER --- src/webserver/ph7.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/webserver/ph7.c b/src/webserver/ph7.c index b092b82f8..2246519e5 100644 --- a/src/webserver/ph7.c +++ b/src/webserver/ph7.c @@ -91,7 +91,7 @@ int ph7_handler(struct mg_connection *conn, void *cbdata) else if( rc == PH7_VM_ERR ) { logg_web(PH7_ERROR, "%s: VM initialization error", full_path); - // Mark file as processes - this prevents the HTTP server + // Mark file as processed - this prevents the HTTP server // from printing the raw PHP source code to the user return 1; } @@ -111,7 +111,7 @@ int ph7_handler(struct mg_connection *conn, void *cbdata) /* zErrLog is null terminated */ logg_web(PH7_ERROR, " ---> %s", zErrLog); } - // Mark file as processes - this prevents the HTTP server + // Mark file as processed - this prevents the HTTP server // from printing the raw PHP source code to the user return 1; } @@ -144,7 +144,7 @@ int ph7_handler(struct mg_connection *conn, void *cbdata) if( rc != PH7_OK ) { logg_web(PH7_ERROR, "%s: VM execution error", full_path); - // Mark file as processes - this prevents the HTTP server + // Mark file as processed - this prevents the HTTP server // from printing the raw PHP source code to the user return 1; } @@ -173,7 +173,7 @@ static int PH7_error_report(const void *pOutput, unsigned int nOutputLen, // Log error message, strip trailing newline character if any if(((const char*)pOutput)[nOutputLen-1] == '\n') nOutputLen--; - logg_web(PH7_ERROR, "PH7 error: %.*s", nOutputLen, (const char*)pOutput); + logg_web(PH7_ERROR, "%.*s", nOutputLen, (const char*)pOutput); return PH7_OK; } From 17d356209929a5f804ead41a7bfd729a10cd1836 Mon Sep 17 00:00:00 2001 From: DL6ER Date: Tue, 16 Jun 2020 19:09:25 +0200 Subject: [PATCH 0164/1669] Improve cmake system. Signed-off-by: DL6ER --- src/civetweb/CMakeLists.txt | 16 ++++++++++------ src/ph7/CMakeLists.txt | 11 ++++++++++- src/ph7/ph7.c | 6 +++--- 3 files changed, 23 insertions(+), 10 deletions(-) diff --git a/src/civetweb/CMakeLists.txt b/src/civetweb/CMakeLists.txt index 672f8caaa..a7c980e32 100644 --- a/src/civetweb/CMakeLists.txt +++ b/src/civetweb/CMakeLists.txt @@ -11,8 +11,13 @@ set(sources civetweb.c civetweb.h + handle_form.inl + md5.inl + sha1.inl + timer.inl ) +add_library(civetweb OBJECT ${sources}) # We can remove the NO_SSL later on. It adds additional constraints to the build system (availablity of libSSL-dev) # NO_CGI = no CGI support (we don't need it) # NO_SSL_DL NO_SSL = no SSL support (for now) @@ -20,11 +25,10 @@ set(sources # - Number of connections (currently and total) # - Amount of data read and written # USE_IPV6: add IPv6 support -set(CIVETWEB_OPTS - "" - ) +target_compile_definitions(civetweb PRIVATE NO_CGI + NO_SSL_DL + NO_SSL + USE_SERVER_STATS + USE_IPV6) - -add_library(civetweb OBJECT ${sources}) -target_compile_definitions(civetweb PRIVATE NO_CGI NO_SSL_DL NO_SSL USE_SERVER_STATS USE_IPV6) target_include_directories(civetweb PRIVATE ${PROJECT_SOURCE_DIR}/src) diff --git a/src/ph7/CMakeLists.txt b/src/ph7/CMakeLists.txt index d811f3b13..79b5c07ea 100644 --- a/src/ph7/CMakeLists.txt +++ b/src/ph7/CMakeLists.txt @@ -13,5 +13,14 @@ set(sources ) add_library(ph7 OBJECT ${sources}) -target_compile_options(ph7 PRIVATE -Wno-unused-but-set-parameter -Wno-array-bounds -DPH7_ENABLE_THREADS) +target_compile_definitions(ph7 PRIVATE PH7_ENABLE_THREADS) + +# Accept known warnings in the PH7 engine +target_compile_options(ph7 PRIVATE -Wno-unused-but-set-parameter + -Wno-unused-but-set-variable + -Wno-unused-const-variable + -Wno-array-bounds + -Wno-misleading-indentation + -Wno-sign-compare) + target_include_directories(ph7 PRIVATE ${PROJECT_SOURCE_DIR}/src) diff --git a/src/ph7/ph7.c b/src/ph7/ph7.c index 6160dd924..20252abff 100644 --- a/src/ph7/ph7.c +++ b/src/ph7/ph7.c @@ -126,7 +126,8 @@ * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /******************************* Pi-hole modification ******************************/ -extern void logg(const char* format, ...) __attribute__ ((format (gnu_printf, 1, 2))); +enum web_code { HTTP_INFO, PH7_ERROR }; +extern void logg_web(enum web_code code, const char* format, ...) __attribute__ ((format (gnu_printf, 2, 3))); /***********************************************************************************/ /* $SymiscID: ph7.h v2.1 UNIX|WIN32/64 2012-09-15 09:43 stable $ */ #include /* needed for the definition of va_list */ @@ -5008,9 +5009,8 @@ static sxi32 VmThrowException(ph7_vm *pVm,ph7_class_instance *pThis); */ static sxi32 VmCallErrorHandler(ph7_vm *pVm,SyBlob *pMsg) { - ph7_output_consumer *pCons = &pVm->sVmConsumer; /* Invoke the output consumer callback */ - logg("PH7 Error: %.*s", SyBlobLength(pMsg), SyBlobData(pMsg)); + logg_web(PH7_ERROR, "%.*s", SyBlobLength(pMsg), (char *)SyBlobData(pMsg)); return SXRET_OK; } /* From 2c78622cd2bb421e110a08c43afe788b7f4bfad3 Mon Sep 17 00:00:00 2001 From: DL6ER Date: Tue, 16 Jun 2020 19:55:55 +0200 Subject: [PATCH 0165/1669] Streamline /api/dns endpoints while writing the documentation for them Signed-off-by: DL6ER --- src/api/dns.c | 174 ++++++++++++++++++++++++++---------- src/database/gravity-db.c | 95 ++++++++++++++++---- src/database/gravity-db.h | 10 ++- src/webserver/http-common.c | 4 + src/webserver/http-common.h | 2 +- src/webserver/json_macros.h | 4 +- 6 files changed, 220 insertions(+), 69 deletions(-) diff --git a/src/api/dns.c b/src/api/dns.c index aea834c8b..99bdae255 100644 --- a/src/api/dns.c +++ b/src/api/dns.c @@ -49,16 +49,16 @@ int api_dns_status(struct mg_connection *conn) if ((data_len < 1) || (data_len >= (int)sizeof(buffer))) { return send_json_error(conn, 400, "bad_request", "No request body data", - NULL); + NULL); } buffer[data_len] = '\0'; cJSON *obj = cJSON_Parse(buffer); if (obj == NULL) { return send_json_error(conn, 400, - "bad_request", - "Invalid request body data", - NULL); + "bad_request", + "Invalid request body data", + NULL); } cJSON *elem1 = cJSON_GetObjectItemCaseSensitive(obj, "action"); @@ -66,8 +66,8 @@ int api_dns_status(struct mg_connection *conn) cJSON_Delete(obj); return send_json_error(conn, 400, "bad_request", - "No \"action\" string in body data", - NULL); + "No \"action\" string in body data", + NULL); } const char *action = elem1->valuestring; @@ -102,8 +102,8 @@ int api_dns_status(struct mg_connection *conn) cJSON_Delete(obj); return send_json_error(conn, 400, "bad_request", - "Invalid \"action\" requested", - NULL); + "Invalid \"action\" requested", + NULL); } JSON_SEND_OBJECT(json); } @@ -131,20 +131,27 @@ static int getTableType(bool whitelist, bool exact) static int api_dns_somelist_read(struct mg_connection *conn, bool exact, bool whitelist) { int type = getTableType(whitelist, exact); - if(!gravityDB_readTable(type)) + const char *sql_msg = NULL; + if(!gravityDB_readTable(type, &sql_msg)) { cJSON *json = JSON_NEW_OBJ(); - JSON_OBJ_REF_STR(json, "database_location", FTLfiles.gravity_db); - JSON_OBJ_ADD_NUMBER(json, "type", type); - return send_json_error(conn, 500, - "database_error", - "Could not read domain from database table", - json); + + // Add SQL message (may be NULL = not available) + if (sql_msg != NULL) { + JSON_OBJ_REF_STR(json, "sql_msg", sql_msg); + } else { + JSON_OBJ_ADD_NULL(json, "sql_msg"); + } + + return send_json_error(conn, 402, // 402 Request Failed + "database_error", + "Could not read domains from database table", + json); } domainrecord domain; cJSON *json = JSON_NEW_ARRAY(); - while(gravityDB_readTableGetDomain(&domain)) + while(gravityDB_readTableGetDomain(&domain, &sql_msg)) { cJSON *item = JSON_NEW_OBJ(); JSON_OBJ_COPY_STR(item, "domain", domain.domain); @@ -156,7 +163,28 @@ static int api_dns_somelist_read(struct mg_connection *conn, bool exact, bool wh } gravityDB_readTableFinalize(); - JSON_SEND_OBJECT(json); + if(sql_msg == NULL) + { + // No error + JSON_SEND_OBJECT(json); + } + else + { + JSON_DELETE(json); + json = JSON_NEW_OBJ(); + + // Add SQL message (may be NULL = not available) + if (sql_msg != NULL) { + JSON_OBJ_REF_STR(json, "sql_msg", sql_msg); + } else { + JSON_OBJ_ADD_NULL(json, "sql_msg"); + } + + return send_json_error(conn, 402, // 402 Request Failed + "database_error", + "Could not read domains from database table", + json); + } } static int api_dns_somelist_POST(struct mg_connection *conn, @@ -175,41 +203,86 @@ static int api_dns_somelist_POST(struct mg_connection *conn, cJSON *obj = cJSON_Parse(buffer); if (obj == NULL) { return send_json_error(conn, 400, - "bad_request", - "Invalid request body data", - NULL); + "bad_request", + "Invalid request body data", + NULL); } cJSON *elem = cJSON_GetObjectItemCaseSensitive(obj, "domain"); - if (!cJSON_IsString(elem)) { cJSON_Delete(obj); return send_json_error(conn, 400, - "bad_request", - "No \"domain\" string in body data", - NULL); + "bad_request", + "No \"domain\" string in body data", + NULL); } - const char *domain = elem->valuestring; + char *domain = strdup(elem->valuestring); + + bool enabled = true; + elem = cJSON_GetObjectItemCaseSensitive(obj, "enabled"); + if (cJSON_IsBool(elem)) { + enabled = elem->type == cJSON_True; + } + + char *comment = NULL; + elem = cJSON_GetObjectItemCaseSensitive(obj, "comment"); + if (cJSON_IsString(elem)) { + comment = strdup(elem->valuestring); + } + cJSON_Delete(obj); cJSON *json = JSON_NEW_OBJ(); int type = getTableType(whitelist, exact); - if(gravityDB_addToTable(type, domain)) + const char *sql_msg = NULL; + if(gravityDB_addToTable(type, domain, enabled, comment, &sql_msg)) { - JSON_OBJ_REF_STR(json, "key", "added"); + // Add domain JSON_OBJ_COPY_STR(json, "domain", domain); - cJSON_Delete(obj); - JSON_SEND_OBJECT(json); + free(domain); + + // Add enabled boolean + JSON_OBJ_ADD_BOOL(json, "enabled", enabled); + + // Add comment (may be NULL) + if (comment != NULL) { + JSON_OBJ_COPY_STR(json, "comment", comment); + free(comment); + } else { + JSON_OBJ_ADD_NULL(json, "comment"); + } + + // Send success reply + JSON_SEND_OBJECT_CODE(json, 201); // 201 Created } else { + // Add domain JSON_OBJ_COPY_STR(json, "domain", domain); - JSON_OBJ_REF_STR(json, "database_location", FTLfiles.gravity_db); - JSON_OBJ_ADD_NUMBER(json, "type", type); - cJSON_Delete(obj); - return send_json_error(conn, 500, - "database_error", - "Could not add domain to gravity database", - json); + free(domain); + + // Add enabled boolean + JSON_OBJ_ADD_BOOL(json, "enabled", enabled); + + // Add comment (may be NULL) + if (comment != NULL) { + JSON_OBJ_COPY_STR(json, "comment", comment); + free(comment); + } else { + JSON_OBJ_ADD_NULL(json, "comment"); + } + + // Add SQL message (may be NULL = not available) + if (sql_msg != NULL) { + JSON_OBJ_REF_STR(json, "sql_msg", sql_msg); + } else { + JSON_OBJ_ADD_NULL(json, "sql_msg"); + } + + // Send error reply + return send_json_error(conn, 402, // 402 Request Failed + "database_error", + "Could not add domain to gravity database", + json); } } @@ -222,26 +295,35 @@ static int api_dns_somelist_DELETE(struct mg_connection *conn, char domain[1024]; // Advance one character to strip "/" const char *encoded_uri = strrchr(request->local_uri, '/')+1u; - // Decode URL (necessar for regular expressions, harmless for domains) + // Decode URL (necessary for regular expressions, harmless for domains) mg_url_decode(encoded_uri, strlen(encoded_uri), domain, sizeof(domain)-1u, 0); cJSON *json = JSON_NEW_OBJ(); int type = getTableType(whitelist, exact); - if(gravityDB_delFromTable(type, domain)) + const char *sql_msg = NULL; + if(gravityDB_delFromTable(type, domain, &sql_msg)) { JSON_OBJ_REF_STR(json, "key", "removed"); JSON_OBJ_REF_STR(json, "domain", domain); - JSON_SEND_OBJECT(json); + JSON_SEND_OBJECT_CODE(json, 200); // 200 OK } else { + // Add domain JSON_OBJ_REF_STR(json, "domain", domain); - JSON_OBJ_REF_STR(json, "database_location", FTLfiles.gravity_db); - JSON_OBJ_ADD_NUMBER(json, "type", type); - return send_json_error(conn, 500, - "database_error", - "Could not remove domain from database table", - json); + + // Add SQL message (may be NULL = not available) + if (sql_msg != NULL) { + JSON_OBJ_REF_STR(json, "sql_msg", sql_msg); + } else { + JSON_OBJ_ADD_NULL(json, "sql_msg"); + } + + // Send error reply + return send_json_error(conn, 402, + "database_error", + "Could not remove domain from database table", + json); } } @@ -258,7 +340,7 @@ int api_dns_somelist(struct mg_connection *conn, bool exact, bool whitelist) { return api_dns_somelist_read(conn, exact, whitelist); } - else if(method == HTTP_POST) + else if(method == HTTP_PUT) { // Add domain from exact white-/blacklist when a user sends // the request to the general address /api/dns/{white,black}list diff --git a/src/database/gravity-db.c b/src/database/gravity-db.c index 45f577c94..01f0b331a 100644 --- a/src/database/gravity-db.c +++ b/src/database/gravity-db.c @@ -940,23 +940,34 @@ bool gravityDB_get_regex_client_groups(clientsData* client, const int numregex, return true; } -bool gravityDB_addToTable(const int type, const char* domain) +bool gravityDB_addToTable(const int type, const char *domain, + const bool enabled, const char *comment, + const char **message) { + if(gravity_db == NULL) + { + *message = "Database not available"; + return false; + } + // Prepare SQLite statement sqlite3_stmt* stmt = NULL; - const char *querystr = "INSERT INTO domainlist (domain,type) VALUES (?,?);"; + const char *querystr = "INSERT INTO domainlist (domain,type,enabled,comment) VALUES (?,?,?,?);"; int rc = sqlite3_prepare_v2(gravity_db, querystr, -1, &stmt, NULL); - if( rc != SQLITE_OK ){ + if( rc != SQLITE_OK ) + { + *message = sqlite3_errmsg(gravity_db); logg("gravityDB_addToTable(%d, %s) - SQL error prepare (%i): %s", - type, domain, rc, sqlite3_errmsg(gravity_db)); + type, domain, rc, *message); return false; } // Bind domain string to prepared statement if((rc = sqlite3_bind_text(stmt, 1, domain, -1, SQLITE_STATIC)) != SQLITE_OK) { + *message = sqlite3_errmsg(gravity_db); logg("gravityDB_addToTable(%d, %s): Failed to bind domain (error %d) - %s", - type, domain, rc, sqlite3_errmsg(gravity_db)); + type, domain, rc, *message); sqlite3_reset(stmt); sqlite3_finalize(stmt); return false; @@ -965,19 +976,47 @@ bool gravityDB_addToTable(const int type, const char* domain) // Bind domain type to prepared statement if((rc = sqlite3_bind_int(stmt, 2, type)) != SQLITE_OK) { + *message = sqlite3_errmsg(gravity_db); logg("gravityDB_addToTable(%d, %s): Failed to bind domain (error %d) - %s", - type, domain, rc, sqlite3_errmsg(gravity_db)); + type, domain, rc, *message); + sqlite3_reset(stmt); + sqlite3_finalize(stmt); + return false; + } + + // Bind enabled boolean to prepared statement + if((rc = sqlite3_bind_int(stmt, 3, enabled ? 1 : 0)) != SQLITE_OK) + { + *message = sqlite3_errmsg(gravity_db); + logg("gravityDB_addToTable(%d, %s): Failed to bind enabled (error %d) - %s", + type, domain, rc, *message); + sqlite3_reset(stmt); + sqlite3_finalize(stmt); + return false; + } + + // Bind enabled boolean to prepared statement + if((rc = sqlite3_bind_text(stmt, 4, comment, -1, SQLITE_STATIC)) != SQLITE_OK) + { + *message = sqlite3_errmsg(gravity_db); + logg("gravityDB_addToTable(%d, %s): Failed to bind comment (error %d) - %s", + type, domain, rc, *message); sqlite3_reset(stmt); sqlite3_finalize(stmt); return false; } + // Perform step bool okay = false; if((rc = sqlite3_step(stmt)) == SQLITE_DONE) { // Domain added okay = true; } + else + { + *message = sqlite3_errmsg(gravity_db); + } // Finalize statement and close database handle sqlite3_reset(stmt); @@ -986,23 +1025,31 @@ bool gravityDB_addToTable(const int type, const char* domain) return okay; } -bool gravityDB_delFromTable(const int type, const char* domain) +bool gravityDB_delFromTable(const int type, const char* domain, const char **message) { + if(gravity_db == NULL) + { + *message = "Database not available"; + return false; + } // Prepare SQLite statement sqlite3_stmt* stmt = NULL; const char *querystr = "DELETE FROM domainlist WHERE domain = ? AND type = ?;"; int rc = sqlite3_prepare_v2(gravity_db, querystr, -1, &stmt, NULL); - if( rc != SQLITE_OK ){ + if( rc != SQLITE_OK ) + { + *message = sqlite3_errmsg(gravity_db); logg("gravityDB_delFromTable(%d, %s) - SQL error prepare (%i): %s", - type, domain, rc, sqlite3_errmsg(gravity_db)); + type, domain, rc, *message); return false; } // Bind domain to prepared statement if((rc = sqlite3_bind_text(stmt, 1, domain, -1, SQLITE_STATIC)) != SQLITE_OK) { + *message = sqlite3_errmsg(gravity_db); logg("gravityDB_delFromTable(%d, %s): Failed to bind domain (error %d) - %s", - type, domain, rc, sqlite3_errmsg(gravity_db)); + type, domain, rc, *message); sqlite3_reset(stmt); sqlite3_finalize(stmt); return false; @@ -1011,19 +1058,25 @@ bool gravityDB_delFromTable(const int type, const char* domain) // Bind domain type to prepared statement if((rc = sqlite3_bind_int(stmt, 2, type)) != SQLITE_OK) { + *message = sqlite3_errmsg(gravity_db); logg("gravityDB_delFromTable(%d, %s): Failed to bind domain (error %d) - %s", - type, domain, rc, sqlite3_errmsg(gravity_db)); + type, domain, rc, *message); sqlite3_reset(stmt); sqlite3_finalize(stmt); return false; } + // Perform step bool okay = false; if((rc = sqlite3_step(stmt)) == SQLITE_DONE) { // Domain removed okay = true; } + else + { + *message = sqlite3_errmsg(gravity_db); + } // Finalize statement and close database handle sqlite3_reset(stmt); @@ -1033,22 +1086,30 @@ bool gravityDB_delFromTable(const int type, const char* domain) } static sqlite3_stmt* read_stmt = NULL; -bool gravityDB_readTable(const int type) +bool gravityDB_readTable(const int type, const char **message) { + if(gravity_db == NULL) + { + *message = "Database not available"; + return false; + } + // Prepare SQLite statement const char *querystr = "SELECT domain,enabled,date_added,date_modified,comment FROM domainlist WHERE type = ?;"; int rc = sqlite3_prepare_v2(gravity_db, querystr, -1, &read_stmt, NULL); if( rc != SQLITE_OK ){ + *message = sqlite3_errmsg(gravity_db); logg("gravityDB_readTable(%d) - SQL error prepare (%i): %s", - type, rc, sqlite3_errmsg(gravity_db)); + type, rc, *message); return false; } // Bind domain type to prepared statement if((rc = sqlite3_bind_int(read_stmt, 1, type)) != SQLITE_OK) { + *message = sqlite3_errmsg(gravity_db); logg("gravityDB_readTable(%d): Failed to bind domain (error %d) - %s", - type, rc, sqlite3_errmsg(gravity_db)); + type, rc, *message); sqlite3_reset(read_stmt); sqlite3_finalize(read_stmt); return false; @@ -1057,7 +1118,7 @@ bool gravityDB_readTable(const int type) return true; } -bool gravityDB_readTableGetDomain(domainrecord *domain) +bool gravityDB_readTableGetDomain(domainrecord *domain, const char **message) { // Perform step const int rc = sqlite3_step(read_stmt); @@ -1078,7 +1139,9 @@ bool gravityDB_readTableGetDomain(domainrecord *domain) // SQLITE_DONE (we are finished reading the table) if(rc != SQLITE_DONE) { - logg("gravityDB_readTableGetDomain() - SQL error step (%i): %s", rc, sqlite3_errmsg(gravity_db)); + *message = sqlite3_errmsg(gravity_db); + logg("gravityDB_readTableGetDomain() - SQL error step (%i): %s", + rc, *message); return false; } diff --git a/src/database/gravity-db.h b/src/database/gravity-db.h index 1c3b2dea4..811fde80c 100644 --- a/src/database/gravity-db.h +++ b/src/database/gravity-db.h @@ -46,10 +46,12 @@ bool in_blacklist(const char *domain, const int clientID, clientsData* client); bool gravityDB_get_regex_client_groups(clientsData* client, const int numregex, const int *regexid, const unsigned char type, const char* table, const int clientID); -bool gravityDB_addToTable(const int type, const char* domain); -bool gravityDB_delFromTable(const int type, const char* domain); -bool gravityDB_readTable(const int type); -bool gravityDB_readTableGetDomain(domainrecord *domain); +bool gravityDB_addToTable(const int type, const char *domain, + const bool enabled, const char *comment, + const char **message); +bool gravityDB_delFromTable(const int type, const char* domain, const char **message); +bool gravityDB_readTable(const int type, const char **message); +bool gravityDB_readTableGetDomain(domainrecord *domain, const char **message); void gravityDB_readTableFinalize(void); #endif //GRAVITY_H diff --git a/src/webserver/http-common.c b/src/webserver/http-common.c index a1d708b14..8605385cc 100644 --- a/src/webserver/http-common.c +++ b/src/webserver/http-common.c @@ -182,6 +182,10 @@ int http_method(struct mg_connection *conn) { return HTTP_DELETE; } + else if(strcmp(request->request_method, "PUT") == 0) + { + return HTTP_PUT; + } else if(strcmp(request->request_method, "POST") == 0) { return HTTP_POST; diff --git a/src/webserver/http-common.h b/src/webserver/http-common.h index 54a698946..6913bfea7 100644 --- a/src/webserver/http-common.h +++ b/src/webserver/http-common.h @@ -45,7 +45,7 @@ int get_int_var(const char *source, const char *var); #define GET_VAR(variable, destination, source) mg_get_var(source, strlen(source), variable, destination, sizeof(destination)) // Method routines -enum { HTTP_UNKNOWN, HTTP_GET, HTTP_POST, HTTP_DELETE }; +enum { HTTP_UNKNOWN, HTTP_GET, HTTP_POST, HTTP_PUT, HTTP_DELETE }; int http_method(struct mg_connection *conn); // Utils diff --git a/src/webserver/json_macros.h b/src/webserver/json_macros.h index 22693df76..c659aa8d2 100644 --- a/src/webserver/json_macros.h +++ b/src/webserver/json_macros.h @@ -80,7 +80,7 @@ } #define JSON_OBJ_ADD_BOOL(object, key, value) {\ - cJSON *bool_item = cJSON_CreateBool(value); \ + cJSON *bool_item = cJSON_CreateBool(value ? cJSON_True : cJSON_False); \ if(bool_item == NULL) \ { \ cJSON_Delete(object); \ @@ -173,7 +173,7 @@ } \ send_http_code(conn, "application/json; charset=utf-8", code, msg); \ cJSON_Delete(object); \ - return 200; \ + return code; \ } /* #define JSON_SEND_OBJECT_AND_HEADERS(object, additional_headers){ \ From 56fe849d839358820c342ae2b0ba454bc9238c4e Mon Sep 17 00:00:00 2001 From: DL6ER Date: Tue, 16 Jun 2020 23:09:00 +0200 Subject: [PATCH 0166/1669] Move white- and blacklist endpoints one level up Signed-off-by: DL6ER --- src/api/CMakeLists.txt | 1 + src/api/dns.c | 271 ++--------------------------------------- src/api/list.c | 260 +++++++++++++++++++++++++++++++++++++++ src/api/routes.c | 22 ++-- src/api/routes.h | 4 +- 5 files changed, 285 insertions(+), 273 deletions(-) create mode 100644 src/api/list.c diff --git a/src/api/CMakeLists.txt b/src/api/CMakeLists.txt index 37fb46d95..745915e23 100644 --- a/src/api/CMakeLists.txt +++ b/src/api/CMakeLists.txt @@ -10,6 +10,7 @@ set(sources auth.c + list.c dns.c dns.h ftl.c diff --git a/src/api/dns.c b/src/api/dns.c index 99bdae255..05a5b505e 100644 --- a/src/api/dns.c +++ b/src/api/dns.c @@ -8,23 +8,15 @@ * This file is copyright under the latest version of the EUPL. * Please see LICENSE file for your rights under this license. */ -#include "FTL.h" -// counters -#include "shmem.h" +#include "../FTL.h" #include "dns.h" #include "../webserver/http-common.h" #include "../webserver/json_macros.h" #include "routes.h" -#include "database/gravity-db.h" -#include "log.h" // {s,g}et_blockingstatus() -#include "setupVars.h" -// floor() -#include +#include "../setupVars.h" // set_blockingmode_timer() -#include "timers.h" -// struct config -#include "config.h" +#include "../timers.h" int api_dns_status(struct mg_connection *conn) { @@ -33,7 +25,7 @@ int api_dns_status(struct mg_connection *conn) { // Return current status cJSON *json = JSON_NEW_OBJ(); - JSON_OBJ_REF_STR(json, "status", (get_blockingstatus() ? "enabled" : "disabled")); + JSON_OBJ_REF_STR(json, "status", (get_blockingstatus() ? "active" : "inactive")); JSON_SEND_OBJECT(json); } else if(method == HTTP_POST) @@ -61,7 +53,7 @@ int api_dns_status(struct mg_connection *conn) NULL); } - cJSON *elem1 = cJSON_GetObjectItemCaseSensitive(obj, "action"); + cJSON *elem1 = cJSON_GetObjectItemCaseSensitive(obj, "status"); if (!cJSON_IsString(elem1)) { cJSON_Delete(obj); return send_json_error(conn, 400, @@ -79,19 +71,19 @@ int api_dns_status(struct mg_connection *conn) } cJSON *json = JSON_NEW_OBJ(); - if(strcmp(action, "enable") == 0) + if(strcmp(action, "active") == 0) { cJSON_Delete(obj); - JSON_OBJ_REF_STR(json, "key", "enabled"); + JSON_OBJ_REF_STR(json, "status", "active"); // If no "time" key was present, we call this subroutine with // delay == -1 which will disable all previously set timers set_blockingmode_timer(delay, false); set_blockingstatus(true); } - else if(strcmp(action, "disable") == 0) + else if(strcmp(action, "inactive") == 0) { cJSON_Delete(obj); - JSON_OBJ_REF_STR(json, "key", "disabled"); + JSON_OBJ_REF_STR(json, "status", "inactive"); // If no "time" key was present, we call this subroutine with // delay == -1 which will disable all previously set timers set_blockingmode_timer(delay, true); @@ -114,251 +106,6 @@ int api_dns_status(struct mg_connection *conn) } } -static int getTableType(bool whitelist, bool exact) -{ - if(whitelist) - if(exact) - return GRAVITY_DOMAINLIST_EXACT_WHITELIST; - else - return GRAVITY_DOMAINLIST_REGEX_WHITELIST; - else - if(exact) - return GRAVITY_DOMAINLIST_EXACT_BLACKLIST; - else - return GRAVITY_DOMAINLIST_REGEX_BLACKLIST; -} - -static int api_dns_somelist_read(struct mg_connection *conn, bool exact, bool whitelist) -{ - int type = getTableType(whitelist, exact); - const char *sql_msg = NULL; - if(!gravityDB_readTable(type, &sql_msg)) - { - cJSON *json = JSON_NEW_OBJ(); - - // Add SQL message (may be NULL = not available) - if (sql_msg != NULL) { - JSON_OBJ_REF_STR(json, "sql_msg", sql_msg); - } else { - JSON_OBJ_ADD_NULL(json, "sql_msg"); - } - - return send_json_error(conn, 402, // 402 Request Failed - "database_error", - "Could not read domains from database table", - json); - } - - domainrecord domain; - cJSON *json = JSON_NEW_ARRAY(); - while(gravityDB_readTableGetDomain(&domain, &sql_msg)) - { - cJSON *item = JSON_NEW_OBJ(); - JSON_OBJ_COPY_STR(item, "domain", domain.domain); - JSON_OBJ_ADD_BOOL(item, "enabled", domain.enabled); - JSON_OBJ_ADD_NUMBER(item, "date_added", domain.date_added); - JSON_OBJ_ADD_NUMBER(item, "date_modified", domain.date_modified); - JSON_OBJ_COPY_STR(item, "comment", domain.comment); - JSON_ARRAY_ADD_ITEM(json, item); - } - gravityDB_readTableFinalize(); - - if(sql_msg == NULL) - { - // No error - JSON_SEND_OBJECT(json); - } - else - { - JSON_DELETE(json); - json = JSON_NEW_OBJ(); - - // Add SQL message (may be NULL = not available) - if (sql_msg != NULL) { - JSON_OBJ_REF_STR(json, "sql_msg", sql_msg); - } else { - JSON_OBJ_ADD_NULL(json, "sql_msg"); - } - - return send_json_error(conn, 402, // 402 Request Failed - "database_error", - "Could not read domains from database table", - json); - } -} - -static int api_dns_somelist_POST(struct mg_connection *conn, - bool exact, - bool whitelist) -{ - char buffer[1024]; - int data_len = mg_read(conn, buffer, sizeof(buffer) - 1); - if ((data_len < 1) || (data_len >= (int)sizeof(buffer))) { - return send_json_error(conn, 400, - "bad_request", "No request body data", - NULL); - } - buffer[data_len] = '\0'; - - cJSON *obj = cJSON_Parse(buffer); - if (obj == NULL) { - return send_json_error(conn, 400, - "bad_request", - "Invalid request body data", - NULL); - } - - cJSON *elem = cJSON_GetObjectItemCaseSensitive(obj, "domain"); - if (!cJSON_IsString(elem)) { - cJSON_Delete(obj); - return send_json_error(conn, 400, - "bad_request", - "No \"domain\" string in body data", - NULL); - } - char *domain = strdup(elem->valuestring); - - bool enabled = true; - elem = cJSON_GetObjectItemCaseSensitive(obj, "enabled"); - if (cJSON_IsBool(elem)) { - enabled = elem->type == cJSON_True; - } - - char *comment = NULL; - elem = cJSON_GetObjectItemCaseSensitive(obj, "comment"); - if (cJSON_IsString(elem)) { - comment = strdup(elem->valuestring); - } - cJSON_Delete(obj); - - cJSON *json = JSON_NEW_OBJ(); - int type = getTableType(whitelist, exact); - const char *sql_msg = NULL; - if(gravityDB_addToTable(type, domain, enabled, comment, &sql_msg)) - { - // Add domain - JSON_OBJ_COPY_STR(json, "domain", domain); - free(domain); - - // Add enabled boolean - JSON_OBJ_ADD_BOOL(json, "enabled", enabled); - - // Add comment (may be NULL) - if (comment != NULL) { - JSON_OBJ_COPY_STR(json, "comment", comment); - free(comment); - } else { - JSON_OBJ_ADD_NULL(json, "comment"); - } - - // Send success reply - JSON_SEND_OBJECT_CODE(json, 201); // 201 Created - } - else - { - // Add domain - JSON_OBJ_COPY_STR(json, "domain", domain); - free(domain); - - // Add enabled boolean - JSON_OBJ_ADD_BOOL(json, "enabled", enabled); - - // Add comment (may be NULL) - if (comment != NULL) { - JSON_OBJ_COPY_STR(json, "comment", comment); - free(comment); - } else { - JSON_OBJ_ADD_NULL(json, "comment"); - } - - // Add SQL message (may be NULL = not available) - if (sql_msg != NULL) { - JSON_OBJ_REF_STR(json, "sql_msg", sql_msg); - } else { - JSON_OBJ_ADD_NULL(json, "sql_msg"); - } - - // Send error reply - return send_json_error(conn, 402, // 402 Request Failed - "database_error", - "Could not add domain to gravity database", - json); - } -} - -static int api_dns_somelist_DELETE(struct mg_connection *conn, - bool exact, - bool whitelist) -{ - const struct mg_request_info *request = mg_get_request_info(conn); - - char domain[1024]; - // Advance one character to strip "/" - const char *encoded_uri = strrchr(request->local_uri, '/')+1u; - // Decode URL (necessary for regular expressions, harmless for domains) - mg_url_decode(encoded_uri, strlen(encoded_uri), domain, sizeof(domain)-1u, 0); - - cJSON *json = JSON_NEW_OBJ(); - int type = getTableType(whitelist, exact); - const char *sql_msg = NULL; - if(gravityDB_delFromTable(type, domain, &sql_msg)) - { - JSON_OBJ_REF_STR(json, "key", "removed"); - JSON_OBJ_REF_STR(json, "domain", domain); - JSON_SEND_OBJECT_CODE(json, 200); // 200 OK - } - else - { - // Add domain - JSON_OBJ_REF_STR(json, "domain", domain); - - // Add SQL message (may be NULL = not available) - if (sql_msg != NULL) { - JSON_OBJ_REF_STR(json, "sql_msg", sql_msg); - } else { - JSON_OBJ_ADD_NULL(json, "sql_msg"); - } - - // Send error reply - return send_json_error(conn, 402, - "database_error", - "Could not remove domain from database table", - json); - } -} - -int api_dns_somelist(struct mg_connection *conn, bool exact, bool whitelist) -{ - // Verify requesting client is allowed to see this ressource - if(check_client_auth(conn) < 0) - { - return send_json_unauthorized(conn); - } - - int method = http_method(conn); - if(method == HTTP_GET) - { - return api_dns_somelist_read(conn, exact, whitelist); - } - else if(method == HTTP_PUT) - { - // Add domain from exact white-/blacklist when a user sends - // the request to the general address /api/dns/{white,black}list - return api_dns_somelist_POST(conn, exact, whitelist); - } - else if(method == HTTP_DELETE) - { - // Delete domain from exact white-/blacklist when a user sends - // the request to the general address /api/dns/{white,black}list - return api_dns_somelist_DELETE(conn, exact, whitelist); - } - else - { - // This results in error 404 - return 0; - } -} - int api_dns_cacheinfo(struct mg_connection *conn) { // Verify requesting client is allowed to access this ressource diff --git a/src/api/list.c b/src/api/list.c new file mode 100644 index 000000000..0093dce4d --- /dev/null +++ b/src/api/list.c @@ -0,0 +1,260 @@ +/* Pi-hole: A black hole for Internet advertisements +* (c) 2020 Pi-hole, LLC (https://pi-hole.net) +* Network-wide ad blocking via your own hardware. +* +* FTL Engine +* API Implementation /api/{white,black}list +* +* This file is copyright under the latest version of the EUPL. +* Please see LICENSE file for your rights under this license. */ + +#include "../FTL.h" +#include "../webserver/http-common.h" +#include "../webserver/json_macros.h" +#include "routes.h" +#include "../database/gravity-db.h" + +static int getTableType(bool whitelist, bool exact) +{ + if(whitelist) + if(exact) + return GRAVITY_DOMAINLIST_EXACT_WHITELIST; + else + return GRAVITY_DOMAINLIST_REGEX_WHITELIST; + else + if(exact) + return GRAVITY_DOMAINLIST_EXACT_BLACKLIST; + else + return GRAVITY_DOMAINLIST_REGEX_BLACKLIST; +} + +static int api_dns_domainlist_read(struct mg_connection *conn, bool exact, bool whitelist) +{ + int type = getTableType(whitelist, exact); + const char *sql_msg = NULL; + if(!gravityDB_readTable(type, &sql_msg)) + { + cJSON *json = JSON_NEW_OBJ(); + + // Add SQL message (may be NULL = not available) + if (sql_msg != NULL) { + JSON_OBJ_REF_STR(json, "sql_msg", sql_msg); + } else { + JSON_OBJ_ADD_NULL(json, "sql_msg"); + } + + return send_json_error(conn, 402, // 402 Request Failed + "database_error", + "Could not read domains from database table", + json); + } + + domainrecord domain; + cJSON *json = JSON_NEW_ARRAY(); + while(gravityDB_readTableGetDomain(&domain, &sql_msg)) + { + cJSON *item = JSON_NEW_OBJ(); + JSON_OBJ_COPY_STR(item, "domain", domain.domain); + JSON_OBJ_ADD_BOOL(item, "enabled", domain.enabled); + JSON_OBJ_ADD_NUMBER(item, "date_added", domain.date_added); + JSON_OBJ_ADD_NUMBER(item, "date_modified", domain.date_modified); + JSON_OBJ_COPY_STR(item, "comment", domain.comment); + JSON_ARRAY_ADD_ITEM(json, item); + } + gravityDB_readTableFinalize(); + + if(sql_msg == NULL) + { + // No error + JSON_SEND_OBJECT(json); + } + else + { + JSON_DELETE(json); + json = JSON_NEW_OBJ(); + + // Add SQL message (may be NULL = not available) + if (sql_msg != NULL) { + JSON_OBJ_REF_STR(json, "sql_msg", sql_msg); + } else { + JSON_OBJ_ADD_NULL(json, "sql_msg"); + } + + return send_json_error(conn, 402, // 402 Request Failed + "database_error", + "Could not read domains from database table", + json); + } +} + +static int api_dns_domainlist_POST(struct mg_connection *conn, + bool exact, + bool whitelist) +{ + char buffer[1024]; + int data_len = mg_read(conn, buffer, sizeof(buffer) - 1); + if ((data_len < 1) || (data_len >= (int)sizeof(buffer))) { + return send_json_error(conn, 400, + "bad_request", "No request body data", + NULL); + } + buffer[data_len] = '\0'; + + cJSON *obj = cJSON_Parse(buffer); + if (obj == NULL) { + return send_json_error(conn, 400, + "bad_request", + "Invalid request body data", + NULL); + } + + cJSON *elem = cJSON_GetObjectItemCaseSensitive(obj, "domain"); + if (!cJSON_IsString(elem)) { + cJSON_Delete(obj); + return send_json_error(conn, 400, + "bad_request", + "No \"domain\" string in body data", + NULL); + } + char *domain = strdup(elem->valuestring); + + bool enabled = true; + elem = cJSON_GetObjectItemCaseSensitive(obj, "enabled"); + if (cJSON_IsBool(elem)) { + enabled = elem->type == cJSON_True; + } + + char *comment = NULL; + elem = cJSON_GetObjectItemCaseSensitive(obj, "comment"); + if (cJSON_IsString(elem)) { + comment = strdup(elem->valuestring); + } + cJSON_Delete(obj); + + cJSON *json = JSON_NEW_OBJ(); + int type = getTableType(whitelist, exact); + const char *sql_msg = NULL; + if(gravityDB_addToTable(type, domain, enabled, comment, &sql_msg)) + { + // Add domain + JSON_OBJ_COPY_STR(json, "domain", domain); + free(domain); + + // Add enabled boolean + JSON_OBJ_ADD_BOOL(json, "enabled", enabled); + + // Add comment (may be NULL) + if (comment != NULL) { + JSON_OBJ_COPY_STR(json, "comment", comment); + free(comment); + } else { + JSON_OBJ_ADD_NULL(json, "comment"); + } + + // Send success reply + JSON_SEND_OBJECT_CODE(json, 201); // 201 Created + } + else + { + // Add domain + JSON_OBJ_COPY_STR(json, "domain", domain); + free(domain); + + // Add enabled boolean + JSON_OBJ_ADD_BOOL(json, "enabled", enabled); + + // Add comment (may be NULL) + if (comment != NULL) { + JSON_OBJ_COPY_STR(json, "comment", comment); + free(comment); + } else { + JSON_OBJ_ADD_NULL(json, "comment"); + } + + // Add SQL message (may be NULL = not available) + if (sql_msg != NULL) { + JSON_OBJ_REF_STR(json, "sql_msg", sql_msg); + } else { + JSON_OBJ_ADD_NULL(json, "sql_msg"); + } + + // Send error reply + return send_json_error(conn, 402, // 402 Request Failed + "database_error", + "Could not add domain to gravity database", + json); + } +} + +static int api_dns_domainlist_DELETE(struct mg_connection *conn, + bool exact, + bool whitelist) +{ + const struct mg_request_info *request = mg_get_request_info(conn); + + char domain[1024]; + // Advance one character to strip "/" + const char *encoded_uri = strrchr(request->local_uri, '/')+1u; + // Decode URL (necessary for regular expressions, harmless for domains) + mg_url_decode(encoded_uri, strlen(encoded_uri), domain, sizeof(domain)-1u, 0); + + cJSON *json = JSON_NEW_OBJ(); + int type = getTableType(whitelist, exact); + const char *sql_msg = NULL; + if(gravityDB_delFromTable(type, domain, &sql_msg)) + { + JSON_OBJ_REF_STR(json, "key", "removed"); + JSON_OBJ_REF_STR(json, "domain", domain); + JSON_SEND_OBJECT_CODE(json, 200); // 200 OK + } + else + { + // Add domain + JSON_OBJ_REF_STR(json, "domain", domain); + + // Add SQL message (may be NULL = not available) + if (sql_msg != NULL) { + JSON_OBJ_REF_STR(json, "sql_msg", sql_msg); + } else { + JSON_OBJ_ADD_NULL(json, "sql_msg"); + } + + // Send error reply + return send_json_error(conn, 402, + "database_error", + "Could not remove domain from database table", + json); + } +} + +int api_dns_domainlist(struct mg_connection *conn, bool exact, bool whitelist) +{ + // Verify requesting client is allowed to see this ressource + if(check_client_auth(conn) < 0) + { + return send_json_unauthorized(conn); + } + + int method = http_method(conn); + if(method == HTTP_GET) + { + return api_dns_domainlist_read(conn, exact, whitelist); + } + else if(method == HTTP_PUT) + { + // Add domain from exact white-/blacklist when a user sends + // the request to the general address /api/dns/{white,black}list + return api_dns_domainlist_POST(conn, exact, whitelist); + } + else if(method == HTTP_DELETE) + { + // Delete domain from exact white-/blacklist when a user sends + // the request to the general address /api/dns/{white,black}list + return api_dns_domainlist_DELETE(conn, exact, whitelist); + } + else + { + // This results in error 404 + return 0; + } +} diff --git a/src/api/routes.c b/src/api/routes.c index f6b7fa89b..9ddff8ecd 100644 --- a/src/api/routes.c +++ b/src/api/routes.c @@ -29,25 +29,27 @@ int api_handler(struct mg_connection *conn, void *ignored) { ret = api_dns_status(conn); } - else if(startsWith("/api/dns/whitelist/exact", request->local_uri)) + else if(startsWith("/api/dns/cacheinfo", request->local_uri)) { - ret = api_dns_somelist(conn, true, true); + ret = api_dns_cacheinfo(conn); } - else if(startsWith("/api/dns/whitelist/regex", request->local_uri)) + /******************************** api/whitelist ****************************/ + else if(startsWith("/api/whitelist/exact", request->local_uri)) { - ret = api_dns_somelist(conn, false, true); + ret = api_dns_domainlist(conn, true, true); } - else if(startsWith("/api/dns/blacklist/exact", request->local_uri)) + else if(startsWith("/api/whitelist/regex", request->local_uri)) { - ret = api_dns_somelist(conn, true, false); + ret = api_dns_domainlist(conn, false, true); } - else if(startsWith("/api/dns/blacklist/regex", request->local_uri)) + /******************************** api/blacklist ****************************/ + else if(startsWith("/api/blacklist/exact", request->local_uri)) { - ret = api_dns_somelist(conn, false, false); + ret = api_dns_domainlist(conn, true, false); } - else if(startsWith("/api/dns/cacheinfo", request->local_uri)) + else if(startsWith("/api/blacklist/regex", request->local_uri)) { - ret = api_dns_cacheinfo(conn); + ret = api_dns_domainlist(conn, false, false); } /******************************** api/ftl ****************************/ else if(startsWith("/api/ftl/clientIP", request->local_uri)) diff --git a/src/api/routes.h b/src/api/routes.h index bc81d2e59..3f8406e63 100644 --- a/src/api/routes.h +++ b/src/api/routes.h @@ -42,9 +42,11 @@ int api_ftl_network(struct mg_connection *conn); // DNS methods int api_dns_status(struct mg_connection *conn); -int api_dns_somelist(struct mg_connection *conn, bool exact, bool whitelist); int api_dns_cacheinfo(struct mg_connection *conn); +// White-/Blacklist methods +int api_dns_domainlist(struct mg_connection *conn, bool exact, bool whitelist); + // Version method int api_version(struct mg_connection *conn); From fc4f850259951087588beae504d894d7e6f2399c Mon Sep 17 00:00:00 2001 From: DL6ER Date: Tue, 16 Jun 2020 23:43:36 +0200 Subject: [PATCH 0167/1669] Uniform GET and POST keys and rename /dns/status to /dns/blocking to avoid misunderstandings Signed-off-by: DL6ER --- src/api/dns.c | 28 +++++++++++++++------------- src/api/routes.c | 4 ++-- src/api/routes.h | 2 +- 3 files changed, 18 insertions(+), 16 deletions(-) diff --git a/src/api/dns.c b/src/api/dns.c index 05a5b505e..16edae85c 100644 --- a/src/api/dns.c +++ b/src/api/dns.c @@ -18,14 +18,15 @@ // set_blockingmode_timer() #include "../timers.h" -int api_dns_status(struct mg_connection *conn) +int api_dns_blockingstatus(struct mg_connection *conn) { int method = http_method(conn); if(method == HTTP_GET) { // Return current status cJSON *json = JSON_NEW_OBJ(); - JSON_OBJ_REF_STR(json, "status", (get_blockingstatus() ? "active" : "inactive")); + const char *action = get_blockingstatus() ? "active" : "inactive"; + JSON_OBJ_REF_STR(json, "status", action); JSON_SEND_OBJECT(json); } else if(method == HTTP_POST) @@ -61,29 +62,29 @@ int api_dns_status(struct mg_connection *conn) "No \"action\" string in body data", NULL); } - const char *action = elem1->valuestring; + char *status = strdup(elem1->valuestring); unsigned int delay = -1; - cJSON *elem2 = cJSON_GetObjectItemCaseSensitive(obj, "time"); + cJSON *elem2 = cJSON_GetObjectItemCaseSensitive(obj, "delay"); if (cJSON_IsNumber(elem2) && elem2->valuedouble > 0.0) { delay = elem2->valueint; } + cJSON_Delete(obj); cJSON *json = JSON_NEW_OBJ(); - if(strcmp(action, "active") == 0) + JSON_OBJ_COPY_STR(json, "status", status); + + // Execute requested status + if(strcmp(status, "active") == 0) { - cJSON_Delete(obj); - JSON_OBJ_REF_STR(json, "status", "active"); // If no "time" key was present, we call this subroutine with // delay == -1 which will disable all previously set timers set_blockingmode_timer(delay, false); set_blockingstatus(true); } - else if(strcmp(action, "inactive") == 0) + else if(strcmp(status, "inactive") == 0) { - cJSON_Delete(obj); - JSON_OBJ_REF_STR(json, "status", "inactive"); // If no "time" key was present, we call this subroutine with // delay == -1 which will disable all previously set timers set_blockingmode_timer(delay, true); @@ -91,12 +92,13 @@ int api_dns_status(struct mg_connection *conn) } else { - cJSON_Delete(obj); + free(status); return send_json_error(conn, 400, "bad_request", - "Invalid \"action\" requested", - NULL); + "Invalid \"status\" requested", + json); } + free(status); JSON_SEND_OBJECT(json); } else diff --git a/src/api/routes.c b/src/api/routes.c index 9ddff8ecd..d30a1327f 100644 --- a/src/api/routes.c +++ b/src/api/routes.c @@ -25,9 +25,9 @@ int api_handler(struct mg_connection *conn, void *ignored) const struct mg_request_info *request = mg_get_request_info(conn); /******************************** api/dns ********************************/ - if(startsWith("/api/dns/status", request->local_uri)) + if(startsWith("/api/dns/blocking", request->local_uri)) { - ret = api_dns_status(conn); + ret = api_dns_blockingstatus(conn); } else if(startsWith("/api/dns/cacheinfo", request->local_uri)) { diff --git a/src/api/routes.h b/src/api/routes.h index 3f8406e63..c30aa4199 100644 --- a/src/api/routes.h +++ b/src/api/routes.h @@ -41,7 +41,7 @@ int api_ftl_dnsmasq_log(struct mg_connection *conn); int api_ftl_network(struct mg_connection *conn); // DNS methods -int api_dns_status(struct mg_connection *conn); +int api_dns_blockingstatus(struct mg_connection *conn); int api_dns_cacheinfo(struct mg_connection *conn); // White-/Blacklist methods From be3b4045ae1604a3da5429eab7685bbf1a4538ea Mon Sep 17 00:00:00 2001 From: DL6ER Date: Wed, 17 Jun 2020 00:44:44 +0200 Subject: [PATCH 0168/1669] Add filtering for domainlist endpoints Signed-off-by: DL6ER --- src/api/dns.c | 2 +- src/api/list.c | 12 +++++++++++- src/database/gravity-db.c | 19 +++++++++++++++++-- src/database/gravity-db.h | 7 ++++--- 4 files changed, 33 insertions(+), 7 deletions(-) diff --git a/src/api/dns.c b/src/api/dns.c index 16edae85c..968e3fed4 100644 --- a/src/api/dns.c +++ b/src/api/dns.c @@ -59,7 +59,7 @@ int api_dns_blockingstatus(struct mg_connection *conn) cJSON_Delete(obj); return send_json_error(conn, 400, "bad_request", - "No \"action\" string in body data", + "No \"status\" string in body data", NULL); } char *status = strdup(elem1->valuestring); diff --git a/src/api/list.c b/src/api/list.c index 0093dce4d..169bc834c 100644 --- a/src/api/list.c +++ b/src/api/list.c @@ -30,9 +30,19 @@ static int getTableType(bool whitelist, bool exact) static int api_dns_domainlist_read(struct mg_connection *conn, bool exact, bool whitelist) { + const struct mg_request_info *request = mg_get_request_info(conn); + + char domain_filter[1024] = { '\0' }; + // Advance one character to strip "/" + const char *encoded_uri = strrchr(request->local_uri, '/')+1u; + logg("'%s'", encoded_uri); + // Decode URL (necessary for regular expressions, harmless for domains) + if(strlen(encoded_uri) != 0 && strcmp(encoded_uri, "exact") != 0 && strcmp(encoded_uri, "regex") != 0) + mg_url_decode(encoded_uri, strlen(encoded_uri), domain_filter, sizeof(domain_filter)-1u, 0); + int type = getTableType(whitelist, exact); const char *sql_msg = NULL; - if(!gravityDB_readTable(type, &sql_msg)) + if(!gravityDB_readTable(type, domain_filter, &sql_msg)) { cJSON *json = JSON_NEW_OBJ(); diff --git a/src/database/gravity-db.c b/src/database/gravity-db.c index 01f0b331a..53709ca8c 100644 --- a/src/database/gravity-db.c +++ b/src/database/gravity-db.c @@ -1086,7 +1086,7 @@ bool gravityDB_delFromTable(const int type, const char* domain, const char **mes } static sqlite3_stmt* read_stmt = NULL; -bool gravityDB_readTable(const int type, const char **message) +bool gravityDB_readTable(const int type, const char *domain, const char **message) { if(gravity_db == NULL) { @@ -1095,7 +1095,11 @@ bool gravityDB_readTable(const int type, const char **message) } // Prepare SQLite statement - const char *querystr = "SELECT domain,enabled,date_added,date_modified,comment FROM domainlist WHERE type = ?;"; + const char *querystr; + if(domain[0] == '\0') + querystr = "SELECT domain,enabled,date_added,date_modified,comment FROM domainlist WHERE type = ?;"; + else + querystr = "SELECT domain,enabled,date_added,date_modified,comment FROM domainlist WHERE type = ? AND domain = ?;"; int rc = sqlite3_prepare_v2(gravity_db, querystr, -1, &read_stmt, NULL); if( rc != SQLITE_OK ){ *message = sqlite3_errmsg(gravity_db); @@ -1106,6 +1110,17 @@ bool gravityDB_readTable(const int type, const char **message) // Bind domain type to prepared statement if((rc = sqlite3_bind_int(read_stmt, 1, type)) != SQLITE_OK) + { + *message = sqlite3_errmsg(gravity_db); + logg("gravityDB_readTable(%d): Failed to bind type (error %d) - %s", + type, rc, *message); + sqlite3_reset(read_stmt); + sqlite3_finalize(read_stmt); + return false; + } + + // Bind domain to prepared statement + if(domain[0] != '\0' && (rc = sqlite3_bind_text(read_stmt, 2, domain, -1, SQLITE_STATIC)) != SQLITE_OK) { *message = sqlite3_errmsg(gravity_db); logg("gravityDB_readTable(%d): Failed to bind domain (error %d) - %s", diff --git a/src/database/gravity-db.h b/src/database/gravity-db.h index 811fde80c..27a16cc49 100644 --- a/src/database/gravity-db.h +++ b/src/database/gravity-db.h @@ -46,12 +46,13 @@ bool in_blacklist(const char *domain, const int clientID, clientsData* client); bool gravityDB_get_regex_client_groups(clientsData* client, const int numregex, const int *regexid, const unsigned char type, const char* table, const int clientID); + +bool gravityDB_readTable(const int type, const char *domain, const char **message); +bool gravityDB_readTableGetDomain(domainrecord *domain, const char **message); +void gravityDB_readTableFinalize(void); bool gravityDB_addToTable(const int type, const char *domain, const bool enabled, const char *comment, const char **message); bool gravityDB_delFromTable(const int type, const char* domain, const char **message); -bool gravityDB_readTable(const int type, const char **message); -bool gravityDB_readTableGetDomain(domainrecord *domain, const char **message); -void gravityDB_readTableFinalize(void); #endif //GRAVITY_H From fa5f6cdcdff22b1bcb974682f50fef2405f8c5e3 Mon Sep 17 00:00:00 2001 From: DL6ER Date: Wed, 17 Jun 2020 18:43:10 +0200 Subject: [PATCH 0169/1669] Use booleans instead of string for controlling the blocking status. Return the GET response after a successful PATCH request. Signed-off-by: DL6ER --- src/api/dns.c | 171 ++++++++++++++++++++---------------- src/timers.c | 14 ++- src/timers.h | 1 + src/webserver/http-common.c | 12 +-- src/webserver/http-common.h | 2 +- src/webserver/json_macros.h | 2 +- src/webserver/ph7.c | 4 +- 7 files changed, 113 insertions(+), 93 deletions(-) diff --git a/src/api/dns.c b/src/api/dns.c index 968e3fed4..bc6fc38ba 100644 --- a/src/api/dns.c +++ b/src/api/dns.c @@ -18,88 +18,109 @@ // set_blockingmode_timer() #include "../timers.h" +static int get_blocking(struct mg_connection *conn) +{ + // Return current status + cJSON *json = JSON_NEW_OBJ(); + const bool blocking = get_blockingstatus(); + JSON_OBJ_ADD_BOOL(json, "blocking", blocking); + + // Get timer information (if applicable) + int delay; + bool target_status; + get_blockingmode_timer(&delay, &target_status); + if(delay > -1) + { + cJSON *timer = JSON_NEW_OBJ(); + JSON_OBJ_ADD_NUMBER(timer, "delay", delay); + JSON_OBJ_ADD_BOOL(timer, "blocking_target", target_status); + JSON_OBJ_ADD_ITEM(json, "timer", timer); + } + else + { + JSON_OBJ_ADD_NULL(json, "timer"); + } + + // Send object (HTTP 200 OK) + JSON_SEND_OBJECT(json); +} + +static int set_blocking(struct mg_connection *conn) +{ + // Verify requesting client is allowed to access this ressource + if(check_client_auth(conn) < 0) + { + return send_json_unauthorized(conn); + } + + char buffer[1024]; + const int data_len = mg_read(conn, buffer, sizeof(buffer) - 1); + if ((data_len < 1) || (data_len >= (int)sizeof(buffer))) { + return send_json_error(conn, 400, + "bad_request", "No request body data", + NULL); + } + buffer[data_len] = '\0'; + + cJSON *obj = cJSON_Parse(buffer); + if (obj == NULL) { + return send_json_error(conn, 400, + "bad_request", + "Invalid request body data", + NULL); + } + + cJSON *elem = cJSON_GetObjectItemCaseSensitive(obj, "blocking"); + if (!cJSON_IsBool(elem)) { + cJSON_Delete(obj); + return send_json_error(conn, 400, + "bad_request", + "No \"blocking\" boolean in body data", + NULL); + } + const bool target_status = cJSON_IsTrue(elem); + + // Get (optional) delay + int delay = -1; + elem = cJSON_GetObjectItemCaseSensitive(obj, "delay"); + if (cJSON_IsNumber(elem) && elem->valuedouble > 0.0) + delay = elem->valueint; + + // Free memory not needed any longer + cJSON_Delete(obj); + + if(target_status == get_blockingstatus()) + { + // The blocking status does not need to be changed + + // If delay is absent (or -1), we delete a possibly running timer + if(delay < 0) + set_blockingmode_timer(-1, true); + } + else + { + // Activate requested status + set_blockingstatus(target_status); + + // Start timer (-1 disables all running timers) + set_blockingmode_timer(delay, !target_status); + } + + // Return GET property as result of POST/PUT/PATCH action + // if no error happened above + return get_blocking(conn); +} + int api_dns_blockingstatus(struct mg_connection *conn) { int method = http_method(conn); if(method == HTTP_GET) { - // Return current status - cJSON *json = JSON_NEW_OBJ(); - const char *action = get_blockingstatus() ? "active" : "inactive"; - JSON_OBJ_REF_STR(json, "status", action); - JSON_SEND_OBJECT(json); + return get_blocking(conn); } - else if(method == HTTP_POST) + else if(method == HTTP_PATCH) { - // Verify requesting client is allowed to access this ressource - if(check_client_auth(conn) < 0) - { - return send_json_unauthorized(conn); - } - - char buffer[1024]; - int data_len = mg_read(conn, buffer, sizeof(buffer) - 1); - if ((data_len < 1) || (data_len >= (int)sizeof(buffer))) { - return send_json_error(conn, 400, - "bad_request", "No request body data", - NULL); - } - buffer[data_len] = '\0'; - - cJSON *obj = cJSON_Parse(buffer); - if (obj == NULL) { - return send_json_error(conn, 400, - "bad_request", - "Invalid request body data", - NULL); - } - - cJSON *elem1 = cJSON_GetObjectItemCaseSensitive(obj, "status"); - if (!cJSON_IsString(elem1)) { - cJSON_Delete(obj); - return send_json_error(conn, 400, - "bad_request", - "No \"status\" string in body data", - NULL); - } - char *status = strdup(elem1->valuestring); - - unsigned int delay = -1; - cJSON *elem2 = cJSON_GetObjectItemCaseSensitive(obj, "delay"); - if (cJSON_IsNumber(elem2) && elem2->valuedouble > 0.0) - { - delay = elem2->valueint; - } - - cJSON_Delete(obj); - cJSON *json = JSON_NEW_OBJ(); - JSON_OBJ_COPY_STR(json, "status", status); - - // Execute requested status - if(strcmp(status, "active") == 0) - { - // If no "time" key was present, we call this subroutine with - // delay == -1 which will disable all previously set timers - set_blockingmode_timer(delay, false); - set_blockingstatus(true); - } - else if(strcmp(status, "inactive") == 0) - { - // If no "time" key was present, we call this subroutine with - // delay == -1 which will disable all previously set timers - set_blockingmode_timer(delay, true); - set_blockingstatus(false); - } - else - { - free(status); - return send_json_error(conn, 400, - "bad_request", - "Invalid \"status\" requested", - json); - } - free(status); - JSON_SEND_OBJECT(json); + return set_blocking(conn); } else { diff --git a/src/timers.c b/src/timers.c index 2f5ec9b1a..431f9792d 100644 --- a/src/timers.c +++ b/src/timers.c @@ -50,12 +50,18 @@ void sleepms(const int milliseconds) } static int timer_delay = -1; -static bool timer_targer_state; +static bool timer_target_status; -void set_blockingmode_timer(int delay, bool blocked) +void set_blockingmode_timer(int delay, bool target_status) { timer_delay = delay; - timer_targer_state = blocked; + timer_target_status = target_status; +} + +void get_blockingmode_timer(int *delay, bool *target_status) +{ + *delay = timer_delay; + *target_status = timer_target_status; } void *timer(void *val) @@ -74,7 +80,7 @@ void *timer(void *val) } else if(timer_delay == 0) { - set_blockingstatus(timer_targer_state); + set_blockingstatus(timer_target_status); timer_delay = -1; } sleepms(1000); diff --git a/src/timers.h b/src/timers.h index 8656917eb..940821846 100644 --- a/src/timers.h +++ b/src/timers.h @@ -19,6 +19,7 @@ void timer_start(const int i); double timer_elapsed_msec(const int i); void sleepms(const int milliseconds); void set_blockingmode_timer(int delay, bool blocked); +void get_blockingmode_timer(int *delay, bool *target_status); void *timer(void *val); #endif //TIMERS_H diff --git a/src/webserver/http-common.c b/src/webserver/http-common.c index 8605385cc..349d9ae5e 100644 --- a/src/webserver/http-common.c +++ b/src/webserver/http-common.c @@ -175,23 +175,15 @@ int http_method(struct mg_connection *conn) { const struct mg_request_info *request = mg_get_request_info(conn); if(strcmp(request->request_method, "GET") == 0) - { return HTTP_GET; - } else if(strcmp(request->request_method, "DELETE") == 0) - { return HTTP_DELETE; - } else if(strcmp(request->request_method, "PUT") == 0) - { return HTTP_PUT; - } else if(strcmp(request->request_method, "POST") == 0) - { return HTTP_POST; - } + else if(strcmp(request->request_method, "PATCH") == 0) + return HTTP_PATCH; else - { return HTTP_UNKNOWN; - } } diff --git a/src/webserver/http-common.h b/src/webserver/http-common.h index 6913bfea7..9f62ab0fd 100644 --- a/src/webserver/http-common.h +++ b/src/webserver/http-common.h @@ -45,7 +45,7 @@ int get_int_var(const char *source, const char *var); #define GET_VAR(variable, destination, source) mg_get_var(source, strlen(source), variable, destination, sizeof(destination)) // Method routines -enum { HTTP_UNKNOWN, HTTP_GET, HTTP_POST, HTTP_PUT, HTTP_DELETE }; +enum { HTTP_UNKNOWN, HTTP_GET, HTTP_POST, HTTP_PUT, HTTP_PATCH, HTTP_DELETE }; int http_method(struct mg_connection *conn); // Utils diff --git a/src/webserver/json_macros.h b/src/webserver/json_macros.h index c659aa8d2..de6da75d8 100644 --- a/src/webserver/json_macros.h +++ b/src/webserver/json_macros.h @@ -80,7 +80,7 @@ } #define JSON_OBJ_ADD_BOOL(object, key, value) {\ - cJSON *bool_item = cJSON_CreateBool(value ? cJSON_True : cJSON_False); \ + cJSON *bool_item = cJSON_CreateBool((cJSON_bool)value); \ if(bool_item == NULL) \ { \ cJSON_Delete(object); \ diff --git a/src/webserver/ph7.c b/src/webserver/ph7.c index 2246519e5..2e284936f 100644 --- a/src/webserver/ph7.c +++ b/src/webserver/ph7.c @@ -60,9 +60,9 @@ int ph7_handler(struct mg_connection *conn, void *cbdata) } char full_path[buffer_len]; - strncpy(full_path, httpsettings.webroot, webroot_len); + memcpy(full_path, httpsettings.webroot, webroot_len); full_path[webroot_len] = '/'; - strncpy(full_path + webroot_len + 1u, local_uri, local_uri_len); + memcpy(full_path + webroot_len + 1u, local_uri, local_uri_len); full_path[webroot_len + local_uri_len + 1u] = '\0'; if(append_index) { From 784b8ba3c98117f43980ee5981549209f878e172 Mon Sep 17 00:00:00 2001 From: DL6ER Date: Wed, 17 Jun 2020 18:55:40 +0200 Subject: [PATCH 0170/1669] Differentiate between POST (error on existing) and PUT, PATCH (replace if existing) for domain lists. Also return GET style reponse on success. Signed-off-by: DL6ER --- src/api/list.c | 124 ++++++++++++++++++------------------ src/database/gravity-db.c | 13 +++- src/database/gravity-db.h | 4 +- src/webserver/http-common.h | 2 +- 4 files changed, 78 insertions(+), 65 deletions(-) diff --git a/src/api/list.c b/src/api/list.c index 169bc834c..f542bbd85 100644 --- a/src/api/list.c +++ b/src/api/list.c @@ -28,24 +28,16 @@ static int getTableType(bool whitelist, bool exact) return GRAVITY_DOMAINLIST_REGEX_BLACKLIST; } -static int api_dns_domainlist_read(struct mg_connection *conn, bool exact, bool whitelist) +static int get_domainlist(struct mg_connection *conn, const int code, const int type, const char *domain_filter) { - const struct mg_request_info *request = mg_get_request_info(conn); - - char domain_filter[1024] = { '\0' }; - // Advance one character to strip "/" - const char *encoded_uri = strrchr(request->local_uri, '/')+1u; - logg("'%s'", encoded_uri); - // Decode URL (necessary for regular expressions, harmless for domains) - if(strlen(encoded_uri) != 0 && strcmp(encoded_uri, "exact") != 0 && strcmp(encoded_uri, "regex") != 0) - mg_url_decode(encoded_uri, strlen(encoded_uri), domain_filter, sizeof(domain_filter)-1u, 0); - - int type = getTableType(whitelist, exact); const char *sql_msg = NULL; if(!gravityDB_readTable(type, domain_filter, &sql_msg)) { cJSON *json = JSON_NEW_OBJ(); + // Add domain_filter (may be NULL = not available) + JSON_OBJ_REF_STR(json, "domain_filter", domain_filter); + // Add SQL message (may be NULL = not available) if (sql_msg != NULL) { JSON_OBJ_REF_STR(json, "sql_msg", sql_msg); @@ -75,14 +67,17 @@ static int api_dns_domainlist_read(struct mg_connection *conn, bool exact, bool if(sql_msg == NULL) { - // No error - JSON_SEND_OBJECT(json); + // No error, send requested HTTP code + JSON_SEND_OBJECT_CODE(json, code); } else { JSON_DELETE(json); json = JSON_NEW_OBJ(); + // Add domain_filter (may be NULL = not available) + JSON_OBJ_REF_STR(json, "domain_filter", domain_filter); + // Add SQL message (may be NULL = not available) if (sql_msg != NULL) { JSON_OBJ_REF_STR(json, "sql_msg", sql_msg); @@ -97,10 +92,28 @@ static int api_dns_domainlist_read(struct mg_connection *conn, bool exact, bool } } -static int api_dns_domainlist_POST(struct mg_connection *conn, - bool exact, - bool whitelist) +static int api_dns_domainlist_read(struct mg_connection *conn, bool exact, bool whitelist) { + // Extract domain from path (option for GET) + const struct mg_request_info *request = mg_get_request_info(conn); + char domain_filter[1024]; + // Advance one character to strip "/" + const char *encoded_uri = strrchr(request->local_uri, '/')+1u; + // Decode URL (necessary for regular expressions, harmless for domains) + if(strlen(encoded_uri) != 0 && strcmp(encoded_uri, "exact") != 0 && strcmp(encoded_uri, "regex") != 0) + mg_url_decode(encoded_uri, strlen(encoded_uri), domain_filter, sizeof(domain_filter), 0); + + const int type = getTableType(whitelist, exact); + // Send GET style reply with code 200 OK + return get_domainlist(conn, 200, type, domain_filter); +} + +static int api_dns_domainlist_write(struct mg_connection *conn, + bool exact, + bool whitelist, + const enum http_method method) +{ + // Extract payload char buffer[1024]; int data_len = mg_read(conn, buffer, sizeof(buffer) - 1); if ((data_len < 1) || (data_len >= (int)sizeof(buffer))) { @@ -118,57 +131,45 @@ static int api_dns_domainlist_POST(struct mg_connection *conn, NULL); } - cJSON *elem = cJSON_GetObjectItemCaseSensitive(obj, "domain"); - if (!cJSON_IsString(elem)) { + cJSON *elem_domain = cJSON_GetObjectItemCaseSensitive(obj, "domain"); + if (!cJSON_IsString(elem_domain)) { cJSON_Delete(obj); return send_json_error(conn, 400, "bad_request", "No \"domain\" string in body data", NULL); } - char *domain = strdup(elem->valuestring); + size_t len = strlen(elem_domain->valuestring); + char domain[len]; + strcpy(domain, elem_domain->valuestring); bool enabled = true; - elem = cJSON_GetObjectItemCaseSensitive(obj, "enabled"); - if (cJSON_IsBool(elem)) { - enabled = elem->type == cJSON_True; + cJSON *elem_enabled = cJSON_GetObjectItemCaseSensitive(obj, "enabled"); + if (cJSON_IsBool(elem_enabled)) { + enabled = cJSON_IsTrue(elem_enabled); } char *comment = NULL; - elem = cJSON_GetObjectItemCaseSensitive(obj, "comment"); - if (cJSON_IsString(elem)) { - comment = strdup(elem->valuestring); + cJSON *elem_comment = cJSON_GetObjectItemCaseSensitive(obj, "comment"); + if (cJSON_IsString(elem_comment)) { + comment = elem_comment->valuestring; } - cJSON_Delete(obj); - cJSON *json = JSON_NEW_OBJ(); - int type = getTableType(whitelist, exact); + // Try to add domain to table const char *sql_msg = NULL; - if(gravityDB_addToTable(type, domain, enabled, comment, &sql_msg)) + int type = getTableType(whitelist, exact); + if(gravityDB_addToTable(type, domain, enabled, comment, &sql_msg, method)) { - // Add domain - JSON_OBJ_COPY_STR(json, "domain", domain); - free(domain); - - // Add enabled boolean - JSON_OBJ_ADD_BOOL(json, "enabled", enabled); - - // Add comment (may be NULL) - if (comment != NULL) { - JSON_OBJ_COPY_STR(json, "comment", comment); - free(comment); - } else { - JSON_OBJ_ADD_NULL(json, "comment"); - } - - // Send success reply - JSON_SEND_OBJECT_CODE(json, 201); // 201 Created + cJSON_Delete(obj); + // Send GET style reply with code 201 Created + return get_domainlist(conn, 201, type, domain); } else { + // Error adding domain, prepare error object // Add domain + cJSON *json = JSON_NEW_OBJ(); JSON_OBJ_COPY_STR(json, "domain", domain); - free(domain); // Add enabled boolean JSON_OBJ_ADD_BOOL(json, "enabled", enabled); @@ -176,11 +177,13 @@ static int api_dns_domainlist_POST(struct mg_connection *conn, // Add comment (may be NULL) if (comment != NULL) { JSON_OBJ_COPY_STR(json, "comment", comment); - free(comment); } else { JSON_OBJ_ADD_NULL(json, "comment"); } + // Only delete payload object after having extracted the data + cJSON_Delete(obj); + // Add SQL message (may be NULL = not available) if (sql_msg != NULL) { JSON_OBJ_REF_STR(json, "sql_msg", sql_msg); @@ -196,9 +199,9 @@ static int api_dns_domainlist_POST(struct mg_connection *conn, } } -static int api_dns_domainlist_DELETE(struct mg_connection *conn, - bool exact, - bool whitelist) +static int api_dns_domainlist_remove(struct mg_connection *conn, + bool exact, + bool whitelist) { const struct mg_request_info *request = mg_get_request_info(conn); @@ -208,14 +211,13 @@ static int api_dns_domainlist_DELETE(struct mg_connection *conn, // Decode URL (necessary for regular expressions, harmless for domains) mg_url_decode(encoded_uri, strlen(encoded_uri), domain, sizeof(domain)-1u, 0); - cJSON *json = JSON_NEW_OBJ(); - int type = getTableType(whitelist, exact); + cJSON *json = JSON_NEW_OBJ(); const char *sql_msg = NULL; + int type = getTableType(whitelist, exact); if(gravityDB_delFromTable(type, domain, &sql_msg)) { - JSON_OBJ_REF_STR(json, "key", "removed"); - JSON_OBJ_REF_STR(json, "domain", domain); - JSON_SEND_OBJECT_CODE(json, 200); // 200 OK + // Send empty reply with code 204 No Content + JSON_SEND_OBJECT_CODE(json, 204); } else { @@ -245,22 +247,22 @@ int api_dns_domainlist(struct mg_connection *conn, bool exact, bool whitelist) return send_json_unauthorized(conn); } - int method = http_method(conn); + const enum http_method method = http_method(conn); if(method == HTTP_GET) { return api_dns_domainlist_read(conn, exact, whitelist); } - else if(method == HTTP_PUT) + else if(method == HTTP_POST || method == HTTP_PUT || method == HTTP_PATCH) { // Add domain from exact white-/blacklist when a user sends // the request to the general address /api/dns/{white,black}list - return api_dns_domainlist_POST(conn, exact, whitelist); + return api_dns_domainlist_write(conn, exact, whitelist, method); } else if(method == HTTP_DELETE) { // Delete domain from exact white-/blacklist when a user sends // the request to the general address /api/dns/{white,black}list - return api_dns_domainlist_DELETE(conn, exact, whitelist); + return api_dns_domainlist_remove(conn, exact, whitelist); } else { diff --git a/src/database/gravity-db.c b/src/database/gravity-db.c index 53709ca8c..2df1bf3f6 100644 --- a/src/database/gravity-db.c +++ b/src/database/gravity-db.c @@ -942,7 +942,7 @@ bool gravityDB_get_regex_client_groups(clientsData* client, const int numregex, bool gravityDB_addToTable(const int type, const char *domain, const bool enabled, const char *comment, - const char **message) + const char **message, const enum http_method method) { if(gravity_db == NULL) { @@ -952,7 +952,16 @@ bool gravityDB_addToTable(const int type, const char *domain, // Prepare SQLite statement sqlite3_stmt* stmt = NULL; - const char *querystr = "INSERT INTO domainlist (domain,type,enabled,comment) VALUES (?,?,?,?);"; + const char *querystr; + if(method == HTTP_POST) // Create NEW entry, error if existing + querystr = "INSERT INTO domainlist (domain,type,enabled,comment) VALUES (?1,?2,?3,?4);"; + else // Create new or replace existing entry, no error if existing + // We have to use a subquery here to avoid violating FOREIGN KEY + // contraints (REPLACE recreates (= new ID) entries instead of updatinging them) + querystr = "REPLACE INTO domainlist (domain,type,enabled,comment,id,date_added) " + "VALUES (?1,?2,?3,?4," + "(SELECT id FROM domainlist WHERE domain = ?1 and type = ?2)," + "(SELECT date_added FROM domainlist WHERE domain = ?1 and type = ?2));"; int rc = sqlite3_prepare_v2(gravity_db, querystr, -1, &stmt, NULL); if( rc != SQLITE_OK ) { diff --git a/src/database/gravity-db.h b/src/database/gravity-db.h index 27a16cc49..3fa0e8501 100644 --- a/src/database/gravity-db.h +++ b/src/database/gravity-db.h @@ -14,6 +14,8 @@ #include "memory.h" // clients data structure #include "datastructure.h" +// enum http_method +#include "webserver/http-common.h" // Table indices enum { GRAVITY_TABLE, EXACT_BLACKLIST_TABLE, EXACT_WHITELIST_TABLE, REGEX_BLACKLIST_TABLE, REGEX_WHITELIST_TABLE, UNKNOWN_TABLE }; @@ -52,7 +54,7 @@ bool gravityDB_readTableGetDomain(domainrecord *domain, const char **message); void gravityDB_readTableFinalize(void); bool gravityDB_addToTable(const int type, const char *domain, const bool enabled, const char *comment, - const char **message); + const char **message, const enum http_method method); bool gravityDB_delFromTable(const int type, const char* domain, const char **message); #endif //GRAVITY_H diff --git a/src/webserver/http-common.h b/src/webserver/http-common.h index 9f62ab0fd..e41f68c32 100644 --- a/src/webserver/http-common.h +++ b/src/webserver/http-common.h @@ -45,7 +45,7 @@ int get_int_var(const char *source, const char *var); #define GET_VAR(variable, destination, source) mg_get_var(source, strlen(source), variable, destination, sizeof(destination)) // Method routines -enum { HTTP_UNKNOWN, HTTP_GET, HTTP_POST, HTTP_PUT, HTTP_PATCH, HTTP_DELETE }; +enum http_method { HTTP_UNKNOWN, HTTP_GET, HTTP_POST, HTTP_PUT, HTTP_PATCH, HTTP_DELETE }; int http_method(struct mg_connection *conn); // Utils From e018146faa518684cfd9926e9c30c6538fcf137b Mon Sep 17 00:00:00 2001 From: DL6ER Date: Wed, 17 Jun 2020 19:05:31 +0200 Subject: [PATCH 0171/1669] Rename /api/ftl/clientIP to /api/ftl/client as we return more data than just the IP address (this is mostly a testing endpoint) Signed-off-by: DL6ER --- src/api/ftl.c | 25 +++++++++++++++++++++++-- src/api/routes.c | 4 ++-- src/api/routes.h | 2 +- 3 files changed, 26 insertions(+), 5 deletions(-) diff --git a/src/api/ftl.c b/src/api/ftl.c index a3b208d9a..80458f101 100644 --- a/src/api/ftl.c +++ b/src/api/ftl.c @@ -25,11 +25,32 @@ // networkrecord #include "../database/network-table.h" -int api_ftl_clientIP(struct mg_connection *conn) +int api_ftl_client(struct mg_connection *conn) { cJSON *json = JSON_NEW_OBJ(); const struct mg_request_info *request = mg_get_request_info(conn); - JSON_OBJ_REF_STR(json,"remote_addr", request->remote_addr); + + // Add client's IP address + JSON_OBJ_REF_STR(json, "remote_addr", request->remote_addr); + + // Add HTTP version + JSON_OBJ_REF_STR(json, "http_version", request->http_version); + + // Add request method + JSON_OBJ_REF_STR(json, "method", request->request_method); + + // Add HTTP headers + cJSON *headers = JSON_NEW_ARRAY(); + for(int i = 0; i < request->num_headers; i++) + { + // Add headers + cJSON *header = JSON_NEW_OBJ(); + JSON_OBJ_REF_STR(header, "name", request->http_headers[i].name); + JSON_OBJ_REF_STR(header, "value", request->http_headers[i].value); + JSON_ARRAY_ADD_ITEM(headers, header); + } + JSON_OBJ_ADD_ITEM(json, "headers", headers); + JSON_SEND_OBJECT(json); } diff --git a/src/api/routes.c b/src/api/routes.c index d30a1327f..613e72d10 100644 --- a/src/api/routes.c +++ b/src/api/routes.c @@ -52,9 +52,9 @@ int api_handler(struct mg_connection *conn, void *ignored) ret = api_dns_domainlist(conn, false, false); } /******************************** api/ftl ****************************/ - else if(startsWith("/api/ftl/clientIP", request->local_uri)) + else if(startsWith("/api/ftl/client", request->local_uri)) { - ret = api_ftl_clientIP(conn); + ret = api_ftl_client(conn); } else if(startsWith("/api/ftl/dnsmasq_log", request->local_uri)) { diff --git a/src/api/routes.h b/src/api/routes.h index c30aa4199..e9a73668d 100644 --- a/src/api/routes.h +++ b/src/api/routes.h @@ -36,7 +36,7 @@ int api_stats_database_query_types(struct mg_connection *conn); int api_stats_database_upstreams(struct mg_connection *conn); // FTL methods -int api_ftl_clientIP(struct mg_connection *conn); +int api_ftl_client(struct mg_connection *conn); int api_ftl_dnsmasq_log(struct mg_connection *conn); int api_ftl_network(struct mg_connection *conn); From e10d7fe27e10b0ba01ee7698d0b3b5c8adba3d29 Mon Sep 17 00:00:00 2001 From: DL6ER Date: Wed, 17 Jun 2020 19:06:28 +0200 Subject: [PATCH 0172/1669] Avoid buffer overflow. Signed-off-by: DL6ER --- src/api/ftl.c | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/api/ftl.c b/src/api/ftl.c index 80458f101..30d48d73d 100644 --- a/src/api/ftl.c +++ b/src/api/ftl.c @@ -134,16 +134,17 @@ void add_to_dnsmasq_log_fifo_buffer(const char *payload, const int length) } // Copy relevant string into temporary buffer - memcpy(fifo_log->message[idx], payload, length); + size_t copybytes = length < MAX_MESSAGE ? length : MAX_MESSAGE; + memcpy(fifo_log->message[idx], payload, copybytes); // Zero-terminate buffer, truncate newline if found - if(fifo_log->message[idx][length - 1u] == '\n') + if(fifo_log->message[idx][copybytes - 1u] == '\n') { - fifo_log->message[idx][length - 1u] = '\0'; + fifo_log->message[idx][copybytes - 1u] = '\0'; } else { - fifo_log->message[idx][length] = '\0'; + fifo_log->message[idx][copybytes] = '\0'; } // Set timestamp @@ -166,9 +167,9 @@ int api_ftl_network(struct mg_connection *conn) { cJSON *json = JSON_NEW_OBJ(); return send_json_error(conn, 500, - "database_error", - "Could not read network details from database table", - json); + "database_error", + "Could not read network details from database table", + json); } // Read record for a single device @@ -192,9 +193,8 @@ int api_ftl_network(struct mg_connection *conn) // Only walk known IP addresses when SELECT query succeeded const char *ipaddr; while((ipaddr = networkTable_readIPsGetRecord()) != NULL) - { JSON_ARRAY_COPY_STR(ip, ipaddr); - } + networkTable_readIPsFinalize(); } JSON_OBJ_ADD_ITEM(item, "ip", ip); From 3906490958d90e8c91a287915a951928f8d0a391 Mon Sep 17 00:00:00 2001 From: DL6ER Date: Wed, 17 Jun 2020 19:10:08 +0200 Subject: [PATCH 0173/1669] Return SQL error to user if sourcing data from the network table fails Signed-off-by: DL6ER --- src/api/ftl.c | 26 +++++++++++++++++++++++--- src/database/network-table.c | 10 ++++++---- src/database/network-table.h | 4 ++-- 3 files changed, 31 insertions(+), 9 deletions(-) diff --git a/src/api/ftl.c b/src/api/ftl.c index 30d48d73d..46072aea7 100644 --- a/src/api/ftl.c +++ b/src/api/ftl.c @@ -163,9 +163,16 @@ int api_ftl_network(struct mg_connection *conn) } // Connect to database - if(!networkTable_readDevices()) + const char *sql_msg = NULL; + if(!networkTable_readDevices(&sql_msg)) { + // Add SQL message (may be NULL = not available) cJSON *json = JSON_NEW_OBJ(); + if (sql_msg != NULL) { + JSON_OBJ_REF_STR(json, "sql_msg", sql_msg); + } else { + JSON_OBJ_ADD_NULL(json, "sql_msg"); + } return send_json_error(conn, 500, "database_error", "Could not read network details from database table", @@ -173,9 +180,9 @@ int api_ftl_network(struct mg_connection *conn) } // Read record for a single device - networkrecord network; cJSON *json = JSON_NEW_ARRAY(); - while(networkTable_readDevicesGetRecord(&network)) + networkrecord network; + while(networkTable_readDevicesGetRecord(&network, &sql_msg)) { cJSON *item = JSON_NEW_OBJ(); JSON_OBJ_COPY_STR(item, "hwaddr", network.hwaddr); @@ -201,6 +208,19 @@ int api_ftl_network(struct mg_connection *conn) JSON_ARRAY_ADD_ITEM(json, item); } + + if(sql_msg != NULL) + { + // Add SQL message (may be NULL = not available) + cJSON_Delete(json); + json = JSON_NEW_OBJ(); + JSON_OBJ_REF_STR(json, "sql_msg", sql_msg); + return send_json_error(conn, 500, + "database_error", + "Could not read network details from database table (step)", + json); + } + networkTable_readDevicesFinalize(); JSON_SEND_OBJECT(json); diff --git a/src/database/network-table.c b/src/database/network-table.c index db0ecf192..567e44cf8 100644 --- a/src/database/network-table.c +++ b/src/database/network-table.c @@ -944,7 +944,7 @@ char* __attribute__((malloc)) getDatabaseHostname(const char* ipaddr) } static sqlite3_stmt* read_stmt = NULL; -bool networkTable_readDevices(void) +bool networkTable_readDevices(const char **message) { // Open pihole-FTL.db database file if(!dbopen()) @@ -957,15 +957,16 @@ bool networkTable_readDevices(void) const char *querystr = "SELECT id,hwaddr,interface,name,firstSeen,lastQuery,numQueries,macVendor FROM network;"; int rc = sqlite3_prepare_v2(FTL_db, querystr, -1, &read_stmt, NULL); if( rc != SQLITE_OK ){ + *message = sqlite3_errmsg(FTL_db); logg("networkTable_readDevices() - SQL error prepare (%i): %s", - rc, sqlite3_errmsg(FTL_db)); + rc, *message); return false; } return true; } -bool networkTable_readDevicesGetRecord(networkrecord *network) +bool networkTable_readDevicesGetRecord(networkrecord *network, const char **message) { // Perform step const int rc = sqlite3_step(read_stmt); @@ -989,8 +990,9 @@ bool networkTable_readDevicesGetRecord(networkrecord *network) // SQLITE_DONE (we are finished reading the table) if(rc != SQLITE_DONE) { + *message = sqlite3_errmsg(FTL_db); logg("networkTable_readDevicesGetRecord() - SQL error step (%i): %s", - rc, sqlite3_errmsg(FTL_db)); + rc, *message); return false; } diff --git a/src/database/network-table.h b/src/database/network-table.h index 3c8e84b31..f41e6a891 100644 --- a/src/database/network-table.h +++ b/src/database/network-table.h @@ -28,8 +28,8 @@ typedef struct networkrecord { time_t lastQuery; } networkrecord; -bool networkTable_readDevices(void); -bool networkTable_readDevicesGetRecord(networkrecord *network); +bool networkTable_readDevices(const char **message); +bool networkTable_readDevicesGetRecord(networkrecord *network, const char **message); void networkTable_readDevicesFinalize(void); bool networkTable_readIPs(const int id); From ae035c4ba767895977813f41cdcbe4d05e19bcc7 Mon Sep 17 00:00:00 2001 From: DL6ER Date: Wed, 17 Jun 2020 19:12:22 +0200 Subject: [PATCH 0174/1669] Put FIFO log into its own unit (out of the API code) Signed-off-by: DL6ER --- src/CMakeLists.txt | 2 ++ src/api/CMakeLists.txt | 1 - src/api/ftl.c | 54 ++++++++------------------------------- src/dnsmasq_interface.c | 2 +- src/fifo.c | 49 +++++++++++++++++++++++++++++++++++ src/{api/ftl.h => fifo.h} | 12 ++++----- src/shmem.c | 2 +- 7 files changed, 69 insertions(+), 53 deletions(-) create mode 100644 src/fifo.c rename src/{api/ftl.h => fifo.h} (77%) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index fa15dd675..cd11427b7 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -114,6 +114,8 @@ set(sources datastructure.h dnsmasq_interface.c dnsmasq_interface.h + fifo.c + fifo.h files.c files.h FTL.h diff --git a/src/api/CMakeLists.txt b/src/api/CMakeLists.txt index 745915e23..9c8f1f29f 100644 --- a/src/api/CMakeLists.txt +++ b/src/api/CMakeLists.txt @@ -14,7 +14,6 @@ set(sources dns.c dns.h ftl.c - ftl.h routes.c routes.h settings.c diff --git a/src/api/ftl.c b/src/api/ftl.c index 46072aea7..234cacfd8 100644 --- a/src/api/ftl.c +++ b/src/api/ftl.c @@ -8,22 +8,21 @@ * This file is copyright under the latest version of the EUPL. * Please see LICENSE file for your rights under this license. */ -#include "FTL.h" +#include "../FTL.h" #include "../webserver/http-common.h" #include "../webserver/json_macros.h" #include "routes.h" -#include "ftl.h" -#include "datastructure.h" +#include "../datastructure.h" // get_FTL_version() -#include "log.h" +#include "../log.h" // git constants -#include "version.h" +#include "../version.h" // config struct -#include "config.h" -// {un,}lock_shm() -#include "../shmem.h" +#include "../config.h" // networkrecord #include "../database/network-table.h" +// struct fifologData +#include "../fifo.h" int api_ftl_client(struct mg_connection *conn) { @@ -54,6 +53,7 @@ int api_ftl_client(struct mg_connection *conn) JSON_SEND_OBJECT(json); } +// fifologData is allocated in shared memory for cross-fork compatibility fifologData *fifo_log = NULL; int api_ftl_dnsmasq_log(struct mg_connection *conn) { @@ -115,43 +115,9 @@ int api_ftl_dnsmasq_log(struct mg_connection *conn) } JSON_OBJ_ADD_ITEM(json, "log", log); JSON_OBJ_ADD_NUMBER(json, "nextID", fifo_log->next_id); - JSON_SEND_OBJECT(json); -} - -void add_to_dnsmasq_log_fifo_buffer(const char *payload, const int length) -{ - // Lock SHM - lock_shm(); - - unsigned int idx = fifo_log->next_id++; - if(idx >= LOG_SIZE) - { - // Log is full, move everything one slot forward to make space for a new record at the end - // This pruges the oldest message from the list (it is overwritten by the second message) - memmove(fifo_log->message[0], fifo_log->message[1], (LOG_SIZE - 1u) * MAX_MESSAGE); - memmove(&fifo_log->timestamp[0], &fifo_log->timestamp[1], (LOG_SIZE - 1u) * sizeof(time_t)); - idx = LOG_SIZE - 1u; - } - - // Copy relevant string into temporary buffer - size_t copybytes = length < MAX_MESSAGE ? length : MAX_MESSAGE; - memcpy(fifo_log->message[idx], payload, copybytes); - // Zero-terminate buffer, truncate newline if found - if(fifo_log->message[idx][copybytes - 1u] == '\n') - { - fifo_log->message[idx][copybytes - 1u] = '\0'; - } - else - { - fifo_log->message[idx][copybytes] = '\0'; - } - - // Set timestamp - fifo_log->timestamp[idx] = time(NULL); - - // Unlock SHM - unlock_shm(); + // Send data + JSON_SEND_OBJECT(json); } int api_ftl_network(struct mg_connection *conn) diff --git a/src/dnsmasq_interface.c b/src/dnsmasq_interface.c index 261272edb..7c5ef53ed 100644 --- a/src/dnsmasq_interface.c +++ b/src/dnsmasq_interface.c @@ -35,7 +35,7 @@ // http_init() #include "webserver/webserver.h" // add_to_dnsmasq_log_buffer() -#include "api/ftl.h" +#include "fifo.h" static void print_flags(const unsigned int flags); static void save_reply_type(const unsigned int flags, const union all_addr *addr, diff --git a/src/fifo.c b/src/fifo.c new file mode 100644 index 000000000..b5c034aab --- /dev/null +++ b/src/fifo.c @@ -0,0 +1,49 @@ +/* Pi-hole: A black hole for Internet advertisements +* (c) 2019 Pi-hole, LLC (https://pi-hole.net) +* Network-wide ad blocking via your own hardware. +* +* FTL Engine +* dnsmasq FIFO log for Pi-hole's API +* +* This file is copyright under the latest version of the EUPL. +* Please see LICENSE file for your rights under this license. */ + +#include "fifo.h" +// {un,}lock_shm() +#include "shmem.h" + +void add_to_dnsmasq_log_fifo_buffer(const char *payload, const int length) +{ + // Lock SHM + lock_shm(); + + unsigned int idx = fifo_log->next_id++; + if(idx >= LOG_SIZE) + { + // Log is full, move everything one slot forward to make space for a new record at the end + // This pruges the oldest message from the list (it is overwritten by the second message) + memmove(fifo_log->message[0], fifo_log->message[1], (LOG_SIZE - 1u) * MAX_MESSAGE); + memmove(&fifo_log->timestamp[0], &fifo_log->timestamp[1], (LOG_SIZE - 1u) * sizeof(time_t)); + idx = LOG_SIZE - 1u; + } + + // Copy relevant string into temporary buffer + size_t copybytes = length < MAX_MESSAGE ? length : MAX_MESSAGE; + memcpy(fifo_log->message[idx], payload, copybytes); + + // Zero-terminate buffer, truncate newline if found + if(fifo_log->message[idx][copybytes - 1u] == '\n') + { + fifo_log->message[idx][copybytes - 1u] = '\0'; + } + else + { + fifo_log->message[idx][copybytes] = '\0'; + } + + // Set timestamp + fifo_log->timestamp[idx] = time(NULL); + + // Unlock SHM + unlock_shm(); +} \ No newline at end of file diff --git a/src/api/ftl.h b/src/fifo.h similarity index 77% rename from src/api/ftl.h rename to src/fifo.h index c359be781..18ad33703 100644 --- a/src/api/ftl.h +++ b/src/fifo.h @@ -3,23 +3,23 @@ * Network-wide ad blocking via your own hardware. * * FTL Engine -* API FTL prototypes +* dnsmasq FIFO log prototypes * * This file is copyright under the latest version of the EUPL. * Please see LICENSE file for your rights under this license. */ #ifndef API_FTL_H #define API_FTL_H +#include "FTL.h" + /* From RFC 3164 */ #define MAX_MESSAGE 1024 // How many messages do we keep in memory (FIFO message buffer)? -// The memory required is the set number in kilobytes -// Defaults to 64 [uses 64 KB of memory] -#define LOG_SIZE 64 +// This number multiplied by MAX_MESSAGE (see above) gives the total buffer size +// Defaults to 128 [use 128 KB of memory for the log] +#define LOG_SIZE 128 -void init_dnsmasq_fifo_log(void); -void free_dnsmasq_fifo_log(void); void add_to_dnsmasq_log_fifo_buffer(const char *payload, const int length); typedef struct { diff --git a/src/shmem.c b/src/shmem.c index fd7e2e60f..4321ecebe 100644 --- a/src/shmem.c +++ b/src/shmem.c @@ -17,7 +17,7 @@ // data getter functions #include "datastructure.h" // fifologData -#include "api/ftl.h" +#include "fifo.h" /// The version of shared memory used #define SHARED_MEMORY_VERSION 9 From 822f8c6a280b0cde491ef534d346467dfcd7bc60 Mon Sep 17 00:00:00 2001 From: DL6ER Date: Wed, 17 Jun 2020 19:18:09 +0200 Subject: [PATCH 0175/1669] Implement proper error handling for network table IP address sub-query Signed-off-by: DL6ER --- src/api/CMakeLists.txt | 1 + src/api/ftl.c | 81 -------------------------- src/api/network.c | 110 +++++++++++++++++++++++++++++++++++ src/api/routes.c | 5 +- src/api/routes.h | 4 +- src/database/network-table.c | 43 ++++++++------ src/database/network-table.h | 28 +++++---- 7 files changed, 157 insertions(+), 115 deletions(-) create mode 100644 src/api/network.c diff --git a/src/api/CMakeLists.txt b/src/api/CMakeLists.txt index 9c8f1f29f..5257c7087 100644 --- a/src/api/CMakeLists.txt +++ b/src/api/CMakeLists.txt @@ -14,6 +14,7 @@ set(sources dns.c dns.h ftl.c + network.c routes.c routes.h settings.c diff --git a/src/api/ftl.c b/src/api/ftl.c index 234cacfd8..eae582ec1 100644 --- a/src/api/ftl.c +++ b/src/api/ftl.c @@ -12,15 +12,6 @@ #include "../webserver/http-common.h" #include "../webserver/json_macros.h" #include "routes.h" -#include "../datastructure.h" -// get_FTL_version() -#include "../log.h" -// git constants -#include "../version.h" -// config struct -#include "../config.h" -// networkrecord -#include "../database/network-table.h" // struct fifologData #include "../fifo.h" @@ -119,75 +110,3 @@ int api_ftl_dnsmasq_log(struct mg_connection *conn) // Send data JSON_SEND_OBJECT(json); } - -int api_ftl_network(struct mg_connection *conn) -{ - // Verify requesting client is allowed to see this ressource - if(check_client_auth(conn) < 0) - { - return send_json_unauthorized(conn); - } - - // Connect to database - const char *sql_msg = NULL; - if(!networkTable_readDevices(&sql_msg)) - { - // Add SQL message (may be NULL = not available) - cJSON *json = JSON_NEW_OBJ(); - if (sql_msg != NULL) { - JSON_OBJ_REF_STR(json, "sql_msg", sql_msg); - } else { - JSON_OBJ_ADD_NULL(json, "sql_msg"); - } - return send_json_error(conn, 500, - "database_error", - "Could not read network details from database table", - json); - } - - // Read record for a single device - cJSON *json = JSON_NEW_ARRAY(); - networkrecord network; - while(networkTable_readDevicesGetRecord(&network, &sql_msg)) - { - cJSON *item = JSON_NEW_OBJ(); - JSON_OBJ_COPY_STR(item, "hwaddr", network.hwaddr); - JSON_OBJ_COPY_STR(item, "interface", network.interface); - JSON_OBJ_COPY_STR(item, "name", network.name); - JSON_OBJ_ADD_NUMBER(item, "firstSeen", network.firstSeen); - JSON_OBJ_ADD_NUMBER(item, "lastQuery", network.lastQuery); - JSON_OBJ_ADD_NUMBER(item, "numQueries", network.numQueries); - JSON_OBJ_COPY_STR(item, "macVendor", network.macVendor); - - // Build array of all IP addresses known associated to this client - cJSON *ip = JSON_NEW_ARRAY(); - if(networkTable_readIPs(network.id)) - { - // Only walk known IP addresses when SELECT query succeeded - const char *ipaddr; - while((ipaddr = networkTable_readIPsGetRecord()) != NULL) - JSON_ARRAY_COPY_STR(ip, ipaddr); - - networkTable_readIPsFinalize(); - } - JSON_OBJ_ADD_ITEM(item, "ip", ip); - - JSON_ARRAY_ADD_ITEM(json, item); - } - - if(sql_msg != NULL) - { - // Add SQL message (may be NULL = not available) - cJSON_Delete(json); - json = JSON_NEW_OBJ(); - JSON_OBJ_REF_STR(json, "sql_msg", sql_msg); - return send_json_error(conn, 500, - "database_error", - "Could not read network details from database table (step)", - json); - } - - networkTable_readDevicesFinalize(); - - JSON_SEND_OBJECT(json); -} diff --git a/src/api/network.c b/src/api/network.c new file mode 100644 index 000000000..b57c7e111 --- /dev/null +++ b/src/api/network.c @@ -0,0 +1,110 @@ +/* Pi-hole: A black hole for Internet advertisements +* (c) 2019 Pi-hole, LLC (https://pi-hole.net) +* Network-wide ad blocking via your own hardware. +* +* FTL Engine +* API Implementation /api/network +* +* This file is copyright under the latest version of the EUPL. +* Please see LICENSE file for your rights under this license. */ + +#include "../FTL.h" +#include "../webserver/http-common.h" +#include "../webserver/json_macros.h" +#include "routes.h" +// networkrecord +#include "../database/network-table.h" + +int api_network(struct mg_connection *conn) +{ + // Verify requesting client is allowed to see this ressource + if(check_client_auth(conn) < 0) + { + return send_json_unauthorized(conn); + } + + // Connect to database + const char *sql_msg = NULL; + if(!networkTable_readDevices(&sql_msg)) + { + // Add SQL message (may be NULL = not available) + cJSON *json = JSON_NEW_OBJ(); + if (sql_msg != NULL) { + JSON_OBJ_REF_STR(json, "sql_msg", sql_msg); + } else { + JSON_OBJ_ADD_NULL(json, "sql_msg"); + } + return send_json_error(conn, 500, + "database_error", + "Could not read network details from database table", + json); + } + + // Read record for a single device + cJSON *json = JSON_NEW_ARRAY(); + network_record network; + while(networkTable_readDevicesGetRecord(&network, &sql_msg)) + { + cJSON *item = JSON_NEW_OBJ(); + JSON_OBJ_ADD_NUMBER(item, "id", network.id); + JSON_OBJ_COPY_STR(item, "hwaddr", network.hwaddr); + JSON_OBJ_COPY_STR(item, "interface", network.iface); + JSON_OBJ_COPY_STR(item, "name", network.name); + JSON_OBJ_ADD_NUMBER(item, "firstSeen", network.firstSeen); + JSON_OBJ_ADD_NUMBER(item, "lastQuery", network.lastQuery); + JSON_OBJ_ADD_NUMBER(item, "numQueries", network.numQueries); + JSON_OBJ_COPY_STR(item, "macVendor", network.macVendor); + + // Build array of all IP addresses known associated to this client + cJSON *ip = JSON_NEW_ARRAY(); + if(networkTable_readIPs(network.id, &sql_msg)) + { + // Walk known IP addresses + network_addresses_record network_address; + while(networkTable_readIPsGetRecord(&network_address, &sql_msg)) + JSON_ARRAY_COPY_STR(ip, network_address.ip); + + // Possible error handling + if(sql_msg != NULL) + { + cJSON_Delete(json); + json = JSON_NEW_OBJ(); + // Add item leading to the error when generating the error message + JSON_OBJ_ADD_ITEM(json, "last_item", item); + // Add SQL message + JSON_OBJ_REF_STR(json, "sql_msg", sql_msg); + return send_json_error(conn, 500, + "database_error", + "Could not read network details from database table (getting IP records)", + json); + } + + // Finalize sub-query + networkTable_readIPsFinalize(); + } + + // Add array of IP addresses to device + JSON_OBJ_ADD_ITEM(item, "ip", ip); + + // Add device to array of all devices + JSON_ARRAY_ADD_ITEM(json, item); + } + + if(sql_msg != NULL) + { + cJSON_Delete(json); + json = JSON_NEW_OBJ(); + // Add SQL message + JSON_OBJ_REF_STR(json, "sql_msg", sql_msg); + return send_json_error(conn, 500, + "database_error", + "Could not read network details from database table (step)", + json); + } + + // Finalize query + networkTable_readDevicesFinalize(); + + // Return data to user + JSON_SEND_OBJECT(json); +} diff --git a/src/api/routes.c b/src/api/routes.c index 613e72d10..e276e9a2f 100644 --- a/src/api/routes.c +++ b/src/api/routes.c @@ -60,9 +60,10 @@ int api_handler(struct mg_connection *conn, void *ignored) { ret = api_ftl_dnsmasq_log(conn); } - else if(startsWith("/api/ftl/network", request->local_uri)) + /******************************** api/network ****************************/ + else if(startsWith("/api/network", request->local_uri)) { - ret = api_ftl_network(conn); + ret = api_network(conn); } /******************************** api/stats **************************/ else if(startsWith("/api/stats/summary", request->local_uri)) diff --git a/src/api/routes.h b/src/api/routes.h index e9a73668d..2f0862f74 100644 --- a/src/api/routes.h +++ b/src/api/routes.h @@ -38,7 +38,9 @@ int api_stats_database_upstreams(struct mg_connection *conn); // FTL methods int api_ftl_client(struct mg_connection *conn); int api_ftl_dnsmasq_log(struct mg_connection *conn); -int api_ftl_network(struct mg_connection *conn); + +// Network methods +int api_network(struct mg_connection *conn); // DNS methods int api_dns_blockingstatus(struct mg_connection *conn); diff --git a/src/database/network-table.c b/src/database/network-table.c index 567e44cf8..715eb9a66 100644 --- a/src/database/network-table.c +++ b/src/database/network-table.c @@ -8,15 +8,16 @@ * This file is copyright under the latest version of the EUPL. * Please see LICENSE file for your rights under this license. */ -#include "FTL.h" -#include "database/network-table.h" -#include "database/common.h" -#include "shmem.h" -#include "memory.h" -#include "log.h" -#include "timers.h" -#include "config.h" -#include "datastructure.h" +#include "../FTL.h" +#include "network-table.h" +#include "common.h" +#include "../shmem.h" +#include "../memory.h" +#include "../log.h" +// timer_elapsed_msec() +#include "../timers.h" +#include "../config.h" +#include "../datastructure.h" // Private prototypes static char* getMACVendor(const char* hwaddr); @@ -966,7 +967,7 @@ bool networkTable_readDevices(const char **message) return true; } -bool networkTable_readDevicesGetRecord(networkrecord *network, const char **message) +bool networkTable_readDevicesGetRecord(network_record *network, const char **message) { // Perform step const int rc = sqlite3_step(read_stmt); @@ -976,7 +977,7 @@ bool networkTable_readDevicesGetRecord(networkrecord *network, const char **mess { network->id = sqlite3_column_int(read_stmt, 0); network->hwaddr = (char*)sqlite3_column_text(read_stmt, 1); - network->interface = (char*)sqlite3_column_text(read_stmt, 2); + network->iface = (char*)sqlite3_column_text(read_stmt, 2); network->name = (char*)sqlite3_column_text(read_stmt, 3); network->firstSeen = sqlite3_column_int(read_stmt, 4); network->lastQuery = sqlite3_column_int(read_stmt, 5); @@ -1011,22 +1012,24 @@ void networkTable_readDevicesFinalize(void) } static sqlite3_stmt* read_stmt_ip = NULL; -bool networkTable_readIPs(const int id) +bool networkTable_readIPs(const int id, const char **message) { // Prepare SQLite statement const char *querystr = "SELECT ip FROM network_addresses WHERE network_id = ? ORDER BY lastSeen DESC;"; int rc = sqlite3_prepare_v2(FTL_db, querystr, -1, &read_stmt_ip, NULL); if( rc != SQLITE_OK ){ + *message = sqlite3_errmsg(FTL_db); logg("networkTable_readIPs(%i) - SQL error prepare (%i): %s", - id, rc, sqlite3_errmsg(FTL_db)); + id, rc, *message); return false; } // Bind ipaddr to prepared statement if((rc = sqlite3_bind_int(read_stmt_ip, 1, id)) != SQLITE_OK) { + *message = sqlite3_errmsg(FTL_db); logg("networkTable_readIPs(%i): Failed to bind domain (error %d) - %s", - id, rc, sqlite3_errmsg(FTL_db)); + id, rc, *message); sqlite3_reset(read_stmt_ip); sqlite3_finalize(read_stmt_ip); return false; @@ -1035,7 +1038,7 @@ bool networkTable_readIPs(const int id) return true; } -const char *networkTable_readIPsGetRecord(void) +bool networkTable_readIPsGetRecord(network_addresses_record *network_addresses, const char **message) { // Perform step const int rc = sqlite3_step(read_stmt_ip); @@ -1043,7 +1046,8 @@ const char *networkTable_readIPsGetRecord(void) // Valid row if(rc == SQLITE_ROW) { - return (char*)sqlite3_column_text(read_stmt_ip, 0); + network_addresses->ip = (char*)sqlite3_column_text(read_stmt_ip, 0); + return true; } // Check for error. An error happened when the result is neither @@ -1051,13 +1055,14 @@ const char *networkTable_readIPsGetRecord(void) // SQLITE_DONE (we are finished reading the table) if(rc != SQLITE_DONE) { + *message = sqlite3_errmsg(FTL_db); logg("networkTable_readDevicesGetIP() - SQL error step (%i): %s", - rc, sqlite3_errmsg(FTL_db)); - return NULL; + rc, *message); + return false; } // Finished reading, nothing to get here - return NULL; + return false; } // Finalize statement of a gravity database transaction diff --git a/src/database/network-table.h b/src/database/network-table.h index f41e6a891..d0685ae61 100644 --- a/src/database/network-table.h +++ b/src/database/network-table.h @@ -17,23 +17,27 @@ void updateMACVendorRecords(void); bool unify_hwaddr(void); char* getDatabaseHostname(const char* ipaddr) __attribute__((malloc)); -typedef struct networkrecord { - unsigned int id; +typedef struct { + unsigned int id; const char *hwaddr; - const char* interface; - const char *name; - const char *macVendor; - unsigned long numQueries; - time_t firstSeen; - time_t lastQuery; -} networkrecord; + const char *iface; + const char *name; + const char *macVendor; + unsigned long numQueries; + time_t firstSeen; + time_t lastQuery; +} network_record; bool networkTable_readDevices(const char **message); -bool networkTable_readDevicesGetRecord(networkrecord *network, const char **message); +bool networkTable_readDevicesGetRecord(network_record *network, const char **message); void networkTable_readDevicesFinalize(void); -bool networkTable_readIPs(const int id); -const char *networkTable_readIPsGetRecord(void); +typedef struct { + const char *ip; +} network_addresses_record; + +bool networkTable_readIPs(const int id, const char **message); +bool networkTable_readIPsGetRecord(network_addresses_record *network_addresses, const char **message); void networkTable_readIPsFinalize(void); #endif //NETWORKTABLE_H From b5e965e2482fde9df0526f38d4365e22b9831975 Mon Sep 17 00:00:00 2001 From: DL6ER Date: Wed, 17 Jun 2020 19:20:06 +0200 Subject: [PATCH 0176/1669] Improve /api/version endpoint. Signed-off-by: DL6ER --- src/api/version.c | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/src/api/version.c b/src/api/version.c index 87a374a4c..871239a37 100644 --- a/src/api/version.c +++ b/src/api/version.c @@ -21,40 +21,39 @@ int api_version(struct mg_connection *conn) cJSON *json = JSON_NEW_OBJ(); FILE* file; - char coreversion[256] = "N/A"; - char webversion[256] = "N/A"; + char coreversion[256] = "N/A", webversion[256] = "N/A"; if((file = fopen("/etc/pihole/localversions", "r")) != NULL) { igr(fscanf(file, "%255s %255s", coreversion, webversion)); fclose(file); } - char corebranch[256] = "N/A"; - char webbranch[256] = "N/A"; + char corebranch[256] = "N/A", webbranch[256] = "N/A"; if((file = fopen("/etc/pihole/localbranches", "r")) != NULL) { igr(fscanf(file, "%255s %255s", corebranch, webbranch)); fclose(file); } + // Build web object cJSON *web = JSON_NEW_OBJ(); JSON_OBJ_REF_STR(web, "branch", webbranch); - JSON_OBJ_REF_STR(web, "hash", "none"); JSON_OBJ_REF_STR(web, "tag", webversion); JSON_OBJ_ADD_ITEM(json, "web", web); + // Build core object cJSON *core = JSON_NEW_OBJ(); JSON_OBJ_REF_STR(core, "branch", corebranch); - JSON_OBJ_REF_STR(core, "hash", "none"); JSON_OBJ_REF_STR(core, "tag", coreversion); JSON_OBJ_ADD_ITEM(json, "core", core); + // Build ftl object cJSON *ftl = JSON_NEW_OBJ(); JSON_OBJ_REF_STR(ftl, "branch", GIT_BRANCH); - JSON_OBJ_REF_STR(ftl, "hash", GIT_HASH); - JSON_OBJ_REF_STR(ftl, "date", GIT_DATE); - char *version = get_FTL_version(); + const char *version = get_FTL_version(); JSON_OBJ_REF_STR(ftl, "tag", version); + JSON_OBJ_REF_STR(ftl, "date", GIT_DATE); JSON_OBJ_ADD_ITEM(json, "ftl", ftl); + // Send reply JSON_SEND_OBJECT(json); } \ No newline at end of file From aa3f7443f905cf267b252959ef706c3cc86ea3ee Mon Sep 17 00:00:00 2001 From: DL6ER Date: Wed, 17 Jun 2020 19:21:19 +0200 Subject: [PATCH 0177/1669] Ensure we use the same reply type in /api/stats/summary as in /api/dns/status Signed-off-by: DL6ER --- src/api/stats.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/api/stats.c b/src/api/stats.c index e5dd1bfbe..2b28fa4f3 100644 --- a/src/api/stats.c +++ b/src/api/stats.c @@ -82,6 +82,8 @@ int api_stats_summary(struct mg_connection *conn) // Send response cJSON *json = JSON_NEW_OBJ(); + const bool blocking = get_blockingstatus(); + JSON_OBJ_ADD_BOOL(json, "blocking", blocking); // same reply type as in /api/dns/status JSON_OBJ_ADD_NUMBER(json, "gravity_size", counters->gravity); JSON_OBJ_ADD_NUMBER(json, "blocked_queries", counters->blocked); JSON_OBJ_ADD_NUMBER(json, "percent_blocked", percent_blocked); @@ -91,7 +93,6 @@ int api_stats_summary(struct mg_connection *conn) JSON_OBJ_ADD_NUMBER(json, "privacy_level", config.privacylevel); JSON_OBJ_ADD_NUMBER(json, "total_clients", counters->clients); JSON_OBJ_ADD_NUMBER(json, "active_clients", activeclients); - JSON_OBJ_REF_STR(json, "status", (counters->gravity > 0 ? "enabled" : "disabled")); cJSON *total_queries = JSON_NEW_OBJ(); JSON_OBJ_ADD_NUMBER(total_queries, "A", counters->querytype[TYPE_A]); @@ -111,6 +112,7 @@ int api_stats_summary(struct mg_connection *conn) JSON_OBJ_ADD_NUMBER(reply_types, "NXDOMAIN", counters->reply_NXDOMAIN); JSON_OBJ_ADD_NUMBER(reply_types, "CNAME", counters->reply_CNAME); JSON_OBJ_ADD_NUMBER(reply_types, "IP", counters->reply_IP); + JSON_OBJ_ADD_NUMBER(reply_types, "domain", counters->reply_domain); JSON_OBJ_ADD_ITEM(json, "reply_types", reply_types); JSON_SEND_OBJECT(json); From 49dc3bfda03de1cf5561860db5e56d978ffdbc94 Mon Sep 17 00:00:00 2001 From: DL6ER Date: Wed, 17 Jun 2020 19:32:02 +0200 Subject: [PATCH 0178/1669] Simplify request variable extraction Signed-off-by: DL6ER --- src/api/ftl.c | 12 +++---- src/api/stats.c | 33 +++++++------------ src/api/stats_database.c | 65 +++++++++++++------------------------ src/database/common.c | 12 +++---- src/database/common.h | 4 +-- src/fifo.c | 2 +- src/fifo.h | 8 ++--- src/webserver/http-common.c | 26 +++++++++++---- src/webserver/http-common.h | 5 +-- 9 files changed, 76 insertions(+), 91 deletions(-) diff --git a/src/api/ftl.c b/src/api/ftl.c index eae582ec1..bf0b56ae1 100644 --- a/src/api/ftl.c +++ b/src/api/ftl.c @@ -59,15 +59,15 @@ int api_ftl_dnsmasq_log(struct mg_connection *conn) if(request->query_string != NULL) { // Does the user request an ID to sent from? - int num; - if((num = get_int_var(request->query_string, "nextID")) > 0) + unsigned int nextID; + if(get_uint_var(request->query_string, "nextID", &nextID)) { - if(num >= fifo_log->next_id) + if(nextID >= fifo_log->next_id) { // Do not return any data start = LOG_SIZE; } - else if(num < max((fifo_log->next_id) - LOG_SIZE, 0)) + else if((fifo_log->next_id > LOG_SIZE) && nextID < (fifo_log->next_id) - LOG_SIZE) { // Requested an ID smaller than the lowest one we have // We return the entire buffer @@ -77,13 +77,13 @@ int api_ftl_dnsmasq_log(struct mg_connection *conn) { // Reply with partial buffer, measure from the end // (the log is full) - start = LOG_SIZE - (fifo_log->next_id - num); + start = LOG_SIZE - (fifo_log->next_id - nextID); } else { // Reply with partial buffer, measure from the start // (the log is not yet full) - start = num; + start = nextID; } } } diff --git a/src/api/stats.c b/src/api/stats.c index 2b28fa4f3..a07b26c89 100644 --- a/src/api/stats.c +++ b/src/api/stats.c @@ -196,16 +196,14 @@ int api_stats_top_domains(bool blocked, struct mg_connection *conn) if(request->query_string != NULL) { // Should blocked clients be shown? - blocked = get_bool_var(request->query_string, "blocked"); + get_bool_var(request->query_string, "blocked", &blocked); // Does the user request a non-default number of replies? // Note: We do not accept zero query requests here - int num; - if((num = get_int_var(request->query_string, "show")) > 0) - show = num; + get_int_var(request->query_string, "show", &show); // Apply Audit Log filtering? - audit = get_bool_var(request->query_string, "audit"); + get_bool_var(request->query_string, "audit", &audit); } for(int domainID=0; domainID < counters->domains; domainID++) @@ -352,16 +350,14 @@ int api_stats_top_clients(bool blocked, struct mg_connection *conn) if(request->query_string != NULL) { // Should blocked clients be shown? - blocked = get_bool_var(request->query_string, "blocked"); + get_bool_var(request->query_string, "blocked", &blocked); // Does the user request a non-default number of replies? // Note: We do not accept zero query requests here - int num; - if((num = get_int_var(request->query_string, "show")) > 0) - show = num; + get_int_var(request->query_string, "show", &show); // Show also clients which have not been active recently? - includezeroclients = get_bool_var(request->query_string, "withzero"); + get_bool_var(request->query_string, "withzero", &includezeroclients); } for(int clientID = 0; clientID < counters->clients; clientID++) @@ -611,21 +607,18 @@ int api_stats_history(struct mg_connection *conn) const struct mg_request_info *request = mg_get_request_info(conn); if(request->query_string != NULL) { - int num; // Time filtering? - if((num = get_int_var(request->query_string, "from")) > 0) - from = num; - if((num = get_int_var(request->query_string, "until")) > 0) - until = num; + get_uint_var(request->query_string, "from", &from); + get_uint_var(request->query_string, "until", &until); // Query type filtering? - if((num = get_int_var(request->query_string, "querytype")) > 0 && num < TYPE_MAX) + int num; + if(get_int_var(request->query_string, "querytype", &num) && num < TYPE_MAX) querytype = num; // Does the user request a non-default number of replies? // Note: We do not accept zero query requests here - if((num = get_int_var(request->query_string, "show")) > 0) - show = num; + get_uint_var(request->query_string, "show", &show); // Forward destination filtering? char buffer[256] = { 0 }; @@ -980,9 +973,7 @@ int api_stats_recentblocked(struct mg_connection *conn) { // Does the user request a non-default number of replies? // Note: We do not accept zero query requests here - int num; - if((num = get_int_var(request->query_string, "show")) > 0) - show = num; + get_uint_var(request->query_string, "show", &show); } // Find most recently blocked query diff --git a/src/api/stats_database.c b/src/api/stats_database.c index 2ab8b2cfe..8c77755ad 100644 --- a/src/api/stats_database.c +++ b/src/api/stats_database.c @@ -21,27 +21,24 @@ int api_stats_database_overTime_history(struct mg_connection *conn) { - int from = 0, until = 0; + unsigned int from = 0, until = 0; const int interval = 600; const struct mg_request_info *request = mg_get_request_info(conn); if(request->query_string != NULL) { - int num; - if((num = get_int_var(request->query_string, "from")) > 0) - from = num; - if((num = get_int_var(request->query_string, "until")) > 0) - until = num; + get_uint_var(request->query_string, "from", &from); + get_uint_var(request->query_string, "until", &until); } // Check if we received the required information - if(from == 0 || until == 0) + if(until == 0) { cJSON *json = JSON_NEW_OBJ(); JSON_OBJ_ADD_NUMBER(json, "from", from); JSON_OBJ_ADD_NUMBER(json, "until", until); return send_json_error(conn, 400, "bad_request", - "You need to specify both \"from\" and \"until\" in the request.", + "You need to specify \"until\" in the request.", json); } @@ -186,25 +183,21 @@ int api_stats_database_overTime_history(struct mg_connection *conn) int api_stats_database_top_items(bool blocked, bool domains, struct mg_connection *conn) { - int from = 0, until = 0, show = 10; + unsigned int from = 0, until = 0, show = 10; const struct mg_request_info *request = mg_get_request_info(conn); if(request->query_string != NULL) { - int num; - if((num = get_int_var(request->query_string, "from")) > 0) - from = num; - if((num = get_int_var(request->query_string, "until")) > 0) - until = num; + get_uint_var(request->query_string, "from", &from); + get_uint_var(request->query_string, "until", &until); // Get blocked queries not only for .../top_blocked // but also for .../top_domains?blocked=true - if((num = get_bool_var(request->query_string, "blocked")) > 0) - blocked = true; + // Note: this may overwrite the blocked propery from the URL + get_bool_var(request->query_string, "blocked", &blocked); // Does the user request a non-default number of replies? // Note: We do not accept zero query requests here - if((num = get_int_var(request->query_string, "show")) > 0) - show = num; + get_uint_var(request->query_string, "show", &show); } // Check if we received the required information @@ -386,15 +379,12 @@ int api_stats_database_top_items(bool blocked, bool domains, struct mg_connectio int api_stats_database_summary(struct mg_connection *conn) { - int from = 0, until = 0; + unsigned int from = 0, until = 0; const struct mg_request_info *request = mg_get_request_info(conn); if(request->query_string != NULL) { - int num; - if((num = get_int_var(request->query_string, "from")) > 0) - from = num; - if((num = get_int_var(request->query_string, "until")) > 0) - until = num; + get_uint_var(request->query_string, "from", &from); + get_uint_var(request->query_string, "until", &until); } // Check if we received the required information @@ -470,16 +460,13 @@ int api_stats_database_summary(struct mg_connection *conn) int api_stats_database_overTime_clients(struct mg_connection *conn) { - int from = 0, until = 0; + unsigned int from = 0, until = 0; const int interval = 600; const struct mg_request_info *request = mg_get_request_info(conn); if(request->query_string != NULL) { - int num; - if((num = get_int_var(request->query_string, "from")) > 0) - from = num; - if((num = get_int_var(request->query_string, "until")) > 0) - until = num; + get_uint_var(request->query_string, "from", &from); + get_uint_var(request->query_string, "until", &until); } // Check if we received the required information @@ -737,15 +724,12 @@ int api_stats_database_overTime_clients(struct mg_connection *conn) static const char *querytypes[8] = {"A","AAAA","ANY","SRV","SOA","PTR","TXT","UNKN"}; int api_stats_database_query_types(struct mg_connection *conn) { - int from = 0, until = 0; + unsigned int from = 0, until = 0; const struct mg_request_info *request = mg_get_request_info(conn); if(request->query_string != NULL) { - int num; - if((num = get_int_var(request->query_string, "from")) > 0) - from = num; - if((num = get_int_var(request->query_string, "until")) > 0) - until = num; + get_uint_var(request->query_string, "from", &from); + get_uint_var(request->query_string, "until", &until); } // Check if we received the required information @@ -795,15 +779,12 @@ int api_stats_database_query_types(struct mg_connection *conn) int api_stats_database_upstreams(struct mg_connection *conn) { - int from = 0, until = 0; + unsigned int from = 0, until = 0; const struct mg_request_info *request = mg_get_request_info(conn); if(request->query_string != NULL) { - int num; - if((num = get_int_var(request->query_string, "from")) > 0) - from = num; - if((num = get_int_var(request->query_string, "until")) > 0) - until = num; + get_uint_var(request->query_string, "from", &from); + get_uint_var(request->query_string, "until", &until); } // Check if we received the required information diff --git a/src/database/common.c b/src/database/common.c index 472f88646..fc7929c1e 100644 --- a/src/database/common.c +++ b/src/database/common.c @@ -481,7 +481,7 @@ int db_query_int(const char* querystr) return result; } -int db_query_int_from_until(const char* querystr, const int from, const int until) +int db_query_int_from_until(const char* querystr, const unsigned int from, const unsigned int until) { if(!database) { @@ -497,8 +497,8 @@ int db_query_int_from_until(const char* querystr, const int from, const int unti } // Bind from and until to prepared statement - if((rc = sqlite3_bind_int(stmt, 1, from)) != SQLITE_OK || - (rc = sqlite3_bind_int(stmt, 2, until)) != SQLITE_OK) + if((rc = sqlite3_bind_int64(stmt, 1, from)) != SQLITE_OK || + (rc = sqlite3_bind_int64(stmt, 2, until)) != SQLITE_OK) { logg("db_query_int_from_until(%s) - SQL error bind (%i): %s", querystr, rc, sqlite3_errmsg(FTL_db)); } @@ -526,7 +526,7 @@ int db_query_int_from_until(const char* querystr, const int from, const int unti return result; } -int db_query_int_from_until_type(const char* querystr, const int from, const int until, const int type) +int db_query_int_from_until_type(const char* querystr, const unsigned int from, const unsigned int until, const int type) { if(!database) { @@ -542,8 +542,8 @@ int db_query_int_from_until_type(const char* querystr, const int from, const int } // Bind from and until to prepared statement - if((rc = sqlite3_bind_int(stmt, 1, from)) != SQLITE_OK || - (rc = sqlite3_bind_int(stmt, 2, until)) != SQLITE_OK || + if((rc = sqlite3_bind_int64(stmt, 1, from)) != SQLITE_OK || + (rc = sqlite3_bind_int64(stmt, 2, until)) != SQLITE_OK || (rc = sqlite3_bind_int(stmt, 3, type)) != SQLITE_OK) { logg("db_query_int_from_until(%s) - SQL error bind (%i): %s", querystr, rc, sqlite3_errmsg(FTL_db)); diff --git a/src/database/common.h b/src/database/common.h index 604f33f9d..5b21d6815 100644 --- a/src/database/common.h +++ b/src/database/common.h @@ -22,8 +22,8 @@ int dbquery(const char *format, ...); bool dbopen(void); void dbclose(void); int db_query_int(const char*); -int db_query_int_from_until(const char* querystr, const int from, const int until); -int db_query_int_from_until_type(const char* querystr, const int from, const int until, const int type); +int db_query_int_from_until(const char* querystr, const unsigned int from, const unsigned int until); +int db_query_int_from_until_type(const char* querystr, const unsigned int from, const unsigned int until, const int type); long get_lastID(void); void SQLite3LogCallback(void *pArg, int iErrCode, const char *zMsg); long int get_max_query_ID(void); diff --git a/src/fifo.c b/src/fifo.c index b5c034aab..00ed5b1b6 100644 --- a/src/fifo.c +++ b/src/fifo.c @@ -12,7 +12,7 @@ // {un,}lock_shm() #include "shmem.h" -void add_to_dnsmasq_log_fifo_buffer(const char *payload, const int length) +void add_to_dnsmasq_log_fifo_buffer(const char *payload, const size_t length) { // Lock SHM lock_shm(); diff --git a/src/fifo.h b/src/fifo.h index 18ad33703..90f1949c2 100644 --- a/src/fifo.h +++ b/src/fifo.h @@ -13,17 +13,17 @@ #include "FTL.h" /* From RFC 3164 */ -#define MAX_MESSAGE 1024 +#define MAX_MESSAGE 1024u // How many messages do we keep in memory (FIFO message buffer)? // This number multiplied by MAX_MESSAGE (see above) gives the total buffer size // Defaults to 128 [use 128 KB of memory for the log] -#define LOG_SIZE 128 +#define LOG_SIZE 128u -void add_to_dnsmasq_log_fifo_buffer(const char *payload, const int length); +void add_to_dnsmasq_log_fifo_buffer(const char *payload, const size_t length); typedef struct { - int next_id; + unsigned int next_id; time_t timestamp[LOG_SIZE]; char message[LOG_SIZE][MAX_MESSAGE]; } fifologData; diff --git a/src/webserver/http-common.c b/src/webserver/http-common.c index 349d9ae5e..521ab6c12 100644 --- a/src/webserver/http-common.c +++ b/src/webserver/http-common.c @@ -103,27 +103,39 @@ int send_http_internal_error(struct mg_connection *conn) return mg_send_http_error(conn, 500, "Internal server error"); } -bool get_bool_var(const char *source, const char *var) +bool get_bool_var(const char *source, const char *var, bool *boolean) { char buffer[16] = { 0 }; if(GET_VAR(var, buffer, source)) { - return (strstr(buffer, "true") != NULL); + *boolean = (strcasecmp(buffer, "true") == 0); + return true; } return false; } -int get_int_var(const char *source, const char *var) +bool get_int_var(const char *source, const char *var, int *num) { - int num = -1; char buffer[16] = { 0 }; if(GET_VAR(var, buffer, source) && - sscanf(buffer, "%d", &num) == 1) + sscanf(buffer, "%d", num) == 1) { - return num; + return true; } - return -1; + return false; +} + +bool get_uint_var(const char *source, const char *var, unsigned int *num) +{ + char buffer[16] = { 0 }; + if(GET_VAR(var, buffer, source) && + sscanf(buffer, "%u", num) == 1) + { + return true; + } + + return false; } bool __attribute__((pure)) startsWith(const char *path, const char *uri) diff --git a/src/webserver/http-common.h b/src/webserver/http-common.h index e41f68c32..fbdd29e24 100644 --- a/src/webserver/http-common.h +++ b/src/webserver/http-common.h @@ -38,8 +38,9 @@ bool http_get_cookie_int(struct mg_connection *conn, const char *cookieName, int bool http_get_cookie_str(struct mg_connection *conn, const char *cookieName, char *str, size_t str_size); // HTTP parameter routines -bool get_bool_var(const char *source, const char *var); -int get_int_var(const char *source, const char *var); +bool get_bool_var(const char *source, const char *var, bool *boolean); +bool get_uint_var(const char *source, const char *var, unsigned int *num); +bool get_int_var(const char *source, const char *var, int *num); // HTTP macros #define GET_VAR(variable, destination, source) mg_get_var(source, strlen(source), variable, destination, sizeof(destination)) From a292c8f67f1f3a510f7c190f85e1af2a1259fbac Mon Sep 17 00:00:00 2001 From: DL6ER Date: Wed, 17 Jun 2020 19:54:38 +0200 Subject: [PATCH 0179/1669] Improve database statistics endpoint Signed-off-by: DL6ER --- src/api/ftl.c | 82 ++++++++++++++++++++++++++++++++++++++++++++++ src/api/routes.c | 8 ++--- src/api/routes.h | 2 +- src/api/settings.c | 23 ------------- src/files.c | 43 ++++++++++++++---------- src/files.h | 2 ++ 6 files changed, 114 insertions(+), 46 deletions(-) diff --git a/src/api/ftl.c b/src/api/ftl.c index bf0b56ae1..6673466fc 100644 --- a/src/api/ftl.c +++ b/src/api/ftl.c @@ -14,6 +14,14 @@ #include "routes.h" // struct fifologData #include "../fifo.h" +// get_FTL_db_filesize() +#include "files.h" +// get_sqlite3_version() +#include "database/common.h" +// get_number_of_queries_in_DB() +#include "database/query-table.h" +// getgrgid() +#include int api_ftl_client(struct mg_connection *conn) { @@ -110,3 +118,77 @@ int api_ftl_dnsmasq_log(struct mg_connection *conn) // Send data JSON_SEND_OBJECT(json); } + +int api_ftl_database(struct mg_connection *conn) +{ + // Verify requesting client is allowed to see this ressource + if(check_client_auth(conn) < 0) + { + send_json_unauthorized(conn); + } + + cJSON *json = JSON_NEW_OBJ(); + + // Add database stat details + struct stat st; + get_database_stat(&st); + JSON_OBJ_ADD_NUMBER(json, "size", st.st_size); // Total size, in bytes + + // File type + char octal[5]; const char *human; + cJSON *type = JSON_NEW_OBJ(); + snprintf(octal, sizeof(octal), "%04o", (st.st_mode & S_IFMT) >> 9); + JSON_OBJ_COPY_STR(type, "octal", octal); + if((st.st_mode & S_IFMT) == S_IFREG) + human = "Regular file"; + else if((st.st_mode & S_IFMT) == S_IFLNK) + human = "Symbolic link"; + else + human = "Unknown"; + JSON_OBJ_REF_STR(type, "human", human); + JSON_OBJ_ADD_ITEM(json, "type", type); + + // File mode + cJSON *mode = JSON_NEW_OBJ(); + snprintf(octal, sizeof(octal), "%03o", st.st_mode & 0x1FF); + JSON_OBJ_COPY_STR(mode, "octal", octal); + char permissions[10]; + get_permission_string(permissions, &st); + JSON_OBJ_REF_STR(mode, "human", permissions); + JSON_OBJ_ADD_ITEM(json, "mode", mode); + + JSON_OBJ_ADD_NUMBER(json, "atime", st.st_atime); // Time of last access + JSON_OBJ_ADD_NUMBER(json, "mtime", st.st_mtime); // Time of last modification + JSON_OBJ_ADD_NUMBER(json, "ctime", st.st_ctime); // Time of last status change (owner or mode change, etc.) + + // Get owner details + cJSON *user = JSON_NEW_OBJ(); + JSON_OBJ_ADD_NUMBER(user, "uid", st.st_uid); // UID + const struct passwd *pw = getpwuid(st.st_uid); + if(pw != NULL) + { + JSON_OBJ_COPY_STR(user, "name", pw->pw_name); // User name + JSON_OBJ_COPY_STR(user, "info", pw->pw_gecos); // User information + } + cJSON *group = JSON_NEW_OBJ(); + JSON_OBJ_ADD_NUMBER(group, "gid", st.st_gid); // GID + const struct group *gr = getgrgid(st.st_uid); + if(gr != NULL) + { + JSON_OBJ_COPY_STR(group, "name", gr->gr_name); // Group name + } + cJSON *owner = JSON_NEW_OBJ(); + JSON_OBJ_ADD_ITEM(owner, "user", user); + JSON_OBJ_ADD_ITEM(owner, "group", group); + JSON_OBJ_ADD_ITEM(json, "owner", owner); + + // Add number of queries in database + const int queries_in_database = get_number_of_queries_in_DB(); + JSON_OBJ_ADD_NUMBER(json, "queries", queries_in_database); + + // Add SQLite library version + JSON_OBJ_REF_STR(json, "sqlite_version", get_sqlite3_version()); + + // Send reply to user + JSON_SEND_OBJECT(json); +} diff --git a/src/api/routes.c b/src/api/routes.c index e276e9a2f..2033e73da 100644 --- a/src/api/routes.c +++ b/src/api/routes.c @@ -60,6 +60,10 @@ int api_handler(struct mg_connection *conn, void *ignored) { ret = api_ftl_dnsmasq_log(conn); } + else if(startsWith("/api/ftl/database", request->local_uri)) + { + ret = api_ftl_database(conn); + } /******************************** api/network ****************************/ else if(startsWith("/api/network", request->local_uri)) { @@ -161,10 +165,6 @@ int api_handler(struct mg_connection *conn, void *ignored) { ret = api_settings_web(conn); } - else if(startsWith("/api/settings/ftldb", request->local_uri)) - { - ret = api_settings_ftldb(conn); - } /******************************** not found ******************************/ else { diff --git a/src/api/routes.h b/src/api/routes.h index 2f0862f74..caaf4db8d 100644 --- a/src/api/routes.h +++ b/src/api/routes.h @@ -38,6 +38,7 @@ int api_stats_database_upstreams(struct mg_connection *conn); // FTL methods int api_ftl_client(struct mg_connection *conn); int api_ftl_dnsmasq_log(struct mg_connection *conn); +int api_ftl_database(struct mg_connection *conn); // Network methods int api_network(struct mg_connection *conn); @@ -59,6 +60,5 @@ int api_auth_salt(struct mg_connection *conn); // Settings methods int api_settings_web(struct mg_connection *conn); -int api_settings_ftldb(struct mg_connection *conn); #endif // ROUTES_H diff --git a/src/api/settings.c b/src/api/settings.c index 89971b531..f8bda43ac 100644 --- a/src/api/settings.c +++ b/src/api/settings.c @@ -12,12 +12,6 @@ #include "../webserver/http-common.h" #include "../webserver/json_macros.h" #include "routes.h" -// get_FTL_db_filesize() -#include "files.h" -// get_sqlite3_version() -#include "database/common.h" -// get_number_of_queries_in_DB() -#include "database/query-table.h" int api_settings_web(struct mg_connection *conn) { @@ -25,21 +19,4 @@ int api_settings_web(struct mg_connection *conn) JSON_OBJ_REF_STR(json, "layout", "boxed"); JSON_OBJ_REF_STR(json, "language", "en"); JSON_SEND_OBJECT(json); -} - -int api_settings_ftldb(struct mg_connection *conn) -{ - // Verify requesting client is allowed to see this ressource - if(check_client_auth(conn) < 0) - { - send_json_unauthorized(conn); - } - - cJSON *json = JSON_NEW_OBJ(); - const int db_filesize = get_FTL_db_filesize(); - JSON_OBJ_ADD_NUMBER(json, "filesize", db_filesize); - const int queries_in_database = get_number_of_queries_in_DB(); - JSON_OBJ_ADD_NUMBER(json, "queries", queries_in_database); - JSON_OBJ_REF_STR(json, "sqlite_version", get_sqlite3_version()); - JSON_SEND_OBJECT(json); } \ No newline at end of file diff --git a/src/files.c b/src/files.c index 7f1d3d947..3836742a1 100644 --- a/src/files.c +++ b/src/files.c @@ -59,15 +59,33 @@ bool file_exists(const char *filename) return stat(filename, &st) == 0; } +bool get_database_stat(struct stat *st) +{ + return stat(FTLfiles.FTL_db, st) != 0; +} + unsigned long long get_FTL_db_filesize(void) { struct stat st; - if(stat(FTLfiles.FTL_db, &st) != 0) - { - // stat() failed (maybe the DB file does not exist?) - return 0; - } - return st.st_size; + if(get_database_stat(&st)) + return st.st_size; + return 0llu; +} + +void get_permission_string(char permissions[10], struct stat *st) +{ + // Get human-readable format of permissions as known from ls + snprintf(permissions, 10u, + "%s%s%s%s%s%s%s%s%s", + st->st_mode & S_IRUSR ? "r":"-", + st->st_mode & S_IWUSR ? "w":"-", + st->st_mode & S_IXUSR ? "x":"-", + st->st_mode & S_IRGRP ? "r":"-", + st->st_mode & S_IWGRP ? "w":"-", + st->st_mode & S_IXGRP ? "x":"-", + st->st_mode & S_IROTH ? "r":"-", + st->st_mode & S_IWOTH ? "w":"-", + st->st_mode & S_IXOTH ? "x":"-"); } void ls_dir(const char* path) @@ -121,18 +139,7 @@ void ls_dir(const char* path) snprintf(group, sizeof(group), "%d", st.st_gid); char permissions[10]; - // Get human-readable format of permissions as known from ls - snprintf(permissions, sizeof(permissions), - "%s%s%s%s%s%s%s%s%s", - st.st_mode & S_IRUSR ? "r":"-", - st.st_mode & S_IWUSR ? "w":"-", - st.st_mode & S_IXUSR ? "x":"-", - st.st_mode & S_IRGRP ? "r":"-", - st.st_mode & S_IWGRP ? "w":"-", - st.st_mode & S_IXGRP ? "x":"-", - st.st_mode & S_IROTH ? "r":"-", - st.st_mode & S_IWOTH ? "w":"-", - st.st_mode & S_IXOTH ? "x":"-"); + get_permission_string(permissions, &st); char prefix[2] = " "; double formated = 0.0; diff --git a/src/files.h b/src/files.h index 4df7b745d..57bde5d75 100644 --- a/src/files.h +++ b/src/files.h @@ -12,7 +12,9 @@ bool chmod_file(const char *filename, const mode_t mode); bool file_exists(const char *filename); +bool get_database_stat(struct stat *st); unsigned long long get_FTL_db_filesize(void); +void get_permission_string(char permissions[10], struct stat *st); void ls_dir(const char* path); #endif //FILE_H From ff4b2bcfef0c45477ddc3256ab301f916b069ebb Mon Sep 17 00:00:00 2001 From: DL6ER Date: Wed, 17 Jun 2020 21:50:24 +0200 Subject: [PATCH 0180/1669] Zero-initialize memory on the stack to avoid picking up outdated content Signed-off-by: DL6ER --- src/api/dns.c | 2 +- src/api/ftl.c | 5 +++-- src/api/list.c | 10 +++++----- 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/src/api/dns.c b/src/api/dns.c index bc6fc38ba..8a8ecb4ab 100644 --- a/src/api/dns.c +++ b/src/api/dns.c @@ -53,7 +53,7 @@ static int set_blocking(struct mg_connection *conn) return send_json_unauthorized(conn); } - char buffer[1024]; + char buffer[1024] = { 0 }; const int data_len = mg_read(conn, buffer, sizeof(buffer) - 1); if ((data_len < 1) || (data_len >= (int)sizeof(buffer))) { return send_json_error(conn, 400, diff --git a/src/api/ftl.c b/src/api/ftl.c index 6673466fc..2534462aa 100644 --- a/src/api/ftl.c +++ b/src/api/ftl.c @@ -135,7 +135,8 @@ int api_ftl_database(struct mg_connection *conn) JSON_OBJ_ADD_NUMBER(json, "size", st.st_size); // Total size, in bytes // File type - char octal[5]; const char *human; + char octal[5] = { 0 }; + const char *human; cJSON *type = JSON_NEW_OBJ(); snprintf(octal, sizeof(octal), "%04o", (st.st_mode & S_IFMT) >> 9); JSON_OBJ_COPY_STR(type, "octal", octal); @@ -152,7 +153,7 @@ int api_ftl_database(struct mg_connection *conn) cJSON *mode = JSON_NEW_OBJ(); snprintf(octal, sizeof(octal), "%03o", st.st_mode & 0x1FF); JSON_OBJ_COPY_STR(mode, "octal", octal); - char permissions[10]; + char permissions[10] = { 0 }; get_permission_string(permissions, &st); JSON_OBJ_REF_STR(mode, "human", permissions); JSON_OBJ_ADD_ITEM(json, "mode", mode); diff --git a/src/api/list.c b/src/api/list.c index f542bbd85..791e5908d 100644 --- a/src/api/list.c +++ b/src/api/list.c @@ -96,7 +96,7 @@ static int api_dns_domainlist_read(struct mg_connection *conn, bool exact, bool { // Extract domain from path (option for GET) const struct mg_request_info *request = mg_get_request_info(conn); - char domain_filter[1024]; + char domain_filter[1024] = { 0 }; // Advance one character to strip "/" const char *encoded_uri = strrchr(request->local_uri, '/')+1u; // Decode URL (necessary for regular expressions, harmless for domains) @@ -114,12 +114,12 @@ static int api_dns_domainlist_write(struct mg_connection *conn, const enum http_method method) { // Extract payload - char buffer[1024]; + char buffer[1024] = { 0 }; int data_len = mg_read(conn, buffer, sizeof(buffer) - 1); if ((data_len < 1) || (data_len >= (int)sizeof(buffer))) { return send_json_error(conn, 400, - "bad_request", "No request body data", - NULL); + "bad_request", "No request body data", + NULL); } buffer[data_len] = '\0'; @@ -205,7 +205,7 @@ static int api_dns_domainlist_remove(struct mg_connection *conn, { const struct mg_request_info *request = mg_get_request_info(conn); - char domain[1024]; + char domain[1024] = { 0 }; // Advance one character to strip "/" const char *encoded_uri = strrchr(request->local_uri, '/')+1u; // Decode URL (necessary for regular expressions, harmless for domains) From 12112396f0bb2111eb299b177da4a6231f204612 Mon Sep 17 00:00:00 2001 From: DL6ER Date: Wed, 17 Jun 2020 23:17:45 +0200 Subject: [PATCH 0181/1669] Delete running blocking status timer when requesting the same blocking mode as already active. Signed-off-by: DL6ER --- src/api/dns.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/api/dns.c b/src/api/dns.c index 8a8ecb4ab..7bf2af827 100644 --- a/src/api/dns.c +++ b/src/api/dns.c @@ -93,9 +93,8 @@ static int set_blocking(struct mg_connection *conn) { // The blocking status does not need to be changed - // If delay is absent (or -1), we delete a possibly running timer - if(delay < 0) - set_blockingmode_timer(-1, true); + // Delete a possibly running timer + set_blockingmode_timer(-1, true); } else { From 5375ea0a62bc8c3a74fec82b865ea65f44dcc7c6 Mon Sep 17 00:00:00 2001 From: DL6ER Date: Wed, 17 Jun 2020 23:18:34 +0200 Subject: [PATCH 0182/1669] Return null for domain comment if this is what is stored in the database. Signed-off-by: DL6ER --- src/api/list.c | 10 ++++++++-- src/api/stats.c | 18 +++++++++--------- 2 files changed, 17 insertions(+), 11 deletions(-) diff --git a/src/api/list.c b/src/api/list.c index 791e5908d..35c58eeae 100644 --- a/src/api/list.c +++ b/src/api/list.c @@ -60,7 +60,12 @@ static int get_domainlist(struct mg_connection *conn, const int code, const int JSON_OBJ_ADD_BOOL(item, "enabled", domain.enabled); JSON_OBJ_ADD_NUMBER(item, "date_added", domain.date_added); JSON_OBJ_ADD_NUMBER(item, "date_modified", domain.date_modified); - JSON_OBJ_COPY_STR(item, "comment", domain.comment); + if(domain.comment != NULL) { + JSON_OBJ_COPY_STR(item, "comment", domain.comment); + } else { + JSON_OBJ_ADD_NULL(item, "comment"); + } + JSON_ARRAY_ADD_ITEM(json, item); } gravityDB_readTableFinalize(); @@ -118,7 +123,8 @@ static int api_dns_domainlist_write(struct mg_connection *conn, int data_len = mg_read(conn, buffer, sizeof(buffer) - 1); if ((data_len < 1) || (data_len >= (int)sizeof(buffer))) { return send_json_error(conn, 400, - "bad_request", "No request body data", + "bad_request", + "No request body data", NULL); } buffer[data_len] = '\0'; diff --git a/src/api/stats.c b/src/api/stats.c index a07b26c89..41cf3bbbb 100644 --- a/src/api/stats.c +++ b/src/api/stats.c @@ -672,9 +672,9 @@ int api_stats_history(struct mg_connection *conn) free(forwarddest); return send_json_error(conn, 400, - "bad_request", - "Requested upstream not found", - json); + "bad_request", + "Requested upstream not found", + json); } } } @@ -760,9 +760,9 @@ int api_stats_history(struct mg_connection *conn) free(clientname); return send_json_error(conn, 400, - "bad_request", - "Requested client not found", - json); + "bad_request", + "Requested client not found", + json); } } @@ -785,9 +785,9 @@ int api_stats_history(struct mg_connection *conn) free(clientname); return send_json_error(conn, 400, - "bad_request", - "Requested cursor larger than number of queries", - json); + "bad_request", + "Requested cursor larger than number of queries", + json); } } From 3f918fec95815decc355292a6ebbf18022bb1725 Mon Sep 17 00:00:00 2001 From: DL6ER Date: Wed, 17 Jun 2020 23:24:00 +0200 Subject: [PATCH 0183/1669] Move the API from http://pi.hole:8080/admin/api to http://pi.hole:8080/api Signed-off-by: DL6ER --- src/api/routes.c | 18 +++++++++--------- src/webserver/http-common.c | 19 +------------------ src/webserver/webserver.c | 18 +++++------------- test/test_suite.bats | 8 ++++---- 4 files changed, 19 insertions(+), 44 deletions(-) diff --git a/src/api/routes.c b/src/api/routes.c index 2033e73da..2c82a92f3 100644 --- a/src/api/routes.c +++ b/src/api/routes.c @@ -24,7 +24,7 @@ int api_handler(struct mg_connection *conn, void *ignored) int ret = 0; const struct mg_request_info *request = mg_get_request_info(conn); - /******************************** api/dns ********************************/ + /******************************** /api/dns ********************************/ if(startsWith("/api/dns/blocking", request->local_uri)) { ret = api_dns_blockingstatus(conn); @@ -33,7 +33,7 @@ int api_handler(struct mg_connection *conn, void *ignored) { ret = api_dns_cacheinfo(conn); } - /******************************** api/whitelist ****************************/ + /******************************** /api/whitelist ****************************/ else if(startsWith("/api/whitelist/exact", request->local_uri)) { ret = api_dns_domainlist(conn, true, true); @@ -42,7 +42,7 @@ int api_handler(struct mg_connection *conn, void *ignored) { ret = api_dns_domainlist(conn, false, true); } - /******************************** api/blacklist ****************************/ + /******************************** /api/blacklist ****************************/ else if(startsWith("/api/blacklist/exact", request->local_uri)) { ret = api_dns_domainlist(conn, true, false); @@ -51,7 +51,7 @@ int api_handler(struct mg_connection *conn, void *ignored) { ret = api_dns_domainlist(conn, false, false); } - /******************************** api/ftl ****************************/ + /******************************** /api/ftl ****************************/ else if(startsWith("/api/ftl/client", request->local_uri)) { ret = api_ftl_client(conn); @@ -64,12 +64,12 @@ int api_handler(struct mg_connection *conn, void *ignored) { ret = api_ftl_database(conn); } - /******************************** api/network ****************************/ + /******************************** /api/network ****************************/ else if(startsWith("/api/network", request->local_uri)) { ret = api_network(conn); } - /******************************** api/stats **************************/ + /******************************** /api/stats **************************/ else if(startsWith("/api/stats/summary", request->local_uri)) { ret = api_stats_summary(conn); @@ -146,12 +146,12 @@ int api_handler(struct mg_connection *conn, void *ignored) { ret = api_stats_database_upstreams(conn); } - /******************************** api/version ****************************/ + /******************************** /api/version ****************************/ else if(startsWith("/api/version", request->local_uri)) { ret = api_version(conn); } - /******************************** api/auth ****************************/ + /******************************** /api/auth ****************************/ else if(startsWith("/api/auth", request->local_uri)) { ret = api_auth(conn); @@ -160,7 +160,7 @@ int api_handler(struct mg_connection *conn, void *ignored) { ret = api_auth_salt(conn); } - /******************************** api/settings ****************************/ + /******************************** /api/settings ****************************/ else if(startsWith("/api/settings/web", request->local_uri)) { ret = api_settings_web(conn); diff --git a/src/webserver/http-common.c b/src/webserver/http-common.c index 521ab6c12..4c3c40585 100644 --- a/src/webserver/http-common.c +++ b/src/webserver/http-common.c @@ -140,24 +140,7 @@ bool get_uint_var(const char *source, const char *var, unsigned int *num) bool __attribute__((pure)) startsWith(const char *path, const char *uri) { - // We subtract 1 to include the trailing slash in webhome - unsigned int webhome_length = strlen(httpsettings.webhome)-1u; - unsigned int uri_length = strlen(uri); - if(uri_length > webhome_length) - { - // Compare strings while skipping any possible webhome - // Note: this is not an issue here as the API callback - // doesn't even get called when the path does not start in - // what is configured by httpsettings.webhome. - // In other words: This strips the webhome such that any - // request will look like "/api/dns/status" even when the - // webhome is configured to something like "/admin" - return strncmp(path, uri+webhome_length, strlen(path)) == 0; - } - else - { - return false; - } + return strncmp(path, uri, strlen(path)) == 0; } bool http_get_cookie_int(struct mg_connection *conn, const char *cookieName, int *i) diff --git a/src/webserver/webserver.c b/src/webserver/webserver.c index 748fc2d5e..e0cd424b5 100644 --- a/src/webserver/webserver.c +++ b/src/webserver/webserver.c @@ -113,22 +113,14 @@ void http_init(void) /* Add simple demonstration callbacks */ mg_set_request_handler(ctx, "/ping", print_simple, (char*)"pong\n"); - char *api_path = NULL; - if(asprintf(&api_path, "%sapi", httpsettings.webhome) > 4) - { - if(config.debug & DEBUG_API) - { - logg_web(HTTP_INFO, "Installing API handler at %s", api_path); - } - mg_set_request_handler(ctx, api_path, api_handler, NULL); - // The request handler URI got duplicated - free(api_path); - } + + // Register API handler + mg_set_request_handler(ctx, "/api", api_handler, NULL); // Initialize PH7 engine and register PHP request handler init_ph7(); - mg_set_request_handler(ctx, "**/$", ph7_handler, 0); - mg_set_request_handler(ctx, "**.php$", ph7_handler, 0); + mg_set_request_handler(ctx, "**/$", ph7_handler, NULL); + mg_set_request_handler(ctx, "**.php$", ph7_handler, NULL); } void http_terminate(void) diff --git a/test/test_suite.bats b/test/test_suite.bats index a2001f357..023834ee5 100644 --- a/test/test_suite.bats +++ b/test/test_suite.bats @@ -271,9 +271,9 @@ } @test "HTTP server responds with JSON error 404 to unknown API path" { - run bash -c 'curl -s 127.0.0.1:8080/admin/api/undefined' + run bash -c 'curl -s 127.0.0.1:8080/api/undefined' printf "%s\n" "${lines[@]}" - [[ ${lines[0]} == "{\"error\":{\"key\":\"not_found\",\"message\":\"Not found\",\"data\":{\"path\":\"/admin/api/undefined\"}}}" ]] + [[ ${lines[0]} == "{\"error\":{\"key\":\"not_found\",\"message\":\"Not found\",\"data\":{\"path\":\"/api/undefined\"}}}" ]] } @test "HTTP server responds with normal error 404 to path outside /admin" { @@ -290,14 +290,14 @@ #} @test "API authorization: Unauthorized for request without password" { - run bash -c 'curl -I -s 127.0.0.1:8080/admin/api/auth' + run bash -c 'curl -I -s 127.0.0.1:8080/api/auth' printf "%s\n" "${lines[@]}" [[ ${lines[0]} == "HTTP/1.1 401 Unauthorized"* ]] } # This test is assuming the user password is empty @test "API authorization: Success for request with correct password" { - run bash -c 'curl -s -H "X-Pi-hole-Authenticate: cd372fb85148700fa88095e3492d3f9f5beb43e555e5ff26d95f5a6adc36f8e6" 127.0.0.1:8080/admin/api/auth' + run bash -c 'curl -s -H "X-Pi-hole-Authenticate: cd372fb85148700fa88095e3492d3f9f5beb43e555e5ff26d95f5a6adc36f8e6" 127.0.0.1:8080/api/auth' printf "%s\n" "${lines[@]}" [[ ${lines[0]} == "{\"status\":\"success\"}" ]] } From fd2f8d7926ee0e284b0d534e4bdfc8f99354603a Mon Sep 17 00:00:00 2001 From: DL6ER Date: Thu, 3 Sep 2020 21:51:44 +0200 Subject: [PATCH 0184/1669] Update cJSON from 1.7.13 to 1.7.14 (released 3 September 2020). The structure of linkedlist has been changed to improve the efficiency of adding items to arrays/objects. Signed-off-by: DL6ER --- src/cJSON/cJSON.c | 47 ++++++++++++++++++++++++++++++++++------------- src/cJSON/cJSON.h | 6 +++--- 2 files changed, 37 insertions(+), 16 deletions(-) diff --git a/src/cJSON/cJSON.c b/src/cJSON/cJSON.c index a5d39878c..4c6a308ee 100644 --- a/src/cJSON/cJSON.c +++ b/src/cJSON/cJSON.c @@ -92,7 +92,7 @@ CJSON_PUBLIC(const char *) cJSON_GetErrorPtr(void) return (const char*) (global_error.json + global_error.position); } -CJSON_PUBLIC(char *) cJSON_GetStringValue(cJSON *item) +CJSON_PUBLIC(char *) cJSON_GetStringValue(const cJSON * const item) { if (!cJSON_IsString(item)) { @@ -102,18 +102,18 @@ CJSON_PUBLIC(char *) cJSON_GetStringValue(cJSON *item) return item->valuestring; } -CJSON_PUBLIC(double) cJSON_GetNumberValue(cJSON *item) +CJSON_PUBLIC(double) cJSON_GetNumberValue(const cJSON * const item) { if (!cJSON_IsNumber(item)) { - return NAN; + return (double) NAN; } return item->valuedouble; } /* This is a safeguard to prevent copy-pasters from using incompatible C and header files */ -#if (CJSON_VERSION_MAJOR != 1) || (CJSON_VERSION_MINOR != 7) || (CJSON_VERSION_PATCH != 13) +#if (CJSON_VERSION_MAJOR != 1) || (CJSON_VERSION_MINOR != 7) || (CJSON_VERSION_PATCH != 14) #error cJSON.h and cJSON.c have different versions. Make sure that both have the same. #endif @@ -1509,6 +1509,10 @@ static cJSON_bool parse_array(cJSON * const item, parse_buffer * const input_buf success: input_buffer->depth--; + if (head != NULL) { + head->prev = current_item; + } + item->type = cJSON_Array; item->child = head; @@ -1681,6 +1685,10 @@ static cJSON_bool parse_object(cJSON * const item, parse_buffer * const input_bu success: input_buffer->depth--; + if (head != NULL) { + head->prev = current_item; + } + item->type = cJSON_Object; item->child = head; @@ -1967,15 +1975,6 @@ static cJSON_bool add_item_to_array(cJSON *array, cJSON *item) suffix_object(child->prev, item); array->child->prev = item; } - else - { - while (child->next) - { - child = child->next; - } - suffix_object(child, item); - array->child->prev = item; - } } return true; @@ -2202,6 +2201,12 @@ CJSON_PUBLIC(cJSON *) cJSON_DetachItemViaPointer(cJSON *parent, cJSON * const it /* first element */ parent->child = item->next; } + else if (item->next == NULL) + { + /* last element */ + parent->child->prev = item->prev; + } + /* make sure the detached item doesn't point anywhere anymore */ item->prev = NULL; item->next = NULL; @@ -2299,6 +2304,10 @@ CJSON_PUBLIC(cJSON_bool) cJSON_ReplaceItemViaPointer(cJSON * const parent, cJSON } if (parent->child == item) { + if (parent->child->prev == parent->child) + { + replacement->prev = replacement; + } parent->child = replacement; } else @@ -2310,6 +2319,10 @@ CJSON_PUBLIC(cJSON_bool) cJSON_ReplaceItemViaPointer(cJSON * const parent, cJSON { replacement->prev->next = replacement; } + if (replacement->next == NULL) + { + parent->child->prev = replacement; + } } item->next = NULL; @@ -2549,6 +2562,7 @@ CJSON_PUBLIC(cJSON *) cJSON_CreateIntArray(const int *numbers, int count) } p = n; } + a->child->prev = n; return a; } @@ -2585,6 +2599,7 @@ CJSON_PUBLIC(cJSON *) cJSON_CreateFloatArray(const float *numbers, int count) } p = n; } + a->child->prev = n; return a; } @@ -2621,6 +2636,7 @@ CJSON_PUBLIC(cJSON *) cJSON_CreateDoubleArray(const double *numbers, int count) } p = n; } + a->child->prev = n; return a; } @@ -2657,6 +2673,7 @@ CJSON_PUBLIC(cJSON *) cJSON_CreateStringArray(const char *const *strings, int co } p = n; } + a->child->prev = n; return a; } @@ -2729,6 +2746,10 @@ CJSON_PUBLIC(cJSON *) cJSON_Duplicate(const cJSON *item, cJSON_bool recurse) } child = child->next; } + if (newitem && newitem->child) + { + newitem->child->prev = newchild; + } return newitem; diff --git a/src/cJSON/cJSON.h b/src/cJSON/cJSON.h index 0c6c8e070..e97e5f4cd 100644 --- a/src/cJSON/cJSON.h +++ b/src/cJSON/cJSON.h @@ -81,7 +81,7 @@ then using the CJSON_API_VISIBILITY flag to "export" the same symbols the way CJ /* project version */ #define CJSON_VERSION_MAJOR 1 #define CJSON_VERSION_MINOR 7 -#define CJSON_VERSION_PATCH 13 +#define CJSON_VERSION_PATCH 14 #include @@ -176,8 +176,8 @@ CJSON_PUBLIC(cJSON_bool) cJSON_HasObjectItem(const cJSON *object, const char *st CJSON_PUBLIC(const char *) cJSON_GetErrorPtr(void); /* Check item type and return its value */ -CJSON_PUBLIC(char *) cJSON_GetStringValue(cJSON *item); -CJSON_PUBLIC(double) cJSON_GetNumberValue(cJSON *item); +CJSON_PUBLIC(char *) cJSON_GetStringValue(const cJSON * const item); +CJSON_PUBLIC(double) cJSON_GetNumberValue(const cJSON * const item); /* These functions check the type of an item */ CJSON_PUBLIC(cJSON_bool) cJSON_IsInvalid(const cJSON * const item); From e5ce78a9bda6a011fa42b4da5695e98ac693acd0 Mon Sep 17 00:00:00 2001 From: DL6ER Date: Mon, 26 Oct 2020 17:36:23 +0100 Subject: [PATCH 0185/1669] Redirect pi.hole/ -> pi.hole/admin Signed-off-by: DL6ER --- src/civetweb/civetweb.c | 35 +++++++++++++++++++++++++++++++++++ src/civetweb/civetweb.h | 1 + src/webserver/webserver.c | 14 ++++++++++++-- 3 files changed, 48 insertions(+), 2 deletions(-) diff --git a/src/civetweb/civetweb.c b/src/civetweb/civetweb.c index d34a7a247..d247f6015 100644 --- a/src/civetweb/civetweb.c +++ b/src/civetweb/civetweb.c @@ -13504,6 +13504,41 @@ redirect_to_https_port(struct mg_connection *conn, int ssl_index) } } +/**************************** Pi-hole modification ****************************/ +void redirect_elsewhere(struct mg_connection *conn, const char *target) +{ + char target_url[MG_BUF_LEN]; + int truncated = 0; + + conn->must_close = 1; + + /* Only redirect when the host is pi.hole */ + if (conn->host && strcmp(conn->host, "pi.hole") == 0) { + + /* Use "308 Permanent Redirect" */ + int redirect_code = 308; + + /* Create target URL */ + mg_snprintf( + conn, + &truncated, + target_url, + sizeof(target_url), + "%s", + target); + + /* Check overflow in location buffer (will not occur if MG_BUF_LEN + * is used as buffer size) */ + if (truncated) { + mg_send_http_error(conn, 500, "%s", "Redirect URL too long"); + return; + } + + /* Use redirect helper function */ + mg_send_http_redirect(conn, target_url, redirect_code); + } +} +/******************************************************************************/ static void handler_info_acquire(struct mg_handler_info *handler_info) diff --git a/src/civetweb/civetweb.h b/src/civetweb/civetweb.h index 7ed1177d3..61c99c92b 100644 --- a/src/civetweb/civetweb.h +++ b/src/civetweb/civetweb.h @@ -968,6 +968,7 @@ void my_send_http_error_headers(struct mg_connection *conn, // const char *additional_headers, long long content_length); void my_set_cookie_header(struct mg_connection *conn, const char *cookie_header); +void redirect_elsewhere(struct mg_connection *conn, const char *target); /********************************************************************************************/ diff --git a/src/webserver/webserver.c b/src/webserver/webserver.c index e0cd424b5..a5a4c3c73 100644 --- a/src/webserver/webserver.c +++ b/src/webserver/webserver.c @@ -28,6 +28,12 @@ static int print_simple(struct mg_connection *conn, void *input) return send_http(conn, "text/plain", input); } +static int redirect_handler(struct mg_connection *conn, void *input) +{ + redirect_elsewhere(conn, (const char*)input); + return 1; +} + static int log_http_message(const struct mg_connection *conn, const char *message) { logg_web(HTTP_INFO, "HTTP info: %s", message); @@ -106,8 +112,9 @@ void http_init(void) /* Start the server */ if((ctx = mg_start(&callbacks, NULL, options)) == NULL) { - logg("ERROR: Initializing HTTP library failed!\n" - " Web interface will not be available!"); + logg("ERROR: Start of webserver failed!. Web interface will not be available!"); + logg(" Check webroot %s and listening ports %s", + httpsettings.webroot, httpsettings.port); return; } @@ -117,6 +124,9 @@ void http_init(void) // Register API handler mg_set_request_handler(ctx, "/api", api_handler, NULL); + // Register / -> /admin handler + mg_set_request_handler(ctx, "/$", redirect_handler, httpsettings.webhome); + // Initialize PH7 engine and register PHP request handler init_ph7(); mg_set_request_handler(ctx, "**/$", ph7_handler, NULL); From 2e8e7d1febe6e59e8c8c0994de665dfe841b05a3 Mon Sep 17 00:00:00 2001 From: DL6ER Date: Tue, 27 Oct 2020 19:57:22 +0100 Subject: [PATCH 0186/1669] Add more API debugging output Signed-off-by: DL6ER --- src/api/stats.c | 26 +++++++++++++++++--------- src/database/gravity-db.h | 6 +++--- src/webserver/http-common.c | 2 +- src/webserver/webserver.c | 2 +- 4 files changed, 22 insertions(+), 14 deletions(-) diff --git a/src/api/stats.c b/src/api/stats.c index f1c0b286a..d95b807d2 100644 --- a/src/api/stats.c +++ b/src/api/stats.c @@ -8,24 +8,24 @@ * This file is copyright under the latest version of the EUPL. * Please see LICENSE file for your rights under this license. */ -#include "FTL.h" +#include "../FTL.h" #include "../webserver/http-common.h" #include "../webserver/json_macros.h" #include "routes.h" -#include "shmem.h" -#include "datastructure.h" +#include "../shmem.h" +#include "../datastructure.h" // read_setupVarsconf() -#include "setupVars.h" +#include "../setupVars.h" // logg() -#include "log.h" +#include "../log.h" // config struct -#include "config.h" +#include "../config.h" // in_auditlist() -#include "database/gravity-db.h" +#include "../database/gravity-db.h" // overTime data -#include "overTime.h" +#include "../overTime.h" // enum REGEX -#include "regex_r.h" +#include "../regex_r.h" // my_sqrt() #include "../math.h" @@ -183,6 +183,10 @@ int api_stats_top_domains(bool blocked, struct mg_connection *conn) get_privacy_level(NULL); if(config.privacylevel >= PRIVACY_HIDE_DOMAINS) { + if(config.debug & DEBUG_API) + logg("Not returning top domains: Privacy level is set to %i", + config.privacylevel); + // Minimum structure is // {"top_domains":[]} cJSON *json = JSON_NEW_OBJ(); @@ -337,6 +341,10 @@ int api_stats_top_clients(bool blocked, struct mg_connection *conn) get_privacy_level(NULL); if(config.privacylevel >= PRIVACY_HIDE_DOMAINS_CLIENTS) { + if(config.debug & DEBUG_API) + logg("Not returning top clients: Privacy level is set to %i", + config.privacylevel); + // Minimum structure is // {"top_clients":[]} cJSON *json = JSON_NEW_OBJ(); diff --git a/src/database/gravity-db.h b/src/database/gravity-db.h index 5d69fa7bf..5ce769f84 100644 --- a/src/database/gravity-db.h +++ b/src/database/gravity-db.h @@ -11,11 +11,11 @@ #define GRAVITY_H // global variable counters -#include "memory.h" +#include "../memory.h" // clients data structure -#include "datastructure.h" +#include "../datastructure.h" // enum http_method -#include "webserver/http-common.h" +#include "../webserver/http-common.h" // Definition of struct regex_data #include "../regex_r.h" diff --git a/src/webserver/http-common.c b/src/webserver/http-common.c index 4c3c40585..d98a73c3f 100644 --- a/src/webserver/http-common.c +++ b/src/webserver/http-common.c @@ -8,7 +8,7 @@ * This file is copyright under the latest version of the EUPL. * Please see LICENSE file for your rights under this license. */ -#include "FTL.h" +#include "../FTL.h" #include "http-common.h" #include "../config.h" #include "../log.h" diff --git a/src/webserver/webserver.c b/src/webserver/webserver.c index a5a4c3c73..b21258b38 100644 --- a/src/webserver/webserver.c +++ b/src/webserver/webserver.c @@ -124,7 +124,7 @@ void http_init(void) // Register API handler mg_set_request_handler(ctx, "/api", api_handler, NULL); - // Register / -> /admin handler + // Register / -> /admin redirect handler mg_set_request_handler(ctx, "/$", redirect_handler, httpsettings.webhome); // Initialize PH7 engine and register PHP request handler From 81a32048c7047e8ccb1197ecebae9190036a7c52 Mon Sep 17 00:00:00 2001 From: DL6ER Date: Fri, 6 Nov 2020 08:47:07 +0100 Subject: [PATCH 0187/1669] Restructure root redirection handler Signed-off-by: DL6ER --- src/civetweb/civetweb.c | 35 ----- src/civetweb/civetweb.h | 206 ++++++++++++++++-------- src/civetweb/response.inl | 318 ++++++++++++++++++++++++++++++++++++++ src/webserver/webserver.c | 63 +++++++- 4 files changed, 519 insertions(+), 103 deletions(-) create mode 100644 src/civetweb/response.inl diff --git a/src/civetweb/civetweb.c b/src/civetweb/civetweb.c index d247f6015..d34a7a247 100644 --- a/src/civetweb/civetweb.c +++ b/src/civetweb/civetweb.c @@ -13504,41 +13504,6 @@ redirect_to_https_port(struct mg_connection *conn, int ssl_index) } } -/**************************** Pi-hole modification ****************************/ -void redirect_elsewhere(struct mg_connection *conn, const char *target) -{ - char target_url[MG_BUF_LEN]; - int truncated = 0; - - conn->must_close = 1; - - /* Only redirect when the host is pi.hole */ - if (conn->host && strcmp(conn->host, "pi.hole") == 0) { - - /* Use "308 Permanent Redirect" */ - int redirect_code = 308; - - /* Create target URL */ - mg_snprintf( - conn, - &truncated, - target_url, - sizeof(target_url), - "%s", - target); - - /* Check overflow in location buffer (will not occur if MG_BUF_LEN - * is used as buffer size) */ - if (truncated) { - mg_send_http_error(conn, 500, "%s", "Redirect URL too long"); - return; - } - - /* Use redirect helper function */ - mg_send_http_redirect(conn, target_url, redirect_code); - } -} -/******************************************************************************/ static void handler_info_acquire(struct mg_handler_info *handler_info) diff --git a/src/civetweb/civetweb.h b/src/civetweb/civetweb.h index 61c99c92b..521ef8ae9 100644 --- a/src/civetweb/civetweb.h +++ b/src/civetweb/civetweb.h @@ -23,9 +23,9 @@ #ifndef CIVETWEB_HEADER_INCLUDED #define CIVETWEB_HEADER_INCLUDED -#define CIVETWEB_VERSION "1.12" +#define CIVETWEB_VERSION "1.13" #define CIVETWEB_VERSION_MAJOR (1) -#define CIVETWEB_VERSION_MINOR (12) +#define CIVETWEB_VERSION_MINOR (13) #define CIVETWEB_VERSION_PATCH (0) #ifndef CIVETWEB_API @@ -294,21 +294,8 @@ struct mg_callbacks { void **ssl_ctx, void *user_data); -#if defined(MG_LEGACY_INTERFACE) /* 2015-08-19 */ \ - || defined(MG_EXPERIMENTAL_INTERFACES) /* 2019-11-03 */ - /* Called when websocket request is received, before websocket handshake. - Return value: - 0: civetweb proceeds with websocket handshake. - 1: connection is closed immediately. - This callback is deprecated: Use mg_set_websocket_handler instead. */ - int (*websocket_connect)(const struct mg_connection *); - - /* Called when websocket handshake is successfully completed, and - connection is ready for data exchange. - This callback is deprecated: Use mg_set_websocket_handler instead. */ - void (*websocket_ready)(struct mg_connection *); - - /* Called when data frame has been received from the client. +#if defined(MG_EXPERIMENTAL_INTERFACES) /* 2019-11-03 */ + /* Called when data frame has been received from the peer. Parameters: bits: first byte of the websocket frame, see websocket RFC at http://tools.ietf.org/html/rfc6455, section 5.2 @@ -340,21 +327,22 @@ struct mg_callbacks { */ void (*connection_close)(const struct mg_connection *); - /* Called when civetweb is about to serve Lua server page, if - Lua support is enabled. + /* init_lua is called when civetweb is about to serve Lua server page. + exit_lua is called when the Lua processing is complete. + Both will work only if Lua support is enabled. Parameters: conn: current connection. - lua_context: "lua_State *" pointer. */ - void (*init_lua)(const struct mg_connection *conn, void *lua_context); + lua_context: "lua_State *" pointer. + context_flags: context type information as bitmask: + context_flags & 0x0F: (0-15) Lua environment type + */ + void (*init_lua)(const struct mg_connection *conn, + void *lua_context, + unsigned context_flags); + void (*exit_lua)(const struct mg_connection *conn, + void *lua_context, + unsigned context_flags); -#if defined(MG_LEGACY_INTERFACE) /* 2016-05-14 */ - /* Called when civetweb has uploaded a file to a temporary directory as a - result of mg_upload() call. - Note that mg_upload is deprecated. Use mg_handle_form_request instead. - Parameters: - file_name: full path name to the uploaded file. */ - void (*upload)(struct mg_connection *, const char *file_name); -#endif /* Called when civetweb is about to send HTTP error to the client. Implementing this callback allows to create custom error pages. @@ -381,6 +369,8 @@ struct mg_callbacks { void (*exit_context)(const struct mg_context *ctx); /* Called when a new worker thread is initialized. + * It is always called from the newly created thread and can be used to + * initialize thread local storage data. * Parameters: * ctx: context handle * thread_type: @@ -504,7 +494,8 @@ typedef int (*mg_request_handler)(struct mg_connection *conn, void *cbdata); /* mg_set_request_handler Sets or removes a URI mapping for a request handler. - This function uses mg_lock_context internally. + This function waits until a removing/updating handler becomes unused, so + do not call from the handler itself. URI's are ordered and prefixed URI's are supported. For example, consider two URIs: /a/b and /a @@ -683,16 +674,6 @@ CIVETWEB_API int mg_get_request_link(const struct mg_connection *conn, char *buf, size_t buflen); -#if defined(MG_LEGACY_INTERFACE) /* 2014-02-21 */ -/* Return array of strings that represent valid configuration options. - For each option, option name and default value is returned, i.e. the - number of entries in the array equals to number_of_options x 2. - Array is NULL terminated. */ -/* Deprecated: Use mg_get_valid_options instead. */ -CIVETWEB_API const char **mg_get_valid_option_names(void); -#endif - - struct mg_option { const char *name; int type; @@ -859,19 +840,15 @@ CIVETWEB_API int mg_websocket_client_write(struct mg_connection *conn, with websockets only. Invoke this before mg_write or mg_printf when communicating with a websocket if your code has server-initiated communication as well as - communication in direct response to a message. */ + communication in direct response to a message. + Do not acquire this lock while holding mg_lock_context(). */ CIVETWEB_API void mg_lock_connection(struct mg_connection *conn); CIVETWEB_API void mg_unlock_connection(struct mg_connection *conn); -#if defined(MG_LEGACY_INTERFACE) /* 2014-06-21 */ -#define mg_lock mg_lock_connection -#define mg_unlock mg_unlock_connection -#endif - - /* Lock server context. This lock may be used to protect resources - that are shared between different connection/worker threads. */ + that are shared between different connection/worker threads. + If the given context is not server, these functions do nothing. */ CIVETWEB_API void mg_lock_context(struct mg_context *ctx); CIVETWEB_API void mg_unlock_context(struct mg_context *ctx); @@ -968,7 +945,6 @@ void my_send_http_error_headers(struct mg_connection *conn, // const char *additional_headers, long long content_length); void my_set_cookie_header(struct mg_connection *conn, const char *cookie_header); -void redirect_elsewhere(struct mg_connection *conn, const char *target); /********************************************************************************************/ @@ -1136,10 +1112,9 @@ CIVETWEB_API int mg_get_var(const char *data, var_name: variable name to decode from the buffer dst: destination buffer for the decoded variable dst_len: length of the destination buffer - occurrence: which occurrence of the variable, 0 is the first, 1 the - second... - this makes it possible to parse a query like - b=x&a=y&a=z which will have occurrence values b:0, a:0 and a:1 + occurrence: which occurrence of the variable, 0 is the 1st, 1 the 2nd, ... + this makes it possible to parse a query like + b=x&a=y&a=z which will have occurrence values b:0, a:0 and a:1 Return: On success, length of the decoded variable. @@ -1158,6 +1133,39 @@ CIVETWEB_API int mg_get_var2(const char *data, size_t occurrence); +/* Split form encoded data into a list of key value pairs. + A form encoded input might be a query string, the body of a + x-www-form-urlencoded POST request or any other data with this + structure: "keyName1=value1&keyName2=value2&keyName3=value3". + Values might be percent-encoded - this function will transform + them to the unencoded characters. + The input string is modified by this function: To split the + "query_string" member of struct request_info, create a copy first + (e.g., using strdup). + The function itself does not allocate memory. Thus, it is not + required to free any pointer returned from this function. + The output list of is limited to MG_MAX_FORM_FIELDS name-value- + pairs. The default value is reasonably oversized for typical + applications, however, for special purpose systems it might be + required to increase this value at compile time. + + Parameters: + data: form encoded iput string. Will be modified by this function. + form_fields: output list of name/value-pairs. A buffer with a size + specified by num_form_fields must be provided by the + caller. + num_form_fields: Size of provided form_fields buffer in number of + "struct mg_header" elements. + + Return: + On success: number of form_fields filled + On error: + -1 (parameter error). */ +CIVETWEB_API int mg_split_form_urlencoded(char *data, + struct mg_header *form_fields, + unsigned num_form_fields); + + /* Fetch value of certain cookie variable into the destination buffer. Destination buffer is guaranteed to be '\0' - terminated. In case of @@ -1210,16 +1218,6 @@ mg_download(const char *host, CIVETWEB_API void mg_close_connection(struct mg_connection *conn); -#if defined(MG_LEGACY_INTERFACE) /* 2016-05-14 */ -/* File upload functionality. Each uploaded file gets saved into a temporary - file and MG_UPLOAD event is sent. - Return number of uploaded files. - Deprecated: Use mg_handle_form_request instead. */ -CIVETWEB_API int mg_upload(struct mg_connection *conn, - const char *destination_dir); -#endif - - /* This structure contains callback functions for handling form fields. It is used as an argument to mg_handle_form_request. */ struct mg_form_data_handler { @@ -1471,11 +1469,23 @@ mg_connect_client_secure(const struct mg_client_options *client_options, size_t error_buffer_size); +CIVETWEB_API struct mg_connection *mg_connect_websocket_client_secure( + const struct mg_client_options *client_options, + char *error_buffer, + size_t error_buffer_size, + const char *path, + const char *origin, + mg_websocket_data_handler data_func, + mg_websocket_close_handler close_func, + void *user_data); + + #if defined(MG_LEGACY_INTERFACE) /* 2019-11-02 */ enum { TIMEOUT_INFINITE = -1 }; #endif enum { MG_TIMEOUT_INFINITE = -1 }; + /* Wait for a response from the server Parameters: conn: connection @@ -1493,6 +1503,76 @@ CIVETWEB_API int mg_get_response(struct mg_connection *conn, int timeout); +/* mg_response_header_* functions can be used from server callbacks + * to prepare HTTP server response headers. Using this function will + * allow a callback to work with HTTP/1.x and HTTP/2. + */ + +/* Initialize a new HTTP response + * Parameters: + * conn: Current connection handle. + * status: HTTP status code (e.g., 200 for "OK"). + * Return: + * 0: ok + * -1: parameter error + * -2: invalid connection type + * -3: invalid connection status + */ +CIVETWEB_API int mg_response_header_start(struct mg_connection *conn, + int status); + + +/* Add a new HTTP response header line + * Parameters: + * conn: Current connection handle. + * header: Header name. + * value: Header value. + * value_len: Length of header value, excluding the terminating zero. + * Use -1 for "strlen(value)". + * Return: + * 0: ok + * -1: parameter error + * -2: invalid connection type + * -3: invalid connection status + * -4: too many headers + * -5: out of memory + */ +CIVETWEB_API int mg_response_header_add(struct mg_connection *conn, + const char *header, + const char *value, + int value_len); + + +/* Add a complete header string (key + value). + * This function is less efficient as compared to mg_response_header_add, + * and should only be used to convert complete HTTP/1.x header lines. + * Parameters: + * conn: Current connection handle. + * http1_headers: Header line(s) in the form "name: value\r\n". + * Return: + * >=0: no error, number of header lines added + * -1: parameter error + * -2: invalid connection type + * -3: invalid connection status + * -4: too many headers + * -5: out of memory + */ +CIVETWEB_API int mg_response_header_add_lines(struct mg_connection *conn, + const char *http1_headers); + + +/* Send http response + * Parameters: + * conn: Current connection handle. + * Return: + * 0: ok + * -1: parameter error + * -2: invalid connection type + * -3: invalid connection status + */ +CIVETWEB_API int mg_response_header_send(struct mg_connection *conn); + + /* Check which features where set when the civetweb library has been compiled. The function explicitly addresses compile time defines used when building the library - it does not mean, the feature has been initialized using a diff --git a/src/civetweb/response.inl b/src/civetweb/response.inl new file mode 100644 index 000000000..68dbd0fc5 --- /dev/null +++ b/src/civetweb/response.inl @@ -0,0 +1,318 @@ +/* response.inl + * + * Bufferring for HTTP headers for HTTP response. + * This function are only intended to be used at the server side. + * Optional for HTTP/1.0 and HTTP/1.1, mandatory for HTTP/2. + * + * This file is part of the CivetWeb project. + */ + +#if defined(NO_RESPONSE_BUFFERING) && defined(USE_HTTP2) +#error "HTTP2 currently works only if NO_RESPONSE_BUFFERING is not set" +#endif + + +/* Internal function to free header list */ +static void +free_buffered_response_header_list(struct mg_connection *conn) +{ +#if !defined(NO_RESPONSE_BUFFERING) + while (conn->response_info.num_headers > 0) { + conn->response_info.num_headers--; + mg_free((void *)conn->response_info + .http_headers[conn->response_info.num_headers] + .name); + conn->response_info.http_headers[conn->response_info.num_headers].name = + 0; + mg_free((void *)conn->response_info + .http_headers[conn->response_info.num_headers] + .value); + conn->response_info.http_headers[conn->response_info.num_headers] + .value = 0; + } +#else + (void)conn; /* Nothing to do */ +#endif +} + + +/* Send first line of HTTP/1.x response */ +static void +send_http1_response_status_line(struct mg_connection *conn) +{ + /* mg_get_response_code_text will never return NULL */ + const char *txt = mg_get_response_code_text(conn, conn->status_code); + mg_printf(conn, + "HTTP/%s %i %s\r\n", + conn->request_info.http_version, + conn->status_code, + txt); +} + + +/* Initialize a new HTTP response + * Parameters: + * conn: Current connection handle. + * status: HTTP status code (e.g., 200 for "OK"). + * Return: + * 0: ok + * -1: parameter error + * -2: invalid connection type + * -3: invalid connection status + */ +int +mg_response_header_start(struct mg_connection *conn, int status) +{ + if ((conn == NULL) || (status < 100) || (status > 999)) { + /* Parameter error */ + return -1; + } + if ((conn->connection_type != CONNECTION_TYPE_REQUEST) + || (conn->protocol_type == PROTOCOL_TYPE_WEBSOCKET)) { + /* Only allowed in server context */ + return -2; + } + if (conn->request_state != 0) { + /* only allowed if nothing was sent up to now */ + return -3; + } + conn->status_code = status; + conn->request_state = 1; + + /* Buffered response is stored, unbuffered response will be sent directly, + * but we can only send HTTP/1.x response here */ +#if !defined(NO_RESPONSE_BUFFERING) + free_buffered_response_header_list(conn); +#else + send_http1_response_status_line(conn); + conn->request_state = 1; /* Reset from 10 to 1 */ +#endif + + return 0; +} + + +/* Add a new HTTP response header line + * Parameters: + * conn: Current connection handle. + * header: Header name. + * value: Header value. + * value_len: Length of header value, excluding the terminating zero. + * Use -1 for "strlen(value)". + * Return: + * 0: ok + * -1: parameter error + * -2: invalid connection type + * -3: invalid connection status + * -4: too many headers + * -5: out of memory + */ +int +mg_response_header_add(struct mg_connection *conn, + const char *header, + const char *value, + int value_len) +{ +#if !defined(NO_RESPONSE_BUFFERING) + int hidx; +#endif + + if ((conn == NULL) || (header == NULL) || (value == NULL)) { + /* Parameter error */ + return -1; + } + if ((conn->connection_type != CONNECTION_TYPE_REQUEST) + || (conn->protocol_type == PROTOCOL_TYPE_WEBSOCKET)) { + /* Only allowed in server context */ + return -2; + } + if (conn->request_state != 1) { + /* only allowed if mg_response_header_start has been called before */ + return -3; + } + +#if !defined(NO_RESPONSE_BUFFERING) + hidx = conn->response_info.num_headers; + if (hidx >= MG_MAX_HEADERS) { + /* Too many headers */ + return -4; + } + + /* Alloc new element */ + conn->response_info.http_headers[hidx].name = + mg_strdup_ctx(header, conn->phys_ctx); + if (value_len >= 0) { + char *hbuf = mg_malloc_ctx((unsigned)value_len + 1, conn->phys_ctx); + if (hbuf) { + memcpy(hbuf, value, (unsigned)value_len); + hbuf[value_len] = 0; + } + conn->response_info.http_headers[hidx].value = hbuf; + } else { + conn->response_info.http_headers[hidx].value = + mg_strdup_ctx(value, conn->phys_ctx); + } + + if ((conn->response_info.http_headers[hidx].name == 0) + || (conn->response_info.http_headers[hidx].value == 0)) { + /* Out of memory */ + mg_free((void *)conn->response_info.http_headers[hidx].name); + conn->response_info.http_headers[hidx].name = 0; + mg_free((void *)conn->response_info.http_headers[hidx].value); + conn->response_info.http_headers[hidx].value = 0; + return -5; + } + + /* OK, header stored */ + conn->response_info.num_headers++; + +#else + if (value_len >= 0) { + mg_printf(conn, "%s: %.*s\r\n", header, (int)value_len, value); + } else { + mg_printf(conn, "%s: %s\r\n", header, value); + } + conn->request_state = 1; /* Reset from 10 to 1 */ +#endif + + return 0; +} + + +/* forward */ +static int parse_http_headers(char **buf, struct mg_header hdr[MG_MAX_HEADERS]); + + +/* Add a complete header string (key + value). + * Parameters: + * conn: Current connection handle. + * http1_headers: Header line(s) in the form "name: value". + * Return: + * >=0: no error, number of header lines added + * -1: parameter error + * -2: invalid connection type + * -3: invalid connection status + * -4: too many headers + * -5: out of memory + */ +int +mg_response_header_add_lines(struct mg_connection *conn, + const char *http1_headers) +{ + struct mg_header add_hdr[MG_MAX_HEADERS]; + int num_hdr, i, ret; + char *workbuffer, *parse; + + /* We need to work on a copy of the work buffer, sice parse_http_headers + * will modify */ + workbuffer = mg_strdup_ctx(http1_headers, conn->phys_ctx); + if (!workbuffer) { + /* Out of memory */ + return -5; + } + + /* Call existing method to split header buffer */ + parse = workbuffer; + num_hdr = parse_http_headers(&parse, add_hdr); + ret = num_hdr; + + for (i = 0; i < num_hdr; i++) { + int lret = + mg_response_header_add(conn, add_hdr[i].name, add_hdr[i].value, -1); + if ((ret > 0) && (lret < 0)) { + /* Store error return value */ + ret = lret; + } + } + + /* mg_response_header_add created a copy, so we can free the original */ + mg_free(workbuffer); + return ret; +} + + +#if defined USE_HTTP2 +static int http2_send_response_headers(struct mg_connection *conn); +#endif + + +/* Send http response + * Parameters: + * conn: Current connection handle. + * Return: + * 0: ok + * -1: parameter error + * -2: invalid connection type + * -3: invalid connection status + */ +int +mg_response_header_send(struct mg_connection *conn) +{ +#if !defined(NO_RESPONSE_BUFFERING) + int i; + int has_date = 0; + int has_connection = 0; +#endif + + if (conn == NULL) { + /* Parameter error */ + return -1; + } + if ((conn->connection_type != CONNECTION_TYPE_REQUEST) + || (conn->protocol_type == PROTOCOL_TYPE_WEBSOCKET)) { + /* Only allowed in server context */ + return -2; + } + if (conn->request_state != 1) { + /* only allowed if mg_response_header_start has been called before */ + return -3; + } + + /* State: 2 */ + conn->request_state = 2; + +#if !defined(NO_RESPONSE_BUFFERING) + if (conn->protocol_type == PROTOCOL_TYPE_HTTP2) { +#if defined USE_HTTP2 + int ret = http2_send_response_headers(conn); + return ret ? 0 : 0; /* todo */ +#else + return -2; +#endif + } + + /* Send */ + send_http1_response_status_line(conn); + for (i = 0; i < conn->response_info.num_headers; i++) { + mg_printf(conn, + "%s: %s\r\n", + conn->response_info.http_headers[i].name, + conn->response_info.http_headers[i].value); + + /* Check for some special headers */ + if (!mg_strcasecmp("Date", conn->response_info.http_headers[i].name)) { + has_date = 1; + } + if (!mg_strcasecmp("Connection", + conn->response_info.http_headers[i].name)) { + has_connection = 1; + } + } + + if (!has_date) { + time_t curtime = time(NULL); + char date[64]; + gmt_time_string(date, sizeof(date), &curtime); + mg_printf(conn, "Date: %s\r\n", date); + } + if (!has_connection) { + mg_printf(conn, "Connection: %s\r\n", suggest_connection_header(conn)); + } +#endif + + mg_write(conn, "\r\n", 2); + conn->request_state = 3; + + /* ok */ + return 0; +} diff --git a/src/webserver/webserver.c b/src/webserver/webserver.c index b21258b38..c3caa2572 100644 --- a/src/webserver/webserver.c +++ b/src/webserver/webserver.c @@ -8,7 +8,7 @@ * This file is copyright under the latest version of the EUPL. * Please see LICENSE file for your rights under this license. */ -#include "FTL.h" +#include "../FTL.h" #include "../api/routes.h" // send_http() #include "http-common.h" @@ -28,10 +28,63 @@ static int print_simple(struct mg_connection *conn, void *input) return send_http(conn, "text/plain", input); } -static int redirect_handler(struct mg_connection *conn, void *input) +static int redirect_root_handler(struct mg_connection *conn, void *input) { - redirect_elsewhere(conn, (const char*)input); - return 1; + // Get requested host + const char *host = mg_get_header(conn, "Host"); + size_t host_len = 0; + if (host != NULL) + { + // If the "Host" is an IPv6 address, like [::1], parse until ] is found. + if (*host == '[') + { + char *pos = strchr(host, ']'); + if (!pos) + { + // Malformed hostname starts with '[', but no ']' found + logg("ERROR: Host name format error '[' without ']'"); + return 0; + } + /* terminate after ']' */ + host_len = (size_t)(pos + 1 - host); + } + else + { + char *pos = strchr(host, ':'); + if (pos != NULL) + { + // A ':' separates hostname and port number + host_len = (size_t)(pos - host); + } + else + { + // Host header only contains the host name iteself + host_len = strlen(host); + } + } + } + + // API debug logging + if(config.debug & DEBUG_API) + { + logg("Host header: \"%s\", extracted host: \"%.*s\"", host, (int)host_len, host); + + // Get requested URI + const struct mg_request_info *request = mg_get_request_info(conn); + const char *uri = request->local_uri; + + logg("URI: %s", uri); + } + + // 308 Permanent Redirect from http://pi.hole -> http://pi.hole/admin + if(host != NULL && strncmp(host, "pi.hole", host_len) == 0) + { + mg_send_http_redirect(conn, httpsettings.webhome, 308); + return 1; + } + + // else: Not redirecting + return 0; } static int log_http_message(const struct mg_connection *conn, const char *message) @@ -125,7 +178,7 @@ void http_init(void) mg_set_request_handler(ctx, "/api", api_handler, NULL); // Register / -> /admin redirect handler - mg_set_request_handler(ctx, "/$", redirect_handler, httpsettings.webhome); + mg_set_request_handler(ctx, "/$", redirect_root_handler, NULL); // Initialize PH7 engine and register PHP request handler init_ph7(); From 92ec8e8d70c7bf5595d2bdd76977f2eeb76d460e Mon Sep 17 00:00:00 2001 From: DL6ER Date: Fri, 6 Nov 2020 08:47:07 +0100 Subject: [PATCH 0188/1669] Update civetweb v1.12 -> v1.13 (and simplify auth-cookie handling) Signed-off-by: DL6ER --- src/api/auth.c | 35 +- src/api/settings.c | 2 +- src/api/version.c | 6 +- src/civetweb/civetweb.c | 4728 ++++++++++++++++++++--------------- src/civetweb/civetweb.h | 6 +- src/webserver/http-common.c | 2 + 6 files changed, 2669 insertions(+), 2110 deletions(-) diff --git a/src/api/auth.c b/src/api/auth.c index 49bada035..1ceba0f21 100644 --- a/src/api/auth.c +++ b/src/api/auth.c @@ -8,14 +8,14 @@ * This file is copyright under the latest version of the EUPL. * Please see LICENSE file for your rights under this license. */ -#include "FTL.h" +#include "../FTL.h" #include "../webserver/http-common.h" #include "../webserver/json_macros.h" #include "routes.h" -#include "log.h" -#include "config.h" -// read_setupVarsconf() -#include "setupVars.h" +#include "../log.h" +#include "../config.h" +// read_se../tupVarsconf() +#include "../setupVars.h" static struct { bool used; @@ -77,15 +77,12 @@ int check_client_auth(struct mg_connection *conn) auth_data[num].valid_until = now + httpsettings.session_timeout; // Update user cookie - char *buffer = NULL; - if(asprintf(&buffer, - "Set-Cookie: user_id=%u; Path=/; Max-Age=%u\r\n", - num, httpsettings.session_timeout) < 0) + if(snprintf(pi_hole_extra_headers, sizeof(pi_hole_extra_headers), + "Set-Cookie: user_id=%u; Path=/; Max-Age=%u\r\n", + num, httpsettings.session_timeout) < 0) { return send_json_error(conn, 500, "internal_error", "Internal server error", NULL); } - my_set_cookie_header(conn, buffer); - free(buffer); if(config.debug & DEBUG_API) { @@ -201,15 +198,12 @@ int api_auth(struct mg_connection *conn) if(config.debug & DEBUG_API) logg("API Authentification: OK, localhost does not need auth."); // We still have to send a cookie for the web interface to be happy - char *buffer = NULL; - if(asprintf(&buffer, + if(snprintf(pi_hole_extra_headers, sizeof(pi_hole_extra_headers), "Set-Cookie: user_id=%u; Path=/; Max-Age=%u\r\n", API_MAX_CLIENTS, API_SESSION_EXPIRE) < 0) { return send_json_error(conn, 500, "internal_error", "Internal server error", NULL); } - my_set_cookie_header(conn, buffer); - free(buffer); } if(user_id > -1 && method == HTTP_GET) { @@ -219,15 +213,12 @@ int api_auth(struct mg_connection *conn) cJSON *json = JSON_NEW_OBJ(); JSON_OBJ_REF_STR(json, "status", "success"); // Ten minutes validity - char *buffer = NULL; - if(asprintf(&buffer, + if(snprintf(pi_hole_extra_headers, sizeof(pi_hole_extra_headers), "Set-Cookie: user_id=%u; Path=/; Max-Age=%u\r\n", user_id, API_SESSION_EXPIRE) < 0) { return send_json_error(conn, 500, "internal_error", "Internal server error", NULL); } - my_set_cookie_header(conn, buffer); - free(buffer); return send_json_success(conn); } @@ -242,14 +233,12 @@ int api_auth(struct mg_connection *conn) free(auth_data[user_id].remote_addr); auth_data[user_id].remote_addr = NULL; - const char *buffer = "Set-Cookie: user_id=deleted; Path=/; Max-Age=-1\r\n"; - my_set_cookie_header(conn, buffer); + strncpy(pi_hole_extra_headers, "Set-Cookie: user_id=deleted; Path=/; Max-Age=-1\r\n", sizeof(pi_hole_extra_headers)); return send_json_success(conn); } else { - const char *buffer = "Set-Cookie: user_id=deleted; Path=/; Max-Age=-1\r\n"; - my_set_cookie_header(conn, buffer); + strncpy(pi_hole_extra_headers, "Set-Cookie: user_id=deleted; Path=/; Max-Age=-1\r\n", sizeof(pi_hole_extra_headers)); return send_json_unauthorized(conn); } } diff --git a/src/api/settings.c b/src/api/settings.c index f8bda43ac..cac6525be 100644 --- a/src/api/settings.c +++ b/src/api/settings.c @@ -8,7 +8,7 @@ * This file is copyright under the latest version of the EUPL. * Please see LICENSE file for your rights under this license. */ -#include "FTL.h" +#include "../FTL.h" #include "../webserver/http-common.h" #include "../webserver/json_macros.h" #include "routes.h" diff --git a/src/api/version.c b/src/api/version.c index 871239a37..f72b24fa5 100644 --- a/src/api/version.c +++ b/src/api/version.c @@ -8,13 +8,13 @@ * This file is copyright under the latest version of the EUPL. * Please see LICENSE file for your rights under this license. */ -#include "FTL.h" +#include "../FTL.h" #include "../webserver/http-common.h" #include "../webserver/json_macros.h" #include "routes.h" // get_FTL_version() -#include "log.h" -#include "version.h" +#include "../log.h" +#include "../version.h" int api_version(struct mg_connection *conn) { diff --git a/src/civetweb/civetweb.c b/src/civetweb/civetweb.c index d34a7a247..ad0eb5514 100644 --- a/src/civetweb/civetweb.c +++ b/src/civetweb/civetweb.c @@ -50,7 +50,7 @@ #define _CRT_SECURE_NO_WARNINGS /* Disable deprecation warning in VS2005 */ #endif #if !defined(_WIN32_WINNT) /* defined for tdm-gcc so we can use getnameinfo */ -#define _WIN32_WINNT 0x0501 +#define _WIN32_WINNT 0x0502 #endif #else #if !defined(_GNU_SOURCE) @@ -59,7 +59,7 @@ #if defined(__linux__) && !defined(_XOPEN_SOURCE) #define _XOPEN_SOURCE 600 /* For flockfile() on Linux */ #endif -#if defined(__LSB_VERSION__) +#if defined(__LSB_VERSION__) || defined(__sun) #define NEED_TIMEGM #define NO_THREAD_NAME #endif @@ -205,7 +205,7 @@ mg_static_assert(sizeof(void *) >= sizeof(int), "data type size check"); #if defined(USE_STACK_SIZE) && (USE_STACK_SIZE > 1) #define ZEPHYR_STACK_SIZE USE_STACK_SIZE #else -#define ZEPHYR_STACK_SIZE 8096 +#define ZEPHYR_STACK_SIZE (1024 * 16) #endif K_THREAD_STACK_DEFINE(civetweb_main_stack, ZEPHYR_STACK_SIZE); @@ -234,8 +234,8 @@ static void DEBUG_TRACE_FUNC(const char *func, DEBUG_TRACE_FUNC(__func__, __LINE__, fmt, __VA_ARGS__) #define NEED_DEBUG_TRACE_FUNC -#ifndef DEBUG_TRACE_STREAM -# define DEBUG_TRACE_STREAM stdout +#if !defined(DEBUG_TRACE_STREAM) +#define DEBUG_TRACE_STREAM stdout #endif #else @@ -433,6 +433,20 @@ _civet_safe_clock_gettime(int clk_id, struct timespec *t) #endif +#if !defined(_WIN32) +/* Unix might return different error codes indicating to try again. + * For Linux EAGAIN==EWOULDBLOCK, maybe EAGAIN!=EWOULDBLOCK is history from + * decades ago, but better check both and let the compile optimize it. */ +#define ERROR_TRY_AGAIN(err) \ + (((err) == EAGAIN) || ((err) == EWOULDBLOCK) || ((err) == EINTR)) +#endif + +#if defined(USE_ZLIB) +#include "zconf.h" +#include "zlib.h" +#endif + + /********************************************************************/ /* CivetWeb configuration defines */ /********************************************************************/ @@ -570,30 +584,38 @@ typedef long off_t; #define ERRNO ((int)(GetLastError())) #define NO_SOCKLEN_T + #if defined(_WIN64) || defined(__MINGW64__) #if !defined(SSL_LIB) + #if defined(OPENSSL_API_1_1) #define SSL_LIB "libssl-1_1-x64.dll" -#else /* OPENSSL_API_1_1 */ -#define SSL_LIB "ssleay64.dll" -#endif /* OPENSSL_API_1_1 */ -#endif /* SSL_LIB */ -#if !defined(CRYPTO_LIB) -#if defined(OPENSSL_API_1_1) #define CRYPTO_LIB "libcrypto-1_1-x64.dll" -#else /* OPENSSL_API_1_1 */ -#define CRYPTO_LIB "libeay64.dll" #endif /* OPENSSL_API_1_1 */ -#endif /* CRYPTO_LIB */ + +#if defined(OPENSSL_API_1_0) +#define SSL_LIB "ssleay64.dll" +#define CRYPTO_LIB "libeay64.dll" +#endif /* OPENSSL_API_1_0 */ + +#endif #else /* defined(_WIN64) || defined(__MINGW64__) */ #if !defined(SSL_LIB) + +#if defined(OPENSSL_API_1_1) +#define SSL_LIB "libssl-1_1.dll" +#define CRYPTO_LIB "libcrypto-1_1.dll" +#endif /* OPENSSL_API_1_1 */ + +#if defined(OPENSSL_API_1_0) #define SSL_LIB "ssleay32.dll" -#endif /* SSL_LIB */ -#if !defined(CRYPTO_LIB) #define CRYPTO_LIB "libeay32.dll" -#endif /* CRYPTO_LIB */ +#endif /* OPENSSL_API_1_0 */ + +#endif /* SSL_LIB */ #endif /* defined(_WIN64) || defined(__MINGW64__) */ + #define O_NONBLOCK (0) #if !defined(W_OK) #define W_OK (2) /* http://msdn.microsoft.com/en-us/library/1w06ktdy.aspx */ @@ -789,8 +811,7 @@ static void path_to_unicode(const struct mg_connection *conn, struct mg_file; -static const char * -mg_fgets(char *buf, size_t size, struct mg_file *filep, char **p); +static const char *mg_fgets(char *buf, size_t size, struct mg_file *filep); /* POSIX dirent interface */ @@ -1105,16 +1126,16 @@ gmtime_s(const time_t *ptime, struct tm *ptm) } -static int mg_atomic_inc(volatile int *addr); -static struct tm tm_array[MAX_WORKER_THREADS]; -static int tm_index = 0; +static ptrdiff_t mg_atomic_inc(volatile ptrdiff_t *addr); +static struct tm tm_array[MAX_WORKER_THREADS]; /* Must be 2^n */ +static volatile ptrdiff_t tm_index = 0; FUNCTION_MAY_BE_UNUSED static struct tm * localtime(const time_t *ptime) { - int i = mg_atomic_inc(&tm_index) % (sizeof(tm_array) / sizeof(tm_array[0])); + ptrdiff_t i = mg_atomic_inc(&tm_index) % ARRAY_SIZE(tm_array); return localtime_s(ptime, tm_array + i); } @@ -1123,7 +1144,7 @@ FUNCTION_MAY_BE_UNUSED static struct tm * gmtime(const time_t *ptime) { - int i = mg_atomic_inc(&tm_index) % ARRAY_SIZE(tm_array); + ptrdiff_t i = mg_atomic_inc(&tm_index) % ARRAY_SIZE(tm_array); return gmtime_s(ptime, tm_array + i); } @@ -1236,16 +1257,27 @@ mg_global_unlock(void) } +#if defined(_WIN64) +mg_static_assert(SIZE_MAX == 0xFFFFFFFFFFFFFFFFu, "Mismatch for atomic types"); +#elif defined(_WIN32) +mg_static_assert(SIZE_MAX == 0xFFFFFFFFu, "Mismatch for atomic types"); +#endif + + +/* Atomic functions working on ptrdiff_t ("signed size_t"). + * Operations: Increment, Decrement, Add, Maximum. + * Up to size_t, they do not an atomic "load" operation. + */ FUNCTION_MAY_BE_UNUSED -static int -mg_atomic_inc(volatile int *addr) +static ptrdiff_t +mg_atomic_inc(volatile ptrdiff_t *addr) { - int ret; -#if defined(_WIN32) && !defined(NO_ATOMICS) - /* Depending on the SDK, this function uses either - * (volatile unsigned int *) or (volatile LONG *), - * so whatever you use, the other SDK is likely to raise a warning. */ - ret = InterlockedIncrement((volatile long *)addr); + ptrdiff_t ret; + +#if defined(_WIN64) && !defined(NO_ATOMICS) + ret = InterlockedIncrement64(addr); +#elif defined(_WIN32) && !defined(NO_ATOMICS) + ret = InterlockedIncrement(addr); #elif defined(__GNUC__) \ && ((__GNUC__ > 4) || ((__GNUC__ == 4) && (__GNUC_MINOR__ > 0))) \ && !defined(NO_ATOMICS) @@ -1260,15 +1292,15 @@ mg_atomic_inc(volatile int *addr) FUNCTION_MAY_BE_UNUSED -static int -mg_atomic_dec(volatile int *addr) +static ptrdiff_t +mg_atomic_dec(volatile ptrdiff_t *addr) { - int ret; -#if defined(_WIN32) && !defined(NO_ATOMICS) - /* Depending on the SDK, this function uses either - * (volatile unsigned int *) or (volatile LONG *), - * so whatever you use, the other SDK is likely to raise a warning. */ - ret = InterlockedDecrement((volatile long *)addr); + ptrdiff_t ret; + +#if defined(_WIN64) && !defined(NO_ATOMICS) + ret = InterlockedDecrement64(addr); +#elif defined(_WIN32) && !defined(NO_ATOMICS) + ret = InterlockedDecrement(addr); #elif defined(__GNUC__) \ && ((__GNUC__ > 4) || ((__GNUC__ == 4) && (__GNUC_MINOR__ > 0))) \ && !defined(NO_ATOMICS) @@ -1283,12 +1315,95 @@ mg_atomic_dec(volatile int *addr) #if defined(USE_SERVER_STATS) +static ptrdiff_t +mg_atomic_add(volatile ptrdiff_t *addr, ptrdiff_t value) +{ + ptrdiff_t ret; + +#if defined(_WIN64) && !defined(NO_ATOMICS) + ret = InterlockedAdd64(addr, value); +#elif defined(_WIN32) && !defined(NO_ATOMICS) + ret = InterlockedExchangeAdd(addr, value) + value; +#elif defined(__GNUC__) \ + && ((__GNUC__ > 4) || ((__GNUC__ == 4) && (__GNUC_MINOR__ > 0))) \ + && !defined(NO_ATOMICS) + ret = __sync_add_and_fetch(addr, value); +#else + mg_global_lock(); + *addr += value; + ret = (*addr); + mg_global_unlock(); +#endif + return ret; +} + + +FUNCTION_MAY_BE_UNUSED +static ptrdiff_t +mg_atomic_compare_and_swap(volatile ptrdiff_t *addr, + ptrdiff_t oldval, + ptrdiff_t newval) +{ + ptrdiff_t ret; + +#if defined(_WIN64) && !defined(NO_ATOMICS) + ret = InterlockedCompareExchange64(addr, newval, oldval); +#elif defined(_WIN32) && !defined(NO_ATOMICS) + ret = InterlockedCompareExchange(addr, newval, oldval); +#elif defined(__GNUC__) \ + && ((__GNUC__ > 4) || ((__GNUC__ == 4) && (__GNUC_MINOR__ > 0))) \ + && !defined(NO_ATOMICS) + ret = __sync_val_compare_and_swap(addr, oldval, newval); +#else + mg_global_lock(); + ret = *addr; + if ((ret != newval) && (ret == oldval)) { + *addr = newval; + } + mg_global_unlock(); +#endif + return ret; +} + + +static void +mg_atomic_max(volatile ptrdiff_t *addr, ptrdiff_t value) +{ + register ptrdiff_t tmp = *addr; + +#if defined(_WIN64) && !defined(NO_ATOMICS) + while (tmp < value) { + tmp = InterlockedCompareExchange64(addr, value, tmp); + } +#elif defined(_WIN32) && !defined(NO_ATOMICS) + while (tmp < value) { + tmp = InterlockedCompareExchange(addr, value, tmp); + } +#elif defined(__GNUC__) \ + && ((__GNUC__ > 4) || ((__GNUC__ == 4) && (__GNUC_MINOR__ > 0))) \ + && !defined(NO_ATOMICS) + while (tmp < value) { + tmp = __sync_val_compare_and_swap(addr, tmp, value); + } +#else + mg_global_lock(); + if (*addr < value) { + *addr = value; + } + mg_global_unlock(); +#endif +} + + static int64_t -mg_atomic_add(volatile int64_t *addr, int64_t value) +mg_atomic_add64(volatile int64_t *addr, int64_t value) { int64_t ret; + #if defined(_WIN64) && !defined(NO_ATOMICS) ret = InterlockedAdd64(addr, value); +#elif defined(_WIN32) && !defined(NO_ATOMICS) + ret = InterlockedExchangeAdd64(addr, value) + value; #elif defined(__GNUC__) \ && ((__GNUC__ > 4) || ((__GNUC__ == 4) && (__GNUC_MINOR__ > 0))) \ && !defined(NO_ATOMICS) @@ -1317,9 +1432,9 @@ mg_atomic_add(volatile int64_t *addr, int64_t value) #if defined(USE_SERVER_STATS) struct mg_memory_stat { - volatile int64_t totalMemUsed; - volatile int64_t maxMemUsed; - volatile int blockCount; + volatile ptrdiff_t totalMemUsed; + volatile ptrdiff_t maxMemUsed; + volatile ptrdiff_t blockCount; }; @@ -1344,12 +1459,8 @@ mg_malloc_ex(size_t size, #endif if (data) { - int64_t mmem = mg_atomic_add(&mstat->totalMemUsed, (int64_t)size); - if (mmem > mstat->maxMemUsed) { - /* could use atomic compare exchange, but this - * seems overkill for statistics data */ - mstat->maxMemUsed = mmem; - } + ptrdiff_t mmem = mg_atomic_add(&mstat->totalMemUsed, (ptrdiff_t)size); + mg_atomic_max(&mstat->maxMemUsed, mmem); mg_atomic_inc(&mstat->blockCount); ((uintptr_t *)data)[0] = size; @@ -1366,11 +1477,7 @@ mg_malloc_ex(size_t size, (unsigned long)mstat->blockCount, file, line); -#if defined(_WIN32) - OutputDebugStringA(mallocStr); -#else DEBUG_TRACE("%s", mallocStr); -#endif #endif return memory; @@ -1396,9 +1503,6 @@ mg_calloc_ex(size_t count, static void mg_free_ex(void *memory, const char *file, unsigned line) { - void *data = (void *)(((char *)memory) - 2 * sizeof(uintptr_t)); - - #if defined(MEMORY_DEBUGGING) char mallocStr[256]; #else @@ -1407,11 +1511,13 @@ mg_free_ex(void *memory, const char *file, unsigned line) #endif if (memory) { + void *data = (void *)(((char *)memory) - 2 * sizeof(uintptr_t)); uintptr_t size = ((uintptr_t *)data)[0]; struct mg_memory_stat *mstat = (struct mg_memory_stat *)(((uintptr_t *)data)[1]); - mg_atomic_add(&mstat->totalMemUsed, -(int64_t)size); + mg_atomic_add(&mstat->totalMemUsed, -(ptrdiff_t)size); mg_atomic_dec(&mstat->blockCount); + #if defined(MEMORY_DEBUGGING) sprintf(mallocStr, "MEM: %p %5lu free %7lu %4lu --- %s:%u\n", @@ -1421,11 +1527,7 @@ mg_free_ex(void *memory, const char *file, unsigned line) (unsigned long)mstat->blockCount, file, line); -#if defined(_WIN32) - OutputDebugStringA(mallocStr); -#else DEBUG_TRACE("%s", mallocStr); -#endif #endif free(data); } @@ -1460,7 +1562,7 @@ mg_realloc_ex(void *memory, _realloc = realloc(data, newsize + 2 * sizeof(uintptr_t)); if (_realloc) { data = _realloc; - mg_atomic_add(&mstat->totalMemUsed, -(int64_t)oldsize); + mg_atomic_add(&mstat->totalMemUsed, -(ptrdiff_t)oldsize); #if defined(MEMORY_DEBUGGING) sprintf(mallocStr, "MEM: %p %5lu r-free %7lu %4lu --- %s:%u\n", @@ -1470,13 +1572,10 @@ mg_realloc_ex(void *memory, (unsigned long)mstat->blockCount, file, line); -#if defined(_WIN32) - OutputDebugStringA(mallocStr); -#else DEBUG_TRACE("%s", mallocStr); #endif -#endif - mg_atomic_add(&mstat->totalMemUsed, (int64_t)newsize); + mg_atomic_add(&mstat->totalMemUsed, (ptrdiff_t)newsize); + #if defined(MEMORY_DEBUGGING) sprintf(mallocStr, "MEM: %p %5lu r-alloc %7lu %4lu --- %s:%u\n", @@ -1486,21 +1585,13 @@ mg_realloc_ex(void *memory, (unsigned long)mstat->blockCount, file, line); -#if defined(_WIN32) - OutputDebugStringA(mallocStr); -#else DEBUG_TRACE("%s", mallocStr); -#endif #endif *(uintptr_t *)data = newsize; data = (void *)(((char *)data) + 2 * sizeof(uintptr_t)); } else { #if defined(MEMORY_DEBUGGING) -#if defined(_WIN32) - OutputDebugStringA("MEM: realloc failed\n"); -#else DEBUG_TRACE("%s", "MEM: realloc failed\n"); -#endif #endif return _realloc; } @@ -1517,6 +1608,7 @@ mg_realloc_ex(void *memory, return data; } + #define mg_malloc(a) mg_malloc_ex(a, NULL, __FILE__, __LINE__) #define mg_calloc(a, b) mg_calloc_ex(a, b, NULL, __FILE__, __LINE__) #define mg_realloc(a, b) mg_realloc_ex(a, b, NULL, __FILE__, __LINE__) @@ -1526,8 +1618,10 @@ mg_realloc_ex(void *memory, #define mg_calloc_ctx(a, b, c) mg_calloc_ex(a, b, c, __FILE__, __LINE__) #define mg_realloc_ctx(a, b, c) mg_realloc_ex(a, b, c, __FILE__, __LINE__) + #else /* USE_SERVER_STATS */ + static __inline void * mg_malloc(size_t a) { @@ -1611,10 +1705,20 @@ static int mg_init_library_called = 0; #if !defined(NO_SSL) static int mg_ssl_initialized = 0; + + +/* TODO: Selection of SSL library and version */ +#if !defined(OPENSSL_API_1_0) && !defined(OPENSSL_API_1_1) +#error "Please define OPENSSL_API_1_0 or OPENSSL_API_1_1" #endif +#if defined(OPENSSL_API_1_0) && defined(OPENSSL_API_1_1) +#error "Multiple OPENSSL_API versions defined" +#endif +#endif /* NO_SSL */ + static pthread_key_t sTlsKey; /* Thread local storage index */ -static int thread_idx_max = 0; +static volatile ptrdiff_t thread_idx_max = 0; #if defined(MG_LEGACY_INTERFACE) #define MG_ALLOW_USING_GET_REQUEST_INFO_FOR_RESPONSE @@ -1628,6 +1732,7 @@ struct mg_workerTLS { HANDLE pthread_cond_helper_mutex; struct mg_workerTLS *next_waiting_thread; #endif + const char *alpn_proto; #if defined(MG_ALLOW_USING_GET_REQUEST_INFO_FOR_RESPONSE) char txtbuf[4]; #endif @@ -1731,36 +1836,27 @@ static void DEBUG_TRACE_FUNC(const char *func, unsigned line, const char *fmt, ...) { va_list args; - uint64_t nsnow; - static uint64_t nslast; struct timespec tsnow; /* Get some operating system independent thread id */ unsigned long thread_id = mg_current_thread_id(); clock_gettime(CLOCK_REALTIME, &tsnow); - nsnow = ((uint64_t)tsnow.tv_sec) * ((uint64_t)1000000000) - + ((uint64_t)tsnow.tv_nsec); - - if (!nslast) { - nslast = nsnow; - } flockfile(DEBUG_TRACE_STREAM); - fprintf( DEBUG_TRACE_STREAM,"*** %lu.%09lu %12" INT64_FMT " %lu %s:%u: ", - (unsigned long)tsnow.tv_sec, - (unsigned long)tsnow.tv_nsec, - nsnow - nslast, - thread_id, - func, - line); + fprintf(DEBUG_TRACE_STREAM, + "*** %lu.%09lu %lu %s:%u: ", + (unsigned long)tsnow.tv_sec, + (unsigned long)tsnow.tv_nsec, + thread_id, + func, + line); va_start(args, fmt); vfprintf(DEBUG_TRACE_STREAM, fmt, args); va_end(args); putc('\n', DEBUG_TRACE_STREAM); fflush(DEBUG_TRACE_STREAM); funlockfile(DEBUG_TRACE_STREAM); - nslast = nsnow; } #endif /* NEED_DEBUG_TRACE_FUNC */ @@ -1818,16 +1914,21 @@ typedef struct SSL_CTX SSL_CTX; #define ENGINE_cleanup() ((void)0) #endif -#if (OPENSSL_VERSION_NUMBER >= 0x10100000L) + /* If OpenSSL headers are included, automatically select the API version */ +#if (OPENSSL_VERSION_NUMBER >= 0x10100000L) #if !defined(OPENSSL_API_1_1) #define OPENSSL_API_1_1 #endif #define OPENSSL_REMOVE_THREAD_STATE() #else +#if !defined(OPENSSL_API_1_0) +#define OPENSSL_API_1_0 +#endif #define OPENSSL_REMOVE_THREAD_STATE() ERR_remove_thread_state(NULL) #endif + #else /* SSL loaded dynamically from DLL. @@ -1893,9 +1994,21 @@ typedef struct x509 X509; #define SSL_TLSEXT_ERR_ALERT_FATAL (2) #define SSL_TLSEXT_ERR_NOACK (3) +#define SSL_SESS_CACHE_BOTH (3) + +enum ssl_func_category { + TLS_Mandatory, /* required for HTTPS */ + TLS_ALPN, /* required for Application Layer Protocol Negotiation */ + TLS_END_OF_LIST +}; + +/* Check if all TLS functions/features are available */ +static int tls_feature_missing[TLS_END_OF_LIST] = {0}; + struct ssl_func { - const char *name; /* SSL function name */ - void (*ptr)(void); /* Function pointer */ + const char *name; /* SSL function name */ + enum ssl_func_category required; /* Mandatory or optional */ + void (*ptr)(void); /* Function pointer */ }; @@ -1961,6 +2074,25 @@ struct ssl_func { (*(const char *(*)(const SSL *, int type))ssl_sw[36].ptr) #define SSL_set_SSL_CTX (*(SSL_CTX * (*)(SSL *, SSL_CTX *)) ssl_sw[37].ptr) #define SSL_ctrl (*(long (*)(SSL *, int, long, void *))ssl_sw[38].ptr) +#define SSL_CTX_set_alpn_protos \ + (*(int (*)(SSL_CTX *, const unsigned char *, unsigned))ssl_sw[39].ptr) +typedef int (*tSSL_alpn_select_cb)(SSL *ssl, + const unsigned char **out, + unsigned char *outlen, + const unsigned char *in, + unsigned int inlen, + void *arg); +#define SSL_CTX_set_alpn_select_cb \ + (*(void (*)(SSL_CTX *, tSSL_alpn_select_cb, void *))ssl_sw[40].ptr) +typedef int (*tSSL_next_protos_advertised_cb)(SSL *ssl, + const unsigned char **out, + unsigned int *outlen, + void *arg); +#define SSL_CTX_set_next_protos_advertised_cb \ + (*(void (*)(SSL_CTX *, tSSL_next_protos_advertised_cb, void *))ssl_sw[41] \ + .ptr) + +#define SSL_CTX_set_timeout (*(long (*)(SSL_CTX *, long))ssl_sw[42].ptr) #define SSL_CTX_clear_options(ctx, op) \ SSL_CTX_ctrl((ctx), SSL_CTRL_CLEAR_OPTIONS, (op), NULL) @@ -1974,8 +2106,6 @@ struct ssl_func { SSL_CTX_callback_ctrl(ctx, \ SSL_CTRL_SET_TLSEXT_SERVERNAME_CB, \ (void (*)(void))cb) -#define SSL_CTX_set_tlsext_servername_arg(ctx, arg) \ - SSL_CTX_ctrl(ctx, SSL_CTRL_SET_TLSEXT_SERVERNAME_ARG, 0, (void *)arg) #define SSL_set_tlsext_host_name(ctx, arg) \ SSL_ctrl(ctx, SSL_CTRL_SET_TLSEXT_HOSTNAME, 0, (void *)arg) @@ -1985,6 +2115,11 @@ struct ssl_func { #define SSL_set_app_data(s, arg) (SSL_set_ex_data(s, 0, (char *)arg)) #define SSL_get_app_data(s) (SSL_get_ex_data(s, 0)) +#define SSL_CTX_sess_set_cache_size(ctx, size) SSL_CTX_ctrl(ctx, 42, size, NULL) +#define SSL_CTX_set_session_cache_mode(ctx, mode) \ + SSL_CTX_ctrl(ctx, 44, mode, NULL) + + #define ERR_get_error (*(unsigned long (*)(void))crypto_sw[0].ptr) #define ERR_error_string (*(char *(*)(unsigned long, char *))crypto_sw[1].ptr) #define CONF_modules_unload (*(void (*)(int))crypto_sw[2].ptr) @@ -2017,67 +2152,73 @@ struct ssl_func { * It loads SSL library dynamically and changes NULLs to the actual addresses * of respective functions. The macros above (like SSL_connect()) are really * just calling these functions indirectly via the pointer. */ -static struct ssl_func ssl_sw[] = {{"SSL_free", NULL}, - {"SSL_accept", NULL}, - {"SSL_connect", NULL}, - {"SSL_read", NULL}, - {"SSL_write", NULL}, - {"SSL_get_error", NULL}, - {"SSL_set_fd", NULL}, - {"SSL_new", NULL}, - {"SSL_CTX_new", NULL}, - {"TLS_server_method", NULL}, - {"OPENSSL_init_ssl", NULL}, - {"SSL_CTX_use_PrivateKey_file", NULL}, - {"SSL_CTX_use_certificate_file", NULL}, - {"SSL_CTX_set_default_passwd_cb", NULL}, - {"SSL_CTX_free", NULL}, - {"SSL_CTX_use_certificate_chain_file", NULL}, - {"TLS_client_method", NULL}, - {"SSL_pending", NULL}, - {"SSL_CTX_set_verify", NULL}, - {"SSL_shutdown", NULL}, - {"SSL_CTX_load_verify_locations", NULL}, - {"SSL_CTX_set_default_verify_paths", NULL}, - {"SSL_CTX_set_verify_depth", NULL}, - {"SSL_get_peer_certificate", NULL}, - {"SSL_get_version", NULL}, - {"SSL_get_current_cipher", NULL}, - {"SSL_CIPHER_get_name", NULL}, - {"SSL_CTX_check_private_key", NULL}, - {"SSL_CTX_set_session_id_context", NULL}, - {"SSL_CTX_ctrl", NULL}, - {"SSL_CTX_set_cipher_list", NULL}, - {"SSL_CTX_set_options", NULL}, - {"SSL_CTX_set_info_callback", NULL}, - {"SSL_get_ex_data", NULL}, - {"SSL_set_ex_data", NULL}, - {"SSL_CTX_callback_ctrl", NULL}, - {"SSL_get_servername", NULL}, - {"SSL_set_SSL_CTX", NULL}, - {"SSL_ctrl", NULL}, - {NULL, NULL}}; +static struct ssl_func ssl_sw[] = { + {"SSL_free", TLS_Mandatory, NULL}, + {"SSL_accept", TLS_Mandatory, NULL}, + {"SSL_connect", TLS_Mandatory, NULL}, + {"SSL_read", TLS_Mandatory, NULL}, + {"SSL_write", TLS_Mandatory, NULL}, + {"SSL_get_error", TLS_Mandatory, NULL}, + {"SSL_set_fd", TLS_Mandatory, NULL}, + {"SSL_new", TLS_Mandatory, NULL}, + {"SSL_CTX_new", TLS_Mandatory, NULL}, + {"TLS_server_method", TLS_Mandatory, NULL}, + {"OPENSSL_init_ssl", TLS_Mandatory, NULL}, + {"SSL_CTX_use_PrivateKey_file", TLS_Mandatory, NULL}, + {"SSL_CTX_use_certificate_file", TLS_Mandatory, NULL}, + {"SSL_CTX_set_default_passwd_cb", TLS_Mandatory, NULL}, + {"SSL_CTX_free", TLS_Mandatory, NULL}, + {"SSL_CTX_use_certificate_chain_file", TLS_Mandatory, NULL}, + {"TLS_client_method", TLS_Mandatory, NULL}, + {"SSL_pending", TLS_Mandatory, NULL}, + {"SSL_CTX_set_verify", TLS_Mandatory, NULL}, + {"SSL_shutdown", TLS_Mandatory, NULL}, + {"SSL_CTX_load_verify_locations", TLS_Mandatory, NULL}, + {"SSL_CTX_set_default_verify_paths", TLS_Mandatory, NULL}, + {"SSL_CTX_set_verify_depth", TLS_Mandatory, NULL}, + {"SSL_get_peer_certificate", TLS_Mandatory, NULL}, + {"SSL_get_version", TLS_Mandatory, NULL}, + {"SSL_get_current_cipher", TLS_Mandatory, NULL}, + {"SSL_CIPHER_get_name", TLS_Mandatory, NULL}, + {"SSL_CTX_check_private_key", TLS_Mandatory, NULL}, + {"SSL_CTX_set_session_id_context", TLS_Mandatory, NULL}, + {"SSL_CTX_ctrl", TLS_Mandatory, NULL}, + {"SSL_CTX_set_cipher_list", TLS_Mandatory, NULL}, + {"SSL_CTX_set_options", TLS_Mandatory, NULL}, + {"SSL_CTX_set_info_callback", TLS_Mandatory, NULL}, + {"SSL_get_ex_data", TLS_Mandatory, NULL}, + {"SSL_set_ex_data", TLS_Mandatory, NULL}, + {"SSL_CTX_callback_ctrl", TLS_Mandatory, NULL}, + {"SSL_get_servername", TLS_Mandatory, NULL}, + {"SSL_set_SSL_CTX", TLS_Mandatory, NULL}, + {"SSL_ctrl", TLS_Mandatory, NULL}, + {"SSL_CTX_set_alpn_protos", TLS_ALPN, NULL}, + {"SSL_CTX_set_alpn_select_cb", TLS_ALPN, NULL}, + {"SSL_CTX_set_next_protos_advertised_cb", TLS_ALPN, NULL}, + {"SSL_CTX_set_timeout", TLS_Mandatory, NULL}, + {NULL, TLS_END_OF_LIST, NULL}}; /* Similar array as ssl_sw. These functions could be located in different * lib. */ -static struct ssl_func crypto_sw[] = {{"ERR_get_error", NULL}, - {"ERR_error_string", NULL}, - {"CONF_modules_unload", NULL}, - {"X509_free", NULL}, - {"X509_get_subject_name", NULL}, - {"X509_get_issuer_name", NULL}, - {"X509_NAME_oneline", NULL}, - {"X509_get_serialNumber", NULL}, - {"EVP_get_digestbyname", NULL}, - {"EVP_Digest", NULL}, - {"i2d_X509", NULL}, - {"BN_bn2hex", NULL}, - {"ASN1_INTEGER_to_BN", NULL}, - {"BN_free", NULL}, - {"CRYPTO_free", NULL}, - {"ERR_clear_error", NULL}, - {NULL, NULL}}; +static struct ssl_func crypto_sw[] = { + {"ERR_get_error", TLS_Mandatory, NULL}, + {"ERR_error_string", TLS_Mandatory, NULL}, + {"CONF_modules_unload", TLS_Mandatory, NULL}, + {"X509_free", TLS_Mandatory, NULL}, + {"X509_get_subject_name", TLS_Mandatory, NULL}, + {"X509_get_issuer_name", TLS_Mandatory, NULL}, + {"X509_NAME_oneline", TLS_Mandatory, NULL}, + {"X509_get_serialNumber", TLS_Mandatory, NULL}, + {"EVP_get_digestbyname", TLS_Mandatory, NULL}, + {"EVP_Digest", TLS_Mandatory, NULL}, + {"i2d_X509", TLS_Mandatory, NULL}, + {"BN_bn2hex", TLS_Mandatory, NULL}, + {"ASN1_INTEGER_to_BN", TLS_Mandatory, NULL}, + {"BN_free", TLS_Mandatory, NULL}, + {"CRYPTO_free", TLS_Mandatory, NULL}, + {"ERR_clear_error", TLS_Mandatory, NULL}, + {NULL, TLS_END_OF_LIST, NULL}}; #else #define SSL_free (*(void (*)(SSL *))ssl_sw[0].ptr) @@ -2135,6 +2276,26 @@ static struct ssl_func crypto_sw[] = {{"ERR_get_error", NULL}, (*(const char *(*)(const SSL *, int type))ssl_sw[36].ptr) #define SSL_set_SSL_CTX (*(SSL_CTX * (*)(SSL *, SSL_CTX *)) ssl_sw[37].ptr) #define SSL_ctrl (*(long (*)(SSL *, int, long, void *))ssl_sw[38].ptr) +#define SSL_CTX_set_alpn_protos \ + (*(int (*)(SSL_CTX *, const unsigned char *, unsigned))ssl_sw[39].ptr) +typedef int (*tSSL_alpn_select_cb)(SSL *ssl, + const unsigned char **out, + unsigned char *outlen, + const unsigned char *in, + unsigned int inlen, + void *arg); +#define SSL_CTX_set_alpn_select_cb \ + (*(void (*)(SSL_CTX *, tSSL_alpn_select_cb, void *))ssl_sw[40].ptr) +typedef int (*tSSL_next_protos_advertised_cb)(SSL *ssl, + const unsigned char **out, + unsigned int *outlen, + void *arg); +#define SSL_CTX_set_next_protos_advertised_cb \ + (*(void (*)(SSL_CTX *, tSSL_next_protos_advertised_cb, void *))ssl_sw[41] \ + .ptr) + +#define SSL_CTX_set_timeout (*(long (*)(SSL_CTX *, long))ssl_sw[42].ptr) + #define SSL_CTX_set_options(ctx, op) \ SSL_CTX_ctrl((ctx), SSL_CTRL_OPTIONS, (op), NULL) @@ -2150,8 +2311,6 @@ static struct ssl_func crypto_sw[] = {{"ERR_get_error", NULL}, SSL_CTX_callback_ctrl(ctx, \ SSL_CTRL_SET_TLSEXT_SERVERNAME_CB, \ (void (*)(void))cb) -#define SSL_CTX_set_tlsext_servername_arg(ctx, arg) \ - SSL_CTX_ctrl(ctx, SSL_CTRL_SET_TLSEXT_SERVERNAME_ARG, 0, (void *)arg) #define SSL_set_tlsext_host_name(ctx, arg) \ SSL_ctrl(ctx, SSL_CTRL_SET_TLSEXT_HOSTNAME, 0, (void *)arg) @@ -2161,6 +2320,11 @@ static struct ssl_func crypto_sw[] = {{"ERR_get_error", NULL}, #define SSL_set_app_data(s, arg) (SSL_set_ex_data(s, 0, (char *)arg)) #define SSL_get_app_data(s) (SSL_get_ex_data(s, 0)) +#define SSL_CTX_sess_set_cache_size(ctx, size) SSL_CTX_ctrl(ctx, 42, size, NULL) +#define SSL_CTX_set_session_cache_mode(ctx, mode) \ + SSL_CTX_ctrl(ctx, 44, mode, NULL) + + #define CRYPTO_num_locks (*(int (*)(void))crypto_sw[0].ptr) #define CRYPTO_set_locking_callback \ (*(void (*)(void (*)(int, int, const char *, int)))crypto_sw[1].ptr) @@ -2208,76 +2372,82 @@ static struct ssl_func crypto_sw[] = {{"ERR_get_error", NULL}, * It loads SSL library dynamically and changes NULLs to the actual addresses * of respective functions. The macros above (like SSL_connect()) are really * just calling these functions indirectly via the pointer. */ -static struct ssl_func ssl_sw[] = {{"SSL_free", NULL}, - {"SSL_accept", NULL}, - {"SSL_connect", NULL}, - {"SSL_read", NULL}, - {"SSL_write", NULL}, - {"SSL_get_error", NULL}, - {"SSL_set_fd", NULL}, - {"SSL_new", NULL}, - {"SSL_CTX_new", NULL}, - {"SSLv23_server_method", NULL}, - {"SSL_library_init", NULL}, - {"SSL_CTX_use_PrivateKey_file", NULL}, - {"SSL_CTX_use_certificate_file", NULL}, - {"SSL_CTX_set_default_passwd_cb", NULL}, - {"SSL_CTX_free", NULL}, - {"SSL_load_error_strings", NULL}, - {"SSL_CTX_use_certificate_chain_file", NULL}, - {"SSLv23_client_method", NULL}, - {"SSL_pending", NULL}, - {"SSL_CTX_set_verify", NULL}, - {"SSL_shutdown", NULL}, - {"SSL_CTX_load_verify_locations", NULL}, - {"SSL_CTX_set_default_verify_paths", NULL}, - {"SSL_CTX_set_verify_depth", NULL}, - {"SSL_get_peer_certificate", NULL}, - {"SSL_get_version", NULL}, - {"SSL_get_current_cipher", NULL}, - {"SSL_CIPHER_get_name", NULL}, - {"SSL_CTX_check_private_key", NULL}, - {"SSL_CTX_set_session_id_context", NULL}, - {"SSL_CTX_ctrl", NULL}, - {"SSL_CTX_set_cipher_list", NULL}, - {"SSL_CTX_set_info_callback", NULL}, - {"SSL_get_ex_data", NULL}, - {"SSL_set_ex_data", NULL}, - {"SSL_CTX_callback_ctrl", NULL}, - {"SSL_get_servername", NULL}, - {"SSL_set_SSL_CTX", NULL}, - {"SSL_ctrl", NULL}, - {NULL, NULL}}; +static struct ssl_func ssl_sw[] = { + {"SSL_free", TLS_Mandatory, NULL}, + {"SSL_accept", TLS_Mandatory, NULL}, + {"SSL_connect", TLS_Mandatory, NULL}, + {"SSL_read", TLS_Mandatory, NULL}, + {"SSL_write", TLS_Mandatory, NULL}, + {"SSL_get_error", TLS_Mandatory, NULL}, + {"SSL_set_fd", TLS_Mandatory, NULL}, + {"SSL_new", TLS_Mandatory, NULL}, + {"SSL_CTX_new", TLS_Mandatory, NULL}, + {"SSLv23_server_method", TLS_Mandatory, NULL}, + {"SSL_library_init", TLS_Mandatory, NULL}, + {"SSL_CTX_use_PrivateKey_file", TLS_Mandatory, NULL}, + {"SSL_CTX_use_certificate_file", TLS_Mandatory, NULL}, + {"SSL_CTX_set_default_passwd_cb", TLS_Mandatory, NULL}, + {"SSL_CTX_free", TLS_Mandatory, NULL}, + {"SSL_load_error_strings", TLS_Mandatory, NULL}, + {"SSL_CTX_use_certificate_chain_file", TLS_Mandatory, NULL}, + {"SSLv23_client_method", TLS_Mandatory, NULL}, + {"SSL_pending", TLS_Mandatory, NULL}, + {"SSL_CTX_set_verify", TLS_Mandatory, NULL}, + {"SSL_shutdown", TLS_Mandatory, NULL}, + {"SSL_CTX_load_verify_locations", TLS_Mandatory, NULL}, + {"SSL_CTX_set_default_verify_paths", TLS_Mandatory, NULL}, + {"SSL_CTX_set_verify_depth", TLS_Mandatory, NULL}, + {"SSL_get_peer_certificate", TLS_Mandatory, NULL}, + {"SSL_get_version", TLS_Mandatory, NULL}, + {"SSL_get_current_cipher", TLS_Mandatory, NULL}, + {"SSL_CIPHER_get_name", TLS_Mandatory, NULL}, + {"SSL_CTX_check_private_key", TLS_Mandatory, NULL}, + {"SSL_CTX_set_session_id_context", TLS_Mandatory, NULL}, + {"SSL_CTX_ctrl", TLS_Mandatory, NULL}, + {"SSL_CTX_set_cipher_list", TLS_Mandatory, NULL}, + {"SSL_CTX_set_info_callback", TLS_Mandatory, NULL}, + {"SSL_get_ex_data", TLS_Mandatory, NULL}, + {"SSL_set_ex_data", TLS_Mandatory, NULL}, + {"SSL_CTX_callback_ctrl", TLS_Mandatory, NULL}, + {"SSL_get_servername", TLS_Mandatory, NULL}, + {"SSL_set_SSL_CTX", TLS_Mandatory, NULL}, + {"SSL_ctrl", TLS_Mandatory, NULL}, + {"SSL_CTX_set_alpn_protos", TLS_ALPN, NULL}, + {"SSL_CTX_set_alpn_select_cb", TLS_ALPN, NULL}, + {"SSL_CTX_set_next_protos_advertised_cb", TLS_ALPN, NULL}, + {"SSL_CTX_set_timeout", TLS_Mandatory, NULL}, + {NULL, TLS_END_OF_LIST, NULL}}; /* Similar array as ssl_sw. These functions could be located in different * lib. */ -static struct ssl_func crypto_sw[] = {{"CRYPTO_num_locks", NULL}, - {"CRYPTO_set_locking_callback", NULL}, - {"CRYPTO_set_id_callback", NULL}, - {"ERR_get_error", NULL}, - {"ERR_error_string", NULL}, - {"ERR_remove_state", NULL}, - {"ERR_free_strings", NULL}, - {"ENGINE_cleanup", NULL}, - {"CONF_modules_unload", NULL}, - {"CRYPTO_cleanup_all_ex_data", NULL}, - {"EVP_cleanup", NULL}, - {"X509_free", NULL}, - {"X509_get_subject_name", NULL}, - {"X509_get_issuer_name", NULL}, - {"X509_NAME_oneline", NULL}, - {"X509_get_serialNumber", NULL}, - {"i2c_ASN1_INTEGER", NULL}, - {"EVP_get_digestbyname", NULL}, - {"EVP_Digest", NULL}, - {"i2d_X509", NULL}, - {"BN_bn2hex", NULL}, - {"ASN1_INTEGER_to_BN", NULL}, - {"BN_free", NULL}, - {"CRYPTO_free", NULL}, - {"ERR_clear_error", NULL}, - {NULL, NULL}}; +static struct ssl_func crypto_sw[] = { + {"CRYPTO_num_locks", TLS_Mandatory, NULL}, + {"CRYPTO_set_locking_callback", TLS_Mandatory, NULL}, + {"CRYPTO_set_id_callback", TLS_Mandatory, NULL}, + {"ERR_get_error", TLS_Mandatory, NULL}, + {"ERR_error_string", TLS_Mandatory, NULL}, + {"ERR_remove_state", TLS_Mandatory, NULL}, + {"ERR_free_strings", TLS_Mandatory, NULL}, + {"ENGINE_cleanup", TLS_Mandatory, NULL}, + {"CONF_modules_unload", TLS_Mandatory, NULL}, + {"CRYPTO_cleanup_all_ex_data", TLS_Mandatory, NULL}, + {"EVP_cleanup", TLS_Mandatory, NULL}, + {"X509_free", TLS_Mandatory, NULL}, + {"X509_get_subject_name", TLS_Mandatory, NULL}, + {"X509_get_issuer_name", TLS_Mandatory, NULL}, + {"X509_NAME_oneline", TLS_Mandatory, NULL}, + {"X509_get_serialNumber", TLS_Mandatory, NULL}, + {"i2c_ASN1_INTEGER", TLS_Mandatory, NULL}, + {"EVP_get_digestbyname", TLS_Mandatory, NULL}, + {"EVP_Digest", TLS_Mandatory, NULL}, + {"i2d_X509", TLS_Mandatory, NULL}, + {"BN_bn2hex", TLS_Mandatory, NULL}, + {"ASN1_INTEGER_to_BN", TLS_Mandatory, NULL}, + {"BN_free", TLS_Mandatory, NULL}, + {"CRYPTO_free", TLS_Mandatory, NULL}, + {"ERR_clear_error", TLS_Mandatory, NULL}, + {NULL, TLS_END_OF_LIST, NULL}}; #endif /* OPENSSL_API_1_1 */ #endif /* NO_SSL_DL */ #endif /* NO_SSL */ @@ -2299,8 +2469,7 @@ static const char month_names[][4] = {"Jan", #endif /* !NO_CACHING */ /* Unified socket address. For IPv6 support, add IPv6 address structure in - * the - * union u. */ + * the union u. */ union usa { struct sockaddr sa; struct sockaddr_in sin; @@ -2309,6 +2478,13 @@ union usa { #endif }; +#if defined(USE_IPV6) +#define USA_IN_PORT_UNSAFE(s) \ + (((s)->sa.sa_family == AF_INET6) ? (s)->sin6.sin6_port : (s)->sin.sin_port) +#else +#define USA_IN_PORT_UNSAFE(s) ((s)->sin.sin_port) +#endif + /* Describes a string (chunk of memory). */ struct vec { const char *ptr; @@ -2325,23 +2501,10 @@ struct mg_file_stat { int location; /* 0 = nowhere, 1 = on disk, 2 = in memory */ }; -struct mg_file_in_memory { - char *p; - uint32_t pos; - char mode; -}; struct mg_file_access { /* File properties filled by mg_fopen: */ FILE *fp; -#if defined(MG_USE_OPEN_FILE) - /* TODO (low): Remove obsolete "file in memory" implementation. - * In an "early 2017" discussion at Google groups - * https://groups.google.com/forum/#!topic/civetweb/h9HT4CmeYqI - * we decided to get rid of this feature (after some fade-out - * phase). */ - const char *membuf; -#endif }; struct mg_file { @@ -2349,17 +2512,6 @@ struct mg_file { struct mg_file_access access; }; -#if defined(MG_USE_OPEN_FILE) - -#define STRUCT_FILE_INITIALIZER \ - { \ - {(uint64_t)0, (time_t)0, 0, 0, 0}, \ - { \ - (FILE *)NULL, (const char *)NULL \ - } \ - } - -#else #define STRUCT_FILE_INITIALIZER \ { \ @@ -2369,8 +2521,6 @@ struct mg_file { } \ } -#endif - /* Describes listening socket, or socket which was accept()-ed by the master * thread and queued for future handling by the worker thread. */ @@ -2422,16 +2572,27 @@ enum { LUA_BACKGROUND_SCRIPT, LUA_BACKGROUND_SCRIPT_PARAMS, #endif -#if defined(USE_TIMERS) - CGI_TIMEOUT, +#if defined(USE_HTTP2) + ENABLE_HTTP2, #endif /* Once for each domain */ DOCUMENT_ROOT, + CGI_EXTENSIONS, + CGI2_EXTENSIONS, CGI_ENVIRONMENT, - PUT_DELETE_PASSWORDS_FILE, + CGI2_ENVIRONMENT, CGI_INTERPRETER, + CGI2_INTERPRETER, + CGI_INTERPRETER_ARGS, + CGI2_INTERPRETER_ARGS, +#if defined(USE_TIMERS) + CGI_TIMEOUT, + CGI2_TIMEOUT, +#endif + + PUT_DELETE_PASSWORDS_FILE, PROTECT_URI, AUTHENTICATION_DOMAIN, ENABLE_AUTH_DOMAIN_CHECK, @@ -2446,6 +2607,7 @@ enum { URL_REWRITE_PATTERN, HIDE_FILES, SSL_DO_VERIFY_PEER, + SSL_CACHE_TIMEOUT, SSL_CA_PATH, SSL_CA_FILE, SSL_VERIFY_DEPTH, @@ -2529,16 +2691,27 @@ static const struct mg_option config_options[] = { {"lua_background_script", MG_CONFIG_TYPE_FILE, NULL}, {"lua_background_script_params", MG_CONFIG_TYPE_STRING_LIST, NULL}, #endif -#if defined(USE_TIMERS) - {"cgi_timeout_ms", MG_CONFIG_TYPE_NUMBER, NULL}, +#if defined(USE_HTTP2) + {"enable_http2", MG_CONFIG_TYPE_BOOLEAN, "no"}, #endif /* Once for each domain */ {"document_root", MG_CONFIG_TYPE_DIRECTORY, NULL}, + {"cgi_pattern", MG_CONFIG_TYPE_EXT_PATTERN, "**.cgi$|**.pl$|**.php$"}, + {"cgi2_pattern", MG_CONFIG_TYPE_EXT_PATTERN, NULL}, {"cgi_environment", MG_CONFIG_TYPE_STRING_LIST, NULL}, - {"put_delete_auth_file", MG_CONFIG_TYPE_FILE, NULL}, + {"cgi2_environment", MG_CONFIG_TYPE_STRING_LIST, NULL}, {"cgi_interpreter", MG_CONFIG_TYPE_FILE, NULL}, + {"cgi2_interpreter", MG_CONFIG_TYPE_FILE, NULL}, + {"cgi_interpreter_args", MG_CONFIG_TYPE_STRING, NULL}, + {"cgi2_interpreter_args", MG_CONFIG_TYPE_STRING, NULL}, +#if defined(USE_TIMERS) + {"cgi_timeout_ms", MG_CONFIG_TYPE_NUMBER, NULL}, + {"cgi2_timeout_ms", MG_CONFIG_TYPE_NUMBER, NULL}, +#endif + + {"put_delete_auth_file", MG_CONFIG_TYPE_FILE, NULL}, {"protect_uri", MG_CONFIG_TYPE_STRING_LIST, NULL}, {"authentication_domain", MG_CONFIG_TYPE_STRING, "mydomain.com"}, {"enable_auth_domain_check", MG_CONFIG_TYPE_BOOLEAN, "yes"}, @@ -2562,13 +2735,23 @@ static const struct mg_option config_options[] = { {"hide_files_patterns", MG_CONFIG_TYPE_EXT_PATTERN, NULL}, {"ssl_verify_peer", MG_CONFIG_TYPE_YES_NO_OPTIONAL, "no"}, + {"ssl_cache_timeout", MG_CONFIG_TYPE_NUMBER, "-1"}, {"ssl_ca_path", MG_CONFIG_TYPE_DIRECTORY, NULL}, {"ssl_ca_file", MG_CONFIG_TYPE_FILE, NULL}, {"ssl_verify_depth", MG_CONFIG_TYPE_NUMBER, "9"}, {"ssl_default_verify_paths", MG_CONFIG_TYPE_BOOLEAN, "yes"}, {"ssl_cipher_list", MG_CONFIG_TYPE_STRING, NULL}, + +#if defined(USE_HTTP2) + /* HTTP2 requires ALPN, and anyway TLS1.2 should be considered + * as a minimum in 2020 */ + {"ssl_protocol_version", MG_CONFIG_TYPE_NUMBER, "4"}, +#else + /* Keep the default (compatibility) */ {"ssl_protocol_version", MG_CONFIG_TYPE_NUMBER, "0"}, +#endif /* defined(USE_HTTP2) */ + {"ssl_short_trust", MG_CONFIG_TYPE_BOOLEAN, "no"}, #if defined(USE_LUA) @@ -2632,9 +2815,7 @@ struct mg_handler_info { /* Handler for http/https or authorization requests. */ mg_request_handler handler; unsigned int refcount; - pthread_mutex_t refcount_mutex; /* Protects refcount */ - pthread_cond_t - refcount_cond; /* Signaled when handler refcount is decremented */ + int removing; /* Handler for ws/wss (websocket) requests. */ mg_websocket_connect_handler connect_handler; @@ -2668,6 +2849,7 @@ struct mg_domain_context { SSL_CTX *ssl_ctx; /* SSL context */ char *config[NUM_OPTIONS]; /* Civetweb configuration parameters */ struct mg_handler_info *handlers; /* linked list of uri handlers */ + int64_t ssl_cert_last_mtime; /* Server nonce */ uint64_t auth_nonce_mask; /* Mask for all nonce values */ @@ -2683,6 +2865,46 @@ struct mg_domain_context { }; +/* Stop flag can be "volatile" or require a lock. + * MSDN uses volatile for "Interlocked" operations, but also explicitly + * states a read operation for int is always atomic. */ +#if defined(STOP_FLAG_NEEDS_LOCK) + +typedef ptrdiff_t volatile stop_flag_t; + +static int +STOP_FLAG_IS_ZERO(stop_flag_t *f) +{ + stop_flag_t sf = mg_atomic_add(f, 0); + return (sf == 0); +} + +static int +STOP_FLAG_IS_TWO(stop_flag_t *f) +{ + stop_flag_t sf = mg_atomic_add(f, 0); + return (sf == 2); +} + +static void +STOP_FLAG_ASSIGN(stop_flag_t *f, stop_flag_t v) +{ + stop_flag_t sf; + do { + sf = mg_atomic_compare_and_swap(f, *f, v); + } while (sf != v); +} + +#else /* STOP_FLAG_NEEDS_LOCK */ + +typedef int volatile stop_flag_t; +#define STOP_FLAG_IS_ZERO(f) ((*(f)) == 0) +#define STOP_FLAG_IS_TWO(f) ((*(f)) == 2) +#define STOP_FLAG_ASSIGN(f, v) ((*(f)) = (v)) + +#endif /* STOP_FLAG_NEEDS_LOCK */ + + struct mg_context { /* Part 1 - Physical context: @@ -2702,17 +2924,17 @@ struct mg_context { * allocated for each worker */ #if defined(USE_SERVER_STATS) - int active_connections; - int max_active_connections; - int64_t total_connections; - int64_t total_requests; - int64_t total_data_read; - int64_t total_data_written; + volatile ptrdiff_t active_connections; + volatile ptrdiff_t max_active_connections; + volatile ptrdiff_t total_connections; + volatile ptrdiff_t total_requests; + volatile int64_t total_data_read; + volatile int64_t total_data_written; #endif /* Thread related */ - volatile int stop_flag; /* Should we stop event loop */ - pthread_mutex_t thread_mutex; /* Protects (max|num)_threads */ + stop_flag_t stop_flag; /* Should we stop event loop */ + pthread_mutex_t thread_mutex; /* Protects client_socks or queue */ pthread_t masterthreadid; /* The master thread ID */ unsigned int @@ -2760,7 +2982,9 @@ struct mg_context { #endif /* Server nonce */ - pthread_mutex_t nonce_mutex; /* Protects nonce_count */ + pthread_mutex_t nonce_mutex; /* Protects ssl_ctx, handlers, + * ssl_cert_last_mtime, nonce_count, and + * next (linked list) */ /* Server callbacks */ struct mg_callbacks callbacks; /* User-defined callback function */ @@ -2791,13 +3015,39 @@ get_memory_stat(struct mg_context *ctx) #endif enum { - CONNECTION_TYPE_INVALID, - CONNECTION_TYPE_REQUEST, - CONNECTION_TYPE_RESPONSE + CONNECTION_TYPE_INVALID = 0, + CONNECTION_TYPE_REQUEST = 1, + CONNECTION_TYPE_RESPONSE = 2 +}; + +enum { + PROTOCOL_TYPE_HTTP1 = 0, + PROTOCOL_TYPE_WEBSOCKET = 1, + PROTOCOL_TYPE_HTTP2 = 2 +}; + + +#if defined(USE_HTTP2) +#if !defined(HTTP2_DYN_TABLE_SIZE) +#define HTTP2_DYN_TABLE_SIZE (256) +#endif + +struct mg_http2_connection { + uint32_t stream_id; + uint32_t dyn_table_size; + struct mg_header dyn_table[HTTP2_DYN_TABLE_SIZE]; }; +#endif + struct mg_connection { int connection_type; /* see CONNECTION_TYPE_* above */ + int protocol_type; /* see PROTOCOL_TYPE_*: 0=http/1.x, 1=ws, 2=http/2 */ + int request_state; /* 0: nothing sent, 1: header partially sent, 2: header + fully sent */ +#if defined(USE_HTTP2) + struct mg_http2_connection http2; +#endif struct mg_request_info request_info; struct mg_response_info response_info; @@ -2811,7 +3061,6 @@ struct mg_connection { * mg_get_connection_info_impl */ #endif - const char *host; /* Host (HTTP/1.1 header or SNI) */ SSL *ssl; /* SSL descriptor */ struct socket client; /* Connected client */ time_t conn_birth_time; /* Time (wall clock) when connection was @@ -2846,6 +3095,18 @@ struct mg_connection { * pages */ #if defined(USE_WEBSOCKET) int in_websocket_handling; /* 1 if in read_websocket */ +#endif +#if defined(USE_ZLIB) && defined(USE_WEBSOCKET) \ + && defined(MG_EXPERIMENTAL_INTERFACES) + /* Parameters for websocket data compression according to rfc7692 */ + int websocket_deflate_server_max_windows_bits; + int websocket_deflate_client_max_windows_bits; + int websocket_deflate_server_no_context_takeover; + int websocket_deflate_client_no_context_takeover; + int websocket_deflate_initialized; + int websocket_deflate_flush; + z_stream websocket_deflate_state; + z_stream websocket_inflate_state; #endif int handled_requests; /* Number of requests handled by this connection */ @@ -2866,8 +3127,6 @@ struct mg_connection { void *tls_user_ptr; /* User defined pointer in thread local storage, * for quick access */ - - char *cookie_header; // <---- Pi-hole modification }; @@ -2879,13 +3138,6 @@ struct de { }; -#if defined(USE_WEBSOCKET) -static int is_websocket_protocol(const struct mg_connection *conn); -#else -#define is_websocket_protocol(conn) (0) -#endif - - #define mg_cry_internal(conn, fmt, ...) \ mg_cry_internal_wrap(conn, NULL, __func__, __LINE__, fmt, __VA_ARGS__) @@ -3135,25 +3387,6 @@ mg_set_thread_name(const char *threadName) #endif -#if defined(MG_LEGACY_INTERFACE) -const char ** -mg_get_valid_option_names(void) -{ - /* This function is deprecated. Use mg_get_valid_options instead. */ - static const char - *data[2 * sizeof(config_options) / sizeof(config_options[0])] = {0}; - int i; - - for (i = 0; config_options[i].name != NULL; i++) { - data[i * 2] = config_options[i].name; - data[i * 2 + 1] = config_options[i].default_value; - } - - return data; -} -#endif - - const struct mg_option * mg_get_valid_options(void) { @@ -3161,7 +3394,7 @@ mg_get_valid_options(void) } -/* Do not open file (used in is_file_in_memory) */ +/* Do not open file (unused) */ #define MG_FOPEN_MODE_NONE (0) /* Open file for read only access */ @@ -3174,103 +3407,68 @@ mg_get_valid_options(void) #define MG_FOPEN_MODE_APPEND (4) -/* If a file is in memory, set all "stat" members and the membuf pointer of - * output filep and return 1, otherwise return 0 and don't modify anything. - */ static int -open_file_in_memory(const struct mg_connection *conn, - const char *path, - struct mg_file *filep, - int mode) +is_file_opened(const struct mg_file_access *fileacc) { -#if defined(MG_USE_OPEN_FILE) - - size_t size = 0; - const char *buf = NULL; - if (!conn) { + if (!fileacc) { return 0; } - if ((mode != MG_FOPEN_MODE_NONE) && (mode != MG_FOPEN_MODE_READ)) { - return 0; - } + return (fileacc->fp != NULL); +} - if (conn->phys_ctx->callbacks.open_file) { - buf = conn->phys_ctx->callbacks.open_file(conn, path, &size); - if (buf != NULL) { - if (filep == NULL) { - /* This is a file in memory, but we cannot store the - * properties - * now. - * Called from "is_file_in_memory" function. */ - return 1; - } - /* NOTE: override filep->size only on success. Otherwise, it - * might - * break constructs like if (!mg_stat() || !mg_fopen()) ... */ - filep->access.membuf = buf; - filep->access.fp = NULL; +#if !defined(NO_FILESYSTEMS) +static int mg_stat(const struct mg_connection *conn, + const char *path, + struct mg_file_stat *filep); - /* Size was set by the callback */ - filep->stat.size = size; - /* Assume the data may change during runtime by setting - * last_modified = now */ - filep->stat.last_modified = time(NULL); +/* Reject files with special characters */ +static int +mg_path_suspicious(const struct mg_connection *conn, const char *path) +{ + const uint8_t *c = (const uint8_t *)path; + (void)conn; /* not used */ - filep->stat.is_directory = 0; - filep->stat.is_gzipped = 0; - } + if ((c == NULL) || (c[0] == 0)) { + /* Null pointer or empty path --> suspicious */ + return 1; } - return (buf != NULL); - + while (*c) { + if (*c <= 32) { + /* Control character or space */ + return 0; + } + if ((*c == '>') || (*c == '<') || (*c == '|')) { + /* stdin/stdout redirection character */ + return 0; + } +#if defined(_WIN32) + if (*c == '\\') { + /* Windows backslash */ + return 0; + } #else - (void)conn; - (void)path; - (void)filep; - (void)mode; - - return 0; - + if (*c == '&') { + /* Linux ampersand */ + return 0; + } #endif -} - - -static int -is_file_in_memory(const struct mg_connection *conn, const char *path) -{ - return open_file_in_memory(conn, path, NULL, MG_FOPEN_MODE_NONE); -} - - -static int -is_file_opened(const struct mg_file_access *fileacc) -{ - if (!fileacc) { - return 0; + c++; } -#if defined(MG_USE_OPEN_FILE) - return (fileacc->membuf != NULL) || (fileacc->fp != NULL); -#else - return (fileacc->fp != NULL); -#endif + /* Nothing suspicious found */ + return 0; } -#if !defined(NO_FILESYSTEMS) -static int mg_stat(const struct mg_connection *conn, - const char *path, - struct mg_file_stat *filep); - - /* mg_fopen will open a file either in memory or on the disk. * The input parameter path is a string in UTF-8 encoding. * The input parameter mode is MG_FOPEN_MODE_* - * On success, either fp or membuf will be set in the output - * struct file. All status members will also be set. + * On success, fp will be set in the output struct mg_file. + * All status members will also be set. * The function returns 1 on success, 0 on error. */ static int mg_fopen(const struct mg_connection *conn, @@ -3284,75 +3482,61 @@ mg_fopen(const struct mg_connection *conn, return 0; } filep->access.fp = NULL; -#if defined(MG_USE_OPEN_FILE) - filep->access.membuf = NULL; -#endif - if (!is_file_in_memory(conn, path)) { + if (mg_path_suspicious(conn, path)) { + return 0; + } - /* filep is initialized in mg_stat: all fields with memset to, - * some fields like size and modification date with values */ - found = mg_stat(conn, path, &(filep->stat)); + /* filep is initialized in mg_stat: all fields with memset to, + * some fields like size and modification date with values */ + found = mg_stat(conn, path, &(filep->stat)); - if ((mode == MG_FOPEN_MODE_READ) && (!found)) { - /* file does not exist and will not be created */ - return 0; - } + if ((mode == MG_FOPEN_MODE_READ) && (!found)) { + /* file does not exist and will not be created */ + return 0; + } #if defined(_WIN32) - { - wchar_t wbuf[W_PATH_MAX]; - path_to_unicode(conn, path, wbuf, ARRAY_SIZE(wbuf)); - switch (mode) { - case MG_FOPEN_MODE_READ: - filep->access.fp = _wfopen(wbuf, L"rb"); - break; - case MG_FOPEN_MODE_WRITE: - filep->access.fp = _wfopen(wbuf, L"wb"); - break; - case MG_FOPEN_MODE_APPEND: - filep->access.fp = _wfopen(wbuf, L"ab"); - break; - } - } -#else - /* Linux et al already use unicode. No need to convert. */ + { + wchar_t wbuf[W_PATH_MAX]; + path_to_unicode(conn, path, wbuf, ARRAY_SIZE(wbuf)); switch (mode) { case MG_FOPEN_MODE_READ: - filep->access.fp = fopen(path, "r"); + filep->access.fp = _wfopen(wbuf, L"rb"); break; case MG_FOPEN_MODE_WRITE: - filep->access.fp = fopen(path, "w"); + filep->access.fp = _wfopen(wbuf, L"wb"); break; case MG_FOPEN_MODE_APPEND: - filep->access.fp = fopen(path, "a"); + filep->access.fp = _wfopen(wbuf, L"ab"); break; } + } +#else + /* Linux et al already use unicode. No need to convert. */ + switch (mode) { + case MG_FOPEN_MODE_READ: + filep->access.fp = fopen(path, "r"); + break; + case MG_FOPEN_MODE_WRITE: + filep->access.fp = fopen(path, "w"); + break; + case MG_FOPEN_MODE_APPEND: + filep->access.fp = fopen(path, "a"); + break; + } #endif - if (!found) { - /* File did not exist before fopen was called. - * Maybe it has been created now. Get stat info - * like creation time now. */ - found = mg_stat(conn, path, &(filep->stat)); - (void)found; - } - - /* file is on disk */ - return (filep->access.fp != NULL); - - } else { -#if defined(MG_USE_OPEN_FILE) - /* is_file_in_memory returned true */ - if (open_file_in_memory(conn, path, filep, mode)) { - /* file is in memory */ - return (filep->access.membuf != NULL); - } -#endif + if (!found) { + /* File did not exist before fopen was called. + * Maybe it has been created now. Get stat info + * like creation time now. */ + found = mg_stat(conn, path, &(filep->stat)); + (void)found; } - /* Open failed */ - return 0; + /* return OK if file is opened */ + return (filep->access.fp != NULL); } @@ -3364,10 +3548,6 @@ mg_fclose(struct mg_file_access *fileacc) if (fileacc != NULL) { if (fileacc->fp != NULL) { ret = fclose(fileacc->fp); -#if defined(MG_USE_OPEN_FILE) - } else if (fileacc->membuf != NULL) { - ret = 0; -#endif } /* reset all members of fileacc */ memset(fileacc, 0, sizeof(*fileacc)); @@ -3630,13 +3810,7 @@ mg_get_ports(const struct mg_context *ctx, size_t size, int *ports, int *ssl) } for (i = 0; i < size && i < ctx->num_listening_sockets; i++) { ssl[i] = ctx->listening_sockets[i].is_ssl; - ports[i] = -#if defined(USE_IPV6) - (ctx->listening_sockets[i].lsa.sa.sa_family == AF_INET6) - ? ntohs(ctx->listening_sockets[i].lsa.sin6.sin6_port) - : -#endif - ntohs(ctx->listening_sockets[i].lsa.sin.sin_port); + ports[i] = ntohs(USA_IN_PORT_UNSAFE(&(ctx->listening_sockets[i].lsa))); } return i; } @@ -3664,12 +3838,7 @@ mg_get_server_ports(const struct mg_context *ctx, for (i = 0; (i < size) && (i < (int)ctx->num_listening_sockets); i++) { ports[cnt].port = -#if defined(USE_IPV6) - (ctx->listening_sockets[i].lsa.sa.sa_family == AF_INET6) - ? ntohs(ctx->listening_sockets[i].lsa.sin6.sin6_port) - : -#endif - ntohs(ctx->listening_sockets[i].lsa.sin.sin_port); + ntohs(USA_IN_PORT_UNSAFE(&(ctx->listening_sockets[i].lsa))); ports[cnt].is_ssl = ctx->listening_sockets[i].is_ssl; ports[cnt].is_redirect = ctx->listening_sockets[i].ssl_redir; @@ -3968,9 +4137,9 @@ get_proto_name(const struct mg_connection *conn) const struct mg_request_info *ri = &conn->request_info; - const char *proto = - (is_websocket_protocol(conn) ? (ri->is_ssl ? "wss" : "ws") - : (ri->is_ssl ? "https" : "http")); + const char *proto = ((conn->protocol_type == PROTOCOL_TYPE_WEBSOCKET) + ? (ri->is_ssl ? "wss" : "ws") + : (ri->is_ssl ? "https" : "http")); return proto; @@ -3980,8 +4149,13 @@ get_proto_name(const struct mg_connection *conn) } -int -mg_get_request_link(const struct mg_connection *conn, char *buf, size_t buflen) +static int +mg_construct_local_link(const struct mg_connection *conn, + char *buf, + size_t buflen, + const char *define_proto, + int define_port, + const char *define_uri) { if ((buflen < 1) || (buf == 0) || (conn == 0)) { return -1; @@ -3990,53 +4164,49 @@ mg_get_request_link(const struct mg_connection *conn, char *buf, size_t buflen) int truncated = 0; const struct mg_request_info *ri = &conn->request_info; - const char *proto = get_proto_name(conn); - - if (ri->local_uri == NULL) { + const char *proto = + (define_proto != NULL) ? define_proto : get_proto_name(conn); + const char *uri = + (define_uri != NULL) + ? define_uri + : ((ri->request_uri != NULL) ? ri->request_uri : ri->local_uri); + int port = (define_port > 0) + ? define_port + : htons(USA_IN_PORT_UNSAFE(&conn->client.lsa)); + int default_port = 80; + + if (uri == NULL) { return -1; } - if ((ri->request_uri != NULL) - && (0 != strcmp(ri->local_uri, ri->request_uri))) { - /* The request uri is different from the local uri. - * This is usually if an absolute URI, including server - * name has been provided. */ - mg_snprintf(conn, - &truncated, - buf, - buflen, - "%s://%s", - proto, - ri->request_uri); - if (truncated) { - return -1; + if (define_proto) { + /* If we got a protocol name, use the default port accordingly. */ + if ((0 == strcmp(define_proto, "https")) + || (0 == strcmp(define_proto, "wss"))) { + default_port = 443; } - return 0; - - } else { - - /* The common case is a relative URI, so we have to - * construct an absolute URI from server name and port */ + } else if (ri->is_ssl) { + /* If we did not get a protocol name, use TLS as default if it is + * already used. */ + default_port = 443; + } + { #if defined(USE_IPV6) int is_ipv6 = (conn->client.lsa.sa.sa_family == AF_INET6); - int port = is_ipv6 ? htons(conn->client.lsa.sin6.sin6_port) - : htons(conn->client.lsa.sin.sin_port); -#else - int port = htons(conn->client.lsa.sin.sin_port); #endif - int def_port = ri->is_ssl ? 443 : 80; int auth_domain_check_enabled = conn->dom_ctx->config[ENABLE_AUTH_DOMAIN_CHECK] && (!mg_strcasecmp( conn->dom_ctx->config[ENABLE_AUTH_DOMAIN_CHECK], "yes")); + const char *server_domain = conn->dom_ctx->config[AUTHENTICATION_DOMAIN]; char portstr[16]; char server_ip[48]; - if (port != def_port) { + if (port != default_port) { sprintf(portstr, ":%u", (unsigned)port); } else { portstr[0] = 0; @@ -4055,11 +4225,20 @@ mg_get_request_link(const struct mg_connection *conn, char *buf, size_t buflen) &truncated, buf, buflen, +#if defined(USE_IPV6) + "%s://%s%s%s%s%s", + proto, + (is_ipv6 && (server_domain == server_ip)) ? "[" : "", + server_domain, + (is_ipv6 && (server_domain == server_ip)) ? "]" : "", +#else "%s://%s%s%s", proto, server_domain, +#endif portstr, ri->local_uri); + if (truncated) { return -1; } @@ -4068,6 +4247,14 @@ mg_get_request_link(const struct mg_connection *conn, char *buf, size_t buflen) } } + +int +mg_get_request_link(const struct mg_connection *conn, char *buf, size_t buflen) +{ + return mg_construct_local_link(conn, buf, buflen, NULL, -1, NULL); +} + + /* Skip the characters until one of the delimiters characters found. * 0-terminate resulting word. Skip the delimiter and following whitespaces. * Advance pointer to buffer to the next word. Return found 0-terminated @@ -4318,15 +4505,17 @@ match_prefix(const char *pattern, size_t pattern_len, const char *str) i++; if (pattern[i] == '*') { i++; - len = strlen(str + j); + len = (ptrdiff_t)strlen(str + j); } else { - len = strcspn(str + j, "/"); + len = (ptrdiff_t)strcspn(str + j, "/"); } if (i == (ptrdiff_t)pattern_len) { return j + len; } do { - res = match_prefix(pattern + i, pattern_len - i, str + j + len); + res = match_prefix(pattern + i, + (pattern_len - (size_t)i), + str + j + len); } while (res == -1 && len-- > 0); return (res == -1) ? -1 : j + res + len; } else if (lowercase(&pattern[i]) != lowercase(&str[j])) { @@ -4337,6 +4526,16 @@ match_prefix(const char *pattern, size_t pattern_len, const char *str) } +static ptrdiff_t +match_prefix_strlen(const char *pattern, const char *str) +{ + if (pattern == NULL) { + return -1; + } + return match_prefix(pattern, strlen(pattern), str); +} + + /* HTTP 1.1 assumes keep alive if "Connection:" header is not set * This function must tolerate situations when connection info is not * set up, for example if request parsing failed. */ @@ -4397,28 +4596,43 @@ suggest_connection_header(const struct mg_connection *conn) } -static int +#include "response.inl" + + +static void send_no_cache_header(struct mg_connection *conn) { /* Send all current and obsolete cache opt-out directives. */ - return mg_printf(conn, - "Cache-Control: no-cache, no-store, " - "must-revalidate, private, max-age=0\r\n" - "Pragma: no-cache\r\n" - "Expires: 0\r\n"); + mg_response_header_add(conn, + "Cache-Control", + "no-cache, no-store, " + "must-revalidate, private, max-age=0", + -1); + mg_response_header_add(conn, "Expires", "0", -1); + + if (conn->protocol_type == PROTOCOL_TYPE_HTTP1) { + /* Obsolete, but still send it for HTTP/1.0 */ + mg_response_header_add(conn, "Pragma", "no-cache", -1); + } } -static int +static void send_static_cache_header(struct mg_connection *conn) { #if !defined(NO_CACHING) int max_age; + char val[64]; + const char *cache_control = conn->dom_ctx->config[STATIC_FILE_CACHE_CONTROL]; + + /* If there is a full cache-control option configured,0 use it */ if (cache_control != NULL) { - return mg_printf(conn, "Cache-Control: %s\r\n", cache_control); + mg_response_header_add(conn, "Cache-Control", cache_control, -1); + return; } + /* Read the server config to check how long a file may be cached. * The configuration is in seconds. */ max_age = atoi(conn->dom_ctx->config[STATIC_FILE_MAX_AGE]); @@ -4427,7 +4641,8 @@ send_static_cache_header(struct mg_connection *conn) * and may be used differently in the future. */ /* If a file should not be cached, do not only send * max-age=0, but also pragmas and Expires headers. */ - return send_no_cache_header(conn); + send_no_cache_header(conn); + return; } /* Use "Cache-Control: max-age" instead of "Expires" header. @@ -4439,45 +4654,49 @@ send_static_cache_header(struct mg_connection *conn) * year to 31622400 seconds. For the moment, we just send whatever has * been configured, still the behavior for >1 year should be considered * as undefined. */ - return mg_printf(conn, "Cache-Control: max-age=%u\r\n", (unsigned)max_age); + mg_snprintf( + conn, NULL, val, sizeof(val), "max-age=%lu", (unsigned long)max_age); + mg_response_header_add(conn, "Cache-Control", val, -1); + #else /* NO_CACHING */ - return send_no_cache_header(conn); + + send_no_cache_header(conn); #endif /* !NO_CACHING */ } -static int +static void send_additional_header(struct mg_connection *conn) { - int i = 0; const char *header = conn->dom_ctx->config[ADDITIONAL_HEADER]; #if !defined(NO_SSL) if (conn->dom_ctx->config[STRICT_HTTPS_MAX_AGE]) { - int max_age = atoi(conn->dom_ctx->config[STRICT_HTTPS_MAX_AGE]); + long max_age = atol(conn->dom_ctx->config[STRICT_HTTPS_MAX_AGE]); if (max_age >= 0) { - i += mg_printf(conn, - "Strict-Transport-Security: max-age=%u\r\n", - (unsigned)max_age); + char val[64]; + mg_snprintf(conn, + NULL, + val, + sizeof(val), + "max-age=%lu", + (unsigned long)max_age); + mg_response_header_add(conn, "Strict-Transport-Security", val, -1); } } #endif - /**************** Pi-hole modification ****************/ - if(conn->cookie_header != NULL && - conn->cookie_header[0]) - { - i += mg_printf(conn, "%s", conn->cookie_header); - mg_free(conn->cookie_header); - conn->cookie_header = NULL; - } - /******************************************************/ - if (header && header[0]) { - i += mg_printf(conn, "%s\r\n", header); + mg_response_header_add_lines(conn, header); } - return i; + /*************** Pi-hole modification ****************/ + if (pi_hole_extra_headers[0] != '\0') { + mg_response_header_add_lines(conn, pi_hole_extra_headers); + // Invalidate extra headers after having sent them to avoid repetitions + pi_hole_extra_headers[0] = '\0'; + } + /*****************************************************/ } @@ -4696,8 +4915,7 @@ mg_send_http_error_impl(struct mg_connection *conn, char errmsg_buf[MG_BUF_LEN]; va_list ap; int has_body; - char date[64]; - time_t curtime = time(NULL); + #if !defined(NO_FILESYSTEMS) char path_buf[PATH_MAX]; int len, i, page_handler_found, scope, truncated; @@ -4707,8 +4925,6 @@ mg_send_http_error_impl(struct mg_connection *conn, #endif /* NO_FILESYSTEMS */ int handled_by_callback = 0; - const char *status_text = mg_get_response_code_text(conn, status); - if ((conn == NULL) || (fmt == NULL)) { return -2; } @@ -4795,8 +5011,17 @@ mg_send_http_error_impl(struct mg_connection *conn, * from the config, not from a client. */ (void)truncated; + /* The following code is redundant, but it should avoid + * false positives in static source code analyzers and + * vulnerability scanners. + */ + path_buf[sizeof(path_buf) - 32] = 0; len = (int)strlen(path_buf); + if (len > (int)sizeof(path_buf) - 32) { + len = (int)sizeof(path_buf) - 32; + } + /* Start with the file extenstion from the configuration. */ tstr = strchr(error_page_file_ext, '.'); while (tstr) { @@ -4822,6 +5047,8 @@ mg_send_http_error_impl(struct mg_connection *conn, DEBUG_TRACE("Check error page %s - not found", path_buf); + /* Continue with the next file extenstion from the + * configuration (if there is a next one). */ tstr = strchr(tstr + i, '.'); } } @@ -4837,25 +5064,22 @@ mg_send_http_error_impl(struct mg_connection *conn, } /* No custom error page. Send default error page. */ - gmt_time_string(date, sizeof(date), &curtime); - conn->must_close = 1; - mg_printf(conn, "HTTP/1.1 %d %s\r\n", status, status_text); + mg_response_header_start(conn, status); send_no_cache_header(conn); send_additional_header(conn); if (has_body) { - mg_printf(conn, - "%s", - "Content-Type: text/plain; charset=utf-8\r\n"); + mg_response_header_add(conn, + "Content-Type", + "text/plain; charset=utf-8", + -1); } - mg_printf(conn, - "Date: %s\r\n" - "Connection: close\r\n\r\n", - date); + mg_response_header_send(conn); /* HTTP responses 1xx, 204 and 304 MUST NOT send a body */ if (has_body) { /* For other errors, send a generic error message. */ + const char *status_text = mg_get_response_code_text(conn, status); mg_printf(conn, "Error %d: %s\n", status, status_text); mg_write(conn, errmsg_buf, strlen(errmsg_buf)); @@ -4878,8 +5102,10 @@ void my_send_http_error_headers(struct mg_connection *conn, const char *status_text = mg_get_response_code_text(conn, status); mg_printf(conn, "HTTP/1.1 %d %s\r\n", status, status_text); + mg_response_header_start(conn, status); send_no_cache_header(conn); send_additional_header(conn); + mg_response_header_send(conn); conn->must_close = 1; char date[64]; @@ -4895,12 +5121,6 @@ void my_send_http_error_headers(struct mg_connection *conn, (uint64_t)content_length); } -void my_set_cookie_header(struct mg_connection *conn, - const char *cookie_header) -{ - conn->cookie_header = mg_strdup(cookie_header); -} - /********************************************************************************************/ int @@ -4923,41 +5143,37 @@ mg_send_http_ok(struct mg_connection *conn, const char *additional_headers, long long content_length) { - char date[64]; - time_t curtime = time(NULL); - if ((mime_type == NULL) || (*mime_type == 0)) { /* No content type defined: default to text/html */ mime_type = "text/html"; } - gmt_time_string(date, sizeof(date), &curtime); - - mg_printf(conn, - "HTTP/1.1 200 OK\r\n" - "Content-Type: %s\r\n" - "Date: %s\r\n" - "Connection: %s\r\n", - mime_type, - date, - suggest_connection_header(conn)); - - /********************** Pi-hole modification **********************/ - if(additional_headers != NULL && strlen(additional_headers) > 0) - { - mg_write(conn, additional_headers, strlen(additional_headers)); - } - /******************************************************************/ - + mg_response_header_start(conn, 200); send_no_cache_header(conn); send_additional_header(conn); + mg_response_header_add(conn, "Content-Type", mime_type, -1); if (content_length < 0) { - mg_printf(conn, "Transfer-Encoding: chunked\r\n\r\n"); + /* Size not known. Use chunked encoding (HTTP/1.x) */ + if (conn->protocol_type == PROTOCOL_TYPE_HTTP1) { + /* Only HTTP/1.x defines "chunked" encoding, HTTP/2 does not*/ + mg_response_header_add(conn, "Transfer-Encoding", "chunked", -1); + } } else { - mg_printf(conn, - "Content-Length: %" UINT64_FMT "\r\n\r\n", - (uint64_t)content_length); + char len[32]; + int trunc = 0; + mg_snprintf(conn, + &trunc, + len, + sizeof(len), + "%" UINT64_FMT, + (uint64_t)content_length); + if (!trunc) { + /* Since 32 bytes is enough to hold any 64 bit decimal number, + * !trunc is always true */ + mg_response_header_add(conn, "Content-Length", len, -1); + } } + mg_response_header_send(conn); return 0; } @@ -4982,7 +5198,9 @@ mg_send_http_redirect(struct mg_connection *conn, const char *redirect_text; int ret; size_t content_len = 0; +#if defined(MG_SEND_REDIRECT_BODY) char reply[MG_BUF_LEN]; +#endif /* In case redirect_code=0, use 307. */ if (redirect_code == 0) { @@ -5038,8 +5256,6 @@ mg_send_http_redirect(struct mg_connection *conn, target_url, target_url); content_len = strlen(reply); -#else - reply[0] = 0; #endif /* Do not send any additional header. For all other options, @@ -5055,6 +5271,7 @@ mg_send_http_redirect(struct mg_connection *conn, (unsigned int)content_len, suggest_connection_header(conn)); +#if defined(MG_SEND_REDIRECT_BODY) /* Send response body */ if (ret > 0) { /* ... unless it is a HEAD request */ @@ -5062,6 +5279,7 @@ mg_send_http_redirect(struct mg_connection *conn, ret = mg_write(conn, reply, content_len); } } +#endif return (ret > 0) ? ret : -1; } @@ -5293,7 +5511,7 @@ change_slashes_to_backslashes(char *path) /* remove double backslash (check i > 0 to preserve UNC paths, * like \\server\file.txt) */ - if ((path[i] == '\\') && (i > 0)) { + if ((i > 0) && (path[i] == '\\')) { while ((path[i + 1] == '\\') || (path[i + 1] == '/')) { (void)memmove(path + i + 1, path + i + 2, strlen(path + i + 1)); } @@ -5391,6 +5609,7 @@ path_to_unicode(const struct mg_connection *conn, #if !defined(NO_FILESYSTEMS) +/* Get file information, return 1 if file exists, 0 if not */ static int mg_stat(const struct mg_connection *conn, const char *path, @@ -5406,33 +5625,8 @@ mg_stat(const struct mg_connection *conn, } memset(filep, 0, sizeof(*filep)); - if (conn && is_file_in_memory(conn, path)) { - /* filep->is_directory = 0; filep->gzipped = 0; .. already done by - * memset */ - - /* Quick fix (for 1.9.x): */ - /* mg_stat must fill all fields, also for files in memory */ - struct mg_file tmp_file = STRUCT_FILE_INITIALIZER; - open_file_in_memory(conn, path, &tmp_file, MG_FOPEN_MODE_NONE); - filep->size = tmp_file.stat.size; - filep->location = 2; - /* TODO: for 1.10: restructure how files in memory are handled */ - - /* The "file in memory" feature is a candidate for deletion. - * Please join the discussion at - * https://groups.google.com/forum/#!topic/civetweb/h9HT4CmeYqI - */ - - filep->last_modified = time(NULL); /* TODO */ - /* last_modified = now ... assumes the file may change during - * runtime, - * so every mg_fopen call may return different data */ - /* last_modified = conn->phys_ctx.start_time; - * May be used it the data does not change during runtime. This - * allows - * browser caching. Since we do not know, we have to assume the file - * in memory may change. */ - return 1; + if (mg_path_suspicious(conn, path)) { + return 0; } path_to_unicode(conn, path, wbuf, ARRAY_SIZE(wbuf)); @@ -5832,8 +6026,9 @@ spawn_process(struct mg_connection *conn, const char *dir) { HANDLE me; - char *p, *interp, full_interp[PATH_MAX], full_dir[PATH_MAX], - cmdline[PATH_MAX], buf[PATH_MAX]; + char *interp; + char *interp_arg = 0; + char full_dir[PATH_MAX], cmdline[PATH_MAX], buf[PATH_MAX]; int truncated; struct mg_file file = STRUCT_FILE_INITIALIZER; STARTUPINFOA si; @@ -5883,12 +6078,19 @@ spawn_process(struct mg_connection *conn, HANDLE_FLAG_INHERIT, 0); - /* If CGI file is a script, try to read the interpreter line */ + /* First check, if there is a CGI interpreter configured for all CGI + * scripts. */ interp = conn->dom_ctx->config[CGI_INTERPRETER]; - if (interp == NULL) { + if (interp != NULL) { + /* If there is a configured interpreter, check for additional arguments + */ + interp_arg = conn->dom_ctx->config[CGI_INTERPRETER_ARGS]; + } else { + /* Otherwise, the interpreter must be stated in the first line of the + * CGI script file, after a #! (shebang) mark. */ buf[0] = buf[1] = '\0'; - /* Read the first line of the script into the buffer */ + /* Get the full script path */ mg_snprintf( conn, &truncated, cmdline, sizeof(cmdline), "%s/%s", dir, prog); @@ -5897,13 +6099,11 @@ spawn_process(struct mg_connection *conn, goto spawn_cleanup; } + /* Open the script file, to read the first line */ if (mg_fopen(conn, cmdline, MG_FOPEN_MODE_READ, &file)) { -#if defined(MG_USE_OPEN_FILE) - p = (char *)file.access.membuf; -#else - p = (char *)NULL; -#endif - mg_fgets(buf, sizeof(buf), &file, &p); + + /* Read the first line of the script into the buffer */ + mg_fgets(buf, sizeof(buf), &file); (void)mg_fclose(&file.access); /* ignore error on read only file */ buf[sizeof(buf) - 1] = '\0'; } @@ -5916,22 +6116,32 @@ spawn_process(struct mg_connection *conn, interp = buf + 2; } - if (interp[0] != '\0') { - GetFullPathNameA(interp, sizeof(full_interp), full_interp, NULL); - interp = full_interp; - } GetFullPathNameA(dir, sizeof(full_dir), full_dir, NULL); if (interp[0] != '\0') { - mg_snprintf(conn, - &truncated, - cmdline, - sizeof(cmdline), - "\"%s\" \"%s\\%s\"", - interp, - full_dir, - prog); + /* This is an interpreted script file. We must call the interpreter. */ + if ((interp_arg != 0) && (interp_arg[0] != 0)) { + mg_snprintf(conn, + &truncated, + cmdline, + sizeof(cmdline), + "\"%s\" %s \"%s\\%s\"", + interp, + interp_arg, + full_dir, + prog); + } else { + mg_snprintf(conn, + &truncated, + cmdline, + sizeof(cmdline), + "\"%s\" \"%s\\%s\"", + interp, + full_dir, + prog); + } } else { + /* This is (probably) a compiled program. We call it directly. */ mg_snprintf(conn, &truncated, cmdline, @@ -5984,6 +6194,7 @@ set_blocking_mode(SOCKET sock) return ioctlsocket(sock, (long)FIONBIO, &non_blocking); } + static int set_non_blocking_mode(SOCKET sock) { @@ -5991,8 +6202,10 @@ set_non_blocking_mode(SOCKET sock) return ioctlsocket(sock, (long)FIONBIO, &non_blocking); } + #else + #if !defined(NO_FILESYSTEMS) static int mg_stat(const struct mg_connection *conn, @@ -6005,18 +6218,8 @@ mg_stat(const struct mg_connection *conn, } memset(filep, 0, sizeof(*filep)); - if (conn && is_file_in_memory(conn, path)) { - - /* Quick fix (for 1.9.x): */ - /* mg_stat must fill all fields, also for files in memory */ - struct mg_file tmp_file = STRUCT_FILE_INITIALIZER; - open_file_in_memory(conn, path, &tmp_file, MG_FOPEN_MODE_NONE); - filep->size = tmp_file.stat.size; - filep->last_modified = time(NULL); - filep->location = 2; - /* TODO: remove legacy "files in memory" feature */ - - return 1; + if (mg_path_suspicious(conn, path)) { + return 0; } if (0 == stat(path, &st)) { @@ -6195,6 +6398,7 @@ spawn_process(struct mg_connection *conn, interp = conn->dom_ctx->config[CGI_INTERPRETER]; if (interp == NULL) { + /* no interpreter configured, call the programm directly */ (void)execle(prog, prog, NULL, envp); mg_cry_internal(conn, "%s: execle(%s): %s", @@ -6202,7 +6406,15 @@ spawn_process(struct mg_connection *conn, prog, strerror(ERRNO)); } else { - (void)execle(interp, interp, prog, NULL, envp); + /* call the configured interpreter */ + const char *interp_args = + conn->dom_ctx->config[CGI_INTERPRETER_ARGS]; + + if ((interp_args != NULL) && (interp_args[0] != 0)) { + (void)execle(interp, interp, interp_args, prog, NULL, envp); + } else { + (void)execle(interp, interp, prog, NULL, envp); + } mg_cry_internal(conn, "%s: execle(%s %s): %s", __func__, @@ -6285,7 +6497,7 @@ static int mg_poll(struct mg_pollfd *pfd, unsigned int n, int milliseconds, - volatile int *stop_server) + stop_flag_t *stop_flag) { /* Call poll, but only for a maximum time of a few seconds. * This will allow to stop the server after some seconds, instead @@ -6295,7 +6507,7 @@ mg_poll(struct mg_pollfd *pfd, do { int result; - if (*stop_server) { + if (!STOP_FLAG_IS_ZERO(&*stop_flag)) { /* Shut down signal */ return -2; } @@ -6316,7 +6528,7 @@ mg_poll(struct mg_pollfd *pfd, milliseconds -= ms_now; } - } while (milliseconds != 0); + } while (milliseconds > 0); /* timeout: return 0 */ return 0; @@ -6371,6 +6583,7 @@ push_inner(struct mg_context *ctx, #if !defined(NO_SSL) if (ssl != NULL) { + ERR_clear_error(); n = SSL_write(ssl, buf, len); if (n <= 0) { err = SSL_get_error(ssl, n); @@ -6381,8 +6594,10 @@ push_inner(struct mg_context *ctx, n = 0; } else { DEBUG_TRACE("SSL_write() failed, error %d", err); + ERR_clear_error(); return -2; } + ERR_clear_error(); } else { err = 0; } @@ -6405,7 +6620,7 @@ push_inner(struct mg_context *ctx, n = 0; } #else - if (err == EWOULDBLOCK) { + if (ERROR_TRY_AGAIN(err)) { err = 0; n = 0; } @@ -6416,7 +6631,7 @@ push_inner(struct mg_context *ctx, } } - if (ctx->stop_flag) { + if (!STOP_FLAG_IS_ZERO(&ctx->stop_flag)) { return -2; } @@ -6452,7 +6667,7 @@ push_inner(struct mg_context *ctx, pfd[0].fd = sock; pfd[0].events = POLLOUT; pollres = mg_poll(pfd, 1, (int)(ms_wait), &(ctx->stop_flag)); - if (ctx->stop_flag) { + if (!STOP_FLAG_IS_ZERO(&ctx->stop_flag)) { return -2; } if (pollres > 0) { @@ -6494,8 +6709,11 @@ push_all(struct mg_context *ctx, if (ctx->dd.config[REQUEST_TIMEOUT]) { timeout = atoi(ctx->dd.config[REQUEST_TIMEOUT]) / 1000.0; } + if (timeout <= 0.0) { + timeout = atof(config_options[REQUEST_TIMEOUT].default_value) / 1000.0; + } - while ((len > 0) && (ctx->stop_flag == 0)) { + while ((len > 0) && STOP_FLAG_IS_ZERO(&ctx->stop_flag)) { n = push_inner(ctx, fp, sock, ssl, buf + nwritten, len, timeout); if (n < 0) { if (nwritten == 0) { @@ -6534,9 +6752,6 @@ pull_inner(FILE *fp, #else typedef size_t len_t; #endif -#if !defined(NO_SSL) - int ssl_pending; -#endif /* We need an additional wait loop around this, because in some cases * with TLSwe may get data from the socket but not from SSL_read. @@ -6561,49 +6776,34 @@ pull_inner(FILE *fp, } #if !defined(NO_SSL) - } else if ((conn->ssl != NULL) - && ((ssl_pending = SSL_pending(conn->ssl)) > 0)) { - /* We already know there is no more data buffered in conn->buf - * but there is more available in the SSL layer. So don't poll - * conn->client.sock yet. */ - if (ssl_pending > len) { - ssl_pending = len; - } - nread = SSL_read(conn->ssl, buf, ssl_pending); - if (nread <= 0) { - err = SSL_get_error(conn->ssl, nread); - if ((err == SSL_ERROR_SYSCALL) && (nread == -1)) { - err = ERRNO; - } else if ((err == SSL_ERROR_WANT_READ) - || (err == SSL_ERROR_WANT_WRITE)) { - nread = 0; - } else { - /* All errors should return -2 */ - DEBUG_TRACE("SSL_read() failed, error %d", err); - return -2; - } - - ERR_clear_error(); - } else { - err = 0; - } - } else if (conn->ssl != NULL) { - + int ssl_pending; struct mg_pollfd pfd[1]; int pollres; - pfd[0].fd = conn->client.sock; - pfd[0].events = POLLIN; - pollres = mg_poll(pfd, - 1, - (int)(timeout * 1000.0), - &(conn->phys_ctx->stop_flag)); - if (conn->phys_ctx->stop_flag) { - return -2; + if ((ssl_pending = SSL_pending(conn->ssl)) > 0) { + /* We already know there is no more data buffered in conn->buf + * but there is more available in the SSL layer. So don't poll + * conn->client.sock yet. */ + if (ssl_pending > len) { + ssl_pending = len; + } + pollres = 1; + } else { + pfd[0].fd = conn->client.sock; + pfd[0].events = POLLIN; + pollres = mg_poll(pfd, + 1, + (int)(timeout * 1000.0), + &(conn->phys_ctx->stop_flag)); + if (!STOP_FLAG_IS_ZERO(&conn->phys_ctx->stop_flag)) { + return -2; + } } if (pollres > 0) { - nread = SSL_read(conn->ssl, buf, len); + ERR_clear_error(); + nread = + SSL_read(conn->ssl, buf, (ssl_pending > 0) ? ssl_pending : len); if (nread <= 0) { err = SSL_get_error(conn->ssl, nread); if ((err == SSL_ERROR_SYSCALL) && (nread == -1)) { @@ -6612,13 +6812,15 @@ pull_inner(FILE *fp, || (err == SSL_ERROR_WANT_WRITE)) { nread = 0; } else { + /* All errors should return -2 */ DEBUG_TRACE("SSL_read() failed, error %d", err); + ERR_clear_error(); return -2; } + ERR_clear_error(); } else { err = 0; } - ERR_clear_error(); } else if (pollres < 0) { /* Error */ return -2; @@ -6638,7 +6840,7 @@ pull_inner(FILE *fp, 1, (int)(timeout * 1000.0), &(conn->phys_ctx->stop_flag)); - if (conn->phys_ctx->stop_flag) { + if (!STOP_FLAG_IS_ZERO(&conn->phys_ctx->stop_flag)) { return -2; } if (pollres > 0) { @@ -6657,7 +6859,7 @@ pull_inner(FILE *fp, } } - if (conn->phys_ctx->stop_flag) { + if (!STOP_FLAG_IS_ZERO(&conn->phys_ctx->stop_flag)) { return -2; } @@ -6690,7 +6892,7 @@ pull_inner(FILE *fp, * blocking in close_socket_gracefully, so we can not distinguish * here. We have to wait for the timeout in both cases for now. */ - if ((err == EAGAIN) || (err == EWOULDBLOCK) || (err == EINTR)) { + if (ERROR_TRY_AGAIN(err)) { /* TODO (low): check if this is still required */ /* EAGAIN/EWOULDBLOCK: * standard case if called from close_socket_gracefully @@ -6724,12 +6926,13 @@ pull_all(FILE *fp, struct mg_connection *conn, char *buf, int len) if (conn->dom_ctx->config[REQUEST_TIMEOUT]) { timeout = atoi(conn->dom_ctx->config[REQUEST_TIMEOUT]) / 1000.0; } - if (timeout >= 0.0) { - start_time = mg_get_current_time_ns(); - timeout_ns = (uint64_t)(timeout * 1.0E9); + if (timeout <= 0.0) { + timeout = atof(config_options[REQUEST_TIMEOUT].default_value) / 1000.0; } + start_time = mg_get_current_time_ns(); + timeout_ns = (uint64_t)(timeout * 1.0E9); - while ((len > 0) && (conn->phys_ctx->stop_flag == 0)) { + while ((len > 0) && STOP_FLAG_IS_ZERO(&conn->phys_ctx->stop_flag)) { n = pull_inner(fp, conn, buf + nread, len, timeout); if (n == -2) { if (nread == 0) { @@ -6829,6 +7032,29 @@ mg_read_inner(struct mg_connection *conn, void *buf, size_t len) } +/* Forward declarations */ +static void handle_request(struct mg_connection *); + + +#if defined(USE_HTTP2) +#if defined(NO_SSL) +#error "HTTP2 requires ALPN, APLN requires SSL/TLS" +#endif +#define USE_ALPN +#include "mod_http2.inl" +/* Not supported with HTTP/2 */ +#define HTTP1_only \ + { \ + if (conn->protocol_type == PROTOCOL_TYPE_HTTP2) { \ + http2_must_use_http1(conn); \ + return; \ + } \ + } +#else +#define HTTP1_only +#endif + + int mg_read(struct mg_connection *conn, void *buf, size_t len) { @@ -6927,7 +7153,7 @@ mg_read(struct mg_connection *conn, void *buf, size_t len) } /* append a new chunk */ - conn->content_len += chunkSize; + conn->content_len += (int64_t)chunkSize; } } @@ -6950,6 +7176,14 @@ mg_write(struct mg_connection *conn, const void *buf, size_t len) return -1; } + /* Mark connection as "data sent" */ + conn->request_state = 10; +#if defined(USE_HTTP2) + if (conn->protocol_type == PROTOCOL_TYPE_HTTP2) { + http2_data_frame_head(conn, len, 0); + } +#endif + if (conn->throttle > 0) { if ((now = time(NULL)) != conn->last_throttle_time) { conn->last_throttle_time = now; @@ -6959,26 +7193,32 @@ mg_write(struct mg_connection *conn, const void *buf, size_t len) if (allowed > (int)len) { allowed = (int)len; } - if ((total = push_all(conn->phys_ctx, - NULL, - conn->client.sock, - conn->ssl, - (const char *)buf, - allowed)) - == allowed) { + + total = push_all(conn->phys_ctx, + NULL, + conn->client.sock, + conn->ssl, + (const char *)buf, + allowed); + + if (total == allowed) { + buf = (const char *)buf + total; conn->last_throttle_bytes += total; - while ((total < (int)len) && (conn->phys_ctx->stop_flag == 0)) { + while ((total < (int)len) + && STOP_FLAG_IS_ZERO(&conn->phys_ctx->stop_flag)) { allowed = (conn->throttle > ((int)len - total)) ? (int)len - total : conn->throttle; - if ((n = push_all(conn->phys_ctx, - NULL, - conn->client.sock, - conn->ssl, - (const char *)buf, - allowed)) - != allowed) { + + n = push_all(conn->phys_ctx, + NULL, + conn->client.sock, + conn->ssl, + (const char *)buf, + allowed); + + if (n != allowed) { break; } sleep(1); @@ -7209,6 +7449,15 @@ mg_url_decode(const char *src, } +/* form url decoding of an entire string */ +static void +url_decode_in_place(char *buf) +{ + int len = (int)strlen(buf); + (void)mg_url_decode(buf, len, buf, len + 1, 1); +} + + int mg_get_var(const char *data, size_t data_len, @@ -7277,6 +7526,105 @@ mg_get_var2(const char *data, } +/* split a string "key1=val1&key2=val2" into key/value pairs */ +int +mg_split_form_urlencoded(char *data, + struct mg_header *form_fields, + unsigned num_form_fields) +{ + char *b; + int i; + int num = 0; + + if (data == NULL) { + /* parameter error */ + return -1; + } + + if ((form_fields == NULL) && (num_form_fields == 0)) { + /* determine the number of expected fields */ + if (data[0] == 0) { + return 0; + } + /* count number of & to return the number of key-value-pairs */ + num = 1; + while (*data) { + if (*data == '&') { + num++; + } + data++; + } + return num; + } + + if ((form_fields == NULL) || ((int)num_form_fields <= 0)) { + /* parameter error */ + return -1; + } + + for (i = 0; i < (int)num_form_fields; i++) { + /* extract key-value pairs from input data */ + while ((*data == ' ') || (*data == '\t')) { + /* skip initial spaces */ + data++; + } + if (*data == 0) { + /* end of string reached */ + break; + } + form_fields[num].name = data; + + /* find & or = */ + b = data; + while ((*b != 0) && (*b != '&') && (*b != '=')) { + b++; + } + + if (*b == 0) { + /* last key without value */ + form_fields[num].value = NULL; + } else if (*b == '&') { + /* mid key without value */ + form_fields[num].value = NULL; + } else { + /* terminate string */ + *b = 0; + /* value starts after '=' */ + data = b + 1; + form_fields[num].value = data; + } + + /* new field is stored */ + num++; + + /* find a next key */ + b = strchr(data, '&'); + if (b == 0) { + /* no more data */ + break; + } else { + /* terminate value of last field at '&' */ + *b = 0; + /* next key-value-pairs starts after '&' */ + data = b + 1; + } + } + + /* Decode all values */ + for (i = 0; i < num; i++) { + if (form_fields[i].name) { + url_decode_in_place((char *)form_fields[i].name); + } + if (form_fields[i].value) { + url_decode_in_place((char *)form_fields[i].value); + } + } + + /* return number of fields found */ + return num; +} + + /* HCP24: some changes to compare hole var_name */ int mg_get_cookie(const char *cookie_header, @@ -7446,25 +7794,25 @@ extention_matches_script( ) { #if !defined(NO_CGI) - if (match_prefix(conn->dom_ctx->config[CGI_EXTENSIONS], - strlen(conn->dom_ctx->config[CGI_EXTENSIONS]), - filename) + if (match_prefix_strlen(conn->dom_ctx->config[CGI_EXTENSIONS], filename) + > 0) { + return 1; + } + if (match_prefix_strlen(conn->dom_ctx->config[CGI2_EXTENSIONS], filename) > 0) { return 1; } #endif #if defined(USE_LUA) - if (match_prefix(conn->dom_ctx->config[LUA_SCRIPT_EXTENSIONS], - strlen(conn->dom_ctx->config[LUA_SCRIPT_EXTENSIONS]), - filename) + if (match_prefix_strlen(conn->dom_ctx->config[LUA_SCRIPT_EXTENSIONS], + filename) > 0) { return 1; } #endif #if defined(USE_DUKTAPE) - if (match_prefix(conn->dom_ctx->config[DUKTAPE_SCRIPT_EXTENSIONS], - strlen(conn->dom_ctx->config[DUKTAPE_SCRIPT_EXTENSIONS]), - filename) + if (match_prefix_strlen(conn->dom_ctx->config[DUKTAPE_SCRIPT_EXTENSIONS], + filename) > 0) { return 1; } @@ -7478,6 +7826,27 @@ extention_matches_script( } +static int +extention_matches_template_text( + struct mg_connection *conn, /* in: request (must be valid) */ + const char *filename /* in: filename (must be valid) */ +) +{ +#if defined(USE_LUA) + if (match_prefix_strlen(conn->dom_ctx->config[LUA_SERVER_PAGE_EXTENSIONS], + filename) + > 0) { + return 1; + } +#endif + if (match_prefix_strlen(conn->dom_ctx->config[SSI_EXTENSIONS], filename) + > 0) { + return 1; + } + return 0; +} + + /* For given directory path, substitute it to valid index file. * Return 1 if index file has been found, 0 if not found. * If the file is found, it's stats is returned in stp. */ @@ -7537,7 +7906,8 @@ interpret_uri(struct mg_connection *conn, /* in/out: request (must be valid) */ int *is_found, /* out: file found (directly) */ int *is_script_resource, /* out: handled by a script? */ int *is_websocket_request, /* out: websocket connetion? */ - int *is_put_or_delete_request /* out: put/delete a file? */ + int *is_put_or_delete_request, /* out: put/delete a file? */ + int *is_template_text /* out: SSI file or LSP file? */ ) { char const *accept_encoding; @@ -7564,6 +7934,7 @@ interpret_uri(struct mg_connection *conn, /* in/out: request (must be valid) */ *filename = 0; *is_found = 0; *is_script_resource = 0; + *is_template_text = 0; /* Step 2: Check if the request attempts to modify the file system */ *is_put_or_delete_request = is_put_or_delete_method(conn); @@ -7571,7 +7942,7 @@ interpret_uri(struct mg_connection *conn, /* in/out: request (must be valid) */ /* Step 3: Check if it is a websocket request, and modify the document * root if required */ #if defined(USE_WEBSOCKET) - *is_websocket_request = is_websocket_protocol(conn); + *is_websocket_request = (conn->protocol_type == PROTOCOL_TYPE_WEBSOCKET); #if !defined(NO_FILES) if (*is_websocket_request && conn->dom_ctx->config[WEBSOCKET_ROOT]) { root = conn->dom_ctx->config[WEBSOCKET_ROOT]; @@ -7604,6 +7975,7 @@ interpret_uri(struct mg_connection *conn, /* in/out: request (must be valid) */ * request uri. */ /* Using filename_buf_len - 1 because memmove() for PATH_INFO may shift * part of the path one byte on the right. */ + truncated = 0; mg_snprintf( conn, &truncated, filename, filename_buf_len - 1, "%s%s", root, uri); @@ -7657,7 +8029,17 @@ interpret_uri(struct mg_connection *conn, /* in/out: request (must be valid) */ *is_script_resource = (!*is_put_or_delete_request); } - /* 8.3: If the request target is a directory, there could be + /* 8.3: Check for SSI and LSP files */ + if (extention_matches_template_text(conn, filename)) { + /* Same as above, but for *.lsp and *.shtml files. */ + /* A "template text" is a file delivered directly to the client, + * but with some text tags replaced by dynamic content. + * E.g. a Server Side Include (SSI) or Lua Page/Lua Server Page + * (LP, LSP) file. */ + *is_template_text = (!*is_put_or_delete_request); + } + + /* 8.4: If the request target is a directory, there could be * a substitute file (index.html, index.cgi, ...). */ if (filestat->is_directory && is_uri_end_slash) { /* Use a local copy here, since substitute_index_file will @@ -7675,6 +8057,9 @@ interpret_uri(struct mg_connection *conn, /* in/out: request (must be valid) */ if (extention_matches_script(conn, filename)) { /* Substitute file is a script file */ *is_script_resource = 1; + } else if (extention_matches_template_text(conn, filename)) { + /* Substitute file is a LSP or SSI file */ + *is_template_text = 1; } else { /* Substitute file is a regular file */ *is_script_resource = 0; @@ -7759,31 +8144,30 @@ interpret_uri(struct mg_connection *conn, /* in/out: request (must be valid) */ /* some intermediate directory has an index file */ if (extention_matches_script(conn, tmp_str)) { - char *tmp_str2; + size_t script_name_len = strlen(tmp_str); + + /* subres_name read before this memory locatio will be + overwritten */ + char *subres_name = filename + sep_pos; + size_t subres_name_len = strlen(subres_name); DEBUG_TRACE("Substitute script %s serving path %s", tmp_str, filename); /* this index file is a script */ - tmp_str2 = mg_strdup_ctx(filename + sep_pos + 1, - conn->phys_ctx); - mg_snprintf(conn, - &truncated, - filename, - filename_buf_len, - "%s//%s", - tmp_str, - tmp_str2); - mg_free(tmp_str2); - - if (truncated) { + if ((script_name_len + subres_name_len + 2) + >= filename_buf_len) { mg_free(tmp_str); goto interpret_cleanup; } - sep_pos = strlen(tmp_str); - filename[sep_pos] = 0; - conn->path_info = filename + sep_pos + 1; + + conn->path_info = + filename + script_name_len + 1; /* new target */ + memmove(conn->path_info, subres_name, subres_name_len); + conn->path_info[subres_name_len] = 0; + memcpy(filename, tmp_str, script_name_len + 1); + *is_script_resource = 1; *is_found = 1; break; @@ -7848,9 +8232,7 @@ get_http_header_len(const char *buf, int buflen) if (i < buflen - 1) { if ((buf[i] == '\n') && (buf[i + 1] == '\n')) { /* Two newline, no carriage return - not standard compliant, - * but - * it - * should be accepted */ + * but it should be accepted */ return i + 2; } } @@ -7954,13 +8336,15 @@ static void remove_dot_segments(char *inout) { /* Windows backend protection - * (https://tools.ietf.org/html/rfc3986#section-7.3): Replace backslash in - * URI by slash */ - char *in_copy = mg_strdup(inout); - char *out_begin = inout; + * (https://tools.ietf.org/html/rfc3986#section-7.3): Replace backslash + * in URI by slash */ char *out_end = inout; - char *in = in_copy; - int replaced; + char *in = inout; + + if (!in) { + /* Param error. */ + return; + } while (*in) { if (*in == '\\') { @@ -7975,11 +8359,13 @@ remove_dot_segments(char *inout) * The input buffer is initialized. * The output buffer is initialized to the empty string. */ - in = in_copy; + in = inout; /* Step 2: * While the input buffer is not empty, loop as follows: */ + /* Less than out_end of the inout buffer is used as output, so keep + * condition: out_end <= in */ while (*in) { /* Step 2a: * If the input buffer begins with a prefix of "../" or "./", @@ -8011,21 +8397,19 @@ remove_dot_segments(char *inout) */ else if (!strncmp(in, "/../", 4)) { in += 3; - if (out_begin != out_end) { + if (inout != out_end) { /* remove last segment */ do { out_end--; - *out_end = 0; - } while ((out_begin != out_end) && (*out_end != '/')); + } while ((inout != out_end) && (*out_end != '/')); } } else if (!strcmp(in, "/..")) { in[1] = 0; - if (out_begin != out_end) { + if (inout != out_end) { /* remove last segment */ do { out_end--; - *out_end = 0; - } while ((out_begin != out_end) && (*out_end != '/')); + } while ((inout != out_end) && (*out_end != '/')); } } /* otherwise */ @@ -8060,47 +8444,44 @@ remove_dot_segments(char *inout) *out_end = 0; /* For Windows, the files/folders "x" and "x." (with a dot but without - * extension) are identical. Replace all "./" by "/" and remove a "." at the - * end. - * Also replace all "//" by "/". - * Repeat until there is no "./" or "//" anymore. + * extension) are identical. Replace all "./" by "/" and remove a "." at + * the end. Also replace all "//" by "/". Repeat until there is no "./" + * or "//" anymore. */ - do { - replaced = 0; - - /* replace ./ by / */ - out_end = out_begin; - while (*out_end) { - if ((*out_end == '.') - && ((out_end[1] == '/') || (out_end[1] == 0))) { - char *r = out_end; - do { - r[0] = r[1]; - r++; - replaced = 1; - } while (r[0] != 0); - } - out_end++; - } - - /* replace ./ by / */ - out_end = out_begin; - while (*out_end) { - if ((out_end[0] == '/') && (out_end[1] == '/')) { - char *c = out_end; - while (*c) { - c[0] = c[1]; - c++; + out_end = in = inout; + while (*in) { + if (*in == '.') { + /* remove . at the end or preceding of / */ + char *in_ahead = in; + do { + in_ahead++; + } while (*in_ahead == '.'); + if (*in_ahead == '/') { + in = in_ahead; + if ((out_end != inout) && (out_end[-1] == '/')) { + /* remove generated // */ + out_end--; } - replaced = 1; + } else if (*in_ahead == 0) { + in = in_ahead; + } else { + do { + *out_end++ = '.'; + in++; + } while (in != in_ahead); } - out_end++; + } else if (*in == '/') { + /* replace // by / */ + *out_end++ = '/'; + do { + in++; + } while (*in == '/'); + } else { + *out_end++ = *in; + in++; } - - } while (replaced); - - /* Free temporary copies */ - mg_free(in_copy); + } + *out_end = 0; } @@ -8361,7 +8742,8 @@ open_auth_file(struct mg_connection *conn, /* Use global passwords file */ if (!mg_fopen(conn, gpass, MG_FOPEN_MODE_READ, filep)) { #if defined(DEBUG) - /* Use mg_cry_internal here, since gpass has been configured. */ + /* Use mg_cry_internal here, since gpass has been + * configured. */ mg_cry_internal(conn, "fopen(%s): %s", gpass, strerror(ERRNO)); #endif } @@ -8382,9 +8764,8 @@ open_auth_file(struct mg_connection *conn, if (truncated || !mg_fopen(conn, name, MG_FOPEN_MODE_READ, filep)) { #if defined(DEBUG) - /* Don't use mg_cry_internal here, but only a trace, since this - * is - * a typical case. It will occur for every directory + /* Don't use mg_cry_internal here, but only a trace, since + * this is a typical case. It will occur for every directory * without a password file. */ DEBUG_TRACE("fopen(%s): %s", name, strerror(ERRNO)); #endif @@ -8407,9 +8788,8 @@ open_auth_file(struct mg_connection *conn, if (truncated || !mg_fopen(conn, name, MG_FOPEN_MODE_READ, filep)) { #if defined(DEBUG) - /* Don't use mg_cry_internal here, but only a trace, since this - * is - * a typical case. It will occur for every directory + /* Don't use mg_cry_internal here, but only a trace, since + * this is a typical case. It will occur for every directory * without a password file. */ DEBUG_TRACE("fopen(%s): %s", name, strerror(ERRNO)); #endif @@ -8467,8 +8847,8 @@ parse_auth_header(struct mg_connection *conn, s++; } } else { - value = skip_quoted(&s, ", ", " ", 0); /* IE uses commas, FF uses - * spaces */ + value = skip_quoted(&s, ", ", " ", 0); /* IE uses commas, FF + * uses spaces */ } if (*name == '\0') { break; @@ -8541,39 +8921,13 @@ parse_auth_header(struct mg_connection *conn, static const char * -mg_fgets(char *buf, size_t size, struct mg_file *filep, char **p) +mg_fgets(char *buf, size_t size, struct mg_file *filep) { -#if defined(MG_USE_OPEN_FILE) - const char *eof; - size_t len; - const char *memend; -#else - (void)p; /* parameter is unused */ -#endif - if (!filep) { return NULL; } -#if defined(MG_USE_OPEN_FILE) - if ((filep->access.membuf != NULL) && (*p != NULL)) { - memend = (const char *)&filep->access.membuf[filep->stat.size]; - /* Search for \n from p till the end of stream */ - eof = (char *)memchr(*p, '\n', (size_t)(memend - *p)); - if (eof != NULL) { - eof += 1; /* Include \n */ - } else { - eof = memend; /* Copy remaining data */ - } - len = - ((size_t)(eof - *p) > (size - 1)) ? (size - 1) : (size_t)(eof - *p); - memcpy(buf, *p, len); - buf[len] = '\0'; - *p += len; - return len ? eof : NULL; - } else /* filep->access.fp block below */ -#endif - if (filep->access.fp != NULL) { + if (filep->access.fp != NULL) { return fgets(buf, (int)size, filep->access.fp); } else { return NULL; @@ -8608,7 +8962,6 @@ read_auth_file(struct mg_file *filep, struct read_auth_file_struct *workdata, int depth) { - char *p = NULL /* init if MG_USE_OPEN_FILE is not set */; int is_authorized = 0; struct mg_file fp; size_t l; @@ -8617,11 +8970,8 @@ read_auth_file(struct mg_file *filep, return 0; } -/* Loop over passwords file */ -#if defined(MG_USE_OPEN_FILE) - p = (char *)filep->access.membuf; -#endif - while (mg_fgets(workdata->buf, sizeof(workdata->buf), filep, &p) != NULL) { + /* Loop over passwords file */ + while (mg_fgets(workdata->buf, sizeof(workdata->buf), filep) != NULL) { l = strlen(workdata->buf); while (l > 0) { if (isspace((unsigned char)workdata->buf[l - 1]) @@ -8829,38 +9179,45 @@ check_authorization(struct mg_connection *conn, const char *path) static void send_authorization_request(struct mg_connection *conn, const char *realm) { - char date[64]; - time_t curtime = time(NULL); uint64_t nonce = (uint64_t)(conn->phys_ctx->start_time); + int trunc = 0; + char buf[128]; if (!realm) { realm = conn->dom_ctx->config[AUTHENTICATION_DOMAIN]; } - (void)pthread_mutex_lock(&conn->phys_ctx->nonce_mutex); + mg_lock_context(conn->phys_ctx); nonce += conn->dom_ctx->nonce_count; ++conn->dom_ctx->nonce_count; - (void)pthread_mutex_unlock(&conn->phys_ctx->nonce_mutex); + mg_unlock_context(conn->phys_ctx); nonce ^= conn->dom_ctx->auth_nonce_mask; - conn->status_code = 401; conn->must_close = 1; - gmt_time_string(date, sizeof(date), &curtime); - - mg_printf(conn, "HTTP/1.1 401 Unauthorized\r\n"); + /* Create 401 response */ + mg_response_header_start(conn, 401); send_no_cache_header(conn); send_additional_header(conn); - mg_printf(conn, - "Date: %s\r\n" - "Connection: %s\r\n" - "Content-Length: 0\r\n" - "WWW-Authenticate: Digest qop=\"auth\", realm=\"%s\", " - "nonce=\"%" UINT64_FMT "\"\r\n\r\n", - date, - suggest_connection_header(conn), - realm, - nonce); + mg_response_header_add(conn, "Content-Length", "0", -1); + + /* Content for "WWW-Authenticate" header */ + mg_snprintf(conn, + &trunc, + buf, + sizeof(buf), + "Digest qop=\"auth\", realm=\"%s\", " + "nonce=\"%" UINT64_FMT "\"", + realm, + nonce); + + if (!trunc) { + /* !trunc should always be true */ + mg_response_header_add(conn, "WWW-Authenticate", buf, -1); + } + + /* Send all headers */ + mg_response_header_send(conn); } @@ -9021,7 +9378,7 @@ is_valid_port(unsigned long port) static int -mg_inet_pton(int af, const char *src, void *dst, size_t dstlen) +mg_inet_pton(int af, const char *src, void *dst, size_t dstlen, int resolve_src) { struct addrinfo hints, *res, *ressave; int func_ret = 0; @@ -9029,6 +9386,9 @@ mg_inet_pton(int af, const char *src, void *dst, size_t dstlen) memset(&hints, 0, sizeof(struct addrinfo)); hints.ai_family = af; + if (!resolve_src) { + hints.ai_flags = AI_NUMERICHOST; + } gai_ret = getaddrinfo(src, NULL, &hints, &res); if (gai_ret != 0) { @@ -9045,7 +9405,8 @@ mg_inet_pton(int af, const char *src, void *dst, size_t dstlen) ressave = res; while (res) { - if (dstlen >= (size_t)res->ai_addrlen) { + if ((dstlen >= (size_t)res->ai_addrlen) + && (res->ai_addr->sa_family == af)) { memcpy(dst, res->ai_addr, res->ai_addrlen); func_ret = 1; } @@ -9129,13 +9490,11 @@ connect_socket(struct mg_context *ctx /* may be NULL */, (void)use_ssl; #endif /* !defined(NO_SSL) */ - if (mg_inet_pton(AF_INET, host, &sa->sin, sizeof(sa->sin))) { - sa->sin.sin_family = AF_INET; + if (mg_inet_pton(AF_INET, host, &sa->sin, sizeof(sa->sin), 1)) { sa->sin.sin_port = htons((uint16_t)port); ip_ver = 4; #if defined(USE_IPV6) - } else if (mg_inet_pton(AF_INET6, host, &sa->sin6, sizeof(sa->sin6))) { - sa->sin6.sin6_family = AF_INET6; + } else if (mg_inet_pton(AF_INET6, host, &sa->sin6, sizeof(sa->sin6), 1)) { sa->sin6.sin6_port = htons((uint16_t)port); ip_ver = 6; } else if (host[0] == '[') { @@ -9145,8 +9504,7 @@ connect_socket(struct mg_context *ctx /* may be NULL */, char *h = (l > 1) ? mg_strdup_ctx(host + 1, ctx) : NULL; if (h) { h[l - 1] = 0; - if (mg_inet_pton(AF_INET6, h, &sa->sin6, sizeof(sa->sin6))) { - sa->sin6.sin6_family = AF_INET6; + if (mg_inet_pton(AF_INET6, h, &sa->sin6, sizeof(sa->sin6), 0)) { sa->sin6.sin6_port = htons((uint16_t)port); ip_ver = 6; } @@ -9236,7 +9594,8 @@ connect_socket(struct mg_context *ctx /* may be NULL */, struct mg_pollfd pfd[1]; int pollres; int ms_wait = 10000; /* 10 second timeout */ - int nonstop = 0; + stop_flag_t nonstop; + STOP_FLAG_ASSIGN(&nonstop, 0); /* For a non-blocking socket, the connect sequence is: * 1) call connect (will not block) @@ -9471,9 +9830,8 @@ must_hide_file(struct mg_connection *conn, const char *path) if (conn && conn->dom_ctx) { const char *pw_pattern = "**" PASSWORDS_FILE_NAME "$"; const char *pattern = conn->dom_ctx->config[HIDE_FILES]; - return (match_prefix(pw_pattern, strlen(pw_pattern), path) > 0) - || ((pattern != NULL) - && (match_prefix(pattern, strlen(pattern), path) > 0)); + return (match_prefix_strlen(pw_pattern, path) > 0) + || (match_prefix_strlen(pattern, path) > 0); } return 0; } @@ -9527,7 +9885,10 @@ scan_directory(struct mg_connection *conn, strerror(ERRNO)); } de.file_name = dp->d_name; - cb(&de, data); + if (cb(&de, data)) { + /* stopped */ + break; + } } (void)mg_closedir(dirp); } @@ -9607,44 +9968,37 @@ remove_directory(struct mg_connection *conn, const char *dir) struct dir_scan_data { struct de *entries; - unsigned int num_entries; - unsigned int arr_size; + size_t num_entries; + size_t arr_size; }; -/* Behaves like realloc(), but frees original pointer on failure */ -static void * -realloc2(void *ptr, size_t size) -{ - void *new_ptr = mg_realloc(ptr, size); - if ((new_ptr == NULL) && (size > 0)) { - mg_free(ptr); - } - return new_ptr; -} - - #if !defined(NO_FILESYSTEMS) static int dir_scan_callback(struct de *de, void *data) { struct dir_scan_data *dsd = (struct dir_scan_data *)data; - - if ((dsd->entries == NULL) || (dsd->num_entries >= dsd->arr_size)) { + struct de *entries = dsd->entries; + + if ((entries == NULL) || (dsd->num_entries >= dsd->arr_size)) { + entries = + (struct de *)mg_realloc(entries, + dsd->arr_size * 2 * sizeof(entries[0])); + if (entries == NULL) { + /* stop scan */ + return 1; + } + dsd->entries = entries; dsd->arr_size *= 2; - dsd->entries = - (struct de *)realloc2(dsd->entries, - dsd->arr_size * sizeof(dsd->entries[0])); } - if (dsd->entries == NULL) { - /* TODO(lsm, low): propagate an error to the caller */ - dsd->num_entries = 0; - } else { - dsd->entries[dsd->num_entries].file_name = mg_strdup(de->file_name); - dsd->entries[dsd->num_entries].file = de->file; - dsd->entries[dsd->num_entries].conn = de->conn; - dsd->num_entries++; + entries[dsd->num_entries].file_name = mg_strdup(de->file_name); + if (entries[dsd->num_entries].file_name == NULL) { + /* stop scan */ + return 1; } + entries[dsd->num_entries].file = de->file; + entries[dsd->num_entries].conn = de->conn; + dsd->num_entries++; return 0; } @@ -9653,13 +10007,17 @@ dir_scan_callback(struct de *de, void *data) static void handle_directory_request(struct mg_connection *conn, const char *dir) { - unsigned int i; + size_t i; int sort_direction; struct dir_scan_data data = {NULL, 0, 128}; char date[64], *esc, *p; const char *title; time_t curtime = time(NULL); + if (!conn) { + return; + } + if (!scan_directory(conn, dir, &data, dir_scan_callback)) { mg_send_http_error(conn, 500, @@ -9671,10 +10029,6 @@ handle_directory_request(struct mg_connection *conn, const char *dir) gmt_time_string(date, sizeof(date), &curtime); - if (!conn) { - return; - } - esc = NULL; title = conn->request_info.local_uri; if (title[strcspn(title, "&<>")]) { @@ -9703,14 +10057,20 @@ handle_directory_request(struct mg_connection *conn, const char *dir) : 'd'; conn->must_close = 1; - mg_printf(conn, "HTTP/1.1 200 OK\r\n"); + + /* Create 200 OK response */ + mg_response_header_start(conn, 200); send_static_cache_header(conn); send_additional_header(conn); - mg_printf(conn, - "Date: %s\r\n" - "Connection: close\r\n" - "Content-Type: text/html; charset=utf-8\r\n\r\n", - date); + mg_response_header_add(conn, + "Content-Type", + "text/html; charset=utf-8", + -1); + + /* Send all headers */ + mg_response_header_send(conn); + + /* Body */ mg_printf(conn, "Index of %s" "" @@ -9738,7 +10098,7 @@ handle_directory_request(struct mg_connection *conn, const char *dir) /* Sort and print directory entries */ if (data.entries != NULL) { qsort(data.entries, - (size_t)data.num_entries, + data.num_entries, sizeof(data.entries[0]), compare_dir_entries); for (i = 0; i < data.num_entries; i++) { @@ -9774,16 +10134,7 @@ send_file_data(struct mg_connection *conn, : (int64_t)(filep->stat.size); offset = (offset < 0) ? 0 : ((offset > size) ? size : offset); -#if defined(MG_USE_OPEN_FILE) - if ((len > 0) && (filep->access.membuf != NULL) && (size > 0)) { - /* file stored in memory */ - if (len > size - offset) { - len = size - offset; - } - mg_write(conn, filep->access.membuf + offset, (size_t)len); - } else /* else block below */ -#endif - if (len > 0 && filep->access.fp != NULL) { + if (len > 0 && filep->access.fp != NULL) { /* file stored on disk */ #if defined(__linux__) /* sendfile is only available for Linux */ @@ -9921,25 +10272,23 @@ handle_static_file_request(struct mg_connection *conn, const char *mime_type, const char *additional_headers) { - char date[64], lm[64], etag[64]; + char lm[64], etag[64]; char range[128]; /* large enough, so there will be no overflow */ - const char *msg = "OK"; const char *range_hdr; - time_t curtime = time(NULL); int64_t cl, r1, r2; struct vec mime_vec; int n, truncated; char gz_path[PATH_MAX]; - const char *encoding = ""; + const char *encoding = 0; const char *origin_hdr; const char *cors_orig_cfg; - const char *cors1, *cors2, *cors3; + const char *cors1, *cors2; int is_head_request; #if defined(USE_ZLIB) - /* Compression is allowed, unless there is a reason not to use compression. - * If the file is already compressed, too small or a "range" request was - * made, on the fly compression is not possible. */ + /* Compression is allowed, unless there is a reason not to use + * compression. If the file is already compressed, too small or a + * "range" request was made, on the fly compression is not possible. */ int allow_on_the_fly_compression = 1; #endif @@ -9991,7 +10340,7 @@ handle_static_file_request(struct mg_connection *conn, } path = gz_path; - encoding = "Content-Encoding: gzip\r\n"; + encoding = "gzip"; #if defined(USE_ZLIB) /* File is already compressed. No "on the fly" compression. */ @@ -10009,7 +10358,7 @@ handle_static_file_request(struct mg_connection *conn, filep->stat = file_stat; cl = (int64_t)filep->stat.size; path = gz_path; - encoding = "Content-Encoding: gzip\r\n"; + encoding = "gzip"; #if defined(USE_ZLIB) /* File is already compressed. No "on the fly" compression. */ @@ -10053,12 +10402,11 @@ handle_static_file_request(struct mg_connection *conn, NULL, /* range buffer is big enough */ range, sizeof(range), - "Content-Range: bytes " + "bytes " "%" INT64_FMT "-%" INT64_FMT "/%" INT64_FMT "\r\n", r1, r1 + cl - 1, filep->stat.size); - msg = "Partial Content"; #if defined(USE_ZLIB) /* Do not compress ranges. */ @@ -10084,78 +10432,74 @@ handle_static_file_request(struct mg_connection *conn, * http://www.html5rocks.com/static/images/cors_server_flowchart.png * - * preflight is not supported for files. */ - cors1 = "Access-Control-Allow-Origin: "; + cors1 = "Access-Control-Allow-Origin"; cors2 = cors_orig_cfg; - cors3 = "\r\n"; } else { - cors1 = cors2 = cors3 = ""; + cors1 = cors2 = ""; } - /* Prepare Etag, Date, Last-Modified headers. Must be in UTC, - * according to - * http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3 */ - gmt_time_string(date, sizeof(date), &curtime); + /* Prepare Etag, and Last-Modified headers. */ gmt_time_string(lm, sizeof(lm), &filep->stat.last_modified); construct_etag(etag, sizeof(etag), &filep->stat); - /* Send header */ - (void)mg_printf(conn, - "HTTP/1.1 %d %s\r\n" - "%s%s%s" /* CORS */ - "Date: %s\r\n" - "Last-Modified: %s\r\n" - "Etag: %s\r\n" - "Content-Type: %.*s\r\n" - "Connection: %s\r\n", - conn->status_code, - msg, - cors1, - cors2, - cors3, - date, - lm, - etag, - (int)mime_vec.len, - mime_vec.ptr, - suggest_connection_header(conn)); + /* Create 2xx (200, 206) response */ + mg_response_header_start(conn, conn->status_code); send_static_cache_header(conn); send_additional_header(conn); + mg_response_header_add(conn, + "Content-Type", + mime_vec.ptr, + (int)mime_vec.len); + if (cors1[0] != 0) { + mg_response_header_add(conn, cors1, cors2, -1); + } + mg_response_header_add(conn, "Last-Modified", lm, -1); + mg_response_header_add(conn, "Etag", etag, -1); #if defined(USE_ZLIB) /* On the fly compression allowed */ if (allow_on_the_fly_compression) { /* For on the fly compression, we don't know the content size in * advance, so we have to use chunked encoding */ - (void)mg_printf(conn, - "Content-Encoding: gzip\r\n" - "Transfer-Encoding: chunked\r\n"); + encoding = "gzip"; + if (conn->protocol_type == PROTOCOL_TYPE_HTTP1) { + /* HTTP/2 is always using "chunks" (frames) */ + mg_response_header_add(conn, "Transfer-Encoding", "chunked", -1); + } + } else #endif { /* Without on-the-fly compression, we know the content-length * and we can use ranges (with on-the-fly compression we cannot). * So we send these response headers only in this case. */ - (void)mg_printf(conn, - "Content-Length: %" INT64_FMT "\r\n" - "Accept-Ranges: bytes\r\n" - "%s" /* range */ - "%s" /* encoding */, - cl, - range, - encoding); + char len[32]; + int trunc = 0; + mg_snprintf(conn, &trunc, len, sizeof(len), "%" INT64_FMT, cl); + + if (!trunc) { + mg_response_header_add(conn, "Content-Length", len, -1); + } + + mg_response_header_add(conn, "Accept-Ranges", "bytes", -1); + } + + if (encoding) { + mg_response_header_add(conn, "Content-Encoding", encoding, -1); + } + if (range[0] != 0) { + mg_response_header_add(conn, "Content-Range", range, -1); } - /* The previous code must not add any header starting with X- to make + /* The code above does not add any header starting with X- to make * sure no one of the additional_headers is included twice */ - if (additional_headers != NULL) { - (void)mg_printf(conn, - "%.*s\r\n\r\n", - (int)strlen(additional_headers), - additional_headers); - } else { - (void)mg_printf(conn, "\r\n"); + if ((additional_headers != NULL) && (*additional_headers != 0)) { + mg_response_header_add_lines(conn, additional_headers); } + /* Send all headers */ + mg_response_header_send(conn); + if (!is_head_request) { #if defined(USE_ZLIB) if (allow_on_the_fly_compression) { @@ -10203,37 +10547,29 @@ is_not_modified(const struct mg_connection *conn, && (filestat->last_modified <= parse_date_string(ims))); } + static void handle_not_modified_static_file_request(struct mg_connection *conn, struct mg_file *filep) { - char date[64], lm[64], etag[64]; - time_t curtime = time(NULL); + char lm[64], etag[64]; if ((conn == NULL) || (filep == NULL)) { return; } - conn->status_code = 304; - gmt_time_string(date, sizeof(date), &curtime); + gmt_time_string(lm, sizeof(lm), &filep->stat.last_modified); construct_etag(etag, sizeof(etag), &filep->stat); - (void)mg_printf(conn, - "HTTP/1.1 %d %s\r\n" - "Date: %s\r\n", - conn->status_code, - mg_get_response_code_text(conn, conn->status_code), - date); + /* Create 304 "not modified" response */ + mg_response_header_start(conn, 304); send_static_cache_header(conn); send_additional_header(conn); - (void)mg_printf(conn, - "Last-Modified: %s\r\n" - "Etag: %s\r\n" - "Connection: %s\r\n" - "\r\n", - lm, - etag, - suggest_connection_header(conn)); + mg_response_header_add(conn, "Last-Modified", lm, -1); + mg_response_header_add(conn, "Etag", etag, -1); + + /* Send all headers */ + mg_response_header_send(conn); } #endif @@ -10537,7 +10873,7 @@ struct mg_http_method_info { /* https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods */ -static struct mg_http_method_info http_methods[] = { +static const struct mg_http_method_info http_methods[] = { /* HTTP (RFC 2616) */ {"GET", 0, 1, 1, 1, 1}, {"POST", 1, 1, 0, 0, 0}, @@ -10707,7 +11043,6 @@ parse_http_request(char *buf, int len, struct mg_request_info *ri) } ri->http_version += 5; - /* Parse all HTTP headers */ ri->num_headers = parse_http_headers(&buf, ri->http_headers); if (ri->num_headers < 0) { @@ -10847,7 +11182,8 @@ read_message(FILE *fp, /* value of request_timeout is in seconds, config in milliseconds */ request_timeout = atof(conn->dom_ctx->config[REQUEST_TIMEOUT]) / 1000.0; } else { - request_timeout = -1.0; + request_timeout = + atof(config_options[REQUEST_TIMEOUT].default_value) / 1000.0; } if (conn->handled_requests > 0) { if (conn->dom_ctx->config[KEEP_ALIVE_TIMEOUT]) { @@ -10860,7 +11196,7 @@ read_message(FILE *fp, while (request_len == 0) { /* Full request not yet received */ - if (conn->phys_ctx->stop_flag != 0) { + if (!STOP_FLAG_IS_ZERO(&conn->phys_ctx->stop_flag)) { /* Server is to be stopped. */ return -1; } @@ -10883,8 +11219,6 @@ read_message(FILE *fp, if (n > 0) { *nread += n; request_len = get_http_header_len(buf, *nread); - } else { - request_len = 0; } if ((request_len == 0) && (request_timeout >= 0)) { @@ -10974,7 +11308,7 @@ forward_body_data(struct mg_connection *conn, FILE *fp, SOCKET sock, SSL *ssl) #if !defined(NO_CGI) /* This structure helps to create an environment for the spawned CGI * program. - * Environment is an array of "VARIABLE=VALUE\0" ASCIIZ strings, + * Environment is an array of "VARIABLE=VALUE\0" ASCII strings, * last element must be NULL. * However, on Windows there is a requirement that all these * VARIABLE=VALUE\0 @@ -11117,14 +11451,7 @@ prepare_cgi_environment(struct mg_connection *conn, addenv(env, "%s", "SERVER_PROTOCOL=HTTP/1.1"); addenv(env, "%s", "REDIRECT_STATUS=200"); /* For PHP */ -#if defined(USE_IPV6) - if (conn->client.lsa.sa.sa_family == AF_INET6) { - addenv(env, "SERVER_PORT=%d", ntohs(conn->client.lsa.sin6.sin6_port)); - } else -#endif - { - addenv(env, "SERVER_PORT=%d", ntohs(conn->client.lsa.sin.sin_port)); - } + addenv(env, "SERVER_PORT=%d", ntohs(USA_IN_PORT_UNSAFE(&conn->client.lsa))); sockaddr_to_string(src_addr, sizeof(src_addr), &conn->client.rsa); addenv(env, "REMOTE_ADDR=%s", src_addr); @@ -11271,18 +11598,18 @@ prepare_cgi_environment(struct mg_connection *conn, /* Data for CGI process control: PID and number of references */ struct process_control_data { pid_t pid; - int references; + ptrdiff_t references; }; static int -abort_process(void *data) +abort_cgi_process(void *data) { - /* Waitpid checks for child status and won't work for a pid that does not - * identify a child of the current process. Thus, if the pid is reused, - * we will not affect a different process. */ + /* Waitpid checks for child status and won't work for a pid that does + * not identify a child of the current process. Thus, if the pid is + * reused, we will not affect a different process. */ struct process_control_data *proc = (struct process_control_data *)data; int status = 0; - int refs; + ptrdiff_t refs; pid_t ret_pid; ret_pid = waitpid(proc->pid, &status, WNOHANG); @@ -11326,11 +11653,15 @@ handle_cgi_request(struct mg_connection *conn, const char *prog) struct process_control_data *proc = NULL; #if defined(USE_TIMERS) - double cgi_timeout = -1.0; + double cgi_timeout; if (conn->dom_ctx->config[CGI_TIMEOUT]) { /* Get timeout in seconds */ cgi_timeout = atof(conn->dom_ctx->config[CGI_TIMEOUT]) * 0.001; + } else { + cgi_timeout = + atof(config_options[REQUEST_TIMEOUT].default_value) * 0.001; } + #endif buf = NULL; @@ -11393,11 +11724,7 @@ handle_cgi_request(struct mg_connection *conn, const char *prog) "Error: CGI program \"%s\": Can not spawn CGI process: %s", prog, status); - mg_send_http_error(conn, - 500, - "Error: Cannot spawn CGI process [%s]: %s", - prog, - status); + mg_send_http_error(conn, 500, "Error: Cannot spawn CGI process"); mg_free(proc); proc = NULL; goto done; @@ -11416,8 +11743,9 @@ handle_cgi_request(struct mg_connection *conn, const char *prog) cgi_timeout /* in seconds */, 0.0, 1, - abort_process, - (void *)proc); + abort_cgi_process, + (void *)proc, + NULL); } #endif @@ -11590,7 +11918,7 @@ handle_cgi_request(struct mg_connection *conn, const char *prog) mg_free(blk.buf); if (pid != (pid_t)-1) { - abort_process((void *)proc); + abort_cgi_process((void *)proc); } if (fdin[0] != -1) { @@ -11632,8 +11960,6 @@ mkcol(struct mg_connection *conn, const char *path) { int rc, body_len; struct de de; - char date[64]; - time_t curtime = time(NULL); if (conn == NULL) { return; @@ -11670,19 +11996,16 @@ mkcol(struct mg_connection *conn, const char *path) rc = mg_mkdir(conn, path, 0755); if (rc == 0) { - conn->status_code = 201; - gmt_time_string(date, sizeof(date), &curtime); - mg_printf(conn, - "HTTP/1.1 %d Created\r\n" - "Date: %s\r\n", - conn->status_code, - date); + + /* Create 201 "Created" response */ + mg_response_header_start(conn, 201); send_static_cache_header(conn); send_additional_header(conn); - mg_printf(conn, - "Content-Length: 0\r\n" - "Connection: %s\r\n\r\n", - suggest_connection_header(conn)); + mg_response_header_add(conn, "Content-Length", "0", -1); + + /* Send all headers - there is no body */ + mg_response_header_send(conn); + } else { if (errno == EEXIST) { mg_send_http_error( @@ -11708,8 +12031,6 @@ put_file(struct mg_connection *conn, const char *path) const char *range; int64_t r1, r2; int rc; - char date[64]; - time_t curtime = time(NULL); if (conn == NULL) { return; @@ -11728,22 +12049,9 @@ put_file(struct mg_connection *conn, const char *path) /* File exists and is not a directory. */ /* Can it be replaced? */ -#if defined(MG_USE_OPEN_FILE) - if (file.access.membuf != NULL) { - /* This is an "in-memory" file, that can not be replaced */ - mg_send_http_error(conn, - 405, - "Error: Put not possible\nReplacing %s " - "is not supported", - path); - return; - } -#endif - /* Check if the server may write this file */ if (access(path, W_OK) == 0) { /* Access granted */ - conn->status_code = 200; rc = 1; } else { mg_send_http_error( @@ -11762,19 +12070,15 @@ put_file(struct mg_connection *conn, const char *path) if (rc == 0) { /* put_dir returns 0 if path is a directory */ - gmt_time_string(date, sizeof(date), &curtime); - mg_printf(conn, - "HTTP/1.1 %d %s\r\n", - conn->status_code, - mg_get_response_code_text(NULL, conn->status_code)); + + /* Create response */ + mg_response_header_start(conn, conn->status_code); send_no_cache_header(conn); send_additional_header(conn); - mg_printf(conn, - "Date: %s\r\n" - "Content-Length: 0\r\n" - "Connection: %s\r\n\r\n", - date, - suggest_connection_header(conn)); + mg_response_header_add(conn, "Content-Length", "0", -1); + + /* Send all headers - there is no body */ + mg_response_header_send(conn); /* Request to create a directory has been fulfilled successfully. * No need to put a file. */ @@ -11836,19 +12140,14 @@ put_file(struct mg_connection *conn, const char *path) conn->status_code = 507; } - gmt_time_string(date, sizeof(date), &curtime); - mg_printf(conn, - "HTTP/1.1 %d %s\r\n", - conn->status_code, - mg_get_response_code_text(NULL, conn->status_code)); + /* Create response (status_code has been set before) */ + mg_response_header_start(conn, conn->status_code); send_no_cache_header(conn); send_additional_header(conn); - mg_printf(conn, - "Date: %s\r\n" - "Content-Length: 0\r\n" - "Connection: %s\r\n\r\n", - date, - suggest_connection_header(conn)); + mg_response_header_add(conn, "Content-Length", "0", -1); + + /* Send all headers - there is no body */ + mg_response_header_send(conn); } @@ -11866,18 +12165,6 @@ delete_file(struct mg_connection *conn, const char *path) return; } -#if 0 /* Ignore if a file in memory is inside a folder */ - if (de.access.membuf != NULL) { - /* the file is cached in memory */ - mg_send_http_error( - conn, - 405, - "Error: Delete not possible\nDeleting %s is not supported", - path); - return; - } -#endif - if (de.file.is_directory) { if (remove_directory(conn, path)) { /* Delete is successful: Return 204 without content. */ @@ -11904,7 +12191,12 @@ delete_file(struct mg_connection *conn, const char *path) /* Try to delete it. */ if (mg_remove(conn, path) == 0) { /* Delete was successful: Return 204 without content. */ - mg_send_http_error(conn, 204, "%s", ""); + mg_response_header_start(conn, 204); + send_no_cache_header(conn); + send_additional_header(conn); + mg_response_header_add(conn, "Content-Length", "0", -1); + mg_response_header_send(conn); + } else { /* Delete not successful (file locked). */ mg_send_http_error(conn, @@ -11995,9 +12287,7 @@ do_ssi_include(struct mg_connection *conn, strerror(ERRNO)); } else { fclose_on_exec(&file.access, conn); - if (match_prefix(conn->dom_ctx->config[SSI_EXTENSIONS], - strlen(conn->dom_ctx->config[SSI_EXTENSIONS]), - path) + if (match_prefix_strlen(conn->dom_ctx->config[SSI_EXTENSIONS], path) > 0) { send_ssi_file(conn, path, &file, include_level + 1); } else { @@ -12034,20 +12324,13 @@ do_ssi_exec(struct mg_connection *conn, char *tag) static int -mg_fgetc(struct mg_file *filep, int offset) +mg_fgetc(struct mg_file *filep) { - (void)offset; /* unused in case MG_USE_OPEN_FILE is set */ - if (filep == NULL) { return EOF; } -#if defined(MG_USE_OPEN_FILE) - if ((filep->access.membuf != NULL) && (offset >= 0) - && (((unsigned int)(offset)) < filep->stat.size)) { - return ((const unsigned char *)filep->access.membuf)[offset]; - } else /* else block below */ -#endif - if (filep->access.fp != NULL) { + + if (filep->access.fp != NULL) { return fgetc(filep->access.fp); } else { return EOF; @@ -12062,17 +12345,17 @@ send_ssi_file(struct mg_connection *conn, int include_level) { char buf[MG_BUF_LEN]; - int ch, offset, len, in_tag, in_ssi_tag; + int ch, len, in_tag, in_ssi_tag; if (include_level > 10) { mg_cry_internal(conn, "SSI #include level is too deep (%s)", path); return; } - in_tag = in_ssi_tag = len = offset = 0; + in_tag = in_ssi_tag = len = 0; /* Read file, byte by byte, and look for SSI include tags */ - while ((ch = mg_fgetc(filep, offset++)) != EOF) { + while ((ch = mg_fgetc(filep)) != EOF) { if (in_tag) { /* We are in a tag, either SSI tag or html tag */ @@ -12169,7 +12452,7 @@ handle_ssi_file_request(struct mg_connection *conn, char date[64]; time_t curtime = time(NULL); const char *cors_orig_cfg; - const char *cors1, *cors2, *cors3; + const char *cors1, *cors2; if ((conn == NULL) || (path == NULL) || (filep == NULL)) { return; @@ -12178,11 +12461,10 @@ handle_ssi_file_request(struct mg_connection *conn, cors_orig_cfg = conn->dom_ctx->config[ACCESS_CONTROL_ALLOW_ORIGIN]; if (cors_orig_cfg && *cors_orig_cfg && mg_get_header(conn, "Origin")) { /* Cross-origin resource sharing (CORS). */ - cors1 = "Access-Control-Allow-Origin: "; + cors1 = "Access-Control-Allow-Origin"; cors2 = cors_orig_cfg; - cors3 = "\r\n"; } else { - cors1 = cors2 = cors3 = ""; + cors1 = cors2 = ""; } if (!mg_fopen(conn, path, MG_FOPEN_MODE_READ, filep)) { @@ -12194,22 +12476,23 @@ handle_ssi_file_request(struct mg_connection *conn, path, strerror(ERRNO)); } else { + /* Set "must_close" for HTTP/1.x, since we do not know the + * content length */ conn->must_close = 1; gmt_time_string(date, sizeof(date), &curtime); fclose_on_exec(&filep->access, conn); - mg_printf(conn, "HTTP/1.1 200 OK\r\n"); + + /* 200 OK response */ + mg_response_header_start(conn, 200); send_no_cache_header(conn); send_additional_header(conn); - mg_printf(conn, - "%s%s%s" - "Date: %s\r\n" - "Content-Type: text/html\r\n" - "Connection: %s\r\n\r\n", - cors1, - cors2, - cors3, - date, - suggest_connection_header(conn)); + mg_response_header_add(conn, "Content-Type", "text/html", -1); + if (cors1[0]) { + mg_response_header_add(conn, cors1, cors2, -1); + } + mg_response_header_send(conn); + + /* Header sent, now send body */ send_ssi_file(conn, path, filep, 0); (void)mg_fclose(&filep->access); /* Ignore errors for readonly files */ } @@ -12221,31 +12504,30 @@ handle_ssi_file_request(struct mg_connection *conn, static void send_options(struct mg_connection *conn) { - char date[64]; - time_t curtime = time(NULL); - if (!conn) { return; } - conn->status_code = 200; - conn->must_close = 1; - gmt_time_string(date, sizeof(date), &curtime); - /* We do not set a "Cache-Control" header here, but leave the default. * Since browsers do not send an OPTIONS request, we can not test the * effect anyway. */ - mg_printf(conn, - "HTTP/1.1 200 OK\r\n" - "Date: %s\r\n" - "Connection: %s\r\n" - "Allow: GET, POST, HEAD, CONNECT, PUT, DELETE, OPTIONS, " - "PROPFIND, MKCOL\r\n" - "DAV: 1\r\n", - date, - suggest_connection_header(conn)); + + mg_response_header_start(conn, 200); + mg_response_header_add(conn, "Content-Type", "text/html", -1); + if (conn->protocol_type == PROTOCOL_TYPE_HTTP1) { + /* Use the same as before */ + mg_response_header_add( + conn, + "Allow", + "GET, POST, HEAD, CONNECT, PUT, DELETE, OPTIONS, PROPFIND, MKCOL", + -1); + mg_response_header_add(conn, "DAV", "1", -1); + } else { + /* TODO: Check this later for HTTP/2 */ + mg_response_header_add(conn, "Allow", "GET, POST", -1); + } send_additional_header(conn); - mg_printf(conn, "\r\n"); + mg_response_header_send(conn); } @@ -12314,7 +12596,8 @@ print_dav_dir_entry(struct de *de, void *data) if (!de || !conn || !print_props( conn, conn->request_info.local_uri, de->file_name, &de->file)) { - return -1; + /* stop scan */ + return 1; } return 0; } @@ -12336,18 +12619,15 @@ handle_propfind(struct mg_connection *conn, } conn->must_close = 1; - conn->status_code = 207; - mg_printf(conn, - "HTTP/1.1 207 Multi-Status\r\n" - "Date: %s\r\n", - date); + + /* return 207 "Multi-Status" */ + mg_response_header_start(conn, 207); send_static_cache_header(conn); send_additional_header(conn); - mg_printf(conn, - "Connection: %s\r\n" - "Content-Type: text/xml; charset=utf-8\r\n\r\n", - suggest_connection_header(conn)); + mg_response_header_add(conn, "Content-Type", "text/xml; charset=utf-8", -1); + mg_response_header_send(conn); + /* Content */ mg_printf(conn, "" "\n"); @@ -12387,7 +12667,7 @@ mg_unlock_connection(struct mg_connection *conn) void mg_lock_context(struct mg_context *ctx) { - if (ctx) { + if (ctx && (ctx->context_type == CONTEXT_SERVER)) { (void)pthread_mutex_lock(&ctx->nonce_mutex); } } @@ -12395,7 +12675,7 @@ mg_lock_context(struct mg_context *ctx) void mg_unlock_context(struct mg_context *ctx) { - if (ctx) { + if (ctx && (ctx->context_type == CONTEXT_SERVER)) { (void)pthread_mutex_unlock(&ctx->nonce_mutex); } } @@ -12443,6 +12723,12 @@ send_websocket_handshake(struct mg_connection *conn, const char *websock_key) "Connection: Upgrade\r\n" "Sec-WebSocket-Accept: %s\r\n", b64_sha); + +#if defined(USE_ZLIB) && defined(MG_EXPERIMENTAL_INTERFACES) + // Send negotiated compression extension parameters + websocket_deflate_response(conn); +#endif + if (conn->request_info.acceptedWebSocketSubprotocol) { mg_printf(conn, "Sec-WebSocket-Protocol: %s\r\n\r\n", @@ -12514,6 +12800,9 @@ read_websocket(struct mg_connection *conn, if ((timeout <= 0.0) && (conn->dom_ctx->config[REQUEST_TIMEOUT])) { timeout = atoi(conn->dom_ctx->config[REQUEST_TIMEOUT]) / 1000.0; } + if (timeout <= 0.0) { + timeout = atof(config_options[REQUEST_TIMEOUT].default_value) / 1000.0; + } /* Enter data processing loop */ DEBUG_TRACE("Websocket connection %s:%u start data processing loop", @@ -12524,7 +12813,8 @@ read_websocket(struct mg_connection *conn, /* Loop continuously, reading messages from the socket, invoking the * callback, and waiting repeatedly until an error occurs. */ - while (!conn->phys_ctx->stop_flag && !conn->must_close) { + while (STOP_FLAG_IS_ZERO(&conn->phys_ctx->stop_flag) + && (!conn->must_close)) { header_len = 0; DEBUG_ASSERT(conn->data_len >= conn->request_len); if ((body_len = (size_t)(conn->data_len - conn->request_len)) >= 2) { @@ -12677,13 +12967,93 @@ read_websocket(struct mg_connection *conn, } else { /* Exit the loop if callback signals to exit (server side), * or "connection close" opcode received (client side). */ - if ((ws_data_handler != NULL) - && !ws_data_handler(conn, - mop, - (char *)data, - (size_t)data_len, - callback_data)) { - exit_by_callback = 1; + if (ws_data_handler != NULL) { +#if defined(USE_ZLIB) && defined(MG_EXPERIMENTAL_INTERFACES) + if (mop & 0x40) { + /* Inflate the data received if bit RSV1 is set. */ + if (!conn->websocket_deflate_initialized) { + if (websocket_deflate_initialize(conn, 1) != Z_OK) + exit_by_callback = 1; + } + if (!exit_by_callback) { + size_t inflate_buf_size_old = 0; + size_t inflate_buf_size = + data_len + * 4; // Initial guess of the inflated message + // size. We double the memory when needed. + Bytef *inflated; + Bytef *new_mem; + conn->websocket_inflate_state.avail_in = + (uInt)(data_len + 4); + conn->websocket_inflate_state.next_in = data; + // Add trailing 0x00 0x00 0xff 0xff bytes + data[data_len] = '\x00'; + data[data_len + 1] = '\x00'; + data[data_len + 2] = '\xff'; + data[data_len + 3] = '\xff'; + do { + if (inflate_buf_size_old == 0) { + new_mem = mg_calloc(inflate_buf_size, + sizeof(Bytef)); + } else { + inflate_buf_size *= 2; + new_mem = + mg_realloc(inflated, inflate_buf_size); + } + if (new_mem == NULL) { + mg_cry_internal( + conn, + "Out of memory: Cannot allocate " + "inflate buffer of %i bytes", + inflate_buf_size); + exit_by_callback = 1; + break; + } + inflated = new_mem; + conn->websocket_inflate_state.avail_out = + (uInt)(inflate_buf_size + - inflate_buf_size_old); + conn->websocket_inflate_state.next_out = + inflated + inflate_buf_size_old; + int ret = + inflate(&conn->websocket_inflate_state, + Z_SYNC_FLUSH); + if (ret == Z_NEED_DICT || ret == Z_DATA_ERROR + || ret == Z_MEM_ERROR || ret < 0) { + mg_cry_internal( + conn, + "ZLIB inflate error: %i %s", + ret, + (conn->websocket_inflate_state.msg + ? conn->websocket_inflate_state.msg + : "")); + exit_by_callback = 1; + break; + } + inflate_buf_size_old = inflate_buf_size; + + } while (conn->websocket_inflate_state.avail_out + == 0); + inflate_buf_size -= + conn->websocket_inflate_state.avail_out; + if (!ws_data_handler(conn, + mop, + (char *)inflated, + inflate_buf_size, + callback_data)) { + exit_by_callback = 1; + } + mg_free(inflated); + } + } else +#endif + if (!ws_data_handler(conn, + mop, + (char *)data, + (size_t)data_len, + callback_data)) { + exit_by_callback = 1; + } } } @@ -12727,7 +13097,8 @@ read_websocket(struct mg_connection *conn, /* Reset open PING count */ ping_count = 0; } else { - if (!conn->phys_ctx->stop_flag && !conn->must_close) { + if (STOP_FLAG_IS_ZERO(&conn->phys_ctx->stop_flag) + && (!conn->must_close)) { if (ping_count > MG_MAX_UNANSWERED_PING) { /* Stop sending PING */ DEBUG_TRACE("Too many (%i) unanswered ping from %s:%u " @@ -12787,16 +13158,62 @@ mg_websocket_write_exec(struct mg_connection *conn, #pragma GCC diagnostic ignored "-Wconversion" #endif - header[0] = 0x80u | (unsigned char)((unsigned)opcode & 0xf); - -#if defined(GCC_DIAGNOSTIC) -#pragma GCC diagnostic pop -#endif + /* Note that POSIX/Winsock's send() is threadsafe + * http://stackoverflow.com/questions/1981372/are-parallel-calls-to-send-recv-on-the-same-socket-valid + * but mongoose's mg_printf/mg_write is not (because of the loop in + * push(), although that is only a problem if the packet is large or + * outgoing buffer is full). */ - /* Frame format: http://tools.ietf.org/html/rfc6455#section-5.2 */ - if (dataLen < 126) { - /* inline 7-bit length field */ - header[1] = (unsigned char)dataLen; + /* TODO: Check if this lock should be moved to user land. + * Currently the server sets this lock for websockets, but + * not for any other connection. It must be set for every + * conn read/written by more than one thread, no matter if + * it is a websocket or regular connection. */ + (void)mg_lock_connection(conn); + +#if defined(USE_ZLIB) && defined(MG_EXPERIMENTAL_INTERFACES) + size_t deflated_size; + Bytef *deflated; + // Deflate websocket messages over 100kb + int use_deflate = dataLen > 100 * 1024 && conn->accept_gzip; + + if (use_deflate) { + if (!conn->websocket_deflate_initialized) { + if (websocket_deflate_initialize(conn, 1) != Z_OK) + return 0; + } + + // Deflating the message + header[0] = 0xC0u | (unsigned char)((unsigned)opcode & 0xf); + conn->websocket_deflate_state.avail_in = (uInt)dataLen; + conn->websocket_deflate_state.next_in = (unsigned char *)data; + deflated_size = compressBound((uLong)dataLen); + deflated = mg_calloc(deflated_size, sizeof(Bytef)); + if (deflated == NULL) { + mg_cry_internal( + conn, + "Out of memory: Cannot allocate deflate buffer of %i bytes", + deflated_size); + mg_unlock_connection(conn); + return -1; + } + conn->websocket_deflate_state.avail_out = (uInt)deflated_size; + conn->websocket_deflate_state.next_out = deflated; + deflate(&conn->websocket_deflate_state, conn->websocket_deflate_flush); + dataLen = deflated_size - conn->websocket_deflate_state.avail_out + - 4; // Strip trailing 0x00 0x00 0xff 0xff bytes + } else +#endif + header[0] = 0x80u | (unsigned char)((unsigned)opcode & 0xf); + +#if defined(GCC_DIAGNOSTIC) +#pragma GCC diagnostic pop +#endif + + /* Frame format: http://tools.ietf.org/html/rfc6455#section-5.2 */ + if (dataLen < 126) { + /* inline 7-bit length field */ + header[1] = (unsigned char)dataLen; headerLen = 2; } else if (dataLen <= 0xFFFF) { /* 16-bit length field */ @@ -12821,26 +13238,19 @@ mg_websocket_write_exec(struct mg_connection *conn, headerLen += 4; } - /* Note that POSIX/Winsock's send() is threadsafe - * http://stackoverflow.com/questions/1981372/are-parallel-calls-to-send-recv-on-the-same-socket-valid - * but mongoose's mg_printf/mg_write is not (because of the loop in - * push(), although that is only a problem if the packet is large or - * outgoing buffer is full). */ - - /* TODO: Check if this lock should be moved to user land. - * Currently the server sets this lock for websockets, but - * not for any other connection. It must be set for every - * conn read/written by more than one thread, no matter if - * it is a websocket or regular connection. */ - (void)mg_lock_connection(conn); - retval = mg_write(conn, header, headerLen); if (retval != (int)headerLen) { /* Did not send complete header */ retval = -1; } else { if (dataLen > 0) { - retval = mg_write(conn, data, dataLen); +#if defined(USE_ZLIB) && defined(MG_EXPERIMENTAL_INTERFACES) + if (use_deflate) { + retval = mg_write(conn, deflated, dataLen); + mg_free(deflated); + } else +#endif + retval = mg_write(conn, data, dataLen); } /* if dataLen == 0, the header length (2) is returned */ } @@ -13011,7 +13421,6 @@ handle_websocket_request(struct mg_connection *conn, ; // ignore leading whitespaces protocol = sep; - for (idx = 0; idx < subprotocols->nb_subprotocols; idx++) { if ((strlen(subprotocols->subprotocols[idx]) == len) && (strncmp(curSubProtocol, @@ -13057,6 +13466,10 @@ handle_websocket_request(struct mg_connection *conn, } } +#if defined(USE_ZLIB) && defined(MG_EXPERIMENTAL_INTERFACES) + websocket_deflate_negotiate(conn); +#endif + if ((ws_connect_handler != NULL) && (ws_connect_handler(conn, cbData) != 0)) { /* C callback has returned non-zero, do not proceed with @@ -13073,10 +13486,8 @@ handle_websocket_request(struct mg_connection *conn, else { /* Step 3.1: Check if Lua is responsible. */ if (conn->dom_ctx->config[LUA_WEBSOCKET_EXTENSIONS]) { - lua_websock = match_prefix( - conn->dom_ctx->config[LUA_WEBSOCKET_EXTENSIONS], - strlen(conn->dom_ctx->config[LUA_WEBSOCKET_EXTENSIONS]), - path); + lua_websock = match_prefix_strlen( + conn->dom_ctx->config[LUA_WEBSOCKET_EXTENSIONS], path); } if (lua_websock) { @@ -13129,15 +13540,30 @@ handle_websocket_request(struct mg_connection *conn, #endif } - /* Step 8: Call the close handler */ +#if defined(USE_ZLIB) && defined(MG_EXPERIMENTAL_INTERFACES) + /* Step 8: Close the deflate & inflate buffers */ + if (conn->websocket_deflate_initialized) { + deflateEnd(&conn->websocket_deflate_state); + inflateEnd(&conn->websocket_inflate_state); + } +#endif + + /* Step 9: Call the close handler */ if (ws_close_handler) { ws_close_handler(conn, cbData); } } +#endif /* !USE_WEBSOCKET */ +/* Is upgrade request: + * 0 = regular HTTP/1.0 or HTTP/1.1 request + * 1 = upgrade to websocket + * 2 = upgrade to HTTP/2 + * -1 = upgrade to unknown protocol + */ static int -is_websocket_protocol(const struct mg_connection *conn) +should_switch_to_protocol(const struct mg_connection *conn) { const char *upgrade, *connection; @@ -13147,69 +13573,147 @@ is_websocket_protocol(const struct mg_connection *conn) * Upgrade: Websocket */ - upgrade = mg_get_header(conn, "Upgrade"); - if (upgrade == NULL) { - return 0; /* fail early, don't waste time checking other header - * fields - */ - } - DEBUG_TRACE("Upgrade: %s", upgrade); - if (!mg_strcasestr(upgrade, "websocket")) { - return 0; - } - connection = mg_get_header(conn, "Connection"); if (connection == NULL) { - return 0; + return PROTOCOL_TYPE_HTTP1; } if (!mg_strcasestr(connection, "upgrade")) { - return 0; + return PROTOCOL_TYPE_HTTP1; } - /* The headers "Host", "Sec-WebSocket-Key", "Sec-WebSocket-Protocol" and - * "Sec-WebSocket-Version" are also required. - * Don't check them here, since even an unsupported websocket protocol - * request still IS a websocket request (in contrast to a standard HTTP - * request). It will fail later in handle_websocket_request. - */ + upgrade = mg_get_header(conn, "Upgrade"); + if (upgrade == NULL) { + /* "Connection: Upgrade" without "Upgrade" Header --> Error */ + return -1; + } - return 1; + /* Upgrade to ... */ + if (0 != mg_strcasestr(upgrade, "websocket")) { + /* The headers "Host", "Sec-WebSocket-Key", "Sec-WebSocket-Protocol" and + * "Sec-WebSocket-Version" are also required. + * Don't check them here, since even an unsupported websocket protocol + * request still IS a websocket request (in contrast to a standard HTTP + * request). It will fail later in handle_websocket_request. + */ + return PROTOCOL_TYPE_WEBSOCKET; /* Websocket */ + } + if (0 != mg_strcasestr(upgrade, "h2")) { + return PROTOCOL_TYPE_HTTP2; /* Websocket */ + } + + /* Upgrade to another protocol */ + return -1; } -#endif /* !USE_WEBSOCKET */ static int -isbyte(int n) +parse_match_net(const struct vec *vec, const union usa *sa, int no_strict) { - return (n >= 0) && (n <= 255); -} + int n; + unsigned int a, b, c, d, slash; + if (sscanf(vec->ptr, "%u.%u.%u.%u/%u%n", &a, &b, &c, &d, &slash, &n) != 5) { + slash = 32; + if (sscanf(vec->ptr, "%u.%u.%u.%u%n", &a, &b, &c, &d, &n) != 4) { + n = 0; + } + } -static int -parse_net(const char *spec, uint32_t *net, uint32_t *mask) -{ - int n, a, b, c, d, slash = 32, len = 0; + if ((n > 0) && ((size_t)n == vec->len)) { + if ((a < 256) && (b < 256) && (c < 256) && (d < 256) && (slash < 33)) { + /* IPv4 format */ + if (sa->sa.sa_family == AF_INET) { + uint32_t ip = (uint32_t)ntohl(sa->sin.sin_addr.s_addr); + uint32_t net = ((uint32_t)a << 24) | ((uint32_t)b << 16) + | ((uint32_t)c << 8) | (uint32_t)d; + uint32_t mask = slash ? (0xFFFFFFFFu << (32 - slash)) : 0; + return (ip & mask) == net; + } + return 0; + } + } +#if defined(USE_IPV6) + else { + char ad[50]; + const char *p; + + if (sscanf(vec->ptr, "[%49[^]]]/%u%n", ad, &slash, &n) != 2) { + slash = 128; + if (sscanf(vec->ptr, "[%49[^]]]%n", ad, &n) != 1) { + n = 0; + } + } - if (((sscanf(spec, "%d.%d.%d.%d/%d%n", &a, &b, &c, &d, &slash, &n) == 5) - || (sscanf(spec, "%d.%d.%d.%d%n", &a, &b, &c, &d, &n) == 4)) - && isbyte(a) && isbyte(b) && isbyte(c) && isbyte(d) && (slash >= 0) - && (slash < 33)) { - len = n; - *net = ((uint32_t)a << 24) | ((uint32_t)b << 16) | ((uint32_t)c << 8) - | (uint32_t)d; - *mask = slash ? (0xffffffffU << (32 - slash)) : 0; + if ((n <= 0) && no_strict) { + /* no square brackets? */ + p = strchr(vec->ptr, '/'); + if (p && (p < (vec->ptr + vec->len))) { + if (((size_t)(p - vec->ptr) < sizeof(ad)) + && (sscanf(p, "/%u%n", &slash, &n) == 1)) { + n += (int)(p - vec->ptr); + mg_strlcpy(ad, vec->ptr, (size_t)(p - vec->ptr) + 1); + } else { + n = 0; + } + } else if (vec->len < sizeof(ad)) { + n = (int)vec->len; + slash = 128; + mg_strlcpy(ad, vec->ptr, vec->len + 1); + } + } + + if ((n > 0) && ((size_t)n == vec->len) && (slash < 129)) { + p = ad; + c = 0; + /* zone indexes are unsupported, at least two colons are needed */ + while (isxdigit((unsigned char)*p) || (*p == '.') || (*p == ':')) { + if (*(p++) == ':') { + c++; + } + } + if ((*p == '\0') && (c >= 2)) { + struct sockaddr_in6 sin6; + unsigned int i; + + /* for strict validation, an actual IPv6 argument is needed */ + if (sa->sa.sa_family != AF_INET6) { + return 0; + } + if (mg_inet_pton(AF_INET6, ad, &sin6, sizeof(sin6), 0)) { + /* IPv6 format */ + for (i = 0; i < 16; i++) { + uint8_t ip = sa->sin6.sin6_addr.s6_addr[i]; + uint8_t net = sin6.sin6_addr.s6_addr[i]; + uint8_t mask = 0; + + if (8 * i + 8 < slash) { + mask = 0xFFu; + } else if (8 * i < slash) { + mask = (uint8_t)(0xFFu << (8 * i + 8 - slash)); + } + if ((ip & mask) != net) { + return 0; + } + } + return 1; + } + } + } } +#else + (void)no_strict; +#endif - return len; + /* malformed */ + return -1; } static int -set_throttle(const char *spec, uint32_t remote_ip, const char *uri) +set_throttle(const char *spec, const union usa *rsa, const char *uri) { int throttle = 0; struct vec vec, val; - uint32_t net, mask; char mult; double v; @@ -13226,12 +13730,16 @@ set_throttle(const char *spec, uint32_t remote_ip, const char *uri) : ((lowercase(&mult) == 'm') ? 1048576 : 1); if (vec.len == 1 && vec.ptr[0] == '*') { throttle = (int)v; - } else if (parse_net(vec.ptr, &net, &mask) > 0) { - if ((remote_ip & mask) == net) { + } else { + int matched = parse_match_net(&vec, rsa, 0); + if (matched >= 0) { + /* a valid IP subnet */ + if (matched) { + throttle = (int)v; + } + } else if (match_prefix(vec.ptr, vec.len, uri) > 0) { throttle = (int)v; } - } else if (match_prefix(vec.ptr, vec.len, uri) > 0) { - throttle = (int)v; } } @@ -13239,118 +13747,10 @@ set_throttle(const char *spec, uint32_t remote_ip, const char *uri) } -static uint32_t -get_remote_ip(const struct mg_connection *conn) -{ - if (!conn) { - return 0; - } - return ntohl(*(const uint32_t *)&conn->client.rsa.sin.sin_addr); -} - - /* The mg_upload function is superseeded by mg_handle_form_request. */ #include "handle_form.inl" -#if defined(MG_LEGACY_INTERFACE) -/* Implement the deprecated mg_upload function by calling the new - * mg_handle_form_request function. While mg_upload could only handle - * HTML forms sent as POST request in multipart/form-data format - * containing only file input elements, mg_handle_form_request can - * handle all form input elements and all standard request methods. */ -struct mg_upload_user_data { - struct mg_connection *conn; - const char *destination_dir; - int num_uploaded_files; -}; - - -/* Helper function for deprecated mg_upload. */ -static int -mg_upload_field_found(const char *key, - const char *filename, - char *path, - size_t pathlen, - void *user_data) -{ - int truncated = 0; - struct mg_upload_user_data *fud = (struct mg_upload_user_data *)user_data; - (void)key; - - if (!filename) { - mg_cry_internal(fud->conn, "%s: No filename set", __func__); - return FORM_FIELD_STORAGE_ABORT; - } - mg_snprintf(fud->conn, - &truncated, - path, - pathlen - 1, - "%s/%s", - fud->destination_dir, - filename); - if (truncated) { - mg_cry_internal(fud->conn, "%s: File path too long", __func__); - return FORM_FIELD_STORAGE_ABORT; - } - return FORM_FIELD_STORAGE_STORE; -} - - -/* Helper function for deprecated mg_upload. */ -static int -mg_upload_field_get(const char *key, - const char *value, - size_t value_size, - void *user_data) -{ - /* Function should never be called */ - (void)key; - (void)value; - (void)value_size; - (void)user_data; - - return 0; -} - - -/* Helper function for deprecated mg_upload. */ -static int -mg_upload_field_stored(const char *path, long long file_size, void *user_data) -{ - struct mg_upload_user_data *fud = (struct mg_upload_user_data *)user_data; - (void)file_size; - - fud->num_uploaded_files++; - fud->conn->phys_ctx->callbacks.upload(fud->conn, path); - - return 0; -} - - -/* Deprecated function mg_upload - use mg_handle_form_request instead. */ -int -mg_upload(struct mg_connection *conn, const char *destination_dir) -{ - struct mg_upload_user_data fud = {conn, destination_dir, 0}; - struct mg_form_data_handler fdh = {mg_upload_field_found, - mg_upload_field_get, - mg_upload_field_stored, - 0}; - int ret; - - fdh.user_data = (void *)&fud; - ret = mg_handle_form_request(conn, &fdh); - - if (ret < 0) { - mg_cry_internal(conn, "%s: Error while parsing the request", __func__); - } - - return fud.num_uploaded_files; -} -#endif - - static int get_first_ssl_listener_index(const struct mg_context *ctx) { @@ -13366,68 +13766,76 @@ get_first_ssl_listener_index(const struct mg_context *ctx) /* Return host (without port) */ -/* Use mg_free to free the result */ -static const char * -alloc_get_host(struct mg_connection *conn) +static void +get_host_from_request_info(struct vec *host, const struct mg_request_info *ri) { - char buf[1025]; - size_t buflen = sizeof(buf); - const char *host_header = get_header(conn->request_info.http_headers, - conn->request_info.num_headers, - "Host"); - char *host; + const char *host_header = + get_header(ri->http_headers, ri->num_headers, "Host"); + + host->ptr = NULL; + host->len = 0; if (host_header != NULL) { char *pos; - /* Create a local copy of the "Host" header, since it might be - * modified here. */ - mg_strlcpy(buf, host_header, buflen); - buf[buflen - 1] = '\0'; - host = buf; - while (isspace((unsigned char)*host)) { - host++; - } - /* If the "Host" is an IPv6 address, like [::1], parse until ] * is found. */ - if (*host == '[') { - pos = strchr(host, ']'); + if (*host_header == '[') { + pos = strchr(host_header, ']'); if (!pos) { /* Malformed hostname starts with '[', but no ']' found */ DEBUG_TRACE("%s", "Host name format error '[' without ']'"); - return NULL; + return; } /* terminate after ']' */ - pos[1] = 0; + host->ptr = host_header; + host->len = (size_t)(pos + 1 - host_header); } else { /* Otherwise, a ':' separates hostname and port number */ - pos = strchr(host, ':'); + pos = strchr(host_header, ':'); if (pos != NULL) { - *pos = '\0'; + host->len = (size_t)(pos - host_header); + } else { + host->len = strlen(host_header); } + host->ptr = host_header; } + } +} + +static int +switch_domain_context(struct mg_connection *conn) +{ + struct vec host; + + get_host_from_request_info(&host, &conn->request_info); + + if (host.ptr) { if (conn->ssl) { /* This is a HTTPS connection, maybe we have a hostname * from SNI (set in ssl_servername_callback). */ const char *sslhost = conn->dom_ctx->config[AUTHENTICATION_DOMAIN]; if (sslhost && (conn->dom_ctx != &(conn->phys_ctx->dd))) { /* We are not using the default domain */ - if (mg_strcasecmp(host, sslhost)) { + if ((strlen(sslhost) != host.len) + || mg_strncasecmp(host.ptr, sslhost, host.len)) { /* Mismatch between SNI domain and HTTP domain */ - DEBUG_TRACE("Host mismatch: SNI: %s, HTTPS: %s", + DEBUG_TRACE("Host mismatch: SNI: %s, HTTPS: %.*s", sslhost, - host); - return NULL; + (int)host.len, + host.ptr); + return 0; } } - DEBUG_TRACE("HTTPS Host: %s", host); } else { struct mg_domain_context *dom = &(conn->phys_ctx->dd); while (dom) { - if (!mg_strcasecmp(host, dom->config[AUTHENTICATION_DOMAIN])) { + if ((strlen(dom->config[AUTHENTICATION_DOMAIN]) == host.len) + && !mg_strncasecmp(host.ptr, + dom->config[AUTHENTICATION_DOMAIN], + host.len)) { /* Found matching domain */ DEBUG_TRACE("HTTP domain %s found", @@ -13437,102 +13845,75 @@ alloc_get_host(struct mg_connection *conn) conn->dom_ctx = dom; break; } + mg_lock_context(conn->phys_ctx); dom = dom->next; + mg_unlock_context(conn->phys_ctx); } - - DEBUG_TRACE("HTTP Host: %s", host); } } else { - sockaddr_to_string(buf, buflen, &conn->client.lsa); - host = buf; - - DEBUG_TRACE("IP: %s", host); + DEBUG_TRACE("HTTP%s Host is not set", conn->ssl ? "S" : ""); + return 1; } - return mg_strdup_ctx(host, conn->phys_ctx); + DEBUG_TRACE("HTTP%s Host: %.*s", + conn->ssl ? "S" : "", + (int)host.len, + host.ptr); + return 1; } +static int mg_construct_local_link(const struct mg_connection *conn, + char *buf, + size_t buflen, + const char *define_proto, + int define_port, + const char *define_uri); + + static void -redirect_to_https_port(struct mg_connection *conn, int ssl_index) +redirect_to_https_port(struct mg_connection *conn, int port) { char target_url[MG_BUF_LEN]; int truncated = 0; + const char *expect_proto = + (conn->protocol_type == PROTOCOL_TYPE_WEBSOCKET) ? "wss" : "https"; + + /* Use "308 Permanent Redirect" */ + int redirect_code = 308; + /* In any case, close the current connection */ conn->must_close = 1; /* Send host, port, uri and (if it exists) ?query_string */ - if (conn->host) { - - /* Use "308 Permanent Redirect" */ - int redirect_code = 308; - - /* Create target URL */ - mg_snprintf( - conn, - &truncated, - target_url, - sizeof(target_url), - "https://%s:%d%s%s%s", - - conn->host, -#if defined(USE_IPV6) - (conn->phys_ctx->listening_sockets[ssl_index].lsa.sa.sa_family - == AF_INET6) - ? (int)ntohs(conn->phys_ctx->listening_sockets[ssl_index] - .lsa.sin6.sin6_port) - : -#endif - (int)ntohs(conn->phys_ctx->listening_sockets[ssl_index] - .lsa.sin.sin_port), - conn->request_info.local_uri, - (conn->request_info.query_string == NULL) ? "" : "?", - (conn->request_info.query_string == NULL) - ? "" - : conn->request_info.query_string); - - /* Check overflow in location buffer (will not occur if MG_BUF_LEN - * is used as buffer size) */ - if (truncated) { - mg_send_http_error(conn, 500, "%s", "Redirect URL too long"); - return; + if (mg_construct_local_link( + conn, target_url, sizeof(target_url), expect_proto, port, NULL) + < 0) { + truncated = 1; + } else if (conn->request_info.query_string != NULL) { + size_t slen1 = strlen(target_url); + size_t slen2 = strlen(conn->request_info.query_string); + if ((slen1 + slen2 + 2) < sizeof(target_url)) { + target_url[slen1] = '?'; + memcpy(target_url + slen1 + 1, + conn->request_info.query_string, + slen2); + target_url[slen1 + slen2 + 1] = 0; + } else { + truncated = 1; } - - /* Use redirect helper function */ - mg_send_http_redirect(conn, target_url, redirect_code); } -} - - -static void -handler_info_acquire(struct mg_handler_info *handler_info) -{ - pthread_mutex_lock(&handler_info->refcount_mutex); - handler_info->refcount++; - pthread_mutex_unlock(&handler_info->refcount_mutex); -} - - -static void -handler_info_release(struct mg_handler_info *handler_info) -{ - pthread_mutex_lock(&handler_info->refcount_mutex); - handler_info->refcount--; - pthread_cond_signal(&handler_info->refcount_cond); - pthread_mutex_unlock(&handler_info->refcount_mutex); -} - -static void -handler_info_wait_unused(struct mg_handler_info *handler_info) -{ - pthread_mutex_lock(&handler_info->refcount_mutex); - while (handler_info->refcount) { - pthread_cond_wait(&handler_info->refcount_cond, - &handler_info->refcount_mutex); + /* Check overflow in location buffer (will not occur if MG_BUF_LEN + * is used as buffer size) */ + if (truncated) { + mg_send_http_error(conn, 500, "%s", "Redirect URL too long"); + return; } - pthread_mutex_unlock(&handler_info->refcount_mutex); + + /* Use redirect helper function */ + mg_send_http_redirect(conn, target_url, redirect_code); } @@ -13553,8 +13934,6 @@ mg_set_handler_type(struct mg_context *phys_ctx, { struct mg_handler_info *tmp_rh, **lastref; size_t urilen = strlen(uri); - struct mg_workerTLS tls; - int is_tls_set = 0; if (handler_type == WEBSOCKET_HANDLER) { DEBUG_ASSERT(handler == NULL); @@ -13611,35 +13990,26 @@ mg_set_handler_type(struct mg_context *phys_ctx, return; } - /* Internal callbacks have their contexts set - * if called from non-related thread, context must be set - * since internal function assumes it exists. - * For an example see how handler_info_wait_unused() - * waits for reference to become zero - */ - if (NULL == pthread_getspecific(sTlsKey)) { - is_tls_set = 1; - tls.is_master = -1; - tls.thread_idx = phys_ctx->starter_thread_idx; -#if defined(_WIN32) - tls.pthread_cond_helper_mutex = NULL; -#endif - pthread_setspecific(sTlsKey, &tls); - } - mg_lock_context(phys_ctx); /* first try to find an existing handler */ - lastref = &(dom_ctx->handlers); - for (tmp_rh = dom_ctx->handlers; tmp_rh != NULL; tmp_rh = tmp_rh->next) { - if (tmp_rh->handler_type == handler_type) { - if ((urilen == tmp_rh->uri_len) && !strcmp(tmp_rh->uri, uri)) { + do { + lastref = &(dom_ctx->handlers); + for (tmp_rh = dom_ctx->handlers; tmp_rh != NULL; + tmp_rh = tmp_rh->next) { + if (tmp_rh->handler_type == handler_type + && (urilen == tmp_rh->uri_len) && !strcmp(tmp_rh->uri, uri)) { if (!is_delete_request) { /* update existing handler */ if (handler_type == REQUEST_HANDLER) { /* Wait for end of use before updating */ - handler_info_wait_unused(tmp_rh); - + if (tmp_rh->refcount) { + mg_unlock_context(phys_ctx); + mg_sleep(1); + mg_lock_context(phys_ctx); + /* tmp_rh might have been freed, search again. */ + break; + } /* Ok, the handler is no more use -> Update it */ tmp_rh->handler = handler; } else if (handler_type == WEBSOCKET_HANDLER) { @@ -13656,34 +14026,31 @@ mg_set_handler_type(struct mg_context *phys_ctx, /* remove existing handler */ if (handler_type == REQUEST_HANDLER) { /* Wait for end of use before removing */ - handler_info_wait_unused(tmp_rh); - - /* Ok, the handler is no more used -> Destroy resources - */ - pthread_cond_destroy(&tmp_rh->refcount_cond); - pthread_mutex_destroy(&tmp_rh->refcount_mutex); + if (tmp_rh->refcount) { + tmp_rh->removing = 1; + mg_unlock_context(phys_ctx); + mg_sleep(1); + mg_lock_context(phys_ctx); + /* tmp_rh might have been freed, search again. */ + break; + } + /* Ok, the handler is no more used */ } *lastref = tmp_rh->next; mg_free(tmp_rh->uri); mg_free(tmp_rh); } mg_unlock_context(phys_ctx); - if (is_tls_set) { - pthread_setspecific(sTlsKey, NULL); - } return; } + lastref = &(tmp_rh->next); } - lastref = &(tmp_rh->next); - } + } while (tmp_rh != NULL); if (is_delete_request) { /* no handler to set, this was a remove request to a non-existing * handler */ mg_unlock_context(phys_ctx); - if (is_tls_set) { - pthread_setspecific(sTlsKey, NULL); - } return; } @@ -13696,9 +14063,6 @@ mg_set_handler_type(struct mg_context *phys_ctx, mg_cry_ctx_internal(phys_ctx, "%s", "Cannot create new request handler struct, OOM"); - if (is_tls_set) { - pthread_setspecific(sTlsKey, NULL); - } return; } tmp_rh->uri = mg_strdup_ctx(uri, phys_ctx); @@ -13708,34 +14072,12 @@ mg_set_handler_type(struct mg_context *phys_ctx, mg_cry_ctx_internal(phys_ctx, "%s", "Cannot create new request handler struct, OOM"); - if (is_tls_set) { - pthread_setspecific(sTlsKey, NULL); - } return; } tmp_rh->uri_len = urilen; if (handler_type == REQUEST_HANDLER) { - /* Init refcount mutex and condition */ - if (0 != pthread_mutex_init(&tmp_rh->refcount_mutex, NULL)) { - mg_unlock_context(phys_ctx); - mg_free(tmp_rh); - mg_cry_ctx_internal(phys_ctx, "%s", "Cannot init refcount mutex"); - if (is_tls_set) { - pthread_setspecific(sTlsKey, NULL); - } - return; - } - if (0 != pthread_cond_init(&tmp_rh->refcount_cond, NULL)) { - mg_unlock_context(phys_ctx); - pthread_mutex_destroy(&tmp_rh->refcount_mutex); - mg_free(tmp_rh); - mg_cry_ctx_internal(phys_ctx, "%s", "Cannot init refcount cond"); - if (is_tls_set) { - pthread_setspecific(sTlsKey, NULL); - } - return; - } tmp_rh->refcount = 0; + tmp_rh->removing = 0; tmp_rh->handler = handler; } else if (handler_type == WEBSOCKET_HANDLER) { tmp_rh->subprotocols = subprotocols; @@ -13752,9 +14094,6 @@ mg_set_handler_type(struct mg_context *phys_ctx, *lastref = tmp_rh; mg_unlock_context(phys_ctx); - if (is_tls_set) { - pthread_setspecific(sTlsKey, NULL); - } } @@ -13870,6 +14209,7 @@ get_request_handler(struct mg_connection *conn, const char *uri = request_info->local_uri; size_t urilen = strlen(uri); struct mg_handler_info *tmp_rh; + int step, matched; if (!conn || !conn->phys_ctx || !conn->dom_ctx) { return 0; @@ -13877,64 +14217,29 @@ get_request_handler(struct mg_connection *conn, mg_lock_context(conn->phys_ctx); - /* first try for an exact match */ - for (tmp_rh = conn->dom_ctx->handlers; tmp_rh != NULL; - tmp_rh = tmp_rh->next) { - if (tmp_rh->handler_type == handler_type) { - if ((urilen == tmp_rh->uri_len) && !strcmp(tmp_rh->uri, uri)) { - if (handler_type == WEBSOCKET_HANDLER) { - *subprotocols = tmp_rh->subprotocols; - *connect_handler = tmp_rh->connect_handler; - *ready_handler = tmp_rh->ready_handler; - *data_handler = tmp_rh->data_handler; - *close_handler = tmp_rh->close_handler; - } else if (handler_type == REQUEST_HANDLER) { - *handler = tmp_rh->handler; - /* Acquire handler and give it back */ - handler_info_acquire(tmp_rh); - *handler_info = tmp_rh; - } else { /* AUTH_HANDLER */ - *auth_handler = tmp_rh->auth_handler; - } - *cbdata = tmp_rh->cbdata; - mg_unlock_context(conn->phys_ctx); - return 1; + for (step = 0; step < 3; step++) { + for (tmp_rh = conn->dom_ctx->handlers; tmp_rh != NULL; + tmp_rh = tmp_rh->next) { + if (tmp_rh->handler_type != handler_type) { + continue; } - } - } - - /* next try for a partial match, we will accept uri/something */ - for (tmp_rh = conn->dom_ctx->handlers; tmp_rh != NULL; - tmp_rh = tmp_rh->next) { - if (tmp_rh->handler_type == handler_type) { - if ((tmp_rh->uri_len < urilen) && (uri[tmp_rh->uri_len] == '/') - && (memcmp(tmp_rh->uri, uri, tmp_rh->uri_len) == 0)) { - if (handler_type == WEBSOCKET_HANDLER) { - *subprotocols = tmp_rh->subprotocols; - *connect_handler = tmp_rh->connect_handler; - *ready_handler = tmp_rh->ready_handler; - *data_handler = tmp_rh->data_handler; - *close_handler = tmp_rh->close_handler; - } else if (handler_type == REQUEST_HANDLER) { - *handler = tmp_rh->handler; - /* Acquire handler and give it back */ - handler_info_acquire(tmp_rh); - *handler_info = tmp_rh; - } else { /* AUTH_HANDLER */ - *auth_handler = tmp_rh->auth_handler; - } - *cbdata = tmp_rh->cbdata; - mg_unlock_context(conn->phys_ctx); - return 1; + if (step == 0) { + /* first try for an exact match */ + matched = (tmp_rh->uri_len == urilen) + && (strcmp(tmp_rh->uri, uri) == 0); + } else if (step == 1) { + /* next try for a partial match, we will accept + uri/something */ + matched = + (tmp_rh->uri_len < urilen) + && (uri[tmp_rh->uri_len] == '/') + && (memcmp(tmp_rh->uri, uri, tmp_rh->uri_len) == 0); + } else { + /* finally try for pattern match */ + matched = + match_prefix(tmp_rh->uri, tmp_rh->uri_len, uri) > 0; } - } - } - - /* finally try for pattern match */ - for (tmp_rh = conn->dom_ctx->handlers; tmp_rh != NULL; - tmp_rh = tmp_rh->next) { - if (tmp_rh->handler_type == handler_type) { - if (match_prefix(tmp_rh->uri, tmp_rh->uri_len, uri) > 0) { + if (matched) { if (handler_type == WEBSOCKET_HANDLER) { *subprotocols = tmp_rh->subprotocols; *connect_handler = tmp_rh->connect_handler; @@ -13942,9 +14247,14 @@ get_request_handler(struct mg_connection *conn, *data_handler = tmp_rh->data_handler; *close_handler = tmp_rh->close_handler; } else if (handler_type == REQUEST_HANDLER) { + if (tmp_rh->removing) { + /* Treat as none found */ + step = 2; + break; + } *handler = tmp_rh->handler; /* Acquire handler and give it back */ - handler_info_acquire(tmp_rh); + tmp_rh->refcount++; *handler_info = tmp_rh; } else { /* AUTH_HANDLER */ *auth_handler = tmp_rh->auth_handler; @@ -13977,37 +14287,13 @@ is_in_script_path(const struct mg_connection *conn, const char *path) } -#if defined(USE_WEBSOCKET) \ - && (defined(MG_LEGACY_INTERFACE) || defined(MG_EXPERIMENTAL_INTERFACES)) -static int -deprecated_websocket_connect_wrapper(const struct mg_connection *conn, - void *cbdata) -{ - struct mg_callbacks *pcallbacks = (struct mg_callbacks *)cbdata; - if (pcallbacks->websocket_connect) { - return pcallbacks->websocket_connect(conn); - } - /* No handler set - assume "OK" */ - return 0; -} - - -static void -deprecated_websocket_ready_wrapper(struct mg_connection *conn, void *cbdata) -{ - struct mg_callbacks *pcallbacks = (struct mg_callbacks *)cbdata; - if (pcallbacks->websocket_ready) { - pcallbacks->websocket_ready(conn); - } -} - - +#if defined(USE_WEBSOCKET) && defined(MG_EXPERIMENTAL_INTERFACES) static int -deprecated_websocket_data_wrapper(struct mg_connection *conn, - int bits, - char *data, - size_t len, - void *cbdata) +experimental_websocket_client_data_wrapper(struct mg_connection *conn, + int bits, + char *data, + size_t len, + void *cbdata) { struct mg_callbacks *pcallbacks = (struct mg_callbacks *)cbdata; if (pcallbacks->websocket_data) { @@ -14019,8 +14305,8 @@ deprecated_websocket_data_wrapper(struct mg_connection *conn, static void -deprecated_websocket_close_wrapper(const struct mg_connection *conn, - void *cbdata) +experimental_websocket_client_close_wrapper(const struct mg_connection *conn, + void *cbdata) { struct mg_callbacks *pcallbacks = (struct mg_callbacks *)cbdata; if (pcallbacks->connection_close) { @@ -14041,7 +14327,8 @@ handle_request(struct mg_connection *conn) char path[PATH_MAX]; int uri_len, ssl_index; int is_found = 0, is_script_resource = 0, is_websocket_request = 0, - is_put_or_delete_request = 0, is_callback_resource = 0; + is_put_or_delete_request = 0, is_callback_resource = 0, + is_template_text_file = 0; int i; struct mg_file file = STRUCT_FILE_INITIALIZER; mg_request_handler callback_handler = NULL; @@ -14060,6 +14347,9 @@ handle_request(struct mg_connection *conn) path[0] = 0; + /* 0. Reset internal state (required for HTTP/2 proxy) */ + conn->request_state = 0; + /* 1. get the request url */ /* 1.1. split into url and query string */ if ((conn->request_info.query_string = strchr(ri->request_uri, '?')) @@ -14071,7 +14361,9 @@ handle_request(struct mg_connection *conn) if (!conn->client.is_ssl && conn->client.ssl_redir) { ssl_index = get_first_ssl_listener_index(conn->phys_ctx); if (ssl_index >= 0) { - redirect_to_https_port(conn, ssl_index); + int port = (int)ntohs(USA_IN_PORT_UNSAFE( + &(conn->phys_ctx->listening_sockets[ssl_index].lsa))); + redirect_to_https_port(conn, port); } else { /* A http to https forward port has been specified, * but no https port to forward to. */ @@ -14091,6 +14383,10 @@ handle_request(struct mg_connection *conn) if (should_decode_url(conn)) { mg_url_decode( ri->local_uri, uri_len, (char *)ri->local_uri, uri_len + 1, 0); + + if (conn->request_info.query_string) { + url_decode_in_place((char *)conn->request_info.query_string); + } } /* 1.4. clean URIs, so a path like allowed_dir/../forbidden_file is @@ -14103,7 +14399,7 @@ handle_request(struct mg_connection *conn) /* 2. if this ip has limited speed, set it for this connection */ conn->throttle = set_throttle(conn->dom_ctx->config[THROTTLE], - get_remote_ip(conn), + &conn->client.rsa, ri->local_uri); /* 3. call a "handle everything" callback, if registered */ @@ -14155,8 +14451,7 @@ handle_request(struct mg_connection *conn) && (cors_orig_cfg != NULL) && (*cors_orig_cfg != 0) && (cors_origin != NULL) && (cors_acrm != NULL)) { /* This is a valid CORS preflight, and the server is configured - * to - * handle it automatically. */ + * to handle it automatically. */ const char *cors_acrh = get_header(ri->http_headers, ri->num_headers, @@ -14204,12 +14499,17 @@ handle_request(struct mg_connection *conn) /* 5.1. first test, if the request targets the regular http(s):// * protocol namespace or the websocket ws(s):// protocol namespace. */ - is_websocket_request = is_websocket_protocol(conn); + is_websocket_request = (conn->protocol_type == PROTOCOL_TYPE_WEBSOCKET); #if defined(USE_WEBSOCKET) handler_type = is_websocket_request ? WEBSOCKET_HANDLER : REQUEST_HANDLER; #else handler_type = REQUEST_HANDLER; #endif /* defined(USE_WEBSOCKET) */ + + if (is_websocket_request) { + HTTP1_only; + } + /* 5.2. check if the request will be handled by a callback */ if (get_request_handler(conn, handler_type, @@ -14223,9 +14523,8 @@ handle_request(struct mg_connection *conn) &callback_data, &handler_info)) { /* 5.2.1. A callback will handle this request. All requests - * handled - * by a callback have to be considered as requests to a script - * resource. */ + * handled by a callback have to be considered as requests + * to a script resource. */ is_callback_resource = 1; is_script_resource = 1; is_put_or_delete_request = is_put_or_delete_method(conn); @@ -14243,7 +14542,8 @@ handle_request(struct mg_connection *conn) &is_found, &is_script_resource, &is_websocket_request, - &is_put_or_delete_request); + &is_put_or_delete_request, + &is_template_text_file); } /* 6. authorization check */ @@ -14264,6 +14564,7 @@ handle_request(struct mg_connection *conn) } } else if (is_put_or_delete_request && !is_script_resource && !is_callback_resource) { + HTTP1_only; /* 6.2. this request is a PUT/DELETE to a real file */ /* 6.2.1. thus, the server must have real files */ #if defined(NO_FILES) @@ -14304,11 +14605,14 @@ handle_request(struct mg_connection *conn) /* 7. check if there are request handlers for this uri */ if (is_callback_resource) { + HTTP1_only; if (!is_websocket_request) { i = callback_handler(conn, callback_data); /* Callback handler will not be used anymore. Release it */ - handler_info_release(handler_info); + mg_lock_context(conn->phys_ctx); + handler_info->refcount--; + mg_unlock_context(conn->phys_ctx); if (i > 0) { /* Do nothing, callback has served the request. Store @@ -14330,7 +14634,7 @@ handle_request(struct mg_connection *conn) * * TODO: What would be the best reaction here? * (Note: The reaction may change, if there is a better - *idea.) + * idea.) */ /* For the moment, use option c: We look for a proper file, @@ -14343,7 +14647,8 @@ handle_request(struct mg_connection *conn) &is_found, &is_script_resource, &is_websocket_request, - &is_put_or_delete_request); + &is_put_or_delete_request, + &is_template_text_file); callback_handler = NULL; /* Here we are at a dead end: @@ -14376,6 +14681,7 @@ handle_request(struct mg_connection *conn) /* 8. handle websocket requests */ #if defined(USE_WEBSOCKET) if (is_websocket_request) { + HTTP1_only; if (is_script_resource) { if (is_in_script_path(conn, path)) { @@ -14394,20 +14700,7 @@ handle_request(struct mg_connection *conn) mg_send_http_error(conn, 403, "%s", "Forbidden"); } } else { -#if defined(MG_LEGACY_INTERFACE) - handle_websocket_request( - conn, - path, - !is_script_resource /* could be deprecated global callback */, - NULL, - deprecated_websocket_connect_wrapper, - deprecated_websocket_ready_wrapper, - deprecated_websocket_data_wrapper, - NULL, - conn->phys_ctx->callbacks); -#else mg_send_http_error(conn, 404, "%s", "Not found"); -#endif } return; } else @@ -14429,12 +14722,14 @@ handle_request(struct mg_connection *conn) /* 10. Request is handled by a script */ if (is_script_resource) { + HTTP1_only; handle_file_based_request(conn, path, &file); return; } /* 11. Handle put/delete/mkcol requests */ if (is_put_or_delete_request) { + HTTP1_only; /* 11.1. PUT method */ if (!strcmp(ri->request_method, "PUT")) { put_file(conn, path); @@ -14470,19 +14765,17 @@ handle_request(struct mg_connection *conn) /* 12. Directory uris should end with a slash */ if (file.stat.is_directory && (uri_len > 0) && (ri->local_uri[uri_len - 1] != '/')) { - gmt_time_string(date, sizeof(date), &curtime); - mg_printf(conn, - "HTTP/1.1 301 Moved Permanently\r\n" - "Location: %s/\r\n" - "Date: %s\r\n" - /* "Cache-Control: private\r\n" (= default) */ - "Content-Length: 0\r\n" - "Connection: %s\r\n", - ri->request_uri, - date, - suggest_connection_header(conn)); - send_additional_header(conn); - mg_printf(conn, "\r\n"); + + size_t len = strlen(ri->request_uri); + char *new_path = mg_malloc_ctx(len + 2, conn->phys_ctx); + if (!new_path) { + mg_send_http_error(conn, 500, "out or memory"); + } else { + memcpy(new_path, ri->request_uri, len); + new_path[len] = '/'; + new_path[len + 1] = 0; + mg_send_http_redirect(conn, new_path, 301); + } return; } @@ -14529,8 +14822,25 @@ handle_request(struct mg_connection *conn) return; } - /* 15. read a normal file with GET or HEAD */ - handle_file_based_request(conn, path, &file); + /* 15. Files with search/replace patterns: LSP and SSI */ + if (is_template_text_file) { + HTTP1_only; + handle_file_based_request(conn, path, &file); + return; + } + + /* 16. Static file - maybe cached */ +#if !defined(NO_CACHING) + if ((!conn->in_error_handler) && is_not_modified(conn, &file.stat)) { + /* Send 304 "Not Modified" - this must not send any body data */ + handle_not_modified_static_file_request(conn, &file); + return; + } +#endif /* !NO_CACHING */ + + /* 17. Static file - not cached */ + handle_static_file_request(conn, path, &file, NULL, NULL); + #endif /* !defined(NO_FILES) */ } @@ -14547,26 +14857,20 @@ handle_file_based_request(struct mg_connection *conn, if (0) { #if defined(USE_LUA) - } else if (match_prefix( - conn->dom_ctx->config[LUA_SERVER_PAGE_EXTENSIONS], - strlen(conn->dom_ctx->config[LUA_SERVER_PAGE_EXTENSIONS]), - path) + } else if (match_prefix_strlen( + conn->dom_ctx->config[LUA_SERVER_PAGE_EXTENSIONS], path) > 0) { if (is_in_script_path(conn, path)) { /* Lua server page: an SSI like page containing mostly plain - * html - * code - * plus some tags with server generated contents. */ + * html code plus some tags with server generated contents. */ handle_lsp_request(conn, path, file, NULL); } else { /* Script was in an illegal path */ mg_send_http_error(conn, 403, "%s", "Forbidden"); } - } else if (match_prefix(conn->dom_ctx->config[LUA_SCRIPT_EXTENSIONS], - strlen( - conn->dom_ctx->config[LUA_SCRIPT_EXTENSIONS]), - path) + } else if (match_prefix_strlen(conn->dom_ctx->config[LUA_SCRIPT_EXTENSIONS], + path) > 0) { if (is_in_script_path(conn, path)) { /* Lua in-server module script: a CGI like script used to @@ -14580,10 +14884,8 @@ handle_file_based_request(struct mg_connection *conn, } #endif #if defined(USE_DUKTAPE) - } else if (match_prefix( - conn->dom_ctx->config[DUKTAPE_SCRIPT_EXTENSIONS], - strlen(conn->dom_ctx->config[DUKTAPE_SCRIPT_EXTENSIONS]), - path) + } else if (match_prefix_strlen( + conn->dom_ctx->config[DUKTAPE_SCRIPT_EXTENSIONS], path) > 0) { if (is_in_script_path(conn, path)) { /* Call duktape to generate the page */ @@ -14594,9 +14896,7 @@ handle_file_based_request(struct mg_connection *conn, } #endif #if !defined(NO_CGI) - } else if (match_prefix(conn->dom_ctx->config[CGI_EXTENSIONS], - strlen(conn->dom_ctx->config[CGI_EXTENSIONS]), - path) + } else if (match_prefix_strlen(conn->dom_ctx->config[CGI_EXTENSIONS], path) > 0) { if (is_in_script_path(conn, path)) { /* CGI scripts may support all HTTP methods */ @@ -14606,9 +14906,7 @@ handle_file_based_request(struct mg_connection *conn, mg_send_http_error(conn, 403, "%s", "Forbidden"); } #endif /* !NO_CGI */ - } else if (match_prefix(conn->dom_ctx->config[SSI_EXTENSIONS], - strlen(conn->dom_ctx->config[SSI_EXTENSIONS]), - path) + } else if (match_prefix_strlen(conn->dom_ctx->config[SSI_EXTENSIONS], path) > 0) { if (is_in_script_path(conn, path)) { handle_ssi_file_request(conn, path, file); @@ -14665,7 +14963,9 @@ close_all_listening_sockets(struct mg_context *ctx) static int parse_port_string(const struct vec *vec, struct socket *so, int *ip_version) { - unsigned int a, b, c, d, port; + unsigned int a, b, c, d; + unsigned port; + unsigned long portUL; int ch, len; const char *cb; char *endptr; @@ -14680,7 +14980,7 @@ parse_port_string(const struct vec *vec, struct socket *so, int *ip_version) so->lsa.sin.sin_family = AF_INET; *ip_version = 0; - /* Initialize port and len as invalid. */ + /* Initialize len as invalid. */ port = 0; len = 0; @@ -14695,8 +14995,9 @@ parse_port_string(const struct vec *vec, struct socket *so, int *ip_version) #if defined(USE_IPV6) } else if (sscanf(vec->ptr, "[%49[^]]]:%u%n", buf, &port, &len) == 2 + && ((size_t)len <= vec->len) && mg_inet_pton( - AF_INET6, buf, &so->lsa.sin6, sizeof(so->lsa.sin6))) { + AF_INET6, buf, &so->lsa.sin6, sizeof(so->lsa.sin6), 0)) { /* IPv6 address, examples: see above */ /* so->lsa.sin6.sin6_family = AF_INET6; already set by mg_inet_pton */ @@ -14722,9 +15023,10 @@ parse_port_string(const struct vec *vec, struct socket *so, int *ip_version) *ip_version = 4; #endif - } else if (is_valid_port(port = strtoul(vec->ptr, &endptr, 0)) - && vec->ptr != endptr) { - len = endptr - vec->ptr; + } else if (is_valid_port(portUL = strtoul(vec->ptr, &endptr, 0)) + && (vec->ptr != endptr)) { + len = (int)(endptr - vec->ptr); + port = (uint16_t)portUL; /* If only port is specified, bind to IPv4, INADDR_ANY */ so->lsa.sin.sin_port = htons((uint16_t)port); *ip_version = 4; @@ -14742,62 +15044,57 @@ parse_port_string(const struct vec *vec, struct socket *so, int *ip_version) char hostname[256]; size_t hostnlen = (size_t)(cb - vec->ptr); - if (hostnlen >= sizeof(hostname)) { + if ((hostnlen >= vec->len) || (hostnlen >= sizeof(hostname))) { /* This would be invalid in any case */ *ip_version = 0; return 0; } - memcpy(hostname, vec->ptr, hostnlen); - hostname[hostnlen] = 0; + mg_strlcpy(hostname, vec->ptr, hostnlen + 1); if (mg_inet_pton( - AF_INET, hostname, &so->lsa.sin, sizeof(so->lsa.sin))) { + AF_INET, hostname, &so->lsa.sin, sizeof(so->lsa.sin), 1)) { if (sscanf(cb + 1, "%u%n", &port, &len) == 1) { *ip_version = 4; - so->lsa.sin.sin_family = AF_INET; so->lsa.sin.sin_port = htons((uint16_t)port); len += (int)(hostnlen + 1); } else { - port = 0; len = 0; } #if defined(USE_IPV6) } else if (mg_inet_pton(AF_INET6, hostname, &so->lsa.sin6, - sizeof(so->lsa.sin6))) { + sizeof(so->lsa.sin6), + 1)) { if (sscanf(cb + 1, "%u%n", &port, &len) == 1) { *ip_version = 6; - so->lsa.sin6.sin6_family = AF_INET6; - so->lsa.sin.sin_port = htons((uint16_t)port); + so->lsa.sin6.sin6_port = htons((uint16_t)port); len += (int)(hostnlen + 1); } else { - port = 0; len = 0; } #endif + } else { + len = 0; } - } else { /* Parsing failure. */ + len = 0; } /* sscanf and the option splitting code ensure the following condition - */ - if ((len < 0) && ((unsigned)len > (unsigned)vec->len)) { - *ip_version = 0; - return 0; - } - ch = vec->ptr[len]; /* Next character after the port number */ - so->is_ssl = (ch == 's'); - so->ssl_redir = (ch == 'r'); - - /* Make sure the port is valid and vector ends with 's', 'r' or ',' */ - if (is_valid_port(port) - && ((ch == '\0') || (ch == 's') || (ch == 'r') || (ch == ','))) { - return 1; + * Make sure the port is valid and vector ends with the port, 's' or 'r' */ + if ((len > 0) && is_valid_port(port) + && (((size_t)len == vec->len) || ((size_t)(len + 1) == vec->len))) { + /* Next character after the port number */ + ch = ((size_t)len < vec->len) ? vec->ptr[len] : '\0'; + so->is_ssl = (ch == 's'); + so->ssl_redir = (ch == 'r'); + if ((ch == '\0') || (ch == 's') || (ch == 'r')) { + return 1; + } } /* Reset ip_version to 0 if there is an error */ @@ -14839,8 +15136,8 @@ is_ssl_port_used(const char *ports) * reading it as a list element for element and parsing with an * algorithm equivalent to parse_port_string. * - * In fact, we use local interface names here, not arbitrary hostnames, - * so in most cases the only name will be "localhost". + * In fact, we use local interface names here, not arbitrary + * hostnames, so in most cases the only name will be "localhost". * * So, for now, we use this simple algorithm, that may still return * a false positive in bizarre cases. @@ -14981,10 +15278,10 @@ set_ports_option(struct mg_context *phys_ctx) != 0) { /* Set IPv6 only option, but don't abort on errors. */ - mg_cry_ctx_internal( - phys_ctx, - "cannot set socket option IPV6_V6ONLY=off (entry %i)", - portsTotal); + mg_cry_ctx_internal(phys_ctx, + "cannot set socket option " + "IPV6_V6ONLY=off (entry %i)", + portsTotal); } } else { if (so.lsa.sa.sa_family == AF_INET6 @@ -14996,10 +15293,10 @@ set_ports_option(struct mg_context *phys_ctx) != 0) { /* Set IPv6 only option, but don't abort on errors. */ - mg_cry_ctx_internal( - phys_ctx, - "cannot set socket option IPV6_V6ONLY=on (entry %i)", - portsTotal); + mg_cry_ctx_internal(phys_ctx, + "cannot set socket option " + "IPV6_V6ONLY=on (entry %i)", + portsTotal); } } #else @@ -15059,6 +15356,8 @@ set_ports_option(struct mg_context *phys_ctx) "%s value \"%s\" is invalid", config_options[LISTEN_BACKLOG_SIZE].name, opt_txt); + closesocket(so.sock); + so.sock = INVALID_SOCKET; continue; } @@ -15263,10 +15562,9 @@ log_access(const struct mg_connection *conn) * Return -1 if ACL is malformed, 0 if address is disallowed, 1 if allowed. */ static int -check_acl(struct mg_context *phys_ctx, uint32_t remote_ip) +check_acl(struct mg_context *phys_ctx, const union usa *sa) { - int allowed, flag; - uint32_t net, mask; + int allowed, flag, matched; struct vec vec; if (phys_ctx) { @@ -15277,15 +15575,19 @@ check_acl(struct mg_context *phys_ctx, uint32_t remote_ip) while ((list = next_option(list, &vec, NULL)) != NULL) { flag = vec.ptr[0]; - if ((flag != '+' && flag != '-') - || (parse_net(&vec.ptr[1], &net, &mask) == 0)) { + matched = -1; + if ((vec.len > 0) && ((flag == '+') || (flag == '-'))) { + vec.ptr++; + vec.len--; + matched = parse_match_net(&vec, sa, 1); + } + if (matched < 0) { mg_cry_ctx_internal(phys_ctx, - "%s: subnet must be [+|-]x.x.x.x[/x]", + "%s: subnet must be [+|-]IP-addr[/x]", __func__); return -1; } - - if (net == (remote_ip & mask)) { + if (matched) { allowed = flag; } } @@ -15379,12 +15681,8 @@ static const char *ssl_error(void); static int refresh_trust(struct mg_connection *conn) { - static int reload_lock = 0; - static long int data_check = 0; - volatile int *p_reload_lock = (volatile int *)&reload_lock; - struct stat cert_buf; - long int t; + int64_t t = 0; const char *pem; const char *chain; int should_verify_peer; @@ -15403,13 +15701,13 @@ refresh_trust(struct mg_connection *conn) chain = NULL; } - t = data_check; if (stat(pem, &cert_buf) != -1) { - t = (long int)cert_buf.st_mtime; + t = (int64_t)cert_buf.st_mtime; } - if (data_check != t) { - data_check = t; + mg_lock_context(conn->phys_ctx); + if ((t != 0) && (conn->dom_ctx->ssl_cert_last_mtime != t)) { + conn->dom_ctx->ssl_cert_last_mtime = t; should_verify_peer = 0; if (conn->dom_ctx->config[SSL_DO_VERIFY_PEER] != NULL) { @@ -15430,6 +15728,7 @@ refresh_trust(struct mg_connection *conn) ca_file, ca_path) != 1) { + mg_unlock_context(conn->phys_ctx); mg_cry_ctx_internal( conn->phys_ctx, "SSL_CTX_load_verify_locations error: %s " @@ -15442,18 +15741,12 @@ refresh_trust(struct mg_connection *conn) } } - if (1 == mg_atomic_inc(p_reload_lock)) { - if (ssl_use_pem_file(conn->phys_ctx, conn->dom_ctx, pem, chain) - == 0) { - return 0; - } - *p_reload_lock = 0; + if (ssl_use_pem_file(conn->phys_ctx, conn->dom_ctx, pem, chain) == 0) { + mg_unlock_context(conn->phys_ctx); + return 0; } } - /* lock while cert is reloading */ - while (*p_reload_lock) { - sleep(1); - } + mg_unlock_context(conn->phys_ctx); return 1; } @@ -15465,9 +15758,7 @@ static pthread_mutex_t *ssl_mutexes; static int sslize(struct mg_connection *conn, - SSL_CTX *s, int (*func)(SSL *), - volatile int *stop_server, const struct mg_client_options *client_options) { int ret, err; @@ -15490,16 +15781,19 @@ sslize(struct mg_connection *conn, } } - conn->ssl = SSL_new(s); + mg_lock_context(conn->phys_ctx); + conn->ssl = SSL_new(conn->dom_ctx->ssl_ctx); + mg_unlock_context(conn->phys_ctx); if (conn->ssl == NULL) { + mg_cry_internal(conn, "sslize error: %s", ssl_error()); + OPENSSL_REMOVE_THREAD_STATE(); return 0; } SSL_set_app_data(conn->ssl, (char *)conn); ret = SSL_set_fd(conn->ssl, conn->client.sock); if (ret != 1) { - err = SSL_get_error(conn->ssl, ret); - mg_cry_internal(conn, "SSL error %i, destroying SSL context", err); + mg_cry_internal(conn, "sslize error: %s", ssl_error()); SSL_free(conn->ssl); conn->ssl = NULL; OPENSSL_REMOVE_THREAD_STATE(); @@ -15516,13 +15810,18 @@ sslize(struct mg_connection *conn, if (conn->dom_ctx->config[REQUEST_TIMEOUT]) { /* NOTE: The loop below acts as a back-off, so we can end * up sleeping for more (or less) than the REQUEST_TIMEOUT. */ - timeout = atoi(conn->dom_ctx->config[REQUEST_TIMEOUT]); + int to = atoi(conn->dom_ctx->config[REQUEST_TIMEOUT]); + if (to >= 0) { + timeout = (unsigned)to; + } } /* SSL functions may fail and require to be called again: * see https://www.openssl.org/docs/manmaster/ssl/SSL_get_error.html * Here "func" could be SSL_connect or SSL_accept. */ for (i = 0; i <= timeout; i += 50) { + ERR_clear_error(); + /* conn->dom_ctx may be changed here (see ssl_servername_callback) */ ret = func(conn->ssl); if (ret != 1) { err = SSL_get_error(conn->ssl, ret); @@ -15530,7 +15829,7 @@ sslize(struct mg_connection *conn, || (err == SSL_ERROR_WANT_ACCEPT) || (err == SSL_ERROR_WANT_READ) || (err == SSL_ERROR_WANT_WRITE) || (err == SSL_ERROR_WANT_X509_LOOKUP)) { - if (*stop_server) { + if (!STOP_FLAG_IS_ZERO(&conn->phys_ctx->stop_flag)) { /* Don't wait if the server is going to be stopped. */ break; } @@ -15548,7 +15847,8 @@ sslize(struct mg_connection *conn, || (err == SSL_ERROR_WANT_WRITE)) ? POLLOUT : POLLIN; - pollres = mg_poll(&pfd, 1, 50, stop_server); + pollres = + mg_poll(&pfd, 1, 50, &(conn->phys_ctx->stop_flag)); if (pollres < 0) { /* Break if error occured (-1) * or server shutdown (-2) */ @@ -15558,8 +15858,7 @@ sslize(struct mg_connection *conn, } else if (err == SSL_ERROR_SYSCALL) { /* This is an IO error. Look at errno. */ - err = errno; - mg_cry_internal(conn, "SSL syscall error %i", err); + mg_cry_internal(conn, "SSL syscall error %i", ERRNO); break; } else { @@ -15567,13 +15866,13 @@ sslize(struct mg_connection *conn, mg_cry_internal(conn, "sslize error: %s", ssl_error()); break; } - ERR_clear_error(); } else { /* success */ break; } } + ERR_clear_error(); if (ret != 1) { SSL_free(conn->ssl); @@ -15622,14 +15921,13 @@ hexdump2string(void *mem, int memlen, char *buf, int buflen) } -static void -ssl_get_client_cert_info(struct mg_connection *conn) +static int +ssl_get_client_cert_info(const struct mg_connection *conn, + struct mg_client_cert *client_cert) { X509 *cert = SSL_get_peer_certificate(conn->ssl); if (cert) { - char str_subject[1024]; - char str_issuer[1024]; - char str_finger[1024]; + char str_buf[1024]; unsigned char buf[256]; char *str_serial = NULL; unsigned int ulen; @@ -15649,12 +15947,18 @@ ssl_get_client_cert_info(struct mg_connection *conn) /* Translate serial number to a hex string */ BIGNUM *serial_bn = ASN1_INTEGER_to_BN(serial, NULL); - str_serial = BN_bn2hex(serial_bn); - BN_free(serial_bn); + if (serial_bn) { + str_serial = BN_bn2hex(serial_bn); + BN_free(serial_bn); + } + client_cert->serial = + str_serial ? mg_strdup_ctx(str_serial, conn->phys_ctx) : NULL; /* Translate subject and issuer to a string */ - (void)X509_NAME_oneline(subj, str_subject, (int)sizeof(str_subject)); - (void)X509_NAME_oneline(iss, str_issuer, (int)sizeof(str_issuer)); + (void)X509_NAME_oneline(subj, str_buf, (int)sizeof(str_buf)); + client_cert->subject = mg_strdup_ctx(str_buf, conn->phys_ctx); + (void)X509_NAME_oneline(iss, str_buf, (int)sizeof(str_buf)); + client_cert->issuer = mg_strdup_ctx(str_buf, conn->phys_ctx); /* Calculate SHA1 fingerprint and store as a hex string */ ulen = 0; @@ -15676,34 +15980,19 @@ ssl_get_client_cert_info(struct mg_connection *conn) mg_free(tmp_buf); } - if (!hexdump2string( - buf, (int)ulen, str_finger, (int)sizeof(str_finger))) { - *str_finger = 0; + if (!hexdump2string(buf, (int)ulen, str_buf, (int)sizeof(str_buf))) { + *str_buf = 0; } + client_cert->finger = mg_strdup_ctx(str_buf, conn->phys_ctx); - conn->request_info.client_cert = (struct mg_client_cert *) - mg_malloc_ctx(sizeof(struct mg_client_cert), conn->phys_ctx); - if (conn->request_info.client_cert) { - conn->request_info.client_cert->peer_cert = (void *)cert; - conn->request_info.client_cert->subject = - mg_strdup_ctx(str_subject, conn->phys_ctx); - conn->request_info.client_cert->issuer = - mg_strdup_ctx(str_issuer, conn->phys_ctx); - conn->request_info.client_cert->serial = - mg_strdup_ctx(str_serial, conn->phys_ctx); - conn->request_info.client_cert->finger = - mg_strdup_ctx(str_finger, conn->phys_ctx); - } else { - mg_cry_internal(conn, - "%s", - "Out of memory: Cannot allocate memory for client " - "certificate"); - } + client_cert->peer_cert = (void *)cert; /* Strings returned from bn_bn2hex must be freed using OPENSSL_free, * see https://linux.die.net/man/3/bn_bn2hex */ OPENSSL_free(str_serial); + return 1; } + return 0; } @@ -15726,8 +16015,13 @@ ssl_locking_callback(int mode, int mutex_num, const char *file, int line) #if !defined(NO_SSL_DL) +/* Load a DLL/Shared Object with a TLS/SSL implementation. */ static void * -load_dll(char *ebuf, size_t ebuf_len, const char *dll_name, struct ssl_func *sw) +load_tls_dll(char *ebuf, + size_t ebuf_len, + const char *dll_name, + struct ssl_func *sw, + int *feature_missing) { union { void *p; @@ -15760,36 +16054,47 @@ load_dll(char *ebuf, size_t ebuf_len, const char *dll_name, struct ssl_func *sw) * cast. */ u.p = dlsym(dll_handle, fp->name); #endif /* _WIN32 */ + + /* Set pointer (might be NULL) */ + fp->ptr = u.fp; + if (u.fp == NULL) { - if (ok) { - mg_snprintf(NULL, - &truncated, - ebuf, - ebuf_len, - "%s: %s: cannot find %s", - __func__, - dll_name, - fp->name); - ok = 0; - } else { - size_t cur_len = strlen(ebuf); - if (!truncated) { + DEBUG_TRACE("Missing function: %s\n", fp->name); + if (feature_missing) { + feature_missing[fp->required]++; + } + if (fp->required == TLS_Mandatory) { + /* Mandatory function is missing */ + if (ok) { + /* This is the first missing function. + * Create a new error message. */ mg_snprintf(NULL, &truncated, - ebuf + cur_len, - ebuf_len - cur_len - 3, - ", %s", + ebuf, + ebuf_len, + "%s: %s: cannot find %s", + __func__, + dll_name, fp->name); - if (truncated) { - /* If truncated, add "..." */ - strcat(ebuf, "..."); + ok = 0; + } else { + /* This is yet anothermissing function. + * Append existing error message. */ + size_t cur_len = strlen(ebuf); + if (!truncated && ((ebuf_len - cur_len) > 3)) { + mg_snprintf(NULL, + &truncated, + ebuf + cur_len, + ebuf_len - cur_len - 3, + ", %s", + fp->name); + if (truncated) { + /* If truncated, add "..." */ + strcat(ebuf, "..."); + } } } } - /* Debug: - * printf("Missing function: %s\n", fp->name); */ - } else { - fp->ptr = u.fp; } } @@ -15809,44 +16114,21 @@ static void *cryptolib_dll_handle; /* Store the crypto library handle. */ #if defined(SSL_ALREADY_INITIALIZED) -static int cryptolib_users = 1; /* Reference counter for crypto library. */ +static volatile ptrdiff_t cryptolib_users = + 1; /* Reference counter for crypto library. */ #else -static int cryptolib_users = 0; /* Reference counter for crypto library. */ +static volatile ptrdiff_t cryptolib_users = + 0; /* Reference counter for crypto library. */ #endif static int initialize_ssl(char *ebuf, size_t ebuf_len) { -#if defined(OPENSSL_API_1_1) - if (ebuf_len > 0) { - ebuf[0] = 0; - } - -#if !defined(NO_SSL_DL) - if (!cryptolib_dll_handle) { - cryptolib_dll_handle = load_dll(ebuf, ebuf_len, CRYPTO_LIB, crypto_sw); - if (!cryptolib_dll_handle) { - mg_snprintf(NULL, - NULL, /* No truncation check for ebuf */ - ebuf, - ebuf_len, - "%s: error loading library %s", - __func__, - CRYPTO_LIB); - DEBUG_TRACE("%s", ebuf); - return 0; - } - } -#endif /* NO_SSL_DL */ - - if (mg_atomic_inc(&cryptolib_users) > 1) { - return 1; - } - -#else /* not OPENSSL_API_1_1 */ +#if !defined(OPENSSL_API_1_1) int i, num_locks; size_t size; +#endif if (ebuf_len > 0) { ebuf[0] = 0; @@ -15854,7 +16136,9 @@ initialize_ssl(char *ebuf, size_t ebuf_len) #if !defined(NO_SSL_DL) if (!cryptolib_dll_handle) { - cryptolib_dll_handle = load_dll(ebuf, ebuf_len, CRYPTO_LIB, crypto_sw); + memset(tls_feature_missing, 0, sizeof(tls_feature_missing)); + cryptolib_dll_handle = load_tls_dll( + ebuf, ebuf_len, CRYPTO_LIB, crypto_sw, tls_feature_missing); if (!cryptolib_dll_handle) { mg_snprintf(NULL, NULL, /* No truncation check for ebuf */ @@ -15873,6 +16157,7 @@ initialize_ssl(char *ebuf, size_t ebuf_len) return 1; } +#if !defined(OPENSSL_API_1_1) /* Initialize locking callbacks, needed for thread safety. * http://www.openssl.org/support/faq.html#PROG1 */ @@ -15927,7 +16212,8 @@ initialize_ssl(char *ebuf, size_t ebuf_len) #if !defined(NO_SSL_DL) if (!ssllib_dll_handle) { - ssllib_dll_handle = load_dll(ebuf, ebuf_len, SSL_LIB, ssl_sw); + ssllib_dll_handle = + load_tls_dll(ebuf, ebuf_len, SSL_LIB, ssl_sw, tls_feature_missing); if (!ssllib_dll_handle) { #if !defined(OPENSSL_API_1_1) mg_free(ssl_mutexes); @@ -16034,7 +16320,7 @@ ssl_get_protocol(int version_id) static long ssl_get_protocol(int version_id) { - long ret = (long)SSL_OP_ALL; + unsigned long ret = (unsigned long)SSL_OP_ALL; if (version_id > 0) ret |= SSL_OP_NO_SSLv2; if (version_id > 1) @@ -16049,7 +16335,7 @@ ssl_get_protocol(int version_id) if (version_id > 5) ret |= SSL_OP_NO_TLSv1_3; #endif - return ret; + return (long)ret; } #endif /* OPENSSL_API_1_1 */ @@ -16059,11 +16345,11 @@ ssl_get_protocol(int version_id) * https://wiki.openssl.org/index.php/Manual:SSL_CTX_set_info_callback(3) * https://linux.die.net/man/3/ssl_set_info_callback */ /* Note: There is no "const" for the first argument in the documentation - * examples, however some (maybe most, but not all) headers of OpenSSL versions - * / OpenSSL compatibility layers have it. Having a different definition will - * cause a warning in C and an error in C++. Use "const SSL *", while - * automatical conversion from "SSL *" works for all compilers, but not other - * way around */ + * examples, however some (maybe most, but not all) headers of OpenSSL + * versions / OpenSSL compatibility layers have it. Having a different + * definition will cause a warning in C and an error in C++. Use "const SSL + * *", while automatical conversion from "SSL *" works for all compilers, + * but not other way around */ static void ssl_info_callback(const SSL *ssl, int what, int ret) { @@ -16083,10 +16369,6 @@ ssl_info_callback(const SSL *ssl, int what, int ret) static int ssl_servername_callback(SSL *ssl, int *ad, void *arg) { - struct mg_context *ctx = (struct mg_context *)arg; - struct mg_domain_context *dom = - (struct mg_domain_context *)ctx ? &(ctx->dd) : NULL; - #if defined(GCC_DIAGNOSTIC) #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wcast-align" @@ -16099,52 +16381,163 @@ ssl_servername_callback(SSL *ssl, int *ad, void *arg) #pragma GCC diagnostic pop #endif /* defined(GCC_DIAGNOSTIC) */ - const char *servername = SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name); + const char *servername = SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name); + + (void)ad; + (void)arg; + + if ((conn == NULL) || (conn->phys_ctx == NULL)) { + DEBUG_ASSERT(0); + return SSL_TLSEXT_ERR_NOACK; + } + conn->dom_ctx = &(conn->phys_ctx->dd); + + /* Old clients (Win XP) will not support SNI. Then, there + * is no server name available in the request - we can + * only work with the default certificate. + * Multiple HTTPS hosts on one IP+port are only possible + * with a certificate containing all alternative names. + */ + if ((servername == NULL) || (*servername == 0)) { + DEBUG_TRACE("%s", "SSL connection not supporting SNI"); + mg_lock_context(conn->phys_ctx); + SSL_set_SSL_CTX(ssl, conn->dom_ctx->ssl_ctx); + mg_unlock_context(conn->phys_ctx); + return SSL_TLSEXT_ERR_NOACK; + } + + DEBUG_TRACE("TLS connection to host %s", servername); + + while (conn->dom_ctx) { + if (!mg_strcasecmp(servername, + conn->dom_ctx->config[AUTHENTICATION_DOMAIN])) { + /* Found matching domain */ + DEBUG_TRACE("TLS domain %s found", + conn->dom_ctx->config[AUTHENTICATION_DOMAIN]); + break; + } + mg_lock_context(conn->phys_ctx); + conn->dom_ctx = conn->dom_ctx->next; + mg_unlock_context(conn->phys_ctx); + } + + if (conn->dom_ctx == NULL) { + /* Default domain */ + DEBUG_TRACE("TLS default domain %s used", + conn->phys_ctx->dd.config[AUTHENTICATION_DOMAIN]); + conn->dom_ctx = &(conn->phys_ctx->dd); + } + mg_lock_context(conn->phys_ctx); + SSL_set_SSL_CTX(ssl, conn->dom_ctx->ssl_ctx); + mg_unlock_context(conn->phys_ctx); + return SSL_TLSEXT_ERR_OK; +} + + +#if defined(USE_ALPN) +static const char alpn_proto_list[] = "\x02h2\x08http/1.1\x08http/1.0"; +static const char *alpn_proto_order_http1[] = {alpn_proto_list + 3, + alpn_proto_list + 3 + 8, + NULL}; +#if defined(USE_HTTP2) +static const char *alpn_proto_order_http2[] = {alpn_proto_list, + alpn_proto_list + 3, + alpn_proto_list + 3 + 8, + NULL}; +#endif + +static int +alpn_select_cb(SSL *ssl, + const unsigned char **out, + unsigned char *outlen, + const unsigned char *in, + unsigned int inlen, + void *arg) +{ + struct mg_domain_context *dom_ctx = (struct mg_domain_context *)arg; + unsigned int i, j, enable_http2 = 0; + const char **alpn_proto_order = alpn_proto_order_http1; + + struct mg_workerTLS *tls = + (struct mg_workerTLS *)pthread_getspecific(sTlsKey); - (void)ad; + (void)ssl; - if ((ctx == NULL) || (conn->phys_ctx == ctx)) { - DEBUG_TRACE("%s", "internal error - assertion failed"); + if (tls == NULL) { + /* Need to store protocol in Thread Local Storage */ + /* If there is no Thread Local Storage, don't use ALPN */ return SSL_TLSEXT_ERR_NOACK; } - /* Old clients (Win XP) will not support SNI. Then, there - * is no server name available in the request - we can - * only work with the default certificate. - * Multiple HTTPS hosts on one IP+port are only possible - * with a certificate containing all alternative names. - */ - if ((servername == NULL) || (*servername == 0)) { - DEBUG_TRACE("%s", "SSL connection not supporting SNI"); - conn->dom_ctx = &(ctx->dd); - SSL_set_SSL_CTX(ssl, conn->dom_ctx->ssl_ctx); - return SSL_TLSEXT_ERR_NOACK; +#if defined(USE_HTTP2) + enable_http2 = (0 == strcmp(dom_ctx->config[ENABLE_HTTP2], "yes")); + if (enable_http2) { + alpn_proto_order = alpn_proto_order_http2; } +#endif - DEBUG_TRACE("TLS connection to host %s", servername); - - while (dom) { - if (!mg_strcasecmp(servername, dom->config[AUTHENTICATION_DOMAIN])) { - - /* Found matching domain */ - DEBUG_TRACE("TLS domain %s found", - dom->config[AUTHENTICATION_DOMAIN]); - SSL_set_SSL_CTX(ssl, dom->ssl_ctx); - conn->dom_ctx = dom; - return SSL_TLSEXT_ERR_OK; + for (j = 0; alpn_proto_order[j] != NULL; j++) { + /* check all accepted protocols in this order */ + const char *alpn_proto = alpn_proto_order[j]; + /* search input for matching protocol */ + for (i = 0; i < inlen; i++) { + if (!memcmp(in + i, alpn_proto, (unsigned char)alpn_proto[0])) { + *out = in + i + 1; + *outlen = in[i]; + tls->alpn_proto = alpn_proto; + return SSL_TLSEXT_ERR_OK; + } } - dom = dom->next; } - /* Default domain */ - DEBUG_TRACE("TLS default domain %s used", - ctx->dd.config[AUTHENTICATION_DOMAIN]); - conn->dom_ctx = &(ctx->dd); - SSL_set_SSL_CTX(ssl, conn->dom_ctx->ssl_ctx); + /* Nothing found */ + return SSL_TLSEXT_ERR_NOACK; +} + + +static int +next_protos_advertised_cb(SSL *ssl, + const unsigned char **data, + unsigned int *len, + void *arg) +{ + struct mg_domain_context *dom_ctx = (struct mg_domain_context *)arg; + *data = (const unsigned char *)alpn_proto_list; + *len = (unsigned int)strlen((const char *)data); + + (void)ssl; + (void)dom_ctx; + return SSL_TLSEXT_ERR_OK; } +static int +init_alpn(struct mg_context *phys_ctx, struct mg_domain_context *dom_ctx) +{ + unsigned int alpn_len = (unsigned int)strlen((char *)alpn_proto_list); + int ret = SSL_CTX_set_alpn_protos(dom_ctx->ssl_ctx, + (const unsigned char *)alpn_proto_list, + alpn_len); + if (ret != 0) { + mg_cry_ctx_internal(phys_ctx, + "SSL_CTX_set_alpn_protos error: %s", + ssl_error()); + } + + SSL_CTX_set_alpn_select_cb(dom_ctx->ssl_ctx, + alpn_select_cb, + (void *)dom_ctx); + + SSL_CTX_set_next_protos_advertised_cb(dom_ctx->ssl_ctx, + next_protos_advertised_cb, + (void *)dom_ctx); + + return ret; +} +#endif + + /* Setup SSL CTX as required by CivetWeb */ static int init_ssl_ctx_impl(struct mg_context *phys_ctx, @@ -16163,6 +16556,7 @@ init_ssl_ctx_impl(struct mg_context *phys_ctx, md5_byte_t ssl_context_id[16]; md5_state_t md5state; int protocol_ver; + int ssl_cache_timeout; #if defined(OPENSSL_API_1_1) if ((dom_ctx->ssl_ctx = SSL_CTX_new(TLS_server_method())) == NULL) { @@ -16199,8 +16593,8 @@ init_ssl_ctx_impl(struct mg_context *phys_ctx, SSL_CTX_set_ecdh_auto(dom_ctx->ssl_ctx, 1); #endif /* NO_SSL_DL */ - /* In SSL documentation examples callback defined without const specifier - * 'void (*)(SSL *, int, int)' See: + /* In SSL documentation examples callback defined without const + * specifier 'void (*)(SSL *, int, int)' See: * https://www.openssl.org/docs/man1.0.2/ssl/ssl.html * https://www.openssl.org/docs/man1.1.0/ssl/ssl.html * But in the source code const SSL is used: @@ -16216,7 +16610,6 @@ init_ssl_ctx_impl(struct mg_context *phys_ctx, SSL_CTX_set_tlsext_servername_callback(dom_ctx->ssl_ctx, ssl_servername_callback); - SSL_CTX_set_tlsext_servername_arg(dom_ctx->ssl_ctx, phys_ctx); /* If a callback has been specified, call it. */ callback_ret = (phys_ctx->callbacks.init_ssl == NULL) @@ -16293,7 +16686,6 @@ init_ssl_ctx_impl(struct mg_context *phys_ctx, if (mg_strcasecmp(dom_ctx->config[SSL_DO_VERIFY_PEER], "yes") == 0) { /* Yes, they are mandatory */ should_verify_peer = 1; - peer_certificate_optional = 0; } else if (mg_strcasecmp(dom_ctx->config[SSL_DO_VERIFY_PEER], "optional") == 0) { @@ -16356,6 +16748,27 @@ init_ssl_ctx_impl(struct mg_context *phys_ctx, } } + /* SSL session caching */ + ssl_cache_timeout = ((dom_ctx->config[SSL_CACHE_TIMEOUT] != NULL) + ? atoi(dom_ctx->config[SSL_CACHE_TIMEOUT]) + : 0); + if (ssl_cache_timeout > 0) { + SSL_CTX_set_session_cache_mode(dom_ctx->ssl_ctx, SSL_SESS_CACHE_BOTH); + /* SSL_CTX_sess_set_cache_size(dom_ctx->ssl_ctx, 10000); ... use + * default */ + SSL_CTX_set_timeout(dom_ctx->ssl_ctx, (long)ssl_cache_timeout); + } + +#if defined(USE_ALPN) + /* Initialize ALPN only of TLS library (OpenSSL version) supports ALPN */ +#if !defined(NO_SSL_DL) + if (!tls_feature_missing[TLS_ALPN]) +#endif + { + init_alpn(phys_ctx, dom_ctx); + } +#endif + return 1; } @@ -16393,11 +16806,14 @@ init_ssl_ctx(struct mg_context *phys_ctx, struct mg_domain_context *dom_ctx) phys_ctx->user_data)); if (callback_ret < 0) { + /* Callback exists and returns <0: Initializing failed. */ mg_cry_ctx_internal(phys_ctx, "external_ssl_ctx callback returned error: %i", callback_ret); return 0; } else if (callback_ret > 0) { + /* Callback exists and returns >0: Initializing complete, + * civetweb should not modify the SSL context. */ dom_ctx->ssl_ctx = (SSL_CTX *)ssl_ctx; if (!initialize_ssl(ebuf, sizeof(ebuf))) { mg_cry_ctx_internal(phys_ctx, "%s", ebuf); @@ -16405,8 +16821,10 @@ init_ssl_ctx(struct mg_context *phys_ctx, struct mg_domain_context *dom_ctx) } return 1; } + /* If the callback does not exist or return 0, civetweb must initialize + * the SSL context. Handle "domain" callback next. */ - /* Check for external domain SSL_CTX */ + /* Check for external domain SSL_CTX callback. */ callback_ret = (phys_ctx->callbacks.external_ssl_ctx_domain == NULL) ? 0 : (phys_ctx->callbacks.external_ssl_ctx_domain( @@ -16415,12 +16833,14 @@ init_ssl_ctx(struct mg_context *phys_ctx, struct mg_domain_context *dom_ctx) phys_ctx->user_data)); if (callback_ret < 0) { + /* Callback < 0: Error. Abort init. */ mg_cry_ctx_internal( phys_ctx, "external_ssl_ctx_domain callback returned error: %i", callback_ret); return 0; } else if (callback_ret > 0) { + /* Callback > 0: Consider init done. */ dom_ctx->ssl_ctx = (SSL_CTX *)ssl_ctx; if (!initialize_ssl(ebuf, sizeof(ebuf))) { mg_cry_ctx_internal(phys_ctx, "%s", ebuf); @@ -16428,8 +16848,8 @@ init_ssl_ctx(struct mg_context *phys_ctx, struct mg_domain_context *dom_ctx) } return 1; } - /* else: external_ssl_ctx/external_ssl_ctx_domain do not exist or return 0, - * CivetWeb should continue initializing SSL */ + /* else: external_ssl_ctx/external_ssl_ctx_domain do not exist or return + * 0, CivetWeb should continue initializing SSL */ /* If PEM file is not specified and the init_ssl callbacks * are not specified, setup will fail. */ @@ -16445,11 +16865,14 @@ init_ssl_ctx(struct mg_context *phys_ctx, struct mg_domain_context *dom_ctx) return 0; } + /* If a certificate chain is configured, use it. */ chain = dom_ctx->config[SSL_CERTIFICATE_CHAIN]; if (chain == NULL) { + /* Default: certificate chain in PEM file */ chain = pem; } if ((chain != NULL) && (*chain == 0)) { + /* If the chain is an empty string, don't use it. */ chain = NULL; } @@ -16533,7 +16956,14 @@ set_gpass_option(struct mg_context *phys_ctx, struct mg_domain_context *dom_ctx) static int set_acl_option(struct mg_context *phys_ctx) { - return check_acl(phys_ctx, (uint32_t)0x7f000001UL) != -1; + union usa sa; + memset(&sa, 0, sizeof(sa)); +#if defined(USE_IPV6) + sa.sin6.sin6_family = AF_INET6; +#else + sa.sin.sin_family = AF_INET; +#endif + return check_acl(phys_ctx, &sa) != -1; } @@ -16543,8 +16973,6 @@ reset_per_request_attributes(struct mg_connection *conn) if (!conn) { return; } - conn->connection_type = - CONNECTION_TYPE_INVALID; /* Not yet a valid request/response */ conn->num_bytes_sent = conn->consumed_content = 0; @@ -16554,6 +16982,7 @@ reset_per_request_attributes(struct mg_connection *conn) conn->is_chunked = 0; conn->must_close = 0; conn->request_len = 0; + conn->request_state = 0; conn->throttle = 0; conn->accept_gzip = 0; @@ -16773,11 +17202,6 @@ close_connection(struct mg_connection *conn) conn->client.sock = INVALID_SOCKET; } - if (conn->host) { - mg_free((void *)conn->host); - conn->host = NULL; - } - mg_unlock_connection(conn); #if defined(USE_SERVER_STATS) @@ -16806,7 +17230,7 @@ mg_close_connection(struct mg_connection *conn) unsigned int i; /* client context: loops must end */ - conn->phys_ctx->stop_flag = 1; + STOP_FLAG_ASSIGN(&conn->phys_ctx->stop_flag, 1); conn->must_close = 1; /* We need to get the client thread out of the select/recv call @@ -16837,10 +17261,12 @@ mg_close_connection(struct mg_connection *conn) (void)pthread_mutex_destroy(&conn->mutex); mg_free(conn); } else if (conn->phys_ctx->context_type == CONTEXT_HTTP_CLIENT) { + (void)pthread_mutex_destroy(&conn->mutex); mg_free(conn); } #else if (conn->phys_ctx->context_type == CONTEXT_HTTP_CLIENT) { /* Client */ + (void)pthread_mutex_destroy(&conn->mutex); mg_free(conn); } #endif /* defined(USE_WEBSOCKET) */ @@ -17025,11 +17451,7 @@ mg_connect_client_impl(const struct mg_client_options *client_options, SSL_CTX_set_verify(conn->dom_ctx->ssl_ctx, SSL_VERIFY_NONE, NULL); } - if (!sslize(conn, - conn->dom_ctx->ssl_ctx, - SSL_connect, - &(conn->phys_ctx->stop_flag), - client_options)) { + if (!sslize(conn, SSL_connect, client_options)) { mg_snprintf(NULL, NULL, /* No truncation check for ebuf */ ebuf, @@ -17135,22 +17557,23 @@ mg_connect_client2(const char *host, return NULL; } - /* TODO: The current implementation here just calls the old implementations, - * without using any new options. This is just a first step to test the new - * interfaces. */ + /* TODO: The current implementation here just calls the old + * implementations, without using any new options. This is just a first + * step to test the new interfaces. */ #if defined(USE_WEBSOCKET) if (is_ws) { /* TODO: implement all options */ - return mg_connect_websocket_client(host, - port, - is_ssl, - ((error != NULL) ? error->text : NULL), - ((error != NULL) ? error->text_buffer_size : 0), - (path ? path : ""), - NULL /* TODO: origin */, - deprecated_websocket_data_wrapper, - deprecated_websocket_close_wrapper, - (void *)init->callbacks); + return mg_connect_websocket_client( + host, + port, + is_ssl, + ((error != NULL) ? error->text : NULL), + ((error != NULL) ? error->text_buffer_size : 0), + (path ? path : ""), + NULL /* TODO: origin */, + experimental_websocket_client_data_wrapper, + experimental_websocket_client_close_wrapper, + (void *)init->callbacks); } #endif @@ -17162,7 +17585,8 @@ mg_connect_client2(const char *host, return mg_connect_client_impl(&opts, is_ssl, ((error != NULL) ? error->text : NULL), - ((error != NULL) ? error->text_buffer_size : 0)); + ((error != NULL) ? error->text_buffer_size + : 0)); } #endif @@ -17212,26 +17636,7 @@ get_uri_type(const char *uri) /* control characters and spaces are invalid */ return 0; } - if (uri[i] > 126) { - /* non-ascii characters must be % encoded */ - return 0; - } else { - switch (uri[i]) { - case '"': /* 34 */ - case '<': /* 60 */ - case '>': /* 62 */ - case '\\': /* 92 */ - case '^': /* 94 */ - case '`': /* 96 */ - case '{': /* 123 */ - case '|': /* 124 */ - case '}': /* 125 */ - return 0; - default: - /* character is ok */ - break; - } - } + /* Allow everything else here (See #894) */ } /* A relative uri starts with a / character */ @@ -17324,21 +17729,11 @@ get_rel_url_at_current_server(const char *uri, const struct mg_connection *conn) return 0; } -/* Check if the request is directed to a different server. */ -/* First check if the port is the same (IPv4 and IPv6). */ -#if defined(USE_IPV6) - if (conn->client.lsa.sa.sa_family == AF_INET6) { - if (ntohs(conn->client.lsa.sin6.sin6_port) != port) { - /* Request is directed to a different port */ - return 0; - } - } else -#endif - { - if (ntohs(conn->client.lsa.sin.sin_port) != port) { - /* Request is directed to a different port */ - return 0; - } + /* Check if the request is directed to a different server. */ + /* First check if the port is the same. */ + if (ntohs(USA_IN_PORT_UNSAFE(&conn->client.lsa)) != port) { + /* Request is directed to a different port */ + return 0; } /* Finally check if the server corresponds to the authentication @@ -17466,6 +17861,10 @@ static int get_request(struct mg_connection *conn, char *ebuf, size_t ebuf_len, int *err) { const char *cl; + + conn->connection_type = + CONNECTION_TYPE_REQUEST; /* request (valid of not) */ + if (!get_message(conn, ebuf, ebuf_len, err)) { return 0; } @@ -17484,12 +17883,7 @@ get_request(struct mg_connection *conn, char *ebuf, size_t ebuf_len, int *err) /* Message is a valid request */ - /* Is there a "host" ? */ - if (conn->host != NULL) { - mg_free((void *)conn->host); - } - conn->host = alloc_get_host(conn); - if (!conn->host) { + if (!switch_domain_context(conn)) { mg_snprintf(conn, NULL, /* No truncation check for ebuf */ ebuf, @@ -17541,7 +17935,6 @@ get_request(struct mg_connection *conn, char *ebuf, size_t ebuf_len, int *err) conn->content_len = 0; } - conn->connection_type = CONNECTION_TYPE_REQUEST; /* Valid request */ return 1; } @@ -17551,6 +17944,10 @@ static int get_response(struct mg_connection *conn, char *ebuf, size_t ebuf_len, int *err) { const char *cl; + + conn->connection_type = + CONNECTION_TYPE_RESPONSE; /* response (valid or not) */ + if (!get_message(conn, ebuf, ebuf_len, err)) { return 0; } @@ -17624,7 +18021,6 @@ get_response(struct mg_connection *conn, char *ebuf, size_t ebuf_len, int *err) } } - conn->connection_type = CONNECTION_TYPE_RESPONSE; /* Valid response */ return 1; } @@ -17792,7 +18188,7 @@ websocket_client_thread(void *data) /* The websocket_client context has only this thread. If it runs out, set the stop_flag to 2 (= "stopped"). */ - cdata->conn->phys_ctx->stop_flag = 2; + STOP_FLAG_ASSIGN(&cdata->conn->phys_ctx->stop_flag, 2); if (cdata->conn->phys_ctx->callbacks.exit_thread) { cdata->conn->phys_ctx->callbacks.exit_thread(cdata->conn->phys_ctx, @@ -17811,24 +18207,26 @@ websocket_client_thread(void *data) #endif -struct mg_connection * -mg_connect_websocket_client(const char *host, - int port, - int use_ssl, - char *error_buffer, - size_t error_buffer_size, - const char *path, - const char *origin, - mg_websocket_data_handler data_func, - mg_websocket_close_handler close_func, - void *user_data) +static struct mg_connection * +mg_connect_websocket_client_impl(const struct mg_client_options *client_options, + int use_ssl, + char *error_buffer, + size_t error_buffer_size, + const char *path, + const char *origin, + mg_websocket_data_handler data_func, + mg_websocket_close_handler close_func, + void *user_data) { struct mg_connection *conn = NULL; #if defined(USE_WEBSOCKET) struct websocket_client_thread_data *thread_data; static const char *magic = "x3JJHMbDL1EzLkh9GBhXDw=="; - static const char *handshake_req; + const char *handshake_req; + + const char *host = client_options->host; + int i; if (origin != NULL) { handshake_req = "GET %s HTTP/1.1\r\n" @@ -17855,25 +18253,16 @@ mg_connect_websocket_client(const char *host, #endif /* Establish the client connection and request upgrade */ - conn = mg_download(host, - port, - use_ssl, - error_buffer, - error_buffer_size, - handshake_req, - path, - host, - magic, - origin); - -#if defined(__clang__) -#pragma clang diagnostic pop -#endif + conn = mg_connect_client_impl(client_options, + use_ssl, + error_buffer, + error_buffer_size); /* Connection object will be null if something goes wrong */ if (conn == NULL) { - if (!*error_buffer) { - /* There should be already an error message */ + /* error_buffer should be already filled ... */ + if (!error_buffer[0]) { + /* ... if not add an error message */ mg_snprintf(conn, NULL, /* No truncation check for ebuf */ error_buffer, @@ -17883,6 +18272,29 @@ mg_connect_websocket_client(const char *host, return NULL; } + i = mg_printf(conn, handshake_req, path, host, magic, origin); + if (i <= 0) { + mg_snprintf(conn, + NULL, /* No truncation check for ebuf */ + error_buffer, + error_buffer_size, + "%s", + "Error sending request"); + mg_close_connection(conn); + return NULL; + } + + conn->data_len = 0; + if (!get_response(conn, error_buffer, error_buffer_size, &i)) { + mg_close_connection(conn); + return NULL; + } + conn->request_info.local_uri = conn->request_info.request_uri; + +#if defined(__clang__) +#pragma clang diagnostic pop +#endif + if (conn->response_info.status_code != 101) { /* We sent an "upgrade" request. For a correct websocket * protocol handshake, we expect a "101 Continue" response. @@ -17946,8 +18358,7 @@ mg_connect_websocket_client(const char *host, #else /* Appease "unused parameter" warnings */ - (void)host; - (void)port; + (void)client_options; (void)use_ssl; (void)error_buffer; (void)error_buffer_size; @@ -17962,6 +18373,61 @@ mg_connect_websocket_client(const char *host, } +struct mg_connection * +mg_connect_websocket_client(const char *host, + int port, + int use_ssl, + char *error_buffer, + size_t error_buffer_size, + const char *path, + const char *origin, + mg_websocket_data_handler data_func, + mg_websocket_close_handler close_func, + void *user_data) +{ + struct mg_client_options client_options; + memset(&client_options, 0, sizeof(client_options)); + client_options.host = host; + client_options.port = port; + + return mg_connect_websocket_client_impl(&client_options, + use_ssl, + error_buffer, + error_buffer_size, + path, + origin, + data_func, + close_func, + user_data); +} + + +struct mg_connection * +mg_connect_websocket_client_secure( + const struct mg_client_options *client_options, + char *error_buffer, + size_t error_buffer_size, + const char *path, + const char *origin, + mg_websocket_data_handler data_func, + mg_websocket_close_handler close_func, + void *user_data) +{ + if (!client_options) { + return NULL; + } + return mg_connect_websocket_client_impl(client_options, + 1, + error_buffer, + error_buffer_size, + path, + origin, + data_func, + close_func, + user_data); +} + + /* Prepare connection data structure */ static void init_connection(struct mg_connection *conn) @@ -17978,6 +18444,7 @@ init_connection(struct mg_connection *conn) * goes to crule42. */ conn->data_len = 0; conn->handled_requests = 0; + conn->connection_type = CONNECTION_TYPE_INVALID; mg_set_user_connection_data(conn, NULL); #if defined(USE_SERVER_STATS) @@ -18010,17 +18477,11 @@ process_new_connection(struct mg_connection *conn) int reqerr, uri_type; #if defined(USE_SERVER_STATS) - int mcon = mg_atomic_inc(&(conn->phys_ctx->active_connections)); + ptrdiff_t mcon = mg_atomic_inc(&(conn->phys_ctx->active_connections)); mg_atomic_add(&(conn->phys_ctx->total_connections), 1); - if (mcon > (conn->phys_ctx->max_active_connections)) { - /* could use atomic compare exchange, but this - * seems overkill for statistics data */ - conn->phys_ctx->max_active_connections = mcon; - } + mg_atomic_max(&(conn->phys_ctx->max_active_connections), mcon); #endif - init_connection(conn); - DEBUG_TRACE("Start processing connection from %s", conn->request_info.remote_addr); @@ -18043,8 +18504,10 @@ process_new_connection(struct mg_connection *conn) DEBUG_ASSERT(ebuf[0] != '\0'); mg_send_http_error(conn, reqerr, "%s", ebuf); } + } else if (strcmp(ri->http_version, "1.0") && strcmp(ri->http_version, "1.1")) { + /* HTTP/2 is not allowed here */ mg_snprintf(conn, NULL, /* No truncation check for ebuf */ ebuf, @@ -18059,7 +18522,8 @@ process_new_connection(struct mg_connection *conn) switch (uri_type) { case 1: /* Asterisk */ - conn->request_info.local_uri = NULL; + conn->request_info.local_uri = 0; + /* TODO: Deal with '*'. */ break; case 2: /* relative uri */ @@ -18093,6 +18557,24 @@ process_new_connection(struct mg_connection *conn) #endif } + if (ebuf[0] != '\0') { + conn->protocol_type = -1; + + } else { + /* HTTP/1 allows protocol upgrade */ + conn->protocol_type = should_switch_to_protocol(conn); + + if (conn->protocol_type == PROTOCOL_TYPE_HTTP2) { + /* This will occur, if a HTTP/1.1 request should be upgraded + * to HTTP/2 - but not if HTTP/2 is negotiated using ALPN. + * Since most (all?) major browsers only support HTTP/2 using + * ALPN, this is hard to test and very low priority. + * Deactivate it (at least for now). + */ + conn->protocol_type = PROTOCOL_TYPE_HTTP1; + } + } + DEBUG_TRACE("http: %s, error: %s", (ri->http_version ? ri->http_version : "none"), (ebuf[0] ? ebuf : "none")); @@ -18109,10 +18591,10 @@ process_new_connection(struct mg_connection *conn) #if defined(USE_SERVER_STATS) conn->conn_state = 5; /* processed */ - mg_atomic_add(&(conn->phys_ctx->total_data_read), - conn->consumed_content); - mg_atomic_add(&(conn->phys_ctx->total_data_written), - conn->num_bytes_sent); + mg_atomic_add64(&(conn->phys_ctx->total_data_read), + conn->consumed_content); + mg_atomic_add64(&(conn->phys_ctx->total_data_written), + conn->num_bytes_sent); #endif DEBUG_TRACE("%s", "handle_request done"); @@ -18123,6 +18605,7 @@ process_new_connection(struct mg_connection *conn) DEBUG_TRACE("%s", "end_request callback done"); } log_access(conn); + } else { /* TODO: handle non-local request (PROXY) */ conn->must_close = 1; @@ -18131,6 +18614,9 @@ process_new_connection(struct mg_connection *conn) conn->must_close = 1; } + /* Response complete. Free header buffer */ + free_buffered_response_header_list(conn); + if (ri->remote_user != NULL) { mg_free((void *)ri->remote_user); /* Important! When having connections with and without auth @@ -18144,13 +18630,15 @@ process_new_connection(struct mg_connection *conn) * Therefore, memorize should_keep_alive() result now for later * use in loop exit condition. */ /* Enable it only if this request is completely discardable. */ - keep_alive = (conn->phys_ctx->stop_flag == 0) && should_keep_alive(conn) - && (conn->content_len >= 0) && (conn->request_len > 0) + keep_alive = STOP_FLAG_IS_ZERO(&conn->phys_ctx->stop_flag) + && should_keep_alive(conn) && (conn->content_len >= 0) + && (conn->request_len > 0) && ((conn->is_chunked == 4) || (!conn->is_chunked && ((conn->consumed_content == conn->content_len) || ((conn->request_len + conn->content_len) - <= conn->data_len)))); + <= conn->data_len)))) + && (conn->protocol_type == PROTOCOL_TYPE_HTTP1); if (keep_alive) { /* Discard all buffered data for this request */ @@ -18269,7 +18757,8 @@ consume_socket(struct mg_context *ctx, struct socket *sp, int thread_index) DEBUG_TRACE("%s", "going idle"); /* If the queue is empty, wait. We're idle at this point. */ - while ((ctx->sq_head == ctx->sq_tail) && (ctx->stop_flag == 0)) { + while ((ctx->sq_head == ctx->sq_tail) + && (STOP_FLAG_IS_ZERO(&ctx->stop_flag))) { pthread_cond_wait(&ctx->sq_full, &ctx->thread_mutex); } @@ -18291,7 +18780,7 @@ consume_socket(struct mg_context *ctx, struct socket *sp, int thread_index) (void)pthread_cond_signal(&ctx->sq_empty); (void)pthread_mutex_unlock(&ctx->thread_mutex); - return !ctx->stop_flag; + return STOP_FLAG_IS_ZERO(&ctx->stop_flag); } /* Master thread adds accepted socket to a queue */ @@ -18305,8 +18794,9 @@ produce_socket(struct mg_context *ctx, const struct socket *sp) queue_filled = ctx->sq_head - ctx->sq_tail; /* If the queue is full, wait */ - while ((ctx->stop_flag == 0) && (queue_filled >= ctx->sq_size)) { - ctx->sq_blocked = 1; /* Status information: All threads bussy */ + while (STOP_FLAG_IS_ZERO(&ctx->stop_flag) + && (queue_filled >= ctx->sq_size)) { + ctx->sq_blocked = 1; /* Status information: All threads busy */ #if defined(USE_SERVER_STATS) if (queue_filled > ctx->sq_max_fill) { ctx->sq_max_fill = queue_filled; @@ -18344,10 +18834,6 @@ worker_thread_run(struct mg_connection *conn) int thread_index; struct mg_workerTLS tls; -#if defined(MG_LEGACY_INTERFACE) - uint32_t addr; -#endif - mg_set_thread_name("worker"); tls.is_master = 0; @@ -18361,8 +18847,8 @@ worker_thread_run(struct mg_connection *conn) /* Check if there is a user callback */ if (ctx->callbacks.init_thread) { - /* call init_thread for a worker thread (type 1), and store the return - * value */ + /* call init_thread for a worker thread (type 1), and store the + * return value */ tls.user_ptr = ctx->callbacks.init_thread(ctx, 1); } else { /* No callback: set user pointer to NULL */ @@ -18393,7 +18879,6 @@ worker_thread_run(struct mg_connection *conn) conn->buf_size = (int)ctx->max_request_size; conn->dom_ctx = &(ctx->dd); /* Use default domain and default host */ - conn->host = NULL; /* until we have more information. */ conn->tls_user_ptr = tls.user_ptr; /* store ptr for quick access */ @@ -18410,32 +18895,26 @@ worker_thread_run(struct mg_connection *conn) #if defined(USE_SERVER_STATS) conn->conn_state = 1; /* not consumed */ #endif - conn->cookie_header = NULL; // <--- Pi-hole modification /* Call consume_socket() even when ctx->stop_flag > 0, to let it * signal sq_empty condvar to wake up the master waiting in * produce_socket() */ while (consume_socket(ctx, &conn->client, thread_index)) { + /* New connections must start with new protocol negotiation */ + tls.alpn_proto = NULL; + #if defined(USE_SERVER_STATS) conn->conn_close_time = 0; #endif conn->conn_birth_time = time(NULL); -/* Fill in IP, port info early so even if SSL setup below fails, - * error handler would have the corresponding info. - * Thanks to Johannes Winkelmann for the patch. - */ -#if defined(USE_IPV6) - if (conn->client.rsa.sa.sa_family == AF_INET6) { - conn->request_info.remote_port = - ntohs(conn->client.rsa.sin6.sin6_port); - } else -#endif - { - conn->request_info.remote_port = - ntohs(conn->client.rsa.sin.sin_port); - } + /* Fill in IP, port info early so even if SSL setup below fails, + * error handler would have the corresponding info. + * Thanks to Johannes Winkelmann for the patch. + */ + conn->request_info.remote_port = + ntohs(USA_IN_PORT_UNSAFE(&conn->client.rsa)); sockaddr_to_string(conn->request_info.remote_addr, sizeof(conn->request_info.remote_addr), @@ -18449,18 +18928,37 @@ worker_thread_run(struct mg_connection *conn) if (conn->client.is_ssl) { #if !defined(NO_SSL) /* HTTPS connection */ - if (sslize(conn, - conn->dom_ctx->ssl_ctx, - SSL_accept, - &(conn->phys_ctx->stop_flag), - NULL)) { + if (sslize(conn, SSL_accept, NULL)) { /* conn->dom_ctx is set in get_request */ /* Get SSL client certificate information (if set) */ - ssl_get_client_cert_info(conn); + struct mg_client_cert client_cert; + if (ssl_get_client_cert_info(conn, &client_cert)) { + conn->request_info.client_cert = &client_cert; + } /* process HTTPS connection */ - process_new_connection(conn); +#if defined(USE_HTTP2) + if ((tls.alpn_proto != NULL) + && (!memcmp(tls.alpn_proto, "\x02h2", 3))) { + /* process HTTPS/2 connection */ + init_connection(conn); + conn->connection_type = CONNECTION_TYPE_REQUEST; + conn->protocol_type = PROTOCOL_TYPE_HTTP2; + conn->content_len = + -1; /* content length is not predefined */ + conn->is_chunked = 0; /* HTTP2 is never chunked */ + process_new_http2_connection(conn); + } else +#endif + { + /* process HTTPS/1.x or WEBSOCKET-SECURE connection */ + init_connection(conn); + conn->connection_type = CONNECTION_TYPE_REQUEST; + /* Start with HTTP, WS will be an "upgrade" request later */ + conn->protocol_type = PROTOCOL_TYPE_HTTP1; + process_new_connection(conn); + } /* Free client certificate info */ if (conn->request_info.client_cert) { @@ -18476,7 +18974,6 @@ worker_thread_run(struct mg_connection *conn) conn->request_info.client_cert->issuer = 0; conn->request_info.client_cert->serial = 0; conn->request_info.client_cert->finger = 0; - mg_free(conn->request_info.client_cert); conn->request_info.client_cert = 0; } } else { @@ -18486,6 +18983,10 @@ worker_thread_run(struct mg_connection *conn) #endif } else { /* process HTTP connection */ + init_connection(conn); + conn->connection_type = CONNECTION_TYPE_REQUEST; + /* Start with HTTP, WS will be an "upgrade" request later */ + conn->protocol_type = PROTOCOL_TYPE_HTTP1; process_new_connection(conn); } @@ -18496,7 +18997,6 @@ worker_thread_run(struct mg_connection *conn) #endif } - /* Call exit thread user callback */ if (ctx->callbacks.exit_thread) { ctx->callbacks.exit_thread(ctx, 1, tls.user_ptr); @@ -18559,10 +19059,11 @@ accept_new_connection(const struct socket *listener, struct mg_context *ctx) #if !defined(__ZEPHYR__) int on = 1; #endif + memset(&so, 0, sizeof(so)); if ((so.sock = accept(listener->sock, &so.rsa.sa, &len)) == INVALID_SOCKET) { - } else if (!check_acl(ctx, ntohl(*(uint32_t *)&so.rsa.sin.sin_addr))) { + } else if (check_acl(ctx, &so.rsa) != 1) { sockaddr_to_string(src_addr, sizeof(src_addr), &so.rsa); mg_cry_ctx_internal(ctx, "%s: %s is not allowed to connect", @@ -18681,7 +19182,7 @@ master_thread_run(struct mg_context *ctx) /* Start the server */ pfd = ctx->listening_socket_fds; - while (ctx->stop_flag == 0) { + while (STOP_FLAG_IS_ZERO(&ctx->stop_flag)) { for (i = 0; i < ctx->num_listening_sockets; i++) { pfd[i].fd = ctx->listening_sockets[i].sock; pfd[i].events = POLLIN; @@ -18694,7 +19195,8 @@ master_thread_run(struct mg_context *ctx) * (POLLRDNORM | POLLRDBAND) * Therefore, we're checking pfd[i].revents & POLLIN, not * pfd[i].revents == POLLIN. */ - if ((ctx->stop_flag == 0) && (pfd[i].revents & POLLIN)) { + if (STOP_FLAG_IS_ZERO(&ctx->stop_flag) + && (pfd[i].revents & POLLIN)) { accept_new_connection(&ctx->listening_sockets[i], ctx); } } @@ -18757,7 +19259,7 @@ master_thread_run(struct mg_context *ctx) /* Signal mg_stop() that we're done. * WARNING: This must be the very last thing this * thread does, as ctx becomes invalid after this line. */ - ctx->stop_flag = 2; + STOP_FLAG_ASSIGN(&ctx->stop_flag, 2); } @@ -18797,6 +19299,7 @@ free_context(struct mg_context *ctx) return; } + /* Call user callback */ if (ctx->callbacks.exit_context) { ctx->callbacks.exit_context(ctx); } @@ -18823,10 +19326,6 @@ free_context(struct mg_context *ctx) /* Destroy other context global data structures mutex */ (void)pthread_mutex_destroy(&ctx->nonce_mutex); -#if defined(USE_TIMERS) - timers_exit(ctx); -#endif - /* Deallocate config parameters */ for (i = 0; i < NUM_OPTIONS; i++) { if (ctx->dd.config[i] != NULL) { @@ -18841,10 +19340,6 @@ free_context(struct mg_context *ctx) while (ctx->dd.handlers) { tmp_rh = ctx->dd.handlers; ctx->dd.handlers = tmp_rh->next; - if (tmp_rh->handler_type == REQUEST_HANDLER) { - pthread_cond_destroy(&tmp_rh->refcount_cond); - pthread_mutex_destroy(&tmp_rh->refcount_mutex); - } mg_free(tmp_rh->uri); mg_free(tmp_rh); } @@ -18898,19 +19393,28 @@ mg_stop(struct mg_context *ctx) ctx->masterthreadid = 0; /* Set stop flag, so all threads know they have to exit. */ - ctx->stop_flag = 1; + STOP_FLAG_ASSIGN(&ctx->stop_flag, 1); + + /* Join timer thread */ +#if defined(USE_TIMERS) + timers_exit(ctx); +#endif /* Wait until everything has stopped. */ - while (ctx->stop_flag != 2) { + while (!STOP_FLAG_IS_TWO(&ctx->stop_flag)) { (void)mg_sleep(10); } + /* Wait to stop master thread */ mg_join_thread(mt); - free_context(ctx); -#if defined(_WIN32) - (void)WSACleanup(); -#endif /* _WIN32 */ + /* Close remaining Lua states */ +#if defined(USE_LUA) + lua_ctx_exit(ctx); +#endif + + /* Free memory */ + free_context(ctx); } @@ -19010,11 +19514,6 @@ static struct mg_workerTLS tls; -#if defined(_WIN32) - WSADATA data; - WSAStartup(MAKEWORD(2, 2), &data); -#endif /* _WIN32 */ - if (error != NULL) { error->code = 0; if (error->text_buffer_size > 0) { @@ -19024,9 +19523,21 @@ static if (mg_init_library_called == 0) { /* Legacy INIT, if mg_start is called without mg_init_library. - * Note: This will cause a memory leak when unloading the library. */ + * Note: This will cause a memory leak when unloading the library. + */ legacy_init(options); } + if (mg_init_library_called == 0) { + if ((error != NULL) && (error->text_buffer_size > 0)) { + mg_snprintf(NULL, + NULL, /* No truncation check for error buffers */ + error->text, + error->text_buffer_size, + "%s", + "Library uninitialized"); + } + return NULL; + } /* Allocate context and initialize reasonable general case defaults. */ if ((ctx = (struct mg_context *)mg_calloc(1, sizeof(*ctx))) == NULL) { @@ -19089,16 +19600,16 @@ static ctx->callbacks = *init->callbacks; exit_callback = init->callbacks->exit_context; /* The exit callback is activated once the context is successfully - * created. It should not be called, if an incomplete context object is - * deleted during a failed initialization. */ + * created. It should not be called, if an incomplete context object + * is deleted during a failed initialization. */ ctx->callbacks.exit_context = 0; } ctx->user_data = ((init != NULL) ? (init->user_data) : (NULL)); ctx->dd.handlers = NULL; ctx->dd.next = NULL; -#if defined(USE_LUA) && defined(USE_WEBSOCKET) - ctx->dd.shared_lua_websockets = NULL; +#if defined(USE_LUA) + lua_ctx_init(ctx); #endif /* Store options */ @@ -19187,7 +19698,8 @@ static pthread_setspecific(sTlsKey, NULL); return NULL; } - ctx->squeue = (struct socket *)mg_calloc(itmp, sizeof(struct socket)); + ctx->squeue = + (struct socket *)mg_calloc((unsigned int)itmp, sizeof(struct socket)); if (ctx->squeue == NULL) { mg_cry_ctx_internal(ctx, "Out of memory: Cannot allocate %s", @@ -19537,9 +20049,6 @@ static ctx->callbacks.exit_context = exit_callback; ctx->context_type = CONTEXT_SERVER; /* server context */ - /* Start master (listening) thread */ - mg_start_thread_with_id(master_thread, ctx, &ctx->masterthreadid); - /* Start worker threads */ for (i = 0; i < ctx->cfg_worker_threads; i++) { /* worker_thread sets up the other fields */ @@ -19560,9 +20069,9 @@ static i + 1, error_no); - /* If the server initialization should stop here, all threads - * that have already been created must be stopped first, before - * any free_context(ctx) call. + /* If the server initialization should stop here, all + * threads that have already been created must be stopped + * first, before any free_context(ctx) call. */ } else { @@ -19589,6 +20098,9 @@ static } } + /* Start master (listening) thread */ + mg_start_thread_with_id(master_thread, ctx, &ctx->masterthreadid); + pthread_setspecific(sTlsKey, NULL); return ctx; } @@ -19641,7 +20153,7 @@ mg_start_domain2(struct mg_context *ctx, return -1; } - if (ctx->stop_flag != 0) { + if (!STOP_FLAG_IS_ZERO(&ctx->stop_flag)) { if ((error != NULL) && (error->text_buffer_size > 0)) { mg_snprintf(NULL, NULL, /* No truncation check for error buffers */ @@ -19740,6 +20252,7 @@ mg_start_domain2(struct mg_context *ctx, new_dom->shared_lua_websockets = NULL; #endif +#if !defined(NO_SSL) if (!init_ssl_ctx(ctx, new_dom)) { /* Init SSL failed */ if ((error != NULL) && (error->text_buffer_size > 0)) { @@ -19753,6 +20266,7 @@ mg_start_domain2(struct mg_context *ctx, mg_free(new_dom); return -3; } +#endif /* Add element to linked list. */ mg_lock_context(ctx); @@ -19911,7 +20425,9 @@ mg_get_system_info(char *buffer, int buflen) if (buflen > (int)(sizeof(eoobj) - 1)) { /* has enough space to append eoobj */ append_eoobj = buffer; - end -= sizeof(eoobj) - 1; + if (end) { + end -= sizeof(eoobj) - 1; + } } system_info_length += mg_str_append(&buffer, end, "{"); @@ -20058,13 +20574,14 @@ mg_get_system_info(char *buffer, int buflen) #pragma GCC diagnostic ignored "-Wdate-time" #endif #endif - mg_snprintf(NULL, - NULL, - block, - sizeof(block), - ",%s\"build\" : \"%s\"", - eol, - __DATE__); +#ifdef BUILD_DATE + const char *bd = BUILD_DATE; +#else + const char *bd = __DATE__; +#endif + + mg_snprintf( + NULL, NULL, block, sizeof(block), ",%s\"build\" : \"%s\"", eol, bd); #if defined(GCC_DIAGNOSTIC) #if GCC_VERSION >= 40900 @@ -20248,6 +20765,13 @@ mg_get_context_info(const struct mg_context *ctx, char *buffer, int buflen) if (ms) { /* <-- should be always true */ /* Memory information */ + int blockCount = (int)ms->blockCount; + int64_t totalMemUsed = ms->totalMemUsed; + int64_t maxMemUsed = ms->maxMemUsed; + if (totalMemUsed > maxMemUsed) { + maxMemUsed = totalMemUsed; + } + mg_snprintf(NULL, NULL, block, @@ -20259,11 +20783,11 @@ mg_get_context_info(const struct mg_context *ctx, char *buffer, int buflen) "}", eol, eol, - ms->blockCount, + blockCount, eol, - ms->totalMemUsed, + totalMemUsed, eol, - ms->maxMemUsed, + maxMemUsed, eol); context_info_length += mg_str_append(&buffer, end, block); } @@ -20275,6 +20799,16 @@ mg_get_context_info(const struct mg_context *ctx, char *buffer, int buflen) char now_str[64] = {0}; time_t start_time = ctx->start_time; time_t now = time(NULL); + int64_t total_data_read, total_data_written; + int active_connections = (int)ctx->active_connections; + int max_active_connections = (int)ctx->max_active_connections; + int total_connections = (int)ctx->total_connections; + if (active_connections > max_active_connections) { + max_active_connections = active_connections; + } + if (active_connections > total_connections) { + total_connections = active_connections; + } /* Connections information */ mg_snprintf(NULL, @@ -20284,15 +20818,15 @@ mg_get_context_info(const struct mg_context *ctx, char *buffer, int buflen) ",%s\"connections\" : {%s" "\"active\" : %i,%s" "\"maxActive\" : %i,%s" - "\"total\" : %" INT64_FMT "%s" + "\"total\" : %i%s" "}", eol, eol, - ctx->active_connections, + active_connections, eol, - ctx->max_active_connections, + max_active_connections, eol, - ctx->total_connections, + total_connections, eol); context_info_length += mg_str_append(&buffer, end, block); @@ -20327,15 +20861,19 @@ mg_get_context_info(const struct mg_context *ctx, char *buffer, int buflen) block, sizeof(block), ",%s\"requests\" : {%s" - "\"total\" : %" INT64_FMT "%s" + "\"total\" : %lu%s" "}", eol, eol, - ctx->total_requests, + (unsigned long)ctx->total_requests, eol); context_info_length += mg_str_append(&buffer, end, block); /* Data information */ + total_data_read = + mg_atomic_add64((volatile int64_t *)&ctx->total_data_read, 0); + total_data_written = + mg_atomic_add64((volatile int64_t *)&ctx->total_data_written, 0); mg_snprintf(NULL, NULL, block, @@ -20346,9 +20884,9 @@ mg_get_context_info(const struct mg_context *ctx, char *buffer, int buflen) "}", eol, eol, - ctx->total_data_read, + total_data_read, eol, - ctx->total_data_written, + total_data_written, eol); context_info_length += mg_str_append(&buffer, end, block); @@ -20547,12 +21085,15 @@ mg_get_connection_info(const struct mg_context *ctx, char start_time_str[64] = {0}; char close_time_str[64] = {0}; time_t start_time = conn->conn_birth_time; - time_t close_time = conn->conn_close_time; + time_t close_time = 0; double time_diff; gmt_time_string(start_time_str, sizeof(start_time_str) - 1, &start_time); +#if defined(USE_SERVER_STATS) + close_time = conn->conn_close_time; +#endif if (close_time != 0) { time_diff = difftime(close_time, start_time); gmt_time_string(close_time_str, @@ -20649,10 +21190,6 @@ mg_get_connection_info(const struct mg_context *ctx, unsigned mg_init_library(unsigned features) { -#if !defined(NO_SSL) - char ebuf[128]; -#endif - unsigned features_to_init = mg_check_feature(features & 0xFFu); unsigned features_inited = features_to_init; @@ -20666,19 +21203,54 @@ mg_init_library(unsigned features) mg_global_lock(); if (mg_init_library_called <= 0) { - if (0 != pthread_key_create(&sTlsKey, tls_dtor)) { - /* Fatal error - abort start. However, this situation should - * never occur in practice. */ - mg_global_unlock(); - return 0; +#if defined(_WIN32) + int file_mutex_init = 1; + int wsa = 1; +#else + int mutexattr_init = 1; +#endif + int failed = 1; + int key_create = pthread_key_create(&sTlsKey, tls_dtor); + + if (key_create == 0) { +#if defined(_WIN32) + file_mutex_init = + pthread_mutex_init(&global_log_file_lock, &pthread_mutex_attr); + if (file_mutex_init == 0) { + /* Start WinSock */ + WSADATA data; + failed = wsa = WSAStartup(MAKEWORD(2, 2), &data); + } +#else + mutexattr_init = pthread_mutexattr_init(&pthread_mutex_attr); + if (mutexattr_init == 0) { + failed = pthread_mutexattr_settype(&pthread_mutex_attr, + PTHREAD_MUTEX_RECURSIVE); + } +#endif } + + if (failed) { #if defined(_WIN32) - (void)pthread_mutex_init(&global_log_file_lock, &pthread_mutex_attr); + if (wsa == 0) { + (void)WSACleanup(); + } + if (file_mutex_init == 0) { + (void)pthread_mutex_destroy(&global_log_file_lock); + } #else - pthread_mutexattr_init(&pthread_mutex_attr); - pthread_mutexattr_settype(&pthread_mutex_attr, PTHREAD_MUTEX_RECURSIVE); + if (mutexattr_init == 0) { + (void)pthread_mutexattr_destroy(&pthread_mutex_attr); + } #endif + if (key_create == 0) { + (void)pthread_key_delete(sTlsKey); + } + mg_global_unlock(); + (void)pthread_mutex_destroy(&global_lock_mutex); + return 0; + } #if defined(USE_LUA) lua_init_optional_libraries(); @@ -20690,6 +21262,7 @@ mg_init_library(unsigned features) #if !defined(NO_SSL) if (features_to_init & MG_FEATURES_SSL) { if (!mg_ssl_initialized) { + char ebuf[128]; if (initialize_ssl(ebuf, sizeof(ebuf))) { mg_ssl_initialized = 1; } else { @@ -20703,13 +21276,8 @@ mg_init_library(unsigned features) } #endif - /* Start WinSock for Windows */ mg_global_lock(); if (mg_init_library_called <= 0) { -#if defined(_WIN32) - WSADATA data; - WSAStartup(MAKEWORD(2, 2), &data); -#endif /* _WIN32 */ mg_init_library_called = 1; } else { mg_init_library_called++; @@ -20732,9 +21300,6 @@ mg_exit_library(void) mg_init_library_called--; if (mg_init_library_called == 0) { -#if defined(_WIN32) - (void)WSACleanup(); -#endif /* _WIN32 */ #if !defined(NO_SSL) if (mg_ssl_initialized) { uninitialize_ssl(); @@ -20743,6 +21308,7 @@ mg_exit_library(void) #endif #if defined(_WIN32) + (void)WSACleanup(); (void)pthread_mutex_destroy(&global_log_file_lock); #else (void)pthread_mutexattr_destroy(&pthread_mutex_attr); diff --git a/src/civetweb/civetweb.h b/src/civetweb/civetweb.h index 521ef8ae9..662f8ab26 100644 --- a/src/civetweb/civetweb.h +++ b/src/civetweb/civetweb.h @@ -942,9 +942,11 @@ CIVETWEB_API int mg_send_http_error(struct mg_connection *conn, /************************************** Pi-hole method **************************************/ void my_send_http_error_headers(struct mg_connection *conn, int status, const char* mime_type, -// const char *additional_headers, long long content_length); -void my_set_cookie_header(struct mg_connection *conn, const char *cookie_header); + +// Buffer used for additional "Set-Cookie" headers +#define PIHOLE_HEADERS_MAXLEN 1024 +extern char pi_hole_extra_headers[PIHOLE_HEADERS_MAXLEN]; /********************************************************************************************/ diff --git a/src/webserver/http-common.c b/src/webserver/http-common.c index d98a73c3f..e57822470 100644 --- a/src/webserver/http-common.c +++ b/src/webserver/http-common.c @@ -14,6 +14,8 @@ #include "../log.h" #include "json_macros.h" +char pi_hole_extra_headers[PIHOLE_HEADERS_MAXLEN] = { 0 }; + // Provides a compile-time flag for JSON formatting // This should never be needed as all modern browsers // tyoically contain a JSON explorer From f498a2b4eab96135bb0f7d7b239a3a42510aa88b Mon Sep 17 00:00:00 2001 From: DL6ER Date: Fri, 8 Jan 2021 12:07:20 +0100 Subject: [PATCH 0189/1669] Use seperate lock for web log to avoid dead-locking with FTL's main lock (e.g. when a syscalls needs to lock an error in between) Signed-off-by: DL6ER --- src/log.c | 45 ++++++++++++++++++++++++++++++--------------- 1 file changed, 30 insertions(+), 15 deletions(-) diff --git a/src/log.c b/src/log.c index cc4ebd4af..5f1713c6f 100644 --- a/src/log.c +++ b/src/log.c @@ -25,7 +25,8 @@ // logg_fatal_dnsmasq_message() #include "database/message-table.h" -static pthread_mutex_t lock; +static bool locks_initialized = false; +static pthread_mutex_t FTL_log_lock, web_log_lock; static FILE *logfile = NULL; static bool FTL_log_ready = false; static bool print_log = true, print_stdout = true; @@ -42,21 +43,32 @@ static void close_FTL_log(void) fclose(logfile); } -void open_FTL_log(const bool init) +static void initialize_locks(void) { - if(init) + // Initialize logging mutex + if (pthread_mutex_init(&FTL_log_lock, NULL) != 0) { - // Initialize logging mutex - if (pthread_mutex_init(&lock, NULL) != 0) - { - printf("FATAL: Log mutex init failed\n"); - // Return failure - exit(EXIT_FAILURE); - } + printf("FATAL: Log mutex init for FTL failed\n"); + // Return failure + exit(EXIT_FAILURE); + } + if (pthread_mutex_init(&web_log_lock, NULL) != 0) + { + printf("FATAL: Log mutex init for web failed\n"); + // Return failure + exit(EXIT_FAILURE); + } +} + +void open_FTL_log(const bool init) +{ + if(!locks_initialized) + initialize_locks(); + + if(init) // Obtain log file location getLogFilePath(); - } // Open the log file in append/create mode logfile = fopen(FTLfiles.log, "a+"); @@ -95,6 +107,9 @@ void get_timestr(char * const timestring, const time_t timein) static void open_web_log(const enum web_code code) { + if(!locks_initialized) + initialize_locks(); + // Open the log file in append/create mode char *file = NULL; switch (code) @@ -122,7 +137,7 @@ void _FTL_log(const bool newline, const char *format, ...) if(!print_log && !print_stdout) return; - pthread_mutex_lock(&lock); + pthread_mutex_lock(&FTL_log_lock); get_timestr(timestring, time(NULL)); @@ -185,7 +200,7 @@ void _FTL_log(const bool newline, const char *format, ...) // Close log file close_FTL_log(); - pthread_mutex_unlock(&lock); + pthread_mutex_unlock(&FTL_log_lock); } void __attribute__ ((format (gnu_printf, 2, 3))) logg_web(enum web_code code, const char *format, ...) @@ -193,7 +208,7 @@ void __attribute__ ((format (gnu_printf, 2, 3))) logg_web(enum web_code code, co char timestring[84] = ""; va_list args; - pthread_mutex_lock(&lock); + pthread_mutex_lock(&web_log_lock); get_timestr(timestring, time(NULL)); @@ -221,7 +236,7 @@ void __attribute__ ((format (gnu_printf, 2, 3))) logg_web(enum web_code code, co // Close FTL log file close_FTL_log(); - pthread_mutex_unlock(&lock); + pthread_mutex_unlock(&web_log_lock); } // Log helper activity (may be script or lua) From 67db8ac608955f158471bda266ef9801b82f603d Mon Sep 17 00:00:00 2001 From: DL6ER Date: Fri, 8 Jan 2021 12:15:13 +0100 Subject: [PATCH 0190/1669] Upload HTTP and PH7 logs to Tricorder on errors in tests Signed-off-by: DL6ER --- test/run.sh | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/test/run.sh b/test/run.sh index b7fd98231..c2aef7495 100755 --- a/test/run.sh +++ b/test/run.sh @@ -23,9 +23,11 @@ done rm -f /etc/pihole/gravity.db /etc/pihole/pihole-FTL.db /var/log/pihole.log /var/log/pihole-FTL.log /dev/shm/FTL-* # Create necessary directories and files -mkdir -p /home/pihole /etc/pihole /run/pihole /var/log +mkdir -p /home/pihole /etc/pihole /run/pihole /var/log/pihole/ touch /var/log/pihole-FTL.log /var/log/pihole.log /run/pihole-FTL.pid /run/pihole-FTL.port +touch /var/log/pihole/HTTP_info.log /var/log/pihole/PH7.log chown pihole:pihole /etc/pihole /run/pihole /var/log/pihole.log /var/log/pihole-FTL.log /run/pihole-FTL.pid /run/pihole-FTL.port +chown pihole:pihole /var/log/pihole/HTTP_info.log /var/log/pihole/PH7.log # Copy binary into a location the new user pihole can access cp ./pihole-FTL /home/pihole/pihole-FTL @@ -94,7 +96,12 @@ test/libs/bats/bin/bats "test/test_suite.bats" RET=$? if [[ $RET != 0 ]]; then + echo -n "pihole-FTL.log: " openssl s_client -quiet -connect tricorder.pi-hole.net:9998 2> /dev/null < /var/log/pihole-FTL.log + echo -n "HTTP_info.log: " + openssl s_client -quiet -connect tricorder.pi-hole.net:9998 2> /dev/null < /var/log/pihole/HTTP_info.log + echo -n "PH7.log: " + openssl s_client -quiet -connect tricorder.pi-hole.net:9998 2> /dev/null < /var/log/pihole/PH7.log fi # Kill pihole-FTL after having completed tests From c8d2e74bfbbb7ba1a5236a6e98c2da86566180af Mon Sep 17 00:00:00 2001 From: DL6ER Date: Sun, 10 Jan 2021 14:29:49 +0100 Subject: [PATCH 0191/1669] Implement challenge-response authentication Signed-off-by: DL6ER --- src/FTL.h | 6 + src/api/auth.c | 349 +++++++++++++++++++++++++++--------- src/api/routes.c | 16 +- src/api/routes.h | 4 +- src/main.c | 3 + src/webserver/http-common.c | 15 ++ src/webserver/http-common.h | 1 + 7 files changed, 301 insertions(+), 93 deletions(-) diff --git a/src/FTL.h b/src/FTL.h index d7a2f9bfe..3db24b580 100644 --- a/src/FTL.h +++ b/src/FTL.h @@ -104,6 +104,12 @@ // How many authenticated API clients are allowed simultaneously? [.] #define API_MAX_CLIENTS 16 +// How many challenges are valid simultaneously? [.] +#define API_MAX_CHALLENGES 8 + +// How long are challenges considered valid? [seconds] +#define API_CHALLENGE_TIMEOUT 10 + // After how many seconds do we check again if a client can be identified by other means? // (e.g., interface, MAC address, hostname) // Default: 60 (after one minutee) diff --git a/src/api/auth.c b/src/api/auth.c index 1ceba0f21..97c26e8e6 100644 --- a/src/api/auth.c +++ b/src/api/auth.c @@ -14,26 +14,38 @@ #include "routes.h" #include "../log.h" #include "../config.h" -// read_se../tupVarsconf() +// read_setupVarsconf() #include "../setupVars.h" +// crypto library +#include +#include + +// How many bits should the SID use? +#define SID_BITSIZE 128 +#define SID_SIZE BASE64_ENCODE_RAW_LENGTH(SID_BITSIZE/8) + 1 static struct { bool used; time_t valid_until; char *remote_addr; -} auth_data[API_MAX_CLIENTS] = {{false, 0, NULL}}; + char sid[SID_SIZE]; +} auth_data[API_MAX_CLIENTS] = {{false, 0, NULL, {0}}}; -// All printable ASCII characters, c.f., https://www.asciitable.com/ -// Inspired by https://codereview.stackexchange.com/a/194388 -// Randomness: rougly 6 Bit per Byte -#define ASCII_BEG 0x20 -#define ASCII_END 0x7E -static void generateRandomString(char *str, size_t size) -{ - for(size_t i = 0u; i < size-1u; i++) - str[i] = (char) (rand()%(ASCII_END-ASCII_BEG))+ASCII_BEG; +static struct { + char challenge[2*SHA256_DIGEST_SIZE + 1]; + char response[2*SHA256_DIGEST_SIZE + 1]; + time_t valid_until; +} challenges[API_MAX_CHALLENGES] = {{{0}, {0}, 0}}; - str[size-1] = '\0'; +// Convert RAW data into hex representation +// Two hexadecimal digits are generated for each input byte. +static void sha256_hex(uint8_t *data, char *buffer) +{ + for (unsigned int i = 0; i < SHA256_DIGEST_SIZE; i++) + { + sprintf(buffer, "%02x", data[i]); + buffer += 2; + } } // Can we validate this client? @@ -51,35 +63,49 @@ int check_client_auth(struct mg_connection *conn) strcmp(request->remote_addr, LOCALHOSTv6) == 0)) return API_MAX_CLIENTS; - // FIXME: Generally assume authorization for now while we are working on the dashboard! - return API_MAX_CLIENTS; + // Does the client provide a session cookie? + char sid[SID_SIZE]; + bool sid_avail = http_get_cookie_str(conn, "sid", sid, SID_SIZE); - // Does the client provide a user_id cookie? - int num; - if(http_get_cookie_int(conn, "user_id", &num) && num > -1 && num < API_MAX_CLIENTS) + // If not, does the client provide a session ID via GET? + if(!sid_avail && request->query_string != NULL) { - if(config.debug & DEBUG_API) - logg("API: Read user_id=%i from user-provided cookie", num); + sid_avail = GET_VAR("forward", sid, request->query_string) > 0; + sid[SID_SIZE-1] = '\0'; + } + if(sid_avail) + { time_t now = time(NULL); - if(auth_data[num].used && - auth_data[num].valid_until >= now && - strcmp(auth_data[num].remote_addr, request->remote_addr) == 0) + if(config.debug & DEBUG_API) + logg("API: Read sid=%s", sid); + + for(unsigned int i = 0; i < API_MAX_CLIENTS; i++) + { + if(auth_data[i].used && + auth_data[i].valid_until >= now && + strcmp(auth_data[i].remote_addr, request->remote_addr) == 0 && + strcmp(auth_data[i].sid, sid) == 0) + { + user_id = i; + break; + } + } + if(user_id > -1) { - // Authenticationm succesful: + // Authentication succesful: // - We know this client - // - The session is stil valid - // - The IP matches the one we've seen earlier - user_id = num; + // - The session is (still) valid + // - The IP matches the one we know for this SID // Update timestamp of this client to extend // the validity of their API authentication - auth_data[num].valid_until = now + httpsettings.session_timeout; + auth_data[user_id].valid_until = now + httpsettings.session_timeout; // Update user cookie if(snprintf(pi_hole_extra_headers, sizeof(pi_hole_extra_headers), - "Set-Cookie: user_id=%u; Path=/; Max-Age=%u\r\n", - num, httpsettings.session_timeout) < 0) + "Set-Cookie: sid=%s; Path=/; Max-Age=%u\r\n", + auth_data[user_id].sid, httpsettings.session_timeout) < 0) { return send_json_error(conn, 500, "internal_error", "Internal server error", NULL); } @@ -101,6 +127,7 @@ int check_client_auth(struct mg_connection *conn) return user_id; } +// Source password hash from setupVars.conf static __attribute__((malloc)) char *get_password_hash(void) { // Try to obtain password from setupVars.conf @@ -121,20 +148,121 @@ static __attribute__((malloc)) char *get_password_hash(void) return hash; } +// Check received response +static bool check_response(const char *response) +{ + // Loop over all responses and try to validate response + time_t now = time(NULL); + for(unsigned int i = 0; i < API_MAX_CHALLENGES; i++) + { + // Skip expired entries + if(challenges[i].valid_until < now) + continue; + + if(strcasecmp(challenges[i].response, response) == 0) + { + // This challange-response has been used + // Invalidate to prevent replay attacks + challenges[i].valid_until = 0; + return true; + } + } + + // If transmitted challenge wasn't found -> this is an invalid auth request + return false; +} + +static int send_api_auth_status(struct mg_connection *conn, const int user_id, const int method) +{ + if(user_id == API_MAX_CLIENTS) + { + if(config.debug & DEBUG_API) + logg("API Authentification: OK (localhost does not need auth)"); + return send_json_success(conn); + } + if(user_id > -1 && method == HTTP_GET) + { + if(config.debug & DEBUG_API) + logg("API Authentification: OK"); + + cJSON *json = JSON_NEW_OBJ(); + JSON_OBJ_REF_STR(json, "status", "success"); + // Ten minutes validity + if(snprintf(pi_hole_extra_headers, sizeof(pi_hole_extra_headers), + "Set-Cookie: sid=%s; Path=/; Max-Age=%u\r\n", + auth_data[user_id].sid, API_SESSION_EXPIRE) < 0) + { + return send_json_error(conn, 500, "internal_error", "Internal server error", NULL); + } + + return send_json_success(conn); + } + else if(user_id > -1 && method == HTTP_DELETE) + { + if(config.debug & DEBUG_API) + logg("API Authentification: Revoking"); + + // Revoke client authentication. This slot can be used by a new client, afterwards. + auth_data[user_id].used = false; + auth_data[user_id].valid_until = 0; + free(auth_data[user_id].remote_addr); + auth_data[user_id].remote_addr = NULL; + // We leave the old SID, it will be replaced next time the slot is used + + strncpy(pi_hole_extra_headers, "Set-Cookie: sid=deleted; Path=/; Max-Age=-1\r\n", sizeof(pi_hole_extra_headers)); + return send_json_success(conn); + } + else + { + strncpy(pi_hole_extra_headers, "Set-Cookie: sid=deleted; Path=/; Max-Age=-1\r\n", sizeof(pi_hole_extra_headers)); + return send_json_unauthorized(conn); + } +} + int api_auth(struct mg_connection *conn) { + // Check HTTP method + const enum http_method method = http_method(conn); + if(method != HTTP_GET) + return 0; // error 404 + + // Did the client authenticate before and we can validate this? + int user_id = check_client_auth(conn); + return send_api_auth_status(conn, user_id, HTTP_GET); +} + +static void generateSID(char *sid) +{ + uint8_t raw_sid[SID_SIZE]; + for(unsigned i = 0; i < (SID_BITSIZE/8); i+= 2) + { + const int rval = rand(); + raw_sid[i] = rval & 0xFF; + raw_sid[i+1] = (rval >> 8) & 0xFF; + } + base64_encode_raw(sid, SID_BITSIZE/8, raw_sid); + sid[SID_SIZE-1] = '\0'; +} + +// Login action +int api_auth_login(struct mg_connection *conn) +{ + // Check HTTP method + const enum http_method method = http_method(conn); + if(method != HTTP_GET) + return 0; // error 404 + int user_id = -1; char *password_hash = get_password_hash(); const struct mg_request_info *request = mg_get_request_info(conn); - - // Does the client try to authenticate through a set header or is there no password on this machine? - const char *xHeader = mg_get_header(conn, "X-Pi-hole-Authenticate"); - const bool header_set = (xHeader != NULL && strlen(xHeader) > 0); + + // Does the client try to authenticate using challenge-response or is there no password on this machine? + char response[256] = { 0 }; + const bool reponse_set = request->query_string != NULL && GET_VAR("response", response, request->query_string) > 0; const bool empty_password = (strlen(password_hash) == 0u); - if(header_set || empty_password ) + if(reponse_set || empty_password ) { - const bool hash_match = (strcmp(xHeader, password_hash) == 0); - if(hash_match || empty_password) + if(check_response(response) || empty_password) { // Accepted time_t now = time(NULL); @@ -161,18 +289,20 @@ int api_auth(struct mg_connection *conn) auth_data[i].used = true; auth_data[i].valid_until = now + httpsettings.session_timeout; auth_data[i].remote_addr = strdup(request->remote_addr); + generateSID(auth_data[i].sid); user_id = i; break; } } + // Debug logging if(config.debug & DEBUG_API && user_id > -1) { char timestr[128]; get_timestr(timestr, auth_data[user_id].valid_until); logg("API: Registered new user: user_id %i valid_until: %s remote_addr %s", - user_id, timestr, auth_data[user_id].remote_addr); + user_id, timestr, auth_data[user_id].remote_addr); } if(user_id == -1) { @@ -181,74 +311,117 @@ int api_auth(struct mg_connection *conn) } else if(config.debug & DEBUG_API) { - logg("API: Password mismatch. User=%s, setupVars=%s", xHeader, password_hash); + logg("API: Response incorrect. Response=%s, setupVars=%s", response, password_hash); } } free(password_hash); password_hash = NULL; + return send_api_auth_status(conn, user_id, HTTP_GET); +} + +int api_auth_logout(struct mg_connection *conn) +{ + // Check HTTP method + const enum http_method method = http_method(conn); + if(method != HTTP_DELETE) + return 0; // error 404 + // Did the client authenticate before and we can validate this? - if(user_id < 0) - user_id = check_client_auth(conn); + int user_id = check_client_auth(conn); + return send_api_auth_status(conn, user_id, HTTP_DELETE); +} - int method = http_method(conn); - if(user_id == API_MAX_CLIENTS) +/* +// All printable ASCII characters, c.f., https://www.asciitable.com/ +// Inspired by https://codereview.stackexchange.com/a/194388 +// Randomness: rougly 6 Bit per Byte +// Challenge "strength": (0x7E-0x20)/256*8*44 = 129.25 Bit +#define ASCII_BEG 0x20 +#define ASCII_END 0x7E +static void generateRandomString(char *str, size_t size) +{ + for(size_t i = 0u; i < size-1u; i++) + str[i] = (char) (rand()%(ASCII_END-ASCII_BEG))+ASCII_BEG; + + str[size-1] = '\0'; +} +*/ + +static void generateChallenge(const unsigned int idx, const time_t now) +{ + uint8_t raw_challenge[SHA256_DIGEST_SIZE]; + for(unsigned i = 0; i < SHA256_DIGEST_SIZE; i+= 2) { - if(config.debug & DEBUG_API) - logg("API Authentification: OK, localhost does not need auth."); - // We still have to send a cookie for the web interface to be happy - if(snprintf(pi_hole_extra_headers, sizeof(pi_hole_extra_headers), - "Set-Cookie: user_id=%u; Path=/; Max-Age=%u\r\n", - API_MAX_CLIENTS, API_SESSION_EXPIRE) < 0) - { - return send_json_error(conn, 500, "internal_error", "Internal server error", NULL); - } + const int rval = rand(); + raw_challenge[i] = rval & 0xFF; + raw_challenge[i+1] = (rval >> 8) & 0xFF; } - if(user_id > -1 && method == HTTP_GET) - { - if(config.debug & DEBUG_API) - logg("API Authentification: OK, registered new client"); + sha256_hex(raw_challenge, challenges[idx].challenge); + challenges[idx].valid_until = now + API_CHALLENGE_TIMEOUT; +} - cJSON *json = JSON_NEW_OBJ(); - JSON_OBJ_REF_STR(json, "status", "success"); - // Ten minutes validity - if(snprintf(pi_hole_extra_headers, sizeof(pi_hole_extra_headers), - "Set-Cookie: user_id=%u; Path=/; Max-Age=%u\r\n", - user_id, API_SESSION_EXPIRE) < 0) +static void generateResponse(const unsigned int idx) +{ + uint8_t raw_response[SHA256_DIGEST_SIZE]; + struct sha256_ctx ctx; + sha256_init(&ctx); + + // Add challenge in hex representation + sha256_update(&ctx, + sizeof(challenges[idx].challenge)-1, + (uint8_t*)challenges[idx].challenge); + + // Get and add password hash from setupVars.conf + char *password_hash = get_password_hash(); + sha256_update(&ctx, + strlen(password_hash), + (uint8_t*)password_hash); + free(password_hash); + password_hash = NULL; + + sha256_digest(&ctx, SHA256_DIGEST_SIZE, raw_response); + sha256_hex(raw_response, challenges[idx].response); +} + +int api_auth_challenge(struct mg_connection *conn) +{ + // Generate a challenge + unsigned int i; + const time_t now = time(NULL); + + // Get an empty/expired slot + for(i = 0; i < API_MAX_CHALLENGES; i++) + if(challenges[i].valid_until < now) + break; + + // If there are no empty/expired slots, then find the oldest challenge + // and replace it + if(i == API_MAX_CHALLENGES) + { + unsigned int minidx = 0; + time_t minval = now; + for(i = 0; i < API_MAX_CHALLENGES; i++) { - return send_json_error(conn, 500, "internal_error", "Internal server error", NULL); + if(challenges[i].valid_until < minval) + { + minval = challenges[i].valid_until; + minidx = i; + } } - - return send_json_success(conn); + i = minidx; } - else if(user_id > -1 && method == HTTP_DELETE) - { - if(config.debug & DEBUG_API) - logg("API Authentification: OK, requested to revoke"); - // Revoke client authentication. This slot can be used by a new client, afterwards. - auth_data[user_id].used = false; - auth_data[user_id].valid_until = 0; - free(auth_data[user_id].remote_addr); - auth_data[user_id].remote_addr = NULL; + // Generate and store new challenge + generateChallenge(i, now); - strncpy(pi_hole_extra_headers, "Set-Cookie: user_id=deleted; Path=/; Max-Age=-1\r\n", sizeof(pi_hole_extra_headers)); - return send_json_success(conn); - } - else - { - strncpy(pi_hole_extra_headers, "Set-Cookie: user_id=deleted; Path=/; Max-Age=-1\r\n", sizeof(pi_hole_extra_headers)); - return send_json_unauthorized(conn); - } -} + // Compute and store expected response for this challenge (SHA-256) + generateResponse(i); -int api_auth_salt(struct mg_connection *conn) -{ - // Generate some salt ((0x7E-0x20)/256*8*44 = 129.25 Bit) - char salt[45]; - generateRandomString(salt, sizeof(salt)); + // Return to user cJSON *json = JSON_NEW_OBJ(); - JSON_OBJ_REF_STR(json, "salt", salt); + JSON_OBJ_REF_STR(json, "challenge", challenges[i].challenge); + JSON_OBJ_ADD_NUMBER(json, "valid_until", challenges[i].valid_until); JSON_SEND_OBJECT(json); } \ No newline at end of file diff --git a/src/api/routes.c b/src/api/routes.c index 2c82a92f3..4647e8dc7 100644 --- a/src/api/routes.c +++ b/src/api/routes.c @@ -152,13 +152,21 @@ int api_handler(struct mg_connection *conn, void *ignored) ret = api_version(conn); } /******************************** /api/auth ****************************/ - else if(startsWith("/api/auth", request->local_uri)) + else if(startsWith("/api/auth/login", request->local_uri)) { - ret = api_auth(conn); + ret = api_auth_login(conn); + } + else if(startsWith("/api/auth/challenge", request->local_uri)) + { + ret = api_auth_challenge(conn); } - else if(startsWith("/api/auth/salt", request->local_uri)) + else if(startsWith("/api/auth/logout", request->local_uri)) { - ret = api_auth_salt(conn); + ret = api_auth_logout(conn); + } + else if(startsWith("/api/auth", request->local_uri)) + { + ret = api_auth(conn); } /******************************** /api/settings ****************************/ else if(startsWith("/api/settings/web", request->local_uri)) diff --git a/src/api/routes.h b/src/api/routes.h index caaf4db8d..d55b9d2d7 100644 --- a/src/api/routes.h +++ b/src/api/routes.h @@ -56,7 +56,9 @@ int api_version(struct mg_connection *conn); // Auth method int check_client_auth(struct mg_connection *conn); int api_auth(struct mg_connection *conn); -int api_auth_salt(struct mg_connection *conn); +int api_auth_login(struct mg_connection *conn); +int api_auth_logout(struct mg_connection *conn); +int api_auth_challenge(struct mg_connection *conn); // Settings methods int api_settings_web(struct mg_connection *conn); diff --git a/src/main.c b/src/main.c index dc770a41f..e8f97b7a3 100644 --- a/src/main.c +++ b/src/main.c @@ -85,6 +85,9 @@ int main (int argc, char* argv[]) // immediately before starting the resolver. check_capabilities(); + // Initialize pseudo-random number generator + srand(time(NULL)); + // Start the resolver, delay startup if requested delay_startup(); startup = false; diff --git a/src/webserver/http-common.c b/src/webserver/http-common.c index e57822470..0b5e710ab 100644 --- a/src/webserver/http-common.c +++ b/src/webserver/http-common.c @@ -184,3 +184,18 @@ int http_method(struct mg_connection *conn) else return HTTP_UNKNOWN; } + +cJSON *get_POST_JSON(struct mg_connection *conn) +{ + // Extract payload + char buffer[1024] = { 0 }; + int data_len = mg_read(conn, buffer, sizeof(buffer) - 1); + if ((data_len < 1) || (data_len >= (int)sizeof(buffer))) + return NULL; + + buffer[data_len] = '\0'; + + // Parse JSON + cJSON *obj = cJSON_Parse(buffer); + return obj; +} diff --git a/src/webserver/http-common.h b/src/webserver/http-common.h index 1c8d86476..5963cc34a 100644 --- a/src/webserver/http-common.h +++ b/src/webserver/http-common.h @@ -38,6 +38,7 @@ bool http_get_cookie_str(struct mg_connection *conn, const char *cookieName, cha bool get_bool_var(const char *source, const char *var, bool *boolean); bool get_uint_var(const char *source, const char *var, unsigned int *num); bool get_int_var(const char *source, const char *var, int *num); +cJSON *get_POST_JSON(struct mg_connection *conn); // HTTP macros #define GET_VAR(variable, destination, source) mg_get_var(source, strlen(source), variable, destination, sizeof(destination)) From a1ec17cca23b8269312ceb2d11f976f807a57313 Mon Sep 17 00:00:00 2001 From: DL6ER Date: Mon, 11 Jan 2021 20:02:06 +0100 Subject: [PATCH 0192/1669] Require authentication for api_stats_query_types Signed-off-by: DL6ER --- src/api/stats.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/api/stats.c b/src/api/stats.c index 3e3f1ab4e..9847825fa 100644 --- a/src/api/stats.c +++ b/src/api/stats.c @@ -557,6 +557,13 @@ int api_stats_upstreams(struct mg_connection *conn) int api_stats_query_types(struct mg_connection *conn) { + // Verify requesting client is allowed to see this ressource + if(check_client_auth(conn) < 0) + { + return send_json_unauthorized(conn); + } + + // Send response cJSON *json = JSON_NEW_ARRAY(); for(int i = TYPE_A; i < TYPE_MAX; i++) { From b95559bc28c3c4e734ebb5becd8a34cbb111ed08 Mon Sep 17 00:00:00 2001 From: DL6ER Date: Mon, 11 Jan 2021 20:02:30 +0100 Subject: [PATCH 0193/1669] Increase web service thread count to 16 Signed-off-by: DL6ER --- src/webserver/webserver.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/webserver/webserver.c b/src/webserver/webserver.c index c3caa2572..75365bc8a 100644 --- a/src/webserver/webserver.c +++ b/src/webserver/webserver.c @@ -144,7 +144,7 @@ void http_init(void) "listening_ports", httpsettings.port, "decode_url", "no", "enable_directory_listing", "no", - "num_threads", "4", + "num_threads", "16", "access_control_list", httpsettings.acl, "additional_header", "Content-Security-Policy: default-src 'self' 'unsafe-inline';\r\n" "X-Frame-Options: SAMEORIGIN\r\n" From 68ee65836916206df5943c080145ba2c0c26f521 Mon Sep 17 00:00:00 2001 From: DL6ER Date: Tue, 12 Jan 2021 05:50:51 +0100 Subject: [PATCH 0194/1669] Retore errno in FTL's pthread syscall Signed-off-by: DL6ER --- src/shmem.c | 7 +++---- src/syscalls/pthread_mutex_lock.c | 12 ++++++++---- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/src/shmem.c b/src/shmem.c index 03bb3d8c1..35b32f712 100644 --- a/src/shmem.c +++ b/src/shmem.c @@ -343,7 +343,9 @@ void _lock_shm(const char* func, const int line, const char * file) { int result = pthread_mutex_lock(&shmLock->lock); - if(config.debug & DEBUG_LOCKS) + if(result != 0) + logg("Failed to obtain SHM lock: %s in %s() (%s:%i)", strerror(result), func, file, line); + else if(config.debug & DEBUG_LOCKS) logg("Obtained lock for %s() (%s:%i)", func, file, line); // Check if this process needs to remap the shared memory objects @@ -365,9 +367,6 @@ void _lock_shm(const char* func, const int line, const char * file) { // holding the lock result = pthread_mutex_consistent(&shmLock->lock); } - - if(result != 0) - logg("Failed to obtain SHM lock: %s in %s() (%s:%i)", strerror(result), func, file, line); } void _unlock_shm(const char* func, const int line, const char * file) { diff --git a/src/syscalls/pthread_mutex_lock.c b/src/syscalls/pthread_mutex_lock.c index da741764b..150829ba6 100644 --- a/src/syscalls/pthread_mutex_lock.c +++ b/src/syscalls/pthread_mutex_lock.c @@ -17,22 +17,26 @@ #undef pthread_mutex_lock int FTLpthread_mutex_lock(pthread_mutex_t *__mutex, const char *file, const char *func, const int line) { - ssize_t ret = 0; + int errno_bck = 0; + ssize_t ret = 0; do { // Reset errno before trying to write errno = 0; ret = pthread_mutex_lock(__mutex); + errno_bck = errno; } // Try again if the last accept() call failed due to an interruption by an // incoming signal while(ret < 0 && errno == EINTR); - // Final error checking (may have faild for some other reason then an + // Final errno checking (may have faild for some other reason then an // EINTR = interrupted system call) if(ret < 0) logg("WARN: Could not pthread_mutex_lock() in %s() (%s:%i): %s", - func, file, line, strerror(errno)); + func, file, line, strerror(errno)); - return ret; + // Restore errno (may have been altered by logg()) + errno = errno_bck; + return ret; } \ No newline at end of file From 5f110140c9a1e4c49c1e90540ea500b7ed069db2 Mon Sep 17 00:00:00 2001 From: DL6ER Date: Tue, 12 Jan 2021 19:52:56 +0100 Subject: [PATCH 0195/1669] Improve shared memory lock mutexes Signed-off-by: DL6ER --- src/shmem.c | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/src/shmem.c b/src/shmem.c index 35b32f712..1e33854dd 100644 --- a/src/shmem.c +++ b/src/shmem.c @@ -295,11 +295,28 @@ static pthread_mutex_t create_mutex(void) { pthread_mutexattr_init(&lock_attr); // Allow the lock to be used by other processes + // Mutexes created with this attributes object can be shared between any + // threads that have access to the memory containing the object, + // including threads in different processes. pthread_mutexattr_setpshared(&lock_attr, PTHREAD_PROCESS_SHARED); - // Make the lock robust against process death + // Make the lock robust against thread death + // If a mutex is initialized with the PTHREAD_MUTEX_ROBUST attribute and + // its owner dies without unlocking it, any future attempts to call + // pthread_mutex_lock(3) on this mutex will succeed and return + // EOWNERDEAD to indicate that the original owner no longer exists and + // the mutex is in an inconsistent state. pthread_mutexattr_setrobust(&lock_attr, PTHREAD_MUTEX_ROBUST); + // Enabled pthread error checking + // - A thread attempting to relock this mutex without first unlocking it + // shall return with an error (EDEADLK). + // - A thread attempting to unlock a mutex which another thread has + // locked shall return with an error (EPERM). + // - A thread attempting to unlock an unlocked mutex shall return with + // an error (EPERM). + pthread_mutexattr_settype(&lock_attr, PTHREAD_MUTEX_ERRORCHECK); + // Initialize the lock pthread_mutex_init(&lock, &lock_attr); @@ -372,11 +389,10 @@ void _lock_shm(const char* func, const int line, const char * file) { void _unlock_shm(const char* func, const int line, const char * file) { int result = pthread_mutex_unlock(&shmLock->lock); - if(config.debug & DEBUG_LOCKS) - logg("Removed lock in %s() (%s:%i)", func, file, line); - if(result != 0) logg("Failed to unlock SHM lock: %s in %s() (%s:%i)", strerror(result), func, file, line); + else if(config.debug & DEBUG_LOCKS) + logg("Removed lock in %s() (%s:%i)", func, file, line); } bool init_shmem(bool create_new) From 7394722185c0875869334709b305f6d06c7aabb4 Mon Sep 17 00:00:00 2001 From: DL6ER Date: Wed, 13 Jan 2021 20:01:01 +0100 Subject: [PATCH 0196/1669] Improve log file locking Signed-off-by: DL6ER --- src/api/routes.c | 4 + src/log.c | 134 +++++++++++++++++------------- src/log.h | 7 +- src/main.c | 4 +- src/shmem.c | 39 ++++++--- src/shmem.h | 8 ++ src/syscalls/pthread_mutex_lock.c | 18 ++-- 7 files changed, 130 insertions(+), 84 deletions(-) diff --git a/src/api/routes.c b/src/api/routes.c index 4647e8dc7..bd680633b 100644 --- a/src/api/routes.c +++ b/src/api/routes.c @@ -15,6 +15,7 @@ #include "../webserver/json_macros.h" #include "routes.h" #include "../shmem.h" +#include "../config.h" int api_handler(struct mg_connection *conn, void *ignored) { @@ -24,6 +25,9 @@ int api_handler(struct mg_connection *conn, void *ignored) int ret = 0; const struct mg_request_info *request = mg_get_request_info(conn); + if(config.debug & DEBUG_API) + logg("Requested API URI: %s", request->local_uri); + /******************************** /api/dns ********************************/ if(startsWith("/api/dns/blocking", request->local_uri)) { diff --git a/src/log.c b/src/log.c index 5f1713c6f..32b442de3 100644 --- a/src/log.c +++ b/src/log.c @@ -27,7 +27,6 @@ static bool locks_initialized = false; static pthread_mutex_t FTL_log_lock, web_log_lock; -static FILE *logfile = NULL; static bool FTL_log_ready = false; static bool print_log = true, print_stdout = true; @@ -37,42 +36,54 @@ void log_ctrl(bool plog, bool pstdout) print_stdout = pstdout; } -static void close_FTL_log(void) -{ - if(logfile != NULL) - fclose(logfile); -} - static void initialize_locks(void) { + // Initialize the lock attributes + pthread_mutexattr_t lock_attr = {}; + pthread_mutexattr_init(&lock_attr); + + // Make the lock robust against thread death + // If a mutex is initialized with the PTHREAD_MUTEX_ROBUST attribute and + // its owner dies without unlocking it, any future attempts to call + // pthread_mutex_lock(3) on this mutex will succeed and return + // EOWNERDEAD to indicate that the original owner no longer exists and + // the mutex is in an inconsistent state. + pthread_mutexattr_setrobust(&lock_attr, PTHREAD_MUTEX_ROBUST); + + // Enabled pthread error checking + // - A thread attempting to relock this mutex without first unlocking it + // shall return with an error (EDEADLK). + // - A thread attempting to unlock a mutex which another thread has + // locked shall return with an error (EPERM). + // - A thread attempting to unlock an unlocked mutex shall return with + // an error (EPERM). + pthread_mutexattr_settype(&lock_attr, PTHREAD_MUTEX_ERRORCHECK); + // Initialize logging mutex - if (pthread_mutex_init(&FTL_log_lock, NULL) != 0) + if (pthread_mutex_init(&FTL_log_lock, &lock_attr) != 0) { printf("FATAL: Log mutex init for FTL failed\n"); // Return failure exit(EXIT_FAILURE); } - if (pthread_mutex_init(&web_log_lock, NULL) != 0) + if (pthread_mutex_init(&web_log_lock, &lock_attr) != 0) { printf("FATAL: Log mutex init for web failed\n"); // Return failure exit(EXIT_FAILURE); } + + locks_initialized = true; } -void open_FTL_log(const bool init) +void init_FTL_log(void) { - if(!locks_initialized) - initialize_locks(); + getLogFilePath(); - if(init) - // Obtain log file location - getLogFilePath(); - - // Open the log file in append/create mode - logfile = fopen(FTLfiles.log, "a+"); - if((logfile == NULL) && init){ + // Try to open the log file in append/create mode + FILE *logfile = fopen(FTLfiles.log, "a+"); + if(logfile == NULL){ syslog(LOG_ERR, "Opening of FTL\'s log file failed!"); printf("FATAL: Opening of FTL log (%s) failed!\n",FTLfiles.log); printf(" Make sure it exists and is writeable by user %s\n", username); @@ -83,10 +94,8 @@ void open_FTL_log(const bool init) // Set log as ready (we were able to open it) FTL_log_ready = true; - if(init) - { - close_FTL_log(); - } + // Close log file + fclose(logfile); } // The size of 84 bytes has been carefully selected for all possible timestamps @@ -105,31 +114,11 @@ void get_timestr(char * const timestring, const time_t timein) tm.tm_hour, tm.tm_min, tm.tm_sec, millisec); } -static void open_web_log(const enum web_code code) +void _FTL_log(const bool newline, const char *func, const char *file, const int line, const char *format, ...) { if(!locks_initialized) initialize_locks(); - // Open the log file in append/create mode - char *file = NULL; - switch (code) - { - case HTTP_INFO: - file = httpsettings.log_info; - break; - case PH7_ERROR: - file = httpsettings.log_error; - break; - default: - file = httpsettings.log_error; - break; - } - - logfile = fopen(file, "a+"); -} - -void _FTL_log(const bool newline, const char *format, ...) -{ char timestring[84] = ""; va_list args; @@ -137,8 +126,10 @@ void _FTL_log(const bool newline, const char *format, ...) if(!print_log && !print_stdout) return; + // Lock mutex pthread_mutex_lock(&FTL_log_lock); + // Get human-readable time get_timestr(timestring, time(NULL)); // Get and log PID of current process to avoid ambiguities when more than one @@ -177,10 +168,11 @@ void _FTL_log(const bool newline, const char *format, ...) printf("\n"); } + // Write to file if ready if(print_log && FTL_log_ready) { // Open log file - open_FTL_log(false); + FILE *logfile = fopen(FTLfiles.log, "a+"); // Write to log file if(logfile != NULL) @@ -190,6 +182,9 @@ void _FTL_log(const bool newline, const char *format, ...) vfprintf(logfile, format, args); va_end(args); fputc('\n',logfile); + + // Close file after writing + fclose(logfile); } else if(!daemonmode) { @@ -198,35 +193,60 @@ void _FTL_log(const bool newline, const char *format, ...) } } - // Close log file - close_FTL_log(); + // Unlock mutex pthread_mutex_unlock(&FTL_log_lock); } +static FILE *open_web_log(const enum web_code code) +{ + // Open the log file in append/create mode + char *file = NULL; + switch (code) + { + case HTTP_INFO: + file = httpsettings.log_info; + break; + case PH7_ERROR: + file = httpsettings.log_error; + break; + default: + file = httpsettings.log_error; + break; + } + + return fopen(file, "a+"); +} + void __attribute__ ((format (gnu_printf, 2, 3))) logg_web(enum web_code code, const char *format, ...) { char timestring[84] = ""; va_list args; + if(!locks_initialized) + initialize_locks(); + + // Lock mutex pthread_mutex_lock(&web_log_lock); + // Get human-readable time get_timestr(timestring, time(NULL)); // Get and log PID of current process to avoid ambiguities when more than one // pihole-FTL instance is logging into the same file const long pid = (long)getpid(); - // Open log file - open_web_log(code); + // Open web log file + FILE *weblog = open_web_log(code); - // Write to log file - if(logfile != NULL) + // Write to web log file + if(weblog != NULL) { - fprintf(logfile, "[%s %ld] ", timestring, pid); + fprintf(weblog, "[%s %ld] ", timestring, pid); va_start(args, format); - vfprintf(logfile, format, args); + vfprintf(weblog, format, args); va_end(args); - fputc('\n',logfile); + fputc('\n',weblog); + fclose(weblog); } else if(!daemonmode) { @@ -234,8 +254,7 @@ void __attribute__ ((format (gnu_printf, 2, 3))) logg_web(enum web_code code, co syslog(LOG_ERR, "Writing to web log file failed!"); } - // Close FTL log file - close_FTL_log(); + // Unlock mutex pthread_mutex_unlock(&web_log_lock); } @@ -246,6 +265,9 @@ void FTL_log_helper(const unsigned char n, ...) if(!(config.debug & DEBUG_HELPER)) return; + if(!locks_initialized) + initialize_locks(); + // Extract all variable arguments va_list args; char *arg[n]; diff --git a/src/log.h b/src/log.h index 9f22269db..a293cb213 100644 --- a/src/log.h +++ b/src/log.h @@ -16,7 +16,6 @@ #include "enums.h" void init_FTL_log(void); -void open_FTL_log(const bool init); void log_counter_info(void); void format_memory_size(char * const prefix, unsigned long long int bytes, double * const formated); @@ -29,9 +28,9 @@ const char *get_ordinal_suffix(unsigned int number) __attribute__ ((const)); // The actual logging routine can take extra options for specialized logging // The more general interfaces can be defined here as appropriate shortcuts -#define logg(format, ...) _FTL_log(true, format, ## __VA_ARGS__) -#define logg_sameline(format, ...) _FTL_log(false, format, ## __VA_ARGS__) -void _FTL_log(const bool newline, const char* format, ...) __attribute__ ((format (gnu_printf, 2, 3))); +#define logg(format, ...) _FTL_log(true, __FUNCTION__, __FILE__, __LINE__, format, ## __VA_ARGS__) +#define logg_sameline(format, ...) _FTL_log(false, __FUNCTION__, __FILE__, __LINE__, format, ## __VA_ARGS__) +void _FTL_log(const bool newline, const char *func, const char *file, const int line, const char* format, ...) __attribute__ ((format (gnu_printf, 5, 6))); void FTL_log_dnsmasq_fatal(const char *format, ...) __attribute__ ((format (gnu_printf, 1, 2))); void log_ctrl(bool vlog, bool vstdout); void FTL_log_helper(const unsigned char n, ...); diff --git a/src/main.c b/src/main.c index e8f97b7a3..5400eb62f 100644 --- a/src/main.c +++ b/src/main.c @@ -45,8 +45,8 @@ int main (int argc, char* argv[]) // to have arg{c,v}_dnsmasq initialized parse_args(argc, argv); - // Try to open FTL log - open_FTL_log(true); + // Initialize FTL log + init_FTL_log(); timer_start(EXIT_TIMER); logg("########## FTL started! ##########"); log_FTL_version(false); diff --git a/src/shmem.c b/src/shmem.c index 1e33854dd..719c65da8 100644 --- a/src/shmem.c +++ b/src/shmem.c @@ -21,6 +21,10 @@ #include // get_num_regex() #include "regex_r.h" +// sleepms() +#include "timers.h" +// FTL_gettid +#include "daemon.h" /// The version of shared memory used #define SHARED_MEMORY_VERSION 10 @@ -286,10 +290,9 @@ const char *getstr(const size_t pos) } /// Create a mutex for shared memory -static pthread_mutex_t create_mutex(void) { - logg("Creating mutex"); +static void create_mutex(pthread_mutex_t *lock) { + logg("Creating SHM mutex lock"); pthread_mutexattr_t lock_attr = {}; - pthread_mutex_t lock = {}; // Initialize the lock attributes pthread_mutexattr_init(&lock_attr); @@ -318,12 +321,10 @@ static pthread_mutex_t create_mutex(void) { pthread_mutexattr_settype(&lock_attr, PTHREAD_MUTEX_ERRORCHECK); // Initialize the lock - pthread_mutex_init(&lock, &lock_attr); + pthread_mutex_init(lock, &lock_attr); // Destroy the lock attributes since we're done with it pthread_mutexattr_destroy(&lock_attr); - - return lock; } static void remap_shm(void) @@ -351,20 +352,38 @@ static void remap_shm(void) local_shm_counter = shmSettings->global_shm_counter; } +static lockInfoStruct shm_lockInfo = INIT_LOCK_INFO; void _lock_shm(const char* func, const int line, const char * file) { // Signal that FTL is waiting for a lock shmLock->waitingForLock = true; if(config.debug & DEBUG_LOCKS) - logg("Waiting for lock in %s() (%s:%i)", func, file, line); + { + logg("Waiting for SHM lock in %s() (%s:%i)\n" + " -> currently lock by %i/%i in %s()", + func, file, line, + shm_lockInfo.pid, + shm_lockInfo.tid, + shm_lockInfo.func); + } - int result = pthread_mutex_lock(&shmLock->lock); + int result; + while((result = pthread_mutex_trylock(&shmLock->lock)) == EBUSY) + sleepms(10); if(result != 0) logg("Failed to obtain SHM lock: %s in %s() (%s:%i)", strerror(result), func, file, line); else if(config.debug & DEBUG_LOCKS) logg("Obtained lock for %s() (%s:%i)", func, file, line); + // Store lock info in debug mode + if(config.debug & DEBUG_LOCKS) + { + shm_lockInfo.func = func; + shm_lockInfo.pid = getpid(); + shm_lockInfo.tid = gettid(); + } + // Check if this process needs to remap the shared memory objects if(shmSettings != NULL && local_shm_counter != shmSettings->global_shm_counter) @@ -392,7 +411,7 @@ void _unlock_shm(const char* func, const int line, const char * file) { if(result != 0) logg("Failed to unlock SHM lock: %s in %s() (%s:%i)", strerror(result), func, file, line); else if(config.debug & DEBUG_LOCKS) - logg("Removed lock in %s() (%s:%i)", func, file, line); + logg("Removed SHM lock in %s() (%s:%i)", func, file, line); } bool init_shmem(bool create_new) @@ -408,7 +427,7 @@ bool init_shmem(bool create_new) shmLock = (ShmLock*) shm_lock.ptr; if(create_new) { - shmLock->lock = create_mutex(); + create_mutex(&shmLock->lock); shmLock->waitingForLock = false; } diff --git a/src/shmem.h b/src/shmem.h index 9d1f2c34b..8863154c7 100644 --- a/src/shmem.h +++ b/src/shmem.h @@ -56,6 +56,14 @@ typedef struct { unsigned int regex_change; } countersStruct; +typedef struct { + const char *func; + pid_t pid; + int tid; +} lockInfoStruct; + +#define INIT_LOCK_INFO {"", -1, -1} + extern countersStruct *counters; /// Create shared memory diff --git a/src/syscalls/pthread_mutex_lock.c b/src/syscalls/pthread_mutex_lock.c index 150829ba6..f3208cb77 100644 --- a/src/syscalls/pthread_mutex_lock.c +++ b/src/syscalls/pthread_mutex_lock.c @@ -17,26 +17,20 @@ #undef pthread_mutex_lock int FTLpthread_mutex_lock(pthread_mutex_t *__mutex, const char *file, const char *func, const int line) { - int errno_bck = 0; - ssize_t ret = 0; + int ret = 0; do { - // Reset errno before trying to write - errno = 0; ret = pthread_mutex_lock(__mutex); - errno_bck = errno; } // Try again if the last accept() call failed due to an interruption by an // incoming signal - while(ret < 0 && errno == EINTR); + while(ret == EINTR); - // Final errno checking (may have faild for some other reason then an + // Final errer checking (may have faild for some other reason then an // EINTR = interrupted system call) - if(ret < 0) + if(ret != 0) logg("WARN: Could not pthread_mutex_lock() in %s() (%s:%i): %s", - func, file, line, strerror(errno)); + func, file, line, strerror(ret)); - // Restore errno (may have been altered by logg()) - errno = errno_bck; return ret; -} \ No newline at end of file +} From ae713a9d69d33cc2623a62f55e3fdba246c95bd8 Mon Sep 17 00:00:00 2001 From: DL6ER Date: Wed, 13 Jan 2021 20:19:48 +0100 Subject: [PATCH 0197/1669] Request challange over /api/auth/login Signed-off-by: DL6ER --- src/api/auth.c | 219 +++++++++++++++++++++-------------------------- src/api/routes.c | 8 +- src/api/routes.h | 1 - src/config.c | 19 ++-- src/setupVars.c | 21 +++++ src/setupVars.h | 1 + 6 files changed, 128 insertions(+), 141 deletions(-) diff --git a/src/api/auth.c b/src/api/auth.c index 97c26e8e6..8ed70d266 100644 --- a/src/api/auth.c +++ b/src/api/auth.c @@ -127,27 +127,6 @@ int check_client_auth(struct mg_connection *conn) return user_id; } -// Source password hash from setupVars.conf -static __attribute__((malloc)) char *get_password_hash(void) -{ - // Try to obtain password from setupVars.conf - const char* password = read_setupVarsconf("WEBPASSWORD"); - - // If the value was not set (or we couldn't open the file for reading), - // we hand an empty string back to the caller - if(password == NULL || (password != NULL && strlen(password) == 0u)) - { - password = ""; - } - - char *hash = strdup(password); - - // Free memory, harmless to call if read_setupVarsconf() didn't return a result - clearSetupVarsArray(); - - return hash; -} - // Check received response static bool check_response(const char *response) { @@ -178,7 +157,11 @@ static int send_api_auth_status(struct mg_connection *conn, const int user_id, c { if(config.debug & DEBUG_API) logg("API Authentification: OK (localhost does not need auth)"); - return send_json_success(conn); + + cJSON *json = JSON_NEW_OBJ(); + JSON_OBJ_REF_STR(json, "status", "success"); + JSON_OBJ_ADD_NULL(json, "sid"); + JSON_SEND_OBJECT(json); } if(user_id > -1 && method == HTTP_GET) { @@ -187,6 +170,8 @@ static int send_api_auth_status(struct mg_connection *conn, const int user_id, c cJSON *json = JSON_NEW_OBJ(); JSON_OBJ_REF_STR(json, "status", "success"); + JSON_OBJ_REF_STR(json, "sid", auth_data[user_id].sid); + // Ten minutes validity if(snprintf(pi_hole_extra_headers, sizeof(pi_hole_extra_headers), "Set-Cookie: sid=%s; Path=/; Max-Age=%u\r\n", @@ -194,8 +179,8 @@ static int send_api_auth_status(struct mg_connection *conn, const int user_id, c { return send_json_error(conn, 500, "internal_error", "Internal server error", NULL); } - - return send_json_success(conn); + + JSON_SEND_OBJECT(json); } else if(user_id > -1 && method == HTTP_DELETE) { @@ -219,6 +204,45 @@ static int send_api_auth_status(struct mg_connection *conn, const int user_id, c } } +static void generateChallenge(const unsigned int idx, const time_t now) +{ + uint8_t raw_challenge[SHA256_DIGEST_SIZE]; + for(unsigned i = 0; i < SHA256_DIGEST_SIZE; i+= 2) + { + const int rval = rand(); + raw_challenge[i] = rval & 0xFF; + raw_challenge[i+1] = (rval >> 8) & 0xFF; + } + sha256_hex(raw_challenge, challenges[idx].challenge); + challenges[idx].valid_until = now + API_CHALLENGE_TIMEOUT; +} + +static void generateResponse(const unsigned int idx) +{ + uint8_t raw_response[SHA256_DIGEST_SIZE]; + struct sha256_ctx ctx; + sha256_init(&ctx); + + // Add challenge in hex representation + sha256_update(&ctx, + sizeof(challenges[idx].challenge)-1, + (uint8_t*)challenges[idx].challenge); + + // Add separator + sha256_update(&ctx, 1, (uint8_t*)":"); + + // Get and add password hash from setupVars.conf + char *password_hash = get_password_hash(); + sha256_update(&ctx, + strlen(password_hash), + (uint8_t*)password_hash); + free(password_hash); + password_hash = NULL; + + sha256_digest(&ctx, SHA256_DIGEST_SIZE, raw_response); + sha256_hex(raw_response, challenges[idx].response); +} + int api_auth(struct mg_connection *conn) { // Check HTTP method @@ -256,12 +280,13 @@ int api_auth_login(struct mg_connection *conn) char *password_hash = get_password_hash(); const struct mg_request_info *request = mg_get_request_info(conn); - // Does the client try to authenticate using challenge-response or is there no password on this machine? char response[256] = { 0 }; const bool reponse_set = request->query_string != NULL && GET_VAR("response", response, request->query_string) > 0; const bool empty_password = (strlen(password_hash) == 0u); if(reponse_set || empty_password ) { + // - Client tries to authenticate using a challenge response, or + // - There no password on this machine if(check_response(response) || empty_password) { // Accepted @@ -314,11 +339,56 @@ int api_auth_login(struct mg_connection *conn) logg("API: Response incorrect. Response=%s, setupVars=%s", response, password_hash); } + // Free allocated memory + free(password_hash); + password_hash = NULL; + return send_api_auth_status(conn, user_id, HTTP_GET); } - free(password_hash); - password_hash = NULL; + else + { + // Client wants to get a challenge + // Generate a challenge + unsigned int i; + const time_t now = time(NULL); - return send_api_auth_status(conn, user_id, HTTP_GET); + // Get an empty/expired slot + for(i = 0; i < API_MAX_CHALLENGES; i++) + if(challenges[i].valid_until < now) + break; + + // If there are no empty/expired slots, then find the oldest challenge + // and replace it + if(i == API_MAX_CHALLENGES) + { + unsigned int minidx = 0; + time_t minval = now; + for(i = 0; i < API_MAX_CHALLENGES; i++) + { + if(challenges[i].valid_until < minval) + { + minval = challenges[i].valid_until; + minidx = i; + } + } + i = minidx; + } + + // Generate and store new challenge + generateChallenge(i, now); + + // Compute and store expected response for this challenge (SHA-256) + generateResponse(i); + + // Free allocated memory + free(password_hash); + password_hash = NULL; + + // Return to user + cJSON *json = JSON_NEW_OBJ(); + JSON_OBJ_REF_STR(json, "challenge", challenges[i].challenge); + JSON_OBJ_ADD_NUMBER(json, "valid_until", challenges[i].valid_until); + JSON_SEND_OBJECT(json); + } } int api_auth_logout(struct mg_connection *conn) @@ -332,96 +402,3 @@ int api_auth_logout(struct mg_connection *conn) int user_id = check_client_auth(conn); return send_api_auth_status(conn, user_id, HTTP_DELETE); } - -/* -// All printable ASCII characters, c.f., https://www.asciitable.com/ -// Inspired by https://codereview.stackexchange.com/a/194388 -// Randomness: rougly 6 Bit per Byte -// Challenge "strength": (0x7E-0x20)/256*8*44 = 129.25 Bit -#define ASCII_BEG 0x20 -#define ASCII_END 0x7E -static void generateRandomString(char *str, size_t size) -{ - for(size_t i = 0u; i < size-1u; i++) - str[i] = (char) (rand()%(ASCII_END-ASCII_BEG))+ASCII_BEG; - - str[size-1] = '\0'; -} -*/ - -static void generateChallenge(const unsigned int idx, const time_t now) -{ - uint8_t raw_challenge[SHA256_DIGEST_SIZE]; - for(unsigned i = 0; i < SHA256_DIGEST_SIZE; i+= 2) - { - const int rval = rand(); - raw_challenge[i] = rval & 0xFF; - raw_challenge[i+1] = (rval >> 8) & 0xFF; - } - sha256_hex(raw_challenge, challenges[idx].challenge); - challenges[idx].valid_until = now + API_CHALLENGE_TIMEOUT; -} - -static void generateResponse(const unsigned int idx) -{ - uint8_t raw_response[SHA256_DIGEST_SIZE]; - struct sha256_ctx ctx; - sha256_init(&ctx); - - // Add challenge in hex representation - sha256_update(&ctx, - sizeof(challenges[idx].challenge)-1, - (uint8_t*)challenges[idx].challenge); - - // Get and add password hash from setupVars.conf - char *password_hash = get_password_hash(); - sha256_update(&ctx, - strlen(password_hash), - (uint8_t*)password_hash); - free(password_hash); - password_hash = NULL; - - sha256_digest(&ctx, SHA256_DIGEST_SIZE, raw_response); - sha256_hex(raw_response, challenges[idx].response); -} - -int api_auth_challenge(struct mg_connection *conn) -{ - // Generate a challenge - unsigned int i; - const time_t now = time(NULL); - - // Get an empty/expired slot - for(i = 0; i < API_MAX_CHALLENGES; i++) - if(challenges[i].valid_until < now) - break; - - // If there are no empty/expired slots, then find the oldest challenge - // and replace it - if(i == API_MAX_CHALLENGES) - { - unsigned int minidx = 0; - time_t minval = now; - for(i = 0; i < API_MAX_CHALLENGES; i++) - { - if(challenges[i].valid_until < minval) - { - minval = challenges[i].valid_until; - minidx = i; - } - } - i = minidx; - } - - // Generate and store new challenge - generateChallenge(i, now); - - // Compute and store expected response for this challenge (SHA-256) - generateResponse(i); - - // Return to user - cJSON *json = JSON_NEW_OBJ(); - JSON_OBJ_REF_STR(json, "challenge", challenges[i].challenge); - JSON_OBJ_ADD_NUMBER(json, "valid_until", challenges[i].valid_until); - JSON_SEND_OBJECT(json); -} \ No newline at end of file diff --git a/src/api/routes.c b/src/api/routes.c index bd680633b..accaab1ac 100644 --- a/src/api/routes.c +++ b/src/api/routes.c @@ -160,10 +160,6 @@ int api_handler(struct mg_connection *conn, void *ignored) { ret = api_auth_login(conn); } - else if(startsWith("/api/auth/challenge", request->local_uri)) - { - ret = api_auth_challenge(conn); - } else if(startsWith("/api/auth/logout", request->local_uri)) { ret = api_auth_logout(conn); @@ -177,8 +173,8 @@ int api_handler(struct mg_connection *conn, void *ignored) { ret = api_settings_web(conn); } - /******************************** not found ******************************/ - else + /******************************** not found or invalid request**************/ + if(ret == 0) { cJSON *json = JSON_NEW_OBJ(); JSON_OBJ_REF_STR(json, "path", request->local_uri); diff --git a/src/api/routes.h b/src/api/routes.h index d55b9d2d7..c28d9babf 100644 --- a/src/api/routes.h +++ b/src/api/routes.h @@ -58,7 +58,6 @@ int check_client_auth(struct mg_connection *conn); int api_auth(struct mg_connection *conn); int api_auth_login(struct mg_connection *conn); int api_auth_logout(struct mg_connection *conn); -int api_auth_challenge(struct mg_connection *conn); // Settings methods int api_settings_web(struct mg_connection *conn); diff --git a/src/config.c b/src/config.c index 0a43ad4b2..b1f312372 100644 --- a/src/config.c +++ b/src/config.c @@ -395,19 +395,15 @@ void read_FTLconf(void) logg(" WEBACL: Allowing all access."); } - // API_AUTH_FOR_LOALHOST + // API_AUTH_FOR_LOCALHOST // defaults to: false - httpsettings.api_auth_for_localhost = false; - buffer = parse_FTLconf(fp, "API_AUTH_FOR_LOALHOST"); - - if(buffer != NULL && (strcasecmp(buffer, "yes") == 0 || - strcasecmp(buffer, "true") == 0)) - httpsettings.api_auth_for_localhost = true; + buffer = parse_FTLconf(fp, "API_AUTH_FOR_LOCALHOST"); + httpsettings.api_auth_for_localhost = read_bool(buffer, true); if(httpsettings.api_auth_for_localhost) - logg(" API_AUTH_FOR_LOCALHOST: Active"); + logg(" API_AUTH_FOR_LOCALHOST: Local devices need to login"); else - logg(" API_AUTH_FOR_LOCALHOST: Inactive"); + logg(" API_AUTH_FOR_LOCALHOST: Local devices do not need to login"); // API_SESSION_TIMEOUT // How long should a session be considered valid after login? @@ -424,11 +420,8 @@ void read_FTLconf(void) // API_PRETTY_JSON // defaults to: false - httpsettings.prettyJSON = false; buffer = parse_FTLconf(fp, "API_PRETTY_JSON"); - - if(buffer != NULL && strcasecmp(buffer, "true") == 0) - httpsettings.prettyJSON = true; + httpsettings.prettyJSON = read_bool(buffer, false); if(httpsettings.prettyJSON) logg(" API_PRETTY_JSON: Enabled. Using additional formatting in API output."); diff --git a/src/setupVars.c b/src/setupVars.c index 08ce8af93..0e81b2c83 100644 --- a/src/setupVars.c +++ b/src/setupVars.c @@ -266,3 +266,24 @@ void set_blockingstatus(bool enabled) blockingstatus = enabled; raise(SIGHUP); } + +// Source password hash from setupVars.conf +__attribute__((malloc)) char *get_password_hash(void) +{ + // Try to obtain password from setupVars.conf + const char* password = read_setupVarsconf("WEBPASSWORD"); + + // If the value was not set (or we couldn't open the file for reading), + // we hand an empty string back to the caller + if(password == NULL || (password != NULL && strlen(password) == 0u)) + { + password = ""; + } + + char *hash = strdup(password); + + // Free memory, harmless to call if read_setupVarsconf() didn't return a result + clearSetupVarsArray(); + + return hash; +} diff --git a/src/setupVars.h b/src/setupVars.h index 923a70d36..6cd38ef56 100644 --- a/src/setupVars.h +++ b/src/setupVars.h @@ -21,5 +21,6 @@ void trim_whitespace(char *string); void check_blocking_status(void); bool get_blockingstatus(void) __attribute__((pure)); void set_blockingstatus(bool enabled); +char *get_password_hash(void) __attribute__((malloc)); #endif //SETUPVARS_H From de8d8708e677da360374b3e4eb1feee3af7370e9 Mon Sep 17 00:00:00 2001 From: DL6ER Date: Wed, 13 Jan 2021 20:25:10 +0100 Subject: [PATCH 0198/1669] Improve used randomness generator Signed-off-by: DL6ER --- src/api/auth.c | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/api/auth.c b/src/api/auth.c index 8ed70d266..550719139 100644 --- a/src/api/auth.c +++ b/src/api/auth.c @@ -207,11 +207,13 @@ static int send_api_auth_status(struct mg_connection *conn, const int user_id, c static void generateChallenge(const unsigned int idx, const time_t now) { uint8_t raw_challenge[SHA256_DIGEST_SIZE]; - for(unsigned i = 0; i < SHA256_DIGEST_SIZE; i+= 2) + for(unsigned i = 0; i < SHA256_DIGEST_SIZE; i+= 4) { - const int rval = rand(); + const long rval = random(); raw_challenge[i] = rval & 0xFF; raw_challenge[i+1] = (rval >> 8) & 0xFF; + raw_challenge[i+2] = (rval >> 16) & 0xFF; + raw_challenge[i+3] = (rval >> 24) & 0xFF; } sha256_hex(raw_challenge, challenges[idx].challenge); challenges[idx].valid_until = now + API_CHALLENGE_TIMEOUT; @@ -258,11 +260,13 @@ int api_auth(struct mg_connection *conn) static void generateSID(char *sid) { uint8_t raw_sid[SID_SIZE]; - for(unsigned i = 0; i < (SID_BITSIZE/8); i+= 2) + for(unsigned i = 0; i < (SID_BITSIZE/8); i+= 4) { - const int rval = rand(); + const long rval = random(); raw_sid[i] = rval & 0xFF; raw_sid[i+1] = (rval >> 8) & 0xFF; + raw_sid[i+2] = (rval >> 16) & 0xFF; + raw_sid[i+3] = (rval >> 24) & 0xFF; } base64_encode_raw(sid, SID_BITSIZE/8, raw_sid); sid[SID_SIZE-1] = '\0'; From 5b6ad3c85336035a0ca8271c2cf7d75540395f4e Mon Sep 17 00:00:00 2001 From: DL6ER Date: Wed, 13 Jan 2021 20:32:50 +0100 Subject: [PATCH 0199/1669] Always report success when there is no password set Signed-off-by: DL6ER --- src/api/auth.c | 53 +++++++++++++++++++++++++++++++++++++------------- 1 file changed, 39 insertions(+), 14 deletions(-) diff --git a/src/api/auth.c b/src/api/auth.c index 550719139..707b5374c 100644 --- a/src/api/auth.c +++ b/src/api/auth.c @@ -53,6 +53,9 @@ static void sha256_hex(uint8_t *data, char *buffer) // Returns >= 0 for any valid authentication #define LOCALHOSTv4 "127.0.0.1" #define LOCALHOSTv6 "::1" +#define API_AUTH_UNUSED -1 +#define API_AUTH_LOCALHOST -2 +#define API_AUTH_EMPTYPASS -3 int check_client_auth(struct mg_connection *conn) { int user_id = -1; @@ -61,7 +64,14 @@ int check_client_auth(struct mg_connection *conn) // Is the user requesting from localhost? if(!httpsettings.api_auth_for_localhost && (strcmp(request->remote_addr, LOCALHOSTv4) == 0 || strcmp(request->remote_addr, LOCALHOSTv6) == 0)) - return API_MAX_CLIENTS; + return API_AUTH_LOCALHOST; + + // Check if there is a password hash + char *password_hash = get_password_hash(); + const bool empty_password = (strlen(password_hash) == 0u); + free(password_hash); + if(empty_password) + return API_AUTH_EMPTYPASS; // Does the client provide a session cookie? char sid[SID_SIZE]; @@ -91,7 +101,7 @@ int check_client_auth(struct mg_connection *conn) break; } } - if(user_id > -1) + if(user_id > API_AUTH_UNUSED) { // Authentication succesful: // - We know this client @@ -153,7 +163,7 @@ static bool check_response(const char *response) static int send_api_auth_status(struct mg_connection *conn, const int user_id, const int method) { - if(user_id == API_MAX_CLIENTS) + if(user_id == API_AUTH_LOCALHOST) { if(config.debug & DEBUG_API) logg("API Authentification: OK (localhost does not need auth)"); @@ -163,7 +173,19 @@ static int send_api_auth_status(struct mg_connection *conn, const int user_id, c JSON_OBJ_ADD_NULL(json, "sid"); JSON_SEND_OBJECT(json); } - if(user_id > -1 && method == HTTP_GET) + + if(user_id == API_AUTH_EMPTYPASS) + { + if(config.debug & DEBUG_API) + logg("API Authentification: OK (empty password)"); + + cJSON *json = JSON_NEW_OBJ(); + JSON_OBJ_REF_STR(json, "status", "success"); + JSON_OBJ_ADD_NULL(json, "sid"); + JSON_SEND_OBJECT(json); + } + + if(user_id > API_AUTH_UNUSED && method == HTTP_GET) { if(config.debug & DEBUG_API) logg("API Authentification: OK"); @@ -182,7 +204,7 @@ static int send_api_auth_status(struct mg_connection *conn, const int user_id, c JSON_SEND_OBJECT(json); } - else if(user_id > -1 && method == HTTP_DELETE) + else if(user_id > API_AUTH_UNUSED && method == HTTP_DELETE) { if(config.debug & DEBUG_API) logg("API Authentification: Revoking"); @@ -280,18 +302,20 @@ int api_auth_login(struct mg_connection *conn) if(method != HTTP_GET) return 0; // error 404 - int user_id = -1; char *password_hash = get_password_hash(); + const bool empty_password = (strlen(password_hash) == 0u); + + int user_id = API_AUTH_UNUSED; const struct mg_request_info *request = mg_get_request_info(conn); char response[256] = { 0 }; const bool reponse_set = request->query_string != NULL && GET_VAR("response", response, request->query_string) > 0; - const bool empty_password = (strlen(password_hash) == 0u); - if(reponse_set || empty_password ) + if(reponse_set || empty_password) { // - Client tries to authenticate using a challenge response, or // - There no password on this machine - if(check_response(response) || empty_password) + const bool response_correct = check_response(response); + if(response_correct || empty_password) { // Accepted time_t now = time(NULL); @@ -326,16 +350,17 @@ int api_auth_login(struct mg_connection *conn) } // Debug logging - if(config.debug & DEBUG_API && user_id > -1) + if(config.debug & DEBUG_API && user_id > API_AUTH_UNUSED) { char timestr[128]; get_timestr(timestr, auth_data[user_id].valid_until); - logg("API: Registered new user: user_id %i valid_until: %s remote_addr %s", - user_id, timestr, auth_data[user_id].remote_addr); + logg("API: Registered new user: user_id %i valid_until: %s remote_addr %s (%s)", + user_id, timestr, auth_data[user_id].remote_addr, + response_correct ? "correct response" : "empty password"); } - if(user_id == -1) + if(user_id == API_AUTH_UNUSED) { - logg("WARNING: No free API slots available, not authenticating user"); + logg("WARNING: No free API seats available, not authenticating user"); } } else if(config.debug & DEBUG_API) From 2c3cefe074e10ae6a5689f79e57e8efddc3a101b Mon Sep 17 00:00:00 2001 From: DL6ER Date: Thu, 14 Jan 2021 20:45:51 +0100 Subject: [PATCH 0200/1669] Ensure every auth object has the session object Signed-off-by: DL6ER --- src/api/auth.c | 133 +++++++++++++++++++++++++++------------- src/civetweb/civetweb.c | 49 +++++++++------ src/civetweb/civetweb.h | 6 +- 3 files changed, 124 insertions(+), 64 deletions(-) diff --git a/src/api/auth.c b/src/api/auth.c index 707b5374c..adcfc1d5c 100644 --- a/src/api/auth.c +++ b/src/api/auth.c @@ -27,9 +27,9 @@ static struct { bool used; time_t valid_until; - char *remote_addr; + char remote_addr[48]; // Large enough for IPv4 and IPv6 addresses, hard-coded in civetweb.h as mg_request_info.remote_addr char sid[SID_SIZE]; -} auth_data[API_MAX_CLIENTS] = {{false, 0, NULL, {0}}}; +} auth_data[API_MAX_CLIENTS] = {{false, 0, {0}, {0}}}; static struct { char challenge[2*SHA256_DIGEST_SIZE + 1]; @@ -64,7 +64,9 @@ int check_client_auth(struct mg_connection *conn) // Is the user requesting from localhost? if(!httpsettings.api_auth_for_localhost && (strcmp(request->remote_addr, LOCALHOSTv4) == 0 || strcmp(request->remote_addr, LOCALHOSTv6) == 0)) + { return API_AUTH_LOCALHOST; + } // Check if there is a password hash char *password_hash = get_password_hash(); @@ -86,7 +88,7 @@ int check_client_auth(struct mg_connection *conn) if(sid_avail) { - time_t now = time(NULL); + const time_t now = time(NULL); if(config.debug & DEBUG_API) logg("API: Read sid=%s", sid); @@ -125,23 +127,22 @@ int check_client_auth(struct mg_connection *conn) char timestr[128]; get_timestr(timestr, auth_data[user_id].valid_until); logg("API: Recognized known user: user_id %i valid_until: %s remote_addr %s", - user_id, timestr, auth_data[user_id].remote_addr); + user_id, timestr, auth_data[user_id].remote_addr); } } else if(config.debug & DEBUG_API) - logg("API Authentification: FAIL (cookie invalid/expired)"); + logg("API Authentification: FAIL (SID invalid/expired)"); } else if(config.debug & DEBUG_API) - logg("API Authentification: FAIL (no cookie provided)"); + logg("API Authentification: FAIL (no SID provided)"); return user_id; } // Check received response -static bool check_response(const char *response) +static bool check_response(const char *response, const time_t now) { // Loop over all responses and try to validate response - time_t now = time(NULL); for(unsigned int i = 0; i < API_MAX_CHALLENGES; i++) { // Skip expired entries @@ -161,38 +162,77 @@ static bool check_response(const char *response) return false; } -static int send_api_auth_status(struct mg_connection *conn, const int user_id, const int method) +static int get_session_object(struct mg_connection *conn, cJSON *json, const int user_id, const time_t now) +{ + // Authentication not needed + if(user_id == API_AUTH_LOCALHOST || user_id == API_AUTH_EMPTYPASS) + { + cJSON *session = JSON_NEW_OBJ(); + JSON_OBJ_ADD_BOOL(session, "valid", true); + JSON_OBJ_ADD_NULL(session, "sid"); + JSON_OBJ_ADD_NULL(session, "validity"); + JSON_OBJ_ADD_ITEM(json, "session", session); + return 0; + } + + // Valid session + if(user_id > API_AUTH_UNUSED && auth_data[user_id].used) + { + cJSON *session = JSON_NEW_OBJ(); + JSON_OBJ_ADD_BOOL(session, "valid", true); + JSON_OBJ_REF_STR(session, "sid", auth_data[user_id].sid); + JSON_OBJ_ADD_NUMBER(session, "validity", auth_data[user_id].valid_until - now); + JSON_OBJ_ADD_ITEM(json, "session", session); + return 0; + } + + // No valid session + cJSON *session = JSON_NEW_OBJ(); + JSON_OBJ_ADD_BOOL(session, "valid", false); + JSON_OBJ_ADD_NULL(session, "sid"); + JSON_OBJ_ADD_NULL(session, "validity"); + JSON_OBJ_ADD_ITEM(json, "session", session); + return 0; +} + +static void delete_session(const int user_id) +{ + // Skip if nothing to be done here + if(user_id < 0) + return; + + auth_data[user_id].used = false; + auth_data[user_id].valid_until = 0; + memset(auth_data[user_id].sid, 0, sizeof(auth_data[user_id].sid)); + memset(auth_data[user_id].remote_addr, 0, sizeof(auth_data[user_id].remote_addr)); +} + +static int send_api_auth_status(struct mg_connection *conn, const int user_id, const int method, const time_t now) { if(user_id == API_AUTH_LOCALHOST) { if(config.debug & DEBUG_API) - logg("API Authentification: OK (localhost does not need auth)"); + logg("API Auth status: OK (localhost does not need auth)"); cJSON *json = JSON_NEW_OBJ(); - JSON_OBJ_REF_STR(json, "status", "success"); - JSON_OBJ_ADD_NULL(json, "sid"); + get_session_object(conn, json, user_id, now); JSON_SEND_OBJECT(json); } if(user_id == API_AUTH_EMPTYPASS) { if(config.debug & DEBUG_API) - logg("API Authentification: OK (empty password)"); + logg("API Auth status: OK (empty password)"); cJSON *json = JSON_NEW_OBJ(); - JSON_OBJ_REF_STR(json, "status", "success"); - JSON_OBJ_ADD_NULL(json, "sid"); + get_session_object(conn, json, user_id, now); JSON_SEND_OBJECT(json); } if(user_id > API_AUTH_UNUSED && method == HTTP_GET) { if(config.debug & DEBUG_API) - logg("API Authentification: OK"); - - cJSON *json = JSON_NEW_OBJ(); - JSON_OBJ_REF_STR(json, "status", "success"); - JSON_OBJ_REF_STR(json, "sid", auth_data[user_id].sid); + logg("API Auth status: OK"); // Ten minutes validity if(snprintf(pi_hole_extra_headers, sizeof(pi_hole_extra_headers), @@ -202,27 +242,32 @@ static int send_api_auth_status(struct mg_connection *conn, const int user_id, c return send_json_error(conn, 500, "internal_error", "Internal server error", NULL); } + cJSON *json = JSON_NEW_OBJ(); + get_session_object(conn, json, user_id, now); JSON_SEND_OBJECT(json); } else if(user_id > API_AUTH_UNUSED && method == HTTP_DELETE) { if(config.debug & DEBUG_API) - logg("API Authentification: Revoking"); + logg("API Auth status: Revoking, asking to delete cookie"); - // Revoke client authentication. This slot can be used by a new client, afterwards. - auth_data[user_id].used = false; - auth_data[user_id].valid_until = 0; - free(auth_data[user_id].remote_addr); - auth_data[user_id].remote_addr = NULL; - // We leave the old SID, it will be replaced next time the slot is used + // Revoke client authentication. This slot can be used by a new client afterwards. + delete_session(user_id); strncpy(pi_hole_extra_headers, "Set-Cookie: sid=deleted; Path=/; Max-Age=-1\r\n", sizeof(pi_hole_extra_headers)); - return send_json_success(conn); + cJSON *json = JSON_NEW_OBJ(); + get_session_object(conn, json, user_id, now); + JSON_SEND_OBJECT_CODE(json, 410); // 410 Gone } else { + if(config.debug & DEBUG_API) + logg("API Auth status: Invalid, asking to delete cookie"); + strncpy(pi_hole_extra_headers, "Set-Cookie: sid=deleted; Path=/; Max-Age=-1\r\n", sizeof(pi_hole_extra_headers)); - return send_json_unauthorized(conn); + cJSON *json = JSON_NEW_OBJ(); + get_session_object(conn, json, user_id, now); + JSON_SEND_OBJECT_CODE(json, 401); // 401 Unauthorized } } @@ -276,7 +321,10 @@ int api_auth(struct mg_connection *conn) // Did the client authenticate before and we can validate this? int user_id = check_client_auth(conn); - return send_api_auth_status(conn, user_id, HTTP_GET); + + // Send back status, extending validity of the session + const time_t now = time(NULL); + return send_api_auth_status(conn, user_id, method, now); } static void generateSID(char *sid) @@ -302,6 +350,8 @@ int api_auth_login(struct mg_connection *conn) if(method != HTTP_GET) return 0; // error 404 + const time_t now = time(NULL); + char *password_hash = get_password_hash(); const bool empty_password = (strlen(password_hash) == 0u); @@ -314,11 +364,10 @@ int api_auth_login(struct mg_connection *conn) { // - Client tries to authenticate using a challenge response, or // - There no password on this machine - const bool response_correct = check_response(response); + const bool response_correct = check_response(response, now); if(response_correct || empty_password) { // Accepted - time_t now = time(NULL); for(unsigned int i = 0; i < API_MAX_CLIENTS; i++) { // Expired slow, mark as unused @@ -330,10 +379,7 @@ int api_auth_login(struct mg_connection *conn) logg("API: Session of client %u (%s) expired, freeing...", i, auth_data[i].remote_addr); } - auth_data[i].used = false; - auth_data[i].valid_until = 0; - free(auth_data[i].remote_addr); - auth_data[i].remote_addr = NULL; + delete_session(user_id); } // Found unused authentication slot (might have been freed before) @@ -341,7 +387,8 @@ int api_auth_login(struct mg_connection *conn) { auth_data[i].used = true; auth_data[i].valid_until = now + httpsettings.session_timeout; - auth_data[i].remote_addr = strdup(request->remote_addr); + strncpy(auth_data[i].remote_addr, request->remote_addr, sizeof(auth_data[i].remote_addr)); + auth_data[i].remote_addr[sizeof(auth_data[i].remote_addr)-1] = '\0'; generateSID(auth_data[i].sid); user_id = i; @@ -354,7 +401,7 @@ int api_auth_login(struct mg_connection *conn) { char timestr[128]; get_timestr(timestr, auth_data[user_id].valid_until); - logg("API: Registered new user: user_id %i valid_until: %s remote_addr %s (%s)", + logg("API: Registered new user: user_id %i valid_until: %s remote_addr %s (accepted due to %s)", user_id, timestr, auth_data[user_id].remote_addr, response_correct ? "correct response" : "empty password"); } @@ -371,14 +418,13 @@ int api_auth_login(struct mg_connection *conn) // Free allocated memory free(password_hash); password_hash = NULL; - return send_api_auth_status(conn, user_id, HTTP_GET); + return send_api_auth_status(conn, user_id, method, now); } else { // Client wants to get a challenge // Generate a challenge unsigned int i; - const time_t now = time(NULL); // Get an empty/expired slot for(i = 0; i < API_MAX_CHALLENGES; i++) @@ -415,7 +461,10 @@ int api_auth_login(struct mg_connection *conn) // Return to user cJSON *json = JSON_NEW_OBJ(); JSON_OBJ_REF_STR(json, "challenge", challenges[i].challenge); - JSON_OBJ_ADD_NUMBER(json, "valid_until", challenges[i].valid_until); + cJSON *validity = JSON_NEW_OBJ(); + JSON_OBJ_ADD_NUMBER(validity, "from", now); + JSON_OBJ_ADD_NUMBER(validity, "until", challenges[i].valid_until); + JSON_OBJ_ADD_ITEM(json, "validity", validity); JSON_SEND_OBJECT(json); } } @@ -429,5 +478,5 @@ int api_auth_logout(struct mg_connection *conn) // Did the client authenticate before and we can validate this? int user_id = check_client_auth(conn); - return send_api_auth_status(conn, user_id, HTTP_DELETE); + return send_api_auth_status(conn, user_id, HTTP_DELETE, time(NULL)); } diff --git a/src/civetweb/civetweb.c b/src/civetweb/civetweb.c index ad0eb5514..e4c3c6191 100644 --- a/src/civetweb/civetweb.c +++ b/src/civetweb/civetweb.c @@ -5093,32 +5093,43 @@ mg_send_http_error_impl(struct mg_connection *conn, /************************************** Pi-hole method **************************************/ -void my_send_http_error_headers(struct mg_connection *conn, - int status, const char* mime_type, - long long content_length) +int my_send_http_error_headers(struct mg_connection *conn, + int status, const char* mime_type, + long long content_length) { - /* Set status (for log) */ - conn->status_code = status; - const char *status_text = mg_get_response_code_text(conn, status); - mg_printf(conn, "HTTP/1.1 %d %s\r\n", status, status_text); + if ((mime_type == NULL) || (*mime_type == 0)) { + /* No content type defined: default to text/html */ + mime_type = "text/html"; + } mg_response_header_start(conn, status); send_no_cache_header(conn); send_additional_header(conn); + mg_response_header_add(conn, "Content-Type", mime_type, -1); + if (content_length < 0) { + /* Size not known. Use chunked encoding (HTTP/1.x) */ + if (conn->protocol_type == PROTOCOL_TYPE_HTTP1) { + /* Only HTTP/1.x defines "chunked" encoding, HTTP/2 does not*/ + mg_response_header_add(conn, "Transfer-Encoding", "chunked", -1); + } + } else { + char len[32]; + int trunc = 0; + mg_snprintf(conn, + &trunc, + len, + sizeof(len), + "%" UINT64_FMT, + (uint64_t)content_length); + if (!trunc) { + /* Since 32 bytes is enough to hold any 64 bit decimal number, + * !trunc is always true */ + mg_response_header_add(conn, "Content-Length", len, -1); + } + } mg_response_header_send(conn); - conn->must_close = 1; - char date[64]; - time_t curtime = time(NULL); - gmt_time_string(date, sizeof(date), &curtime); - mg_printf(conn, "Content-Type: %s\r\n" - "Date: %s\r\n" - "Connection: close\r\n", - mime_type, - date); - - mg_printf(conn, "Content-Length: %" UINT64_FMT "\r\n\r\n", - (uint64_t)content_length); + return 0; } /********************************************************************************************/ diff --git a/src/civetweb/civetweb.h b/src/civetweb/civetweb.h index 662f8ab26..59fdadf42 100644 --- a/src/civetweb/civetweb.h +++ b/src/civetweb/civetweb.h @@ -940,9 +940,9 @@ CIVETWEB_API int mg_send_http_error(struct mg_connection *conn, ...) PRINTF_ARGS(3, 4); /************************************** Pi-hole method **************************************/ -void my_send_http_error_headers(struct mg_connection *conn, - int status, const char* mime_type, - long long content_length); +int my_send_http_error_headers(struct mg_connection *conn, + int status, const char* mime_type, + long long content_length); // Buffer used for additional "Set-Cookie" headers #define PIHOLE_HEADERS_MAXLEN 1024 From 2af933ce884f77b4ff8d9179fcfdfa7c2a69902b Mon Sep 17 00:00:00 2001 From: DL6ER Date: Thu, 14 Jan 2021 20:58:15 +0100 Subject: [PATCH 0201/1669] Implement login as POST to /api/auth, logout as DELETE to /api/auth Signed-off-by: DL6ER --- src/api/auth.c | 118 ++++++++++++++++++++++-------------- src/api/routes.c | 8 --- src/api/routes.h | 2 - src/webserver/http-common.c | 25 ++++++++ src/webserver/http-common.h | 1 + 5 files changed, 98 insertions(+), 56 deletions(-) diff --git a/src/api/auth.c b/src/api/auth.c index adcfc1d5c..395ec9b38 100644 --- a/src/api/auth.c +++ b/src/api/auth.c @@ -31,9 +31,10 @@ static struct { char sid[SID_SIZE]; } auth_data[API_MAX_CLIENTS] = {{false, 0, {0}, {0}}}; +#define CHALLENGE_SIZE (2*SHA256_DIGEST_SIZE) static struct { - char challenge[2*SHA256_DIGEST_SIZE + 1]; - char response[2*SHA256_DIGEST_SIZE + 1]; + char challenge[CHALLENGE_SIZE + 1]; + char response[CHALLENGE_SIZE + 1]; time_t valid_until; } challenges[API_MAX_CHALLENGES] = {{{0}, {0}, 0}}; @@ -79,18 +80,28 @@ int check_client_auth(struct mg_connection *conn) char sid[SID_SIZE]; bool sid_avail = http_get_cookie_str(conn, "sid", sid, SID_SIZE); - // If not, does the client provide a session ID via GET? - if(!sid_avail && request->query_string != NULL) + // If not, does the client provide a session ID via GET/POST? + char payload[1024] = { 0 }; + bool sid_payload = false; + if(!sid_avail && http_get_payload(conn, payload, sizeof(payload))) { - sid_avail = GET_VAR("forward", sid, request->query_string) > 0; + sid_avail = GET_VAR("sid", sid, payload) > 0; + + // "+" may have been replaced by " ", undo this here + for(unsigned int i = 0; i < SID_SIZE; i++) + if(sid[i] == ' ') + sid[i] = '+'; + + // Zero terminate sid[SID_SIZE-1] = '\0'; + sid_payload = true; } if(sid_avail) { const time_t now = time(NULL); if(config.debug & DEBUG_API) - logg("API: Read sid=%s", sid); + logg("API: Read sid=\"%s\" from %s", sid, sid_payload ? "payload" : "cookie"); for(unsigned int i = 0; i < API_MAX_CLIENTS; i++) { @@ -229,7 +240,7 @@ static int send_api_auth_status(struct mg_connection *conn, const int user_id, c JSON_SEND_OBJECT(json); } - if(user_id > API_AUTH_UNUSED && method == HTTP_GET) + if(user_id > API_AUTH_UNUSED && (method == HTTP_GET || method == HTTP_POST)) { if(config.debug & DEBUG_API) logg("API Auth status: OK"); @@ -249,7 +260,7 @@ static int send_api_auth_status(struct mg_connection *conn, const int user_id, c else if(user_id > API_AUTH_UNUSED && method == HTTP_DELETE) { if(config.debug & DEBUG_API) - logg("API Auth status: Revoking, asking to delete cookie"); + logg("API Auth status: Logout, asking to delete cookie"); // Revoke client authentication. This slot can be used by a new client afterwards. delete_session(user_id); @@ -312,21 +323,6 @@ static void generateResponse(const unsigned int idx) sha256_hex(raw_response, challenges[idx].response); } -int api_auth(struct mg_connection *conn) -{ - // Check HTTP method - const enum http_method method = http_method(conn); - if(method != HTTP_GET) - return 0; // error 404 - - // Did the client authenticate before and we can validate this? - int user_id = check_client_auth(conn); - - // Send back status, extending validity of the session - const time_t now = time(NULL); - return send_api_auth_status(conn, user_id, method, now); -} - static void generateSID(char *sid) { uint8_t raw_sid[SID_SIZE]; @@ -342,14 +338,14 @@ static void generateSID(char *sid) sid[SID_SIZE-1] = '\0'; } -// Login action -int api_auth_login(struct mg_connection *conn) +// api/auth +// GET: Check authentication and obtain a challenge +// POST: Login +// DELETE: Logout +int api_auth(struct mg_connection *conn) { // Check HTTP method const enum http_method method = http_method(conn); - if(method != HTTP_GET) - return 0; // error 404 - const time_t now = time(NULL); char *password_hash = get_password_hash(); @@ -358,8 +354,47 @@ int api_auth_login(struct mg_connection *conn) int user_id = API_AUTH_UNUSED; const struct mg_request_info *request = mg_get_request_info(conn); + bool reponse_set = false; char response[256] = { 0 }; - const bool reponse_set = request->query_string != NULL && GET_VAR("response", response, request->query_string) > 0; + + // Login attempt, extract response + if(method == HTTP_POST) + { + // Extract payload + char payload[1024] = { 0 }; + http_get_payload(conn, payload, sizeof(payload)); + + // Try to extract response from payload + int len = 0; + if((len = GET_VAR("response", response, payload)) != CHALLENGE_SIZE) + { + const char *message = len < 0 ? "No response found" : "Invalid response length"; + if(config.debug & DEBUG_API) + logg("API auth error: %s", message); + return send_json_error(conn, 400, + "bad_request", + message, + NULL); + } + reponse_set = true; + } + + // Did the client authenticate before and we can validate this? + user_id = check_client_auth(conn); + + // Logout attempt + if(method == HTTP_DELETE) + { + if(config.debug & DEBUG_API) + logg("API Auth: User with ID %i wants to log out", user_id); + return send_api_auth_status(conn, user_id, method, now); + } + + // If this is a valid session, we can exit early at this point + if(user_id != API_AUTH_UNUSED) + return send_api_auth_status(conn, user_id, method, now); + + // Login attempt and/or auth check if(reponse_set || empty_password) { // - Client tries to authenticate using a challenge response, or @@ -407,7 +442,7 @@ int api_auth_login(struct mg_connection *conn) } if(user_id == API_AUTH_UNUSED) { - logg("WARNING: No free API seats available, not authenticating user"); + logg("WARNING: No free API seats available, not authenticating client"); } } else if(config.debug & DEBUG_API) @@ -458,25 +493,16 @@ int api_auth_login(struct mg_connection *conn) free(password_hash); password_hash = NULL; + if(config.debug & DEBUG_API) + { + logg("API: Sending challenge=%s, expecting response=%s", + challenges[i].challenge, challenges[i].response); + } + // Return to user cJSON *json = JSON_NEW_OBJ(); JSON_OBJ_REF_STR(json, "challenge", challenges[i].challenge); - cJSON *validity = JSON_NEW_OBJ(); - JSON_OBJ_ADD_NUMBER(validity, "from", now); - JSON_OBJ_ADD_NUMBER(validity, "until", challenges[i].valid_until); - JSON_OBJ_ADD_ITEM(json, "validity", validity); + get_session_object(conn, json, -1, now); JSON_SEND_OBJECT(json); } } - -int api_auth_logout(struct mg_connection *conn) -{ - // Check HTTP method - const enum http_method method = http_method(conn); - if(method != HTTP_DELETE) - return 0; // error 404 - - // Did the client authenticate before and we can validate this? - int user_id = check_client_auth(conn); - return send_api_auth_status(conn, user_id, HTTP_DELETE, time(NULL)); -} diff --git a/src/api/routes.c b/src/api/routes.c index accaab1ac..7e1242eca 100644 --- a/src/api/routes.c +++ b/src/api/routes.c @@ -156,14 +156,6 @@ int api_handler(struct mg_connection *conn, void *ignored) ret = api_version(conn); } /******************************** /api/auth ****************************/ - else if(startsWith("/api/auth/login", request->local_uri)) - { - ret = api_auth_login(conn); - } - else if(startsWith("/api/auth/logout", request->local_uri)) - { - ret = api_auth_logout(conn); - } else if(startsWith("/api/auth", request->local_uri)) { ret = api_auth(conn); diff --git a/src/api/routes.h b/src/api/routes.h index c28d9babf..db3775b2c 100644 --- a/src/api/routes.h +++ b/src/api/routes.h @@ -56,8 +56,6 @@ int api_version(struct mg_connection *conn); // Auth method int check_client_auth(struct mg_connection *conn); int api_auth(struct mg_connection *conn); -int api_auth_login(struct mg_connection *conn); -int api_auth_logout(struct mg_connection *conn); // Settings methods int api_settings_web(struct mg_connection *conn); diff --git a/src/webserver/http-common.c b/src/webserver/http-common.c index 0b5e710ab..f8aed2ed9 100644 --- a/src/webserver/http-common.c +++ b/src/webserver/http-common.c @@ -140,6 +140,31 @@ bool get_uint_var(const char *source, const char *var, unsigned int *num) return false; } +// Extract payload either from GET or POST data +bool http_get_payload(struct mg_connection *conn, char *payload, const size_t size) +{ + const enum http_method method = http_method(conn); + const struct mg_request_info *request = mg_get_request_info(conn); + if(method == HTTP_GET && request->query_string != NULL) + { + strncpy(payload, request->query_string, size-1); + return true; + } + + if(method == HTTP_POST) + { + int data_len = mg_read(conn, payload, size - 1); + if ((data_len < 1) || (data_len >= (int)size)) + return false; + + payload[data_len] = '\0'; + return true; + } + + // Everything else + return false; +} + bool __attribute__((pure)) startsWith(const char *path, const char *uri) { return strncmp(path, uri, strlen(path)) == 0; diff --git a/src/webserver/http-common.h b/src/webserver/http-common.h index 5963cc34a..2a1e66e66 100644 --- a/src/webserver/http-common.h +++ b/src/webserver/http-common.h @@ -38,6 +38,7 @@ bool http_get_cookie_str(struct mg_connection *conn, const char *cookieName, cha bool get_bool_var(const char *source, const char *var, bool *boolean); bool get_uint_var(const char *source, const char *var, unsigned int *num); bool get_int_var(const char *source, const char *var, int *num); +bool http_get_payload(struct mg_connection *conn, char *payload, const size_t size); cJSON *get_POST_JSON(struct mg_connection *conn); // HTTP macros From 485642834f4ed9dddb098e0f6bb7eae1a5097509 Mon Sep 17 00:00:00 2001 From: DL6ER Date: Thu, 14 Jan 2021 20:44:03 +0100 Subject: [PATCH 0202/1669] Add /api/ftl/system for information about CPU, memory and temperature (if available) Signed-off-by: DL6ER --- src/api/ftl.c | 127 +++++++++++++++++++++++++++++++++++++++++++ src/api/routes.c | 4 ++ src/api/routes.h | 1 + src/syscalls/fopen.c | 3 +- 4 files changed, 134 insertions(+), 1 deletion(-) diff --git a/src/api/ftl.c b/src/api/ftl.c index 2534462aa..4fd02e029 100644 --- a/src/api/ftl.c +++ b/src/api/ftl.c @@ -22,6 +22,8 @@ #include "database/query-table.h" // getgrgid() #include +// sysinfo() +#include int api_ftl_client(struct mg_connection *conn) { @@ -193,3 +195,128 @@ int api_ftl_database(struct mg_connection *conn) // Send reply to user JSON_SEND_OBJECT(json); } + +int api_ftl_system(struct mg_connection *conn) +{ + cJSON *json = JSON_NEW_OBJ(); + + const int nprocs = get_nprocs(); + struct sysinfo info; + if(sysinfo(&info) != 0) + return send_json_error(conn, 500, "error", strerror(errno), NULL); + + // Seconds since boot + JSON_OBJ_ADD_NUMBER(json, "uptime", info.uptime); + + cJSON *ram = JSON_NEW_OBJ(); + // Total usable main memory size + JSON_OBJ_ADD_NUMBER(ram, "total", info.totalram * info.mem_unit); + // Available memory size + JSON_OBJ_ADD_NUMBER(ram, "free", info.freeram * info.mem_unit); + // Amount of shared memory + JSON_OBJ_ADD_NUMBER(ram, "shared", info.sharedram * info.mem_unit); + // Memory used by buffers + JSON_OBJ_ADD_NUMBER(ram, "buffer", info.bufferram * info.mem_unit); + unsigned long used = info.totalram - info.freeram; + // The following is a fall-back from procps code for lxc containers + // messing around with memory information + if(info.sharedram + info.bufferram < used) + used -= info.sharedram + info.bufferram; + JSON_OBJ_ADD_NUMBER(ram, "used", used * info.mem_unit); + + cJSON *swap = JSON_NEW_OBJ(); + // Total swap space size + JSON_OBJ_ADD_NUMBER(swap, "total", info.totalswap * info.mem_unit); + // Swap space still available + JSON_OBJ_ADD_NUMBER(swap, "free", info.freeswap * info.mem_unit); + + cJSON *high = JSON_NEW_OBJ(); + // Total high memory size + JSON_OBJ_ADD_NUMBER(high, "total", info.totalhigh * info.mem_unit); + // High memory still available + JSON_OBJ_ADD_NUMBER(high, "free", info.freehigh * info.mem_unit); + + cJSON *memory = JSON_NEW_OBJ(); + JSON_OBJ_ADD_ITEM(memory, "ram", ram); + JSON_OBJ_ADD_ITEM(memory, "swap", swap); + JSON_OBJ_ADD_ITEM(memory, "high", high); + JSON_OBJ_ADD_ITEM(json, "memory", memory); + + // Number of current processes + JSON_OBJ_ADD_NUMBER(json, "procs", info.procs); + + cJSON *cpu = JSON_NEW_OBJ(); + // Number of available processors + JSON_OBJ_ADD_NUMBER(cpu, "nprocs", nprocs); + + // 1, 5, and 15 minute load averages (we need to convert them) + cJSON *load = JSON_NEW_ARRAY(); + cJSON *percent = JSON_NEW_ARRAY(); + float load_f[3] = { 0.f }; + const float longfloat = 1.f / (1 << SI_LOAD_SHIFT); + for(unsigned int i = 0; i < 3; i++) + { + load_f[i] = longfloat * info.loads[i]; + JSON_ARRAY_ADD_NUMBER(load, load_f[i]); + JSON_ARRAY_ADD_NUMBER(percent, (100.f*load_f[i]/nprocs)); + } + + // Averaged CPU usage in percent + JSON_OBJ_ADD_ITEM(cpu, "load", load); + JSON_OBJ_ADD_ITEM(cpu, "percent", percent); + JSON_OBJ_ADD_ITEM(json, "cpu", cpu); + + // Source available temperatures + cJSON *sensors = JSON_NEW_ARRAY(); + char buffer[256]; + for(int i = 0; i < nprocs; i++) + { + FILE *f_label = NULL, *f_value = NULL; + // Try /sys/class/thermal/thermal_zoneX/{type,temp} + sprintf(buffer, "/sys/class/thermal/thermal_zone%d/type", i); + f_label = fopen(buffer, "r"); + sprintf(buffer, "/sys/class/thermal/thermal_zone%d/temp", i); + f_value = fopen(buffer, "r"); + if(f_label != NULL && f_value != NULL) + { + int temp = 0; + char label[1024]; + if(fread(label, sizeof(label)-1, 1, f_label) > 0 && fscanf(f_value, "%d", &temp) == 1) + { + cJSON *item = JSON_NEW_OBJ(); + JSON_OBJ_COPY_STR(item, "label", label); + JSON_OBJ_ADD_NUMBER(item, "value", temp < 1000 ? temp : 1e-3f*temp); + JSON_ARRAY_ADD_ITEM(sensors, item); + } + } + if(f_label != NULL) + fclose(f_label); + if(f_value != NULL) + fclose(f_value); + + // Try /sys/class/hwmon/hwmon0X/tempX_{label,input} + sprintf(buffer, "/sys/class/hwmon/hwmon0/temp%d_label", i); + f_label = fopen(buffer, "r"); + sprintf(buffer, "/sys/class/hwmon/hwmon0/temp%d_input", i); + f_value = fopen(buffer, "r"); + if(f_label != NULL && f_value != NULL) + { + int temp = 0; + char label[1024]; + if(fread(label, sizeof(label)-1, 1, f_label) > 0 && fscanf(f_value, "%d", &temp) == 1) + { + cJSON *item = JSON_NEW_OBJ(); + JSON_OBJ_COPY_STR(item, "label", label); + JSON_OBJ_ADD_NUMBER(item, "value", temp < 1000 ? temp : 1e-3f*temp); + JSON_ARRAY_ADD_ITEM(sensors, item); + } + } + if(f_label != NULL) + fclose(f_label); + if(f_value != NULL) + fclose(f_value); + } + JSON_OBJ_ADD_ITEM(json, "sensors", sensors); + + JSON_SEND_OBJECT(json); +} diff --git a/src/api/routes.c b/src/api/routes.c index 7e1242eca..5354df3e0 100644 --- a/src/api/routes.c +++ b/src/api/routes.c @@ -68,6 +68,10 @@ int api_handler(struct mg_connection *conn, void *ignored) { ret = api_ftl_database(conn); } + else if(startsWith("/api/ftl/system", request->local_uri)) + { + ret = api_ftl_system(conn); + } /******************************** /api/network ****************************/ else if(startsWith("/api/network", request->local_uri)) { diff --git a/src/api/routes.h b/src/api/routes.h index db3775b2c..c134a7d11 100644 --- a/src/api/routes.h +++ b/src/api/routes.h @@ -39,6 +39,7 @@ int api_stats_database_upstreams(struct mg_connection *conn); int api_ftl_client(struct mg_connection *conn); int api_ftl_dnsmasq_log(struct mg_connection *conn); int api_ftl_database(struct mg_connection *conn); +int api_ftl_system(struct mg_connection *conn); // Network methods int api_network(struct mg_connection *conn); diff --git a/src/syscalls/fopen.c b/src/syscalls/fopen.c index 96f4bb077..eaeda413d 100644 --- a/src/syscalls/fopen.c +++ b/src/syscalls/fopen.c @@ -28,7 +28,8 @@ FILE *FTLfopen(const char *pathname, const char *mode, const char *file, const c // Final error checking (may have faild for some other reason then an // EINTR = interrupted system call) - if(file_ptr == NULL) + // We accept "No such file or directory" as this is something we'll deal with elsewhere + if(file_ptr == NULL && errno != ENOENT) logg("WARN: Could not fopen(\"%s\", \"%s\") in %s() (%s:%i): %s", pathname, mode, func, file, line, strerror(errno)); From 8e63f75a714559d24b9f35b370fc4779bcf1d086 Mon Sep 17 00:00:00 2001 From: DL6ER Date: Thu, 14 Jan 2021 20:59:29 +0100 Subject: [PATCH 0203/1669] Add /api/ftl/system?full=true for sourceing even more information (thinking of third-party applications) Signed-off-by: DL6ER --- src/api/ftl.c | 147 +++++++++++++++++++++++++++++--------------------- 1 file changed, 87 insertions(+), 60 deletions(-) diff --git a/src/api/ftl.c b/src/api/ftl.c index 4fd02e029..4876afc64 100644 --- a/src/api/ftl.c +++ b/src/api/ftl.c @@ -24,6 +24,10 @@ #include // sysinfo() #include +// get_blockingstatus() +#include "../setupVars.h" +// counters +#include "../shmem.h" int api_ftl_client(struct mg_connection *conn) { @@ -196,6 +200,33 @@ int api_ftl_database(struct mg_connection *conn) JSON_SEND_OBJECT(json); } +static int read_temp_sensor(struct mg_connection *conn, + const char *label_path, + const char *value_path, + cJSON *object) +{ + FILE *f_label = fopen(label_path, "r"); + FILE *f_value = fopen(value_path, "r"); + if(f_label != NULL && f_value != NULL) + { + int temp = 0; + char label[1024]; + if(fread(label, sizeof(label)-1, 1, f_label) > 0 && fscanf(f_value, "%d", &temp) == 1) + { + cJSON *item = JSON_NEW_OBJ(); + JSON_OBJ_COPY_STR(item, "label", label); + JSON_OBJ_ADD_NUMBER(item, "value", temp < 1000 ? temp : 1e-3f*temp); + JSON_ARRAY_ADD_ITEM(object, item); + } + } + if(f_label != NULL) + fclose(f_label); + if(f_value != NULL) + fclose(f_value); + + return 0; +} + int api_ftl_system(struct mg_connection *conn) { cJSON *json = JSON_NEW_OBJ(); @@ -205,41 +236,58 @@ int api_ftl_system(struct mg_connection *conn) if(sysinfo(&info) != 0) return send_json_error(conn, 500, "error", strerror(errno), NULL); + // Extract payload + char payload[1024] = { 0 }; + bool full_info = false; + if(http_get_payload(conn, payload, sizeof(payload))) + get_bool_var(payload, "full", &full_info); + // Seconds since boot JSON_OBJ_ADD_NUMBER(json, "uptime", info.uptime); + cJSON *memory = JSON_NEW_OBJ(); cJSON *ram = JSON_NEW_OBJ(); // Total usable main memory size JSON_OBJ_ADD_NUMBER(ram, "total", info.totalram * info.mem_unit); - // Available memory size - JSON_OBJ_ADD_NUMBER(ram, "free", info.freeram * info.mem_unit); - // Amount of shared memory - JSON_OBJ_ADD_NUMBER(ram, "shared", info.sharedram * info.mem_unit); - // Memory used by buffers - JSON_OBJ_ADD_NUMBER(ram, "buffer", info.bufferram * info.mem_unit); + if(full_info) + { + // Available memory size + JSON_OBJ_ADD_NUMBER(ram, "free", info.freeram * info.mem_unit); + // Amount of shared memory + JSON_OBJ_ADD_NUMBER(ram, "shared", info.sharedram * info.mem_unit); + // Memory used by buffers + JSON_OBJ_ADD_NUMBER(ram, "buffer", info.bufferram * info.mem_unit); + } unsigned long used = info.totalram - info.freeram; // The following is a fall-back from procps code for lxc containers // messing around with memory information if(info.sharedram + info.bufferram < used) used -= info.sharedram + info.bufferram; JSON_OBJ_ADD_NUMBER(ram, "used", used * info.mem_unit); + JSON_OBJ_ADD_ITEM(memory, "ram", ram); cJSON *swap = JSON_NEW_OBJ(); // Total swap space size JSON_OBJ_ADD_NUMBER(swap, "total", info.totalswap * info.mem_unit); - // Swap space still available - JSON_OBJ_ADD_NUMBER(swap, "free", info.freeswap * info.mem_unit); + if(full_info) + { + // Swap space still available + JSON_OBJ_ADD_NUMBER(swap, "free", info.freeswap * info.mem_unit); + } + // Used swap space + JSON_OBJ_ADD_NUMBER(swap, "used", (info.totalswap - info.freeswap) * info.mem_unit); + JSON_OBJ_ADD_ITEM(memory, "swap", swap); - cJSON *high = JSON_NEW_OBJ(); - // Total high memory size - JSON_OBJ_ADD_NUMBER(high, "total", info.totalhigh * info.mem_unit); - // High memory still available - JSON_OBJ_ADD_NUMBER(high, "free", info.freehigh * info.mem_unit); + if(full_info) + { + cJSON *high = JSON_NEW_OBJ(); + // Total high memory size + JSON_OBJ_ADD_NUMBER(high, "total", info.totalhigh * info.mem_unit); + // High memory still available + JSON_OBJ_ADD_NUMBER(high, "free", info.freehigh * info.mem_unit); + JSON_OBJ_ADD_ITEM(memory, "high", high); + } - cJSON *memory = JSON_NEW_OBJ(); - JSON_OBJ_ADD_ITEM(memory, "ram", ram); - JSON_OBJ_ADD_ITEM(memory, "swap", swap); - JSON_OBJ_ADD_ITEM(memory, "high", high); JSON_OBJ_ADD_ITEM(json, "memory", memory); // Number of current processes @@ -266,57 +314,36 @@ int api_ftl_system(struct mg_connection *conn) JSON_OBJ_ADD_ITEM(cpu, "percent", percent); JSON_OBJ_ADD_ITEM(json, "cpu", cpu); - // Source available temperatures + // Source available temperatures, we try to read as many + // temperature sensors as there are cores on this system cJSON *sensors = JSON_NEW_ARRAY(); - char buffer[256]; + char label_path[256], value_path[256]; + int ret; for(int i = 0; i < nprocs; i++) { - FILE *f_label = NULL, *f_value = NULL; // Try /sys/class/thermal/thermal_zoneX/{type,temp} - sprintf(buffer, "/sys/class/thermal/thermal_zone%d/type", i); - f_label = fopen(buffer, "r"); - sprintf(buffer, "/sys/class/thermal/thermal_zone%d/temp", i); - f_value = fopen(buffer, "r"); - if(f_label != NULL && f_value != NULL) - { - int temp = 0; - char label[1024]; - if(fread(label, sizeof(label)-1, 1, f_label) > 0 && fscanf(f_value, "%d", &temp) == 1) - { - cJSON *item = JSON_NEW_OBJ(); - JSON_OBJ_COPY_STR(item, "label", label); - JSON_OBJ_ADD_NUMBER(item, "value", temp < 1000 ? temp : 1e-3f*temp); - JSON_ARRAY_ADD_ITEM(sensors, item); - } - } - if(f_label != NULL) - fclose(f_label); - if(f_value != NULL) - fclose(f_value); + sprintf(label_path, "/sys/class/thermal/thermal_zone%d/type", i); + sprintf(value_path, "/sys/class/thermal/thermal_zone%d/temp", i); + ret = read_temp_sensor(conn, label_path, value_path, sensors); + // Error handling + if(ret != 0) + return ret; // Try /sys/class/hwmon/hwmon0X/tempX_{label,input} - sprintf(buffer, "/sys/class/hwmon/hwmon0/temp%d_label", i); - f_label = fopen(buffer, "r"); - sprintf(buffer, "/sys/class/hwmon/hwmon0/temp%d_input", i); - f_value = fopen(buffer, "r"); - if(f_label != NULL && f_value != NULL) - { - int temp = 0; - char label[1024]; - if(fread(label, sizeof(label)-1, 1, f_label) > 0 && fscanf(f_value, "%d", &temp) == 1) - { - cJSON *item = JSON_NEW_OBJ(); - JSON_OBJ_COPY_STR(item, "label", label); - JSON_OBJ_ADD_NUMBER(item, "value", temp < 1000 ? temp : 1e-3f*temp); - JSON_ARRAY_ADD_ITEM(sensors, item); - } - } - if(f_label != NULL) - fclose(f_label); - if(f_value != NULL) - fclose(f_value); + sprintf(label_path, "/sys/class/hwmon/hwmon0/temp%d_label", i); + sprintf(value_path, "/sys/class/hwmon/hwmon0/temp%d_input", i); + ret = read_temp_sensor(conn, label_path, value_path, sensors); + // Error handling + if(ret != 0) + return ret; } JSON_OBJ_ADD_ITEM(json, "sensors", sensors); + + cJSON *dns = JSON_NEW_OBJ(); + const bool blocking = get_blockingstatus(); + JSON_OBJ_ADD_BOOL(dns, "blocking", blocking); // same reply type as in /api/dns/status + JSON_OBJ_ADD_NUMBER(dns, "gravity_size", counters->gravity); + JSON_OBJ_ADD_ITEM(json, "dns", dns); JSON_SEND_OBJECT(json); } From 9966c84f54e7b3e1d6a4978f13d7d5367f2e2cd6 Mon Sep 17 00:00:00 2001 From: DL6ER Date: Thu, 14 Jan 2021 21:12:06 +0100 Subject: [PATCH 0204/1669] Localhost should be able to request all ressources if this is set via a config option Signed-off-by: DL6ER --- src/api/auth.c | 19 ++++++++----------- src/api/dns.c | 4 ++-- src/api/ftl.c | 4 ++-- src/api/network.c | 2 +- src/api/stats.c | 14 +++++++------- src/enums.h | 21 ++++++++++++++++----- 6 files changed, 36 insertions(+), 28 deletions(-) diff --git a/src/api/auth.c b/src/api/auth.c index 395ec9b38..a589fb752 100644 --- a/src/api/auth.c +++ b/src/api/auth.c @@ -54,9 +54,6 @@ static void sha256_hex(uint8_t *data, char *buffer) // Returns >= 0 for any valid authentication #define LOCALHOSTv4 "127.0.0.1" #define LOCALHOSTv6 "::1" -#define API_AUTH_UNUSED -1 -#define API_AUTH_LOCALHOST -2 -#define API_AUTH_EMPTYPASS -3 int check_client_auth(struct mg_connection *conn) { int user_id = -1; @@ -114,7 +111,7 @@ int check_client_auth(struct mg_connection *conn) break; } } - if(user_id > API_AUTH_UNUSED) + if(user_id > API_AUTH_UNAUTHORIZED) { // Authentication succesful: // - We know this client @@ -187,7 +184,7 @@ static int get_session_object(struct mg_connection *conn, cJSON *json, const int } // Valid session - if(user_id > API_AUTH_UNUSED && auth_data[user_id].used) + if(user_id > API_AUTH_UNAUTHORIZED && auth_data[user_id].used) { cJSON *session = JSON_NEW_OBJ(); JSON_OBJ_ADD_BOOL(session, "valid", true); @@ -240,7 +237,7 @@ static int send_api_auth_status(struct mg_connection *conn, const int user_id, c JSON_SEND_OBJECT(json); } - if(user_id > API_AUTH_UNUSED && (method == HTTP_GET || method == HTTP_POST)) + if(user_id > API_AUTH_UNAUTHORIZED && (method == HTTP_GET || method == HTTP_POST)) { if(config.debug & DEBUG_API) logg("API Auth status: OK"); @@ -257,7 +254,7 @@ static int send_api_auth_status(struct mg_connection *conn, const int user_id, c get_session_object(conn, json, user_id, now); JSON_SEND_OBJECT(json); } - else if(user_id > API_AUTH_UNUSED && method == HTTP_DELETE) + else if(user_id > API_AUTH_UNAUTHORIZED && method == HTTP_DELETE) { if(config.debug & DEBUG_API) logg("API Auth status: Logout, asking to delete cookie"); @@ -351,7 +348,7 @@ int api_auth(struct mg_connection *conn) char *password_hash = get_password_hash(); const bool empty_password = (strlen(password_hash) == 0u); - int user_id = API_AUTH_UNUSED; + int user_id = API_AUTH_UNAUTHORIZED; const struct mg_request_info *request = mg_get_request_info(conn); bool reponse_set = false; @@ -391,7 +388,7 @@ int api_auth(struct mg_connection *conn) } // If this is a valid session, we can exit early at this point - if(user_id != API_AUTH_UNUSED) + if(user_id != API_AUTH_UNAUTHORIZED) return send_api_auth_status(conn, user_id, method, now); // Login attempt and/or auth check @@ -432,7 +429,7 @@ int api_auth(struct mg_connection *conn) } // Debug logging - if(config.debug & DEBUG_API && user_id > API_AUTH_UNUSED) + if(config.debug & DEBUG_API && user_id > API_AUTH_UNAUTHORIZED) { char timestr[128]; get_timestr(timestr, auth_data[user_id].valid_until); @@ -440,7 +437,7 @@ int api_auth(struct mg_connection *conn) user_id, timestr, auth_data[user_id].remote_addr, response_correct ? "correct response" : "empty password"); } - if(user_id == API_AUTH_UNUSED) + if(user_id == API_AUTH_UNAUTHORIZED) { logg("WARNING: No free API seats available, not authenticating client"); } diff --git a/src/api/dns.c b/src/api/dns.c index 7bf2af827..59b30d1c1 100644 --- a/src/api/dns.c +++ b/src/api/dns.c @@ -48,7 +48,7 @@ static int get_blocking(struct mg_connection *conn) static int set_blocking(struct mg_connection *conn) { // Verify requesting client is allowed to access this ressource - if(check_client_auth(conn) < 0) + if(check_client_auth(conn) == API_AUTH_UNAUTHORIZED) { return send_json_unauthorized(conn); } @@ -131,7 +131,7 @@ int api_dns_blockingstatus(struct mg_connection *conn) int api_dns_cacheinfo(struct mg_connection *conn) { // Verify requesting client is allowed to access this ressource - if(check_client_auth(conn) < 0) + if(check_client_auth(conn) == API_AUTH_UNAUTHORIZED) { return send_json_unauthorized(conn); } diff --git a/src/api/ftl.c b/src/api/ftl.c index 4876afc64..158491908 100644 --- a/src/api/ftl.c +++ b/src/api/ftl.c @@ -63,7 +63,7 @@ fifologData *fifo_log = NULL; int api_ftl_dnsmasq_log(struct mg_connection *conn) { // Verify requesting client is allowed to see this ressource - if(check_client_auth(conn) < 0) + if(check_client_auth(conn) == API_AUTH_UNAUTHORIZED) { return send_json_unauthorized(conn); } @@ -128,7 +128,7 @@ int api_ftl_dnsmasq_log(struct mg_connection *conn) int api_ftl_database(struct mg_connection *conn) { // Verify requesting client is allowed to see this ressource - if(check_client_auth(conn) < 0) + if(check_client_auth(conn) == API_AUTH_UNAUTHORIZED) { send_json_unauthorized(conn); } diff --git a/src/api/network.c b/src/api/network.c index b57c7e111..c811e386d 100644 --- a/src/api/network.c +++ b/src/api/network.c @@ -18,7 +18,7 @@ int api_network(struct mg_connection *conn) { // Verify requesting client is allowed to see this ressource - if(check_client_auth(conn) < 0) + if(check_client_auth(conn) == API_AUTH_UNAUTHORIZED) { return send_json_unauthorized(conn); } diff --git a/src/api/stats.c b/src/api/stats.c index 9847825fa..e7cd9fca1 100644 --- a/src/api/stats.c +++ b/src/api/stats.c @@ -175,7 +175,7 @@ int api_stats_top_domains(bool blocked, struct mg_connection *conn) bool audit = false; // Verify requesting client is allowed to see this ressource - if(check_client_auth(conn) < 0) + if(check_client_auth(conn) == API_AUTH_UNAUTHORIZED) { return send_json_unauthorized(conn); } @@ -333,7 +333,7 @@ int api_stats_top_clients(bool blocked, struct mg_connection *conn) bool includezeroclients = false; // Verify requesting client is allowed to see this ressource - if(check_client_auth(conn) < 0) + if(check_client_auth(conn) == API_AUTH_UNAUTHORIZED) { return send_json_unauthorized(conn); } @@ -457,7 +457,7 @@ int api_stats_upstreams(struct mg_connection *conn) int temparray[counters->forwarded][2]; // Verify requesting client is allowed to see this ressource - if(check_client_auth(conn) < 0) + if(check_client_auth(conn) == API_AUTH_UNAUTHORIZED) { return send_json_unauthorized(conn); } @@ -558,7 +558,7 @@ int api_stats_upstreams(struct mg_connection *conn) int api_stats_query_types(struct mg_connection *conn) { // Verify requesting client is allowed to see this ressource - if(check_client_auth(conn) < 0) + if(check_client_auth(conn) == API_AUTH_UNAUTHORIZED) { return send_json_unauthorized(conn); } @@ -594,7 +594,7 @@ int api_stats_history(struct mg_connection *conn) } // Verify requesting client is allowed to see this ressource - if(check_client_auth(conn) < 0) + if(check_client_auth(conn) == API_AUTH_UNAUTHORIZED) { return send_json_unauthorized(conn); } @@ -1042,7 +1042,7 @@ int api_stats_recentblocked(struct mg_connection *conn) unsigned int show = 1; // Verify requesting client is allowed to see this ressource - if(check_client_auth(conn) < 0) + if(check_client_auth(conn) == API_AUTH_UNAUTHORIZED) { return send_json_unauthorized(conn); } @@ -1113,7 +1113,7 @@ int api_stats_overTime_clients(struct mg_connection *conn) int sendit = -1, until = OVERTIME_SLOTS; // Verify requesting client is allowed to see this ressource - if(check_client_auth(conn) < 0) + if(check_client_auth(conn) == API_AUTH_UNAUTHORIZED) { return send_json_unauthorized(conn); } diff --git a/src/enums.h b/src/enums.h index 9d86b33c2..f44d2a722 100644 --- a/src/enums.h +++ b/src/enums.h @@ -156,11 +156,16 @@ enum events { } __attribute__ ((packed)); -enum gravitry_domainlist_indices { - GRAVITY_DOMAINLIST_EXACT_WHITELIST = 0, - GRAVITY_DOMAINLIST_EXACT_BLACKLIST = 1, - GRAVITY_DOMAINLIST_REGEX_WHITELIST = 2, - GRAVITY_DOMAINLIST_REGEX_BLACKLIST = 3 +enum domainlist_type { + GRAVITY_DOMAINLIST_ALLOW_EXACT, + GRAVITY_DOMAINLIST_ALLOW_REGEX, + GRAVITY_DOMAINLIST_ALLOW_ALL, + GRAVITY_DOMAINLIST_DENY_EXACT, + GRAVITY_DOMAINLIST_DENY_REGEX, + GRAVITY_DOMAINLIST_DENY_ALL, + GRAVITY_DOMAINLIST_ALL_EXACT, + GRAVITY_DOMAINLIST_ALL_REGEX, + GRAVITY_DOMAINLIST_ALL_ALL } __attribute__ ((packed)); enum gravity_tables { @@ -194,4 +199,10 @@ enum refresh_hostnames { REFRESH_NONE } __attribute__ ((packed)); +enum api_auth_status { + API_AUTH_UNAUTHORIZED = -1, + API_AUTH_LOCALHOST = -2, + API_AUTH_EMPTYPASS = -3, +} __attribute__ ((packed)); + #endif // ENUMS_H From 4421e88596b44960889258b426b1cf7ad746a3dc Mon Sep 17 00:00:00 2001 From: DL6ER Date: Thu, 14 Jan 2021 21:37:42 +0100 Subject: [PATCH 0205/1669] Rename {white,black} to {allow,deny} Signed-off-by: DL6ER --- src/api/list.c | 158 ++++++++++++++++++++++++-------------- src/api/routes.c | 21 +---- src/api/routes.h | 4 +- src/database/gravity-db.c | 145 ++++++++++++++++++++++++++-------- src/database/gravity-db.h | 7 +- 5 files changed, 222 insertions(+), 113 deletions(-) diff --git a/src/api/list.c b/src/api/list.c index 35c58eeae..8dc612bda 100644 --- a/src/api/list.c +++ b/src/api/list.c @@ -3,7 +3,7 @@ * Network-wide ad blocking via your own hardware. * * FTL Engine -* API Implementation /api/{white,black}list +* API Implementation /api/{allow,deny}list * * This file is copyright under the latest version of the EUPL. * Please see LICENSE file for your rights under this license. */ @@ -14,24 +14,13 @@ #include "routes.h" #include "../database/gravity-db.h" -static int getTableType(bool whitelist, bool exact) -{ - if(whitelist) - if(exact) - return GRAVITY_DOMAINLIST_EXACT_WHITELIST; - else - return GRAVITY_DOMAINLIST_REGEX_WHITELIST; - else - if(exact) - return GRAVITY_DOMAINLIST_EXACT_BLACKLIST; - else - return GRAVITY_DOMAINLIST_REGEX_BLACKLIST; -} - -static int get_domainlist(struct mg_connection *conn, const int code, const int type, const char *domain_filter) +static int get_domainlist(struct mg_connection *conn, + const int code, + const enum domainlist_type listtype, + const char *domain_filter) { const char *sql_msg = NULL; - if(!gravityDB_readTable(type, domain_filter, &sql_msg)) + if(!gravityDB_readTable(listtype, domain_filter, &sql_msg)) { cJSON *json = JSON_NEW_OBJ(); @@ -52,7 +41,7 @@ static int get_domainlist(struct mg_connection *conn, const int code, const int } domainrecord domain; - cJSON *json = JSON_NEW_ARRAY(); + cJSON *domains = JSON_NEW_ARRAY(); while(gravityDB_readTableGetDomain(&domain, &sql_msg)) { cJSON *item = JSON_NEW_OBJ(); @@ -66,19 +55,21 @@ static int get_domainlist(struct mg_connection *conn, const int code, const int JSON_OBJ_ADD_NULL(item, "comment"); } - JSON_ARRAY_ADD_ITEM(json, item); + JSON_ARRAY_ADD_ITEM(domains, item); } gravityDB_readTableFinalize(); if(sql_msg == NULL) { // No error, send requested HTTP code + cJSON *json = JSON_NEW_OBJ(); + JSON_OBJ_ADD_ITEM(json, "domains", domains); JSON_SEND_OBJECT_CODE(json, code); } else { - JSON_DELETE(json); - json = JSON_NEW_OBJ(); + JSON_DELETE(domains); + cJSON *json = JSON_NEW_OBJ(); // Add domain_filter (may be NULL = not available) JSON_OBJ_REF_STR(json, "domain_filter", domain_filter); @@ -97,27 +88,34 @@ static int get_domainlist(struct mg_connection *conn, const int code, const int } } -static int api_dns_domainlist_read(struct mg_connection *conn, bool exact, bool whitelist) +static int api_dns_domainlist_read(struct mg_connection *conn, + const enum domainlist_type listtype) { // Extract domain from path (option for GET) const struct mg_request_info *request = mg_get_request_info(conn); char domain_filter[1024] = { 0 }; + // Advance one character to strip "/" const char *encoded_uri = strrchr(request->local_uri, '/')+1u; + // Decode URL (necessary for regular expressions, harmless for domains) - if(strlen(encoded_uri) != 0 && strcmp(encoded_uri, "exact") != 0 && strcmp(encoded_uri, "regex") != 0) + if(strlen(encoded_uri) != 0 && + strcmp(encoded_uri, "exact") != 0 && + strcmp(encoded_uri, "regex") != 0 && + strcmp(encoded_uri, "allow") != 0 && + strcmp(encoded_uri, "deny") != 0 && + strcmp(encoded_uri, "list") != 0) mg_url_decode(encoded_uri, strlen(encoded_uri), domain_filter, sizeof(domain_filter), 0); - const int type = getTableType(whitelist, exact); - // Send GET style reply with code 200 OK - return get_domainlist(conn, 200, type, domain_filter); + return get_domainlist(conn, 200, listtype, domain_filter); } static int api_dns_domainlist_write(struct mg_connection *conn, - bool exact, - bool whitelist, + const enum domainlist_type listtype, const enum http_method method) { + domainrecord domain; + // Extract payload char buffer[1024] = { 0 }; int data_len = mg_read(conn, buffer, sizeof(buffer) - 1); @@ -145,44 +143,41 @@ static int api_dns_domainlist_write(struct mg_connection *conn, "No \"domain\" string in body data", NULL); } - size_t len = strlen(elem_domain->valuestring); - char domain[len]; - strcpy(domain, elem_domain->valuestring); + domain.domain = elem_domain->valuestring; - bool enabled = true; + domain.enabled = true; cJSON *elem_enabled = cJSON_GetObjectItemCaseSensitive(obj, "enabled"); if (cJSON_IsBool(elem_enabled)) { - enabled = cJSON_IsTrue(elem_enabled); + domain.enabled = cJSON_IsTrue(elem_enabled); } - char *comment = NULL; + domain.comment = NULL; cJSON *elem_comment = cJSON_GetObjectItemCaseSensitive(obj, "comment"); if (cJSON_IsString(elem_comment)) { - comment = elem_comment->valuestring; + domain.comment = elem_comment->valuestring; } // Try to add domain to table const char *sql_msg = NULL; - int type = getTableType(whitelist, exact); - if(gravityDB_addToTable(type, domain, enabled, comment, &sql_msg, method)) + if(gravityDB_addToTable(listtype, domain, &sql_msg, method)) { cJSON_Delete(obj); // Send GET style reply with code 201 Created - return get_domainlist(conn, 201, type, domain); + return get_domainlist(conn, 201, listtype, domain.domain); } else { // Error adding domain, prepare error object // Add domain cJSON *json = JSON_NEW_OBJ(); - JSON_OBJ_COPY_STR(json, "domain", domain); + JSON_OBJ_COPY_STR(json, "domain", domain.domain); // Add enabled boolean - JSON_OBJ_ADD_BOOL(json, "enabled", enabled); + JSON_OBJ_ADD_BOOL(json, "enabled", domain.enabled); // Add comment (may be NULL) - if (comment != NULL) { - JSON_OBJ_COPY_STR(json, "comment", comment); + if (domain.comment != NULL) { + JSON_OBJ_COPY_STR(json, "comment", domain.comment); } else { JSON_OBJ_ADD_NULL(json, "comment"); } @@ -206,8 +201,7 @@ static int api_dns_domainlist_write(struct mg_connection *conn, } static int api_dns_domainlist_remove(struct mg_connection *conn, - bool exact, - bool whitelist) + const enum domainlist_type listtype) { const struct mg_request_info *request = mg_get_request_info(conn); @@ -219,8 +213,7 @@ static int api_dns_domainlist_remove(struct mg_connection *conn, cJSON *json = JSON_NEW_OBJ(); const char *sql_msg = NULL; - int type = getTableType(whitelist, exact); - if(gravityDB_delFromTable(type, domain, &sql_msg)) + if(gravityDB_delFromTable(listtype, domain, &sql_msg)) { // Send empty reply with code 204 No Content JSON_SEND_OBJECT_CODE(json, 204); @@ -245,30 +238,81 @@ static int api_dns_domainlist_remove(struct mg_connection *conn, } } -int api_dns_domainlist(struct mg_connection *conn, bool exact, bool whitelist) +int api_dns_domainlist(struct mg_connection *conn) { // Verify requesting client is allowed to see this ressource - if(check_client_auth(conn) < 0) + if(check_client_auth(conn) == API_AUTH_UNAUTHORIZED) { return send_json_unauthorized(conn); } + enum domainlist_type listtype; + bool can_modify = false; + const struct mg_request_info *request = mg_get_request_info(conn); + if(startsWith("/api/list/allow", request->local_uri)) + { + if(startsWith("/api/list/allow/exact", request->local_uri)) + { + listtype = GRAVITY_DOMAINLIST_ALLOW_EXACT; + can_modify = true; + } + else if(startsWith("/api/list/allow/regex", request->local_uri)) + { + listtype = GRAVITY_DOMAINLIST_ALLOW_REGEX; + can_modify = true; + } + else + listtype = GRAVITY_DOMAINLIST_ALLOW_ALL; + } + else if(startsWith("/api/list/deny", request->local_uri)) + { + if(startsWith("/api/list/deny/exact", request->local_uri)) + { + listtype = GRAVITY_DOMAINLIST_DENY_EXACT; + can_modify = true; + } + else if(startsWith("/api/list/deny/regex", request->local_uri)) + { + listtype = GRAVITY_DOMAINLIST_DENY_REGEX; + can_modify = true; + } + else + listtype = GRAVITY_DOMAINLIST_DENY_ALL; + } + else + { + if(startsWith("/api/list/exact", request->local_uri)) + listtype = GRAVITY_DOMAINLIST_ALL_EXACT; + else if(startsWith("/api/list/regex", request->local_uri)) + listtype = GRAVITY_DOMAINLIST_ALL_REGEX; + else + listtype = GRAVITY_DOMAINLIST_ALL_ALL; + } + const enum http_method method = http_method(conn); if(method == HTTP_GET) { - return api_dns_domainlist_read(conn, exact, whitelist); + return api_dns_domainlist_read(conn, listtype); + } + else if(can_modify && (method == HTTP_POST || method == HTTP_PUT || method == HTTP_PATCH)) + { + // Add domain from exact allow-/denylist when a user sends + // the request to the general address /api/dns/{allow,deny}list + return api_dns_domainlist_write(conn, listtype, method); } - else if(method == HTTP_POST || method == HTTP_PUT || method == HTTP_PATCH) + else if(can_modify && method == HTTP_DELETE) { - // Add domain from exact white-/blacklist when a user sends - // the request to the general address /api/dns/{white,black}list - return api_dns_domainlist_write(conn, exact, whitelist, method); + // Delete domain from exact allow-/denylist when a user sends + // the request to the general address /api/dns/{allow,deny}list + return api_dns_domainlist_remove(conn, listtype); } - else if(method == HTTP_DELETE) + else if(!can_modify) { - // Delete domain from exact white-/blacklist when a user sends - // the request to the general address /api/dns/{white,black}list - return api_dns_domainlist_remove(conn, exact, whitelist); + // This list type cannot be modified (e.g., ALL_ALL) + return send_json_error(conn, 400, + "bad_request", + "Invalid request: Specify list to modify", + NULL); } else { diff --git a/src/api/routes.c b/src/api/routes.c index 5354df3e0..7b2d6b950 100644 --- a/src/api/routes.c +++ b/src/api/routes.c @@ -26,7 +26,7 @@ int api_handler(struct mg_connection *conn, void *ignored) const struct mg_request_info *request = mg_get_request_info(conn); if(config.debug & DEBUG_API) - logg("Requested API URI: %s", request->local_uri); + logg("Requested API URI: %s %s", request->request_method, request->local_uri); /******************************** /api/dns ********************************/ if(startsWith("/api/dns/blocking", request->local_uri)) @@ -37,23 +37,10 @@ int api_handler(struct mg_connection *conn, void *ignored) { ret = api_dns_cacheinfo(conn); } - /******************************** /api/whitelist ****************************/ - else if(startsWith("/api/whitelist/exact", request->local_uri)) + /******************************** /api/allowlist ****************************/ + else if(startsWith("/api/list", request->local_uri)) { - ret = api_dns_domainlist(conn, true, true); - } - else if(startsWith("/api/whitelist/regex", request->local_uri)) - { - ret = api_dns_domainlist(conn, false, true); - } - /******************************** /api/blacklist ****************************/ - else if(startsWith("/api/blacklist/exact", request->local_uri)) - { - ret = api_dns_domainlist(conn, true, false); - } - else if(startsWith("/api/blacklist/regex", request->local_uri)) - { - ret = api_dns_domainlist(conn, false, false); + ret = api_dns_domainlist(conn); } /******************************** /api/ftl ****************************/ else if(startsWith("/api/ftl/client", request->local_uri)) diff --git a/src/api/routes.h b/src/api/routes.h index c134a7d11..66a9dd9d2 100644 --- a/src/api/routes.h +++ b/src/api/routes.h @@ -48,8 +48,8 @@ int api_network(struct mg_connection *conn); int api_dns_blockingstatus(struct mg_connection *conn); int api_dns_cacheinfo(struct mg_connection *conn); -// White-/Blacklist methods -int api_dns_domainlist(struct mg_connection *conn, bool exact, bool whitelist); +// List methods +int api_dns_domainlist(struct mg_connection *conn); // Version method int api_version(struct mg_connection *conn); diff --git a/src/database/gravity-db.c b/src/database/gravity-db.c index ef7c16d18..51c4c1122 100644 --- a/src/database/gravity-db.c +++ b/src/database/gravity-db.c @@ -1360,8 +1360,7 @@ bool gravityDB_get_regex_client_groups(clientsData* client, const unsigned int n return true; } -bool gravityDB_addToTable(const int type, const char *domain, - const bool enabled, const char *comment, +bool gravityDB_addToTable(const enum domainlist_type listtype, const domainrecord domain, const char **message, const enum http_method method) { if(gravity_db == NULL) @@ -1370,6 +1369,32 @@ bool gravityDB_addToTable(const int type, const char *domain, return false; } + int type; + switch (listtype) + { + case GRAVITY_DOMAINLIST_ALLOW_EXACT: + type = 0; + break; + case GRAVITY_DOMAINLIST_DENY_EXACT: + type = 1; + break; + case GRAVITY_DOMAINLIST_ALLOW_REGEX: + type = 2; + break; + case GRAVITY_DOMAINLIST_DENY_REGEX: + type = 3; + break; + + // Aggregate types cannot be handled by this routine + case GRAVITY_DOMAINLIST_ALLOW_ALL: + case GRAVITY_DOMAINLIST_DENY_ALL: + case GRAVITY_DOMAINLIST_ALL_EXACT: + case GRAVITY_DOMAINLIST_ALL_REGEX: + case GRAVITY_DOMAINLIST_ALL_ALL: + default: + return false; + } + // Prepare SQLite statement sqlite3_stmt* stmt = NULL; const char *querystr; @@ -1387,16 +1412,16 @@ bool gravityDB_addToTable(const int type, const char *domain, { *message = sqlite3_errmsg(gravity_db); logg("gravityDB_addToTable(%d, %s) - SQL error prepare (%i): %s", - type, domain, rc, *message); + type, domain.domain, rc, *message); return false; } // Bind domain string to prepared statement - if((rc = sqlite3_bind_text(stmt, 1, domain, -1, SQLITE_STATIC)) != SQLITE_OK) + if((rc = sqlite3_bind_text(stmt, 1, domain.domain, -1, SQLITE_STATIC)) != SQLITE_OK) { *message = sqlite3_errmsg(gravity_db); logg("gravityDB_addToTable(%d, %s): Failed to bind domain (error %d) - %s", - type, domain, rc, *message); + type, domain.domain, rc, *message); sqlite3_reset(stmt); sqlite3_finalize(stmt); return false; @@ -1407,29 +1432,29 @@ bool gravityDB_addToTable(const int type, const char *domain, { *message = sqlite3_errmsg(gravity_db); logg("gravityDB_addToTable(%d, %s): Failed to bind domain (error %d) - %s", - type, domain, rc, *message); + type, domain.domain, rc, *message); sqlite3_reset(stmt); sqlite3_finalize(stmt); return false; } // Bind enabled boolean to prepared statement - if((rc = sqlite3_bind_int(stmt, 3, enabled ? 1 : 0)) != SQLITE_OK) + if((rc = sqlite3_bind_int(stmt, 3, domain.enabled ? 1 : 0)) != SQLITE_OK) { *message = sqlite3_errmsg(gravity_db); logg("gravityDB_addToTable(%d, %s): Failed to bind enabled (error %d) - %s", - type, domain, rc, *message); + type, domain.domain, rc, *message); sqlite3_reset(stmt); sqlite3_finalize(stmt); return false; } // Bind enabled boolean to prepared statement - if((rc = sqlite3_bind_text(stmt, 4, comment, -1, SQLITE_STATIC)) != SQLITE_OK) + if((rc = sqlite3_bind_text(stmt, 4, domain.comment, -1, SQLITE_STATIC)) != SQLITE_OK) { *message = sqlite3_errmsg(gravity_db); logg("gravityDB_addToTable(%d, %s): Failed to bind comment (error %d) - %s", - type, domain, rc, *message); + type, domain.domain, rc, *message); sqlite3_reset(stmt); sqlite3_finalize(stmt); return false; @@ -1454,13 +1479,40 @@ bool gravityDB_addToTable(const int type, const char *domain, return okay; } -bool gravityDB_delFromTable(const int type, const char* domain, const char **message) +bool gravityDB_delFromTable(const enum domainlist_type listtype, const char* domain, const char **message) { if(gravity_db == NULL) { *message = "Database not available"; return false; } + + int type; + switch (listtype) + { + case GRAVITY_DOMAINLIST_ALLOW_EXACT: + type = 0; + break; + case GRAVITY_DOMAINLIST_DENY_EXACT: + type = 1; + break; + case GRAVITY_DOMAINLIST_ALLOW_REGEX: + type = 2; + break; + case GRAVITY_DOMAINLIST_DENY_REGEX: + type = 3; + break; + + // Aggregate types cannot be handled by this routine + case GRAVITY_DOMAINLIST_ALLOW_ALL: + case GRAVITY_DOMAINLIST_DENY_ALL: + case GRAVITY_DOMAINLIST_ALL_EXACT: + case GRAVITY_DOMAINLIST_ALL_REGEX: + case GRAVITY_DOMAINLIST_ALL_ALL: + default: + return false; + } + // Prepare SQLite statement sqlite3_stmt* stmt = NULL; const char *querystr = "DELETE FROM domainlist WHERE domain = ? AND type = ?;"; @@ -1515,7 +1567,7 @@ bool gravityDB_delFromTable(const int type, const char* domain, const char **mes } static sqlite3_stmt* read_stmt = NULL; -bool gravityDB_readTable(const int type, const char *domain, const char **message) +bool gravityDB_readTable(const enum domainlist_type listtype, const char *domain, const char **message) { if(gravity_db == NULL) { @@ -1523,37 +1575,64 @@ bool gravityDB_readTable(const int type, const char *domain, const char **messag return false; } + // Get filter string for the requested list type + const char *type = ""; + switch (listtype) + { + case GRAVITY_DOMAINLIST_ALLOW_EXACT: + type = "0"; + break; + case GRAVITY_DOMAINLIST_ALLOW_REGEX: + type = "2"; + break; + case GRAVITY_DOMAINLIST_ALLOW_ALL: + type = "0,2"; + break; + case GRAVITY_DOMAINLIST_DENY_EXACT: + type = "1"; + break; + case GRAVITY_DOMAINLIST_DENY_REGEX: + type = "3"; + break; + case GRAVITY_DOMAINLIST_DENY_ALL: + type = "1,3"; + break; + case GRAVITY_DOMAINLIST_ALL_EXACT: + type = "0,1"; + break; + case GRAVITY_DOMAINLIST_ALL_REGEX: + type = "2,3"; + break; + case GRAVITY_DOMAINLIST_ALL_ALL: + type = "0,1,2,3"; + break; + } + + // Build query statement. We have to do it this way + // as binding a sequence of ints via a prepared + // statement isn't possible in SQLite3 + char querystr[128]; + const char *extra = ""; + if(domain[0] != '\0') + extra = " AND domain = ?;"; + sprintf(querystr, "SELECT domain,enabled,date_added,date_modified,comment FROM domainlist WHERE type IN (%s)%s;", type, extra); + logg("%s", querystr); + // Prepare SQLite statement - const char *querystr; - if(domain[0] == '\0') - querystr = "SELECT domain,enabled,date_added,date_modified,comment FROM domainlist WHERE type = ?;"; - else - querystr = "SELECT domain,enabled,date_added,date_modified,comment FROM domainlist WHERE type = ? AND domain = ?;"; int rc = sqlite3_prepare_v2(gravity_db, querystr, -1, &read_stmt, NULL); if( rc != SQLITE_OK ){ *message = sqlite3_errmsg(gravity_db); - logg("gravityDB_readTable(%d) - SQL error prepare (%i): %s", - type, rc, *message); - return false; - } - - // Bind domain type to prepared statement - if((rc = sqlite3_bind_int(read_stmt, 1, type)) != SQLITE_OK) - { - *message = sqlite3_errmsg(gravity_db); - logg("gravityDB_readTable(%d): Failed to bind type (error %d) - %s", - type, rc, *message); - sqlite3_reset(read_stmt); - sqlite3_finalize(read_stmt); + logg("gravityDB_readTable(%d => (%s)) - SQL error prepare (%i): %s", + listtype, type, rc, *message); return false; } // Bind domain to prepared statement - if(domain[0] != '\0' && (rc = sqlite3_bind_text(read_stmt, 2, domain, -1, SQLITE_STATIC)) != SQLITE_OK) + if(domain[0] != '\0' && (rc = sqlite3_bind_text(read_stmt, 1, domain, -1, SQLITE_STATIC)) != SQLITE_OK) { *message = sqlite3_errmsg(gravity_db); - logg("gravityDB_readTable(%d): Failed to bind domain (error %d) - %s", - type, rc, *message); + logg("gravityDB_readTable(%d => (%s)): Failed to bind domain (error %d) - %s", + listtype, type, rc, *message); sqlite3_reset(read_stmt); sqlite3_finalize(read_stmt); return false; diff --git a/src/database/gravity-db.h b/src/database/gravity-db.h index 093f9e15c..a119ebeb9 100644 --- a/src/database/gravity-db.h +++ b/src/database/gravity-db.h @@ -44,12 +44,11 @@ bool in_whitelist(const char *domain, const DNSCacheData *dns_cache, const int c bool gravityDB_get_regex_client_groups(clientsData* client, const unsigned int numregex, const regex_data *regex, const unsigned char type, const char* table, const int clientID); -bool gravityDB_readTable(const int type, const char *domain, const char **message); +bool gravityDB_readTable(const enum domainlist_type listtype, const char *domain, const char **message); bool gravityDB_readTableGetDomain(domainrecord *domain, const char **message); void gravityDB_readTableFinalize(void); -bool gravityDB_addToTable(const int type, const char *domain, - const bool enabled, const char *comment, +bool gravityDB_addToTable(const enum domainlist_type listtype, const domainrecord domain, const char **message, const enum http_method method); -bool gravityDB_delFromTable(const int type, const char* domain, const char **message); +bool gravityDB_delFromTable(const enum domainlist_type listtype, const char* domain, const char **message); #endif //GRAVITY_H From 9eb9fea198e14577dd9312bd04d5c2b5fdee565a Mon Sep 17 00:00:00 2001 From: DL6ER Date: Thu, 14 Jan 2021 21:42:46 +0100 Subject: [PATCH 0206/1669] Return type and group_id array for domains Signed-off-by: DL6ER --- src/api/list.c | 19 +++++++++++++++++++ src/database/gravity-db.c | 37 +++++++++++++++++++++++++++++-------- src/database/gravity-db.h | 7 +++++-- 3 files changed, 53 insertions(+), 10 deletions(-) diff --git a/src/api/list.c b/src/api/list.c index 8dc612bda..ec8605ff5 100644 --- a/src/api/list.c +++ b/src/api/list.c @@ -45,6 +45,8 @@ static int get_domainlist(struct mg_connection *conn, while(gravityDB_readTableGetDomain(&domain, &sql_msg)) { cJSON *item = JSON_NEW_OBJ(); + JSON_OBJ_ADD_NUMBER(item, "id", domain.id); + JSON_OBJ_REF_STR(item, "type", domain.type); JSON_OBJ_COPY_STR(item, "domain", domain.domain); JSON_OBJ_ADD_BOOL(item, "enabled", domain.enabled); JSON_OBJ_ADD_NUMBER(item, "date_added", domain.date_added); @@ -54,6 +56,23 @@ static int get_domainlist(struct mg_connection *conn, } else { JSON_OBJ_ADD_NULL(item, "comment"); } + if(domain.group_ids != NULL) { + // Black JSON magic at work here: + // We build a JSON array from the group_concat + // result delivered SQLite3, parse it as valid + // array and append it as item to the data + char group_ids_str[strlen(domain.group_ids)+3u]; + group_ids_str[0] = '['; + strcpy(group_ids_str+1u , domain.group_ids); + group_ids_str[sizeof(group_ids_str)-2u] = ']'; + group_ids_str[sizeof(group_ids_str)-1u] = '\0'; + cJSON * group_ids = cJSON_Parse(group_ids_str); + JSON_OBJ_ADD_ITEM(item, "group_ids", group_ids); + } else { + // Empty group set + cJSON *group_ids = JSON_NEW_ARRAY(); + JSON_OBJ_ADD_ITEM(item, "group_ids", group_ids); + } JSON_ARRAY_ADD_ITEM(domains, item); } diff --git a/src/database/gravity-db.c b/src/database/gravity-db.c index 51c4c1122..47c21a189 100644 --- a/src/database/gravity-db.c +++ b/src/database/gravity-db.c @@ -1611,12 +1611,13 @@ bool gravityDB_readTable(const enum domainlist_type listtype, const char *domain // Build query statement. We have to do it this way // as binding a sequence of ints via a prepared // statement isn't possible in SQLite3 - char querystr[128]; + char querystr[200]; const char *extra = ""; if(domain[0] != '\0') extra = " AND domain = ?;"; - sprintf(querystr, "SELECT domain,enabled,date_added,date_modified,comment FROM domainlist WHERE type IN (%s)%s;", type, extra); - logg("%s", querystr); + sprintf(querystr, "SELECT id,type,domain,enabled,date_added,date_modified,comment," + "(SELECT GROUP_CONCAT(group_id) FROM domainlist_by_group g WHERE g.domainlist_id = d.id) " + "FROM domainlist d WHERE d.type IN (%s)%s;", type, extra); // Prepare SQLite statement int rc = sqlite3_prepare_v2(gravity_db, querystr, -1, &read_stmt, NULL); @@ -1649,11 +1650,31 @@ bool gravityDB_readTableGetDomain(domainrecord *domain, const char **message) // Valid row if(rc == SQLITE_ROW) { - domain->domain = (char*)sqlite3_column_text(read_stmt, 0); - domain->enabled = sqlite3_column_int(read_stmt, 1) != 0; - domain->date_added = sqlite3_column_int(read_stmt, 2); - domain->date_modified = sqlite3_column_int(read_stmt, 3); - domain->comment = (char*)sqlite3_column_text(read_stmt, 4); + domain->id = sqlite3_column_int(read_stmt, 0); + switch(sqlite3_column_int(read_stmt, 1)) + { + case 0: + domain->type = "allow/exact"; + break; + case 1: + domain->type = "deny/exact"; + break; + case 2: + domain->type = "allow/regex"; + break; + case 3: + domain->type = "deny/regex"; + break; + default: + domain->type = "unknown"; + break; + } + domain->domain = (char*)sqlite3_column_text(read_stmt, 2); + domain->enabled = sqlite3_column_int(read_stmt, 3) != 0; + domain->date_added = sqlite3_column_int(read_stmt, 4); + domain->date_modified = sqlite3_column_int(read_stmt, 5); + domain->comment = (char*)sqlite3_column_text(read_stmt, 6); + domain->group_ids = (char*)sqlite3_column_text(read_stmt, 7); return true; } diff --git a/src/database/gravity-db.h b/src/database/gravity-db.h index a119ebeb9..74e84418a 100644 --- a/src/database/gravity-db.h +++ b/src/database/gravity-db.h @@ -18,11 +18,14 @@ #include "../regex_r.h" typedef struct domainrecord { + bool enabled; const char *domain; + const char *comment; + const char *group_ids; + const char *type; + long id; time_t date_added; time_t date_modified; - const char *comment; - bool enabled; } domainrecord; void gravityDB_forked(void); From a45c183e3ea9dd9285a4257378117209b946e01a Mon Sep 17 00:00:00 2001 From: DL6ER Date: Thu, 14 Jan 2021 21:55:46 +0100 Subject: [PATCH 0207/1669] Add /api/group endpoint to read/add/modify/delete groups Signed-off-by: DL6ER --- src/api/list.c | 150 +++++++++++++---------- src/api/routes.c | 8 +- src/api/routes.h | 3 +- src/database/gravity-db.c | 249 ++++++++++++++++++++++++++------------ src/database/gravity-db.h | 19 +-- src/enums.h | 5 +- 6 files changed, 283 insertions(+), 151 deletions(-) diff --git a/src/api/list.c b/src/api/list.c index ec8605ff5..ead055942 100644 --- a/src/api/list.c +++ b/src/api/list.c @@ -14,18 +14,18 @@ #include "routes.h" #include "../database/gravity-db.h" -static int get_domainlist(struct mg_connection *conn, - const int code, - const enum domainlist_type listtype, - const char *domain_filter) +static int get_list(struct mg_connection *conn, + const int code, + const enum gravity_list_type listtype, + const char *filter) { const char *sql_msg = NULL; - if(!gravityDB_readTable(listtype, domain_filter, &sql_msg)) + if(!gravityDB_readTable(listtype, filter, &sql_msg)) { cJSON *json = JSON_NEW_OBJ(); - // Add domain_filter (may be NULL = not available) - JSON_OBJ_REF_STR(json, "domain_filter", domain_filter); + // Add filter (may be NULL = not available) + JSON_OBJ_REF_STR(json, "filter", filter); // Add SQL message (may be NULL = not available) if (sql_msg != NULL) { @@ -40,58 +40,78 @@ static int get_domainlist(struct mg_connection *conn, json); } - domainrecord domain; - cJSON *domains = JSON_NEW_ARRAY(); - while(gravityDB_readTableGetDomain(&domain, &sql_msg)) + tablerow row; + cJSON *items = JSON_NEW_ARRAY(); + while(gravityDB_readTableGetRow(&row, &sql_msg)) { cJSON *item = JSON_NEW_OBJ(); - JSON_OBJ_ADD_NUMBER(item, "id", domain.id); - JSON_OBJ_REF_STR(item, "type", domain.type); - JSON_OBJ_COPY_STR(item, "domain", domain.domain); - JSON_OBJ_ADD_BOOL(item, "enabled", domain.enabled); - JSON_OBJ_ADD_NUMBER(item, "date_added", domain.date_added); - JSON_OBJ_ADD_NUMBER(item, "date_modified", domain.date_modified); - if(domain.comment != NULL) { - JSON_OBJ_COPY_STR(item, "comment", domain.comment); - } else { - JSON_OBJ_ADD_NULL(item, "comment"); + JSON_OBJ_ADD_NUMBER(item, "id", row.id); + JSON_OBJ_ADD_BOOL(item, "enabled", row.enabled); + + // Special fields + if(listtype == GRAVITY_GROUPS) + { + JSON_OBJ_COPY_STR(item, "name", row.name); + if(row.description != NULL) { + JSON_OBJ_COPY_STR(item, "description", row.description); + } else { + JSON_OBJ_ADD_NULL(item, "description"); + } } - if(domain.group_ids != NULL) { - // Black JSON magic at work here: - // We build a JSON array from the group_concat - // result delivered SQLite3, parse it as valid - // array and append it as item to the data - char group_ids_str[strlen(domain.group_ids)+3u]; - group_ids_str[0] = '['; - strcpy(group_ids_str+1u , domain.group_ids); - group_ids_str[sizeof(group_ids_str)-2u] = ']'; - group_ids_str[sizeof(group_ids_str)-1u] = '\0'; - cJSON * group_ids = cJSON_Parse(group_ids_str); - JSON_OBJ_ADD_ITEM(item, "group_ids", group_ids); - } else { - // Empty group set - cJSON *group_ids = JSON_NEW_ARRAY(); - JSON_OBJ_ADD_ITEM(item, "group_ids", group_ids); + else // domainlists + { + JSON_OBJ_REF_STR(item, "type", row.type); + JSON_OBJ_COPY_STR(item, "domain", row.domain); + if(row.comment != NULL) { + JSON_OBJ_COPY_STR(item, "comment", row.comment); + } else { + JSON_OBJ_ADD_NULL(item, "comment"); + } + if(row.group_ids != NULL) { + // Black JSON magic at work here: + // We build a JSON array from the group_concat + // result delivered SQLite3, parse it as valid + // array and append it as item to the data + char group_ids_str[strlen(row.group_ids)+3u]; + group_ids_str[0] = '['; + strcpy(group_ids_str+1u , row.group_ids); + group_ids_str[sizeof(group_ids_str)-2u] = ']'; + group_ids_str[sizeof(group_ids_str)-1u] = '\0'; + cJSON * group_ids = cJSON_Parse(group_ids_str); + JSON_OBJ_ADD_ITEM(item, "group_ids", group_ids); + } else { + // Empty group set + cJSON *group_ids = JSON_NEW_ARRAY(); + JSON_OBJ_ADD_ITEM(item, "group_ids", group_ids); + } } + + JSON_OBJ_ADD_NUMBER(item, "date_added", row.date_added); + JSON_OBJ_ADD_NUMBER(item, "date_modified", row.date_modified); - JSON_ARRAY_ADD_ITEM(domains, item); + JSON_ARRAY_ADD_ITEM(items, item); } gravityDB_readTableFinalize(); if(sql_msg == NULL) { - // No error, send requested HTTP code + // No error, send domains array + const char *objname; cJSON *json = JSON_NEW_OBJ(); - JSON_OBJ_ADD_ITEM(json, "domains", domains); + if(listtype == GRAVITY_GROUPS) + objname = "groups"; + else // domainlists + objname = "domains"; + JSON_OBJ_ADD_ITEM(json, objname, items); JSON_SEND_OBJECT_CODE(json, code); } else { - JSON_DELETE(domains); + JSON_DELETE(items); cJSON *json = JSON_NEW_OBJ(); - // Add domain_filter (may be NULL = not available) - JSON_OBJ_REF_STR(json, "domain_filter", domain_filter); + // Add filter (may be NULL = not available) + JSON_OBJ_REF_STR(json, "filter", filter); // Add SQL message (may be NULL = not available) if (sql_msg != NULL) { @@ -107,8 +127,8 @@ static int get_domainlist(struct mg_connection *conn, } } -static int api_dns_domainlist_read(struct mg_connection *conn, - const enum domainlist_type listtype) +static int api_list_read(struct mg_connection *conn, + const enum gravity_list_type listtype) { // Extract domain from path (option for GET) const struct mg_request_info *request = mg_get_request_info(conn); @@ -123,17 +143,18 @@ static int api_dns_domainlist_read(struct mg_connection *conn, strcmp(encoded_uri, "regex") != 0 && strcmp(encoded_uri, "allow") != 0 && strcmp(encoded_uri, "deny") != 0 && - strcmp(encoded_uri, "list") != 0) + strcmp(encoded_uri, "list") != 0 && + strcmp(encoded_uri, "group") != 0) mg_url_decode(encoded_uri, strlen(encoded_uri), domain_filter, sizeof(domain_filter), 0); - return get_domainlist(conn, 200, listtype, domain_filter); + return get_list(conn, 200, listtype, domain_filter); } -static int api_dns_domainlist_write(struct mg_connection *conn, - const enum domainlist_type listtype, - const enum http_method method) +static int api_list_write(struct mg_connection *conn, + const enum gravity_list_type listtype, + const enum http_method method) { - domainrecord domain; + tablerow domain; // Extract payload char buffer[1024] = { 0 }; @@ -182,7 +203,7 @@ static int api_dns_domainlist_write(struct mg_connection *conn, { cJSON_Delete(obj); // Send GET style reply with code 201 Created - return get_domainlist(conn, 201, listtype, domain.domain); + return get_list(conn, 201, listtype, domain.domain); } else { @@ -219,8 +240,8 @@ static int api_dns_domainlist_write(struct mg_connection *conn, } } -static int api_dns_domainlist_remove(struct mg_connection *conn, - const enum domainlist_type listtype) +static int api_list_remove(struct mg_connection *conn, + const enum gravity_list_type listtype) { const struct mg_request_info *request = mg_get_request_info(conn); @@ -257,7 +278,7 @@ static int api_dns_domainlist_remove(struct mg_connection *conn, } } -int api_dns_domainlist(struct mg_connection *conn) +int api_list(struct mg_connection *conn) { // Verify requesting client is allowed to see this ressource if(check_client_auth(conn) == API_AUTH_UNAUTHORIZED) @@ -265,10 +286,15 @@ int api_dns_domainlist(struct mg_connection *conn) return send_json_unauthorized(conn); } - enum domainlist_type listtype; + enum gravity_list_type listtype; bool can_modify = false; const struct mg_request_info *request = mg_get_request_info(conn); - if(startsWith("/api/list/allow", request->local_uri)) + if(startsWith("/api/group", request->local_uri)) + { + listtype = GRAVITY_GROUPS; + can_modify = true; + } + else if(startsWith("/api/list/allow", request->local_uri)) { if(startsWith("/api/list/allow/exact", request->local_uri)) { @@ -311,19 +337,17 @@ int api_dns_domainlist(struct mg_connection *conn) const enum http_method method = http_method(conn); if(method == HTTP_GET) { - return api_dns_domainlist_read(conn, listtype); + return api_list_read(conn, listtype); } else if(can_modify && (method == HTTP_POST || method == HTTP_PUT || method == HTTP_PATCH)) { - // Add domain from exact allow-/denylist when a user sends - // the request to the general address /api/dns/{allow,deny}list - return api_dns_domainlist_write(conn, listtype, method); + // Add item from list + return api_list_write(conn, listtype, method); } else if(can_modify && method == HTTP_DELETE) { - // Delete domain from exact allow-/denylist when a user sends - // the request to the general address /api/dns/{allow,deny}list - return api_dns_domainlist_remove(conn, listtype); + // Delete item from list + return api_list_remove(conn, listtype); } else if(!can_modify) { diff --git a/src/api/routes.c b/src/api/routes.c index 7b2d6b950..e2c28072c 100644 --- a/src/api/routes.c +++ b/src/api/routes.c @@ -37,10 +37,14 @@ int api_handler(struct mg_connection *conn, void *ignored) { ret = api_dns_cacheinfo(conn); } - /******************************** /api/allowlist ****************************/ + /******************************** /api/list, /api/group ****************************/ else if(startsWith("/api/list", request->local_uri)) { - ret = api_dns_domainlist(conn); + ret = api_list(conn); + } + else if(startsWith("/api/group", request->local_uri)) + { + ret = api_list(conn); } /******************************** /api/ftl ****************************/ else if(startsWith("/api/ftl/client", request->local_uri)) diff --git a/src/api/routes.h b/src/api/routes.h index 66a9dd9d2..20e762e9a 100644 --- a/src/api/routes.h +++ b/src/api/routes.h @@ -49,7 +49,8 @@ int api_dns_blockingstatus(struct mg_connection *conn); int api_dns_cacheinfo(struct mg_connection *conn); // List methods -int api_dns_domainlist(struct mg_connection *conn); +int api_list(struct mg_connection *conn); +int api_group(struct mg_connection *conn); // Version method int api_version(struct mg_connection *conn); diff --git a/src/database/gravity-db.c b/src/database/gravity-db.c index 47c21a189..7d8f3516d 100644 --- a/src/database/gravity-db.c +++ b/src/database/gravity-db.c @@ -1360,7 +1360,7 @@ bool gravityDB_get_regex_client_groups(clientsData* client, const unsigned int n return true; } -bool gravityDB_addToTable(const enum domainlist_type listtype, const domainrecord domain, +bool gravityDB_addToTable(const enum gravity_list_type listtype, const tablerow row, const char **message, const enum http_method method) { if(gravity_db == NULL) @@ -1369,7 +1369,7 @@ bool gravityDB_addToTable(const enum domainlist_type listtype, const domainrecor return false; } - int type; + int type = -1; switch (listtype) { case GRAVITY_DOMAINLIST_ALLOW_EXACT: @@ -1385,6 +1385,10 @@ bool gravityDB_addToTable(const enum domainlist_type listtype, const domainrecor type = 3; break; + case GRAVITY_GROUPS: + // No type required for this table + break; + // Aggregate types cannot be handled by this routine case GRAVITY_DOMAINLIST_ALLOW_ALL: case GRAVITY_DOMAINLIST_DENY_ALL: @@ -1399,62 +1403,101 @@ bool gravityDB_addToTable(const enum domainlist_type listtype, const domainrecor sqlite3_stmt* stmt = NULL; const char *querystr; if(method == HTTP_POST) // Create NEW entry, error if existing - querystr = "INSERT INTO domainlist (domain,type,enabled,comment) VALUES (?1,?2,?3,?4);"; + { + if(listtype == GRAVITY_GROUPS) + querystr = "INSERT INTO \"group\" (name,enabled,description) VALUES (:name,:enabled,:description);"; + else // domainlist + querystr = "INSERT INTO domainlist (domain,type,enabled,comment) VALUES (:domain,:type,:enabled,:comment);"; + } else // Create new or replace existing entry, no error if existing // We have to use a subquery here to avoid violating FOREIGN KEY - // contraints (REPLACE recreates (= new ID) entries instead of updatinging them) - querystr = "REPLACE INTO domainlist (domain,type,enabled,comment,id,date_added) " - "VALUES (?1,?2,?3,?4," - "(SELECT id FROM domainlist WHERE domain = ?1 and type = ?2)," - "(SELECT date_added FROM domainlist WHERE domain = ?1 and type = ?2));"; + // contraints (REPLACE recreates (= new ID) entries instead of updating them) + if(listtype == GRAVITY_GROUPS) + querystr = "REPLACE INTO \"group\" (name,enabled,description,id,date_added) " + "VALUES (:name,:enabled,:description," + "(SELECT id FROM \"group\" WHERE name = :name)," + "(SELECT date_added FROM \"group\" WHERE name = :name));"; + else // domainlist + querystr = "REPLACE INTO domainlist (domain,type,enabled,comment,id,date_added) " + "VALUES (:domain,:type,:enabled,:comment," + "(SELECT id FROM domainlist WHERE domain = :domain and type = :type)," + "(SELECT date_added FROM domainlist WHERE domain = :domain and type = :type));"; int rc = sqlite3_prepare_v2(gravity_db, querystr, -1, &stmt, NULL); if( rc != SQLITE_OK ) { *message = sqlite3_errmsg(gravity_db); - logg("gravityDB_addToTable(%d, %s) - SQL error prepare (%i): %s", - type, domain.domain, rc, *message); + logg("gravityDB_addToTable(%d, %s, %s) - SQL error prepare (%i): %s", + type, row.domain, row.name, rc, *message); + return false; + } + + // Bind domain to prepared statement (if requested) + int idx = sqlite3_bind_parameter_index(stmt, "domain"); + if(idx > 0 && (rc = sqlite3_bind_text(stmt, idx, row.domain, -1, SQLITE_STATIC)) != SQLITE_OK) + { + *message = sqlite3_errmsg(gravity_db); + logg("gravityDB_addToTable(%d, %s, %s): Failed to bind domain (error %d) - %s", + type, row.domain, row.name, rc, *message); + sqlite3_reset(stmt); + sqlite3_finalize(stmt); return false; } - // Bind domain string to prepared statement - if((rc = sqlite3_bind_text(stmt, 1, domain.domain, -1, SQLITE_STATIC)) != SQLITE_OK) + // Bind name to prepared statement (if requested) + idx = sqlite3_bind_parameter_index(stmt, "name"); + if(idx > 0 && (rc = sqlite3_bind_text(stmt, idx, row.name, -1, SQLITE_STATIC)) != SQLITE_OK) { *message = sqlite3_errmsg(gravity_db); - logg("gravityDB_addToTable(%d, %s): Failed to bind domain (error %d) - %s", - type, domain.domain, rc, *message); + logg("gravityDB_addToTable(%d, %s, %s): Failed to bind name (error %d) - %s", + type, row.domain, row.name, rc, *message); sqlite3_reset(stmt); sqlite3_finalize(stmt); return false; } - // Bind domain type to prepared statement - if((rc = sqlite3_bind_int(stmt, 2, type)) != SQLITE_OK) + // Bind type to prepared statement (if requested) + idx = sqlite3_bind_parameter_index(stmt, "type"); + if(idx > 0 && (rc = sqlite3_bind_int(stmt, idx, type)) != SQLITE_OK) { *message = sqlite3_errmsg(gravity_db); - logg("gravityDB_addToTable(%d, %s): Failed to bind domain (error %d) - %s", - type, domain.domain, rc, *message); + logg("gravityDB_addToTable(%d, %s, %s): Failed to bind type (error %d) - %s", + type, row.domain, row.name, rc, *message); sqlite3_reset(stmt); sqlite3_finalize(stmt); return false; } - // Bind enabled boolean to prepared statement - if((rc = sqlite3_bind_int(stmt, 3, domain.enabled ? 1 : 0)) != SQLITE_OK) + // Bind enabled boolean to prepared statement (if requested) + idx = sqlite3_bind_parameter_index(stmt, "enabled"); + if(idx > 0 && (rc = sqlite3_bind_int(stmt, idx, row.enabled ? 1 : 0)) != SQLITE_OK) { *message = sqlite3_errmsg(gravity_db); - logg("gravityDB_addToTable(%d, %s): Failed to bind enabled (error %d) - %s", - type, domain.domain, rc, *message); + logg("gravityDB_addToTable(%d, %s, %s): Failed to bind enabled (error %d) - %s", + type, row.domain, row.name, rc, *message); sqlite3_reset(stmt); sqlite3_finalize(stmt); return false; } - // Bind enabled boolean to prepared statement - if((rc = sqlite3_bind_text(stmt, 4, domain.comment, -1, SQLITE_STATIC)) != SQLITE_OK) + // Bind comment string to prepared statement (if requested) + idx = sqlite3_bind_parameter_index(stmt, "comment"); + if(idx > 0 && (rc = sqlite3_bind_text(stmt, idx, row.comment, -1, SQLITE_STATIC)) != SQLITE_OK) { *message = sqlite3_errmsg(gravity_db); - logg("gravityDB_addToTable(%d, %s): Failed to bind comment (error %d) - %s", - type, domain.domain, rc, *message); + logg("gravityDB_addToTable(%d, %s, %s): Failed to bind comment (error %d) - %s", + type, row.domain, row.name, rc, *message); + sqlite3_reset(stmt); + sqlite3_finalize(stmt); + return false; + } + + // Bind description string to prepared statement (if requested) + idx = sqlite3_bind_parameter_index(stmt, "description"); + if(idx > 0 && (rc = sqlite3_bind_text(stmt, idx, row.description, -1, SQLITE_STATIC)) != SQLITE_OK) + { + *message = sqlite3_errmsg(gravity_db); + logg("gravityDB_addToTable(%d, %s, %s): Failed to bind description (error %d) - %s", + type, row.domain, row.name, rc, *message); sqlite3_reset(stmt); sqlite3_finalize(stmt); return false; @@ -1479,7 +1522,7 @@ bool gravityDB_addToTable(const enum domainlist_type listtype, const domainrecor return okay; } -bool gravityDB_delFromTable(const enum domainlist_type listtype, const char* domain, const char **message) +bool gravityDB_delFromTable(const enum gravity_list_type listtype, const char* domain_name, const char **message) { if(gravity_db == NULL) { @@ -1487,7 +1530,7 @@ bool gravityDB_delFromTable(const enum domainlist_type listtype, const char* dom return false; } - int type; + int type = -1; switch (listtype) { case GRAVITY_DOMAINLIST_ALLOW_EXACT: @@ -1502,6 +1545,10 @@ bool gravityDB_delFromTable(const enum domainlist_type listtype, const char* dom case GRAVITY_DOMAINLIST_DENY_REGEX: type = 3; break; + + case GRAVITY_GROUPS: + // No type required for this table + break; // Aggregate types cannot be handled by this routine case GRAVITY_DOMAINLIST_ALLOW_ALL: @@ -1515,33 +1562,39 @@ bool gravityDB_delFromTable(const enum domainlist_type listtype, const char* dom // Prepare SQLite statement sqlite3_stmt* stmt = NULL; - const char *querystr = "DELETE FROM domainlist WHERE domain = ? AND type = ?;"; + const char *querystr = ""; + if(listtype == GRAVITY_GROUPS) + querystr = "DELETE FROM \"group\" WHERE name = :domain_name;"; + else // domainlist + querystr = "DELETE FROM domainlist WHERE domain = :domain_name AND type = :type;"; int rc = sqlite3_prepare_v2(gravity_db, querystr, -1, &stmt, NULL); if( rc != SQLITE_OK ) { *message = sqlite3_errmsg(gravity_db); logg("gravityDB_delFromTable(%d, %s) - SQL error prepare (%i): %s", - type, domain, rc, *message); + type, domain_name, rc, *message); return false; } - // Bind domain to prepared statement - if((rc = sqlite3_bind_text(stmt, 1, domain, -1, SQLITE_STATIC)) != SQLITE_OK) + // Bind domain to prepared statement (if requested) + int idx = sqlite3_bind_parameter_index(stmt, "domain_name"); + if(idx > 0 && (rc = sqlite3_bind_text(stmt, idx, domain_name, -1, SQLITE_STATIC)) != SQLITE_OK) { *message = sqlite3_errmsg(gravity_db); - logg("gravityDB_delFromTable(%d, %s): Failed to bind domain (error %d) - %s", - type, domain, rc, *message); + logg("gravityDB_delFromTable(%d, %s): Failed to bind domain/name (error %d) - %s", + type, domain_name, rc, *message); sqlite3_reset(stmt); sqlite3_finalize(stmt); return false; } - // Bind domain type to prepared statement - if((rc = sqlite3_bind_int(stmt, 2, type)) != SQLITE_OK) + // Bind type to prepared statement (if requested) + idx = sqlite3_bind_parameter_index(stmt, "type"); + if(idx > 0 && (rc = sqlite3_bind_int(stmt, idx, type)) != SQLITE_OK) { *message = sqlite3_errmsg(gravity_db); - logg("gravityDB_delFromTable(%d, %s): Failed to bind domain (error %d) - %s", - type, domain, rc, *message); + logg("gravityDB_delFromTable(%d, %s): Failed to bind type (error %d) - %s", + type, domain_name, rc, *message); sqlite3_reset(stmt); sqlite3_finalize(stmt); return false; @@ -1567,7 +1620,7 @@ bool gravityDB_delFromTable(const enum domainlist_type listtype, const char* dom } static sqlite3_stmt* read_stmt = NULL; -bool gravityDB_readTable(const enum domainlist_type listtype, const char *domain, const char **message) +bool gravityDB_readTable(const enum gravity_list_type listtype, const char *filter, const char **message) { if(gravity_db == NULL) { @@ -1576,7 +1629,7 @@ bool gravityDB_readTable(const enum domainlist_type listtype, const char *domain } // Get filter string for the requested list type - const char *type = ""; + const char *type = "N/A"; switch (listtype) { case GRAVITY_DOMAINLIST_ALLOW_EXACT: @@ -1606,18 +1659,30 @@ bool gravityDB_readTable(const enum domainlist_type listtype, const char *domain case GRAVITY_DOMAINLIST_ALL_ALL: type = "0,1,2,3"; break; + case GRAVITY_GROUPS: + // No type required for this table + break; } // Build query statement. We have to do it this way - // as binding a sequence of ints via a prepared + // as binding a sequence of int via a prepared // statement isn't possible in SQLite3 - char querystr[200]; + char querystr[256]; const char *extra = ""; - if(domain[0] != '\0') - extra = " AND domain = ?;"; - sprintf(querystr, "SELECT id,type,domain,enabled,date_added,date_modified,comment," - "(SELECT GROUP_CONCAT(group_id) FROM domainlist_by_group g WHERE g.domainlist_id = d.id) " - "FROM domainlist d WHERE d.type IN (%s)%s;", type, extra); + if(listtype == GRAVITY_GROUPS) + { + if(filter[0] != '\0') + extra = " WHERE name = :filter;"; + sprintf(querystr, "SELECT id,name,enabled,date_added,date_modified,description FROM \"group\"%s;", extra); + } + else // domainlist + { + if(filter[0] != '\0') + extra = " AND domain = :filter;"; + sprintf(querystr, "SELECT id,type,domain,enabled,date_added,date_modified,comment," + "(SELECT GROUP_CONCAT(group_id) FROM domainlist_by_group g WHERE g.domainlist_id = d.id) AS group_ids " + "FROM domainlist d WHERE d.type IN (%s)%s;", type, extra); + } // Prepare SQLite statement int rc = sqlite3_prepare_v2(gravity_db, querystr, -1, &read_stmt, NULL); @@ -1628,12 +1693,13 @@ bool gravityDB_readTable(const enum domainlist_type listtype, const char *domain return false; } - // Bind domain to prepared statement - if(domain[0] != '\0' && (rc = sqlite3_bind_text(read_stmt, 1, domain, -1, SQLITE_STATIC)) != SQLITE_OK) + // Bind filter to prepared statement (if requested) + int idx = sqlite3_bind_parameter_index(read_stmt, "filter"); + if(idx > 0 && (rc = sqlite3_bind_text(read_stmt, idx, filter, -1, SQLITE_STATIC)) != SQLITE_OK) { *message = sqlite3_errmsg(gravity_db); - logg("gravityDB_readTable(%d => (%s)): Failed to bind domain (error %d) - %s", - listtype, type, rc, *message); + logg("gravityDB_readTable(%d => (%s), %s): Failed to bind filter (error %d) - %s", + listtype, type, filter, rc, *message); sqlite3_reset(read_stmt); sqlite3_finalize(read_stmt); return false; @@ -1642,7 +1708,7 @@ bool gravityDB_readTable(const enum domainlist_type listtype, const char *domain return true; } -bool gravityDB_readTableGetDomain(domainrecord *domain, const char **message) +bool gravityDB_readTableGetRow(tablerow *row, const char **message) { // Perform step const int rc = sqlite3_step(read_stmt); @@ -1650,31 +1716,62 @@ bool gravityDB_readTableGetDomain(domainrecord *domain, const char **message) // Valid row if(rc == SQLITE_ROW) { - domain->id = sqlite3_column_int(read_stmt, 0); - switch(sqlite3_column_int(read_stmt, 1)) + const int cols = sqlite3_column_count(read_stmt); + for(int c = 0; c < cols; c++) { - case 0: - domain->type = "allow/exact"; - break; - case 1: - domain->type = "deny/exact"; - break; - case 2: - domain->type = "allow/regex"; - break; - case 3: - domain->type = "deny/regex"; - break; - default: - domain->type = "unknown"; - break; + const char *cname = sqlite3_column_name(read_stmt, c); + logg("API: %d = %s", c, cname); + if(strcasecmp(cname, "id") == 0) + row->id = sqlite3_column_int(read_stmt, c); + + else if(strcasecmp(cname, "type") == 0) + { + switch(sqlite3_column_int(read_stmt, c)) + { + case 0: + row->type = "allow/exact"; + break; + case 1: + row->type = "deny/exact"; + break; + case 2: + row->type = "allow/regex"; + break; + case 3: + row->type = "deny/regex"; + break; + default: + row->type = "unknown"; + break; + } + } + + else if(strcasecmp(cname, "domain") == 0) + row->domain = (char*)sqlite3_column_text(read_stmt, c); + + else if(strcasecmp(cname, "enabled") == 0) + row->enabled = sqlite3_column_int(read_stmt, c) != 0; + + else if(strcasecmp(cname, "date_added") == 0) + row->date_added = sqlite3_column_int(read_stmt, c); + + else if(strcasecmp(cname, "date_modified") == 0) + row->date_modified = sqlite3_column_int(read_stmt, c); + + else if(strcasecmp(cname, "comment") == 0) + row->comment = (char*)sqlite3_column_text(read_stmt, c); + + else if(strcasecmp(cname, "group_ids") == 0) + row->group_ids = (char*)sqlite3_column_text(read_stmt, c); + + else if(strcasecmp(cname, "name") == 0) + row->name = (char*)sqlite3_column_text(read_stmt, c); + + else if(strcasecmp(cname, "description") == 0) + row->description = (char*)sqlite3_column_text(read_stmt, c); + else + logg("Internal API error: Encountered unknown column %s", cname); } - domain->domain = (char*)sqlite3_column_text(read_stmt, 2); - domain->enabled = sqlite3_column_int(read_stmt, 3) != 0; - domain->date_added = sqlite3_column_int(read_stmt, 4); - domain->date_modified = sqlite3_column_int(read_stmt, 5); - domain->comment = (char*)sqlite3_column_text(read_stmt, 6); - domain->group_ids = (char*)sqlite3_column_text(read_stmt, 7); return true; } @@ -1684,7 +1781,7 @@ bool gravityDB_readTableGetDomain(domainrecord *domain, const char **message) if(rc != SQLITE_DONE) { *message = sqlite3_errmsg(gravity_db); - logg("gravityDB_readTableGetDomain() - SQL error step (%i): %s", + logg("gravityDB_readTableGetRow() - SQL error step (%i): %s", rc, *message); return false; } diff --git a/src/database/gravity-db.h b/src/database/gravity-db.h index 74e84418a..8cbda423d 100644 --- a/src/database/gravity-db.h +++ b/src/database/gravity-db.h @@ -17,16 +17,21 @@ // Definition of struct regex_data #include "../regex_r.h" -typedef struct domainrecord { +// Table row record, not all fields are used by all tables +typedef struct { bool enabled; + bool comment_null; + bool description_null; + const char *name; const char *domain; + const char *type; const char *comment; const char *group_ids; - const char *type; + const char *description; long id; time_t date_added; time_t date_modified; -} domainrecord; +} tablerow; void gravityDB_forked(void); void gravityDB_reopen(void); @@ -47,11 +52,11 @@ bool in_whitelist(const char *domain, const DNSCacheData *dns_cache, const int c bool gravityDB_get_regex_client_groups(clientsData* client, const unsigned int numregex, const regex_data *regex, const unsigned char type, const char* table, const int clientID); -bool gravityDB_readTable(const enum domainlist_type listtype, const char *domain, const char **message); -bool gravityDB_readTableGetDomain(domainrecord *domain, const char **message); +bool gravityDB_readTable(const enum gravity_list_type listtype, const char *filter, const char **message); +bool gravityDB_readTableGetRow(tablerow *row, const char **message); void gravityDB_readTableFinalize(void); -bool gravityDB_addToTable(const enum domainlist_type listtype, const domainrecord domain, +bool gravityDB_addToTable(const enum gravity_list_type listtype, const tablerow row, const char **message, const enum http_method method); -bool gravityDB_delFromTable(const enum domainlist_type listtype, const char* domain, const char **message); +bool gravityDB_delFromTable(const enum gravity_list_type listtype, const char* domain_name, const char **message); #endif //GRAVITY_H diff --git a/src/enums.h b/src/enums.h index f44d2a722..b0e1a4981 100644 --- a/src/enums.h +++ b/src/enums.h @@ -156,7 +156,7 @@ enum events { } __attribute__ ((packed)); -enum domainlist_type { +enum gravity_list_type { GRAVITY_DOMAINLIST_ALLOW_EXACT, GRAVITY_DOMAINLIST_ALLOW_REGEX, GRAVITY_DOMAINLIST_ALLOW_ALL, @@ -165,7 +165,8 @@ enum domainlist_type { GRAVITY_DOMAINLIST_DENY_ALL, GRAVITY_DOMAINLIST_ALL_EXACT, GRAVITY_DOMAINLIST_ALL_REGEX, - GRAVITY_DOMAINLIST_ALL_ALL + GRAVITY_DOMAINLIST_ALL_ALL, + GRAVITY_GROUPS } __attribute__ ((packed)); enum gravity_tables { From a8a26db9c8240b2c5e1ecb96df47462e359759a8 Mon Sep 17 00:00:00 2001 From: DL6ER Date: Thu, 14 Jan 2021 21:59:28 +0100 Subject: [PATCH 0208/1669] Add /api/adlist endpoint to read/add/modify/delete adlists Signed-off-by: DL6ER --- src/api/list.c | 138 ++++++++++++++++++++++++++++---------- src/api/routes.c | 6 +- src/database/gravity-db.c | 53 ++++++++++++--- src/database/gravity-db.h | 1 + src/enums.h | 3 +- 5 files changed, 154 insertions(+), 47 deletions(-) diff --git a/src/api/list.c b/src/api/list.c index ead055942..61a8b3758 100644 --- a/src/api/list.c +++ b/src/api/list.c @@ -58,6 +58,15 @@ static int get_list(struct mg_connection *conn, JSON_OBJ_ADD_NULL(item, "description"); } } + else if(listtype == GRAVITY_ADLISTS) + { + JSON_OBJ_COPY_STR(item, "address", row.address); + if(row.comment != NULL) { + JSON_OBJ_COPY_STR(item, "comment", row.comment); + } else { + JSON_OBJ_ADD_NULL(item, "comment"); + } + } else // domainlists { JSON_OBJ_REF_STR(item, "type", row.type); @@ -100,6 +109,8 @@ static int get_list(struct mg_connection *conn, cJSON *json = JSON_NEW_OBJ(); if(listtype == GRAVITY_GROUPS) objname = "groups"; + else if(listtype == GRAVITY_ADLISTS) + objname = "adlists"; else // domainlists objname = "domains"; JSON_OBJ_ADD_ITEM(json, objname, items); @@ -122,7 +133,7 @@ static int get_list(struct mg_connection *conn, return send_json_error(conn, 402, // 402 Request Failed "database_error", - "Could not read domains from database table", + "Could not read from gravity database", json); } } @@ -144,7 +155,8 @@ static int api_list_read(struct mg_connection *conn, strcmp(encoded_uri, "allow") != 0 && strcmp(encoded_uri, "deny") != 0 && strcmp(encoded_uri, "list") != 0 && - strcmp(encoded_uri, "group") != 0) + strcmp(encoded_uri, "group") != 0 && + strcmp(encoded_uri, "adlist") != 0) mg_url_decode(encoded_uri, strlen(encoded_uri), domain_filter, sizeof(domain_filter), 0); return get_list(conn, 200, listtype, domain_filter); @@ -154,7 +166,30 @@ static int api_list_write(struct mg_connection *conn, const enum gravity_list_type listtype, const enum http_method method) { - tablerow domain; + tablerow row; + bool need_domain = false, need_name = false, need_address = false; + switch (listtype) + { + case GRAVITY_GROUPS: + need_name = true; + break; + + case GRAVITY_ADLISTS: + need_address = true; + break; + + case GRAVITY_DOMAINLIST_ALLOW_EXACT: + case GRAVITY_DOMAINLIST_DENY_EXACT: + case GRAVITY_DOMAINLIST_ALLOW_REGEX: + case GRAVITY_DOMAINLIST_DENY_REGEX: + case GRAVITY_DOMAINLIST_ALLOW_ALL: + case GRAVITY_DOMAINLIST_DENY_ALL: + case GRAVITY_DOMAINLIST_ALL_EXACT: + case GRAVITY_DOMAINLIST_ALL_REGEX: + case GRAVITY_DOMAINLIST_ALL_ALL: + need_domain = true; + break; + } // Extract payload char buffer[1024] = { 0 }; @@ -175,52 +210,80 @@ static int api_list_write(struct mg_connection *conn, NULL); } + const char *argument = ""; cJSON *elem_domain = cJSON_GetObjectItemCaseSensitive(obj, "domain"); - if (!cJSON_IsString(elem_domain)) { - cJSON_Delete(obj); - return send_json_error(conn, 400, - "bad_request", - "No \"domain\" string in body data", - NULL); + if(need_domain) + { + if (!cJSON_IsString(elem_domain)) { + cJSON_Delete(obj); + return send_json_error(conn, 400, + "bad_request", + "No \"domain\" string in body data", + NULL); + } + row.domain = elem_domain->valuestring; + argument = row.domain; + } + + cJSON *elem_name = cJSON_GetObjectItemCaseSensitive(obj, "name"); + if(need_name) + { + if (!cJSON_IsString(elem_name)) { + cJSON_Delete(obj); + return send_json_error(conn, 400, + "bad_request", + "No \"name\" string in body data", + NULL); + } + row.name = elem_name->valuestring; + argument = row.name; + } + + cJSON *elem_address = cJSON_GetObjectItemCaseSensitive(obj, "address"); + if(need_address) + { + if (!cJSON_IsString(elem_address)) { + cJSON_Delete(obj); + return send_json_error(conn, 400, + "bad_request", + "No \"address\" string in body data", + NULL); + } + row.address = elem_address->valuestring; + argument = row.address; } - domain.domain = elem_domain->valuestring; - domain.enabled = true; + row.enabled = true; cJSON *elem_enabled = cJSON_GetObjectItemCaseSensitive(obj, "enabled"); if (cJSON_IsBool(elem_enabled)) { - domain.enabled = cJSON_IsTrue(elem_enabled); + row.enabled = cJSON_IsTrue(elem_enabled); } - domain.comment = NULL; + row.comment = NULL; cJSON *elem_comment = cJSON_GetObjectItemCaseSensitive(obj, "comment"); if (cJSON_IsString(elem_comment)) { - domain.comment = elem_comment->valuestring; + row.comment = elem_comment->valuestring; + } + + row.description = NULL; + cJSON *elem_description = cJSON_GetObjectItemCaseSensitive(obj, "description"); + if (cJSON_IsString(elem_description)) { + row.description = elem_description->valuestring; } // Try to add domain to table const char *sql_msg = NULL; - if(gravityDB_addToTable(listtype, domain, &sql_msg, method)) + if(gravityDB_addToTable(listtype, row, &sql_msg, method)) { cJSON_Delete(obj); // Send GET style reply with code 201 Created - return get_list(conn, 201, listtype, domain.domain); + return get_list(conn, 201, listtype, argument); } else { // Error adding domain, prepare error object - // Add domain cJSON *json = JSON_NEW_OBJ(); - JSON_OBJ_COPY_STR(json, "domain", domain.domain); - - // Add enabled boolean - JSON_OBJ_ADD_BOOL(json, "enabled", domain.enabled); - - // Add comment (may be NULL) - if (domain.comment != NULL) { - JSON_OBJ_COPY_STR(json, "comment", domain.comment); - } else { - JSON_OBJ_ADD_NULL(json, "comment"); - } + JSON_OBJ_COPY_STR(json, "argument", argument); // Only delete payload object after having extracted the data cJSON_Delete(obj); @@ -235,33 +298,33 @@ static int api_list_write(struct mg_connection *conn, // Send error reply return send_json_error(conn, 402, // 402 Request Failed "database_error", - "Could not add domain to gravity database", + "Could not add to gravity database", json); } } static int api_list_remove(struct mg_connection *conn, - const enum gravity_list_type listtype) + const enum gravity_list_type listtype) { const struct mg_request_info *request = mg_get_request_info(conn); - char domain[1024] = { 0 }; + char argument[1024] = { 0 }; // Advance one character to strip "/" const char *encoded_uri = strrchr(request->local_uri, '/')+1u; // Decode URL (necessary for regular expressions, harmless for domains) - mg_url_decode(encoded_uri, strlen(encoded_uri), domain, sizeof(domain)-1u, 0); + mg_url_decode(encoded_uri, strlen(encoded_uri), argument, sizeof(argument)-1u, 0); cJSON *json = JSON_NEW_OBJ(); const char *sql_msg = NULL; - if(gravityDB_delFromTable(listtype, domain, &sql_msg)) + if(gravityDB_delFromTable(listtype, argument, &sql_msg)) { // Send empty reply with code 204 No Content JSON_SEND_OBJECT_CODE(json, 204); } else { - // Add domain - JSON_OBJ_REF_STR(json, "domain", domain); + // Add argument + JSON_OBJ_REF_STR(json, "argument", argument); // Add SQL message (may be NULL = not available) if (sql_msg != NULL) { @@ -294,6 +357,11 @@ int api_list(struct mg_connection *conn) listtype = GRAVITY_GROUPS; can_modify = true; } + else if(startsWith("/api/adlist", request->local_uri)) + { + listtype = GRAVITY_ADLISTS; + can_modify = true; + } else if(startsWith("/api/list/allow", request->local_uri)) { if(startsWith("/api/list/allow/exact", request->local_uri)) diff --git a/src/api/routes.c b/src/api/routes.c index e2c28072c..f9f45be8a 100644 --- a/src/api/routes.c +++ b/src/api/routes.c @@ -37,7 +37,7 @@ int api_handler(struct mg_connection *conn, void *ignored) { ret = api_dns_cacheinfo(conn); } - /******************************** /api/list, /api/group ****************************/ + /******************************** /api/list, /api/group, /api/adlist ****************************/ else if(startsWith("/api/list", request->local_uri)) { ret = api_list(conn); @@ -46,6 +46,10 @@ int api_handler(struct mg_connection *conn, void *ignored) { ret = api_list(conn); } + else if(startsWith("/api/adlist", request->local_uri)) + { + ret = api_list(conn); + } /******************************** /api/ftl ****************************/ else if(startsWith("/api/ftl/client", request->local_uri)) { diff --git a/src/database/gravity-db.c b/src/database/gravity-db.c index 7d8f3516d..0a9373723 100644 --- a/src/database/gravity-db.c +++ b/src/database/gravity-db.c @@ -1386,6 +1386,7 @@ bool gravityDB_addToTable(const enum gravity_list_type listtype, const tablerow break; case GRAVITY_GROUPS: + case GRAVITY_ADLISTS: // No type required for this table break; @@ -1406,6 +1407,8 @@ bool gravityDB_addToTable(const enum gravity_list_type listtype, const tablerow { if(listtype == GRAVITY_GROUPS) querystr = "INSERT INTO \"group\" (name,enabled,description) VALUES (:name,:enabled,:description);"; + else if(listtype == GRAVITY_ADLISTS) + querystr = "INSERT INTO adlist (address,enabled,description) VALUES (:address,:enabled,:description);"; else // domainlist querystr = "INSERT INTO domainlist (domain,type,enabled,comment) VALUES (:domain,:type,:enabled,:comment);"; } @@ -1417,6 +1420,11 @@ bool gravityDB_addToTable(const enum gravity_list_type listtype, const tablerow "VALUES (:name,:enabled,:description," "(SELECT id FROM \"group\" WHERE name = :name)," "(SELECT date_added FROM \"group\" WHERE name = :name));"; + else if(listtype == GRAVITY_ADLISTS) + querystr = "REPLACE INTO adlist (address,enabled,description,id,date_added) " + "VALUES (:address,:enabled,:description," + "(SELECT id FROM adlist WHERE address = :address)," + "(SELECT date_added FROM adlist WHERE address = :address));"; else // domainlist querystr = "REPLACE INTO domainlist (domain,type,enabled,comment,id,date_added) " "VALUES (:domain,:type,:enabled,:comment," @@ -1503,6 +1511,18 @@ bool gravityDB_addToTable(const enum gravity_list_type listtype, const tablerow return false; } + // Bind address string to prepared statement (if requested) + idx = sqlite3_bind_parameter_index(stmt, "address"); + if(idx > 0 && (rc = sqlite3_bind_text(stmt, idx, row.address, -1, SQLITE_STATIC)) != SQLITE_OK) + { + *message = sqlite3_errmsg(gravity_db); + logg("gravityDB_addToTable(%d, %s, %s): Failed to bind address (error %d) - %s", + type, row.domain, row.name, rc, *message); + sqlite3_reset(stmt); + sqlite3_finalize(stmt); + return false; + } + // Perform step bool okay = false; if((rc = sqlite3_step(stmt)) == SQLITE_DONE) @@ -1522,7 +1542,7 @@ bool gravityDB_addToTable(const enum gravity_list_type listtype, const tablerow return okay; } -bool gravityDB_delFromTable(const enum gravity_list_type listtype, const char* domain_name, const char **message) +bool gravityDB_delFromTable(const enum gravity_list_type listtype, const char* argument, const char **message) { if(gravity_db == NULL) { @@ -1547,6 +1567,7 @@ bool gravityDB_delFromTable(const enum gravity_list_type listtype, const char* d break; case GRAVITY_GROUPS: + case GRAVITY_ADLISTS: // No type required for this table break; @@ -1563,26 +1584,28 @@ bool gravityDB_delFromTable(const enum gravity_list_type listtype, const char* d // Prepare SQLite statement sqlite3_stmt* stmt = NULL; const char *querystr = ""; - if(listtype == GRAVITY_GROUPS) - querystr = "DELETE FROM \"group\" WHERE name = :domain_name;"; - else // domainlist - querystr = "DELETE FROM domainlist WHERE domain = :domain_name AND type = :type;"; + if(listtype == GRAVITY_GROUPS) + querystr = "DELETE FROM \"group\" WHERE name = :argument;"; + else if(listtype == GRAVITY_ADLISTS) + querystr = "DELETE FROM adlist WHERE address = :argument;"; + else // domainlist + querystr = "DELETE FROM domainlist WHERE domain = :argument AND type = :type;"; int rc = sqlite3_prepare_v2(gravity_db, querystr, -1, &stmt, NULL); if( rc != SQLITE_OK ) { *message = sqlite3_errmsg(gravity_db); logg("gravityDB_delFromTable(%d, %s) - SQL error prepare (%i): %s", - type, domain_name, rc, *message); + type, argument, rc, *message); return false; } // Bind domain to prepared statement (if requested) - int idx = sqlite3_bind_parameter_index(stmt, "domain_name"); - if(idx > 0 && (rc = sqlite3_bind_text(stmt, idx, domain_name, -1, SQLITE_STATIC)) != SQLITE_OK) + int idx = sqlite3_bind_parameter_index(stmt, "argument"); + if(idx > 0 && (rc = sqlite3_bind_text(stmt, idx, argument, -1, SQLITE_STATIC)) != SQLITE_OK) { *message = sqlite3_errmsg(gravity_db); logg("gravityDB_delFromTable(%d, %s): Failed to bind domain/name (error %d) - %s", - type, domain_name, rc, *message); + type, argument, rc, *message); sqlite3_reset(stmt); sqlite3_finalize(stmt); return false; @@ -1594,7 +1617,7 @@ bool gravityDB_delFromTable(const enum gravity_list_type listtype, const char* d { *message = sqlite3_errmsg(gravity_db); logg("gravityDB_delFromTable(%d, %s): Failed to bind type (error %d) - %s", - type, domain_name, rc, *message); + type, argument, rc, *message); sqlite3_reset(stmt); sqlite3_finalize(stmt); return false; @@ -1660,6 +1683,7 @@ bool gravityDB_readTable(const enum gravity_list_type listtype, const char *filt type = "0,1,2,3"; break; case GRAVITY_GROUPS: + case GRAVITY_ADLISTS: // No type required for this table break; } @@ -1675,6 +1699,12 @@ bool gravityDB_readTable(const enum gravity_list_type listtype, const char *filt extra = " WHERE name = :filter;"; sprintf(querystr, "SELECT id,name,enabled,date_added,date_modified,description FROM \"group\"%s;", extra); } + else if(listtype == GRAVITY_ADLISTS) + { + if(filter[0] != '\0') + extra = " WHERE address = :filter;"; + sprintf(querystr, "SELECT id,address,enabled,date_added,date_modified,comment FROM adlist%s;", extra); + } else // domainlist { if(filter[0] != '\0') @@ -1749,6 +1779,9 @@ bool gravityDB_readTableGetRow(tablerow *row, const char **message) else if(strcasecmp(cname, "domain") == 0) row->domain = (char*)sqlite3_column_text(read_stmt, c); + else if(strcasecmp(cname, "address") == 0) + row->address = (char*)sqlite3_column_text(read_stmt, c); + else if(strcasecmp(cname, "enabled") == 0) row->enabled = sqlite3_column_int(read_stmt, c) != 0; diff --git a/src/database/gravity-db.h b/src/database/gravity-db.h index 8cbda423d..c10d372d3 100644 --- a/src/database/gravity-db.h +++ b/src/database/gravity-db.h @@ -24,6 +24,7 @@ typedef struct { bool description_null; const char *name; const char *domain; + const char *address; const char *type; const char *comment; const char *group_ids; diff --git a/src/enums.h b/src/enums.h index b0e1a4981..ac2df57a4 100644 --- a/src/enums.h +++ b/src/enums.h @@ -166,7 +166,8 @@ enum gravity_list_type { GRAVITY_DOMAINLIST_ALL_EXACT, GRAVITY_DOMAINLIST_ALL_REGEX, GRAVITY_DOMAINLIST_ALL_ALL, - GRAVITY_GROUPS + GRAVITY_GROUPS, + GRAVITY_ADLISTS } __attribute__ ((packed)); enum gravity_tables { From 8fa055122ee47c86625bc5f836e6b6438891561d Mon Sep 17 00:00:00 2001 From: DL6ER Date: Thu, 14 Jan 2021 22:05:14 +0100 Subject: [PATCH 0209/1669] Use payload in form-format when adding/modifying lists Signed-off-by: DL6ER --- src/api/list.c | 82 ++++++++++++++----------------------- src/database/gravity-db.c | 15 ++++--- src/database/gravity-db.h | 1 + src/webserver/http-common.c | 6 +-- 4 files changed, 42 insertions(+), 62 deletions(-) diff --git a/src/api/list.c b/src/api/list.c index 61a8b3758..220d29cb6 100644 --- a/src/api/list.c +++ b/src/api/list.c @@ -192,90 +192,73 @@ static int api_list_write(struct mg_connection *conn, } // Extract payload - char buffer[1024] = { 0 }; - int data_len = mg_read(conn, buffer, sizeof(buffer) - 1); - if ((data_len < 1) || (data_len >= (int)sizeof(buffer))) { - return send_json_error(conn, 400, - "bad_request", - "No request body data", - NULL); - } - buffer[data_len] = '\0'; - - cJSON *obj = cJSON_Parse(buffer); - if (obj == NULL) { - return send_json_error(conn, 400, - "bad_request", - "Invalid request body data", - NULL); - } + char payload[1024] = { 0 }; + const char *argument = NULL; + http_get_payload(conn, payload, sizeof(payload)); - const char *argument = ""; - cJSON *elem_domain = cJSON_GetObjectItemCaseSensitive(obj, "domain"); + // Try to extract data from payload + char domain[256] = { 0 }; if(need_domain) { - if (!cJSON_IsString(elem_domain)) { - cJSON_Delete(obj); + if(GET_VAR("domain", domain, payload) < 1) + { return send_json_error(conn, 400, "bad_request", "No \"domain\" string in body data", NULL); } - row.domain = elem_domain->valuestring; - argument = row.domain; + row.domain = domain; + argument = domain; } - cJSON *elem_name = cJSON_GetObjectItemCaseSensitive(obj, "name"); + char name[256] = { 0 }; if(need_name) { - if (!cJSON_IsString(elem_name)) { - cJSON_Delete(obj); + if(GET_VAR("name", name, payload) < 1) + { return send_json_error(conn, 400, "bad_request", "No \"name\" string in body data", NULL); } - row.name = elem_name->valuestring; - argument = row.name; + row.name = name; + argument = name; } - cJSON *elem_address = cJSON_GetObjectItemCaseSensitive(obj, "address"); + char address[256] = { 0 }; if(need_address) { - if (!cJSON_IsString(elem_address)) { - cJSON_Delete(obj); + if(GET_VAR("address", address, payload) < 1) + { return send_json_error(conn, 400, "bad_request", "No \"address\" string in body data", NULL); } - row.address = elem_address->valuestring; - argument = row.address; + row.address = address; + argument = address; } row.enabled = true; - cJSON *elem_enabled = cJSON_GetObjectItemCaseSensitive(obj, "enabled"); - if (cJSON_IsBool(elem_enabled)) { - row.enabled = cJSON_IsTrue(elem_enabled); - } + get_bool_var(payload, "enabled", &row.enabled); - row.comment = NULL; - cJSON *elem_comment = cJSON_GetObjectItemCaseSensitive(obj, "comment"); - if (cJSON_IsString(elem_comment)) { - row.comment = elem_comment->valuestring; - } + char comment[256] = { 0 }; + GET_VAR("comment", comment, payload); + if(GET_VAR("comment", comment, payload) > 0) + row.comment = comment; + else + row.description = NULL; - row.description = NULL; - cJSON *elem_description = cJSON_GetObjectItemCaseSensitive(obj, "description"); - if (cJSON_IsString(elem_description)) { - row.description = elem_description->valuestring; - } + char description[256] = { 0 }; + if(GET_VAR("description", description, payload) > 0) + row.description = description; + else + row.description = NULL; // Try to add domain to table const char *sql_msg = NULL; if(gravityDB_addToTable(listtype, row, &sql_msg, method)) { - cJSON_Delete(obj); // Send GET style reply with code 201 Created return get_list(conn, 201, listtype, argument); } @@ -285,9 +268,6 @@ static int api_list_write(struct mg_connection *conn, cJSON *json = JSON_NEW_OBJ(); JSON_OBJ_COPY_STR(json, "argument", argument); - // Only delete payload object after having extracted the data - cJSON_Delete(obj); - // Add SQL message (may be NULL = not available) if (sql_msg != NULL) { JSON_OBJ_REF_STR(json, "sql_msg", sql_msg); diff --git a/src/database/gravity-db.c b/src/database/gravity-db.c index 0a9373723..fb21b9230 100644 --- a/src/database/gravity-db.c +++ b/src/database/gravity-db.c @@ -1440,7 +1440,7 @@ bool gravityDB_addToTable(const enum gravity_list_type listtype, const tablerow } // Bind domain to prepared statement (if requested) - int idx = sqlite3_bind_parameter_index(stmt, "domain"); + int idx = sqlite3_bind_parameter_index(stmt, ":domain"); if(idx > 0 && (rc = sqlite3_bind_text(stmt, idx, row.domain, -1, SQLITE_STATIC)) != SQLITE_OK) { *message = sqlite3_errmsg(gravity_db); @@ -1452,7 +1452,7 @@ bool gravityDB_addToTable(const enum gravity_list_type listtype, const tablerow } // Bind name to prepared statement (if requested) - idx = sqlite3_bind_parameter_index(stmt, "name"); + idx = sqlite3_bind_parameter_index(stmt, ":name"); if(idx > 0 && (rc = sqlite3_bind_text(stmt, idx, row.name, -1, SQLITE_STATIC)) != SQLITE_OK) { *message = sqlite3_errmsg(gravity_db); @@ -1464,7 +1464,7 @@ bool gravityDB_addToTable(const enum gravity_list_type listtype, const tablerow } // Bind type to prepared statement (if requested) - idx = sqlite3_bind_parameter_index(stmt, "type"); + idx = sqlite3_bind_parameter_index(stmt, ":type"); if(idx > 0 && (rc = sqlite3_bind_int(stmt, idx, type)) != SQLITE_OK) { *message = sqlite3_errmsg(gravity_db); @@ -1476,7 +1476,7 @@ bool gravityDB_addToTable(const enum gravity_list_type listtype, const tablerow } // Bind enabled boolean to prepared statement (if requested) - idx = sqlite3_bind_parameter_index(stmt, "enabled"); + idx = sqlite3_bind_parameter_index(stmt, ":enabled"); if(idx > 0 && (rc = sqlite3_bind_int(stmt, idx, row.enabled ? 1 : 0)) != SQLITE_OK) { *message = sqlite3_errmsg(gravity_db); @@ -1488,7 +1488,7 @@ bool gravityDB_addToTable(const enum gravity_list_type listtype, const tablerow } // Bind comment string to prepared statement (if requested) - idx = sqlite3_bind_parameter_index(stmt, "comment"); + idx = sqlite3_bind_parameter_index(stmt, ":comment"); if(idx > 0 && (rc = sqlite3_bind_text(stmt, idx, row.comment, -1, SQLITE_STATIC)) != SQLITE_OK) { *message = sqlite3_errmsg(gravity_db); @@ -1500,7 +1500,7 @@ bool gravityDB_addToTable(const enum gravity_list_type listtype, const tablerow } // Bind description string to prepared statement (if requested) - idx = sqlite3_bind_parameter_index(stmt, "description"); + idx = sqlite3_bind_parameter_index(stmt, ":description"); if(idx > 0 && (rc = sqlite3_bind_text(stmt, idx, row.description, -1, SQLITE_STATIC)) != SQLITE_OK) { *message = sqlite3_errmsg(gravity_db); @@ -1512,7 +1512,7 @@ bool gravityDB_addToTable(const enum gravity_list_type listtype, const tablerow } // Bind address string to prepared statement (if requested) - idx = sqlite3_bind_parameter_index(stmt, "address"); + idx = sqlite3_bind_parameter_index(stmt, ":address"); if(idx > 0 && (rc = sqlite3_bind_text(stmt, idx, row.address, -1, SQLITE_STATIC)) != SQLITE_OK) { *message = sqlite3_errmsg(gravity_db); @@ -1750,7 +1750,6 @@ bool gravityDB_readTableGetRow(tablerow *row, const char **message) for(int c = 0; c < cols; c++) { const char *cname = sqlite3_column_name(read_stmt, c); - logg("API: %d = %s", c, cname); if(strcasecmp(cname, "id") == 0) row->id = sqlite3_column_int(read_stmt, c); diff --git a/src/database/gravity-db.h b/src/database/gravity-db.h index c10d372d3..9569bd2be 100644 --- a/src/database/gravity-db.h +++ b/src/database/gravity-db.h @@ -29,6 +29,7 @@ typedef struct { const char *comment; const char *group_ids; const char *description; + int type_int; long id; time_t date_added; time_t date_modified; diff --git a/src/webserver/http-common.c b/src/webserver/http-common.c index f8aed2ed9..9ea8d2b15 100644 --- a/src/webserver/http-common.c +++ b/src/webserver/http-common.c @@ -108,7 +108,7 @@ int send_http_internal_error(struct mg_connection *conn) bool get_bool_var(const char *source, const char *var, bool *boolean) { char buffer[16] = { 0 }; - if(GET_VAR(var, buffer, source)) + if(GET_VAR(var, buffer, source) > 0) { *boolean = (strcasecmp(buffer, "true") == 0); return true; @@ -119,7 +119,7 @@ bool get_bool_var(const char *source, const char *var, bool *boolean) bool get_int_var(const char *source, const char *var, int *num) { char buffer[16] = { 0 }; - if(GET_VAR(var, buffer, source) && + if(GET_VAR(var, buffer, source) > 0 && sscanf(buffer, "%d", num) == 1) { return true; @@ -131,7 +131,7 @@ bool get_int_var(const char *source, const char *var, int *num) bool get_uint_var(const char *source, const char *var, unsigned int *num) { char buffer[16] = { 0 }; - if(GET_VAR(var, buffer, source) && + if(GET_VAR(var, buffer, source) > 0 && sscanf(buffer, "%u", num) == 1) { return true; From 089d25628b4f700220716885b7e4455e315c1249 Mon Sep 17 00:00:00 2001 From: DL6ER Date: Fri, 15 Jan 2021 19:42:51 +0100 Subject: [PATCH 0210/1669] Add fallback label for temperature sensors in case they don't have their own label Signed-off-by: DL6ER --- src/api/ftl.c | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/src/api/ftl.c b/src/api/ftl.c index 158491908..f1391eb3b 100644 --- a/src/api/ftl.c +++ b/src/api/ftl.c @@ -203,18 +203,26 @@ int api_ftl_database(struct mg_connection *conn) static int read_temp_sensor(struct mg_connection *conn, const char *label_path, const char *value_path, + const char *fallback_label, cJSON *object) { FILE *f_label = fopen(label_path, "r"); FILE *f_value = fopen(value_path, "r"); - if(f_label != NULL && f_value != NULL) + if(f_value != NULL) { int temp = 0; char label[1024]; - if(fread(label, sizeof(label)-1, 1, f_label) > 0 && fscanf(f_value, "%d", &temp) == 1) + if(fscanf(f_value, "%d", &temp) == 1) { cJSON *item = JSON_NEW_OBJ(); - JSON_OBJ_COPY_STR(item, "label", label); + if(f_label != NULL && fread(label, sizeof(label)-1, 1, f_label) > 0) + { + JSON_OBJ_COPY_STR(item, "name", label); + } + else + { + JSON_OBJ_COPY_STR(item, "name", fallback_label); + } JSON_OBJ_ADD_NUMBER(item, "value", temp < 1000 ? temp : 1e-3f*temp); JSON_ARRAY_ADD_ITEM(object, item); } @@ -317,14 +325,15 @@ int api_ftl_system(struct mg_connection *conn) // Source available temperatures, we try to read as many // temperature sensors as there are cores on this system cJSON *sensors = JSON_NEW_ARRAY(); - char label_path[256], value_path[256]; + char label_path[256], value_path[256], fallback_label[64]; int ret; for(int i = 0; i < nprocs; i++) { // Try /sys/class/thermal/thermal_zoneX/{type,temp} sprintf(label_path, "/sys/class/thermal/thermal_zone%d/type", i); sprintf(value_path, "/sys/class/thermal/thermal_zone%d/temp", i); - ret = read_temp_sensor(conn, label_path, value_path, sensors); + sprintf(fallback_label, "thermal_zone%d/temp", i); + ret = read_temp_sensor(conn, label_path, value_path, fallback_label, sensors); // Error handling if(ret != 0) return ret; @@ -332,7 +341,8 @@ int api_ftl_system(struct mg_connection *conn) // Try /sys/class/hwmon/hwmon0X/tempX_{label,input} sprintf(label_path, "/sys/class/hwmon/hwmon0/temp%d_label", i); sprintf(value_path, "/sys/class/hwmon/hwmon0/temp%d_input", i); - ret = read_temp_sensor(conn, label_path, value_path, sensors); + sprintf(fallback_label, "hwmon0/temp%d", i); + ret = read_temp_sensor(conn, label_path, value_path, fallback_label, sensors); // Error handling if(ret != 0) return ret; From 9beb0060061ab863a48ad30670113090b53424a5 Mon Sep 17 00:00:00 2001 From: DL6ER Date: Sat, 16 Jan 2021 10:55:35 +0100 Subject: [PATCH 0211/1669] On 2017-08-27 (after v3.3, before v3.4), nettle changed the type of destination from uint_8t* to char* in all base64 and base16 functions (armor-signedness branch). This is a breaking change as this is a change in signedness causing issues when compiling FTL against older versions of nettle. Signed-off-by: DL6ER --- src/api/auth.c | 15 ++++++++++++++- src/shmem.c | 2 +- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/src/api/auth.c b/src/api/auth.c index a589fb752..8f68da9e4 100644 --- a/src/api/auth.c +++ b/src/api/auth.c @@ -20,6 +20,19 @@ // crypto library #include #include +#include + +// On 2017-08-27 (after v3.3, before v3.4), nettle changed the type of +// destination from uint_8t* to char* in all base64 and base16 functions +// (armor-signedness branch). This is a breaking change as this is a change in +// signedness causing issues when compiling FTL against older versions of +// nettle. We create this constant here to have a conversion if necessary. +// See https://github.com/gnutls/nettle/commit/f2da403135e2b2f641cf0f8219ad5b72083b7dfd +#if NETTLE_VERSION_MAJOR == 3 && NETTLE_VERSION_MINOR < 4 +#define NETTLE_SIGN (uint8_t*) +#else +#define NETTLE_SIGN +#endif // How many bits should the SID use? #define SID_BITSIZE 128 @@ -331,7 +344,7 @@ static void generateSID(char *sid) raw_sid[i+2] = (rval >> 16) & 0xFF; raw_sid[i+3] = (rval >> 24) & 0xFF; } - base64_encode_raw(sid, SID_BITSIZE/8, raw_sid); + base64_encode_raw(NETTLE_SIGN sid, SID_BITSIZE/8, raw_sid); sid[SID_SIZE-1] = '\0'; } diff --git a/src/shmem.c b/src/shmem.c index 85c53b5e4..b50bd03a8 100644 --- a/src/shmem.c +++ b/src/shmem.c @@ -360,7 +360,7 @@ void _lock_shm(const char* func, const int line, const char * file) { if(config.debug & DEBUG_LOCKS) { logg("Waiting for SHM lock in %s() (%s:%i)\n" - " -> currently lock by %i/%i in %s()", + " -> last lock by %i/%i in %s()", func, file, line, shm_lockInfo.pid, shm_lockInfo.tid, From 4d1e9368b9b0c4856aa214cb45dc391f94c278e3 Mon Sep 17 00:00:00 2001 From: DL6ER Date: Sat, 16 Jan 2021 11:37:50 +0100 Subject: [PATCH 0212/1669] Tests: GET /api/auth results in a challange being returned to us Signed-off-by: DL6ER --- test/test_suite.bats | 20 +++----------------- 1 file changed, 3 insertions(+), 17 deletions(-) diff --git a/test/test_suite.bats b/test/test_suite.bats index 56f86ad32..168711ead 100644 --- a/test/test_suite.bats +++ b/test/test_suite.bats @@ -703,24 +703,10 @@ [[ ${lines[0]} == "Error 404: Not Found" ]] } -# This test does not work without actually hosting the web interface -#@test "HTTP server responds without error to undefined path inside /admin (rerouted to index.html)" { -# run bash -c 'curl -I -s 127.0.0.1:8080/admin/undefined' -# printf "%s\n" "${lines[@]}" -# [[ ${lines[0]} == "HTTP/1.1 200 OK"* ]] -#} - -@test "API authorization: Unauthorized for request without password" { - run bash -c 'curl -I -s 127.0.0.1:8080/api/auth' - printf "%s\n" "${lines[@]}" - [[ ${lines[0]} == "HTTP/1.1 401 Unauthorized"* ]] -} - -# This test is assuming the user password is empty -@test "API authorization: Success for request with correct password" { - run bash -c 'curl -s -H "X-Pi-hole-Authenticate: cd372fb85148700fa88095e3492d3f9f5beb43e555e5ff26d95f5a6adc36f8e6" 127.0.0.1:8080/api/auth' +@test "API authorization: Getting challenge" { + run bash -c 'curl -s 127.0.0.1:8080/api/auth' printf "%s\n" "${lines[@]}" - [[ ${lines[0]} == "{\"status\":\"success\"}" ]] + [[ ${lines[0]} == "{\"challenge\":\""* ]] } @test "LUA: Interpreter returns FTL version" { From e1922ad1ff52bf5b6d5e812636153e8a8e3a7d19 Mon Sep 17 00:00:00 2001 From: DL6ER Date: Sat, 16 Jan 2021 12:22:13 +0100 Subject: [PATCH 0213/1669] Get memory details directly from the kernel as we cannot rely on the information provided by sysinfo() [there sin't anything we need to compute the proper amount of used = unclaimable memory] Signed-off-by: DL6ER --- src/api/ftl.c | 100 +++++++++++++++++++++++++----------- src/webserver/json_macros.h | 16 +++--- 2 files changed, 78 insertions(+), 38 deletions(-) diff --git a/src/api/ftl.c b/src/api/ftl.c index f1391eb3b..f3f84a0f5 100644 --- a/src/api/ftl.c +++ b/src/api/ftl.c @@ -235,6 +235,47 @@ static int read_temp_sensor(struct mg_connection *conn, return 0; } +// Get RAM information in units of kB +// This is implemented similar to how free (procps) does it +static bool GetRamInKB(long *mem_total, long *mem_used, long *mem_free, long *mem_avail) +{ + long page_cached = -1, buffers = -1, slab_reclaimable = -1; + FILE *meminfo = fopen("/proc/meminfo", "r"); + if(meminfo == NULL) + return false; + + char line[256]; + while(fgets(line, sizeof(line), meminfo)) + { + sscanf(line, "MemTotal: %ld kB", mem_total); + sscanf(line, "MemFree: %ld kB", mem_free); + sscanf(line, "MemAvailable: %ld kB", mem_avail); + sscanf(line, "Cached: %ld kB", &page_cached); + sscanf(line, "Buffers: %ld kB", &buffers); + sscanf(line, "SReclaimable: %ld kB", &slab_reclaimable); + + // Exit if we have them all + if(*mem_total > -1 && *mem_avail > -1 && *mem_free > -1 && + buffers > -1 && slab_reclaimable > -1) + break; + } + fclose(meminfo); + + // Compute actual memory numbers + const long mem_cached = page_cached + slab_reclaimable; + // if mem_avail is greater than mem_total or our calculation of used + // overflows, that's symptomatic of running within a lxc container where + // such values will be dramatically distorted over those of the host. + if (*mem_avail > *mem_total) + *mem_avail = *mem_free; + *mem_used = *mem_total - *mem_free - mem_cached - buffers; + if (*mem_used < 0) + *mem_used = *mem_total - *mem_free; + + // Return success + return true; +} + int api_ftl_system(struct mg_connection *conn) { cJSON *json = JSON_NEW_OBJ(); @@ -255,46 +296,45 @@ int api_ftl_system(struct mg_connection *conn) cJSON *memory = JSON_NEW_OBJ(); cJSON *ram = JSON_NEW_OBJ(); + // We cannot use the memory information available through sysinfo() as + // this is not what we want. It is worth noting that freeram in sysinfo + // is not what most people would call "free RAM". freeram excludes + // memory used by cached filesystem metadata ("buffers") and contents + // ("cache"). Both of these can be a significant portion of RAM but are + // freed by the OS when programs need that memory. sysinfo does contain + // size used by buffers (sysinfo.bufferram), but not cache. The best + // option is to use the MemAvailable (as opposed to MemFree) entry in + // /proc/meminfo instead. + long mem_total = -1, mem_used = -1, mem_free = -1, mem_avail = -1; + GetRamInKB(&mem_total, &mem_used, &mem_free, &mem_avail); // Total usable main memory size - JSON_OBJ_ADD_NUMBER(ram, "total", info.totalram * info.mem_unit); - if(full_info) - { - // Available memory size - JSON_OBJ_ADD_NUMBER(ram, "free", info.freeram * info.mem_unit); - // Amount of shared memory - JSON_OBJ_ADD_NUMBER(ram, "shared", info.sharedram * info.mem_unit); - // Memory used by buffers - JSON_OBJ_ADD_NUMBER(ram, "buffer", info.bufferram * info.mem_unit); - } - unsigned long used = info.totalram - info.freeram; - // The following is a fall-back from procps code for lxc containers - // messing around with memory information - if(info.sharedram + info.bufferram < used) - used -= info.sharedram + info.bufferram; - JSON_OBJ_ADD_NUMBER(ram, "used", used * info.mem_unit); + JSON_OBJ_ADD_NUMBER(ram, "total", mem_total); + // Used memory size + JSON_OBJ_ADD_NUMBER(ram, "used", mem_used); + // Free memory size + JSON_OBJ_ADD_NUMBER(ram, "free", mem_free); + // Available memory size + // See https://git.kernel.org/cgit/linux/kernel/git/torvalds/linux.git/commit/?id=34e431b0ae398fc54ea69ff85ec700722c9da773 + // This Linux kernel commit message explains there are more nuances. It + // says: "Many programs check /proc/meminfo to estimate how much free + // memory is available. They generally do this by adding up "free" and + // "cached", which was fine ten years ago, but is pretty much guaranteed + // to be wrong today." + JSON_OBJ_ADD_NUMBER(ram, "available", mem_avail); JSON_OBJ_ADD_ITEM(memory, "ram", ram); cJSON *swap = JSON_NEW_OBJ(); // Total swap space size JSON_OBJ_ADD_NUMBER(swap, "total", info.totalswap * info.mem_unit); - if(full_info) - { - // Swap space still available - JSON_OBJ_ADD_NUMBER(swap, "free", info.freeswap * info.mem_unit); - } + // Swap space still available + JSON_OBJ_ADD_NUMBER(swap, "free", info.freeswap * info.mem_unit); // Used swap space JSON_OBJ_ADD_NUMBER(swap, "used", (info.totalswap - info.freeswap) * info.mem_unit); JSON_OBJ_ADD_ITEM(memory, "swap", swap); - if(full_info) - { - cJSON *high = JSON_NEW_OBJ(); - // Total high memory size - JSON_OBJ_ADD_NUMBER(high, "total", info.totalhigh * info.mem_unit); - // High memory still available - JSON_OBJ_ADD_NUMBER(high, "free", info.freehigh * info.mem_unit); - JSON_OBJ_ADD_ITEM(memory, "high", high); - } + // Number of current processes + JSON_OBJ_ADD_NUMBER(memory, "ftotal", (sysconf(_SC_PHYS_PAGES) * sysconf(_SC_PAGESIZE))); + JSON_OBJ_ADD_NUMBER(memory, "ffree", (sysconf(_SC_AVPHYS_PAGES) * sysconf(_SC_PAGESIZE))); JSON_OBJ_ADD_ITEM(json, "memory", memory); diff --git a/src/webserver/json_macros.h b/src/webserver/json_macros.h index de6da75d8..15429ea05 100644 --- a/src/webserver/json_macros.h +++ b/src/webserver/json_macros.h @@ -21,7 +21,7 @@ cJSON *string_item = NULL; \ if(string != NULL) \ { \ - string_item = cJSON_CreateString((const char*)string); \ + string_item = cJSON_CreateString((const char*)(string)); \ } \ else \ { \ @@ -41,7 +41,7 @@ cJSON *string_item = NULL; \ if(string != NULL) \ { \ - string_item = cJSON_CreateStringReference((const char*)string); \ + string_item = cJSON_CreateStringReference((const char*)(string)); \ } \ else \ { \ @@ -58,7 +58,7 @@ } #define JSON_OBJ_ADD_NUMBER(object, key, number){ \ - if(cJSON_AddNumberToObject(object, key, (double)number) == NULL) \ + if(cJSON_AddNumberToObject(object, key, (double)(number)) == NULL) \ { \ cJSON_Delete(object); \ send_http_internal_error(conn); \ @@ -80,7 +80,7 @@ } #define JSON_OBJ_ADD_BOOL(object, key, value) {\ - cJSON *bool_item = cJSON_CreateBool((cJSON_bool)value); \ + cJSON *bool_item = cJSON_CreateBool((cJSON_bool)(value)); \ if(bool_item == NULL) \ { \ cJSON_Delete(object); \ @@ -92,12 +92,12 @@ } #define JSON_ARRAY_ADD_NUMBER(object, number){ \ - cJSON *number_item = cJSON_CreateNumber((double)number); \ + cJSON *number_item = cJSON_CreateNumber((double)(number)); \ cJSON_AddItemToArray(object, number_item); \ } #define JSON_ARRAY_REPLACE_NUMBER(object, index, number){ \ - cJSON *number_item = cJSON_CreateNumber((double)number); \ + cJSON *number_item = cJSON_CreateNumber((double)(number)); \ cJSON_ReplaceItemInArray(object, index, number_item); \ } @@ -105,7 +105,7 @@ cJSON *string_item = NULL; \ if(string != NULL) \ { \ - string_item = cJSON_CreateStringReference((const char*)string); \ + string_item = cJSON_CreateStringReference((const char*)(string)); \ } \ else \ { \ @@ -125,7 +125,7 @@ cJSON *string_item = NULL; \ if(string != NULL) \ { \ - string_item = cJSON_CreateString((const char*)string); \ + string_item = cJSON_CreateString((const char*)(string)); \ } \ else \ { \ From 992fac4bdeda512cc79ad0cc09a96f1e012d25b7 Mon Sep 17 00:00:00 2001 From: DL6ER Date: Sat, 16 Jan 2021 12:35:05 +0100 Subject: [PATCH 0214/1669] Use SameSite=Strict as defense against some classes of cross-site request forgery (CSRF) attacks. This ensures the session cookie will only be sent in a first-party (i.e., Pi-hole) context and NOT be sent along with requests initiated by third party websites. Signed-off-by: DL6ER --- src/api/auth.c | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/src/api/auth.c b/src/api/auth.c index 8f68da9e4..a70125727 100644 --- a/src/api/auth.c +++ b/src/api/auth.c @@ -37,6 +37,14 @@ // How many bits should the SID use? #define SID_BITSIZE 128 #define SID_SIZE BASE64_ENCODE_RAW_LENGTH(SID_BITSIZE/8) + 1 + +// Use SameSite=Strict as defense against some classes of cross-site request +// forgery (CSRF) attacks. This ensures the session cookie will only be sent in +// a first-party (i.e., Pi-hole) context and NOT be sent along with requests +// initiated by third party websites. +#define FTL_SET_COOKIE "Set-Cookie: sid=%s; SameSite=Strict; Path=/; Max-Age=%u\r\n" +#define FTL_DELETE_COOKIE "Set-Cookie: sid=deleted; SameSite=Strict; Path=/; Max-Age=-1\r\n" + static struct { bool used; time_t valid_until; @@ -137,7 +145,7 @@ int check_client_auth(struct mg_connection *conn) // Update user cookie if(snprintf(pi_hole_extra_headers, sizeof(pi_hole_extra_headers), - "Set-Cookie: sid=%s; Path=/; Max-Age=%u\r\n", + FTL_SET_COOKIE, auth_data[user_id].sid, httpsettings.session_timeout) < 0) { return send_json_error(conn, 500, "internal_error", "Internal server error", NULL); @@ -257,7 +265,7 @@ static int send_api_auth_status(struct mg_connection *conn, const int user_id, c // Ten minutes validity if(snprintf(pi_hole_extra_headers, sizeof(pi_hole_extra_headers), - "Set-Cookie: sid=%s; Path=/; Max-Age=%u\r\n", + FTL_SET_COOKIE, auth_data[user_id].sid, API_SESSION_EXPIRE) < 0) { return send_json_error(conn, 500, "internal_error", "Internal server error", NULL); @@ -275,7 +283,7 @@ static int send_api_auth_status(struct mg_connection *conn, const int user_id, c // Revoke client authentication. This slot can be used by a new client afterwards. delete_session(user_id); - strncpy(pi_hole_extra_headers, "Set-Cookie: sid=deleted; Path=/; Max-Age=-1\r\n", sizeof(pi_hole_extra_headers)); + strncpy(pi_hole_extra_headers, FTL_DELETE_COOKIE, sizeof(pi_hole_extra_headers)); cJSON *json = JSON_NEW_OBJ(); get_session_object(conn, json, user_id, now); JSON_SEND_OBJECT_CODE(json, 410); // 410 Gone @@ -285,7 +293,7 @@ static int send_api_auth_status(struct mg_connection *conn, const int user_id, c if(config.debug & DEBUG_API) logg("API Auth status: Invalid, asking to delete cookie"); - strncpy(pi_hole_extra_headers, "Set-Cookie: sid=deleted; Path=/; Max-Age=-1\r\n", sizeof(pi_hole_extra_headers)); + strncpy(pi_hole_extra_headers, FTL_DELETE_COOKIE, sizeof(pi_hole_extra_headers)); cJSON *json = JSON_NEW_OBJ(); get_session_object(conn, json, user_id, now); JSON_SEND_OBJECT_CODE(json, 401); // 401 Unauthorized From b7f94c78506728ec2a4712bf5bd5f0210cd837bf Mon Sep 17 00:00:00 2001 From: DL6ER Date: Sat, 16 Jan 2021 12:39:21 +0100 Subject: [PATCH 0215/1669] Tests: No login needed when there is no password Signed-off-by: DL6ER --- test/test_suite.bats | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/test/test_suite.bats b/test/test_suite.bats index 168711ead..eabd9b1a0 100644 --- a/test/test_suite.bats +++ b/test/test_suite.bats @@ -703,7 +703,15 @@ [[ ${lines[0]} == "Error 404: Not Found" ]] } -@test "API authorization: Getting challenge" { +@test "API authorization (without password): No login required" { + run bash -c 'curl -s 127.0.0.1:8080/api/auth' + printf "%s\n" "${lines[@]}" + [[ ${lines[0]} == '{"session":{"valid":true,"sid":null,"validity":null}}' ]] +} + +@test "API authorization (with password): FTL challenges us" { + # Password: ABC + echo "WEBPASSWORD=183c1b634da0078fcf5b0af84bdcbb3e817708c3f22b329be84165f4bad1ae48" >> /etc/pihole/setupVars.conf run bash -c 'curl -s 127.0.0.1:8080/api/auth' printf "%s\n" "${lines[@]}" [[ ${lines[0]} == "{\"challenge\":\""* ]] From c0505d7729d6b96043c536c4ea9e5a2f9177a5c3 Mon Sep 17 00:00:00 2001 From: DL6ER Date: Sat, 16 Jan 2021 16:07:26 +0100 Subject: [PATCH 0216/1669] Improve format of overTime replies. Signed-off-by: DL6ER --- src/api/stats.c | 45 +++++++++++++++++++-------------------------- 1 file changed, 19 insertions(+), 26 deletions(-) diff --git a/src/api/stats.c b/src/api/stats.c index e7cd9fca1..35820e29c 100644 --- a/src/api/stats.c +++ b/src/api/stats.c @@ -157,15 +157,19 @@ int api_stats_overTime_history(struct mg_connection *conn) JSON_SEND_OBJECT(json); } - cJSON *json = JSON_NEW_ARRAY(); + // Minimum structure is + // {"data":[]} + cJSON *json = JSON_NEW_OBJ(); + cJSON *data = JSON_NEW_ARRAY(); for(int slot = from; slot < until; slot++) { cJSON *item = JSON_NEW_OBJ(); JSON_OBJ_ADD_NUMBER(item, "timestamp", overTime[slot].timestamp); JSON_OBJ_ADD_NUMBER(item, "total_queries", overTime[slot].total); JSON_OBJ_ADD_NUMBER(item, "blocked_queries", overTime[slot].blocked); - JSON_ARRAY_ADD_ITEM(json, item); + JSON_ARRAY_ADD_ITEM(data, item); } + JSON_OBJ_ADD_ITEM(json, "data", data); JSON_SEND_OBJECT(json); } @@ -1118,20 +1122,6 @@ int api_stats_overTime_clients(struct mg_connection *conn) return send_json_unauthorized(conn); } - // Exit before processing any data if requested via config setting - get_privacy_level(NULL); - if(config.privacylevel >= PRIVACY_HIDE_DOMAINS_CLIENTS) - { - // Minimum structure is - // {"over_time":[], "clients":[]} - cJSON *json = JSON_NEW_OBJ(); - cJSON *over_time = JSON_NEW_ARRAY(); - JSON_OBJ_ADD_ITEM(json, "over_time", over_time); - cJSON *clients = JSON_NEW_ARRAY(); - JSON_OBJ_ADD_ITEM(json, "clients", clients); - JSON_SEND_OBJECT(json); - } - // Find minimum ID to send for(int slot = 0; slot < OVERTIME_SLOTS; slot++) { @@ -1142,13 +1132,16 @@ int api_stats_overTime_clients(struct mg_connection *conn) break; } } - if(sendit < 0) + + // Exit before processing any data if requested via config setting + get_privacy_level(NULL); + if(config.privacylevel >= PRIVACY_HIDE_DOMAINS_CLIENTS || sendit < 0) { // Minimum structure is - // {"over_time":[], "clients":[]} + // {"data":[], "clients":[]} cJSON *json = JSON_NEW_OBJ(); - cJSON *over_time = JSON_NEW_ARRAY(); - JSON_OBJ_ADD_ITEM(json, "over_time", over_time); + cJSON *data = JSON_NEW_ARRAY(); + JSON_OBJ_ADD_ITEM(json, "data", data); cJSON *clients = JSON_NEW_ARRAY(); JSON_OBJ_ADD_ITEM(json, "clients", clients); JSON_SEND_OBJECT(json); @@ -1189,7 +1182,7 @@ int api_stats_overTime_clients(struct mg_connection *conn) } } - cJSON *over_time = JSON_NEW_ARRAY(); + cJSON *data = JSON_NEW_ARRAY(); // Main return loop for(int slot = sendit; slot < until; slot++) { @@ -1197,7 +1190,7 @@ int api_stats_overTime_clients(struct mg_connection *conn) JSON_OBJ_ADD_NUMBER(item, "timestamp", overTime[slot].timestamp); // Loop over clients to generate output to be sent to the client - cJSON *data = JSON_NEW_ARRAY(); + cJSON *data2 = JSON_NEW_ARRAY(); for(int clientID = 0; clientID < counters->clients; clientID++) { if(skipclient[clientID]) @@ -1209,13 +1202,13 @@ int api_stats_overTime_clients(struct mg_connection *conn) continue; const int thisclient = client->overTime[slot]; - JSON_ARRAY_ADD_NUMBER(data, thisclient); + JSON_ARRAY_ADD_NUMBER(data2, thisclient); } - JSON_OBJ_ADD_ITEM(item, "data", data); - JSON_ARRAY_ADD_ITEM(over_time, item); + JSON_OBJ_ADD_ITEM(item, "data", data2); + JSON_ARRAY_ADD_ITEM(data, item); } cJSON *json = JSON_NEW_OBJ(); - JSON_OBJ_ADD_ITEM(json, "over_time", over_time); + JSON_OBJ_ADD_ITEM(json, "data", data); cJSON *clients = JSON_NEW_ARRAY(); // Loop over clients to generate output to be sent to the client From 5e1c75fb843723917053283f774b8fe5bea5278f Mon Sep 17 00:00:00 2001 From: DL6ER Date: Wed, 20 Jan 2021 11:12:47 +0100 Subject: [PATCH 0217/1669] Include system object in /api/stats/summary to need one AJAX call less Signed-off-by: DL6ER --- src/api/ftl.c | 51 +++++++++++++++++++++++++----------------------- src/api/routes.h | 3 +++ src/api/stats.c | 5 +++++ 3 files changed, 35 insertions(+), 24 deletions(-) diff --git a/src/api/ftl.c b/src/api/ftl.c index f3f84a0f5..0ef3645c3 100644 --- a/src/api/ftl.c +++ b/src/api/ftl.c @@ -276,23 +276,15 @@ static bool GetRamInKB(long *mem_total, long *mem_used, long *mem_free, long *me return true; } -int api_ftl_system(struct mg_connection *conn) +int get_system_obj(struct mg_connection *conn, cJSON *system) { - cJSON *json = JSON_NEW_OBJ(); - const int nprocs = get_nprocs(); struct sysinfo info; if(sysinfo(&info) != 0) return send_json_error(conn, 500, "error", strerror(errno), NULL); - // Extract payload - char payload[1024] = { 0 }; - bool full_info = false; - if(http_get_payload(conn, payload, sizeof(payload))) - get_bool_var(payload, "full", &full_info); - // Seconds since boot - JSON_OBJ_ADD_NUMBER(json, "uptime", info.uptime); + JSON_OBJ_ADD_NUMBER(system, "uptime", info.uptime); cJSON *memory = JSON_NEW_OBJ(); cJSON *ram = JSON_NEW_OBJ(); @@ -331,36 +323,33 @@ int api_ftl_system(struct mg_connection *conn) // Used swap space JSON_OBJ_ADD_NUMBER(swap, "used", (info.totalswap - info.freeswap) * info.mem_unit); JSON_OBJ_ADD_ITEM(memory, "swap", swap); + JSON_OBJ_ADD_ITEM(system, "memory", memory); // Number of current processes - JSON_OBJ_ADD_NUMBER(memory, "ftotal", (sysconf(_SC_PHYS_PAGES) * sysconf(_SC_PAGESIZE))); - JSON_OBJ_ADD_NUMBER(memory, "ffree", (sysconf(_SC_AVPHYS_PAGES) * sysconf(_SC_PAGESIZE))); - - JSON_OBJ_ADD_ITEM(json, "memory", memory); - - // Number of current processes - JSON_OBJ_ADD_NUMBER(json, "procs", info.procs); + JSON_OBJ_ADD_NUMBER(system, "procs", info.procs); cJSON *cpu = JSON_NEW_OBJ(); // Number of available processors JSON_OBJ_ADD_NUMBER(cpu, "nprocs", nprocs); // 1, 5, and 15 minute load averages (we need to convert them) - cJSON *load = JSON_NEW_ARRAY(); + cJSON *raw = JSON_NEW_ARRAY(); cJSON *percent = JSON_NEW_ARRAY(); float load_f[3] = { 0.f }; const float longfloat = 1.f / (1 << SI_LOAD_SHIFT); for(unsigned int i = 0; i < 3; i++) { load_f[i] = longfloat * info.loads[i]; - JSON_ARRAY_ADD_NUMBER(load, load_f[i]); + JSON_ARRAY_ADD_NUMBER(raw, load_f[i]); JSON_ARRAY_ADD_NUMBER(percent, (100.f*load_f[i]/nprocs)); } // Averaged CPU usage in percent + cJSON *load = JSON_NEW_OBJ(); + JSON_OBJ_ADD_ITEM(load, "raw", raw); + JSON_OBJ_ADD_ITEM(load, "percent", percent); JSON_OBJ_ADD_ITEM(cpu, "load", load); - JSON_OBJ_ADD_ITEM(cpu, "percent", percent); - JSON_OBJ_ADD_ITEM(json, "cpu", cpu); + JSON_OBJ_ADD_ITEM(system, "cpu", cpu); // Source available temperatures, we try to read as many // temperature sensors as there are cores on this system @@ -387,13 +376,27 @@ int api_ftl_system(struct mg_connection *conn) if(ret != 0) return ret; } - JSON_OBJ_ADD_ITEM(json, "sensors", sensors); + JSON_OBJ_ADD_ITEM(system, "sensors", sensors); cJSON *dns = JSON_NEW_OBJ(); const bool blocking = get_blockingstatus(); JSON_OBJ_ADD_BOOL(dns, "blocking", blocking); // same reply type as in /api/dns/status JSON_OBJ_ADD_NUMBER(dns, "gravity_size", counters->gravity); - JSON_OBJ_ADD_ITEM(json, "dns", dns); - + JSON_OBJ_ADD_ITEM(system, "dns", dns); + + return 0; +} + +int api_ftl_system(struct mg_connection *conn) +{ + cJSON *json = JSON_NEW_OBJ(); + cJSON *system = JSON_NEW_OBJ(); + + // Get system object + const int ret = get_system_obj(conn, system); + if (ret != 0) + return ret; + + JSON_OBJ_ADD_ITEM(json, "system", system); JSON_SEND_OBJECT(json); } diff --git a/src/api/routes.h b/src/api/routes.h index 20e762e9a..099eef42d 100644 --- a/src/api/routes.h +++ b/src/api/routes.h @@ -12,6 +12,8 @@ // struct mg_connection #include "../civetweb/civetweb.h" +// type cJSON +#include "../cJSON/cJSON.h" // API router int api_handler(struct mg_connection *conn, void *ignored); @@ -40,6 +42,7 @@ int api_ftl_client(struct mg_connection *conn); int api_ftl_dnsmasq_log(struct mg_connection *conn); int api_ftl_database(struct mg_connection *conn); int api_ftl_system(struct mg_connection *conn); +int get_system_obj(struct mg_connection *conn, cJSON *system); // Network methods int api_network(struct mg_connection *conn); diff --git a/src/api/stats.c b/src/api/stats.c index 1671d4750..25cc9a68e 100644 --- a/src/api/stats.c +++ b/src/api/stats.c @@ -117,6 +117,11 @@ int api_stats_summary(struct mg_connection *conn) JSON_OBJ_ADD_NUMBER(reply_types, "domain", counters->reply_domain); JSON_OBJ_ADD_ITEM(json, "reply_types", reply_types); + cJSON *system = JSON_NEW_OBJ(); + const int ret = get_system_obj(conn, system); + if(ret != 0) return ret; + JSON_OBJ_ADD_ITEM(json, "system", system); + JSON_SEND_OBJECT(json); } From b08954aec756c16779e8b9f65ccf24df9a60e68c Mon Sep 17 00:00:00 2001 From: DL6ER Date: Wed, 20 Jan 2021 11:50:45 +0100 Subject: [PATCH 0218/1669] http_get_payload(): Extract body payload also for PUT and PATCH Signed-off-by: DL6ER --- src/api/list.c | 8 ++++---- src/webserver/http-common.c | 3 +-- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/src/api/list.c b/src/api/list.c index 220d29cb6..d8fcd2a65 100644 --- a/src/api/list.c +++ b/src/api/list.c @@ -34,7 +34,7 @@ static int get_list(struct mg_connection *conn, JSON_OBJ_ADD_NULL(json, "sql_msg"); } - return send_json_error(conn, 402, // 402 Request Failed + return send_json_error(conn, 400, // 400 Bad Request "database_error", "Could not read domains from database table", json); @@ -131,7 +131,7 @@ static int get_list(struct mg_connection *conn, JSON_OBJ_ADD_NULL(json, "sql_msg"); } - return send_json_error(conn, 402, // 402 Request Failed + return send_json_error(conn, 400, // 400 Bad Request "database_error", "Could not read from gravity database", json); @@ -276,7 +276,7 @@ static int api_list_write(struct mg_connection *conn, } // Send error reply - return send_json_error(conn, 402, // 402 Request Failed + return send_json_error(conn, 400, // 400 Bad Request "database_error", "Could not add to gravity database", json); @@ -314,7 +314,7 @@ static int api_list_remove(struct mg_connection *conn, } // Send error reply - return send_json_error(conn, 402, + return send_json_error(conn, 400, "database_error", "Could not remove domain from database table", json); diff --git a/src/webserver/http-common.c b/src/webserver/http-common.c index 9ea8d2b15..d478a07ab 100644 --- a/src/webserver/http-common.c +++ b/src/webserver/http-common.c @@ -150,8 +150,7 @@ bool http_get_payload(struct mg_connection *conn, char *payload, const size_t si strncpy(payload, request->query_string, size-1); return true; } - - if(method == HTTP_POST) + else // POST, PUT, PATCH { int data_len = mg_read(conn, payload, size - 1); if ((data_len < 1) || (data_len >= (int)size)) From 7fa2dac90a9c953e2a5ce172f3bee83bded967db Mon Sep 17 00:00:00 2001 From: DL6ER Date: Wed, 20 Jan 2021 12:33:16 +0100 Subject: [PATCH 0219/1669] Allow domains/groups/adlists to be removed from the database Signed-off-by: DL6ER --- src/api/list.c | 3 +-- src/database/gravity-db.c | 6 +++--- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/api/list.c b/src/api/list.c index d8fcd2a65..312ab2267 100644 --- a/src/api/list.c +++ b/src/api/list.c @@ -243,11 +243,10 @@ static int api_list_write(struct mg_connection *conn, get_bool_var(payload, "enabled", &row.enabled); char comment[256] = { 0 }; - GET_VAR("comment", comment, payload); if(GET_VAR("comment", comment, payload) > 0) row.comment = comment; else - row.description = NULL; + row.comment = NULL; char description[256] = { 0 }; if(GET_VAR("description", description, payload) > 0) diff --git a/src/database/gravity-db.c b/src/database/gravity-db.c index 8c3143162..055efdcca 100644 --- a/src/database/gravity-db.c +++ b/src/database/gravity-db.c @@ -1531,7 +1531,7 @@ bool gravityDB_addToTable(const enum gravity_list_type listtype, const tablerow bool okay = false; if((rc = sqlite3_step(stmt)) == SQLITE_DONE) { - // Domain added + // Domain added/modified okay = true; } else @@ -1604,7 +1604,7 @@ bool gravityDB_delFromTable(const enum gravity_list_type listtype, const char* a } // Bind domain to prepared statement (if requested) - int idx = sqlite3_bind_parameter_index(stmt, "argument"); + int idx = sqlite3_bind_parameter_index(stmt, ":argument"); if(idx > 0 && (rc = sqlite3_bind_text(stmt, idx, argument, -1, SQLITE_STATIC)) != SQLITE_OK) { *message = sqlite3_errmsg(gravity_db); @@ -1616,7 +1616,7 @@ bool gravityDB_delFromTable(const enum gravity_list_type listtype, const char* a } // Bind type to prepared statement (if requested) - idx = sqlite3_bind_parameter_index(stmt, "type"); + idx = sqlite3_bind_parameter_index(stmt, ":type"); if(idx > 0 && (rc = sqlite3_bind_int(stmt, idx, type)) != SQLITE_OK) { *message = sqlite3_errmsg(gravity_db); From 5c3a2b509c74e90fe2e7e9407448b86899675885 Mon Sep 17 00:00:00 2001 From: DL6ER Date: Wed, 20 Jan 2021 16:36:02 +0100 Subject: [PATCH 0220/1669] Implement changing group assignments through the API Signed-off-by: DL6ER --- src/api/list.c | 226 +++++++++------------ src/database/gravity-db.c | 394 +++++++++++++++++++++++++++++------- src/database/gravity-db.h | 6 +- src/enums.h | 3 +- src/webserver/http-common.c | 15 +- src/webserver/http-common.h | 2 +- 6 files changed, 436 insertions(+), 210 deletions(-) diff --git a/src/api/list.c b/src/api/list.c index 312ab2267..99507afc7 100644 --- a/src/api/list.c +++ b/src/api/list.c @@ -14,18 +14,18 @@ #include "routes.h" #include "../database/gravity-db.h" -static int get_list(struct mg_connection *conn, - const int code, - const enum gravity_list_type listtype, - const char *filter) +static int api_list_read(struct mg_connection *conn, + const int code, + const enum gravity_list_type listtype, + const char *argument) { const char *sql_msg = NULL; - if(!gravityDB_readTable(listtype, filter, &sql_msg)) + if(!gravityDB_readTable(listtype, argument, &sql_msg)) { cJSON *json = JSON_NEW_OBJ(); - // Add filter (may be NULL = not available) - JSON_OBJ_REF_STR(json, "filter", filter); + // Add argument (may be NULL = not available) + JSON_OBJ_REF_STR(json, "argument", argument); // Add SQL message (may be NULL = not available) if (sql_msg != NULL) { @@ -121,8 +121,8 @@ static int get_list(struct mg_connection *conn, JSON_DELETE(items); cJSON *json = JSON_NEW_OBJ(); - // Add filter (may be NULL = not available) - JSON_OBJ_REF_STR(json, "filter", filter); + // Add argument (may be NULL = not available) + JSON_OBJ_REF_STR(json, "argument", argument); // Add SQL message (may be NULL = not available) if (sql_msg != NULL) { @@ -138,134 +138,85 @@ static int get_list(struct mg_connection *conn, } } -static int api_list_read(struct mg_connection *conn, - const enum gravity_list_type listtype) -{ - // Extract domain from path (option for GET) - const struct mg_request_info *request = mg_get_request_info(conn); - char domain_filter[1024] = { 0 }; - - // Advance one character to strip "/" - const char *encoded_uri = strrchr(request->local_uri, '/')+1u; - - // Decode URL (necessary for regular expressions, harmless for domains) - if(strlen(encoded_uri) != 0 && - strcmp(encoded_uri, "exact") != 0 && - strcmp(encoded_uri, "regex") != 0 && - strcmp(encoded_uri, "allow") != 0 && - strcmp(encoded_uri, "deny") != 0 && - strcmp(encoded_uri, "list") != 0 && - strcmp(encoded_uri, "group") != 0 && - strcmp(encoded_uri, "adlist") != 0) - mg_url_decode(encoded_uri, strlen(encoded_uri), domain_filter, sizeof(domain_filter), 0); - - return get_list(conn, 200, listtype, domain_filter); -} - static int api_list_write(struct mg_connection *conn, const enum gravity_list_type listtype, - const enum http_method method) + const enum http_method method, + const char *argument) { - tablerow row; - bool need_domain = false, need_name = false, need_address = false; - switch (listtype) - { - case GRAVITY_GROUPS: - need_name = true; - break; - - case GRAVITY_ADLISTS: - need_address = true; - break; - - case GRAVITY_DOMAINLIST_ALLOW_EXACT: - case GRAVITY_DOMAINLIST_DENY_EXACT: - case GRAVITY_DOMAINLIST_ALLOW_REGEX: - case GRAVITY_DOMAINLIST_DENY_REGEX: - case GRAVITY_DOMAINLIST_ALLOW_ALL: - case GRAVITY_DOMAINLIST_DENY_ALL: - case GRAVITY_DOMAINLIST_ALL_EXACT: - case GRAVITY_DOMAINLIST_ALL_REGEX: - case GRAVITY_DOMAINLIST_ALL_ALL: - need_domain = true; - break; - } + tablerow row = { 0 }; + + // Set argument + row.argument = argument; // Extract payload char payload[1024] = { 0 }; - const char *argument = NULL; http_get_payload(conn, payload, sizeof(payload)); - // Try to extract data from payload - char domain[256] = { 0 }; - if(need_domain) - { - if(GET_VAR("domain", domain, payload) < 1) - { - return send_json_error(conn, 400, - "bad_request", - "No \"domain\" string in body data", - NULL); - } - row.domain = domain; - argument = domain; - } - - char name[256] = { 0 }; - if(need_name) - { - if(GET_VAR("name", name, payload) < 1) - { - return send_json_error(conn, 400, - "bad_request", - "No \"name\" string in body data", - NULL); - } - row.name = name; - argument = name; + cJSON *obj = cJSON_Parse(payload); + if (obj == NULL) { + return send_json_error(conn, 400, + "bad_request", + "Invalid request body data", + NULL); } - char address[256] = { 0 }; - if(need_address) - { - if(GET_VAR("address", address, payload) < 1) - { - return send_json_error(conn, 400, - "bad_request", - "No \"address\" string in body data", - NULL); - } - row.address = address; - argument = address; + cJSON *json_enabled = cJSON_GetObjectItemCaseSensitive(obj, "enabled"); + if (!cJSON_IsBool(json_enabled)) { + cJSON_Delete(obj); + return send_json_error(conn, 400, + "bad_request", + "No \"enabled\" boolean in body data", + NULL); } + row.enabled = cJSON_IsTrue(json_enabled); - row.enabled = true; - get_bool_var(payload, "enabled", &row.enabled); - - char comment[256] = { 0 }; - if(GET_VAR("comment", comment, payload) > 0) - row.comment = comment; + cJSON *json_comment = cJSON_GetObjectItemCaseSensitive(obj, "comment"); + if(cJSON_IsString(json_comment)) + row.comment = json_comment->valuestring; else row.comment = NULL; - char description[256] = { 0 }; - if(GET_VAR("description", description, payload) > 0) - row.description = description; + cJSON *json_description = cJSON_GetObjectItemCaseSensitive(obj, "description"); + if(cJSON_IsString(json_description)) + row.description = json_description->valuestring; else row.description = NULL; + cJSON *json_name = cJSON_GetObjectItemCaseSensitive(obj, "name"); + if(cJSON_IsString(json_name)) + row.name = json_name->valuestring; + else + row.name = NULL; + + cJSON *json_oldtype = cJSON_GetObjectItemCaseSensitive(obj, "oldtype"); + if(cJSON_IsString(json_oldtype)) + row.oldtype = json_oldtype->valuestring; + else + row.oldtype = NULL; + // Try to add domain to table const char *sql_msg = NULL; - if(gravityDB_addToTable(listtype, row, &sql_msg, method)) + bool okay = false; + if(gravityDB_addToTable(listtype, &row, &sql_msg, method)) { - // Send GET style reply with code 201 Created - return get_list(conn, 201, listtype, argument); + cJSON *groups = cJSON_GetObjectItemCaseSensitive(obj, "groups"); + if(groups != NULL) + okay = gravityDB_edit_groups(listtype, groups, &row, &sql_msg); } - else + if(!okay) { // Error adding domain, prepare error object cJSON *json = JSON_NEW_OBJ(); - JSON_OBJ_COPY_STR(json, "argument", argument); + JSON_OBJ_REF_STR(json, "argument", argument); + JSON_OBJ_ADD_BOOL(json, "enabled", row.enabled); + if(row.comment != NULL) + JSON_OBJ_REF_STR(json, "comment", row.comment); + if(row.description != NULL) + JSON_OBJ_REF_STR(json, "description", row.description); + if(row.name != NULL) + JSON_OBJ_REF_STR(json, "name", row.name); + if(row.oldtype != NULL) + JSON_OBJ_REF_STR(json, "oldtype", row.oldtype); // Add SQL message (may be NULL = not available) if (sql_msg != NULL) { @@ -274,25 +225,28 @@ static int api_list_write(struct mg_connection *conn, JSON_OBJ_ADD_NULL(json, "sql_msg"); } + // Free memory not needed any longer + cJSON_Delete(obj); + // Send error reply return send_json_error(conn, 400, // 400 Bad Request "database_error", "Could not add to gravity database", json); } + // else: everything is okay + + // Free memory not needed any longer + cJSON_Delete(obj); + + // Send GET style reply with code 201 Created + return api_list_read(conn, 201, listtype, argument); } static int api_list_remove(struct mg_connection *conn, - const enum gravity_list_type listtype) + const enum gravity_list_type listtype, + const char *argument) { - const struct mg_request_info *request = mg_get_request_info(conn); - - char argument[1024] = { 0 }; - // Advance one character to strip "/" - const char *encoded_uri = strrchr(request->local_uri, '/')+1u; - // Decode URL (necessary for regular expressions, harmless for domains) - mg_url_decode(encoded_uri, strlen(encoded_uri), argument, sizeof(argument)-1u, 0); - cJSON *json = JSON_NEW_OBJ(); const char *sql_msg = NULL; if(gravityDB_delFromTable(listtype, argument, &sql_msg)) @@ -331,24 +285,25 @@ int api_list(struct mg_connection *conn) enum gravity_list_type listtype; bool can_modify = false; const struct mg_request_info *request = mg_get_request_info(conn); - if(startsWith("/api/group", request->local_uri)) + const char *argument = NULL; + if((argument = startsWith("/api/group", request->local_uri)) != NULL) { listtype = GRAVITY_GROUPS; can_modify = true; } - else if(startsWith("/api/adlist", request->local_uri)) + else if((argument = startsWith("/api/adlist", request->local_uri)) != NULL) { listtype = GRAVITY_ADLISTS; can_modify = true; } - else if(startsWith("/api/list/allow", request->local_uri)) + else if((argument = startsWith("/api/list/allow", request->local_uri)) != NULL) { - if(startsWith("/api/list/allow/exact", request->local_uri)) + if((argument = startsWith("/api/list/allow/exact", request->local_uri)) != NULL) { listtype = GRAVITY_DOMAINLIST_ALLOW_EXACT; can_modify = true; } - else if(startsWith("/api/list/allow/regex", request->local_uri)) + else if((argument = startsWith("/api/list/allow/regex", request->local_uri)) != NULL) { listtype = GRAVITY_DOMAINLIST_ALLOW_REGEX; can_modify = true; @@ -356,14 +311,14 @@ int api_list(struct mg_connection *conn) else listtype = GRAVITY_DOMAINLIST_ALLOW_ALL; } - else if(startsWith("/api/list/deny", request->local_uri)) + else if((argument = startsWith("/api/list/deny", request->local_uri)) != NULL) { - if(startsWith("/api/list/deny/exact", request->local_uri)) + if((argument = startsWith("/api/list/deny/exact", request->local_uri)) != NULL) { listtype = GRAVITY_DOMAINLIST_DENY_EXACT; can_modify = true; } - else if(startsWith("/api/list/deny/regex", request->local_uri)) + else if((argument = startsWith("/api/list/deny/regex", request->local_uri)) != NULL) { listtype = GRAVITY_DOMAINLIST_DENY_REGEX; can_modify = true; @@ -373,28 +328,31 @@ int api_list(struct mg_connection *conn) } else { - if(startsWith("/api/list/exact", request->local_uri)) + if((argument = startsWith("/api/list/exact", request->local_uri)) != NULL) listtype = GRAVITY_DOMAINLIST_ALL_EXACT; - else if(startsWith("/api/list/regex", request->local_uri)) + else if((argument = startsWith("/api/list/regex", request->local_uri)) != NULL) listtype = GRAVITY_DOMAINLIST_ALL_REGEX; else + { + argument = startsWith("/api/list", request->local_uri); listtype = GRAVITY_DOMAINLIST_ALL_ALL; + } } const enum http_method method = http_method(conn); if(method == HTTP_GET) { - return api_list_read(conn, listtype); + return api_list_read(conn, 200, listtype, argument); } else if(can_modify && (method == HTTP_POST || method == HTTP_PUT || method == HTTP_PATCH)) { // Add item from list - return api_list_write(conn, listtype, method); + return api_list_write(conn, listtype, method, argument); } else if(can_modify && method == HTTP_DELETE) { // Delete item from list - return api_list_remove(conn, listtype); + return api_list_remove(conn, listtype, argument); } else if(!can_modify) { diff --git a/src/database/gravity-db.c b/src/database/gravity-db.c index 055efdcca..28d38c266 100644 --- a/src/database/gravity-db.c +++ b/src/database/gravity-db.c @@ -1364,7 +1364,7 @@ bool gravityDB_get_regex_client_groups(clientsData* client, const unsigned int n return true; } -bool gravityDB_addToTable(const enum gravity_list_type listtype, const tablerow row, +bool gravityDB_addToTable(const enum gravity_list_type listtype, tablerow *row, const char **message, const enum http_method method) { if(gravity_db == NULL) @@ -1391,7 +1391,8 @@ bool gravityDB_addToTable(const enum gravity_list_type listtype, const tablerow case GRAVITY_GROUPS: case GRAVITY_ADLISTS: - // No type required for this table + case GRAVITY_CLIENTS: + // No type required for these tables break; // Aggregate types cannot be handled by this routine @@ -1403,6 +1404,7 @@ bool gravityDB_addToTable(const enum gravity_list_type listtype, const tablerow default: return false; } + row->type_int = type; // Prepare SQLite statement sqlite3_stmt* stmt = NULL; @@ -1410,58 +1412,46 @@ bool gravityDB_addToTable(const enum gravity_list_type listtype, const tablerow if(method == HTTP_POST) // Create NEW entry, error if existing { if(listtype == GRAVITY_GROUPS) - querystr = "INSERT INTO \"group\" (name,enabled,description) VALUES (:name,:enabled,:description);"; + querystr = "INSERT INTO \"group\" (name,enabled,description) VALUES (:argument,:enabled,:description);"; else if(listtype == GRAVITY_ADLISTS) - querystr = "INSERT INTO adlist (address,enabled,description) VALUES (:address,:enabled,:description);"; + querystr = "INSERT INTO adlist (address,enabled,description) VALUES (:argument,:enabled,:description);"; else // domainlist - querystr = "INSERT INTO domainlist (domain,type,enabled,comment) VALUES (:domain,:type,:enabled,:comment);"; + querystr = "INSERT INTO domainlist (domain,type,enabled,comment) VALUES (:argument,:type,:enabled,:comment);"; } else // Create new or replace existing entry, no error if existing // We have to use a subquery here to avoid violating FOREIGN KEY // contraints (REPLACE recreates (= new ID) entries instead of updating them) if(listtype == GRAVITY_GROUPS) querystr = "REPLACE INTO \"group\" (name,enabled,description,id,date_added) " - "VALUES (:name,:enabled,:description," - "(SELECT id FROM \"group\" WHERE name = :name)," - "(SELECT date_added FROM \"group\" WHERE name = :name));"; + "VALUES (:argument,:enabled,:description," + "(SELECT id FROM \"group\" WHERE name = :argument)," + "(SELECT date_added FROM \"group\" WHERE name = :argument));"; else if(listtype == GRAVITY_ADLISTS) querystr = "REPLACE INTO adlist (address,enabled,description,id,date_added) " - "VALUES (:address,:enabled,:description," - "(SELECT id FROM adlist WHERE address = :address)," - "(SELECT date_added FROM adlist WHERE address = :address));"; + "VALUES (:argument,:enabled,:description," + "(SELECT id FROM adlist WHERE address = :argument)," + "(SELECT date_added FROM adlist WHERE address = :argument));"; else // domainlist querystr = "REPLACE INTO domainlist (domain,type,enabled,comment,id,date_added) " - "VALUES (:domain,:type,:enabled,:comment," - "(SELECT id FROM domainlist WHERE domain = :domain and type = :type)," - "(SELECT date_added FROM domainlist WHERE domain = :domain and type = :type));"; + "VALUES (:argument,:type,:enabled,:comment," + "(SELECT id FROM domainlist WHERE domain = :argument and type = :oldtype)," + "(SELECT date_added FROM domainlist WHERE domain = :argument and type = :oldtype));"; int rc = sqlite3_prepare_v2(gravity_db, querystr, -1, &stmt, NULL); if( rc != SQLITE_OK ) { *message = sqlite3_errmsg(gravity_db); - logg("gravityDB_addToTable(%d, %s, %s) - SQL error prepare (%i): %s", - type, row.domain, row.name, rc, *message); + logg("gravityDB_addToTable(%d, %s) - SQL error prepare (%i): %s", + type, row->domain, rc, *message); return false; } - // Bind domain to prepared statement (if requested) - int idx = sqlite3_bind_parameter_index(stmt, ":domain"); - if(idx > 0 && (rc = sqlite3_bind_text(stmt, idx, row.domain, -1, SQLITE_STATIC)) != SQLITE_OK) - { - *message = sqlite3_errmsg(gravity_db); - logg("gravityDB_addToTable(%d, %s, %s): Failed to bind domain (error %d) - %s", - type, row.domain, row.name, rc, *message); - sqlite3_reset(stmt); - sqlite3_finalize(stmt); - return false; - } - - // Bind name to prepared statement (if requested) - idx = sqlite3_bind_parameter_index(stmt, ":name"); - if(idx > 0 && (rc = sqlite3_bind_text(stmt, idx, row.name, -1, SQLITE_STATIC)) != SQLITE_OK) + // Bind argument to prepared statement (if requested) + int idx = sqlite3_bind_parameter_index(stmt, ":argument"); + if(idx > 0 && (rc = sqlite3_bind_text(stmt, idx, row->argument, -1, SQLITE_STATIC)) != SQLITE_OK) { *message = sqlite3_errmsg(gravity_db); - logg("gravityDB_addToTable(%d, %s, %s): Failed to bind name (error %d) - %s", - type, row.domain, row.name, rc, *message); + logg("gravityDB_addToTable(%d, %s): Failed to bind argument (error %d) - %s", + type, row->argument, rc, *message); sqlite3_reset(stmt); sqlite3_finalize(stmt); return false; @@ -1472,20 +1462,62 @@ bool gravityDB_addToTable(const enum gravity_list_type listtype, const tablerow if(idx > 0 && (rc = sqlite3_bind_int(stmt, idx, type)) != SQLITE_OK) { *message = sqlite3_errmsg(gravity_db); - logg("gravityDB_addToTable(%d, %s, %s): Failed to bind type (error %d) - %s", - type, row.domain, row.name, rc, *message); + logg("gravityDB_addToTable(%d, %s): Failed to bind type (error %d) - %s", + type, row->domain, rc, *message); sqlite3_reset(stmt); sqlite3_finalize(stmt); return false; } + // Bind oldtype to prepared statement (if requested) + idx = sqlite3_bind_parameter_index(stmt, ":oldtype"); + if(idx > 0) + { + if(row->oldtype == NULL) + { + *message = "Field oldtype missing from request."; + logg("gravityDB_addToTable(%d, %s): Oldtype missing", + type, row->domain); + sqlite3_reset(stmt); + sqlite3_finalize(stmt); + return false; + } + int oldtype = -1; + if(strcasecmp("allow/exact", row->oldtype) == 0) + oldtype = 0; + else if(strcasecmp("deny/exact", row->oldtype) == 0) + oldtype = 1; + else if(strcasecmp("allow/regex", row->oldtype) == 0) + oldtype = 2; + else if(strcasecmp("deny/regex", row->oldtype) == 0) + oldtype = 3; + else + { + *message = "Cannot interpret oldtype field."; + logg("gravityDB_addToTable(%d, %s): Failed to identify oldtype \"%s\"", + type, row->domain, row->oldtype); + sqlite3_reset(stmt); + sqlite3_finalize(stmt); + return false; + } + if((rc = sqlite3_bind_int(stmt, idx, oldtype)) != SQLITE_OK) + { + *message = sqlite3_errmsg(gravity_db); + logg("gravityDB_addToTable(%d, %s): Failed to bind oldtype (error %d) - %s", + type, row->domain, rc, *message); + sqlite3_reset(stmt); + sqlite3_finalize(stmt); + return false; + } + } + // Bind enabled boolean to prepared statement (if requested) idx = sqlite3_bind_parameter_index(stmt, ":enabled"); - if(idx > 0 && (rc = sqlite3_bind_int(stmt, idx, row.enabled ? 1 : 0)) != SQLITE_OK) + if(idx > 0 && (rc = sqlite3_bind_int(stmt, idx, row->enabled ? 1 : 0)) != SQLITE_OK) { *message = sqlite3_errmsg(gravity_db); - logg("gravityDB_addToTable(%d, %s, %s): Failed to bind enabled (error %d) - %s", - type, row.domain, row.name, rc, *message); + logg("gravityDB_addToTable(%d, %s): Failed to bind enabled (error %d) - %s", + type, row->domain, rc, *message); sqlite3_reset(stmt); sqlite3_finalize(stmt); return false; @@ -1493,11 +1525,11 @@ bool gravityDB_addToTable(const enum gravity_list_type listtype, const tablerow // Bind comment string to prepared statement (if requested) idx = sqlite3_bind_parameter_index(stmt, ":comment"); - if(idx > 0 && (rc = sqlite3_bind_text(stmt, idx, row.comment, -1, SQLITE_STATIC)) != SQLITE_OK) + if(idx > 0 && (rc = sqlite3_bind_text(stmt, idx, row->comment, -1, SQLITE_STATIC)) != SQLITE_OK) { *message = sqlite3_errmsg(gravity_db); - logg("gravityDB_addToTable(%d, %s, %s): Failed to bind comment (error %d) - %s", - type, row.domain, row.name, rc, *message); + logg("gravityDB_addToTable(%d, %s): Failed to bind comment (error %d) - %s", + type, row->domain, rc, *message); sqlite3_reset(stmt); sqlite3_finalize(stmt); return false; @@ -1505,23 +1537,11 @@ bool gravityDB_addToTable(const enum gravity_list_type listtype, const tablerow // Bind description string to prepared statement (if requested) idx = sqlite3_bind_parameter_index(stmt, ":description"); - if(idx > 0 && (rc = sqlite3_bind_text(stmt, idx, row.description, -1, SQLITE_STATIC)) != SQLITE_OK) - { - *message = sqlite3_errmsg(gravity_db); - logg("gravityDB_addToTable(%d, %s, %s): Failed to bind description (error %d) - %s", - type, row.domain, row.name, rc, *message); - sqlite3_reset(stmt); - sqlite3_finalize(stmt); - return false; - } - - // Bind address string to prepared statement (if requested) - idx = sqlite3_bind_parameter_index(stmt, ":address"); - if(idx > 0 && (rc = sqlite3_bind_text(stmt, idx, row.address, -1, SQLITE_STATIC)) != SQLITE_OK) + if(idx > 0 && (rc = sqlite3_bind_text(stmt, idx, row->description, -1, SQLITE_STATIC)) != SQLITE_OK) { *message = sqlite3_errmsg(gravity_db); - logg("gravityDB_addToTable(%d, %s, %s): Failed to bind address (error %d) - %s", - type, row.domain, row.name, rc, *message); + logg("gravityDB_addToTable(%d, %s): Failed to bind description (error %d) - %s", + type, row->domain, rc, *message); sqlite3_reset(stmt); sqlite3_finalize(stmt); return false; @@ -1572,7 +1592,8 @@ bool gravityDB_delFromTable(const enum gravity_list_type listtype, const char* a case GRAVITY_GROUPS: case GRAVITY_ADLISTS: - // No type required for this table + case GRAVITY_CLIENTS: + // No type required for these tables break; // Aggregate types cannot be handled by this routine @@ -1627,6 +1648,14 @@ bool gravityDB_delFromTable(const enum gravity_list_type listtype, const char* a return false; } + // Debug output + if(config.debug & DEBUG_API) + { + logg("SQL: %s", querystr); + logg(" :argument = \"%s\"", argument); + logg(" :type = \"%i\"", type); + } + // Perform step bool okay = false; if((rc = sqlite3_step(stmt)) == SQLITE_DONE) @@ -1647,7 +1676,7 @@ bool gravityDB_delFromTable(const enum gravity_list_type listtype, const char* a } static sqlite3_stmt* read_stmt = NULL; -bool gravityDB_readTable(const enum gravity_list_type listtype, const char *filter, const char **message) +bool gravityDB_readTable(const enum gravity_list_type listtype, const char *argument, const char **message) { if(gravity_db == NULL) { @@ -1688,7 +1717,8 @@ bool gravityDB_readTable(const enum gravity_list_type listtype, const char *filt break; case GRAVITY_GROUPS: case GRAVITY_ADLISTS: - // No type required for this table + case GRAVITY_CLIENTS: + // No type required for these tables break; } @@ -1699,20 +1729,20 @@ bool gravityDB_readTable(const enum gravity_list_type listtype, const char *filt const char *extra = ""; if(listtype == GRAVITY_GROUPS) { - if(filter[0] != '\0') - extra = " WHERE name = :filter;"; + if(argument != NULL && argument[0] != '\0') + extra = " WHERE name = :argument;"; sprintf(querystr, "SELECT id,name,enabled,date_added,date_modified,description FROM \"group\"%s;", extra); } else if(listtype == GRAVITY_ADLISTS) { - if(filter[0] != '\0') - extra = " WHERE address = :filter;"; + if(argument != NULL && argument[0] != '\0') + extra = " WHERE address = :argument;"; sprintf(querystr, "SELECT id,address,enabled,date_added,date_modified,comment FROM adlist%s;", extra); } else // domainlist { - if(filter[0] != '\0') - extra = " AND domain = :filter;"; + if(argument != NULL && argument[0] != '\0') + extra = " AND domain = :argument;"; sprintf(querystr, "SELECT id,type,domain,enabled,date_added,date_modified,comment," "(SELECT GROUP_CONCAT(group_id) FROM domainlist_by_group g WHERE g.domainlist_id = d.id) AS group_ids " "FROM domainlist d WHERE d.type IN (%s)%s;", type, extra); @@ -1727,18 +1757,25 @@ bool gravityDB_readTable(const enum gravity_list_type listtype, const char *filt return false; } - // Bind filter to prepared statement (if requested) - int idx = sqlite3_bind_parameter_index(read_stmt, "filter"); - if(idx > 0 && (rc = sqlite3_bind_text(read_stmt, idx, filter, -1, SQLITE_STATIC)) != SQLITE_OK) + // Bind argument to prepared statement (if requested) + int idx = sqlite3_bind_parameter_index(read_stmt, ":argument"); + if(idx > 0 && (rc = sqlite3_bind_text(read_stmt, idx, argument, -1, SQLITE_STATIC)) != SQLITE_OK) { *message = sqlite3_errmsg(gravity_db); - logg("gravityDB_readTable(%d => (%s), %s): Failed to bind filter (error %d) - %s", - listtype, type, filter, rc, *message); + logg("gravityDB_readTable(%d => (%s), %s): Failed to bind argument (error %d) - %s", + listtype, type, argument, rc, *message); sqlite3_reset(read_stmt); sqlite3_finalize(read_stmt); return false; } + // Debug output + if(config.debug & DEBUG_API) + { + logg("SQL: %s", querystr); + logg(" :argument = \"%s\"", argument); + } + return true; } @@ -1832,3 +1869,220 @@ void gravityDB_readTableFinalize(void) // Finalize statement sqlite3_finalize(read_stmt); } + +bool gravityDB_edit_groups(const enum gravity_list_type listtype, cJSON *groups, + const tablerow *row, const char **message) +{ + if(gravity_db == NULL) + { + *message = "Database not available"; + return false; + } + + // Prepare SQLite statements + const char *get_querystr, *del_querystr, *add_querystr; + if(listtype == GRAVITY_GROUPS) + return false; + else if(listtype == GRAVITY_CLIENTS) + { + get_querystr = "SELECT id FROM client WHERE name = :argument"; + del_querystr = "DELETE FROM client_by_group WHERE client_id = :id);"; + add_querystr = "INSERT INTO client_by_group (client_id,group_id) VALUES (:id,:gid);"; + } + else if(listtype == GRAVITY_ADLISTS) + { + get_querystr = "SELECT id FROM adlist WHERE address = :argument"; + del_querystr = "DELETE FROM adlist_by_group WHERE adlist_id = :id;"; + add_querystr = "INSERT INTO adlist_by_group (adlist_id,group_id) VALUES (:id,:gid);"; + } + else // domainlist + { + get_querystr = "SELECT id FROM domainlist WHERE domain = :argument AND type = :type"; + del_querystr = "DELETE FROM domainlist_by_group WHERE domainlist_id = :id;"; + add_querystr = "INSERT INTO domainlist_by_group (domainlist_id,group_id) VALUES (:id,:gid);"; + } + + // First step: Get ID of the item to modify + sqlite3_stmt* stmt = NULL; + int rc = sqlite3_prepare_v2(gravity_db, get_querystr, -1, &stmt, NULL); + if( rc != SQLITE_OK ) + { + *message = sqlite3_errmsg(gravity_db); + logg("gravityDB_edit_groups(%d) - SQL error prepare SELECT (%i): %s", + listtype, rc, *message); + return false; + } + + // Bind argument string to prepared statement (if requested) + int idx = sqlite3_bind_parameter_index(stmt, ":argument"); + if(idx > 0 && (rc = sqlite3_bind_text(stmt, idx, row->argument, -1, SQLITE_STATIC)) != SQLITE_OK) + { + *message = sqlite3_errmsg(gravity_db); + logg("gravityDB_edit_groups(%d): Failed to bind argument SELECT (error %d) - %s", + listtype, rc, *message); + sqlite3_reset(stmt); + sqlite3_finalize(stmt); + return false; + } + + // Bind type to prepared statement (if requested) + idx = sqlite3_bind_parameter_index(stmt, ":type"); + if(idx > 0 && (rc = sqlite3_bind_int(stmt, idx, row->type_int)) != SQLITE_OK) + { + *message = sqlite3_errmsg(gravity_db); + logg("gravityDB_edit_groups(%d): Failed to bind type SELECT (error %d) - %s", + listtype, rc, *message); + sqlite3_reset(stmt); + sqlite3_finalize(stmt); + return false; + } + + // Perform step + bool okay = false; + int id = -1; + if((rc = sqlite3_step(stmt)) == SQLITE_ROW) + { + // Get ID of domain + id = sqlite3_column_int(stmt, 0); + okay = true; + } + else + { + *message = sqlite3_errmsg(gravity_db); + } + logg("SELECT: %i -> %i", rc, id); + + // Debug output + if(config.debug & DEBUG_API) + { + logg("SQL: %s", get_querystr); + logg(" :argument = \"%s\"", row->argument); + logg(" :type = \"%d\"", row->type_int); + } + + // Finalize statement + sqlite3_reset(stmt); + sqlite3_finalize(stmt); + + // Return early if getting the ID failed + if(!okay) + return false; + + // Second step: Delete all existing group associations for this item + rc = sqlite3_prepare_v2(gravity_db, del_querystr, -1, &stmt, NULL); + if( rc != SQLITE_OK ) + { + *message = sqlite3_errmsg(gravity_db); + logg("gravityDB_edit_groups(%d) - SQL error prepare DELETE (%i): %s", + listtype, rc, *message); + return false; + } + + // Bind id to prepared statement (if requested) + idx = sqlite3_bind_parameter_index(stmt, ":id"); + if(idx > 0 && (rc = sqlite3_bind_int(stmt, idx, id)) != SQLITE_OK) + { + *message = sqlite3_errmsg(gravity_db); + logg("gravityDB_edit_groups(%d): Failed to bind id DELETE (error %d) - %s", + listtype, rc, *message); + sqlite3_reset(stmt); + sqlite3_finalize(stmt); + return false; + } + + // Perform step + if((rc = sqlite3_step(stmt)) == SQLITE_DONE) + { + // All groups deleted + } + else + { + okay = false; + *message = sqlite3_errmsg(gravity_db); + } + logg("DELETE: %i", rc); + + // Debug output + if(config.debug & DEBUG_API) + { + logg("SQL: %s", del_querystr); + logg(" :id = \"%d\"", id); + } + + // Finalize statement + sqlite3_reset(stmt); + sqlite3_finalize(stmt); + + // Return early if deleting the existing group associations failed + if(!okay) + return false; + + // Third step: Create new group associations for this item + rc = sqlite3_prepare_v2(gravity_db, add_querystr, -1, &stmt, NULL); + if( rc != SQLITE_OK ) + { + *message = sqlite3_errmsg(gravity_db); + logg("gravityDB_edit_groups(%d) - SQL error prepare INSERT (%i): %s", + listtype, rc, *message); + return false; + } + + // Bind id to prepared statement (if requested) + idx = sqlite3_bind_parameter_index(stmt, ":id"); + if(idx > 0 && (rc = sqlite3_bind_int(stmt, idx, id)) != SQLITE_OK) + { + *message = sqlite3_errmsg(gravity_db); + logg("gravityDB_edit_groups(%d): Failed to bind id INSERT (error %d) - %s", + listtype, rc, *message); + sqlite3_reset(stmt); + sqlite3_finalize(stmt); + return false; + } + + // Loop over all loops in array + const int groupcount = cJSON_GetArraySize(groups); + logg("groupscount = %d", groupcount); + for(int i = 0; i < groupcount; i++) + { + cJSON *group = cJSON_GetArrayItem(groups, i); + if(group == NULL || !cJSON_IsNumber(group)) + continue; + + idx = sqlite3_bind_parameter_index(stmt, ":gid"); + if(idx > 0 && (rc = sqlite3_bind_int(stmt, idx, group->valueint)) != SQLITE_OK) + { + *message = sqlite3_errmsg(gravity_db); + logg("gravityDB_edit_groups(%d): Failed to bind gid INSERT (error %d) - %s", + listtype, rc, *message); + sqlite3_reset(stmt); + sqlite3_finalize(stmt); + return false; + } + + // Perform step + if((rc = sqlite3_step(stmt)) != SQLITE_DONE) + { + okay = false; + *message = sqlite3_errmsg(gravity_db); + break; + } + logg("INSERT: %i -> (%i,%i)", rc, id, group->valueint); + + // Debug output + if(config.debug & DEBUG_API) + { + logg("SQL: %s", add_querystr); + logg(" :id = \"%d\"", id); + logg(" :gid = \"%d\"", group->valueint); + } + + // Reset before next iteration, this will not clear the id binding + sqlite3_reset(stmt); + } + + + // Finalize statement + sqlite3_finalize(stmt); + + return okay; +} diff --git a/src/database/gravity-db.h b/src/database/gravity-db.h index d74bf2c1d..6fbeb30ed 100644 --- a/src/database/gravity-db.h +++ b/src/database/gravity-db.h @@ -28,6 +28,8 @@ typedef struct { const char *comment; const char *group_ids; const char *description; + const char *argument; + const char *oldtype; long id; time_t date_added; time_t date_modified; @@ -56,8 +58,10 @@ bool gravityDB_get_regex_client_groups(clientsData* client, const unsigned int n bool gravityDB_readTable(const enum gravity_list_type listtype, const char *filter, const char **message); bool gravityDB_readTableGetRow(tablerow *row, const char **message); void gravityDB_readTableFinalize(void); -bool gravityDB_addToTable(const enum gravity_list_type listtype, const tablerow row, +bool gravityDB_addToTable(const enum gravity_list_type listtype, tablerow *row, const char **message, const enum http_method method); bool gravityDB_delFromTable(const enum gravity_list_type listtype, const char* domain_name, const char **message); +bool gravityDB_edit_groups(const enum gravity_list_type listtype, cJSON *groups, + const tablerow *row, const char **message); #endif //GRAVITY_H diff --git a/src/enums.h b/src/enums.h index ac2df57a4..d82d6260b 100644 --- a/src/enums.h +++ b/src/enums.h @@ -167,7 +167,8 @@ enum gravity_list_type { GRAVITY_DOMAINLIST_ALL_REGEX, GRAVITY_DOMAINLIST_ALL_ALL, GRAVITY_GROUPS, - GRAVITY_ADLISTS + GRAVITY_ADLISTS, + GRAVITY_CLIENTS } __attribute__ ((packed)); enum gravity_tables { diff --git a/src/webserver/http-common.c b/src/webserver/http-common.c index d478a07ab..ad1d4f6cb 100644 --- a/src/webserver/http-common.c +++ b/src/webserver/http-common.c @@ -164,9 +164,18 @@ bool http_get_payload(struct mg_connection *conn, char *payload, const size_t si return false; } -bool __attribute__((pure)) startsWith(const char *path, const char *uri) -{ - return strncmp(path, uri, strlen(path)) == 0; +const char* __attribute__((pure)) startsWith(const char *path, const char *uri) +{ + if(strncmp(path, uri, strlen(path)) == 0) + if(uri[strlen(path)] == '/') + // Path match with argument after ".../" + return uri + strlen(path) + 1u; + else + // Path match without argument + return ""; + else + // Path does not match + return NULL; } bool http_get_cookie_int(struct mg_connection *conn, const char *cookieName, int *i) diff --git a/src/webserver/http-common.h b/src/webserver/http-common.h index 2a1e66e66..e1bacfe4c 100644 --- a/src/webserver/http-common.h +++ b/src/webserver/http-common.h @@ -49,6 +49,6 @@ enum http_method { HTTP_UNKNOWN, HTTP_GET, HTTP_POST, HTTP_PUT, HTTP_PATCH, HTTP int http_method(struct mg_connection *conn); // Utils -bool startsWith(const char *path, const char *uri) __attribute__((pure)); +const char *startsWith(const char *path, const char *uri) __attribute__((pure)); #endif // HTTP_H From 1fc05034ef3682bc6d80c08f1660efb7af0e567d Mon Sep 17 00:00:00 2001 From: DL6ER Date: Wed, 20 Jan 2021 17:07:36 +0100 Subject: [PATCH 0221/1669] Set table columns comment/description to NULL if empty Signed-off-by: DL6ER --- src/api/list.c | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/api/list.c b/src/api/list.c index 99507afc7..c0d3e30ab 100644 --- a/src/api/list.c +++ b/src/api/list.c @@ -171,25 +171,25 @@ static int api_list_write(struct mg_connection *conn, row.enabled = cJSON_IsTrue(json_enabled); cJSON *json_comment = cJSON_GetObjectItemCaseSensitive(obj, "comment"); - if(cJSON_IsString(json_comment)) + if(cJSON_IsString(json_comment) && strlen(json_comment->valuestring) > 0) row.comment = json_comment->valuestring; else row.comment = NULL; cJSON *json_description = cJSON_GetObjectItemCaseSensitive(obj, "description"); - if(cJSON_IsString(json_description)) + if(cJSON_IsString(json_description) && strlen(json_description->valuestring) > 0) row.description = json_description->valuestring; else row.description = NULL; cJSON *json_name = cJSON_GetObjectItemCaseSensitive(obj, "name"); - if(cJSON_IsString(json_name)) + if(cJSON_IsString(json_name) && strlen(json_name->valuestring) > 0) row.name = json_name->valuestring; else row.name = NULL; cJSON *json_oldtype = cJSON_GetObjectItemCaseSensitive(obj, "oldtype"); - if(cJSON_IsString(json_oldtype)) + if(cJSON_IsString(json_oldtype) && strlen(json_oldtype->valuestring) > 0) row.oldtype = json_oldtype->valuestring; else row.oldtype = NULL; @@ -202,6 +202,10 @@ static int api_list_write(struct mg_connection *conn, cJSON *groups = cJSON_GetObjectItemCaseSensitive(obj, "groups"); if(groups != NULL) okay = gravityDB_edit_groups(listtype, groups, &row, &sql_msg); + else + // The groups array is optional, we still succeed if it + // is omitted (groups stay as they are) + okay = true; } if(!okay) { From 0d91fcd55df97dd52e0a34c1cc8515160ff6bb70 Mon Sep 17 00:00:00 2001 From: DL6ER Date: Thu, 21 Jan 2021 08:28:37 +0100 Subject: [PATCH 0222/1669] Rename /api/list -> /api/domains, /api/adlist -> /api/adlists, /api/group -> /api/groups, added /api/clients Signed-off-by: DL6ER --- src/api/list.c | 27 ++++++++++++++++----------- src/api/routes.c | 12 ++++++++---- 2 files changed, 24 insertions(+), 15 deletions(-) diff --git a/src/api/list.c b/src/api/list.c index c0d3e30ab..320fe0bea 100644 --- a/src/api/list.c +++ b/src/api/list.c @@ -290,24 +290,29 @@ int api_list(struct mg_connection *conn) bool can_modify = false; const struct mg_request_info *request = mg_get_request_info(conn); const char *argument = NULL; - if((argument = startsWith("/api/group", request->local_uri)) != NULL) + if((argument = startsWith("/api/groups", request->local_uri)) != NULL) { listtype = GRAVITY_GROUPS; can_modify = true; } - else if((argument = startsWith("/api/adlist", request->local_uri)) != NULL) + else if((argument = startsWith("/api/adlists", request->local_uri)) != NULL) { listtype = GRAVITY_ADLISTS; can_modify = true; } - else if((argument = startsWith("/api/list/allow", request->local_uri)) != NULL) + else if((argument = startsWith("/api/clients", request->local_uri)) != NULL) { - if((argument = startsWith("/api/list/allow/exact", request->local_uri)) != NULL) + listtype = GRAVITY_CLIENTS; + can_modify = true; + } + else if((argument = startsWith("/api/domains/allow", request->local_uri)) != NULL) + { + if((argument = startsWith("/api/domains/allow/exact", request->local_uri)) != NULL) { listtype = GRAVITY_DOMAINLIST_ALLOW_EXACT; can_modify = true; } - else if((argument = startsWith("/api/list/allow/regex", request->local_uri)) != NULL) + else if((argument = startsWith("/api/domains/allow/regex", request->local_uri)) != NULL) { listtype = GRAVITY_DOMAINLIST_ALLOW_REGEX; can_modify = true; @@ -315,14 +320,14 @@ int api_list(struct mg_connection *conn) else listtype = GRAVITY_DOMAINLIST_ALLOW_ALL; } - else if((argument = startsWith("/api/list/deny", request->local_uri)) != NULL) + else if((argument = startsWith("/api/domains/deny", request->local_uri)) != NULL) { - if((argument = startsWith("/api/list/deny/exact", request->local_uri)) != NULL) + if((argument = startsWith("/api/domains/deny/exact", request->local_uri)) != NULL) { listtype = GRAVITY_DOMAINLIST_DENY_EXACT; can_modify = true; } - else if((argument = startsWith("/api/list/deny/regex", request->local_uri)) != NULL) + else if((argument = startsWith("/api/domains/deny/regex", request->local_uri)) != NULL) { listtype = GRAVITY_DOMAINLIST_DENY_REGEX; can_modify = true; @@ -332,13 +337,13 @@ int api_list(struct mg_connection *conn) } else { - if((argument = startsWith("/api/list/exact", request->local_uri)) != NULL) + if((argument = startsWith("/api/domains/exact", request->local_uri)) != NULL) listtype = GRAVITY_DOMAINLIST_ALL_EXACT; - else if((argument = startsWith("/api/list/regex", request->local_uri)) != NULL) + else if((argument = startsWith("/api/domains/regex", request->local_uri)) != NULL) listtype = GRAVITY_DOMAINLIST_ALL_REGEX; else { - argument = startsWith("/api/list", request->local_uri); + argument = startsWith("/api/domains", request->local_uri); listtype = GRAVITY_DOMAINLIST_ALL_ALL; } } diff --git a/src/api/routes.c b/src/api/routes.c index f9f45be8a..14a3bbd7e 100644 --- a/src/api/routes.c +++ b/src/api/routes.c @@ -37,16 +37,20 @@ int api_handler(struct mg_connection *conn, void *ignored) { ret = api_dns_cacheinfo(conn); } - /******************************** /api/list, /api/group, /api/adlist ****************************/ - else if(startsWith("/api/list", request->local_uri)) + /*********** /api/domains, /api/groups, /api/adlists, /api/clients *******/ + else if(startsWith("/api/domains", request->local_uri)) { ret = api_list(conn); } - else if(startsWith("/api/group", request->local_uri)) + else if(startsWith("/api/groups", request->local_uri)) { ret = api_list(conn); } - else if(startsWith("/api/adlist", request->local_uri)) + else if(startsWith("/api/adlists", request->local_uri)) + { + ret = api_list(conn); + } + else if(startsWith("/api/clients", request->local_uri)) { ret = api_list(conn); } From 15ba45e50f1b6344e3e5bf937be1667a4f649a48 Mon Sep 17 00:00:00 2001 From: DL6ER Date: Thu, 21 Jan 2021 13:52:31 +0100 Subject: [PATCH 0223/1669] Extract payload only once Signed-off-by: DL6ER --- src/api/auth.c | 72 ++++++++++++++++++++++--------------- src/api/dns.c | 6 ++-- src/api/ftl.c | 4 +-- src/api/list.c | 13 ++++--- src/api/network.c | 2 +- src/api/routes.h | 3 +- src/api/stats.c | 14 ++++---- src/webserver/http-common.c | 14 ++++---- src/webserver/http-common.h | 6 ++-- 9 files changed, 76 insertions(+), 58 deletions(-) diff --git a/src/api/auth.c b/src/api/auth.c index a70125727..99ac08dae 100644 --- a/src/api/auth.c +++ b/src/api/auth.c @@ -75,7 +75,7 @@ static void sha256_hex(uint8_t *data, char *buffer) // Returns >= 0 for any valid authentication #define LOCALHOSTv4 "127.0.0.1" #define LOCALHOSTv6 "::1" -int check_client_auth(struct mg_connection *conn) +int check_client_auth(struct mg_connection *conn, char payload[MAX_PAYLOAD_BYTES]) { int user_id = -1; const struct mg_request_info *request = mg_get_request_info(conn); @@ -96,30 +96,48 @@ int check_client_auth(struct mg_connection *conn) // Does the client provide a session cookie? char sid[SID_SIZE]; + const char *sid_source = "cookie"; bool sid_avail = http_get_cookie_str(conn, "sid", sid, SID_SIZE); // If not, does the client provide a session ID via GET/POST? - char payload[1024] = { 0 }; - bool sid_payload = false; - if(!sid_avail && http_get_payload(conn, payload, sizeof(payload))) + bool sid_raw_payload = false, sid_json_payload = false; + cJSON *json; + if(!sid_avail && http_get_payload(conn, payload)) { - sid_avail = GET_VAR("sid", sid, payload) > 0; - - // "+" may have been replaced by " ", undo this here - for(unsigned int i = 0; i < SID_SIZE; i++) - if(sid[i] == ' ') - sid[i] = '+'; - - // Zero terminate - sid[SID_SIZE-1] = '\0'; - sid_payload = true; + // Try to extract SID from form-encoded payload + if(GET_VAR("sid", sid, payload) > 0) + { + // "+" may have been replaced by " ", undo this here + for(unsigned int i = 0; i < SID_SIZE; i++) + if(sid[i] == ' ') + sid[i] = '+'; + + // Zero terminate + sid[SID_SIZE-1] = '\0'; + sid_source = "payload (form-data)"; + sid_avail = true; + } + // Try to extract SID from root of a possibly included JSON payload + else if((json = cJSON_Parse(payload)) != NULL) + { + cJSON *sid_obj = cJSON_GetObjectItem(json, "sid"); + if(cJSON_IsString(sid_obj)) + { + strncpy(sid, sid_obj->valuestring, SID_SIZE - 1u); + sid_source = "payload (JSON)"; + sid_avail = true; + } + } } - if(sid_avail) + logg("payload: \"%s\"", payload); + logg("sid: \"%s\"", sid); + + if(sid_avail || sid_raw_payload || sid_json_payload) { const time_t now = time(NULL); if(config.debug & DEBUG_API) - logg("API: Read sid=\"%s\" from %s", sid, sid_payload ? "payload" : "cookie"); + logg("API: Read sid=\"%s\" from %s", sid, sid_source); for(unsigned int i = 0; i < API_MAX_CLIENTS; i++) { @@ -374,14 +392,18 @@ int api_auth(struct mg_connection *conn) bool reponse_set = false; char response[256] = { 0 }; + char payload[MAX_PAYLOAD_BYTES]; + + // Did the client authenticate before and we can validate this? + user_id = check_client_auth(conn, payload); + + // If this is a valid session, we can exit early at this point + if(user_id != API_AUTH_UNAUTHORIZED) + return send_api_auth_status(conn, user_id, method, now); // Login attempt, extract response if(method == HTTP_POST) { - // Extract payload - char payload[1024] = { 0 }; - http_get_payload(conn, payload, sizeof(payload)); - // Try to extract response from payload int len = 0; if((len = GET_VAR("response", response, payload)) != CHALLENGE_SIZE) @@ -397,9 +419,6 @@ int api_auth(struct mg_connection *conn) reponse_set = true; } - // Did the client authenticate before and we can validate this? - user_id = check_client_auth(conn); - // Logout attempt if(method == HTTP_DELETE) { @@ -408,10 +427,6 @@ int api_auth(struct mg_connection *conn) return send_api_auth_status(conn, user_id, method, now); } - // If this is a valid session, we can exit early at this point - if(user_id != API_AUTH_UNAUTHORIZED) - return send_api_auth_status(conn, user_id, method, now); - // Login attempt and/or auth check if(reponse_set || empty_password) { @@ -513,8 +528,7 @@ int api_auth(struct mg_connection *conn) if(config.debug & DEBUG_API) { - logg("API: Sending challenge=%s, expecting response=%s", - challenges[i].challenge, challenges[i].response); + logg("API: Sending challenge=%s", challenges[i].challenge); } // Return to user diff --git a/src/api/dns.c b/src/api/dns.c index 59b30d1c1..173164702 100644 --- a/src/api/dns.c +++ b/src/api/dns.c @@ -48,7 +48,7 @@ static int get_blocking(struct mg_connection *conn) static int set_blocking(struct mg_connection *conn) { // Verify requesting client is allowed to access this ressource - if(check_client_auth(conn) == API_AUTH_UNAUTHORIZED) + if(check_client_auth(conn, NULL) == API_AUTH_UNAUTHORIZED) { return send_json_unauthorized(conn); } @@ -117,7 +117,7 @@ int api_dns_blockingstatus(struct mg_connection *conn) { return get_blocking(conn); } - else if(method == HTTP_PATCH) + else if(method == HTTP_PUT) { return set_blocking(conn); } @@ -131,7 +131,7 @@ int api_dns_blockingstatus(struct mg_connection *conn) int api_dns_cacheinfo(struct mg_connection *conn) { // Verify requesting client is allowed to access this ressource - if(check_client_auth(conn) == API_AUTH_UNAUTHORIZED) + if(check_client_auth(conn, NULL) == API_AUTH_UNAUTHORIZED) { return send_json_unauthorized(conn); } diff --git a/src/api/ftl.c b/src/api/ftl.c index 0ef3645c3..0782586b4 100644 --- a/src/api/ftl.c +++ b/src/api/ftl.c @@ -63,7 +63,7 @@ fifologData *fifo_log = NULL; int api_ftl_dnsmasq_log(struct mg_connection *conn) { // Verify requesting client is allowed to see this ressource - if(check_client_auth(conn) == API_AUTH_UNAUTHORIZED) + if(check_client_auth(conn, NULL) == API_AUTH_UNAUTHORIZED) { return send_json_unauthorized(conn); } @@ -128,7 +128,7 @@ int api_ftl_dnsmasq_log(struct mg_connection *conn) int api_ftl_database(struct mg_connection *conn) { // Verify requesting client is allowed to see this ressource - if(check_client_auth(conn) == API_AUTH_UNAUTHORIZED) + if(check_client_auth(conn, NULL) == API_AUTH_UNAUTHORIZED) { send_json_unauthorized(conn); } diff --git a/src/api/list.c b/src/api/list.c index 320fe0bea..80d0ef7f0 100644 --- a/src/api/list.c +++ b/src/api/list.c @@ -141,7 +141,8 @@ static int api_list_read(struct mg_connection *conn, static int api_list_write(struct mg_connection *conn, const enum gravity_list_type listtype, const enum http_method method, - const char *argument) + const char *argument, + char payload[MAX_PAYLOAD_BYTES]) { tablerow row = { 0 }; @@ -149,9 +150,6 @@ static int api_list_write(struct mg_connection *conn, row.argument = argument; // Extract payload - char payload[1024] = { 0 }; - http_get_payload(conn, payload, sizeof(payload)); - cJSON *obj = cJSON_Parse(payload); if (obj == NULL) { return send_json_error(conn, 400, @@ -281,7 +279,8 @@ static int api_list_remove(struct mg_connection *conn, int api_list(struct mg_connection *conn) { // Verify requesting client is allowed to see this ressource - if(check_client_auth(conn) == API_AUTH_UNAUTHORIZED) + char payload[MAX_PAYLOAD_BYTES] = { 0 }; + if(check_client_auth(conn, payload) == API_AUTH_UNAUTHORIZED) { return send_json_unauthorized(conn); } @@ -353,10 +352,10 @@ int api_list(struct mg_connection *conn) { return api_list_read(conn, 200, listtype, argument); } - else if(can_modify && (method == HTTP_POST || method == HTTP_PUT || method == HTTP_PATCH)) + else if(can_modify && (method == HTTP_POST || method == HTTP_PUT)) { // Add item from list - return api_list_write(conn, listtype, method, argument); + return api_list_write(conn, listtype, method, argument, payload); } else if(can_modify && method == HTTP_DELETE) { diff --git a/src/api/network.c b/src/api/network.c index c811e386d..fc99208e6 100644 --- a/src/api/network.c +++ b/src/api/network.c @@ -18,7 +18,7 @@ int api_network(struct mg_connection *conn) { // Verify requesting client is allowed to see this ressource - if(check_client_auth(conn) == API_AUTH_UNAUTHORIZED) + if(check_client_auth(conn, NULL) == API_AUTH_UNAUTHORIZED) { return send_json_unauthorized(conn); } diff --git a/src/api/routes.h b/src/api/routes.h index 099eef42d..7a91d65a0 100644 --- a/src/api/routes.h +++ b/src/api/routes.h @@ -14,6 +14,7 @@ #include "../civetweb/civetweb.h" // type cJSON #include "../cJSON/cJSON.h" +#include "../webserver/http-common.h" // API router int api_handler(struct mg_connection *conn, void *ignored); @@ -59,7 +60,7 @@ int api_group(struct mg_connection *conn); int api_version(struct mg_connection *conn); // Auth method -int check_client_auth(struct mg_connection *conn); +int check_client_auth(struct mg_connection *conn, char payload[MAX_PAYLOAD_BYTES]); int api_auth(struct mg_connection *conn); // Settings methods diff --git a/src/api/stats.c b/src/api/stats.c index 25cc9a68e..8f22f6540 100644 --- a/src/api/stats.c +++ b/src/api/stats.c @@ -185,7 +185,7 @@ int api_stats_top_domains(bool blocked, struct mg_connection *conn) bool audit = false; // Verify requesting client is allowed to see this ressource - if(check_client_auth(conn) == API_AUTH_UNAUTHORIZED) + if(check_client_auth(conn, NULL) == API_AUTH_UNAUTHORIZED) { return send_json_unauthorized(conn); } @@ -343,7 +343,7 @@ int api_stats_top_clients(bool blocked, struct mg_connection *conn) bool includezeroclients = false; // Verify requesting client is allowed to see this ressource - if(check_client_auth(conn) == API_AUTH_UNAUTHORIZED) + if(check_client_auth(conn, NULL) == API_AUTH_UNAUTHORIZED) { return send_json_unauthorized(conn); } @@ -470,7 +470,7 @@ int api_stats_upstreams(struct mg_connection *conn) int temparray[counters->forwarded][2]; // Verify requesting client is allowed to see this ressource - if(check_client_auth(conn) == API_AUTH_UNAUTHORIZED) + if(check_client_auth(conn, NULL) == API_AUTH_UNAUTHORIZED) { return send_json_unauthorized(conn); } @@ -571,7 +571,7 @@ int api_stats_upstreams(struct mg_connection *conn) int api_stats_query_types(struct mg_connection *conn) { // Verify requesting client is allowed to see this ressource - if(check_client_auth(conn) == API_AUTH_UNAUTHORIZED) + if(check_client_auth(conn, NULL) == API_AUTH_UNAUTHORIZED) { return send_json_unauthorized(conn); } @@ -607,7 +607,7 @@ int api_stats_history(struct mg_connection *conn) } // Verify requesting client is allowed to see this ressource - if(check_client_auth(conn) == API_AUTH_UNAUTHORIZED) + if(check_client_auth(conn, NULL) == API_AUTH_UNAUTHORIZED) { return send_json_unauthorized(conn); } @@ -1065,7 +1065,7 @@ int api_stats_recentblocked(struct mg_connection *conn) unsigned int show = 1; // Verify requesting client is allowed to see this ressource - if(check_client_auth(conn) == API_AUTH_UNAUTHORIZED) + if(check_client_auth(conn, NULL) == API_AUTH_UNAUTHORIZED) { return send_json_unauthorized(conn); } @@ -1129,7 +1129,7 @@ int api_stats_overTime_clients(struct mg_connection *conn) int sendit = -1, until = OVERTIME_SLOTS; // Verify requesting client is allowed to see this ressource - if(check_client_auth(conn) == API_AUTH_UNAUTHORIZED) + if(check_client_auth(conn, NULL) == API_AUTH_UNAUTHORIZED) { return send_json_unauthorized(conn); } diff --git a/src/webserver/http-common.c b/src/webserver/http-common.c index ad1d4f6cb..35238342d 100644 --- a/src/webserver/http-common.c +++ b/src/webserver/http-common.c @@ -141,19 +141,23 @@ bool get_uint_var(const char *source, const char *var, unsigned int *num) } // Extract payload either from GET or POST data -bool http_get_payload(struct mg_connection *conn, char *payload, const size_t size) +bool http_get_payload(struct mg_connection *conn, char *payload) { + // We do not want to extract any payload here + if(payload == NULL) + return false; + const enum http_method method = http_method(conn); const struct mg_request_info *request = mg_get_request_info(conn); if(method == HTTP_GET && request->query_string != NULL) { - strncpy(payload, request->query_string, size-1); + strncpy(payload, request->query_string, MAX_PAYLOAD_BYTES-1); return true; } else // POST, PUT, PATCH { - int data_len = mg_read(conn, payload, size - 1); - if ((data_len < 1) || (data_len >= (int)size)) + int data_len = mg_read(conn, payload, MAX_PAYLOAD_BYTES - 1); + if ((data_len < 1) || (data_len >= MAX_PAYLOAD_BYTES)) return false; payload[data_len] = '\0'; @@ -212,8 +216,6 @@ int http_method(struct mg_connection *conn) return HTTP_PUT; else if(strcmp(request->request_method, "POST") == 0) return HTTP_POST; - else if(strcmp(request->request_method, "PATCH") == 0) - return HTTP_PATCH; else return HTTP_UNKNOWN; } diff --git a/src/webserver/http-common.h b/src/webserver/http-common.h index e1bacfe4c..b593594e8 100644 --- a/src/webserver/http-common.h +++ b/src/webserver/http-common.h @@ -17,6 +17,8 @@ // strlen() #include +#define MAX_PAYLOAD_BYTES 2048 + const char* json_formatter(const cJSON *object); int send_http(struct mg_connection *conn, const char *mime_type, const char *msg); @@ -38,14 +40,14 @@ bool http_get_cookie_str(struct mg_connection *conn, const char *cookieName, cha bool get_bool_var(const char *source, const char *var, bool *boolean); bool get_uint_var(const char *source, const char *var, unsigned int *num); bool get_int_var(const char *source, const char *var, int *num); -bool http_get_payload(struct mg_connection *conn, char *payload, const size_t size); +bool http_get_payload(struct mg_connection *conn, char *payload); cJSON *get_POST_JSON(struct mg_connection *conn); // HTTP macros #define GET_VAR(variable, destination, source) mg_get_var(source, strlen(source), variable, destination, sizeof(destination)) // Method routines -enum http_method { HTTP_UNKNOWN, HTTP_GET, HTTP_POST, HTTP_PUT, HTTP_PATCH, HTTP_DELETE }; +enum http_method { HTTP_UNKNOWN, HTTP_GET, HTTP_POST, HTTP_PUT, HTTP_DELETE }; int http_method(struct mg_connection *conn); // Utils From f67becc3e1bcd7c3201733c45f08b3b2755c0ddd Mon Sep 17 00:00:00 2001 From: DL6ER Date: Thu, 21 Jan 2021 16:24:16 +0100 Subject: [PATCH 0224/1669] Introduce new ftl_conn struct that makes it easier to share stuff across processing subroutines in the same thread. Signed-off-by: DL6ER --- src/api/auth.c | 153 +++++++++++++++++------------------ src/api/dns.c | 51 +++++------- src/api/ftl.c | 47 +++++------ src/api/list.c | 112 +++++++++++--------------- src/api/network.c | 14 ++-- src/api/routes.c | 156 ++++++++++++++++++++---------------- src/api/routes.h | 59 +++++++------- src/api/settings.c | 2 +- src/api/stats.c | 98 +++++++++++----------- src/api/stats_database.c | 106 ++++++++++++------------ src/api/version.c | 2 +- src/database/gravity-db.h | 1 + src/webserver/http-common.c | 98 +++++++++------------- src/webserver/http-common.h | 39 +++++---- src/webserver/json_macros.h | 30 +++---- src/webserver/webserver.c | 11 +-- 16 files changed, 465 insertions(+), 514 deletions(-) diff --git a/src/api/auth.c b/src/api/auth.c index 99ac08dae..78a474e8a 100644 --- a/src/api/auth.c +++ b/src/api/auth.c @@ -75,14 +75,11 @@ static void sha256_hex(uint8_t *data, char *buffer) // Returns >= 0 for any valid authentication #define LOCALHOSTv4 "127.0.0.1" #define LOCALHOSTv6 "::1" -int check_client_auth(struct mg_connection *conn, char payload[MAX_PAYLOAD_BYTES]) +int check_client_auth(struct ftl_conn *api) { - int user_id = -1; - const struct mg_request_info *request = mg_get_request_info(conn); - // Is the user requesting from localhost? - if(!httpsettings.api_auth_for_localhost && (strcmp(request->remote_addr, LOCALHOSTv4) == 0 || - strcmp(request->remote_addr, LOCALHOSTv6) == 0)) + if(!httpsettings.api_auth_for_localhost && (strcmp(api->request->remote_addr, LOCALHOSTv4) == 0 || + strcmp(api->request->remote_addr, LOCALHOSTv6) == 0)) { return API_AUTH_LOCALHOST; } @@ -97,15 +94,13 @@ int check_client_auth(struct mg_connection *conn, char payload[MAX_PAYLOAD_BYTES // Does the client provide a session cookie? char sid[SID_SIZE]; const char *sid_source = "cookie"; - bool sid_avail = http_get_cookie_str(conn, "sid", sid, SID_SIZE); + bool sid_avail = http_get_cookie_str(api, "sid", sid, SID_SIZE); // If not, does the client provide a session ID via GET/POST? - bool sid_raw_payload = false, sid_json_payload = false; - cJSON *json; - if(!sid_avail && http_get_payload(conn, payload)) + if(!sid_avail && api->payload.avail) { // Try to extract SID from form-encoded payload - if(GET_VAR("sid", sid, payload) > 0) + if(GET_VAR("sid", sid, api->payload.raw) > 0) { // "+" may have been replaced by " ", undo this here for(unsigned int i = 0; i < SID_SIZE; i++) @@ -118,70 +113,73 @@ int check_client_auth(struct mg_connection *conn, char payload[MAX_PAYLOAD_BYTES sid_avail = true; } // Try to extract SID from root of a possibly included JSON payload - else if((json = cJSON_Parse(payload)) != NULL) + else if(api->payload.json != NULL) { - cJSON *sid_obj = cJSON_GetObjectItem(json, "sid"); + cJSON *sid_obj = cJSON_GetObjectItem(api->payload.json, "sid"); if(cJSON_IsString(sid_obj)) { strncpy(sid, sid_obj->valuestring, SID_SIZE - 1u); + sid[SID_SIZE-1] = '\0'; sid_source = "payload (JSON)"; sid_avail = true; } } } - logg("payload: \"%s\"", payload); - logg("sid: \"%s\"", sid); - - if(sid_avail || sid_raw_payload || sid_json_payload) + if(!sid_avail) { - const time_t now = time(NULL); if(config.debug & DEBUG_API) - logg("API: Read sid=\"%s\" from %s", sid, sid_source); + logg("API Authentification: FAIL (no SID provided)"); + + return API_AUTH_UNAUTHORIZED; + } + + // else: Analyze SID + int user_id = API_AUTH_UNAUTHORIZED; + const time_t now = time(NULL); + if(config.debug & DEBUG_API) + logg("API: Read sid=\"%s\" from %s", sid, sid_source); - for(unsigned int i = 0; i < API_MAX_CLIENTS; i++) + for(unsigned int i = 0; i < API_MAX_CLIENTS; i++) + { + if(auth_data[i].used && + auth_data[i].valid_until >= now && + strcmp(auth_data[i].remote_addr, api->request->remote_addr) == 0 && + strcmp(auth_data[i].sid, sid) == 0) { - if(auth_data[i].used && - auth_data[i].valid_until >= now && - strcmp(auth_data[i].remote_addr, request->remote_addr) == 0 && - strcmp(auth_data[i].sid, sid) == 0) - { - user_id = i; - break; - } + user_id = i; + break; } - if(user_id > API_AUTH_UNAUTHORIZED) + } + if(user_id > API_AUTH_UNAUTHORIZED) + { + // Authentication succesful: + // - We know this client + // - The session is (still) valid + // - The IP matches the one we know for this SID + + // Update timestamp of this client to extend + // the validity of their API authentication + auth_data[user_id].valid_until = now + httpsettings.session_timeout; + + // Update user cookie + if(snprintf(pi_hole_extra_headers, sizeof(pi_hole_extra_headers), + FTL_SET_COOKIE, + auth_data[user_id].sid, httpsettings.session_timeout) < 0) { - // Authentication succesful: - // - We know this client - // - The session is (still) valid - // - The IP matches the one we know for this SID - - // Update timestamp of this client to extend - // the validity of their API authentication - auth_data[user_id].valid_until = now + httpsettings.session_timeout; - - // Update user cookie - if(snprintf(pi_hole_extra_headers, sizeof(pi_hole_extra_headers), - FTL_SET_COOKIE, - auth_data[user_id].sid, httpsettings.session_timeout) < 0) - { - return send_json_error(conn, 500, "internal_error", "Internal server error", NULL); - } + return send_json_error(api, 500, "internal_error", "Internal server error", NULL); + } - if(config.debug & DEBUG_API) - { - char timestr[128]; - get_timestr(timestr, auth_data[user_id].valid_until); - logg("API: Recognized known user: user_id %i valid_until: %s remote_addr %s", - user_id, timestr, auth_data[user_id].remote_addr); - } + if(config.debug & DEBUG_API) + { + char timestr[128]; + get_timestr(timestr, auth_data[user_id].valid_until); + logg("API: Recognized known user: user_id %i valid_until: %s remote_addr %s", + user_id, timestr, auth_data[user_id].remote_addr); } - else if(config.debug & DEBUG_API) - logg("API Authentification: FAIL (SID invalid/expired)"); } else if(config.debug & DEBUG_API) - logg("API Authentification: FAIL (no SID provided)"); + logg("API Authentification: FAIL (SID invalid/expired)"); return user_id; } @@ -209,7 +207,7 @@ static bool check_response(const char *response, const time_t now) return false; } -static int get_session_object(struct mg_connection *conn, cJSON *json, const int user_id, const time_t now) +static int get_session_object(struct ftl_conn *api, cJSON *json, const int user_id, const time_t now) { // Authentication not needed if(user_id == API_AUTH_LOCALHOST || user_id == API_AUTH_EMPTYPASS) @@ -254,7 +252,7 @@ static void delete_session(const int user_id) memset(auth_data[user_id].remote_addr, 0, sizeof(auth_data[user_id].remote_addr)); } -static int send_api_auth_status(struct mg_connection *conn, const int user_id, const int method, const time_t now) +static int send_api_auth_status(struct ftl_conn *api, const int user_id, const time_t now) { if(user_id == API_AUTH_LOCALHOST) { @@ -262,7 +260,7 @@ static int send_api_auth_status(struct mg_connection *conn, const int user_id, c logg("API Auth status: OK (localhost does not need auth)"); cJSON *json = JSON_NEW_OBJ(); - get_session_object(conn, json, user_id, now); + get_session_object(api, json, user_id, now); JSON_SEND_OBJECT(json); } @@ -272,11 +270,11 @@ static int send_api_auth_status(struct mg_connection *conn, const int user_id, c logg("API Auth status: OK (empty password)"); cJSON *json = JSON_NEW_OBJ(); - get_session_object(conn, json, user_id, now); + get_session_object(api, json, user_id, now); JSON_SEND_OBJECT(json); } - if(user_id > API_AUTH_UNAUTHORIZED && (method == HTTP_GET || method == HTTP_POST)) + if(user_id > API_AUTH_UNAUTHORIZED && (api->method == HTTP_GET || api->method == HTTP_POST)) { if(config.debug & DEBUG_API) logg("API Auth status: OK"); @@ -286,14 +284,14 @@ static int send_api_auth_status(struct mg_connection *conn, const int user_id, c FTL_SET_COOKIE, auth_data[user_id].sid, API_SESSION_EXPIRE) < 0) { - return send_json_error(conn, 500, "internal_error", "Internal server error", NULL); + return send_json_error(api, 500, "internal_error", "Internal server error", NULL); } cJSON *json = JSON_NEW_OBJ(); - get_session_object(conn, json, user_id, now); + get_session_object(api, json, user_id, now); JSON_SEND_OBJECT(json); } - else if(user_id > API_AUTH_UNAUTHORIZED && method == HTTP_DELETE) + else if(user_id > API_AUTH_UNAUTHORIZED && api->method == HTTP_DELETE) { if(config.debug & DEBUG_API) logg("API Auth status: Logout, asking to delete cookie"); @@ -303,7 +301,7 @@ static int send_api_auth_status(struct mg_connection *conn, const int user_id, c strncpy(pi_hole_extra_headers, FTL_DELETE_COOKIE, sizeof(pi_hole_extra_headers)); cJSON *json = JSON_NEW_OBJ(); - get_session_object(conn, json, user_id, now); + get_session_object(api, json, user_id, now); JSON_SEND_OBJECT_CODE(json, 410); // 410 Gone } else @@ -313,7 +311,7 @@ static int send_api_auth_status(struct mg_connection *conn, const int user_id, c strncpy(pi_hole_extra_headers, FTL_DELETE_COOKIE, sizeof(pi_hole_extra_headers)); cJSON *json = JSON_NEW_OBJ(); - get_session_object(conn, json, user_id, now); + get_session_object(api, json, user_id, now); JSON_SEND_OBJECT_CODE(json, 401); // 401 Unauthorized } } @@ -378,40 +376,37 @@ static void generateSID(char *sid) // GET: Check authentication and obtain a challenge // POST: Login // DELETE: Logout -int api_auth(struct mg_connection *conn) +int api_auth(struct ftl_conn *api) { // Check HTTP method - const enum http_method method = http_method(conn); const time_t now = time(NULL); char *password_hash = get_password_hash(); const bool empty_password = (strlen(password_hash) == 0u); int user_id = API_AUTH_UNAUTHORIZED; - const struct mg_request_info *request = mg_get_request_info(conn); bool reponse_set = false; char response[256] = { 0 }; - char payload[MAX_PAYLOAD_BYTES]; // Did the client authenticate before and we can validate this? - user_id = check_client_auth(conn, payload); + user_id = check_client_auth(api); // If this is a valid session, we can exit early at this point if(user_id != API_AUTH_UNAUTHORIZED) - return send_api_auth_status(conn, user_id, method, now); + return send_api_auth_status(api, user_id, now); // Login attempt, extract response - if(method == HTTP_POST) + if(api->method == HTTP_POST) { // Try to extract response from payload int len = 0; - if((len = GET_VAR("response", response, payload)) != CHALLENGE_SIZE) + if((len = GET_VAR("response", response, api->payload.raw)) != CHALLENGE_SIZE) { const char *message = len < 0 ? "No response found" : "Invalid response length"; if(config.debug & DEBUG_API) logg("API auth error: %s", message); - return send_json_error(conn, 400, + return send_json_error(api, 400, "bad_request", message, NULL); @@ -420,11 +415,11 @@ int api_auth(struct mg_connection *conn) } // Logout attempt - if(method == HTTP_DELETE) + if(api->method == HTTP_DELETE) { if(config.debug & DEBUG_API) logg("API Auth: User with ID %i wants to log out", user_id); - return send_api_auth_status(conn, user_id, method, now); + return send_api_auth_status(api, user_id, now); } // Login attempt and/or auth check @@ -455,7 +450,7 @@ int api_auth(struct mg_connection *conn) { auth_data[i].used = true; auth_data[i].valid_until = now + httpsettings.session_timeout; - strncpy(auth_data[i].remote_addr, request->remote_addr, sizeof(auth_data[i].remote_addr)); + strncpy(auth_data[i].remote_addr, api->request->remote_addr, sizeof(auth_data[i].remote_addr)); auth_data[i].remote_addr[sizeof(auth_data[i].remote_addr)-1] = '\0'; generateSID(auth_data[i].sid); @@ -486,7 +481,7 @@ int api_auth(struct mg_connection *conn) // Free allocated memory free(password_hash); password_hash = NULL; - return send_api_auth_status(conn, user_id, method, now); + return send_api_auth_status(api, user_id, now); } else { @@ -534,7 +529,7 @@ int api_auth(struct mg_connection *conn) // Return to user cJSON *json = JSON_NEW_OBJ(); JSON_OBJ_REF_STR(json, "challenge", challenges[i].challenge); - get_session_object(conn, json, -1, now); + get_session_object(api, json, -1, now); JSON_SEND_OBJECT(json); } } diff --git a/src/api/dns.c b/src/api/dns.c index 173164702..745406914 100644 --- a/src/api/dns.c +++ b/src/api/dns.c @@ -18,7 +18,7 @@ // set_blockingmode_timer() #include "../timers.h" -static int get_blocking(struct mg_connection *conn) +static int get_blocking(struct ftl_conn *api) { // Return current status cJSON *json = JSON_NEW_OBJ(); @@ -45,35 +45,24 @@ static int get_blocking(struct mg_connection *conn) JSON_SEND_OBJECT(json); } -static int set_blocking(struct mg_connection *conn) +static int set_blocking(struct ftl_conn *api) { // Verify requesting client is allowed to access this ressource - if(check_client_auth(conn, NULL) == API_AUTH_UNAUTHORIZED) + if(check_client_auth(api) == API_AUTH_UNAUTHORIZED) { - return send_json_unauthorized(conn); + return send_json_unauthorized(api); } - char buffer[1024] = { 0 }; - const int data_len = mg_read(conn, buffer, sizeof(buffer) - 1); - if ((data_len < 1) || (data_len >= (int)sizeof(buffer))) { - return send_json_error(conn, 400, - "bad_request", "No request body data", - NULL); - } - buffer[data_len] = '\0'; - - cJSON *obj = cJSON_Parse(buffer); - if (obj == NULL) { - return send_json_error(conn, 400, + if (api->payload.json == NULL) { + return send_json_error(api, 400, "bad_request", "Invalid request body data", NULL); } - cJSON *elem = cJSON_GetObjectItemCaseSensitive(obj, "blocking"); + cJSON *elem = cJSON_GetObjectItemCaseSensitive(api->payload.json, "blocking"); if (!cJSON_IsBool(elem)) { - cJSON_Delete(obj); - return send_json_error(conn, 400, + return send_json_error(api, 400, "bad_request", "No \"blocking\" boolean in body data", NULL); @@ -82,13 +71,10 @@ static int set_blocking(struct mg_connection *conn) // Get (optional) delay int delay = -1; - elem = cJSON_GetObjectItemCaseSensitive(obj, "delay"); + elem = cJSON_GetObjectItemCaseSensitive(api->payload.json, "delay"); if (cJSON_IsNumber(elem) && elem->valuedouble > 0.0) delay = elem->valueint; - // Free memory not needed any longer - cJSON_Delete(obj); - if(target_status == get_blockingstatus()) { // The blocking status does not need to be changed @@ -107,19 +93,18 @@ static int set_blocking(struct mg_connection *conn) // Return GET property as result of POST/PUT/PATCH action // if no error happened above - return get_blocking(conn); + return get_blocking(api); } -int api_dns_blockingstatus(struct mg_connection *conn) +int api_dns_blockingstatus(struct ftl_conn *api) { - int method = http_method(conn); - if(method == HTTP_GET) + if(api->method == HTTP_GET) { - return get_blocking(conn); + return get_blocking(api); } - else if(method == HTTP_PUT) + else if(api->method == HTTP_PUT) { - return set_blocking(conn); + return set_blocking(api); } else { @@ -128,12 +113,12 @@ int api_dns_blockingstatus(struct mg_connection *conn) } } -int api_dns_cacheinfo(struct mg_connection *conn) +int api_dns_cacheinfo(struct ftl_conn *api) { // Verify requesting client is allowed to access this ressource - if(check_client_auth(conn, NULL) == API_AUTH_UNAUTHORIZED) + if(check_client_auth(api) == API_AUTH_UNAUTHORIZED) { - return send_json_unauthorized(conn); + return send_json_unauthorized(api); } cacheinforecord cacheinfo; diff --git a/src/api/ftl.c b/src/api/ftl.c index 0782586b4..5ff613528 100644 --- a/src/api/ftl.c +++ b/src/api/ftl.c @@ -29,28 +29,26 @@ // counters #include "../shmem.h" -int api_ftl_client(struct mg_connection *conn) +int api_ftl_client(struct ftl_conn *api) { cJSON *json = JSON_NEW_OBJ(); - const struct mg_request_info *request = mg_get_request_info(conn); - // Add client's IP address - JSON_OBJ_REF_STR(json, "remote_addr", request->remote_addr); + JSON_OBJ_REF_STR(json, "remote_addr", api->request->remote_addr); // Add HTTP version - JSON_OBJ_REF_STR(json, "http_version", request->http_version); + JSON_OBJ_REF_STR(json, "http_version", api->request->http_version); // Add request method - JSON_OBJ_REF_STR(json, "method", request->request_method); + JSON_OBJ_REF_STR(json, "method", api->request->request_method); // Add HTTP headers cJSON *headers = JSON_NEW_ARRAY(); - for(int i = 0; i < request->num_headers; i++) + for(int i = 0; i < api->request->num_headers; i++) { // Add headers cJSON *header = JSON_NEW_OBJ(); - JSON_OBJ_REF_STR(header, "name", request->http_headers[i].name); - JSON_OBJ_REF_STR(header, "value", request->http_headers[i].value); + JSON_OBJ_REF_STR(header, "name", api->request->http_headers[i].name); + JSON_OBJ_REF_STR(header, "value", api->request->http_headers[i].value); JSON_ARRAY_ADD_ITEM(headers, header); } JSON_OBJ_ADD_ITEM(json, "headers", headers); @@ -60,21 +58,20 @@ int api_ftl_client(struct mg_connection *conn) // fifologData is allocated in shared memory for cross-fork compatibility fifologData *fifo_log = NULL; -int api_ftl_dnsmasq_log(struct mg_connection *conn) +int api_ftl_dnsmasq_log(struct ftl_conn *api) { // Verify requesting client is allowed to see this ressource - if(check_client_auth(conn, NULL) == API_AUTH_UNAUTHORIZED) + if(check_client_auth(api) == API_AUTH_UNAUTHORIZED) { - return send_json_unauthorized(conn); + return send_json_unauthorized(api); } unsigned int start = 0u; - const struct mg_request_info *request = mg_get_request_info(conn); - if(request->query_string != NULL) + if(api->request->query_string != NULL) { // Does the user request an ID to sent from? unsigned int nextID; - if(get_uint_var(request->query_string, "nextID", &nextID)) + if(get_uint_var(api->request->query_string, "nextID", &nextID)) { if(nextID >= fifo_log->next_id) { @@ -125,12 +122,12 @@ int api_ftl_dnsmasq_log(struct mg_connection *conn) JSON_SEND_OBJECT(json); } -int api_ftl_database(struct mg_connection *conn) +int api_ftl_database(struct ftl_conn *api) { // Verify requesting client is allowed to see this ressource - if(check_client_auth(conn, NULL) == API_AUTH_UNAUTHORIZED) + if(check_client_auth(api) == API_AUTH_UNAUTHORIZED) { - send_json_unauthorized(conn); + send_json_unauthorized(api); } cJSON *json = JSON_NEW_OBJ(); @@ -200,7 +197,7 @@ int api_ftl_database(struct mg_connection *conn) JSON_SEND_OBJECT(json); } -static int read_temp_sensor(struct mg_connection *conn, +static int read_temp_sensor(struct ftl_conn *api, const char *label_path, const char *value_path, const char *fallback_label, @@ -276,12 +273,12 @@ static bool GetRamInKB(long *mem_total, long *mem_used, long *mem_free, long *me return true; } -int get_system_obj(struct mg_connection *conn, cJSON *system) +int get_system_obj(struct ftl_conn *api, cJSON *system) { const int nprocs = get_nprocs(); struct sysinfo info; if(sysinfo(&info) != 0) - return send_json_error(conn, 500, "error", strerror(errno), NULL); + return send_json_error(api, 500, "error", strerror(errno), NULL); // Seconds since boot JSON_OBJ_ADD_NUMBER(system, "uptime", info.uptime); @@ -362,7 +359,7 @@ int get_system_obj(struct mg_connection *conn, cJSON *system) sprintf(label_path, "/sys/class/thermal/thermal_zone%d/type", i); sprintf(value_path, "/sys/class/thermal/thermal_zone%d/temp", i); sprintf(fallback_label, "thermal_zone%d/temp", i); - ret = read_temp_sensor(conn, label_path, value_path, fallback_label, sensors); + ret = read_temp_sensor(api, label_path, value_path, fallback_label, sensors); // Error handling if(ret != 0) return ret; @@ -371,7 +368,7 @@ int get_system_obj(struct mg_connection *conn, cJSON *system) sprintf(label_path, "/sys/class/hwmon/hwmon0/temp%d_label", i); sprintf(value_path, "/sys/class/hwmon/hwmon0/temp%d_input", i); sprintf(fallback_label, "hwmon0/temp%d", i); - ret = read_temp_sensor(conn, label_path, value_path, fallback_label, sensors); + ret = read_temp_sensor(api, label_path, value_path, fallback_label, sensors); // Error handling if(ret != 0) return ret; @@ -387,13 +384,13 @@ int get_system_obj(struct mg_connection *conn, cJSON *system) return 0; } -int api_ftl_system(struct mg_connection *conn) +int api_ftl_system(struct ftl_conn *api) { cJSON *json = JSON_NEW_OBJ(); cJSON *system = JSON_NEW_OBJ(); // Get system object - const int ret = get_system_obj(conn, system); + const int ret = get_system_obj(api, system); if (ret != 0) return ret; diff --git a/src/api/list.c b/src/api/list.c index 80d0ef7f0..c86531105 100644 --- a/src/api/list.c +++ b/src/api/list.c @@ -14,7 +14,7 @@ #include "routes.h" #include "../database/gravity-db.h" -static int api_list_read(struct mg_connection *conn, +static int api_list_read(struct ftl_conn *api, const int code, const enum gravity_list_type listtype, const char *argument) @@ -34,7 +34,7 @@ static int api_list_read(struct mg_connection *conn, JSON_OBJ_ADD_NULL(json, "sql_msg"); } - return send_json_error(conn, 400, // 400 Bad Request + return send_json_error(api, 400, // 400 Bad Request "database_error", "Could not read domains from database table", json); @@ -46,7 +46,6 @@ static int api_list_read(struct mg_connection *conn, { cJSON *item = JSON_NEW_OBJ(); JSON_OBJ_ADD_NUMBER(item, "id", row.id); - JSON_OBJ_ADD_BOOL(item, "enabled", row.enabled); // Special fields if(listtype == GRAVITY_GROUPS) @@ -69,8 +68,8 @@ static int api_list_read(struct mg_connection *conn, } else // domainlists { - JSON_OBJ_REF_STR(item, "type", row.type); JSON_OBJ_COPY_STR(item, "domain", row.domain); + JSON_OBJ_REF_STR(item, "type", row.type); if(row.comment != NULL) { JSON_OBJ_COPY_STR(item, "comment", row.comment); } else { @@ -87,14 +86,15 @@ static int api_list_read(struct mg_connection *conn, group_ids_str[sizeof(group_ids_str)-2u] = ']'; group_ids_str[sizeof(group_ids_str)-1u] = '\0'; cJSON * group_ids = cJSON_Parse(group_ids_str); - JSON_OBJ_ADD_ITEM(item, "group_ids", group_ids); + JSON_OBJ_ADD_ITEM(item, "groups", group_ids); } else { // Empty group set cJSON *group_ids = JSON_NEW_ARRAY(); - JSON_OBJ_ADD_ITEM(item, "group_ids", group_ids); + JSON_OBJ_ADD_ITEM(item, "groups", group_ids); } } - + + JSON_OBJ_ADD_BOOL(item, "enabled", row.enabled); JSON_OBJ_ADD_NUMBER(item, "date_added", row.date_added); JSON_OBJ_ADD_NUMBER(item, "date_modified", row.date_modified); @@ -131,16 +131,15 @@ static int api_list_read(struct mg_connection *conn, JSON_OBJ_ADD_NULL(json, "sql_msg"); } - return send_json_error(conn, 400, // 400 Bad Request + return send_json_error(api, 400, // 400 Bad Request "database_error", "Could not read from gravity database", json); } } -static int api_list_write(struct mg_connection *conn, +static int api_list_write(struct ftl_conn *api, const enum gravity_list_type listtype, - const enum http_method method, const char *argument, char payload[MAX_PAYLOAD_BYTES]) { @@ -149,44 +148,36 @@ static int api_list_write(struct mg_connection *conn, // Set argument row.argument = argument; - // Extract payload - cJSON *obj = cJSON_Parse(payload); - if (obj == NULL) { - return send_json_error(conn, 400, + // Check if valid JSON payload is available + if (api->payload.json == NULL) { + return send_json_error(api, 400, "bad_request", "Invalid request body data", NULL); } - cJSON *json_enabled = cJSON_GetObjectItemCaseSensitive(obj, "enabled"); + cJSON *json_enabled = cJSON_GetObjectItemCaseSensitive(api->payload.json, "enabled"); if (!cJSON_IsBool(json_enabled)) { - cJSON_Delete(obj); - return send_json_error(conn, 400, + return send_json_error(api, 400, "bad_request", "No \"enabled\" boolean in body data", NULL); } row.enabled = cJSON_IsTrue(json_enabled); - cJSON *json_comment = cJSON_GetObjectItemCaseSensitive(obj, "comment"); + cJSON *json_comment = cJSON_GetObjectItemCaseSensitive(api->payload.json, "comment"); if(cJSON_IsString(json_comment) && strlen(json_comment->valuestring) > 0) row.comment = json_comment->valuestring; else row.comment = NULL; - cJSON *json_description = cJSON_GetObjectItemCaseSensitive(obj, "description"); + cJSON *json_description = cJSON_GetObjectItemCaseSensitive(api->payload.json, "description"); if(cJSON_IsString(json_description) && strlen(json_description->valuestring) > 0) row.description = json_description->valuestring; else row.description = NULL; - cJSON *json_name = cJSON_GetObjectItemCaseSensitive(obj, "name"); - if(cJSON_IsString(json_name) && strlen(json_name->valuestring) > 0) - row.name = json_name->valuestring; - else - row.name = NULL; - - cJSON *json_oldtype = cJSON_GetObjectItemCaseSensitive(obj, "oldtype"); + cJSON *json_oldtype = cJSON_GetObjectItemCaseSensitive(api->payload.json, "oldtype"); if(cJSON_IsString(json_oldtype) && strlen(json_oldtype->valuestring) > 0) row.oldtype = json_oldtype->valuestring; else @@ -195,9 +186,9 @@ static int api_list_write(struct mg_connection *conn, // Try to add domain to table const char *sql_msg = NULL; bool okay = false; - if(gravityDB_addToTable(listtype, &row, &sql_msg, method)) + if(gravityDB_addToTable(listtype, &row, &sql_msg, api->method)) { - cJSON *groups = cJSON_GetObjectItemCaseSensitive(obj, "groups"); + cJSON *groups = cJSON_GetObjectItemCaseSensitive(api->payload.json, "groups"); if(groups != NULL) okay = gravityDB_edit_groups(listtype, groups, &row, &sql_msg); else @@ -227,25 +218,22 @@ static int api_list_write(struct mg_connection *conn, JSON_OBJ_ADD_NULL(json, "sql_msg"); } - // Free memory not needed any longer - cJSON_Delete(obj); - // Send error reply - return send_json_error(conn, 400, // 400 Bad Request + return send_json_error(api, 400, // 400 Bad Request "database_error", "Could not add to gravity database", json); } // else: everything is okay - // Free memory not needed any longer - cJSON_Delete(obj); - - // Send GET style reply with code 201 Created - return api_list_read(conn, 201, listtype, argument); + int response_code = 201; // 201 - Created + if(api->method == HTTP_PUT) + response_code = 200; // 200 - OK + // Send GET style reply + return api_list_read(api, response_code, listtype, argument); } -static int api_list_remove(struct mg_connection *conn, +static int api_list_remove(struct ftl_conn *api, const enum gravity_list_type listtype, const char *argument) { @@ -269,49 +257,48 @@ static int api_list_remove(struct mg_connection *conn, } // Send error reply - return send_json_error(conn, 400, + return send_json_error(api, 400, "database_error", "Could not remove domain from database table", json); } } -int api_list(struct mg_connection *conn) +int api_list(struct ftl_conn *api) { // Verify requesting client is allowed to see this ressource char payload[MAX_PAYLOAD_BYTES] = { 0 }; - if(check_client_auth(conn, payload) == API_AUTH_UNAUTHORIZED) + if(check_client_auth(api) == API_AUTH_UNAUTHORIZED) { - return send_json_unauthorized(conn); + return send_json_unauthorized(api); } enum gravity_list_type listtype; bool can_modify = false; - const struct mg_request_info *request = mg_get_request_info(conn); const char *argument = NULL; - if((argument = startsWith("/api/groups", request->local_uri)) != NULL) + if((argument = startsWith("/api/groups", api)) != NULL) { listtype = GRAVITY_GROUPS; can_modify = true; } - else if((argument = startsWith("/api/adlists", request->local_uri)) != NULL) + else if((argument = startsWith("/api/adlists", api)) != NULL) { listtype = GRAVITY_ADLISTS; can_modify = true; } - else if((argument = startsWith("/api/clients", request->local_uri)) != NULL) + else if((argument = startsWith("/api/clients", api)) != NULL) { listtype = GRAVITY_CLIENTS; can_modify = true; } - else if((argument = startsWith("/api/domains/allow", request->local_uri)) != NULL) + else if((argument = startsWith("/api/domains/allow", api)) != NULL) { - if((argument = startsWith("/api/domains/allow/exact", request->local_uri)) != NULL) + if((argument = startsWith("/api/domains/allow/exact", api)) != NULL) { listtype = GRAVITY_DOMAINLIST_ALLOW_EXACT; can_modify = true; } - else if((argument = startsWith("/api/domains/allow/regex", request->local_uri)) != NULL) + else if((argument = startsWith("/api/domains/allow/regex", api)) != NULL) { listtype = GRAVITY_DOMAINLIST_ALLOW_REGEX; can_modify = true; @@ -319,14 +306,14 @@ int api_list(struct mg_connection *conn) else listtype = GRAVITY_DOMAINLIST_ALLOW_ALL; } - else if((argument = startsWith("/api/domains/deny", request->local_uri)) != NULL) + else if((argument = startsWith("/api/domains/deny", api)) != NULL) { - if((argument = startsWith("/api/domains/deny/exact", request->local_uri)) != NULL) + if((argument = startsWith("/api/domains/deny/exact", api)) != NULL) { listtype = GRAVITY_DOMAINLIST_DENY_EXACT; can_modify = true; } - else if((argument = startsWith("/api/domains/deny/regex", request->local_uri)) != NULL) + else if((argument = startsWith("/api/domains/deny/regex", api)) != NULL) { listtype = GRAVITY_DOMAINLIST_DENY_REGEX; can_modify = true; @@ -336,36 +323,35 @@ int api_list(struct mg_connection *conn) } else { - if((argument = startsWith("/api/domains/exact", request->local_uri)) != NULL) + if((argument = startsWith("/api/domains/exact", api)) != NULL) listtype = GRAVITY_DOMAINLIST_ALL_EXACT; - else if((argument = startsWith("/api/domains/regex", request->local_uri)) != NULL) + else if((argument = startsWith("/api/domains/regex", api)) != NULL) listtype = GRAVITY_DOMAINLIST_ALL_REGEX; else { - argument = startsWith("/api/domains", request->local_uri); + argument = startsWith("/api/domains", api); listtype = GRAVITY_DOMAINLIST_ALL_ALL; } } - const enum http_method method = http_method(conn); - if(method == HTTP_GET) + if(api->method == HTTP_GET) { - return api_list_read(conn, 200, listtype, argument); + return api_list_read(api, 200, listtype, argument); } - else if(can_modify && (method == HTTP_POST || method == HTTP_PUT)) + else if(can_modify && (api->method == HTTP_POST || api->method == HTTP_PUT)) { // Add item from list - return api_list_write(conn, listtype, method, argument, payload); + return api_list_write(api, listtype, argument, payload); } - else if(can_modify && method == HTTP_DELETE) + else if(can_modify && api->method == HTTP_DELETE) { // Delete item from list - return api_list_remove(conn, listtype, argument); + return api_list_remove(api, listtype, argument); } else if(!can_modify) { // This list type cannot be modified (e.g., ALL_ALL) - return send_json_error(conn, 400, + return send_json_error(api, 400, "bad_request", "Invalid request: Specify list to modify", NULL); diff --git a/src/api/network.c b/src/api/network.c index fc99208e6..a169b651d 100644 --- a/src/api/network.c +++ b/src/api/network.c @@ -15,15 +15,15 @@ // networkrecord #include "../database/network-table.h" -int api_network(struct mg_connection *conn) +int api_network(struct ftl_conn *api) { // Verify requesting client is allowed to see this ressource - if(check_client_auth(conn, NULL) == API_AUTH_UNAUTHORIZED) + if(check_client_auth(api) == API_AUTH_UNAUTHORIZED) { - return send_json_unauthorized(conn); + return send_json_unauthorized(api); } - // Connect to database + // apiect to database const char *sql_msg = NULL; if(!networkTable_readDevices(&sql_msg)) { @@ -34,7 +34,7 @@ int api_network(struct mg_connection *conn) } else { JSON_OBJ_ADD_NULL(json, "sql_msg"); } - return send_json_error(conn, 500, + return send_json_error(api, 500, "database_error", "Could not read network details from database table", json); @@ -73,7 +73,7 @@ int api_network(struct mg_connection *conn) JSON_OBJ_ADD_ITEM(json, "last_item", item); // Add SQL message JSON_OBJ_REF_STR(json, "sql_msg", sql_msg); - return send_json_error(conn, 500, + return send_json_error(api, 500, "database_error", "Could not read network details from database table (getting IP records)", json); @@ -96,7 +96,7 @@ int api_network(struct mg_connection *conn) json = JSON_NEW_OBJ(); // Add SQL message JSON_OBJ_REF_STR(json, "sql_msg", sql_msg); - return send_json_error(conn, 500, + return send_json_error(api, 500, "database_error", "Could not read network details from database table (step)", json); diff --git a/src/api/routes.c b/src/api/routes.c index 14a3bbd7e..5fa627d5f 100644 --- a/src/api/routes.c +++ b/src/api/routes.c @@ -24,161 +24,177 @@ int api_handler(struct mg_connection *conn, void *ignored) int ret = 0; - const struct mg_request_info *request = mg_get_request_info(conn); + // Prepare API info struct + struct ftl_conn api = { + conn, + mg_get_request_info(conn), + http_method(conn), + { 0 } + }; + read_and_parse_payload(&api); + if(config.debug & DEBUG_API) - logg("Requested API URI: %s %s", request->request_method, request->local_uri); + logg("Requested API URI: %s %s", api.request->request_method, api.request->local_uri); /******************************** /api/dns ********************************/ - if(startsWith("/api/dns/blocking", request->local_uri)) + if(startsWith("/api/dns/blocking", &api)) { - ret = api_dns_blockingstatus(conn); + ret = api_dns_blockingstatus(&api); } - else if(startsWith("/api/dns/cacheinfo", request->local_uri)) + else if(startsWith("/api/dns/cacheinfo", &api)) { - ret = api_dns_cacheinfo(conn); + ret = api_dns_cacheinfo(&api); } /*********** /api/domains, /api/groups, /api/adlists, /api/clients *******/ - else if(startsWith("/api/domains", request->local_uri)) + else if(startsWith("/api/domains", &api)) { - ret = api_list(conn); + ret = api_list(&api); } - else if(startsWith("/api/groups", request->local_uri)) + else if(startsWith("/api/groups", &api)) { - ret = api_list(conn); + ret = api_list(&api); } - else if(startsWith("/api/adlists", request->local_uri)) + else if(startsWith("/api/adlists", &api)) { - ret = api_list(conn); + ret = api_list(&api); } - else if(startsWith("/api/clients", request->local_uri)) + else if(startsWith("/api/clients", &api)) { - ret = api_list(conn); + ret = api_list(&api); } /******************************** /api/ftl ****************************/ - else if(startsWith("/api/ftl/client", request->local_uri)) + else if(startsWith("/api/ftl/client", &api)) { - ret = api_ftl_client(conn); + ret = api_ftl_client(&api); } - else if(startsWith("/api/ftl/dnsmasq_log", request->local_uri)) + else if(startsWith("/api/ftl/dnsmasq_log", &api)) { - ret = api_ftl_dnsmasq_log(conn); + ret = api_ftl_dnsmasq_log(&api); } - else if(startsWith("/api/ftl/database", request->local_uri)) + else if(startsWith("/api/ftl/database", &api)) { - ret = api_ftl_database(conn); + ret = api_ftl_database(&api); } - else if(startsWith("/api/ftl/system", request->local_uri)) + else if(startsWith("/api/ftl/system", &api)) { - ret = api_ftl_system(conn); + ret = api_ftl_system(&api); } /******************************** /api/network ****************************/ - else if(startsWith("/api/network", request->local_uri)) + else if(startsWith("/api/network", &api)) { - ret = api_network(conn); + ret = api_network(&api); } /******************************** /api/stats **************************/ - else if(startsWith("/api/stats/summary", request->local_uri)) + else if(startsWith("/api/stats/summary", &api)) { - ret = api_stats_summary(conn); + ret = api_stats_summary(&api); } - else if(startsWith("/api/stats/overTime/history", request->local_uri)) + else if(startsWith("/api/stats/overTime/history", &api)) { - ret = api_stats_overTime_history(conn); + ret = api_stats_overTime_history(&api); } - else if(startsWith("/api/stats/overTime/clients", request->local_uri)) + else if(startsWith("/api/stats/overTime/clients", &api)) { - ret = api_stats_overTime_clients(conn); + ret = api_stats_overTime_clients(&api); } - else if(startsWith("/api/stats/query_types", request->local_uri)) + else if(startsWith("/api/stats/query_types", &api)) { - ret = api_stats_query_types(conn); + ret = api_stats_query_types(&api); } - else if(startsWith("/api/stats/upstreams", request->local_uri)) + else if(startsWith("/api/stats/upstreams", &api)) { - ret = api_stats_upstreams(conn); + ret = api_stats_upstreams(&api); } - else if(startsWith("/api/stats/top_domains", request->local_uri)) + else if(startsWith("/api/stats/top_domains", &api)) { - ret = api_stats_top_domains(false, conn); + ret = api_stats_top_domains(false, &api); } - else if(startsWith("/api/stats/top_blocked", request->local_uri)) + else if(startsWith("/api/stats/top_blocked", &api)) { - ret = api_stats_top_domains(true, conn); + ret = api_stats_top_domains(true, &api); } - else if(startsWith("/api/stats/top_clients", request->local_uri)) + else if(startsWith("/api/stats/top_clients", &api)) { - ret = api_stats_top_clients(false, conn); + ret = api_stats_top_clients(false, &api); } - else if(startsWith("/api/stats/top_blocked_clients", request->local_uri)) + else if(startsWith("/api/stats/top_blocked_clients", &api)) { - ret = api_stats_top_clients(true, conn); + ret = api_stats_top_clients(true, &api); } - else if(startsWith("/api/stats/history", request->local_uri)) + else if(startsWith("/api/stats/history", &api)) { - ret = api_stats_history(conn); + ret = api_stats_history(&api); } - else if(startsWith("/api/stats/recent_blocked", request->local_uri)) + else if(startsWith("/api/stats/recent_blocked", &api)) { - ret = api_stats_recentblocked(conn); + ret = api_stats_recentblocked(&api); } - else if(startsWith("/api/stats/database/overTime/history", request->local_uri)) + else if(startsWith("/api/stats/database/overTime/history", &api)) { - ret = api_stats_database_overTime_history(conn); + ret = api_stats_database_overTime_history(&api); } - else if(startsWith("/api/stats/database/top_domains", request->local_uri)) + else if(startsWith("/api/stats/database/top_domains", &api)) { - ret = api_stats_database_top_items(false, true, conn); + ret = api_stats_database_top_items(false, true, &api); } - else if(startsWith("/api/stats/database/top_blocked", request->local_uri)) + else if(startsWith("/api/stats/database/top_blocked", &api)) { - ret = api_stats_database_top_items(true, true, conn); + ret = api_stats_database_top_items(true, true, &api); } - else if(startsWith("/api/stats/database/top_clients", request->local_uri)) + else if(startsWith("/api/stats/database/top_clients", &api)) { - ret = api_stats_database_top_items(false, false, conn); + ret = api_stats_database_top_items(false, false, &api); } - else if(startsWith("/api/stats/database/summary", request->local_uri)) + else if(startsWith("/api/stats/database/summary", &api)) { - ret = api_stats_database_summary(conn); + ret = api_stats_database_summary(&api); } - else if(startsWith("/api/stats/database/overTime/clients", request->local_uri)) + else if(startsWith("/api/stats/database/overTime/clients", &api)) { - ret = api_stats_database_overTime_clients(conn); + ret = api_stats_database_overTime_clients(&api); } - else if(startsWith("/api/stats/database/query_types", request->local_uri)) + else if(startsWith("/api/stats/database/query_types", &api)) { - ret = api_stats_database_query_types(conn); + ret = api_stats_database_query_types(&api); } - else if(startsWith("/api/stats/database/upstreams", request->local_uri)) + else if(startsWith("/api/stats/database/upstreams", &api)) { - ret = api_stats_database_upstreams(conn); + ret = api_stats_database_upstreams(&api); } /******************************** /api/version ****************************/ - else if(startsWith("/api/version", request->local_uri)) + else if(startsWith("/api/version", &api)) { - ret = api_version(conn); + ret = api_version(&api); } /******************************** /api/auth ****************************/ - else if(startsWith("/api/auth", request->local_uri)) + else if(startsWith("/api/auth", &api)) { - ret = api_auth(conn); + ret = api_auth(&api); } /******************************** /api/settings ****************************/ - else if(startsWith("/api/settings/web", request->local_uri)) + else if(startsWith("/api/settings/web", &api)) { - ret = api_settings_web(conn); + ret = api_settings_web(&api); } /******************************** not found or invalid request**************/ if(ret == 0) { cJSON *json = JSON_NEW_OBJ(); - JSON_OBJ_REF_STR(json, "path", request->local_uri); - ret = send_json_error(conn, 404, + cJSON *string_item = cJSON_CreateStringReference((const char*)api.request->local_uri); + cJSON_AddItemToObject(json, "path", string_item); + ret = send_json_error(&api, 404, "not_found", "Not found", json); } + // Free JSON-parsed payload memory (if allocated) + if(api.payload.json != NULL) + { + cJSON_Delete(api.payload.json); + api.payload.json = NULL; + } + // Unlock after API access unlock_shm(); diff --git a/src/api/routes.h b/src/api/routes.h index 7a91d65a0..23ac77248 100644 --- a/src/api/routes.h +++ b/src/api/routes.h @@ -19,51 +19,52 @@ // API router int api_handler(struct mg_connection *conn, void *ignored); + // Statistic methods -int api_stats_summary(struct mg_connection *conn); -int api_stats_overTime_history(struct mg_connection *conn); -int api_stats_overTime_clients(struct mg_connection *conn); -int api_stats_query_types(struct mg_connection *conn); -int api_stats_upstreams(struct mg_connection *conn); -int api_stats_top_domains(bool blocked, struct mg_connection *conn); -int api_stats_top_clients(bool blocked, struct mg_connection *conn); -int api_stats_history(struct mg_connection *conn); -int api_stats_recentblocked(struct mg_connection *conn); +int api_stats_summary(struct ftl_conn *api); +int api_stats_overTime_history(struct ftl_conn *api); +int api_stats_overTime_clients(struct ftl_conn *api); +int api_stats_query_types(struct ftl_conn *api); +int api_stats_upstreams(struct ftl_conn *api); +int api_stats_top_domains(bool blocked, struct ftl_conn *api); +int api_stats_top_clients(bool blocked, struct ftl_conn *api); +int api_stats_history(struct ftl_conn *api); +int api_stats_recentblocked(struct ftl_conn *api); // Statistics methods (database) -int api_stats_database_overTime_history(struct mg_connection *conn); -int api_stats_database_top_items(bool blocked, bool domains, struct mg_connection *conn); -int api_stats_database_summary(struct mg_connection *conn); -int api_stats_database_overTime_clients(struct mg_connection *conn); -int api_stats_database_query_types(struct mg_connection *conn); -int api_stats_database_upstreams(struct mg_connection *conn); +int api_stats_database_overTime_history(struct ftl_conn *api); +int api_stats_database_top_items(bool blocked, bool domains, struct ftl_conn *api); +int api_stats_database_summary(struct ftl_conn *api); +int api_stats_database_overTime_clients(struct ftl_conn *api); +int api_stats_database_query_types(struct ftl_conn *api); +int api_stats_database_upstreams(struct ftl_conn *api); // FTL methods -int api_ftl_client(struct mg_connection *conn); -int api_ftl_dnsmasq_log(struct mg_connection *conn); -int api_ftl_database(struct mg_connection *conn); -int api_ftl_system(struct mg_connection *conn); -int get_system_obj(struct mg_connection *conn, cJSON *system); +int api_ftl_client(struct ftl_conn *api); +int api_ftl_dnsmasq_log(struct ftl_conn *api); +int api_ftl_database(struct ftl_conn *api); +int api_ftl_system(struct ftl_conn *api); +int get_system_obj(struct ftl_conn *api, cJSON *system); // Network methods -int api_network(struct mg_connection *conn); +int api_network(struct ftl_conn *api); // DNS methods -int api_dns_blockingstatus(struct mg_connection *conn); -int api_dns_cacheinfo(struct mg_connection *conn); +int api_dns_blockingstatus(struct ftl_conn *api); +int api_dns_cacheinfo(struct ftl_conn *api); // List methods -int api_list(struct mg_connection *conn); -int api_group(struct mg_connection *conn); +int api_list(struct ftl_conn *api); +int api_group(struct ftl_conn *api); // Version method -int api_version(struct mg_connection *conn); +int api_version(struct ftl_conn *api); // Auth method -int check_client_auth(struct mg_connection *conn, char payload[MAX_PAYLOAD_BYTES]); -int api_auth(struct mg_connection *conn); +int check_client_auth(struct ftl_conn *api); +int api_auth(struct ftl_conn *api); // Settings methods -int api_settings_web(struct mg_connection *conn); +int api_settings_web(struct ftl_conn *api); #endif // ROUTES_H diff --git a/src/api/settings.c b/src/api/settings.c index cac6525be..3932669d3 100644 --- a/src/api/settings.c +++ b/src/api/settings.c @@ -13,7 +13,7 @@ #include "../webserver/json_macros.h" #include "routes.h" -int api_settings_web(struct mg_connection *conn) +int api_settings_web(struct ftl_conn *api) { cJSON *json = JSON_NEW_OBJ(); JSON_OBJ_REF_STR(json, "layout", "boxed"); diff --git a/src/api/stats.c b/src/api/stats.c index 8f22f6540..3041d7331 100644 --- a/src/api/stats.c +++ b/src/api/stats.c @@ -59,7 +59,7 @@ static int __attribute__((pure)) cmpdesc(const void *a, const void *b) return 0; } -int api_stats_summary(struct mg_connection *conn) +int api_stats_summary(struct ftl_conn *api) { const int blocked = counters->blocked; const int total = counters->queries; @@ -118,14 +118,14 @@ int api_stats_summary(struct mg_connection *conn) JSON_OBJ_ADD_ITEM(json, "reply_types", reply_types); cJSON *system = JSON_NEW_OBJ(); - const int ret = get_system_obj(conn, system); + const int ret = get_system_obj(api, system); if(ret != 0) return ret; JSON_OBJ_ADD_ITEM(json, "system", system); JSON_SEND_OBJECT(json); } -int api_stats_overTime_history(struct mg_connection *conn) +int api_stats_overTime_history(struct ftl_conn *api) { int from = 0, until = OVERTIME_SLOTS; bool found = false; @@ -179,15 +179,15 @@ int api_stats_overTime_history(struct mg_connection *conn) JSON_SEND_OBJECT(json); } -int api_stats_top_domains(bool blocked, struct mg_connection *conn) +int api_stats_top_domains(bool blocked, struct ftl_conn *api) { int temparray[counters->domains][2], show = 10; bool audit = false; // Verify requesting client is allowed to see this ressource - if(check_client_auth(conn, NULL) == API_AUTH_UNAUTHORIZED) + if(check_client_auth(api) == API_AUTH_UNAUTHORIZED) { - return send_json_unauthorized(conn); + return send_json_unauthorized(api); } // Exit before processing any data if requested via config setting @@ -207,18 +207,17 @@ int api_stats_top_domains(bool blocked, struct mg_connection *conn) } // /api/stats/top_domains?blocked=true is allowed as well - const struct mg_request_info *request = mg_get_request_info(conn); - if(request->query_string != NULL) + if(api->request->query_string != NULL) { // Should blocked clients be shown? - get_bool_var(request->query_string, "blocked", &blocked); + get_bool_var(api->request->query_string, "blocked", &blocked); // Does the user request a non-default number of replies? // Note: We do not accept zero query requests here - get_int_var(request->query_string, "show", &show); + get_int_var(api->request->query_string, "show", &show); // Apply Audit Log filtering? - get_bool_var(request->query_string, "audit", &audit); + get_bool_var(api->request->query_string, "audit", &audit); } for(int domainID=0; domainID < counters->domains; domainID++) @@ -337,15 +336,15 @@ int api_stats_top_domains(bool blocked, struct mg_connection *conn) JSON_SEND_OBJECT(json); } -int api_stats_top_clients(bool blocked, struct mg_connection *conn) +int api_stats_top_clients(bool blocked, struct ftl_conn *api) { int temparray[counters->clients][2], show = 10; bool includezeroclients = false; // Verify requesting client is allowed to see this ressource - if(check_client_auth(conn, NULL) == API_AUTH_UNAUTHORIZED) + if(check_client_auth(api) == API_AUTH_UNAUTHORIZED) { - return send_json_unauthorized(conn); + return send_json_unauthorized(api); } // Exit before processing any data if requested via config setting @@ -365,18 +364,17 @@ int api_stats_top_clients(bool blocked, struct mg_connection *conn) } // /api/stats/top_clients9?blocked=true is allowed as well - const struct mg_request_info *request = mg_get_request_info(conn); - if(request->query_string != NULL) + if(api->request->query_string != NULL) { // Should blocked clients be shown? - get_bool_var(request->query_string, "blocked", &blocked); + get_bool_var(api->request->query_string, "blocked", &blocked); // Does the user request a non-default number of replies? // Note: We do not accept zero query requests here - get_int_var(request->query_string, "show", &show); + get_int_var(api->request->query_string, "show", &show); // Show also clients which have not been active recently? - get_bool_var(request->query_string, "withzero", &includezeroclients); + get_bool_var(api->request->query_string, "withzero", &includezeroclients); } for(int clientID = 0; clientID < counters->clients; clientID++) @@ -465,14 +463,14 @@ int api_stats_top_clients(bool blocked, struct mg_connection *conn) } -int api_stats_upstreams(struct mg_connection *conn) +int api_stats_upstreams(struct ftl_conn *api) { int temparray[counters->forwarded][2]; // Verify requesting client is allowed to see this ressource - if(check_client_auth(conn, NULL) == API_AUTH_UNAUTHORIZED) + if(check_client_auth(api) == API_AUTH_UNAUTHORIZED) { - return send_json_unauthorized(conn); + return send_json_unauthorized(api); } for(int upstreamID = 0; upstreamID < counters->upstreams; upstreamID++) { @@ -568,12 +566,12 @@ int api_stats_upstreams(struct mg_connection *conn) JSON_SEND_OBJECT(json); } -int api_stats_query_types(struct mg_connection *conn) +int api_stats_query_types(struct ftl_conn *api) { // Verify requesting client is allowed to see this ressource - if(check_client_auth(conn, NULL) == API_AUTH_UNAUTHORIZED) + if(check_client_auth(api) == API_AUTH_UNAUTHORIZED) { - return send_json_unauthorized(conn); + return send_json_unauthorized(api); } // Send response @@ -588,7 +586,7 @@ int api_stats_query_types(struct mg_connection *conn) JSON_SEND_OBJECT(json); } -int api_stats_history(struct mg_connection *conn) +int api_stats_history(struct ftl_conn *api) { // Exit before processing any data if requested via config setting get_privacy_level(NULL); @@ -607,9 +605,9 @@ int api_stats_history(struct mg_connection *conn) } // Verify requesting client is allowed to see this ressource - if(check_client_auth(conn, NULL) == API_AUTH_UNAUTHORIZED) + if(check_client_auth(api) == API_AUTH_UNAUTHORIZED) { - return send_json_unauthorized(conn); + return send_json_unauthorized(api); } // Do we want a more specific version of this command (domain/client/time interval filtered)? @@ -635,25 +633,24 @@ int api_stats_history(struct mg_connection *conn) // We send 200 queries (until the API is asked for a different limit) unsigned int show = 200u; - const struct mg_request_info *request = mg_get_request_info(conn); - if(request->query_string != NULL) + if(api->request->query_string != NULL) { // Time filtering? - get_uint_var(request->query_string, "from", &from); - get_uint_var(request->query_string, "until", &until); + get_uint_var(api->request->query_string, "from", &from); + get_uint_var(api->request->query_string, "until", &until); // Query type filtering? int num; - if(get_int_var(request->query_string, "querytype", &num) && num < TYPE_MAX) + if(get_int_var(api->request->query_string, "querytype", &num) && num < TYPE_MAX) querytype = num; // Does the user request a non-default number of replies? // Note: We do not accept zero query requests here - get_uint_var(request->query_string, "show", &show); + get_uint_var(api->request->query_string, "show", &show); // Forward destination filtering? char buffer[256] = { 0 }; - if(GET_VAR("forward", buffer, request->query_string) > 0) + if(GET_VAR("forward", buffer, api->request->query_string) > 0) { forwarddest = calloc(256, sizeof(char)); if(forwarddest == NULL) @@ -712,7 +709,7 @@ int api_stats_history(struct mg_connection *conn) JSON_OBJ_COPY_STR(json, "upstream", forwarddest); free(forwarddest); - return send_json_error(conn, 400, + return send_json_error(api, 400, "bad_request", "Requested upstream not found", json); @@ -721,7 +718,7 @@ int api_stats_history(struct mg_connection *conn) } // Domain filtering? - if(GET_VAR("domain", buffer, request->query_string) > 0) + if(GET_VAR("domain", buffer, api->request->query_string) > 0) { domainname = calloc(512, sizeof(char)); if(domainname == NULL) @@ -755,7 +752,7 @@ int api_stats_history(struct mg_connection *conn) JSON_OBJ_COPY_STR(json, "domain", domainname); free(domainname); - return send_json_error(conn, 400, + return send_json_error(api, 400, "bad_request", "Requested domain not found", json); @@ -763,7 +760,7 @@ int api_stats_history(struct mg_connection *conn) } // Client filtering? - if(GET_VAR("client", buffer, request->query_string) > 0) + if(GET_VAR("client", buffer, api->request->query_string) > 0) { clientname = calloc(512, sizeof(char)); if(clientname == NULL) @@ -805,7 +802,7 @@ int api_stats_history(struct mg_connection *conn) JSON_OBJ_COPY_STR(json, "client", clientname); free(clientname); - return send_json_error(conn, 400, + return send_json_error(api, 400, "bad_request", "Requested client not found", json); @@ -813,7 +810,7 @@ int api_stats_history(struct mg_connection *conn) } unsigned int unum = 0u; - if(GET_VAR("cursor", buffer, request->query_string) > 0 && + if(GET_VAR("cursor", buffer, api->request->query_string) > 0 && sscanf(buffer, "%u", &unum) > 0) { // Do not start at the most recent, but at an older query @@ -830,7 +827,7 @@ int api_stats_history(struct mg_connection *conn) JSON_OBJ_ADD_NUMBER(json, "maxval", counters->queries); free(clientname); - return send_json_error(conn, 400, + return send_json_error(api, 400, "bad_request", "Requested cursor larger than number of queries", json); @@ -1060,14 +1057,14 @@ int api_stats_history(struct mg_connection *conn) JSON_SEND_OBJECT(json); } -int api_stats_recentblocked(struct mg_connection *conn) +int api_stats_recentblocked(struct ftl_conn *api) { unsigned int show = 1; // Verify requesting client is allowed to see this ressource - if(check_client_auth(conn, NULL) == API_AUTH_UNAUTHORIZED) + if(check_client_auth(api) == API_AUTH_UNAUTHORIZED) { - return send_json_unauthorized(conn); + return send_json_unauthorized(api); } // Exit before processing any data if requested via config setting @@ -1081,12 +1078,11 @@ int api_stats_recentblocked(struct mg_connection *conn) JSON_SEND_OBJECT(json); } - const struct mg_request_info *request = mg_get_request_info(conn); - if(request->query_string != NULL) + if(api->request->query_string != NULL) { // Does the user request a non-default number of replies? // Note: We do not accept zero query requests here - get_uint_var(request->query_string, "show", &show); + get_uint_var(api->request->query_string, "show", &show); } // Find most recently blocked query @@ -1124,14 +1120,14 @@ int api_stats_recentblocked(struct mg_connection *conn) JSON_SEND_OBJECT(json); } -int api_stats_overTime_clients(struct mg_connection *conn) +int api_stats_overTime_clients(struct ftl_conn *api) { int sendit = -1, until = OVERTIME_SLOTS; // Verify requesting client is allowed to see this ressource - if(check_client_auth(conn, NULL) == API_AUTH_UNAUTHORIZED) + if(check_client_auth(api) == API_AUTH_UNAUTHORIZED) { - return send_json_unauthorized(conn); + return send_json_unauthorized(api); } // Find minimum ID to send diff --git a/src/api/stats_database.c b/src/api/stats_database.c index a5e623665..007ae201a 100644 --- a/src/api/stats_database.c +++ b/src/api/stats_database.c @@ -20,15 +20,14 @@ // FTL_db #include "../database/common.h" -int api_stats_database_overTime_history(struct mg_connection *conn) +int api_stats_database_overTime_history(struct ftl_conn *api) { unsigned int from = 0, until = 0; const int interval = 600; - const struct mg_request_info *request = mg_get_request_info(conn); - if(request->query_string != NULL) + if(api->request->query_string != NULL) { - get_uint_var(request->query_string, "from", &from); - get_uint_var(request->query_string, "until", &until); + get_uint_var(api->request->query_string, "from", &from); + get_uint_var(api->request->query_string, "until", &until); } // Check if we received the required information @@ -37,7 +36,7 @@ int api_stats_database_overTime_history(struct mg_connection *conn) cJSON *json = JSON_NEW_OBJ(); JSON_OBJ_ADD_NUMBER(json, "from", from); JSON_OBJ_ADD_NUMBER(json, "until", until); - return send_json_error(conn, 400, + return send_json_error(api, 400, "bad_request", "You need to specify \"until\" in the request.", json); @@ -79,7 +78,7 @@ int api_stats_database_overTime_history(struct mg_connection *conn) cJSON *json = JSON_NEW_OBJ(); JSON_OBJ_ADD_NUMBER(json, "from", from); JSON_OBJ_ADD_NUMBER(json, "until", until); - return send_json_error(conn, 500, + return send_json_error(api, 500, "internal_error", "Failed to bind interval", json); @@ -100,7 +99,7 @@ int api_stats_database_overTime_history(struct mg_connection *conn) cJSON *json = JSON_NEW_OBJ(); JSON_OBJ_ADD_NUMBER(json, "from", from); JSON_OBJ_ADD_NUMBER(json, "until", until); - return send_json_error(conn, 500, + return send_json_error(api, 500, "internal_error", "Failed to bind from", json); @@ -121,7 +120,7 @@ int api_stats_database_overTime_history(struct mg_connection *conn) cJSON *json = JSON_NEW_OBJ(); JSON_OBJ_ADD_NUMBER(json, "from", from); JSON_OBJ_ADD_NUMBER(json, "until", until); - return send_json_error(conn, 500, + return send_json_error(api, 500, "internal_error", "Failed to bind until", json); @@ -182,23 +181,22 @@ int api_stats_database_overTime_history(struct mg_connection *conn) JSON_SEND_OBJECT(json); } -int api_stats_database_top_items(bool blocked, bool domains, struct mg_connection *conn) +int api_stats_database_top_items(bool blocked, bool domains, struct ftl_conn *api) { unsigned int from = 0, until = 0, show = 10; - const struct mg_request_info *request = mg_get_request_info(conn); - if(request->query_string != NULL) + if(api->request->query_string != NULL) { - get_uint_var(request->query_string, "from", &from); - get_uint_var(request->query_string, "until", &until); + get_uint_var(api->request->query_string, "from", &from); + get_uint_var(api->request->query_string, "until", &until); // Get blocked queries not only for .../top_blocked // but also for .../top_domains?blocked=true // Note: this may overwrite the blocked propery from the URL - get_bool_var(request->query_string, "blocked", &blocked); + get_bool_var(api->request->query_string, "blocked", &blocked); // Does the user request a non-default number of replies? // Note: We do not accept zero query requests here - get_uint_var(request->query_string, "show", &show); + get_uint_var(api->request->query_string, "show", &show); } // Check if we received the required information @@ -207,7 +205,7 @@ int api_stats_database_top_items(bool blocked, bool domains, struct mg_connectio cJSON *json = JSON_NEW_OBJ(); JSON_OBJ_ADD_NUMBER(json, "from", from); JSON_OBJ_ADD_NUMBER(json, "until", until); - return send_json_error(conn, 400, + return send_json_error(api, 400, "bad_request", "You need to specify both \"from\" and \"until\" in the request.", json); @@ -277,7 +275,7 @@ int api_stats_database_top_items(bool blocked, bool domains, struct mg_connectio JSON_OBJ_ADD_NUMBER(json, "from", from); JSON_OBJ_ADD_NUMBER(json, "until", until); JSON_OBJ_REF_STR(json, "querystr", querystr); - return send_json_error(conn, 500, + return send_json_error(api, 500, "internal_error", "Failed to prepare query string", json); @@ -298,7 +296,7 @@ int api_stats_database_top_items(bool blocked, bool domains, struct mg_connectio cJSON *json = JSON_NEW_OBJ(); JSON_OBJ_ADD_NUMBER(json, "from", from); JSON_OBJ_ADD_NUMBER(json, "until", until); - return send_json_error(conn, 500, + return send_json_error(api, 500, "internal_error", "Failed to bind from", json); @@ -319,7 +317,7 @@ int api_stats_database_top_items(bool blocked, bool domains, struct mg_connectio cJSON *json = JSON_NEW_OBJ(); JSON_OBJ_ADD_NUMBER(json, "from", from); JSON_OBJ_ADD_NUMBER(json, "until", until); - return send_json_error(conn, 500, + return send_json_error(api, 500, "internal_error", "Failed to bind until", json); @@ -340,7 +338,7 @@ int api_stats_database_top_items(bool blocked, bool domains, struct mg_connectio cJSON *json = JSON_NEW_OBJ(); JSON_OBJ_ADD_NUMBER(json, "from", from); JSON_OBJ_ADD_NUMBER(json, "until", until); - return send_json_error(conn, 500, + return send_json_error(api, 500, "internal_error", "Failed to bind show", json); @@ -378,14 +376,13 @@ int api_stats_database_top_items(bool blocked, bool domains, struct mg_connectio JSON_SEND_OBJECT(json); } -int api_stats_database_summary(struct mg_connection *conn) +int api_stats_database_summary(struct ftl_conn *api) { unsigned int from = 0, until = 0; - const struct mg_request_info *request = mg_get_request_info(conn); - if(request->query_string != NULL) + if(api->request->query_string != NULL) { - get_uint_var(request->query_string, "from", &from); - get_uint_var(request->query_string, "until", &until); + get_uint_var(api->request->query_string, "from", &from); + get_uint_var(api->request->query_string, "until", &until); } // Check if we received the required information @@ -394,7 +391,7 @@ int api_stats_database_summary(struct mg_connection *conn) cJSON *json = JSON_NEW_OBJ(); JSON_OBJ_ADD_NUMBER(json, "from", from); JSON_OBJ_ADD_NUMBER(json, "until", until); - return send_json_error(conn, 400, + return send_json_error(api, 400, "bad_request", "You need to specify both \"from\" and \"until\" in the request.", json); @@ -435,7 +432,7 @@ int api_stats_database_summary(struct mg_connection *conn) cJSON *json = JSON_NEW_OBJ(); JSON_OBJ_ADD_NUMBER(json, "from", from); JSON_OBJ_ADD_NUMBER(json, "until", until); - return send_json_error(conn, 500, + return send_json_error(api, 500, "internal_error", "Internal server error", json); @@ -459,15 +456,14 @@ int api_stats_database_summary(struct mg_connection *conn) JSON_SEND_OBJECT(json); } -int api_stats_database_overTime_clients(struct mg_connection *conn) +int api_stats_database_overTime_clients(struct ftl_conn *api) { unsigned int from = 0, until = 0; const int interval = 600; - const struct mg_request_info *request = mg_get_request_info(conn); - if(request->query_string != NULL) + if(api->request->query_string != NULL) { - get_uint_var(request->query_string, "from", &from); - get_uint_var(request->query_string, "until", &until); + get_uint_var(api->request->query_string, "from", &from); + get_uint_var(api->request->query_string, "until", &until); } // Check if we received the required information @@ -476,7 +472,7 @@ int api_stats_database_overTime_clients(struct mg_connection *conn) cJSON *json = JSON_NEW_OBJ(); JSON_OBJ_ADD_NUMBER(json, "from", from); JSON_OBJ_ADD_NUMBER(json, "until", until); - return send_json_error(conn, 400, + return send_json_error(api, 400, "bad_request", "You need to specify both \"from\" and \"until\" in the request.", json); @@ -505,7 +501,7 @@ int api_stats_database_overTime_clients(struct mg_connection *conn) cJSON *json = JSON_NEW_OBJ(); JSON_OBJ_ADD_NUMBER(json, "from", from); JSON_OBJ_ADD_NUMBER(json, "until", until); - return send_json_error(conn, 500, + return send_json_error(api, 500, "internal_error", "Failed to prepare outer statement", json); @@ -526,7 +522,7 @@ int api_stats_database_overTime_clients(struct mg_connection *conn) cJSON *json = JSON_NEW_OBJ(); JSON_OBJ_ADD_NUMBER(json, "from", from); JSON_OBJ_ADD_NUMBER(json, "until", until); - return send_json_error(conn, 500, + return send_json_error(api, 500, "internal_error", "Failed to bind from", json); @@ -547,7 +543,7 @@ int api_stats_database_overTime_clients(struct mg_connection *conn) cJSON *json = JSON_NEW_OBJ(); JSON_OBJ_ADD_NUMBER(json, "from", from); JSON_OBJ_ADD_NUMBER(json, "until", until); - return send_json_error(conn, 500, + return send_json_error(api, 500, "internal_error", "Failed to bind until", json); @@ -584,7 +580,7 @@ int api_stats_database_overTime_clients(struct mg_connection *conn) cJSON *json = JSON_NEW_OBJ(); JSON_OBJ_ADD_NUMBER(json, "from", from); JSON_OBJ_ADD_NUMBER(json, "until", until); - return send_json_error(conn, 500, + return send_json_error(api, 500, "internal_error", "Failed to prepare inner statement", json); @@ -605,7 +601,7 @@ int api_stats_database_overTime_clients(struct mg_connection *conn) cJSON *json = JSON_NEW_OBJ(); JSON_OBJ_ADD_NUMBER(json, "from", from); JSON_OBJ_ADD_NUMBER(json, "until", until); - return send_json_error(conn, 500, + return send_json_error(api, 500, "internal_error", "Failed to bind interval", json); @@ -626,7 +622,7 @@ int api_stats_database_overTime_clients(struct mg_connection *conn) cJSON *json = JSON_NEW_OBJ(); JSON_OBJ_ADD_NUMBER(json, "from", from); JSON_OBJ_ADD_NUMBER(json, "until", until); - return send_json_error(conn, 500, + return send_json_error(api, 500, "internal_error", "Failed to bind from", json); @@ -647,7 +643,7 @@ int api_stats_database_overTime_clients(struct mg_connection *conn) cJSON *json = JSON_NEW_OBJ(); JSON_OBJ_ADD_NUMBER(json, "from", from); JSON_OBJ_ADD_NUMBER(json, "until", until); - return send_json_error(conn, 500, + return send_json_error(api, 500, "internal_error", "Failed to bind until", json); @@ -722,14 +718,13 @@ int api_stats_database_overTime_clients(struct mg_connection *conn) JSON_SEND_OBJECT(json); } -int api_stats_database_query_types(struct mg_connection *conn) +int api_stats_database_query_types(struct ftl_conn *api) { unsigned int from = 0, until = 0; - const struct mg_request_info *request = mg_get_request_info(conn); - if(request->query_string != NULL) + if(api->request->query_string != NULL) { - get_uint_var(request->query_string, "from", &from); - get_uint_var(request->query_string, "until", &until); + get_uint_var(api->request->query_string, "from", &from); + get_uint_var(api->request->query_string, "until", &until); } // Check if we received the required information @@ -738,7 +733,7 @@ int api_stats_database_query_types(struct mg_connection *conn) cJSON *json = JSON_NEW_OBJ(); JSON_OBJ_ADD_NUMBER(json, "from", from); JSON_OBJ_ADD_NUMBER(json, "until", until); - return send_json_error(conn, 400, + return send_json_error(api, 400, "bad_request", "You need to specify both \"from\" and \"until\" in the request.", json); @@ -777,14 +772,13 @@ int api_stats_database_query_types(struct mg_connection *conn) } -int api_stats_database_upstreams(struct mg_connection *conn) +int api_stats_database_upstreams(struct ftl_conn *api) { unsigned int from = 0, until = 0; - const struct mg_request_info *request = mg_get_request_info(conn); - if(request->query_string != NULL) + if(api->request->query_string != NULL) { - get_uint_var(request->query_string, "from", &from); - get_uint_var(request->query_string, "until", &until); + get_uint_var(api->request->query_string, "from", &from); + get_uint_var(api->request->query_string, "until", &until); } // Check if we received the required information @@ -793,7 +787,7 @@ int api_stats_database_upstreams(struct mg_connection *conn) cJSON *json = JSON_NEW_OBJ(); JSON_OBJ_ADD_NUMBER(json, "from", from); JSON_OBJ_ADD_NUMBER(json, "until", until); - return send_json_error(conn, 400, + return send_json_error(api, 400, "bad_request", "You need to specify both \"from\" and \"until\" in the request.", json); @@ -838,7 +832,7 @@ int api_stats_database_upstreams(struct mg_connection *conn) cJSON *json = JSON_NEW_OBJ(); JSON_OBJ_ADD_NUMBER(json, "from", from); JSON_OBJ_ADD_NUMBER(json, "until", until); - return send_json_error(conn, 500, + return send_json_error(api, 500, "internal_error", "Failed to prepare statement", json); @@ -859,7 +853,7 @@ int api_stats_database_upstreams(struct mg_connection *conn) cJSON *json = JSON_NEW_OBJ(); JSON_OBJ_ADD_NUMBER(json, "from", from); JSON_OBJ_ADD_NUMBER(json, "until", until); - return send_json_error(conn, 500, + return send_json_error(api, 500, "internal_error", "Failed to bind from", json); @@ -880,7 +874,7 @@ int api_stats_database_upstreams(struct mg_connection *conn) cJSON *json = JSON_NEW_OBJ(); JSON_OBJ_ADD_NUMBER(json, "from", from); JSON_OBJ_ADD_NUMBER(json, "until", until); - return send_json_error(conn, 500, + return send_json_error(api, 500, "internal_error", "Failed to bind until", json); diff --git a/src/api/version.c b/src/api/version.c index f72b24fa5..1b3c0e58e 100644 --- a/src/api/version.c +++ b/src/api/version.c @@ -16,7 +16,7 @@ #include "../log.h" #include "../version.h" -int api_version(struct mg_connection *conn) +int api_version(struct ftl_conn *api) { cJSON *json = JSON_NEW_OBJ(); diff --git a/src/database/gravity-db.h b/src/database/gravity-db.h index 6fbeb30ed..9ad0adb5e 100644 --- a/src/database/gravity-db.h +++ b/src/database/gravity-db.h @@ -30,6 +30,7 @@ typedef struct { const char *description; const char *argument; const char *oldtype; + const char *ip; long id; time_t date_added; time_t date_modified; diff --git a/src/webserver/http-common.c b/src/webserver/http-common.c index 35238342d..9f2963762 100644 --- a/src/webserver/http-common.c +++ b/src/webserver/http-common.c @@ -41,35 +41,35 @@ const char* json_formatter(const cJSON *object) } } -int send_http(struct mg_connection *conn, const char *mime_type, +int send_http(struct ftl_conn *api, const char *mime_type, const char *msg) { - mg_send_http_ok(conn, mime_type, NULL, strlen(msg)); - return mg_write(conn, msg, strlen(msg)); + mg_send_http_ok(api->conn, mime_type, NULL, strlen(msg)); + return mg_write(api->conn, msg, strlen(msg)); } -int send_http_code(struct mg_connection *conn, const char *mime_type, +int send_http_code(struct ftl_conn *api, const char *mime_type, int code, const char *msg) { // Payload will be sent with text/plain encoding due to // the first line being "Error " by definition //return mg_send_http_error(conn, code, "%s", msg); - my_send_http_error_headers(conn, code, + my_send_http_error_headers(api->conn, code, mime_type, strlen(msg)); - return mg_write(conn, msg, strlen(msg)); + return mg_write(api->conn, msg, strlen(msg)); } -int send_json_unauthorized(struct mg_connection *conn) +int send_json_unauthorized(struct ftl_conn *api) { - return send_json_error(conn, 401, + return send_json_error(api, 401, "unauthorized", "Unauthorized", NULL); } -int send_json_error(struct mg_connection *conn, const int code, +int send_json_error(struct ftl_conn *api, const int code, const char *key, const char* message, cJSON *data) { @@ -93,16 +93,16 @@ int send_json_error(struct mg_connection *conn, const int code, JSON_SEND_OBJECT_CODE(json, code); } -int send_json_success(struct mg_connection *conn) +int send_json_success(struct ftl_conn *api) { cJSON *json = JSON_NEW_OBJ(); JSON_OBJ_REF_STR(json, "status", "success"); JSON_SEND_OBJECT(json); } -int send_http_internal_error(struct mg_connection *conn) +int send_http_internal_error(struct ftl_conn *api) { - return mg_send_http_error(conn, 500, "Internal server error"); + return mg_send_http_error(api->conn, 500, "Internal server error"); } bool get_bool_var(const char *source, const char *var, bool *boolean) @@ -140,40 +140,12 @@ bool get_uint_var(const char *source, const char *var, unsigned int *num) return false; } -// Extract payload either from GET or POST data -bool http_get_payload(struct mg_connection *conn, char *payload) +const char* __attribute__((pure)) startsWith(const char *path, const struct ftl_conn *api) { - // We do not want to extract any payload here - if(payload == NULL) - return false; - - const enum http_method method = http_method(conn); - const struct mg_request_info *request = mg_get_request_info(conn); - if(method == HTTP_GET && request->query_string != NULL) - { - strncpy(payload, request->query_string, MAX_PAYLOAD_BYTES-1); - return true; - } - else // POST, PUT, PATCH - { - int data_len = mg_read(conn, payload, MAX_PAYLOAD_BYTES - 1); - if ((data_len < 1) || (data_len >= MAX_PAYLOAD_BYTES)) - return false; - - payload[data_len] = '\0'; - return true; - } - - // Everything else - return false; -} - -const char* __attribute__((pure)) startsWith(const char *path, const char *uri) -{ - if(strncmp(path, uri, strlen(path)) == 0) - if(uri[strlen(path)] == '/') + if(strncmp(path, api->request->local_uri, strlen(path)) == 0) + if(api->request->local_uri[strlen(path)] == '/') // Path match with argument after ".../" - return uri + strlen(path) + 1u; + return api->request->local_uri + strlen(path) + 1u; else // Path match without argument return ""; @@ -182,11 +154,11 @@ const char* __attribute__((pure)) startsWith(const char *path, const char *uri) return NULL; } -bool http_get_cookie_int(struct mg_connection *conn, const char *cookieName, int *i) +bool http_get_cookie_int(struct ftl_conn *api, const char *cookieName, int *i) { // Maximum cookie length is 4KB char cookieValue[4096]; - const char *cookie = mg_get_header(conn, "Cookie"); + const char *cookie = mg_get_header(api->conn, "Cookie"); if(mg_get_cookie(cookie, cookieName, cookieValue, sizeof(cookieValue)) > 0) { *i = atoi(cookieValue); @@ -195,9 +167,9 @@ bool http_get_cookie_int(struct mg_connection *conn, const char *cookieName, int return false; } -bool http_get_cookie_str(struct mg_connection *conn, const char *cookieName, char *str, size_t str_size) +bool http_get_cookie_str(struct ftl_conn *api, const char *cookieName, char *str, size_t str_size) { - const char *cookie = mg_get_header(conn, "Cookie"); + const char *cookie = mg_get_header(api->conn, "Cookie"); if(mg_get_cookie(cookie, cookieName, str, str_size) > 0) { return true; @@ -205,7 +177,7 @@ bool http_get_cookie_str(struct mg_connection *conn, const char *cookieName, cha return false; } -int http_method(struct mg_connection *conn) +enum http_method __attribute__((pure)) http_method(struct mg_connection *conn) { const struct mg_request_info *request = mg_get_request_info(conn); if(strcmp(request->request_method, "GET") == 0) @@ -220,17 +192,25 @@ int http_method(struct mg_connection *conn) return HTTP_UNKNOWN; } -cJSON *get_POST_JSON(struct mg_connection *conn) +void read_and_parse_payload(struct ftl_conn *api) { - // Extract payload - char buffer[1024] = { 0 }; - int data_len = mg_read(conn, buffer, sizeof(buffer) - 1); - if ((data_len < 1) || (data_len >= (int)sizeof(buffer))) - return NULL; + // Try to extract payload from GET request + if(api->method == HTTP_GET && api->request->query_string != NULL) + { + strncpy(api->payload.raw, api->request->query_string, MAX_PAYLOAD_BYTES-1); + api->payload.avail = true; + } + else // POST, PUT + { + int data_len = mg_read(api->conn, api->payload.raw, MAX_PAYLOAD_BYTES - 1); + logg("Received payload with size: %d", data_len); + if ((data_len < 1) || (data_len >= MAX_PAYLOAD_BYTES)) + return; - buffer[data_len] = '\0'; + api->payload.raw[data_len] = '\0'; + api->payload.avail = true; - // Parse JSON - cJSON *obj = cJSON_Parse(buffer); - return obj; + // Try to parse possibly existing JSON payload + api->payload.json = cJSON_Parse(api->payload.raw); + } } diff --git a/src/webserver/http-common.h b/src/webserver/http-common.h index b593594e8..30c5f6ca2 100644 --- a/src/webserver/http-common.h +++ b/src/webserver/http-common.h @@ -17,40 +17,49 @@ // strlen() #include +// API-internal definitions #define MAX_PAYLOAD_BYTES 2048 +enum http_method { HTTP_UNKNOWN, HTTP_GET, HTTP_POST, HTTP_PUT, HTTP_DELETE }; +struct ftl_conn { + struct mg_connection *conn; + const struct mg_request_info *request; + const enum http_method method; + struct { + bool avail :1; + char raw[MAX_PAYLOAD_BYTES]; + cJSON *json; + } payload; +}; + const char* json_formatter(const cJSON *object); -int send_http(struct mg_connection *conn, const char *mime_type, const char *msg); -int send_http_code(struct mg_connection *conn, const char *mime_type, int code, const char *msg); -int send_http_internal_error(struct mg_connection *conn); -int send_json_unauthorized(struct mg_connection *conn); -int send_json_error(struct mg_connection *conn, const int code, +int send_http(struct ftl_conn *api, const char *mime_type, const char *msg); +int send_http_code(struct ftl_conn *api, const char *mime_type, int code, const char *msg); +int send_http_internal_error(struct ftl_conn *api); +int send_json_unauthorized(struct ftl_conn *api); +int send_json_error(struct ftl_conn *api, const int code, const char *key, const char* message, cJSON *data); -int send_json_success(struct mg_connection *conn); +int send_json_success(struct ftl_conn *api); void http_reread_index_html(void); // Cookie routines -bool http_get_cookie_int(struct mg_connection *conn, const char *cookieName, int *i); -bool http_get_cookie_str(struct mg_connection *conn, const char *cookieName, char *str, size_t str_size); +bool http_get_cookie_int(struct ftl_conn *api, const char *cookieName, int *i); +bool http_get_cookie_str(struct ftl_conn *api, const char *cookieName, char *str, size_t str_size); // HTTP parameter routines bool get_bool_var(const char *source, const char *var, bool *boolean); bool get_uint_var(const char *source, const char *var, unsigned int *num); bool get_int_var(const char *source, const char *var, int *num); -bool http_get_payload(struct mg_connection *conn, char *payload); -cJSON *get_POST_JSON(struct mg_connection *conn); // HTTP macros #define GET_VAR(variable, destination, source) mg_get_var(source, strlen(source), variable, destination, sizeof(destination)) -// Method routines -enum http_method { HTTP_UNKNOWN, HTTP_GET, HTTP_POST, HTTP_PUT, HTTP_DELETE }; -int http_method(struct mg_connection *conn); - // Utils -const char *startsWith(const char *path, const char *uri) __attribute__((pure)); +enum http_method __attribute__((pure)) http_method(struct mg_connection *conn); +const char* __attribute__((pure)) startsWith(const char *path, const struct ftl_conn *api); +void read_and_parse_payload(struct ftl_conn *api); #endif // HTTP_H diff --git a/src/webserver/json_macros.h b/src/webserver/json_macros.h index 15429ea05..31990485f 100644 --- a/src/webserver/json_macros.h +++ b/src/webserver/json_macros.h @@ -30,7 +30,7 @@ if(string_item == NULL) \ { \ cJSON_Delete(object); \ - send_http_internal_error(conn); \ + send_http_internal_error(api); \ logg("JSON_OBJ_COPY_STR FAILED (key: \"%s\", string: \"%s\")!", key, string); \ return 500; \ } \ @@ -50,7 +50,7 @@ if(string_item == NULL) \ { \ cJSON_Delete(object); \ - send_http_internal_error(conn); \ + send_http_internal_error(api); \ logg("JSON_OBJ_REF_STR FAILED (key: \"%s\", string: \"%s\")!", key, string); \ return 500; \ } \ @@ -61,7 +61,7 @@ if(cJSON_AddNumberToObject(object, key, (double)(number)) == NULL) \ { \ cJSON_Delete(object); \ - send_http_internal_error(conn); \ + send_http_internal_error(api); \ logg("JSON_OBJ_ADD_NUMBER FAILED!"); \ return 500; \ } \ @@ -72,7 +72,7 @@ if(null_item == NULL) \ { \ cJSON_Delete(object); \ - send_http_internal_error(conn); \ + send_http_internal_error(api); \ logg("JSON_OBJ_ADD_NULL FAILED!"); \ return 500; \ } \ @@ -84,7 +84,7 @@ if(bool_item == NULL) \ { \ cJSON_Delete(object); \ - send_http_internal_error(conn); \ + send_http_internal_error(api); \ logg("JSON_OBJ_ADD_BOOL FAILED!"); \ return 500; \ } \ @@ -114,7 +114,7 @@ if(string_item == NULL) \ { \ cJSON_Delete(array); \ - send_http_internal_error(conn); \ + send_http_internal_error(api); \ logg("JSON_ARRAY_REF_STR FAILED!"); \ return 500; \ } \ @@ -134,7 +134,7 @@ if(string_item == NULL) \ { \ cJSON_Delete(array); \ - send_http_internal_error(conn); \ + send_http_internal_error(api); \ logg("JSON_ARRAY_COPY_STR FAILED!"); \ return 500; \ } \ @@ -153,11 +153,11 @@ if(msg == NULL) \ { \ cJSON_Delete(object); \ - send_http_internal_error(conn); \ + send_http_internal_error(api); \ logg("JSON_SEND_OBJECT FAILED!"); \ return 500; \ } \ - send_http(conn, "application/json; charset=utf-8", msg); \ + send_http(api, "application/json; charset=utf-8", msg); \ cJSON_Delete(object); \ return 200; \ } @@ -167,11 +167,11 @@ if(msg == NULL) \ { \ cJSON_Delete(object); \ - send_http_internal_error(conn); \ + send_http_internal_error(api); \ logg("JSON_SEND_OBJECT_CODE FAILED!"); \ return 500; \ } \ - send_http_code(conn, "application/json; charset=utf-8", code, msg); \ + send_http_code(api, "application/json; charset=utf-8", code, msg); \ cJSON_Delete(object); \ return code; \ } @@ -181,11 +181,11 @@ if(msg == NULL) \ { \ cJSON_Delete(object); \ - send_http_internal_error(conn); \ + send_http_internal_error(api); \ logg("JSON_SEND_OBJECT_AND_HEADERS FAILED!"); \ return 500; \ } \ - send_http(conn, "application/json; charset=utf-8", additional_headers, msg); \ + send_http(api, "application/json; charset=utf-8", additional_headers, msg); \ cJSON_Delete(object); \ free(additional_headers); \ return 200; \ @@ -196,11 +196,11 @@ if(msg == NULL) \ { \ cJSON_Delete(object); \ - send_http_internal_error(conn); \ + send_http_internal_error(api); \ logg("JSON_SEND_OBJECT_AND_HEADERS_CODE FAILED!"); \ return 500; \ } \ - send_http_code(conn, "application/json; charset=utf-8", additional_headers, code, msg); \ + send_http_code(api, "application/json; charset=utf-8", additional_headers, code, msg); \ cJSON_Delete(object); \ free(additional_headers); \ return code; \ diff --git a/src/webserver/webserver.c b/src/webserver/webserver.c index 75365bc8a..74aa136a3 100644 --- a/src/webserver/webserver.c +++ b/src/webserver/webserver.c @@ -22,12 +22,6 @@ // Server context handle static struct mg_context *ctx = NULL; -// Print passed string directly -static int print_simple(struct mg_connection *conn, void *input) -{ - return send_http(conn, "text/plain", input); -} - static int redirect_root_handler(struct mg_connection *conn, void *input) { // Get requested host @@ -142,7 +136,7 @@ void http_init(void) const char *options[] = { "document_root", httpsettings.webroot, "listening_ports", httpsettings.port, - "decode_url", "no", + "decode_url", "yes", "enable_directory_listing", "no", "num_threads", "16", "access_control_list", httpsettings.acl, @@ -171,9 +165,6 @@ void http_init(void) return; } - /* Add simple demonstration callbacks */ - mg_set_request_handler(ctx, "/ping", print_simple, (char*)"pong\n"); - // Register API handler mg_set_request_handler(ctx, "/api", api_handler, NULL); From 54e206a273abe11a0b0021be338c6b8642b2226a Mon Sep 17 00:00:00 2001 From: DL6ER Date: Fri, 22 Jan 2021 10:28:27 +0100 Subject: [PATCH 0225/1669] Shorten history JSON keys Signed-off-by: DL6ER --- src/api/stats.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/api/stats.c b/src/api/stats.c index 3041d7331..6a871939b 100644 --- a/src/api/stats.c +++ b/src/api/stats.c @@ -171,8 +171,9 @@ int api_stats_overTime_history(struct ftl_conn *api) { cJSON *item = JSON_NEW_OBJ(); JSON_OBJ_ADD_NUMBER(item, "timestamp", overTime[slot].timestamp); - JSON_OBJ_ADD_NUMBER(item, "total_queries", overTime[slot].total); - JSON_OBJ_ADD_NUMBER(item, "blocked_queries", overTime[slot].blocked); + JSON_OBJ_ADD_NUMBER(item, "total", overTime[slot].total); + JSON_OBJ_ADD_NUMBER(item, "cached", overTime[slot].cached); + JSON_OBJ_ADD_NUMBER(item, "blocked", overTime[slot].blocked); JSON_ARRAY_ADD_ITEM(data, item); } JSON_OBJ_ADD_ITEM(json, "data", data); From 45801543db9ecb1a8364d5d9ea2ff13bc6d149ce Mon Sep 17 00:00:00 2001 From: DL6ER Date: Fri, 22 Jan 2021 11:20:12 +0100 Subject: [PATCH 0226/1669] Remove obsolete ping/pong test Signed-off-by: DL6ER --- test/test_suite.bats | 6 ------ 1 file changed, 6 deletions(-) diff --git a/test/test_suite.bats b/test/test_suite.bats index eabd9b1a0..04321780e 100644 --- a/test/test_suite.bats +++ b/test/test_suite.bats @@ -685,12 +685,6 @@ [[ ${lines[0]} == "2" ]] } -@test "HTTP server responds correctly to ping" { - run bash -c 'curl -s 127.0.0.1:8080/ping' - printf "%s\n" "${lines[@]}" - [[ ${lines[0]} == "pong" ]] -} - @test "HTTP server responds with JSON error 404 to unknown API path" { run bash -c 'curl -s 127.0.0.1:8080/api/undefined' printf "%s\n" "${lines[@]}" From 98e74256ae77abf5f1cac89531d325b5c72efe34 Mon Sep 17 00:00:00 2001 From: DL6ER Date: Fri, 22 Jan 2021 11:47:57 +0100 Subject: [PATCH 0227/1669] Send domains/clients/groups/adlists counts in a new ftl object Signed-off-by: DL6ER --- src/api/ftl.c | 25 +++++++++++++++++++++++-- src/api/routes.h | 1 + src/api/stats.c | 13 +++++++++++-- src/database/gravity-db.c | 15 +++++++++++++++ src/datastructure.c | 5 +++++ src/enums.h | 5 +++++ src/shmem.h | 9 +++++++++ 7 files changed, 69 insertions(+), 4 deletions(-) diff --git a/src/api/ftl.c b/src/api/ftl.c index 5ff613528..b7be994cc 100644 --- a/src/api/ftl.c +++ b/src/api/ftl.c @@ -384,16 +384,37 @@ int get_system_obj(struct ftl_conn *api, cJSON *system) return 0; } +int get_ftl_obj(struct ftl_conn *api, cJSON *ftl) +{ + JSON_OBJ_ADD_NUMBER(ftl, "groups", counters->database.groups); + JSON_OBJ_ADD_NUMBER(ftl, "adlists", counters->database.adlists); + JSON_OBJ_ADD_NUMBER(ftl, "clients", counters->database.clients); + cJSON *domains = JSON_NEW_OBJ(); + JSON_OBJ_ADD_NUMBER(domains, "allowed", counters->database.domains.allowed); + JSON_OBJ_ADD_NUMBER(domains, "denied", counters->database.domains.denied); + JSON_OBJ_ADD_ITEM(ftl, "domains", domains); + + return 0; +} + int api_ftl_system(struct ftl_conn *api) { cJSON *json = JSON_NEW_OBJ(); - cJSON *system = JSON_NEW_OBJ(); // Get system object - const int ret = get_system_obj(api, system); + cJSON *system = JSON_NEW_OBJ(); + int ret = get_system_obj(api, system); if (ret != 0) return ret; JSON_OBJ_ADD_ITEM(json, "system", system); + + // Get FTL object + cJSON *ftl = JSON_NEW_OBJ(); + ret = get_ftl_obj(api, ftl); + if(ret != 0) + return ret; + + JSON_OBJ_ADD_ITEM(json, "ftl", ftl); JSON_SEND_OBJECT(json); } diff --git a/src/api/routes.h b/src/api/routes.h index 23ac77248..959c254b7 100644 --- a/src/api/routes.h +++ b/src/api/routes.h @@ -44,6 +44,7 @@ int api_ftl_client(struct ftl_conn *api); int api_ftl_dnsmasq_log(struct ftl_conn *api); int api_ftl_database(struct ftl_conn *api); int api_ftl_system(struct ftl_conn *api); +int get_ftl_obj(struct ftl_conn *api, cJSON *ftl); int get_system_obj(struct ftl_conn *api, cJSON *system); // Network methods diff --git a/src/api/stats.c b/src/api/stats.c index 6a871939b..76d913b69 100644 --- a/src/api/stats.c +++ b/src/api/stats.c @@ -117,11 +117,20 @@ int api_stats_summary(struct ftl_conn *api) JSON_OBJ_ADD_NUMBER(reply_types, "domain", counters->reply_domain); JSON_OBJ_ADD_ITEM(json, "reply_types", reply_types); + // Get system object cJSON *system = JSON_NEW_OBJ(); - const int ret = get_system_obj(api, system); - if(ret != 0) return ret; + int ret = get_system_obj(api, system); + if(ret != 0) + return ret; JSON_OBJ_ADD_ITEM(json, "system", system); + // Get FTL object + cJSON *ftl = JSON_NEW_OBJ(); + ret = get_ftl_obj(api, ftl); + if(ret != 0) + return ret; + JSON_OBJ_ADD_ITEM(json, "ftl", ftl); + JSON_SEND_OBJECT(json); } diff --git a/src/database/gravity-db.c b/src/database/gravity-db.c index 28d38c266..f5ebfbb9a 100644 --- a/src/database/gravity-db.c +++ b/src/database/gravity-db.c @@ -1052,6 +1052,21 @@ int gravityDB_count(const enum gravity_tables list) case REGEX_WHITELIST_TABLE: querystr = "SELECT COUNT(DISTINCT domain) FROM vw_regex_whitelist"; break; + case CLIENTS_TABLE: + querystr = "SELECT COUNT(1) FROM client"; + break; + case GROUPS_TABLE: + querystr = "SELECT COUNT(1) FROM \"group\" WHERE enabled != 0"; + break; + case ADLISTS_TABLE: + querystr = "SELECT COUNT(1) FROM adlist WHERE enabled != 0"; + break; + case DENIED_DOMAINS_TABLE: + querystr = "SELECT COUNT(1) FROM domainlist WHERE (type = 0 OR type = 2) AND enabled != 0"; + break; + case ALLOWED_DOMAINS_TABLE: + querystr = "SELECT COUNT(1) FROM domainlist WHERE (type = 1 OR type = 3) AND enabled != 0"; + break; case UNKNOWN_TABLE: logg("Error: List type %u unknown!", list); gravityDB_close(); diff --git a/src/datastructure.c b/src/datastructure.c index af6523f40..ffdd9e660 100644 --- a/src/datastructure.c +++ b/src/datastructure.c @@ -495,6 +495,11 @@ void FTL_reload_all_domainlists(void) // Reset number of blocked domains counters->gravity = gravityDB_count(GRAVITY_TABLE); + counters->database.groups = gravityDB_count(GROUPS_TABLE); + counters->database.clients = gravityDB_count(CLIENTS_TABLE); + counters->database.adlists = gravityDB_count(ADLISTS_TABLE); + counters->database.domains.allowed = gravityDB_count(DENIED_DOMAINS_TABLE); + counters->database.domains.denied = gravityDB_count(ALLOWED_DOMAINS_TABLE); // Read and compile possible regex filters // only after having called gravityDB_open() diff --git a/src/enums.h b/src/enums.h index d82d6260b..047858cfe 100644 --- a/src/enums.h +++ b/src/enums.h @@ -177,6 +177,11 @@ enum gravity_tables { EXACT_WHITELIST_TABLE, REGEX_BLACKLIST_TABLE, REGEX_WHITELIST_TABLE, + CLIENTS_TABLE, + GROUPS_TABLE, + ADLISTS_TABLE, + DENIED_DOMAINS_TABLE, + ALLOWED_DOMAINS_TABLE, UNKNOWN_TABLE } __attribute__ ((packed)); diff --git a/src/shmem.h b/src/shmem.h index ad5190048..3ddc4dcaa 100644 --- a/src/shmem.h +++ b/src/shmem.h @@ -59,6 +59,15 @@ typedef struct { int dns_cache_size; int dns_cache_MAX; unsigned int regex_change; + struct { + int clients; + int groups; + int adlists; + struct{ + int allowed; + int denied; + } domains; + } database; } countersStruct; typedef struct { From 9a2a2cdf1006fad0c58b78d0c2a4517130654728 Mon Sep 17 00:00:00 2001 From: DL6ER Date: Fri, 22 Jan 2021 11:58:36 +0100 Subject: [PATCH 0228/1669] Actually reload gravity data on list add/edit/remove Signed-off-by: DL6ER --- src/api/list.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/api/list.c b/src/api/list.c index c86531105..15f5e0a67 100644 --- a/src/api/list.c +++ b/src/api/list.c @@ -13,6 +13,7 @@ #include "../webserver/json_macros.h" #include "routes.h" #include "../database/gravity-db.h" +#include "../events.h" static int api_list_read(struct ftl_conn *api, const int code, @@ -226,6 +227,10 @@ static int api_list_write(struct ftl_conn *api, } // else: everything is okay + // Inform the resolver that it needs to reload the domainlists + set_event(RELOAD_GRAVITY); + logg("Setting event"); + int response_code = 201; // 201 - Created if(api->method == HTTP_PUT) response_code = 200; // 200 - OK @@ -241,6 +246,9 @@ static int api_list_remove(struct ftl_conn *api, const char *sql_msg = NULL; if(gravityDB_delFromTable(listtype, argument, &sql_msg)) { + // Inform the resolver that it needs to reload the domainlists + set_event(RELOAD_GRAVITY); + // Send empty reply with code 204 No Content JSON_SEND_OBJECT_CODE(json, 204); } From 8b3df9f5543ebdb0fd15dd591fe339440bfd9db8 Mon Sep 17 00:00:00 2001 From: DL6ER Date: Fri, 22 Jan 2021 15:25:49 +0100 Subject: [PATCH 0229/1669] Implement /api/clients Signed-off-by: DL6ER --- src/api/list.c | 121 +++++++++++++++++++++++++++----------- src/database/gravity-db.c | 34 +++++++++-- 2 files changed, 117 insertions(+), 38 deletions(-) diff --git a/src/api/list.c b/src/api/list.c index 15f5e0a67..98d321a34 100644 --- a/src/api/list.c +++ b/src/api/list.c @@ -14,6 +14,8 @@ #include "routes.h" #include "../database/gravity-db.h" #include "../events.h" +// getNameFromIP() +#include "../database/network-table.h" static int api_list_read(struct ftl_conn *api, const int code, @@ -67,6 +69,34 @@ static int api_list_read(struct ftl_conn *api, JSON_OBJ_ADD_NULL(item, "comment"); } } + else if(listtype == GRAVITY_CLIENTS) + { + if(row.ip != NULL) + { + JSON_OBJ_COPY_STR(item, "ip", row.ip); + char *name = getNameFromIP(row.ip); + if(name != NULL) + { + JSON_OBJ_COPY_STR(item, "name", name); + free(name); + } + else + { + JSON_OBJ_ADD_NULL(item, "name"); + } + } + else + { + JSON_OBJ_ADD_NULL(item, "ip"); + JSON_OBJ_ADD_NULL(item, "name"); + } + + if(row.comment != NULL) { + JSON_OBJ_COPY_STR(item, "comment", row.comment); + } else { + JSON_OBJ_ADD_NULL(item, "comment"); + } + } else // domainlists { JSON_OBJ_COPY_STR(item, "domain", row.domain); @@ -76,26 +106,30 @@ static int api_list_read(struct ftl_conn *api, } else { JSON_OBJ_ADD_NULL(item, "comment"); } - if(row.group_ids != NULL) { - // Black JSON magic at work here: - // We build a JSON array from the group_concat - // result delivered SQLite3, parse it as valid - // array and append it as item to the data - char group_ids_str[strlen(row.group_ids)+3u]; - group_ids_str[0] = '['; - strcpy(group_ids_str+1u , row.group_ids); - group_ids_str[sizeof(group_ids_str)-2u] = ']'; - group_ids_str[sizeof(group_ids_str)-1u] = '\0'; - cJSON * group_ids = cJSON_Parse(group_ids_str); - JSON_OBJ_ADD_ITEM(item, "groups", group_ids); - } else { - // Empty group set - cJSON *group_ids = JSON_NEW_ARRAY(); - JSON_OBJ_ADD_ITEM(item, "groups", group_ids); - } } - JSON_OBJ_ADD_BOOL(item, "enabled", row.enabled); + if(row.group_ids != NULL) { + // Black magic at work here: We build a JSON array from + // the group_concat result delivered from the database, + // parse it as valid array and append it as item to the + // data + logg("row.group_ids = %p \"%s\"", row.group_ids, row.group_ids); + char group_ids_str[strlen(row.group_ids)+3u]; + group_ids_str[0] = '['; + strcpy(group_ids_str+1u , row.group_ids); + group_ids_str[sizeof(group_ids_str)-2u] = ']'; + group_ids_str[sizeof(group_ids_str)-1u] = '\0'; + cJSON * group_ids = cJSON_Parse(group_ids_str); + JSON_OBJ_ADD_ITEM(item, "groups", group_ids); + } else { + // Empty group set + cJSON *group_ids = JSON_NEW_ARRAY(); + JSON_OBJ_ADD_ITEM(item, "groups", group_ids); + } + + // Clients don't have the enabled property + if(listtype != GRAVITY_CLIENTS) + JSON_OBJ_ADD_BOOL(item, "enabled", row.enabled); JSON_OBJ_ADD_NUMBER(item, "date_added", row.date_added); JSON_OBJ_ADD_NUMBER(item, "date_modified", row.date_modified); @@ -112,6 +146,8 @@ static int api_list_read(struct ftl_conn *api, objname = "groups"; else if(listtype == GRAVITY_ADLISTS) objname = "adlists"; + else if(listtype == GRAVITY_CLIENTS) + objname = "clients"; else // domainlists objname = "domains"; JSON_OBJ_ADD_ITEM(json, objname, items); @@ -149,22 +185,34 @@ static int api_list_write(struct ftl_conn *api, // Set argument row.argument = argument; - // Check if valid JSON payload is available - if (api->payload.json == NULL) { + // Check argument is not empty + if (strlen(argument) < 1) { return send_json_error(api, 400, "bad_request", - "Invalid request body data", + "Missing argument, check URI", NULL); } - cJSON *json_enabled = cJSON_GetObjectItemCaseSensitive(api->payload.json, "enabled"); - if (!cJSON_IsBool(json_enabled)) { + // Check if valid JSON payload is available + if (api->payload.json == NULL) { return send_json_error(api, 400, "bad_request", - "No \"enabled\" boolean in body data", + "Invalid request body data", NULL); } - row.enabled = cJSON_IsTrue(json_enabled); + + // Clients don't have the enabled property + if(listtype != GRAVITY_CLIENTS) + { + cJSON *json_enabled = cJSON_GetObjectItemCaseSensitive(api->payload.json, "enabled"); + if (!cJSON_IsBool(json_enabled)) { + return send_json_error(api, 400, + "bad_request", + "No \"enabled\" boolean in body data", + NULL); + } + row.enabled = cJSON_IsTrue(json_enabled); + } cJSON *json_comment = cJSON_GetObjectItemCaseSensitive(api->payload.json, "comment"); if(cJSON_IsString(json_comment) && strlen(json_comment->valuestring) > 0) @@ -184,22 +232,30 @@ static int api_list_write(struct ftl_conn *api, else row.oldtype = NULL; - // Try to add domain to table + // Try to add item to table const char *sql_msg = NULL; bool okay = false; if(gravityDB_addToTable(listtype, &row, &sql_msg, api->method)) { - cJSON *groups = cJSON_GetObjectItemCaseSensitive(api->payload.json, "groups"); - if(groups != NULL) - okay = gravityDB_edit_groups(listtype, groups, &row, &sql_msg); + if(listtype != GRAVITY_GROUPS) + { + cJSON *groups = cJSON_GetObjectItemCaseSensitive(api->payload.json, "groups"); + if(groups != NULL) + okay = gravityDB_edit_groups(listtype, groups, &row, &sql_msg); + else + // The groups array is optional, we still succeed if it + // is omitted (groups stay as they are) + okay = true; + } else - // The groups array is optional, we still succeed if it - // is omitted (groups stay as they are) + { + // Groups cannot be assigned to groups okay = true; + } } if(!okay) { - // Error adding domain, prepare error object + // Error adding item, prepare error object cJSON *json = JSON_NEW_OBJ(); JSON_OBJ_REF_STR(json, "argument", argument); JSON_OBJ_ADD_BOOL(json, "enabled", row.enabled); @@ -229,7 +285,6 @@ static int api_list_write(struct ftl_conn *api, // Inform the resolver that it needs to reload the domainlists set_event(RELOAD_GRAVITY); - logg("Setting event"); int response_code = 201; // 201 - Created if(api->method == HTTP_PUT) diff --git a/src/database/gravity-db.c b/src/database/gravity-db.c index f5ebfbb9a..3804d7d33 100644 --- a/src/database/gravity-db.c +++ b/src/database/gravity-db.c @@ -1430,6 +1430,8 @@ bool gravityDB_addToTable(const enum gravity_list_type listtype, tablerow *row, querystr = "INSERT INTO \"group\" (name,enabled,description) VALUES (:argument,:enabled,:description);"; else if(listtype == GRAVITY_ADLISTS) querystr = "INSERT INTO adlist (address,enabled,description) VALUES (:argument,:enabled,:description);"; + else if(listtype == GRAVITY_CLIENTS) + querystr = "INSERT INTO client (ip,comment) VALUES (:argument,:comment);"; else // domainlist querystr = "INSERT INTO domainlist (domain,type,enabled,comment) VALUES (:argument,:type,:enabled,:comment);"; } @@ -1446,6 +1448,11 @@ bool gravityDB_addToTable(const enum gravity_list_type listtype, tablerow *row, "VALUES (:argument,:enabled,:description," "(SELECT id FROM adlist WHERE address = :argument)," "(SELECT date_added FROM adlist WHERE address = :argument));"; + else if(listtype == GRAVITY_CLIENTS) + querystr = "REPLACE INTO client (ip,comment,id,date_added) " + "VALUES (:argument,:comment," + "(SELECT id FROM client WHERE ip = :argument)," + "(SELECT date_added FROM client WHERE ip = :argument));"; else // domainlist querystr = "REPLACE INTO domainlist (domain,type,enabled,comment,id,date_added) " "VALUES (:argument,:type,:enabled,:comment," @@ -1628,6 +1635,8 @@ bool gravityDB_delFromTable(const enum gravity_list_type listtype, const char* a querystr = "DELETE FROM \"group\" WHERE name = :argument;"; else if(listtype == GRAVITY_ADLISTS) querystr = "DELETE FROM adlist WHERE address = :argument;"; + else if(listtype == GRAVITY_CLIENTS) + querystr = "DELETE FROM client WHERE ip = :argument;"; else // domainlist querystr = "DELETE FROM domainlist WHERE domain = :argument AND type = :type;"; int rc = sqlite3_prepare_v2(gravity_db, querystr, -1, &stmt, NULL); @@ -1752,7 +1761,17 @@ bool gravityDB_readTable(const enum gravity_list_type listtype, const char *argu { if(argument != NULL && argument[0] != '\0') extra = " WHERE address = :argument;"; - sprintf(querystr, "SELECT id,address,enabled,date_added,date_modified,comment FROM adlist%s;", extra); + sprintf(querystr, "SELECT id,address,enabled,date_added,date_modified,comment," + "(SELECT GROUP_CONCAT(group_id) FROM adlist_by_group g WHERE g.adlist_id = a.id) AS group_ids " + "FROM adlist a%s;", extra); + } + else if(listtype == GRAVITY_CLIENTS) + { + if(argument != NULL && argument[0] != '\0') + extra = " WHERE ip = :argument;"; + sprintf(querystr, "SELECT id,ip,date_added,date_modified,comment," + "(SELECT GROUP_CONCAT(group_id) FROM client_by_group g WHERE g.client_id = c.id) AS group_ids " + "FROM client c%s;", extra); } else // domainlist { @@ -1799,6 +1818,9 @@ bool gravityDB_readTableGetRow(tablerow *row, const char **message) // Perform step const int rc = sqlite3_step(read_stmt); + // Ensure no old data stayed in here + memset(row, 0, sizeof(*row)); + // Valid row if(rc == SQLITE_ROW) { @@ -1857,6 +1879,10 @@ bool gravityDB_readTableGetRow(tablerow *row, const char **message) else if(strcasecmp(cname, "description") == 0) row->description = (char*)sqlite3_column_text(read_stmt, c); + + else if(strcasecmp(cname, "ip") == 0) + row->ip = (char*)sqlite3_column_text(read_stmt, c); + else logg("Internal API error: Encountered unknown column %s", cname); } @@ -1900,8 +1926,8 @@ bool gravityDB_edit_groups(const enum gravity_list_type listtype, cJSON *groups, return false; else if(listtype == GRAVITY_CLIENTS) { - get_querystr = "SELECT id FROM client WHERE name = :argument"; - del_querystr = "DELETE FROM client_by_group WHERE client_id = :id);"; + get_querystr = "SELECT id FROM client WHERE ip = :argument"; + del_querystr = "DELETE FROM client_by_group WHERE client_id = :id;"; add_querystr = "INSERT INTO client_by_group (client_id,group_id) VALUES (:id,:gid);"; } else if(listtype == GRAVITY_ADLISTS) @@ -1965,7 +1991,6 @@ bool gravityDB_edit_groups(const enum gravity_list_type listtype, cJSON *groups, { *message = sqlite3_errmsg(gravity_db); } - logg("SELECT: %i -> %i", rc, id); // Debug output if(config.debug & DEBUG_API) @@ -2015,7 +2040,6 @@ bool gravityDB_edit_groups(const enum gravity_list_type listtype, cJSON *groups, okay = false; *message = sqlite3_errmsg(gravity_db); } - logg("DELETE: %i", rc); // Debug output if(config.debug & DEBUG_API) From 5e616f4ac248046fac33c14b6b92408ffb958767 Mon Sep 17 00:00:00 2001 From: DL6ER Date: Sat, 23 Jan 2021 07:11:38 +0100 Subject: [PATCH 0230/1669] Fix a bug in CivetWeb server Signed-off-by: DL6ER --- src/civetweb/civetweb.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/civetweb/civetweb.c b/src/civetweb/civetweb.c index e4c3c6191..af8b63680 100644 --- a/src/civetweb/civetweb.c +++ b/src/civetweb/civetweb.c @@ -7428,7 +7428,6 @@ mg_printf(struct mg_connection *conn, const char *fmt, ...) return result; } - int mg_url_decode(const char *src, int src_len, @@ -8481,8 +8480,9 @@ remove_dot_segments(char *inout) in++; } while (in != in_ahead); } - } else if (*in == '/') { - /* replace // by / */ + } else if (*in == '/' && ((out_end != inout) && (out_end[-1] != ':'))) { + /* Replace // by / if not preseeded by `:` (there may be + * a protocol specifier in the URL) */ *out_end++ = '/'; do { in++; From f12aa58f3f085f6325542d42aefb7d10584dbd8c Mon Sep 17 00:00:00 2001 From: DL6ER Date: Sat, 23 Jan 2021 07:11:52 +0100 Subject: [PATCH 0231/1669] Rename /api/adlists -> /api/lists Signed-off-by: DL6ER --- src/api/ftl.c | 4 +- src/api/list.c | 4 +- src/api/routes.c | 4 +- src/api/stats.c | 1 - src/database/gravity-db.c | 98 +++++++++++++++++++++++++++++++++------ src/datastructure.c | 6 +-- src/shmem.h | 4 +- 7 files changed, 95 insertions(+), 26 deletions(-) diff --git a/src/api/ftl.c b/src/api/ftl.c index b7be994cc..13efe4d4a 100644 --- a/src/api/ftl.c +++ b/src/api/ftl.c @@ -378,7 +378,6 @@ int get_system_obj(struct ftl_conn *api, cJSON *system) cJSON *dns = JSON_NEW_OBJ(); const bool blocking = get_blockingstatus(); JSON_OBJ_ADD_BOOL(dns, "blocking", blocking); // same reply type as in /api/dns/status - JSON_OBJ_ADD_NUMBER(dns, "gravity_size", counters->gravity); JSON_OBJ_ADD_ITEM(system, "dns", dns); return 0; @@ -386,8 +385,9 @@ int get_system_obj(struct ftl_conn *api, cJSON *system) int get_ftl_obj(struct ftl_conn *api, cJSON *ftl) { + JSON_OBJ_ADD_NUMBER(ftl, "gravity", counters->database.gravity); JSON_OBJ_ADD_NUMBER(ftl, "groups", counters->database.groups); - JSON_OBJ_ADD_NUMBER(ftl, "adlists", counters->database.adlists); + JSON_OBJ_ADD_NUMBER(ftl, "lists", counters->database.lists); JSON_OBJ_ADD_NUMBER(ftl, "clients", counters->database.clients); cJSON *domains = JSON_NEW_OBJ(); JSON_OBJ_ADD_NUMBER(domains, "allowed", counters->database.domains.allowed); diff --git a/src/api/list.c b/src/api/list.c index 98d321a34..6892379e9 100644 --- a/src/api/list.c +++ b/src/api/list.c @@ -145,7 +145,7 @@ static int api_list_read(struct ftl_conn *api, if(listtype == GRAVITY_GROUPS) objname = "groups"; else if(listtype == GRAVITY_ADLISTS) - objname = "adlists"; + objname = "lists"; else if(listtype == GRAVITY_CLIENTS) objname = "clients"; else // domainlists @@ -344,7 +344,7 @@ int api_list(struct ftl_conn *api) listtype = GRAVITY_GROUPS; can_modify = true; } - else if((argument = startsWith("/api/adlists", api)) != NULL) + else if((argument = startsWith("/api/lists", api)) != NULL) { listtype = GRAVITY_ADLISTS; can_modify = true; diff --git a/src/api/routes.c b/src/api/routes.c index 5fa627d5f..5489ba780 100644 --- a/src/api/routes.c +++ b/src/api/routes.c @@ -45,7 +45,7 @@ int api_handler(struct mg_connection *conn, void *ignored) { ret = api_dns_cacheinfo(&api); } - /*********** /api/domains, /api/groups, /api/adlists, /api/clients *******/ + /************ /api/domains, /api/groups, /api/lists, /api/clients ********/ else if(startsWith("/api/domains", &api)) { ret = api_list(&api); @@ -54,7 +54,7 @@ int api_handler(struct mg_connection *conn, void *ignored) { ret = api_list(&api); } - else if(startsWith("/api/adlists", &api)) + else if(startsWith("/api/lists", &api)) { ret = api_list(&api); } diff --git a/src/api/stats.c b/src/api/stats.c index 76d913b69..45f36aa58 100644 --- a/src/api/stats.c +++ b/src/api/stats.c @@ -86,7 +86,6 @@ int api_stats_summary(struct ftl_conn *api) cJSON *json = JSON_NEW_OBJ(); const bool blocking = get_blockingstatus(); JSON_OBJ_ADD_BOOL(json, "blocking", blocking); // same reply type as in /api/dns/status - JSON_OBJ_ADD_NUMBER(json, "gravity_size", counters->gravity); JSON_OBJ_ADD_NUMBER(json, "blocked_queries", counters->blocked); JSON_OBJ_ADD_NUMBER(json, "percent_blocked", percent_blocked); JSON_OBJ_ADD_NUMBER(json, "unique_domains", counters->domains); diff --git a/src/database/gravity-db.c b/src/database/gravity-db.c index 3804d7d33..688880791 100644 --- a/src/database/gravity-db.c +++ b/src/database/gravity-db.c @@ -1429,7 +1429,7 @@ bool gravityDB_addToTable(const enum gravity_list_type listtype, tablerow *row, if(listtype == GRAVITY_GROUPS) querystr = "INSERT INTO \"group\" (name,enabled,description) VALUES (:argument,:enabled,:description);"; else if(listtype == GRAVITY_ADLISTS) - querystr = "INSERT INTO adlist (address,enabled,description) VALUES (:argument,:enabled,:description);"; + querystr = "INSERT INTO adlist (address,enabled,comment) VALUES (:argument,:enabled,:comment);"; else if(listtype == GRAVITY_CLIENTS) querystr = "INSERT INTO client (ip,comment) VALUES (:argument,:comment);"; else // domainlist @@ -1444,8 +1444,8 @@ bool gravityDB_addToTable(const enum gravity_list_type listtype, tablerow *row, "(SELECT id FROM \"group\" WHERE name = :argument)," "(SELECT date_added FROM \"group\" WHERE name = :argument));"; else if(listtype == GRAVITY_ADLISTS) - querystr = "REPLACE INTO adlist (address,enabled,description,id,date_added) " - "VALUES (:argument,:enabled,:description," + querystr = "REPLACE INTO adlist (address,enabled,comment,id,date_added) " + "VALUES (:argument,:enabled,:comment," "(SELECT id FROM adlist WHERE address = :argument)," "(SELECT date_added FROM adlist WHERE address = :argument));"; else if(listtype == GRAVITY_CLIENTS) @@ -1630,15 +1630,20 @@ bool gravityDB_delFromTable(const enum gravity_list_type listtype, const char* a // Prepare SQLite statement sqlite3_stmt* stmt = NULL; - const char *querystr = ""; + const char *querystr = "", *querystr2 = ""; if(listtype == GRAVITY_GROUPS) querystr = "DELETE FROM \"group\" WHERE name = :argument;"; else if(listtype == GRAVITY_ADLISTS) - querystr = "DELETE FROM adlist WHERE address = :argument;"; + { + // This is actually a two-step deletion to satisfy foreign-key constraints + querystr = "DELETE FROM gravity WHERE adlist_id = (SELECT id FROM adlist WHERE address = :argument);"; + querystr2 = "DELETE FROM adlist WHERE address = :argument;"; + } else if(listtype == GRAVITY_CLIENTS) querystr = "DELETE FROM client WHERE ip = :argument;"; else // domainlist querystr = "DELETE FROM domainlist WHERE domain = :argument AND type = :type;"; + int rc = sqlite3_prepare_v2(gravity_db, querystr, -1, &stmt, NULL); if( rc != SQLITE_OK ) { @@ -1649,11 +1654,11 @@ bool gravityDB_delFromTable(const enum gravity_list_type listtype, const char* a } // Bind domain to prepared statement (if requested) - int idx = sqlite3_bind_parameter_index(stmt, ":argument"); - if(idx > 0 && (rc = sqlite3_bind_text(stmt, idx, argument, -1, SQLITE_STATIC)) != SQLITE_OK) + int arg_idx = sqlite3_bind_parameter_index(stmt, ":argument"); + if(arg_idx > 0 && (rc = sqlite3_bind_text(stmt, arg_idx, argument, -1, SQLITE_STATIC)) != SQLITE_OK) { *message = sqlite3_errmsg(gravity_db); - logg("gravityDB_delFromTable(%d, %s): Failed to bind domain/name (error %d) - %s", + logg("gravityDB_delFromTable(%d, %s): Failed to bind argument (error %d) - %s", type, argument, rc, *message); sqlite3_reset(stmt); sqlite3_finalize(stmt); @@ -1661,8 +1666,8 @@ bool gravityDB_delFromTable(const enum gravity_list_type listtype, const char* a } // Bind type to prepared statement (if requested) - idx = sqlite3_bind_parameter_index(stmt, ":type"); - if(idx > 0 && (rc = sqlite3_bind_int(stmt, idx, type)) != SQLITE_OK) + int type_idx = sqlite3_bind_parameter_index(stmt, ":type"); + if(type_idx > 0 && (rc = sqlite3_bind_int(stmt, type_idx, type)) != SQLITE_OK) { *message = sqlite3_errmsg(gravity_db); logg("gravityDB_delFromTable(%d, %s): Failed to bind type (error %d) - %s", @@ -1676,15 +1681,17 @@ bool gravityDB_delFromTable(const enum gravity_list_type listtype, const char* a if(config.debug & DEBUG_API) { logg("SQL: %s", querystr); - logg(" :argument = \"%s\"", argument); - logg(" :type = \"%i\"", type); + if(arg_idx > 0) + logg(" :argument = \"%s\"", argument); + if(type_idx > 0) + logg(" :type = \"%i\"", type); } // Perform step bool okay = false; if((rc = sqlite3_step(stmt)) == SQLITE_DONE) { - // Domain removed + // Items removed okay = true; } else @@ -1692,10 +1699,73 @@ bool gravityDB_delFromTable(const enum gravity_list_type listtype, const char* a *message = sqlite3_errmsg(gravity_db); } - // Finalize statement and close database handle + // Finalize statement sqlite3_reset(stmt); sqlite3_finalize(stmt); + if(okay && listtype == GRAVITY_ADLISTS) + { + // We need to perform a second SQL request + rc = sqlite3_prepare_v2(gravity_db, querystr2, -1, &stmt, NULL); + if( rc != SQLITE_OK ) + { + *message = sqlite3_errmsg(gravity_db); + logg("gravityDB_delFromTable(%d, %s) - SQL error prepare 2 (%i): %s", + type, argument, rc, *message); + return false; + } + + // Bind domain to prepared statement (if requested) + arg_idx = sqlite3_bind_parameter_index(stmt, ":argument"); + if(arg_idx > 0 && (rc = sqlite3_bind_text(stmt, arg_idx, argument, -1, SQLITE_STATIC)) != SQLITE_OK) + { + *message = sqlite3_errmsg(gravity_db); + logg("gravityDB_delFromTable(%d, %s): Failed to bind argument (2) (error %d) - %s", + type, argument, rc, *message); + sqlite3_reset(stmt); + sqlite3_finalize(stmt); + return false; + } + + // Bind type to prepared statement (if requested) + type_idx = sqlite3_bind_parameter_index(stmt, ":type"); + if(type_idx > 0 && (rc = sqlite3_bind_int(stmt, type_idx, type)) != SQLITE_OK) + { + *message = sqlite3_errmsg(gravity_db); + logg("gravityDB_delFromTable(%d, %s): Failed to bind type (2) (error %d) - %s", + type, argument, rc, *message); + sqlite3_reset(stmt); + sqlite3_finalize(stmt); + return false; + } + + // Debug output + if(config.debug & DEBUG_API) + { + logg("SQL: %s", querystr2); + if(arg_idx > 0) + logg(" :argument = \"%s\"", argument); + if(type_idx > 0) + logg(" :type = \"%i\"", type); + } + + // Perform step + okay = false; + if((rc = sqlite3_step(stmt)) == SQLITE_DONE) + { + // Item removed + okay = true; + } + else + { + *message = sqlite3_errmsg(gravity_db); + } + + // Finalize statement + sqlite3_reset(stmt); + sqlite3_finalize(stmt); + } + return okay; } diff --git a/src/datastructure.c b/src/datastructure.c index ffdd9e660..046f57512 100644 --- a/src/datastructure.c +++ b/src/datastructure.c @@ -493,11 +493,11 @@ void FTL_reload_all_domainlists(void) // (Re-)open gravity database connection gravityDB_reopen(); - // Reset number of blocked domains - counters->gravity = gravityDB_count(GRAVITY_TABLE); + // Get size of gravity, number of domains, groups, clients, and lists + counters->database.gravity = gravityDB_count(GRAVITY_TABLE); counters->database.groups = gravityDB_count(GROUPS_TABLE); counters->database.clients = gravityDB_count(CLIENTS_TABLE); - counters->database.adlists = gravityDB_count(ADLISTS_TABLE); + counters->database.lists = gravityDB_count(ADLISTS_TABLE); counters->database.domains.allowed = gravityDB_count(DENIED_DOMAINS_TABLE); counters->database.domains.denied = gravityDB_count(ALLOWED_DOMAINS_TABLE); diff --git a/src/shmem.h b/src/shmem.h index 3ddc4dcaa..5ff66b5a5 100644 --- a/src/shmem.h +++ b/src/shmem.h @@ -49,7 +49,6 @@ typedef struct { int clients_MAX; int domains_MAX; int strings_MAX; - int gravity; int querytype[TYPE_MAX]; int reply_NODATA; int reply_NXDOMAIN; @@ -60,9 +59,10 @@ typedef struct { int dns_cache_MAX; unsigned int regex_change; struct { + int gravity; int clients; int groups; - int adlists; + int lists; struct{ int allowed; int denied; From 0358c3b861b8cdbfd5eb29b32bd458f6ada016ba Mon Sep 17 00:00:00 2001 From: DL6ER Date: Sat, 23 Jan 2021 08:31:21 +0100 Subject: [PATCH 0232/1669] Test compile regex before adding to the database (we may want to reject it) Signed-off-by: DL6ER --- src/api/list.c | 21 +++++++++- src/database/message-table.c | 3 ++ src/regex.c | 78 +++++++++++++++++++++--------------- src/regex_r.h | 1 + 4 files changed, 68 insertions(+), 35 deletions(-) diff --git a/src/api/list.c b/src/api/list.c index 6892379e9..7557ebc12 100644 --- a/src/api/list.c +++ b/src/api/list.c @@ -232,10 +232,18 @@ static int api_list_write(struct ftl_conn *api, else row.oldtype = NULL; + bool okay = true; + char *regex_msg = NULL; + if(listtype == GRAVITY_DOMAINLIST_ALLOW_REGEX || listtype == GRAVITY_DOMAINLIST_DENY_REGEX) + { + // Test validity of this regex + regexData regex = { 0 }; + okay = compile_regex(argument, ®ex, ®ex_msg); + } + // Try to add item to table const char *sql_msg = NULL; - bool okay = false; - if(gravityDB_addToTable(listtype, &row, &sql_msg, api->method)) + if(okay && gravityDB_addToTable(listtype, &row, &sql_msg, api->method)) { if(listtype != GRAVITY_GROUPS) { @@ -275,6 +283,15 @@ static int api_list_write(struct ftl_conn *api, JSON_OBJ_ADD_NULL(json, "sql_msg"); } + // Add regex error (may not be available) + if (regex_msg != NULL) { + JSON_OBJ_COPY_STR(json, "regex_msg", regex_msg); + free(regex_msg); + regex_msg = NULL; + } else { + JSON_OBJ_ADD_NULL(json, "regex_msg"); + } + // Send error reply return send_json_error(api, 400, // 400 Bad Request "database_error", diff --git a/src/database/message-table.c b/src/database/message-table.c index 936813d75..99cd555f2 100644 --- a/src/database/message-table.c +++ b/src/database/message-table.c @@ -228,6 +228,9 @@ static bool add_message(enum message_type type, const char *message, void logg_regex_warning(const char *type, const char *warning, const int dbindex, const char *regex) { + if(warning == NULL) + warning = "No further info available"; + // Log to pihole-FTL.log logg("REGEX WARNING: Invalid regex %s filter \"%s\": %s", type, regex, warning); diff --git a/src/regex.c b/src/regex.c index 1ec309c5e..9dbfc287f 100644 --- a/src/regex.c +++ b/src/regex.c @@ -95,11 +95,8 @@ unsigned int __attribute__((pure)) get_num_regex(const enum regex_type regexid) #define FTL_REGEX_SEP ";" /* Compile regular expressions into data structures that can be used with regexec() to match against a string */ -static bool compile_regex(const char *regexin, const enum regex_type regexid) +bool compile_regex(const char *regexin, regexData *regex, char **message) { - regexData *regex = get_regex_ptr(regexid); - int index = num_regex[regexid]++; - // Extract possible Pi-hole extensions char rgxbuf[strlen(regexin) + 1u]; // Parse special FTL syntax if present @@ -119,46 +116,49 @@ static bool compile_regex(const char *regexin, const enum regex_type regexid) if(sscanf(part, "querytype=%16s", extra)) { // Warn if specified more than one querytype option - if(regex[index].query_type != 0) - logg_regex_warning(regextype[regexid], - "Overwriting previous querytype setting", - regex[index].database_id, regexin); + if(regex->query_type != 0) + { + *message = strdup("Overwriting previous querytype setting"); + return false; + } // Test input string against all implemented query types - for(enum query_types type = TYPE_A; type < TYPE_MAX; type++) + for(enum query_types qtype = TYPE_A; qtype < TYPE_MAX; qtype++) { // Check for querytype - if(strcasecmp(extra, querytypes[type]) == 0) + if(strcasecmp(extra, querytypes[qtype]) == 0) { - regex[index].query_type = type; - regex[index].query_type_inverted = false; + regex->query_type = qtype; + regex->query_type_inverted = false; break; } // Check for INVERTED querytype - else if(extra[0] == '!' && strcasecmp(extra + 1u, querytypes[type]) == 0) + else if(extra[0] == '!' && strcasecmp(extra + 1u, querytypes[qtype]) == 0) { - regex[index].query_type = type; - regex[index].query_type_inverted = true; + regex->query_type = qtype; + regex->query_type_inverted = true; break; } } // Nothing found - if(regex[index].query_type == 0) - logg_regex_warning(regextype[regexid], "Unknown querytype", - regex[index].database_id, regexin); + if(regex->query_type == 0) + { + *message = strdup("Unknown querytype"); + return false; + } // Debug output else if(config.debug & DEBUG_REGEX) { logg(" This regex will %s match query type %s", - regex[index].query_type_inverted ? "NOT" : "ONLY", - querytypes[regex[index].query_type]); + regex->query_type_inverted ? "NOT" : "ONLY", + querytypes[regex->query_type]); } } // option: ";invert" else if(strcasecmp(part, "invert") == 0) { - regex[index].inverted = true; + regex->inverted = true; // Debug output if(config.debug & DEBUG_REGEX) @@ -181,22 +181,20 @@ static bool compile_regex(const char *regexin, const enum regex_type regexid) // We use the extended RegEx flavor (ERE) and specify that matching should // always be case INsensitive - const int errcode = regcomp(®ex[index].regex, rgxbuf, REG_EXTENDED | REG_ICASE | REG_NOSUB); + const int errcode = regcomp(®ex->regex, rgxbuf, REG_EXTENDED | REG_ICASE | REG_NOSUB); if(errcode != 0) { // Get error string and log it - const size_t length = regerror(errcode, ®ex[index].regex, NULL, 0); - char *buffer = calloc(length, sizeof(char)); - (void) regerror (errcode, ®ex[index].regex, buffer, length); - logg_regex_warning(regextype[regexid], buffer, regex[index].database_id, regexin); - free(buffer); - regex[index].available = false; + const size_t length = regerror(errcode, ®ex->regex, NULL, 0); + *message = calloc(length, sizeof(char)); + (void) regerror (errcode, ®ex->regex, *message, length); + regex->available = false; return false; } // Store compiled regex string in buffer - regex[index].string = strdup(regexin); - regex[index].available = true; + regex->string = strdup(regexin); + regex->available = true; return true; } @@ -510,7 +508,16 @@ static void read_regex_table(const enum regex_type regexid) regextype[regexid], num_regex[regexid], rowid, domain); } - compile_regex(domain, regexid); + regexData *this_regex = get_regex_ptr(regexid); + const int index = num_regex[regexid]++; + char *message = NULL; + if(!compile_regex(domain, &this_regex[index], &message) && message != NULL) + { + logg_regex_warning(regextype[regexid], message, + regex->database_id, domain); + free(message); + } + regex[num_regex[regexid]-1].database_id = rowid; // Signal other forks that the regex data has changed and should be updated @@ -614,13 +621,18 @@ int regex_test(const bool debug_mode, const bool quiet, const char *domainin, co { // Compile CLI regex logg("%s Compiling regex filter...", cli_info()); - cli_regex = calloc(1, sizeof(regexData)); + regexData regex = { 0 }; // Compile CLI regex timer_start(REGEX_TIMER); log_ctrl(false, true); // Temporarily re-enable terminal output for error logging - if(!compile_regex(regexin, REGEX_CLI)) + char *message = NULL; + if(!compile_regex(regexin, ®ex, &message) && message != NULL) + { + logg_regex_warning("CLI", message, 0, regexin); + free(message); return EXIT_FAILURE; + } log_ctrl(false, !quiet); // Re-apply quiet option after compilation logg(" Compiled regex filter in %.3f msec\n", timer_elapsed_msec(REGEX_TIMER)); diff --git a/src/regex_r.h b/src/regex_r.h index 45b56303c..3f9a7849c 100644 --- a/src/regex_r.h +++ b/src/regex_r.h @@ -38,6 +38,7 @@ typedef struct { } regexData; ASSERT_SIZEOF(regexData, 32, 20, 20); +bool compile_regex(const char *regexin, regexData *regex, char **message); unsigned int get_num_regex(const enum regex_type regexid) __attribute__((pure)); int match_regex(const char *input, const DNSCacheData* dns_cache, const int clientID, const enum regex_type regexid, const bool regextest); From efd5951968d81101d320436f514af1bc7ce9a093 Mon Sep 17 00:00:00 2001 From: DL6ER Date: Sun, 24 Jan 2021 16:07:27 +0100 Subject: [PATCH 0233/1669] POST should not include the target to get pushed Signed-off-by: DL6ER --- src/api/list.c | 35 ++++++++++++++++++++++++++++++++--- src/database/gravity-db.c | 11 ++++++++--- src/database/gravity-db.h | 1 + 3 files changed, 41 insertions(+), 6 deletions(-) diff --git a/src/api/list.c b/src/api/list.c index 7557ebc12..a7da014fc 100644 --- a/src/api/list.c +++ b/src/api/list.c @@ -186,7 +186,7 @@ static int api_list_write(struct ftl_conn *api, row.argument = argument; // Check argument is not empty - if (strlen(argument) < 1) { + if (api->method == HTTP_PUT && strlen(argument) < 1) { return send_json_error(api, 400, "bad_request", "Missing argument, check URI", @@ -214,6 +214,19 @@ static int api_list_write(struct ftl_conn *api, row.enabled = cJSON_IsTrue(json_enabled); } + cJSON *json_item = cJSON_GetObjectItemCaseSensitive(api->payload.json, "item"); + if(cJSON_IsString(json_item) && strlen(json_item->valuestring) > 0) + row.item = json_item->valuestring; + else if(api->method == HTTP_PUT) + row.item = NULL; + else + { + return send_json_error(api, 400, + "bad_request", + "No \"item\" string in body data", + NULL); + } + cJSON *json_comment = cJSON_GetObjectItemCaseSensitive(api->payload.json, "comment"); if(cJSON_IsString(json_comment) && strlen(json_comment->valuestring) > 0) row.comment = json_comment->valuestring; @@ -416,13 +429,29 @@ int api_list(struct ftl_conn *api) if(api->method == HTTP_GET) { + // Read list item identified by URI (or read them all) return api_list_read(api, 200, listtype, argument); } - else if(can_modify && (api->method == HTTP_POST || api->method == HTTP_PUT)) + else if(can_modify && api->method == HTTP_PUT) { - // Add item from list + // Add/update item identified by URI return api_list_write(api, listtype, argument, payload); } + else if(can_modify && api->method == HTTP_POST) + { + // Add item to list identified by payload + if(strlen(argument) != 0) + { + cJSON *json = JSON_NEW_OBJ(); + JSON_OBJ_REF_STR(json, "uri_param", argument); + return send_json_error(api, 400, + "bad_request", + "Invalid request: Specify item in payload, not as URI parameter", + json); + } + else + return api_list_write(api, listtype, argument, payload); + } else if(can_modify && api->method == HTTP_DELETE) { // Delete item from list diff --git a/src/database/gravity-db.c b/src/database/gravity-db.c index 688880791..01914434d 100644 --- a/src/database/gravity-db.c +++ b/src/database/gravity-db.c @@ -1426,6 +1426,8 @@ bool gravityDB_addToTable(const enum gravity_list_type listtype, tablerow *row, const char *querystr; if(method == HTTP_POST) // Create NEW entry, error if existing { + // The item is the argument for all POST requests + row->argument = row->item; if(listtype == GRAVITY_GROUPS) querystr = "INSERT INTO \"group\" (name,enabled,description) VALUES (:argument,:enabled,:description);"; else if(listtype == GRAVITY_ADLISTS) @@ -1435,9 +1437,10 @@ bool gravityDB_addToTable(const enum gravity_list_type listtype, tablerow *row, else // domainlist querystr = "INSERT INTO domainlist (domain,type,enabled,comment) VALUES (:argument,:type,:enabled,:comment);"; } - else // Create new or replace existing entry, no error if existing - // We have to use a subquery here to avoid violating FOREIGN KEY - // contraints (REPLACE recreates (= new ID) entries instead of updating them) + else + { // Create new or replace existing entry, no error if existing + // We have to use a subquery here to avoid violating FOREIGN KEY + // contraints (REPLACE recreates (= new ID) entries instead of updating them) if(listtype == GRAVITY_GROUPS) querystr = "REPLACE INTO \"group\" (name,enabled,description,id,date_added) " "VALUES (:argument,:enabled,:description," @@ -1458,6 +1461,8 @@ bool gravityDB_addToTable(const enum gravity_list_type listtype, tablerow *row, "VALUES (:argument,:type,:enabled,:comment," "(SELECT id FROM domainlist WHERE domain = :argument and type = :oldtype)," "(SELECT date_added FROM domainlist WHERE domain = :argument and type = :oldtype));"; + } + int rc = sqlite3_prepare_v2(gravity_db, querystr, -1, &stmt, NULL); if( rc != SQLITE_OK ) { diff --git a/src/database/gravity-db.h b/src/database/gravity-db.h index 9ad0adb5e..ac50835c6 100644 --- a/src/database/gravity-db.h +++ b/src/database/gravity-db.h @@ -31,6 +31,7 @@ typedef struct { const char *argument; const char *oldtype; const char *ip; + const char *item; long id; time_t date_added; time_t date_modified; From e8e4c5f180dab94e80b0962f32ccf309cbdafa05 Mon Sep 17 00:00:00 2001 From: DL6ER Date: Mon, 25 Jan 2021 19:34:36 +0100 Subject: [PATCH 0234/1669] Move id, date_added, date_modified into extra database object Signed-off-by: DL6ER --- src/api/list.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/api/list.c b/src/api/list.c index a7da014fc..388e66772 100644 --- a/src/api/list.c +++ b/src/api/list.c @@ -48,7 +48,6 @@ static int api_list_read(struct ftl_conn *api, while(gravityDB_readTableGetRow(&row, &sql_msg)) { cJSON *item = JSON_NEW_OBJ(); - JSON_OBJ_ADD_NUMBER(item, "id", row.id); // Special fields if(listtype == GRAVITY_GROUPS) @@ -130,8 +129,12 @@ static int api_list_read(struct ftl_conn *api, // Clients don't have the enabled property if(listtype != GRAVITY_CLIENTS) JSON_OBJ_ADD_BOOL(item, "enabled", row.enabled); - JSON_OBJ_ADD_NUMBER(item, "date_added", row.date_added); - JSON_OBJ_ADD_NUMBER(item, "date_modified", row.date_modified); + + cJSON *database = JSON_NEW_OBJ(); + JSON_OBJ_ADD_NUMBER(database, "id", row.id); + JSON_OBJ_ADD_NUMBER(database, "date_added", row.date_added); + JSON_OBJ_ADD_NUMBER(database, "date_modified", row.date_modified); + JSON_OBJ_ADD_ITEM(item, "database", database); JSON_ARRAY_ADD_ITEM(items, item); } From fa4c1940452f0d431972ddd3becb52ff515aa3e8 Mon Sep 17 00:00:00 2001 From: DL6ER Date: Mon, 25 Jan 2021 20:10:43 +0100 Subject: [PATCH 0235/1669] Improve URI matching algorithm Signed-off-by: DL6ER --- src/api/list.c | 97 ++++++++++++++++++------------------- src/database/gravity-db.c | 1 - src/database/gravity-db.h | 1 - src/webserver/http-common.c | 8 ++- 4 files changed, 54 insertions(+), 53 deletions(-) diff --git a/src/api/list.c b/src/api/list.c index 388e66772..013a5eb48 100644 --- a/src/api/list.c +++ b/src/api/list.c @@ -205,24 +205,16 @@ static int api_list_write(struct ftl_conn *api, } // Clients don't have the enabled property - if(listtype != GRAVITY_CLIENTS) - { - cJSON *json_enabled = cJSON_GetObjectItemCaseSensitive(api->payload.json, "enabled"); - if (!cJSON_IsBool(json_enabled)) { - return send_json_error(api, 400, - "bad_request", - "No \"enabled\" boolean in body data", - NULL); - } + cJSON *json_enabled = cJSON_GetObjectItemCaseSensitive(api->payload.json, "enabled"); + if (cJSON_IsBool(json_enabled)) row.enabled = cJSON_IsTrue(json_enabled); - } + else + row.enabled = true; // Default value cJSON *json_item = cJSON_GetObjectItemCaseSensitive(api->payload.json, "item"); if(cJSON_IsString(json_item) && strlen(json_item->valuestring) > 0) - row.item = json_item->valuestring; - else if(api->method == HTTP_PUT) - row.item = NULL; - else + row.argument = json_item->valuestring; + else if (api->method != HTTP_PUT) // PUT uses the URI argument { return send_json_error(api, 400, "bad_request", @@ -234,19 +226,19 @@ static int api_list_write(struct ftl_conn *api, if(cJSON_IsString(json_comment) && strlen(json_comment->valuestring) > 0) row.comment = json_comment->valuestring; else - row.comment = NULL; + row.comment = NULL; // Default value cJSON *json_description = cJSON_GetObjectItemCaseSensitive(api->payload.json, "description"); if(cJSON_IsString(json_description) && strlen(json_description->valuestring) > 0) row.description = json_description->valuestring; else - row.description = NULL; + row.description = NULL; // Default value cJSON *json_oldtype = cJSON_GetObjectItemCaseSensitive(api->payload.json, "oldtype"); if(cJSON_IsString(json_oldtype) && strlen(json_oldtype->valuestring) > 0) row.oldtype = json_oldtype->valuestring; else - row.oldtype = NULL; + row.oldtype = NULL; // Default value bool okay = true; char *regex_msg = NULL; @@ -387,47 +379,54 @@ int api_list(struct ftl_conn *api) listtype = GRAVITY_CLIENTS; can_modify = true; } + else if((argument = startsWith("/api/domains/allow/exact", api)) != NULL) + { + listtype = GRAVITY_DOMAINLIST_ALLOW_EXACT; + can_modify = true; + } + else if((argument = startsWith("/api/domains/allow/regex", api)) != NULL) + { + listtype = GRAVITY_DOMAINLIST_ALLOW_REGEX; + can_modify = true; + } else if((argument = startsWith("/api/domains/allow", api)) != NULL) { - if((argument = startsWith("/api/domains/allow/exact", api)) != NULL) - { - listtype = GRAVITY_DOMAINLIST_ALLOW_EXACT; - can_modify = true; - } - else if((argument = startsWith("/api/domains/allow/regex", api)) != NULL) - { - listtype = GRAVITY_DOMAINLIST_ALLOW_REGEX; - can_modify = true; - } - else listtype = GRAVITY_DOMAINLIST_ALLOW_ALL; } + else if((argument = startsWith("/api/domains/deny/exact", api)) != NULL) + { + listtype = GRAVITY_DOMAINLIST_DENY_EXACT; + can_modify = true; + } + else if((argument = startsWith("/api/domains/deny/regex", api)) != NULL) + { + listtype = GRAVITY_DOMAINLIST_DENY_REGEX; + can_modify = true; + } else if((argument = startsWith("/api/domains/deny", api)) != NULL) { - if((argument = startsWith("/api/domains/deny/exact", api)) != NULL) - { - listtype = GRAVITY_DOMAINLIST_DENY_EXACT; - can_modify = true; - } - else if((argument = startsWith("/api/domains/deny/regex", api)) != NULL) - { - listtype = GRAVITY_DOMAINLIST_DENY_REGEX; - can_modify = true; - } - else - listtype = GRAVITY_DOMAINLIST_DENY_ALL; + listtype = GRAVITY_DOMAINLIST_DENY_ALL; + } + else if((argument = startsWith("/api/domains/exact", api)) != NULL) + { + listtype = GRAVITY_DOMAINLIST_ALL_EXACT; + } + else if((argument = startsWith("/api/domains/regex", api)) != NULL) + { + listtype = GRAVITY_DOMAINLIST_ALL_REGEX; + } + else if((argument = startsWith("/api/domains", api)) != NULL) + { + listtype = GRAVITY_DOMAINLIST_ALL_ALL; } else { - if((argument = startsWith("/api/domains/exact", api)) != NULL) - listtype = GRAVITY_DOMAINLIST_ALL_EXACT; - else if((argument = startsWith("/api/domains/regex", api)) != NULL) - listtype = GRAVITY_DOMAINLIST_ALL_REGEX; - else - { - argument = startsWith("/api/domains", api); - listtype = GRAVITY_DOMAINLIST_ALL_ALL; - } + cJSON *json = JSON_NEW_OBJ(); + JSON_OBJ_REF_STR(json, "uri", api->request->local_uri); + return send_json_error(api, 400, + "bad_request", + "Invalid request: Specified endpoint not available", + json); } if(api->method == HTTP_GET) diff --git a/src/database/gravity-db.c b/src/database/gravity-db.c index 01914434d..c7ee2c4cf 100644 --- a/src/database/gravity-db.c +++ b/src/database/gravity-db.c @@ -1427,7 +1427,6 @@ bool gravityDB_addToTable(const enum gravity_list_type listtype, tablerow *row, if(method == HTTP_POST) // Create NEW entry, error if existing { // The item is the argument for all POST requests - row->argument = row->item; if(listtype == GRAVITY_GROUPS) querystr = "INSERT INTO \"group\" (name,enabled,description) VALUES (:argument,:enabled,:description);"; else if(listtype == GRAVITY_ADLISTS) diff --git a/src/database/gravity-db.h b/src/database/gravity-db.h index ac50835c6..9ad0adb5e 100644 --- a/src/database/gravity-db.h +++ b/src/database/gravity-db.h @@ -31,7 +31,6 @@ typedef struct { const char *argument; const char *oldtype; const char *ip; - const char *item; long id; time_t date_added; time_t date_modified; diff --git a/src/webserver/http-common.c b/src/webserver/http-common.c index 9f2963762..0f6ed6b8e 100644 --- a/src/webserver/http-common.c +++ b/src/webserver/http-common.c @@ -146,9 +146,13 @@ const char* __attribute__((pure)) startsWith(const char *path, const struct ftl_ if(api->request->local_uri[strlen(path)] == '/') // Path match with argument after ".../" return api->request->local_uri + strlen(path) + 1u; - else - // Path match without argument + else if(strlen(path) == strlen(api->request->local_uri)) + // Path match directly, no argument return ""; + else + // Further components in URL, assume this did't match, e.g. + // /api/domains/regex[123].com + return NULL; else // Path does not match return NULL; From c1f29ed978c20eedbacb4b38f548e706b74c7b96 Mon Sep 17 00:00:00 2001 From: DL6ER Date: Mon, 25 Jan 2021 20:25:52 +0100 Subject: [PATCH 0236/1669] Improving error reporting of the API Signed-off-by: DL6ER --- src/api/list.c | 56 +++++++++++++++++++++++++++++++------ src/api/routes.c | 8 ++++++ src/webserver/http-common.c | 11 +++++++- src/webserver/http-common.h | 3 +- 4 files changed, 67 insertions(+), 11 deletions(-) diff --git a/src/api/list.c b/src/api/list.c index 013a5eb48..8845c5d60 100644 --- a/src/api/list.c +++ b/src/api/list.c @@ -200,7 +200,7 @@ static int api_list_write(struct ftl_conn *api, if (api->payload.json == NULL) { return send_json_error(api, 400, "bad_request", - "Invalid request body data", + "Invalid request body data (no valid JSON)", NULL); } @@ -251,7 +251,7 @@ static int api_list_write(struct ftl_conn *api, // Try to add item to table const char *sql_msg = NULL; - if(okay && gravityDB_addToTable(listtype, &row, &sql_msg, api->method)) + if(okay && (okay = gravityDB_addToTable(listtype, &row, &sql_msg, api->method))) { if(listtype != GRAVITY_GROUPS) { @@ -273,7 +273,7 @@ static int api_list_write(struct ftl_conn *api, { // Error adding item, prepare error object cJSON *json = JSON_NEW_OBJ(); - JSON_OBJ_REF_STR(json, "argument", argument); + JSON_OBJ_REF_STR(json, "item", row.argument); JSON_OBJ_ADD_BOOL(json, "enabled", row.enabled); if(row.comment != NULL) JSON_OBJ_REF_STR(json, "comment", row.comment); @@ -315,7 +315,7 @@ static int api_list_write(struct ftl_conn *api, if(api->method == HTTP_PUT) response_code = 200; // 200 - OK // Send GET style reply - return api_list_read(api, response_code, listtype, argument); + return api_list_read(api, response_code, listtype, row.argument); } static int api_list_remove(struct ftl_conn *api, @@ -437,6 +437,24 @@ int api_list(struct ftl_conn *api) else if(can_modify && api->method == HTTP_PUT) { // Add/update item identified by URI + if(strlen(argument) == 0) + { + cJSON *uri = JSON_NEW_OBJ(); + if(api->action_path != NULL) + { + JSON_OBJ_REF_STR(uri, "path", api->action_path); + } + else + { + JSON_OBJ_ADD_NULL(uri, "path"); + } + JSON_OBJ_REF_STR(uri, "item", argument); + return send_json_error(api, 400, + "bad_request", + "Invalid request: Specify item in URI", + uri); + } + else return api_list_write(api, listtype, argument, payload); } else if(can_modify && api->method == HTTP_POST) @@ -444,12 +462,20 @@ int api_list(struct ftl_conn *api) // Add item to list identified by payload if(strlen(argument) != 0) { - cJSON *json = JSON_NEW_OBJ(); - JSON_OBJ_REF_STR(json, "uri_param", argument); + cJSON *uri = JSON_NEW_OBJ(); + if(api->action_path != NULL) + { + JSON_OBJ_REF_STR(uri, "path", api->action_path); + } + else + { + JSON_OBJ_ADD_NULL(uri, "path"); + } + JSON_OBJ_REF_STR(uri, "item", argument); return send_json_error(api, 400, "bad_request", "Invalid request: Specify item in payload, not as URI parameter", - json); + uri); } else return api_list_write(api, listtype, argument, payload); @@ -462,10 +488,22 @@ int api_list(struct ftl_conn *api) else if(!can_modify) { // This list type cannot be modified (e.g., ALL_ALL) + cJSON *uri = JSON_NEW_OBJ(); + if(api->action_path != NULL) + { + JSON_OBJ_REF_STR(uri, "path", api->action_path); + } + else + { + JSON_OBJ_ADD_NULL(uri, "path"); + } + JSON_OBJ_REF_STR(uri, "item", argument); + cJSON *data = JSON_NEW_OBJ(); + JSON_OBJ_ADD_ITEM(data, "uri", uri); return send_json_error(api, 400, "bad_request", - "Invalid request: Specify list to modify", - NULL); + "Invalid request: Specify list to modify more precisely", + data); } else { diff --git a/src/api/routes.c b/src/api/routes.c index 5489ba780..d76905f3a 100644 --- a/src/api/routes.c +++ b/src/api/routes.c @@ -29,6 +29,7 @@ int api_handler(struct mg_connection *conn, void *ignored) conn, mg_get_request_info(conn), http_method(conn), + NULL, { 0 } }; read_and_parse_payload(&api); @@ -195,6 +196,13 @@ int api_handler(struct mg_connection *conn, void *ignored) api.payload.json = NULL; } + // Free action path (if allocated) + if(api.action_path != NULL) + { + free(api.action_path); + api.action_path = NULL; + } + // Unlock after API access unlock_shm(); diff --git a/src/webserver/http-common.c b/src/webserver/http-common.c index 0f6ed6b8e..f2c456078 100644 --- a/src/webserver/http-common.c +++ b/src/webserver/http-common.c @@ -140,19 +140,28 @@ bool get_uint_var(const char *source, const char *var, unsigned int *num) return false; } -const char* __attribute__((pure)) startsWith(const char *path, const struct ftl_conn *api) +const char* __attribute__((pure)) startsWith(const char *path, struct ftl_conn *api) { if(strncmp(path, api->request->local_uri, strlen(path)) == 0) if(api->request->local_uri[strlen(path)] == '/') + { // Path match with argument after ".../" + api->action_path = strdup(api->request->local_uri); + api->action_path[strlen(path)] = '\0'; return api->request->local_uri + strlen(path) + 1u; + } else if(strlen(path) == strlen(api->request->local_uri)) + { // Path match directly, no argument + api->action_path = strdup(api->request->local_uri); return ""; + } else + { // Further components in URL, assume this did't match, e.g. // /api/domains/regex[123].com return NULL; + } else // Path does not match return NULL; diff --git a/src/webserver/http-common.h b/src/webserver/http-common.h index 30c5f6ca2..1451f9630 100644 --- a/src/webserver/http-common.h +++ b/src/webserver/http-common.h @@ -24,6 +24,7 @@ struct ftl_conn { struct mg_connection *conn; const struct mg_request_info *request; const enum http_method method; + char *action_path; struct { bool avail :1; char raw[MAX_PAYLOAD_BYTES]; @@ -59,7 +60,7 @@ bool get_int_var(const char *source, const char *var, int *num); // Utils enum http_method __attribute__((pure)) http_method(struct mg_connection *conn); -const char* __attribute__((pure)) startsWith(const char *path, const struct ftl_conn *api); +const char* __attribute__((pure)) startsWith(const char *path, struct ftl_conn *api); void read_and_parse_payload(struct ftl_conn *api); #endif // HTTP_H From 54cd12e8951a519b18e77dc8af281dbfe260995f Mon Sep 17 00:00:00 2001 From: DL6ER Date: Mon, 25 Jan 2021 20:38:52 +0100 Subject: [PATCH 0237/1669] Split type and kind into two fields for domains Signed-off-by: DL6ER --- src/api/list.c | 46 ++++++++++++++--------------- src/database/gravity-db.c | 61 +++++++++++++++++++++++++++------------ src/database/gravity-db.h | 4 ++- 3 files changed, 69 insertions(+), 42 deletions(-) diff --git a/src/api/list.c b/src/api/list.c index 8845c5d60..60d36842b 100644 --- a/src/api/list.c +++ b/src/api/list.c @@ -72,7 +72,7 @@ static int api_list_read(struct ftl_conn *api, { if(row.ip != NULL) { - JSON_OBJ_COPY_STR(item, "ip", row.ip); + JSON_OBJ_COPY_STR(item, "client", row.ip); char *name = getNameFromIP(row.ip); if(name != NULL) { @@ -100,6 +100,7 @@ static int api_list_read(struct ftl_conn *api, { JSON_OBJ_COPY_STR(item, "domain", row.domain); JSON_OBJ_REF_STR(item, "type", row.type); + JSON_OBJ_REF_STR(item, "kind", row.kind); if(row.comment != NULL) { JSON_OBJ_COPY_STR(item, "comment", row.comment); } else { @@ -130,11 +131,10 @@ static int api_list_read(struct ftl_conn *api, if(listtype != GRAVITY_CLIENTS) JSON_OBJ_ADD_BOOL(item, "enabled", row.enabled); - cJSON *database = JSON_NEW_OBJ(); - JSON_OBJ_ADD_NUMBER(database, "id", row.id); - JSON_OBJ_ADD_NUMBER(database, "date_added", row.date_added); - JSON_OBJ_ADD_NUMBER(database, "date_modified", row.date_modified); - JSON_OBJ_ADD_ITEM(item, "database", database); + // Add read-only database parameters + JSON_OBJ_ADD_NUMBER(item, "id", row.id); + JSON_OBJ_ADD_NUMBER(item, "date_added", row.date_added); + JSON_OBJ_ADD_NUMBER(item, "date_modified", row.date_modified); JSON_ARRAY_ADD_ITEM(items, item); } @@ -188,14 +188,6 @@ static int api_list_write(struct ftl_conn *api, // Set argument row.argument = argument; - // Check argument is not empty - if (api->method == HTTP_PUT && strlen(argument) < 1) { - return send_json_error(api, 400, - "bad_request", - "Missing argument, check URI", - NULL); - } - // Check if valid JSON payload is available if (api->payload.json == NULL) { return send_json_error(api, 400, @@ -240,13 +232,19 @@ static int api_list_write(struct ftl_conn *api, else row.oldtype = NULL; // Default value + cJSON *json_oldkind = cJSON_GetObjectItemCaseSensitive(api->payload.json, "oldkind"); + if(cJSON_IsString(json_oldkind) && strlen(json_oldkind->valuestring) > 0) + row.oldkind = json_oldkind->valuestring; + else + row.oldkind = NULL; // Default value + bool okay = true; char *regex_msg = NULL; if(listtype == GRAVITY_DOMAINLIST_ALLOW_REGEX || listtype == GRAVITY_DOMAINLIST_DENY_REGEX) { // Test validity of this regex regexData regex = { 0 }; - okay = compile_regex(argument, ®ex, ®ex_msg); + okay = compile_regex(row.argument, ®ex, ®ex_msg); } // Try to add item to table @@ -292,18 +290,22 @@ static int api_list_write(struct ftl_conn *api, } // Add regex error (may not be available) + const char *errortype = "database_error"; + const char *errormsg = "Could not add to gravity database"; if (regex_msg != NULL) { JSON_OBJ_COPY_STR(json, "regex_msg", regex_msg); free(regex_msg); regex_msg = NULL; + errortype = "regex_error"; + errormsg = "Regex validation failed"; } else { JSON_OBJ_ADD_NULL(json, "regex_msg"); } // Send error reply return send_json_error(api, 400, // 400 Bad Request - "database_error", - "Could not add to gravity database", + errortype, + errormsg, json); } // else: everything is okay @@ -450,7 +452,7 @@ int api_list(struct ftl_conn *api) } JSON_OBJ_REF_STR(uri, "item", argument); return send_json_error(api, 400, - "bad_request", + "uri_error", "Invalid request: Specify item in URI", uri); } @@ -473,7 +475,7 @@ int api_list(struct ftl_conn *api) } JSON_OBJ_REF_STR(uri, "item", argument); return send_json_error(api, 400, - "bad_request", + "uri_error", "Invalid request: Specify item in payload, not as URI parameter", uri); } @@ -498,12 +500,10 @@ int api_list(struct ftl_conn *api) JSON_OBJ_ADD_NULL(uri, "path"); } JSON_OBJ_REF_STR(uri, "item", argument); - cJSON *data = JSON_NEW_OBJ(); - JSON_OBJ_ADD_ITEM(data, "uri", uri); return send_json_error(api, 400, - "bad_request", + "uri_error", "Invalid request: Specify list to modify more precisely", - data); + uri); } else { diff --git a/src/database/gravity-db.c b/src/database/gravity-db.c index c7ee2c4cf..23afcc702 100644 --- a/src/database/gravity-db.c +++ b/src/database/gravity-db.c @@ -1458,8 +1458,8 @@ bool gravityDB_addToTable(const enum gravity_list_type listtype, tablerow *row, else // domainlist querystr = "REPLACE INTO domainlist (domain,type,enabled,comment,id,date_added) " "VALUES (:argument,:type,:enabled,:comment," - "(SELECT id FROM domainlist WHERE domain = :argument and type = :oldtype)," - "(SELECT date_added FROM domainlist WHERE domain = :argument and type = :oldtype));"; + "(SELECT id FROM domainlist WHERE domain = :argument AND type = :oldtype)," + "(SELECT date_added FROM domainlist WHERE domain = :argument AND type = :oldtype));"; } int rc = sqlite3_prepare_v2(gravity_db, querystr, -1, &stmt, NULL); @@ -1499,38 +1499,63 @@ bool gravityDB_addToTable(const enum gravity_list_type listtype, tablerow *row, idx = sqlite3_bind_parameter_index(stmt, ":oldtype"); if(idx > 0) { - if(row->oldtype == NULL) + int oldtype = -1; + if(row->oldtype == NULL && row->oldkind == NULL) + { + // User didn't specify oldtype/oldkind, just replace without moving + oldtype = type; + } + else if(row->oldtype == NULL) { - *message = "Field oldtype missing from request."; + // Error, one is not meaningful without the other + *message = "Field oldtype missing from request"; logg("gravityDB_addToTable(%d, %s): Oldtype missing", type, row->domain); sqlite3_reset(stmt); sqlite3_finalize(stmt); return false; } - int oldtype = -1; - if(strcasecmp("allow/exact", row->oldtype) == 0) - oldtype = 0; - else if(strcasecmp("deny/exact", row->oldtype) == 0) - oldtype = 1; - else if(strcasecmp("allow/regex", row->oldtype) == 0) - oldtype = 2; - else if(strcasecmp("deny/regex", row->oldtype) == 0) - oldtype = 3; - else + else if(row->oldkind == NULL) { - *message = "Cannot interpret oldtype field."; - logg("gravityDB_addToTable(%d, %s): Failed to identify oldtype \"%s\"", - type, row->domain, row->oldtype); + // Error, one is not meaningful without the other + *message = "Field oldkind missing from request"; + logg("gravityDB_addToTable(%d, %s): Oldkind missing", + type, row->domain); sqlite3_reset(stmt); sqlite3_finalize(stmt); return false; } + else + { + if(strcasecmp("allow", row->oldtype) == 0 && + strcasecmp("exact", row->oldkind) == 0) + oldtype = 0; + else if(strcasecmp("deny", row->oldtype) == 0 && + strcasecmp("exact", row->oldkind) == 0) + oldtype = 1; + else if(strcasecmp("allow", row->oldtype) == 0 && + strcasecmp("regex", row->oldkind) == 0) + oldtype = 2; + else if(strcasecmp("deny", row->oldtype) == 0 && + strcasecmp("regex", row->oldkind) == 0) + oldtype = 3; + else + { + *message = "Cannot interpret oldtype/oldkind"; + logg("gravityDB_addToTable(%d, %s): Failed to identify oldtype=\"%s\", oldkind=\"%s\"", + type, row->domain, row->oldtype, row->oldkind); + sqlite3_reset(stmt); + sqlite3_finalize(stmt); + return false; + } + } + + // Bind oldtype to database statement if((rc = sqlite3_bind_int(stmt, idx, oldtype)) != SQLITE_OK) { *message = sqlite3_errmsg(gravity_db); logg("gravityDB_addToTable(%d, %s): Failed to bind oldtype (error %d) - %s", - type, row->domain, rc, *message); + type, row->domain, rc, *message); sqlite3_reset(stmt); sqlite3_finalize(stmt); return false; diff --git a/src/database/gravity-db.h b/src/database/gravity-db.h index 9ad0adb5e..d5222e4d2 100644 --- a/src/database/gravity-db.h +++ b/src/database/gravity-db.h @@ -25,11 +25,13 @@ typedef struct { const char *domain; const char *address; const char *type; + const char *oldtype; + const char *kind; + const char *oldkind; const char *comment; const char *group_ids; const char *description; const char *argument; - const char *oldtype; const char *ip; long id; time_t date_added; From 73b3d680ca80f41c3cf7f725ebc60c3c9fc4b8a8 Mon Sep 17 00:00:00 2001 From: DL6ER Date: Mon, 25 Jan 2021 20:55:52 +0100 Subject: [PATCH 0238/1669] Improve error reporting for missing/incorrect sets of payload/URI arguments Signed-off-by: DL6ER --- src/api/list.c | 274 ++++++++++++++++++++++-------------- src/api/routes.c | 1 + src/database/gravity-db.c | 158 ++++++++++----------- src/database/gravity-db.h | 5 +- src/webserver/http-common.c | 4 + src/webserver/http-common.h | 1 + 6 files changed, 253 insertions(+), 190 deletions(-) diff --git a/src/api/list.c b/src/api/list.c index 60d36842b..8fd72ba6b 100644 --- a/src/api/list.c +++ b/src/api/list.c @@ -20,15 +20,15 @@ static int api_list_read(struct ftl_conn *api, const int code, const enum gravity_list_type listtype, - const char *argument) + const char *item) { const char *sql_msg = NULL; - if(!gravityDB_readTable(listtype, argument, &sql_msg)) + if(!gravityDB_readTable(listtype, item, &sql_msg)) { cJSON *json = JSON_NEW_OBJ(); - // Add argument (may be NULL = not available) - JSON_OBJ_REF_STR(json, "argument", argument); + // Add item (may be NULL = not available) + JSON_OBJ_REF_STR(json, "item", item); // Add SQL message (may be NULL = not available) if (sql_msg != NULL) { @@ -43,100 +43,100 @@ static int api_list_read(struct ftl_conn *api, json); } - tablerow row; - cJSON *items = JSON_NEW_ARRAY(); - while(gravityDB_readTableGetRow(&row, &sql_msg)) + tablerow table; + cJSON *rows = JSON_NEW_ARRAY(); + while(gravityDB_readTableGetRow(&table, &sql_msg)) { - cJSON *item = JSON_NEW_OBJ(); + cJSON *row = JSON_NEW_OBJ(); // Special fields if(listtype == GRAVITY_GROUPS) { - JSON_OBJ_COPY_STR(item, "name", row.name); - if(row.description != NULL) { - JSON_OBJ_COPY_STR(item, "description", row.description); + JSON_OBJ_COPY_STR(row, "name", table.name); + if(table.comment != NULL) { + JSON_OBJ_COPY_STR(row, "comment", table.comment); } else { - JSON_OBJ_ADD_NULL(item, "description"); + JSON_OBJ_ADD_NULL(row, "comment"); } } else if(listtype == GRAVITY_ADLISTS) { - JSON_OBJ_COPY_STR(item, "address", row.address); - if(row.comment != NULL) { - JSON_OBJ_COPY_STR(item, "comment", row.comment); + JSON_OBJ_COPY_STR(row, "address", table.address); + if(table.comment != NULL) { + JSON_OBJ_COPY_STR(row, "comment", table.comment); } else { - JSON_OBJ_ADD_NULL(item, "comment"); + JSON_OBJ_ADD_NULL(row, "comment"); } } else if(listtype == GRAVITY_CLIENTS) { - if(row.ip != NULL) + if(table.client != NULL) { - JSON_OBJ_COPY_STR(item, "client", row.ip); - char *name = getNameFromIP(row.ip); + JSON_OBJ_COPY_STR(row, "client", table.client); + char *name = getNameFromIP(table.client); if(name != NULL) { - JSON_OBJ_COPY_STR(item, "name", name); + JSON_OBJ_COPY_STR(row, "name", name); free(name); } else { - JSON_OBJ_ADD_NULL(item, "name"); + JSON_OBJ_ADD_NULL(row, "name"); } } else { - JSON_OBJ_ADD_NULL(item, "ip"); - JSON_OBJ_ADD_NULL(item, "name"); + JSON_OBJ_ADD_NULL(row, "ip"); + JSON_OBJ_ADD_NULL(row, "name"); } - if(row.comment != NULL) { - JSON_OBJ_COPY_STR(item, "comment", row.comment); + if(table.comment != NULL) { + JSON_OBJ_COPY_STR(row, "comment", table.comment); } else { - JSON_OBJ_ADD_NULL(item, "comment"); + JSON_OBJ_ADD_NULL(row, "comment"); } } else // domainlists { - JSON_OBJ_COPY_STR(item, "domain", row.domain); - JSON_OBJ_REF_STR(item, "type", row.type); - JSON_OBJ_REF_STR(item, "kind", row.kind); - if(row.comment != NULL) { - JSON_OBJ_COPY_STR(item, "comment", row.comment); + JSON_OBJ_COPY_STR(row, "domain", table.domain); + JSON_OBJ_REF_STR(row, "type", table.type); + JSON_OBJ_REF_STR(row, "kind", table.kind); + if(table.comment != NULL) { + JSON_OBJ_COPY_STR(row, "comment", table.comment); } else { - JSON_OBJ_ADD_NULL(item, "comment"); + JSON_OBJ_ADD_NULL(row, "comment"); } } - if(row.group_ids != NULL) { + if(table.group_ids != NULL) { // Black magic at work here: We build a JSON array from // the group_concat result delivered from the database, - // parse it as valid array and append it as item to the + // parse it as valid array and append it as row to the // data - logg("row.group_ids = %p \"%s\"", row.group_ids, row.group_ids); - char group_ids_str[strlen(row.group_ids)+3u]; + logg("table.group_ids = %p \"%s\"", table.group_ids, table.group_ids); + char group_ids_str[strlen(table.group_ids)+3u]; group_ids_str[0] = '['; - strcpy(group_ids_str+1u , row.group_ids); + strcpy(group_ids_str+1u , table.group_ids); group_ids_str[sizeof(group_ids_str)-2u] = ']'; group_ids_str[sizeof(group_ids_str)-1u] = '\0'; cJSON * group_ids = cJSON_Parse(group_ids_str); - JSON_OBJ_ADD_ITEM(item, "groups", group_ids); + JSON_OBJ_ADD_ITEM(row, "groups", group_ids); } else { // Empty group set cJSON *group_ids = JSON_NEW_ARRAY(); - JSON_OBJ_ADD_ITEM(item, "groups", group_ids); + JSON_OBJ_ADD_ITEM(row, "groups", group_ids); } // Clients don't have the enabled property if(listtype != GRAVITY_CLIENTS) - JSON_OBJ_ADD_BOOL(item, "enabled", row.enabled); + JSON_OBJ_ADD_BOOL(row, "enabled", table.enabled); // Add read-only database parameters - JSON_OBJ_ADD_NUMBER(item, "id", row.id); - JSON_OBJ_ADD_NUMBER(item, "date_added", row.date_added); - JSON_OBJ_ADD_NUMBER(item, "date_modified", row.date_modified); + JSON_OBJ_ADD_NUMBER(row, "id", table.id); + JSON_OBJ_ADD_NUMBER(row, "date_added", table.date_added); + JSON_OBJ_ADD_NUMBER(row, "date_modified", table.date_modified); - JSON_ARRAY_ADD_ITEM(items, item); + JSON_ARRAY_ADD_ITEM(rows, row); } gravityDB_readTableFinalize(); @@ -153,16 +153,16 @@ static int api_list_read(struct ftl_conn *api, objname = "clients"; else // domainlists objname = "domains"; - JSON_OBJ_ADD_ITEM(json, objname, items); + JSON_OBJ_ADD_ITEM(json, objname, rows); JSON_SEND_OBJECT_CODE(json, code); } else { - JSON_DELETE(items); + JSON_DELETE(rows); cJSON *json = JSON_NEW_OBJ(); - // Add argument (may be NULL = not available) - JSON_OBJ_REF_STR(json, "argument", argument); + // Add item (may be NULL = not available) + JSON_OBJ_REF_STR(json, "item", item); // Add SQL message (may be NULL = not available) if (sql_msg != NULL) { @@ -180,14 +180,11 @@ static int api_list_read(struct ftl_conn *api, static int api_list_write(struct ftl_conn *api, const enum gravity_list_type listtype, - const char *argument, + const char *item, char payload[MAX_PAYLOAD_BYTES]) { tablerow row = { 0 }; - // Set argument - row.argument = argument; - // Check if valid JSON payload is available if (api->payload.json == NULL) { return send_json_error(api, 400, @@ -196,23 +193,97 @@ static int api_list_write(struct ftl_conn *api, NULL); } - // Clients don't have the enabled property - cJSON *json_enabled = cJSON_GetObjectItemCaseSensitive(api->payload.json, "enabled"); - if (cJSON_IsBool(json_enabled)) - row.enabled = cJSON_IsTrue(json_enabled); - else - row.enabled = true; // Default value + if(api->method == HTTP_POST) + { + // Extract domain/name/client/address from payload whe using POST, all + // others specify it as URI-component + cJSON *json_domain, *json_name, *json_address, *json_client; + switch(listtype) + { + case GRAVITY_DOMAINLIST_ALLOW_EXACT: + case GRAVITY_DOMAINLIST_ALLOW_REGEX: + case GRAVITY_DOMAINLIST_DENY_EXACT: + case GRAVITY_DOMAINLIST_DENY_REGEX: + json_domain = cJSON_GetObjectItemCaseSensitive(api->payload.json, "domain"); + if(cJSON_IsString(json_domain) && strlen(json_domain->valuestring) > 0) + { + row.item = json_domain->valuestring; + } + else + { + cJSON *uri = JSON_NEW_OBJ(); + JSON_OBJ_REF_STR(uri, "path", api->action_path); + JSON_OBJ_REF_STR(uri, "item", item); + return send_json_error(api, 400, + "uri_error", + "Invalid request: No item \"domain\" in payload", + uri); + } + break; + + case GRAVITY_GROUPS: + json_name = cJSON_GetObjectItemCaseSensitive(api->payload.json, "name"); + if(cJSON_IsString(json_name) && strlen(json_name->valuestring) > 0) + row.item = json_name->valuestring; + else + { + cJSON *uri = JSON_NEW_OBJ(); + JSON_OBJ_REF_STR(uri, "path", api->action_path); + JSON_OBJ_REF_STR(uri, "item", item); + return send_json_error(api, 400, + "uri_error", + "Invalid request: No item \"name\" in payload", + uri); + } + break; + + case GRAVITY_CLIENTS: + json_client = cJSON_GetObjectItemCaseSensitive(api->payload.json, "client"); + if(cJSON_IsString(json_client) && strlen(json_client->valuestring) > 0) + row.item = json_client->valuestring; + else + { + cJSON *uri = JSON_NEW_OBJ(); + JSON_OBJ_REF_STR(uri, "path", api->action_path); + JSON_OBJ_REF_STR(uri, "item", item); + return send_json_error(api, 400, + "uri_error", + "Invalid request: No item \"client\" in payload", + uri); + } + break; - cJSON *json_item = cJSON_GetObjectItemCaseSensitive(api->payload.json, "item"); - if(cJSON_IsString(json_item) && strlen(json_item->valuestring) > 0) - row.argument = json_item->valuestring; - else if (api->method != HTTP_PUT) // PUT uses the URI argument + case GRAVITY_ADLISTS: + json_address = cJSON_GetObjectItemCaseSensitive(api->payload.json, "address"); + if(cJSON_IsString(json_address) && strlen(json_address->valuestring) > 0) + row.item = json_address->valuestring; + else + { + cJSON *uri = JSON_NEW_OBJ(); + JSON_OBJ_REF_STR(uri, "path", api->action_path); + JSON_OBJ_REF_STR(uri, "item", item); + return send_json_error(api, 400, + "uri_error", + "Invalid request: No item \"address\" in payload", + uri); + } + break; + + // Aggregate types are not handled by this routine + case GRAVITY_DOMAINLIST_ALL_ALL: + case GRAVITY_DOMAINLIST_ALL_EXACT: + case GRAVITY_DOMAINLIST_ALL_REGEX: + case GRAVITY_DOMAINLIST_ALLOW_ALL: + case GRAVITY_DOMAINLIST_DENY_ALL: + return 500; + } + } + else { - return send_json_error(api, 400, - "bad_request", - "No \"item\" string in body data", - NULL); + // PUT = Use URI item + row.item = item; } + cJSON *json_comment = cJSON_GetObjectItemCaseSensitive(api->payload.json, "comment"); if(cJSON_IsString(json_comment) && strlen(json_comment->valuestring) > 0) @@ -220,12 +291,6 @@ static int api_list_write(struct ftl_conn *api, else row.comment = NULL; // Default value - cJSON *json_description = cJSON_GetObjectItemCaseSensitive(api->payload.json, "description"); - if(cJSON_IsString(json_description) && strlen(json_description->valuestring) > 0) - row.description = json_description->valuestring; - else - row.description = NULL; // Default value - cJSON *json_oldtype = cJSON_GetObjectItemCaseSensitive(api->payload.json, "oldtype"); if(cJSON_IsString(json_oldtype) && strlen(json_oldtype->valuestring) > 0) row.oldtype = json_oldtype->valuestring; @@ -238,13 +303,19 @@ static int api_list_write(struct ftl_conn *api, else row.oldkind = NULL; // Default value + cJSON *json_enabled = cJSON_GetObjectItemCaseSensitive(api->payload.json, "enabled"); + if (cJSON_IsBool(json_enabled)) + row.enabled = cJSON_IsTrue(json_enabled); + else + row.enabled = true; // Default value + bool okay = true; char *regex_msg = NULL; if(listtype == GRAVITY_DOMAINLIST_ALLOW_REGEX || listtype == GRAVITY_DOMAINLIST_DENY_REGEX) { // Test validity of this regex regexData regex = { 0 }; - okay = compile_regex(row.argument, ®ex, ®ex_msg); + okay = compile_regex(row.domain, ®ex, ®ex_msg); } // Try to add item to table @@ -271,12 +342,10 @@ static int api_list_write(struct ftl_conn *api, { // Error adding item, prepare error object cJSON *json = JSON_NEW_OBJ(); - JSON_OBJ_REF_STR(json, "item", row.argument); + JSON_OBJ_REF_STR(json, "item", item); JSON_OBJ_ADD_BOOL(json, "enabled", row.enabled); if(row.comment != NULL) JSON_OBJ_REF_STR(json, "comment", row.comment); - if(row.description != NULL) - JSON_OBJ_REF_STR(json, "description", row.description); if(row.name != NULL) JSON_OBJ_REF_STR(json, "name", row.name); if(row.oldtype != NULL) @@ -317,16 +386,16 @@ static int api_list_write(struct ftl_conn *api, if(api->method == HTTP_PUT) response_code = 200; // 200 - OK // Send GET style reply - return api_list_read(api, response_code, listtype, row.argument); + return api_list_read(api, response_code, listtype, row.item); } static int api_list_remove(struct ftl_conn *api, const enum gravity_list_type listtype, - const char *argument) + const char *item) { cJSON *json = JSON_NEW_OBJ(); const char *sql_msg = NULL; - if(gravityDB_delFromTable(listtype, argument, &sql_msg)) + if(gravityDB_delFromTable(listtype, item, &sql_msg)) { // Inform the resolver that it needs to reload the domainlists set_event(RELOAD_GRAVITY); @@ -336,8 +405,8 @@ static int api_list_remove(struct ftl_conn *api, } else { - // Add argument - JSON_OBJ_REF_STR(json, "argument", argument); + // Add item + JSON_OBJ_REF_STR(json, "item", item); // Add SQL message (may be NULL = not available) if (sql_msg != NULL) { @@ -365,59 +434,58 @@ int api_list(struct ftl_conn *api) enum gravity_list_type listtype; bool can_modify = false; - const char *argument = NULL; - if((argument = startsWith("/api/groups", api)) != NULL) + if((api->item = startsWith("/api/groups", api)) != NULL) { listtype = GRAVITY_GROUPS; can_modify = true; } - else if((argument = startsWith("/api/lists", api)) != NULL) + else if((api->item = startsWith("/api/lists", api)) != NULL) { listtype = GRAVITY_ADLISTS; can_modify = true; } - else if((argument = startsWith("/api/clients", api)) != NULL) + else if((api->item = startsWith("/api/clients", api)) != NULL) { listtype = GRAVITY_CLIENTS; can_modify = true; } - else if((argument = startsWith("/api/domains/allow/exact", api)) != NULL) + else if((api->item = startsWith("/api/domains/allow/exact", api)) != NULL) { listtype = GRAVITY_DOMAINLIST_ALLOW_EXACT; can_modify = true; } - else if((argument = startsWith("/api/domains/allow/regex", api)) != NULL) + else if((api->item = startsWith("/api/domains/allow/regex", api)) != NULL) { listtype = GRAVITY_DOMAINLIST_ALLOW_REGEX; can_modify = true; } - else if((argument = startsWith("/api/domains/allow", api)) != NULL) + else if((api->item = startsWith("/api/domains/allow", api)) != NULL) { listtype = GRAVITY_DOMAINLIST_ALLOW_ALL; } - else if((argument = startsWith("/api/domains/deny/exact", api)) != NULL) + else if((api->item = startsWith("/api/domains/deny/exact", api)) != NULL) { listtype = GRAVITY_DOMAINLIST_DENY_EXACT; can_modify = true; } - else if((argument = startsWith("/api/domains/deny/regex", api)) != NULL) + else if((api->item = startsWith("/api/domains/deny/regex", api)) != NULL) { listtype = GRAVITY_DOMAINLIST_DENY_REGEX; can_modify = true; } - else if((argument = startsWith("/api/domains/deny", api)) != NULL) + else if((api->item = startsWith("/api/domains/deny", api)) != NULL) { listtype = GRAVITY_DOMAINLIST_DENY_ALL; } - else if((argument = startsWith("/api/domains/exact", api)) != NULL) + else if((api->item = startsWith("/api/domains/exact", api)) != NULL) { listtype = GRAVITY_DOMAINLIST_ALL_EXACT; } - else if((argument = startsWith("/api/domains/regex", api)) != NULL) + else if((api->item = startsWith("/api/domains/regex", api)) != NULL) { listtype = GRAVITY_DOMAINLIST_ALL_REGEX; } - else if((argument = startsWith("/api/domains", api)) != NULL) + else if((api->item = startsWith("/api/domains", api)) != NULL) { listtype = GRAVITY_DOMAINLIST_ALL_ALL; } @@ -434,12 +502,12 @@ int api_list(struct ftl_conn *api) if(api->method == HTTP_GET) { // Read list item identified by URI (or read them all) - return api_list_read(api, 200, listtype, argument); + return api_list_read(api, 200, listtype, api->item); } else if(can_modify && api->method == HTTP_PUT) { // Add/update item identified by URI - if(strlen(argument) == 0) + if(api->item != NULL && strlen(api->item) == 0) { cJSON *uri = JSON_NEW_OBJ(); if(api->action_path != NULL) @@ -450,19 +518,19 @@ int api_list(struct ftl_conn *api) { JSON_OBJ_ADD_NULL(uri, "path"); } - JSON_OBJ_REF_STR(uri, "item", argument); + JSON_OBJ_REF_STR(uri, "item", api->item); return send_json_error(api, 400, "uri_error", "Invalid request: Specify item in URI", uri); } else - return api_list_write(api, listtype, argument, payload); + return api_list_write(api, listtype, api->item, payload); } else if(can_modify && api->method == HTTP_POST) { // Add item to list identified by payload - if(strlen(argument) != 0) + if(api->item != NULL && strlen(api->item) != 0) { cJSON *uri = JSON_NEW_OBJ(); if(api->action_path != NULL) @@ -473,19 +541,19 @@ int api_list(struct ftl_conn *api) { JSON_OBJ_ADD_NULL(uri, "path"); } - JSON_OBJ_REF_STR(uri, "item", argument); + JSON_OBJ_REF_STR(uri, "item", api->item); return send_json_error(api, 400, "uri_error", "Invalid request: Specify item in payload, not as URI parameter", uri); } else - return api_list_write(api, listtype, argument, payload); + return api_list_write(api, listtype, api->item, payload); } else if(can_modify && api->method == HTTP_DELETE) { // Delete item from list - return api_list_remove(api, listtype, argument); + return api_list_remove(api, listtype, api->item); } else if(!can_modify) { @@ -499,7 +567,7 @@ int api_list(struct ftl_conn *api) { JSON_OBJ_ADD_NULL(uri, "path"); } - JSON_OBJ_REF_STR(uri, "item", argument); + JSON_OBJ_REF_STR(uri, "item", api->item); return send_json_error(api, 400, "uri_error", "Invalid request: Specify list to modify more precisely", diff --git a/src/api/routes.c b/src/api/routes.c index d76905f3a..6e78191cc 100644 --- a/src/api/routes.c +++ b/src/api/routes.c @@ -30,6 +30,7 @@ int api_handler(struct mg_connection *conn, void *ignored) mg_get_request_info(conn), http_method(conn), NULL, + NULL, { 0 } }; read_and_parse_payload(&api); diff --git a/src/database/gravity-db.c b/src/database/gravity-db.c index 23afcc702..789c2f0f7 100644 --- a/src/database/gravity-db.c +++ b/src/database/gravity-db.c @@ -1388,53 +1388,58 @@ bool gravityDB_addToTable(const enum gravity_list_type listtype, tablerow *row, return false; } - int type = -1; switch (listtype) { case GRAVITY_DOMAINLIST_ALLOW_EXACT: - type = 0; + row->type_int = 0; break; case GRAVITY_DOMAINLIST_DENY_EXACT: - type = 1; + row->type_int = 1; break; case GRAVITY_DOMAINLIST_ALLOW_REGEX: - type = 2; + row->type_int = 2; break; case GRAVITY_DOMAINLIST_DENY_REGEX: - type = 3; + row->type_int = 3; break; + // Nothing to be done for these tables case GRAVITY_GROUPS: case GRAVITY_ADLISTS: case GRAVITY_CLIENTS: - // No type required for these tables break; - // Aggregate types cannot be handled by this routine + // Aggregate types are not handled by this routine case GRAVITY_DOMAINLIST_ALLOW_ALL: case GRAVITY_DOMAINLIST_DENY_ALL: case GRAVITY_DOMAINLIST_ALL_EXACT: case GRAVITY_DOMAINLIST_ALL_REGEX: case GRAVITY_DOMAINLIST_ALL_ALL: - default: return false; } - row->type_int = type; // Prepare SQLite statement sqlite3_stmt* stmt = NULL; const char *querystr; if(method == HTTP_POST) // Create NEW entry, error if existing { - // The item is the argument for all POST requests + // The item is the item for all POST requests if(listtype == GRAVITY_GROUPS) - querystr = "INSERT INTO \"group\" (name,enabled,description) VALUES (:argument,:enabled,:description);"; + { + querystr = "INSERT INTO \"group\" (name,enabled,description) VALUES (:item,:enabled,:description);"; + } else if(listtype == GRAVITY_ADLISTS) - querystr = "INSERT INTO adlist (address,enabled,comment) VALUES (:argument,:enabled,:comment);"; + { + querystr = "INSERT INTO adlist (address,enabled,comment) VALUES (:item,:enabled,:comment);"; + } else if(listtype == GRAVITY_CLIENTS) - querystr = "INSERT INTO client (ip,comment) VALUES (:argument,:comment);"; - else // domainlist - querystr = "INSERT INTO domainlist (domain,type,enabled,comment) VALUES (:argument,:type,:enabled,:comment);"; + { + querystr = "INSERT INTO client (ip,comment) VALUES (:item,:comment);"; + } + else // domainlis + { + querystr = "INSERT INTO domainlist (domain,type,enabled,comment) VALUES (:item,:type,:enabled,:comment);"; + } } else { // Create new or replace existing entry, no error if existing @@ -1442,24 +1447,24 @@ bool gravityDB_addToTable(const enum gravity_list_type listtype, tablerow *row, // contraints (REPLACE recreates (= new ID) entries instead of updating them) if(listtype == GRAVITY_GROUPS) querystr = "REPLACE INTO \"group\" (name,enabled,description,id,date_added) " - "VALUES (:argument,:enabled,:description," - "(SELECT id FROM \"group\" WHERE name = :argument)," - "(SELECT date_added FROM \"group\" WHERE name = :argument));"; + "VALUES (:item,:enabled,:description," + "(SELECT id FROM \"group\" WHERE name = :item)," + "(SELECT date_added FROM \"group\" WHERE name = :item));"; else if(listtype == GRAVITY_ADLISTS) querystr = "REPLACE INTO adlist (address,enabled,comment,id,date_added) " - "VALUES (:argument,:enabled,:comment," - "(SELECT id FROM adlist WHERE address = :argument)," - "(SELECT date_added FROM adlist WHERE address = :argument));"; + "VALUES (:item,:enabled,:comment," + "(SELECT id FROM adlist WHERE address = :item)," + "(SELECT date_added FROM adlist WHERE address = :item));"; else if(listtype == GRAVITY_CLIENTS) querystr = "REPLACE INTO client (ip,comment,id,date_added) " - "VALUES (:argument,:comment," - "(SELECT id FROM client WHERE ip = :argument)," - "(SELECT date_added FROM client WHERE ip = :argument));"; + "VALUES (:item,:comment," + "(SELECT id FROM client WHERE ip = :item)," + "(SELECT date_added FROM client WHERE ip = :item));"; else // domainlist querystr = "REPLACE INTO domainlist (domain,type,enabled,comment,id,date_added) " - "VALUES (:argument,:type,:enabled,:comment," - "(SELECT id FROM domainlist WHERE domain = :argument AND type = :oldtype)," - "(SELECT date_added FROM domainlist WHERE domain = :argument AND type = :oldtype));"; + "VALUES (:item,:type,:enabled,:comment," + "(SELECT id FROM domainlist WHERE domain = :item AND type = :oldtype)," + "(SELECT date_added FROM domainlist WHERE domain = :item AND type = :oldtype));"; } int rc = sqlite3_prepare_v2(gravity_db, querystr, -1, &stmt, NULL); @@ -1467,17 +1472,17 @@ bool gravityDB_addToTable(const enum gravity_list_type listtype, tablerow *row, { *message = sqlite3_errmsg(gravity_db); logg("gravityDB_addToTable(%d, %s) - SQL error prepare (%i): %s", - type, row->domain, rc, *message); + row->type_int, row->domain, rc, *message); return false; } - // Bind argument to prepared statement (if requested) - int idx = sqlite3_bind_parameter_index(stmt, ":argument"); - if(idx > 0 && (rc = sqlite3_bind_text(stmt, idx, row->argument, -1, SQLITE_STATIC)) != SQLITE_OK) + // Bind item to prepared statement (if requested) + int idx = sqlite3_bind_parameter_index(stmt, ":item"); + if(idx > 0 && (rc = sqlite3_bind_text(stmt, idx, row->item, -1, SQLITE_STATIC)) != SQLITE_OK) { *message = sqlite3_errmsg(gravity_db); - logg("gravityDB_addToTable(%d, %s): Failed to bind argument (error %d) - %s", - type, row->argument, rc, *message); + logg("gravityDB_addToTable(%d, %s): Failed to bind item (error %d) - %s", + row->type_int, row->item, rc, *message); sqlite3_reset(stmt); sqlite3_finalize(stmt); return false; @@ -1485,11 +1490,11 @@ bool gravityDB_addToTable(const enum gravity_list_type listtype, tablerow *row, // Bind type to prepared statement (if requested) idx = sqlite3_bind_parameter_index(stmt, ":type"); - if(idx > 0 && (rc = sqlite3_bind_int(stmt, idx, type)) != SQLITE_OK) + if(idx > 0 && (rc = sqlite3_bind_int(stmt, idx, row->type_int)) != SQLITE_OK) { *message = sqlite3_errmsg(gravity_db); logg("gravityDB_addToTable(%d, %s): Failed to bind type (error %d) - %s", - type, row->domain, rc, *message); + row->type_int, row->domain, rc, *message); sqlite3_reset(stmt); sqlite3_finalize(stmt); return false; @@ -1503,14 +1508,14 @@ bool gravityDB_addToTable(const enum gravity_list_type listtype, tablerow *row, if(row->oldtype == NULL && row->oldkind == NULL) { // User didn't specify oldtype/oldkind, just replace without moving - oldtype = type; + oldtype = row->type_int; } else if(row->oldtype == NULL) { // Error, one is not meaningful without the other *message = "Field oldtype missing from request"; logg("gravityDB_addToTable(%d, %s): Oldtype missing", - type, row->domain); + row->type_int, row->domain); sqlite3_reset(stmt); sqlite3_finalize(stmt); return false; @@ -1520,7 +1525,7 @@ bool gravityDB_addToTable(const enum gravity_list_type listtype, tablerow *row, // Error, one is not meaningful without the other *message = "Field oldkind missing from request"; logg("gravityDB_addToTable(%d, %s): Oldkind missing", - type, row->domain); + row->type_int, row->domain); sqlite3_reset(stmt); sqlite3_finalize(stmt); return false; @@ -1543,7 +1548,7 @@ bool gravityDB_addToTable(const enum gravity_list_type listtype, tablerow *row, { *message = "Cannot interpret oldtype/oldkind"; logg("gravityDB_addToTable(%d, %s): Failed to identify oldtype=\"%s\", oldkind=\"%s\"", - type, row->domain, row->oldtype, row->oldkind); + row->type_int, row->domain, row->oldtype, row->oldkind); sqlite3_reset(stmt); sqlite3_finalize(stmt); return false; @@ -1555,7 +1560,7 @@ bool gravityDB_addToTable(const enum gravity_list_type listtype, tablerow *row, { *message = sqlite3_errmsg(gravity_db); logg("gravityDB_addToTable(%d, %s): Failed to bind oldtype (error %d) - %s", - type, row->domain, rc, *message); + row->type_int, row->domain, rc, *message); sqlite3_reset(stmt); sqlite3_finalize(stmt); return false; @@ -1568,7 +1573,7 @@ bool gravityDB_addToTable(const enum gravity_list_type listtype, tablerow *row, { *message = sqlite3_errmsg(gravity_db); logg("gravityDB_addToTable(%d, %s): Failed to bind enabled (error %d) - %s", - type, row->domain, rc, *message); + row->type_int, row->domain, rc, *message); sqlite3_reset(stmt); sqlite3_finalize(stmt); return false; @@ -1580,19 +1585,7 @@ bool gravityDB_addToTable(const enum gravity_list_type listtype, tablerow *row, { *message = sqlite3_errmsg(gravity_db); logg("gravityDB_addToTable(%d, %s): Failed to bind comment (error %d) - %s", - type, row->domain, rc, *message); - sqlite3_reset(stmt); - sqlite3_finalize(stmt); - return false; - } - - // Bind description string to prepared statement (if requested) - idx = sqlite3_bind_parameter_index(stmt, ":description"); - if(idx > 0 && (rc = sqlite3_bind_text(stmt, idx, row->description, -1, SQLITE_STATIC)) != SQLITE_OK) - { - *message = sqlite3_errmsg(gravity_db); - logg("gravityDB_addToTable(%d, %s): Failed to bind description (error %d) - %s", - type, row->domain, rc, *message); + row->type_int, row->domain, rc, *message); sqlite3_reset(stmt); sqlite3_finalize(stmt); return false; @@ -1799,7 +1792,7 @@ bool gravityDB_delFromTable(const enum gravity_list_type listtype, const char* a } static sqlite3_stmt* read_stmt = NULL; -bool gravityDB_readTable(const enum gravity_list_type listtype, const char *argument, const char **message) +bool gravityDB_readTable(const enum gravity_list_type listtype, const char *item, const char **message) { if(gravity_db == NULL) { @@ -1852,30 +1845,30 @@ bool gravityDB_readTable(const enum gravity_list_type listtype, const char *argu const char *extra = ""; if(listtype == GRAVITY_GROUPS) { - if(argument != NULL && argument[0] != '\0') - extra = " WHERE name = :argument;"; - sprintf(querystr, "SELECT id,name,enabled,date_added,date_modified,description FROM \"group\"%s;", extra); + if(item != NULL && item[0] != '\0') + extra = " WHERE name = :item;"; + sprintf(querystr, "SELECT id,name,enabled,date_added,date_modified,description AS comment FROM \"group\"%s;", extra); } else if(listtype == GRAVITY_ADLISTS) { - if(argument != NULL && argument[0] != '\0') - extra = " WHERE address = :argument;"; + if(item != NULL && item[0] != '\0') + extra = " WHERE address = :item;"; sprintf(querystr, "SELECT id,address,enabled,date_added,date_modified,comment," "(SELECT GROUP_CONCAT(group_id) FROM adlist_by_group g WHERE g.adlist_id = a.id) AS group_ids " "FROM adlist a%s;", extra); } else if(listtype == GRAVITY_CLIENTS) { - if(argument != NULL && argument[0] != '\0') - extra = " WHERE ip = :argument;"; - sprintf(querystr, "SELECT id,ip,date_added,date_modified,comment," + if(item != NULL && item[0] != '\0') + extra = " WHERE ip = :item;"; + sprintf(querystr, "SELECT id,ip AS client,date_added,date_modified,comment," "(SELECT GROUP_CONCAT(group_id) FROM client_by_group g WHERE g.client_id = c.id) AS group_ids " "FROM client c%s;", extra); } else // domainlist { - if(argument != NULL && argument[0] != '\0') - extra = " AND domain = :argument;"; + if(item != NULL && item[0] != '\0') + extra = " AND domain = :item;"; sprintf(querystr, "SELECT id,type,domain,enabled,date_added,date_modified,comment," "(SELECT GROUP_CONCAT(group_id) FROM domainlist_by_group g WHERE g.domainlist_id = d.id) AS group_ids " "FROM domainlist d WHERE d.type IN (%s)%s;", type, extra); @@ -1890,13 +1883,13 @@ bool gravityDB_readTable(const enum gravity_list_type listtype, const char *argu return false; } - // Bind argument to prepared statement (if requested) - int idx = sqlite3_bind_parameter_index(read_stmt, ":argument"); - if(idx > 0 && (rc = sqlite3_bind_text(read_stmt, idx, argument, -1, SQLITE_STATIC)) != SQLITE_OK) + // Bind item to prepared statement (if requested) + int idx = sqlite3_bind_parameter_index(read_stmt, ":item"); + if(idx > 0 && (rc = sqlite3_bind_text(read_stmt, idx, item, -1, SQLITE_STATIC)) != SQLITE_OK) { *message = sqlite3_errmsg(gravity_db); - logg("gravityDB_readTable(%d => (%s), %s): Failed to bind argument (error %d) - %s", - listtype, type, argument, rc, *message); + logg("gravityDB_readTable(%d => (%s), %s): Failed to bind item (error %d) - %s", + listtype, type, item, rc, *message); sqlite3_reset(read_stmt); sqlite3_finalize(read_stmt); return false; @@ -1906,7 +1899,7 @@ bool gravityDB_readTable(const enum gravity_list_type listtype, const char *argu if(config.debug & DEBUG_API) { logg("SQL: %s", querystr); - logg(" :argument = \"%s\"", argument); + logg(" :item = \"%s\"", item); } return true; @@ -1976,11 +1969,8 @@ bool gravityDB_readTableGetRow(tablerow *row, const char **message) else if(strcasecmp(cname, "name") == 0) row->name = (char*)sqlite3_column_text(read_stmt, c); - else if(strcasecmp(cname, "description") == 0) - row->description = (char*)sqlite3_column_text(read_stmt, c); - - else if(strcasecmp(cname, "ip") == 0) - row->ip = (char*)sqlite3_column_text(read_stmt, c); + else if(strcasecmp(cname, "client") == 0) + row->client = (char*)sqlite3_column_text(read_stmt, c); else logg("Internal API error: Encountered unknown column %s", cname); @@ -2025,19 +2015,19 @@ bool gravityDB_edit_groups(const enum gravity_list_type listtype, cJSON *groups, return false; else if(listtype == GRAVITY_CLIENTS) { - get_querystr = "SELECT id FROM client WHERE ip = :argument"; + get_querystr = "SELECT id FROM client WHERE ip = :item"; del_querystr = "DELETE FROM client_by_group WHERE client_id = :id;"; add_querystr = "INSERT INTO client_by_group (client_id,group_id) VALUES (:id,:gid);"; } else if(listtype == GRAVITY_ADLISTS) { - get_querystr = "SELECT id FROM adlist WHERE address = :argument"; + get_querystr = "SELECT id FROM adlist WHERE address = :item"; del_querystr = "DELETE FROM adlist_by_group WHERE adlist_id = :id;"; add_querystr = "INSERT INTO adlist_by_group (adlist_id,group_id) VALUES (:id,:gid);"; } else // domainlist { - get_querystr = "SELECT id FROM domainlist WHERE domain = :argument AND type = :type"; + get_querystr = "SELECT id FROM domainlist WHERE domain = :item AND type = :type"; del_querystr = "DELETE FROM domainlist_by_group WHERE domainlist_id = :id;"; add_querystr = "INSERT INTO domainlist_by_group (domainlist_id,group_id) VALUES (:id,:gid);"; } @@ -2053,12 +2043,12 @@ bool gravityDB_edit_groups(const enum gravity_list_type listtype, cJSON *groups, return false; } - // Bind argument string to prepared statement (if requested) - int idx = sqlite3_bind_parameter_index(stmt, ":argument"); - if(idx > 0 && (rc = sqlite3_bind_text(stmt, idx, row->argument, -1, SQLITE_STATIC)) != SQLITE_OK) + // Bind item string to prepared statement (if requested) + int idx = sqlite3_bind_parameter_index(stmt, ":item"); + if(idx > 0 && (rc = sqlite3_bind_text(stmt, idx, row->item, -1, SQLITE_STATIC)) != SQLITE_OK) { *message = sqlite3_errmsg(gravity_db); - logg("gravityDB_edit_groups(%d): Failed to bind argument SELECT (error %d) - %s", + logg("gravityDB_edit_groups(%d): Failed to bind item SELECT (error %d) - %s", listtype, rc, *message); sqlite3_reset(stmt); sqlite3_finalize(stmt); @@ -2095,7 +2085,7 @@ bool gravityDB_edit_groups(const enum gravity_list_type listtype, cJSON *groups, if(config.debug & DEBUG_API) { logg("SQL: %s", get_querystr); - logg(" :argument = \"%s\"", row->argument); + logg(" :item = \"%s\"", row->item); logg(" :type = \"%d\"", row->type_int); } diff --git a/src/database/gravity-db.h b/src/database/gravity-db.h index d5222e4d2..25fe54ef6 100644 --- a/src/database/gravity-db.h +++ b/src/database/gravity-db.h @@ -30,9 +30,8 @@ typedef struct { const char *oldkind; const char *comment; const char *group_ids; - const char *description; - const char *argument; - const char *ip; + const char *client; + const char *item; long id; time_t date_added; time_t date_modified; diff --git a/src/webserver/http-common.c b/src/webserver/http-common.c index f2c456078..96178a2c8 100644 --- a/src/webserver/http-common.c +++ b/src/webserver/http-common.c @@ -146,6 +146,8 @@ const char* __attribute__((pure)) startsWith(const char *path, struct ftl_conn * if(api->request->local_uri[strlen(path)] == '/') { // Path match with argument after ".../" + if(api->action_path != NULL) + free(api->action_path); api->action_path = strdup(api->request->local_uri); api->action_path[strlen(path)] = '\0'; return api->request->local_uri + strlen(path) + 1u; @@ -153,6 +155,8 @@ const char* __attribute__((pure)) startsWith(const char *path, struct ftl_conn * else if(strlen(path) == strlen(api->request->local_uri)) { // Path match directly, no argument + if(api->action_path != NULL) + free(api->action_path); api->action_path = strdup(api->request->local_uri); return ""; } diff --git a/src/webserver/http-common.h b/src/webserver/http-common.h index 1451f9630..8be641b1c 100644 --- a/src/webserver/http-common.h +++ b/src/webserver/http-common.h @@ -25,6 +25,7 @@ struct ftl_conn { const struct mg_request_info *request; const enum http_method method; char *action_path; + const char *item; struct { bool avail :1; char raw[MAX_PAYLOAD_BYTES]; From 0271a38ce88e0c0b6a9336262c66e5c81bad7328 Mon Sep 17 00:00:00 2001 From: DL6ER Date: Mon, 25 Jan 2021 20:59:22 +0100 Subject: [PATCH 0239/1669] Groups don't have the groups property Signed-off-by: DL6ER --- src/api/list.c | 38 +++++++++++++++++++++----------------- 1 file changed, 21 insertions(+), 17 deletions(-) diff --git a/src/api/list.c b/src/api/list.c index 8fd72ba6b..f96c55d1e 100644 --- a/src/api/list.c +++ b/src/api/list.c @@ -108,23 +108,27 @@ static int api_list_read(struct ftl_conn *api, } } - if(table.group_ids != NULL) { - // Black magic at work here: We build a JSON array from - // the group_concat result delivered from the database, - // parse it as valid array and append it as row to the - // data - logg("table.group_ids = %p \"%s\"", table.group_ids, table.group_ids); - char group_ids_str[strlen(table.group_ids)+3u]; - group_ids_str[0] = '['; - strcpy(group_ids_str+1u , table.group_ids); - group_ids_str[sizeof(group_ids_str)-2u] = ']'; - group_ids_str[sizeof(group_ids_str)-1u] = '\0'; - cJSON * group_ids = cJSON_Parse(group_ids_str); - JSON_OBJ_ADD_ITEM(row, "groups", group_ids); - } else { - // Empty group set - cJSON *group_ids = JSON_NEW_ARRAY(); - JSON_OBJ_ADD_ITEM(row, "groups", group_ids); + // Groups don't have the groups property + if(listtype != GRAVITY_GROUPS) + { + if(table.group_ids != NULL) { + // Black magic at work here: We build a JSON array from + // the group_concat result delivered from the database, + // parse it as valid array and append it as row to the + // data + logg("table.group_ids = %p \"%s\"", table.group_ids, table.group_ids); + char group_ids_str[strlen(table.group_ids)+3u]; + group_ids_str[0] = '['; + strcpy(group_ids_str+1u , table.group_ids); + group_ids_str[sizeof(group_ids_str)-2u] = ']'; + group_ids_str[sizeof(group_ids_str)-1u] = '\0'; + cJSON * group_ids = cJSON_Parse(group_ids_str); + JSON_OBJ_ADD_ITEM(row, "groups", group_ids); + } else { + // Empty group set + cJSON *group_ids = JSON_NEW_ARRAY(); + JSON_OBJ_ADD_ITEM(row, "groups", group_ids); + } } // Clients don't have the enabled property From c99a3bc15e2f8fe8cdea613abb9a2066b8ee9b6d Mon Sep 17 00:00:00 2001 From: DL6ER Date: Mon, 25 Jan 2021 21:07:12 +0100 Subject: [PATCH 0240/1669] Store comments for groups as well Signed-off-by: DL6ER --- src/api/list.c | 32 +++++--------------------------- src/database/gravity-db.c | 2 +- 2 files changed, 6 insertions(+), 28 deletions(-) diff --git a/src/api/list.c b/src/api/list.c index f96c55d1e..b07812c80 100644 --- a/src/api/list.c +++ b/src/api/list.c @@ -53,20 +53,12 @@ static int api_list_read(struct ftl_conn *api, if(listtype == GRAVITY_GROUPS) { JSON_OBJ_COPY_STR(row, "name", table.name); - if(table.comment != NULL) { - JSON_OBJ_COPY_STR(row, "comment", table.comment); - } else { - JSON_OBJ_ADD_NULL(row, "comment"); - } + JSON_OBJ_COPY_STR(row, "comment", table.comment); } else if(listtype == GRAVITY_ADLISTS) { JSON_OBJ_COPY_STR(row, "address", table.address); - if(table.comment != NULL) { - JSON_OBJ_COPY_STR(row, "comment", table.comment); - } else { - JSON_OBJ_ADD_NULL(row, "comment"); - } + JSON_OBJ_COPY_STR(row, "comment", table.comment); } else if(listtype == GRAVITY_CLIENTS) { @@ -74,15 +66,9 @@ static int api_list_read(struct ftl_conn *api, { JSON_OBJ_COPY_STR(row, "client", table.client); char *name = getNameFromIP(table.client); + JSON_OBJ_COPY_STR(row, "name", name); if(name != NULL) - { - JSON_OBJ_COPY_STR(row, "name", name); free(name); - } - else - { - JSON_OBJ_ADD_NULL(row, "name"); - } } else { @@ -90,22 +76,14 @@ static int api_list_read(struct ftl_conn *api, JSON_OBJ_ADD_NULL(row, "name"); } - if(table.comment != NULL) { - JSON_OBJ_COPY_STR(row, "comment", table.comment); - } else { - JSON_OBJ_ADD_NULL(row, "comment"); - } + JSON_OBJ_COPY_STR(row, "comment", table.comment); } else // domainlists { JSON_OBJ_COPY_STR(row, "domain", table.domain); JSON_OBJ_REF_STR(row, "type", table.type); JSON_OBJ_REF_STR(row, "kind", table.kind); - if(table.comment != NULL) { - JSON_OBJ_COPY_STR(row, "comment", table.comment); - } else { - JSON_OBJ_ADD_NULL(row, "comment"); - } + JSON_OBJ_COPY_STR(row, "comment", table.comment); } // Groups don't have the groups property diff --git a/src/database/gravity-db.c b/src/database/gravity-db.c index 789c2f0f7..831395c1a 100644 --- a/src/database/gravity-db.c +++ b/src/database/gravity-db.c @@ -1447,7 +1447,7 @@ bool gravityDB_addToTable(const enum gravity_list_type listtype, tablerow *row, // contraints (REPLACE recreates (= new ID) entries instead of updating them) if(listtype == GRAVITY_GROUPS) querystr = "REPLACE INTO \"group\" (name,enabled,description,id,date_added) " - "VALUES (:item,:enabled,:description," + "VALUES (:item,:enabled,:comment," "(SELECT id FROM \"group\" WHERE name = :item)," "(SELECT date_added FROM \"group\" WHERE name = :item));"; else if(listtype == GRAVITY_ADLISTS) From b8515663727db373af55b82375787b8ddbdc2bf6 Mon Sep 17 00:00:00 2001 From: DL6ER Date: Mon, 25 Jan 2021 21:14:52 +0100 Subject: [PATCH 0241/1669] Our JSON targets are NULL-tolerant. Signed-off-by: DL6ER --- src/api/list.c | 45 +++++++++------------------------------------ 1 file changed, 9 insertions(+), 36 deletions(-) diff --git a/src/api/list.c b/src/api/list.c index b07812c80..d23b7ee27 100644 --- a/src/api/list.c +++ b/src/api/list.c @@ -334,19 +334,16 @@ static int api_list_write(struct ftl_conn *api, JSON_OBJ_REF_STR(json, "oldtype", row.oldtype); // Add SQL message (may be NULL = not available) - if (sql_msg != NULL) { - JSON_OBJ_REF_STR(json, "sql_msg", sql_msg); - } else { - JSON_OBJ_ADD_NULL(json, "sql_msg"); - } - - // Add regex error (may not be available) const char *errortype = "database_error"; const char *errormsg = "Could not add to gravity database"; + JSON_OBJ_REF_STR(json, "sql_msg", sql_msg); + + // Add regex error (may not be available) + JSON_OBJ_COPY_STR(json, "regex_msg", regex_msg); if (regex_msg != NULL) { - JSON_OBJ_COPY_STR(json, "regex_msg", regex_msg); free(regex_msg); regex_msg = NULL; + // Change error type and message errortype = "regex_error"; errormsg = "Regex validation failed"; } else { @@ -391,11 +388,7 @@ static int api_list_remove(struct ftl_conn *api, JSON_OBJ_REF_STR(json, "item", item); // Add SQL message (may be NULL = not available) - if (sql_msg != NULL) { - JSON_OBJ_REF_STR(json, "sql_msg", sql_msg); - } else { - JSON_OBJ_ADD_NULL(json, "sql_msg"); - } + JSON_OBJ_REF_STR(json, "sql_msg", sql_msg); // Send error reply return send_json_error(api, 400, @@ -493,13 +486,7 @@ int api_list(struct ftl_conn *api) { cJSON *uri = JSON_NEW_OBJ(); if(api->action_path != NULL) - { - JSON_OBJ_REF_STR(uri, "path", api->action_path); - } - else - { - JSON_OBJ_ADD_NULL(uri, "path"); - } + JSON_OBJ_REF_STR(uri, "path", api->action_path); JSON_OBJ_REF_STR(uri, "item", api->item); return send_json_error(api, 400, "uri_error", @@ -515,14 +502,7 @@ int api_list(struct ftl_conn *api) if(api->item != NULL && strlen(api->item) != 0) { cJSON *uri = JSON_NEW_OBJ(); - if(api->action_path != NULL) - { - JSON_OBJ_REF_STR(uri, "path", api->action_path); - } - else - { - JSON_OBJ_ADD_NULL(uri, "path"); - } + JSON_OBJ_REF_STR(uri, "path", api->action_path); JSON_OBJ_REF_STR(uri, "item", api->item); return send_json_error(api, 400, "uri_error", @@ -541,14 +521,7 @@ int api_list(struct ftl_conn *api) { // This list type cannot be modified (e.g., ALL_ALL) cJSON *uri = JSON_NEW_OBJ(); - if(api->action_path != NULL) - { - JSON_OBJ_REF_STR(uri, "path", api->action_path); - } - else - { - JSON_OBJ_ADD_NULL(uri, "path"); - } + JSON_OBJ_REF_STR(uri, "path", api->action_path); JSON_OBJ_REF_STR(uri, "item", api->item); return send_json_error(api, 400, "uri_error", From e4c55ec52a62df1c183dd01a11ec01ab0ccade48 Mon Sep 17 00:00:00 2001 From: DL6ER Date: Mon, 25 Jan 2021 21:40:10 +0100 Subject: [PATCH 0242/1669] Embed API documentation into FTL. This ensures it is (a) always available locally, and (b) always corresponds to the API you have available locally. Signed-off-by: DL6ER --- .gitignore | 3 + build.sh | 12 +- src/CMakeLists.txt | 1 + src/api/CMakeLists.txt | 1 + src/api/docs/CMakeLists.txt | 48 ++ .../external/highlight-default.min.css | 1 + .../docs/content/external/highlight.min.js | 2 + src/api/docs/content/external/rapidoc-min.js | 220 +++++++++ .../docs/content/external/rapidoc-min.js.map | 1 + src/api/docs/content/images/logo.svg | 1 + src/api/docs/content/index.css | 26 ++ src/api/docs/content/index.html | 41 ++ src/api/docs/content/specs/clients.yaml | 279 +++++++++++ src/api/docs/content/specs/domains.yaml | 437 ++++++++++++++++++ src/api/docs/content/specs/groups.yaml | 257 ++++++++++ src/api/docs/content/specs/lists.yaml | 280 +++++++++++ src/api/docs/content/specs/main.yaml | 42 ++ src/api/docs/docs.c | 43 ++ src/api/docs/docs.h | 90 ++++ src/api/list.c | 25 +- src/api/routes.c | 5 + src/api/routes.h | 3 + 22 files changed, 1800 insertions(+), 18 deletions(-) create mode 100644 src/api/docs/CMakeLists.txt create mode 100644 src/api/docs/content/external/highlight-default.min.css create mode 100644 src/api/docs/content/external/highlight.min.js create mode 100644 src/api/docs/content/external/rapidoc-min.js create mode 100644 src/api/docs/content/external/rapidoc-min.js.map create mode 100644 src/api/docs/content/images/logo.svg create mode 100644 src/api/docs/content/index.css create mode 100644 src/api/docs/content/index.html create mode 100644 src/api/docs/content/specs/clients.yaml create mode 100644 src/api/docs/content/specs/domains.yaml create mode 100644 src/api/docs/content/specs/groups.yaml create mode 100644 src/api/docs/content/specs/lists.yaml create mode 100644 src/api/docs/content/specs/main.yaml create mode 100644 src/api/docs/docs.c create mode 100644 src/api/docs/docs.h diff --git a/.gitignore b/.gitignore index 330cbae25..94eb7d345 100644 --- a/.gitignore +++ b/.gitignore @@ -22,3 +22,6 @@ version~ # MAC->Vendor database files tools/manuf.data tools/macvendor.db + +# Documentation files generated by cmake +src/api/docs/hex diff --git a/build.sh b/build.sh index 34829075e..a78a6272a 100755 --- a/build.sh +++ b/build.sh @@ -18,6 +18,9 @@ if [[ "${1}" == "clean" ]]; then exit 0 fi +# Remove possibly generated api/docs elements +rm -rf src/api/docs/hex + # Configure build, pass CMake CACHE entries if present # Wrap multiple options in "" as first argument to ./build.sh: # ./build.sh "-DA=1 -DB=2" install @@ -29,13 +32,12 @@ else cmake .. fi -# Build the sources -cmake --build . -- -j $(nproc) - # If we are asked to install, we do this here -# Otherwise, we simply copy the binary one level up +# Otherwise, we simply build the sources and copy the binary one level up if [[ "${1}" == "install" || "${2}" == "install" ]]; then - sudo make install + sudo make install -j $(nproc) else + # Build the sources + make -j $(nproc) cp pihole-FTL ../ fi diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 17e589b70..3a03d642b 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -170,6 +170,7 @@ add_dependencies(FTL gen_version) add_executable(pihole-FTL $ $ + $ $ $ $ diff --git a/src/api/CMakeLists.txt b/src/api/CMakeLists.txt index 5257c7087..666be34c6 100644 --- a/src/api/CMakeLists.txt +++ b/src/api/CMakeLists.txt @@ -27,3 +27,4 @@ add_library(api OBJECT ${sources}) add_dependencies(api gen_version) target_compile_options(api PRIVATE ${EXTRAWARN}) target_include_directories(api PRIVATE ${PROJECT_SOURCE_DIR}/src) +add_subdirectory(docs) diff --git a/src/api/docs/CMakeLists.txt b/src/api/docs/CMakeLists.txt new file mode 100644 index 000000000..0f19350d8 --- /dev/null +++ b/src/api/docs/CMakeLists.txt @@ -0,0 +1,48 @@ +# Pi-hole: A black hole for Internet advertisements +# (c) 2021 Pi-hole, LLC (https://pi-hole.net) +# Network-wide ad blocking via your own hardware. +# +# FTL Engine +# /src/api/docs/CMakeList.txt +# +# This file is copyright under the latest version of the EUPL. +# Please see LICENSE file for your rights under this license. + +set(sources + hex/index.html + hex/index.css + hex/external/rapidoc-min.js + hex/external/rapidoc-min.js.map + hex/external/highlight.min.js + hex/external/highlight-default.min.css + hex/images/logo.svg + hex/specs/main.yaml + hex/specs/domains.yaml + hex/specs/groups.yaml + hex/specs/clients.yaml + hex/specs/lists.yaml + docs.c + ) + +# Create relevant directories for processed content +file(MAKE_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/hex) +file(MAKE_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/hex/specs) +file(MAKE_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/hex/images) +file(MAKE_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/hex/external) + +find_program(RESOURCE_COMPILER xxd) +file(GLOB_RECURSE COMPILED_RESOURCES RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}/content" "content/*") +foreach(INPUT_FILE ${COMPILED_RESOURCES}) + set(IN ${CMAKE_CURRENT_SOURCE_DIR}/content/${INPUT_FILE}) + set(OUTPUT_FILE ${CMAKE_CURRENT_SOURCE_DIR}/hex/${INPUT_FILE}) + add_custom_command( + OUTPUT hex/${INPUT_FILE} + COMMAND ${RESOURCE_COMPILER} -i < ${IN} > ${OUTPUT_FILE} + COMMENT "Compiling ${INPUT_FILE} to binary" + VERBATIM) + list(APPEND COMPILED_RESOURCES ${OUTPUT_FILE}) +endforeach() + +add_library(api_docs OBJECT ${sources}) +#target_compile_options(api_docs PRIVATE ${EXTRAWARN}) +target_include_directories(api_docs PRIVATE ${PROJECT_SOURCE_DIR}/src) diff --git a/src/api/docs/content/external/highlight-default.min.css b/src/api/docs/content/external/highlight-default.min.css new file mode 100644 index 000000000..2b8ac2cf9 --- /dev/null +++ b/src/api/docs/content/external/highlight-default.min.css @@ -0,0 +1 @@ +.hljs{display:block;overflow-x:auto;padding:.5em;background:#F0F0F0}.hljs,.hljs-subst{color:#444}.hljs-comment{color:#888888}.hljs-keyword,.hljs-attribute,.hljs-selector-tag,.hljs-meta-keyword,.hljs-doctag,.hljs-name{font-weight:bold}.hljs-type,.hljs-string,.hljs-number,.hljs-selector-id,.hljs-selector-class,.hljs-quote,.hljs-template-tag,.hljs-deletion{color:#880000}.hljs-title,.hljs-section{color:#880000;font-weight:bold}.hljs-regexp,.hljs-symbol,.hljs-variable,.hljs-template-variable,.hljs-link,.hljs-selector-attr,.hljs-selector-pseudo{color:#BC6060}.hljs-literal{color:#78A960}.hljs-built_in,.hljs-bullet,.hljs-code,.hljs-addition{color:#397300}.hljs-meta{color:#1f7199}.hljs-meta-string{color:#4d99bf}.hljs-emphasis{font-style:italic}.hljs-strong{font-weight:bold} \ No newline at end of file diff --git a/src/api/docs/content/external/highlight.min.js b/src/api/docs/content/external/highlight.min.js new file mode 100644 index 000000000..8633702c0 --- /dev/null +++ b/src/api/docs/content/external/highlight.min.js @@ -0,0 +1,2 @@ +/*! highlight.js v9.18.1 | BSD3 License | git.io/hljslicense */ +!function(e){var t="object"==typeof window&&window||"object"==typeof self&&self;"undefined"==typeof exports||exports.nodeType?t&&(t.hljs=e({}),"function"==typeof define&&define.amd&&define([],function(){return t.hljs})):e(exports)}(function(n){var u=[],s=Object.keys,w={},b={},x=!0,t=/^(no-?highlight|plain|text)$/i,p=/\blang(?:uage)?-([\w-]+)\b/i,r=/((^(<[^>]+>|\t|)+|(?:\n)))/gm,a={case_insensitive:"cI",lexemes:"l",contains:"c",keywords:"k",subLanguage:"sL",className:"cN",begin:"b",beginKeywords:"bK",end:"e",endsWithParent:"eW",illegal:"i",excludeBegin:"eB",excludeEnd:"eE",returnBegin:"rB",returnEnd:"rE",variants:"v",IDENT_RE:"IR",UNDERSCORE_IDENT_RE:"UIR",NUMBER_RE:"NR",C_NUMBER_RE:"CNR",BINARY_NUMBER_RE:"BNR",RE_STARTERS_RE:"RSR",BACKSLASH_ESCAPE:"BE",APOS_STRING_MODE:"ASM",QUOTE_STRING_MODE:"QSM",PHRASAL_WORDS_MODE:"PWM",C_LINE_COMMENT_MODE:"CLCM",C_BLOCK_COMMENT_MODE:"CBCM",HASH_COMMENT_MODE:"HCM",NUMBER_MODE:"NM",C_NUMBER_MODE:"CNM",BINARY_NUMBER_MODE:"BNM",CSS_NUMBER_MODE:"CSSNM",REGEXP_MODE:"RM",TITLE_MODE:"TM",UNDERSCORE_TITLE_MODE:"UTM",COMMENT:"C",beginRe:"bR",endRe:"eR",illegalRe:"iR",lexemesRe:"lR",terminators:"t",terminator_end:"tE"},k="",E="Could not find the language '{}', did you forget to load/include a language module?",M={classPrefix:"hljs-",tabReplace:null,useBR:!1,languages:void 0},c="of and for in not or if then".split(" ");function C(e){return e.replace(/&/g,"&").replace(//g,">")}function f(e){return e.nodeName.toLowerCase()}function m(e){return t.test(e)}function i(e){var t,r={},a=Array.prototype.slice.call(arguments,1);for(t in e)r[t]=e[t];return a.forEach(function(e){for(t in e)r[t]=e[t]}),r}function g(e){var n=[];return function e(t,r){for(var a=t.firstChild;a;a=a.nextSibling)3===a.nodeType?r+=a.nodeValue.length:1===a.nodeType&&(n.push({event:"start",offset:r,node:a}),r=e(a,r),f(a).match(/br|hr|img|input/)||n.push({event:"stop",offset:r,node:a}));return r}(e,0),n}function _(e,t,r){var a=0,n="",i=[];function s(){return e.length&&t.length?e[0].offset!==t[0].offset?e[0].offset"}function o(e){n+=""}function l(e){("start"===e.event?c:o)(e.node)}for(;e.length||t.length;){var d=s();if(n+=C(r.substring(a,d[0].offset)),a=d[0].offset,d===e){for(i.reverse().forEach(o);l(d.splice(0,1)[0]),(d=s())===e&&d.length&&d[0].offset===a;);i.reverse().forEach(c)}else"start"===d[0].event?i.push(d[0].node):i.pop(),l(d.splice(0,1)[0])}return n+C(r.substr(a))}function o(t){return t.v&&!t.cached_variants&&(t.cached_variants=t.v.map(function(e){return i(t,{v:null},e)})),t.cached_variants?t.cached_variants:function e(t){return!!t&&(t.eW||e(t.starts))}(t)?[i(t,{starts:t.starts?i(t.starts):null})]:Object.isFrozen(t)?[i(t)]:[t]}function l(e){if(a&&!e.langApiRestored){for(var t in e.langApiRestored=!0,a)e[t]&&(e[a[t]]=e[t]);(e.c||[]).concat(e.v||[]).forEach(l)}}function h(t,r){var i={};return"string"==typeof t?a("keyword",t):s(t).forEach(function(e){a(e,t[e])}),i;function a(n,e){r&&(e=e.toLowerCase()),e.split(" ").forEach(function(e){var t,r,a=e.split("|");i[a[0]]=[n,(t=a[0],(r=a[1])?Number(r):function(e){return-1!=c.indexOf(e.toLowerCase())}(t)?0:1)]})}}function B(a){function d(e){return e&&e.source||e}function u(e,t){return new RegExp(d(e),"m"+(a.cI?"i":"")+(t?"g":""))}function n(n){var i,e,s={},c=[],o={},r=1;function t(e,t){s[r]=e,c.push([e,t]),r+=new RegExp(t.toString()+"|").exec("").length-1+1}for(var a=0;a')+t+(r?"":k)}function s(){g+=(null!=f.sL?function(){var e="string"==typeof f.sL;if(e&&!w[f.sL])return C(_);var t=e?R(f.sL,_,!0,m[f.sL]):S(_,f.sL.length?f.sL:void 0);return 0")+'"');if("end"===t.type){var a=d(t);if(null!=a)return a}return _+=r,r.length}var b=A(t);if(!b)throw console.error(E.replace("{}",t)),new Error('Unknown language: "'+t+'"');B(b);var p,f=r||b,m={},g="";for(p=f;p!==b;p=p.parent)p.cN&&(g=c(p.cN,"",!0)+g);var _="",h=0;try{for(var v,y,N=0;f.t.lastIndex=N,v=f.t.exec(i);)y=a(i.substring(N,v.index),v),N=v.index+y;for(a(i.substr(N)),p=f;p.parent;p=p.parent)p.cN&&(g+=k);return{relevance:h,value:g,i:!1,language:t,top:f}}catch(e){if(e.message&&-1!==e.message.indexOf("Illegal"))return{i:!0,relevance:0,value:C(i)};if(x)return{relevance:0,value:C(i),language:t,top:f,errorRaised:e};throw e}}function S(r,e){e=e||M.languages||s(w);var a={relevance:0,value:C(r)},n=a;return e.filter(A).filter(L).forEach(function(e){var t=R(e,r,!1);t.language=e,t.relevance>n.relevance&&(n=t),t.relevance>a.relevance&&(n=a,a=t)}),n.language&&(a.second_best=n),a}function v(e){return M.tabReplace||M.useBR?e.replace(r,function(e,t){return M.useBR&&"\n"===e?"
":M.tabReplace?t.replace(/\t/g,M.tabReplace):""}):e}function d(e){var t,r,a,n,i,s,c,o,l,d,u=function(e){var t,r,a,n,i=e.className+" ";if(i+=e.parentNode?e.parentNode.className:"",r=p.exec(i)){var s=A(r[1]);return s||(console.warn(E.replace("{}",r[1])),console.warn("Falling back to no-highlight mode for this block.",e)),s?r[1]:"no-highlight"}for(t=0,a=(i=i.split(/\s+/)).length;t/g,"\n"):t=e,i=t.textContent,a=u?R(u,i,!0):S(i),(r=g(t)).length&&((n=document.createElement("div")).innerHTML=a.value,a.value=_(r,g(n),i)),a.value=v(a.value),e.innerHTML=a.value,e.className=(s=e.className,c=u,o=a.language,l=c?b[c]:o,d=[s.trim()],s.match(/\bhljs\b/)||d.push("hljs"),-1===s.indexOf(l)&&d.push(l),d.join(" ").trim()),e.result={language:a.language,re:a.relevance},a.second_best&&(e.second_best={language:a.second_best.language,re:a.second_best.relevance}))}function y(){if(!y.called){y.called=!0;var e=document.querySelectorAll("pre code");u.forEach.call(e,d)}}var N={disableAutodetect:!0};function A(e){return e=(e||"").toLowerCase(),w[e]||w[b[e]]}function L(e){var t=A(e);return t&&!t.disableAutodetect}return n.highlight=R,n.highlightAuto=S,n.fixMarkup=v,n.highlightBlock=d,n.configure=function(e){M=i(M,e)},n.initHighlighting=y,n.initHighlightingOnLoad=function(){window.addEventListener("DOMContentLoaded",y,!1),window.addEventListener("load",y,!1)},n.registerLanguage=function(t,e){var r;try{r=e(n)}catch(e){if(console.error("Language definition for '{}' could not be registered.".replace("{}",t)),!x)throw e;console.error(e),r=N}l(w[t]=r),r.rawDefinition=e.bind(null,n),r.aliases&&r.aliases.forEach(function(e){b[e]=t})},n.listLanguages=function(){return s(w)},n.getLanguage=A,n.requireLanguage=function(e){var t=A(e);if(t)return t;throw new Error("The '{}' language is required, but not loaded.".replace("{}",e))},n.autoDetection=L,n.inherit=i,n.debugMode=function(){x=!1},n.IR=n.IDENT_RE="[a-zA-Z]\\w*",n.UIR=n.UNDERSCORE_IDENT_RE="[a-zA-Z_]\\w*",n.NR=n.NUMBER_RE="\\b\\d+(\\.\\d+)?",n.CNR=n.C_NUMBER_RE="(-?)(\\b0[xX][a-fA-F0-9]+|(\\b\\d+(\\.\\d*)?|\\.\\d+)([eE][-+]?\\d+)?)",n.BNR=n.BINARY_NUMBER_RE="\\b(0b[01]+)",n.RSR=n.RE_STARTERS_RE="!|!=|!==|%|%=|&|&&|&=|\\*|\\*=|\\+|\\+=|,|-|-=|/=|/|:|;|<<|<<=|<=|<|===|==|=|>>>=|>>=|>=|>>>|>>|>|\\?|\\[|\\{|\\(|\\^|\\^=|\\||\\|=|\\|\\||~",n.BE=n.BACKSLASH_ESCAPE={b:"\\\\[\\s\\S]",relevance:0},n.ASM=n.APOS_STRING_MODE={cN:"string",b:"'",e:"'",i:"\\n",c:[n.BE]},n.QSM=n.QUOTE_STRING_MODE={cN:"string",b:'"',e:'"',i:"\\n",c:[n.BE]},n.PWM=n.PHRASAL_WORDS_MODE={b:/\b(a|an|the|are|I'm|isn't|don't|doesn't|won't|but|just|should|pretty|simply|enough|gonna|going|wtf|so|such|will|you|your|they|like|more)\b/},n.C=n.COMMENT=function(e,t,r){var a=n.inherit({cN:"comment",b:e,e:t,c:[]},r||{});return a.c.push(n.PWM),a.c.push({cN:"doctag",b:"(?:TODO|FIXME|NOTE|BUG|XXX):",relevance:0}),a},n.CLCM=n.C_LINE_COMMENT_MODE=n.C("//","$"),n.CBCM=n.C_BLOCK_COMMENT_MODE=n.C("/\\*","\\*/"),n.HCM=n.HASH_COMMENT_MODE=n.C("#","$"),n.NM=n.NUMBER_MODE={cN:"number",b:n.NR,relevance:0},n.CNM=n.C_NUMBER_MODE={cN:"number",b:n.CNR,relevance:0},n.BNM=n.BINARY_NUMBER_MODE={cN:"number",b:n.BNR,relevance:0},n.CSSNM=n.CSS_NUMBER_MODE={cN:"number",b:n.NR+"(%|em|ex|ch|rem|vw|vh|vmin|vmax|cm|mm|in|pt|pc|px|deg|grad|rad|turn|s|ms|Hz|kHz|dpi|dpcm|dppx)?",relevance:0},n.RM=n.REGEXP_MODE={cN:"regexp",b:/\//,e:/\/[gimuy]*/,i:/\n/,c:[n.BE,{b:/\[/,e:/\]/,relevance:0,c:[n.BE]}]},n.TM=n.TITLE_MODE={cN:"title",b:n.IR,relevance:0},n.UTM=n.UNDERSCORE_TITLE_MODE={cN:"title",b:n.UIR,relevance:0},n.METHOD_GUARD={b:"\\.\\s*"+n.UIR,relevance:0},[n.BE,n.ASM,n.QSM,n.PWM,n.C,n.CLCM,n.CBCM,n.HCM,n.NM,n.CNM,n.BNM,n.CSSNM,n.RM,n.TM,n.UTM,n.METHOD_GUARD].forEach(function(e){!function t(r){Object.freeze(r);var a="function"==typeof r;Object.getOwnPropertyNames(r).forEach(function(e){!r.hasOwnProperty(e)||null===r[e]||"object"!=typeof r[e]&&"function"!=typeof r[e]||a&&("caller"===e||"callee"===e||"arguments"===e)||Object.isFrozen(r[e])||t(r[e])});return r}(e)}),n.registerLanguage("apache",function(e){var t={cN:"number",b:"[\\$%]\\d+"};return{aliases:["apacheconf"],cI:!0,c:[e.HCM,{cN:"section",b:""},{cN:"attribute",b:/\w+/,relevance:0,k:{nomarkup:"order deny allow setenv rewriterule rewriteengine rewritecond documentroot sethandler errordocument loadmodule options header listen serverroot servername"},starts:{e:/$/,relevance:0,k:{literal:"on off all"},c:[{cN:"meta",b:"\\s\\[",e:"\\]$"},{cN:"variable",b:"[\\$%]\\{",e:"\\}",c:["self",t]},t,e.QSM]}}],i:/\S/}}),n.registerLanguage("bash",function(e){var t={cN:"variable",v:[{b:/\$[\w\d#@][\w\d_]*/},{b:/\$\{(.*?)}/}]},r={cN:"string",b:/"/,e:/"/,c:[e.BE,t,{cN:"variable",b:/\$\(/,e:/\)/,c:[e.BE]}]};return{aliases:["sh","zsh"],l:/\b-?[a-z\._]+\b/,k:{keyword:"if then else elif fi for while in do done case esac function",literal:"true false",built_in:"break cd continue eval exec exit export getopts hash pwd readonly return shift test times trap umask unset alias bind builtin caller command declare echo enable help let local logout mapfile printf read readarray source type typeset ulimit unalias set shopt autoload bg bindkey bye cap chdir clone comparguments compcall compctl compdescribe compfiles compgroups compquote comptags comptry compvalues dirs disable disown echotc echoti emulate fc fg float functions getcap getln history integer jobs kill limit log noglob popd print pushd pushln rehash sched setcap setopt stat suspend ttyctl unfunction unhash unlimit unsetopt vared wait whence where which zcompile zformat zftp zle zmodload zparseopts zprof zpty zregexparse zsocket zstyle ztcp",_:"-ne -eq -lt -gt -f -d -e -s -l -a"},c:[{cN:"meta",b:/^#![^\n]+sh\s*$/,relevance:10},{cN:"function",b:/\w[\w\d_]*\s*\(\s*\)\s*\{/,rB:!0,c:[e.inherit(e.TM,{b:/\w[\w\d_]*/})],relevance:0},e.HCM,r,{cN:"",b:/\\"/},{cN:"string",b:/'/,e:/'/},t]}}),n.registerLanguage("coffeescript",function(e){var t={keyword:"in if for while finally new do return else break catch instanceof throw try this switch continue typeof delete debugger super yield import export from as default await then unless until loop of by when and or is isnt not",literal:"true false null undefined yes no on off",built_in:"npm require console print module global window document"},r="[A-Za-z$_][0-9A-Za-z$_]*",a={cN:"subst",b:/#\{/,e:/}/,k:t},n=[e.BNM,e.inherit(e.CNM,{starts:{e:"(\\s*/)?",relevance:0}}),{cN:"string",v:[{b:/'''/,e:/'''/,c:[e.BE]},{b:/'/,e:/'/,c:[e.BE]},{b:/"""/,e:/"""/,c:[e.BE,a]},{b:/"/,e:/"/,c:[e.BE,a]}]},{cN:"regexp",v:[{b:"///",e:"///",c:[a,e.HCM]},{b:"//[gim]{0,3}(?=\\W)",relevance:0},{b:/\/(?![ *]).*?(?![\\]).\/[gim]{0,3}(?=\W)/}]},{b:"@"+r},{sL:"javascript",eB:!0,eE:!0,v:[{b:"```",e:"```"},{b:"`",e:"`"}]}];a.c=n;var i=e.inherit(e.TM,{b:r}),s="(\\(.*\\))?\\s*\\B[-=]>",c={cN:"params",b:"\\([^\\(]",rB:!0,c:[{b:/\(/,e:/\)/,k:t,c:["self"].concat(n)}]};return{aliases:["coffee","cson","iced"],k:t,i:/\/\*/,c:n.concat([e.C("###","###"),e.HCM,{cN:"function",b:"^\\s*"+r+"\\s*=\\s*"+s,e:"[-=]>",rB:!0,c:[i,c]},{b:/[:\(,=]\s*/,relevance:0,c:[{cN:"function",b:s,e:"[-=]>",rB:!0,c:[c]}]},{cN:"class",bK:"class",e:"$",i:/[:="\[\]]/,c:[{bK:"extends",eW:!0,i:/[:="\[\]]/,c:[i]},i]},{b:r+":",e:":",rB:!0,rE:!0,relevance:0}])}}),n.registerLanguage("cpp",function(e){function t(e){return"(?:"+e+")?"}var r="decltype\\(auto\\)",a="[a-zA-Z_]\\w*::",n=(t(a),t("<.*?>"),{cN:"keyword",b:"\\b[a-z\\d_]*_t\\b"}),i={cN:"string",v:[{b:'(u8?|U|L)?"',e:'"',i:"\\n",c:[e.BE]},{b:"(u8?|U|L)?'(\\\\(x[0-9A-Fa-f]{2}|u[0-9A-Fa-f]{4,8}|[0-7]{3}|\\S)|.)",e:"'",i:"."},{b:/(?:u8?|U|L)?R"([^()\\ ]{0,16})\((?:.|\n)*?\)\1"/}]},s={cN:"number",v:[{b:"\\b(0b[01']+)"},{b:"(-?)\\b([\\d']+(\\.[\\d']*)?|\\.[\\d']+)(u|U|l|L|ul|UL|f|F|b|B)"},{b:"(-?)(\\b0[xX][a-fA-F0-9']+|(\\b[\\d']+(\\.[\\d']*)?|\\.[\\d']+)([eE][-+]?[\\d']+)?)"}],relevance:0},c={cN:"meta",b:/#\s*[a-z]+\b/,e:/$/,k:{"meta-keyword":"if else elif endif define undef warning error line pragma _Pragma ifdef ifndef include"},c:[{b:/\\\n/,relevance:0},e.inherit(i,{cN:"meta-string"}),{cN:"meta-string",b:/<.*?>/,e:/$/,i:"\\n"},e.CLCM,e.CBCM]},o={cN:"title",b:t(a)+e.IR,relevance:0},l=t(a)+e.IR+"\\s*\\(",d={keyword:"int float while private char char8_t char16_t char32_t catch import module export virtual operator sizeof dynamic_cast|10 typedef const_cast|10 const for static_cast|10 union namespace unsigned long volatile static protected bool template mutable if public friend do goto auto void enum else break extern using asm case typeid wchar_tshort reinterpret_cast|10 default double register explicit signed typename try this switch continue inline delete alignas alignof constexpr consteval constinit decltype concept co_await co_return co_yield requires noexcept static_assert thread_local restrict final override atomic_bool atomic_char atomic_schar atomic_uchar atomic_short atomic_ushort atomic_int atomic_uint atomic_long atomic_ulong atomic_llong atomic_ullong new throw return and and_eq bitand bitor compl not not_eq or or_eq xor xor_eq",built_in:"std string wstring cin cout cerr clog stdin stdout stderr stringstream istringstream ostringstream auto_ptr deque list queue stack vector map set bitset multiset multimap unordered_set unordered_map unordered_multiset unordered_multimap array shared_ptr abort terminate abs acos asin atan2 atan calloc ceil cosh cos exit exp fabs floor fmod fprintf fputs free frexp fscanf future isalnum isalpha iscntrl isdigit isgraph islower isprint ispunct isspace isupper isxdigit tolower toupper labs ldexp log10 log malloc realloc memchr memcmp memcpy memset modf pow printf putchar puts scanf sinh sin snprintf sprintf sqrt sscanf strcat strchr strcmp strcpy strcspn strlen strncat strncmp strncpy strpbrk strrchr strspn strstr tanh tan vfprintf vprintf vsprintf endl initializer_list unique_ptr _Bool complex _Complex imaginary _Imaginary",literal:"true false nullptr NULL"},u=[n,e.CLCM,e.CBCM,s,i],b={v:[{b:/=/,e:/;/},{b:/\(/,e:/\)/},{bK:"new throw return else",e:/;/}],k:d,c:u.concat([{b:/\(/,e:/\)/,k:d,c:u.concat(["self"]),relevance:0}]),relevance:0},p={cN:"function",b:"((decltype\\(auto\\)|(?:[a-zA-Z_]\\w*::)?[a-zA-Z_]\\w*(?:<.*?>)?)[\\*&\\s]+)+"+l,rB:!0,e:/[{;=]/,eE:!0,k:d,i:/[^\w\s\*&:<>]/,c:[{b:r,k:d,relevance:0},{b:l,rB:!0,c:[o],relevance:0},{cN:"params",b:/\(/,e:/\)/,k:d,relevance:0,c:[e.CLCM,e.CBCM,i,s,n,{b:/\(/,e:/\)/,k:d,relevance:0,c:["self",e.CLCM,e.CBCM,i,s,n]}]},n,e.CLCM,e.CBCM,c]};return{aliases:["c","cc","h","c++","h++","hpp","hh","hxx","cxx"],k:d,i:"",k:d,c:["self",n]},{b:e.IR+"::",k:d},{cN:"class",bK:"class struct",e:/[{;:]/,c:[{b://,c:["self"]},e.TM]}]),exports:{preprocessor:c,strings:i,k:d}}}),n.registerLanguage("cs",function(e){var t={keyword:"abstract as base bool break byte case catch char checked const continue decimal default delegate do double enum event explicit extern finally fixed float for foreach goto if implicit in int interface internal is lock long object operator out override params private protected public readonly ref sbyte sealed short sizeof stackalloc static string struct switch this try typeof uint ulong unchecked unsafe ushort using virtual void volatile while add alias ascending async await by descending dynamic equals from get global group into join let nameof on orderby partial remove select set value var when where yield",literal:"null false true"},r={cN:"number",v:[{b:"\\b(0b[01']+)"},{b:"(-?)\\b([\\d']+(\\.[\\d']*)?|\\.[\\d']+)(u|U|l|L|ul|UL|f|F|b|B)"},{b:"(-?)(\\b0[xX][a-fA-F0-9']+|(\\b[\\d']+(\\.[\\d']*)?|\\.[\\d']+)([eE][-+]?[\\d']+)?)"}],relevance:0},a={cN:"string",b:'@"',e:'"',c:[{b:'""'}]},n=e.inherit(a,{i:/\n/}),i={cN:"subst",b:"{",e:"}",k:t},s=e.inherit(i,{i:/\n/}),c={cN:"string",b:/\$"/,e:'"',i:/\n/,c:[{b:"{{"},{b:"}}"},e.BE,s]},o={cN:"string",b:/\$@"/,e:'"',c:[{b:"{{"},{b:"}}"},{b:'""'},i]},l=e.inherit(o,{i:/\n/,c:[{b:"{{"},{b:"}}"},{b:'""'},s]});i.c=[o,c,a,e.ASM,e.QSM,r,e.CBCM],s.c=[l,c,n,e.ASM,e.QSM,r,e.inherit(e.CBCM,{i:/\n/})];var d={v:[o,c,a,e.ASM,e.QSM]},u=e.IR+"(<"+e.IR+"(\\s*,\\s*"+e.IR+")*>)?(\\[\\])?";return{aliases:["csharp","c#"],k:t,i:/::/,c:[e.C("///","$",{rB:!0,c:[{cN:"doctag",v:[{b:"///",relevance:0},{b:"\x3c!--|--\x3e"},{b:""}]}]}),e.CLCM,e.CBCM,{cN:"meta",b:"#",e:"$",k:{"meta-keyword":"if else elif endif define undef warning error line region endregion pragma checksum"}},d,r,{bK:"class interface",e:/[{;=]/,i:/[^\s:,]/,c:[e.TM,e.CLCM,e.CBCM]},{bK:"namespace",e:/[{;=]/,i:/[^\s:]/,c:[e.inherit(e.TM,{b:"[a-zA-Z](\\.?\\w)*"}),e.CLCM,e.CBCM]},{cN:"meta",b:"^\\s*\\[",eB:!0,e:"\\]",eE:!0,c:[{cN:"meta-string",b:/"/,e:/"/}]},{bK:"new return throw await else",relevance:0},{cN:"function",b:"("+u+"\\s+)+"+e.IR+"\\s*\\(",rB:!0,e:/\s*[{;=]/,eE:!0,k:t,c:[{b:e.IR+"\\s*\\(",rB:!0,c:[e.TM],relevance:0},{cN:"params",b:/\(/,e:/\)/,eB:!0,eE:!0,k:t,relevance:0,c:[d,r,e.CBCM]},e.CLCM,e.CBCM]}]}}),n.registerLanguage("css",function(e){var t={b:/(?:[A-Z\_\.\-]+|--[a-zA-Z0-9_-]+)\s*:/,rB:!0,e:";",eW:!0,c:[{cN:"attribute",b:/\S/,e:":",eE:!0,starts:{eW:!0,eE:!0,c:[{b:/[\w-]+\(/,rB:!0,c:[{cN:"built_in",b:/[\w-]+/},{b:/\(/,e:/\)/,c:[e.ASM,e.QSM,e.CSSNM]}]},e.CSSNM,e.QSM,e.ASM,e.CBCM,{cN:"number",b:"#[0-9A-Fa-f]+"},{cN:"meta",b:"!important"}]}}]};return{cI:!0,i:/[=\/|'\$]/,c:[e.CBCM,{cN:"selector-id",b:/#[A-Za-z0-9_-]+/},{cN:"selector-class",b:/\.[A-Za-z0-9_-]+/},{cN:"selector-attr",b:/\[/,e:/\]/,i:"$",c:[e.ASM,e.QSM]},{cN:"selector-pseudo",b:/:(:)?[a-zA-Z0-9\_\-\+\(\)"'.]+/},{b:"@(page|font-face)",l:"@[a-z-]+",k:"@page @font-face"},{b:"@",e:"[{;]",i:/:/,rB:!0,c:[{cN:"keyword",b:/@\-?\w[\w]*(\-\w+)*/},{b:/\s/,eW:!0,eE:!0,relevance:0,k:"and or not only",c:[{b:/[a-z-]+:/,cN:"attribute"},e.ASM,e.QSM,e.CSSNM]}]},{cN:"selector-tag",b:"[a-zA-Z-][a-zA-Z0-9_-]*",relevance:0},{b:"{",e:"}",i:/\S/,c:[e.CBCM,t]}]}}),n.registerLanguage("diff",function(e){return{aliases:["patch"],c:[{cN:"meta",relevance:10,v:[{b:/^@@ +\-\d+,\d+ +\+\d+,\d+ +@@$/},{b:/^\*\*\* +\d+,\d+ +\*\*\*\*$/},{b:/^\-\-\- +\d+,\d+ +\-\-\-\-$/}]},{cN:"comment",v:[{b:/Index: /,e:/$/},{b:/={3,}/,e:/$/},{b:/^\-{3}/,e:/$/},{b:/^\*{3} /,e:/$/},{b:/^\+{3}/,e:/$/},{b:/^\*{15}$/}]},{cN:"addition",b:"^\\+",e:"$"},{cN:"deletion",b:"^\\-",e:"$"},{cN:"addition",b:"^\\!",e:"$"}]}}),n.registerLanguage("go",function(e){var t={keyword:"break default func interface select case map struct chan else goto package switch const fallthrough if range type continue for import return var go defer bool byte complex64 complex128 float32 float64 int8 int16 int32 int64 string uint8 uint16 uint32 uint64 int uint uintptr rune",literal:"true false iota nil",built_in:"append cap close complex copy imag len make new panic print println real recover delete"};return{aliases:["golang"],k:t,i:")?\\s+)+"+e.UIR+"\\s*\\(",rB:!0,e:/[{;=]/,eE:!0,k:t,c:[{b:e.UIR+"\\s*\\(",rB:!0,relevance:0,c:[e.UTM]},{cN:"params",b:/\(/,e:/\)/,k:t,relevance:0,c:[e.ASM,e.QSM,e.CNM,e.CBCM]},e.CLCM,e.CBCM]},r,{cN:"meta",b:"@[A-Za-z]+"}]}}),n.registerLanguage("javascript",function(e){var t="<>",r="",a={b:/<[A-Za-z0-9\\._:-]+/,e:/\/[A-Za-z0-9\\._:-]+>|\/>/},n="[A-Za-z$_][0-9A-Za-z$_]*",i={keyword:"in of if for while finally var new function do return void else break catch instanceof with throw case default try this switch continue typeof delete let yield const export super debugger as async await static import from as",literal:"true false null undefined NaN Infinity",built_in:"eval isFinite isNaN parseFloat parseInt decodeURI decodeURIComponent encodeURI encodeURIComponent escape unescape Object Function Boolean Error EvalError InternalError RangeError ReferenceError StopIteration SyntaxError TypeError URIError Number Math Date String RegExp Array Float32Array Float64Array Int16Array Int32Array Int8Array Uint16Array Uint32Array Uint8Array Uint8ClampedArray ArrayBuffer DataView JSON Intl arguments require module console window document Symbol Set Map WeakSet WeakMap Proxy Reflect Promise"},s={cN:"number",v:[{b:"\\b(0[bB][01]+)n?"},{b:"\\b(0[oO][0-7]+)n?"},{b:e.CNR+"n?"}],relevance:0},c={cN:"subst",b:"\\$\\{",e:"\\}",k:i,c:[]},o={b:"html`",e:"",starts:{e:"`",rE:!1,c:[e.BE,c],sL:"xml"}},l={b:"css`",e:"",starts:{e:"`",rE:!1,c:[e.BE,c],sL:"css"}},d={cN:"string",b:"`",e:"`",c:[e.BE,c]};c.c=[e.ASM,e.QSM,o,l,d,s,e.RM];var u=c.c.concat([e.CBCM,e.CLCM]);return{aliases:["js","jsx","mjs","cjs"],k:i,c:[{cN:"meta",relevance:10,b:/^\s*['"]use (strict|asm)['"]/},{cN:"meta",b:/^#!/,e:/$/},e.ASM,e.QSM,o,l,d,e.CLCM,e.C("/\\*\\*","\\*/",{relevance:0,c:[{cN:"doctag",b:"@[A-Za-z]+",c:[{cN:"type",b:"\\{",e:"\\}",relevance:0},{cN:"variable",b:n+"(?=\\s*(-)|$)",endsParent:!0,relevance:0},{b:/(?=[^\n])\s/,relevance:0}]}]}),e.CBCM,s,{b:/[{,\n]\s*/,relevance:0,c:[{b:n+"\\s*:",rB:!0,relevance:0,c:[{cN:"attr",b:n,relevance:0}]}]},{b:"("+e.RSR+"|\\b(case|return|throw)\\b)\\s*",k:"return throw case",c:[e.CLCM,e.CBCM,e.RM,{cN:"function",b:"(\\(.*?\\)|"+n+")\\s*=>",rB:!0,e:"\\s*=>",c:[{cN:"params",v:[{b:n},{b:/\(\s*\)/},{b:/\(/,e:/\)/,eB:!0,eE:!0,k:i,c:u}]}]},{cN:"",b:/\s/,e:/\s*/,skip:!0},{v:[{b:t,e:r},{b:a.b,e:a.e}],sL:"xml",c:[{b:a.b,e:a.e,skip:!0,c:["self"]}]}],relevance:0},{cN:"function",bK:"function",e:/\{/,eE:!0,c:[e.inherit(e.TM,{b:n}),{cN:"params",b:/\(/,e:/\)/,eB:!0,eE:!0,c:u}],i:/\[|%/},{b:/\$[(.]/},e.METHOD_GUARD,{cN:"class",bK:"class",e:/[{;=]/,eE:!0,i:/[:"\[\]]/,c:[{bK:"extends"},e.UTM]},{bK:"constructor get set",e:/\{/,eE:!0}],i:/#(?!!)/}}),n.registerLanguage("json",function(e){var t={literal:"true false null"},r=[e.CLCM,e.CBCM],a=[e.QSM,e.CNM],n={e:",",eW:!0,eE:!0,c:a,k:t},i={b:"{",e:"}",c:[{cN:"attr",b:/"/,e:/"/,c:[e.BE],i:"\\n"},e.inherit(n,{b:/:/})].concat(r),i:"\\S"},s={b:"\\[",e:"\\]",c:[e.inherit(n)],i:"\\S"};return a.push(i,s),r.forEach(function(e){a.push(e)}),{c:a,k:t,i:"\\S"}}),n.registerLanguage("kotlin",function(e){var t={keyword:"abstract as val var vararg get set class object open private protected public noinline crossinline dynamic final enum if else do while for when throw try catch finally import package is in fun override companion reified inline lateinit init interface annotation data sealed internal infix operator out by constructor super tailrec where const inner suspend typealias external expect actual trait volatile transient native default",built_in:"Byte Short Char Int Long Boolean Float Double Void Unit Nothing",literal:"true false null"},r={cN:"symbol",b:e.UIR+"@"},a={cN:"subst",b:"\\${",e:"}",c:[e.CNM]},n={cN:"variable",b:"\\$"+e.UIR},i={cN:"string",v:[{b:'"""',e:'"""(?=[^"])',c:[n,a]},{b:"'",e:"'",i:/\n/,c:[e.BE]},{b:'"',e:'"',i:/\n/,c:[e.BE,n,a]}]};a.c.push(i);var s={cN:"meta",b:"@(?:file|property|field|get|set|receiver|param|setparam|delegate)\\s*:(?:\\s*"+e.UIR+")?"},c={cN:"meta",b:"@"+e.UIR,c:[{b:/\(/,e:/\)/,c:[e.inherit(i,{cN:"meta-string"})]}]},o={cN:"number",b:"\\b(0[bB]([01]+[01_]+[01]+|[01]+)|0[xX]([a-fA-F0-9]+[a-fA-F0-9_]+[a-fA-F0-9]+|[a-fA-F0-9]+)|(([\\d]+[\\d_]+[\\d]+|[\\d]+)(\\.([\\d]+[\\d_]+[\\d]+|[\\d]+))?|\\.([\\d]+[\\d_]+[\\d]+|[\\d]+))([eE][-+]?\\d+)?)[lLfF]?",relevance:0},l=e.C("/\\*","\\*/",{c:[e.CBCM]}),d={v:[{cN:"type",b:e.UIR},{b:/\(/,e:/\)/,c:[]}]},u=d;return u.v[1].c=[d],d.v[1].c=[u],{aliases:["kt"],k:t,c:[e.C("/\\*\\*","\\*/",{relevance:0,c:[{cN:"doctag",b:"@[A-Za-z]+"}]}),e.CLCM,l,{cN:"keyword",b:/\b(break|continue|return|this)\b/,starts:{c:[{cN:"symbol",b:/@\w+/}]}},r,s,c,{cN:"function",bK:"fun",e:"[(]|$",rB:!0,eE:!0,k:t,i:/fun\s+(<.*>)?[^\s\(]+(\s+[^\s\(]+)\s*=/,relevance:5,c:[{b:e.UIR+"\\s*\\(",rB:!0,relevance:0,c:[e.UTM]},{cN:"type",b://,k:"reified",relevance:0},{cN:"params",b:/\(/,e:/\)/,endsParent:!0,k:t,relevance:0,c:[{b:/:/,e:/[=,\/]/,eW:!0,c:[d,e.CLCM,l],relevance:0},e.CLCM,l,s,c,i,e.CNM]},l]},{cN:"class",bK:"class interface trait",e:/[:\{(]|$/,eE:!0,i:"extends implements",c:[{bK:"public protected internal private constructor"},e.UTM,{cN:"type",b://,eB:!0,eE:!0,relevance:0},{cN:"type",b:/[,:]\s*/,e:/[<\(,]|$/,eB:!0,rE:!0},s,c]},i,{cN:"meta",b:"^#!/usr/bin/env",e:"$",i:"\n"},o]}}),n.registerLanguage("less",function(e){function t(e){return{cN:"string",b:"~?"+e+".*?"+e}}function r(e,t,r){return{cN:e,b:t,relevance:r}}var a="[\\w-]+",n="("+a+"|@{"+a+"})",i=[],s=[],c={b:"\\(",e:"\\)",c:s,relevance:0};s.push(e.CLCM,e.CBCM,t("'"),t('"'),e.CSSNM,{b:"(url|data-uri)\\(",starts:{cN:"string",e:"[\\)\\n]",eE:!0}},r("number","#[0-9A-Fa-f]+\\b"),c,r("variable","@@?"+a,10),r("variable","@{"+a+"}"),r("built_in","~?`[^`]*?`"),{cN:"attribute",b:a+"\\s*:",e:":",rB:!0,eE:!0},{cN:"meta",b:"!important"});var o=s.concat({b:"{",e:"}",c:i}),l={bK:"when",eW:!0,c:[{bK:"and not"}].concat(s)},d={b:n+"\\s*:",rB:!0,e:"[;}]",relevance:0,c:[{cN:"attribute",b:n,e:":",eE:!0,starts:{eW:!0,i:"[<=$]",relevance:0,c:s}}]},u={cN:"keyword",b:"@(import|media|charset|font-face|(-[a-z]+-)?keyframes|supports|document|namespace|page|viewport|host)\\b",starts:{e:"[;{}]",rE:!0,c:s,relevance:0}},b={cN:"variable",v:[{b:"@"+a+"\\s*:",relevance:15},{b:"@"+a}],starts:{e:"[;}]",rE:!0,c:o}},p={v:[{b:"[\\.#:&\\[>]",e:"[;{}]"},{b:n,e:"{"}],rB:!0,rE:!0,i:"[<='$\"]",relevance:0,c:[e.CLCM,e.CBCM,l,r("keyword","all\\b"),r("variable","@{"+a+"}"),r("selector-tag",n+"%?",0),r("selector-id","#"+n),r("selector-class","\\."+n,0),r("selector-tag","&",0),{cN:"selector-attr",b:"\\[",e:"\\]"},{cN:"selector-pseudo",b:/:(:)?[a-zA-Z0-9\_\-\+\(\)"'.]+/},{b:"\\(",e:"\\)",c:o},{b:"!important"}]};return i.push(e.CLCM,e.CBCM,u,b,d,p),{cI:!0,i:"[=>'/<($\"]",c:i}}),n.registerLanguage("lua",function(e){var t="\\[=*\\[",r="\\]=*\\]",a={b:t,e:r,c:["self"]},n=[e.C("--(?!"+t+")","$"),e.C("--"+t,r,{c:[a],relevance:10})];return{l:e.UIR,k:{literal:"true false nil",keyword:"and break do else elseif end for goto if in local not or repeat return then until while",built_in:"_G _ENV _VERSION __index __newindex __mode __call __metatable __tostring __len __gc __add __sub __mul __div __mod __pow __concat __unm __eq __lt __le assert collectgarbage dofile error getfenv getmetatable ipairs load loadfile loadstringmodule next pairs pcall print rawequal rawget rawset require select setfenvsetmetatable tonumber tostring type unpack xpcall arg selfcoroutine resume yield status wrap create running debug getupvalue debug sethook getmetatable gethook setmetatable setlocal traceback setfenv getinfo setupvalue getlocal getregistry getfenv io lines write close flush open output type read stderr stdin input stdout popen tmpfile math log max acos huge ldexp pi cos tanh pow deg tan cosh sinh random randomseed frexp ceil floor rad abs sqrt modf asin min mod fmod log10 atan2 exp sin atan os exit setlocale date getenv difftime remove time clock tmpname rename execute package preload loadlib loaded loaders cpath config path seeall string sub upper len gfind rep find match char dump gmatch reverse byte format gsub lower table setn insert getn foreachi maxn foreach concat sort remove"},c:n.concat([{cN:"function",bK:"function",e:"\\)",c:[e.inherit(e.TM,{b:"([_a-zA-Z]\\w*\\.)*([_a-zA-Z]\\w*:)?[_a-zA-Z]\\w*"}),{cN:"params",b:"\\(",eW:!0,c:n}].concat(n)},e.CNM,e.ASM,e.QSM,{cN:"string",b:t,e:r,c:[a],relevance:5}])}}),n.registerLanguage("makefile",function(e){var t={cN:"variable",v:[{b:"\\$\\("+e.UIR+"\\)",c:[e.BE]},{b:/\$[@%`]+/}]}]}]};return{aliases:["html","xhtml","rss","atom","xjb","xsd","xsl","plist","wsf","svg"],cI:!0,c:[{cN:"meta",b:"",relevance:10,c:[r,i,n,a,{b:"\\[",e:"\\]",c:[{cN:"meta",b:"",c:[r,a,i,n]}]}]},e.C("\x3c!--","--\x3e",{relevance:10}),{b:"<\\!\\[CDATA\\[",e:"\\]\\]>",relevance:10},t,{cN:"meta",b:/<\?xml/,e:/\?>/,relevance:10},{b:/<\?(php)?/,e:/\?>/,sL:"php",c:[{b:"/\\*",e:"\\*/",skip:!0},{b:'b"',e:'"',skip:!0},{b:"b'",e:"'",skip:!0},e.inherit(e.ASM,{i:null,cN:null,c:null,skip:!0}),e.inherit(e.QSM,{i:null,cN:null,c:null,skip:!0})]},{cN:"tag",b:")",e:">",k:{name:"style"},c:[s],starts:{e:"",rE:!0,sL:["css","xml"]}},{cN:"tag",b:")",e:">",k:{name:"script"},c:[s],starts:{e:"<\/script>",rE:!0,sL:["actionscript","javascript","handlebars","xml"]}},{cN:"tag",b:"",c:[{cN:"name",b:/[^\/><\s]+/,relevance:0},s]}]}}),n.registerLanguage("markdown",function(e){return{aliases:["md","mkdown","mkd"],c:[{cN:"section",v:[{b:"^#{1,6}",e:"$"},{b:"^.+?\\n[=-]{2,}$"}]},{b:"<",e:">",sL:"xml",relevance:0},{cN:"bullet",b:"^\\s*([*+-]|(\\d+\\.))\\s+"},{cN:"strong",b:"[*_]{2}.+?[*_]{2}"},{cN:"emphasis",v:[{b:"\\*.+?\\*"},{b:"_.+?_",relevance:0}]},{cN:"quote",b:"^>\\s+",e:"$"},{cN:"code",v:[{b:"^```\\w*\\s*$",e:"^```[ ]*$"},{b:"`.+?`"},{b:"^( {4}|\\t)",e:"$",relevance:0}]},{b:"^[-\\*]{3,}",e:"$"},{b:"\\[.+?\\][\\(\\[].*?[\\)\\]]",rB:!0,c:[{cN:"string",b:"\\[",e:"\\]",eB:!0,rE:!0,relevance:0},{cN:"link",b:"\\]\\(",e:"\\)",eB:!0,eE:!0},{cN:"symbol",b:"\\]\\[",e:"\\]",eB:!0,eE:!0}],relevance:10},{b:/^\[[^\n]+\]:/,rB:!0,c:[{cN:"symbol",b:/\[/,e:/\]/,eB:!0,eE:!0},{cN:"link",b:/:\s*/,e:/$/,eB:!0}]}]}}),n.registerLanguage("nginx",function(e){var t={cN:"variable",v:[{b:/\$\d+/},{b:/\$\{/,e:/}/},{b:"[\\$\\@]"+e.UIR}]},r={eW:!0,l:"[a-z/_]+",k:{literal:"on off yes no true false none blocked debug info notice warn error crit select break last permanent redirect kqueue rtsig epoll poll /dev/poll"},relevance:0,i:"=>",c:[e.HCM,{cN:"string",c:[e.BE,t],v:[{b:/"/,e:/"/},{b:/'/,e:/'/}]},{b:"([a-z]+):/",e:"\\s",eW:!0,eE:!0,c:[t]},{cN:"regexp",c:[e.BE,t],v:[{b:"\\s\\^",e:"\\s|{|;",rE:!0},{b:"~\\*?\\s+",e:"\\s|{|;",rE:!0},{b:"\\*(\\.[a-z\\-]+)+"},{b:"([a-z\\-]+\\.)+\\*"}]},{cN:"number",b:"\\b\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}(:\\d{1,5})?\\b"},{cN:"number",b:"\\b\\d+[kKmMgGdshdwy]*\\b",relevance:0},t]};return{aliases:["nginxconf"],c:[e.HCM,{b:e.UIR+"\\s+{",rB:!0,e:"{",c:[{cN:"section",b:e.UIR}],relevance:0},{b:e.UIR+"\\s",e:";|{",rB:!0,c:[{cN:"attribute",b:e.UIR,starts:r}],relevance:0}],i:"[^\\s\\}]"}}),n.registerLanguage("objectivec",function(e){var t=/[a-zA-Z@][a-zA-Z0-9_]*/,r="@interface @class @protocol @implementation";return{aliases:["mm","objc","obj-c"],k:{keyword:"int float while char export sizeof typedef const struct for union unsigned long volatile static bool mutable if do return goto void enum else break extern asm case short default double register explicit signed typename this switch continue wchar_t inline readonly assign readwrite self @synchronized id typeof nonatomic super unichar IBOutlet IBAction strong weak copy in out inout bycopy byref oneway __strong __weak __block __autoreleasing @private @protected @public @try @property @end @throw @catch @finally @autoreleasepool @synthesize @dynamic @selector @optional @required @encode @package @import @defs @compatibility_alias __bridge __bridge_transfer __bridge_retained __bridge_retain __covariant __contravariant __kindof _Nonnull _Nullable _Null_unspecified __FUNCTION__ __PRETTY_FUNCTION__ __attribute__ getter setter retain unsafe_unretained nonnull nullable null_unspecified null_resettable class instancetype NS_DESIGNATED_INITIALIZER NS_UNAVAILABLE NS_REQUIRES_SUPER NS_RETURNS_INNER_POINTER NS_INLINE NS_AVAILABLE NS_DEPRECATED NS_ENUM NS_OPTIONS NS_SWIFT_UNAVAILABLE NS_ASSUME_NONNULL_BEGIN NS_ASSUME_NONNULL_END NS_REFINED_FOR_SWIFT NS_SWIFT_NAME NS_SWIFT_NOTHROW NS_DURING NS_HANDLER NS_ENDHANDLER NS_VALUERETURN NS_VOIDRETURN",literal:"false true FALSE TRUE nil YES NO NULL",built_in:"BOOL dispatch_once_t dispatch_queue_t dispatch_sync dispatch_async dispatch_once"},l:t,i:"/,e:/$/,i:"\\n"},e.CLCM,e.CBCM]},{cN:"class",b:"("+r.split(" ").join("|")+")\\b",e:"({|$)",eE:!0,k:r,l:t,c:[e.UTM]},{b:"\\."+e.UIR,relevance:0}]}}),n.registerLanguage("perl",function(e){var t="getpwent getservent quotemeta msgrcv scalar kill dbmclose undef lc ma syswrite tr send umask sysopen shmwrite vec qx utime local oct semctl localtime readpipe do return format read sprintf dbmopen pop getpgrp not getpwnam rewinddir qqfileno qw endprotoent wait sethostent bless s|0 opendir continue each sleep endgrent shutdown dump chomp connect getsockname die socketpair close flock exists index shmgetsub for endpwent redo lstat msgctl setpgrp abs exit select print ref gethostbyaddr unshift fcntl syscall goto getnetbyaddr join gmtime symlink semget splice x|0 getpeername recv log setsockopt cos last reverse gethostbyname getgrnam study formline endhostent times chop length gethostent getnetent pack getprotoent getservbyname rand mkdir pos chmod y|0 substr endnetent printf next open msgsnd readdir use unlink getsockopt getpriority rindex wantarray hex system getservbyport endservent int chr untie rmdir prototype tell listen fork shmread ucfirst setprotoent else sysseek link getgrgid shmctl waitpid unpack getnetbyname reset chdir grep split require caller lcfirst until warn while values shift telldir getpwuid my getprotobynumber delete and sort uc defined srand accept package seekdir getprotobyname semop our rename seek if q|0 chroot sysread setpwent no crypt getc chown sqrt write setnetent setpriority foreach tie sin msgget map stat getlogin unless elsif truncate exec keys glob tied closedirioctl socket readlink eval xor readline binmode setservent eof ord bind alarm pipe atan2 getgrent exp time push setgrent gt lt or ne m|0 break given say state when",r={cN:"subst",b:"[$@]\\{",e:"\\}",k:t},a={b:"->{",e:"}"},n={v:[{b:/\$\d/},{b:/[\$%@](\^\w\b|#\w+(::\w+)*|{\w+}|\w+(::\w*)*)/},{b:/[\$%@][^\s\w{]/,relevance:0}]},i=[e.BE,r,n],s=[n,e.HCM,e.C("^\\=\\w","\\=cut",{eW:!0}),a,{cN:"string",c:i,v:[{b:"q[qwxr]?\\s*\\(",e:"\\)",relevance:5},{b:"q[qwxr]?\\s*\\[",e:"\\]",relevance:5},{b:"q[qwxr]?\\s*\\{",e:"\\}",relevance:5},{b:"q[qwxr]?\\s*\\|",e:"\\|",relevance:5},{b:"q[qwxr]?\\s*\\<",e:"\\>",relevance:5},{b:"qw\\s+q",e:"q",relevance:5},{b:"'",e:"'",c:[e.BE]},{b:'"',e:'"'},{b:"`",e:"`",c:[e.BE]},{b:"{\\w+}",c:[],relevance:0},{b:"-?\\w+\\s*\\=\\>",c:[],relevance:0}]},{cN:"number",b:"(\\b0[0-7_]+)|(\\b0x[0-9a-fA-F_]+)|(\\b[1-9][0-9_]*(\\.[0-9_]+)?)|[0_]\\b",relevance:0},{b:"(\\/\\/|"+e.RSR+"|\\b(split|return|print|reverse|grep)\\b)\\s*",k:"split return print reverse grep",relevance:0,c:[e.HCM,{cN:"regexp",b:"(s|tr|y)/(\\\\.|[^/])*/(\\\\.|[^/])*/[a-z]*",relevance:10},{cN:"regexp",b:"(m|qr)?/",e:"/[a-z]*",c:[e.BE],relevance:0}]},{cN:"function",bK:"sub",e:"(\\s*\\(.*?\\))?[;{]",eE:!0,relevance:5,c:[e.TM]},{b:"-\\w\\b",relevance:0},{b:"^__DATA__$",e:"^__END__$",sL:"mojolicious",c:[{b:"^@@.*",e:"$",cN:"comment"}]}];return r.c=s,{aliases:["pl","pm"],l:/[\w\.]+/,k:t,c:a.c=s}}),n.registerLanguage("php",function(e){var t={b:"\\$+[a-zA-Z_-ÿ][a-zA-Z0-9_-ÿ]*"},r={cN:"meta",b:/<\?(php)?|\?>/},a={cN:"string",c:[e.BE,r],v:[{b:'b"',e:'"'},{b:"b'",e:"'"},e.inherit(e.ASM,{i:null}),e.inherit(e.QSM,{i:null})]},n={v:[e.BNM,e.CNM]};return{aliases:["php","php3","php4","php5","php6","php7"],cI:!0,k:"and include_once list abstract global private echo interface as static endswitch array null if endwhile or const for endforeach self var while isset public protected exit foreach throw elseif include __FILE__ empty require_once do xor return parent clone use __CLASS__ __LINE__ else break print eval new catch __METHOD__ case exception default die require __FUNCTION__ enddeclare final try switch continue endfor endif declare unset true false trait goto instanceof insteadof __DIR__ __NAMESPACE__ yield finally",c:[e.HCM,e.C("//","$",{c:[r]}),e.C("/\\*","\\*/",{c:[{cN:"doctag",b:"@[A-Za-z]+"}]}),e.C("__halt_compiler.+?;",!1,{eW:!0,k:"__halt_compiler",l:e.UIR}),{cN:"string",b:/<<<['"]?\w+['"]?$/,e:/^\w+;?$/,c:[e.BE,{cN:"subst",v:[{b:/\$\w+/},{b:/\{\$/,e:/\}/}]}]},r,{cN:"keyword",b:/\$this\b/},t,{b:/(::|->)+[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*/},{cN:"function",bK:"function",e:/[;{]/,eE:!0,i:"\\$|\\[|%",c:[e.UTM,{cN:"params",b:"\\(",e:"\\)",c:["self",t,e.CBCM,a,n]}]},{cN:"class",bK:"class interface",e:"{",eE:!0,i:/[:\(\$"]/,c:[{bK:"extends implements"},e.UTM]},{bK:"namespace",e:";",i:/[\.']/,c:[e.UTM]},{bK:"use",e:";",c:[e.UTM]},{b:"=>"},a,n]}}),n.registerLanguage("plaintext",function(e){return{disableAutodetect:!0}}),n.registerLanguage("properties",function(e){var t="[ \\t\\f]*",r="("+t+"[:=]"+t+"|[ \\t\\f]+)",a="([^\\\\\\W:= \\t\\f\\n]|\\\\.)+",n="([^\\\\:= \\t\\f\\n]|\\\\.)+",i={e:r,relevance:0,starts:{cN:"string",e:/$/,relevance:0,c:[{b:"\\\\\\n"}]}};return{cI:!0,i:/\S/,c:[e.C("^\\s*[!#]","$"),{b:a+r,rB:!0,c:[{cN:"attr",b:a,endsParent:!0,relevance:0}],starts:i},{b:n+r,rB:!0,relevance:0,c:[{cN:"meta",b:n,endsParent:!0,relevance:0}],starts:i},{cN:"attr",relevance:0,b:n+t+"$"}]}}),n.registerLanguage("python",function(e){var t={keyword:"and elif is global as in if from raise for except finally print import pass return exec else break not with class assert yield try while continue del or def lambda async await nonlocal|10",built_in:"Ellipsis NotImplemented",literal:"False None True"},r={cN:"meta",b:/^(>>>|\.\.\.) /},a={cN:"subst",b:/\{/,e:/\}/,k:t,i:/#/},n={b:/\{\{/,relevance:0},i={cN:"string",c:[e.BE],v:[{b:/(u|b)?r?'''/,e:/'''/,c:[e.BE,r],relevance:10},{b:/(u|b)?r?"""/,e:/"""/,c:[e.BE,r],relevance:10},{b:/(fr|rf|f)'''/,e:/'''/,c:[e.BE,r,n,a]},{b:/(fr|rf|f)"""/,e:/"""/,c:[e.BE,r,n,a]},{b:/(u|r|ur)'/,e:/'/,relevance:10},{b:/(u|r|ur)"/,e:/"/,relevance:10},{b:/(b|br)'/,e:/'/},{b:/(b|br)"/,e:/"/},{b:/(fr|rf|f)'/,e:/'/,c:[e.BE,n,a]},{b:/(fr|rf|f)"/,e:/"/,c:[e.BE,n,a]},e.ASM,e.QSM]},s={cN:"number",relevance:0,v:[{b:e.BNR+"[lLjJ]?"},{b:"\\b(0o[0-7]+)[lLjJ]?"},{b:e.CNR+"[lLjJ]?"}]},c={cN:"params",b:/\(/,e:/\)/,c:["self",r,s,i,e.HCM]};return a.c=[i,s,r],{aliases:["py","gyp","ipython"],k:t,i:/(<\/|->|\?)|=>/,c:[r,s,{bK:"if",relevance:0},i,e.HCM,{v:[{cN:"function",bK:"def"},{cN:"class",bK:"class"}],e:/:/,i:/[${=;\n,]/,c:[e.UTM,c,{b:/->/,eW:!0,k:"None"}]},{cN:"meta",b:/^[\t ]*@/,e:/$/},{b:/\b(print|exec)\(/}]}}),n.registerLanguage("ruby",function(e){var t="[a-zA-Z_]\\w*[!?=]?|[-+~]\\@|<<|>>|=~|===?|<=>|[<>]=?|\\*\\*|[-/+%^&*~`|]|\\[\\]=?",r={keyword:"and then defined module in return redo if BEGIN retry end for self when next until do begin unless END rescue else break undef not super class case require yield alias while ensure elsif or include attr_reader attr_writer attr_accessor",literal:"true false nil"},a={cN:"doctag",b:"@[A-Za-z]+"},n={b:"#<",e:">"},i=[e.C("#","$",{c:[a]}),e.C("^\\=begin","^\\=end",{c:[a],relevance:10}),e.C("^__END__","\\n$")],s={cN:"subst",b:"#\\{",e:"}",k:r},c={cN:"string",c:[e.BE,s],v:[{b:/'/,e:/'/},{b:/"/,e:/"/},{b:/`/,e:/`/},{b:"%[qQwWx]?\\(",e:"\\)"},{b:"%[qQwWx]?\\[",e:"\\]"},{b:"%[qQwWx]?{",e:"}"},{b:"%[qQwWx]?<",e:">"},{b:"%[qQwWx]?/",e:"/"},{b:"%[qQwWx]?%",e:"%"},{b:"%[qQwWx]?-",e:"-"},{b:"%[qQwWx]?\\|",e:"\\|"},{b:/\B\?(\\\d{1,3}|\\x[A-Fa-f0-9]{1,2}|\\u[A-Fa-f0-9]{4}|\\?\S)\b/},{b:/<<[-~]?'?(\w+)(?:.|\n)*?\n\s*\1\b/,rB:!0,c:[{b:/<<[-~]?'?/},{b:/\w+/,endSameAsBegin:!0,c:[e.BE,s]}]}]},o={cN:"params",b:"\\(",e:"\\)",endsParent:!0,k:r},l=[c,n,{cN:"class",bK:"class module",e:"$|;",i:/=/,c:[e.inherit(e.TM,{b:"[A-Za-z_]\\w*(::\\w+)*(\\?|\\!)?"}),{b:"<\\s*",c:[{b:"("+e.IR+"::)?"+e.IR}]}].concat(i)},{cN:"function",bK:"def",e:"$|;",c:[e.inherit(e.TM,{b:t}),o].concat(i)},{b:e.IR+"::"},{cN:"symbol",b:e.UIR+"(\\!|\\?)?:",relevance:0},{cN:"symbol",b:":(?!\\s)",c:[c,{b:t}],relevance:0},{cN:"number",b:"(\\b0[0-7_]+)|(\\b0x[0-9a-fA-F_]+)|(\\b[1-9][0-9_]*(\\.[0-9_]+)?)|[0_]\\b",relevance:0},{b:"(\\$\\W)|((\\$|\\@\\@?)(\\w+))"},{cN:"params",b:/\|/,e:/\|/,k:r},{b:"("+e.RSR+"|unless)\\s*",k:"unless",c:[n,{cN:"regexp",c:[e.BE,s],i:/\n/,v:[{b:"/",e:"/[a-z]*"},{b:"%r{",e:"}[a-z]*"},{b:"%r\\(",e:"\\)[a-z]*"},{b:"%r!",e:"![a-z]*"},{b:"%r\\[",e:"\\][a-z]*"}]}].concat(i),relevance:0}].concat(i);s.c=l;var d=[{b:/^\s*=>/,starts:{e:"$",c:o.c=l}},{cN:"meta",b:"^([>?]>|[\\w#]+\\(\\w+\\):\\d+:\\d+>|(\\w+-)?\\d+\\.\\d+\\.\\d(p\\d+)?[^>]+>)",starts:{e:"$",c:l}}];return{aliases:["rb","gemspec","podspec","thor","irb"],k:r,i:/\/\*/,c:i.concat(d).concat(l)}}),n.registerLanguage("rust",function(e){var t="([ui](8|16|32|64|128|size)|f(32|64))?",r="drop i8 i16 i32 i64 i128 isize u8 u16 u32 u64 u128 usize f32 f64 str char bool Box Option Result String Vec Copy Send Sized Sync Drop Fn FnMut FnOnce ToOwned Clone Debug PartialEq PartialOrd Eq Ord AsRef AsMut Into From Default Iterator Extend IntoIterator DoubleEndedIterator ExactSizeIterator SliceConcatExt ToString assert! assert_eq! bitflags! bytes! cfg! col! concat! concat_idents! debug_assert! debug_assert_eq! env! panic! file! format! format_args! include_bin! include_str! line! local_data_key! module_path! option_env! print! println! select! stringify! try! unimplemented! unreachable! vec! write! writeln! macro_rules! assert_ne! debug_assert_ne!";return{aliases:["rs"],k:{keyword:"abstract as async await become box break const continue crate do dyn else enum extern false final fn for if impl in let loop macro match mod move mut override priv pub ref return self Self static struct super trait true try type typeof unsafe unsized use virtual where while yield",literal:"true false Some None Ok Err",built_in:r},l:e.IR+"!?",i:""}]}}),n.registerLanguage("scss",function(e){var t="@[a-z-]+",r={cN:"variable",b:"(\\$[a-zA-Z-][a-zA-Z0-9_-]*)\\b"},a={cN:"number",b:"#[0-9A-Fa-f]+"};e.CSSNM,e.QSM,e.ASM,e.CBCM;return{cI:!0,i:"[=/|']",c:[e.CLCM,e.CBCM,{cN:"selector-id",b:"\\#[A-Za-z0-9_-]+",relevance:0},{cN:"selector-class",b:"\\.[A-Za-z0-9_-]+",relevance:0},{cN:"selector-attr",b:"\\[",e:"\\]",i:"$"},{cN:"selector-tag",b:"\\b(a|abbr|acronym|address|area|article|aside|audio|b|base|big|blockquote|body|br|button|canvas|caption|cite|code|col|colgroup|command|datalist|dd|del|details|dfn|div|dl|dt|em|embed|fieldset|figcaption|figure|footer|form|frame|frameset|(h[1-6])|head|header|hgroup|hr|html|i|iframe|img|input|ins|kbd|keygen|label|legend|li|link|map|mark|meta|meter|nav|noframes|noscript|object|ol|optgroup|option|output|p|param|pre|progress|q|rp|rt|ruby|samp|script|section|select|small|span|strike|strong|style|sub|sup|table|tbody|td|textarea|tfoot|th|thead|time|title|tr|tt|ul|var|video)\\b",relevance:0},{cN:"selector-pseudo",b:":(visited|valid|root|right|required|read-write|read-only|out-range|optional|only-of-type|only-child|nth-of-type|nth-last-of-type|nth-last-child|nth-child|not|link|left|last-of-type|last-child|lang|invalid|indeterminate|in-range|hover|focus|first-of-type|first-line|first-letter|first-child|first|enabled|empty|disabled|default|checked|before|after|active)"},{cN:"selector-pseudo",b:"::(after|before|choices|first-letter|first-line|repeat-index|repeat-item|selection|value)"},r,{cN:"attribute",b:"\\b(src|z-index|word-wrap|word-spacing|word-break|width|widows|white-space|visibility|vertical-align|unicode-bidi|transition-timing-function|transition-property|transition-duration|transition-delay|transition|transform-style|transform-origin|transform|top|text-underline-position|text-transform|text-shadow|text-rendering|text-overflow|text-indent|text-decoration-style|text-decoration-line|text-decoration-color|text-decoration|text-align-last|text-align|tab-size|table-layout|right|resize|quotes|position|pointer-events|perspective-origin|perspective|page-break-inside|page-break-before|page-break-after|padding-top|padding-right|padding-left|padding-bottom|padding|overflow-y|overflow-x|overflow-wrap|overflow|outline-width|outline-style|outline-offset|outline-color|outline|orphans|order|opacity|object-position|object-fit|normal|none|nav-up|nav-right|nav-left|nav-index|nav-down|min-width|min-height|max-width|max-height|mask|marks|margin-top|margin-right|margin-left|margin-bottom|margin|list-style-type|list-style-position|list-style-image|list-style|line-height|letter-spacing|left|justify-content|initial|inherit|ime-mode|image-orientation|image-resolution|image-rendering|icon|hyphens|height|font-weight|font-variant-ligatures|font-variant|font-style|font-stretch|font-size-adjust|font-size|font-language-override|font-kerning|font-feature-settings|font-family|font|float|flex-wrap|flex-shrink|flex-grow|flex-flow|flex-direction|flex-basis|flex|filter|empty-cells|display|direction|cursor|counter-reset|counter-increment|content|column-width|column-span|column-rule-width|column-rule-style|column-rule-color|column-rule|column-gap|column-fill|column-count|columns|color|clip-path|clip|clear|caption-side|break-inside|break-before|break-after|box-sizing|box-shadow|box-decoration-break|bottom|border-width|border-top-width|border-top-style|border-top-right-radius|border-top-left-radius|border-top-color|border-top|border-style|border-spacing|border-right-width|border-right-style|border-right-color|border-right|border-radius|border-left-width|border-left-style|border-left-color|border-left|border-image-width|border-image-source|border-image-slice|border-image-repeat|border-image-outset|border-image|border-color|border-collapse|border-bottom-width|border-bottom-style|border-bottom-right-radius|border-bottom-left-radius|border-bottom-color|border-bottom|border|background-size|background-repeat|background-position|background-origin|background-image|background-color|background-clip|background-attachment|background-blend-mode|background|backface-visibility|auto|animation-timing-function|animation-play-state|animation-name|animation-iteration-count|animation-fill-mode|animation-duration|animation-direction|animation-delay|animation|align-self|align-items|align-content)\\b",i:"[^\\s]"},{b:"\\b(whitespace|wait|w-resize|visible|vertical-text|vertical-ideographic|uppercase|upper-roman|upper-alpha|underline|transparent|top|thin|thick|text|text-top|text-bottom|tb-rl|table-header-group|table-footer-group|sw-resize|super|strict|static|square|solid|small-caps|separate|se-resize|scroll|s-resize|rtl|row-resize|ridge|right|repeat|repeat-y|repeat-x|relative|progress|pointer|overline|outside|outset|oblique|nowrap|not-allowed|normal|none|nw-resize|no-repeat|no-drop|newspaper|ne-resize|n-resize|move|middle|medium|ltr|lr-tb|lowercase|lower-roman|lower-alpha|loose|list-item|line|line-through|line-edge|lighter|left|keep-all|justify|italic|inter-word|inter-ideograph|inside|inset|inline|inline-block|inherit|inactive|ideograph-space|ideograph-parenthesis|ideograph-numeric|ideograph-alpha|horizontal|hidden|help|hand|groove|fixed|ellipsis|e-resize|double|dotted|distribute|distribute-space|distribute-letter|distribute-all-lines|disc|disabled|default|decimal|dashed|crosshair|collapse|col-resize|circle|char|center|capitalize|break-word|break-all|bottom|both|bolder|bold|block|bidi-override|below|baseline|auto|always|all-scroll|absolute|table|table-cell)\\b"},{b:":",e:";",c:[r,a,e.CSSNM,e.QSM,e.ASM,{cN:"meta",b:"!important"}]},{b:"@(page|font-face)",l:t,k:"@page @font-face"},{b:"@",e:"[{;]",rB:!0,k:"and or not only",c:[{b:t,cN:"keyword"},r,e.QSM,e.ASM,a,e.CSSNM]}]}}),n.registerLanguage("shell",function(e){return{aliases:["console"],c:[{cN:"meta",b:"^\\s{0,3}[/\\w\\d\\[\\]()@-]*[>%$#]",starts:{e:"$",sL:"bash"}}]}}),n.registerLanguage("sql",function(e){var t=e.C("--","$");return{cI:!0,i:/[<>{}*]/,c:[{bK:"begin end start commit rollback savepoint lock alter create drop rename call delete do handler insert load replace select truncate update set show pragma grant merge describe use explain help declare prepare execute deallocate release unlock purge reset change stop analyze cache flush optimize repair kill install uninstall checksum restore check backup revoke comment values with",e:/;/,eW:!0,l:/[\w\.]+/,k:{keyword:"as abort abs absolute acc acce accep accept access accessed accessible account acos action activate add addtime admin administer advanced advise aes_decrypt aes_encrypt after agent aggregate ali alia alias all allocate allow alter always analyze ancillary and anti any anydata anydataset anyschema anytype apply archive archived archivelog are as asc ascii asin assembly assertion associate asynchronous at atan atn2 attr attri attrib attribu attribut attribute attributes audit authenticated authentication authid authors auto autoallocate autodblink autoextend automatic availability avg backup badfile basicfile before begin beginning benchmark between bfile bfile_base big bigfile bin binary_double binary_float binlog bit_and bit_count bit_length bit_or bit_xor bitmap blob_base block blocksize body both bound bucket buffer_cache buffer_pool build bulk by byte byteordermark bytes cache caching call calling cancel capacity cascade cascaded case cast catalog category ceil ceiling chain change changed char_base char_length character_length characters characterset charindex charset charsetform charsetid check checksum checksum_agg child choose chr chunk class cleanup clear client clob clob_base clone close cluster_id cluster_probability cluster_set clustering coalesce coercibility col collate collation collect colu colum column column_value columns columns_updated comment commit compact compatibility compiled complete composite_limit compound compress compute concat concat_ws concurrent confirm conn connec connect connect_by_iscycle connect_by_isleaf connect_by_root connect_time connection consider consistent constant constraint constraints constructor container content contents context contributors controlfile conv convert convert_tz corr corr_k corr_s corresponding corruption cos cost count count_big counted covar_pop covar_samp cpu_per_call cpu_per_session crc32 create creation critical cross cube cume_dist curdate current current_date current_time current_timestamp current_user cursor curtime customdatum cycle data database databases datafile datafiles datalength date_add date_cache date_format date_sub dateadd datediff datefromparts datename datepart datetime2fromparts day day_to_second dayname dayofmonth dayofweek dayofyear days db_role_change dbtimezone ddl deallocate declare decode decompose decrement decrypt deduplicate def defa defau defaul default defaults deferred defi defin define degrees delayed delegate delete delete_all delimited demand dense_rank depth dequeue des_decrypt des_encrypt des_key_file desc descr descri describ describe descriptor deterministic diagnostics difference dimension direct_load directory disable disable_all disallow disassociate discardfile disconnect diskgroup distinct distinctrow distribute distributed div do document domain dotnet double downgrade drop dumpfile duplicate duration each edition editionable editions element ellipsis else elsif elt empty enable enable_all enclosed encode encoding encrypt end end-exec endian enforced engine engines enqueue enterprise entityescaping eomonth error errors escaped evalname evaluate event eventdata events except exception exceptions exchange exclude excluding execu execut execute exempt exists exit exp expire explain explode export export_set extended extent external external_1 external_2 externally extract failed failed_login_attempts failover failure far fast feature_set feature_value fetch field fields file file_name_convert filesystem_like_logging final finish first first_value fixed flash_cache flashback floor flush following follows for forall force foreign form forma format found found_rows freelist freelists freepools fresh from from_base64 from_days ftp full function general generated get get_format get_lock getdate getutcdate global global_name globally go goto grant grants greatest group group_concat group_id grouping grouping_id groups gtid_subtract guarantee guard handler hash hashkeys having hea head headi headin heading heap help hex hierarchy high high_priority hosts hour hours http id ident_current ident_incr ident_seed identified identity idle_time if ifnull ignore iif ilike ilm immediate import in include including increment index indexes indexing indextype indicator indices inet6_aton inet6_ntoa inet_aton inet_ntoa infile initial initialized initially initrans inmemory inner innodb input insert install instance instantiable instr interface interleaved intersect into invalidate invisible is is_free_lock is_ipv4 is_ipv4_compat is_not is_not_null is_used_lock isdate isnull isolation iterate java join json json_exists keep keep_duplicates key keys kill language large last last_day last_insert_id last_value lateral lax lcase lead leading least leaves left len lenght length less level levels library like like2 like4 likec limit lines link list listagg little ln load load_file lob lobs local localtime localtimestamp locate locator lock locked log log10 log2 logfile logfiles logging logical logical_reads_per_call logoff logon logs long loop low low_priority lower lpad lrtrim ltrim main make_set makedate maketime managed management manual map mapping mask master master_pos_wait match matched materialized max maxextents maximize maxinstances maxlen maxlogfiles maxloghistory maxlogmembers maxsize maxtrans md5 measures median medium member memcompress memory merge microsecond mid migration min minextents minimum mining minus minute minutes minvalue missing mod mode model modification modify module monitoring month months mount move movement multiset mutex name name_const names nan national native natural nav nchar nclob nested never new newline next nextval no no_write_to_binlog noarchivelog noaudit nobadfile nocheck nocompress nocopy nocycle nodelay nodiscardfile noentityescaping noguarantee nokeep nologfile nomapping nomaxvalue nominimize nominvalue nomonitoring none noneditionable nonschema noorder nopr nopro noprom nopromp noprompt norely noresetlogs noreverse normal norowdependencies noschemacheck noswitch not nothing notice notnull notrim novalidate now nowait nth_value nullif nulls num numb numbe nvarchar nvarchar2 object ocicoll ocidate ocidatetime ociduration ociinterval ociloblocator ocinumber ociref ocirefcursor ocirowid ocistring ocitype oct octet_length of off offline offset oid oidindex old on online only opaque open operations operator optimal optimize option optionally or oracle oracle_date oradata ord ordaudio orddicom orddoc order ordimage ordinality ordvideo organization orlany orlvary out outer outfile outline output over overflow overriding package pad parallel parallel_enable parameters parent parse partial partition partitions pascal passing password password_grace_time password_lock_time password_reuse_max password_reuse_time password_verify_function patch path patindex pctincrease pctthreshold pctused pctversion percent percent_rank percentile_cont percentile_disc performance period period_add period_diff permanent physical pi pipe pipelined pivot pluggable plugin policy position post_transaction pow power pragma prebuilt precedes preceding precision prediction prediction_cost prediction_details prediction_probability prediction_set prepare present preserve prior priority private private_sga privileges procedural procedure procedure_analyze processlist profiles project prompt protection public publishingservername purge quarter query quick quiesce quota quotename radians raise rand range rank raw read reads readsize rebuild record records recover recovery recursive recycle redo reduced ref reference referenced references referencing refresh regexp_like register regr_avgx regr_avgy regr_count regr_intercept regr_r2 regr_slope regr_sxx regr_sxy reject rekey relational relative relaylog release release_lock relies_on relocate rely rem remainder rename repair repeat replace replicate replication required reset resetlogs resize resource respect restore restricted result result_cache resumable resume retention return returning returns reuse reverse revoke right rlike role roles rollback rolling rollup round row row_count rowdependencies rowid rownum rows rtrim rules safe salt sample save savepoint sb1 sb2 sb4 scan schema schemacheck scn scope scroll sdo_georaster sdo_topo_geometry search sec_to_time second seconds section securefile security seed segment select self semi sequence sequential serializable server servererror session session_user sessions_per_user set sets settings sha sha1 sha2 share shared shared_pool short show shrink shutdown si_averagecolor si_colorhistogram si_featurelist si_positionalcolor si_stillimage si_texture siblings sid sign sin size size_t sizes skip slave sleep smalldatetimefromparts smallfile snapshot some soname sort soundex source space sparse spfile split sql sql_big_result sql_buffer_result sql_cache sql_calc_found_rows sql_small_result sql_variant_property sqlcode sqldata sqlerror sqlname sqlstate sqrt square standalone standby start starting startup statement static statistics stats_binomial_test stats_crosstab stats_ks_test stats_mode stats_mw_test stats_one_way_anova stats_t_test_ stats_t_test_indep stats_t_test_one stats_t_test_paired stats_wsr_test status std stddev stddev_pop stddev_samp stdev stop storage store stored str str_to_date straight_join strcmp strict string struct stuff style subdate subpartition subpartitions substitutable substr substring subtime subtring_index subtype success sum suspend switch switchoffset switchover sync synchronous synonym sys sys_xmlagg sysasm sysaux sysdate sysdatetimeoffset sysdba sysoper system system_user sysutcdatetime table tables tablespace tablesample tan tdo template temporary terminated tertiary_weights test than then thread through tier ties time time_format time_zone timediff timefromparts timeout timestamp timestampadd timestampdiff timezone_abbr timezone_minute timezone_region to to_base64 to_date to_days to_seconds todatetimeoffset trace tracking transaction transactional translate translation treat trigger trigger_nestlevel triggers trim truncate try_cast try_convert try_parse type ub1 ub2 ub4 ucase unarchived unbounded uncompress under undo unhex unicode uniform uninstall union unique unix_timestamp unknown unlimited unlock unnest unpivot unrecoverable unsafe unsigned until untrusted unusable unused update updated upgrade upped upper upsert url urowid usable usage use use_stored_outlines user user_data user_resources users using utc_date utc_timestamp uuid uuid_short validate validate_password_strength validation valist value values var var_samp varcharc vari varia variab variabl variable variables variance varp varraw varrawc varray verify version versions view virtual visible void wait wallet warning warnings week weekday weekofyear wellformed when whene whenev wheneve whenever where while whitespace window with within without work wrapped xdb xml xmlagg xmlattributes xmlcast xmlcolattval xmlelement xmlexists xmlforest xmlindex xmlnamespaces xmlpi xmlquery xmlroot xmlschema xmlserialize xmltable xmltype xor year year_to_month years yearweek",literal:"true false null unknown",built_in:"array bigint binary bit blob bool boolean char character date dec decimal float int int8 integer interval number numeric real record serial serial8 smallint text time timestamp tinyint varchar varchar2 varying void"},c:[{cN:"string",b:"'",e:"'",c:[{b:"''"}]},{cN:"string",b:'"',e:'"',c:[{b:'""'}]},{cN:"string",b:"`",e:"`"},e.CNM,e.CBCM,t,e.HCM]},e.CBCM,t,e.HCM]}}),n.registerLanguage("swift",function(e){var t={keyword:"#available #colorLiteral #column #else #elseif #endif #file #fileLiteral #function #if #imageLiteral #line #selector #sourceLocation _ __COLUMN__ __FILE__ __FUNCTION__ __LINE__ Any as as! as? associatedtype associativity break case catch class continue convenience default defer deinit didSet do dynamic dynamicType else enum extension fallthrough false fileprivate final for func get guard if import in indirect infix init inout internal is lazy left let mutating nil none nonmutating open operator optional override postfix precedence prefix private protocol Protocol public repeat required rethrows return right self Self set static struct subscript super switch throw throws true try try! try? Type typealias unowned var weak where while willSet",literal:"true false nil",built_in:"abs advance alignof alignofValue anyGenerator assert assertionFailure bridgeFromObjectiveC bridgeFromObjectiveCUnconditional bridgeToObjectiveC bridgeToObjectiveCUnconditional c contains count countElements countLeadingZeros debugPrint debugPrintln distance dropFirst dropLast dump encodeBitsAsWords enumerate equal fatalError filter find getBridgedObjectiveCType getVaList indices insertionSort isBridgedToObjectiveC isBridgedVerbatimToObjectiveC isUniquelyReferenced isUniquelyReferencedNonObjC join lazy lexicographicalCompare map max maxElement min minElement numericCast overlaps partition posix precondition preconditionFailure print println quickSort readLine reduce reflect reinterpretCast reverse roundUpToAlignment sizeof sizeofValue sort split startsWith stride strideof strideofValue swap toString transcode underestimateCount unsafeAddressOf unsafeBitCast unsafeDowncast unsafeUnwrap unsafeReflect withExtendedLifetime withObjectAtPlusZero withUnsafePointer withUnsafePointerToObject withUnsafeMutablePointer withUnsafeMutablePointers withUnsafePointer withUnsafePointers withVaList zip"},r=e.C("/\\*","\\*/",{c:["self"]}),a={cN:"subst",b:/\\\(/,e:"\\)",k:t,c:[]},n={cN:"string",c:[e.BE,a],v:[{b:/"""/,e:/"""/},{b:/"/,e:/"/}]},i={cN:"number",b:"\\b([\\d_]+(\\.[\\deE_]+)?|0x[a-fA-F0-9_]+(\\.[a-fA-F0-9p_]+)?|0b[01_]+|0o[0-7_]+)\\b",relevance:0};return a.c=[i],{k:t,c:[n,e.CLCM,r,{cN:"type",b:"\\b[A-Z][\\wÀ-ʸ']*[!?]"},{cN:"type",b:"\\b[A-Z][\\wÀ-ʸ']*",relevance:0},i,{cN:"function",bK:"func",e:"{",eE:!0,c:[e.inherit(e.TM,{b:/[A-Za-z$_][0-9A-Za-z$_]*/}),{b://},{cN:"params",b:/\(/,e:/\)/,endsParent:!0,k:t,c:["self",i,n,e.CBCM,{b:":"}],i:/["']/}],i:/\[|%/},{cN:"class",bK:"struct protocol class extension enum",k:t,e:"\\{",eE:!0,c:[e.inherit(e.TM,{b:/[A-Za-z$_][\u00C0-\u02B80-9A-Za-z$_]*/})]},{cN:"meta",b:"(@discardableResult|@warn_unused_result|@exported|@lazy|@noescape|@NSCopying|@NSManaged|@objc|@objcMembers|@convention|@required|@noreturn|@IBAction|@IBDesignable|@IBInspectable|@IBOutlet|@infix|@prefix|@postfix|@autoclosure|@testable|@available|@nonobjc|@NSApplicationMain|@UIApplicationMain|@dynamicMemberLookup|@propertyWrapper)"},{bK:"import",e:/$/,c:[e.CLCM,r]}]}}),n.registerLanguage("typescript",function(e){var t="[A-Za-z$_][0-9A-Za-z$_]*",r={keyword:"in if for while finally var new function do return void else break catch instanceof with throw case default try this switch continue typeof delete let yield const class public private protected get set super static implements enum export import declare type namespace abstract as from extends async await",literal:"true false null undefined NaN Infinity",built_in:"eval isFinite isNaN parseFloat parseInt decodeURI decodeURIComponent encodeURI encodeURIComponent escape unescape Object Function Boolean Error EvalError InternalError RangeError ReferenceError StopIteration SyntaxError TypeError URIError Number Math Date String RegExp Array Float32Array Float64Array Int16Array Int32Array Int8Array Uint16Array Uint32Array Uint8Array Uint8ClampedArray ArrayBuffer DataView JSON Intl arguments require module console window document any number boolean string void Promise"},a={cN:"meta",b:"@"+t},n={b:"\\(",e:/\)/,k:r,c:["self",e.QSM,e.ASM,e.NM]},i={cN:"params",b:/\(/,e:/\)/,eB:!0,eE:!0,k:r,c:[e.CLCM,e.CBCM,a,n]},s={cN:"number",v:[{b:"\\b(0[bB][01]+)n?"},{b:"\\b(0[oO][0-7]+)n?"},{b:e.CNR+"n?"}],relevance:0},c={cN:"subst",b:"\\$\\{",e:"\\}",k:r,c:[]},o={b:"html`",e:"",starts:{e:"`",rE:!1,c:[e.BE,c],sL:"xml"}},l={b:"css`",e:"",starts:{e:"`",rE:!1,c:[e.BE,c],sL:"css"}},d={cN:"string",b:"`",e:"`",c:[e.BE,c]};return c.c=[e.ASM,e.QSM,o,l,d,s,e.RM],{aliases:["ts"],k:r,c:[{cN:"meta",b:/^\s*['"]use strict['"]/},e.ASM,e.QSM,o,l,d,e.CLCM,e.CBCM,s,{b:"("+e.RSR+"|\\b(case|return|throw)\\b)\\s*",k:"return throw case",c:[e.CLCM,e.CBCM,e.RM,{cN:"function",b:"(\\(.*?\\)|"+e.IR+")\\s*=>",rB:!0,e:"\\s*=>",c:[{cN:"params",v:[{b:e.IR},{b:/\(\s*\)/},{b:/\(/,e:/\)/,eB:!0,eE:!0,k:r,c:["self",e.CLCM,e.CBCM]}]}]}],relevance:0},{cN:"function",bK:"function",e:/[\{;]/,eE:!0,k:r,c:["self",e.inherit(e.TM,{b:t}),i],i:/%/,relevance:0},{bK:"constructor",e:/[\{;]/,eE:!0,c:["self",i]},{b:/module\./,k:{built_in:"module"},relevance:0},{bK:"module",e:/\{/,eE:!0},{bK:"interface",e:/\{/,eE:!0,k:"interface extends"},{b:/\$[(.]/},{b:"\\."+e.IR,relevance:0},a,n]}}),n.registerLanguage("yaml",function(e){var t="true false yes no null",r={cN:"string",relevance:0,v:[{b:/'/,e:/'/},{b:/"/,e:/"/},{b:/\S+/}],c:[e.BE,{cN:"template-variable",v:[{b:"{{",e:"}}"},{b:"%{",e:"}"}]}]};return{cI:!0,aliases:["yml","YAML","yaml"],c:[{cN:"attr",v:[{b:"\\w[\\w :\\/.-]*:(?=[ \t]|$)"},{b:'"\\w[\\w :\\/.-]*":(?=[ \t]|$)'},{b:"'\\w[\\w :\\/.-]*':(?=[ \t]|$)"}]},{cN:"meta",b:"^---s*$",relevance:10},{cN:"string",b:"[\\|>]([0-9]?[+-])?[ ]*\\n( *)[\\S ]+\\n(\\2[\\S ]+\\n?)*"},{b:"<%[%=-]?",e:"[%-]?%>",sL:"ruby",eB:!0,eE:!0,relevance:0},{cN:"type",b:"!"+e.UIR},{cN:"type",b:"!!"+e.UIR},{cN:"meta",b:"&"+e.UIR+"$"},{cN:"meta",b:"\\*"+e.UIR+"$"},{cN:"bullet",b:"\\-(?=[ ]|$)",relevance:0},e.HCM,{bK:t,k:{literal:t}},{cN:"number",b:e.CNR+"\\b"},r]}}),n}); \ No newline at end of file diff --git a/src/api/docs/content/external/rapidoc-min.js b/src/api/docs/content/external/rapidoc-min.js new file mode 100644 index 000000000..be431d198 --- /dev/null +++ b/src/api/docs/content/external/rapidoc-min.js @@ -0,0 +1,220 @@ +/*! + * RapiDoc 8.4.3 - WebComponent to View OpenAPI docs + * License: MIT + * Repo : https://github.com/mrin9/RapiDoc + * Author : Mrinmoy Majumdar + */!function(e){var t=window.webpackHotUpdate;window.webpackHotUpdate=function(e,n){!function(e,t){if(!w[e]||!x[e])return;for(var n in x[e]=!1,t)Object.prototype.hasOwnProperty.call(t,n)&&(h[n]=t[n]);0==--g&&0===y&&A()}(e,n),t&&t(e,n)};var n,r=!0,o="c765d882fc3c0a3d9ed2",i={},a=[],s=[];function c(e){var t=T[e];if(!t)return C;var r=function(r){return t.hot.active?(T[r]?-1===T[r].parents.indexOf(e)&&T[r].parents.push(e):(a=[e],n=r),-1===t.children.indexOf(r)&&t.children.push(r)):(console.warn("[HMR] unexpected require("+r+") from disposed module "+e),a=[]),C(r)},o=function(e){return{configurable:!0,enumerable:!0,get:function(){return C[e]},set:function(t){C[e]=t}}};for(var i in C)Object.prototype.hasOwnProperty.call(C,i)&&"e"!==i&&"t"!==i&&Object.defineProperty(r,i,o(i));return r.e=function(e){return"ready"===p&&f("prepare"),y++,C.e(e).then(t,(function(e){throw t(),e}));function t(){y--,"prepare"===p&&(b[e]||O(e),0===y&&0===g&&A())}},r.t=function(e,t){return 1&t&&(e=r(e)),C.t(e,-2&t)},r}function l(t){var r={_acceptedDependencies:{},_declinedDependencies:{},_selfAccepted:!1,_selfDeclined:!1,_selfInvalidated:!1,_disposeHandlers:[],_main:n!==t,active:!0,accept:function(e,t){if(void 0===e)r._selfAccepted=!0;else if("function"==typeof e)r._selfAccepted=e;else if("object"==typeof e)for(var n=0;n=0&&r._disposeHandlers.splice(t,1)},invalidate:function(){switch(this._selfInvalidated=!0,p){case"idle":(h={})[t]=e[t],f("ready");break;case"ready":j(t);break;case"prepare":case"check":case"dispose":case"apply":(m=m||[]).push(t)}},check:S,apply:E,status:function(e){if(!e)return p;u.push(e)},addStatusHandler:function(e){u.push(e)},removeStatusHandler:function(e){var t=u.indexOf(e);t>=0&&u.splice(t,1)},data:i[t]};return n=void 0,r}var u=[],p="idle";function f(e){p=e;for(var t=0;t0;){var o=r.pop(),i=o.id,a=o.chain;if((u=T[i])&&(!u.hot._selfAccepted||u.hot._selfInvalidated)){if(u.hot._selfDeclined)return{type:"self-declined",chain:a,moduleId:i};if(u.hot._main)return{type:"unaccepted",chain:a,moduleId:i};for(var s=0;s ")),A.type){case"self-declined":r.onDeclined&&r.onDeclined(A),r.ignoreDeclined||(E=new Error("Aborted because of self decline: "+A.moduleId+P));break;case"declined":r.onDeclined&&r.onDeclined(A),r.ignoreDeclined||(E=new Error("Aborted because of declined dependency: "+A.moduleId+" in "+A.parentId+P));break;case"unaccepted":r.onUnaccepted&&r.onUnaccepted(A),r.ignoreUnaccepted||(E=new Error("Aborted because "+p+" is not accepted"+P));break;case"accepted":r.onAccepted&&r.onAccepted(A),j=!0;break;case"disposed":r.onDisposed&&r.onDisposed(A),I=!0;break;default:throw new Error("Unexception type "+A.type)}if(E)return f("abort"),Promise.reject(E);if(j)for(p in x[p]=h[p],g(b,A.outdatedModules),A.outdatedDependencies)Object.prototype.hasOwnProperty.call(A.outdatedDependencies,p)&&(y[p]||(y[p]=[]),g(y[p],A.outdatedDependencies[p]));I&&(g(b,[A.moduleId]),x[p]=S)}var R,L=[];for(c=0;c0;)if(p=B.pop(),u=T[p]){var D={},q=u.hot._disposeHandlers;for(l=0;l=0&&F.parents.splice(R,1))}}for(p in y)if(Object.prototype.hasOwnProperty.call(y,p)&&(u=T[p]))for(M=y[p],l=0;l=0&&u.children.splice(R,1);f("apply"),void 0!==v&&(o=v,v=void 0);for(p in h=void 0,x)Object.prototype.hasOwnProperty.call(x,p)&&(e[p]=x[p]);var z=null;for(p in y)if(Object.prototype.hasOwnProperty.call(y,p)&&(u=T[p])){M=y[p];var U=[];for(c=0;ce.length)&&(t=e.length);for(var n=0,r=new Array(t);n=e.length?{done:!0}:{done:!1,value:e[t++]}},e:function(e){throw e},f:n}}throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}var r,o,i=!0,a=!1;return{s:function(){r=e[Symbol.iterator]()},n:function(){var e=r.next();return i=e.done,e},e:function(e){a=!0,o=e},f:function(){try{i||null==r.return||r.return()}finally{if(a)throw o}}}}n.d(t,"a",(function(){return A})),n.d(t,"b",(function(){return L})),n.d(t,"c",(function(){return F})),n.d(t,"d",(function(){return R})),n.d(t,"e",(function(){return E})),n.d(t,"f",(function(){return D})),n.d(t,"g",(function(){return O})),n.d(t,"h",(function(){return r})),n.d(t,"i",(function(){return a})),n.d(t,"j",(function(){return o})),n.d(t,"k",(function(){return _})),n.d(t,"l",(function(){return s})),n.d(t,"m",(function(){return q})),n.d(t,"n",(function(){return B})),n.d(t,"o",(function(){return y})),n.d(t,"p",(function(){return N})),n.d(t,"q",(function(){return c})),n.d(t,"r",(function(){return m})),n.d(t,"s",(function(){return g})),n.d(t,"t",(function(){return l})),n.d(t,"u",(function(){return j})),n.d(t,"v",(function(){return M})),n.d(t,"w",(function(){return h})),n.d(t,"x",(function(){return b})),n.d(t,"y",(function(){return v}));var A={ANCHOR:"&",COMMENT:"#",TAG:"!",DIRECTIVES_END:"-",DOCUMENT_END:"."},E={ALIAS:"ALIAS",BLANK_LINE:"BLANK_LINE",BLOCK_FOLDED:"BLOCK_FOLDED",BLOCK_LITERAL:"BLOCK_LITERAL",COMMENT:"COMMENT",DIRECTIVE:"DIRECTIVE",DOCUMENT:"DOCUMENT",FLOW_MAP:"FLOW_MAP",FLOW_SEQ:"FLOW_SEQ",MAP:"MAP",MAP_KEY:"MAP_KEY",MAP_VALUE:"MAP_VALUE",PLAIN:"PLAIN",QUOTE_DOUBLE:"QUOTE_DOUBLE",QUOTE_SINGLE:"QUOTE_SINGLE",SEQ:"SEQ",SEQ_ITEM:"SEQ_ITEM"},_="tag:yaml.org,2002:",j={MAP:"tag:yaml.org,2002:map",SEQ:"tag:yaml.org,2002:seq",STR:"tag:yaml.org,2002:str"};function T(e){for(var t=[0],n=e.indexOf("\n");-1!==n;)n+=1,t.push(n),n=e.indexOf("\n",n);return t}function C(e){var t,n;return"string"==typeof e?(t=T(e),n=e):(Array.isArray(e)&&(e=e[0]),e&&e.context&&(e.lineStarts||(e.lineStarts=T(e.context.src)),t=e.lineStarts,n=e.context.src)),{lineStarts:t,src:n}}function I(e,t){if("number"!=typeof e||e<0)return null;var n=C(t),r=n.lineStarts,o=n.src;if(!r||!o||e>o.length)return null;for(var i=0;i=1)||e>r.length)return null;for(var i=r[e-1],a=r[e];a&&a>i&&"\n"===o[a-1];)--a;return o.slice(i,a)}var R=function(){function e(t,n){o(this,e),this.start=t,this.end=n||t}return a(e,null,[{key:"copy",value:function(t){return new e(t.start,t.end)}}]),a(e,[{key:"isEmpty",value:function(){return"number"!=typeof this.start||!this.end||this.end<=this.start}},{key:"setOrigRange",value:function(e,t){var n=this.start,r=this.end;if(0===e.length||r<=e[0])return this.origStart=n,this.origEnd=r,t;for(var o=t;on);)++o;this.origStart=n+o;for(var i=o;o=r);)++o;return this.origEnd=r+o,i}}]),e}(),L=function(){function e(t,n,r){o(this,e),Object.defineProperty(this,"context",{value:r||null,writable:!0}),this.error=null,this.range=null,this.valueRange=null,this.props=n||[],this.type=t,this.value=null}return a(e,null,[{key:"addStringTerminator",value:function(t,n,r){if("\n"===r[r.length-1])return r;var o=e.endOfWhiteSpace(t,n);return o>=t.length||"\n"===t[o]?r+"\n":r}},{key:"atDocumentBoundary",value:function(e,t,n){var r=e[t];if(!r)return!0;var o=e[t-1];if(o&&"\n"!==o)return!1;if(n){if(r!==n)return!1}else if(r!==A.DIRECTIVES_END&&r!==A.DOCUMENT_END)return!1;var i=e[t+1],a=e[t+2];if(i!==r||a!==r)return!1;var s=e[t+3];return!s||"\n"===s||"\t"===s||" "===s}},{key:"endOfIdentifier",value:function(e,t){for(var n=e[t],r="<"===n,o=r?["\n","\t"," ",">"]:["\n","\t"," ","[","]","{","}",","];n&&-1===o.indexOf(n);)n=e[t+=1];return r&&">"===n&&(t+=1),t}},{key:"endOfIndent",value:function(e,t){for(var n=e[t];" "===n;)n=e[t+=1];return t}},{key:"endOfLine",value:function(e,t){for(var n=e[t];n&&"\n"!==n;)n=e[t+=1];return t}},{key:"endOfWhiteSpace",value:function(e,t){for(var n=e[t];"\t"===n||" "===n;)n=e[t+=1];return t}},{key:"startOfLine",value:function(e,t){var n=e[t-1];if("\n"===n)return t;for(;n&&"\n"!==n;)n=e[t-=1];return t+1}},{key:"endOfBlockIndent",value:function(t,n,r){var o=e.endOfIndent(t,r);if(o>r+n)return o;var i=e.endOfWhiteSpace(t,o),a=t[i];return a&&"\n"!==a?null:i}},{key:"atBlank",value:function(e,t,n){var r=e[t];return"\n"===r||"\t"===r||" "===r||n&&!r}},{key:"nextNodeIsIndented",value:function(e,t,n){return!(!e||t<0)&&(t>0||n&&"-"===e)}},{key:"normalizeOffset",value:function(t,n){var r=t[n];return r?"\n"!==r&&"\n"===t[n-1]?n-1:e.endOfWhiteSpace(t,n):n}},{key:"foldNewline",value:function(t,n,r){for(var o=0,i=!1,a="",s=t[n+1];" "===s||"\t"===s||"\n"===s;){switch(s){case"\n":o=0,n+=1,a+="\n";break;case"\t":o<=r&&(i=!0),n=e.endOfWhiteSpace(t,n+2)-1;break;case" ":o+=1,n+=1}s=t[n+1]}return a||(a=" "),s&&o<=r&&(i=!0),{fold:a,offset:n,error:i}}}]),a(e,[{key:"getPropValue",value:function(e,t,n){if(!this.context)return null;var r=this.context.src,o=this.props[e];return o&&r[o.start]===t?r.slice(o.start+(n?1:0),o.end):null}},{key:"commentHasRequiredWhitespace",value:function(t){var n=this.context.src;if(this.header&&t===this.header.end)return!1;if(!this.valueRange)return!1;var r=this.valueRange.end;return t!==r||e.atBlank(n,r-1)}},{key:"parseComment",value:function(t){var n=this.context.src;if(n[t]===A.COMMENT){var r=e.endOfLine(n,t+1),o=new R(t,r);return this.props.push(o),r}return t}},{key:"setOrigRanges",value:function(e,t){return this.range&&(t=this.range.setOrigRange(e,t)),this.valueRange&&this.valueRange.setOrigRange(e,t),this.props.forEach((function(n){return n.setOrigRange(e,t)})),t}},{key:"toString",value:function(){var t=this.context.src,n=this.range,r=this.value;if(null!=r)return r;var o=t.slice(n.start,n.end);return e.addStringTerminator(t,n.end,o)}},{key:"anchor",get:function(){for(var e=0;e0?e.join("\n"):null}},{key:"hasComment",get:function(){if(this.context)for(var e=this.context.src,t=0;t2&&void 0!==arguments[2]?arguments[2]:80,i=P(n.line,t);if(!i)return null;var a=n.col;if(i.length>o)if(a<=o-10)i=i.substr(0,o-1)+"…";else{var s=Math.round(o/2);i.length>a+s&&(i=i.substr(0,a+s-1)+"…"),a-=i.length-o,i="…"+i.substr(1-o)}var c=1,l="";r&&(r.line===n.line&&a+(r.col-n.col)<=o+1?c=r.col-n.col:(c=Math.min(i.length+1,o)-a,l="…"));var u=a>1?" ".repeat(a-1):"",p="^".repeat(c);return"".concat(i,"\n").concat(u).concat(p).concat(l)}(this.linePos,e);a&&(this.message+=":\n\n".concat(a,"\n"))}delete this.source}}}]),n}(d(Error)),M=function(e){c(n,e);var t=m(n);function n(e,r){return o(this,n),t.call(this,"YAMLReferenceError",e,r)}return n}(N),B=function(e){c(n,e);var t=m(n);function n(e,r){return o(this,n),t.call(this,"YAMLSemanticError",e,r)}return n}(N),D=function(e){c(n,e);var t=m(n);function n(e,r){return o(this,n),t.call(this,"YAMLSyntaxError",e,r)}return n}(N),q=function(e){c(n,e);var t=m(n);function n(e,r){return o(this,n),t.call(this,"YAMLWarning",e,r)}return n}(N),F=function(e){c(n,e);var t=m(n);function n(){return o(this,n),t.apply(this,arguments)}return a(n,[{key:"parseBlockValue",value:function(e){for(var t=this.context,r=t.indent,o=t.inFlow,i=t.src,a=e,s=e,c=i[a];"\n"===c&&!L.atDocumentBoundary(i,a+1);c=i[a]){var l=L.endOfBlockIndent(i,r,a+1);if(null===l||"#"===i[l])break;a="\n"===i[l]?l:s=n.endOfLine(i,l,o)}return this.valueRange.isEmpty()&&(this.valueRange.start=e),this.valueRange.end=s,s}},{key:"parse",value:function(e,t){this.context=e;var r=e.inFlow,o=e.src,i=t,a=o[i];return a&&"#"!==a&&"\n"!==a&&(i=n.endOfLine(o,t,r)),this.valueRange=new R(t,i),i=L.endOfWhiteSpace(o,i),i=this.parseComment(i),this.hasComment&&!this.valueRange.isEmpty()||(i=this.parseBlockValue(i)),i}},{key:"strValue",get:function(){if(!this.valueRange||!this.context)return null;for(var e=this.valueRange,t=e.start,n=e.end,r=this.context.src,o=r[n-1];tl?r.slice(l,a+1):s)}else i+=s}var p=r[t];switch(p){case"\t":return{errors:[new B(this,"Plain value cannot start with a tab character")],str:i};case"@":case"`":var f="Plain value cannot start with reserved character ".concat(p);return{errors:[new B(this,f)],str:i};default:return i}}}],[{key:"endOfLine",value:function(e,t,n){for(var r=e[t],o=t;r&&"\n"!==r&&(!n||"["!==r&&"]"!==r&&"{"!==r&&"}"!==r&&","!==r);){var i=e[o+1];if(":"===r&&(!i||"\n"===i||"\t"===i||" "===i||n&&","===i))break;if((" "===r||"\t"===r)&&"#"===i)break;o+=1,r=i}return o}}]),n}(L)},function(e,t,n){"use strict";n.d(t,"a",(function(){return v})),n.d(t,"b",(function(){return u})),n.d(t,"c",(function(){return y})),n.d(t,"d",(function(){return i})),n.d(t,"e",(function(){return d})),n.d(t,"f",(function(){return s})),n.d(t,"g",(function(){return f})),n.d(t,"h",(function(){return x})),n.d(t,"i",(function(){return b})),n.d(t,"j",(function(){return P})),n.d(t,"k",(function(){return g})),n.d(t,"l",(function(){return l})),n.d(t,"m",(function(){return o})),n.d(t,"n",(function(){return H})),n.d(t,"o",(function(){return V})),n.d(t,"p",(function(){return w})),n.d(t,"q",(function(){return D})),n.d(t,"r",(function(){return R})),n.d(t,"s",(function(){return m})),n.d(t,"t",(function(){return k})),n.d(t,"u",(function(){return $})),n.d(t,"v",(function(){return S})),n.d(t,"w",(function(){return a}));var r=n(0);function o(e,t,n){return n?-1===n.indexOf("\n")?"".concat(e," #").concat(n):"".concat(e,"\n")+n.replace(/^/gm,"".concat(t||"","#")):e}var i=function e(){Object(r.j)(this,e)};function a(e,t,n){if(Array.isArray(e))return e.map((function(e,t){return a(e,String(t),n)}));if(e&&"function"==typeof e.toJSON){var r=n&&n.anchors&&n.anchors.get(e);r&&(n.onCreate=function(e){r.res=e,delete n.onCreate});var o=e.toJSON(t,n);return r&&n.onCreate&&n.onCreate(o),o}return n&&n.keep||"bigint"!=typeof e?e:Number(e)}var s=function(e){Object(r.q)(n,e);var t=Object(r.r)(n);function n(e){var o;return Object(r.j)(this,n),(o=t.call(this)).value=e,o}return Object(r.i)(n,[{key:"toJSON",value:function(e,t){return t&&t.keep?this.value:a(this.value,e,t)}},{key:"toString",value:function(){return String(this.value)}}]),n}(i);function c(e,t,n){for(var r=n,o=t.length-1;o>=0;--o){var i=t[o],a=Number.isInteger(i)&&i>=0?[]:{};a[i]=r,r=a}return e.createNode(r,!1)}var l=function(e){return null==e||"object"===Object(r.h)(e)&&e[Symbol.iterator]().next().done},u=function(e){Object(r.q)(n,e);var t=Object(r.r)(n);function n(e){var o;return Object(r.j)(this,n),o=t.call(this),Object(r.l)(Object(r.w)(o),"items",[]),o.schema=e,o}return Object(r.i)(n,[{key:"addIn",value:function(e,t){if(l(e))this.add(t);else{var o=Object(r.x)(e),i=o[0],a=o.slice(1),s=this.get(i,!0);if(s instanceof n)s.addIn(a,t);else{if(void 0!==s||!this.schema)throw new Error("Expected YAML collection at ".concat(i,". Remaining path: ").concat(a));this.set(i,c(this.schema,a,t))}}}},{key:"deleteIn",value:function(e){var t=Object(r.x)(e),o=t[0],i=t.slice(1);if(0===i.length)return this.delete(o);var a=this.get(o,!0);if(a instanceof n)return a.deleteIn(i);throw new Error("Expected YAML collection at ".concat(o,". Remaining path: ").concat(i))}},{key:"getIn",value:function(e,t){var o=Object(r.x)(e),i=o[0],a=o.slice(1),c=this.get(i,!0);return 0===a.length?!t&&c instanceof s?c.value:c:c instanceof n?c.getIn(a,t):void 0}},{key:"hasAllNullValues",value:function(){return this.items.every((function(e){if(!e||"PAIR"!==e.type)return!1;var t=e.value;return null==t||t instanceof s&&null==t.value&&!t.commentBefore&&!t.comment&&!t.tag}))}},{key:"hasIn",value:function(e){var t=Object(r.x)(e),o=t[0],i=t.slice(1);if(0===i.length)return this.has(o);var a=this.get(o,!0);return a instanceof n&&a.hasIn(i)}},{key:"setIn",value:function(e,t){var o=Object(r.x)(e),i=o[0],a=o.slice(1);if(0===a.length)this.set(i,t);else{var s=this.get(i,!0);if(s instanceof n)s.setIn(a,t);else{if(void 0!==s||!this.schema)throw new Error("Expected YAML collection at ".concat(i,". Remaining path: ").concat(a));this.set(i,c(this.schema,a,t))}}}},{key:"toJSON",value:function(){return null}},{key:"toString",value:function(e,t,i,a){var s=this,c=t.blockItem,l=t.flowChars,u=t.isMap,p=t.itemIndent,f=e,d=f.indent,h=f.indentStep,v=f.stringify,m=this.type===r.e.FLOW_MAP||this.type===r.e.FLOW_SEQ||e.inFlow;m&&(p+=h);var g=u&&this.hasAllNullValues();e=Object.assign({},e,{allNullValues:g,indent:p,inFlow:m,type:null});var y,b=!1,x=!1,w=this.items.reduce((function(t,n,r){var i;n&&(!b&&n.spaceBefore&&t.push({type:"comment",str:""}),n.commentBefore&&n.commentBefore.match(/^.*$/gm).forEach((function(e){t.push({type:"comment",str:"#".concat(e)})})),n.comment&&(i=n.comment),m&&(!b&&n.spaceBefore||n.commentBefore||n.comment||n.key&&(n.key.commentBefore||n.key.comment)||n.value&&(n.value.commentBefore||n.value.comment))&&(x=!0)),b=!1;var a=v(n,e,(function(){return i=null}),(function(){return b=!0}));return m&&!x&&a.includes("\n")&&(x=!0),m&&rn.maxFlowStringSingleLineLength){y=k;var A,E=Object(r.g)(O);try{for(E.s();!(A=E.n()).done;){var _=A.value;y+=_?"\n".concat(h).concat(d).concat(_):"\n"}}catch(e){E.e(e)}finally{E.f()}y+="\n".concat(d).concat(S)}else y="".concat(k," ").concat(O.join(" ")," ").concat(S)}else{var j=w.map(c);y=j.shift();var T,C=Object(r.g)(j);try{for(C.s();!(T=C.n()).done;){var I=T.value;y+=I?"\n".concat(d).concat(I):"\n"}}catch(e){C.e(e)}finally{C.f()}}return this.comment?(y+="\n"+this.comment.replace(/^/gm,"".concat(d,"#")),i&&i()):b&&a&&a(),y}}]),n}(i);function p(e){var t=e instanceof s?e.value:e;return t&&"string"==typeof t&&(t=Number(t)),Number.isInteger(t)&&t>=0?t:null}Object(r.l)(u,"maxFlowStringSingleLineLength",60);var f=function(e){Object(r.q)(n,e);var t=Object(r.r)(n);function n(){return Object(r.j)(this,n),t.apply(this,arguments)}return Object(r.i)(n,[{key:"add",value:function(e){this.items.push(e)}},{key:"delete",value:function(e){var t=p(e);return"number"==typeof t&&this.items.splice(t,1).length>0}},{key:"get",value:function(e,t){var n=p(e);if("number"==typeof n){var r=this.items[n];return!t&&r instanceof s?r.value:r}}},{key:"has",value:function(e){var t=p(e);return"number"==typeof t&&t1&&void 0!==arguments[1]?arguments[1]:null;return Object(r.j)(this,n),(o=t.call(this)).key=e,o.value=i,o.type=n.Type.PAIR,o}return Object(r.i)(n,[{key:"addToJSMap",value:function(e,t){var n=a(this.key,"",e);if(t instanceof Map){var o=a(this.value,n,e);t.set(n,o)}else if(t instanceof Set)t.add(n);else{var s=function(e,t,n){return null===t?"":"object"!==Object(r.h)(t)?String(t):e instanceof i&&n&&n.doc?e.toString({anchors:{},doc:n.doc,indent:"",indentStep:n.indentStep,inFlow:!0,inStringifyKey:!0,stringify:n.stringify}):JSON.stringify(t)}(this.key,n,e);t[s]=a(this.value,s,e)}return t}},{key:"toJSON",value:function(e,t){var n=t&&t.mapAsMap?new Map:{};return this.addToJSMap(t,n)}},{key:"toString",value:function(e,t,n){if(!e||!e.doc)return JSON.stringify(this);var a=e.doc.options,c=a.indent,l=a.indentSeq,p=a.simpleKeys,d=this.key,h=this.value,v=d instanceof i&&d.comment;if(p){if(v)throw new Error("With simple keys, key nodes cannot have comments");if(d instanceof u){throw new Error("With simple keys, collection cannot be used as a key value")}}var m=!p&&(!d||v||d instanceof u||d.type===r.e.BLOCK_FOLDED||d.type===r.e.BLOCK_LITERAL),g=e,y=g.doc,b=g.indent,x=g.indentStep,w=g.stringify;e=Object.assign({},e,{implicitKey:!m,indent:b+x});var k=!1,S=w(d,e,(function(){return v=null}),(function(){return k=!0}));if(S=o(S,e.indent,v),e.allNullValues&&!p)return this.comment?(S=o(S,e.indent,this.comment),t&&t()):k&&!v&&n&&n(),e.inFlow?S:"? ".concat(S);S=m?"? ".concat(S,"\n").concat(b,":"):"".concat(S,":"),this.comment&&(S=o(S,e.indent,this.comment),t&&t());var O="",A=null;if(h instanceof i){if(h.spaceBefore&&(O="\n"),h.commentBefore){var E=h.commentBefore.replace(/^/gm,"".concat(e.indent,"#"));O+="\n".concat(E)}A=h.comment}else h&&"object"===Object(r.h)(h)&&(h=y.schema.createNode(h,!0));e.implicitKey=!1,!m&&!this.comment&&h instanceof s&&(e.indentAtStart=S.length+1),k=!1,!l&&c>=2&&!e.inFlow&&!m&&h instanceof f&&h.type!==r.e.FLOW_SEQ&&!h.tag&&!y.anchors.getName(h)&&(e.indent=e.indent.substr(2));var _=w(h,e,(function(){return A=null}),(function(){return k=!0})),j=" ";if(O||this.comment)j="".concat(O,"\n").concat(e.indent);else if(!m&&h instanceof u){("["===_[0]||"{"===_[0])&&!_.includes("\n")||(j="\n".concat(e.indent))}return k&&!A&&n&&n(),o(S+j+_,e.indent,A)}},{key:"commentBefore",get:function(){return this.key instanceof i?this.key.commentBefore:void 0},set:function(e){if(null==this.key&&(this.key=new s(null)),!(this.key instanceof i)){throw new Error("Pair.commentBefore is an alias for Pair.key.commentBefore. To set it, the key must be a Node.")}this.key.commentBefore=e}}]),n}(i);Object(r.l)(d,"Type",{PAIR:"PAIR",MERGE_PAIR:"MERGE_PAIR"});var h=function e(t,n){if(t instanceof v){var o=n.get(t.source);return o.count*o.aliasCount}if(t instanceof u){var i,a=0,s=Object(r.g)(t.items);try{for(s.s();!(i=s.n()).done;){var c=e(i.value,n);c>a&&(a=c)}}catch(e){s.e(e)}finally{s.f()}return a}if(t instanceof d){var l=e(t.key,n),p=e(t.value,n);return Math.max(l,p)}return 1},v=function(e){Object(r.q)(n,e);var t=Object(r.r)(n);function n(e){var o;return Object(r.j)(this,n),(o=t.call(this)).source=e,o.type=r.e.ALIAS,o}return Object(r.i)(n,null,[{key:"stringify",value:function(e,t){var n=e.range,r=e.source,o=t.anchors,i=t.doc,a=t.implicitKey,s=t.inStringifyKey,c=Object.keys(o).find((function(e){return o[e]===r}));if(!c&&s&&(c=i.anchors.getName(r)||i.anchors.newName()),c)return"*".concat(c).concat(a?" ":"");var l=i.anchors.getName(r)?"Alias node must be after source node":"Source node not found for alias node";throw new Error("".concat(l," [").concat(n,"]"))}}]),Object(r.i)(n,[{key:"toJSON",value:function(e,t){if(!t)return a(this.source,e,t);var n=t.anchors,o=t.maxAliasCount,i=n.get(this.source);if(!i||void 0===i.res){var s="This should not happen: Alias anchor was not resolved?";throw this.cstNode?new r.v(this.cstNode,s):new ReferenceError(s)}if(o>=0&&(i.count+=1,0===i.aliasCount&&(i.aliasCount=h(this.source,n)),i.count*i.aliasCount>o)){var c="Excessive alias count indicates a resource exhaustion attack";throw this.cstNode?new r.v(this.cstNode,c):new ReferenceError(c)}return i.res}},{key:"toString",value:function(e){return n.stringify(this,e)}},{key:"tag",set:function(e){throw new Error("Alias nodes cannot have tags")}}]),n}(i);function m(e,t){var n,o=t instanceof s?t.value:t,i=Object(r.g)(e);try{for(i.s();!(n=i.n()).done;){var a=n.value;if(a instanceof d){if(a.key===t||a.key===o)return a;if(a.key&&a.key.value===o)return a}}}catch(e){i.e(e)}finally{i.f()}}Object(r.l)(v,"default",!0);var g=function(e){Object(r.q)(n,e);var t=Object(r.r)(n);function n(){return Object(r.j)(this,n),t.apply(this,arguments)}return Object(r.i)(n,[{key:"add",value:function(e,t){e?e instanceof d||(e=new d(e.key||e,e.value)):e=new d(e);var n=m(this.items,e.key),r=this.schema&&this.schema.sortMapEntries;if(n){if(!t)throw new Error("Key ".concat(e.key," already set"));n.value=e.value}else if(r){var o=this.items.findIndex((function(t){return r(e,t)<0}));-1===o?this.items.push(e):this.items.splice(o,0,e)}else this.items.push(e)}},{key:"delete",value:function(e){var t=m(this.items,e);return!!t&&this.items.splice(this.items.indexOf(t),1).length>0}},{key:"get",value:function(e,t){var n=m(this.items,e),r=n&&n.value;return!t&&r instanceof s?r.value:r}},{key:"has",value:function(e){return!!m(this.items,e)}},{key:"set",value:function(e,t){this.add(new d(e,t),!0)}},{key:"toJSON",value:function(e,t,n){var o=n?new n:t&&t.mapAsMap?new Map:{};t&&t.onCreate&&t.onCreate(o);var i,a=Object(r.g)(this.items);try{for(a.s();!(i=a.n()).done;){i.value.addToJSMap(t,o)}}catch(e){a.e(e)}finally{a.f()}return o}},{key:"toString",value:function(e,t,o){if(!e)return JSON.stringify(this);var i,a=Object(r.g)(this.items);try{for(a.s();!(i=a.n()).done;){var s=i.value;if(!(s instanceof d))throw new Error("Map items must all be pairs; found ".concat(JSON.stringify(s)," instead"))}}catch(e){a.e(e)}finally{a.f()}return Object(r.s)(Object(r.t)(n.prototype),"toString",this).call(this,e,{blockItem:function(e){return e.str},flowChars:{start:"{",end:"}"},isMap:!0,itemIndent:e.indent||""},t,o)}}]),n}(u),y=function(e){Object(r.q)(n,e);var t=Object(r.r)(n);function n(e){var o;if(Object(r.j)(this,n),e instanceof d){var i=e.value;i instanceof f||((i=new f).items.push(e.value),i.range=e.value.range),(o=t.call(this,e.key,i)).range=e.range}else o=t.call(this,new s("<<"),new f);return o.type=d.Type.MERGE_PAIR,Object(r.y)(o)}return Object(r.i)(n,[{key:"addToJSMap",value:function(e,t){var n,o=Object(r.g)(this.value.items);try{for(o.s();!(n=o.n()).done;){var i=n.value.source;if(!(i instanceof g))throw new Error("Merge sources must be maps");var a,s=i.toJSON(null,e,Map),c=Object(r.g)(s);try{for(c.s();!(a=c.n()).done;){var l=Object(r.o)(a.value,2),u=l[0],p=l[1];t instanceof Map?t.has(u)||t.set(u,p):t instanceof Set?t.add(u):Object.prototype.hasOwnProperty.call(t,u)||(t[u]=p)}}catch(e){c.e(e)}finally{c.f()}}}catch(e){o.e(e)}finally{o.f()}return t}},{key:"toString",value:function(e,t){var o=this.value;if(o.items.length>1)return Object(r.s)(Object(r.t)(n.prototype),"toString",this).call(this,e,t);this.value=o.items[0];var i=Object(r.s)(Object(r.t)(n.prototype),"toString",this).call(this,e,t);return this.value=o,i}}]),n}(d),b={defaultType:r.e.BLOCK_LITERAL,lineWidth:76},x={trueStr:"true",falseStr:"false"},w={asBigInt:!1},k={nullStr:"null"},S={defaultType:r.e.PLAIN,doubleQuoted:{jsonEncoding:!1,minMultiLineLength:40},fold:{lineWidth:80,minContentWidth:20}};function O(e,t,n){var o,i=Object(r.g)(t);try{for(i.s();!(o=i.n()).done;){var a=o.value,c=a.format,l=a.test,u=a.resolve;if(l){var p=e.match(l);if(p){var f=u.apply(null,p);return f instanceof s||(f=new s(f)),c&&(f.format=c),f}}}}catch(e){i.e(e)}finally{i.f()}return n&&(e=n(e)),new s(e)}var A=function(e,t){for(var n=e[t+1];" "===n||"\t"===n;){do{n=e[t+=1]}while(n&&"\n"!==n);n=e[t+1]}return t};function E(e,t,n,r){var o=r.indentAtStart,i=r.lineWidth,a=void 0===i?80:i,s=r.minContentWidth,c=void 0===s?20:s,l=r.onFold,u=r.onOverflow;if(!a||a<0)return e;var p=Math.max(1+c,1+a-t.length);if(e.length<=p)return e;var f,d=[],h={},v=a-("number"==typeof o?o:t.length),m=void 0,g=void 0,y=!1,b=-1;for("block"===n&&-1!==(b=A(e,b))&&(v=b+p);f=e[b+=1];){if("quoted"===n&&"\\"===f)switch(e[b+1]){case"x":b+=3;break;case"u":b+=5;break;case"U":b+=9;break;default:b+=1}if("\n"===f)"block"===n&&(b=A(e,b)),v=b+p,m=void 0;else{if(" "===f&&g&&" "!==g&&"\n"!==g&&"\t"!==g){var x=e[b+1];x&&" "!==x&&"\n"!==x&&"\t"!==x&&(m=b)}if(b>=v)if(m)d.push(m),v=m+p,m=void 0;else if("quoted"===n){for(;" "===g||"\t"===g;)g=f,f=e[b+=1],y=!0;d.push(b-2),h[b-2]=!0,v=b-2+p,m=void 0}else y=!0}g=f}if(y&&u&&u(),0===d.length)return e;l&&l();for(var w=e.slice(0,d[0]),k=0;kt)return!0;if(n-(o=r+1)<=t)return!1}return!0}(s,S.fold.lineWidth-c.length)),p=u?"|":">";if(!s)return p+"\n";var f="",d="";if(s=s.replace(/[\n\t ]*$/,(function(e){var t=e.indexOf("\n");return-1===t?p+="-":s!==e&&t===e.length-1||(p+="+",o&&o()),d=e.replace(/\n$/,""),""})).replace(/^[\n ]*/,(function(e){-1!==e.indexOf(" ")&&(p+=l);var t=e.match(/ +$/);return t?(f=e.slice(0,-t[0].length),t[0]):(f=e,"")})),d&&(d=d.replace(/\n+(?!\n|$)/g,"$&".concat(c))),f&&(f=f.replace(/\n+/g,"$&".concat(c))),i&&(p+=" #"+i.replace(/ ?[\r\n]+/g," "),n&&n()),!s)return"".concat(p).concat(l,"\n").concat(c).concat(d);if(u)return s=s.replace(/\n+/g,"$&".concat(c)),"".concat(p,"\n").concat(c).concat(f).concat(s).concat(d);s=s.replace(/\n+/g,"\n$&").replace(/(?:^|\n)([\t ].*)(?:([\n\t ]*)\n(?![\n\t ]))?/g,"$1$2").replace(/\n+/g,"$&".concat(c));var h=E("".concat(f).concat(s).concat(d),c,"block",S.fold);return"".concat(p,"\n").concat(c).concat(h)}function P(e,t,n,o){var i=S.defaultType,a=t.implicitKey,s=t.inFlow,c=e,l=c.type,u=c.value;"string"!=typeof u&&(u=String(u),e=Object.assign({},e,{value:u}));var p=function(i){switch(i){case r.e.BLOCK_FOLDED:case r.e.BLOCK_LITERAL:return I(e,t,n,o);case r.e.QUOTE_DOUBLE:return T(u,t);case r.e.QUOTE_SINGLE:return C(u,t);case r.e.PLAIN:return function(e,t,n,o){var i=e.comment,a=e.type,s=e.value,c=t.actualString,l=t.implicitKey,u=t.indent,p=t.inFlow;if(l&&/[\n[\]{},]/.test(s)||p&&/[[\]{},]/.test(s))return T(s,t);if(!s||/^[\n\t ,[\]{}#&*!|>'"%@`]|^[?-]$|^[?-][ \t]|[\n:][ \t]|[ \t]\n|[\n\t ]#|[\n\t :]$/.test(s))return l||p||-1===s.indexOf("\n")?-1!==s.indexOf('"')&&-1===s.indexOf("'")?C(s,t):T(s,t):I(e,t,n,o);if(!l&&!p&&a!==r.e.PLAIN&&-1!==s.indexOf("\n"))return I(e,t,n,o);if(""===u&&j(s))return t.forceBlockIndent=!0,I(e,t,n,o);var f=s.replace(/\n+/g,"$&\n".concat(u));if(c){var d=t.doc.schema.tags;if("string"!=typeof O(f,d,d.scalarFallback).value)return T(s,t)}var h=l?f:E(f,u,"flow",_(t));return!i||p||-1===h.indexOf("\n")&&-1===i.indexOf("\n")?h:(n&&n(),function(e,t,n){if(!n)return e;var r=n.replace(/[\s\S]^/gm,"$&".concat(t,"#"));return"#".concat(r,"\n").concat(t).concat(e)}(h,u,i))}(e,t,n,o);default:return null}};l!==r.e.QUOTE_DOUBLE&&/[\x00-\x08\x0b-\x1f\x7f-\x9f]/.test(u)?l=r.e.QUOTE_DOUBLE:!a&&!s||l!==r.e.BLOCK_FOLDED&&l!==r.e.BLOCK_LITERAL||(l=r.e.QUOTE_DOUBLE);var f=p(l);if(null===f&&null===(f=p(i)))throw new Error("Unsupported default string type ".concat(i));return f}function R(e){var t=e.format,n=e.minFractionDigits,r=e.tag,o=e.value;if("bigint"==typeof o)return String(o);if(!isFinite(o))return isNaN(o)?".nan":o<0?"-.inf":".inf";var i=JSON.stringify(o);if(!t&&n&&(!r||"tag:yaml.org,2002:float"===r)&&/^\d/.test(i)){var a=i.indexOf(".");a<0&&(a=i.length,i+=".");for(var s=n-(i.length-a-1);s-- >0;)i+="0"}return i}function L(e,t){var n,o,i;switch(t.type){case r.e.FLOW_MAP:n="}",o="flow map";break;case r.e.FLOW_SEQ:n="]",o="flow sequence";break;default:return void e.push(new r.n(t,"Not a flow collection!?"))}for(var a=t.items.length-1;a>=0;--a){var s=t.items[a];if(!s||s.type!==r.e.COMMENT){i=s;break}}if(i&&i.char!==n){var c,l="Expected ".concat(o," to end with ").concat(n);"number"==typeof i.offset?(c=new r.n(t,l)).offset=i.offset+1:(c=new r.n(i,l),i.range&&i.range.end&&(c.offset=i.range.end-i.range.start)),e.push(c)}}function N(e,t){var n=t.context.src[t.range.start-1];if("\n"!==n&&"\t"!==n&&" "!==n){e.push(new r.n(t,"Comments must be separated from other tokens by white space characters"))}}function M(e,t){var n=String(t),o=n.substr(0,8)+"..."+n.substr(-8);return new r.n(e,'The "'.concat(o,'" key is too long'))}function B(e,t){var n,o=Object(r.g)(t);try{for(o.s();!(n=o.n()).done;){var i=n.value,a=i.afterKey,s=i.before,c=i.comment,l=e.items[s];l?(a&&l.value&&(l=l.value),void 0===c?!a&&l.commentBefore||(l.spaceBefore=!0):l.commentBefore?l.commentBefore+="\n"+c:l.commentBefore=c):void 0!==c&&(e.comment?e.comment+="\n"+c:e.comment=c)}}catch(e){o.e(e)}finally{o.f()}}function D(e,t){var n=t.strValue;return n?"string"==typeof n?n:(n.errors.forEach((function(n){n.source||(n.source=t),e.errors.push(n)})),n.str):""}function q(e,t){var n=t.tag,o=t.type,i=!1;if(n){var a=n.handle,s=n.suffix,c=n.verbatim;if(c){if("!"!==c&&"!!"!==c)return c;var l="Verbatim tags aren't resolved, so ".concat(c," is invalid.");e.errors.push(new r.n(t,l))}else if("!"!==a||s)try{return function(e,t){var n=t.tag,o=n.handle,i=n.suffix,a=e.tagPrefixes.find((function(e){return e.handle===o}));if(!a){var s=e.getDefaults().tagPrefixes;if(s&&(a=s.find((function(e){return e.handle===o}))),!a)throw new r.n(t,"The ".concat(o," tag handle is non-default and was not declared."))}if(!i)throw new r.n(t,"The ".concat(o," tag has no suffix."));if("!"===o&&"1.0"===(e.version||e.options.version)){if("^"===i[0])return e.warnings.push(new r.m(t,"YAML 1.0 ^ tag expansion is not supported")),i;if(/[:/]/.test(i)){var c=i.match(/^([a-z0-9-]+)\/(.*)/i);return c?"tag:".concat(c[1],".yaml.org,2002:").concat(c[2]):"tag:".concat(i)}}return a.prefix+decodeURIComponent(i)}(e,t)}catch(t){e.errors.push(t)}else i=!0}switch(o){case r.e.BLOCK_FOLDED:case r.e.BLOCK_LITERAL:case r.e.QUOTE_DOUBLE:case r.e.QUOTE_SINGLE:return r.u.STR;case r.e.FLOW_MAP:case r.e.MAP:return r.u.MAP;case r.e.FLOW_SEQ:case r.e.SEQ:return r.u.SEQ;case r.e.PLAIN:return i?r.u.STR:null;default:return null}}function F(e,t,n){var o,i=e.schema.tags,a=[],c=Object(r.g)(i);try{for(c.s();!(o=c.n()).done;){var l=o.value;if(l.tag===n){if(!l.test){var p=l.resolve(e,t);return p instanceof u?p:new s(p)}a.push(l)}}}catch(e){c.e(e)}finally{c.f()}var f=D(e,t);return"string"==typeof f&&a.length>0?O(f,a,i.scalarFallback):null}function z(e,t,n){try{var o=F(e,t,n);if(o)return n&&t.tag&&(o.tag=n),o}catch(n){return n.source||(n.source=t),e.errors.push(n),null}try{var i=function(e){switch(e.type){case r.e.FLOW_MAP:case r.e.MAP:return r.u.MAP;case r.e.FLOW_SEQ:case r.e.SEQ:return r.u.SEQ;default:return r.u.STR}}(t);if(!i)throw new Error("The tag ".concat(n," is unavailable"));var a="The tag ".concat(n," is unavailable, falling back to ").concat(i);e.warnings.push(new r.m(t,a));var s=F(e,t,i);return s.tag=n,s}catch(n){var c=new r.v(t,n.message);return c.stack=n.stack,e.errors.push(c),null}}function U(e,t){var n,o={before:[],after:[]},i=!1,a=!1,s=function(e){if(!e)return!1;var t=e.type;return t===r.e.MAP_KEY||t===r.e.MAP_VALUE||t===r.e.SEQ_ITEM}(t.context.parent)?t.context.parent.props.concat(t.props):t.props,c=Object(r.g)(s);try{for(c.s();!(n=c.n()).done;){var l=n.value,u=l.start,p=l.end;switch(t.context.src[u]){case r.a.COMMENT:if(!t.commentHasRequiredWhitespace(u)){e.push(new r.n(t,"Comments must be separated from other tokens by white space characters"))}var f=t.header,d=t.valueRange;(d&&(u>d.start||f&&u>f.start)?o.after:o.before).push(t.context.src.slice(u+1,p));break;case r.a.ANCHOR:if(i){e.push(new r.n(t,"A node can have at most one anchor"))}i=!0;break;case r.a.TAG:if(a){e.push(new r.n(t,"A node can have at most one tag"))}a=!0}}}catch(e){c.e(e)}finally{c.f()}return{comments:o,hasAnchor:i,hasTag:a}}function $(e,t){if(!t)return null;t.error&&e.errors.push(t.error);var n=U(e.errors,t),o=n.comments,i=n.hasAnchor,a=n.hasTag;if(i){var s=e.anchors,c=t.anchor,l=s.getNode(c);l&&(s.map[s.newName(c)]=l),s.map[c]=t}if(t.type===r.e.ALIAS&&(i||a)){e.errors.push(new r.n(t,"An alias node must not specify any properties"))}var u=function(e,t){var n=e.anchors,o=e.errors,i=e.schema;if(t.type===r.e.ALIAS){var a=t.rawValue,s=n.getNode(a);if(!s){var c="Aliased anchor not found: ".concat(a);return o.push(new r.v(t,c)),null}var l=new v(s);return n._cstAliases.push(l),l}var u=q(e,t);if(u)return z(e,t,u);if(t.type!==r.e.PLAIN){var p="Failed to resolve ".concat(t.type," node here");return o.push(new r.f(t,p)),null}try{return O(D(e,t),i.tags,i.tags.scalarFallback)}catch(e){return e.source||(e.source=t),o.push(e),null}}(e,t);if(u){u.range=[t.range.start,t.range.end],e.options.keepCstNodes&&(u.cstNode=t),e.options.keepNodeTypes&&(u.type=t.type);var p=o.before.join("\n");p&&(u.commentBefore=u.commentBefore?"".concat(u.commentBefore,"\n").concat(p):p);var f=o.after.join("\n");f&&(u.comment=u.comment?"".concat(u.comment,"\n").concat(f):f)}return t.resolved=u}function H(e,t){if(t.type!==r.e.MAP&&t.type!==r.e.FLOW_MAP){var n="A ".concat(t.type," node cannot be resolved as a mapping");return e.errors.push(new r.f(t,n)),null}var o=t.type===r.e.FLOW_MAP?function(e,t){for(var n=[],o=[],i=void 0,a=!1,s="{",c=0;c0){(l=new r.c(r.e.PLAIN,[])).context={parent:c,src:c.context.src};var u=c.range.start+1;if(l.range={start:u,end:u},l.valueRange={start:u,end:u},"number"==typeof c.range.origStart){var p=c.range.origStart+1;l.range.origStart=l.range.origEnd=p,l.valueRange.origStart=l.valueRange.origEnd=p}}var f=new d(i,$(e,l));W(c,f),o.push(f),i&&"number"==typeof a&&c.range.start>a+1024&&e.errors.push(M(t,i)),i=void 0,a=null;break;default:void 0!==i&&o.push(new d(i)),i=$(e,c),a=c.range.start,c.error&&e.errors.push(c.error);e:for(var h=s+1;;++h){var v=t.items[h];switch(v&&v.type){case r.e.BLANK_LINE:case r.e.COMMENT:continue e;case r.e.MAP_VALUE:break e;default:e.errors.push(new r.n(c,"Implicit map keys need to be followed by map values"));break e}}if(c.valueRangeContainsNewline){e.errors.push(new r.n(c,"Implicit map keys need to be on a single line"))}}}void 0!==i&&o.push(new d(i));return{comments:n,items:o}}(e,t),i=o.comments,a=o.items,s=new g;s.items=a,B(s,i);for(var c=!1,l=0;lo.valueRange.start)return!1;if(i[s]!==r.a.COMMENT)return!1;for(var c=n;cs+1024&&e.errors.push(M(t,a));for(var g=l.context.src,y=s;ye.length)&&(t=e.length);for(var n=0,r=new Array(t);n=e.length?{done:!0}:{done:!1,value:e[o++]}}}throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}return(r=e[Symbol.iterator]()).next.bind(r)}var r=function(e,t){return e(t={exports:{}},t.exports),t.exports}((function(e){function t(){return{baseUrl:null,breaks:!1,gfm:!0,headerIds:!0,headerPrefix:"",highlight:null,langPrefix:"language-",mangle:!0,pedantic:!1,renderer:null,sanitize:!1,sanitizer:null,silent:!1,smartLists:!1,smartypants:!1,tokenizer:null,walkTokens:null,xhtml:!1}}e.exports={defaults:{baseUrl:null,breaks:!1,gfm:!0,headerIds:!0,headerPrefix:"",highlight:null,langPrefix:"language-",mangle:!0,pedantic:!1,renderer:null,sanitize:!1,sanitizer:null,silent:!1,smartLists:!1,smartypants:!1,tokenizer:null,walkTokens:null,xhtml:!1},getDefaults:t,changeDefaults:function(t){e.exports.defaults=t}}})),o=(r.defaults,r.getDefaults,r.changeDefaults,/[&<>"']/),i=/[&<>"']/g,a=/[<>"']|&(?!#?\w+;)/,s=/[<>"']|&(?!#?\w+;)/g,c={"&":"&","<":"<",">":">",'"':""","'":"'"},l=function(e){return c[e]},u=/&(#(?:\d+)|(?:#x[0-9A-Fa-f]+)|(?:\w+));?/gi;function p(e){return e.replace(u,(function(e,t){return"colon"===(t=t.toLowerCase())?":":"#"===t.charAt(0)?"x"===t.charAt(1)?String.fromCharCode(parseInt(t.substring(2),16)):String.fromCharCode(+t.substring(1)):""}))}var f=/(^|[^\[])\^/g,d=/[^\w:]/g,h=/^$|^[a-z][a-z0-9+.-]*:|^[?#]/i,v={},m=/^[^:]+:\/*[^/]*$/,g=/^([^:]+:)[\s\S]*$/,y=/^([^:]+:\/*[^/]*)[\s\S]*$/;function b(e,t){v[" "+e]||(m.test(e)?v[" "+e]=e+"/":v[" "+e]=x(e,"/",!0));var n=-1===(e=v[" "+e]).indexOf(":");return"//"===t.substring(0,2)?n?t:e.replace(g,"$1")+t:"/"===t.charAt(0)?n?t:e.replace(y,"$1")+t:e+t}function x(e,t,n){var r=e.length;if(0===r)return"";for(var o=0;o=0&&"\\"===n[o];)r=!r;return r?"|":" |"})).split(/ \|/),r=0;if(n.length>t)n.splice(t);else for(;n.length1;)1&t&&(n+=e),t>>=1,e+=e;return n+e},P=r.defaults,R=j,L=_,N=w,M=T;function B(e,t,n){var r=t.href,o=t.title?N(t.title):null,i=e[1].replace(/\\([\[\]])/g,"$1");return"!"!==e[0].charAt(0)?{type:"link",raw:n,href:r,title:o,text:i}:{type:"image",raw:n,href:r,title:o,text:N(i)}}var D=function(){function e(e){this.options=e||P}var t=e.prototype;return t.space=function(e){var t=this.rules.block.newline.exec(e);if(t)return t[0].length>1?{type:"space",raw:t[0]}:{raw:"\n"}},t.code=function(e,t){var n=this.rules.block.code.exec(e);if(n){var r=t[t.length-1];if(r&&"paragraph"===r.type)return{raw:n[0],text:n[0].trimRight()};var o=n[0].replace(/^ {4}/gm,"");return{type:"code",raw:n[0],codeBlockStyle:"indented",text:this.options.pedantic?o:R(o,"\n")}}},t.fences=function(e){var t=this.rules.block.fences.exec(e);if(t){var n=t[0],r=function(e,t){var n=e.match(/^(\s+)(?:```)/);if(null===n)return t;var r=n[1];return t.split("\n").map((function(e){var t=e.match(/^\s+/);return null===t?e:t[0].length>=r.length?e.slice(r.length):e})).join("\n")}(n,t[3]||"");return{type:"code",raw:n,lang:t[2]?t[2].trim():t[2],text:r}}},t.heading=function(e){var t=this.rules.block.heading.exec(e);if(t){var n=t[2].trim();if(/#$/.test(n)){var r=R(n,"#");this.options.pedantic?n=r.trim():r&&!/ $/.test(r)||(n=r.trim())}return{type:"heading",raw:t[0],depth:t[1].length,text:n}}},t.nptable=function(e){var t=this.rules.block.nptable.exec(e);if(t){var n={type:"table",header:L(t[1].replace(/^ *| *\| *$/g,"")),align:t[2].replace(/^ *|\| *$/g,"").split(/ *\| */),cells:t[3]?t[3].replace(/\n$/,"").split("\n"):[],raw:t[0]};if(n.header.length===n.align.length){var r,o=n.align.length;for(r=0;r ?/gm,"");return{type:"blockquote",raw:t[0],text:n}}},t.list=function(e){var t=this.rules.block.list.exec(e);if(t){var n,r,o,i,a,s,c,l,u=t[0],p=t[2],f=p.length>1,d={type:"list",raw:u,ordered:f,start:f?+p.slice(0,-1):"",loose:!1,items:[]},h=t[0].match(this.rules.block.item),v=!1,m=h.length;o=this.rules.block.listItemStart.exec(h[0]);for(var g=0;go[0].length||i[1].length>3){h.splice(g,2,h[g]+"\n"+h[g+1]),g--,m--;continue}(!this.options.pedantic||this.options.smartLists?i[2][i[2].length-1]!==p[p.length-1]:f===(1===i[2].length))&&(a=h.slice(g+1).join("\n"),d.raw=d.raw.substring(0,d.raw.length-a.length),g=m-1),o=i}r=n.length,~(n=n.replace(/^ *([*+-]|\d+[.)]) ?/,"")).indexOf("\n ")&&(r-=n.length,n=this.options.pedantic?n.replace(/^ {1,4}/gm,""):n.replace(new RegExp("^ {1,"+r+"}","gm"),"")),s=v||/\n\n(?!\s*$)/.test(n),g!==m-1&&(v="\n"===n.charAt(n.length-1),s||(s=v)),s&&(d.loose=!0),this.options.gfm&&(l=void 0,(c=/^\[[ xX]\] /.test(n))&&(l=" "!==n[1],n=n.replace(/^\[[ xX]\] +/,""))),d.items.push({type:"list_item",raw:u,task:c,checked:l,loose:s,text:n})}return d}},t.html=function(e){var t=this.rules.block.html.exec(e);if(t)return{type:this.options.sanitize?"paragraph":"html",raw:t[0],pre:!this.options.sanitizer&&("pre"===t[1]||"script"===t[1]||"style"===t[1]),text:this.options.sanitize?this.options.sanitizer?this.options.sanitizer(t[0]):N(t[0]):t[0]}},t.def=function(e){var t=this.rules.block.def.exec(e);if(t)return t[3]&&(t[3]=t[3].substring(1,t[3].length-1)),{tag:t[1].toLowerCase().replace(/\s+/g," "),raw:t[0],href:t[2],title:t[3]}},t.table=function(e){var t=this.rules.block.table.exec(e);if(t){var n={type:"table",header:L(t[1].replace(/^ *| *\| *$/g,"")),align:t[2].replace(/^ *|\| *$/g,"").split(/ *\| */),cells:t[3]?t[3].replace(/\n$/,"").split("\n"):[]};if(n.header.length===n.align.length){n.raw=t[0];var r,o=n.align.length;for(r=0;r/i.test(r[0])&&(t=!1),!n&&/^<(pre|code|kbd|script)(\s|>)/i.test(r[0])?n=!0:n&&/^<\/(pre|code|kbd|script)(\s|>)/i.test(r[0])&&(n=!1),{type:this.options.sanitize?"text":"html",raw:r[0],inLink:t,inRawBlock:n,text:this.options.sanitize?this.options.sanitizer?this.options.sanitizer(r[0]):N(r[0]):r[0]}},t.link=function(e){var t=this.rules.inline.link.exec(e);if(t){var n=t[2].trim();if(!this.options.pedantic&&/^$/.test(n))return;var r=R(n.slice(0,-1),"\\");if((n.length-r.length)%2==0)return}else{var o=M(t[2],"()");if(o>-1){var i=(0===t[0].indexOf("!")?5:4)+t[1].length+o;t[2]=t[2].substring(0,o),t[0]=t[0].substring(0,i).trim(),t[3]=""}}var a=t[2],s="";if(this.options.pedantic){var c=/^([^'"]*[^\s])\s+(['"])(.*)\2/.exec(a);c&&(a=c[1],s=c[3])}else s=t[3]?t[3].slice(1,-1):"";return a=a.trim(),/^$/.test(n)?a.slice(1):a.slice(1,-1)),B(t,{href:a?a.replace(this.rules.inline._escapes,"$1"):a,title:s?s.replace(this.rules.inline._escapes,"$1"):s},t[0])}},t.reflink=function(e,t){var n;if((n=this.rules.inline.reflink.exec(e))||(n=this.rules.inline.nolink.exec(e))){var r=(n[2]||n[1]).replace(/\s+/g," ");if(!(r=t[r.toLowerCase()])||!r.href){var o=n[0].charAt(0);return{type:"text",raw:o,text:o}}return B(n,r,n[0])}},t.strong=function(e,t,n){void 0===n&&(n="");var r=this.rules.inline.strong.start.exec(e);if(r&&(!r[1]||r[1]&&(""===n||this.rules.inline.punctuation.exec(n)))){t=t.slice(-1*e.length);var o,i="**"===r[0]?this.rules.inline.strong.endAst:this.rules.inline.strong.endUnd;for(i.lastIndex=0;null!=(r=i.exec(t));)if(o=this.rules.inline.strong.middle.exec(t.slice(0,r.index+3)))return{type:"strong",raw:e.slice(0,o[0].length),text:e.slice(2,o[0].length-2)}}},t.em=function(e,t,n){void 0===n&&(n="");var r=this.rules.inline.em.start.exec(e);if(r&&(!r[1]||r[1]&&(""===n||this.rules.inline.punctuation.exec(n)))){t=t.slice(-1*e.length);var o,i="*"===r[0]?this.rules.inline.em.endAst:this.rules.inline.em.endUnd;for(i.lastIndex=0;null!=(r=i.exec(t));)if(o=this.rules.inline.em.middle.exec(t.slice(0,r.index+2)))return{type:"em",raw:e.slice(0,o[0].length),text:e.slice(1,o[0].length-1)}}},t.codespan=function(e){var t=this.rules.inline.code.exec(e);if(t){var n=t[2].replace(/\n/g," "),r=/[^ ]/.test(n),o=/^ /.test(n)&&/ $/.test(n);return r&&o&&(n=n.substring(1,n.length-1)),n=N(n,!0),{type:"codespan",raw:t[0],text:n}}},t.br=function(e){var t=this.rules.inline.br.exec(e);if(t)return{type:"br",raw:t[0]}},t.del=function(e){var t=this.rules.inline.del.exec(e);if(t)return{type:"del",raw:t[0],text:t[2]}},t.autolink=function(e,t){var n,r,o=this.rules.inline.autolink.exec(e);if(o)return r="@"===o[2]?"mailto:"+(n=N(this.options.mangle?t(o[1]):o[1])):n=N(o[1]),{type:"link",raw:o[0],text:n,href:r,tokens:[{type:"text",raw:n,text:n}]}},t.url=function(e,t){var n;if(n=this.rules.inline.url.exec(e)){var r,o;if("@"===n[2])o="mailto:"+(r=N(this.options.mangle?t(n[0]):n[0]));else{var i;do{i=n[0],n[0]=this.rules.inline._backpedal.exec(n[0])[0]}while(i!==n[0]);r=N(n[0]),o="www."===n[1]?"http://"+r:r}return{type:"link",raw:n[0],text:r,href:o,tokens:[{type:"text",raw:r,text:r}]}}},t.inlineText=function(e,t,n){var r,o=this.rules.inline.text.exec(e);if(o)return r=t?this.options.sanitize?this.options.sanitizer?this.options.sanitizer(o[0]):N(o[0]):o[0]:N(this.options.smartypants?n(o[0]):o[0]),{type:"text",raw:o[0],text:r}},e}(),q=A,F=S,z=E,U={newline:/^\n+/,code:/^( {4}[^\n]+\n*)+/,fences:/^ {0,3}(`{3,}(?=[^`\n]*\n)|~{3,})([^\n]*)\n(?:|([\s\S]*?)\n)(?: {0,3}\1[~`]* *(?:\n+|$)|$)/,hr:/^ {0,3}((?:- *){3,}|(?:_ *){3,}|(?:\* *){3,})(?:\n+|$)/,heading:/^ {0,3}(#{1,6})(?=\s|$)(.*)(?:\n+|$)/,blockquote:/^( {0,3}> ?(paragraph|[^\n]*)(?:\n|$))+/,list:/^( {0,3})(bull) [\s\S]+?(?:hr|def|\n{2,}(?! )(?! {0,3}bull )\n*|\s*$)/,html:"^ {0,3}(?:<(script|pre|style)[\\s>][\\s\\S]*?(?:[^\\n]*\\n+|$)|comment[^\\n]*(\\n+|$)|<\\?[\\s\\S]*?(?:\\?>\\n*|$)|\\n*|$)|\\n*|$)|)[\\s\\S]*?(?:\\n{2,}|$)|<(?!script|pre|style)([a-z][\\w-]*)(?:attribute)*? */?>(?=[ \\t]*(?:\\n|$))[\\s\\S]*?(?:\\n{2,}|$)|(?=[ \\t]*(?:\\n|$))[\\s\\S]*?(?:\\n{2,}|$))",def:/^ {0,3}\[(label)\]: *\n? *]+)>?(?:(?: +\n? *| *\n *)(title))? *(?:\n+|$)/,nptable:q,table:q,lheading:/^([^\n]+)\n {0,3}(=+|-+) *(?:\n+|$)/,_paragraph:/^([^\n]+(?:\n(?!hr|heading|lheading|blockquote|fences|list|html)[^\n]+)*)/,text:/^[^\n]+/,_label:/(?!\s*\])(?:\\[\[\]]|[^\[\]])+/,_title:/(?:"(?:\\"?|[^"\\])*"|'[^'\n]*(?:\n[^'\n]+)*\n?'|\([^()]*\))/};U.def=F(U.def).replace("label",U._label).replace("title",U._title).getRegex(),U.bullet=/(?:[*+-]|\d{1,9}[.)])/,U.item=/^( *)(bull) ?[^\n]*(?:\n(?! *bull ?)[^\n]*)*/,U.item=F(U.item,"gm").replace(/bull/g,U.bullet).getRegex(),U.listItemStart=F(/^( *)(bull)/).replace("bull",U.bullet).getRegex(),U.list=F(U.list).replace(/bull/g,U.bullet).replace("hr","\\n+(?=\\1?(?:(?:- *){3,}|(?:_ *){3,}|(?:\\* *){3,})(?:\\n+|$))").replace("def","\\n+(?="+U.def.source+")").getRegex(),U._tag="address|article|aside|base|basefont|blockquote|body|caption|center|col|colgroup|dd|details|dialog|dir|div|dl|dt|fieldset|figcaption|figure|footer|form|frame|frameset|h[1-6]|head|header|hr|html|iframe|legend|li|link|main|menu|menuitem|meta|nav|noframes|ol|optgroup|option|p|param|section|source|summary|table|tbody|td|tfoot|th|thead|title|tr|track|ul",U._comment=/|$)/,U.html=F(U.html,"i").replace("comment",U._comment).replace("tag",U._tag).replace("attribute",/ +[a-zA-Z:_][\w.:-]*(?: *= *"[^"\n]*"| *= *'[^'\n]*'| *= *[^\s"'=<>`]+)?/).getRegex(),U.paragraph=F(U._paragraph).replace("hr",U.hr).replace("heading"," {0,3}#{1,6} ").replace("|lheading","").replace("blockquote"," {0,3}>").replace("fences"," {0,3}(?:`{3,}(?=[^`\\n]*\\n)|~{3,})[^\\n]*\\n").replace("list"," {0,3}(?:[*+-]|1[.)]) ").replace("html",")|<(?:script|pre|style|!--)").replace("tag",U._tag).getRegex(),U.blockquote=F(U.blockquote).replace("paragraph",U.paragraph).getRegex(),U.normal=z({},U),U.gfm=z({},U.normal,{nptable:"^ *([^|\\n ].*\\|.*)\\n {0,3}([-:]+ *\\|[-| :]*)(?:\\n((?:(?!\\n|hr|heading|blockquote|code|fences|list|html).*(?:\\n|$))*)\\n*|$)",table:"^ *\\|(.+)\\n {0,3}\\|?( *[-:]+[-| :]*)(?:\\n *((?:(?!\\n|hr|heading|blockquote|code|fences|list|html).*(?:\\n|$))*)\\n*|$)"}),U.gfm.nptable=F(U.gfm.nptable).replace("hr",U.hr).replace("heading"," {0,3}#{1,6} ").replace("blockquote"," {0,3}>").replace("code"," {4}[^\\n]").replace("fences"," {0,3}(?:`{3,}(?=[^`\\n]*\\n)|~{3,})[^\\n]*\\n").replace("list"," {0,3}(?:[*+-]|1[.)]) ").replace("html",")|<(?:script|pre|style|!--)").replace("tag",U._tag).getRegex(),U.gfm.table=F(U.gfm.table).replace("hr",U.hr).replace("heading"," {0,3}#{1,6} ").replace("blockquote"," {0,3}>").replace("code"," {4}[^\\n]").replace("fences"," {0,3}(?:`{3,}(?=[^`\\n]*\\n)|~{3,})[^\\n]*\\n").replace("list"," {0,3}(?:[*+-]|1[.)]) ").replace("html",")|<(?:script|pre|style|!--)").replace("tag",U._tag).getRegex(),U.pedantic=z({},U.normal,{html:F("^ *(?:comment *(?:\\n|\\s*$)|<(tag)[\\s\\S]+? *(?:\\n{2,}|\\s*$)|\\s]*)*?/?> *(?:\\n{2,}|\\s*$))").replace("comment",U._comment).replace(/tag/g,"(?!(?:a|em|strong|small|s|cite|q|dfn|abbr|data|time|code|var|samp|kbd|sub|sup|i|b|u|mark|ruby|rt|rp|bdi|bdo|span|br|wbr|ins|del|img)\\b)\\w+(?!:|[^\\w\\s@]*@)\\b").getRegex(),def:/^ *\[([^\]]+)\]: *]+)>?(?: +(["(][^\n]+[")]))? *(?:\n+|$)/,heading:/^(#{1,6})(.*)(?:\n+|$)/,fences:q,paragraph:F(U.normal._paragraph).replace("hr",U.hr).replace("heading"," *#{1,6} *[^\n]").replace("lheading",U.lheading).replace("blockquote"," {0,3}>").replace("|fences","").replace("|list","").replace("|html","").getRegex()});var $={escape:/^\\([!"#$%&'()*+,\-./:;<=>?@\[\]\\^_`{|}~])/,autolink:/^<(scheme:[^\s\x00-\x1f<>]*|email)>/,url:q,tag:"^comment|^|^<[a-zA-Z][\\w-]*(?:attribute)*?\\s*/?>|^<\\?[\\s\\S]*?\\?>|^|^",link:/^!?\[(label)\]\(\s*(href)(?:\s+(title))?\s*\)/,reflink:/^!?\[(label)\]\[(?!\s*\])((?:\\[\[\]]?|[^\[\]\\])+)\]/,nolink:/^!?\[(?!\s*\])((?:\[[^\[\]]*\]|\\[\[\]]|[^\[\]])*)\](?:\[\])?/,reflinkSearch:"reflink|nolink(?!\\()",strong:{start:/^(?:(\*\*(?=[*punctuation]))|\*\*)(?![\s])|__/,middle:/^\*\*(?:(?:(?!overlapSkip)(?:[^*]|\\\*)|overlapSkip)|\*(?:(?!overlapSkip)(?:[^*]|\\\*)|overlapSkip)*?\*)+?\*\*$|^__(?![\s])((?:(?:(?!overlapSkip)(?:[^_]|\\_)|overlapSkip)|_(?:(?!overlapSkip)(?:[^_]|\\_)|overlapSkip)*?_)+?)__$/,endAst:/[^punctuation\s]\*\*(?!\*)|[punctuation]\*\*(?!\*)(?:(?=[punctuation_\s]|$))/,endUnd:/[^\s]__(?!_)(?:(?=[punctuation*\s])|$)/},em:{start:/^(?:(\*(?=[punctuation]))|\*)(?![*\s])|_/,middle:/^\*(?:(?:(?!overlapSkip)(?:[^*]|\\\*)|overlapSkip)|\*(?:(?!overlapSkip)(?:[^*]|\\\*)|overlapSkip)*?\*)+?\*$|^_(?![_\s])(?:(?:(?!overlapSkip)(?:[^_]|\\_)|overlapSkip)|_(?:(?!overlapSkip)(?:[^_]|\\_)|overlapSkip)*?_)+?_$/,endAst:/[^punctuation\s]\*(?!\*)|[punctuation]\*(?!\*)(?:(?=[punctuation_\s]|$))/,endUnd:/[^\s]_(?!_)(?:(?=[punctuation*\s])|$)/},code:/^(`+)([^`]|[^`][\s\S]*?[^`])\1(?!`)/,br:/^( {2,}|\\)\n(?!\s*$)/,del:q,text:/^(`+|[^`])(?:(?= {2,}\n)|[\s\S]*?(?:(?=[\\?@\\[\\]`^{|}~"};$.punctuation=F($.punctuation).replace(/punctuation/g,$._punctuation).getRegex(),$._blockSkip="\\[[^\\]]*?\\]\\([^\\)]*?\\)|`[^`]*?`|<[^>]*?>",$._overlapSkip="__[^_]*?__|\\*\\*\\[^\\*\\]*?\\*\\*",$._comment=F(U._comment).replace("(?:--\x3e|$)","--\x3e").getRegex(),$.em.start=F($.em.start).replace(/punctuation/g,$._punctuation).getRegex(),$.em.middle=F($.em.middle).replace(/punctuation/g,$._punctuation).replace(/overlapSkip/g,$._overlapSkip).getRegex(),$.em.endAst=F($.em.endAst,"g").replace(/punctuation/g,$._punctuation).getRegex(),$.em.endUnd=F($.em.endUnd,"g").replace(/punctuation/g,$._punctuation).getRegex(),$.strong.start=F($.strong.start).replace(/punctuation/g,$._punctuation).getRegex(),$.strong.middle=F($.strong.middle).replace(/punctuation/g,$._punctuation).replace(/overlapSkip/g,$._overlapSkip).getRegex(),$.strong.endAst=F($.strong.endAst,"g").replace(/punctuation/g,$._punctuation).getRegex(),$.strong.endUnd=F($.strong.endUnd,"g").replace(/punctuation/g,$._punctuation).getRegex(),$.blockSkip=F($._blockSkip,"g").getRegex(),$.overlapSkip=F($._overlapSkip,"g").getRegex(),$._escapes=/\\([!"#$%&'()*+,\-./:;<=>?@\[\]\\^_`{|}~])/g,$._scheme=/[a-zA-Z][a-zA-Z0-9+.-]{1,31}/,$._email=/[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+(@)[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)+(?![-_])/,$.autolink=F($.autolink).replace("scheme",$._scheme).replace("email",$._email).getRegex(),$._attribute=/\s+[a-zA-Z:_][\w.:-]*(?:\s*=\s*"[^"]*"|\s*=\s*'[^']*'|\s*=\s*[^\s"'=<>`]+)?/,$.tag=F($.tag).replace("comment",$._comment).replace("attribute",$._attribute).getRegex(),$._label=/(?:\[(?:\\.|[^\[\]\\])*\]|\\.|`[^`]*`|[^\[\]\\`])*?/,$._href=/<(?:\\.|[^\n<>\\])+>|[^\s\x00-\x1f]*/,$._title=/"(?:\\"?|[^"\\])*"|'(?:\\'?|[^'\\])*'|\((?:\\\)?|[^)\\])*\)/,$.link=F($.link).replace("label",$._label).replace("href",$._href).replace("title",$._title).getRegex(),$.reflink=F($.reflink).replace("label",$._label).getRegex(),$.reflinkSearch=F($.reflinkSearch,"g").replace("reflink",$.reflink).replace("nolink",$.nolink).getRegex(),$.normal=z({},$),$.pedantic=z({},$.normal,{strong:{start:/^__|\*\*/,middle:/^__(?=\S)([\s\S]*?\S)__(?!_)|^\*\*(?=\S)([\s\S]*?\S)\*\*(?!\*)/,endAst:/\*\*(?!\*)/g,endUnd:/__(?!_)/g},em:{start:/^_|\*/,middle:/^()\*(?=\S)([\s\S]*?\S)\*(?!\*)|^_(?=\S)([\s\S]*?\S)_(?!_)/,endAst:/\*(?!\*)/g,endUnd:/_(?!_)/g},link:F(/^!?\[(label)\]\((.*?)\)/).replace("label",$._label).getRegex(),reflink:F(/^!?\[(label)\]\s*\[([^\]]*)\]/).replace("label",$._label).getRegex()}),$.gfm=z({},$.normal,{escape:F($.escape).replace("])","~|])").getRegex(),_extended_email:/[A-Za-z0-9._+-]+(@)[a-zA-Z0-9-_]+(?:\.[a-zA-Z0-9-_]*[a-zA-Z0-9])+(?![-_])/,url:/^((?:ftp|https?):\/\/|www\.)(?:[a-zA-Z0-9\-]+\.?)+[^\s<]*|^email/,_backpedal:/(?:[^?!.,:;*_~()&]+|\([^)]*\)|&(?![a-zA-Z0-9]+;$)|[?!.,:;*_~)]+(?!$))+/,del:/^(~~?)(?=[^\s~])([\s\S]*?[^\s~])\1(?=[^~]|$)/,text:/^([`~]+|[^`~])(?:(?= {2,}\n)|[\s\S]*?(?:(?=[\\.5&&(n="x"+n.toString(16)),r+="&#"+n+";";return r}var Q=function(){function t(e){this.tokens=[],this.tokens.links=Object.create(null),this.options=e||W,this.options.tokenizer=this.options.tokenizer||new D,this.tokenizer=this.options.tokenizer,this.tokenizer.options=this.options;var t={block:V.normal,inline:Y.normal};this.options.pedantic?(t.block=V.pedantic,t.inline=Y.pedantic):this.options.gfm&&(t.block=V.gfm,this.options.breaks?t.inline=Y.breaks:t.inline=Y.gfm),this.tokenizer.rules=t}t.lex=function(e,n){return new t(n).lex(e)},t.lexInline=function(e,n){return new t(n).inlineTokens(e)};var n,r,o,i=t.prototype;return i.lex=function(e){return e=e.replace(/\r\n|\r/g,"\n").replace(/\t/g," "),this.blockTokens(e,this.tokens,!0),this.inline(this.tokens),this.tokens},i.blockTokens=function(e,t,n){var r,o,i,a;for(void 0===t&&(t=[]),void 0===n&&(n=!0),e=e.replace(/^ +$/gm,"");e;)if(r=this.tokenizer.space(e))e=e.substring(r.raw.length),r.type&&t.push(r);else if(r=this.tokenizer.code(e,t))e=e.substring(r.raw.length),r.type?t.push(r):((a=t[t.length-1]).raw+="\n"+r.raw,a.text+="\n"+r.text);else if(r=this.tokenizer.fences(e))e=e.substring(r.raw.length),t.push(r);else if(r=this.tokenizer.heading(e))e=e.substring(r.raw.length),t.push(r);else if(r=this.tokenizer.nptable(e))e=e.substring(r.raw.length),t.push(r);else if(r=this.tokenizer.hr(e))e=e.substring(r.raw.length),t.push(r);else if(r=this.tokenizer.blockquote(e))e=e.substring(r.raw.length),r.tokens=this.blockTokens(r.text,[],n),t.push(r);else if(r=this.tokenizer.list(e)){for(e=e.substring(r.raw.length),i=r.items.length,o=0;o0)for(;null!=(i=this.tokenizer.rules.inline.reflinkSearch.exec(c));)l.includes(i[0].slice(i[0].lastIndexOf("[")+1,-1))&&(c=c.slice(0,i.index)+"["+K("a",i[0].length-2)+"]"+c.slice(this.tokenizer.rules.inline.reflinkSearch.lastIndex))}for(;null!=(i=this.tokenizer.rules.inline.blockSkip.exec(c));)c=c.slice(0,i.index)+"["+K("a",i[0].length-2)+"]"+c.slice(this.tokenizer.rules.inline.blockSkip.lastIndex);for(;e;)if(a||(s=""),a=!1,o=this.tokenizer.escape(e))e=e.substring(o.raw.length),t.push(o);else if(o=this.tokenizer.tag(e,n,r))e=e.substring(o.raw.length),n=o.inLink,r=o.inRawBlock,t.push(o);else if(o=this.tokenizer.link(e))e=e.substring(o.raw.length),"link"===o.type&&(o.tokens=this.inlineTokens(o.text,[],!0,r)),t.push(o);else if(o=this.tokenizer.reflink(e,this.tokens.links))e=e.substring(o.raw.length),"link"===o.type&&(o.tokens=this.inlineTokens(o.text,[],!0,r)),t.push(o);else if(o=this.tokenizer.strong(e,c,s))e=e.substring(o.raw.length),o.tokens=this.inlineTokens(o.text,[],n,r),t.push(o);else if(o=this.tokenizer.em(e,c,s))e=e.substring(o.raw.length),o.tokens=this.inlineTokens(o.text,[],n,r),t.push(o);else if(o=this.tokenizer.codespan(e))e=e.substring(o.raw.length),t.push(o);else if(o=this.tokenizer.br(e))e=e.substring(o.raw.length),t.push(o);else if(o=this.tokenizer.del(e))e=e.substring(o.raw.length),o.tokens=this.inlineTokens(o.text,[],n,r),t.push(o);else if(o=this.tokenizer.autolink(e,J))e=e.substring(o.raw.length),t.push(o);else if(n||!(o=this.tokenizer.url(e,J))){if(o=this.tokenizer.inlineText(e,r,G))e=e.substring(o.raw.length),s=o.raw.slice(-1),a=!0,t.push(o);else if(e){var u="Infinite loop on byte: "+e.charCodeAt(0);if(this.options.silent){console.error(u);break}throw new Error(u)}}else e=e.substring(o.raw.length),t.push(o);return t},n=t,o=[{key:"rules",get:function(){return{block:V,inline:Y}}}],(r=null)&&e(n.prototype,r),o&&e(n,o),t}(),X=r.defaults,Z=O,ee=w,te=function(){function e(e){this.options=e||X}var t=e.prototype;return t.code=function(e,t,n){var r=(t||"").match(/\S*/)[0];if(this.options.highlight){var o=this.options.highlight(e,r);null!=o&&o!==e&&(n=!0,e=o)}return r?'
'+(n?e:ee(e,!0))+"
\n":"
"+(n?e:ee(e,!0))+"
\n"},t.blockquote=function(e){return"
\n"+e+"
\n"},t.html=function(e){return e},t.heading=function(e,t,n,r){return this.options.headerIds?"'+e+"\n":""+e+"\n"},t.hr=function(){return this.options.xhtml?"
\n":"
\n"},t.list=function(e,t,n){var r=t?"ol":"ul";return"<"+r+(t&&1!==n?' start="'+n+'"':"")+">\n"+e+"\n"},t.listitem=function(e){return"
  • "+e+"
  • \n"},t.checkbox=function(e){return" "},t.paragraph=function(e){return"

    "+e+"

    \n"},t.table=function(e,t){return t&&(t=""+t+""),"\n\n"+e+"\n"+t+"
    \n"},t.tablerow=function(e){return"\n"+e+"\n"},t.tablecell=function(e,t){var n=t.header?"th":"td";return(t.align?"<"+n+' align="'+t.align+'">':"<"+n+">")+e+"\n"},t.strong=function(e){return""+e+""},t.em=function(e){return""+e+""},t.codespan=function(e){return""+e+""},t.br=function(){return this.options.xhtml?"
    ":"
    "},t.del=function(e){return""+e+""},t.link=function(e,t,n){if(null===(e=Z(this.options.sanitize,this.options.baseUrl,e)))return n;var r='"},t.image=function(e,t,n){if(null===(e=Z(this.options.sanitize,this.options.baseUrl,e)))return n;var r=''+n+'":">"},t.text=function(e){return e},e}(),ne=function(){function e(){}var t=e.prototype;return t.strong=function(e){return e},t.em=function(e){return e},t.codespan=function(e){return e},t.del=function(e){return e},t.html=function(e){return e},t.text=function(e){return e},t.link=function(e,t,n){return""+n},t.image=function(e,t,n){return""+n},t.br=function(){return""},e}(),re=function(){function e(){this.seen={}}var t=e.prototype;return t.serialize=function(e){return e.toLowerCase().trim().replace(/<[!\/a-z].*?>/gi,"").replace(/[\u2000-\u206F\u2E00-\u2E7F\\'!"#$%&()*+,./:;<=>?@[\]^`{|}~]/g,"").replace(/\s/g,"-")},t.getNextSafeSlug=function(e,t){var n=e,r=0;if(this.seen.hasOwnProperty(n)){r=this.seen[e];do{n=e+"-"+ ++r}while(this.seen.hasOwnProperty(n))}return t||(this.seen[e]=r,this.seen[n]=0),n},t.slug=function(e,t){void 0===t&&(t={});var n=this.serialize(e);return this.getNextSafeSlug(n,t.dryrun)},e}(),oe=r.defaults,ie=k,ae=function(){function e(e){this.options=e||oe,this.options.renderer=this.options.renderer||new te,this.renderer=this.options.renderer,this.renderer.options=this.options,this.textRenderer=new ne,this.slugger=new re}e.parse=function(t,n){return new e(n).parse(t)},e.parseInline=function(t,n){return new e(n).parseInline(t)};var t=e.prototype;return t.parse=function(e,t){void 0===t&&(t=!0);var n,r,o,i,a,s,c,l,u,p,f,d,h,v,m,g,y,b,x="",w=e.length;for(n=0;n0&&"text"===m.tokens[0].type?(m.tokens[0].text=b+" "+m.tokens[0].text,m.tokens[0].tokens&&m.tokens[0].tokens.length>0&&"text"===m.tokens[0].tokens[0].type&&(m.tokens[0].tokens[0].text=b+" "+m.tokens[0].tokens[0].text)):m.tokens.unshift({type:"text",text:b}):v+=b),v+=this.parse(m.tokens,h),u+=this.renderer.listitem(v,y,g);x+=this.renderer.list(u,f,d);continue;case"html":x+=this.renderer.html(p.text);continue;case"paragraph":x+=this.renderer.paragraph(this.parseInline(p.tokens));continue;case"text":for(u=p.tokens?this.parseInline(p.tokens):p.text;n+1An error occurred:

    "+le(e.message+"",!0)+"
    ";throw e}}return de.options=de.setOptions=function(e){return se(de.defaults,e),pe(de.defaults),de},de.getDefaults=ue,de.defaults=fe,de.use=function(e){var t=se({},e);if(e.renderer&&function(){var n=de.defaults.renderer||new te,r=function(t){var r=n[t];n[t]=function(){for(var o=arguments.length,i=new Array(o),a=0;aAn error occurred:

    "+le(e.message+"",!0)+"
    ";throw e}},de.Parser=ae,de.parser=ae.parse,de.Renderer=te,de.TextRenderer=ne,de.Lexer=Q,de.lexer=Q.lex,de.Tokenizer=D,de.Slugger=re,de.parse=de,de}()},function(e,t,n){e.exports=n(444)},function(e,t,n){e.exports=n(428)},function(e,t,n){e.exports=n(429)},function(e,t,n){"use strict";var r=n(16),o=n(69).f,i=n(193),a=n(12),s=n(85),c=n(37),l=n(29),u=function(e){var t=function(t,n,r){if(this instanceof e){switch(arguments.length){case 0:return new e;case 1:return new e(t);case 2:return new e(t,n)}return new e(t,n,r)}return e.apply(this,arguments)};return t.prototype=e.prototype,t};e.exports=function(e,t){var n,p,f,d,h,v,m,g,y=e.target,b=e.global,x=e.stat,w=e.proto,k=b?r:x?r[y]:(r[y]||{}).prototype,S=b?a:a[y]||(a[y]={}),O=S.prototype;for(f in t)n=!i(b?f:y+(x?".":"#")+f,e.forced)&&k&&l(k,f),h=S[f],n&&(v=e.noTargetGet?(g=o(k,f))&&g.value:k[f]),d=n&&v?v:t[f],n&&typeof h==typeof d||(m=e.bind&&n?s(d,r):e.wrap&&n?u(d):w&&"function"==typeof d?s(Function.call,d):d,(e.sham||d&&d.sham||h&&h.sham)&&c(m,"sham",!0),S[f]=m,w&&(l(a,p=y+"Prototype")||c(a,p,{}),a[p][f]=d,e.real&&O&&!O[f]&&c(O,f,d)))}},function(e,t,n){var r=n(136),o=n(325),i=n(329),a=n(334),s=n(206),c=n(348),l=n(209),u=n(211),p=n(172);function f(e,t){var n=u(e);if(l){var r=l(e);t&&(r=c(r).call(r,(function(t){return s(e,t).enumerable}))),n.push.apply(n,r)}return n}e.exports=function(e){for(var t=1;t1){throw new o.n(t,"Each pair must have its own sequence indicator")}var s=a.items[0]||new i.e;a.commentBefore&&(s.commentBefore=s.commentBefore?"".concat(a.commentBefore,"\n").concat(s.commentBefore):a.commentBefore),a.comment&&(s.comment=s.comment?"".concat(a.comment,"\n").concat(s.comment):a.comment),a=s}n.items[r]=a instanceof i.e?a:new i.e(a)}}return n}function c(e,t,n){var r=new i.g(e);r.tag="tag:yaml.org,2002:pairs";var a,s=Object(o.g)(t);try{for(s.s();!(a=s.n()).done;){var c=a.value,l=void 0,u=void 0;if(Array.isArray(c)){if(2!==c.length)throw new TypeError("Expected [key, value] tuple: ".concat(c));l=c[0],u=c[1]}else if(c&&c instanceof Object){var p=Object.keys(c);if(1!==p.length)throw new TypeError("Expected { key: value } tuple: ".concat(c));u=c[l=p[0]]}else l=c;var f=e.createPair(l,u,n);r.items.push(f)}}catch(e){s.e(e)}finally{s.f()}return r}var l={default:!1,tag:"tag:yaml.org,2002:pairs",resolve:s,createNode:c},u=function(e){Object(o.q)(n,e);var t=Object(o.r)(n);function n(){var e;return Object(o.j)(this,n),e=t.call(this),Object(o.l)(Object(o.w)(e),"add",i.k.prototype.add.bind(Object(o.w)(e))),Object(o.l)(Object(o.w)(e),"delete",i.k.prototype.delete.bind(Object(o.w)(e))),Object(o.l)(Object(o.w)(e),"get",i.k.prototype.get.bind(Object(o.w)(e))),Object(o.l)(Object(o.w)(e),"has",i.k.prototype.has.bind(Object(o.w)(e))),Object(o.l)(Object(o.w)(e),"set",i.k.prototype.set.bind(Object(o.w)(e))),e.tag=n.tag,e}return Object(o.i)(n,[{key:"toJSON",value:function(e,t){var n=new Map;t&&t.onCreate&&t.onCreate(n);var r,a=Object(o.g)(this.items);try{for(a.s();!(r=a.n()).done;){var s=r.value,c=void 0,l=void 0;if(s instanceof i.e?(c=Object(i.w)(s.key,"",t),l=Object(i.w)(s.value,c,t)):c=Object(i.w)(s,"",t),n.has(c))throw new Error("Ordered maps must not include duplicate keys");n.set(c,l)}}catch(e){a.e(e)}finally{a.f()}return n}}]),n}(i.g);Object(o.l)(u,"tag","tag:yaml.org,2002:omap");var p={identify:function(e){return e instanceof Map},nodeClass:u,default:!1,tag:"tag:yaml.org,2002:omap",resolve:function(e,t){var n,r=s(e,t),a=[],c=Object(o.g)(r.items);try{for(c.s();!(n=c.n()).done;){var l=n.value.key;if(l instanceof i.f){if(a.includes(l.value)){throw new o.n(t,"Ordered maps must not include duplicate keys")}a.push(l.value)}}}catch(e){c.e(e)}finally{c.f()}return Object.assign(new u,r)},createNode:function(e,t,n){var r=c(e,t,n),o=new u;return o.items=r.items,o}},f=function(e){Object(o.q)(n,e);var t=Object(o.r)(n);function n(){var e;return Object(o.j)(this,n),(e=t.call(this)).tag=n.tag,e}return Object(o.i)(n,[{key:"add",value:function(e){var t=e instanceof i.e?e:new i.e(e);Object(i.s)(this.items,t.key)||this.items.push(t)}},{key:"get",value:function(e,t){var n=Object(i.s)(this.items,e);return!t&&n instanceof i.e?n.key instanceof i.f?n.key.value:n.key:n}},{key:"set",value:function(e,t){if("boolean"!=typeof t)throw new Error("Expected boolean value for set(key, value) in a YAML set, not ".concat(Object(o.h)(t)));var n=Object(i.s)(this.items,e);n&&!t?this.items.splice(this.items.indexOf(n),1):!n&&t&&this.items.push(new i.e(e))}},{key:"toJSON",value:function(e,t){return Object(o.s)(Object(o.t)(n.prototype),"toJSON",this).call(this,e,t,Set)}},{key:"toString",value:function(e,t,r){if(!e)return JSON.stringify(this);if(this.hasAllNullValues())return Object(o.s)(Object(o.t)(n.prototype),"toString",this).call(this,e,t,r);throw new Error("Set items must all have null values")}}]),n}(i.k);Object(o.l)(f,"tag","tag:yaml.org,2002:set");var d={identify:function(e){return e instanceof Set},nodeClass:f,default:!1,tag:"tag:yaml.org,2002:set",resolve:function(e,t){var n=Object(i.n)(e,t);if(!n.hasAllNullValues())throw new o.n(t,"Set items must all have null values");return Object.assign(new f,n)},createNode:function(e,t,n){var r,i=new f,a=Object(o.g)(t);try{for(a.s();!(r=a.n()).done;){var s=r.value;i.items.push(e.createPair(s,null,n))}}catch(e){a.e(e)}finally{a.f()}return i}},h=function(e,t){var n=t.split(":").reduce((function(e,t){return 60*e+Number(t)}),0);return"-"===e?-n:n},v=function(e){var t=e.value;if(isNaN(t)||!isFinite(t))return Object(i.r)(t);var n="";t<0&&(n="-",t=Math.abs(t));var r=[t%60];return t<60?r.unshift(0):(t=Math.round((t-r[0])/60),r.unshift(t%60),t>=60&&(t=Math.round((t-r[0])/60),r.unshift(t))),n+r.map((function(e){return e<10?"0"+String(e):String(e)})).join(":").replace(/000000\d*$/,"")},m={identify:function(e){return"number"==typeof e},default:!0,tag:"tag:yaml.org,2002:int",format:"TIME",test:/^([-+]?)([0-9][0-9_]*(?::[0-5]?[0-9])+)$/,resolve:function(e,t,n){return h(t,n.replace(/_/g,""))},stringify:v},g={identify:function(e){return"number"==typeof e},default:!0,tag:"tag:yaml.org,2002:float",format:"TIME",test:/^([-+]?)([0-9][0-9_]*(?::[0-5]?[0-9])+\.[0-9_]*)$/,resolve:function(e,t,n){return h(t,n.replace(/_/g,""))},stringify:v},y={identify:function(e){return e instanceof Date},default:!0,tag:"tag:yaml.org,2002:timestamp",test:RegExp("^(?:([0-9]{4})-([0-9]{1,2})-([0-9]{1,2})(?:(?:t|T|[ \\t]+)([0-9]{1,2}):([0-9]{1,2}):([0-9]{1,2}(\\.[0-9]+)?)(?:[ \\t]*(Z|[-+][012]?[0-9](?::[0-9]{2})?))?)?)$"),resolve:function(e,t,n,r,o,i,a,s,c){s&&(s=(s+"00").substr(1,3));var l=Date.UTC(t,n-1,r,o||0,i||0,a||0,s||0);if(c&&"Z"!==c){var u=h(c[0],c.slice(1));Math.abs(u)<30&&(u*=60),l-=6e4*u}return new Date(l)},stringify:function(e){return e.value.toISOString().replace(/((T00:00)?:00)?\.000Z$/,"")}};function b(e){var t=void 0!==r&&r.env||{};return e?"undefined"!=typeof YAML_SILENCE_DEPRECATION_WARNINGS?!YAML_SILENCE_DEPRECATION_WARNINGS:!t.YAML_SILENCE_DEPRECATION_WARNINGS:"undefined"!=typeof YAML_SILENCE_WARNINGS?!YAML_SILENCE_WARNINGS:!t.YAML_SILENCE_WARNINGS}function x(e,t){if(b(!1)){var n=void 0!==r&&r.emitWarning;n?n(e,t):console.warn(t?"".concat(t,": ").concat(e):e)}}var w={};function k(e,t){if(!w[e]&&b(!0)){w[e]=!0;var n="The option '".concat(e,"' will be removed in a future release");x(n+=t?", use '".concat(t,"' instead."):".","DeprecationWarning")}}}).call(this,n(32).Buffer,n(41))},function(e,t,n){var r=n(13);e.exports=!r((function(){return 7!=Object.defineProperty({},1,{get:function(){return 7}})[1]}))},function(e,t,n){e.exports=n(562)},function(e,t,n){e.exports=n(440)},function(e,t,n){e.exports=n(572)},function(e,t){var n;n=function(){return this}();try{n=n||new Function("return this")()}catch(e){"object"==typeof window&&(n=window)}e.exports=n},function(e,t,n){var r=n(12);e.exports=function(e){return r[e+"Prototype"]}},function(e,t,n){(function(t){var n=function(e){var t=/\blang(?:uage)?-([\w-]+)\b/i,n=0,r={manual:e.Prism&&e.Prism.manual,disableWorkerMessageHandler:e.Prism&&e.Prism.disableWorkerMessageHandler,util:{encode:function e(t){return t instanceof o?new o(t.type,e(t.content),t.alias):Array.isArray(t)?t.map(e):t.replace(/&/g,"&").replace(/=p.reach);S+=k.value.length,k=k.next){var O=k.value;if(n.length>t.length)return;if(!(O instanceof o)){var A,E=1;if(y){if(!(A=i(w,S,t,g)))break;var _=A.index,j=A.index+A[0].length,T=S;for(T+=k.value.length;_>=T;)k=k.next,T+=k.value.length;if(T-=k.value.length,S=T,k.value instanceof o)continue;for(var C=k;C!==n.tail&&(Tp.reach&&(p.reach=L);var N=k.prev;P&&(N=s(n,N,P),S+=P.length),c(n,N,E);var M=new o(f,m?r.tokenize(I,m):I,b,I);k=s(n,N,M),R&&s(n,k,R),E>1&&e(t,n,a,k.prev,S,{cause:f+","+h,reach:L})}}}}}(e,u,t,u.head,0),function(e){var t=[],n=e.head.next;for(;n!==e.tail;)t.push(n.value),n=n.next;return t}(u)},hooks:{all:{},add:function(e,t){var n=r.hooks.all;n[e]=n[e]||[],n[e].push(t)},run:function(e,t){var n=r.hooks.all[e];if(n&&n.length)for(var o,i=0;o=n[i++];)o(t)}},Token:o};function o(e,t,n,r){this.type=e,this.content=t,this.alias=n,this.length=0|(r||"").length}function i(e,t,n,r){e.lastIndex=t;var o=e.exec(n);if(o&&r&&o[1]){var i=o[1].length;o.index+=i,o[0]=o[0].slice(i)}return o}function a(){var e={value:null,prev:null,next:null},t={value:null,prev:e,next:null};e.next=t,this.head=e,this.tail=t,this.length=0}function s(e,t,n){var r=t.next,o={value:n,prev:t,next:r};return t.next=o,r.prev=o,e.length++,o}function c(e,t,n){for(var r=t.next,o=0;o"+i.content+""},!e.document)return e.addEventListener?(r.disableWorkerMessageHandler||e.addEventListener("message",(function(t){var n=JSON.parse(t.data),o=n.language,i=n.code,a=n.immediateClose;e.postMessage(r.highlight(i,r.languages[o],o)),a&&e.close()}),!1),r):r;var l=r.util.currentScript();function u(){r.manual||r.highlightAll()}if(l&&(r.filename=l.src,l.hasAttribute("data-manual")&&(r.manual=!0)),!r.manual){var p=document.readyState;"loading"===p||"interactive"===p&&l&&l.defer?document.addEventListener("DOMContentLoaded",u):window.requestAnimationFrame?window.requestAnimationFrame(u):window.setTimeout(u,16)}return r}("undefined"!=typeof window?window:"undefined"!=typeof WorkerGlobalScope&&self instanceof WorkerGlobalScope?self:{}); +/** + * Prism: Lightweight, robust, elegant syntax highlighting + * + * @license MIT + * @author Lea Verou + * @namespace + * @public + */e.exports&&(e.exports=n),void 0!==t&&(t.Prism=n),n.languages.markup={comment://,prolog:/<\?[\s\S]+?\?>/,doctype:{pattern:/"'[\]]|"[^"]*"|'[^']*')+(?:\[(?:[^<"'\]]|"[^"]*"|'[^']*'|<(?!!--)|)*\]\s*)?>/i,greedy:!0,inside:{"internal-subset":{pattern:/(\[)[\s\S]+(?=\]>$)/,lookbehind:!0,greedy:!0,inside:null},string:{pattern:/"[^"]*"|'[^']*'/,greedy:!0},punctuation:/^$|[[\]]/,"doctype-tag":/^DOCTYPE/,name:/[^\s<>'"]+/}},cdata://i,tag:{pattern:/<\/?(?!\d)[^\s>\/=$<%]+(?:\s(?:\s*[^\s>\/=]+(?:\s*=\s*(?:"[^"]*"|'[^']*'|[^\s'">=]+(?=[\s>]))|(?=[\s/>])))+)?\s*\/?>/,greedy:!0,inside:{tag:{pattern:/^<\/?[^\s>\/]+/,inside:{punctuation:/^<\/?/,namespace:/^[^\s>\/:]+:/}},"attr-value":{pattern:/=\s*(?:"[^"]*"|'[^']*'|[^\s'">=]+)/,inside:{punctuation:[{pattern:/^=/,alias:"attr-equals"},/"|'/]}},punctuation:/\/?>/,"attr-name":{pattern:/[^\s>\/]+/,inside:{namespace:/^[^\s>\/:]+:/}}}},entity:[{pattern:/&[\da-z]{1,8};/i,alias:"named-entity"},/&#x?[\da-f]{1,8};/i]},n.languages.markup.tag.inside["attr-value"].inside.entity=n.languages.markup.entity,n.languages.markup.doctype.inside["internal-subset"].inside=n.languages.markup,n.hooks.add("wrap",(function(e){"entity"===e.type&&(e.attributes.title=e.content.replace(/&/,"&"))})),Object.defineProperty(n.languages.markup.tag,"addInlined",{value:function(e,t){var r={};r["language-"+t]={pattern:/(^$)/i,lookbehind:!0,inside:n.languages[t]},r.cdata=/^$/i;var o={"included-cdata":{pattern://i,inside:r}};o["language-"+t]={pattern:/[\s\S]+/,inside:n.languages[t]};var i={};i[e]={pattern:RegExp(/(<__[^>]*>)(?:))*\]\]>|(?!)/.source.replace(/__/g,(function(){return e})),"i"),lookbehind:!0,greedy:!0,inside:o},n.languages.insertBefore("markup","cdata",i)}}),n.languages.html=n.languages.markup,n.languages.mathml=n.languages.markup,n.languages.svg=n.languages.markup,n.languages.xml=n.languages.extend("markup",{}),n.languages.ssml=n.languages.xml,n.languages.atom=n.languages.xml,n.languages.rss=n.languages.xml,function(e){var t=/("|')(?:\\(?:\r\n|[\s\S])|(?!\1)[^\\\r\n])*\1/;e.languages.css={comment:/\/\*[\s\S]*?\*\//,atrule:{pattern:/@[\w-](?:[^;{\s]|\s+(?![\s{]))*(?:;|(?=\s*\{))/,inside:{rule:/^@[\w-]+/,"selector-function-argument":{pattern:/(\bselector\s*\(\s*(?![\s)]))(?:[^()\s]|\s+(?![\s)])|\((?:[^()]|\([^()]*\))*\))+(?=\s*\))/,lookbehind:!0,alias:"selector"},keyword:{pattern:/(^|[^\w-])(?:and|not|only|or)(?![\w-])/,lookbehind:!0}}},url:{pattern:RegExp("\\burl\\((?:"+t.source+"|"+/(?:[^\\\r\n()"']|\\[\s\S])*/.source+")\\)","i"),greedy:!0,inside:{function:/^url/i,punctuation:/^\(|\)$/,string:{pattern:RegExp("^"+t.source+"$"),alias:"url"}}},selector:RegExp("[^{}\\s](?:[^{};\"'\\s]|\\s+(?![\\s{])|"+t.source+")*(?=\\s*\\{)"),string:{pattern:t,greedy:!0},property:/(?!\s)[-_a-z\xA0-\uFFFF](?:(?!\s)[-\w\xA0-\uFFFF])*(?=\s*:)/i,important:/!important\b/i,function:/[-a-z0-9]+(?=\()/i,punctuation:/[(){};:,]/},e.languages.css.atrule.inside.rest=e.languages.css;var n=e.languages.markup;n&&(n.tag.addInlined("style","css"),e.languages.insertBefore("inside","attr-value",{"style-attr":{pattern:/(^|["'\s])style\s*=\s*(?:"[^"]*"|'[^']*')/i,lookbehind:!0,inside:{"attr-value":{pattern:/=\s*(?:"[^"]*"|'[^']*'|[^\s'">=]+)/,inside:{style:{pattern:/(["'])[\s\S]+(?=["']$)/,lookbehind:!0,alias:"language-css",inside:e.languages.css},punctuation:[{pattern:/^=/,alias:"attr-equals"},/"|'/]}},"attr-name":/^style/i}}},n.tag))}(n),n.languages.clike={comment:[{pattern:/(^|[^\\])\/\*[\s\S]*?(?:\*\/|$)/,lookbehind:!0,greedy:!0},{pattern:/(^|[^\\:])\/\/.*/,lookbehind:!0,greedy:!0}],string:{pattern:/(["'])(?:\\(?:\r\n|[\s\S])|(?!\1)[^\\\r\n])*\1/,greedy:!0},"class-name":{pattern:/(\b(?:class|interface|extends|implements|trait|instanceof|new)\s+|\bcatch\s+\()[\w.\\]+/i,lookbehind:!0,inside:{punctuation:/[.\\]/}},keyword:/\b(?:if|else|while|do|for|return|in|instanceof|function|new|try|throw|catch|finally|null|break|continue)\b/,boolean:/\b(?:true|false)\b/,function:/\w+(?=\()/,number:/\b0x[\da-f]+\b|(?:\b\d+(?:\.\d*)?|\B\.\d+)(?:e[+-]?\d+)?/i,operator:/[<>]=?|[!=]=?=?|--?|\+\+?|&&?|\|\|?|[?*/~^%]/,punctuation:/[{}[\];(),.:]/},n.languages.javascript=n.languages.extend("clike",{"class-name":[n.languages.clike["class-name"],{pattern:/(^|[^$\w\xA0-\uFFFF])(?!\s)[_$A-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*(?=\.(?:prototype|constructor))/,lookbehind:!0}],keyword:[{pattern:/((?:^|})\s*)(?:catch|finally)\b/,lookbehind:!0},{pattern:/(^|[^.]|\.\.\.\s*)\b(?:as|async(?=\s*(?:function\b|\(|[$\w\xA0-\uFFFF]|$))|await|break|case|class|const|continue|debugger|default|delete|do|else|enum|export|extends|for|from|function|(?:get|set)(?=\s*[\[$\w\xA0-\uFFFF])|if|implements|import|in|instanceof|interface|let|new|null|of|package|private|protected|public|return|static|super|switch|this|throw|try|typeof|undefined|var|void|while|with|yield)\b/,lookbehind:!0}],function:/#?(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*(?=\s*(?:\.\s*(?:apply|bind|call)\s*)?\()/,number:/\b(?:(?:0[xX](?:[\dA-Fa-f](?:_[\dA-Fa-f])?)+|0[bB](?:[01](?:_[01])?)+|0[oO](?:[0-7](?:_[0-7])?)+)n?|(?:\d(?:_\d)?)+n|NaN|Infinity)\b|(?:\b(?:\d(?:_\d)?)+\.?(?:\d(?:_\d)?)*|\B\.(?:\d(?:_\d)?)+)(?:[Ee][+-]?(?:\d(?:_\d)?)+)?/,operator:/--|\+\+|\*\*=?|=>|&&=?|\|\|=?|[!=]==|<<=?|>>>?=?|[-+*/%&|^!=<>]=?|\.{3}|\?\?=?|\?\.?|[~:]/}),n.languages.javascript["class-name"][0].pattern=/(\b(?:class|interface|extends|implements|instanceof|new)\s+)[\w.\\]+/,n.languages.insertBefore("javascript","keyword",{regex:{pattern:/((?:^|[^$\w\xA0-\uFFFF."'\])\s]|\b(?:return|yield))\s*)\/(?:\[(?:[^\]\\\r\n]|\\.)*]|\\.|[^/\\\[\r\n])+\/[gimyus]{0,6}(?=(?:\s|\/\*(?:[^*]|\*(?!\/))*\*\/)*(?:$|[\r\n,.;:})\]]|\/\/))/,lookbehind:!0,greedy:!0,inside:{"regex-source":{pattern:/^(\/)[\s\S]+(?=\/[a-z]*$)/,lookbehind:!0,alias:"language-regex",inside:n.languages.regex},"regex-flags":/[a-z]+$/,"regex-delimiter":/^\/|\/$/}},"function-variable":{pattern:/#?(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*(?=\s*[=:]\s*(?:async\s*)?(?:\bfunction\b|(?:\((?:[^()]|\([^()]*\))*\)|(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*)\s*=>))/,alias:"function"},parameter:[{pattern:/(function(?:\s+(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*)?\s*\(\s*)(?!\s)(?:[^()\s]|\s+(?![\s)])|\([^()]*\))+(?=\s*\))/,lookbehind:!0,inside:n.languages.javascript},{pattern:/(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*(?=\s*=>)/i,inside:n.languages.javascript},{pattern:/(\(\s*)(?!\s)(?:[^()\s]|\s+(?![\s)])|\([^()]*\))+(?=\s*\)\s*=>)/,lookbehind:!0,inside:n.languages.javascript},{pattern:/((?:\b|\s|^)(?!(?:as|async|await|break|case|catch|class|const|continue|debugger|default|delete|do|else|enum|export|extends|finally|for|from|function|get|if|implements|import|in|instanceof|interface|let|new|null|of|package|private|protected|public|return|set|static|super|switch|this|throw|try|typeof|undefined|var|void|while|with|yield)(?![$\w\xA0-\uFFFF]))(?:(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*\s*)\(\s*|\]\s*\(\s*)(?!\s)(?:[^()\s]|\s+(?![\s)])|\([^()]*\))+(?=\s*\)\s*\{)/,lookbehind:!0,inside:n.languages.javascript}],constant:/\b[A-Z](?:[A-Z_]|\dx?)*\b/}),n.languages.insertBefore("javascript","string",{"template-string":{pattern:/`(?:\\[\s\S]|\${(?:[^{}]|{(?:[^{}]|{[^}]*})*})+}|(?!\${)[^\\`])*`/,greedy:!0,inside:{"template-punctuation":{pattern:/^`|`$/,alias:"string"},interpolation:{pattern:/((?:^|[^\\])(?:\\{2})*)\${(?:[^{}]|{(?:[^{}]|{[^}]*})*})+}/,lookbehind:!0,inside:{"interpolation-punctuation":{pattern:/^\${|}$/,alias:"punctuation"},rest:n.languages.javascript}},string:/[\s\S]+/}}}),n.languages.markup&&n.languages.markup.tag.addInlined("script","javascript"),n.languages.js=n.languages.javascript,function(){if("undefined"!=typeof self&&self.Prism&&self.document){Element.prototype.matches||(Element.prototype.matches=Element.prototype.msMatchesSelector||Element.prototype.webkitMatchesSelector);var e=window.Prism,t={js:"javascript",py:"python",rb:"ruby",ps1:"powershell",psm1:"powershell",sh:"bash",bat:"batch",h:"c",tex:"latex"},n='pre[data-src]:not([data-src-status="loaded"]):not([data-src-status="loading"])',r=/\blang(?:uage)?-([\w-]+)\b/i;e.hooks.add("before-highlightall",(function(e){e.selector+=", "+n})),e.hooks.add("before-sanity-check",(function(r){var o=r.element;if(o.matches(n)){r.code="",o.setAttribute("data-src-status","loading");var a=o.appendChild(document.createElement("CODE"));a.textContent="Loading…";var s=o.getAttribute("data-src"),c=r.language;if("none"===c){var l=(/\.(\w+)$/.exec(s)||[,"none"])[1];c=t[l]||l}i(a,c),i(o,c);var u=e.plugins.autoloader;u&&u.loadLanguages(c);var p=new XMLHttpRequest;p.open("GET",s,!0),p.onreadystatechange=function(){var t,n;4==p.readyState&&(p.status<400&&p.responseText?(o.setAttribute("data-src-status","loaded"),a.textContent=p.responseText,e.highlightElement(a)):(o.setAttribute("data-src-status","failed"),p.status>=400?a.textContent=(t=p.status,n=p.statusText,"✖ Error "+t+" while fetching file: "+n):a.textContent="✖ Error: File does not exist or is empty"))},p.send(null)}})),e.plugins.fileHighlight={highlight:function(t){for(var r,o=(t||document).querySelectorAll(n),i=0;r=o[i++];)e.highlightElement(r)}};var o=!1;e.fileHighlight=function(){o||(console.warn("Prism.fileHighlight is deprecated. Use `Prism.plugins.fileHighlight.highlight` instead."),o=!0),e.plugins.fileHighlight.highlight.apply(this,arguments)}}function i(e,t){var n=e.className;n=n.replace(r," ")+" language-"+t,e.className=n.replace(/\s+/g," ").trim()}}()}).call(this,n(26))},function(e,t){var n={}.hasOwnProperty;e.exports=function(e,t){return n.call(e,t)}},function(e,t,n){var r=n(17);e.exports=function(e){if(!r(e))throw TypeError(String(e)+" is not an object");return e}},function(e,t){var n=Array.isArray;e.exports=n},function(e,t,n){"use strict";(function(e){ +/*! + * The buffer module from node.js, for the browser. + * + * @author Feross Aboukhadijeh + * @license MIT + */ +var r=n(302),o=n(303),i=n(180);function a(){return c.TYPED_ARRAY_SUPPORT?2147483647:1073741823}function s(e,t){if(a()=a())throw new RangeError("Attempt to allocate Buffer larger than maximum size: 0x"+a().toString(16)+" bytes");return 0|e}function h(e,t){if(c.isBuffer(e))return e.length;if("undefined"!=typeof ArrayBuffer&&"function"==typeof ArrayBuffer.isView&&(ArrayBuffer.isView(e)||e instanceof ArrayBuffer))return e.byteLength;"string"!=typeof e&&(e=""+e);var n=e.length;if(0===n)return 0;for(var r=!1;;)switch(t){case"ascii":case"latin1":case"binary":return n;case"utf8":case"utf-8":case void 0:return F(e).length;case"ucs2":case"ucs-2":case"utf16le":case"utf-16le":return 2*n;case"hex":return n>>>1;case"base64":return z(e).length;default:if(r)return F(e).length;t=(""+t).toLowerCase(),r=!0}}function v(e,t,n){var r=!1;if((void 0===t||t<0)&&(t=0),t>this.length)return"";if((void 0===n||n>this.length)&&(n=this.length),n<=0)return"";if((n>>>=0)<=(t>>>=0))return"";for(e||(e="utf8");;)switch(e){case"hex":return T(this,t,n);case"utf8":case"utf-8":return E(this,t,n);case"ascii":return _(this,t,n);case"latin1":case"binary":return j(this,t,n);case"base64":return A(this,t,n);case"ucs2":case"ucs-2":case"utf16le":case"utf-16le":return C(this,t,n);default:if(r)throw new TypeError("Unknown encoding: "+e);e=(e+"").toLowerCase(),r=!0}}function m(e,t,n){var r=e[t];e[t]=e[n],e[n]=r}function g(e,t,n,r,o){if(0===e.length)return-1;if("string"==typeof n?(r=n,n=0):n>2147483647?n=2147483647:n<-2147483648&&(n=-2147483648),n=+n,isNaN(n)&&(n=o?0:e.length-1),n<0&&(n=e.length+n),n>=e.length){if(o)return-1;n=e.length-1}else if(n<0){if(!o)return-1;n=0}if("string"==typeof t&&(t=c.from(t,r)),c.isBuffer(t))return 0===t.length?-1:y(e,t,n,r,o);if("number"==typeof t)return t&=255,c.TYPED_ARRAY_SUPPORT&&"function"==typeof Uint8Array.prototype.indexOf?o?Uint8Array.prototype.indexOf.call(e,t,n):Uint8Array.prototype.lastIndexOf.call(e,t,n):y(e,[t],n,r,o);throw new TypeError("val must be string, number or Buffer")}function y(e,t,n,r,o){var i,a=1,s=e.length,c=t.length;if(void 0!==r&&("ucs2"===(r=String(r).toLowerCase())||"ucs-2"===r||"utf16le"===r||"utf-16le"===r)){if(e.length<2||t.length<2)return-1;a=2,s/=2,c/=2,n/=2}function l(e,t){return 1===a?e[t]:e.readUInt16BE(t*a)}if(o){var u=-1;for(i=n;is&&(n=s-c),i=n;i>=0;i--){for(var p=!0,f=0;fo&&(r=o):r=o;var i=t.length;if(i%2!=0)throw new TypeError("Invalid hex string");r>i/2&&(r=i/2);for(var a=0;a>8,o=n%256,i.push(o),i.push(r);return i}(t,e.length-n),e,n,r)}function A(e,t,n){return 0===t&&n===e.length?r.fromByteArray(e):r.fromByteArray(e.slice(t,n))}function E(e,t,n){n=Math.min(e.length,n);for(var r=[],o=t;o239?4:l>223?3:l>191?2:1;if(o+p<=n)switch(p){case 1:l<128&&(u=l);break;case 2:128==(192&(i=e[o+1]))&&(c=(31&l)<<6|63&i)>127&&(u=c);break;case 3:i=e[o+1],a=e[o+2],128==(192&i)&&128==(192&a)&&(c=(15&l)<<12|(63&i)<<6|63&a)>2047&&(c<55296||c>57343)&&(u=c);break;case 4:i=e[o+1],a=e[o+2],s=e[o+3],128==(192&i)&&128==(192&a)&&128==(192&s)&&(c=(15&l)<<18|(63&i)<<12|(63&a)<<6|63&s)>65535&&c<1114112&&(u=c)}null===u?(u=65533,p=1):u>65535&&(u-=65536,r.push(u>>>10&1023|55296),u=56320|1023&u),r.push(u),o+=p}return function(e){var t=e.length;if(t<=4096)return String.fromCharCode.apply(String,e);var n="",r=0;for(;r0&&(e=this.toString("hex",0,n).match(/.{2}/g).join(" "),this.length>n&&(e+=" ... ")),""},c.prototype.compare=function(e,t,n,r,o){if(!c.isBuffer(e))throw new TypeError("Argument must be a Buffer");if(void 0===t&&(t=0),void 0===n&&(n=e?e.length:0),void 0===r&&(r=0),void 0===o&&(o=this.length),t<0||n>e.length||r<0||o>this.length)throw new RangeError("out of range index");if(r>=o&&t>=n)return 0;if(r>=o)return-1;if(t>=n)return 1;if(this===e)return 0;for(var i=(o>>>=0)-(r>>>=0),a=(n>>>=0)-(t>>>=0),s=Math.min(i,a),l=this.slice(r,o),u=e.slice(t,n),p=0;po)&&(n=o),e.length>0&&(n<0||t<0)||t>this.length)throw new RangeError("Attempt to write outside buffer bounds");r||(r="utf8");for(var i=!1;;)switch(r){case"hex":return b(this,e,t,n);case"utf8":case"utf-8":return x(this,e,t,n);case"ascii":return w(this,e,t,n);case"latin1":case"binary":return k(this,e,t,n);case"base64":return S(this,e,t,n);case"ucs2":case"ucs-2":case"utf16le":case"utf-16le":return O(this,e,t,n);default:if(i)throw new TypeError("Unknown encoding: "+r);r=(""+r).toLowerCase(),i=!0}},c.prototype.toJSON=function(){return{type:"Buffer",data:Array.prototype.slice.call(this._arr||this,0)}};function _(e,t,n){var r="";n=Math.min(e.length,n);for(var o=t;or)&&(n=r);for(var o="",i=t;in)throw new RangeError("Trying to access beyond buffer length")}function P(e,t,n,r,o,i){if(!c.isBuffer(e))throw new TypeError('"buffer" argument must be a Buffer instance');if(t>o||te.length)throw new RangeError("Index out of range")}function R(e,t,n,r){t<0&&(t=65535+t+1);for(var o=0,i=Math.min(e.length-n,2);o>>8*(r?o:1-o)}function L(e,t,n,r){t<0&&(t=4294967295+t+1);for(var o=0,i=Math.min(e.length-n,4);o>>8*(r?o:3-o)&255}function N(e,t,n,r,o,i){if(n+r>e.length)throw new RangeError("Index out of range");if(n<0)throw new RangeError("Index out of range")}function M(e,t,n,r,i){return i||N(e,0,n,4),o.write(e,t,n,r,23,4),n+4}function B(e,t,n,r,i){return i||N(e,0,n,8),o.write(e,t,n,r,52,8),n+8}c.prototype.slice=function(e,t){var n,r=this.length;if((e=~~e)<0?(e+=r)<0&&(e=0):e>r&&(e=r),(t=void 0===t?r:~~t)<0?(t+=r)<0&&(t=0):t>r&&(t=r),t0&&(o*=256);)r+=this[e+--t]*o;return r},c.prototype.readUInt8=function(e,t){return t||I(e,1,this.length),this[e]},c.prototype.readUInt16LE=function(e,t){return t||I(e,2,this.length),this[e]|this[e+1]<<8},c.prototype.readUInt16BE=function(e,t){return t||I(e,2,this.length),this[e]<<8|this[e+1]},c.prototype.readUInt32LE=function(e,t){return t||I(e,4,this.length),(this[e]|this[e+1]<<8|this[e+2]<<16)+16777216*this[e+3]},c.prototype.readUInt32BE=function(e,t){return t||I(e,4,this.length),16777216*this[e]+(this[e+1]<<16|this[e+2]<<8|this[e+3])},c.prototype.readIntLE=function(e,t,n){e|=0,t|=0,n||I(e,t,this.length);for(var r=this[e],o=1,i=0;++i=(o*=128)&&(r-=Math.pow(2,8*t)),r},c.prototype.readIntBE=function(e,t,n){e|=0,t|=0,n||I(e,t,this.length);for(var r=t,o=1,i=this[e+--r];r>0&&(o*=256);)i+=this[e+--r]*o;return i>=(o*=128)&&(i-=Math.pow(2,8*t)),i},c.prototype.readInt8=function(e,t){return t||I(e,1,this.length),128&this[e]?-1*(255-this[e]+1):this[e]},c.prototype.readInt16LE=function(e,t){t||I(e,2,this.length);var n=this[e]|this[e+1]<<8;return 32768&n?4294901760|n:n},c.prototype.readInt16BE=function(e,t){t||I(e,2,this.length);var n=this[e+1]|this[e]<<8;return 32768&n?4294901760|n:n},c.prototype.readInt32LE=function(e,t){return t||I(e,4,this.length),this[e]|this[e+1]<<8|this[e+2]<<16|this[e+3]<<24},c.prototype.readInt32BE=function(e,t){return t||I(e,4,this.length),this[e]<<24|this[e+1]<<16|this[e+2]<<8|this[e+3]},c.prototype.readFloatLE=function(e,t){return t||I(e,4,this.length),o.read(this,e,!0,23,4)},c.prototype.readFloatBE=function(e,t){return t||I(e,4,this.length),o.read(this,e,!1,23,4)},c.prototype.readDoubleLE=function(e,t){return t||I(e,8,this.length),o.read(this,e,!0,52,8)},c.prototype.readDoubleBE=function(e,t){return t||I(e,8,this.length),o.read(this,e,!1,52,8)},c.prototype.writeUIntLE=function(e,t,n,r){(e=+e,t|=0,n|=0,r)||P(this,e,t,n,Math.pow(2,8*n)-1,0);var o=1,i=0;for(this[t]=255&e;++i=0&&(i*=256);)this[t+o]=e/i&255;return t+n},c.prototype.writeUInt8=function(e,t,n){return e=+e,t|=0,n||P(this,e,t,1,255,0),c.TYPED_ARRAY_SUPPORT||(e=Math.floor(e)),this[t]=255&e,t+1},c.prototype.writeUInt16LE=function(e,t,n){return e=+e,t|=0,n||P(this,e,t,2,65535,0),c.TYPED_ARRAY_SUPPORT?(this[t]=255&e,this[t+1]=e>>>8):R(this,e,t,!0),t+2},c.prototype.writeUInt16BE=function(e,t,n){return e=+e,t|=0,n||P(this,e,t,2,65535,0),c.TYPED_ARRAY_SUPPORT?(this[t]=e>>>8,this[t+1]=255&e):R(this,e,t,!1),t+2},c.prototype.writeUInt32LE=function(e,t,n){return e=+e,t|=0,n||P(this,e,t,4,4294967295,0),c.TYPED_ARRAY_SUPPORT?(this[t+3]=e>>>24,this[t+2]=e>>>16,this[t+1]=e>>>8,this[t]=255&e):L(this,e,t,!0),t+4},c.prototype.writeUInt32BE=function(e,t,n){return e=+e,t|=0,n||P(this,e,t,4,4294967295,0),c.TYPED_ARRAY_SUPPORT?(this[t]=e>>>24,this[t+1]=e>>>16,this[t+2]=e>>>8,this[t+3]=255&e):L(this,e,t,!1),t+4},c.prototype.writeIntLE=function(e,t,n,r){if(e=+e,t|=0,!r){var o=Math.pow(2,8*n-1);P(this,e,t,n,o-1,-o)}var i=0,a=1,s=0;for(this[t]=255&e;++i>0)-s&255;return t+n},c.prototype.writeIntBE=function(e,t,n,r){if(e=+e,t|=0,!r){var o=Math.pow(2,8*n-1);P(this,e,t,n,o-1,-o)}var i=n-1,a=1,s=0;for(this[t+i]=255&e;--i>=0&&(a*=256);)e<0&&0===s&&0!==this[t+i+1]&&(s=1),this[t+i]=(e/a>>0)-s&255;return t+n},c.prototype.writeInt8=function(e,t,n){return e=+e,t|=0,n||P(this,e,t,1,127,-128),c.TYPED_ARRAY_SUPPORT||(e=Math.floor(e)),e<0&&(e=255+e+1),this[t]=255&e,t+1},c.prototype.writeInt16LE=function(e,t,n){return e=+e,t|=0,n||P(this,e,t,2,32767,-32768),c.TYPED_ARRAY_SUPPORT?(this[t]=255&e,this[t+1]=e>>>8):R(this,e,t,!0),t+2},c.prototype.writeInt16BE=function(e,t,n){return e=+e,t|=0,n||P(this,e,t,2,32767,-32768),c.TYPED_ARRAY_SUPPORT?(this[t]=e>>>8,this[t+1]=255&e):R(this,e,t,!1),t+2},c.prototype.writeInt32LE=function(e,t,n){return e=+e,t|=0,n||P(this,e,t,4,2147483647,-2147483648),c.TYPED_ARRAY_SUPPORT?(this[t]=255&e,this[t+1]=e>>>8,this[t+2]=e>>>16,this[t+3]=e>>>24):L(this,e,t,!0),t+4},c.prototype.writeInt32BE=function(e,t,n){return e=+e,t|=0,n||P(this,e,t,4,2147483647,-2147483648),e<0&&(e=4294967295+e+1),c.TYPED_ARRAY_SUPPORT?(this[t]=e>>>24,this[t+1]=e>>>16,this[t+2]=e>>>8,this[t+3]=255&e):L(this,e,t,!1),t+4},c.prototype.writeFloatLE=function(e,t,n){return M(this,e,t,!0,n)},c.prototype.writeFloatBE=function(e,t,n){return M(this,e,t,!1,n)},c.prototype.writeDoubleLE=function(e,t,n){return B(this,e,t,!0,n)},c.prototype.writeDoubleBE=function(e,t,n){return B(this,e,t,!1,n)},c.prototype.copy=function(e,t,n,r){if(n||(n=0),r||0===r||(r=this.length),t>=e.length&&(t=e.length),t||(t=0),r>0&&r=this.length)throw new RangeError("sourceStart out of bounds");if(r<0)throw new RangeError("sourceEnd out of bounds");r>this.length&&(r=this.length),e.length-t=0;--o)e[o+t]=this[o+n];else if(i<1e3||!c.TYPED_ARRAY_SUPPORT)for(o=0;o>>=0,n=void 0===n?this.length:n>>>0,e||(e=0),"number"==typeof e)for(i=t;i55295&&n<57344){if(!o){if(n>56319){(t-=3)>-1&&i.push(239,191,189);continue}if(a+1===r){(t-=3)>-1&&i.push(239,191,189);continue}o=n;continue}if(n<56320){(t-=3)>-1&&i.push(239,191,189),o=n;continue}n=65536+(o-55296<<10|n-56320)}else o&&(t-=3)>-1&&i.push(239,191,189);if(o=null,n<128){if((t-=1)<0)break;i.push(n)}else if(n<2048){if((t-=2)<0)break;i.push(n>>6|192,63&n|128)}else if(n<65536){if((t-=3)<0)break;i.push(n>>12|224,n>>6&63|128,63&n|128)}else{if(!(n<1114112))throw new Error("Invalid code point");if((t-=4)<0)break;i.push(n>>18|240,n>>12&63|128,n>>6&63|128,63&n|128)}}return i}function z(e){return r.toByteArray(function(e){if((e=function(e){return e.trim?e.trim():e.replace(/^\s+|\s+$/g,"")}(e).replace(D,"")).length<2)return"";for(;e.length%4!=0;)e+="=";return e}(e))}function U(e,t,n,r){for(var o=0;o=t.length||o>=e.length);++o)t[o+n]=e[o];return o}}).call(this,n(26))},function(e,t,n){e.exports=n(383)},function(e,t,n){"use strict";var r=n(296),o=n(297);function i(){this.protocol=null,this.slashes=null,this.auth=null,this.host=null,this.port=null,this.hostname=null,this.hash=null,this.search=null,this.query=null,this.pathname=null,this.path=null,this.href=null}t.parse=b,t.resolve=function(e,t){return b(e,!1,!0).resolve(t)},t.resolveObject=function(e,t){return e?b(e,!1,!0).resolveObject(t):t},t.format=function(e){o.isString(e)&&(e=b(e));return e instanceof i?e.format():i.prototype.format.call(e)},t.Url=i;var a=/^([a-z0-9.+-]+:)/i,s=/:[0-9]*$/,c=/^(\/\/?(?!\/)[^\?\s]*)(\?[^\s]*)?$/,l=["{","}","|","\\","^","`"].concat(["<",">",'"',"`"," ","\r","\n","\t"]),u=["'"].concat(l),p=["%","/","?",";","#"].concat(u),f=["/","?","#"],d=/^[+a-z0-9A-Z_-]{0,63}$/,h=/^([+a-z0-9A-Z_-]{0,63})(.*)$/,v={javascript:!0,"javascript:":!0},m={javascript:!0,"javascript:":!0},g={http:!0,https:!0,ftp:!0,gopher:!0,file:!0,"http:":!0,"https:":!0,"ftp:":!0,"gopher:":!0,"file:":!0},y=n(298);function b(e,t,n){if(e&&o.isObject(e)&&e instanceof i)return e;var r=new i;return r.parse(e,t,n),r}i.prototype.parse=function(e,t,n){if(!o.isString(e))throw new TypeError("Parameter 'url' must be a string, not "+typeof e);var i=e.indexOf("?"),s=-1!==i&&i127?R+="x":R+=P[L];if(!R.match(d)){var M=C.slice(0,_),B=C.slice(_+1),D=P.match(h);D&&(M.push(D[1]),B.unshift(D[2])),B.length&&(b="/"+B.join(".")+b),this.hostname=M.join(".");break}}}this.hostname.length>255?this.hostname="":this.hostname=this.hostname.toLowerCase(),T||(this.hostname=r.toASCII(this.hostname));var q=this.port?":"+this.port:"",F=this.hostname||"";this.host=F+q,this.href+=this.host,T&&(this.hostname=this.hostname.substr(1,this.hostname.length-2),"/"!==b[0]&&(b="/"+b))}if(!v[k])for(_=0,I=u.length;_0)&&n.host.split("@"))&&(n.auth=T.shift(),n.host=n.hostname=T.shift());return n.search=e.search,n.query=e.query,o.isNull(n.pathname)&&o.isNull(n.search)||(n.path=(n.pathname?n.pathname:"")+(n.search?n.search:"")),n.href=n.format(),n}if(!S.length)return n.pathname=null,n.search?n.path="/"+n.search:n.path=null,n.href=n.format(),n;for(var A=S.slice(-1)[0],E=(n.host||e.host||S.length>1)&&("."===A||".."===A)||""===A,_=0,j=S.length;j>=0;j--)"."===(A=S[j])?S.splice(j,1):".."===A?(S.splice(j,1),_++):_&&(S.splice(j,1),_--);if(!w&&!k)for(;_--;_)S.unshift("..");!w||""===S[0]||S[0]&&"/"===S[0].charAt(0)||S.unshift(""),E&&"/"!==S.join("/").substr(-1)&&S.push("");var T,C=""===S[0]||S[0]&&"/"===S[0].charAt(0);O&&(n.hostname=n.host=C?"":S.length?S.shift():"",(T=!!(n.host&&n.host.indexOf("@")>0)&&n.host.split("@"))&&(n.auth=T.shift(),n.host=n.hostname=T.shift()));return(w=w||n.host&&S.length)&&!C&&S.unshift(""),S.length?n.pathname=S.join("/"):(n.pathname=null,n.path=null),o.isNull(n.pathname)&&o.isNull(n.search)||(n.path=(n.pathname?n.pathname:"")+(n.search?n.search:"")),n.auth=e.auth||n.auth,n.slashes=n.slashes||e.slashes,n.href=n.format(),n},i.prototype.parseHost=function(){var e=this.host,t=s.exec(e);t&&(":"!==(t=t[0])&&(this.port=t.substr(1)),e=e.substr(0,e.length-t.length)),e&&(this.hostname=e)}},function(e,t,n){var r=n(448),o=n(449),i=n(158),a=n(452);e.exports=function(e,t){return r(e)||o(e,t)||i(e,t)||a()}},function(e,t){e.exports=function(e){var t=typeof e;return null!=e&&("object"==t||"function"==t)}},function(e,t,n){var r=n(22),o=n(38),i=n(70);e.exports=r?function(e,t,n){return o.f(e,t,i(1,n))}:function(e,t,n){return e[t]=n,e}},function(e,t,n){var r=n(22),o=n(192),i=n(30),a=n(111),s=Object.defineProperty;t.f=r?s:function(e,t,n){if(i(e),t=a(t,!0),i(n),o)try{return s(e,t,n)}catch(e){}if("get"in n||"set"in n)throw TypeError("Accessors not supported");return"value"in n&&(e[t]=n.value),e}},function(e,t,n){var r=n(22),o=n(13),i=n(29),a=Object.defineProperty,s={},c=function(e){throw e};e.exports=function(e,t){if(i(s,e))return s[e];t||(t={});var n=[][e],l=!!i(t,"ACCESSORS")&&t.ACCESSORS,u=i(t,0)?t[0]:c,p=i(t,1)?t[1]:void 0;return s[e]=!!n&&!o((function(){if(l&&!r)return!0;var e={length:-1};l?a(e,1,{enumerable:!0,get:c}):e[1]=1,n.call(e,u,p)}))}},function(e,t,n){var r=n(563),o=n(564),i=n(158),a=n(565);e.exports=function(e){return r(e)||o(e)||i(e)||a()}},function(e,t){var n,r,o=e.exports={};function i(){throw new Error("setTimeout has not been defined")}function a(){throw new Error("clearTimeout has not been defined")}function s(e){if(n===setTimeout)return setTimeout(e,0);if((n===i||!n)&&setTimeout)return n=setTimeout,setTimeout(e,0);try{return n(e,0)}catch(t){try{return n.call(null,e,0)}catch(t){return n.call(this,e,0)}}}!function(){try{n="function"==typeof setTimeout?setTimeout:i}catch(e){n=i}try{r="function"==typeof clearTimeout?clearTimeout:a}catch(e){r=a}}();var c,l=[],u=!1,p=-1;function f(){u&&c&&(u=!1,c.length?l=c.concat(l):p=-1,l.length&&d())}function d(){if(!u){var e=s(f);u=!0;for(var t=l.length;t;){for(c=l,l=[];++p1)for(var n=1;n=e.length?{done:!0}:{done:!1,value:e[c++]}},e:function(e){throw e},f:l}}throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}var u,p=!0,f=!1;return{s:function(){n=r(e)},n:function(){var e=n.next();return p=e.done,e},e:function(e){f=!0,u=e},f:function(){try{p||null==n.return||n.return()}finally{if(f)throw u}}}}},function(e,t){e.exports=function(e){if("function"!=typeof e)throw TypeError(String(e)+" is not a function");return e}},function(e,t,n){var r=n(85),o=n(109),i=n(44),a=n(54),s=n(148),c=[].push,l=function(e){var t=1==e,n=2==e,l=3==e,u=4==e,p=6==e,f=5==e||p;return function(d,h,v,m){for(var g,y,b=i(d),x=o(b),w=r(h,v,3),k=a(x.length),S=0,O=m||s,A=t?O(d,k):n?O(d,0):void 0;k>S;S++)if((f||S in x)&&(y=w(g=x[S],S,b),e))if(t)A[S]=y;else if(y)switch(e){case 3:return!0;case 5:return g;case 6:return S;case 2:c.call(A,g)}else if(u)return!1;return p?-1:l||u?u:A}};e.exports={forEach:l(0),map:l(1),filter:l(2),some:l(3),every:l(4),find:l(5),findIndex:l(6)}},function(e,t){e.exports=function(e){return null!=e&&"object"==typeof e}},function(e,t,n){var r=n(64),o=n(36);e.exports=function(e){if(!o(e))return!1;var t=r(e);return"[object Function]"==t||"[object GeneratorFunction]"==t||"[object AsyncFunction]"==t||"[object Proxy]"==t}},function(e,t,n){var r=n(162);e.exports=function(e,t,n){var o=null==e?void 0:r(e,t);return void 0===o?n:o}},function(e,t,n){e.exports=n(570)},function(e,t,n){var r=n(112),o=Math.min;e.exports=function(e){return e>0?o(r(e),9007199254740991):0}},function(e,t,n){n(197);var r=n(340),o=n(16),i=n(72),a=n(37),s=n(61),c=n(14)("toStringTag");for(var l in r){var u=o[l],p=u&&u.prototype;p&&i(p)!==c&&a(p,c,l),s[l]=s.Array}},function(e,t,n){var r,o,i,a=n(198),s=n(16),c=n(17),l=n(37),u=n(29),p=n(113),f=n(87),d=s.WeakMap;if(a){var h=new d,v=h.get,m=h.has,g=h.set;r=function(e,t){return g.call(h,e,t),t},o=function(e){return v.call(h,e)||{}},i=function(e){return m.call(h,e)}}else{var y=p("state");f[y]=!0,r=function(e,t){return l(e,y,t),t},o=function(e){return u(e,y)?e[y]:{}},i=function(e){return u(e,y)}}e.exports={set:r,get:o,has:i,enforce:function(e){return i(e)?o(e):r(e,{})},getterFor:function(e){return function(t){var n;if(!c(t)||(n=o(t)).type!==e)throw TypeError("Incompatible receiver, "+e+" required");return n}}}},function(e,t){e.exports=null},function(e,t,n){e.exports=n(357)},function(e,t){"function"==typeof Object.create?e.exports=function(e,t){t&&(e.super_=t,e.prototype=Object.create(t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}}))}:e.exports=function(e,t){if(t){e.super_=t;var n=function(){};n.prototype=t.prototype,e.prototype=new n,e.prototype.constructor=e}}},function(e,t){var n={}.toString;e.exports=function(e){return n.call(e).slice(8,-1)}},function(e,t){e.exports={}},function(e,t,n){var r=n(146),o=n(38).f,i=n(37),a=n(29),s=n(338),c=n(14)("toStringTag");e.exports=function(e,t,n,l){if(e){var u=n?e:e.prototype;a(u,c)||o(u,c,{configurable:!0,value:t}),l&&!r&&i(u,"toString",s)}}},function(e,t,n){var r=n(361),o=n(366);e.exports=function(e,t){var n=o(e,t);return r(n)?n:void 0}},function(e,t,n){var r=n(73),o=n(362),i=n(363),a=r?r.toStringTag:void 0;e.exports=function(e){return null==e?void 0===e?"[object Undefined]":"[object Null]":a&&a in Object(e)?o(e):i(e)}},function(e,t,n){var r=n(115),o=n(95),i=n(367),a=n(74),s=n(96),c=n(75),l=Object.prototype.hasOwnProperty,u=i((function(e,t){if(s(t)||a(t))o(t,c(t),e);else for(var n in t)l.call(t,n)&&r(e,n,t[n])}));e.exports=u},function(e,t,n){e.exports=n(437)},function(e,t,n){var r=n(136);function o(e,t){for(var n=0;n=n.length?{value:void 0,done:!0}:(e=r(n,o),t.index+=e.length,{value:e,done:!1})}))},function(e,t,n){var r=n(30),o=n(233),i=n(54),a=n(85),s=n(121),c=n(232),l=function(e,t){this.stopped=e,this.result=t};(e.exports=function(e,t,n,u,p){var f,d,h,v,m,g,y,b=a(t,n,u?2:1);if(p)f=e;else{if("function"!=typeof(d=s(e)))throw TypeError("Target is not iterable");if(o(d)){for(h=0,v=i(e.length);v>h;h++)if((m=u?b(r(y=e[h])[0],y[1]):b(e[h]))&&m instanceof l)return m;return new l(!1)}f=d.call(e)}for(g=f.next;!(y=g.call(f)).done;)if("object"==typeof(m=c(f,b,y.value,u))&&m&&m instanceof l)return m;return new l(!1)}).stop=function(e){return new l(!0,e)}},function(e,t,n){"use strict";function r(e){return null==e}e.exports.isNothing=r,e.exports.isObject=function(e){return"object"==typeof e&&null!==e},e.exports.toArray=function(e){return Array.isArray(e)?e:r(e)?[]:[e]},e.exports.repeat=function(e,t){var n,r="";for(n=0;n"+e+"<\/script>"},h=function(){try{r=document.domain&&new ActiveXObject("htmlfile")}catch(e){}var e,t;h=r?function(e){e.write(d("")),e.close();var t=e.parentWindow.Object;return e=null,t}(r):((t=l("iframe")).style.display="none",c.appendChild(t),t.src=String("javascript:"),(e=t.contentWindow.document).open(),e.write(d("document.F=Object")),e.close(),e.F);for(var n=a.length;n--;)delete h.prototype[a[n]];return h()};s[p]=!0,e.exports=Object.create||function(e,t){var n;return null!==e?(f.prototype=o(e),n=new f,f.prototype=null,n[p]=e):n=h(),void 0===t?n:i(n,t)}},function(e,t,n){var r=n(37);e.exports=function(e,t,n,o){o&&o.enumerable?e[t]=n:r(e,t,n)}},function(e,t,n){var r=n(60);e.exports=Array.isArray||function(e){return"Array"==r(e)}},function(e,t,n){"use strict";var r=n(13);e.exports=function(e,t){var n=[][e];return!!n&&r((function(){n.call(null,t||function(){throw 1},1)}))}},function(e,t,n){var r=n(13),o=n(14),i=n(149),a=o("species");e.exports=function(e){return i>=51||!r((function(){var t=[];return(t.constructor={})[a]=function(){return{foo:1}},1!==t[e](Boolean).foo}))}},function(e,t,n){var r=n(115),o=n(214);e.exports=function(e,t,n,i){var a=!n;n||(n={});for(var s=-1,c=t.length;++s=0){let e=t.split("#");if(e[0])return!1;t=e[1],t=decodeURIComponent(t.slice(1).split("+").join(" "))}t.startsWith("/")&&(t=t.slice(1));let o=t.split("/");for(let t=0;t0?o[t-1]:"",-1!=a||e.hasOwnProperty(o[t]))if(a>=0)i&&(e[a]=n),e=e[a];else{if(-2===a)return i?(Array.isArray(e)&&e.push(n),n):void 0;i&&(e[o[t]]=n),e=e[o[t]]}else{if(void 0===n||"object"!=typeof e||Array.isArray(e))return!1;e[o[t]]=i?n:"0"===o[t+1]||"-"===o[t+1]?[]:{},e=e[o[t]]}}return e},jpescape:function(e){return e.replace(/\~/g,"~0").replace(/\//g,"~1")},jpunescape:r}},function(e,t,n){"use strict";e.exports={nop:function(e){return e},clone:function(e){return JSON.parse(JSON.stringify(e))},shallowClone:function(e){let t={};for(let n in e)e.hasOwnProperty(n)&&(t[n]=e[n]);return t},deepClone:function e(t){let n=Array.isArray(t)?[]:{};for(let r in t)(t.hasOwnProperty(r)||Array.isArray(t))&&(n[r]="object"==typeof t[r]?e(t[r]):t[r]);return n},fastClone:function(e){return Object.assign({},e)},circularClone:function e(t,n){if(n||(n=new WeakMap),Object(t)!==t||t instanceof Function)return t;if(n.has(t))return n.get(t);try{var r=new t.constructor}catch(e){r=Object.create(Object.getPrototypeOf(t))}return n.set(t,r),Object.assign(r,...Object.keys(t).map(r=>({[r]:e(t[r],n)})))}}},function(e,t,n){"use strict";(function(t){!t.version||0===t.version.indexOf("v0.")||0===t.version.indexOf("v1.")&&0!==t.version.indexOf("v1.8.")?e.exports={nextTick:function(e,n,r,o){if("function"!=typeof e)throw new TypeError('"callback" argument must be a function');var i,a,s=arguments.length;switch(s){case 0:case 1:return t.nextTick(e);case 2:return t.nextTick((function(){e.call(null,n)}));case 3:return t.nextTick((function(){e.call(null,n,r)}));case 4:return t.nextTick((function(){e.call(null,n,r,o)}));default:for(i=new Array(s-1),a=0;a0?r:n)(e)}},function(e,t,n){var r=n(144),o=n(114),i=r("keys");e.exports=function(e){return i[e]||(i[e]=o(e))}},function(e,t){var n=0,r=Math.random();e.exports=function(e){return"Symbol("+String(void 0===e?"":e)+")_"+(++n+r).toString(36)}},function(e,t,n){var r=n(214),o=n(116),i=Object.prototype.hasOwnProperty;e.exports=function(e,t,n){var a=e[t];i.call(e,t)&&o(a,n)&&(void 0!==n||t in e)||r(e,t,n)}},function(e,t){e.exports=function(e,t){return e===t||e!=e&&t!=t}},function(e,t){var n=/^(?:0|[1-9]\d*)$/;e.exports=function(e,t){var r=typeof e;return!!(t=null==t?9007199254740991:t)&&("number"==r||"symbol"!=r&&n.test(e))&&e>-1&&e%1==0&&eu;)if((s=c[u++])!=s)return!0}else for(;l>u;u++)if((e||u in c)&&c[u]===n)return e||u||0;return!e&&-1}};e.exports={includes:a(!0),indexOf:a(!1)}},function(e,t,n){var r=n(112),o=Math.max,i=Math.min;e.exports=function(e,t){var n=r(e);return n<0?o(n+t,0):i(n,t)}},function(e,t){e.exports=["constructor","hasOwnProperty","isPrototypeOf","propertyIsEnumerable","toLocaleString","toString","valueOf"]},function(e,t,n){var r=n(195),o=n(140).concat("length","prototype");t.f=Object.getOwnPropertyNames||function(e){return r(e,o)}},function(e,t){t.f=Object.getOwnPropertySymbols},function(e,t){e.exports=function(){}},function(e,t,n){var r=n(71),o=n(200);(e.exports=function(e,t){return o[e]||(o[e]=void 0!==t?t:{})})("versions",[]).push({version:"3.6.4",mode:r?"pure":"global",copyright:"© 2020 Denis Pushkarev (zloirock.ru)"})},function(e,t,n){var r=n(13);e.exports=!!Object.getOwnPropertySymbols&&!r((function(){return!String(Symbol())}))},function(e,t,n){var r={};r[n(14)("toStringTag")]="z",e.exports="[object z]"===String(r)},function(e,t,n){var r=n(30),o=n(339);e.exports=Object.setPrototypeOf||("__proto__"in{}?function(){var e,t=!1,n={};try{(e=Object.getOwnPropertyDescriptor(Object.prototype,"__proto__").set).call(n,[]),t=n instanceof Array}catch(e){}return function(n,i){return r(n),o(i),t?e.call(n,i):n.__proto__=i,n}}():void 0)},function(e,t,n){var r=n(17),o=n(92),i=n(14)("species");e.exports=function(e,t){var n;return o(e)&&("function"!=typeof(n=e.constructor)||n!==Array&&!o(n.prototype)?r(n)&&null===(n=n[i])&&(n=void 0):n=void 0),new(void 0===n?Array:n)(0===t?0:t)}},function(e,t,n){var r,o,i=n(16),a=n(208),s=i.process,c=s&&s.versions,l=c&&c.v8;l?o=(r=l.split("."))[0]+r[1]:a&&(!(r=a.match(/Edge\/(\d+)/))||r[1]>=74)&&(r=a.match(/Chrome\/(\d+)/))&&(o=r[1]),e.exports=o&&+o},function(e,t,n){var r=n(14);t.f=r},function(e,t){e.exports=function(e){return e}},function(e,t){e.exports=function(e){return"number"==typeof e&&e>-1&&e%1==0&&e<=9007199254740991}},function(e,t,n){var r=n(377),o=n(154),i=n(155),a=i&&i.isTypedArray,s=a?o(a):r;e.exports=s},function(e,t){e.exports=function(e){return function(t){return e(t)}}},function(e,t,n){(function(e){var r=n(216),o=t&&!t.nodeType&&t,i=o&&"object"==typeof e&&e&&!e.nodeType&&e,a=i&&i.exports===o&&r.process,s=function(){try{var e=i&&i.require&&i.require("util").types;return e||a&&a.binding&&a.binding("util")}catch(e){}}();e.exports=s}).call(this,n(103)(e))},function(e,t,n){e.exports=n(388)},function(e,t){},function(e,t,n){var r=n(230),o=n(417),i=n(236);e.exports=function(e,t){var n;if(e){if("string"==typeof e)return i(e,t);var a=o(n=Object.prototype.toString.call(e)).call(n,8,-1);return"Object"===a&&e.constructor&&(a=e.constructor.name),"Map"===a||"Set"===a?r(e):"Arguments"===a||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(a)?i(e,t):void 0}}},function(e,t,n){var r=n(91);e.exports=function(e,t,n){for(var o in t)n&&n.unsafe&&e[o]?e[o]=t[o]:r(e,o,t[o],n);return e}},function(e,t){e.exports=function(e,t,n){if(!(e instanceof t))throw TypeError("Incorrect "+(n?n+" ":"")+"invocation");return e}},function(e,t,n){"use strict";var r=n(79);e.exports=new r({explicit:[n(489),n(490),n(491)]})},function(e,t,n){var r=n(125),o=n(100);e.exports=function(e,t){for(var n=0,i=(t=r(t,e)).length;null!=e&&n=48&&u<=57)n=u-48;else if(u>=65&&u<=70)n=u-65+10;else{if(!(u>=97&&u<=102)){a[l++]=37,a[l++]=u,s=0;break}n=u-97+10}s=2;break;case 2:if(s=0,u>=48&&u<=57)r=u-48;else if(u>=65&&u<=70)r=u-65+10;else{if(!(u>=97&&u<=102)){a[l++]=37,a[l++]=i,a[l++]=u;break}r=u-97+10}a[l++]=16*n+r}}return a.slice(0,l-1)},r.unescape=i;for(var a=new Array(256),s=0;s<256;++s)a[s]="%"+((s<16?"0":"")+s.toString(16)).toUpperCase();r.escape=function(e){"string"!=typeof e&&(e+="");for(var t="",n=0,r=0;r=39&&o<=42||o>=48&&o<=57||o>=65&&o<=90||o>=97&&o<=122))if(r-n>0&&(t+=e.slice(n,r)),o<128)n=r+1,t+=a[o];else if(o<2048)n=r+1,t+=a[192|o>>6]+a[128|63&o];else if(o<55296||o>=57344)n=r+1,t+=a[224|o>>12]+a[128|o>>6&63]+a[128|63&o];else{var i;if(!(++r>18]+a[128|o>>12&63]+a[128|o>>6&63]+a[128|63&o]}}return 0===n?e:n0&&(p=u);var f=r.unescape;o&&"function"==typeof o.decodeURIComponent&&(f=o.decodeURIComponent);for(var d=f!==i,h=[],v=0,m=0,g=0,y="",b="",x=d,w=d,k=0,S=0;S0&&(O>=48&&O<=57||O>=65&&O<=70||O>=97&&O<=102)?3==++k&&(w=!0):k=0),g0&&(O>=48&&O<=57||O>=65&&O<=70||O>=97&&O<=102)?3==++k&&(x=!0):k=0)}43===O&&(g0&&(y+=e.slice(v,S)),y+="%20",x=!0):(S-v>0&&(b+=e.slice(v,S)),b+="%20",w=!0),v=S+1)}else if(++m===c){var A,E=S-m+1;if(g0&&(v0)&&(v=0;r--){var o=e[r];"."===o?e.splice(r,1):".."===o?(e.splice(r,1),n++):n&&(e.splice(r,1),n--)}if(t)for(;n--;n)e.unshift("..");return e}function r(e,t){if(e.filter)return e.filter(t);for(var n=[],r=0;r=-1&&!o;i--){var a=i>=0?arguments[i]:e.cwd();if("string"!=typeof a)throw new TypeError("Arguments to path.resolve must be strings");a&&(t=a+"/"+t,o="/"===a.charAt(0))}return(o?"/":"")+(t=n(r(t.split("/"),(function(e){return!!e})),!o).join("/"))||"."},t.normalize=function(e){var i=t.isAbsolute(e),a="/"===o(e,-1);return(e=n(r(e.split("/"),(function(e){return!!e})),!i).join("/"))||i||(e="."),e&&a&&(e+="/"),(i?"/":"")+e},t.isAbsolute=function(e){return"/"===e.charAt(0)},t.join=function(){var e=Array.prototype.slice.call(arguments,0);return t.normalize(r(e,(function(e,t){if("string"!=typeof e)throw new TypeError("Arguments to path.join must be strings");return e})).join("/"))},t.relative=function(e,n){function r(e){for(var t=0;t=0&&""===e[n];n--);return t>n?[]:e.slice(t,n-t+1)}e=t.resolve(e).substr(1),n=t.resolve(n).substr(1);for(var o=r(e.split("/")),i=r(n.split("/")),a=Math.min(o.length,i.length),s=a,c=0;c=1;--i)if(47===(t=e.charCodeAt(i))){if(!o){r=i;break}}else o=!1;return-1===r?n?"/":".":n&&1===r?"/":e.slice(0,r)},t.basename=function(e,t){var n=function(e){"string"!=typeof e&&(e+="");var t,n=0,r=-1,o=!0;for(t=e.length-1;t>=0;--t)if(47===e.charCodeAt(t)){if(!o){n=t+1;break}}else-1===r&&(o=!1,r=t+1);return-1===r?"":e.slice(n,r)}(e);return t&&n.substr(-1*t.length)===t&&(n=n.substr(0,n.length-t.length)),n},t.extname=function(e){"string"!=typeof e&&(e+="");for(var t=-1,n=0,r=-1,o=!0,i=0,a=e.length-1;a>=0;--a){var s=e.charCodeAt(a);if(47!==s)-1===r&&(o=!1,r=a+1),46===s?-1===t?t=a:1!==i&&(i=1):-1!==t&&(i=-1);else if(!o){n=a+1;break}}return-1===t||-1===r||0===i||1===i&&t===r-1&&t===n+1?"":e.slice(t,r)};var o="b"==="ab".substr(-1)?function(e,t,n){return e.substr(t,n)}:function(e,t,n){return t<0&&(t=e.length+t),e.substr(t,n)}}).call(this,n(41))},function(e,t,n){e.exports=n(638).YAML},function(e,t){var n={}.toString;e.exports=Array.isArray||function(e){return"[object Array]"==n.call(e)}},function(e,t,n){"use strict";(function(t){const r=n(306),o=t.env.NODE_DISABLE_COLORS?{red:"",yellow:"",green:"",normal:""}:{red:"",yellow:"",green:"",normal:""};function i(e,t){function n(e,t){return r.stringify(e)===r.stringify(Object.assign({},e,t))}return n(e,t)&&n(t,e)}String.prototype.toCamelCase=function(){return this.toLowerCase().replace(/[-_ \/\.](.)/g,(function(e,t){return t.toUpperCase()}))};function a(e){let t=(e=e.replace("[]","Array")).split("/");return t[0]=t[0].replace(/[^A-Za-z0-9_\-\.]+|\s+/gm,"_"),t.join("/")}e.exports={colour:o,uniqueOnly:function(e,t,n){return n.indexOf(e)===t},hasDuplicates:function(e){return new Set(e).size!==e.length},allSame:function(e){return new Set(e).size<=1},distinctArray:function(e){return e.length===function(e){let t=[];for(let n of e){t.find((function(e,t,r){return i(e,n)}))||t.push(n)}return t}(e).length},firstDupe:function(e){return e.find((function(t,n,r){return e.indexOf(t)e._pos){var i=n.substr(e._pos);if("x-user-defined"===e._charset){for(var a=new r(i.length),s=0;se._pos&&(e.push(new r(new Uint8Array(l.result.slice(e._pos)))),e._pos=l.result.byteLength)},l.onload=function(){e.push(null)},l.readAsArrayBuffer(n)}e._xhr.readyState===c.DONE&&"ms-stream"!==e._mode&&e.push(null)}}).call(this,n(41),n(32).Buffer,n(26))},function(e,t,n){(t=e.exports=n(185)).Stream=t,t.Readable=t,t.Writable=n(189),t.Duplex=n(68),t.Transform=n(191),t.PassThrough=n(317)},function(e,t,n){"use strict";(function(t,r){var o=n(106);e.exports=b;var i,a=n(180);b.ReadableState=y;n(186).EventEmitter;var s=function(e,t){return e.listeners(t).length},c=n(187),l=n(107).Buffer,u=t.Uint8Array||function(){};var p=n(84);p.inherits=n(59);var f=n(311),d=void 0;d=f&&f.debuglog?f.debuglog("stream"):function(){};var h,v=n(312),m=n(188);p.inherits(b,c);var g=["error","close","destroy","pause","resume"];function y(e,t){e=e||{};var r=t instanceof(i=i||n(68));this.objectMode=!!e.objectMode,r&&(this.objectMode=this.objectMode||!!e.readableObjectMode);var o=e.highWaterMark,a=e.readableHighWaterMark,s=this.objectMode?16:16384;this.highWaterMark=o||0===o?o:r&&(a||0===a)?a:s,this.highWaterMark=Math.floor(this.highWaterMark),this.buffer=new v,this.length=0,this.pipes=null,this.pipesCount=0,this.flowing=null,this.ended=!1,this.endEmitted=!1,this.reading=!1,this.sync=!0,this.needReadable=!1,this.emittedReadable=!1,this.readableListening=!1,this.resumeScheduled=!1,this.destroyed=!1,this.defaultEncoding=e.defaultEncoding||"utf8",this.awaitDrain=0,this.readingMore=!1,this.decoder=null,this.encoding=null,e.encoding&&(h||(h=n(190).StringDecoder),this.decoder=new h(e.encoding),this.encoding=e.encoding)}function b(e){if(i=i||n(68),!(this instanceof b))return new b(e);this._readableState=new y(e,this),this.readable=!0,e&&("function"==typeof e.read&&(this._read=e.read),"function"==typeof e.destroy&&(this._destroy=e.destroy)),c.call(this)}function x(e,t,n,r,o){var i,a=e._readableState;null===t?(a.reading=!1,function(e,t){if(t.ended)return;if(t.decoder){var n=t.decoder.end();n&&n.length&&(t.buffer.push(n),t.length+=t.objectMode?1:n.length)}t.ended=!0,S(e)}(e,a)):(o||(i=function(e,t){var n;r=t,l.isBuffer(r)||r instanceof u||"string"==typeof t||void 0===t||e.objectMode||(n=new TypeError("Invalid non-string/buffer chunk"));var r;return n}(a,t)),i?e.emit("error",i):a.objectMode||t&&t.length>0?("string"==typeof t||a.objectMode||Object.getPrototypeOf(t)===l.prototype||(t=function(e){return l.from(e)}(t)),r?a.endEmitted?e.emit("error",new Error("stream.unshift() after end event")):w(e,a,t,!0):a.ended?e.emit("error",new Error("stream.push() after EOF")):(a.reading=!1,a.decoder&&!n?(t=a.decoder.write(t),a.objectMode||0!==t.length?w(e,a,t,!1):A(e,a)):w(e,a,t,!1))):r||(a.reading=!1));return function(e){return!e.ended&&(e.needReadable||e.lengtht.highWaterMark&&(t.highWaterMark=function(e){return e>=8388608?e=8388608:(e--,e|=e>>>1,e|=e>>>2,e|=e>>>4,e|=e>>>8,e|=e>>>16,e++),e}(e)),e<=t.length?e:t.ended?t.length:(t.needReadable=!0,0))}function S(e){var t=e._readableState;t.needReadable=!1,t.emittedReadable||(d("emitReadable",t.flowing),t.emittedReadable=!0,t.sync?o.nextTick(O,e):O(e))}function O(e){d("emit readable"),e.emit("readable"),T(e)}function A(e,t){t.readingMore||(t.readingMore=!0,o.nextTick(E,e,t))}function E(e,t){for(var n=t.length;!t.reading&&!t.flowing&&!t.ended&&t.length=t.length?(n=t.decoder?t.buffer.join(""):1===t.buffer.length?t.buffer.head.data:t.buffer.concat(t.length),t.buffer.clear()):n=function(e,t,n){var r;ei.length?i.length:e;if(a===i.length?o+=i:o+=i.slice(0,e),0===(e-=a)){a===i.length?(++r,n.next?t.head=n.next:t.head=t.tail=null):(t.head=n,n.data=i.slice(a));break}++r}return t.length-=r,o}(e,t):function(e,t){var n=l.allocUnsafe(e),r=t.head,o=1;r.data.copy(n),e-=r.data.length;for(;r=r.next;){var i=r.data,a=e>i.length?i.length:e;if(i.copy(n,n.length-e,0,a),0===(e-=a)){a===i.length?(++o,r.next?t.head=r.next:t.head=t.tail=null):(t.head=r,r.data=i.slice(a));break}++o}return t.length-=o,n}(e,t);return r}(e,t.buffer,t.decoder),n);var n}function I(e){var t=e._readableState;if(t.length>0)throw new Error('"endReadable()" called on non-empty stream');t.endEmitted||(t.ended=!0,o.nextTick(P,t,e))}function P(e,t){e.endEmitted||0!==e.length||(e.endEmitted=!0,t.readable=!1,t.emit("end"))}function R(e,t){for(var n=0,r=e.length;n=t.highWaterMark||t.ended))return d("read: emitReadable",t.length,t.ended),0===t.length&&t.ended?I(this):S(this),null;if(0===(e=k(e,t))&&t.ended)return 0===t.length&&I(this),null;var r,o=t.needReadable;return d("need readable",o),(0===t.length||t.length-e0?C(e,t):null)?(t.needReadable=!0,e=0):t.length-=e,0===t.length&&(t.ended||(t.needReadable=!0),n!==e&&t.ended&&I(this)),null!==r&&this.emit("data",r),r},b.prototype._read=function(e){this.emit("error",new Error("_read() is not implemented"))},b.prototype.pipe=function(e,t){var n=this,i=this._readableState;switch(i.pipesCount){case 0:i.pipes=e;break;case 1:i.pipes=[i.pipes,e];break;default:i.pipes.push(e)}i.pipesCount+=1,d("pipe count=%d opts=%j",i.pipesCount,t);var c=(!t||!1!==t.end)&&e!==r.stdout&&e!==r.stderr?u:b;function l(t,r){d("onunpipe"),t===n&&r&&!1===r.hasUnpiped&&(r.hasUnpiped=!0,d("cleanup"),e.removeListener("close",g),e.removeListener("finish",y),e.removeListener("drain",p),e.removeListener("error",m),e.removeListener("unpipe",l),n.removeListener("end",u),n.removeListener("end",b),n.removeListener("data",v),f=!0,!i.awaitDrain||e._writableState&&!e._writableState.needDrain||p())}function u(){d("onend"),e.end()}i.endEmitted?o.nextTick(c):n.once("end",c),e.on("unpipe",l);var p=function(e){return function(){var t=e._readableState;d("pipeOnDrain",t.awaitDrain),t.awaitDrain&&t.awaitDrain--,0===t.awaitDrain&&s(e,"data")&&(t.flowing=!0,T(e))}}(n);e.on("drain",p);var f=!1;var h=!1;function v(t){d("ondata"),h=!1,!1!==e.write(t)||h||((1===i.pipesCount&&i.pipes===e||i.pipesCount>1&&-1!==R(i.pipes,e))&&!f&&(d("false write response, pause",n._readableState.awaitDrain),n._readableState.awaitDrain++,h=!0),n.pause())}function m(t){d("onerror",t),b(),e.removeListener("error",m),0===s(e,"error")&&e.emit("error",t)}function g(){e.removeListener("finish",y),b()}function y(){d("onfinish"),e.removeListener("close",g),b()}function b(){d("unpipe"),n.unpipe(e)}return n.on("data",v),function(e,t,n){if("function"==typeof e.prependListener)return e.prependListener(t,n);e._events&&e._events[t]?a(e._events[t])?e._events[t].unshift(n):e._events[t]=[n,e._events[t]]:e.on(t,n)}(e,"error",m),e.once("close",g),e.once("finish",y),e.emit("pipe",n),i.flowing||(d("pipe resume"),n.resume()),e},b.prototype.unpipe=function(e){var t=this._readableState,n={hasUnpiped:!1};if(0===t.pipesCount)return this;if(1===t.pipesCount)return e&&e!==t.pipes||(e||(e=t.pipes),t.pipes=null,t.pipesCount=0,t.flowing=!1,e&&e.emit("unpipe",this,n)),this;if(!e){var r=t.pipes,o=t.pipesCount;t.pipes=null,t.pipesCount=0,t.flowing=!1;for(var i=0;i0&&a.length>o&&!a.warned){a.warned=!0;var c=new Error("Possible EventEmitter memory leak detected. "+a.length+" "+String(t)+" listeners added. Use emitter.setMaxListeners() to increase limit");c.name="MaxListenersExceededWarning",c.emitter=e,c.type=t,c.count=a.length,s=c,console&&console.warn&&console.warn(s)}return e}function p(){for(var e=[],t=0;t0&&(a=t[0]),a instanceof Error)throw a;var s=new Error("Unhandled error."+(a?" ("+a.message+")":""));throw s.context=a,s}var c=o[e];if(void 0===c)return!1;if("function"==typeof c)i(c,this,t);else{var l=c.length,u=v(c,l);for(n=0;n=0;i--)if(n[i]===t||n[i].listener===t){a=n[i].listener,o=i;break}if(o<0)return this;0===o?n.shift():function(e,t){for(;t+1=0;r--)this.removeListener(e,t[r]);return this},s.prototype.listeners=function(e){return d(this,e,!0)},s.prototype.rawListeners=function(e){return d(this,e,!1)},s.listenerCount=function(e,t){return"function"==typeof e.listenerCount?e.listenerCount(t):h.call(e,t)},s.prototype.listenerCount=h,s.prototype.eventNames=function(){return this._eventsCount>0?r(this._events):[]}},function(e,t,n){e.exports=n(186).EventEmitter},function(e,t,n){"use strict";var r=n(106);function o(e,t){e.emit("error",t)}e.exports={destroy:function(e,t){var n=this,i=this._readableState&&this._readableState.destroyed,a=this._writableState&&this._writableState.destroyed;return i||a?(t?t(e):!e||this._writableState&&this._writableState.errorEmitted||r.nextTick(o,this,e),this):(this._readableState&&(this._readableState.destroyed=!0),this._writableState&&(this._writableState.destroyed=!0),this._destroy(e||null,(function(e){!t&&e?(r.nextTick(o,n,e),n._writableState&&(n._writableState.errorEmitted=!0)):t&&t(e)})),this)},undestroy:function(){this._readableState&&(this._readableState.destroyed=!1,this._readableState.reading=!1,this._readableState.ended=!1,this._readableState.endEmitted=!1),this._writableState&&(this._writableState.destroyed=!1,this._writableState.ended=!1,this._writableState.ending=!1,this._writableState.finished=!1,this._writableState.errorEmitted=!1)}}},function(e,t,n){"use strict";(function(t,r,o){var i=n(106);function a(e){var t=this;this.next=null,this.entry=null,this.finish=function(){!function(e,t,n){var r=e.entry;e.entry=null;for(;r;){var o=r.callback;t.pendingcb--,o(n),r=r.next}t.corkedRequestsFree?t.corkedRequestsFree.next=e:t.corkedRequestsFree=e}(t,e)}}e.exports=y;var s,c=!t.browser&&["v0.10","v0.9."].indexOf(t.version.slice(0,5))>-1?r:i.nextTick;y.WritableState=g;var l=n(84);l.inherits=n(59);var u={deprecate:n(316)},p=n(187),f=n(107).Buffer,d=o.Uint8Array||function(){};var h,v=n(188);function m(){}function g(e,t){s=s||n(68),e=e||{};var r=t instanceof s;this.objectMode=!!e.objectMode,r&&(this.objectMode=this.objectMode||!!e.writableObjectMode);var o=e.highWaterMark,l=e.writableHighWaterMark,u=this.objectMode?16:16384;this.highWaterMark=o||0===o?o:r&&(l||0===l)?l:u,this.highWaterMark=Math.floor(this.highWaterMark),this.finalCalled=!1,this.needDrain=!1,this.ending=!1,this.ended=!1,this.finished=!1,this.destroyed=!1;var p=!1===e.decodeStrings;this.decodeStrings=!p,this.defaultEncoding=e.defaultEncoding||"utf8",this.length=0,this.writing=!1,this.corked=0,this.sync=!0,this.bufferProcessing=!1,this.onwrite=function(e){!function(e,t){var n=e._writableState,r=n.sync,o=n.writecb;if(function(e){e.writing=!1,e.writecb=null,e.length-=e.writelen,e.writelen=0}(n),t)!function(e,t,n,r,o){--t.pendingcb,n?(i.nextTick(o,r),i.nextTick(O,e,t),e._writableState.errorEmitted=!0,e.emit("error",r)):(o(r),e._writableState.errorEmitted=!0,e.emit("error",r),O(e,t))}(e,n,r,t,o);else{var a=k(n);a||n.corked||n.bufferProcessing||!n.bufferedRequest||w(e,n),r?c(x,e,n,a,o):x(e,n,a,o)}}(t,e)},this.writecb=null,this.writelen=0,this.bufferedRequest=null,this.lastBufferedRequest=null,this.pendingcb=0,this.prefinished=!1,this.errorEmitted=!1,this.bufferedRequestCount=0,this.corkedRequestsFree=new a(this)}function y(e){if(s=s||n(68),!(h.call(y,this)||this instanceof s))return new y(e);this._writableState=new g(e,this),this.writable=!0,e&&("function"==typeof e.write&&(this._write=e.write),"function"==typeof e.writev&&(this._writev=e.writev),"function"==typeof e.destroy&&(this._destroy=e.destroy),"function"==typeof e.final&&(this._final=e.final)),p.call(this)}function b(e,t,n,r,o,i,a){t.writelen=r,t.writecb=a,t.writing=!0,t.sync=!0,n?e._writev(o,t.onwrite):e._write(o,i,t.onwrite),t.sync=!1}function x(e,t,n,r){n||function(e,t){0===t.length&&t.needDrain&&(t.needDrain=!1,e.emit("drain"))}(e,t),t.pendingcb--,r(),O(e,t)}function w(e,t){t.bufferProcessing=!0;var n=t.bufferedRequest;if(e._writev&&n&&n.next){var r=t.bufferedRequestCount,o=new Array(r),i=t.corkedRequestsFree;i.entry=n;for(var s=0,c=!0;n;)o[s]=n,n.isBuf||(c=!1),n=n.next,s+=1;o.allBuffers=c,b(e,t,!0,t.length,o,"",i.finish),t.pendingcb++,t.lastBufferedRequest=null,i.next?(t.corkedRequestsFree=i.next,i.next=null):t.corkedRequestsFree=new a(t),t.bufferedRequestCount=0}else{for(;n;){var l=n.chunk,u=n.encoding,p=n.callback;if(b(e,t,!1,t.objectMode?1:l.length,l,u,p),n=n.next,t.bufferedRequestCount--,t.writing)break}null===n&&(t.lastBufferedRequest=null)}t.bufferedRequest=n,t.bufferProcessing=!1}function k(e){return e.ending&&0===e.length&&null===e.bufferedRequest&&!e.finished&&!e.writing}function S(e,t){e._final((function(n){t.pendingcb--,n&&e.emit("error",n),t.prefinished=!0,e.emit("prefinish"),O(e,t)}))}function O(e,t){var n=k(t);return n&&(!function(e,t){t.prefinished||t.finalCalled||("function"==typeof e._final?(t.pendingcb++,t.finalCalled=!0,i.nextTick(S,e,t)):(t.prefinished=!0,e.emit("prefinish")))}(e,t),0===t.pendingcb&&(t.finished=!0,e.emit("finish"))),n}l.inherits(y,p),g.prototype.getBuffer=function(){for(var e=this.bufferedRequest,t=[];e;)t.push(e),e=e.next;return t},function(){try{Object.defineProperty(g.prototype,"buffer",{get:u.deprecate((function(){return this.getBuffer()}),"_writableState.buffer is deprecated. Use _writableState.getBuffer instead.","DEP0003")})}catch(e){}}(),"function"==typeof Symbol&&Symbol.hasInstance&&"function"==typeof Function.prototype[Symbol.hasInstance]?(h=Function.prototype[Symbol.hasInstance],Object.defineProperty(y,Symbol.hasInstance,{value:function(e){return!!h.call(this,e)||this===y&&(e&&e._writableState instanceof g)}})):h=function(e){return e instanceof this},y.prototype.pipe=function(){this.emit("error",new Error("Cannot pipe, not readable"))},y.prototype.write=function(e,t,n){var r,o=this._writableState,a=!1,s=!o.objectMode&&(r=e,f.isBuffer(r)||r instanceof d);return s&&!f.isBuffer(e)&&(e=function(e){return f.from(e)}(e)),"function"==typeof t&&(n=t,t=null),s?t="buffer":t||(t=o.defaultEncoding),"function"!=typeof n&&(n=m),o.ended?function(e,t){var n=new Error("write after end");e.emit("error",n),i.nextTick(t,n)}(this,n):(s||function(e,t,n,r){var o=!0,a=!1;return null===n?a=new TypeError("May not write null values to stream"):"string"==typeof n||void 0===n||t.objectMode||(a=new TypeError("Invalid non-string/buffer chunk")),a&&(e.emit("error",a),i.nextTick(r,a),o=!1),o}(this,o,e,n))&&(o.pendingcb++,a=function(e,t,n,r,o,i){if(!n){var a=function(e,t,n){e.objectMode||!1===e.decodeStrings||"string"!=typeof t||(t=f.from(t,n));return t}(t,r,o);r!==a&&(n=!0,o="buffer",r=a)}var s=t.objectMode?1:r.length;t.length+=s;var c=t.length-1))throw new TypeError("Unknown encoding: "+e);return this._writableState.defaultEncoding=e,this},Object.defineProperty(y.prototype,"writableHighWaterMark",{enumerable:!1,get:function(){return this._writableState.highWaterMark}}),y.prototype._write=function(e,t,n){n(new Error("_write() is not implemented"))},y.prototype._writev=null,y.prototype.end=function(e,t,n){var r=this._writableState;"function"==typeof e?(n=e,e=null,t=null):"function"==typeof t&&(n=t,t=null),null!=e&&this.write(e,t),r.corked&&(r.corked=1,this.uncork()),r.ending||r.finished||function(e,t,n){t.ending=!0,O(e,t),n&&(t.finished?i.nextTick(n):e.once("finish",n));t.ended=!0,e.writable=!1}(this,r,n)},Object.defineProperty(y.prototype,"destroyed",{get:function(){return void 0!==this._writableState&&this._writableState.destroyed},set:function(e){this._writableState&&(this._writableState.destroyed=e)}}),y.prototype.destroy=v.destroy,y.prototype._undestroy=v.undestroy,y.prototype._destroy=function(e,t){this.end(),t(e)}}).call(this,n(41),n(314).setImmediate,n(26))},function(e,t,n){"use strict";var r=n(107).Buffer,o=r.isEncoding||function(e){switch((e=""+e)&&e.toLowerCase()){case"hex":case"utf8":case"utf-8":case"ascii":case"binary":case"base64":case"ucs2":case"ucs-2":case"utf16le":case"utf-16le":case"raw":return!0;default:return!1}};function i(e){var t;switch(this.encoding=function(e){var t=function(e){if(!e)return"utf8";for(var t;;)switch(e){case"utf8":case"utf-8":return"utf8";case"ucs2":case"ucs-2":case"utf16le":case"utf-16le":return"utf16le";case"latin1":case"binary":return"latin1";case"base64":case"ascii":case"hex":return e;default:if(t)return;e=(""+e).toLowerCase(),t=!0}}(e);if("string"!=typeof t&&(r.isEncoding===o||!o(e)))throw new Error("Unknown encoding: "+e);return t||e}(e),this.encoding){case"utf16le":this.text=c,this.end=l,t=4;break;case"utf8":this.fillLast=s,t=4;break;case"base64":this.text=u,this.end=p,t=3;break;default:return this.write=f,void(this.end=d)}this.lastNeed=0,this.lastTotal=0,this.lastChar=r.allocUnsafe(t)}function a(e){return e<=127?0:e>>5==6?2:e>>4==14?3:e>>3==30?4:e>>6==2?-1:-2}function s(e){var t=this.lastTotal-this.lastNeed,n=function(e,t,n){if(128!=(192&t[0]))return e.lastNeed=0,"�";if(e.lastNeed>1&&t.length>1){if(128!=(192&t[1]))return e.lastNeed=1,"�";if(e.lastNeed>2&&t.length>2&&128!=(192&t[2]))return e.lastNeed=2,"�"}}(this,e);return void 0!==n?n:this.lastNeed<=e.length?(e.copy(this.lastChar,t,0,this.lastNeed),this.lastChar.toString(this.encoding,0,this.lastTotal)):(e.copy(this.lastChar,t,0,e.length),void(this.lastNeed-=e.length))}function c(e,t){if((e.length-t)%2==0){var n=e.toString("utf16le",t);if(n){var r=n.charCodeAt(n.length-1);if(r>=55296&&r<=56319)return this.lastNeed=2,this.lastTotal=4,this.lastChar[0]=e[e.length-2],this.lastChar[1]=e[e.length-1],n.slice(0,-1)}return n}return this.lastNeed=1,this.lastTotal=2,this.lastChar[0]=e[e.length-1],e.toString("utf16le",t,e.length-1)}function l(e){var t=e&&e.length?this.write(e):"";if(this.lastNeed){var n=this.lastTotal-this.lastNeed;return t+this.lastChar.toString("utf16le",0,n)}return t}function u(e,t){var n=(e.length-t)%3;return 0===n?e.toString("base64",t):(this.lastNeed=3-n,this.lastTotal=3,1===n?this.lastChar[0]=e[e.length-1]:(this.lastChar[0]=e[e.length-2],this.lastChar[1]=e[e.length-1]),e.toString("base64",t,e.length-n))}function p(e){var t=e&&e.length?this.write(e):"";return this.lastNeed?t+this.lastChar.toString("base64",0,3-this.lastNeed):t}function f(e){return e.toString(this.encoding)}function d(e){return e&&e.length?this.write(e):""}t.StringDecoder=i,i.prototype.write=function(e){if(0===e.length)return"";var t,n;if(this.lastNeed){if(void 0===(t=this.fillLast(e)))return"";n=this.lastNeed,this.lastNeed=0}else n=0;return n=0)return o>0&&(e.lastNeed=o-1),o;if(--r=0)return o>0&&(e.lastNeed=o-2),o;if(--r=0)return o>0&&(2===o?o=0:e.lastNeed=o-3),o;return 0}(this,e,t);if(!this.lastNeed)return e.toString("utf8",t);this.lastTotal=n;var r=e.length-(n-this.lastNeed);return e.copy(this.lastChar,0,r),e.toString("utf8",t,r)},i.prototype.fillLast=function(e){if(this.lastNeed<=e.length)return e.copy(this.lastChar,this.lastTotal-this.lastNeed,0,this.lastNeed),this.lastChar.toString(this.encoding,0,this.lastTotal);e.copy(this.lastChar,this.lastTotal-this.lastNeed,0,e.length),this.lastNeed-=e.length}},function(e,t,n){"use strict";e.exports=a;var r=n(68),o=n(84);function i(e,t){var n=this._transformState;n.transforming=!1;var r=n.writecb;if(!r)return this.emit("error",new Error("write callback called multiple times"));n.writechunk=null,n.writecb=null,null!=t&&this.push(t),r(e);var o=this._readableState;o.reading=!1,(o.needReadable||o.lengthc;)o.f(e,n=r[c++],t[n]);return e}},function(e,t,n){var r=n(29),o=n(42),i=n(138).indexOf,a=n(87);e.exports=function(e,t){var n,s=o(e),c=0,l=[];for(n in s)!r(a,n)&&r(s,n)&&l.push(n);for(;t.length>c;)r(s,n=t[c++])&&(~i(l,n)||l.push(n));return l}},function(e,t,n){n(55);var r=n(341),o=n(72),i=Array.prototype,a={DOMTokenList:!0,NodeList:!0};e.exports=function(e){var t=e.forEach;return e===i||e instanceof Array&&t===i.forEach||a.hasOwnProperty(o(e))?r:t}},function(e,t,n){"use strict";var r=n(42),o=n(143),i=n(61),a=n(56),s=n(201),c=a.set,l=a.getterFor("Array Iterator");e.exports=s(Array,"Array",(function(e,t){c(this,{type:"Array Iterator",target:r(e),index:0,kind:t})}),(function(){var e=l(this),t=e.target,n=e.kind,r=e.index++;return!t||r>=t.length?(e.target=void 0,{value:void 0,done:!0}):"keys"==n?{value:r,done:!1}:"values"==n?{value:t[r],done:!1}:{value:[r,t[r]],done:!1}}),"values"),i.Arguments=i.Array,o("keys"),o("values"),o("entries")},function(e,t,n){var r=n(16),o=n(199),i=r.WeakMap;e.exports="function"==typeof i&&/native code/.test(o(i))},function(e,t,n){var r=n(200),o=Function.toString;"function"!=typeof r.inspectSource&&(r.inspectSource=function(e){return o.call(e)}),e.exports=r.inspectSource},function(e,t,n){var r=n(16),o=n(336),i=r["__core-js_shared__"]||o("__core-js_shared__",{});e.exports=i},function(e,t,n){"use strict";var r=n(7),o=n(337),i=n(89),a=n(147),s=n(62),c=n(37),l=n(91),u=n(14),p=n(71),f=n(61),d=n(202),h=d.IteratorPrototype,v=d.BUGGY_SAFARI_ITERATORS,m=u("iterator"),g=function(){return this};e.exports=function(e,t,n,u,d,y,b){o(n,t,u);var x,w,k,S=function(e){if(e===d&&j)return j;if(!v&&e in E)return E[e];switch(e){case"keys":case"values":case"entries":return function(){return new n(this,e)}}return function(){return new n(this)}},O=t+" Iterator",A=!1,E=e.prototype,_=E[m]||E["@@iterator"]||d&&E[d],j=!v&&_||S(d),T="Array"==t&&E.entries||_;if(T&&(x=i(T.call(new e)),h!==Object.prototype&&x.next&&(p||i(x)===h||(a?a(x,h):"function"!=typeof x[m]&&c(x,m,g)),s(x,O,!0,!0),p&&(f[O]=g))),"values"==d&&_&&"values"!==_.name&&(A=!0,j=function(){return _.call(this)}),p&&!b||E[m]===j||c(E,m,j),f[t]=j,d)if(w={values:S("values"),keys:y?j:S("keys"),entries:S("entries")},b)for(k in w)(v||A||!(k in E))&&l(E,k,w[k]);else r({target:t,proto:!0,forced:v||A},w);return w}},function(e,t,n){"use strict";var r,o,i,a=n(89),s=n(37),c=n(29),l=n(14),u=n(71),p=l("iterator"),f=!1;[].keys&&("next"in(i=[].keys())?(o=a(a(i)))!==Object.prototype&&(r=o):f=!0),null==r&&(r={}),u||c(r,p)||s(r,p,(function(){return this})),e.exports={IteratorPrototype:r,BUGGY_SAFARI_ITERATORS:f}},function(e,t,n){var r=n(13);e.exports=!r((function(){function e(){}return e.prototype.constructor=null,Object.getPrototypeOf(new e)!==e.prototype}))},function(e,t,n){var r=n(145);e.exports=r&&!Symbol.sham&&"symbol"==typeof Symbol.iterator},function(e,t,n){var r=n(43);e.exports=r("document","documentElement")},function(e,t,n){e.exports=n(345)},function(e,t,n){var r=n(350),o=Array.prototype;e.exports=function(e){var t=e.filter;return e===o||e instanceof Array&&t===o.filter?r:t}},function(e,t,n){var r=n(43);e.exports=r("navigator","userAgent")||""},function(e,t,n){e.exports=n(352)},function(e,t,n){"use strict";var r=n(7),o=n(16),i=n(43),a=n(71),s=n(22),c=n(145),l=n(204),u=n(13),p=n(29),f=n(92),d=n(17),h=n(30),v=n(44),m=n(42),g=n(111),y=n(70),b=n(90),x=n(86),w=n(141),k=n(354),S=n(142),O=n(69),A=n(38),E=n(108),_=n(37),j=n(91),T=n(144),C=n(113),I=n(87),P=n(114),R=n(14),L=n(150),N=n(18),M=n(62),B=n(56),D=n(49).forEach,q=C("hidden"),F=R("toPrimitive"),z=B.set,U=B.getterFor("Symbol"),$=Object.prototype,H=o.Symbol,W=i("JSON","stringify"),V=O.f,Y=A.f,K=k.f,G=E.f,J=T("symbols"),Q=T("op-symbols"),X=T("string-to-symbol-registry"),Z=T("symbol-to-string-registry"),ee=T("wks"),te=o.QObject,ne=!te||!te.prototype||!te.prototype.findChild,re=s&&u((function(){return 7!=b(Y({},"a",{get:function(){return Y(this,"a",{value:7}).a}})).a}))?function(e,t,n){var r=V($,t);r&&delete $[t],Y(e,t,n),r&&e!==$&&Y($,t,r)}:Y,oe=function(e,t){var n=J[e]=b(H.prototype);return z(n,{type:"Symbol",tag:e,description:t}),s||(n.description=t),n},ie=l?function(e){return"symbol"==typeof e}:function(e){return Object(e)instanceof H},ae=function(e,t,n){e===$&&ae(Q,t,n),h(e);var r=g(t,!0);return h(n),p(J,r)?(n.enumerable?(p(e,q)&&e[q][r]&&(e[q][r]=!1),n=b(n,{enumerable:y(0,!1)})):(p(e,q)||Y(e,q,y(1,{})),e[q][r]=!0),re(e,r,n)):Y(e,r,n)},se=function(e,t){h(e);var n=m(t),r=x(n).concat(pe(n));return D(r,(function(t){s&&!ce.call(n,t)||ae(e,t,n[t])})),e},ce=function(e){var t=g(e,!0),n=G.call(this,t);return!(this===$&&p(J,t)&&!p(Q,t))&&(!(n||!p(this,t)||!p(J,t)||p(this,q)&&this[q][t])||n)},le=function(e,t){var n=m(e),r=g(t,!0);if(n!==$||!p(J,r)||p(Q,r)){var o=V(n,r);return!o||!p(J,r)||p(n,q)&&n[q][r]||(o.enumerable=!0),o}},ue=function(e){var t=K(m(e)),n=[];return D(t,(function(e){p(J,e)||p(I,e)||n.push(e)})),n},pe=function(e){var t=e===$,n=K(t?Q:m(e)),r=[];return D(n,(function(e){!p(J,e)||t&&!p($,e)||r.push(J[e])})),r};(c||(j((H=function(){if(this instanceof H)throw TypeError("Symbol is not a constructor");var e=arguments.length&&void 0!==arguments[0]?String(arguments[0]):void 0,t=P(e),n=function(e){this===$&&n.call(Q,e),p(this,q)&&p(this[q],t)&&(this[q][t]=!1),re(this,t,y(1,e))};return s&&ne&&re($,t,{configurable:!0,set:n}),oe(t,e)}).prototype,"toString",(function(){return U(this).tag})),j(H,"withoutSetter",(function(e){return oe(P(e),e)})),E.f=ce,A.f=ae,O.f=le,w.f=k.f=ue,S.f=pe,L.f=function(e){return oe(R(e),e)},s&&(Y(H.prototype,"description",{configurable:!0,get:function(){return U(this).description}}),a||j($,"propertyIsEnumerable",ce,{unsafe:!0}))),r({global:!0,wrap:!0,forced:!c,sham:!c},{Symbol:H}),D(x(ee),(function(e){N(e)})),r({target:"Symbol",stat:!0,forced:!c},{for:function(e){var t=String(e);if(p(X,t))return X[t];var n=H(t);return X[t]=n,Z[n]=t,n},keyFor:function(e){if(!ie(e))throw TypeError(e+" is not a symbol");if(p(Z,e))return Z[e]},useSetter:function(){ne=!0},useSimple:function(){ne=!1}}),r({target:"Object",stat:!0,forced:!c,sham:!s},{create:function(e,t){return void 0===t?b(e):se(b(e),t)},defineProperty:ae,defineProperties:se,getOwnPropertyDescriptor:le}),r({target:"Object",stat:!0,forced:!c},{getOwnPropertyNames:ue,getOwnPropertySymbols:pe}),r({target:"Object",stat:!0,forced:u((function(){S.f(1)}))},{getOwnPropertySymbols:function(e){return S.f(v(e))}}),W)&&r({target:"JSON",stat:!0,forced:!c||u((function(){var e=H();return"[null]"!=W([e])||"{}"!=W({a:e})||"{}"!=W(Object(e))}))},{stringify:function(e,t,n){for(var r,o=[e],i=1;arguments.length>i;)o.push(arguments[i++]);if(r=t,(d(t)||void 0!==e)&&!ie(e))return f(t)||(t=function(e,t){if("function"==typeof r&&(t=r.call(this,e,t)),!ie(t))return t}),o[1]=t,W.apply(null,o)}});H.prototype[F]||_(H.prototype,F,H.prototype.valueOf),M(H,"Symbol"),I[q]=!0},function(e,t,n){e.exports=n(355)},function(e,t,n){n(356);var r=n(12);e.exports=r.Object.keys},function(e,t,n){"use strict";var r=n(48),o=n(17),i=[].slice,a={},s=function(e,t,n){if(!(t in a)){for(var r=[],o=0;o=51||!o((function(){var e=[];return e[h]=!1,e.concat()[0]!==e})),m=p("concat"),g=function(e){if(!a(e))return!1;var t=e[h];return void 0!==t?!!t:i(e)};r({target:"Array",proto:!0,forced:!v||!m},{concat:function(e){var t,n,r,o,i,a=s(this),p=u(a,0),f=0;for(t=-1,r=arguments.length;t9007199254740991)throw TypeError("Maximum allowed index exceeded");for(n=0;n=9007199254740991)throw TypeError("Maximum allowed index exceeded");l(p,f++,i)}return p.length=f,p}})},function(e,t,n){n(18)("iterator")},function(e,t,n){e.exports=n(414)},function(e,t,n){n(76),n(415);var r=n(12);e.exports=r.Array.from},function(e,t,n){var r=n(30);e.exports=function(e,t,n,o){try{return o?t(r(n)[0],n[1]):t(n)}catch(t){var i=e.return;throw void 0!==i&&r(i.call(e)),t}}},function(e,t,n){var r=n(14),o=n(61),i=r("iterator"),a=Array.prototype;e.exports=function(e){return void 0!==e&&(o.Array===e||a[i]===e)}},function(e,t,n){var r=n(14)("iterator"),o=!1;try{var i=0,a={next:function(){return{done:!!i++}},return:function(){o=!0}};a[r]=function(){return this},Array.from(a,(function(){throw 2}))}catch(e){}e.exports=function(e,t){if(!t&&!o)return!1;var n=!1;try{var i={};i[r]=function(){return{next:function(){return{done:n=!0}}}},e(i)}catch(e){}return n}},function(e,t,n){var r=n(419),o=Array.prototype;e.exports=function(e){var t=e.slice;return e===o||e instanceof Array&&t===o.slice?r:t}},function(e,t){e.exports=function(e,t){(null==t||t>e.length)&&(t=e.length);for(var n=0,r=new Array(t);nn;)t.push(arguments[n++]);return x[++b]=function(){("function"==typeof e?e:Function(e)).apply(void 0,t)},r(b),b},v=function(e){delete x[e]},"process"==c(m)?r=function(e){m.nextTick(k(e))}:y&&y.now?r=function(e){y.now(k(e))}:g&&!f?(i=(o=new g).port2,o.port1.onmessage=S,r=l(i.postMessage,i,1)):!a.addEventListener||"function"!=typeof postMessage||a.importScripts||s(O)||"file:"===d.protocol?r="onreadystatechange"in p("script")?function(e){u.appendChild(p("script")).onreadystatechange=function(){u.removeChild(this),w(e)}}:function(e){setTimeout(k(e),0)}:(r=O,a.addEventListener("message",S,!1))),e.exports={set:h,clear:v}},function(e,t,n){var r=n(208);e.exports=/(iphone|ipod|ipad).*applewebkit/i.test(r)},function(e,t,n){var r=n(30),o=n(17),i=n(97);e.exports=function(e,t){if(r(e),o(t)&&t.constructor===e)return t;var n=i.f(e);return(0,n.resolve)(t),n.promise}},function(e,t,n){"use strict";var r=n(7),o=n(48),i=n(97),a=n(123),s=n(77);r({target:"Promise",stat:!0},{allSettled:function(e){var t=this,n=i.f(t),r=n.resolve,c=n.reject,l=a((function(){var n=o(t.resolve),i=[],a=0,c=1;s(e,(function(e){var o=a++,s=!1;i.push(void 0),c++,n.call(t,e).then((function(e){s||(s=!0,i[o]={status:"fulfilled",value:e},--c||r(i))}),(function(e){s||(s=!0,i[o]={status:"rejected",reason:e},--c||r(i))}))})),--c||r(i)}));return l.error&&c(l.value),n.promise}})},function(e,t,n){"use strict";var r=n(79);e.exports=new r({include:[n(247)]})},function(e,t,n){"use strict";var r=n(79);e.exports=new r({include:[n(161)],implicit:[n(492),n(493),n(494),n(495)]})},function(e,t,n){var r=n(530),o=n(531);e.exports=function(e,t){return null!=e&&o(e,t,r)}},function(e,t,n){e.exports=n(549)},function(e,t,n){n(554);var r=n(12).Object;e.exports=function(e,t){return r.create(e,t)}},function(e,t,n){e.exports=n(556)},function(e,t,n){var r=n(577),o=n(600),i=n(151),a=n(31),s=n(601);e.exports=function(e){return"function"==typeof e?e:null==e?i:"object"==typeof e?a(e)?o(e[0],e[1]):r(e):s(e)}},function(e,t,n){var r=n(584),o=n(50);e.exports=function e(t,n,i,a,s){return t===n||(null==t||null==n||!o(t)&&!o(n)?t!=t&&n!=n:r(t,n,i,a,e,s))}},function(e,t,n){var r=n(585),o=n(588),i=n(589);e.exports=function(e,t,n,a,s,c){var l=1&n,u=e.length,p=t.length;if(u!=p&&!(l&&p>u))return!1;var f=c.get(e);if(f&&c.get(t))return f==t;var d=-1,h=!0,v=2&n?new r:void 0;for(c.set(e,t),c.set(t,e);++de.length)&&(t=e.length);for(var n=0,r=new Array(t);n1&&k("Lost properties from oneOf",e,n),delete e.oneOf)}e.type&&Array.isArray(e.type)&&1===e.type.length&&(e.type=e.type[0])}else w("(Patchable) schema type must not be an array",n);e.type&&"null"===e.type&&(delete e.type,e.nullable=!0),"array"!==e.type||e.items||(e.items={}),"file"===e.type&&(e.type="string",e.format="binary"),"boolean"==typeof e.required&&(e.required&&e.name&&(void 0===t.required&&(t.required=[]),Array.isArray(t.required)&&t.required.push(e.name)),delete e.required),e.xml&&"string"==typeof e.xml.namespace&&(e.xml.namespace||delete e.xml.namespace),void 0!==e.allowEmptyValue&&(n.patches++,delete e.allowEmptyValue)}(e,n,t)}))}function O(e,t,n){let r=n.payload.options;if(u(e,t)){if(e[t].startsWith("#/components/"));else if("#/consumes"===e[t])delete e[t],n.parent[n.pkey]=p(r.openapi.consumes);else if("#/produces"===e[t])delete e[t],n.parent[n.pkey]=p(r.openapi.produces);else if(e[t].startsWith("#/definitions/")){let n=e[t].replace("#/definitions/","").split("/");const o=c.jpunescape(n[0]);let i=b.schemas[decodeURIComponent(o)];i?n[0]=i:k("Could not resolve reference "+e[t],e,r),e[t]="#/components/schemas/"+n.join("/")}else if(e[t].startsWith("#/parameters/"))e[t]="#/components/parameters/"+m.sanitise(e[t].replace("#/parameters/",""));else if(e[t].startsWith("#/responses/"))e[t]="#/components/responses/"+m.sanitise(e[t].replace("#/responses/",""));else if(e[t].startsWith("#")){let n=p(c.jptr(r.openapi,e[t]));if(!1===n)k("direct $ref not found "+e[t],e,r);else if(r.refmap[e[t]])e[t]=r.refmap[e[t]];else{let i=e[t];i=i.replace("/properties/headers/",""),i=i.replace("/properties/responses/",""),i=i.replace("/properties/parameters/",""),i=i.replace("/properties/schemas/","");let a="schemas",s=i.lastIndexOf("/schema");if(a=i.indexOf("/headers/")>s?"headers":i.indexOf("/responses/")>s?"responses":i.indexOf("/example")>s?"examples":i.indexOf("/x-")>s?"extensions":i.indexOf("/parameters/")>s?"parameters":"schemas","schemas"===a&&S(n,r),"responses"!==a&&"extensions"!==a){let i=a.substr(0,a.length-1);"parameter"===i&&n.name&&n.name===m.sanitise(n.name)&&(i=encodeURIComponent(n.name));let s=1;for(e["x-miro"]&&(o=(o=e["x-miro"]).indexOf("#")>=0?o.split("#")[1].split("/").pop():o.split("/").pop().split(".")[0],i=encodeURIComponent(m.sanitise(o)),s="");c.jptr(r.openapi,"#/components/"+a+"/"+i+s);)s=""===s?2:++s;let l="#/components/"+a+"/"+i+s,u="";"examples"===a&&(n={value:n},u="/value"),c.jptr(r.openapi,l,n),r.refmap[e[t]]=l+u,e[t]=l+u}}}if(delete e["x-miro"],Object.keys(e).length>1){const o=e[t],i=n.path.indexOf("/schema")>=0;"preserve"===r.refSiblings||(i&&"allOf"===r.refSiblings?(delete e.$ref,n.parent[n.pkey]={allOf:[{$ref:o},e]}):n.parent[n.pkey]={$ref:o})}}var o;if("x-ms-odata"===t&&"string"==typeof e[t]&&e[t].startsWith("#/")){let n=e[t].replace("#/definitions/","").replace("#/components/schemas/","").split("/"),o=b.schemas[decodeURIComponent(n[0])];o?n[0]=o:k("Could not resolve reference "+e[t],e,r),e[t]="#/components/schemas/"+n.join("/")}}function A(e){for(let t in e)for(let n in e[t]){let r=m.sanitise(n);n!==r&&(e[t][r]=e[t][n],delete e[t][n])}}function E(e,t){if("basic"===e.type&&(e.type="http",e.scheme="basic"),"oauth2"===e.type){let n={},r=e.flow;"application"===e.flow&&(r="clientCredentials"),"accessCode"===e.flow&&(r="authorizationCode"),void 0!==e.authorizationUrl&&(n.authorizationUrl=e.authorizationUrl.split("?")[0].trim()||"/"),"string"==typeof e.tokenUrl&&(n.tokenUrl=e.tokenUrl.split("?")[0].trim()||"/"),n.scopes=e.scopes||{},e.flows={},e.flows[r]=n,delete e.flow,delete e.authorizationUrl,delete e.tokenUrl,delete e.scopes,void 0!==e.name&&(t.patch?(t.patches++,delete e.name):w("(Patchable) oauth2 securitySchemes should not have name property",t))}}function _(e){return e&&!e["x-s2o-delete"]}function j(e,t){if(e.$ref)e.$ref=e.$ref.replace("#/responses/","#/components/responses/");else{e.type&&!e.schema&&(e.schema={}),e.type&&(e.schema.type=e.type),e.items&&"array"!==e.items.type&&(e.items.collectionFormat!==e.collectionFormat&&k("Nested collectionFormats are not supported",e,t),delete e.items.collectionFormat),"array"===e.type?("ssv"===e.collectionFormat?k("collectionFormat:ssv is no longer supported for headers",e,t):"pipes"===e.collectionFormat?k("collectionFormat:pipes is no longer supported for headers",e,t):"multi"===e.collectionFormat?e.explode=!0:"tsv"===e.collectionFormat?(k("collectionFormat:tsv is no longer supported",e,t),e["x-collectionFormat"]="tsv"):e.style="simple",delete e.collectionFormat):e.collectionFormat&&(t.patch?(t.patches++,delete e.collectionFormat):w("(Patchable) collectionFormat is only applicable to header.type array",t)),delete e.type;for(let t of m.parameterTypeProperties)void 0!==e[t]&&(e.schema[t]=e[t],delete e[t]);for(let t of m.arrayProperties)void 0!==e[t]&&(e.schema[t]=e[t],delete e[t])}}function T(e,t){if(e.$ref.indexOf("#/parameters/")>=0){let t=e.$ref.split("#/parameters/");e.$ref=t[0]+"#/components/parameters/"+m.sanitise(t[1])}e.$ref.indexOf("#/definitions/")>=0&&k("Definition used as parameter",e,t)}function C(e,t,n,r,o,i,a){let s,c={},u=!0;if(t&&t.consumes&&"string"==typeof t.consumes){if(!a.patch)return w("(Patchable) operation.consumes must be an array",a);a.patches++,t.consumes=[t.consumes]}Array.isArray(i.consumes)||delete i.consumes;let f=((t?t.consumes:null)||i.consumes||[]).filter(m.uniqueOnly);if(e&&e.$ref&&"string"==typeof e.$ref){T(e,a);let t=decodeURIComponent(e.$ref.replace("#/components/parameters/","")),n=!1,r=i.components.parameters[t];if(r&&!r["x-s2o-delete"]||!e.$ref.startsWith("#/")||(e["x-s2o-delete"]=!0,n=!0),n){let t=e.$ref,n=l(i,e.$ref);!n&&t.startsWith("#/")?k("Could not resolve reference "+t,e,a):n&&(e=n)}}if(e&&(e.name||e.in)){"boolean"==typeof e["x-deprecated"]&&(e.deprecated=e["x-deprecated"],delete e["x-deprecated"]),void 0!==e["x-example"]&&(e.example=e["x-example"],delete e["x-example"]),"body"===e.in||e.type||(a.patch?(a.patches++,e.type="string"):w("(Patchable) parameter.type is mandatory for non-body parameters",a)),e.type&&"object"==typeof e.type&&e.type.$ref&&(e.type=l(i,e.type.$ref)),"file"===e.type&&(e["x-s2o-originalType"]=e.type,s=e.type),e.description&&"object"==typeof e.description&&e.description.$ref&&(e.description=l(i,e.description.$ref)),null===e.description&&delete e.description;let t=e.collectionFormat;if("array"!==e.type||t||(t="csv"),t&&("array"!==e.type&&(a.patch?(a.patches++,delete e.collectionFormat):w("(Patchable) collectionFormat is only applicable to param.type array",a)),"csv"!==t||"query"!==e.in&&"cookie"!==e.in||(e.style="form",e.explode=!1),"csv"!==t||"path"!==e.in&&"header"!==e.in||(e.style="simple"),"ssv"===t&&("query"===e.in?e.style="spaceDelimited":k("collectionFormat:ssv is no longer supported except for in:query parameters",e,a)),"pipes"===t&&("query"===e.in?e.style="pipeDelimited":k("collectionFormat:pipes is no longer supported except for in:query parameters",e,a)),"multi"===t&&(e.explode=!0),"tsv"===t&&(k("collectionFormat:tsv is no longer supported",e,a),e["x-collectionFormat"]="tsv"),delete e.collectionFormat),e.type&&"body"!==e.type&&"formData"!==e.in)if(e.items&&e.schema)k("parameter has array,items and schema",e,a);else{e.schema&&a.patches++,e.schema&&"object"==typeof e.schema||(e.schema={}),e.schema.type=e.type,e.items&&(e.schema.items=e.items,delete e.items,d(e.schema.items,null,(function(n,r,o){"collectionFormat"===r&&"string"==typeof n[r]&&(t&&n[r]!==t&&k("Nested collectionFormats are not supported",e,a),delete n[r])})));for(let t of m.parameterTypeProperties)void 0!==e[t]&&(e.schema[t]=e[t]),delete e[t]}e.schema&&S(e.schema,a),e["x-ms-skip-url-encoding"]&&"query"===e.in&&(e.allowReserved=!0,delete e["x-ms-skip-url-encoding"])}if(e&&"formData"===e.in){u=!1,c.content={};let t="application/x-www-form-urlencoded";if(f.length&&f.indexOf("multipart/form-data")>=0&&(t="multipart/form-data"),c.content[t]={},e.schema)c.content[t].schema=e.schema,e.schema.$ref&&(c["x-s2o-name"]=decodeURIComponent(e.schema.$ref.replace("#/components/schemas/","")));else{c.content[t].schema={},c.content[t].schema.type="object",c.content[t].schema.properties={},c.content[t].schema.properties[e.name]={};let n=c.content[t].schema,r=c.content[t].schema.properties[e.name];e.description&&(r.description=e.description),e.example&&(r.example=e.example),e.type&&(r.type=e.type);for(let t of m.parameterTypeProperties)void 0!==e[t]&&(r[t]=e[t]);!0===e.required&&(n.required||(n.required=[]),n.required.push(e.name),c.required=!0),void 0!==e.default&&(r.default=e.default),r.properties&&(r.properties=e.properties),e.allOf&&(r.allOf=e.allOf),"array"===e.type&&e.items&&(r.items=e.items,r.items.collectionFormat&&delete r.items.collectionFormat),"file"!==s&&"file"!==e["x-s2o-originalType"]||(r.type="string",r.format="binary"),I(e,r)}}else e&&"file"===e.type&&(e.required&&(c.required=e.required),c.content={},c.content["application/octet-stream"]={},c.content["application/octet-stream"].schema={},c.content["application/octet-stream"].schema.type="string",c.content["application/octet-stream"].schema.format="binary",I(e,c));if(e&&"body"===e.in){c.content={},e.name&&(c["x-s2o-name"]=(t&&t.operationId?m.sanitiseAll(t.operationId):"")+("_"+e.name).toCamelCase()),e.description&&(c.description=e.description),e.required&&(c.required=e.required),t&&a.rbname&&e.name&&(t[a.rbname]=e.name),e.schema&&e.schema.$ref?c["x-s2o-name"]=decodeURIComponent(e.schema.$ref.replace("#/components/schemas/","")):e.schema&&"array"===e.schema.type&&e.schema.items&&e.schema.items.$ref&&(c["x-s2o-name"]=decodeURIComponent(e.schema.items.$ref.replace("#/components/schemas/",""))+"Array"),f.length||f.push("application/json");for(let t of f)c.content[t]={},c.content[t].schema=p(e.schema||{}),S(c.content[t].schema,a);I(e,c)}if(Object.keys(c).length>0&&(e["x-s2o-delete"]=!0,t))if(t.requestBody&&u){t.requestBody["x-s2o-overloaded"]=!0,k("Operation "+(t.operationId||o)+" has multiple requestBodies",t,a)}else t.requestBody||(t=n[r]=function(e,t){let n={};for(let r of Object.keys(e))n[r]=e[r],"parameters"===r&&(n.requestBody={},t.rbname&&(n[t.rbname]=""));return n.requestBody={},n}(t,a)),t.requestBody.content&&t.requestBody.content["multipart/form-data"]&&t.requestBody.content["multipart/form-data"].schema&&t.requestBody.content["multipart/form-data"].schema.properties&&c.content["multipart/form-data"]&&c.content["multipart/form-data"].schema&&c.content["multipart/form-data"].schema.properties?(t.requestBody.content["multipart/form-data"].schema.properties=Object.assign(t.requestBody.content["multipart/form-data"].schema.properties,c.content["multipart/form-data"].schema.properties),t.requestBody.content["multipart/form-data"].schema.required=(t.requestBody.content["multipart/form-data"].schema.required||[]).concat(c.content["multipart/form-data"].schema.required||[]),t.requestBody.content["multipart/form-data"].schema.required.length||delete t.requestBody.content["multipart/form-data"].schema.required):t.requestBody.content&&t.requestBody.content["application/x-www-form-urlencoded"]&&t.requestBody.content["application/x-www-form-urlencoded"].schema&&t.requestBody.content["application/x-www-form-urlencoded"].schema.properties&&c.content["application/x-www-form-urlencoded"]&&c.content["application/x-www-form-urlencoded"].schema&&c.content["application/x-www-form-urlencoded"].schema.properties?(t.requestBody.content["application/x-www-form-urlencoded"].schema.properties=Object.assign(t.requestBody.content["application/x-www-form-urlencoded"].schema.properties,c.content["application/x-www-form-urlencoded"].schema.properties),t.requestBody.content["application/x-www-form-urlencoded"].schema.required=(t.requestBody.content["application/x-www-form-urlencoded"].schema.required||[]).concat(c.content["application/x-www-form-urlencoded"].schema.required||[]),t.requestBody.content["application/x-www-form-urlencoded"].schema.required.length||delete t.requestBody.content["application/x-www-form-urlencoded"].schema.required):(t.requestBody=Object.assign(t.requestBody,c),t.requestBody["x-s2o-name"]||(t.requestBody.schema&&t.requestBody.schema.$ref?t.requestBody["x-s2o-name"]=decodeURIComponent(t.requestBody.schema.$ref.replace("#/components/schemas/","")).split("/").join(""):t.operationId&&(t.requestBody["x-s2o-name"]=m.sanitiseAll(t.operationId))));if(e&&!e["x-s2o-delete"]){delete e.type;for(let t of m.parameterTypeProperties)delete e[t];"path"!==e.in||void 0!==e.required&&!0===e.required||(a.patch?(a.patches++,e.required=!0):w("(Patchable) path parameters must be required:true ["+e.name+" in "+o+"]",a))}return t}function I(e,t){for(let n in e)n.startsWith("x-")&&!n.startsWith("x-s2o")&&(t[n]=e[n])}function P(e,t,n,r,o){if(!e)return!1;if(e.$ref&&"string"==typeof e.$ref)e.$ref.indexOf("#/definitions/")>=0?k("definition used as response: "+e.$ref,e,o):e.$ref.startsWith("#/responses/")&&(e.$ref="#/components/responses/"+m.sanitise(decodeURIComponent(e.$ref.replace("#/responses/",""))));else{if((void 0===e.description||null===e.description||""===e.description&&o.patch)&&(o.patch?"object"!=typeof e||Array.isArray(e)||(o.patches++,e.description=g[e]||""):w("(Patchable) response.description is mandatory",o)),void 0!==e.schema){if(S(e.schema,o),e.schema.$ref&&"string"==typeof e.schema.$ref&&e.schema.$ref.startsWith("#/responses/")&&(e.schema.$ref="#/components/responses/"+m.sanitise(decodeURIComponent(e.schema.$ref.replace("#/responses/","")))),n&&n.produces&&"string"==typeof n.produces){if(!o.patch)return w("(Patchable) operation.produces must be an array",o);o.patches++,n.produces=[n.produces]}r.produces&&!Array.isArray(r.produces)&&delete r.produces;let t=((n?n.produces:null)||r.produces||[]).filter(m.uniqueOnly);t.length||t.push("*/*"),e.content={};for(let n of t){if(e.content[n]={},e.content[n].schema=p(e.schema),e.examples&&e.examples[n]){let t={};t.value=e.examples[n],e.content[n].examples={},e.content[n].examples.response=t,delete e.examples[n]}"file"===e.content[n].schema.type&&(e.content[n].schema={type:"string",format:"binary"})}delete e.schema}for(let t in e.examples)e.content||(e.content={}),e.content[t]||(e.content[t]={}),e.content[t].examples={},e.content[t].examples.response={},e.content[t].examples.response.value=e.examples[t];if(delete e.examples,e.headers)for(let t in e.headers)"status code"===t.toLowerCase()?o.patch?(o.patches++,delete e.headers[t]):w('(Patchable) "Status Code" is not a valid header',o):j(e.headers[t],o)}}function R(e,t,n,r,i){for(let a in e){let s=e[a];s&&s["x-trace"]&&"object"==typeof s["x-trace"]&&(s.trace=s["x-trace"],delete s["x-trace"]),s&&s["x-summary"]&&"string"==typeof s["x-summary"]&&(s.summary=s["x-summary"],delete s["x-summary"]),s&&s["x-description"]&&"string"==typeof s["x-description"]&&(s.description=s["x-description"],delete s["x-description"]),s&&s["x-servers"]&&Array.isArray(s["x-servers"])&&(s.servers=s["x-servers"],delete s["x-servers"]);for(let e in s)if(m.httpMethods.indexOf(e)>=0||"x-amazon-apigateway-any-method"===e){let u=s[e];if(u&&u.parameters&&Array.isArray(u.parameters)){if(s.parameters)for(let t of s.parameters){"string"==typeof t.$ref&&(T(t,n),t=l(i,t.$ref)),u.parameters.find((function(e,n,r){return e.name===t.name&&e.in===t.in}))||"formData"!==t.in&&"body"!==t.in&&"file"!==t.type||(u=C(t,u,s,e,a,i,n),n.rbname&&""===u[n.rbname]&&delete u[n.rbname])}for(let t of u.parameters)u=C(t,u,s,e,e+":"+a,i,n);n.rbname&&""===u[n.rbname]&&delete u[n.rbname],n.debug||u.parameters&&(u.parameters=u.parameters.filter(_))}if(u&&u.security&&A(u.security),"object"==typeof u){if(!u.responses){let e={description:"Default response"};u.responses={default:e}}for(let e in u.responses){P(u.responses[e],0,u,i,n)}}if(u&&u["x-servers"]&&Array.isArray(u["x-servers"]))u.servers=u["x-servers"],delete u["x-servers"];else if(u&&u.schemes&&u.schemes.length)for(let e of u.schemes)if((!i.schemes||i.schemes.indexOf(e)<0)&&(u.servers||(u.servers=[]),Array.isArray(i.servers)))for(let t of i.servers){let n=p(t),r=o.parse(n.url);r.protocol=e,n.url=r.format(),u.servers.push(n)}if(n.debug&&(u["x-s2o-consumes"]=u.consumes||[],u["x-s2o-produces"]=u.produces||[]),u){if(delete u.consumes,delete u.produces,delete u.schemes,u["x-ms-examples"]){for(let e in u["x-ms-examples"]){let t=u["x-ms-examples"][e],n=m.sanitiseAll(e);if(t.parameters)for(let n in t.parameters){let r=t.parameters[n];for(let t of(u.parameters||[]).concat(s.parameters||[]))t.$ref&&(t=c.jptr(i,t.$ref)),t.name!==n||t.example||(t.examples||(t.examples={}),t.examples[e]={value:r})}if(t.responses)for(let r in t.responses){if(t.responses[r].headers)for(let e in t.responses[r].headers){let n=t.responses[r].headers[e];for(let t in u.responses[r].headers)if(t===e){u.responses[r].headers[t].example=n}}if(t.responses[r].body&&(i.components.examples[n]={value:p(t.responses[r].body)},u.responses[r]&&u.responses[r].content))for(let t in u.responses[r].content){let o=u.responses[r].content[t];o.examples||(o.examples={}),o.examples[e]={$ref:"#/components/examples/"+n}}}}delete u["x-ms-examples"]}if(u.parameters&&0===u.parameters.length&&delete u.parameters,u.requestBody){let n=u.operationId?m.sanitiseAll(u.operationId):m.sanitiseAll(e+a).toCamelCase(),o=m.sanitise(u.requestBody["x-s2o-name"]||n||"");delete u.requestBody["x-s2o-name"];let i=JSON.stringify(u.requestBody),s=m.hash(i);if(!r[s]){let e={};e.name=o,e.body=u.requestBody,e.refs=[],r[s]=e}let l="#/"+t+"/"+encodeURIComponent(c.jpescape(a))+"/"+e+"/requestBody";r[s].refs.push(l)}}}if(s&&s.parameters){for(let e in s.parameters){C(s.parameters[e],null,s,null,a,i,n)}!n.debug&&Array.isArray(s.parameters)&&(s.parameters=s.parameters.filter(_))}}}function L(e,t){let n={};b={schemas:{}},e.security&&A(e.security);for(let n in e.components.securitySchemes){let r=m.sanitise(n);n!==r&&(e.components.securitySchemes[r]&&w("Duplicate sanitised securityScheme name "+r,t),e.components.securitySchemes[r]=e.components.securitySchemes[n],delete e.components.securitySchemes[n]),E(e.components.securitySchemes[r],t)}for(let n in e.components.schemas){let r=m.sanitiseAll(n),o="";if(n!==r){for(;e.components.schemas[r+o];)o=o?++o:2;e.components.schemas[r+o]=e.components.schemas[n],delete e.components.schemas[n]}b.schemas[n]=r+o,S(e.components.schemas[r+o],t)}t.refmap={},d(e,{payload:{options:t}},O),function(e,t){for(let n in t.refmap)c.jptr(e,n,{$ref:t.refmap[n]})}(e,t);for(let n in e.components.parameters){let r=m.sanitise(n);n!==r&&(e.components.parameters[r]&&w("Duplicate sanitised parameter name "+r,t),e.components.parameters[r]=e.components.parameters[n],delete e.components.parameters[n]),C(e.components.parameters[r],null,null,null,r,e,t)}for(let n in e.components.responses){let r=m.sanitise(n);n!==r&&(e.components.responses[r]&&w("Duplicate sanitised response name "+r,t),e.components.responses[r]=e.components.responses[n],delete e.components.responses[n]);let o=e.components.responses[r];if(P(o,0,null,e,t),o.headers)for(let e in o.headers)"status code"===e.toLowerCase()?t.patch?(t.patches++,delete o.headers[e]):w('(Patchable) "Status Code" is not a valid header',t):j(o.headers[e],t)}for(let t in e.components.requestBodies){let r=e.components.requestBodies[t],o=JSON.stringify(r),i=m.hash(o),a={};a.name=t,a.body=r,a.refs=[],n[i]=a}if(R(e.paths,"paths",t,n,e),e["x-ms-paths"]&&R(e["x-ms-paths"],"x-ms-paths",t,n,e),!t.debug)for(let t in e.components.parameters){e.components.parameters[t]["x-s2o-delete"]&&delete e.components.parameters[t]}t.debug&&(e["x-s2o-consumes"]=e.consumes||[],e["x-s2o-produces"]=e.produces||[]),delete e.consumes,delete e.produces,delete e.schemes;let r=[];if(e.components.requestBodies={},!t.resolveInternal){let t=1;for(let o in n){let i=n[o];if(i.refs.length>1){let n="";for(i.name||(i.name="requestBody",n=t++);r.indexOf(i.name+n)>=0;)n=n?++n:2;i.name=i.name+n,r.push(i.name),e.components.requestBodies[i.name]=p(i.body);for(let t in i.refs){let n={};n.$ref="#/components/requestBodies/"+i.name,c.jptr(e,i.refs[t],n)}}}}return e.components.responses&&0===Object.keys(e.components.responses).length&&delete e.components.responses,e.components.parameters&&0===Object.keys(e.components.parameters).length&&delete e.components.parameters,e.components.examples&&0===Object.keys(e.components.examples).length&&delete e.components.examples,e.components.requestBodies&&0===Object.keys(e.components.requestBodies).length&&delete e.components.requestBodies,e.components.securitySchemes&&0===Object.keys(e.components.securitySchemes).length&&delete e.components.securitySchemes,e.components.headers&&0===Object.keys(e.components.headers).length&&delete e.components.headers,e.components.schemas&&0===Object.keys(e.components.schemas).length&&delete e.components.schemas,e.components&&0===Object.keys(e.components).length&&delete e.components,e}function N(e){return e&&e.url&&"string"==typeof e.url?(e.url=e.url.split("{{").join("{"),e.url=e.url.split("}}").join("}"),e.url.replace(/\{(.+?)\}/g,(function(t,n){e.variables||(e.variables={}),e.variables[n]={default:"unknown"}})),e):e}function M(e,t,n){if(void 0===e.info||null===e.info){if(!t.patch)return n(new x("(Patchable) info object is mandatory"));t.patches++,e.info={version:"",title:""}}if("object"!=typeof e.info||Array.isArray(e.info))return n(new x("info must be an object"));if(void 0===e.info.title||null===e.info.title){if(!t.patch)return n(new x("(Patchable) info.title cannot be null"));t.patches++,e.info.title=""}if(void 0===e.info.version||null===e.info.version){if(!t.patch)return n(new x("(Patchable) info.version cannot be null"));t.patches++,e.info.version=""}if("string"!=typeof e.info.version){if(!t.patch)return n(new x("(Patchable) info.version must be a string"));t.patches++,e.info.version=e.info.version.toString()}if(void 0!==e.info.logo){if(!t.patch)return n(new x("(Patchable) info should not have logo property"));t.patches++,e.info["x-logo"]=e.info.logo,delete e.info.logo}if(void 0!==e.info.termsOfService){if(null===e.info.termsOfService){if(!t.patch)return n(new x("(Patchable) info.termsOfService cannot be null"));t.patches++,e.info.termsOfService=""}try{new URL(e.info.termsOfService)}catch(r){if(!t.patch)return n(new x("(Patchable) info.termsOfService must be a URL"));t.patches++,delete e.info.termsOfService}}}function B(e,t,n){if(void 0===e.paths){if(!t.patch)return n(new x("(Patchable) paths object is mandatory"));t.patches++,e.paths={}}}function D(e,t,n){return i(n,new Promise((function(n,r){if(e||(e={}),t.original=e,t.text||(t.text=s.stringify(e)),t.externals=[],t.externalRefs={},t.rewriteRefs=!0,t.preserveMiro=!0,t.promise={},t.promise.resolve=n,t.promise.reject=r,t.patches=0,t.cache||(t.cache={}),t.source&&(t.cache[t.source]=t.original),function(e,t){const n=new WeakSet;d(e,{identityDetection:!0},(function(e,r,o){"object"==typeof e[r]&&null!==e[r]&&(n.has(e[r])?t.anchors?e[r]=p(e[r]):w("YAML anchor or merge key at "+o.path,t):n.add(e[r]))}))}(e,t),e.openapi&&"string"==typeof e.openapi&&e.openapi.startsWith("3."))return t.openapi=f(e),M(t.openapi,t,r),B(t.openapi,t,r),void h.optionalResolve(t).then((function(){return t.direct?n(t.openapi):n(t)})).catch((function(e){console.warn(e),r(e)}));if(!e.swagger||"2.0"!=e.swagger)return r(new x("Unsupported swagger/OpenAPI version: "+(e.openapi?e.openapi:e.swagger)));let o=t.openapi={};if(o.openapi="string"==typeof t.targetVersion&&t.targetVersion.startsWith("3.")?t.targetVersion:"3.0.0",t.origin){o["x-origin"]||(o["x-origin"]=[]);let n={};n.url=t.source||t.origin,n.format="swagger",n.version=e.swagger,n.converter={},n.converter.url="https://github.com/mermade/oas-kit",n.converter.version=y,o["x-origin"].push(n)}if(o=Object.assign(o,f(e)),delete o.swagger,d(o,{},(function(e,t,n){null===e[t]&&!t.startsWith("x-")&&"default"!==t&&n.path.indexOf("/example")<0&&delete e[t]})),e.host)for(let t of Array.isArray(e.schemes)?e.schemes:[""]){let n={};n.url=(t?t+":":"")+"//"+e.host+(e.basePath?e.basePath:""),N(n),o.servers||(o.servers=[]),o.servers.push(n)}else if(e.basePath){let t={};t.url=e.basePath,N(t),o.servers||(o.servers=[]),o.servers.push(t)}if(delete o.host,delete o.basePath,o["x-servers"]&&Array.isArray(o["x-servers"])&&(o.servers=o["x-servers"],delete o["x-servers"]),e["x-ms-parameterized-host"]){let t=e["x-ms-parameterized-host"],n={};n.url=t.hostTemplate+(e.basePath?e.basePath:""),n.variables={};const r=n.url.match(/\{\w+\}/g);for(let e in t.parameters){let i=t.parameters[e];i.$ref&&(i=p(l(o,i.$ref))),e.startsWith("x-")||(delete i.required,delete i.type,delete i.in,void 0===i.default&&(i.enum?i.default=i.enum[0]:i.default="none"),i.name||(i.name=r[e].replace("{","").replace("}","")),n.variables[i.name]=i,delete i.name)}o.servers||(o.servers=[]),!1===t.useSchemePrefix?o.servers.push(n):e.schemes.forEach(e=>{o.servers.push(Object.assign({},n,{url:e+"://"+n.url}))}),delete o["x-ms-parameterized-host"]}M(o,t,r),B(o,t,r),"string"==typeof o.consumes&&(o.consumes=[o.consumes]),"string"==typeof o.produces&&(o.produces=[o.produces]),o.components={},o["x-callbacks"]&&(o.components.callbacks=o["x-callbacks"],delete o["x-callbacks"]),o.components.examples={},o.components.headers={},o["x-links"]&&(o.components.links=o["x-links"],delete o["x-links"]),o.components.parameters=o.parameters||{},o.components.responses=o.responses||{},o.components.requestBodies={},o.components.securitySchemes=o.securityDefinitions||{},o.components.schemas=o.definitions||{},delete o.definitions,delete o.responses,delete o.parameters,delete o.securityDefinitions,h.optionalResolve(t).then((function(){L(t.openapi,t),t.direct?n(t.openapi):n(t)})).catch((function(e){console.warn(e),r(e)}))})))}function q(e,t,n){return i(n,new Promise((function(n,r){let o=null,i=null;try{o=JSON.parse(e),t.text=JSON.stringify(o,null,2)}catch(n){i=n;try{o=s.parse(e,{schema:"core",prettyErrors:!0}),t.sourceYaml=!0,t.text=e}catch(e){i=e}}o?D(o,t).then(e=>n(e)).catch(e=>r(e)):r(new x(i?i.message:"Could not parse string"))})))}e.exports={S2OError:x,targetVersion:"3.0.0",convert:D,convertObj:D,convertUrl:function(e,t,n){return i(n,new Promise((function(n,r){t.origin=!0,t.source||(t.source=e),t.verbose&&console.warn("GET "+e),t.fetch||(t.fetch=a);const o=Object.assign({},t.fetchOptions,{agent:t.agent});t.fetch(e,o).then((function(t){if(200!==t.status)throw new x(`Received status code ${t.status}: ${e}`);return t.text()})).then((function(e){q(e,t).then(e=>n(e)).catch(e=>r(e))})).catch((function(e){r(e)}))})))},convertStr:q,convertFile:function(e,t,n){return i(n,new Promise((function(n,o){r.readFile(e,t.encoding||"utf8",(function(r,i){r?o(r):(t.sourceFile=e,q(i,t).then(e=>n(e)).catch(e=>o(e)))}))})))},convertStream:function(e,t,n){return i(n,new Promise((function(n,r){let o="";e.on("data",(function(e){o+=e})).on("end",(function(){q(o,t).then(e=>n(e)).catch(e=>r(e))}))})))}}},function(e,t,n){e.exports=n(421)},function(e,t,n){e.exports=n(453)},function(e,t,n){e.exports=n(536)},function(e,t,n){var r=n(206),o=n(540),i=n(544);function a(t,n,s){return"undefined"!=typeof Reflect&&o?e.exports=a=o:e.exports=a=function(e,t,n){var o=i(e,t);if(o){var a=r(o,t);return a.get?a.get.call(n):a.value}},a(t,n,s||t)}e.exports=a},function(e,t,n){var r=n(552),o=n(555);e.exports=function(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Super expression must either be null or a function");e.prototype=r(t&&t.prototype,{constructor:{value:e,writable:!0,configurable:!0}}),t&&o(e,t)}},function(e,t,n){var r=n(251),o=n(130),i=n(559),a=n(560);e.exports=function(e){var t=i();return function(){var n,i=o(e);if(t){var s=o(this).constructor;n=r(i,arguments,s)}else n=i.apply(this,arguments);return a(this,n)}}},function(e,t){e.exports=window.FormData},function(e,t,n){e.exports=n(566)},function(e,t,n){var r=n(576)(n(604));e.exports=r},function(e,t,n){"use strict";(function(t){ +/*! + * @description Recursive object extending + * @author Viacheslav Lotsmanov + * @license MIT + * + * The MIT License (MIT) + * + * Copyright (c) 2013-2018 Viacheslav Lotsmanov + * + * 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. + */ +function n(e){return e instanceof t||e instanceof Date||e instanceof RegExp}function r(e){if(e instanceof t){var n=t.alloc?t.alloc(e.length):new t(e.length);return e.copy(n),n}if(e instanceof Date)return new Date(e.getTime());if(e instanceof RegExp)return new RegExp(e);throw new Error("Unexpected situation")}function o(e){var t=[];return e.forEach((function(e,i){"object"==typeof e&&null!==e?Array.isArray(e)?t[i]=o(e):n(e)?t[i]=r(e):t[i]=a({},e):t[i]=e})),t}function i(e,t){return"__proto__"===t?void 0:e[t]}var a=e.exports=function(){if(arguments.length<1||"object"!=typeof arguments[0])return!1;if(arguments.length<2)return arguments[0];var e,t,s=arguments[0],c=Array.prototype.slice.call(arguments,1);return c.forEach((function(c){"object"!=typeof c||null===c||Array.isArray(c)||Object.keys(c).forEach((function(l){return t=i(s,l),(e=i(c,l))===s?void 0:"object"!=typeof e||null===e?void(s[l]=e):Array.isArray(e)?void(s[l]=o(e)):n(e)?void(s[l]=r(e)):"object"!=typeof t||null===t||Array.isArray(t)?void(s[l]=a({},e)):void(s[l]=a(t,e))}))})),s}}).call(this,n(32).Buffer)},function(e,t,n){var r=n(606);e.exports=function(e){return r(e,5)}},function(e,t,n){e.exports=n(629)},function(e,t){var n=e.exports=function(e){return new r(e)};function r(e){this.value=e}function o(e,t,n){var r=[],o=[],s=!0;return function e(p){var f=n?i(p):p,d={},h=!0,v={node:f,node_:p,path:[].concat(r),parent:o[o.length-1],parents:o,key:r.slice(-1)[0],isRoot:0===r.length,level:r.length,circular:null,update:function(e,t){v.isRoot||(v.parent.node[v.key]=e),v.node=e,t&&(h=!1)},delete:function(e){delete v.parent.node[v.key],e&&(h=!1)},remove:function(e){c(v.parent.node)?v.parent.node.splice(v.key,1):delete v.parent.node[v.key],e&&(h=!1)},keys:null,before:function(e){d.before=e},after:function(e){d.after=e},pre:function(e){d.pre=e},post:function(e){d.post=e},stop:function(){s=!1},block:function(){h=!1}};if(!s)return v;function m(){if("object"==typeof v.node&&null!==v.node){v.keys&&v.node_===v.node||(v.keys=a(v.node)),v.isLeaf=0==v.keys.length;for(var e=0;e=0||Object.prototype.propertyIsEnumerable.call(e,n)&&(s[n]=e[n])}return s}},function(e,t,n){var r=n(64),o=n(169),i=n(50),a=Function.prototype,s=Object.prototype,c=a.toString,l=s.hasOwnProperty,u=c.call(Object);e.exports=function(e){if(!i(e)||"[object Object]"!=r(e))return!1;var t=o(e);if(null===t)return!0;var n=l.call(t,"constructor")&&t.constructor;return"function"==typeof n&&n instanceof n&&c.call(n)==u}},function(e,t,n){"use strict"; +/*! + * cookie + * Copyright(c) 2012-2014 Roman Shtylman + * Copyright(c) 2015 Douglas Christopher Wilson + * MIT Licensed + */t.parse=function(e,t){if("string"!=typeof e)throw new TypeError("argument str must be a string");for(var n={},o=t||{},a=e.split(i),c=o.decode||r,l=0;l=]+)/,inside:{style:{pattern:/(["'])[\s\S]+(?=["']$)/,lookbehind:!0,alias:"language-css",inside:e.languages.css},punctuation:[{pattern:/^=/,alias:"attr-equals"},/"|'/]}},"attr-name":/^style/i}}},n.tag))}(Prism)},function(e,t){!function(e){var t=/[*&][^\s[\]{},]+/,n=/!(?:<[\w\-%#;/?:@&=+$,.!~*'()[\]]+>|(?:[a-zA-Z\d-]*!)?[\w\-%#;/?:@&=+$.~*'()]+)?/,r="(?:"+n.source+"(?:[ \t]+"+t.source+")?|"+t.source+"(?:[ \t]+"+n.source+")?)",o=/(?:[^\s\x00-\x08\x0e-\x1f!"#%&'*,\-:>?@[\]`{|}\x7f-\x84\x86-\x9f\ud800-\udfff\ufffe\uffff]|[?:-])(?:[ \t]*(?:(?![#:])|:))*/.source.replace(//g,(function(){return/[^\s\x00-\x08\x0e-\x1f,[\]{}\x7f-\x84\x86-\x9f\ud800-\udfff\ufffe\uffff]/.source})),i=/"(?:[^"\\\r\n]|\\.)*"|'(?:[^'\\\r\n]|\\.)*'/.source;function a(e,t){t=(t||"").replace(/m/g,"")+"m";var n=/([:\-,[{]\s*(?:\s<>[ \t]+)?)(?:<>)(?=[ \t]*(?:$|,|]|}|(?:[\r\n]\s*)?#))/.source.replace(/<>/g,(function(){return r})).replace(/<>/g,(function(){return e}));return RegExp(n,t)}e.languages.yaml={scalar:{pattern:RegExp(/([\-:]\s*(?:\s<>[ \t]+)?[|>])[ \t]*(?:((?:\r?\n|\r)[ \t]+)\S[^\r\n]*(?:\2[^\r\n]+)*)/.source.replace(/<>/g,(function(){return r}))),lookbehind:!0,alias:"string"},comment:/#.*/,key:{pattern:RegExp(/((?:^|[:\-,[{\r\n?])[ \t]*(?:<>[ \t]+)?)<>(?=\s*:\s)/.source.replace(/<>/g,(function(){return r})).replace(/<>/g,(function(){return"(?:"+o+"|"+i+")"}))),lookbehind:!0,greedy:!0,alias:"atrule"},directive:{pattern:/(^[ \t]*)%.+/m,lookbehind:!0,alias:"important"},datetime:{pattern:a(/\d{4}-\d\d?-\d\d?(?:[tT]|[ \t]+)\d\d?:\d{2}:\d{2}(?:\.\d*)?(?:[ \t]*(?:Z|[-+]\d\d?(?::\d{2})?))?|\d{4}-\d{2}-\d{2}|\d\d?:\d{2}(?::\d{2}(?:\.\d*)?)?/.source),lookbehind:!0,alias:"number"},boolean:{pattern:a(/true|false/.source,"i"),lookbehind:!0,alias:"important"},null:{pattern:a(/null|~/.source,"i"),lookbehind:!0,alias:"important"},string:{pattern:a(i),lookbehind:!0,greedy:!0},number:{pattern:a(/[+-]?(?:0x[\da-f]+|0o[0-7]+|(?:\d+(?:\.\d*)?|\.?\d+)(?:e[+-]?\d+)?|\.inf|\.nan)/.source,"i"),lookbehind:!0},tag:n,important:t,punctuation:/---|[:[\]{}\-,|>?]|\.\.\./},e.languages.yml=e.languages.yaml}(Prism)},function(e,t){Prism.languages.go=Prism.languages.extend("clike",{string:{pattern:/(["'`])(?:\\[\s\S]|(?!\1)[^\\])*\1/,greedy:!0},keyword:/\b(?:break|case|chan|const|continue|default|defer|else|fallthrough|for|func|go(?:to)?|if|import|interface|map|package|range|return|select|struct|switch|type|var)\b/,boolean:/\b(?:_|iota|nil|true|false)\b/,number:/(?:\b0x[a-f\d]+|(?:\b\d+(?:\.\d*)?|\B\.\d+)(?:e[-+]?\d+)?)i?/i,operator:/[*\/%^!=]=?|\+[=+]?|-[=-]?|\|[=|]?|&(?:=|&|\^=?)?|>(?:>=?|=)?|<(?:<=?|=|-)?|:=|\.\.\./,builtin:/\b(?:bool|byte|complex(?:64|128)|error|float(?:32|64)|rune|string|u?int(?:8|16|32|64)?|uintptr|append|cap|close|complex|copy|delete|imag|len|make|new|panic|print(?:ln)?|real|recover)\b/}),delete Prism.languages.go["class-name"]},function(e,t){!function(e){var t=/\b(?:abstract|assert|boolean|break|byte|case|catch|char|class|const|continue|default|do|double|else|enum|exports|extends|final|finally|float|for|goto|if|implements|import|instanceof|int|interface|long|module|native|new|non-sealed|null|open|opens|package|permits|private|protected|provides|public|record|requires|return|sealed|short|static|strictfp|super|switch|synchronized|this|throw|throws|to|transient|transitive|try|uses|var|void|volatile|while|with|yield)\b/,n=/(^|[^\w.])(?:[a-z]\w*\s*\.\s*)*(?:[A-Z]\w*\s*\.\s*)*/.source,r={pattern:RegExp(n+/[A-Z](?:[\d_A-Z]*[a-z]\w*)?\b/.source),lookbehind:!0,inside:{namespace:{pattern:/^[a-z]\w*(?:\s*\.\s*[a-z]\w*)*(?:\s*\.)?/,inside:{punctuation:/\./}},punctuation:/\./}};e.languages.java=e.languages.extend("clike",{"class-name":[r,{pattern:RegExp(n+/[A-Z]\w*(?=\s+\w+\s*[;,=())])/.source),lookbehind:!0,inside:r.inside}],keyword:t,function:[e.languages.clike.function,{pattern:/(\:\:\s*)[a-z_]\w*/,lookbehind:!0}],number:/\b0b[01][01_]*L?\b|\b0x(?:\.[\da-f_p+-]+|[\da-f_]+(?:\.[\da-f_p+-]+)?)\b|(?:\b\d[\d_]*(?:\.[\d_]*)?|\B\.\d[\d_]*)(?:e[+-]?\d[\d_]*)?[dfl]?/i,operator:{pattern:/(^|[^.])(?:<<=?|>>>?=?|->|--|\+\+|&&|\|\||::|[?:~]|[-+*/%&|^!=<>]=?)/m,lookbehind:!0}}),e.languages.insertBefore("java","string",{"triple-quoted-string":{pattern:/"""[ \t]*[\r\n](?:(?:"|"")?(?:\\.|[^"\\]))*"""/,greedy:!0,alias:"string"}}),e.languages.insertBefore("java","class-name",{annotation:{pattern:/(^|[^.])@\w+(?:\s*\.\s*\w+)*/,lookbehind:!0,alias:"punctuation"},generics:{pattern:/<(?:[\w\s,.&?]|<(?:[\w\s,.&?]|<(?:[\w\s,.&?]|<[\w\s,.&?]*>)*>)*>)*>/,inside:{"class-name":r,keyword:t,punctuation:/[<>(),.:]/,operator:/[?&|]/}},namespace:{pattern:RegExp(/(\b(?:exports|import(?:\s+static)?|module|open|opens|package|provides|requires|to|transitive|uses|with)\s+)(?!)[a-z]\w*(?:\.[a-z]\w*)*\.?/.source.replace(//g,(function(){return t.source}))),lookbehind:!0,inside:{punctuation:/\./}}})}(Prism)},function(e,t){Prism.languages.json={property:{pattern:/"(?:\\.|[^\\"\r\n])*"(?=\s*:)/,greedy:!0},string:{pattern:/"(?:\\.|[^\\"\r\n])*"(?!\s*:)/,greedy:!0},comment:{pattern:/\/\/.*|\/\*[\s\S]*?(?:\*\/|$)/,greedy:!0},number:/-?\b\d+(?:\.\d+)?(?:e[+-]?\d+)?\b/i,punctuation:/[{}[\],]/,operator:/:/,boolean:/\b(?:true|false)\b/,null:{pattern:/\bnull\b/,alias:"keyword"}},Prism.languages.webmanifest=Prism.languages.json},function(e,t){!function(e){var t="\\b(?:BASH|BASHOPTS|BASH_ALIASES|BASH_ARGC|BASH_ARGV|BASH_CMDS|BASH_COMPLETION_COMPAT_DIR|BASH_LINENO|BASH_REMATCH|BASH_SOURCE|BASH_VERSINFO|BASH_VERSION|COLORTERM|COLUMNS|COMP_WORDBREAKS|DBUS_SESSION_BUS_ADDRESS|DEFAULTS_PATH|DESKTOP_SESSION|DIRSTACK|DISPLAY|EUID|GDMSESSION|GDM_LANG|GNOME_KEYRING_CONTROL|GNOME_KEYRING_PID|GPG_AGENT_INFO|GROUPS|HISTCONTROL|HISTFILE|HISTFILESIZE|HISTSIZE|HOME|HOSTNAME|HOSTTYPE|IFS|INSTANCE|JOB|LANG|LANGUAGE|LC_ADDRESS|LC_ALL|LC_IDENTIFICATION|LC_MEASUREMENT|LC_MONETARY|LC_NAME|LC_NUMERIC|LC_PAPER|LC_TELEPHONE|LC_TIME|LESSCLOSE|LESSOPEN|LINES|LOGNAME|LS_COLORS|MACHTYPE|MAILCHECK|MANDATORY_PATH|NO_AT_BRIDGE|OLDPWD|OPTERR|OPTIND|ORBIT_SOCKETDIR|OSTYPE|PAPERSIZE|PATH|PIPESTATUS|PPID|PS1|PS2|PS3|PS4|PWD|RANDOM|REPLY|SECONDS|SELINUX_INIT|SESSION|SESSIONTYPE|SESSION_MANAGER|SHELL|SHELLOPTS|SHLVL|SSH_AUTH_SOCK|TERM|UID|UPSTART_EVENTS|UPSTART_INSTANCE|UPSTART_JOB|UPSTART_SESSION|USER|WINDOWID|XAUTHORITY|XDG_CONFIG_DIRS|XDG_CURRENT_DESKTOP|XDG_DATA_DIRS|XDG_GREETER_DATA_DIR|XDG_MENU_PREFIX|XDG_RUNTIME_DIR|XDG_SEAT|XDG_SEAT_PATH|XDG_SESSION_DESKTOP|XDG_SESSION_ID|XDG_SESSION_PATH|XDG_SESSION_TYPE|XDG_VTNR|XMODIFIERS)\\b",n={pattern:/(^(["']?)\w+\2)[ \t]+\S.*/,lookbehind:!0,alias:"punctuation",inside:null},r={bash:n,environment:{pattern:RegExp("\\$"+t),alias:"constant"},variable:[{pattern:/\$?\(\([\s\S]+?\)\)/,greedy:!0,inside:{variable:[{pattern:/(^\$\(\([\s\S]+)\)\)/,lookbehind:!0},/^\$\(\(/],number:/\b0x[\dA-Fa-f]+\b|(?:\b\d+(?:\.\d*)?|\B\.\d+)(?:[Ee]-?\d+)?/,operator:/--?|-=|\+\+?|\+=|!=?|~|\*\*?|\*=|\/=?|%=?|<<=?|>>=?|<=?|>=?|==?|&&?|&=|\^=?|\|\|?|\|=|\?|:/,punctuation:/\(\(?|\)\)?|,|;/}},{pattern:/\$\((?:\([^)]+\)|[^()])+\)|`[^`]+`/,greedy:!0,inside:{variable:/^\$\(|^`|\)$|`$/}},{pattern:/\$\{[^}]+\}/,greedy:!0,inside:{operator:/:[-=?+]?|[!\/]|##?|%%?|\^\^?|,,?/,punctuation:/[\[\]]/,environment:{pattern:RegExp("(\\{)"+t),lookbehind:!0,alias:"constant"}}},/\$(?:\w+|[#?*!@$])/],entity:/\\(?:[abceEfnrtv\\"]|O?[0-7]{1,3}|x[0-9a-fA-F]{1,2}|u[0-9a-fA-F]{4}|U[0-9a-fA-F]{8})/};e.languages.bash={shebang:{pattern:/^#!\s*\/.*/,alias:"important"},comment:{pattern:/(^|[^"{\\$])#.*/,lookbehind:!0},"function-name":[{pattern:/(\bfunction\s+)\w+(?=(?:\s*\(?:\s*\))?\s*\{)/,lookbehind:!0,alias:"function"},{pattern:/\b\w+(?=\s*\(\s*\)\s*\{)/,alias:"function"}],"for-or-select":{pattern:/(\b(?:for|select)\s+)\w+(?=\s+in\s)/,alias:"variable",lookbehind:!0},"assign-left":{pattern:/(^|[\s;|&]|[<>]\()\w+(?=\+?=)/,inside:{environment:{pattern:RegExp("(^|[\\s;|&]|[<>]\\()"+t),lookbehind:!0,alias:"constant"}},alias:"variable",lookbehind:!0},string:[{pattern:/((?:^|[^<])<<-?\s*)(\w+?)\s[\s\S]*?(?:\r?\n|\r)\2/,lookbehind:!0,greedy:!0,inside:r},{pattern:/((?:^|[^<])<<-?\s*)(["'])(\w+)\2\s[\s\S]*?(?:\r?\n|\r)\3/,lookbehind:!0,greedy:!0,inside:{bash:n}},{pattern:/(^|[^\\](?:\\\\)*)(["'])(?:\\[\s\S]|\$\([^)]+\)|\$(?!\()|`[^`]+`|(?!\2)[^\\`$])*\2/,lookbehind:!0,greedy:!0,inside:r}],environment:{pattern:RegExp("\\$?"+t),alias:"constant"},variable:r.variable,function:{pattern:/(^|[\s;|&]|[<>]\()(?:add|apropos|apt|aptitude|apt-cache|apt-get|aspell|automysqlbackup|awk|basename|bash|bc|bconsole|bg|bzip2|cal|cat|cfdisk|chgrp|chkconfig|chmod|chown|chroot|cksum|clear|cmp|column|comm|composer|cp|cron|crontab|csplit|curl|cut|date|dc|dd|ddrescue|debootstrap|df|diff|diff3|dig|dir|dircolors|dirname|dirs|dmesg|du|egrep|eject|env|ethtool|expand|expect|expr|fdformat|fdisk|fg|fgrep|file|find|fmt|fold|format|free|fsck|ftp|fuser|gawk|git|gparted|grep|groupadd|groupdel|groupmod|groups|grub-mkconfig|gzip|halt|head|hg|history|host|hostname|htop|iconv|id|ifconfig|ifdown|ifup|import|install|ip|jobs|join|kill|killall|less|link|ln|locate|logname|logrotate|look|lpc|lpr|lprint|lprintd|lprintq|lprm|ls|lsof|lynx|make|man|mc|mdadm|mkconfig|mkdir|mke2fs|mkfifo|mkfs|mkisofs|mknod|mkswap|mmv|more|most|mount|mtools|mtr|mutt|mv|nano|nc|netstat|nice|nl|nohup|notify-send|npm|nslookup|op|open|parted|passwd|paste|pathchk|ping|pkill|pnpm|popd|pr|printcap|printenv|ps|pushd|pv|quota|quotacheck|quotactl|ram|rar|rcp|reboot|remsync|rename|renice|rev|rm|rmdir|rpm|rsync|scp|screen|sdiff|sed|sendmail|seq|service|sftp|sh|shellcheck|shuf|shutdown|sleep|slocate|sort|split|ssh|stat|strace|su|sudo|sum|suspend|swapon|sync|tac|tail|tar|tee|time|timeout|top|touch|tr|traceroute|tsort|tty|umount|uname|unexpand|uniq|units|unrar|unshar|unzip|update-grub|uptime|useradd|userdel|usermod|users|uudecode|uuencode|v|vdir|vi|vim|virsh|vmstat|wait|watch|wc|wget|whereis|which|who|whoami|write|xargs|xdg-open|yarn|yes|zenity|zip|zsh|zypper)(?=$|[)\s;|&])/,lookbehind:!0},keyword:{pattern:/(^|[\s;|&]|[<>]\()(?:if|then|else|elif|fi|for|while|in|case|esac|function|select|do|done|until)(?=$|[)\s;|&])/,lookbehind:!0},builtin:{pattern:/(^|[\s;|&]|[<>]\()(?:\.|:|break|cd|continue|eval|exec|exit|export|getopts|hash|pwd|readonly|return|shift|test|times|trap|umask|unset|alias|bind|builtin|caller|command|declare|echo|enable|help|let|local|logout|mapfile|printf|read|readarray|source|type|typeset|ulimit|unalias|set|shopt)(?=$|[)\s;|&])/,lookbehind:!0,alias:"class-name"},boolean:{pattern:/(^|[\s;|&]|[<>]\()(?:true|false)(?=$|[)\s;|&])/,lookbehind:!0},"file-descriptor":{pattern:/\B&\d\b/,alias:"important"},operator:{pattern:/\d?<>|>\||\+=|==?|!=?|=~|<<[<-]?|[&\d]?>>|\d?[<>]&?|&[>&]?|\|[&|]?|<=?|>=?/,inside:{"file-descriptor":{pattern:/^\d/,alias:"important"}}},punctuation:/\$?\(\(?|\)\)?|\.\.|[{}[\];\\]/,number:{pattern:/(^|\s)(?:[1-9]\d*|0)(?:[.,]\d+)?\b/,lookbehind:!0}},n.inside=e.languages.bash;for(var o=["comment","function-name","for-or-select","assign-left","string","environment","function","keyword","builtin","boolean","file-descriptor","operator","punctuation","number"],i=r.variable[1].inside,a=0;a]?|>[=>]?|[&|^~]/,punctuation:/[{}[\];(),.:]/},Prism.languages.python["string-interpolation"].inside.interpolation.inside.rest=Prism.languages.python,Prism.languages.py=Prism.languages.python},function(e,t){!function(e){e.languages.http={"request-line":{pattern:/^(?:POST|GET|PUT|DELETE|OPTIONS|PATCH|TRACE|CONNECT)\s(?:https?:\/\/|\/)\S+\sHTTP\/[0-9.]+/m,inside:{property:/^(?:POST|GET|PUT|DELETE|OPTIONS|PATCH|TRACE|CONNECT)\b/,"attr-name":/:\w+/}},"response-status":{pattern:/^HTTP\/1.[01] \d.*/m,inside:{property:{pattern:/(^HTTP\/1.[01] )\d.*/i,lookbehind:!0}}},"header-name":{pattern:/^[\w-]+:(?=.)/m,alias:"keyword"}};var t,n=e.languages,r={"application/javascript":n.javascript,"application/json":n.json||n.javascript,"application/xml":n.xml,"text/xml":n.xml,"text/html":n.html,"text/css":n.css},o={"application/json":!0,"application/xml":!0};function i(e){var t=e.replace(/^[a-z]+\//,"");return"(?:"+e+"|"+("\\w+/(?:[\\w.-]+\\+)+"+t+"(?![+\\w.-])")+")"}for(var a in r)if(r[a]){t=t||{};var s=o[a]?i(a):a;t[a.replace(/\//g,"-")]={pattern:RegExp("(content-type:\\s*"+s+".*)(?:\\r?\\n|\\r){2}[\\s\\S]*","i"),lookbehind:!0,inside:r[a]}}t&&e.languages.insertBefore("http","header-name",t)}(Prism)},function(e,t){!function(e){function t(e,t){return e.replace(/<<(\d+)>>/g,(function(e,n){return"(?:"+t[+n]+")"}))}function n(e,n,r){return RegExp(t(e,n),r||"")}function r(e,t){for(var n=0;n>/g,(function(){return"(?:"+e+")"}));return e.replace(/<>/g,"[^\\s\\S]")}var o="bool byte char decimal double dynamic float int long object sbyte short string uint ulong ushort var void",i="class enum interface struct",a="add alias and ascending async await by descending from get global group into join let nameof not notnull on or orderby partial remove select set unmanaged value when where",s="abstract as base break case catch checked const continue default delegate do else event explicit extern finally fixed for foreach goto if implicit in internal is lock namespace new null operator out override params private protected public readonly ref return sealed sizeof stackalloc static switch this throw try typeof unchecked unsafe using virtual volatile while yield";function c(e){return"\\b(?:"+e.trim().replace(/ /g,"|")+")\\b"}var l=c(i),u=RegExp(c(o+" "+i+" "+a+" "+s)),p=c(i+" "+a+" "+s),f=c(o+" "+i+" "+s),d=r(/<(?:[^<>;=+\-*/%&|^]|<>)*>/.source,2),h=r(/\((?:[^()]|<>)*\)/.source,2),v=/@?\b[A-Za-z_]\w*\b/.source,m=t(/<<0>>(?:\s*<<1>>)?/.source,[v,d]),g=t(/(?!<<0>>)<<1>>(?:\s*\.\s*<<1>>)*/.source,[p,m]),y=/\[\s*(?:,\s*)*\]/.source,b=t(/<<0>>(?:\s*(?:\?\s*)?<<1>>)*(?:\s*\?)?/.source,[g,y]),x=t(/[^,()<>[\];=+\-*/%&|^]|<<0>>|<<1>>|<<2>>/.source,[d,h,y]),w=t(/\(<<0>>+(?:,<<0>>+)+\)/.source,[x]),k=t(/(?:<<0>>|<<1>>)(?:\s*(?:\?\s*)?<<2>>)*(?:\s*\?)?/.source,[w,g,y]),S={keyword:u,punctuation:/[<>()?,.:[\]]/},O=/'(?:[^\r\n'\\]|\\.|\\[Uux][\da-fA-F]{1,8})'/.source,A=/"(?:\\.|[^\\"\r\n])*"/.source,E=/@"(?:""|\\[\s\S]|[^\\"])*"(?!")/.source;e.languages.csharp=e.languages.extend("clike",{string:[{pattern:n(/(^|[^$\\])<<0>>/.source,[E]),lookbehind:!0,greedy:!0},{pattern:n(/(^|[^@$\\])<<0>>/.source,[A]),lookbehind:!0,greedy:!0},{pattern:RegExp(O),greedy:!0,alias:"character"}],"class-name":[{pattern:n(/(\busing\s+static\s+)<<0>>(?=\s*;)/.source,[g]),lookbehind:!0,inside:S},{pattern:n(/(\busing\s+<<0>>\s*=\s*)<<1>>(?=\s*;)/.source,[v,k]),lookbehind:!0,inside:S},{pattern:n(/(\busing\s+)<<0>>(?=\s*=)/.source,[v]),lookbehind:!0},{pattern:n(/(\b<<0>>\s+)<<1>>/.source,[l,m]),lookbehind:!0,inside:S},{pattern:n(/(\bcatch\s*\(\s*)<<0>>/.source,[g]),lookbehind:!0,inside:S},{pattern:n(/(\bwhere\s+)<<0>>/.source,[v]),lookbehind:!0},{pattern:n(/(\b(?:is(?:\s+not)?|as)\s+)<<0>>/.source,[b]),lookbehind:!0,inside:S},{pattern:n(/\b<<0>>(?=\s+(?!<<1>>)<<2>>(?:\s*[=,;:{)\]]|\s+(?:in|when)\b))/.source,[k,f,v]),inside:S}],keyword:u,number:/(?:\b0(?:x[\da-f_]*[\da-f]|b[01_]*[01])|(?:\B\.\d+(?:_+\d+)*|\b\d+(?:_+\d+)*(?:\.\d+(?:_+\d+)*)?)(?:e[-+]?\d+(?:_+\d+)*)?)(?:ul|lu|[dflmu])?\b/i,operator:/>>=?|<<=?|[-=]>|([-+&|])\1|~|\?\?=?|[-+*/%&|^!=<>]=?/,punctuation:/\?\.?|::|[{}[\];(),.:]/}),e.languages.insertBefore("csharp","number",{range:{pattern:/\.\./,alias:"operator"}}),e.languages.insertBefore("csharp","punctuation",{"named-parameter":{pattern:n(/([(,]\s*)<<0>>(?=\s*:)/.source,[v]),lookbehind:!0,alias:"punctuation"}}),e.languages.insertBefore("csharp","class-name",{namespace:{pattern:n(/(\b(?:namespace|using)\s+)<<0>>(?:\s*\.\s*<<0>>)*(?=\s*[;{])/.source,[v]),lookbehind:!0,inside:{punctuation:/\./}},"type-expression":{pattern:n(/(\b(?:default|typeof|sizeof)\s*\(\s*(?!\s))(?:[^()\s]|\s(?!\s)|<<0>>)*(?=\s*\))/.source,[h]),lookbehind:!0,alias:"class-name",inside:S},"return-type":{pattern:n(/<<0>>(?=\s+(?:<<1>>\s*(?:=>|[({]|\.\s*this\s*\[)|this\s*\[))/.source,[k,g]),inside:S,alias:"class-name"},"constructor-invocation":{pattern:n(/(\bnew\s+)<<0>>(?=\s*[[({])/.source,[k]),lookbehind:!0,inside:S,alias:"class-name"},"generic-method":{pattern:n(/<<0>>\s*<<1>>(?=\s*\()/.source,[v,d]),inside:{function:n(/^<<0>>/.source,[v]),generic:{pattern:RegExp(d),alias:"class-name",inside:S}}},"type-list":{pattern:n(/\b((?:<<0>>\s+<<1>>|where\s+<<2>>)\s*:\s*)(?:<<3>>|<<4>>)(?:\s*,\s*(?:<<3>>|<<4>>))*(?=\s*(?:where|[{;]|=>|$))/.source,[l,m,v,k,u.source]),lookbehind:!0,inside:{keyword:u,"class-name":{pattern:RegExp(k),greedy:!0,inside:S},punctuation:/,/}},preprocessor:{pattern:/(^\s*)#.*/m,lookbehind:!0,alias:"property",inside:{directive:{pattern:/(\s*#)\b(?:define|elif|else|endif|endregion|error|if|line|pragma|region|undef|warning)\b/,lookbehind:!0,alias:"keyword"}}}});var _=A+"|"+O,j=t(/\/(?![*/])|\/\/[^\r\n]*[\r\n]|\/\*(?:[^*]|\*(?!\/))*\*\/|<<0>>/.source,[_]),T=r(t(/[^"'/()]|<<0>>|\(<>*\)/.source,[j]),2),C=/\b(?:assembly|event|field|method|module|param|property|return|type)\b/.source,I=t(/<<0>>(?:\s*\(<<1>>*\))?/.source,[g,T]);e.languages.insertBefore("csharp","class-name",{attribute:{pattern:n(/((?:^|[^\s\w>)?])\s*\[\s*)(?:<<0>>\s*:\s*)?<<1>>(?:\s*,\s*<<1>>)*(?=\s*\])/.source,[C,I]),lookbehind:!0,greedy:!0,inside:{target:{pattern:n(/^<<0>>(?=\s*:)/.source,[C]),alias:"keyword"},"attribute-arguments":{pattern:n(/\(<<0>>*\)/.source,[T]),inside:e.languages.csharp},"class-name":{pattern:RegExp(g),inside:{punctuation:/\./}},punctuation:/[:,]/}}});var P=/:[^}\r\n]+/.source,R=r(t(/[^"'/()]|<<0>>|\(<>*\)/.source,[j]),2),L=t(/\{(?!\{)(?:(?![}:])<<0>>)*<<1>>?\}/.source,[R,P]),N=r(t(/[^"'/()]|\/(?!\*)|\/\*(?:[^*]|\*(?!\/))*\*\/|<<0>>|\(<>*\)/.source,[_]),2),M=t(/\{(?!\{)(?:(?![}:])<<0>>)*<<1>>?\}/.source,[N,P]);function B(t,r){return{interpolation:{pattern:n(/((?:^|[^{])(?:\{\{)*)<<0>>/.source,[t]),lookbehind:!0,inside:{"format-string":{pattern:n(/(^\{(?:(?![}:])<<0>>)*)<<1>>(?=\}$)/.source,[r,P]),lookbehind:!0,inside:{punctuation:/^:/}},punctuation:/^\{|\}$/,expression:{pattern:/[\s\S]+/,alias:"language-csharp",inside:e.languages.csharp}}},string:/[\s\S]+/}}e.languages.insertBefore("csharp","string",{"interpolation-string":[{pattern:n(/(^|[^\\])(?:\$@|@\$)"(?:""|\\[\s\S]|\{\{|<<0>>|[^\\{"])*"/.source,[L]),lookbehind:!0,greedy:!0,inside:B(L,R)},{pattern:n(/(^|[^@\\])\$"(?:\\.|\{\{|<<0>>|[^\\"{])*"/.source,[M]),lookbehind:!0,greedy:!0,inside:B(M,N)}]})}(Prism),Prism.languages.dotnet=Prism.languages.cs=Prism.languages.csharp},function(e,t,n){(function(e,r){var o;/*! https://mths.be/punycode v1.4.1 by @mathias */!function(i){t&&t.nodeType,e&&e.nodeType;var a="object"==typeof r&&r;a.global!==a&&a.window!==a&&a.self;var s,c=2147483647,l=/^xn--/,u=/[^\x20-\x7E]/,p=/[\x2E\u3002\uFF0E\uFF61]/g,f={overflow:"Overflow: input needs wider integers to process","not-basic":"Illegal input >= 0x80 (not a basic code point)","invalid-input":"Invalid input"},d=Math.floor,h=String.fromCharCode;function v(e){throw new RangeError(f[e])}function m(e,t){for(var n=e.length,r=[];n--;)r[n]=t(e[n]);return r}function g(e,t){var n=e.split("@"),r="";return n.length>1&&(r=n[0]+"@",e=n[1]),r+m((e=e.replace(p,".")).split("."),t).join(".")}function y(e){for(var t,n,r=[],o=0,i=e.length;o=55296&&t<=56319&&o65535&&(t+=h((e-=65536)>>>10&1023|55296),e=56320|1023&e),t+=h(e)})).join("")}function x(e,t){return e+22+75*(e<26)-((0!=t)<<5)}function w(e,t,n){var r=0;for(e=n?d(e/700):e>>1,e+=d(e/t);e>455;r+=36)e=d(e/35);return d(r+36*e/(e+38))}function k(e){var t,n,r,o,i,a,s,l,u,p,f,h=[],m=e.length,g=0,y=128,x=72;for((n=e.lastIndexOf("-"))<0&&(n=0),r=0;r=128&&v("not-basic"),h.push(e.charCodeAt(r));for(o=n>0?n+1:0;o=m&&v("invalid-input"),((l=(f=e.charCodeAt(o++))-48<10?f-22:f-65<26?f-65:f-97<26?f-97:36)>=36||l>d((c-g)/a))&&v("overflow"),g+=l*a,!(l<(u=s<=x?1:s>=x+26?26:s-x));s+=36)a>d(c/(p=36-u))&&v("overflow"),a*=p;x=w(g-i,t=h.length+1,0==i),d(g/t)>c-y&&v("overflow"),y+=d(g/t),g%=t,h.splice(g++,0,y)}return b(h)}function S(e){var t,n,r,o,i,a,s,l,u,p,f,m,g,b,k,S=[];for(m=(e=y(e)).length,t=128,n=0,i=72,a=0;a=t&&fd((c-n)/(g=r+1))&&v("overflow"),n+=(s-t)*g,t=s,a=0;ac&&v("overflow"),f==t){for(l=n,u=36;!(l<(p=u<=i?1:u>=i+26?26:u-i));u+=36)k=l-p,b=36-p,S.push(h(x(p+k%b,0))),l=d(k/b);S.push(h(x(l,0))),i=w(n,g,r==o),n=0,++r}++n,++t}return S.join("")}s={version:"1.4.1",ucs2:{decode:y,encode:b},decode:k,encode:S,toASCII:function(e){return g(e,(function(e){return u.test(e)?"xn--"+S(e):e}))},toUnicode:function(e){return g(e,(function(e){return l.test(e)?k(e.slice(4).toLowerCase()):e}))}},void 0===(o=function(){return s}.call(t,n,t,e))||(e.exports=o)}()}).call(this,n(103)(e),n(26))},function(e,t,n){"use strict";e.exports={isString:function(e){return"string"==typeof e},isObject:function(e){return"object"==typeof e&&null!==e},isNull:function(e){return null===e},isNullOrUndefined:function(e){return null==e}}},function(e,t,n){"use strict";t.decode=t.parse=n(299),t.encode=t.stringify=n(300)},function(e,t,n){"use strict";function r(e,t){return Object.prototype.hasOwnProperty.call(e,t)}e.exports=function(e,t,n,i){t=t||"&",n=n||"=";var a={};if("string"!=typeof e||0===e.length)return a;var s=/\+/g;e=e.split(t);var c=1e3;i&&"number"==typeof i.maxKeys&&(c=i.maxKeys);var l=e.length;c>0&&l>c&&(l=c);for(var u=0;u=0?(p=v.substr(0,m),f=v.substr(m+1)):(p=v,f=""),d=decodeURIComponent(p),h=decodeURIComponent(f),r(a,d)?o(a[d])?a[d].push(h):a[d]=[a[d],h]:a[d]=h}return a};var o=Array.isArray||function(e){return"[object Array]"===Object.prototype.toString.call(e)}},function(e,t,n){"use strict";var r=function(e){switch(typeof e){case"string":return e;case"boolean":return e?"true":"false";case"number":return isFinite(e)?e:"";default:return""}};e.exports=function(e,t,n,s){return t=t||"&",n=n||"=",null===e&&(e=void 0),"object"==typeof e?i(a(e),(function(a){var s=encodeURIComponent(r(a))+n;return o(e[a])?i(e[a],(function(e){return s+encodeURIComponent(r(e))})).join(t):s+encodeURIComponent(r(e[a]))})).join(t):s?encodeURIComponent(r(s))+n+encodeURIComponent(r(e)):""};var o=Array.isArray||function(e){return"[object Array]"===Object.prototype.toString.call(e)};function i(e,t){if(e.map)return e.map(t);for(var n=[],r=0;r0?r-4:r,p=0;p>16&255,s[c++]=t>>8&255,s[c++]=255&t;2===a&&(t=o[e.charCodeAt(p)]<<2|o[e.charCodeAt(p+1)]>>4,s[c++]=255&t);1===a&&(t=o[e.charCodeAt(p)]<<10|o[e.charCodeAt(p+1)]<<4|o[e.charCodeAt(p+2)]>>2,s[c++]=t>>8&255,s[c++]=255&t);return s},t.fromByteArray=function(e){for(var t,n=e.length,o=n%3,i=[],a=0,s=n-o;as?s:a+16383));1===o?(t=e[n-1],i.push(r[t>>2]+r[t<<4&63]+"==")):2===o&&(t=(e[n-2]<<8)+e[n-1],i.push(r[t>>10]+r[t>>4&63]+r[t<<2&63]+"="));return i.join("")};for(var r=[],o=[],i="undefined"!=typeof Uint8Array?Uint8Array:Array,a="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",s=0,c=a.length;s0)throw new Error("Invalid string. Length must be a multiple of 4");var n=e.indexOf("=");return-1===n&&(n=t),[n,n===t?0:4-n%4]}function u(e,t,n){for(var o,i,a=[],s=t;s>18&63]+r[i>>12&63]+r[i>>6&63]+r[63&i]);return a.join("")}o["-".charCodeAt(0)]=62,o["_".charCodeAt(0)]=63},function(e,t){t.read=function(e,t,n,r,o){var i,a,s=8*o-r-1,c=(1<>1,u=-7,p=n?o-1:0,f=n?-1:1,d=e[t+p];for(p+=f,i=d&(1<<-u)-1,d>>=-u,u+=s;u>0;i=256*i+e[t+p],p+=f,u-=8);for(a=i&(1<<-u)-1,i>>=-u,u+=r;u>0;a=256*a+e[t+p],p+=f,u-=8);if(0===i)i=1-l;else{if(i===c)return a?NaN:1/0*(d?-1:1);a+=Math.pow(2,r),i-=l}return(d?-1:1)*a*Math.pow(2,i-r)},t.write=function(e,t,n,r,o,i){var a,s,c,l=8*i-o-1,u=(1<>1,f=23===o?Math.pow(2,-24)-Math.pow(2,-77):0,d=r?0:i-1,h=r?1:-1,v=t<0||0===t&&1/t<0?1:0;for(t=Math.abs(t),isNaN(t)||t===1/0?(s=isNaN(t)?1:0,a=u):(a=Math.floor(Math.log(t)/Math.LN2),t*(c=Math.pow(2,-a))<1&&(a--,c*=2),(t+=a+p>=1?f/c:f*Math.pow(2,1-p))*c>=2&&(a++,c/=2),a+p>=u?(s=0,a=u):a+p>=1?(s=(t*c-1)*Math.pow(2,o),a+=p):(s=t*Math.pow(2,p-1)*Math.pow(2,o),a=0));o>=8;e[n+d]=255&s,d+=h,s/=256,o-=8);for(a=a<0;e[n+d]=255&a,d+=h,a/=256,l-=8);e[n+d-h]|=128*v}},function(e,t,n){"use strict";const r=n(177),o=n(178),i=n(34),a=n(57),s=n(179),c=n(104).jptr,l=n(135).recurse,u=n(105).clone,p=n(305).dereference,f=n(134).isRef,d=n(181);function h(e,t,n,r,o,a){let s=a.externalRefs[n+r].paths[0],p=i.parse(o),h={},v=1;for(;v;)v=0,l(e,{identityDetection:!0},(function(e,n,r){if(f(e,n))if(e[n].startsWith("#"))if(h[e[n]]||e.$fixed){if(!e.$fixed){let t=(s+"/"+h[e[n]]).split("/#/").join("/");r.parent[r.pkey]={$ref:t,"x-miro":e[n],$fixed:!0},a.verbose>1&&console.warn("Replacing with",t),v++}}else{let o=u(c(t,e[n]));if(a.verbose>1&&console.warn((!1===o?d.colour.red:d.colour.green)+"Fragment resolution",e[n],d.colour.normal),!1===o){if(r.parent[r.pkey]={},a.fatal){let t=new Error("Fragment $ref resolution failed "+e[n]);if(!a.promise)throw t;a.promise.reject(t)}}else v++,r.parent[r.pkey]=o,h[e[n]]=r.path.replace("/%24ref","")}else if(p.protocol){let t=i.resolve(o,e[n]).toString();a.verbose>1&&console.warn(d.colour.yellow+"Rewriting external url ref",e[n],"as",t,d.colour.normal),e["x-miro"]=e[n],a.externalRefs[e[n]]&&(a.externalRefs[t]||(a.externalRefs[t]=a.externalRefs[e[n]]),a.externalRefs[t].failed=a.externalRefs[e[n]].failed),e[n]=t}else if(!e["x-miro"]){let t=i.resolve(o,e[n]).toString(),r=!1;a.externalRefs[e[n]]&&(r=a.externalRefs[e[n]].failed),r||(a.verbose>1&&console.warn(d.colour.yellow+"Rewriting external ref",e[n],"as",t,d.colour.normal),e["x-miro"]=e[n],e[n]=t)}}));return l(e,{},(function(e,t,n){f(e,t)&&void 0!==e.$fixed&&delete e.$fixed})),a.verbose>1&&console.warn("Finished fragment resolution"),e}function v(e,t){if(!t.filters||!t.filters.length)return e;for(let n of t.filters)e=n(e,t);return e}function m(e,t,n,a){var l=i.parse(n.source),p=n.source.split("\\").join("/").split("/");p.pop()||p.pop();let f="",d=t.split("#");d.length>1&&(f="#"+d[1],t=d[0]),p=p.join("/");let m=i.parse(t),g=(y=m.protocol,b=l.protocol,y&&y.length>2?y:b&&b.length>2?b:"file:");var y,b;let x;if(x="file:"===g?o.resolve(p?p+"/":"",t):i.resolve(p?p+"/":"",t),n.cache[x]){n.verbose&&console.warn("CACHED",x,f);let e=u(n.cache[x]),r=n.externalRef=e;if(f&&(r=c(r,f),!1===r&&(r={},n.fatal))){let e=new Error("Cached $ref resolution failed "+x+f);if(!n.promise)throw e;n.promise.reject(e)}return r=h(r,e,t,f,x,n),r=v(r,n),a(u(r),x,n),Promise.resolve(r)}if(n.verbose&&console.warn("GET",x,f),n.handlers&&n.handlers[g])return n.handlers[g](p,t,f,n).then((function(e){return n.externalRef=e,e=v(e,n),n.cache[x]=e,a(e,x,n),e})).catch((function(e){throw n.verbose&&console.warn(e),e}));if(g&&g.startsWith("http")){const e=Object.assign({},n.fetchOptions,{agent:n.agent});return n.fetch(x,e).then((function(e){if(200!==e.status){if(n.ignoreIOErrors)return n.verbose&&console.warn("FAILED",t),n.externalRefs[t].failed=!0,'{"$ref":"'+t+'"}';throw new Error(`Received status code ${e.status}: ${x}`)}return e.text()})).then((function(e){try{let r=s.parse(e,{schema:"core",prettyErrors:!0});if(e=n.externalRef=r,n.cache[x]=u(e),f&&!1===(e=c(e,f))&&(e={},n.fatal)){let e=new Error("Remote $ref resolution failed "+x+f);if(!n.promise)throw e;n.promise.reject(e)}e=v(e=h(e,r,t,f,x,n),n)}catch(e){if(n.verbose&&console.warn(e),!n.promise||!n.fatal)throw e;n.promise.reject(e)}return a(e,x,n),e})).catch((function(e){if(n.verbose&&console.warn(e),n.cache[x]={},!n.promise||!n.fatal)throw e;n.promise.reject(e)}))}{const e='{"$ref":"'+t+'"}';return function(e,t,n,o,i){return new Promise((function(a,s){r.readFile(e,t,(function(e,t){e?n.ignoreIOErrors&&i?(n.verbose&&console.warn("FAILED",o),n.externalRefs[o].failed=!0,a(i)):s(e):a(t)}))}))}(x,n.encoding||"utf8",n,t,e).then((function(e){try{let r=s.parse(e,{schema:"core",prettyErrors:!0});if(e=n.externalRef=r,n.cache[x]=u(e),f&&!1===(e=c(e,f))&&(e={},n.fatal)){let e=new Error("File $ref resolution failed "+x+f);if(!n.promise)throw e;n.promise.reject(e)}e=v(e=h(e,r,t,f,x,n),n)}catch(e){if(n.verbose&&console.warn(e),!n.promise||!n.fatal)throw e;n.promise.reject(e)}return a(e,x,n),e})).catch((function(e){if(n.verbose&&console.warn(e),!n.promise||!n.fatal)throw e;n.promise.reject(e)}))}}function g(e){return new Promise((function(t,n){(function(e){return new Promise((function(t,n){function r(t,n,r){if(t[n]&&f(t[n],"$ref")){let i=t[n].$ref;if(!i.startsWith("#")){let a="";if(!o[i]){let t=Object.keys(o).find((function(e,t,n){return i.startsWith(e+"/")}));t&&(e.verbose&&console.warn("Found potential subschema at",t),a="/"+(i.split("#")[1]||"").replace(t.split("#")[1]||""),a=a.split("/undefined").join(""),i=t)}if(o[i]||(o[i]={resolved:!1,paths:[],extras:{},description:t[n].description}),o[i].resolved)if(o[i].failed);else if(e.rewriteRefs){let r=o[i].resolvedAt;e.verbose>1&&console.warn("Rewriting ref",i,r),t[n]["x-miro"]=i,t[n].$ref=r+a}else t[n]=u(o[i].data);else o[i].paths.push(r.path),o[i].extras[r.path]=a}}}let o=e.externalRefs;if(e.resolver.depth>0&&e.source===e.resolver.base)return t(o);l(e.openapi.definitions,{identityDetection:!0,path:"#/definitions"},r),l(e.openapi.components,{identityDetection:!0,path:"#/components"},r),l(e.openapi,{identityDetection:!0},r),t(o)}))})(e).then((function(t){for(let n in t)if(!t[n].resolved){let r=e.resolver.depth;r>0&&r++,e.resolver.actions[r].push((function(){return m(e.openapi,n,e,(function(e,r,o){if(!t[n].resolved){let i={};i.context=t[n],i.$ref=n,i.original=u(e),i.updated=e,i.source=r,o.externals.push(i),t[n].resolved=!0}let i=Object.assign({},o,{source:"",resolver:{actions:o.resolver.actions,depth:o.resolver.actions.length-1,base:o.resolver.base}});o.patch&&t[n].description&&!e.description&&"object"==typeof e&&(e.description=t[n].description),t[n].data=e;let a=(s=t[n].paths,[...new Set(s)]);var s;a=a.sort((function(e,t){const n=e.startsWith("#/components/")||e.startsWith("#/definitions/"),r=t.startsWith("#/components/")||t.startsWith("#/definitions/");return n&&!r?-1:r&&!n?1:0}));for(let r of a)if(t[n].resolvedAt&&r!==t[n].resolvedAt&&r.indexOf("x-ms-examples/")<0)o.verbose>1&&console.warn("Creating pointer to data at",r),c(o.openapi,r,{$ref:t[n].resolvedAt+t[n].extras[r],"x-miro":n+t[n].extras[r]});else{t[n].resolvedAt?o.verbose>1&&console.warn("Avoiding circular reference"):(t[n].resolvedAt=r,o.verbose>1&&console.warn("Creating initial clone of data at",r));let i=u(e);c(o.openapi,r,i)}0===o.resolver.actions[i.resolver.depth].length&&o.resolver.actions[i.resolver.depth].push((function(){return g(i)}))}))}))}})).catch((function(t){e.verbose&&console.warn(t),n(t)}));let r={options:e};r.actions=e.resolver.actions[e.resolver.depth],t(r)}))}function y(e,t,n){e.resolver.actions.push([]),g(e).then((function(r){var o;(o=r.actions,o.reduce((e,t)=>e.then(e=>t().then(Array.prototype.concat.bind(e))),Promise.resolve([]))).then((function(){if(e.resolver.depth>=e.resolver.actions.length)return console.warn("Ran off the end of resolver actions"),t(!0);e.resolver.depth++,e.resolver.actions[e.resolver.depth].length?setTimeout((function(){y(r.options,t,n)}),0):(e.verbose>1&&console.warn(d.colour.yellow+"Finished external resolution!",d.colour.normal),e.resolveInternal&&(e.verbose>1&&console.warn(d.colour.yellow+"Starting internal resolution!",d.colour.normal),e.openapi=p(e.openapi,e.original,{verbose:e.verbose-1}),e.verbose>1&&console.warn(d.colour.yellow+"Finished internal resolution!",d.colour.normal)),l(e.openapi,{},(function(t,n,r){f(t,n)&&(e.preserveMiro||delete t["x-miro"])})),t(e))})).catch((function(t){e.verbose&&console.warn(t),n(t)}))})).catch((function(t){e.verbose&&console.warn(t),n(t)}))}function b(e){if(e.cache||(e.cache={}),e.fetch||(e.fetch=a),e.source){let t=i.parse(e.source);(!t.protocol||t.protocol.length<=2)&&(e.source=o.resolve(e.source))}e.externals||(e.externals=[]),e.externalRefs||(e.externalRefs={}),e.rewriteRefs=!0,e.resolver={},e.resolver.depth=0,e.resolver.base=e.source,e.resolver.actions=[[]]}e.exports={optionalResolve:function(e){return b(e),new Promise((function(t,n){e.resolve?y(e,t,n):t(e)}))},resolve:function(e,t,n){return n||(n={}),n.openapi=e,n.source=t,n.resolve=!0,b(n),new Promise((function(e,t){y(n,e,t)}))}}},function(e,t,n){"use strict";const r=n(135).recurse,o=n(105).shallowClone,i=n(104).jptr,a=n(134).isRef;e.exports={dereference:function e(t,n,s){s||(s={}),s.cache||(s.cache={}),s.state||(s.state={}),s.state.identityDetection=!0,s.depth=s.depth?s.depth+1:1;let c=s.depth>1?t:o(t),l={data:c},u=s.depth>1?n:o(n);s.master||(s.master=c);let p=function(e){return e&&e.verbose?{warn:function(){var e=Array.prototype.slice.call(arguments);console.warn.apply(console,e)}}:{warn:function(){}}}(s),f=1;for(;f>0;)f=0,r(l,s.state,(function(t,n,r){if(a(t,n)){let o=t[n];if(f++,s.cache[o]){let e=s.cache[o];if(e.resolved)p.warn("Patching %s for %s",o,e.path),r.parent[r.pkey]=e.data,s.$ref&&"object"==typeof r.parent[r.pkey]&&(r.parent[r.pkey][s.$ref]=o);else{if(o===e.path)throw new Error("Tight circle at "+e.path);p.warn("Unresolved ref"),r.parent[r.pkey]=i(e.source,e.path),!1===r.parent[r.pkey]&&(r.parent[r.pkey]=i(e.source,e.key)),s.$ref&&"object"==typeof r.parent[r.pkey]&&(r.parent[s.$ref]=o)}}else{let t={};t.path=r.path.split("/$ref")[0],t.key=o,p.warn("Dereffing %s at %s",o,t.path),t.source=u,t.data=i(t.source,t.key),!1===t.data&&(t.data=i(s.master,t.key),t.source=s.master),!1===t.data&&p.warn("Missing $ref target",t.key),s.cache[o]=t,t.data=r.parent[r.pkey]=e(i(t.source,t.key),t.source,s),s.$ref&&"object"==typeof r.parent[r.pkey]&&(r.parent[r.pkey][s.$ref]=o),t.resolved=!0}}}));return l.data}}},function(e,t){e.exports=o,o.default=o,o.stable=a,o.stableStringify=a;var n=[],r=[];function o(e,t,o){var i;for(!function e(t,o,i,a){var s;if("object"==typeof t&&null!==t){for(s=0;st?1:0}function a(e,t,o){var a,c=function e(t,o,a,s){var c;if("object"==typeof t&&null!==t){for(c=0;c0)for(var o=0;o0?this.tail.next=t:this.head=t,this.tail=t,++this.length},e.prototype.unshift=function(e){var t={data:e,next:this.head};0===this.length&&(this.tail=t),this.head=t,++this.length},e.prototype.shift=function(){if(0!==this.length){var e=this.head.data;return 1===this.length?this.head=this.tail=null:this.head=this.head.next,--this.length,e}},e.prototype.clear=function(){this.head=this.tail=null,this.length=0},e.prototype.join=function(e){if(0===this.length)return"";for(var t=this.head,n=""+t.data;t=t.next;)n+=e+t.data;return n},e.prototype.concat=function(e){if(0===this.length)return r.alloc(0);if(1===this.length)return this.head.data;for(var t,n,o,i=r.allocUnsafe(e>>>0),a=this.head,s=0;a;)t=a.data,n=i,o=s,t.copy(n,o),s+=a.data.length,a=a.next;return i},e}(),o&&o.inspect&&o.inspect.custom&&(e.exports.prototype[o.inspect.custom]=function(){var e=o.inspect({length:this.length});return this.constructor.name+" "+e})},function(e,t){},function(e,t,n){(function(e){var r=void 0!==e&&e||"undefined"!=typeof self&&self||window,o=Function.prototype.apply;function i(e,t){this._id=e,this._clearFn=t}t.setTimeout=function(){return new i(o.call(setTimeout,r,arguments),clearTimeout)},t.setInterval=function(){return new i(o.call(setInterval,r,arguments),clearInterval)},t.clearTimeout=t.clearInterval=function(e){e&&e.close()},i.prototype.unref=i.prototype.ref=function(){},i.prototype.close=function(){this._clearFn.call(r,this._id)},t.enroll=function(e,t){clearTimeout(e._idleTimeoutId),e._idleTimeout=t},t.unenroll=function(e){clearTimeout(e._idleTimeoutId),e._idleTimeout=-1},t._unrefActive=t.active=function(e){clearTimeout(e._idleTimeoutId);var t=e._idleTimeout;t>=0&&(e._idleTimeoutId=setTimeout((function(){e._onTimeout&&e._onTimeout()}),t))},n(315),t.setImmediate="undefined"!=typeof self&&self.setImmediate||void 0!==e&&e.setImmediate||this&&this.setImmediate,t.clearImmediate="undefined"!=typeof self&&self.clearImmediate||void 0!==e&&e.clearImmediate||this&&this.clearImmediate}).call(this,n(26))},function(e,t,n){(function(e,t){!function(e,n){"use strict";if(!e.setImmediate){var r,o,i,a,s,c=1,l={},u=!1,p=e.document,f=Object.getPrototypeOf&&Object.getPrototypeOf(e);f=f&&f.setTimeout?f:e,"[object process]"==={}.toString.call(e.process)?r=function(e){t.nextTick((function(){h(e)}))}:!function(){if(e.postMessage&&!e.importScripts){var t=!0,n=e.onmessage;return e.onmessage=function(){t=!1},e.postMessage("","*"),e.onmessage=n,t}}()?e.MessageChannel?((i=new MessageChannel).port1.onmessage=function(e){h(e.data)},r=function(e){i.port2.postMessage(e)}):p&&"onreadystatechange"in p.createElement("script")?(o=p.documentElement,r=function(e){var t=p.createElement("script");t.onreadystatechange=function(){h(e),t.onreadystatechange=null,o.removeChild(t),t=null},o.appendChild(t)}):r=function(e){setTimeout(h,0,e)}:(a="setImmediate$"+Math.random()+"$",s=function(t){t.source===e&&"string"==typeof t.data&&0===t.data.indexOf(a)&&h(+t.data.slice(a.length))},e.addEventListener?e.addEventListener("message",s,!1):e.attachEvent("onmessage",s),r=function(t){e.postMessage(a+t,"*")}),f.setImmediate=function(e){"function"!=typeof e&&(e=new Function(""+e));for(var t=new Array(arguments.length-1),n=0;n","license":"BSD-3-Clause","dependencies":{"call-me-maybe":"^1.0.1","node-fetch":"^2.6.1","node-fetch-h2":"^2.3.0","node-readfiles":"^0.2.0","oas-kit-common":"^1.0.8","oas-resolver":"^2.5.3","oas-schema-walker":"^1.1.5","oas-validator":"^5.0.4","reftools":"^1.1.7","yaml":"^1.10.0","yargs":"^16.1.1"},"keywords":["swagger","openapi","openapi2","openapi3","converter","conversion","validator","validation","resolver","lint","linter"],"gitHead":"e26cda02a7d9491a1d2fed6d252dc80a01bd32d8"}')},function(e,t,n){var r=n(323);e.exports=r},function(e,t,n){n(324);var r=n(12).Object,o=e.exports=function(e,t,n){return r.defineProperty(e,t,n)};r.defineProperty.sham&&(o.sham=!0)},function(e,t,n){var r=n(7),o=n(22);r({target:"Object",stat:!0,forced:!o,sham:!o},{defineProperty:n(38).f})},function(e,t,n){e.exports=n(326)},function(e,t,n){var r=n(327);e.exports=r},function(e,t,n){n(328);var r=n(12).Object,o=e.exports=function(e,t){return r.defineProperties(e,t)};r.defineProperties.sham&&(o.sham=!0)},function(e,t,n){var r=n(7),o=n(22);r({target:"Object",stat:!0,forced:!o,sham:!o},{defineProperties:n(194)})},function(e,t,n){e.exports=n(330)},function(e,t,n){var r=n(331);e.exports=r},function(e,t,n){n(332);var r=n(12);e.exports=r.Object.getOwnPropertyDescriptors},function(e,t,n){var r=n(7),o=n(22),i=n(333),a=n(42),s=n(69),c=n(88);r({target:"Object",stat:!0,sham:!o},{getOwnPropertyDescriptors:function(e){for(var t,n,r=a(e),o=s.f,l=i(r),u={},p=0;l.length>p;)void 0!==(n=o(r,t=l[p++]))&&c(u,t,n);return u}})},function(e,t,n){var r=n(43),o=n(141),i=n(142),a=n(30);e.exports=r("Reflect","ownKeys")||function(e){var t=o.f(a(e)),n=i.f;return n?t.concat(n(e)):t}},function(e,t,n){e.exports=n(335)},function(e,t,n){var r=n(196);e.exports=r},function(e,t,n){var r=n(16),o=n(37);e.exports=function(e,t){try{o(r,e,t)}catch(n){r[e]=t}return t}},function(e,t,n){"use strict";var r=n(202).IteratorPrototype,o=n(90),i=n(70),a=n(62),s=n(61),c=function(){return this};e.exports=function(e,t,n){var l=t+" Iterator";return e.prototype=o(r,{next:i(1,n)}),a(e,l,!1,!0),s[l]=c,e}},function(e,t,n){"use strict";var r=n(146),o=n(72);e.exports=r?{}.toString:function(){return"[object "+o(this)+"]"}},function(e,t,n){var r=n(17);e.exports=function(e){if(!r(e)&&null!==e)throw TypeError("Can't set "+String(e)+" as a prototype");return e}},function(e,t){e.exports={CSSRuleList:0,CSSStyleDeclaration:0,CSSValueList:0,ClientRectList:0,DOMRectList:0,DOMStringList:0,DOMTokenList:1,DataTransferItemList:0,FileList:0,HTMLAllCollection:0,HTMLCollection:0,HTMLFormElement:0,HTMLSelectElement:0,MediaList:0,MimeTypeArray:0,NamedNodeMap:0,NodeList:1,PaintRequestList:0,Plugin:0,PluginArray:0,SVGLengthList:0,SVGNumberList:0,SVGPathSegList:0,SVGPointList:0,SVGStringList:0,SVGTransformList:0,SourceBufferList:0,StyleSheetList:0,TextTrackCueList:0,TextTrackList:0,TouchList:0}},function(e,t,n){var r=n(342);e.exports=r},function(e,t,n){n(343);var r=n(27);e.exports=r("Array").forEach},function(e,t,n){"use strict";var r=n(7),o=n(344);r({target:"Array",proto:!0,forced:[].forEach!=o},{forEach:o})},function(e,t,n){"use strict";var r=n(49).forEach,o=n(93),i=n(39),a=o("forEach"),s=i("forEach");e.exports=a&&s?[].forEach:function(e){return r(this,e,arguments.length>1?arguments[1]:void 0)}},function(e,t,n){var r=n(346);e.exports=r},function(e,t,n){n(347);var r=n(12).Object,o=e.exports=function(e,t){return r.getOwnPropertyDescriptor(e,t)};r.getOwnPropertyDescriptor.sham&&(o.sham=!0)},function(e,t,n){var r=n(7),o=n(13),i=n(42),a=n(69).f,s=n(22),c=o((function(){a(1)}));r({target:"Object",stat:!0,forced:!s||c,sham:!s},{getOwnPropertyDescriptor:function(e,t){return a(i(e),t)}})},function(e,t,n){e.exports=n(349)},function(e,t,n){var r=n(207);e.exports=r},function(e,t,n){n(351);var r=n(27);e.exports=r("Array").filter},function(e,t,n){"use strict";var r=n(7),o=n(49).filter,i=n(94),a=n(39),s=i("filter"),c=a("filter");r({target:"Array",proto:!0,forced:!s||!c},{filter:function(e){return o(this,e,arguments.length>1?arguments[1]:void 0)}})},function(e,t,n){var r=n(353);e.exports=r},function(e,t,n){n(210);var r=n(12);e.exports=r.Object.getOwnPropertySymbols},function(e,t,n){var r=n(42),o=n(141).f,i={}.toString,a="object"==typeof window&&window&&Object.getOwnPropertyNames?Object.getOwnPropertyNames(window):[];e.exports.f=function(e){return a&&"[object Window]"==i.call(e)?function(e){try{return o(e)}catch(e){return a.slice()}}(e):o(r(e))}},function(e,t,n){var r=n(212);e.exports=r},function(e,t,n){var r=n(7),o=n(44),i=n(86);r({target:"Object",stat:!0,forced:n(13)((function(){i(1)}))},{keys:function(e){return i(o(e))}})},function(e,t,n){var r=n(358);e.exports=r},function(e,t,n){var r=n(359),o=Function.prototype;e.exports=function(e){var t=e.bind;return e===o||e instanceof Function&&t===o.bind?r:t}},function(e,t,n){n(360);var r=n(27);e.exports=r("Function").bind},function(e,t,n){n(7)({target:"Function",proto:!0},{bind:n(213)})},function(e,t,n){var r=n(51),o=n(364),i=n(36),a=n(217),s=/^\[object .+?Constructor\]$/,c=Function.prototype,l=Object.prototype,u=c.toString,p=l.hasOwnProperty,f=RegExp("^"+u.call(p).replace(/[\\^$.*+?()[\]{}|]/g,"\\$&").replace(/hasOwnProperty|(function).*?(?=\\\()| for .+?(?=\\\])/g,"$1.*?")+"$");e.exports=function(e){return!(!i(e)||o(e))&&(r(e)?f:s).test(a(e))}},function(e,t,n){var r=n(73),o=Object.prototype,i=o.hasOwnProperty,a=o.toString,s=r?r.toStringTag:void 0;e.exports=function(e){var t=i.call(e,s),n=e[s];try{e[s]=void 0;var r=!0}catch(e){}var o=a.call(e);return r&&(t?e[s]=n:delete e[s]),o}},function(e,t){var n=Object.prototype.toString;e.exports=function(e){return n.call(e)}},function(e,t,n){var r,o=n(365),i=(r=/[^.]+$/.exec(o&&o.keys&&o.keys.IE_PROTO||""))?"Symbol(src)_1."+r:"";e.exports=function(e){return!!i&&i in e}},function(e,t,n){var r=n(45)["__core-js_shared__"];e.exports=r},function(e,t){e.exports=function(e,t){return null==e?void 0:e[t]}},function(e,t,n){var r=n(368),o=n(373);e.exports=function(e){return r((function(t,n){var r=-1,i=n.length,a=i>1?n[i-1]:void 0,s=i>2?n[2]:void 0;for(a=e.length>3&&"function"==typeof a?(i--,a):void 0,s&&o(n[0],n[1],s)&&(a=i<3?void 0:a,i=1),t=Object(t);++r0){if(++t>=800)return arguments[0]}else t=0;return e.apply(void 0,arguments)}}},function(e,t,n){var r=n(116),o=n(74),i=n(117),a=n(36);e.exports=function(e,t,n){if(!a(n))return!1;var s=typeof t;return!!("number"==s?o(n)&&i(t,n.length):"string"==s&&t in n)&&r(n[t],e)}},function(e,t){e.exports=function(e,t){for(var n=-1,r=Array(e);++n=t?e:t)),e}},function(e,t){e.exports=function(e,t){for(var n=-1,r=null==e?0:e.length,o=Array(r);++n=0;--o){var i=this.tryEntries[o],a=i.completion;if("root"===i.tryLoc)return r("end");if(i.tryLoc<=this.prev){var s=n.call(i,"catchLoc"),c=n.call(i,"finallyLoc");if(s&&c){if(this.prev=0;--r){var o=this.tryEntries[r];if(o.tryLoc<=this.prev&&n.call(o,"finallyLoc")&&this.prev=0;--t){var n=this.tryEntries[t];if(n.finallyLoc===e)return this.complete(n.completion,n.afterLoc),w(n),l}},catch:function(e){for(var t=this.tryEntries.length-1;t>=0;--t){var n=this.tryEntries[t];if(n.tryLoc===e){var r=n.completion;if("throw"===r.type){var o=r.arg;w(n)}return o}}throw new Error("illegal catch attempt")},delegateYield:function(e,t,n){return this.delegate={iterator:S(e),resultName:t,nextLoc:n},"next"===this.method&&(this.arg=void 0),l}},e}(e.exports);try{regeneratorRuntime=r}catch(e){Function("r","regeneratorRuntime = r")(r)}},function(e,t,n){var r=n(207);e.exports=r},function(e,t,n){n(55),n(76);var r=n(387);e.exports=r},function(e,t,n){var r=n(112),o=n(110),i=function(e){return function(t,n){var i,a,s=String(o(t)),c=r(n),l=s.length;return c<0||c>=l?e?"":void 0:(i=s.charCodeAt(c))<55296||i>56319||c+1===l||(a=s.charCodeAt(c+1))<56320||a>57343?e?s.charAt(c):i:e?s.slice(c,c+2):a-56320+(i-55296<<10)+65536}};e.exports={codeAt:i(!1),charAt:i(!0)}},function(e,t,n){var r=n(30),o=n(121);e.exports=function(e){var t=o(e);if("function"!=typeof t)throw TypeError(String(e)+" is not iterable");return r(t.call(e))}},function(e,t,n){var r=n(227);e.exports=r},function(e,t,n){n(7)({target:"Array",stat:!0},{isArray:n(92)})},function(e,t,n){e.exports=n(391)},function(e,t,n){n(55),n(76);var r=n(121);e.exports=r},function(e,t,n){var r=n(393);n(409),n(410),n(411),n(412),n(413),e.exports=r},function(e,t,n){n(228),n(157),n(210),n(394),n(395),n(396),n(397),n(229),n(398),n(399),n(400),n(401),n(402),n(403),n(404),n(405),n(406),n(407),n(408);var r=n(12);e.exports=r.Symbol},function(e,t,n){n(18)("asyncIterator")},function(e,t){},function(e,t,n){n(18)("hasInstance")},function(e,t,n){n(18)("isConcatSpreadable")},function(e,t,n){n(18)("match")},function(e,t,n){n(18)("matchAll")},function(e,t,n){n(18)("replace")},function(e,t,n){n(18)("search")},function(e,t,n){n(18)("species")},function(e,t,n){n(18)("split")},function(e,t,n){n(18)("toPrimitive")},function(e,t,n){n(18)("toStringTag")},function(e,t,n){n(18)("unscopables")},function(e,t,n){n(62)(Math,"Math",!0)},function(e,t,n){var r=n(16);n(62)(r.JSON,"JSON",!0)},function(e,t,n){n(18)("asyncDispose")},function(e,t,n){n(18)("dispose")},function(e,t,n){n(18)("observable")},function(e,t,n){n(18)("patternMatch")},function(e,t,n){n(18)("replaceAll")},function(e,t,n){var r=n(231);e.exports=r},function(e,t,n){var r=n(7),o=n(416);r({target:"Array",stat:!0,forced:!n(234)((function(e){Array.from(e)}))},{from:o})},function(e,t,n){"use strict";var r=n(85),o=n(44),i=n(232),a=n(233),s=n(54),c=n(88),l=n(121);e.exports=function(e){var t,n,u,p,f,d,h=o(e),v="function"==typeof this?this:Array,m=arguments.length,g=m>1?arguments[1]:void 0,y=void 0!==g,b=l(h),x=0;if(y&&(g=r(g,m>2?arguments[2]:void 0,2)),null==b||v==Array&&a(b))for(n=new v(t=s(h.length));t>x;x++)d=y?g(h[x],x):h[x],c(n,x,d);else for(f=(p=b.call(h)).next,n=new v;!(u=f.call(p)).done;x++)d=y?i(p,g,[u.value,x],!0):u.value,c(n,x,d);return n.length=x,n}},function(e,t,n){e.exports=n(418)},function(e,t,n){var r=n(235);e.exports=r},function(e,t,n){n(420);var r=n(27);e.exports=r("Array").slice},function(e,t,n){"use strict";var r=n(7),o=n(17),i=n(92),a=n(139),s=n(54),c=n(42),l=n(88),u=n(14),p=n(94),f=n(39),d=p("slice"),h=f("slice",{ACCESSORS:!0,0:0,1:2}),v=u("species"),m=[].slice,g=Math.max;r({target:"Array",proto:!0,forced:!d||!h},{slice:function(e,t){var n,r,u,p=c(this),f=s(p.length),d=a(e,f),h=a(void 0===t?f:t,f);if(i(p)&&("function"!=typeof(n=p.constructor)||n!==Array&&!i(n.prototype)?o(n)&&null===(n=n[v])&&(n=void 0):n=void 0,n===Array||void 0===n))return m.call(p,d,h);for(r=new(void 0===n?Array:n)(g(h-d,0)),u=0;du;)n=c[u++],r&&!a.call(s,n)||p.push(e?[n,s[n]]:s[n]);return p}};e.exports={entries:s(!0),values:s(!1)}},function(e,t,n){var r=n(426);e.exports=r},function(e,t,n){var r=n(427),o=Array.prototype;e.exports=function(e){var t=e.concat;return e===o||e instanceof Array&&t===o.concat?r:t}},function(e,t,n){n(228);var r=n(27);e.exports=r("Array").concat},function(e,t,n){var r=n(212);e.exports=r},function(e,t,n){var r=n(430);e.exports=r},function(e,t,n){var r=n(431),o=Array.prototype;e.exports=function(e){var t=e.map;return e===o||e instanceof Array&&t===o.map?r:t}},function(e,t,n){n(432);var r=n(27);e.exports=r("Array").map},function(e,t,n){"use strict";var r=n(7),o=n(49).map,i=n(94),a=n(39),s=i("map"),c=a("map");r({target:"Array",proto:!0,forced:!s||!c},{map:function(e){return o(this,e,arguments.length>1?arguments[1]:void 0)}})},function(e,t,n){var r=n(434);e.exports=r},function(e,t,n){var r=n(435),o=Array.prototype;e.exports=function(e){var t=e.every;return e===o||e instanceof Array&&t===o.every?r:t}},function(e,t,n){n(436);var r=n(27);e.exports=r("Array").every},function(e,t,n){"use strict";var r=n(7),o=n(49).every,i=n(93),a=n(39),s=i("every"),c=a("every");r({target:"Array",proto:!0,forced:!s||!c},{every:function(e){return o(this,e,arguments.length>1?arguments[1]:void 0)}})},function(e,t,n){var r=n(438);e.exports=r},function(e,t,n){n(439);var r=n(12);r.JSON||(r.JSON={stringify:JSON.stringify}),e.exports=function(e,t,n){return r.JSON.stringify.apply(null,arguments)}},function(e,t,n){var r=n(7),o=n(43),i=n(13),a=o("JSON","stringify"),s=/[\uD800-\uDFFF]/g,c=/^[\uD800-\uDBFF]$/,l=/^[\uDC00-\uDFFF]$/,u=function(e,t,n){var r=n.charAt(t-1),o=n.charAt(t+1);return c.test(e)&&!l.test(o)||l.test(e)&&!c.test(r)?"\\u"+e.charCodeAt(0).toString(16):e},p=i((function(){return'"\\udf06\\ud834"'!==a("\udf06\ud834")||'"\\udead"'!==a("\udead")}));a&&r({target:"JSON",stat:!0,forced:p},{stringify:function(e,t,n){var r=a.apply(null,arguments);return"string"==typeof r?r.replace(s,u):r}})},function(e,t,n){var r=n(441);e.exports=r},function(e,t,n){var r=n(442),o=Array.prototype;e.exports=function(e){var t=e.some;return e===o||e instanceof Array&&t===o.some?r:t}},function(e,t,n){n(443);var r=n(27);e.exports=r("Array").some},function(e,t,n){"use strict";var r=n(7),o=n(49).some,i=n(93),a=n(39),s=i("some"),c=a("some");r({target:"Array",proto:!0,forced:!s||!c},{some:function(e){return o(this,e,arguments.length>1?arguments[1]:void 0)}})},function(e,t,n){var r=n(227);e.exports=r},function(e,t,n){e.exports=n(446)},function(e,t,n){var r=n(447);e.exports=r},function(e,t,n){n(229),n(76),n(55);var r=n(150);e.exports=r.f("iterator")},function(e,t,n){var r=n(156);e.exports=function(e){if(r(e))return e}},function(e,t,n){var r=n(226),o=n(237),i=n(122);e.exports=function(e,t){if(void 0!==i&&o(Object(e))){var n=[],a=!0,s=!1,c=void 0;try{for(var l,u=r(e);!(a=(l=u.next()).done)&&(n.push(l.value),!t||n.length!==t);a=!0);}catch(e){s=!0,c=e}finally{try{a||null==u.return||u.return()}finally{if(s)throw c}}return n}}},function(e,t,n){n(55),n(76);var r=n(451);e.exports=r},function(e,t,n){var r=n(72),o=n(14),i=n(61),a=o("iterator");e.exports=function(e){var t=Object(e);return void 0!==t[a]||"@@iterator"in t||i.hasOwnProperty(r(t))}},function(e,t){e.exports=function(){throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}},function(e,t,n){var r=n(231);e.exports=r},function(e,t,n){var r=n(455);e.exports=r},function(e,t,n){var r=n(456),o=Array.prototype;e.exports=function(e){var t=e.reduce;return e===o||e instanceof Array&&t===o.reduce?r:t}},function(e,t,n){n(457);var r=n(27);e.exports=r("Array").reduce},function(e,t,n){"use strict";var r=n(7),o=n(458).left,i=n(93),a=n(39),s=i("reduce"),c=a("reduce",{1:0});r({target:"Array",proto:!0,forced:!s||!c},{reduce:function(e){return o(this,e,arguments.length,arguments.length>1?arguments[1]:void 0)}})},function(e,t,n){var r=n(48),o=n(44),i=n(109),a=n(54),s=function(e){return function(t,n,s,c){r(n);var l=o(t),u=i(l),p=a(l.length),f=e?p-1:0,d=e?-1:1;if(s<2)for(;;){if(f in u){c=u[f],f+=d;break}if(f+=d,e?f<0:p<=f)throw TypeError("Reduce of empty array with no initial value")}for(;e?f>=0:p>f;f+=d)f in u&&(c=n(c,u[f],f,l));return c}};e.exports={left:s(!1),right:s(!0)}},function(e,t,n){n(55);var r=n(460),o=n(72),i=Array.prototype,a={DOMTokenList:!0,NodeList:!0};e.exports=function(e){var t=e.entries;return e===i||e instanceof Array&&t===i.entries||a.hasOwnProperty(o(e))?r:t}},function(e,t,n){var r=n(461);e.exports=r},function(e,t,n){n(197);var r=n(27);e.exports=r("Array").entries},function(e,t,n){var r=n(463);e.exports=r},function(e,t,n){var r=n(464),o=n(466),i=Array.prototype,a=String.prototype;e.exports=function(e){var t=e.includes;return e===i||e instanceof Array&&t===i.includes?r:"string"==typeof e||e===a||e instanceof String&&t===a.includes?o:t}},function(e,t,n){n(465);var r=n(27);e.exports=r("Array").includes},function(e,t,n){"use strict";var r=n(7),o=n(138).includes,i=n(143);r({target:"Array",proto:!0,forced:!n(39)("indexOf",{ACCESSORS:!0,1:0})},{includes:function(e){return o(this,e,arguments.length>1?arguments[1]:void 0)}}),i("includes")},function(e,t,n){n(467);var r=n(27);e.exports=r("String").includes},function(e,t,n){"use strict";var r=n(7),o=n(468),i=n(110);r({target:"String",proto:!0,forced:!n(470)("includes")},{includes:function(e){return!!~String(i(this)).indexOf(o(e),arguments.length>1?arguments[1]:void 0)}})},function(e,t,n){var r=n(469);e.exports=function(e){if(r(e))throw TypeError("The method doesn't accept regular expressions");return e}},function(e,t,n){var r=n(17),o=n(60),i=n(14)("match");e.exports=function(e){var t;return r(e)&&(void 0!==(t=e[i])?!!t:"RegExp"==o(e))}},function(e,t,n){var r=n(14)("match");e.exports=function(e){var t=/./;try{"/./"[e](t)}catch(n){try{return t[r]=!1,"/./"[e](t)}catch(e){}}return!1}},function(e,t,n){var r=n(238);e.exports=r},function(e,t,n){n(473);var r=n(27);e.exports=r("Array").indexOf},function(e,t,n){"use strict";var r=n(7),o=n(138).indexOf,i=n(93),a=n(39),s=[].indexOf,c=!!s&&1/[1].indexOf(1,-0)<0,l=i("indexOf"),u=a("indexOf",{ACCESSORS:!0,1:0});r({target:"Array",proto:!0,forced:c||!l||!u},{indexOf:function(e){return c?s.apply(this,arguments)||0:o(this,e,arguments.length>1?arguments[1]:void 0)}})},function(e,t,n){e.exports=n(475)},function(e,t,n){var r=n(239);n(481),n(482),n(483),n(484),e.exports=r},function(e,t,n){"use strict";var r,o,i,a,s=n(7),c=n(71),l=n(16),u=n(43),p=n(240),f=n(91),d=n(159),h=n(62),v=n(477),m=n(17),g=n(48),y=n(160),b=n(60),x=n(199),w=n(77),k=n(234),S=n(241),O=n(242).set,A=n(478),E=n(244),_=n(479),j=n(97),T=n(123),C=n(56),I=n(193),P=n(14),R=n(149),L=P("species"),N="Promise",M=C.get,B=C.set,D=C.getterFor(N),q=p,F=l.TypeError,z=l.document,U=l.process,$=u("fetch"),H=j.f,W=H,V="process"==b(U),Y=!!(z&&z.createEvent&&l.dispatchEvent),K=I(N,(function(){if(!(x(q)!==String(q))){if(66===R)return!0;if(!V&&"function"!=typeof PromiseRejectionEvent)return!0}if(c&&!q.prototype.finally)return!0;if(R>=51&&/native code/.test(q))return!1;var e=q.resolve(1),t=function(e){e((function(){}),(function(){}))};return(e.constructor={})[L]=t,!(e.then((function(){}))instanceof t)})),G=K||!k((function(e){q.all(e).catch((function(){}))})),J=function(e){var t;return!(!m(e)||"function"!=typeof(t=e.then))&&t},Q=function(e,t,n){if(!t.notified){t.notified=!0;var r=t.reactions;A((function(){for(var o=t.value,i=1==t.state,a=0;r.length>a;){var s,c,l,u=r[a++],p=i?u.ok:u.fail,f=u.resolve,d=u.reject,h=u.domain;try{p?(i||(2===t.rejection&&te(e,t),t.rejection=1),!0===p?s=o:(h&&h.enter(),s=p(o),h&&(h.exit(),l=!0)),s===u.promise?d(F("Promise-chain cycle")):(c=J(s))?c.call(s,f,d):f(s)):d(o)}catch(e){h&&!l&&h.exit(),d(e)}}t.reactions=[],t.notified=!1,n&&!t.rejection&&Z(e,t)}))}},X=function(e,t,n){var r,o;Y?((r=z.createEvent("Event")).promise=t,r.reason=n,r.initEvent(e,!1,!0),l.dispatchEvent(r)):r={promise:t,reason:n},(o=l["on"+e])?o(r):"unhandledrejection"===e&&_("Unhandled promise rejection",n)},Z=function(e,t){O.call(l,(function(){var n,r=t.value;if(ee(t)&&(n=T((function(){V?U.emit("unhandledRejection",r,e):X("unhandledrejection",e,r)})),t.rejection=V||ee(t)?2:1,n.error))throw n.value}))},ee=function(e){return 1!==e.rejection&&!e.parent},te=function(e,t){O.call(l,(function(){V?U.emit("rejectionHandled",e):X("rejectionhandled",e,t.value)}))},ne=function(e,t,n,r){return function(o){e(t,n,o,r)}},re=function(e,t,n,r){t.done||(t.done=!0,r&&(t=r),t.value=n,t.state=2,Q(e,t,!0))},oe=function(e,t,n,r){if(!t.done){t.done=!0,r&&(t=r);try{if(e===n)throw F("Promise can't be resolved itself");var o=J(n);o?A((function(){var r={done:!1};try{o.call(n,ne(oe,e,r,t),ne(re,e,r,t))}catch(n){re(e,r,n,t)}})):(t.value=n,t.state=1,Q(e,t,!1))}catch(n){re(e,{done:!1},n,t)}}};K&&(q=function(e){y(this,q,N),g(e),r.call(this);var t=M(this);try{e(ne(oe,this,t),ne(re,this,t))}catch(e){re(this,t,e)}},(r=function(e){B(this,{type:N,done:!1,notified:!1,parent:!1,reactions:[],rejection:!1,state:0,value:void 0})}).prototype=d(q.prototype,{then:function(e,t){var n=D(this),r=H(S(this,q));return r.ok="function"!=typeof e||e,r.fail="function"==typeof t&&t,r.domain=V?U.domain:void 0,n.parent=!0,n.reactions.push(r),0!=n.state&&Q(this,n,!1),r.promise},catch:function(e){return this.then(void 0,e)}}),o=function(){var e=new r,t=M(e);this.promise=e,this.resolve=ne(oe,e,t),this.reject=ne(re,e,t)},j.f=H=function(e){return e===q||e===i?new o(e):W(e)},c||"function"!=typeof p||(a=p.prototype.then,f(p.prototype,"then",(function(e,t){var n=this;return new q((function(e,t){a.call(n,e,t)})).then(e,t)}),{unsafe:!0}),"function"==typeof $&&s({global:!0,enumerable:!0,forced:!0},{fetch:function(e){return E(q,$.apply(l,arguments))}}))),s({global:!0,wrap:!0,forced:K},{Promise:q}),h(q,N,!1,!0),v(N),i=u(N),s({target:N,stat:!0,forced:K},{reject:function(e){var t=H(this);return t.reject.call(void 0,e),t.promise}}),s({target:N,stat:!0,forced:c||K},{resolve:function(e){return E(c&&this===i?q:this,e)}}),s({target:N,stat:!0,forced:G},{all:function(e){var t=this,n=H(t),r=n.resolve,o=n.reject,i=T((function(){var n=g(t.resolve),i=[],a=0,s=1;w(e,(function(e){var c=a++,l=!1;i.push(void 0),s++,n.call(t,e).then((function(e){l||(l=!0,i[c]=e,--s||r(i))}),o)})),--s||r(i)}));return i.error&&o(i.value),n.promise},race:function(e){var t=this,n=H(t),r=n.reject,o=T((function(){var o=g(t.resolve);w(e,(function(e){o.call(t,e).then(n.resolve,r)}))}));return o.error&&r(o.value),n.promise}})},function(e,t,n){"use strict";var r=n(43),o=n(38),i=n(14),a=n(22),s=i("species");e.exports=function(e){var t=r(e),n=o.f;a&&t&&!t[s]&&n(t,s,{configurable:!0,get:function(){return this}})}},function(e,t,n){var r,o,i,a,s,c,l,u,p=n(16),f=n(69).f,d=n(60),h=n(242).set,v=n(243),m=p.MutationObserver||p.WebKitMutationObserver,g=p.process,y=p.Promise,b="process"==d(g),x=f(p,"queueMicrotask"),w=x&&x.value;w||(r=function(){var e,t;for(b&&(e=g.domain)&&e.exit();o;){t=o.fn,o=o.next;try{t()}catch(e){throw o?a():i=void 0,e}}i=void 0,e&&e.enter()},b?a=function(){g.nextTick(r)}:m&&!v?(s=!0,c=document.createTextNode(""),new m(r).observe(c,{characterData:!0}),a=function(){c.data=s=!s}):y&&y.resolve?(l=y.resolve(void 0),u=l.then,a=function(){u.call(l,r)}):a=function(){h.call(p,r)}),e.exports=w||function(e){var t={fn:e,next:void 0};i&&(i.next=t),o||(o=t,a()),i=t}},function(e,t,n){var r=n(16);e.exports=function(e,t){var n=r.console;n&&n.error&&(1===arguments.length?n.error(e):n.error(e,t))}},function(e,t,n){"use strict";var r=n(7),o=n(71),i=n(240),a=n(13),s=n(43),c=n(241),l=n(244),u=n(91);r({target:"Promise",proto:!0,real:!0,forced:!!i&&a((function(){i.prototype.finally.call({then:function(){}},(function(){}))}))},{finally:function(e){var t=c(this,s("Promise")),n="function"==typeof e;return this.then(n?function(n){return l(t,e()).then((function(){return n}))}:e,n?function(n){return l(t,e()).then((function(){throw n}))}:e)}}),o||"function"!=typeof i||i.prototype.finally||u(i.prototype,"finally",s("Promise").prototype.finally)},function(e,t,n){"use strict";var r=n(7),o=n(22),i=n(89),a=n(147),s=n(90),c=n(38),l=n(70),u=n(77),p=n(37),f=n(56),d=f.set,h=f.getterFor("AggregateError"),v=function(e,t){var n=this;if(!(n instanceof v))return new v(e,t);a&&(n=a(new Error(t),i(n)));var r=[];return u(e,r.push,r),o?d(n,{errors:r,type:"AggregateError"}):n.errors=r,void 0!==t&&p(n,"message",String(t)),n};v.prototype=s(Error.prototype,{constructor:l(5,v),message:l(5,""),name:l(5,"AggregateError")}),o&&c.f(v.prototype,"errors",{get:function(){return h(this).errors},configurable:!0}),r({global:!0},{AggregateError:v})},function(e,t,n){n(245)},function(e,t,n){"use strict";var r=n(7),o=n(97),i=n(123);r({target:"Promise",stat:!0},{try:function(e){var t=o.f(this),n=i(e);return(n.error?t.reject:t.resolve)(n.value),t.promise}})},function(e,t,n){"use strict";var r=n(7),o=n(48),i=n(43),a=n(97),s=n(123),c=n(77);r({target:"Promise",stat:!0},{any:function(e){var t=this,n=a.f(t),r=n.resolve,l=n.reject,u=s((function(){var n=o(t.resolve),a=[],s=0,u=1,p=!1;c(e,(function(e){var o=s++,c=!1;a.push(void 0),u++,n.call(t,e).then((function(e){c||p||(p=!0,r(e))}),(function(e){c||p||(c=!0,a[o]=e,--u||l(new(i("AggregateError"))(a,"No one promise resolved")))}))})),--u||l(new(i("AggregateError"))(a,"No one promise resolved"))}));return u.error&&l(u.value),n.promise}})},function(e,t){!function(e){!function(t){var n="URLSearchParams"in e,r="Symbol"in e&&"iterator"in Symbol,o="FileReader"in e&&"Blob"in e&&function(){try{return new Blob,!0}catch(e){return!1}}(),i="FormData"in e,a="ArrayBuffer"in e;if(a)var s=["[object Int8Array]","[object Uint8Array]","[object Uint8ClampedArray]","[object Int16Array]","[object Uint16Array]","[object Int32Array]","[object Uint32Array]","[object Float32Array]","[object Float64Array]"],c=ArrayBuffer.isView||function(e){return e&&s.indexOf(Object.prototype.toString.call(e))>-1};function l(e){if("string"!=typeof e&&(e=String(e)),/[^a-z0-9\-#$%&'*+.^_`|~]/i.test(e))throw new TypeError("Invalid character in header field name");return e.toLowerCase()}function u(e){return"string"!=typeof e&&(e=String(e)),e}function p(e){var t={next:function(){var t=e.shift();return{done:void 0===t,value:t}}};return r&&(t[Symbol.iterator]=function(){return t}),t}function f(e){this.map={},e instanceof f?e.forEach((function(e,t){this.append(t,e)}),this):Array.isArray(e)?e.forEach((function(e){this.append(e[0],e[1])}),this):e&&Object.getOwnPropertyNames(e).forEach((function(t){this.append(t,e[t])}),this)}function d(e){if(e.bodyUsed)return Promise.reject(new TypeError("Already read"));e.bodyUsed=!0}function h(e){return new Promise((function(t,n){e.onload=function(){t(e.result)},e.onerror=function(){n(e.error)}}))}function v(e){var t=new FileReader,n=h(t);return t.readAsArrayBuffer(e),n}function m(e){if(e.slice)return e.slice(0);var t=new Uint8Array(e.byteLength);return t.set(new Uint8Array(e)),t.buffer}function g(){return this.bodyUsed=!1,this._initBody=function(e){var t;this._bodyInit=e,e?"string"==typeof e?this._bodyText=e:o&&Blob.prototype.isPrototypeOf(e)?this._bodyBlob=e:i&&FormData.prototype.isPrototypeOf(e)?this._bodyFormData=e:n&&URLSearchParams.prototype.isPrototypeOf(e)?this._bodyText=e.toString():a&&o&&((t=e)&&DataView.prototype.isPrototypeOf(t))?(this._bodyArrayBuffer=m(e.buffer),this._bodyInit=new Blob([this._bodyArrayBuffer])):a&&(ArrayBuffer.prototype.isPrototypeOf(e)||c(e))?this._bodyArrayBuffer=m(e):this._bodyText=e=Object.prototype.toString.call(e):this._bodyText="",this.headers.get("content-type")||("string"==typeof e?this.headers.set("content-type","text/plain;charset=UTF-8"):this._bodyBlob&&this._bodyBlob.type?this.headers.set("content-type",this._bodyBlob.type):n&&URLSearchParams.prototype.isPrototypeOf(e)&&this.headers.set("content-type","application/x-www-form-urlencoded;charset=UTF-8"))},o&&(this.blob=function(){var e=d(this);if(e)return e;if(this._bodyBlob)return Promise.resolve(this._bodyBlob);if(this._bodyArrayBuffer)return Promise.resolve(new Blob([this._bodyArrayBuffer]));if(this._bodyFormData)throw new Error("could not read FormData body as blob");return Promise.resolve(new Blob([this._bodyText]))},this.arrayBuffer=function(){return this._bodyArrayBuffer?d(this)||Promise.resolve(this._bodyArrayBuffer):this.blob().then(v)}),this.text=function(){var e,t,n,r=d(this);if(r)return r;if(this._bodyBlob)return e=this._bodyBlob,t=new FileReader,n=h(t),t.readAsText(e),n;if(this._bodyArrayBuffer)return Promise.resolve(function(e){for(var t=new Uint8Array(e),n=new Array(t.length),r=0;r-1?r:n),this.mode=t.mode||this.mode||null,this.signal=t.signal||this.signal,this.referrer=null,("GET"===this.method||"HEAD"===this.method)&&o)throw new TypeError("Body not allowed for GET or HEAD requests");this._initBody(o)}function x(e){var t=new FormData;return e.trim().split("&").forEach((function(e){if(e){var n=e.split("="),r=n.shift().replace(/\+/g," "),o=n.join("=").replace(/\+/g," ");t.append(decodeURIComponent(r),decodeURIComponent(o))}})),t}function w(e,t){t||(t={}),this.type="default",this.status=void 0===t.status?200:t.status,this.ok=this.status>=200&&this.status<300,this.statusText="statusText"in t?t.statusText:"OK",this.headers=new f(t.headers),this.url=t.url||"",this._initBody(e)}b.prototype.clone=function(){return new b(this,{body:this._bodyInit})},g.call(b.prototype),g.call(w.prototype),w.prototype.clone=function(){return new w(this._bodyInit,{status:this.status,statusText:this.statusText,headers:new f(this.headers),url:this.url})},w.error=function(){var e=new w(null,{status:0,statusText:""});return e.type="error",e};var k=[301,302,303,307,308];w.redirect=function(e,t){if(-1===k.indexOf(t))throw new RangeError("Invalid status code");return new w(null,{status:t,headers:{location:e}})},t.DOMException=e.DOMException;try{new t.DOMException}catch(e){t.DOMException=function(e,t){this.message=e,this.name=t;var n=Error(e);this.stack=n.stack},t.DOMException.prototype=Object.create(Error.prototype),t.DOMException.prototype.constructor=t.DOMException}function S(e,n){return new Promise((function(r,i){var a=new b(e,n);if(a.signal&&a.signal.aborted)return i(new t.DOMException("Aborted","AbortError"));var s=new XMLHttpRequest;function c(){s.abort()}s.onload=function(){var e,t,n={status:s.status,statusText:s.statusText,headers:(e=s.getAllResponseHeaders()||"",t=new f,e.replace(/\r?\n[\t ]+/g," ").split(/\r?\n/).forEach((function(e){var n=e.split(":"),r=n.shift().trim();if(r){var o=n.join(":").trim();t.append(r,o)}})),t)};n.url="responseURL"in s?s.responseURL:n.headers.get("X-Request-URL");var o="response"in s?s.response:s.responseText;r(new w(o,n))},s.onerror=function(){i(new TypeError("Network request failed"))},s.ontimeout=function(){i(new TypeError("Network request failed"))},s.onabort=function(){i(new t.DOMException("Aborted","AbortError"))},s.open(a.method,a.url,!0),"include"===a.credentials?s.withCredentials=!0:"omit"===a.credentials&&(s.withCredentials=!1),"responseType"in s&&o&&(s.responseType="blob"),a.headers.forEach((function(e,t){s.setRequestHeader(t,e)})),a.signal&&(a.signal.addEventListener("abort",c),s.onreadystatechange=function(){4===s.readyState&&a.signal.removeEventListener("abort",c)}),s.send(void 0===a._bodyInit?null:a._bodyInit)}))}S.polyfill=!0,e.fetch||(e.fetch=S,e.Headers=f,e.Request=b,e.Response=w),t.Headers=f,t.Request=b,t.Response=w,t.fetch=S}({})}("undefined"!=typeof self?self:this)},function(e,t,n){"use strict";var r=n(487),o=n(506);function i(e){return function(){throw new Error("Function "+e+" is deprecated and cannot be used.")}}e.exports.Type=n(20),e.exports.Schema=n(79),e.exports.FAILSAFE_SCHEMA=n(161),e.exports.JSON_SCHEMA=n(247),e.exports.CORE_SCHEMA=n(246),e.exports.DEFAULT_SAFE_SCHEMA=n(99),e.exports.DEFAULT_FULL_SCHEMA=n(124),e.exports.load=r.load,e.exports.loadAll=r.loadAll,e.exports.safeLoad=r.safeLoad,e.exports.safeLoadAll=r.safeLoadAll,e.exports.dump=o.dump,e.exports.safeDump=o.safeDump,e.exports.YAMLException=n(98),e.exports.MINIMAL_SCHEMA=n(161),e.exports.SAFE_SCHEMA=n(99),e.exports.DEFAULT_SCHEMA=n(124),e.exports.scan=i("scan"),e.exports.parse=i("parse"),e.exports.compose=i("compose"),e.exports.addConstructor=i("addConstructor")},function(e,t,n){"use strict";var r=n(78),o=n(98),i=n(488),a=n(99),s=n(124),c=Object.prototype.hasOwnProperty,l=/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F-\x84\x86-\x9F\uFFFE\uFFFF]|[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?:[^\uD800-\uDBFF]|^)[\uDC00-\uDFFF]/,u=/[\x85\u2028\u2029]/,p=/[,\[\]\{\}]/,f=/^(?:!|!!|![a-z\-]+!)$/i,d=/^(?:!|[^,\[\]\{\}])(?:%[0-9a-f]{2}|[0-9a-z\-#;\/\?:@&=\+\$,_\.!~\*'\(\)\[\]])*$/i;function h(e){return Object.prototype.toString.call(e)}function v(e){return 10===e||13===e}function m(e){return 9===e||32===e}function g(e){return 9===e||32===e||10===e||13===e}function y(e){return 44===e||91===e||93===e||123===e||125===e}function b(e){var t;return 48<=e&&e<=57?e-48:97<=(t=32|e)&&t<=102?t-97+10:-1}function x(e){return 48===e?"\0":97===e?"":98===e?"\b":116===e||9===e?"\t":110===e?"\n":118===e?"\v":102===e?"\f":114===e?"\r":101===e?"":32===e?" ":34===e?'"':47===e?"/":92===e?"\\":78===e?"…":95===e?" ":76===e?"\u2028":80===e?"\u2029":""}function w(e){return e<=65535?String.fromCharCode(e):String.fromCharCode(55296+(e-65536>>10),56320+(e-65536&1023))}for(var k=new Array(256),S=new Array(256),O=0;O<256;O++)k[O]=x(O)?1:0,S[O]=x(O);function A(e,t){this.input=e,this.filename=t.filename||null,this.schema=t.schema||s,this.onWarning=t.onWarning||null,this.legacy=t.legacy||!1,this.json=t.json||!1,this.listener=t.listener||null,this.implicitTypes=this.schema.compiledImplicit,this.typeMap=this.schema.compiledTypeMap,this.length=e.length,this.position=0,this.line=0,this.lineStart=0,this.lineIndent=0,this.documents=[]}function E(e,t){return new o(t,new i(e.filename,e.input,e.position,e.line,e.position-e.lineStart))}function _(e,t){throw E(e,t)}function j(e,t){e.onWarning&&e.onWarning.call(null,E(e,t))}var T={YAML:function(e,t,n){var r,o,i;null!==e.version&&_(e,"duplication of %YAML directive"),1!==n.length&&_(e,"YAML directive accepts exactly one argument"),null===(r=/^([0-9]+)\.([0-9]+)$/.exec(n[0]))&&_(e,"ill-formed argument of the YAML directive"),o=parseInt(r[1],10),i=parseInt(r[2],10),1!==o&&_(e,"unacceptable YAML version of the document"),e.version=n[0],e.checkLineBreaks=i<2,1!==i&&2!==i&&j(e,"unsupported YAML version of the document")},TAG:function(e,t,n){var r,o;2!==n.length&&_(e,"TAG directive accepts exactly two arguments"),r=n[0],o=n[1],f.test(r)||_(e,"ill-formed tag handle (first argument) of the TAG directive"),c.call(e.tagMap,r)&&_(e,'there is a previously declared suffix for "'+r+'" tag handle'),d.test(o)||_(e,"ill-formed tag prefix (second argument) of the TAG directive"),e.tagMap[r]=o}};function C(e,t,n,r){var o,i,a,s;if(t1&&(e.result+=r.repeat("\n",t-1))}function B(e,t){var n,r,o=e.tag,i=e.anchor,a=[],s=!1;for(null!==e.anchor&&(e.anchorMap[e.anchor]=a),r=e.input.charCodeAt(e.position);0!==r&&45===r&&g(e.input.charCodeAt(e.position+1));)if(s=!0,e.position++,L(e,!0,-1)&&e.lineIndent<=t)a.push(null),r=e.input.charCodeAt(e.position);else if(n=e.line,F(e,t,3,!1,!0),a.push(e.result),L(e,!0,-1),r=e.input.charCodeAt(e.position),(e.line===n||e.lineIndent>t)&&0!==r)_(e,"bad indentation of a sequence entry");else if(e.lineIndentt?x=1:e.lineIndent===t?x=0:e.lineIndentt?x=1:e.lineIndent===t?x=0:e.lineIndentt)&&(F(e,t,4,!0,o)&&(v?d=e.result:h=e.result),v||(P(e,u,p,f,d,h,i,a),f=d=h=null),L(e,!0,-1),s=e.input.charCodeAt(e.position)),e.lineIndent>t&&0!==s)_(e,"bad indentation of a mapping entry");else if(e.lineIndent=0))break;0===i?_(e,"bad explicit indentation width of a block scalar; it cannot be less than one"):u?_(e,"repeat of an indentation width identifier"):(p=t+i-1,u=!0)}if(m(a)){do{a=e.input.charCodeAt(++e.position)}while(m(a));if(35===a)do{a=e.input.charCodeAt(++e.position)}while(!v(a)&&0!==a)}for(;0!==a;){for(R(e),e.lineIndent=0,a=e.input.charCodeAt(e.position);(!u||e.lineIndentp&&(p=e.lineIndent),v(a))f++;else{if(e.lineIndent0){for(o=a,i=0;o>0;o--)(a=b(s=e.input.charCodeAt(++e.position)))>=0?i=(i<<4)+a:_(e,"expected hexadecimal character");e.result+=w(i),e.position++}else _(e,"unknown escape sequence");n=r=e.position}else v(s)?(C(e,n,r,!0),M(e,L(e,!1,t)),n=r=e.position):e.position===e.lineStart&&N(e)?_(e,"unexpected end of the document within a double quoted scalar"):(e.position++,r=e.position)}_(e,"unexpected end of the stream within a double quoted scalar")}(e,d)?A=!0:!function(e){var t,n,r;if(42!==(r=e.input.charCodeAt(e.position)))return!1;for(r=e.input.charCodeAt(++e.position),t=e.position;0!==r&&!g(r)&&!y(r);)r=e.input.charCodeAt(++e.position);return e.position===t&&_(e,"name of an alias node must contain at least one character"),n=e.input.slice(t,e.position),e.anchorMap.hasOwnProperty(n)||_(e,'unidentified alias "'+n+'"'),e.result=e.anchorMap[n],L(e,!0,-1),!0}(e)?function(e,t,n){var r,o,i,a,s,c,l,u,p=e.kind,f=e.result;if(g(u=e.input.charCodeAt(e.position))||y(u)||35===u||38===u||42===u||33===u||124===u||62===u||39===u||34===u||37===u||64===u||96===u)return!1;if((63===u||45===u)&&(g(r=e.input.charCodeAt(e.position+1))||n&&y(r)))return!1;for(e.kind="scalar",e.result="",o=i=e.position,a=!1;0!==u;){if(58===u){if(g(r=e.input.charCodeAt(e.position+1))||n&&y(r))break}else if(35===u){if(g(e.input.charCodeAt(e.position-1)))break}else{if(e.position===e.lineStart&&N(e)||n&&y(u))break;if(v(u)){if(s=e.line,c=e.lineStart,l=e.lineIndent,L(e,!1,-1),e.lineIndent>=t){a=!0,u=e.input.charCodeAt(e.position);continue}e.position=i,e.line=s,e.lineStart=c,e.lineIndent=l;break}}a&&(C(e,o,i,!1),M(e,e.line-s),o=i=e.position,a=!1),m(u)||(i=e.position+1),u=e.input.charCodeAt(++e.position)}return C(e,o,i,!1),!!e.result||(e.kind=p,e.result=f,!1)}(e,d,1===n)&&(A=!0,null===e.tag&&(e.tag="?")):(A=!0,null===e.tag&&null===e.anchor||_(e,"alias node should not have any properties")),null!==e.anchor&&(e.anchorMap[e.anchor]=e.result)):0===x&&(A=l&&B(e,h))),null!==e.tag&&"!"!==e.tag)if("?"===e.tag){for(null!==e.result&&"scalar"!==e.kind&&_(e,'unacceptable node kind for ! tag; it should be "scalar", not "'+e.kind+'"'),u=0,p=e.implicitTypes.length;u tag; it should be "'+f.kind+'", not "'+e.kind+'"'),f.resolve(e.result)?(e.result=f.construct(e.result),null!==e.anchor&&(e.anchorMap[e.anchor]=e.result)):_(e,"cannot resolve a node with !<"+e.tag+"> explicit tag")):_(e,"unknown tag !<"+e.tag+">");return null!==e.listener&&e.listener("close",e),null!==e.tag||null!==e.anchor||A}function z(e){var t,n,r,o,i=e.position,a=!1;for(e.version=null,e.checkLineBreaks=e.legacy,e.tagMap={},e.anchorMap={};0!==(o=e.input.charCodeAt(e.position))&&(L(e,!0,-1),o=e.input.charCodeAt(e.position),!(e.lineIndent>0||37!==o));){for(a=!0,o=e.input.charCodeAt(++e.position),t=e.position;0!==o&&!g(o);)o=e.input.charCodeAt(++e.position);for(r=[],(n=e.input.slice(t,e.position)).length<1&&_(e,"directive name must not be less than one character in length");0!==o;){for(;m(o);)o=e.input.charCodeAt(++e.position);if(35===o){do{o=e.input.charCodeAt(++e.position)}while(0!==o&&!v(o));break}if(v(o))break;for(t=e.position;0!==o&&!g(o);)o=e.input.charCodeAt(++e.position);r.push(e.input.slice(t,e.position))}0!==o&&R(e),c.call(T,n)?T[n](e,n,r):j(e,'unknown document directive "'+n+'"')}L(e,!0,-1),0===e.lineIndent&&45===e.input.charCodeAt(e.position)&&45===e.input.charCodeAt(e.position+1)&&45===e.input.charCodeAt(e.position+2)?(e.position+=3,L(e,!0,-1)):a&&_(e,"directives end mark is expected"),F(e,e.lineIndent-1,4,!1,!0),L(e,!0,-1),e.checkLineBreaks&&u.test(e.input.slice(i,e.position))&&j(e,"non-ASCII line breaks are interpreted as content"),e.documents.push(e.result),e.position===e.lineStart&&N(e)?46===e.input.charCodeAt(e.position)&&(e.position+=3,L(e,!0,-1)):e.position0&&-1==="\0\r\n…\u2028\u2029".indexOf(this.buffer.charAt(o-1));)if(o-=1,this.position-o>t/2-1){n=" ... ",o+=5;break}for(i="",a=this.position;at/2-1){i=" ... ",a-=5;break}return s=this.buffer.slice(o,a),r.repeat(" ",e)+n+s+i+"\n"+r.repeat(" ",e+this.position-o+n.length)+"^"},o.prototype.toString=function(e){var t,n="";return this.name&&(n+='in "'+this.name+'" '),n+="at line "+(this.line+1)+", column "+(this.column+1),e||(t=this.getSnippet())&&(n+=":\n"+t),n},e.exports=o},function(e,t,n){"use strict";var r=n(20);e.exports=new r("tag:yaml.org,2002:str",{kind:"scalar",construct:function(e){return null!==e?e:""}})},function(e,t,n){"use strict";var r=n(20);e.exports=new r("tag:yaml.org,2002:seq",{kind:"sequence",construct:function(e){return null!==e?e:[]}})},function(e,t,n){"use strict";var r=n(20);e.exports=new r("tag:yaml.org,2002:map",{kind:"mapping",construct:function(e){return null!==e?e:{}}})},function(e,t,n){"use strict";var r=n(20);e.exports=new r("tag:yaml.org,2002:null",{kind:"scalar",resolve:function(e){if(null===e)return!0;var t=e.length;return 1===t&&"~"===e||4===t&&("null"===e||"Null"===e||"NULL"===e)},construct:function(){return null},predicate:function(e){return null===e},represent:{canonical:function(){return"~"},lowercase:function(){return"null"},uppercase:function(){return"NULL"},camelcase:function(){return"Null"}},defaultStyle:"lowercase"})},function(e,t,n){"use strict";var r=n(20);e.exports=new r("tag:yaml.org,2002:bool",{kind:"scalar",resolve:function(e){if(null===e)return!1;var t=e.length;return 4===t&&("true"===e||"True"===e||"TRUE"===e)||5===t&&("false"===e||"False"===e||"FALSE"===e)},construct:function(e){return"true"===e||"True"===e||"TRUE"===e},predicate:function(e){return"[object Boolean]"===Object.prototype.toString.call(e)},represent:{lowercase:function(e){return e?"true":"false"},uppercase:function(e){return e?"TRUE":"FALSE"},camelcase:function(e){return e?"True":"False"}},defaultStyle:"lowercase"})},function(e,t,n){"use strict";var r=n(78),o=n(20);function i(e){return 48<=e&&e<=55}function a(e){return 48<=e&&e<=57}e.exports=new o("tag:yaml.org,2002:int",{kind:"scalar",resolve:function(e){if(null===e)return!1;var t,n,r=e.length,o=0,s=!1;if(!r)return!1;if("-"!==(t=e[o])&&"+"!==t||(t=e[++o]),"0"===t){if(o+1===r)return!0;if("b"===(t=e[++o])){for(o++;o=0?"0b"+e.toString(2):"-0b"+e.toString(2).slice(1)},octal:function(e){return e>=0?"0"+e.toString(8):"-0"+e.toString(8).slice(1)},decimal:function(e){return e.toString(10)},hexadecimal:function(e){return e>=0?"0x"+e.toString(16).toUpperCase():"-0x"+e.toString(16).toUpperCase().slice(1)}},defaultStyle:"decimal",styleAliases:{binary:[2,"bin"],octal:[8,"oct"],decimal:[10,"dec"],hexadecimal:[16,"hex"]}})},function(e,t,n){"use strict";var r=n(78),o=n(20),i=new RegExp("^(?:[-+]?(?:0|[1-9][0-9_]*)(?:\\.[0-9_]*)?(?:[eE][-+]?[0-9]+)?|\\.[0-9_]+(?:[eE][-+]?[0-9]+)?|[-+]?[0-9][0-9_]*(?::[0-5]?[0-9])+\\.[0-9_]*|[-+]?\\.(?:inf|Inf|INF)|\\.(?:nan|NaN|NAN))$");var a=/^[-+]?[0-9]+e/;e.exports=new o("tag:yaml.org,2002:float",{kind:"scalar",resolve:function(e){return null!==e&&!(!i.test(e)||"_"===e[e.length-1])},construct:function(e){var t,n,r,o;return n="-"===(t=e.replace(/_/g,"").toLowerCase())[0]?-1:1,o=[],"+-".indexOf(t[0])>=0&&(t=t.slice(1)),".inf"===t?1===n?Number.POSITIVE_INFINITY:Number.NEGATIVE_INFINITY:".nan"===t?NaN:t.indexOf(":")>=0?(t.split(":").forEach((function(e){o.unshift(parseFloat(e,10))})),t=0,r=1,o.forEach((function(e){t+=e*r,r*=60})),n*t):n*parseFloat(t,10)},predicate:function(e){return"[object Number]"===Object.prototype.toString.call(e)&&(e%1!=0||r.isNegativeZero(e))},represent:function(e,t){var n;if(isNaN(e))switch(t){case"lowercase":return".nan";case"uppercase":return".NAN";case"camelcase":return".NaN"}else if(Number.POSITIVE_INFINITY===e)switch(t){case"lowercase":return".inf";case"uppercase":return".INF";case"camelcase":return".Inf"}else if(Number.NEGATIVE_INFINITY===e)switch(t){case"lowercase":return"-.inf";case"uppercase":return"-.INF";case"camelcase":return"-.Inf"}else if(r.isNegativeZero(e))return"-0.0";return n=e.toString(10),a.test(n)?n.replace("e",".e"):n},defaultStyle:"lowercase"})},function(e,t,n){"use strict";var r=n(20),o=new RegExp("^([0-9][0-9][0-9][0-9])-([0-9][0-9])-([0-9][0-9])$"),i=new RegExp("^([0-9][0-9][0-9][0-9])-([0-9][0-9]?)-([0-9][0-9]?)(?:[Tt]|[ \\t]+)([0-9][0-9]?):([0-9][0-9]):([0-9][0-9])(?:\\.([0-9]*))?(?:[ \\t]*(Z|([-+])([0-9][0-9]?)(?::([0-9][0-9]))?))?$");e.exports=new r("tag:yaml.org,2002:timestamp",{kind:"scalar",resolve:function(e){return null!==e&&(null!==o.exec(e)||null!==i.exec(e))},construct:function(e){var t,n,r,a,s,c,l,u,p=0,f=null;if(null===(t=o.exec(e))&&(t=i.exec(e)),null===t)throw new Error("Date resolve error");if(n=+t[1],r=+t[2]-1,a=+t[3],!t[4])return new Date(Date.UTC(n,r,a));if(s=+t[4],c=+t[5],l=+t[6],t[7]){for(p=t[7].slice(0,3);p.length<3;)p+="0";p=+p}return t[9]&&(f=6e4*(60*+t[10]+ +(t[11]||0)),"-"===t[9]&&(f=-f)),u=new Date(Date.UTC(n,r,a,s,c,l,p)),f&&u.setTime(u.getTime()-f),u},instanceOf:Date,represent:function(e){return e.toISOString()}})},function(e,t,n){"use strict";var r=n(20);e.exports=new r("tag:yaml.org,2002:merge",{kind:"scalar",resolve:function(e){return"<<"===e||null===e}})},function(e,t,n){"use strict";var r;try{r=n(32).Buffer}catch(e){}var o=n(20),i="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=\n\r";e.exports=new o("tag:yaml.org,2002:binary",{kind:"scalar",resolve:function(e){if(null===e)return!1;var t,n,r=0,o=e.length,a=i;for(n=0;n64)){if(t<0)return!1;r+=6}return r%8==0},construct:function(e){var t,n,o=e.replace(/[\r\n=]/g,""),a=o.length,s=i,c=0,l=[];for(t=0;t>16&255),l.push(c>>8&255),l.push(255&c)),c=c<<6|s.indexOf(o.charAt(t));return 0===(n=a%4*6)?(l.push(c>>16&255),l.push(c>>8&255),l.push(255&c)):18===n?(l.push(c>>10&255),l.push(c>>2&255)):12===n&&l.push(c>>4&255),r?r.from?r.from(l):new r(l):l},predicate:function(e){return r&&r.isBuffer(e)},represent:function(e){var t,n,r="",o=0,a=e.length,s=i;for(t=0;t>18&63],r+=s[o>>12&63],r+=s[o>>6&63],r+=s[63&o]),o=(o<<8)+e[t];return 0===(n=a%3)?(r+=s[o>>18&63],r+=s[o>>12&63],r+=s[o>>6&63],r+=s[63&o]):2===n?(r+=s[o>>10&63],r+=s[o>>4&63],r+=s[o<<2&63],r+=s[64]):1===n&&(r+=s[o>>2&63],r+=s[o<<4&63],r+=s[64],r+=s[64]),r}})},function(e,t,n){"use strict";var r=n(20),o=Object.prototype.hasOwnProperty,i=Object.prototype.toString;e.exports=new r("tag:yaml.org,2002:omap",{kind:"sequence",resolve:function(e){if(null===e)return!0;var t,n,r,a,s,c=[],l=e;for(t=0,n=l.length;t3)return!1;if("/"!==t[t.length-r.length-1])return!1}return!0},construct:function(e){var t=e,n=/\/([gim]*)$/.exec(e),r="";return"/"===t[0]&&(n&&(r=n[1]),t=t.slice(1,t.length-r.length-1)),new RegExp(t,r)},predicate:function(e){return"[object RegExp]"===Object.prototype.toString.call(e)},represent:function(e){var t="/"+e.source+"/";return e.global&&(t+="g"),e.multiline&&(t+="m"),e.ignoreCase&&(t+="i"),t}})},function(e,t,n){"use strict";var r;try{r=n(505)}catch(e){"undefined"!=typeof window&&(r=window.esprima)}var o=n(20);e.exports=new o("tag:yaml.org,2002:js/function",{kind:"scalar",resolve:function(e){if(null===e)return!1;try{var t="("+e+")",n=r.parse(t,{range:!0});return"Program"===n.type&&1===n.body.length&&"ExpressionStatement"===n.body[0].type&&("ArrowFunctionExpression"===n.body[0].expression.type||"FunctionExpression"===n.body[0].expression.type)}catch(e){return!1}},construct:function(e){var t,n="("+e+")",o=r.parse(n,{range:!0}),i=[];if("Program"!==o.type||1!==o.body.length||"ExpressionStatement"!==o.body[0].type||"ArrowFunctionExpression"!==o.body[0].expression.type&&"FunctionExpression"!==o.body[0].expression.type)throw new Error("Failed to resolve function");return o.body[0].expression.params.forEach((function(e){i.push(e.name)})),t=o.body[0].expression.body.range,"BlockStatement"===o.body[0].expression.body.type?new Function(i,n.slice(t[0]+1,t[1]-1)):new Function(i,"return "+n.slice(t[0],t[1]))},predicate:function(e){return"[object Function]"===Object.prototype.toString.call(e)},represent:function(e){return e.toString()}})},function(e,t){if("undefined"==typeof esprima){var n=new Error("Cannot find module 'esprima'");throw n.code="MODULE_NOT_FOUND",n}e.exports=esprima},function(e,t,n){"use strict";var r=n(78),o=n(98),i=n(124),a=n(99),s=Object.prototype.toString,c=Object.prototype.hasOwnProperty,l={0:"\\0",7:"\\a",8:"\\b",9:"\\t",10:"\\n",11:"\\v",12:"\\f",13:"\\r",27:"\\e",34:'\\"',92:"\\\\",133:"\\N",160:"\\_",8232:"\\L",8233:"\\P"},u=["y","Y","yes","Yes","YES","on","On","ON","n","N","no","No","NO","off","Off","OFF"];function p(e){var t,n,i;if(t=e.toString(16).toUpperCase(),e<=255)n="x",i=2;else if(e<=65535)n="u",i=4;else{if(!(e<=4294967295))throw new o("code point within a string may not be greater than 0xFFFFFFFF");n="U",i=8}return"\\"+n+r.repeat("0",i-t.length)+t}function f(e){this.schema=e.schema||i,this.indent=Math.max(1,e.indent||2),this.noArrayIndent=e.noArrayIndent||!1,this.skipInvalid=e.skipInvalid||!1,this.flowLevel=r.isNothing(e.flowLevel)?-1:e.flowLevel,this.styleMap=function(e,t){var n,r,o,i,a,s,l;if(null===t)return{};for(n={},o=0,i=(r=Object.keys(t)).length;o0?e.charCodeAt(i-1):null,d=d&&g(a,s)}else{for(i=0;ir&&" "!==e[f+1],f=i);else if(!m(a))return 5;s=i>0?e.charCodeAt(i-1):null,d=d&&g(a,s)}u=u||p&&i-f-1>r&&" "!==e[f+1]}return l||u?n>9&&y(e)?5:u?4:3:d&&!o(e)?1:2}function x(e,t,n,r){e.dump=function(){if(0===t.length)return"''";if(!e.noCompatMode&&-1!==u.indexOf(t))return"'"+t+"'";var i=e.indent*Math.max(1,n),a=-1===e.lineWidth?-1:Math.max(Math.min(e.lineWidth,40),e.lineWidth-i),s=r||e.flowLevel>-1&&n>=e.flowLevel;switch(b(t,s,e.indent,a,(function(t){return function(e,t){var n,r;for(n=0,r=e.implicitTypes.length;n"+w(t,e.indent)+k(d(function(e,t){var n,r,o=/(\n+)([^\n]*)/g,i=(s=e.indexOf("\n"),s=-1!==s?s:e.length,o.lastIndex=s,S(e.slice(0,s),t)),a="\n"===e[0]||" "===e[0];var s;for(;r=o.exec(e);){var c=r[1],l=r[2];n=" "===l[0],i+=c+(a||n||""===l?"":"\n")+S(l,t),a=n}return i}(t,a),i));case 5:return'"'+function(e){for(var t,n,r,o="",i=0;i=55296&&t<=56319&&(n=e.charCodeAt(i+1))>=56320&&n<=57343?(o+=p(1024*(t-55296)+n-56320+65536),i++):(r=l[t],o+=!r&&m(t)?e[i]:r||p(t));return o}(t)+'"';default:throw new o("impossible error: invalid scalar style")}}()}function w(e,t){var n=y(e)?String(t):"",r="\n"===e[e.length-1];return n+(r&&("\n"===e[e.length-2]||"\n"===e)?"+":r?"":"-")+"\n"}function k(e){return"\n"===e[e.length-1]?e.slice(0,-1):e}function S(e,t){if(""===e||" "===e[0])return e;for(var n,r,o=/ [^ ]/g,i=0,a=0,s=0,c="";n=o.exec(e);)(s=n.index)-i>t&&(r=a>i?a:s,c+="\n"+e.slice(i,r),i=r+1),a=s;return c+="\n",e.length-i>t&&a>i?c+=e.slice(i,a)+"\n"+e.slice(a+1):c+=e.slice(i),c.slice(1)}function O(e,t,n){var r,i,a,l,u,p;for(a=0,l=(i=n?e.explicitTypes:e.implicitTypes).length;a tag resolver accepts not "'+p+'" style');r=u.represent[p](t,p)}e.dump=r}return!0}return!1}function A(e,t,n,r,i,a){e.tag=null,e.dump=n,O(e,n,!1)||O(e,n,!0);var c=s.call(e.dump);r&&(r=e.flowLevel<0||e.flowLevel>t);var l,u,p="[object Object]"===c||"[object Array]"===c;if(p&&(u=-1!==(l=e.duplicates.indexOf(n))),(null!==e.tag&&"?"!==e.tag||u||2!==e.indent&&t>0)&&(i=!1),u&&e.usedDuplicates[l])e.dump="*ref_"+l;else{if(p&&u&&!e.usedDuplicates[l]&&(e.usedDuplicates[l]=!0),"[object Object]"===c)r&&0!==Object.keys(e.dump).length?(!function(e,t,n,r){var i,a,s,c,l,u,p="",f=e.tag,d=Object.keys(n);if(!0===e.sortKeys)d.sort();else if("function"==typeof e.sortKeys)d.sort(e.sortKeys);else if(e.sortKeys)throw new o("sortKeys must be a boolean or a function");for(i=0,a=d.length;i1024)&&(e.dump&&10===e.dump.charCodeAt(0)?u+="?":u+="? "),u+=e.dump,l&&(u+=h(e,t)),A(e,t+1,c,!0,l)&&(e.dump&&10===e.dump.charCodeAt(0)?u+=":":u+=": ",p+=u+=e.dump));e.tag=f,e.dump=p||"{}"}(e,t,e.dump,i),u&&(e.dump="&ref_"+l+e.dump)):(!function(e,t,n){var r,o,i,a,s,c="",l=e.tag,u=Object.keys(n);for(r=0,o=u.length;r1024&&(s+="? "),s+=e.dump+(e.condenseFlow?'"':"")+":"+(e.condenseFlow?"":" "),A(e,t,a,!1,!1)&&(c+=s+=e.dump));e.tag=l,e.dump="{"+c+"}"}(e,t,e.dump),u&&(e.dump="&ref_"+l+" "+e.dump));else if("[object Array]"===c){var f=e.noArrayIndent&&t>0?t-1:t;r&&0!==e.dump.length?(!function(e,t,n,r){var o,i,a="",s=e.tag;for(o=0,i=n.length;o "+e.dump)}return!0}function E(e,t){var n,r,o=[],i=[];for(function e(t,n,r){var o,i,a;if(null!==t&&"object"==typeof t)if(-1!==(i=n.indexOf(t)))-1===r.indexOf(i)&&r.push(i);else if(n.push(t),Array.isArray(t))for(i=0,a=t.length;i-1}},function(e,t,n){var r=n(128);e.exports=function(e,t){var n=this.__data__,o=r(n,e);return o<0?(++this.size,n.push([e,t])):n[o][1]=t,this}},function(e,t,n){var r=n(129);e.exports=function(e){var t=r(this,e).delete(e);return this.size-=t?1:0,t}},function(e,t){e.exports=function(e){var t=typeof e;return"string"==t||"number"==t||"symbol"==t||"boolean"==t?"__proto__"!==e:null===e}},function(e,t,n){var r=n(129);e.exports=function(e){return r(this,e).get(e)}},function(e,t,n){var r=n(129);e.exports=function(e){return r(this,e).has(e)}},function(e,t,n){var r=n(129);e.exports=function(e,t){var n=r(this,e),o=n.size;return n.set(e,t),this.size+=n.size==o?0:1,this}},function(e,t,n){var r=n(115),o=n(125),i=n(117),a=n(36),s=n(100);e.exports=function(e,t,n,c){if(!a(e))return e;for(var l=-1,u=(t=o(t,e)).length,p=u-1,f=e;null!=f&&++l0&&i(u)?n>1?e(u,n-1,i,a,s):r(s,u):a||(s[s.length]=u)}return s}},function(e,t,n){var r=n(73),o=n(118),i=n(31),a=r?r.isConcatSpreadable:void 0;e.exports=function(e){return i(e)||o(e)||!!(a&&e&&e[a])}},function(e,t,n){var r=n(537);e.exports=r},function(e,t,n){var r=n(538),o=Array.prototype;e.exports=function(e){var t=e.find;return e===o||e instanceof Array&&t===o.find?r:t}},function(e,t,n){n(539);var r=n(27);e.exports=r("Array").find},function(e,t,n){"use strict";var r=n(7),o=n(49).find,i=n(143),a=n(39),s=!0,c=a("find");"find"in[]&&Array(1).find((function(){s=!1})),r({target:"Array",proto:!0,forced:s||!c},{find:function(e){return o(this,e,arguments.length>1?arguments[1]:void 0)}}),i("find")},function(e,t,n){e.exports=n(541)},function(e,t,n){var r=n(542);e.exports=r},function(e,t,n){n(543);var r=n(12);e.exports=r.Reflect.get},function(e,t,n){var r=n(7),o=n(17),i=n(30),a=n(29),s=n(69),c=n(89);r({target:"Reflect",stat:!0},{get:function e(t,n){var r,l,u=arguments.length<3?t:arguments[2];return i(t)===u?t[n]:(r=s.f(t,n))?a(r,"value")?r.value:void 0===r.get?void 0:r.get.call(u):o(l=c(t))?e(l,n,u):void 0}})},function(e,t,n){var r=n(130);e.exports=function(e,t){for(;!Object.prototype.hasOwnProperty.call(e,t)&&null!==(e=r(e)););return e}},function(e,t,n){e.exports=n(546)},function(e,t,n){var r=n(547);e.exports=r},function(e,t,n){n(548);var r=n(12);e.exports=r.Object.getPrototypeOf},function(e,t,n){var r=n(7),o=n(13),i=n(44),a=n(89),s=n(203);r({target:"Object",stat:!0,forced:o((function(){a(1)})),sham:!s},{getPrototypeOf:function(e){return a(i(e))}})},function(e,t,n){var r=n(550);e.exports=r},function(e,t,n){n(551);var r=n(12);e.exports=r.Object.setPrototypeOf},function(e,t,n){n(7)({target:"Object",stat:!0},{setPrototypeOf:n(147)})},function(e,t,n){e.exports=n(553)},function(e,t,n){var r=n(250);e.exports=r},function(e,t,n){n(7)({target:"Object",stat:!0,sham:!n(22)},{create:n(90)})},function(e,t,n){var r=n(249);function o(t,n){return e.exports=o=r||function(e,t){return e.__proto__=t,e},o(t,n)}e.exports=o},function(e,t,n){var r=n(557);e.exports=r},function(e,t,n){n(558);var r=n(12);e.exports=r.Reflect.construct},function(e,t,n){var r=n(7),o=n(43),i=n(48),a=n(30),s=n(17),c=n(90),l=n(213),u=n(13),p=o("Reflect","construct"),f=u((function(){function e(){}return!(p((function(){}),[],e)instanceof e)})),d=!u((function(){p((function(){}))})),h=f||d;r({target:"Reflect",stat:!0,forced:h,sham:h},{construct:function(e,t){i(e),a(t);var n=arguments.length<3?e:i(arguments[2]);if(d&&!f)return p(e,t,n);if(e==n){switch(t.length){case 0:return new e;case 1:return new e(t[0]);case 2:return new e(t[0],t[1]);case 3:return new e(t[0],t[1],t[2]);case 4:return new e(t[0],t[1],t[2],t[3])}var r=[null];return r.push.apply(r,t),new(l.apply(e,r))}var o=n.prototype,u=c(s(o)?o:Object.prototype),h=Function.apply.call(e,u,t);return s(h)?h:u}})},function(e,t,n){var r=n(251);e.exports=function(){if("undefined"==typeof Reflect||!r)return!1;if(r.sham)return!1;if("function"==typeof Proxy)return!0;try{return Date.prototype.toString.call(r(Date,[],(function(){}))),!0}catch(e){return!1}}},function(e,t,n){var r=n(10),o=n(561);e.exports=function(e,t){return!t||"object"!==r(t)&&"function"!=typeof t?o(e):t}},function(e,t){e.exports=function(e){if(void 0===e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return e}},function(e,t,n){var r=n(235);e.exports=r},function(e,t,n){var r=n(156),o=n(236);e.exports=function(e){if(r(e))return o(e)}},function(e,t,n){var r=n(230),o=n(237),i=n(122);e.exports=function(e){if(void 0!==i&&o(Object(e)))return r(e)}},function(e,t){e.exports=function(){throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}},function(e,t,n){var r=n(567);e.exports=r},function(e,t,n){var r=n(568),o=Array.prototype;e.exports=function(e){var t=e.splice;return e===o||e instanceof Array&&t===o.splice?r:t}},function(e,t,n){n(569);var r=n(27);e.exports=r("Array").splice},function(e,t,n){"use strict";var r=n(7),o=n(139),i=n(112),a=n(54),s=n(44),c=n(148),l=n(88),u=n(94),p=n(39),f=u("splice"),d=p("splice",{ACCESSORS:!0,0:0,1:2}),h=Math.max,v=Math.min;r({target:"Array",proto:!0,forced:!f||!d},{splice:function(e,t){var n,r,u,p,f,d,m=s(this),g=a(m.length),y=o(e,g),b=arguments.length;if(0===b?n=r=0:1===b?(n=0,r=g-y):(n=b-2,r=v(h(i(t),0),g-y)),g+n-r>9007199254740991)throw TypeError("Maximum allowed length exceeded");for(u=c(m,r),p=0;pg-r+n;p--)delete m[p-1]}else if(n>r)for(p=g-r;p>y;p--)d=p+n-1,(f=p+r-1)in m?m[d]=m[f]:delete m[d];for(p=0;pu;)for(var d,h=l(arguments[u++]),v=p?i(h).concat(p(h)):i(h),m=v.length,g=0;m>g;)d=v[g++],r&&!f.call(h,d)||(n[d]=h[d]);return n}:u},function(e,t,n){var r=n(252),o=n(74),i=n(75);e.exports=function(e){return function(t,n,a){var s=Object(t);if(!o(t)){var c=r(n,3);t=i(t),n=function(e){return c(s[e],e,s)}}var l=e(t,n,a);return l>-1?s[c?t[l]:l]:void 0}}},function(e,t,n){var r=n(578),o=n(599),i=n(260);e.exports=function(e){var t=o(e);return 1==t.length&&t[0][2]?i(t[0][0],t[0][1]):function(n){return n===e||r(n,e,t)}}},function(e,t,n){var r=n(167),o=n(253);e.exports=function(e,t,n,i){var a=n.length,s=a,c=!i;if(null==e)return!s;for(e=Object(e);a--;){var l=n[a];if(c&&l[2]?l[1]!==e[l[0]]:!(l[0]in e))return!1}for(;++a=0||(a[n]=e[n]);return a}},function(e,t,n){"use strict";n.r(t),n.d(t,"OAuthReceiver",(function(){return _f}));var r={};n.r(r),n.d(r,"JsonPatchError",(function(){return rr})),n.d(r,"deepClone",(function(){return or})),n.d(r,"getValueByPointer",(function(){return sr})),n.d(r,"applyOperation",(function(){return cr})),n.d(r,"applyPatch",(function(){return lr})),n.d(r,"applyReducer",(function(){return ur})),n.d(r,"validator",(function(){return pr})),n.d(r,"validate",(function(){return fr})),n.d(r,"_areEquals",(function(){return dr}));var o={};n.r(o),n.d(o,"unobserve",(function(){return gr})),n.d(o,"observe",(function(){return yr})),n.d(o,"generate",(function(){return br})),n.d(o,"compare",(function(){return wr}));var i={};n.r(i),n.d(i,"path",(function(){return ai})),n.d(i,"query",(function(){return si})),n.d(i,"header",(function(){return li})),n.d(i,"cookie",(function(){return ui}));n(286); +/** + * @license + * Copyright (c) 2017 The Polymer Project Authors. All rights reserved. + * This code may only be used under the BSD style license found at + * http://polymer.github.io/LICENSE.txt + * The complete set of authors may be found at + * http://polymer.github.io/AUTHORS.txt + * The complete set of contributors may be found at + * http://polymer.github.io/CONTRIBUTORS.txt + * Code distributed by Google as part of the polymer project is also + * subject to an additional IP rights grant found at + * http://polymer.github.io/PATENTS.txt + */const a="undefined"!=typeof window&&null!=window.customElements&&void 0!==window.customElements.polyfillWrapFlushCallback,s=(e,t,n=null)=>{for(;t!==n;){const n=t.nextSibling;e.removeChild(t),t=n}},c=`{{lit-${String(Math.random()).slice(2)}}}`,l=`\x3c!--${c}--\x3e`,u=new RegExp(`${c}|${l}`);class p{constructor(e,t){this.parts=[],this.element=t;const n=[],r=[],o=document.createTreeWalker(t.content,133,null,!1);let i=0,a=-1,s=0;const{strings:l,values:{length:p}}=e;for(;s0;){const t=l[s],n=v.exec(t)[2],r=n.toLowerCase()+"$lit$",o=e.getAttribute(r);e.removeAttribute(r);const i=o.split(u);this.parts.push({type:"attribute",index:a,name:n,strings:i}),s+=i.length-1}}"TEMPLATE"===e.tagName&&(r.push(e),o.currentNode=e.content)}else if(3===e.nodeType){const t=e.data;if(t.indexOf(c)>=0){const r=e.parentNode,o=t.split(u),i=o.length-1;for(let t=0;t{const n=e.length-t.length;return n>=0&&e.slice(n)===t},d=e=>-1!==e.index,h=()=>document.createComment(""),v=/([ \x09\x0a\x0c\x0d])([^\0-\x1F\x7F-\x9F "'>=/]+)([ \x09\x0a\x0c\x0d]*=[ \x09\x0a\x0c\x0d]*(?:[^ \x09\x0a\x0c\x0d"'`<>=]*|"[^"]*|'[^']*))$/;function m(e,t){const{element:{content:n},parts:r}=e,o=document.createTreeWalker(n,133,null,!1);let i=y(r),a=r[i],s=-1,c=0;const l=[];let u=null;for(;o.nextNode();){s++;const e=o.currentNode;for(e.previousSibling===u&&(u=null),t.has(e)&&(l.push(e),null===u&&(u=e)),null!==u&&c++;void 0!==a&&a.index===s;)a.index=null!==u?-1:a.index-c,i=y(r,i),a=r[i]}l.forEach(e=>e.parentNode.removeChild(e))}const g=e=>{let t=11===e.nodeType?0:1;const n=document.createTreeWalker(e,133,null,!1);for(;n.nextNode();)t++;return t},y=(e,t=-1)=>{for(let n=t+1;n"function"==typeof e&&b.has(e),w={},k={}; +/** + * @license + * Copyright (c) 2017 The Polymer Project Authors. All rights reserved. + * This code may only be used under the BSD style license found at + * http://polymer.github.io/LICENSE.txt + * The complete set of authors may be found at + * http://polymer.github.io/AUTHORS.txt + * The complete set of contributors may be found at + * http://polymer.github.io/CONTRIBUTORS.txt + * Code distributed by Google as part of the polymer project is also + * subject to an additional IP rights grant found at + * http://polymer.github.io/PATENTS.txt + */ +class S{constructor(e,t,n){this.__parts=[],this.template=e,this.processor=t,this.options=n}update(e){let t=0;for(const n of this.__parts)void 0!==n&&n.setValue(e[t]),t++;for(const e of this.__parts)void 0!==e&&e.commit()}_clone(){const e=a?this.template.element.content.cloneNode(!0):document.importNode(this.template.element.content,!0),t=[],n=this.template.parts,r=document.createTreeWalker(e,133,null,!1);let o,i=0,s=0,c=r.nextNode();for(;i-1||n)&&-1===e.indexOf("--\x3e",o+1);const i=v.exec(e);t+=null===i?e+(n?O:l):e.substr(0,i.index)+i[1]+i[2]+"$lit$"+i[3]+c}return t+=this.strings[e],t}getTemplateElement(){const e=document.createElement("template");return e.innerHTML=this.getHTML(),e}} +/** + * @license + * Copyright (c) 2017 The Polymer Project Authors. All rights reserved. + * This code may only be used under the BSD style license found at + * http://polymer.github.io/LICENSE.txt + * The complete set of authors may be found at + * http://polymer.github.io/AUTHORS.txt + * The complete set of contributors may be found at + * http://polymer.github.io/CONTRIBUTORS.txt + * Code distributed by Google as part of the polymer project is also + * subject to an additional IP rights grant found at + * http://polymer.github.io/PATENTS.txt + */ +const E=e=>null===e||!("object"==typeof e||"function"==typeof e),_=e=>Array.isArray(e)||!(!e||!e[Symbol.iterator]);class j{constructor(e,t,n){this.dirty=!0,this.element=e,this.name=t,this.strings=n,this.parts=[];for(let e=0;e{try{const e={get capture(){return L=!0,!1}};window.addEventListener("test",e,e),window.removeEventListener("test",e,e)}catch(e){}})();class N{constructor(e,t,n){this.value=void 0,this.__pendingValue=void 0,this.element=e,this.eventName=t,this.eventContext=n,this.__boundHandleEvent=e=>this.handleEvent(e)}setValue(e){this.__pendingValue=e}commit(){for(;x(this.__pendingValue);){const e=this.__pendingValue;this.__pendingValue=w,e(this)}if(this.__pendingValue===w)return;const e=this.__pendingValue,t=this.value,n=null==e||null!=t&&(e.capture!==t.capture||e.once!==t.once||e.passive!==t.passive),r=null!=e&&(null==t||n);n&&this.element.removeEventListener(this.eventName,this.__boundHandleEvent,this.__options),r&&(this.__options=M(e),this.element.addEventListener(this.eventName,this.__boundHandleEvent,this.__options)),this.value=e,this.__pendingValue=w}handleEvent(e){"function"==typeof this.value?this.value.call(this.eventContext||this.element,e):this.value.handleEvent(e)}}const M=e=>e&&(L?{capture:e.capture,passive:e.passive,once:e.once}:e.capture) +/** + * @license + * Copyright (c) 2017 The Polymer Project Authors. All rights reserved. + * This code may only be used under the BSD style license found at + * http://polymer.github.io/LICENSE.txt + * The complete set of authors may be found at + * http://polymer.github.io/AUTHORS.txt + * The complete set of contributors may be found at + * http://polymer.github.io/CONTRIBUTORS.txt + * Code distributed by Google as part of the polymer project is also + * subject to an additional IP rights grant found at + * http://polymer.github.io/PATENTS.txt + */;function B(e){let t=D.get(e.type);void 0===t&&(t={stringsArray:new WeakMap,keyString:new Map},D.set(e.type,t));let n=t.stringsArray.get(e.strings);if(void 0!==n)return n;const r=e.strings.join(c);return n=t.keyString.get(r),void 0===n&&(n=new p(e,e.getTemplateElement()),t.keyString.set(r,n)),t.stringsArray.set(e.strings,n),n}const D=new Map,q=new WeakMap; +/** + * @license + * Copyright (c) 2017 The Polymer Project Authors. All rights reserved. + * This code may only be used under the BSD style license found at + * http://polymer.github.io/LICENSE.txt + * The complete set of authors may be found at + * http://polymer.github.io/AUTHORS.txt + * The complete set of contributors may be found at + * http://polymer.github.io/CONTRIBUTORS.txt + * Code distributed by Google as part of the polymer project is also + * subject to an additional IP rights grant found at + * http://polymer.github.io/PATENTS.txt + */const F=new +/** + * @license + * Copyright (c) 2017 The Polymer Project Authors. All rights reserved. + * This code may only be used under the BSD style license found at + * http://polymer.github.io/LICENSE.txt + * The complete set of authors may be found at + * http://polymer.github.io/AUTHORS.txt + * The complete set of contributors may be found at + * http://polymer.github.io/CONTRIBUTORS.txt + * Code distributed by Google as part of the polymer project is also + * subject to an additional IP rights grant found at + * http://polymer.github.io/PATENTS.txt + */ +class{handleAttributeExpressions(e,t,n,r){const o=t[0];if("."===o){return new P(e,t.slice(1),n).parts}if("@"===o)return[new N(e,t.slice(1),r.eventContext)];if("?"===o)return[new I(e,t.slice(1),n)];return new j(e,t,n).parts}handleTextExpression(e){return new C(e)}}; +/** + * @license + * Copyright (c) 2017 The Polymer Project Authors. All rights reserved. + * This code may only be used under the BSD style license found at + * http://polymer.github.io/LICENSE.txt + * The complete set of authors may be found at + * http://polymer.github.io/AUTHORS.txt + * The complete set of contributors may be found at + * http://polymer.github.io/CONTRIBUTORS.txt + * Code distributed by Google as part of the polymer project is also + * subject to an additional IP rights grant found at + * http://polymer.github.io/PATENTS.txt + */"undefined"!=typeof window&&(window.litHtmlVersions||(window.litHtmlVersions=[])).push("1.2.1");const z=(e,...t)=>new A(e,t,"html",F),U=(e,t)=>`${e}--${t}`;let $=!0;void 0===window.ShadyCSS?$=!1:void 0===window.ShadyCSS.prepareTemplateDom&&(console.warn("Incompatible ShadyCSS version detected. Please update to at least @webcomponents/webcomponentsjs@2.0.2 and @webcomponents/shadycss@1.3.1."),$=!1);const H=e=>t=>{const n=U(t.type,e);let r=D.get(n);void 0===r&&(r={stringsArray:new WeakMap,keyString:new Map},D.set(n,r));let o=r.stringsArray.get(t.strings);if(void 0!==o)return o;const i=t.strings.join(c);if(o=r.keyString.get(i),void 0===o){const n=t.getTemplateElement();$&&window.ShadyCSS.prepareTemplateDom(n,e),o=new p(t,n),r.keyString.set(i,o)}return r.stringsArray.set(t.strings,o),o},W=["html","svg"],V=new Set,Y=(e,t,n)=>{V.add(e);const r=n?n.element:document.createElement("template"),o=t.querySelectorAll("style"),{length:i}=o;if(0===i)return void window.ShadyCSS.prepareTemplateStyles(r,e);const a=document.createElement("style");for(let e=0;e{W.forEach(t=>{const n=D.get(U(t,e));void 0!==n&&n.keyString.forEach(e=>{const{element:{content:t}}=e,n=new Set;Array.from(t.querySelectorAll("style")).forEach(e=>{n.add(e)}),m(e,n)})})})(e);const s=r.content;n?function(e,t,n=null){const{element:{content:r},parts:o}=e;if(null==n)return void r.appendChild(t);const i=document.createTreeWalker(r,133,null,!1);let a=y(o),s=0,c=-1;for(;i.nextNode();){c++;for(i.currentNode===n&&(s=g(t),n.parentNode.insertBefore(t,n));-1!==a&&o[a].index===c;){if(s>0){for(;-1!==a;)o[a].index+=s,a=y(o,a);return}a=y(o,a)}}}(n,a,s.firstChild):s.insertBefore(a,s.firstChild),window.ShadyCSS.prepareTemplateStyles(r,e);const c=s.querySelector("style");if(window.ShadyCSS.nativeShadow&&null!==c)t.insertBefore(c.cloneNode(!0),t.firstChild);else if(n){s.insertBefore(a,s.firstChild);const e=new Set;e.add(a),m(n,e)}};window.JSCompiler_renameProperty=(e,t)=>e;const K={toAttribute(e,t){switch(t){case Boolean:return e?"":null;case Object:case Array:return null==e?e:JSON.stringify(e)}return e},fromAttribute(e,t){switch(t){case Boolean:return null!==e;case Number:return null===e?null:Number(e);case Object:case Array:return JSON.parse(e)}return e}},G=(e,t)=>t!==e&&(t==t||e==e),J={attribute:!0,type:String,converter:K,reflect:!1,hasChanged:G};class Q extends HTMLElement{constructor(){super(),this.initialize()}static get observedAttributes(){this.finalize();const e=[];return this._classProperties.forEach((t,n)=>{const r=this._attributeNameForProperty(n,t);void 0!==r&&(this._attributeToPropertyMap.set(r,n),e.push(r))}),e}static _ensureClassProperties(){if(!this.hasOwnProperty(JSCompiler_renameProperty("_classProperties",this))){this._classProperties=new Map;const e=Object.getPrototypeOf(this)._classProperties;void 0!==e&&e.forEach((e,t)=>this._classProperties.set(t,e))}}static createProperty(e,t=J){if(this._ensureClassProperties(),this._classProperties.set(e,t),t.noAccessor||this.prototype.hasOwnProperty(e))return;const n="symbol"==typeof e?Symbol():"__"+e,r=this.getPropertyDescriptor(e,n,t);void 0!==r&&Object.defineProperty(this.prototype,e,r)}static getPropertyDescriptor(e,t,n){return{get(){return this[t]},set(r){const o=this[e];this[t]=r,this.requestUpdateInternal(e,o,n)},configurable:!0,enumerable:!0}}static getPropertyOptions(e){return this._classProperties&&this._classProperties.get(e)||J}static finalize(){const e=Object.getPrototypeOf(this);if(e.hasOwnProperty("finalized")||e.finalize(),this.finalized=!0,this._ensureClassProperties(),this._attributeToPropertyMap=new Map,this.hasOwnProperty(JSCompiler_renameProperty("properties",this))){const e=this.properties,t=[...Object.getOwnPropertyNames(e),..."function"==typeof Object.getOwnPropertySymbols?Object.getOwnPropertySymbols(e):[]];for(const n of t)this.createProperty(n,e[n])}}static _attributeNameForProperty(e,t){const n=t.attribute;return!1===n?void 0:"string"==typeof n?n:"string"==typeof e?e.toLowerCase():void 0}static _valueHasChanged(e,t,n=G){return n(e,t)}static _propertyValueFromAttribute(e,t){const n=t.type,r=t.converter||K,o="function"==typeof r?r:r.fromAttribute;return o?o(e,n):e}static _propertyValueToAttribute(e,t){if(void 0===t.reflect)return;const n=t.type,r=t.converter;return(r&&r.toAttribute||K.toAttribute)(e,n)}initialize(){this._updateState=0,this._updatePromise=new Promise(e=>this._enableUpdatingResolver=e),this._changedProperties=new Map,this._saveInstanceProperties(),this.requestUpdateInternal()}_saveInstanceProperties(){this.constructor._classProperties.forEach((e,t)=>{if(this.hasOwnProperty(t)){const e=this[t];delete this[t],this._instanceProperties||(this._instanceProperties=new Map),this._instanceProperties.set(t,e)}})}_applyInstanceProperties(){this._instanceProperties.forEach((e,t)=>this[t]=e),this._instanceProperties=void 0}connectedCallback(){this.enableUpdating()}enableUpdating(){void 0!==this._enableUpdatingResolver&&(this._enableUpdatingResolver(),this._enableUpdatingResolver=void 0)}disconnectedCallback(){}attributeChangedCallback(e,t,n){t!==n&&this._attributeToProperty(e,n)}_propertyToAttribute(e,t,n=J){const r=this.constructor,o=r._attributeNameForProperty(e,n);if(void 0!==o){const e=r._propertyValueToAttribute(t,n);if(void 0===e)return;this._updateState=8|this._updateState,null==e?this.removeAttribute(o):this.setAttribute(o,e),this._updateState=-9&this._updateState}}_attributeToProperty(e,t){if(8&this._updateState)return;const n=this.constructor,r=n._attributeToPropertyMap.get(e);if(void 0!==r){const e=n.getPropertyOptions(r);this._updateState=16|this._updateState,this[r]=n._propertyValueFromAttribute(t,e),this._updateState=-17&this._updateState}}requestUpdateInternal(e,t,n){let r=!0;if(void 0!==e){const o=this.constructor;n=n||o.getPropertyOptions(e),o._valueHasChanged(this[e],t,n.hasChanged)?(this._changedProperties.has(e)||this._changedProperties.set(e,t),!0!==n.reflect||16&this._updateState||(void 0===this._reflectingProperties&&(this._reflectingProperties=new Map),this._reflectingProperties.set(e,n))):r=!1}!this._hasRequestedUpdate&&r&&(this._updatePromise=this._enqueueUpdate())}requestUpdate(e,t){return this.requestUpdateInternal(e,t),this.updateComplete}async _enqueueUpdate(){this._updateState=4|this._updateState;try{await this._updatePromise}catch(e){}const e=this.performUpdate();return null!=e&&await e,!this._hasRequestedUpdate}get _hasRequestedUpdate(){return 4&this._updateState}get hasUpdated(){return 1&this._updateState}performUpdate(){if(!this._hasRequestedUpdate)return;this._instanceProperties&&this._applyInstanceProperties();let e=!1;const t=this._changedProperties;try{e=this.shouldUpdate(t),e?this.update(t):this._markUpdated()}catch(t){throw e=!1,this._markUpdated(),t}e&&(1&this._updateState||(this._updateState=1|this._updateState,this.firstUpdated(t)),this.updated(t))}_markUpdated(){this._changedProperties=new Map,this._updateState=-5&this._updateState}get updateComplete(){return this._getUpdateComplete()}_getUpdateComplete(){return this._updatePromise}shouldUpdate(e){return!0}update(e){void 0!==this._reflectingProperties&&this._reflectingProperties.size>0&&(this._reflectingProperties.forEach((e,t)=>this._propertyToAttribute(t,this[t],e)),this._reflectingProperties=void 0),this._markUpdated()}updated(e){}firstUpdated(e){}}Q.finalized=!0;const X=Element.prototype;X.msMatchesSelector||X.webkitMatchesSelector; +/** +@license +Copyright (c) 2019 The Polymer Project Authors. All rights reserved. +This code may only be used under the BSD style license found at +http://polymer.github.io/LICENSE.txt The complete set of authors may be found at +http://polymer.github.io/AUTHORS.txt The complete set of contributors may be +found at http://polymer.github.io/CONTRIBUTORS.txt Code distributed by Google as +part of the polymer project is also subject to an additional IP rights grant +found at http://polymer.github.io/PATENTS.txt +*/ +const Z=window.ShadowRoot&&(void 0===window.ShadyCSS||window.ShadyCSS.nativeShadow)&&"adoptedStyleSheets"in Document.prototype&&"replace"in CSSStyleSheet.prototype,ee=Symbol();class te{constructor(e,t){if(t!==ee)throw new Error("CSSResult is not constructable. Use `unsafeCSS` or `css` instead.");this.cssText=e}get styleSheet(){return void 0===this._styleSheet&&(Z?(this._styleSheet=new CSSStyleSheet,this._styleSheet.replaceSync(this.cssText)):this._styleSheet=null),this._styleSheet}toString(){return this.cssText}}const ne=e=>new te(String(e),ee),re=(e,...t)=>{const n=t.reduce((t,n,r)=>t+(e=>{if(e instanceof te)return e.cssText;if("number"==typeof e)return e;throw new Error(`Value passed to 'css' function must be a 'css' function result: ${e}. Use 'unsafeCSS' to pass non-literal values, but\n take care to ensure page security.`)})(n)+e[r+1],e[0]);return new te(n,ee)}; +/** + * @license + * Copyright (c) 2017 The Polymer Project Authors. All rights reserved. + * This code may only be used under the BSD style license found at + * http://polymer.github.io/LICENSE.txt + * The complete set of authors may be found at + * http://polymer.github.io/AUTHORS.txt + * The complete set of contributors may be found at + * http://polymer.github.io/CONTRIBUTORS.txt + * Code distributed by Google as part of the polymer project is also + * subject to an additional IP rights grant found at + * http://polymer.github.io/PATENTS.txt + */ +(window.litElementVersions||(window.litElementVersions=[])).push("2.4.0");const oe={};class ie extends Q{static getStyles(){return this.styles}static _getUniqueStyles(){if(this.hasOwnProperty(JSCompiler_renameProperty("_styles",this)))return;const e=this.getStyles();if(Array.isArray(e)){const t=(e,n)=>e.reduceRight((e,n)=>Array.isArray(n)?t(n,e):(e.add(n),e),n),n=t(e,new Set),r=[];n.forEach(e=>r.unshift(e)),this._styles=r}else this._styles=void 0===e?[]:[e];this._styles=this._styles.map(e=>{if(e instanceof CSSStyleSheet&&!Z){const t=Array.prototype.slice.call(e.cssRules).reduce((e,t)=>e+t.cssText,"");return ne(t)}return e})}initialize(){super.initialize(),this.constructor._getUniqueStyles(),this.renderRoot=this.createRenderRoot(),window.ShadowRoot&&this.renderRoot instanceof window.ShadowRoot&&this.adoptStyles()}createRenderRoot(){return this.attachShadow({mode:"open"})}adoptStyles(){const e=this.constructor._styles;0!==e.length&&(void 0===window.ShadyCSS||window.ShadyCSS.nativeShadow?Z?this.renderRoot.adoptedStyleSheets=e.map(e=>e instanceof CSSStyleSheet?e:e.styleSheet):this._needsShimAdoptedStyleSheets=!0:window.ShadyCSS.ScopingShim.prepareAdoptedCssText(e.map(e=>e.cssText),this.localName))}connectedCallback(){super.connectedCallback(),this.hasUpdated&&void 0!==window.ShadyCSS&&window.ShadyCSS.styleElement(this)}update(e){const t=this.render();super.update(e),t!==oe&&this.constructor.render(t,this.renderRoot,{scopeName:this.localName,eventContext:this}),this._needsShimAdoptedStyleSheets&&(this._needsShimAdoptedStyleSheets=!1,this.constructor._styles.forEach(e=>{const t=document.createElement("style");t.textContent=e.cssText,this.renderRoot.appendChild(t)}))}render(){return oe}}ie.finalized=!0,ie.render=(e,t,n)=>{if(!n||"object"!=typeof n||!n.scopeName)throw new Error("The `scopeName` option is required.");const r=n.scopeName,o=q.has(t),i=$&&11===t.nodeType&&!!t.host,a=i&&!V.has(r),c=a?document.createDocumentFragment():t;if(((e,t,n)=>{let r=q.get(t);void 0===r&&(s(t,t.firstChild),q.set(t,r=new C(Object.assign({templateFactory:B},n))),r.appendInto(t)),r.setValue(e),r.commit()})(e,c,Object.assign({templateFactory:H(r)},n)),a){const e=q.get(c);q.delete(c);const n=e.value instanceof S?e.value.template:void 0;Y(r,c,n),s(t,t.firstChild),t.appendChild(c),q.set(t,e)}!o&&i&&window.ShadyCSS.styleElement(t.host)};var ae=n(3),se=n.n(ae),ce=n(28),le=n.n(ce);n(287),n(288),n(289),n(290),n(291),n(292),n(293),n(294),n(295);function ue(){const e=function(e,t){t||(t=e.slice(0));return Object.freeze(Object.defineProperties(e,{raw:{value:Object.freeze(t)}}))}([".hover-bg:hover{background:var(--bg3)}::selection{background:var(--selection-bg);color:var(--selection-fg)}.regular-font{font-family:var(--font-regular)}.mono-font{font-family:var(--font-mono)}.title{font-size:calc(var(--font-size-small) + 18px);font-weight:400}.sub-title{font-size:20px}.req-res-title{font-family:var(--font-regular);font-size:calc(var(--font-size-small) + 4px);font-weight:700;margin-bottom:8px}.tiny-title{font-size:calc(var(--font-size-small) + 1px);font-weight:700}.regular-font-size{font-size:var(--font-size-regular)}.small-font-size{font-size:var(--font-size-small)}.upper{text-transform:uppercase}.primary-text{color:var(--primary-color)}.bold-text{font-weight:700}.gray-text{color:var(--light-fg)}.red-text{color:var(--red)}.blue-text{color:var(--blue)}.multiline{overflow:scroll;max-height:var(--resp-area-height,300px);color:var(--fg3)}.method-fg.put{color:var(--orange)}.method-fg.post{color:var(--green)}.method-fg.get{color:var(--blue)}.method-fg.delete{color:var(--red)}.method-fg.head,.method-fg.options,.method-fg.patch{color:var(--yellow)}h1{font-family:var(--font-regular);font-size:28px;padding-top:10px;letter-spacing:normal;font-weight:400}h2{font-family:var(--font-regular);font-size:24px;padding-top:10px;letter-spacing:normal;font-weight:400}h3{font-family:var(--font-regular);font-size:18px;padding-top:10px;letter-spacing:normal;font-weight:400}h4{font-family:var(--font-regular);font-size:16px;padding-top:10px;letter-spacing:normal;font-weight:400}h5{font-family:var(--font-regular);font-size:14px;padding-top:10px;letter-spacing:normal;font-weight:400}h6{font-family:var(--font-regular);font-size:14px;padding-top:10px;letter-spacing:normal;font-weight:400}h1,h2,h3,h4,h5{margin-block-end:.2em}p{margin-block-start:.5em}a{color:var(--blue);cursor:pointer}a.inactive-link{color:var(--fg);text-decoration:none;cursor:text}code,pre{margin:0;font-family:var(--font-mono);font-size:calc(var(--font-size-mono) - 1px)}.m-markdown,.m-markdown-small{display:block}.m-markdown li,.m-markdown p,.m-markdown span{font-size:var(--font-size-regular);line-height:calc(var(--font-size-regular) + 8px)}.m-markdown-small li,.m-markdown-small p,.m-markdown-small span{font-size:var(--font-size-small);line-height:calc(var(--font-size-small) + 6px)}.m-markdown-small li{line-height:calc(var(--font-size-small) + 8px)}.m-markdown p:not(:first-child){margin-block-start:24px}.m-markdown-small p:not(:first-child){margin-block-start:12px}.m-markdown-small p:first-child{margin-block-start:0}.m-markdown p,.m-markdown-small p{margin-block-end:0}.m-markdown code span{font-size:var(--font-size-mono)}.m-markdown code,.m-markdown-small code{padding:1px 6px;border-radius:2px;color:var(--inline-code-fg);background-color:var(--bg3);font-size:calc(var(--font-size-mono));line-height:1.2}.m-markdown-small code{font-size:calc(var(--font-size-mono) - 1px)}.m-markdown pre,.m-markdown-small pre{white-space:pre-wrap;overflow-x:auto;line-height:normal;border-radius:2px;border:1px solid var(--code-border-color)}.m-markdown pre{margin-top:8px;padding:12px;background-color:var(--code-bg);color:var(--code-fg)}.m-markdown-small pre{margin-top:4px;padding:2px 4px;background-color:var(--bg3);color:var(--fg2)}.m-markdown pre code,.m-markdown-small pre code{border:none;padding:0}.m-markdown pre code{color:var(--code-fg);background-color:var(--code-bg)}.m-markdown-small pre code{color:var(--fg2);background-color:var(--bg3)}.m-markdown ol,.m-markdown ul{padding-inline-start:30px}.m-markdown-small ol,.m-markdown-small ul{padding-inline-start:20px}.m-markdown a,.m-markdown-small a{color:var(--blue)}.m-markdown img,.m-markdown-small img{max-width:100%}.m-markdown table,.m-markdown-small table{border-spacing:0;margin:10px 0;border-collapse:separate;border:1px solid var(--border-color);border-radius:var(--border-radius);font-size:calc(var(--font-size-small) + 1px);line-height:calc(var(--font-size-small) + 4px);max-width:100%}.m-markdown-small table{font-size:var(--font-size-small);line-height:calc(var(--font-size-small) + 2px);margin:8px 0}.m-markdown td,.m-markdown th,.m-markdown-small td,.m-markdown-small th{vertical-align:top;border-top:1px solid var(--border-color);line-height:calc(var(--font-size-small) + 4px)}.m-markdown tr:first-child th,.m-markdown-small tr:first-child th{border-top:0 none}.m-markdown td,.m-markdown th{padding:10px 12px}.m-markdown-small td,.m-markdown-small th{padding:8px 8px}.m-markdown th,.m-markdown-small th{font-weight:600;background-color:var(--bg2);vertical-align:middle}.m-markdown-small table code{font-size:calc(var(--font-size-mono) - 2px)}.m-markdown table code{font-size:calc(var(--font-size-mono) - 1px)}.m-markdown blockquote,.m-markdown-small blockquote{margin-inline-start:0;margin-inline-end:0;border-left:3px solid var(--border-color);padding:6px 0 6px 6px}"]);return ue=function(){return e},e}var pe=re(ue());function fe(){const e=function(e,t){t||(t=e.slice(0));return Object.freeze(Object.defineProperties(e,{raw:{value:Object.freeze(t)}}))}([".m-btn{border-radius:var(--border-radius);box-sizing:border-box;font-weight:600;display:inline-block;padding:6px 16px;font-size:var(--font-size-small);outline:0;line-height:1;text-align:center;white-space:nowrap;border:2px solid var(--primary-color);background-color:transparent;transition:background-color .2s;user-select:none;cursor:pointer;box-shadow:0 1px 3px rgba(0,0,0,.12),0 1px 2px rgba(0,0,0,.24)}.m-btn.primary{background-color:var(--primary-color);color:var(--primary-color-invert)}.m-btn.thin-border{border-width:1px}.m-btn.large{padding:8px 14px}.m-btn.small{padding:5px 12px}.m-btn.tiny{padding:5px 6px}.m-btn.circle{border-radius:50%}.m-btn:hover{background-color:var(--primary-color);color:var(--primary-color-invert)}.m-btn.nav{border:2px solid var(--nav-accent-color)}.m-btn.nav:hover{background-color:var(--nav-accent-color)}.m-btn:disabled{background-color:var(--bg3);color:var(--fg3);border-color:var(--fg3);cursor:not-allowed;opacity:.4}.toolbar-btn{cursor:pointer;padding:2px 0 4px;margin:0 2px;min-width:50px;color:var(--primary-color-invert);border-radius:2px;border:none;background-color:var(--primary-color)}button,input,pre,select,textarea{color:var(--fg);outline:0;background-color:var(--input-bg);border:1px solid var(--border-color);border-radius:var(--border-radius)}input[type=file],input[type=password],input[type=text],pre,select,textarea{font-family:var(--font-mono);font-weight:400;font-size:var(--font-size-small);transition:border .2s;padding:6px 5px;box-sizing:border-box}select{font-family:var(--font-regular);padding:5px 30px 5px 5px;background-image:url(\"data:image/svg+xml;charset=utf8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2212%22%20height%3D%2212%22%3E%3Cpath%20d%3D%22M10.3%203.3L6%207.6%201.7%203.3A1%201%200%2000.3%204.7l5%205a1%201%200%20001.4%200l5-5a1%201%200%2010-1.4-1.4z%22%20fill%3D%22%23777777%22%2F%3E%3C%2Fsvg%3E\");background-position:calc(100% - 5px) center;background-repeat:no-repeat;background-size:10px;-webkit-appearance:none;-moz-appearance:none;appearance:none;cursor:pointer}select:hover{border-color:var(--primary-color)}input[type=password]::placeholder,input[type=text]::placeholder,textarea::placeholder{color:var(--placeholder-color);opacity:1}input[type=password]:active,input[type=password]:focus,input[type=text]:active,input[type=text]:focus,select:focus,textarea:active,textarea:focus{border:1px solid var(--primary-color)}input[type=file]{font-family:var(--font-regular);padding:2px;cursor:pointer;border:1px solid var(--primary-color);min-height:calc(var(--font-size-small) + 18px)}input[type=file]::-webkit-file-upload-button{font-family:var(--font-regular);font-size:var(--font-size-small);outline:0;cursor:pointer;padding:3px 8px;border:1px solid var(--primary-color);background-color:var(--primary-color);color:var(--primary-color-invert);border-radius:var(--border-radius);-webkit-appearance:none}pre,textarea{scrollbar-width:thin;scrollbar-color:var(--border-color) var(--input-bg)}pre::-webkit-scrollbar,textarea::-webkit-scrollbar{width:8px;height:8px}pre::-webkit-scrollbar-track,textarea::-webkit-scrollbar-track{background:var(--input-bg)}pre::-webkit-scrollbar-thumb,textarea::-webkit-scrollbar-thumb{border-radius:2px;background-color:var(--border-color)}.link{font-size:var(--font-size-small);text-decoration:underline;color:var(--blue);font-family:var(--font-mono);margin-bottom:2px}input[type=checkbox]:focus{outline:0}input[type=checkbox]{appearance:none;display:inline-block;background-color:var(--light-bg);border:1px solid var(--light-bg);border-radius:10px;cursor:pointer;height:20px;position:relative;transition:border .25s .15s,box-shadow .25s .3s,padding .25s;min-width:36px;width:36px;vertical-align:top}input[type=checkbox]:after{position:absolute;background-color:var(--bg);border:1px solid var(--light-bg);border-radius:8px;content:'';top:0;left:0;right:16px;display:block;height:16px;transition:border .25s .15s,left .25s .1s,right .15s .175s}input[type=checkbox]:checked{box-shadow:inset 0 0 0 13px var(--green);border-color:var(--green)}input[type=checkbox]:checked:after{border:1px solid var(--green);left:16px;right:1px;transition:border .25s,left .15s .25s,right .25s .175s}"]);return fe=function(){return e},e}var de=re(fe());function he(){const e=function(e,t){t||(t=e.slice(0));return Object.freeze(Object.defineProperties(e,{raw:{value:Object.freeze(t)}}))}([".col,.row{display:flex}.row{align-items:center;flex-direction:row}.col{align-items:stretch;flex-direction:column}"]);return he=function(){return e},e}var ve=re(he());function me(){const e=function(e,t){t||(t=e.slice(0));return Object.freeze(Object.defineProperties(e,{raw:{value:Object.freeze(t)}}))}([".m-table{border-spacing:0;border-collapse:separate;border:1px solid var(--light-border-color);border-radius:var(--border-radius);margin:0;max-width:100%}.m-table tr:first-child td,.m-table tr:first-child th{border-top:0 none}.m-table td,.m-table th{font-size:var(--font-size-small);line-height:calc(var(--font-size-small) + 4px);padding:4px 5px 4px;vertical-align:top}.m-table td:not([align]),.m-table th:not([align]){text-align:left}.m-table th{color:var(--fg2);font-size:var(--font-size-small);line-height:calc(var(--font-size-small) + 18px);font-weight:600;letter-spacing:normal;background-color:var(--bg2);vertical-align:bottom;border-bottom:1px solid var(--light-border-color)}.m-table>tbody>tr>td,.m-table>tr>td{border-top:1px solid var(--light-border-color);text-overflow:ellipsis;overflow:hidden}.table-title{font-size:var(--font-size-small);font-weight:700;vertical-align:middle;margin:12px 0 4px 0}"]);return me=function(){return e},e}var ge=re(me());function ye(){const e=function(e,t){t||(t=e.slice(0));return Object.freeze(Object.defineProperties(e,{raw:{value:Object.freeze(t)}}))}([".only-large-screen{display:none}.endpoint-head .path{display:flex;font-family:var(--font-mono);font-size:var(--font-size-small);align-items:center;overflow-wrap:break-word;word-break:break-all}.endpoint-head .descr{font-size:var(--font-size-small);color:var(--light-fg);font-weight:400;align-items:center;overflow-wrap:break-word;word-break:break-all;display:none}.m-endpoint.expanded{margin-bottom:16px}.m-endpoint>.endpoint-head{border-width:1px 1px 1px 5px;border-style:solid;border-color:transparent;border-top-color:var(--light-border-color);display:flex;padding:6px 16px;align-items:center;cursor:pointer}.m-endpoint>.endpoint-head.put.expanded,.m-endpoint>.endpoint-head.put:hover{border-color:var(--orange);background-color:var(--light-orange)}.m-endpoint>.endpoint-head.post.expanded,.m-endpoint>.endpoint-head.post:hover{border-color:var(--green);background-color:var(--light-green)}.m-endpoint>.endpoint-head.get.expanded,.m-endpoint>.endpoint-head.get:hover{border-color:var(--blue);background-color:var(--light-blue)}.m-endpoint>.endpoint-head.delete.expanded,.m-endpoint>.endpoint-head.delete:hover{border-color:var(--red);background-color:var(--light-red)}.m-endpoint>.endpoint-head.head.expanded,.m-endpoint>.endpoint-head.head:hover,.m-endpoint>.endpoint-head.options.expanded,.m-endpoint>.endpoint-head.options:hover,.m-endpoint>.endpoint-head.patch.expanded,.m-endpoint>.endpoint-head.patch:hover{border-color:var(--yellow);background-color:var(--light-yellow)}.m-endpoint>.endpoint-head.deprecated.expanded,.m-endpoint>.endpoint-head.deprecated:hover{border-color:var(--border-color);filter:opacity(.6)}.m-endpoint .endpoint-body{flex-wrap:wrap;padding:16px 0 0 0;border-width:0 1px 1px 5px;border-style:solid;box-shadow:0 4px 3px -3px rgba(0,0,0,.15)}.m-endpoint .endpoint-body.delete{border-color:var(--red)}.m-endpoint .endpoint-body.put{border-color:var(--orange)}.m-endpoint .endpoint-body.post{border-color:var(--green)}.m-endpoint .endpoint-body.get{border-color:var(--blue)}.m-endpoint .endpoint-body.head,.m-endpoint .endpoint-body.options,.m-endpoint .endpoint-body.patch{border-color:var(--yellow)}.m-endpoint .endpoint-body.deprecated{border-color:var(--border-color);filter:opacity(.6)}.endpoint-head .deprecated{color:var(--light-fg);filter:opacity(.6)}.summary{padding:8px 8px}.summary .title{font-size:calc(var(--title-font-size) + 2px);margin-bottom:6px;word-break:break-all}.method{padding:2px 5px;vertical-align:middle;font-size:var(--font-size-small);height:calc(var(--font-size-small) + 8px);line-height:calc(var(--font-size-small) + 8px);min-width:48px;border-radius:2px;display:inline-block;text-align:center;font-weight:700;text-transform:uppercase;margin-right:5px}.method.delete{border:2px solid var(--red)}.method.put{border:2px solid var(--orange)}.method.post{border:2px solid var(--green)}.method.get{border:2px solid var(--blue)}.method.get.deprecated{border:2px solid var(--border-color)}.method.head,.method.options,.method.patch{border:2px solid var(--yellow)}.req-resp-container{display:flex;margin-top:16px;align-items:stretch;flex-wrap:wrap;flex-direction:column;border-top:1px solid var(--light-border-color)}.request,.response{flex:1;min-height:100px;padding:16px 8px;overflow:hidden}.request{border-width:0 0 1px 0;border-style:dashed}.head .request,.options .request,.patch .request{border-color:var(--yellow)}.put .request{border-color:var(--orange)}.post .request{border-color:var(--green)}.get .request{border-color:var(--blue)}.delete .request{border-color:var(--red)}@media only screen and (min-width:768px){.only-large-screen{display:block}.endpoint-head .path{font-size:var(--font-size-regular);min-width:400px}.endpoint-head .descr{display:flex}.descr .m-markdown-small,.endpoint-head .m-markdown-small{display:block}.req-resp-container{flex-direction:var(--layout,row)}.request{border-width:0 1px 0 0;padding:16px 24px}.response{padding:16px 24px}.summary{padding:8px 24px}}"]);return ye=function(){return e},e}var be=re(ye());function xe(){const e=function(e,t){t||(t=e.slice(0));return Object.freeze(Object.defineProperties(e,{raw:{value:Object.freeze(t)}}))}(["code[class*=language-],pre[class*=language-]{text-align:left;white-space:pre;word-spacing:normal;word-break:normal;word-wrap:normal;line-height:1.5;tab-size:2;-webkit-hyphens:none;-moz-hyphens:none;-ms-hyphens:none;hyphens:none}pre[class*=language-]{padding:1em;margin:.5em 0;overflow:auto}:not(pre)>code[class*=language-]{white-space:normal}.token.block-comment,.token.cdata,.token.comment,.token.doctype,.token.prolog{color:#999}.token.punctuation{color:#ccc}.token.attr-name,.token.deleted,.token.namespace,.token.tag{color:#ffb2b2}.token.function-name{color:#71b7ff}.token.boolean,.token.function,.token.number{color:#f08d49}.token.class-name,.token.constant,.token.property,.token.symbol{color:#f8c555}.token.atrule,.token.builtin,.token.important,.token.keyword,.token.selector{color:#cc99cd}.token.attr-value,.token.char,.token.regex,.token.string,.token.variable{color:#7ec699}.token.entity,.token.operator,.token.url{color:#67cdcc}.token.bold,.token.important{font-weight:700}.token.italic{font-style:italic}.token.entity{cursor:help}.token.inserted{color:green}"]);return xe=function(){return e},e}var we=re(xe());function ke(){const e=function(e,t){t||(t=e.slice(0));return Object.freeze(Object.defineProperties(e,{raw:{value:Object.freeze(t)}}))}([".tab-panel{border:none}.tab-buttons{height:30px;border-bottom:1px solid var(--light-border-color);align-items:stretch;overflow-y:hidden;overflow-x:auto;scrollbar-width:thin}.tab-buttons::-webkit-scrollbar{height:1px;background-color:var(--border-color)}.tab-btn{border:none;border-bottom:3px solid transparent;white-space:nowrap;background-color:transparent;cursor:pointer;outline:0;font-size:var(--font-size-small);margin-right:16px;padding:1px}.tab-btn.active{border-bottom:3px solid var(--primary-color);font-weight:700;color:var(--primary-color)}.tab-btn:hover{color:var(--primary-color)}.tab-content{margin:-1px 0 0 0;position:relative}"]);return ke=function(){return e},e}var Se=re(ke());function Oe(){const e=function(e,t){t||(t=e.slice(0));return Object.freeze(Object.defineProperties(e,{raw:{value:Object.freeze(t)}}))}([".nav-bar{width:0;height:100%;overflow:hidden;color:var(--nav-text-color);background-color:var(--nav-bg-color);background-image:var(--nav-bg-image);background-size:var(--nav-bg-image-size);background-repeat:var(--nav-bg-image-repeat, 'no-repeat');background-blend-mode:multiply;box-sizing:border-box;line-height:calc(var(--font-size-small) + 4px);display:none;position:relative;flex-direction:column;flex-wrap:nowrap;word-break:break-word}.nav-scroll{overflow-x:hidden;overflow-y:auto;overflow-y:overlay;scrollbar-width:thin;scrollbar-color:var(--nav-hover-bg-color) transparent}.nav-scroll::-webkit-scrollbar{width:10px}.nav-scroll::-webkit-scrollbar-track{background:0 0}.nav-scroll::-webkit-scrollbar-thumb{background-color:var(--nav-hover-bg-color)}.nav-bar-tag{font-size:var(--font-size-regular);color:var(--nav-accent-color);border-left:4px solid transparent;font-weight:700;padding:15px 30px 15px 10px;text-transform:capitalize}.nav-bar-components,.nav-bar-h1,.nav-bar-h2,.nav-bar-info,.nav-bar-path,.nav-bar-tag{display:flex;cursor:pointer;border-left:4px solid transparent}.nav-bar-h1,.nav-bar-h2,.nav-bar-path{font-size:calc(var(--font-size-small) + 1px);padding:var(--nav-item-padding)}.nav-bar-path.small-font{font-size:var(--font-size-small)}.nav-bar-info{font-size:var(--font-size-regular);padding:16px 10px;font-weight:700}.nav-bar-section{display:block;font-size:var(--font-size-small);color:var(--nav-text-color);text-transform:uppercase;padding:15px 15px 5px 5px;text-align:right;filter:opacity(.5);font-weight:700;border-bottom:1px solid var(--nav-text-color)}.nav-bar-section:first-child{display:none}.nav-bar-h2{margin-left:12px}.nav-bar-h1.active,.nav-bar-h2.active,.nav-bar-info.active,.nav-bar-path.active,.nav-bar-tag.active{border-left:4px solid var(--nav-accent-color);color:var(--nav-hover-text-color)}.nav-bar-h1:hover,.nav-bar-h2:hover,.nav-bar-info:hover,.nav-bar-path:hover,.nav-bar-tag:hover{color:var(--nav-hover-text-color);background-color:var(--nav-hover-bg-color)}"]);return Oe=function(){return e},e}var Ae=re(Oe());function Ee(){const e=function(e,t){t||(t=e.slice(0));return Object.freeze(Object.defineProperties(e,{raw:{value:Object.freeze(t)}}))}(['#api-info{font-size:calc(var(--font-size-regular) - 1px);margin-top:8px margin-left: -15px}#api-info span:before{content:"|";display:inline-block;opacity:.5;width:15px;text-align:center}#api-info span:first-child:before{content:"";width:0}']);return Ee=function(){return e},e}var _e=re(Ee());function je(){const e=function(e,t){t||(t=e.slice(0));return Object.freeze(Object.defineProperties(e,{raw:{value:Object.freeze(t)}}))}([""]);return je=function(){return e},e}var Te=re(je());const Ce=new RegExp(/[\s#:?&={}]/,"g");function Ie(e){return new Promise(t=>setTimeout(t,e))}function Pe(e,t){const n=t.currentTarget,r=document.createElement("textarea");r.value=e,r.style.position="fixed",document.body.appendChild(r),r.focus(),r.select();try{document.execCommand("copy"),n.innerText="Copied",setTimeout(()=>{n.innerText="Copy"},5e3)}catch(e){console.error("Unable to copy",e)}document.body.removeChild(r)}function Re(e,t){return"".concat(t.method," ").concat(t.path," ").concat(t.summary||t.description||""," ").concat(t.operationId||"").toLowerCase().includes(e)}function Le(e,t=new Set){return e?(Object.keys(e).forEach(n=>{var r;if(t.add(n),e[n].properties)Le(e[n].properties,t);else if(null!==(r=e[n].items)&&void 0!==r&&r.properties){var o;Le(null===(o=e[n].items)||void 0===o?void 0:o.properties,t)}}),t):t}var Ne=n(267),Me=n.n(Ne),Be=n(8),De=n.n(Be),qe=n(58),Fe=n.n(qe),ze=n(65),Ue=n.n(ze),$e=n(102),He=n.n($e),We=n(34),Ve=n.n(We),Ye=n(33),Ke=n.n(Ye),Ge=n(9),Je=n.n(Ge),Qe=n(19),Xe=n.n(Qe),Ze=n(47),et=n.n(Ze),tt=n(268),nt=n.n(tt),rt=n(2),ot=n.n(rt),it=n(5),at=n.n(it),st=n(6),ct=n.n(st),lt=n(131),ut=n.n(lt),pt=n(66),ft=n.n(pt),dt=n(24),ht=n.n(dt),vt=n(4),mt=n.n(vt),gt=n(10),yt=n.n(gt),bt=n(35),xt=n.n(bt),wt=n(269),kt=n.n(wt),St=n(15),Ot=n.n(St),At=n(173),Et=n.n(At),_t=n(80),jt=n.n(_t),Tt=n(11),Ct=n.n(Tt),It=n(81),Pt=n.n(It),Rt=(n(485),n(57)),Lt=n.n(Rt),Nt=n(132),Mt=n.n(Nt),Bt=n(82),Dt=n.n(Bt),qt=n(51),Ft=n.n(qt),zt=n(32),Ut=n(270),$t=n.n(Ut),Ht=n(83),Wt=n.n(Ht),Vt=n(67),Yt=n.n(Vt),Kt=n(271),Gt=n.n(Kt),Jt=n(130),Qt=n.n(Jt),Xt=n(272),Zt=n.n(Xt),en=n(273),tn=n.n(en),nn=n(274),rn=function(e){var t=function(e,t){return{name:e,value:t}};return Ft()(e.prototype.set)||Ft()(e.prototype.get)||Ft()(e.prototype.getAll)||Ft()(e.prototype.has)?e:function(e){Zt()(r,e);var n=tn()(r);function r(e){var t;return Wt()(this,r),(t=n.call(this,e)).entryList=[],t}return Yt()(r,[{key:"append",value:function(e,n,o){return this.entryList.push(t(e,n)),Gt()(Qt()(r.prototype),"append",this).call(this,e,n,o)}},{key:"set",value:function(e,n){var r,o=t(e,n);this.entryList=Xe()(r=this.entryList).call(r,(function(t){return t.name!==e})),this.entryList.push(o)}},{key:"get",value:function(e){var t,n=$t()(t=this.entryList).call(t,(function(t){return t.name===e}));return void 0===n?null:n}},{key:"getAll",value:function(e){var t,n;return ct()(t=Xe()(n=this.entryList).call(n,(function(t){return t.name===e}))).call(t,(function(e){return e.value}))}},{key:"has",value:function(e){var t;return ht()(t=this.entryList).call(t,(function(t){return t.name===e}))}}]),r}(e)}(n.n(nn).a),on=n(23),an=n.n(on),sn=n(40),cn=n.n(sn),ln=n(32).Buffer,un=function(e){var t;return Ct()(t=":/?#[]@!$&'()*+,;=").call(t,e)>-1},pn=function(e){return/^[a-z0-9\-._~]+$/i.test(e)};function fn(e){var t,n=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},r=n.escape,o=arguments.length>2?arguments[2]:void 0;return"number"==typeof e&&(e=e.toString()),"string"==typeof e&&e.length&&r?o?JSON.parse(e):ct()(t=cn()(e)).call(t,(function(e){var t,n;return pn(e)||un(e)&&"unsafe"===r?e:ct()(t=ct()(n=ln.from(e).toJSON().data||[]).call(n,(function(e){var t;return an()(t="0".concat(e.toString(16).toUpperCase())).call(t,-2)}))).call(t,(function(e){return"%".concat(e)})).join("")})).join(""):e}function dn(e){var t=e.value;return mt()(t)?function(e){var t,n=e.key,r=e.value,o=e.style,i=e.explode,a=e.escape,s=function(e){return fn(e,{escape:a})};if("simple"===o)return ct()(r).call(r,(function(e){return s(e)})).join(",");if("label"===o)return".".concat(ct()(r).call(r,(function(e){return s(e)})).join("."));if("matrix"===o)return Ot()(t=ct()(r).call(r,(function(e){return s(e)}))).call(t,(function(e,t){var r,o,a;return!e||i?ot()(o=ot()(a="".concat(e||"",";")).call(a,n,"=")).call(o,t):ot()(r="".concat(e,",")).call(r,t)}),"");if("form"===o){var c=i?"&".concat(n,"="):",";return ct()(r).call(r,(function(e){return s(e)})).join(c)}if("spaceDelimited"===o){var l=i?"".concat(n,"="):"";return ct()(r).call(r,(function(e){return s(e)})).join(" ".concat(l))}if("pipeDelimited"===o){var u=i?"".concat(n,"="):"";return ct()(r).call(r,(function(e){return s(e)})).join("|".concat(u))}return}(e):"object"===yt()(t)?function(e){var t=e.key,n=e.value,r=e.style,o=e.explode,i=e.escape,a=function(e){return fn(e,{escape:i})},s=at()(n);if("simple"===r)return Ot()(s).call(s,(function(e,t){var r,i,s,c=a(n[t]),l=o?"=":",",u=e?"".concat(e,","):"";return ot()(r=ot()(i=ot()(s="".concat(u)).call(s,t)).call(i,l)).call(r,c)}),"");if("label"===r)return Ot()(s).call(s,(function(e,t){var r,i,s,c=a(n[t]),l=o?"=":".",u=e?"".concat(e,"."):".";return ot()(r=ot()(i=ot()(s="".concat(u)).call(s,t)).call(i,l)).call(r,c)}),"");if("matrix"===r&&o)return Ot()(s).call(s,(function(e,t){var r,o,i=a(n[t]),s=e?"".concat(e,";"):";";return ot()(r=ot()(o="".concat(s)).call(o,t,"=")).call(r,i)}),"");if("matrix"===r)return Ot()(s).call(s,(function(e,r){var o,i,s=a(n[r]),c=e?"".concat(e,","):";".concat(t,"=");return ot()(o=ot()(i="".concat(c)).call(i,r,",")).call(o,s)}),"");if("form"===r)return Ot()(s).call(s,(function(e,t){var r,i,s,c,l=a(n[t]),u=e?ot()(r="".concat(e)).call(r,o?"&":","):"",p=o?"=":",";return ot()(i=ot()(s=ot()(c="".concat(u)).call(c,t)).call(s,p)).call(i,l)}),"");return}(e):function(e){var t,n=e.key,r=e.value,o=e.style,i=e.escape,a=function(e){return fn(e,{escape:i})};if("simple"===o)return a(r);if("label"===o)return".".concat(a(r));if("matrix"===o)return ot()(t=";".concat(n,"=")).call(t,a(r));if("form"===o)return a(r);if("deepObject"===o)return a(r);return}(e)}var hn={serializeRes:bn,mergeInQueryOrForm:Cn};function vn(e){return mn.apply(this,arguments)}function mn(){return(mn=Pt()(Ke.a.mark((function e(t){var n,r,o,i,a,s,c=arguments;return Ke.a.wrap((function(e){for(;;)switch(e.prev=e.next){case 0:if(n=c.length>1&&void 0!==c[1]?c[1]:{},"object"===yt()(t)&&(t=(n=t).url),n.headers=n.headers||{},hn.mergeInQueryOrForm(n),n.headers&&Je()(r=at()(n.headers)).call(r,(function(e){var t=n.headers[e];"string"==typeof t&&(n.headers[e]=t.replace(/\n+/g," "))})),!n.requestInterceptor){e.next=12;break}return e.next=8,n.requestInterceptor(n);case 8:if(e.t0=e.sent,e.t0){e.next=11;break}e.t0=n;case 11:n=e.t0;case 12:return o=n.headers["content-type"]||n.headers["Content-Type"],/multipart\/form-data/i.test(o)&&(delete n.headers["content-type"],delete n.headers["Content-Type"]),e.prev=14,e.next=17,(n.userFetch||fetch)(n.url,n);case 17:return i=e.sent,e.next=20,hn.serializeRes(i,t,n);case 20:if(i=e.sent,!n.responseInterceptor){e.next=28;break}return e.next=24,n.responseInterceptor(i);case 24:if(e.t1=e.sent,e.t1){e.next=27;break}e.t1=i;case 27:i=e.t1;case 28:e.next=39;break;case 30:if(e.prev=30,e.t2=e.catch(14),i){e.next=34;break}throw e.t2;case 34:throw(a=new Error(i.statusText)).status=i.status,a.statusCode=i.status,a.responseError=e.t2,a;case 39:if(i.ok){e.next=45;break}throw(s=new Error(i.statusText)).status=i.status,s.statusCode=i.status,s.response=i,s;case 45:return e.abrupt("return",i);case 46:case"end":return e.stop()}}),e,null,[[14,30]])})))).apply(this,arguments)}var gn=function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:"";return/(json|xml|yaml|text)\b/.test(e)};function yn(e,t){return t&&(0===Ct()(t).call(t,"application/json")||Ct()(t).call(t,"+json")>0)?JSON.parse(e):Mt.a.safeLoad(e)}function bn(e,t){var n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{},r=n.loadSpec,o=void 0!==r&&r,i={ok:e.ok,url:e.url||t,status:e.status,statusText:e.statusText,headers:wn(e.headers)},a=i.headers["content-type"],s=o||gn(a),c=s?e.text:e.blob||e.buffer;return c.call(e).then((function(e){if(i.text=e,i.data=e,s)try{var t=yn(e,a);i.body=t,i.obj=t}catch(e){i.parseError=e}return i}))}function xn(e){return jt()(e).call(e,", ")?e.split(", "):e}function wn(){var e,t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};return Ft()(Et()(t))?Ot()(e=kt()(Et()(t).call(t))).call(e,(function(e,t){var n=xt()(t,2),r=n[0],o=n[1];return e[r]=xn(o),e}),{}):{}}function kn(e,t){return t||"undefined"==typeof navigator||(t=navigator),t&&"ReactNative"===t.product?!(!e||"object"!==yt()(e)||"string"!=typeof e.uri):"undefined"!=typeof File&&e instanceof File||("undefined"!=typeof Blob&&e instanceof Blob||(void 0!==zt.Buffer&&e instanceof zt.Buffer||null!==e&&"object"===yt()(e)&&"function"==typeof e.pipe))}function Sn(e,t){return mt()(e)&&ht()(e).call(e,(function(e){return kn(e,t)}))}var On={form:",",spaceDelimited:"%20",pipeDelimited:"|"},An={csv:",",ssv:"%20",tsv:"%09",pipes:"|"};function En(e,t){var n=arguments.length>2&&void 0!==arguments[2]&&arguments[2],r=t.collectionFormat,o=t.allowEmptyValue,i=t.serializationOption,a=t.encoding,s="object"!==yt()(t)||mt()(t)?t:t.value,c=n?function(e){return e.toString()}:function(e){return encodeURIComponent(e)},l=c(e);if(void 0===s&&o)return[[l,""]];if(kn(s)||Sn(s))return[[l,s]];if(i)return _n(e,s,n,i);if(a){var u;if(ht()(u=[yt()(a.style),yt()(a.explode),yt()(a.allowReserved)]).call(u,(function(e){return"undefined"!==e})))return _n(e,s,n,Dt()(a,["style","explode","allowReserved"]));if(a.contentType){if("application/json"===a.contentType){var p="string"==typeof s?s:ft()(s);return[[l,c(p)]]}return[[l,c(s.toString())]]}return"object"!==yt()(s)?[[l,c(s)]]:mt()(s)&&ut()(s).call(s,(function(e){return"object"!==yt()(e)}))?[[l,ct()(s).call(s,c).join(",")]]:[[l,c(ft()(s))]]}return"object"!==yt()(s)?[[l,c(s)]]:mt()(s)?"multi"===r?[[l,ct()(s).call(s,c)]]:[[l,ct()(s).call(s,c).join(An[r||"csv"])]]:[[l,""]]}function _n(e,t,n,r){var o,i,a,s=r.style||"form",c=void 0===r.explode?"form"===s:r.explode,l=!n&&(r&&r.allowReserved?"unsafe":"reserved"),u=function(e){return fn(e,{escape:l})},p=n?function(e){return e}:function(e){return fn(e,{escape:l})};return"object"!==yt()(t)?[[p(e),u(t)]]:mt()(t)?c?[[p(e),ct()(t).call(t,u)]]:[[p(e),ct()(t).call(t,u).join(On[s])]]:"deepObject"===s?ct()(i=at()(t)).call(i,(function(n){var r;return[p(ot()(r="".concat(e,"[")).call(r,n,"]")),u(t[n])]})):c?ct()(a=at()(t)).call(a,(function(e){return[p(e),u(t[e])]})):[[p(e),ct()(o=at()(t)).call(o,(function(e){var n;return[ot()(n="".concat(p(e),",")).call(n,u(t[e]))]})).join(",")]]}function jn(e){var t;return Ot()(t=nt()(e)).call(t,(function(e,t){var n,r=xt()(t,2),o=r[0],i=r[1],a=et()(En(o,i,!0));try{for(a.s();!(n=a.n()).done;){var s=xt()(n.value,2),c=s[0],l=s[1];if(mt()(l)){var u,p=et()(l);try{for(p.s();!(u=p.n()).done;){var f=u.value;e.append(c,f)}}catch(e){p.e(e)}finally{p.f()}}else e.append(c,l)}}catch(e){a.e(e)}finally{a.f()}return e}),new rn)}function Tn(e){var t,n=Ot()(t=at()(e)).call(t,(function(t,n){var r,o=et()(En(n,e[n]));try{for(o.s();!(r=o.n()).done;){var i=xt()(r.value,2),a=i[0],s=i[1];t[a]=s}}catch(e){o.e(e)}finally{o.f()}return t}),{});return Lt.a.stringify(n,{encode:!1,indices:!1})||""}function Cn(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},t=e.url,n=void 0===t?"":t,r=e.query,o=e.form,i=function(){for(var e=arguments.length,t=new Array(e),n=0;n=48&&t<=57))return!1;n++}return!0}function Zn(e){return-1===e.indexOf("/")&&-1===e.indexOf("~")?e:e.replace(/~/g,"~0").replace(/\//g,"~1")}function er(e){return e.replace(/~1/g,"/").replace(/~0/g,"~")}function tr(e,t){var n=[e];for(var r in t){var o="object"==typeof t[r]?JSON.stringify(t[r],null,2):t[r];void 0!==o&&n.push(r+": "+o)}return n.join("\n")}var nr=function(e){function t(t,n,r,o,i){var a=this.constructor,s=e.call(this,tr(t,{name:n,index:r,operation:o,tree:i}))||this;return s.name=n,s.index=r,s.operation=o,s.tree=i,Object.setPrototypeOf(s,a.prototype),s.message=tr(t,{name:n,index:r,operation:o,tree:i}),s}return Yn(t,e),t}(Error),rr=nr,or=Qn,ir={add:function(e,t,n){return e[t]=this.value,{newDocument:n}},remove:function(e,t,n){var r=e[t];return delete e[t],{newDocument:n,removed:r}},replace:function(e,t,n){var r=e[t];return e[t]=this.value,{newDocument:n,removed:r}},move:function(e,t,n){var r=sr(n,this.path);r&&(r=Qn(r));var o=cr(n,{op:"remove",path:this.from}).removed;return cr(n,{op:"add",path:this.path,value:o}),{newDocument:n,removed:r}},copy:function(e,t,n){var r=sr(n,this.from);return cr(n,{op:"add",path:this.path,value:Qn(r)}),{newDocument:n}},test:function(e,t,n){return{newDocument:n,test:dr(e[t],this.value)}},_get:function(e,t,n){return this.value=e[t],{newDocument:n}}},ar={add:function(e,t,n){return Xn(t)?e.splice(t,0,this.value):e[t]=this.value,{newDocument:n,index:t}},remove:function(e,t,n){return{newDocument:n,removed:e.splice(t,1)[0]}},replace:function(e,t,n){var r=e[t];return e[t]=this.value,{newDocument:n,removed:r}},move:ir.move,copy:ir.copy,test:ir.test,_get:ir._get};function sr(e,t){if(""==t)return e;var n={op:"_get",path:t};return cr(e,n),n.value}function cr(e,t,n,r,o,i){if(void 0===n&&(n=!1),void 0===r&&(r=!0),void 0===o&&(o=!0),void 0===i&&(i=0),n&&("function"==typeof n?n(t,0,e,t.path):pr(t,0)),""===t.path){var a={newDocument:e};if("add"===t.op)return a.newDocument=t.value,a;if("replace"===t.op)return a.newDocument=t.value,a.removed=e,a;if("move"===t.op||"copy"===t.op)return a.newDocument=sr(e,t.from),"move"===t.op&&(a.removed=e),a;if("test"===t.op){if(a.test=dr(e,t.value),!1===a.test)throw new rr("Test operation failed","TEST_OPERATION_FAILED",i,t,e);return a.newDocument=e,a}if("remove"===t.op)return a.removed=e,a.newDocument=null,a;if("_get"===t.op)return t.value=e,a;if(n)throw new rr("Operation `op` property is not one of operations defined in RFC-6902","OPERATION_OP_INVALID",i,t,e);return a}r||(e=Qn(e));var s=(t.path||"").split("/"),c=e,l=1,u=s.length,p=void 0,f=void 0,d=void 0;for(d="function"==typeof n?n:pr;;){if(f=s[l],o&&"__proto__"==f)throw new TypeError("JSON-Patch: modifying `__proto__` prop is banned for security reasons, if this was on purpose, please set `banPrototypeModifications` flag false and pass it to this function. More info in fast-json-patch README");if(n&&void 0===p&&(void 0===c[f]?p=s.slice(0,l).join("/"):l==u-1&&(p=t.path),void 0!==p&&d(t,0,e,p)),l++,Array.isArray(c)){if("-"===f)f=c.length;else{if(n&&!Xn(f))throw new rr("Expected an unsigned base-10 integer value, making the new referenced value the array element with the zero-based index","OPERATION_PATH_ILLEGAL_ARRAY_INDEX",i,t,e);Xn(f)&&(f=~~f)}if(l>=u){if(n&&"add"===t.op&&f>c.length)throw new rr("The specified index MUST NOT be greater than the number of elements in the array","OPERATION_VALUE_OUT_OF_BOUNDS",i,t,e);if(!1===(a=ar[t.op].call(t,c,f,e)).test)throw new rr("Test operation failed","TEST_OPERATION_FAILED",i,t,e);return a}}else if(f&&-1!=f.indexOf("~")&&(f=er(f)),l>=u){if(!1===(a=ir[t.op].call(t,c,f,e)).test)throw new rr("Test operation failed","TEST_OPERATION_FAILED",i,t,e);return a}c=c[f]}}function lr(e,t,n,r,o){if(void 0===r&&(r=!0),void 0===o&&(o=!0),n&&!Array.isArray(t))throw new rr("Patch sequence must be an array","SEQUENCE_NOT_AN_ARRAY");r||(e=Qn(e));for(var i=new Array(t.length),a=0,s=t.length;a0)throw new rr('Operation `path` property must start with "/"',"OPERATION_PATH_INVALID",t,e,n);if(("move"===e.op||"copy"===e.op)&&"string"!=typeof e.from)throw new rr("Operation `from` property is not present (applicable in `move` and `copy` operations)","OPERATION_FROM_REQUIRED",t,e,n);if(("add"===e.op||"replace"===e.op||"test"===e.op)&&void 0===e.value)throw new rr("Operation `value` property is not present (applicable in `add`, `replace` and `test` operations)","OPERATION_VALUE_REQUIRED",t,e,n);if(("add"===e.op||"replace"===e.op||"test"===e.op)&&function e(t){if(void 0===t)return!0;if(t)if(Array.isArray(t)){for(var n=0,r=t.length;n0&&(e.patches=[],e.callback&&e.callback(r)),r}function xr(e,t,n,r,o){if(t!==e){"function"==typeof t.toJSON&&(t=t.toJSON());for(var i=Jn(t),a=Jn(e),s=!1,c=a.length-1;c>=0;c--){var l=e[p=a[c]];if(!Gn(t,p)||void 0===t[p]&&void 0!==l&&!1===Array.isArray(t))Array.isArray(e)===Array.isArray(t)?(o&&n.push({op:"test",path:r+"/"+Zn(p),value:Qn(l)}),n.push({op:"remove",path:r+"/"+Zn(p)}),s=!0):(o&&n.push({op:"test",path:r,value:e}),n.push({op:"replace",path:r,value:t}),!0);else{var u=t[p];"object"==typeof l&&null!=l&&"object"==typeof u&&null!=u?xr(l,u,n,r+"/"+Zn(p),o):l!==u&&(!0,o&&n.push({op:"test",path:r+"/"+Zn(p),value:Qn(l)}),n.push({op:"replace",path:r+"/"+Zn(p),value:Qn(u)}))}}if(s||i.length!=a.length)for(c=0;c0){var o=t(e,n[n.length-1],n);o&&(r=ot()(r).call(r,o))}if(mt()(e)){var i=ct()(e).call(e,(function(e,r){return Ir(e,t,ot()(n).call(n,r))}));i&&(r=ot()(r).call(r,i))}else if(Nr(e)){var a,s=ct()(a=at()(e)).call(a,(function(r){return Ir(e[r],t,ot()(n).call(n,r))}));s&&(r=ot()(r).call(r,s))}return r=Rr(r)}function Pr(e){return mt()(e)?e:[e]}function Rr(e){var t,n,r;return(n=ot()(t=[])).call.apply(n,ot()(r=[t]).call(r,cn()(ct()(e).call(e,(function(e){return mt()(e)?Rr(e):e})))))}function Lr(e){return Xe()(e).call(e,(function(e){return void 0!==e}))}function Nr(e){return e&&"object"===yt()(e)}function Mr(e){return e&&"function"==typeof e}function Br(e){if(Fr(e)){var t=e.op;return"add"===t||"remove"===t||"replace"===t}return!1}function Dr(e){return Br(e)||Fr(e)&&"mutation"===e.type}function qr(e){return Dr(e)&&("add"===e.op||"replace"===e.op||"merge"===e.op||"mergeDeep"===e.op)}function Fr(e){return e&&"object"===yt()(e)}function zr(e,t){try{return sr(e,t)}catch(e){return console.error(e),{}}}var Ur=n(279),$r=n.n(Ur),Hr=n(176),Wr=n.n(Hr);function Vr(e,t){function n(){Error.captureStackTrace?Error.captureStackTrace(this,this.constructor):this.stack=(new Error).stack;for(var e=arguments.length,n=new Array(e),r=0;r-1&&-1===Ct()(Xr).call(Xr,n)||Ct()(Zr).call(Zr,r)>-1||ht()(eo).call(eo,(function(e){return Ct()(r).call(r,e)>-1}))}function no(e,t){var n,r=e.split("#"),o=xt()(r,2),i=o[0],a=o[1],s=Ve.a.resolve(i||"",t||"");return a?ot()(n="".concat(s,"#")).call(n,a):s}var ro=new RegExp("^([a-z]+://|//)","i"),oo=Vr("JSONRefError",(function(e,t,n){this.originalError=n,Fn()(this,t||{})})),io={},ao=new $r.a,so=[function(e){return"paths"===e[0]&&"responses"===e[3]&&"examples"===e[5]},function(e){return"paths"===e[0]&&"responses"===e[3]&&"content"===e[5]&&"example"===e[7]},function(e){return"paths"===e[0]&&"responses"===e[3]&&"content"===e[5]&&"examples"===e[7]&&"value"===e[9]},function(e){return"paths"===e[0]&&"requestBody"===e[3]&&"content"===e[4]&&"example"===e[6]},function(e){return"paths"===e[0]&&"requestBody"===e[3]&&"content"===e[4]&&"examples"===e[6]&&"value"===e[8]},function(e){return"paths"===e[0]&&"parameters"===e[2]&&"example"===e[4]},function(e){return"paths"===e[0]&&"parameters"===e[3]&&"example"===e[5]},function(e){return"paths"===e[0]&&"parameters"===e[2]&&"examples"===e[4]&&"value"===e[6]},function(e){return"paths"===e[0]&&"parameters"===e[3]&&"examples"===e[5]&&"value"===e[7]},function(e){return"paths"===e[0]&&"parameters"===e[2]&&"content"===e[4]&&"example"===e[6]},function(e){return"paths"===e[0]&&"parameters"===e[2]&&"content"===e[4]&&"examples"===e[6]&&"value"===e[8]},function(e){return"paths"===e[0]&&"parameters"===e[3]&&"content"===e[4]&&"example"===e[7]},function(e){return"paths"===e[0]&&"parameters"===e[3]&&"content"===e[5]&&"examples"===e[7]&&"value"===e[9]}],co={key:"$ref",plugin:function(e,t,n,r){var o=r.getInstance(),i=an()(n).call(n,0,-1);if(!to(i)&&(a=i,!ht()(so).call(so,(function(e){return e(a)})))){var a,s=r.getContext(n).baseDoc;if("string"!=typeof e)return new oo("$ref: must be a string (JSON-Ref)",{$ref:e,baseDoc:s,fullPath:n});var c,l,u,p=ho(e),f=p[0],d=p[1]||"";try{c=s||f?po(f,s):null}catch(t){return fo(t,{pointer:d,$ref:e,basePath:c,fullPath:n})}if(function(e,t,n,r){var o,i,a=ao.get(r);a||(a={},ao.set(r,a));var s=function(e){if(0===e.length)return"";return"/".concat(ct()(e).call(e,xo).join("/"))}(n),c=ot()(o="".concat(t||"","#")).call(o,e),l=s.replace(/allOf\/\d+\/?/g,""),u=r.contextTree.get([]).baseDoc;if(t==u&&wo(l,e))return!0;var p="";if(ht()(n).call(n,(function(e){var t,n;return p=ot()(t="".concat(p,"/")).call(t,xo(e)),a[p]&&ht()(n=a[p]).call(n,(function(e){return wo(e,c)||wo(c,e)}))})))return!0;return void(a[l]=ot()(i=a[l]||[]).call(i,c))}(d,c,i,r)&&!o.useCircularStructures){var h=no(e,c);return e===h?null:Er.replace(n,h)}if(null==c?(u=yo(d),void 0===(l=r.get(u))&&(l=new oo("Could not resolve reference: ".concat(e),{pointer:d,$ref:e,baseDoc:s,fullPath:n}))):l=null!=(l=vo(c,d)).__value?l.__value:l.catch((function(t){throw fo(t,{pointer:d,$ref:e,baseDoc:s,fullPath:n})})),l instanceof Error)return[Er.remove(n),l];var v=no(e,c),m=Er.replace(i,l,{$$ref:v});if(c&&c!==s)return[m,Er.context(i,{baseDoc:c})];try{if(!function(e,t){var n,r=[e];return Ot()(n=t.path).call(n,(function(e,t){return r.push(e[t]),e[t]}),e),function e(t){var n;return Er.isObject(t)&&(Ct()(r).call(r,t)>=0||ht()(n=at()(t)).call(n,(function(n){return e(t[n])})))}(t.value)}(r.state,m)||o.useCircularStructures)return m}catch(e){return null}}}},lo=Fn()(co,{docCache:io,absoluteify:po,clearCache:function(e){var t;void 0!==e?delete io[e]:Je()(t=at()(io)).call(t,(function(e){delete io[e]}))},JSONRefError:oo,wrapError:fo,getDoc:mo,split:ho,extractFromDoc:vo,fetchJSON:function(e){return Object(Rt.fetch)(e,{headers:{Accept:"application/json, application/yaml"},loadSpec:!0}).then((function(e){return e.text()})).then((function(e){return Mt.a.safeLoad(e)}))},extract:go,jsonPointerToArray:yo,unescapeJsonPointerToken:bo}),uo=lo;function po(e,t){if(!ro.test(e)){var n;if(!t)throw new oo(ot()(n="Tried to resolve a relative URL, without having a basePath. path: '".concat(e,"' basePath: '")).call(n,t,"'"));return Ve.a.resolve(t,e)}return e}function fo(e,t){var n,r;e&&e.response&&e.response.body?n=ot()(r="".concat(e.response.body.code," ")).call(r,e.response.body.message):n=e.message;return new oo("Could not resolve reference: ".concat(n),t,e)}function ho(e){return(e+"").split("#")}function vo(e,t){var n=io[e];if(n&&!Er.isPromise(n))try{var r=go(t,n);return Fn()(Mn.a.resolve(r),{__value:r})}catch(e){return Mn.a.reject(e)}return mo(e).then((function(e){return go(t,e)}))}function mo(e){var t=io[e];return t?Er.isPromise(t)?t:Mn.a.resolve(t):(io[e]=lo.fetchJSON(e).then((function(t){return io[e]=t,t})),io[e])}function go(e,t){var n=yo(e);if(n.length<1)return t;var r=Er.getIn(t,n);if(void 0===r)throw new oo("Could not resolve pointer: ".concat(e," does not exist in document"),{pointer:e});return r}function yo(e){var t;if("string"!=typeof e)throw new TypeError("Expected a string, got a ".concat(yt()(e)));return"/"===e[0]&&(e=e.substr(1)),""===e?[]:ct()(t=e.split("/")).call(t,bo)}function bo(e){return"string"!=typeof e?e:Wr.a.unescape(e.replace(/~1/g,"/").replace(/~0/g,"~"))}function xo(e){return Wr.a.escape(e.replace(/~/g,"~0").replace(/\//g,"~1"))}function wo(e,t){if(!(n=t)||"/"===n||"#"===n)return!0;var n,r=e.charAt(t.length),o=an()(t).call(t,-1);return 0===Ct()(e).call(e,t)&&(!r||"/"===r||"#"===r)&&"#"!==o}var ko=n(282),So=n.n(ko),Oo={key:"allOf",plugin:function(e,t,n,r,o){if(!o.meta||!o.meta.$$ref){var i=an()(n).call(n,0,-1);if(!to(i)){if(!mt()(e)){var a=new TypeError("allOf must be an array");return a.fullPath=n,a}var s=!1,c=o.value;if(Je()(i).call(i,(function(e){c&&(c=c[e])})),c=De()({},c),!So()(c)){delete c.allOf;var l,u=[];if(u.push(r.replace(i,{})),Je()(e).call(e,(function(e,t){if(!r.isObject(e)){if(s)return null;s=!0;var o=new TypeError("Elements in allOf must be objects");return o.fullPath=n,u.push(o)}u.push(r.mergeDeep(i,e));var a=function(e,t){var n,r=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{},o=r.specmap,i=r.getBaseUrlForNodePath,a=void 0===i?function(e){var n;return o.getContext(ot()(n=[]).call(n,cn()(t),cn()(e))).baseDoc}:i,s=r.targetKeys,c=void 0===s?["$ref","$$ref"]:s,l=[];return Je()(n=Kr()(e)).call(n,(function(){if(jt()(c).call(c,this.key)&&Jr()(this.node)){var e=this.path,n=ot()(t).call(t,this.path),r=no(this.node,a(e));l.push(o.replace(n,r))}})),l}(e,an()(n).call(n,0,-1),{getBaseUrlForNodePath:function(e){var o;return r.getContext(ot()(o=[]).call(o,cn()(n),[t],cn()(e))).baseDoc},specmap:r});u.push.apply(u,cn()(a))})),u.push(r.mergeDeep(i,c)),!c.$$ref)u.push(r.remove(ot()(l=[]).call(l,i,"$$ref")));return u}}}}},Ao={key:"parameters",plugin:function(e,t,n,r){if(mt()(e)&&e.length){var o=Fn()([],e),i=an()(n).call(n,0,-1),a=De()({},Er.getIn(r.spec,i));return Je()(e).call(e,(function(e,t){try{o[t].default=r.parameterMacro(a,e)}catch(e){var i=new Error(e);return i.fullPath=n,i}})),Er.replace(n,o)}return Er.replace(n,e)}},Eo={key:"properties",plugin:function(e,t,n,r){var o=De()({},e);for(var i in e)try{o[i].default=r.modelPropertyMacro(o[i])}catch(e){var a=new Error(e);return a.fullPath=n,a}return Er.replace(n,o)}},_o=function(){function e(t){Wt()(this,e),this.root=jo(t||{})}return Yt()(e,[{key:"set",value:function(e,t){var n=this.getParent(e,!0);if(n){var r=e[e.length-1],o=n.children;o[r]?To(o[r],t,n):o[r]=jo(t,n)}else To(this.root,t,null)}},{key:"get",value:function(e){if((e=e||[]).length<1)return this.root.value;for(var t,n,r=this.root,o=0;o1?n-1:0),o=1;o1?r-1:0),i=1;i0}))}},{key:"nextPromisedPatch",value:function(){var e;if(this.promisedPatches.length>0)return Mn.a.race(ct()(e=this.promisedPatches).call(e,(function(e){return e.value})))}},{key:"getPluginHistory",value:function(e){var t=this.constructor.getPluginName(e);return this.pluginHistory[t]||[]}},{key:"getPluginRunCount",value:function(e){return this.getPluginHistory(e).length}},{key:"getPluginHistoryTip",value:function(e){var t=this.getPluginHistory(e);return t&&t[t.length-1]||{}}},{key:"getPluginMutationIndex",value:function(e){var t=this.getPluginHistoryTip(e).mutationIndex;return"number"!=typeof t?-1:t}},{key:"updatePluginHistory",value:function(e,t){var n=this.constructor.getPluginName(e);this.pluginHistory[n]=this.pluginHistory[n]||[],this.pluginHistory[n].push(t)}},{key:"updatePatches",value:function(e){var t,n=this;Je()(t=Er.normalizeArray(e)).call(t,(function(e){if(e instanceof Error)n.errors.push(e);else try{if(!Er.isObject(e))return void n.debug("updatePatches","Got a non-object patch",e);if(n.showDebug&&n.allPatches.push(e),Er.isPromise(e.value))return n.promisedPatches.push(e),void n.promisedPatchThen(e);if(Er.isContextPatch(e))return void n.setContext(e.path,e.value);if(Er.isMutation(e))return void n.updateMutations(e)}catch(e){console.error(e),n.errors.push(e)}}))}},{key:"updateMutations",value:function(e){"object"===yt()(e.value)&&!mt()(e.value)&&this.allowMetaPatches&&(e.value=De()({},e.value));var t=Er.applyPatch(this.state,e,{allowMetaPatches:this.allowMetaPatches});t&&(this.mutations.push(e),this.state=t)}},{key:"removePromisedPatch",value:function(e){var t,n,r=Ct()(t=this.promisedPatches).call(t,e);r<0?this.debug("Tried to remove a promisedPatch that isn't there!"):Ln()(n=this.promisedPatches).call(n,r,1)}},{key:"promisedPatchThen",value:function(e){var t=this;return e.value=e.value.then((function(n){var r=De()(De()({},e),{},{value:n});t.removePromisedPatch(e),t.updatePatches(r)})).catch((function(n){t.removePromisedPatch(e),t.updatePatches(n)})),e.value}},{key:"getMutations",value:function(e,t){var n;return e=e||0,"number"!=typeof t&&(t=this.mutations.length),an()(n=this.mutations).call(n,e,t)}},{key:"getCurrentMutations",value:function(){return this.getMutationsForPlugin(this.getCurrentPlugin())}},{key:"getMutationsForPlugin",value:function(e){var t=this.getPluginMutationIndex(e);return this.getMutations(t+1)}},{key:"getCurrentPlugin",value:function(){return this.currentPlugin}},{key:"getLib",value:function(){return this.libMethods}},{key:"_get",value:function(e){return Er.getIn(this.state,e)}},{key:"_getContext",value:function(e){return this.contextTree.get(e)}},{key:"setContext",value:function(e,t){return this.contextTree.set(e,t)}},{key:"_hasRun",value:function(e){return this.getPluginRunCount(this.getCurrentPlugin())>(e||0)}},{key:"dispatch",value:function(){var e,t=this,n=this,r=this.nextPlugin();if(!r){var o=this.nextPromisedPatch();if(o)return o.then((function(){return t.dispatch()})).catch((function(){return t.dispatch()}));var i={spec:this.state,errors:this.errors};return this.showDebug&&(i.patches=this.allPatches),Mn.a.resolve(i)}if(n.pluginCount=n.pluginCount||{},n.pluginCount[r]=(n.pluginCount[r]||0)+1,n.pluginCount[r]>100)return Mn.a.resolve({spec:n.state,errors:ot()(e=n.errors).call(e,new Error("We've reached a hard limit of ".concat(100," plugin runs")))});if(r!==this.currentPlugin&&this.promisedPatches.length){var a,s=ct()(a=this.promisedPatches).call(a,(function(e){return e.value}));return Mn.a.all(ct()(s).call(s,(function(e){return e.then(Hn.a,Hn.a)}))).then((function(){return t.dispatch()}))}return function(){n.currentPlugin=r;var e=n.getCurrentMutations(),t=n.mutations.length-1;try{if(r.isGenerator){var o,i=et()(r(e,n.getLib()));try{for(i.s();!(o=i.n()).done;){c(o.value)}}catch(e){i.e(e)}finally{i.f()}}else{c(r(e,n.getLib()))}}catch(e){console.error(e),c([Fn()(Dn()(e),{plugin:r})])}finally{n.updatePluginHistory(r,{mutationIndex:t})}return n.dispatch()}();function c(e){e&&(e=Er.fullyNormalizeArray(e),n.updatePatches(e,r))}}}]),e}();var Io={refs:uo,allOf:Oo,parameters:Ao,properties:Eo},Po=n(36),Ro=n.n(Po),Lo=function(e){return String.prototype.toLowerCase.call(e)},No=function(e){return e.replace(/[^\w]/gi,"_")};function Mo(e){var t=e.openapi;return!!t&&He()(t,"3")}function Bo(e,t){var n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:"",r=arguments.length>3&&void 0!==arguments[3]?arguments[3]:{},o=r.v2OperationIdCompatibilityMode;if(!e||"object"!==yt()(e))return null;var i=(e.operationId||"").replace(/\s/g,"");return i.length?No(e.operationId):Do(t,n,{v2OperationIdCompatibilityMode:o})}function Do(e,t){var n,r=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{},o=r.v2OperationIdCompatibilityMode;if(o){var i,a,s=ot()(i="".concat(t.toLowerCase(),"_")).call(i,e).replace(/[\s!@#$%^&*()_+=[{\]};:<>|./?,\\'""-]/g,"_");return(s=s||ot()(a="".concat(e.substring(1),"_")).call(a,t)).replace(/((_){2,})/g,"_").replace(/^(_)*/g,"").replace(/([_])*$/g,"")}return ot()(n="".concat(Lo(t))).call(n,No(e))}function qo(e,t){var n;return ot()(n="".concat(Lo(t),"-")).call(n,e)}function Fo(e,t){return e&&e.paths?function(e,t){return zo(e,t,!0)||null}(e,(function(e){var n,r=e.pathName,o=e.method,i=e.operation;if(!i||"object"!==yt()(i))return!1;var a=i.operationId,s=Bo(i,r,o),c=qo(r,o);return ht()(n=[s,c,a]).call(n,(function(e){return e&&e===t}))})):null}function zo(e,t,n){if(!e||"object"!==yt()(e)||!e.paths||"object"!==yt()(e.paths))return null;var r=e.paths;for(var o in r)for(var i in r[o])if("PARAMETERS"!==i.toUpperCase()){var a=r[o][i];if(a&&"object"===yt()(a)){var s={spec:e,pathName:o,method:i.toUpperCase(),operation:a},c=t(s);if(n&&c)return s}}}function Uo(e){var t=e.spec,n=t.paths,r={};if(!n||t.$$normalized)return e;for(var o in n){var i=n[o];if(Ro()(i)){var a=i.parameters,s=function(e){var n=i[e];if(!Ro()(n))return"continue";var s=Bo(n,o,e);if(s){r[s]?r[s].push(n):r[s]=[n];var c=r[s];if(c.length>1)Je()(c).call(c,(function(e,t){var n;e.__originalOperationId=e.__originalOperationId||e.operationId,e.operationId=ot()(n="".concat(s)).call(n,t+1)}));else if(void 0!==n.operationId){var l=c[0];l.__originalOperationId=l.__originalOperationId||n.operationId,l.operationId=s}}if("parameters"!==e){var u=[],p={};for(var f in t)"produces"!==f&&"consumes"!==f&&"security"!==f||(p[f]=t[f],u.push(p));if(a&&(p.parameters=a,u.push(p)),u.length){var d,h=et()(u);try{for(h.s();!(d=h.n()).done;){var v=d.value;for(var m in v)if(n[m]){if("parameters"===m){var g,y=et()(v[m]);try{var b=function(){var e,t=g.value;ht()(e=n[m]).call(e,(function(e){return e.name&&e.name===t.name||e.$ref&&e.$ref===t.$ref||e.$$ref&&e.$$ref===t.$$ref||e===t}))||n[m].push(t)};for(y.s();!(g=y.n()).done;)b()}catch(e){y.e(e)}finally{y.f()}}}else n[m]=v[m]}}catch(e){h.e(e)}finally{h.f()}}}};for(var c in i)s(c)}}return t.$$normalized=!0,e}function $o(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},n=t.requestInterceptor,r=t.responseInterceptor,o=e.withCredentials?"include":"same-origin";return function(t){return e({url:t,loadSpec:!0,requestInterceptor:n,responseInterceptor:r,headers:{Accept:"application/json, application/yaml"},credentials:o}).then((function(e){return e.body}))}}function Ho(e){var t=e.fetch,n=e.spec,r=e.url,o=e.mode,i=e.allowMetaPatches,a=void 0===i||i,s=e.pathDiscriminator,c=e.modelPropertyMacro,l=e.parameterMacro,u=e.requestInterceptor,p=e.responseInterceptor,f=e.skipNormalization,d=e.useCircularStructures,h=e.http,v=e.baseDoc;return v=v||r,h=t||h||vn,n?m(n):$o(h,{requestInterceptor:u,responseInterceptor:p})(v).then(m);function m(e){v&&(Io.refs.docCache[v]=e),Io.refs.fetchJSON=$o(h,{requestInterceptor:u,responseInterceptor:p});var t,n=[Io.refs];return"function"==typeof l&&n.push(Io.parameters),"function"==typeof c&&n.push(Io.properties),"strict"!==o&&n.push(Io.allOf),(t={spec:e,context:{baseDoc:v},plugins:n,allowMetaPatches:a,pathDiscriminator:s,parameterMacro:l,modelPropertyMacro:c,useCircularStructures:d},new Co(t).dispatch()).then(f?function(){var e=Pt()(Ke.a.mark((function e(t){return Ke.a.wrap((function(e){for(;;)switch(e.prev=e.next){case 0:return e.abrupt("return",t);case 1:case"end":return e.stop()}}),e)})));return function(t){return e.apply(this,arguments)}}():Uo)}}var Wo=n(52),Vo=n.n(Wo);function Yo(){return(Yo=Pt()(Ke.a.mark((function e(t,n){var r,o,i,a,s,c,l,u,p,f,d,h,v=arguments;return Ke.a.wrap((function(e){for(;;)switch(e.prev=e.next){case 0:return r=v.length>2&&void 0!==v[2]?v[2]:{},o=r.returnEntireTree,i=r.baseDoc,a=r.requestInterceptor,s=r.responseInterceptor,c=r.parameterMacro,l=r.modelPropertyMacro,u=r.useCircularStructures,p={pathDiscriminator:n,baseDoc:i,requestInterceptor:a,responseInterceptor:s,parameterMacro:c,modelPropertyMacro:l,useCircularStructures:u},f=Uo({spec:t}),d=f.spec,e.next=6,Ho(De()(De()({},p),{},{spec:d,allowMetaPatches:!0,skipNormalization:!0}));case 6:return h=e.sent,!o&&mt()(n)&&n.length&&(h.spec=Vo()(h.spec,n)||null),e.abrupt("return",h);case 9:case"end":return e.stop()}}),e)})))).apply(this,arguments)}var Ko=function(){return null},Go={mapTagOperations:function(e){var t=e.spec,n=e.cb,r=void 0===n?Ko:n,o=e.defaultTag,i=void 0===o?"default":o,a=e.v2OperationIdCompatibilityMode,s={},c={};return zo(t,(function(e){var n,o=e.pathName,l=e.method,u=e.operation,p=u.tags?(n=u.tags,mt()(n)?n:[n]):[i];Je()(p).call(p,(function(e){if("string"==typeof e){c[e]=c[e]||{};var n,i=c[e],p=Bo(u,o,l,{v2OperationIdCompatibilityMode:a}),f=r({spec:t,pathName:o,method:l,operation:u,operationId:p});if(s[p])s[p]+=1,i[ot()(n="".concat(p)).call(n,s[p])]=f;else if(void 0!==i[p]){var d,h,v=s[p]||1;s[p]=v+1,i[ot()(d="".concat(p)).call(d,s[p])]=f;var m=i[p];delete i[p],i[ot()(h="".concat(p)).call(h,v)]=m}else i[p]=f}}))})),c},makeExecute:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};return function(t){var n=t.pathName,r=t.method,o=t.operationId;return function(t){var i=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};return e.execute(De()(De()({spec:e.spec},Dt()(e,"requestInterceptor","responseInterceptor","userFetch")),{},{pathName:n,method:r,parameters:t,operationId:o},i))}}}};var Jo=n(283),Qo=n.n(Jo),Xo=n(284),Zo=n.n(Xo),ei=n(31),ti=n.n(ei),ni=n(285),ri=n.n(ni),oi={body:function(e){var t=e.req,n=e.value;t.body=n},header:function(e){var t=e.req,n=e.parameter,r=e.value;t.headers=t.headers||{},void 0!==r&&(t.headers[n.name]=r)},query:function(e){var t,n=e.req,r=e.value,o=e.parameter;n.query=n.query||{},!1===r&&"boolean"===o.type&&(r="false");0===r&&Ct()(t=["number","integer"]).call(t,o.type)>-1&&(r="0");if(r)n.query[o.name]={collectionFormat:o.collectionFormat,value:r};else if(o.allowEmptyValue&&void 0!==r){var i=o.name;n.query[i]=n.query[i]||{},n.query[i].allowEmptyValue=!0}},path:function(e){var t=e.req,n=e.value,r=e.parameter;t.url=t.url.split("{".concat(r.name,"}")).join(encodeURIComponent(n))},formData:function(e){var t=e.req,n=e.value,r=e.parameter;(n||r.allowEmptyValue)&&(t.form=t.form||{},t.form[r.name]={value:n,allowEmptyValue:r.allowEmptyValue,collectionFormat:r.collectionFormat})}};function ii(e,t){return jt()(t).call(t,"application/json")?"string"==typeof e?e:ft()(e):e.toString()}function ai(e){var t=e.req,n=e.value,r=e.parameter,o=r.name,i=r.style,a=r.explode,s=r.content;if(s){var c=at()(s)[0];t.url=t.url.split("{".concat(o,"}")).join(fn(ii(n,c),{escape:!0}))}else{var l=dn({key:r.name,value:n,style:i||"simple",explode:a||!1,escape:!0});t.url=t.url.split("{".concat(o,"}")).join(l)}}function si(e){var t=e.req,n=e.value,r=e.parameter;if(t.query=t.query||{},r.content){var o=at()(r.content)[0];t.query[r.name]=ii(n,o)}else if(!1===n&&(n="false"),0===n&&(n="0"),n)t.query[r.name]={value:n,serializationOption:Dt()(r,["style","explode","allowReserved"])};else if(r.allowEmptyValue&&void 0!==n){var i=r.name;t.query[i]=t.query[i]||{},t.query[i].allowEmptyValue=!0}}var ci=["accept","authorization","content-type"];function li(e){var t=e.req,n=e.parameter,r=e.value;if(t.headers=t.headers||{},!(Ct()(ci).call(ci,n.name.toLowerCase())>-1))if(n.content){var o=at()(n.content)[0];t.headers[n.name]=ii(r,o)}else void 0!==r&&(t.headers[n.name]=dn({key:n.name,value:r,style:n.style||"simple",explode:void 0!==n.explode&&n.explode,escape:!1}))}function ui(e){var t=e.req,n=e.parameter,r=e.value;t.headers=t.headers||{};var o=yt()(r);if(n.content){var i,a=at()(n.content)[0];t.headers.Cookie=ot()(i="".concat(n.name,"=")).call(i,ii(r,a))}else if("undefined"!==o){var s="object"===o&&!mt()(r)&&n.explode?"":"".concat(n.name,"=");t.headers.Cookie=s+dn({key:n.name,value:r,escape:!1,style:n.style||"form",explode:void 0!==n.explode&&n.explode})}}var pi=n(133),fi=n.n(pi);function di(e,t){var n=e.operation,r=e.requestBody,o=e.securities,i=e.spec,a=e.attachContentTypeForEmptyPayload,s=e.requestContentType;t=function(e){var t=e.request,n=e.securities,r=void 0===n?{}:n,o=e.operation,i=void 0===o?{}:o,a=e.spec,s=Ue()({},t),c=r.authorized,l=void 0===c?{}:c,u=i.security||a.security||[],p=l&&!!at()(l).length,f=Vo()(a,["components","securitySchemes"])||{};if(s.headers=s.headers||{},s.query=s.query||{},!at()(r).length||!p||!u||mt()(i.security)&&!i.security.length)return t;return Je()(u).call(u,(function(e){var t;Je()(t=at()(e)).call(t,(function(e){var t=l[e],n=f[e];if(t){var r=t.value||t,o=n.type;if(t)if("apiKey"===o)"query"===n.in&&(s.query[n.name]=r),"header"===n.in&&(s.headers[n.name]=r),"cookie"===n.in&&(s.cookies[n.name]=r);else if("http"===o){if(/^basic$/i.test(n.scheme)){var i,a=r.username||"",c=r.password||"",u=fi()(ot()(i="".concat(a,":")).call(i,c));s.headers.Authorization="Basic ".concat(u)}/^bearer$/i.test(n.scheme)&&(s.headers.Authorization="Bearer ".concat(r))}else if("oauth2"===o||"openIdConnect"===o){var p,d=t.token||{},h=d[n["x-tokenName"]||"access_token"],v=d.token_type;v&&"bearer"!==v.toLowerCase()||(v="Bearer"),s.headers.Authorization=ot()(p="".concat(v," ")).call(p,h)}}}))})),s}({request:t,securities:o,operation:n,spec:i});var c=n.requestBody||{},l=at()(c.content||{}),u=s&&Ct()(l).call(l,s)>-1;if(r||a){if(s&&u)t.headers["Content-Type"]=s;else if(!s){var p=l[0];p&&(t.headers["Content-Type"]=p,s=p)}}else s&&u&&(t.headers["Content-Type"]=s);if(r)if(s){if(Ct()(l).call(l,s)>-1)if("application/x-www-form-urlencoded"===s||"multipart/form-data"===s)if("object"===yt()(r)){var f,d=(c.content[s]||{}).encoding||{};t.form={},Je()(f=at()(r)).call(f,(function(e){t.form[e]={value:r[e],encoding:d[e]||{}}}))}else t.form=r;else t.body=r}else t.body=r;return t}function hi(e,t){var n,r,o=e.spec,i=e.operation,a=e.securities,s=e.requestContentType,c=e.attachContentTypeForEmptyPayload;if((t=function(e){var t=e.request,n=e.securities,r=void 0===n?{}:n,o=e.operation,i=void 0===o?{}:o,a=e.spec,s=Ue()({},t),c=r.authorized,l=void 0===c?{}:c,u=r.specSecurity,p=void 0===u?[]:u,f=i.security||p,d=l&&!!at()(l).length,h=a.securityDefinitions;if(s.headers=s.headers||{},s.query=s.query||{},!at()(r).length||!d||!f||mt()(i.security)&&!i.security.length)return t;return Je()(f).call(f,(function(e){var t;Je()(t=at()(e)).call(t,(function(e){var t=l[e];if(t){var n=t.token,r=t.value||t,o=h[e],i=o.type,a=o["x-tokenName"]||"access_token",c=n&&n[a],u=n&&n.token_type;if(t)if("apiKey"===i){var p="query"===o.in?"query":"headers";s[p]=s[p]||{},s[p][o.name]=r}else if("basic"===i)if(r.header)s.headers.authorization=r.header;else{var f,d=r.username||"",v=r.password||"";r.base64=fi()(ot()(f="".concat(d,":")).call(f,v)),s.headers.authorization="Basic ".concat(r.base64)}else if("oauth2"===i&&c){var m;u=u&&"bearer"!==u.toLowerCase()?u:"Bearer",s.headers.authorization=ot()(m="".concat(u," ")).call(m,c)}}}))})),s}({request:t,securities:a,operation:i,spec:o})).body||t.form||c)if(s)t.headers["Content-Type"]=s;else if(mt()(i.consumes)){var l=xt()(i.consumes,1);t.headers["Content-Type"]=l[0]}else if(mt()(o.consumes)){var u=xt()(o.consumes,1);t.headers["Content-Type"]=u[0]}else i.parameters&&Xe()(n=i.parameters).call(n,(function(e){return"file"===e.type})).length?t.headers["Content-Type"]="multipart/form-data":i.parameters&&Xe()(r=i.parameters).call(r,(function(e){return"formData"===e.in})).length&&(t.headers["Content-Type"]="application/x-www-form-urlencoded");else if(s){var p,f,d=i.parameters&&Xe()(p=i.parameters).call(p,(function(e){return"body"===e.in})).length>0,h=i.parameters&&Xe()(f=i.parameters).call(f,(function(e){return"formData"===e.in})).length>0;(d||h)&&(t.headers["Content-Type"]=s)}return t}var vi=function(e){return mt()(e)?e:[]},mi=Vr("OperationNotFoundError",(function(e,t,n){this.originalError=n,Fn()(this,t||{})})),gi={buildRequest:yi};function yi(e){var t,n,r=e.spec,o=e.operationId,a=e.responseContentType,s=e.scheme,c=e.requestInterceptor,l=e.responseInterceptor,u=e.contextUrl,p=e.userFetch,f=e.server,d=e.serverVariables,h=e.http,v=e.parameters,m=e.parameterBuilders,g=Mo(r);m||(m=g?i:oi);var y={url:"",credentials:h&&h.withCredentials?"include":"same-origin",headers:{},cookies:{}};c&&(y.requestInterceptor=c),l&&(y.responseInterceptor=l),p&&(y.userFetch=p);var b=Fo(r,o);if(!b)throw new mi("Operation ".concat(o," not found"));var x=b.operation,w=void 0===x?{}:x,k=b.method,S=b.pathName;if(y.url+=xi({spec:r,scheme:s,contextUrl:u,server:f,serverVariables:d,pathName:S,method:k}),!o)return delete y.cookies,y;y.url+=S,y.method="".concat(k).toUpperCase(),v=v||{};var O=r.paths[S]||{};a&&(y.headers.accept=a);var A=function(e){var t,n={};Je()(e).call(e,(function(e){n[e.in]||(n[e.in]={}),n[e.in][e.name]=e}));var r=[];return Je()(t=at()(n)).call(t,(function(e){var t;Je()(t=at()(n[e])).call(t,(function(t){r.push(n[e][t])}))})),r}(ot()(t=ot()(n=[]).call(n,vi(w.parameters))).call(t,vi(O.parameters)));Je()(A).call(A,(function(e){var t,n,o=m[e.in];if("body"===e.in&&e.schema&&e.schema.properties&&(t=v),void 0===(t=e&&e.name&&v[e.name]))t=e&&e.name&&v[ot()(n="".concat(e.in,".")).call(n,e.name)];else if(function(e,t){return Xe()(t).call(t,(function(t){return t.name===e}))}(e.name,A).length>1){var i;console.warn(ot()(i="Parameter '".concat(e.name,"' is ambiguous because the defined spec has more than one parameter with the name: '")).call(i,e.name,"' and the passed-in parameter values did not define an 'in' value."))}if(null!==t){if(void 0!==e.default&&void 0===t&&(t=e.default),void 0===t&&e.required&&!e.allowEmptyValue)throw new Error("Required parameter ".concat(e.name," is not provided"));if(g&&e.schema&&"object"===e.schema.type&&"string"==typeof t)try{t=JSON.parse(t)}catch(e){throw new Error("Could not parse object parameter value string as JSON")}o&&o({req:y,parameter:e,value:t,operation:w,spec:r})}}));var E=De()(De()({},e),{},{operation:w});if((y=g?di(E,y):hi(E,y)).cookies&&at()(y.cookies).length){var _,j=Ot()(_=at()(y.cookies)).call(_,(function(e,t){var n=y.cookies[t];return e+(e?"&":"")+ri.a.serialize(t,n)}),"");y.headers.Cookie=j}return y.cookies&&delete y.cookies,Cn(y),y}var bi=function(e){return e?e.replace(/\W/g,""):null};function xi(e){return Mo(e.spec)?function(e){var t=e.spec,n=e.pathName,r=e.method,o=e.server,i=e.contextUrl,a=e.serverVariables,s=void 0===a?{}:a,c=Vo()(t,["paths",n,(r||"").toLowerCase(),"servers"])||Vo()(t,["paths",n,"servers"])||Vo()(t,["servers"]),l="",u=null;if(o&&c&&c.length){var p=ct()(c).call(c,(function(e){return e.url}));Ct()(p).call(p,o)>-1&&(l=o,u=c[Ct()(p).call(p,o)])}if(!l&&c&&c.length){l=c[0].url;var f=xt()(c,1);u=f[0]}if(Ct()(l).call(l,"{")>-1){var d=function(e){var t,n=[],r=/{([^}]+)}/g;for(;t=r.exec(e);)n.push(t[1]);return n}(l);Je()(d).call(d,(function(e){if(u.variables&&u.variables[e]){var t=u.variables[e],n=s[e]||t.default,r=new RegExp("{".concat(e,"}"),"g");l=l.replace(r,n)}}))}return function(){var e,t,n=arguments.length>0&&void 0!==arguments[0]?arguments[0]:"",r=arguments.length>1&&void 0!==arguments[1]?arguments[1]:"",o=Ve.a.parse(n),i=Ve.a.parse(r),a=bi(o.protocol)||bi(i.protocol)||"",s=o.host||i.host,c=o.pathname||"";e=a&&s?ot()(t="".concat(a,"://")).call(t,s+c):c;return"/"===e[e.length-1]?an()(e).call(e,0,-1):e}(l,i)}(e):function(e){var t,n,r=e.spec,o=e.scheme,i=e.contextUrl,a=void 0===i?"":i,s=Ve.a.parse(a),c=mt()(r.schemes)?r.schemes[0]:null,l=o||c||bi(s.protocol)||"http",u=r.host||s.host||"",p=r.basePath||"";t=l&&u?ot()(n="".concat(l,"://")).call(n,u+p):p;return"/"===t[t.length-1]?an()(t).call(t,0,-1):t}(e)}function wi(e){var t=this,n=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};if("string"==typeof e?n.url=e:n=e,!(this instanceof wi))return new wi(n);Ue()(this,n);var r=this.resolve().then((function(){return t.disableInterfaces||Ue()(t,wi.makeApisTagOperation(t)),t}));return r.client=this,r}wi.http=vn,wi.makeHttp=Fe()(In).call(In,null,wi.http),wi.resolve=Ho,wi.resolveSubtree=function(e,t){return Yo.apply(this,arguments)},wi.execute=function(e){var t=e.http,n=e.fetch,r=e.spec,o=e.operationId,i=e.pathName,a=e.method,s=e.parameters,c=e.securities,l=Qo()(e,["http","fetch","spec","operationId","pathName","method","parameters","securities"]),u=t||n||vn;i&&a&&!o&&(o=qo(i,a));var p=gi.buildRequest(De()({spec:r,operationId:o,parameters:s,securities:c,http:u},l));return p.body&&(Zo()(p.body)||ti()(p.body))&&(p.body=ft()(p.body)),u(p)},wi.serializeRes=bn,wi.serializeHeaders=wn,wi.clearCache=function(){Io.refs.clearCache()},wi.makeApisTagOperation=function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},t=Go.makeExecute(e);return{apis:Go.mapTagOperations({v2OperationIdCompatibilityMode:e.v2OperationIdCompatibilityMode,spec:e.spec,cb:t})}},wi.buildRequest=yi,wi.helpers={opId:Bo},wi.getBaseUrl=xi,wi.prototype={http:vn,execute:function(e){return this.applyDefaults(),wi.execute(De()({spec:this.spec,http:this.http,securities:{authorized:this.authorizations},contextUrl:"string"==typeof this.url?this.url:void 0,requestInterceptor:this.requestInterceptor||null,responseInterceptor:this.responseInterceptor||null},e))},resolve:function(){var e=this,t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};return wi.resolve(De()({spec:this.spec,url:this.url,http:this.http||this.fetch,allowMetaPatches:this.allowMetaPatches,useCircularStructures:this.useCircularStructures,requestInterceptor:this.requestInterceptor||null,responseInterceptor:this.responseInterceptor||null},t)).then((function(t){return e.originalSpec=e.spec,e.spec=t.spec,e.errors=t.errors,e}))}},wi.prototype.applyDefaults=function(){var e=this.spec,t=this.url;if(t&&He()(t,"http")){var n=Ve.a.parse(t);e.host||(e.host=n.host),e.schemes||(e.schemes=[n.protocol.replace(":","")]),e.basePath||(e.basePath="/")}};wi.helpers;var ki=wi;async function Si(e,t=!1,n="",r="",o="",i="",a=""){let s,c;const l={patch:!0,warnOnly:!0,resolveInternal:!0,anchors:!0};try{let t;t="string"==typeof e?await ki(e):await ki({spec:e}),s=t.spec,t.spec.swagger&&(c=await Me.a.convertObj(t.spec,l),s=c.openapi)}catch(e){console.info("RapiDoc: %c There was an issue while parsing the spec %o ","color:orangered",e)}const u=function(e,t=!1,n){const r=["get","put","post","delete","patch","head","options"],o=e.tags&&Array.isArray(e.tags)?e.tags.map(e=>({show:!0,name:e.name,description:e.description,paths:[],expanded:!1!==e["x-tag-expanded"]})):[];for(const t in e.paths){const n=e.paths[t].parameters,i={summary:e.paths[t].summary,description:e.paths[t].description,servers:e.paths[t].servers?e.paths[t].servers:[],parameters:e.paths[t].parameters?e.paths[t].parameters:[]};r.forEach(r=>{if(e.paths[t][r]){const a=e.paths[t][r],s=a.tags?a.tags:[];if(0===s.length){let e=t.indexOf("/",1);-1===e?e=t.length-1:e-=1,s.push(t.substr(1,e))}s.forEach(s=>{let c,l;e.tags&&(l=e.tags.find(e=>e.name.toLowerCase()===s.toLowerCase())),c=o.find(e=>e.name===s),c||(c={show:!0,name:s,paths:[],description:l?l.description:"",expanded:!l||!1!==l["x-tag-expanded"]},o.push(c));let u=(a.summary||a.description||"".concat(r," ").concat(t)).trim().split("/\r?\n/")[0];u.length>100&&(u=u.split(".")[0]),a.description||(a.description=(a.summary||"-").trim());let p=[];p=n?a.parameters?n.filter(e=>{if(!a.parameters.some(t=>e.name===t.name&&e.in===t.in))return e}).concat(a.parameters):n.slice(0):a.parameters?a.parameters.slice(0):[],c.paths.push({show:!0,expanded:!1,expandedAtLeastOnce:!1,summary:u,method:r,description:a.description,path:t,operationId:a.operationId,servers:a.servers?i.servers.concat(a.servers):i.servers,parameters:p,requestBody:a.requestBody,responses:a.responses,callbacks:a.callbacks,deprecated:a.deprecated,security:a.security,commonSummary:i.summary,commonDescription:i.description,xCodeSamples:a["x-codeSamples"]||a["x-code-samples"]||""})})}})}const i=o.filter(e=>e.paths&&e.paths.length>0);"method"===n?i.forEach(e=>{e.paths&&e.paths.sort((e,t)=>r.indexOf(e.method).toString().localeCompare(r.indexOf(t.method)))}):"summary"===n?i.forEach(e=>{e.paths&&e.paths.sort((e,t)=>(e.summary||e.description||e.path).localeCompare(t.summary||t.description||t.path))}):i.forEach(e=>{e.paths&&e.paths.sort((e,t)=>e.path.localeCompare(t.path))});return t?i.sort((e,t)=>e.name.localeCompare(t.name)):i} +/** + * @license + * Copyright (c) 2017 The Polymer Project Authors. All rights reserved. + * This code may only be used under the BSD style license found at + * http://polymer.github.io/LICENSE.txt + * The complete set of authors may be found at + * http://polymer.github.io/AUTHORS.txt + * The complete set of contributors may be found at + * http://polymer.github.io/CONTRIBUTORS.txt + * Code distributed by Google as part of the polymer project is also + * subject to an additional IP rights grant found at + * http://polymer.github.io/PATENTS.txt + */(s,t,n),p=function(e){if(!e.components)return[];const t=[];for(const n in e.components){const r=[];for(const t in e.components[n]){const o={show:!0,id:"".concat(n.toLowerCase(),"-").concat(t.toLowerCase()).replace(Ce,"-"),name:t,component:e.components[n][t]};r.push(o)}let o=n,i=n;switch(n){case"schemas":i="Schemas",o="Schemas allows the definition of input and output data types. These types can be objects, but also primitives and arrays.";break;case"responses":i="Responses",o="Describes responses from an API Operation, including design-time, static links to operations based on the response.";break;case"parameters":i="Parameters",o="Describes operation parameters. A unique parameter is defined by a combination of a name and location.";break;case"examples":i="Examples",o="List of Examples for operations, can be requests, responses and objects examples.";break;case"requestBodies":i="Request Bodies",o="Describes common request bodies that are used across the API operations.";break;case"headers":i="Headers",o='Headers follows the structure of the Parameters but they are explicitly in "header"';break;case"securitySchemes":i="Security Schemes",o="Defines a security scheme that can be used by the operations. Supported schemes are HTTP authentication, an API key (either as a header, a cookie parameter or as a query parameter), OAuth2's common flows(implicit, password, client credentials and authorization code) as defined in RFC6749, and OpenID Connect Discovery.";break;case"links":i="Links",o="Links represent a possible design-time link for a response. The presence of a link does not guarantee the caller's ability to successfully invoke it, rather it provides a known relationship and traversal mechanism between responses and other operations.";break;case"callbacks":i="Callbacks",o="A map of possible out-of band callbacks related to the parent operation. Each value in the map is a Path Item Object that describes a set of requests that may be initiated by the API provider and the expected responses. The key value used to identify the path item object is an expression, evaluated at runtime, that identifies a URL to use for the callback operation.";break;default:i=n,o=n}const a={show:!0,name:i,description:o,subComponents:r};t.push(a)}return t||[]}(s),f=function(e){if(e&&e.info&&e.info.description){const t=se.a.lexer(e.info.description);return t.filter(e=>"heading"===e.type&&e.depth<=2)||[]}return[]}(s),d=[];s.components&&s.components.securitySchemes&&Object.entries(s.components.securitySchemes).forEach(e=>{const t={apiKeyId:e[0],...e[1]};t.value="",t.finalKeyValue="","apiKey"===e[1].type||"http"===e[1].type?(t.in=e[1].in||"header",t.name=e[1].name||"Authorization",t.user="",t.password=""):"oauth2"===e[1].type&&(t.in="header",t.name="Authorization",t.clientId="",t.clientSecret=""),d.push(t)}),r&&o&&i&&d.push({apiKeyId:"_rapidoc_api_key",description:"api-key provided in rapidoc element attributes",type:"apiKey",oAuthFlow:"",name:r,in:o,value:i,finalKeyValue:i}),d.forEach(e=>{"http"===e.type?e.typeDisplay="basic"===e.scheme?"HTTP Basic":"HTTP Bearer":"apiKey"===e.type?e.typeDisplay="API Key (".concat(e.name,")"):"oauth2"===e.type?e.typeDisplay="OAuth (".concat(e.apiKeyId,")"):e.typeDisplay=e.type});let h=[];s.servers&&Array.isArray(s.servers)?(s.servers.forEach(e=>{let t=e.url.trim();t.startsWith("http")||t.startsWith("//")||t.startsWith("{")||window.location.origin.startsWith("http")&&(e.url=window.location.origin+e.url,t=e.url),e.variables&&Object.entries(e.variables).forEach(e=>{const n=new RegExp("{".concat(e[0],"}"),"g");t=t.replace(n,e[1].default||""),e[1].value=e[1].default||""}),e.computedUrl=t}),a&&s.servers.push({url:a,computedUrl:a})):a?s.servers=[{url:a,computedUrl:a}]:window.location.origin.startsWith("http")?s.servers=[{url:window.location.origin,computedUrl:window.location.origin}]:s.servers=[{url:"http://localhost",computedUrl:"http://localhost"}],h=s.servers;return{info:s.info,infoDescriptionHeaders:f,tags:u,components:p,externalDocs:s.externalDocs,securitySchemes:d,servers:h,basePath:s.basePath}}const Oi=new WeakMap,Ai=(Ei=e=>t=>{if(!(t instanceof C))throw new Error("unsafeHTML can only be used in text bindings");const n=Oi.get(t);if(void 0!==n&&E(e)&&e===n.value&&t.value===n.fragment)return;const r=document.createElement("template");r.innerHTML=e;const o=document.importNode(r.content,!0);t.setValue(o),Oi.set(t,{value:e,fragment:o})},(...e)=>{const t=Ei(...e);return b.set(t,!0),t});var Ei;function _i(){const e=fa(["Requires"]);return _i=function(){return e},e}function ji(){const e=fa(["",".  "]);return ji=function(){return e},e}function Ti(){const e=fa(["
    "," Token in "," ","
    "]);return Ti=function(){return e},e}function Ci(){const e=fa(["Requires"]);return Ci=function(){return e},e}function Ii(){const e=fa(["",".  "]);return Ii=function(){return e},e}function Pi(){const e=fa(["
    "," "," in Authorization header
    "]);return Pi=function(){return e},e}function Ri(){const e=fa(["Requires"]);return Ri=function(){return e},e}function Li(){const e=fa(["",".  "]);return Li=function(){return e},e}function Ni(){const e=fa(["
    "," OAuth Token (",") in Authorization header
    "]);return Ni=function(){return e},e}function Mi(){const e=fa([" "," "]);return Mi=function(){return e},e}function Bi(){const e=fa(["
    Requires all of the following
    "]);return Bi=function(){return e},e}function Di(){const e=fa(['
    OR
    ']);return Di=function(){return e},e}function qi(){const e=fa([" ",'
    ','
    ','
    ',"
    "]);return qi=function(){return e},e}function Fi(){const e=fa(['
    ',"
    "]);return Fi=function(){return e},e}function zi(){const e=fa([' '," "]);return zi=function(){return e},e}function Ui(){const e=fa([' Send Authorization in header containing the word Basic followed by a space and a base64 encoded string of username:password.
    "]);return Ui=function(){return e},e}function $i(){const e=fa([' "]);return $i=function(){return e},e}function Hi(){const e=fa(["Send Authorization in header containing the word Bearer followed by a space and a Token String."]);return Hi=function(){return e},e}function Wi(){const e=fa(["Send "," in "," with the given value"]);return Wi=function(){return e},e}function Vi(){const e=fa([" ",'
    ',"
    "]);return Vi=function(){return e},e}function Yi(){const e=fa(['
    ',"
    "]);return Yi=function(){return e},e}function Ki(){const e=fa([' ',' ']);return Ki=function(){return e},e}function Gi(){const e=fa(['
    '," ","
    "," "," "," "," "]);return Gi=function(){return e},e}function Ji(){const e=fa([' ',"
    "]);return Ji=function(){return e},e}function Qi(){const e=fa(['
    No API key applied
    ']);return Qi=function(){return e},e}function Xi(){const e=fa(['
    ',' API key applied
    ']);return Xi=function(){return e},e}function Zi(){const e=fa(['
    AUTHENTICATION
    ',"
    ","
    "]);return Zi=function(){return e},e}function ea(){const e=fa(['
    ']);return ea=function(){return e},e}function ta(){const e=fa([' ']);return ta=function(){return e},e}function na(){const e=fa(['
    ']);return na=function(){return e},e}function ra(){const e=fa([' ']);return ra=function(){return e},e}function oa(){const e=fa([' ',""]);return oa=function(){return e},e}function ia(){const e=fa(['
    "]);return ia=function(){return e},e}function aa(){const e=fa([' Scopes
    ',"
    "]);return aa=function(){return e},e}function sa(){const e=fa([" ",'
    '," ","
    ",'
    ']);return sa=function(){return e},e}function ca(){const e=fa(['
    Refresh URL ',"
    "]);return ca=function(){return e},e}function la(){const e=fa(['
    Token URL ',"
    "]);return la=function(){return e},e}function ua(){const e=fa(['
    Auth URL ',"
    "]);return ua=function(){return e},e}function pa(){const e=fa(['
    ',"
    "," "," "," ","
    "]);return pa=function(){return e},e}function fa(e,t){return t||(t=e.slice(0)),Object.freeze(Object.defineProperties(e,{raw:{value:Object.freeze(t)}}))}function da(e,t){let n="";const r=this.resolvedSpec.securitySchemes.find(t=>t.apiKeyId===e);if(r){const e=t.target.closest("tr");if(r.type&&r.scheme&&"http"===r.type&&"basic"===r.scheme.toLowerCase()){const t=e.querySelector(".api-key-user").value.trim(),r=e.querySelector(".api-key-password").value.trim();t&&r&&(n="Basic ".concat(btoa("".concat(t,":").concat(r))))}else n=e.querySelector(".api-key-input").value.trim(),n&&r.scheme&&"bearer"===r.scheme.toLowerCase()&&(n="Bearer ".concat(n));r.finalKeyValue=n}this.requestUpdate()}function ha(){this.resolvedSpec.securitySchemes.forEach(e=>{e.user="",e.password="",e.value="",e.finalKeyValue=""}),this.requestUpdate()}function va(e,t="Bearer",n){this.resolvedSpec.securitySchemes.find(t=>t.apiKeyId===e).finalKeyValue="".concat("bearer"===t.toLowerCase()?"Bearer":"mac"===t.toLowerCase()?"MAC":t," ").concat(n),this.requestUpdate()}async function ma(e,t,n,r,o,i,a="header",s,c,l=null){const u=c?c.querySelector(".oauth-resp-display"):void 0,p=new URLSearchParams,f=new Headers;p.append("grant_type",o),"client_credentials"!==o&&p.append("redirect_uri",r),i&&p.append("code",i),"header"===a?f.set("Authorization","Basic ".concat(btoa("".concat(t,":").concat(n)))):(p.append("client_id",t),p.append("client_secret",n)),l&&p.append("scope",l);try{const t=await fetch(e,{method:"POST",headers:f,body:p}),n=await t.json();if(!t.ok)return u&&(u.innerHTML=''.concat(n.error_description||n.error_description||"Unable to get access token","")),!1;if(n.token_type&&n.access_token)return va.call(this,s,n.token_type,n.access_token),u&&(u.innerHTML='Access Token Received'),!0}catch(e){return u&&(u.innerHTML='Failed to get access token'),!1}}async function ga(e,t,n,r,o,i,a,s,c,l){sessionStorage.removeItem("winMessageEventActive"),t.close(),e.data.fake||(e.data||console.warn("RapiDoc: Received no data with authorization message"),e.data.error&&console.warn("RapiDoc: Error while receiving data"),e.data&&("code"===e.data.responseType?ma.call(this,n,r,o,i,a,e.data.code,s,c,l):"token"===e.data.responseType&&va.call(this,c,e.data.token_type,e.data.access_token)))}async function ya(e,t,n,r,o){const i=o.target.closest(".oauth-flow"),a=i.querySelector(".oauth-client-id")?i.querySelector(".oauth-client-id").value.trim():"",s=i.querySelector(".oauth-client-secret")?i.querySelector(".oauth-client-secret").value.trim():"",c=i.querySelector(".oauth-send-client-secret-in")?i.querySelector(".oauth-send-client-secret-in").value.trim():"header",l=[...i.querySelectorAll('input[type="checkbox"]:checked')],u="".concat(Math.random().toString(36),"random").slice(2,9),p=new URL("".concat(window.location.origin).concat(window.location.pathname.substring(0,window.location.pathname.lastIndexOf("/")),"/").concat(this.oauthReceiver));let f,d="",h="";if([...i.parentNode.querySelectorAll(".oauth-resp-display")].forEach(e=>{e.innerHTML=""}),"authorizationCode"===t||"implicit"===t){const o=new URL(n);"authorizationCode"===t?(d="authorization_code",h="code"):"implicit"===t&&(h="token");const v=new URLSearchParams(o.search),m=l.map(e=>e.value).join(" ");m&&v.set("scope",m),v.set("client_id",a),v.set("redirect_uri",p.toString()),v.set("response_type",h),v.set("state",u),v.set("show_dialog",!0),o.search=v.toString(),"true"===sessionStorage.getItem("winMessageEventActive")&&window.postMessage({fake:!0},this),setTimeout(()=>{f=window.open(o.toString()),f?(sessionStorage.setItem("winMessageEventActive","true"),window.addEventListener("message",t=>ga.call(this,t,f,r,a,s,p.toString(),d,c,e,i),{once:!0})):console.error("RapiDoc: Unable to open ".concat(o.toString()," in a new window"))},10)}else if("clientCredentials"===t){d="client_credentials";const t=l.map(e=>e.value).join(" ");ma.call(this,r,a,s,p.toString(),d,"",c,e,i,t)}}function ba(e,t,n,r,o){let i;return i="authorizationCode"===e?"Authorization Code Flow":"clientCredentials"===e?"Client Credentials Flow":"implicit"===e?"Implicit Flow":"password"===e?"Password Flow":e,z(pa(),i,o.authorizationUrl?z(ua(),o.authorizationUrl):"",o.tokenUrl?z(la(),o.tokenUrl):"",o.refreshUrl?z(ca(),o.refreshUrl):"","authorizationCode"===e||"clientCredentials"===e||"implicit"===e||"password"===e?z(sa(),o.scopes?z(aa(),Object.entries(o.scopes).map((t,n)=>z(ia(),e,n,t[0],e,n,t[0],t[0]!==t[1]?" - ".concat(t[1]||""):""))):"",t,"authorizationCode"===e||"clientCredentials"===e||"password"===e?z(oa(),n,"authorizationCode"===e||"clientCredentials"===e?z(ra()):""):z(na()),"authorizationCode"===e||"clientCredentials"===e||"implicit"===e?z(ta(),t=>{ya.call(this,r,e,o.authorizationUrl,o.tokenUrl,t)}):"","password"===e?z(ea()):""):"")}function xa(){const e=this.resolvedSpec.securitySchemes.filter(e=>e.finalKeyValue);return z(Zi(),"read focused".includes(this.renderStyle)?"section-gap--read-mode":"section-gap ",e.length>0?z(Xi(),e.length,()=>{ha.call(this)}):z(Qi()),this.resolvedSpec.securitySchemes&&this.resolvedSpec.securitySchemes.length>0?z(Ji(),this.resolvedSpec.securitySchemes.map(e=>z(Gi(),e.typeDisplay,e.finalKeyValue?z(Ki(),e.finalKeyValue?"Key Applied":"",()=>{e.finalKeyValue="",this.requestUpdate()}):"",e.description?z(Yi(),Ai(se()(e.description||""))):"","apikey"===e.type.toLowerCase()||"http"===e.type.toLowerCase()&&"bearer"===e.scheme.toLowerCase()?z(Vi(),"apikey"===e.type.toLowerCase()?z(Wi(),e.name,e.in):z(Hi()),"cookie"!==e.in?z($i(),e.value,t=>{da.call(this,e.apiKeyId,t)},e.finalKeyValue?"UPDATE":"SET"):""):"","http"===e.type.toLowerCase()&&"basic"===e.scheme.toLowerCase()?z(Ui(),e.user,e.password,t=>{da.call(this,e.apiKeyId,t)},e.finalKeyValue?"UPDATE":"SET"):"","oauth2"===e.type.toLowerCase()?z(zi(),Object.keys(e.flows).map(t=>ba.call(this,t,e.clientId,e.clientSecret,e.apiKeyId,e.flows[t]))):""))):"")}function wa(e){if(this.resolvedSpec.securitySchemes&&e){const t=[];return e.forEach(e=>{const n=[],r=[];let o="";Object.keys(e).forEach(t=>{const i=this.resolvedSpec.securitySchemes.find(e=>e.apiKeyId===t);o||(o=e[t].join(", ")),i&&(r.push(i.typeDisplay),n.push(i))}),t.push({pathScopes:o,securityTypes:r.length>1?"".concat(r[0]," + ").concat(r.length-1," more"):r[0],securityDefs:n})}),z(Fi(),t.map((e,t)=>z(qi(),0!==t?z(Di()):"",e.securityTypes,e.securityDefs.length>1?z(Bi()):"",e.securityDefs.map((t,n)=>z(Mi(),"oauth2"===t.type?z(Ni(),e.securityDefs.length>1?z(Li(),n+1):z(Ri()),t.apiKeyId):"http"===t.type?z(Pi(),e.securityDefs.length>1?z(Ii(),n+1):z(Ci()),"basic"===t.scheme?"Base 64 encoded username:password":"Bearer Token"):z(Ti(),e.securityDefs.length>1?z(ji(),n+1):z(_i()),t.name,t.in))))))}return""}function ka(){const e=Aa(['
    ',"
    "]);return ka=function(){return e},e}function Sa(){const e=Aa(['"]);return Sa=function(){return e},e}function Oa(){const e=Aa(['
    CODE SAMPLES
    ',"
    ","
    "]);return Oa=function(){return e},e}function Aa(e,t){return t||(t=e.slice(0)),Object.freeze(Object.defineProperties(e,{raw:{value:Object.freeze(t)}}))}function Ea(e){return z(Oa(),e=>{if(!e.target.classList.contains("tab-btn"))return;const t=e.target.dataset.tab,n=[...e.currentTarget.querySelectorAll(".tab-btn")],r=[...e.currentTarget.querySelectorAll(".tab-content")];n.forEach(e=>e.classList[e.dataset.tab===t?"add":"remove"]("active")),r.forEach(e=>{e.style.display=e.dataset.tab===t?"block":"none"})},e.map((e,t)=>z(Sa(),0===t?"active":"",e.lang,t,e.label||e.lang)),e.map((e,t)=>z(ka(),0===t?"block":"none",e.lang,t,t=>{Pe(e.source,t)},le.a.languages[e.lang.toLowerCase()]?Ai(le.a.highlight(e.source,le.a.languages[e.lang.toLowerCase()],e.lang.toLowerCase())):e.source)))}function _a(){const e=Ia(['
    ','
    ','
    ']);return _a=function(){return e},e}function ja(){const e=Ia(['
    ',"
    "]);return ja=function(){return e},e}function Ta(){const e=Ia(['
    '," ","
    "]);return Ta=function(){return e},e}function Ca(){const e=Ia(['
    CALLBACKS
    '," "]);return Ca=function(){return e},e}function Ia(e,t){return t||(t=e.slice(0)),Object.freeze(Object.defineProperties(e,{raw:{value:Object.freeze(t)}}))}function Pa(e){return z(Ca(),Object.entries(e).map(e=>z(Ta(),e[0],Object.entries(e[1]).map(e=>z(ja(),Object.entries(e[1]).map(t=>{var n,r,o;return z(_a(),t[0],t[0],e[0],t[0]||"",e[0]||"",(null===(n=t[1])||void 0===n?void 0:n.parameters)||"",(null===(r=t[1])||void 0===r?void 0:r.requestBody)||"",this.fillRequestFieldsWithExample,this.renderStyle,this.schemaStyle,this.defaultSchemaTab,this.schemaExpandLevel,this.schemaDescriptionExpanded,null===(o=t[1])||void 0===o?void 0:o.responses,this.renderStyle,this.schemaStyle,this.defaultSchemaTab,this.schemaExpandLevel,this.schemaDescriptionExpanded)}))))))}function Ra(){const e=function(e,t){t||(t=e.slice(0));return Object.freeze(Object.defineProperties(e,{raw:{value:Object.freeze(t)}}))}([".border-top{border-top:1px solid var(--border-color)}.border{border:1px solid var(--border-color);border-radius:var(--border-radius)}.light-border{border:1px solid var(--light-border-color);border-radius:var(--border-radius)}.pad-8-16{padding:8px 16px}.pad-top-8{padding-top:8px}.mar-top-8{margin-top:8px}"]);return Ra=function(){return e},e}var La=re(Ra());function Na(e){if(!e)return;const t={type:e.$ref?"{recursive}":e.enum?"enum":e.format?e.format:e.type?e.type:"{missing-type-info}",format:e.format?e.format:"",pattern:e.pattern&&!e.enum?e.pattern:"",readOrWriteOnly:e.readOnly?"🆁":e.writeOnly?"🆆":"",deprecated:e.deprecated?"❌":"",example:void 0===e.example?"":Array.isArray(e.example)?e.example:"".concat(e.example),default:void 0===e.default?"":"".concat(e.default),description:e.description?e.description:"",constrain:"",allowedValues:"",arrayType:"",html:""};if("{recursive}"===t.type?t.description=e.$ref.substring(e.$ref.lastIndexOf("/")+1):"{missing-type-info}"===t.type&&(t.description=t.description||""),e.enum){let n="";e.enum.map(e=>{n+="".concat(e,", ")}),t.allowedValues=n.slice(0,-2)}if("array"===e.type&&e.items){const n=e.items;if(t.arrayType="".concat(e.type," of ").concat(n.type),t.default=0===n.default?"0 ":n.default?n.default:"",n.enum){let e="";n.enum.map(t=>{e+="".concat(t,", ")}),t.allowedValues=e.slice(0,-2)}}else"integer"===e.type||"number"===e.type?(void 0!==e.minimum&&void 0!==e.maximum?t.constrain="".concat(e.exclusiveMinimum?">":">=").concat(e.minimum," and ").concat(e.exclusiveMaximum?"<":"<="," ").concat(e.maximum):void 0!==e.minimum&&void 0===e.maximum?t.constrain="".concat(e.exclusiveMinimum?">":">=").concat(e.minimum):void 0===e.minimum&&void 0!==e.maximum&&(t.constrain="".concat(e.exclusiveMaximum?"<":"<=").concat(e.maximum)),void 0!==e.multipleOf&&(t.constrain="(multiple of ".concat(e.multipleOf,")"))):"string"===e.type&&(void 0!==e.minLength&&void 0!==e.maxLength?t.constrain="(".concat(e.minLength," to ").concat(e.maxLength," chars)"):void 0!==e.minLength&&void 0===e.maxLength?t.constrain="min ".concat(e.minLength," chars"):void 0===e.minLength&&void 0!==e.maxLength&&(t.constrain="max ".concat(e.maxLength," chars")));return t.html="".concat(t.type,"~|~").concat(t.readOrWriteOnly,"~|~").concat(t.constrain,"~|~").concat(t.default,"~|~").concat(t.allowedValues,"~|~").concat(t.pattern,"~|~").concat(t.description,"~|~").concat(e.title||"","~|~").concat(t.deprecated?"deprecated":""),t}function Ma(e){if(""===e.example)return"";if(null===e.example)return null;if(0===e.example)return 0;if(e.example)return e.example;if(0===Object.keys(e).length)return null;if(e.$ref)return e.$ref;let t=e.format||e.type||(e.enum?"enum":"");switch(t||(e.enum?t="enum":e.anyOf?t="anyOf":e.oneOf&&(t="oneOf")),t.toLowerCase()){case"int32":case"int64":case"integer":return 0;case"float":case"double":case"number":case"decimal":return.5;case"string":return e.enum?e.enum[0]:e.pattern?e.pattern:"string";case"url":case"uri":return"http://example.com";case"byte":return btoa("string");case"binary":return"binary";case"boolean":return!1;case"date":return new Date(0).toISOString().split("T")[0];case"date-time":case"dateTime":return new Date(0).toISOString();case"password":return"password";case"enum":return e.enum[0];case"uuid":return"3fa85f64-5717-4562-b3fc-2c963f66afa6";case"email":return"user@example.com";case"hostname":return"example.com";case"ipv4":return"198.51.100.42";case"ipv6":return"2001:0db8:5b96:0000:0000:426f:8e17:642a";case"null":return null;default:return e.nullable?null:e.$ref?"data of type ".concat(e.$ref):"?"}}function Ba(e,t=1){const n=" ".repeat(t);let r="";if(1===t&&"object"!=typeof e)return"\n".concat(n).concat(e.toString());for(const o in e)r=Array.isArray(e[o])||"object"==typeof e[o]?"".concat(r,"\n").concat(n,"<").concat(o,"> ").concat(Ba(e[o],t+1),"\n").concat(n,""):"".concat(r,"\n").concat(n,"<").concat(o,"> ").concat(e[o].toString()," ");return r}function Da(e,t){"object"==typeof t&&null!==t&&(e.title&&(t["::TITLE"]=e.title),e.description&&(t["::DESCRIPTION"]=e.description))}function qa(e){if("object"==typeof e&&null!==e){delete e["::TITLE"],delete e["::DESCRIPTION"];for(const t in e)qa(e[t])}}function Fa(e,t,n){for(const r in t)t[r][n]=e}function za(e,t,n){let r=0;const o={};for(const i in e)for(const a in n)o["example-".concat(r)]={...e[i]},o["example-".concat(r)][t]=n[a],r++;return o}function Ua(e,t,n=0,r=""){if(e){if(e.allOf){const r={};if(1===e.allOf.length&&!e.allOf[0].properties&&!e.allOf[0].items){const t=e.allOf[0];return"".concat(Na(t).html)}e.allOf.map((e,t)=>{if("object"===e.type||e.properties||e.allOf||e.anyOf||e.oneOf){const o=(e.anyOf||e.oneOf)&&t>0?t:"",i=Ua(e,{},n+1,o);Object.assign(r,i)}else if("array"===e.type||e.items){const t=Ua(e,{},n+1);Object.assign(r,t)}else{if(!e.type)return"";{const t="prop".concat(Object.keys(r).length),n=Na(e);r[t]="".concat(n.html)}}}),t=r}else if(e.anyOf||e.oneOf){if(t["::description"]=e.description||"","object"===e.type||e.properties){t["::description"]=e.description||"",t["::type"]="object";for(const r in e.properties)e.required&&e.required.includes(r)?t["".concat(r,"*")]=Ua(e.properties[r],{},n+1):t[r]=Ua(e.properties[r],{},n+1)}const o={};e[e.anyOf?"anyOf":"oneOf"].forEach((e,t)=>{if("object"===e.type||e.properties||e.allOf||e.anyOf||e.oneOf){const n=Ua(e,{});o["::OPTION~".concat(t+1).concat(e.title?"~".concat(e.title):"")]=n,o["::type"]="xxx-of-option"}else if("array"===e.type||e.items){const n=Ua(e,{});o["::OPTION~".concat(t+1).concat(e.title?"~".concat(e.title):"")]=n,o["::type"]="xxx-of-array"}else{const n="::OPTION~".concat(t+1).concat(e.title?"~".concat(e.title):"");o[n]="".concat(Na(e).html)}}),t[e.anyOf?"::ANY~OF ".concat(r):"::ONE~OF ".concat(r)]=o,t["::type"]="xxx-of"}else if("object"===e.type||e.properties){t["::description"]=e.description||"",t["::type"]="object",t["::deprecated"]=e.deprecated||!1;for(const r in e.properties)e.required&&e.required.includes(r)?t["".concat(r,"*")]=Ua(e.properties[r],{},n+1):t[r]=Ua(e.properties[r],{},n+1);e.additionalProperties&&(t[""]=Ua(e.additionalProperties,{}))}else{if(!e.items){const t=Na(e);return t.html?"".concat(t.html):""}t["::description"]=e.description?e.description:e.items.description?"array<".concat(e.items.description,">"):"",t["::type"]="array",t["::props"]=Ua(e.items,{},n+1)}return t}}function $a(e,t,n,r,o=!0,i){const a=[];if(e)for(const t in e){let n="",o="json";if(r.toLowerCase().includes("json")){if("text"===i)n="string"==typeof e[t].value?e[t].value:JSON.stringify(e[t].value,void 0,2),o="text";else if(n=e[t].value,"string"==typeof e[t].value)try{const r=e[t].value.replace(/([\w]+)(:)/g,'"$1"$2').replace(/'/g,'"');n=JSON.parse(r),o="json"}catch(r){o="text",n=e[t].value}}else n=e[t].value,o="text";a.push({exampleId:t,exampleSummary:e[t].summary||t,exampleDescription:e[t].description||"",exampleType:r,exampleValue:n,exampleFormat:o})}else if(t){let e="",n="json";if(r.toLowerCase().includes("json")){if("text"===i)e="string"==typeof t?t:JSON.stringify(t,void 0,2),n="text";else if("object"==typeof t)e=t,n="json";else if("string"==typeof t)try{e=JSON.parse(t),n="json"}catch(r){n="text",e=t}}else e=t,n="text";a.push({exampleId:"Example",exampleSummary:"",exampleDescription:"",exampleType:r,exampleValue:e,exampleFormat:n})}if(0===a.length)if(n)if(n.example)a.push({exampleId:"Example",exampleSummary:"",exampleDescription:"",exampleType:r,exampleValue:n.example,exampleFormat:r.toLowerCase().includes("json")&&"object"==typeof n.example?"json":"text"});else if(r.toLowerCase().includes("json")||r.toLowerCase().includes("text")||r.toLowerCase().includes("*/*")||r.toLowerCase().includes("xml")){let e="",t="",s="",c="";r.toLowerCase().includes("xml")?(e=n.xml&&n.xml.name?"<".concat(n.xml.name,">"):"",t=n.xml&&n.xml.name?""):"",s="text"):s=i;const l=function e(t,n={}){let r={};if(t){if(t.allOf){const o={};if(1===t.allOf.length&&!t.allOf[0].properties&&!t.allOf[0].items){if(t.allOf[0].$ref)return"{ }";if(t.allOf[0].readOnly&&n.includeReadOnly){return Ma(t.allOf[0])}return}t.allOf.map(t=>{if("object"===t.type||t.properties||t.allOf||t.anyOf||t.oneOf){const r=e(t,n);Object.assign(o,r)}else if("array"===t.type||t.items){const r=[e(t,n)];Object.assign(o,r)}else{if(!t.type)return"";{const e="prop".concat(Object.keys(o).length);o[e]=Ma(t)}}}),r=o}else if(t.oneOf){if(t.oneOf.length>0){let o=0;for(const i in t.oneOf){const a=e(t.oneOf[i],n);for(const e in a)r["example-".concat(o)]=a[e],Da(t.oneOf[i],r["example-".concat(o)]),o++}}}else if(t.anyOf){let o;if("object"===t.type||t.properties){o={"example-0":{}};for(const r in t.properties){if(t.example){o=t;break}t.properties[r].deprecated&&!n.includeDeprecated||(t.properties[r].readOnly&&!n.includeReadOnly||t.properties[r].writeOnly&&!n.includeWriteOnly||(o=za(o,r,e(t.properties[r],n))))}}let i=0;for(const a in t.anyOf){const s=e(t.anyOf[a],n);for(const e in s){if(void 0!==o)for(const t in o)r["example-".concat(i)]={...o[t],...s[e]};else r["example-".concat(i)]=s[e];Da(t.anyOf[a],r["example-".concat(i)]),i++}}}else if("object"===t.type||t.properties)if(r["example-0"]={},Da(t,r["example-0"]),t.example)r["example-0"]=t.example;else for(const a in t.properties){var o,i;if(!t.properties[a].deprecated||n.includeDeprecated)if(!t.properties[a].readOnly||n.includeReadOnly)if(!t.properties[a].writeOnly||n.includeWriteOnly)if("array"===t.properties[a].type||t.properties[a].items)if(t.properties[a].example)Fa(t.properties[a].example,r,a);else if(null!==(o=t.properties[a])&&void 0!==o&&null!==(i=o.items)&&void 0!==i&&i.example)Fa([t.properties[a].items.example],r,a);else{const o=e(t.properties[a].items,n),i=[];for(const e in o)i[e]=[o[e]];r=za(r,a,i)}else r=za(r,a,e(t.properties[a],n))}else{if("array"!==t.type&&!t.items)return{"example-0":Ma(t)};var a;if(t.example)r["example-0"]=t.example;else if(null!==(a=t.items)&&void 0!==a&&a.example)r["example-0"]=[t.items.example];else{const o=e(t.items,n);let i=0;for(const e in o)r["example-".concat(i)]=[o[e]],Da(t.items,r["example-".concat(i)]),i++}}return r}}(n,{includeReadOnly:o,includeWriteOnly:!0,deprecated:!0});let u=0;for(const n in l){if(!l[n])continue;const o=l[n]["::TITLE"]||"Example ".concat(++u),p=l[n]["::DESCRIPTION"]||"";qa(l[n]),c=r.toLowerCase().includes("xml")?"".concat(e).concat(Ba(l[n]),"\n").concat(t):"text"===i?JSON.stringify(l[n],null,2):l[n],a.push({exampleId:n,exampleSummary:o,exampleDescription:p,exampleType:r,exampleFormat:s,exampleValue:c})}}else a.push({exampleId:"Example",exampleSummary:"",exampleDescription:"",exampleType:r,exampleValue:"",exampleFormat:"text"});else a.push({exampleId:"Example",exampleSummary:"",exampleDescription:"",exampleType:r,exampleValue:"",exampleFormat:"text"});return a}function Ha(){const e=Za(['',"",""]);return Ha=function(){return e},e}function Wa(){const e=Za(['"','"',""]);return Wa=function(){return e},e}function Va(){const e=Za(["",":"]);return Va=function(){return e},e}function Ya(){const e=Za(['
    '," ","
    "]);return Ya=function(){return e},e}function Ka(){const e=Za(['
    ','
    ','
    ',"","
    "]);return Ka=function(){return e},e}function Ga(){const e=Za(["",""]);return Ga=function(){return e},e}function Ja(){const e=Za(['
    null
    ']);return Ja=function(){return e},e}function Qa(){const e=Za(['
    ',"
    "]);return Qa=function(){return e},e}function Xa(){const e=Za([":host{display:flex}.json-tree{font-family:var(--font-mono);font-size:var(--font-size-small);display:inline-block;overflow:hidden;word-break:break-all;flex:1;line-height:calc(var(--font-size-small) + 6px)}.open-bracket{display:inline-block;padding:0 20px 0 0;cursor:pointer;border:1px solid transparent;border-radius:3px}.open-bracket:hover{color:var(--primary-color);background-color:var(--hover-color);border:1px solid var(--border-color)}.inside-bracket{padding-left:12px;border-left:1px dotted var(--border-color)}.open-bracket.collapsed+.inside-bracket,.open-bracket.collapsed+.inside-bracket+.close-bracket{display:none}.string{color:var(--green)}.number{color:var(--blue)}.null{color:var(--red)}.boolean{color:var(--purple)}.object{color:var(--fg)}.toolbar{display:flex;width:100%;padding:2px 0;color:var(--primary-color);font-family:var(--font-regular);margin-bottom:4px;align-items:center;font-size:calc(var(--font-size-small) - 1px)}"]);return Xa=function(){return e},e}function Za(e,t){return t||(t=e.slice(0)),Object.freeze(Object.defineProperties(e,{raw:{value:Object.freeze(t)}}))}function es(){const e=function(e,t){t||(t=e.slice(0));return Object.freeze(Object.defineProperties(e,{raw:{value:Object.freeze(t)}}))}([".tr{display:flex;flex:none;width:100%;border-bottom:1px dotted transparent}.td{display:block;flex:0 0 auto;box-sizing:border-box}.key{font-family:var(--font-mono);white-space:normal;word-break:break-all}.collapsed-descr .key{overflow:hidden}.key-descr{font-family:var(--font-regular);color:var(--light-fg);flex-shrink:1;text-overflow:ellipsis;overflow:hidden;display:none}.expanded-descr .key-descr{max-height:auto;overflow:hidden;display:none}.collapsed-descr .tr{max-height:20px}.tr.xxx-of{border-top:1px dotted var(--primary-color)}.xxx-of-key{font-size:calc(var(--font-size-small) - 2px);font-weight:700;background-color:var(--primary-color);color:var(--primary-color-invert);border-radius:2px;line-height:calc(var(--font-size-small) + 6px);padding:0 5px;margin-bottom:1px;display:inline-block}.xxx-of-descr{font-family:var(--font-regular);color:var(--primary-color);font-size:calc(var(--font-size-small) - 1px);margin-left:2px}.bina,.byte,.date,.emai,.host,.ipv4,.pass,.stri,.string,.uri,.url,.uuid{color:var(--green)}.deci .blue,.doub,.floa,.int3,.int6,.inte,.numb,.number{color:var(--blue)}.null{color:var(--red)}.bool,.boolean{color:var(--purple)}.enum{color:var(--orange)}.recu{color:var(--brown)}.toolbar{display:flex;width:100%;padding:2px 0;color:var(--primary-color)}.toolbar-item{cursor:pointer;padding:5px 0;margin:0 2px}.schema-root-type{cursor:auto;color:var(--fg2);font-weight:700;text-transform:uppercase}.schema-root-type.xxx-of{display:none}.toolbar-item:first-of-type{margin:0 2px 0 0}@media only screen and (min-width:500px){.key-descr{display:block}.expanded-descr .key-descr{display:block}}"]);return es=function(){return e},e}customElements.define("json-tree",class extends ie{static get properties(){return{data:{type:Object},renderStyle:{type:String,attribute:"render-style"}}}static get styles(){return[pe,La,de,re(Xa()),Te]}render(){return z(Qa(),e=>{Pe(JSON.stringify(this.data,null,2),e)},this.generateTree(this.data,!0))}generateTree(e,t=!1){if(null===e)return z(Ja());if("object"==typeof e&&e instanceof Date==!1){const n=Array.isArray(e)?"array":"pure_object";return 0===Object.keys(e).length?z(Ga(),Array.isArray(e)?"[ ],":"{ },"):z(Ka(),"array"===n?"array":"object",this.toggleExpand,"array"===n?"[":"{",Object.keys(e).map((t,r,o)=>z(Ya(),"pure_object"===n?z(Va(),t):"",this.generateTree(e[t],r===o.length-1))),"array"===n?"]":"}",t?"":",")}return"string"==typeof e||e instanceof Date?z(Wa(),typeof e,e,t?"":","):z(Ha(),typeof e,e,t?"":",")}toggleExpand(e){const t=e.target;t.classList.contains("expanded")?(t.classList.replace("expanded","collapsed"),e.target.innerHTML=e.target.classList.contains("array")?"[...]":"{...}"):(t.classList.replace("collapsed","expanded"),e.target.innerHTML=e.target.classList.contains("array")?"[":"{")}});var ts=re(es());function ns(){const e=Ms(['',""]);return ns=function(){return e},e}function rs(){const e=Ms(['
    Pattern: ',"
    "]);return rs=function(){return e},e}function os(){const e=Ms(['
    Allowed:   ',"
    "]);return os=function(){return e},e}function is(){const e=Ms(['
    Default: ',"
    "]);return is=function(){return e},e}function as(){const e=Ms(['
    ',"
    "]);return as=function(){return e},e}function ss(){const e=Ms(['',":"]);return ss=function(){return e},e}function cs(){const e=Ms(['',""]);return cs=function(){return e},e}function ls(){const e=Ms(['','*:']);return ls=function(){return e},e}function us(){const e=Ms(['
    ',' '," ",'
    '," "," "," "," "," ","
    "]);return us=function(){return e},e}function ps(){const e=Ms(['
    ',"
    "]);return ps=function(){return e},e}function fs(){const e=Ms(["",""]);return fs=function(){return e},e}function ds(){const e=Ms(["",""]);return ds=function(){return e},e}function hs(){const e=Ms([" "," "]);return hs=function(){return e},e}function vs(){const e=Ms([" "," "]);return vs=function(){return e},e}function ms(){const e=Ms(["",""]);return ms=function(){return e},e}function gs(){const e=Ms(['ARRAY']);return gs=function(){return e},e}function ys(){const e=Ms(['',""]);return ys=function(){return e},e}function bs(){const e=Ms(['','*']);return bs=function(){return e},e}function xs(){const e=Ms(['','',""]);return xs=function(){return e},e}function ws(){const e=Ms(['
    '," "," "," ",'
    ','
    ',"
    "," "]);return ws=function(){return e},e}function ks(){const e=Ms(['[...]']);return ks=function(){return e},e}function Ss(){const e=Ms(['[']);return Ss=function(){return e},e}function Os(){const e=Ms(['[[...]]']);return Os=function(){return e},e}function As(){const e=Ms(['[[']);return As=function(){return e},e}function Es(){const e=Ms(['{...}']);return Es=function(){return e},e}function _s(){const e=Ms(['{']);return _s=function(){return e},e}function js(){const e=Ms(['[{...}]']);return js=function(){return e},e}function Ts(){const e=Ms(['[{']);return Ts=function(){return e},e}function Cs(){const e=Ms(['',":{ }"]);return Cs=function(){return e},e}function Is(){const e=Ms(['
    null
    ']);return Is=function(){return e},e}function Ps(){const e=Ms([' Schema not found ']);return Ps=function(){return e},e}function Rs(){const e=Ms([" ",""]);return Rs=function(){return e},e}function Ls(){const e=Ms(['
    ','
    ','
    '," ","
    "]);return Ls=function(){return e},e}function Ns(){const e=Ms([".tree{font-size:var(--font-size-small);text-align:left;line-height:calc(var(--font-size-small) + 6px)}.tree .tr:hover{background-color:var(--hover-color)}.collapsed-descr .tr{max-height:calc(var(--font-size-small) + 8px)}.collapsed-descr .m-markdown-small p{line-height:calc(var(--font-size-small) + 6px)}.tree .key{max-width:300px}.key.deprecated .key-label{text-decoration:line-through}.open-bracket{display:inline-block;padding:0 20px 0 0;cursor:pointer;border:1px solid transparent;border-radius:3px}.open-bracket:hover{color:var(--primary-color);background-color:var(--hover-color);border:1px solid var(--border-color)}.close-bracket{display:inline-block;font-family:var(--font-mono)}.tr.collapsed+.inside-bracket,.tr.collapsed+.inside-bracket+.close-bracket{display:none}.inside-bracket.array,.inside-bracket.object{border-left:1px dotted var(--border-color)}.inside-bracket.xxx-of{padding:5px 0;border-style:dotted;border-width:0 0 1px 0;border-color:var(--primary-color)}"]);return Ns=function(){return e},e}function Ms(e,t){return t||(t=e.slice(0)),Object.freeze(Object.defineProperties(e,{raw:{value:Object.freeze(t)}}))}function Bs(){const e=zs([".tags{display:flex;flex-wrap:wrap;outline:0;padding:0;border-radius:var(--border-radius);border:1px solid var(--border-color);cursor:text;overflow:hidden;background:var(--input-bg)}.editor,.tag{padding:3px;margin:2px}.tag{border:1px solid var(--border-color);background-color:var(--bg3);color:var(--fg3);border-radius:var(--border-radius);word-break:break-all;font-size:var(--font-size-small)}.tag:hover~#cursor{display:block}.editor{flex:1;border:1px solid transparent;color:var(--fg);min-width:60px;outline:0;line-height:inherit;font-family:inherit;background:0 0;font-size:calc(var(--font-size-small) + 1px)}.editor::placeholder{color:var(--placeholder-color);opacity:1}"]);return Bs=function(){return e},e}function Ds(){const e=zs([' '," "]);return Ds=function(){return e},e}function qs(){const e=zs(["",""]);return qs=function(){return e},e}function Fs(){const e=zs(['
    ','
    ']);return Fs=function(){return e},e}function zs(e,t){return t||(t=e.slice(0)),Object.freeze(Object.defineProperties(e,{raw:{value:Object.freeze(t)}}))}customElements.define("schema-tree",class extends ie{static get properties(){return{data:{type:Object},schemaExpandLevel:{type:Number,attribute:"schema-expand-level"},schemaDescriptionExpanded:{type:String,attribute:"schema-description-expanded"}}}connectedCallback(){super.connectedCallback(),(!this.schemaExpandLevel||this.schemaExpandLevel<1)&&(this.schemaExpandLevel=99999),this.schemaDescriptionExpanded&&"true false".includes(this.schemaDescriptionExpanded)||(this.schemaDescriptionExpanded="false")}static get styles(){return[pe,ts,La,re(Ns()),Te]}render(){var e,t;return z(Ls(),"true"===this.schemaDescriptionExpanded?"expanded-descr":"collapsed-descr",(null===(e=this.data)||void 0===e?void 0:e["::type"])||"",(null===(t=this.data)||void 0===t?void 0:t["::type"])||"",()=>{this.schemaDescriptionExpanded="true"===this.schemaDescriptionExpanded?"false":"true"},"true"===this.schemaDescriptionExpanded?"Single line description":"Multiline description",this.data?Ai(se()(this.data["::description"]||"")):"",this.data?z(Rs(),this.generateTree("array"===this.data["::type"]?this.data["::props"]:this.data,this.data["::type"],"","")):z(Ps()))}generateTree(e,t="object",n="",r="",o=0){if(!e)return z(Is());if(0===Object.keys(e).length)return z(Cs(),n);let i="",a="";if(n.startsWith("::ONE~OF")||n.startsWith("::ANY~OF"))i=n.replace("::","").replace("~"," ");else if(n.startsWith("::OPTION")){const e=n.split("~");i=e[1],a=e[2]}else i=n;const s=300-12*o;let c="",l="";if("object"===e["::type"]?"array"===t?(c=o0&&!(n.startsWith("::props")||n.startsWith("::ONE~")||n.startsWith("::ANY~")||n.startsWith("::OPTION~")||n.startsWith("::ARRAY~OF"))?":":"","xxx-of"===e["::type"]&&"array"===t?z(gs()):"",c,Ai(se()(r||"")),e["::type"]||"no-type-info","xxx-of-option"===e["::type"]||"xxx-of-array"===e["::type"]?0:12,Array.isArray(e)&&e[0]?z(ms(),this.generateTree(e[0],"xxx-of-option","::ARRAY~OF","",o)):z(vs(),Object.keys(e).map(t=>z(hs(),["::description","::type","::props","::deprecated"].includes(t)?"array"===e[t]["::type"]||"object"===e[t]["::type"]?z(ds(),this.generateTree("array"===e[t]["::type"]?e[t]["::props"]:e[t],e[t]["::type"],t,e[t]["::description"],o+1)):"":z(fs(),this.generateTree("array"===e[t]["::type"]?e[t]["::props"]:e[t],e[t]["::type"],t,e[t]["::description"],o+1))))),e["::type"]&&e["::type"].includes("xxx-of")?"":z(ps(),l));const u=e.split("~|~"),p=u[0].replace("{","").substring(0,4).toLowerCase();return z(us(),u[8],s,i.endsWith("*")?z(ls(),i.substring(0,i.length-1)):n.startsWith("::OPTION")?z(cs(),i):z(ss(),i),p,"array"===t?"[".concat(u[0],"]"):"".concat(u[0]),u[1],"array"===t?r:"",u[2]?z(as(),u[2]):"",u[3]?z(is(),u[3]):"",u[4]?z(os(),u[4]):"",u[5]?z(rs(),u[5]):"",u[6]?z(ns(),Ai(se()(u[6]))):"")}toggleObjectExpand(e){const t=e.target.closest(".tr");t.classList.contains("expanded")?(t.classList.replace("expanded","collapsed"),e.target.innerHTML=e.target.classList.contains("array-of-object")?"[{...}]":e.target.classList.contains("array-of-array")?"[[...]]":e.target.classList.contains("array")?"[...]":"{...}"):(t.classList.replace("collapsed","expanded"),e.target.innerHTML=e.target.classList.contains("array-of-object")?"[{":e.target.classList.contains("array-of-array")?"[[":e.target.classList.contains("object")?"{":"[")}});function Us(){const e=ul([' ']);return Us=function(){return e},e}function $s(){const e=ul(['
    No API key applied
    ']);return $s=function(){return e},e}function Hs(){const e=ul(['
    ',"
    "]);return Hs=function(){return e},e}function Ws(){const e=ul(['
    ','
    Authentication
    ',"
    ",'
    '," "]);return Ws=function(){return e},e}function Vs(){const e=ul(['
    API Server
    ',"
    "]);return Vs=function(){return e},e}function Ys(){const e=ul(['
    '," ","
    "]);return Ys=function(){return e},e}function Ks(){const e=ul(['"]);return Ks=function(){return e},e}function Gs(){const e=ul([' "]);return Gs=function(){return e},e}function Js(){const e=ul(["",""]);return Js=function(){return e},e}function Qs(){const e=ul(['
    ',"\n              
    "]);return Qs=function(){return e},e}function Xs(){const e=ul(['']);return Xs=function(){return e},e}function Zs(){const e=ul(['
    ',"
    "]);return Zs=function(){return e},e}function ec(){const e=ul(['
    Response Status: ','
    ','
    ','
    ',"
    "]);return ec=function(){return e},e}function tc(){const e=ul(['',""]);return tc=function(){return e},e}function nc(){const e=ul([' "," "]);return nc=function(){return e},e}function rc(){const e=ul([' ',"
    "]);return rc=function(){return e},e}function oc(){const e=ul([' Example: ','
    '," "," "]);return oc=function(){return e},e}function ic(){const e=ul([' '," "," "]);return ic=function(){return e},e}function ac(){const e=ul([' '," "]);return ac=function(){return e},e}function sc(){const e=ul([' Allowed: ']);return sc=function(){return e},e}function cc(){const e=ul([" "," ",""]);return cc=function(){return e},e}function lc(){const e=ul(["","
    "]);return lc=function(){return e},e}function uc(){const e=ul(['Pattern: ',"
    "]);return uc=function(){return e},e}function pc(){const e=ul(['Default: ',"
    "]);return pc=function(){return e},e}function fc(){const e=ul(['
    '," "," "," ","
    "]);return fc=function(){return e},e}function dc(){const e=ul([" "," "]);return dc=function(){return e},e}function hc(){const e=ul(['']);return hc=function(){return e},e}function vc(){const e=ul([" "," "]);return vc=function(){return e},e}function mc(){const e=ul(['
    "]);return mc=function(){return e},e}function gc(){const e=ul(['
    ']);return gc=function(){return e},e}function yc(){const e=ul(['
    '," ","
    "]);return yc=function(){return e},e}function bc(){const e=ul([" ",""]);return bc=function(){return e},e}function xc(){const e=ul([' ']);return xc=function(){return e},e}function wc(){const e=ul(['
    ']);return wc=function(){return e},e}function kc(){const e=ul(["",""]);return kc=function(){return e},e}function Sc(){const e=ul(['*',""]);return Sc=function(){return e},e}function Oc(){const e=ul(['
    ','
    ','
    '," "," ",""]);return Oc=function(){return e},e}function Ac(){const e=ul([" "," ",""]);return Ac=function(){return e},e}function Ec(){const e=ul(['
    ',"
    "]);return Ec=function(){return e},e}function _c(){const e=ul(['
    ',"
    "]);return _c=function(){return e},e}function jc(){const e=ul(['
    '," ","
    "]);return jc=function(){return e},e}function Tc(){const e=ul(['
    ',"
    "]);return Tc=function(){return e},e}function Cc(){const e=ul(['*']);return Cc=function(){return e},e}function Ic(){const e=ul(['
    REQUEST BODY ',' ',' ',"
    "," ","
    "]);return Ic=function(){return e},e}function Pc(){const e=ul([" ",' ']);return Pc=function(){return e},e}function Rc(){const e=ul([" ",' ']);return Rc=function(){return e},e}function Lc(){const e=ul(['
    ']);return Lc=function(){return e},e}function Nc(){const e=ul(['
    ',"
    "]);return Nc=function(){return e},e}function Mc(){const e=ul(['
    ',"
    "]);return Mc=function(){return e},e}function Bc(){const e=ul(['
    '," ",'
    "]);return Bc=function(){return e},e}function Dc(){const e=ul(['"]);return Dc=function(){return e},e}function qc(){const e=ul([' "]);return qc=function(){return e},e}function Fc(){const e=ul([" ",'
    '," ","
    "]);return Fc=function(){return e},e}function zc(){const e=ul([' "]);return zc=function(){return e},e}function Uc(){const e=ul([' "]);return Uc=function(){return e},e}function $c(){const e=ul(['
    ','
    ',"
    "]);return $c=function(){return e},e}function Hc(){const e=ul([" ◇"]);return Hc=function(){return e},e}function Wc(){const e=ul([" "," ",' '," "," "]);return Wc=function(){return e},e}function Vc(){const e=ul([' Example: '," "]);return Vc=function(){return e},e}function Yc(){const e=ul([' ']);return Yc=function(){return e},e}function Kc(){const e=ul([' '," "]);return Kc=function(){return e},e}function Gc(){const e=ul([' Allowed: ']);return Gc=function(){return e},e}function Jc(){const e=ul([" "," ",""]);return Jc=function(){return e},e}function Qc(){const e=ul(["","
    "]);return Qc=function(){return e},e}function Xc(){const e=ul(['Pattern: ',"
    "]);return Xc=function(){return e},e}function Zc(){const e=ul(['Default: ',"
    "]);return Zc=function(){return e},e}function el(){const e=ul(['
    '," "," "," ","
    "]);return el=function(){return e},e}function tl(){const e=ul([' ']);return tl=function(){return e},e}function nl(){const e=ul([' "]);return nl=function(){return e},e}function rl(){const e=ul([' ']);return rl=function(){return e},e}function ol(){const e=ul([' '," "]);return ol=function(){return e},e}function il(){const e=ul(['*']);return il=function(){return e},e}function al(){const e=ul(['
    ',"",'
    ',"
    ",' '," ",' '," "," "]);return al=function(){return e},e}function sl(){const e=ul(["",""]);return sl=function(){return e},e}function cl(){const e=ul(['
    ',"
    "," "," "," "," "," ","
    "]);return cl=function(){return e},e}function ll(){const e=ul([".read-mode{margin-top:24px}.param-name,.param-type{margin:1px 0;text-align:right;line-height:var(--font-size-small)}.param-name{color:var(--fg);font-family:var(--font-mono)}.param-type{color:var(--light-fg);font-family:var(--font-regular)}.param-constraint{min-width:100px}.param-constraint:empty{display:none}.top-gap{margin-top:24px}.textarea{min-height:220px;padding:5px;resize:vertical}.example:first-child{margin-top:-9px}.response-message{font-weight:700;text-overflow:ellipsis}.response-message.error{color:var(--red)}.response-message.success{color:var(--blue)}.file-input-container{align-items:flex-end}.file-input-container .input-set:first-child .file-input-remove-btn{visibility:hidden}.file-input-remove-btn{font-size:16px;color:var(--red);outline:0;border:none;background:0 0;cursor:pointer}.v-tab-btn{font-size:var(--smal-font-size);height:24px;border:none;background:0 0;opacity:.3;cursor:pointer;padding:4px 8px}.v-tab-btn.active{font-weight:700;background:var(--bg);opacity:1}@media only screen and (min-width:768px){.textarea{padding:8px}}"]);return ll=function(){return e},e}function ul(e,t){return t||(t=e.slice(0)),Object.freeze(Object.defineProperties(e,{raw:{value:Object.freeze(t)}}))}customElements.define("tag-input",class extends ie{render(){return z(Fs(),Array.isArray(this.value)&&this.value.length>0?z(qs(),this.value.map(e=>z(Ds(),e))):"",this.afterPaste,this.afterKeyDown,this.placeholder||"")}static get properties(){return{placeholder:{type:String},value:{type:Array,attribute:"value"}}}attributeChangedCallback(e,t,n){if("value"===e&&n&&t!==n){const e=n.split(",").filter(e=>""!==e.trim());this.value=e||""}}afterPaste(e){const t=(e.clipboardData||window.clipboardData).getData("Text");console.log(t)}afterKeyDown(e){13===e.keyCode?(e.stopPropagation(),e.preventDefault(),e.target.value&&(Array.isArray(this.value)?this.value=[...this.value,e.target.value]:this.value=[e.target.value],e.target.value="")):8===e.keyCode&&0===e.target.value.length&&Array.isArray(this.value)&&this.value.length>0&&(this.value.splice(-1),this.value=[...this.value])}static get styles(){return[re(Bs())]}});function pl(){const e=Nl(['',""]);return pl=function(){return e},e}function fl(){const e=Nl(['
    Pattern:   ',"
    "]);return fl=function(){return e},e}function dl(){const e=Nl(['
    Allowed:   ',"
    "]);return dl=function(){return e},e}function hl(){const e=Nl(['
    Default: ',"
    "]);return hl=function(){return e},e}function vl(){const e=Nl(['
    ',"
    "]);return vl=function(){return e},e}function ml(){const e=Nl(['',""]);return ml=function(){return e},e}function gl(){const e=Nl([' ',""]);return gl=function(){return e},e}function yl(){const e=Nl(["",""]);return yl=function(){return e},e}function bl(){const e=Nl(['','',""]);return bl=function(){return e},e}function xl(){const e=Nl(['','*']);return xl=function(){return e},e}function wl(){const e=Nl(['
    ','
    ',' ','
    '," "," "," "," "," ","
    "]);return wl=function(){return e},e}function kl(){const e=Nl(["",""]);return kl=function(){return e},e}function Sl(){const e=Nl([" "," "]);return Sl=function(){return e},e}function Ol(){const e=Nl(['',""]);return Ol=function(){return e},e}function Al(){const e=Nl([' ','*']);return Al=function(){return e},e}function El(){const e=Nl(['','',""]);return El=function(){return e},e}function _l(){const e=Nl([' '," "]);return _l=function(){return e},e}function jl(){const e=Nl(['
    '," ",'
    ','
    ',"
    "]);return jl=function(){return e},e}function Tl(){const e=Nl([" ",'
    ',"
    "]);return Tl=function(){return e},e}function Cl(){const e=Nl(['',""]);return Cl=function(){return e},e}function Il(){const e=Nl(['
    null
    ']);return Il=function(){return e},e}function Pl(){const e=Nl([" ",""]);return Pl=function(){return e},e}function Rl(){const e=Nl(['
    ','
    ',' ','
    Field
    Type
    Description
    ',"
    "]);return Rl=function(){return e},e}function Ll(){const e=Nl([".table{font-size:var(--font-size-small);text-align:left;line-height:calc(var(--font-size-small) + 6px)}.table .tr{width:calc(100% - 5px);padding:0 0 0 5px;border-bottom:1px dotted var(--light-border-color)}.table .td{padding:4px 0}.table .key{width:240px}.key.deprecated .key-label{text-decoration:line-through}.table .key-type{white-space:normal;width:70px}.collapsed-descr .tr{max-height:calc(var(--font-size-small) + var(--font-size-small) + 4px)}.obj-toggle{padding:0 2px;border-radius:2px;border:1px solid transparent;display:inline-block;margin-left:-16px;color:var(--primary-color);cursor:pointer;font-size:calc(var(--font-size-small) + 4px);font-family:var(--font-mono);background-clip:border-box}.obj-toggle:hover{border-color:var(--primary-color)}.tr.expanded+.object-body{display:block}.tr.collapsed+.object-body{display:none}"]);return Ll=function(){return e},e}function Nl(e,t){return t||(t=e.slice(0)),Object.freeze(Object.defineProperties(e,{raw:{value:Object.freeze(t)}}))}customElements.define("api-request",class extends ie{constructor(){super(),this.responseMessage="",this.responseStatus="success",this.responseHeaders="",this.responseText="",this.responseUrl="",this.curlSyntax="",this.activeResponseTab="response",this.selectedRequestBodyType="",this.selectedRequestBodyExample=""}static get properties(){return{serverUrl:{type:String,attribute:"server-url"},servers:{type:Array},method:{type:String},path:{type:String},parameters:{type:Array},request_body:{type:Object},api_keys:{type:Array},parser:{type:Object},accept:{type:String},callback:{type:String},responseMessage:{type:String,attribute:!1},responseText:{type:String,attribute:!1},responseHeaders:{type:String,attribute:!1},responseStatus:{type:String,attribute:!1},responseUrl:{type:String,attribute:!1},fillRequestFieldsWithExample:{type:String,attribute:"fill-request-fields-with-example"},allowTry:{type:String,attribute:"allow-try"},renderStyle:{type:String,attribute:"render-style"},schemaStyle:{type:String,attribute:"schema-style"},activeSchemaTab:{type:String,attribute:"active-schema-tab"},schemaExpandLevel:{type:Number,attribute:"schema-expand-level"},schemaDescriptionExpanded:{type:String,attribute:"schema-description-expanded"},activeResponseTab:{type:String},selectedRequestBodyType:{type:String,attribute:"selected-request-body-type"},selectedRequestBodyExample:{type:String,attribute:"selected-request-body-example"}}}static get styles(){return[ge,de,pe,ve,La,Se,we,re(ll()),Te]}render(){return z(cl(),"read focused".includes(this.renderStyle)||"true"===this.callback?"read-mode":"view-mode","true"===this.callback?"tiny-title":"req-res-title","true"===this.callback?"CALLBACK REQUEST":"REQUEST",this.inputParametersTemplate("path"),this.inputParametersTemplate("query"),this.requestBodyTemplate(),this.inputParametersTemplate("header"),this.inputParametersTemplate("cookie"),"false"===this.allowTry?"":z(sl(),this.apiCallTemplate()))}updated(e){if("focused"===this.renderStyle)if(1===e.size&&e.has("activeSchemaTab"));else{[...this.shadowRoot.querySelectorAll('textarea[data-ptype="form-data"]')].forEach(e=>{const t=this.shadowRoot.querySelector("textarea[data-pname='hidden-".concat(e.dataset.pname,"']"));t&&(e.value=t.value)})}}inputParametersTemplate(e){const t=this.parameters?this.parameters.filter(t=>t.in===e):[];if(0===t.length)return"";let n="";"path"===e?n="PATH PARAMETERS":"query"===e?n="QUERY-STRING PARAMETERS":"header"===e?n="REQUEST HEADERS":"cookie"===e&&(n="COOKIES");const r=[];for(const n of t){if(!n.schema)continue;const t=Na(n.schema);let i="",a=[],s="form",c=!0;if("query"===e&&(n.style&&"form spaceDelimited pipeDelimited".includes(n.style)&&(s=n.style),"boolean"==typeof n.explode&&(c=n.explode)),n.example=void 0===n.example?"":Array.isArray(n.example)?n.example:"".concat(n.example),n.example)i="array"===t.type?n.example:"".concat(n.example),a=[{value:n.example,description:"".concat(n.example)}];else if(t.example)i="array"===t.type?t.example:"".concat(t.example),a=[{value:t.example,description:"".concat(t.example)}];else if(n.examples&&Object.values(n.examples).length>0)if(Array.isArray(n.examples)){const e=Object.values(n.examples)[0]||"";i="array"===t.type?[e]:e,a=Object.values(n.examples).map(e=>({value:e,description:e}))}else{var o;i=null===(o=Object.values(n.examples)[0])||void 0===o?void 0:o.value,a=Object.values(n.examples).map(e=>({value:e.value,description:e.description||e.summary||e.value}))}r.push(z(al(),"true"===this.allowTry?"1":"2",n.required?z(il()):"",n.name,"array"===t.type?"".concat(t.arrayType):"".concat(t.format?t.format:t.type),"true"===this.allowTry?z(ol(),"array"===t.type||"object"===t.type?"read focused".includes(this.renderStyle)?"300px":"220px":"160px","array"===t.type?z(rl(),e,n.name,Array.isArray(i)?i.join("~|~"):i,s,c,Array.isArray(i)?i.join(","):i):"object"===t.type?z(nl(),e,n.name,i,s,c,"read focused".includes(this.renderStyle)?"180px":"120px","true"===this.fillRequestFieldsWithExample?i:""):z(tl(),"password"===t.format?"password":"text",e,n.name,Array.isArray(i)?i.join("~|~"):i,"true"===this.fillRequestFieldsWithExample?i:"")):"","true"===this.allowTry?"1":"2",t.default||t.constrain||t.allowedValues||t.pattern?z(el(),t.default?z(Zc(),t.default):"",t.pattern?z(Xc(),t.pattern):"",t.constrain?z(Qc(),t.constrain):"",t.allowedValues&&t.allowedValues.split(",").map((e,r)=>z(Jc(),r>0?" | ":z(Gc()),z(Kc(),"true"===this.allowTry?"":"inactive-link","array"===t.type?t.type:"string",e.trim(),e=>{const t=e.target.closest("table").querySelector('[data-pname="'.concat(n.name,'"]'));t&&("array"===e.target.dataset.type?t.value=[e.target.dataset.enum]:t.value=e.target.dataset.enum)},e)))):"","true"===this.allowTry?z(Yc()):"",Ai(se()(n.description||"")),Array.isArray(a)&&a.length>0?z(Vc(),a.map((e,r)=>{var o;return z(Wc(),0===r?"":z(Hc()),"array"===t.type?"[":"","true"===this.allowTry?"":"inactive-link","array"===t.type?t.type:"string","array"===t.type?(null===(o=e.value)||void 0===o?void 0:o.join("~|~"))||"":e.value||"",e=>{const t=e.target.closest("table").querySelector('[data-pname="'.concat(n.name,'"]'));t&&("array"===e.target.dataset.exampleType?t.value=e.target.dataset.example.split("~|~"):t.value=e.target.dataset.example)},e.description||"","array"===t.type?"] ":"")})):""))}return z($c(),n,r)}resetRequestBodySelection(){this.selectedRequestBodyType="",this.selectedRequestBodyExample="",this.clearResponseData()}onSelectExample(e){this.selectedRequestBodyExample=e.target.value;const t=e.target;window.setTimeout(e=>{const t=e.closest(".example-panel").querySelector(".request-body-param");e.closest(".example-panel").querySelector(".request-body-param-user-input").value=t.value},0,t)}onMimeTypeChange(e){this.selectedRequestBodyType=e.target.value;const t=e.target;this.selectedRequestBodyExample="",window.setTimeout(e=>{const t=e.closest(".request-body-container").querySelector(".request-body-param");if(t){e.closest(".request-body-container").querySelector(".request-body-param-user-input").value=t.value}},0,t)}requestBodyTemplate(){if(!this.request_body)return"";if(0===Object.keys(this.request_body).length)return"";let e="",t="",n="",r="",o="";const i=[],a=this.request_body.content;for(const e in a)i.push({mimeType:e,schema:a[e].schema,example:a[e].example,examples:a[e].examples}),this.selectedRequestBodyType||(this.selectedRequestBodyType=e);return e=1===i.length?"":z(Uc(),e=>this.onMimeTypeChange(e),i.map(e=>z(zc(),e.mimeType,e.mimeType===this.selectedRequestBodyType,e.mimeType))),i.forEach(e=>{let i,a=[];if(this.selectedRequestBodyType.includes("json")||this.selectedRequestBodyType.includes("xml")||this.selectedRequestBodyType.includes("text"))e.mimeType===this.selectedRequestBodyType&&(a=$a(e.examples?e.examples:"",e.example?e.example:"",e.schema,e.mimeType,!1,"text"),this.selectedRequestBodyExample||(this.selectedRequestBodyExample=a.length>0?a[0].exampleId:""),o=z(Fc(),o,1===a.length?"":z(qc(),e=>this.onSelectExample(e),a.map(e=>z(Dc(),e.exampleId,e.exampleId===this.selectedRequestBodyExample,e.exampleSummary.length>80?e.exampleId:e.exampleSummary?e.exampleSummary:e.exampleId))),a.filter(e=>e.exampleId===this.selectedRequestBodyExample).map(t=>z(Bc(),t.exampleId===this.selectedRequestBodyExample?"example-selected":"",t.exampleId,t.exampleSummary&&t.exampleSummary.length>80?z(Mc(),t.exampleSummary):"",t.exampleDescription?z(Nc(),Ai(se()(t.exampleDescription||""))):"",e.mimeType,"text"===t.exampleFormat?t.exampleValue:JSON.stringify(t.exampleValue,null,2),t.exampleFormat,"true"===this.fillRequestFieldsWithExample?"text"===t.exampleFormat?t.exampleValue:JSON.stringify(t.exampleValue,null,2):"",e.mimeType.substring(e.mimeType.indexOf("/")+1),e.mimeType,"text"===t.exampleFormat?t.exampleValue:JSON.stringify(t.exampleValue,null,2)))));else if(this.selectedRequestBodyType.includes("form-urlencoded")||this.selectedRequestBodyType.includes("form-data")){if(e.mimeType===this.selectedRequestBodyType){const t=$a(e.examples?e.examples:"",e.example?e.example:"",e.schema,e.mimeType,!1,"text");e.schema&&(n=this.formDataTemplate(e.schema,e.mimeType,t[0]?t[0].exampleValue:""))}}else RegExp("^audio/|^image/|^video/|^font/|tar$|zip$|7z$|rtf$|msword$|excel$|/pdf$|/octet-stream$").test(this.selectedRequestBodyType)&&e.mimeType===this.selectedRequestBodyType&&(t=z(Lc(),e.mimeType));(e.mimeType.includes("json")||e.mimeType.includes("xml")||e.mimeType.includes("text"))&&(i=Ua(e.schema,{}),"table"===this.schemaStyle?r=z(Rc(),r,e.mimeType.substring(e.mimeType.indexOf("/")+1),this.selectedRequestBodyType===e.mimeType?"block":"none",i,this.schemaExpandLevel,this.schemaDescriptionExpanded):"tree"===this.schemaStyle&&(r=z(Pc(),r,e.mimeType.substring(e.mimeType.indexOf("/")+1),this.selectedRequestBodyType===e.mimeType?"block":"none",i,this.schemaExpandLevel,this.schemaDescriptionExpanded)))}),z(Ic(),this.selectedRequestBodyType,this.request_body.required?z(Cc()):"",this.selectedRequestBodyType,e,this.request_body.description?z(Tc(),Ai(se()(this.request_body.description))):"",this.selectedRequestBodyType.includes("json")||this.selectedRequestBodyType.includes("xml")||this.selectedRequestBodyType.includes("text")?z(jc(),e=>{"button"===e.target.tagName.toLowerCase()&&(this.activeSchemaTab=e.target.dataset.tab)},"model"===this.activeSchemaTab?"active":"","example"===this.activeSchemaTab?"active":"",z(_c(),"model"===this.activeSchemaTab?"block":"none",r),z(Ec(),"model"===this.activeSchemaTab?"none":"block",o)):z(Ac(),t,n))}formDataTemplate(e,t,n=""){const r=[];if(e.properties){for(const n in e.properties){var o,i,a;const s=e.properties[n],c=s.type,l=Ua(s,{}),u=Na(s),p=$a("",s.example?s.example:"",s,"json",!1,"text");r.push(z(Oc(),s.required?z(Sc(),n):z(kc(),n),u.type,"object"===c?"width:100%; padding:0;":"true"===this.allowTry?"width:160px;":"display:none;","object"===c?2:1,"array"===c?"binary"===(null===(o=s.items)||void 0===o?void 0:o.format)?z(wc(),e=>this.onAddRemoveFileInput(e,n,t),n,t.includes("form-urlencode")?"form-urlencode":"form-data"):z(xc(),t.includes("form-urlencode")?"form-urlencode":"form-data",n,Array.isArray(s.example)?s.example.join("~|~"):s.example||"",Array.isArray(s.example)?s.example.join(","):s.example):z(bc(),"object"===c?z(yc(),e=>{if(e.target.classList.contains("v-tab-btn")){const t=e.target.dataset.tab;if(t){const n=e.target.closest(".tab-panel"),r=n.querySelector('.v-tab-btn[data-tab="'.concat(t,'"]')),o=[...n.querySelectorAll('.v-tab-btn:not([data-tab="'.concat(t,'"])'))],i=n.querySelector('.tab-content[data-tab="'.concat(t,'"]')),a=[...n.querySelectorAll('.tab-content:not([data-tab="'.concat(t,'"])'))];r.classList.add("active"),i.style.display="block",o.forEach(e=>{e.classList.remove("active")}),a.forEach(e=>{e.style.display="none"})}}"button"===e.target.tagName.toLowerCase()&&(this.activeSchemaTab=e.target.dataset.tab)},"model"===this.activeSchemaTab?"active":"","example"===this.activeSchemaTab?"active":"",z(gc(),"model"===this.activeSchemaTab?"block":"none",l,this.schemaExpandLevel,this.schemaDescriptionExpanded),z(mc(),"example"===this.activeSchemaTab?"block":"none",t.includes("form-urlencode")?"form-urlencode":"form-data",n,(null===(i=p[0])||void 0===i?void 0:i.exampleValue)||"","true"===this.fillRequestFieldsWithExample?p[0].exampleValue:"",n,t.includes("form-urlencode")?"hidden-form-urlencode":"hidden-form-data",p[0].exampleValue)):z(vc(),"true"===this.allowTry?z(hc(),"true"===this.fillRequestFieldsWithExample&&s.example||"","binary"===s.format?"file":"password"===s.format?"password":"text",t.includes("form-urlencode")?"form-urlencode":"form-data",n,s.example||""):"")),"object"===c?"":z(dc(),u.default||u.constrain||u.allowedValues||u.pattern?z(fc(),u.default?z(pc(),u.default):"",u.pattern?z(uc(),u.pattern):"",u.constrain?z(lc(),u.constrain):"",u.allowedValues&&u.allowedValues.split(",").map((e,t)=>z(cc(),t>0?" | ":z(sc()),z(ac(),"true"===this.allowTry?"":"inactive-link","array"===u.type?u.type:"string",e.trim(),e=>{const t=e.target.closest("table").querySelector('[data-pname="'.concat(n,'"]'));t&&("array"===e.target.dataset.type?t.value=[e.target.dataset.enum]:t.value=e.target.dataset.enum)},e)))):""),"object"===c?"":z(ic(),Ai(se()(s.description||"")),u.example?z(oc(),"array"===u.type?"[ ":"","true"===this.allowTry?"":"inactive-link","array"===u.type?u.type:"string","array"===u.type?(null===(a=u.example)||void 0===a?void 0:a.join("~|~"))||"":u.example,e=>{const t=e.target.closest("table").querySelector('[data-pname="'.concat(n,'"]'));t&&("array"===e.target.dataset.exampleType?t.value=e.target.dataset.example.split("~|~"):t.value=e.target.dataset.example)},"array"===u.type?u.example.join(", "):u.example,"array"===u.type?"] ":""):"")))}return z(rc(),r)}return z(nc(),t,t,n,e.description?z(tc(),Ai(se()(e.description))):"")}apiResponseTabTemplate(){const e=this.responseHeaders.includes("json")?"json":this.responseHeaders.includes("html")||this.responseHeaders.includes("xml")?"html":"";return z(ec(),this.responseStatus,this.responseMessage,this.clearResponseData,e=>{!1!==e.target.classList.contains("tab-btn")&&(this.activeResponseTab=e.target.dataset.tab)},"response"===this.activeResponseTab?"active":"","headers"===this.activeResponseTab?"active":"","curl"===this.activeResponseTab?"active":"",this.responseIsBlob?z(Zs(),"response"===this.activeResponseTab?"flex":"none",this.downloadResponseBlob,"view"===this.responseBlobType?z(Xs(),this.viewResponseBlob):""):z(Qs(),"response"===this.activeResponseTab?"flex":"none",e=>{Pe(this.responseText,e)},e?z(Js(),Ai(le.a.highlight(this.responseText,le.a.languages[e],e))):"".concat(this.responseText)),"headers"===this.activeResponseTab?"flex":"none",e=>{Pe(this.responseHeaders,e)},Ai(le.a.highlight(this.responseHeaders,le.a.languages.css,"css")),"curl"===this.activeResponseTab?"flex":"none",e=>{Pe(this.curlSyntax.replace(/\\$/,""),e)},Ai(le.a.highlight(this.curlSyntax.trim().replace(/\\$/,""),le.a.languages.shell,"shell")))}apiCallTemplate(){var e;let t="";this.servers&&this.servers.length>0&&(t=z(Gs(),e=>{this.serverUrl=e.target.value},this.servers.map(e=>z(Ks(),e.url,e.url,e.description))));const n=z(Ys(),t,this.serverUrl?z(Vs(),this.serverUrl):"");return z(Ws(),n,this.api_keys.length>0?z(Hs(),1===this.api_keys.length?"".concat(null===(e=this.api_keys[0])||void 0===e?void 0:e.typeDisplay," in ").concat(this.api_keys[0].in):"".concat(this.api_keys.length," API keys applied")):z($s()),this.parameters.length>0||this.request_body?z(Us(),this.onFillRequestData,this.onClearRequestData):"",this.onTryClick,""===this.responseMessage?"":this.apiResponseTabTemplate())}async onFillRequestData(e){[...e.target.closest(".request-panel").querySelectorAll("input, tag-input, textarea:not(.is-hidden)")].forEach(e=>{e.dataset.example&&("TAG-INPUT"===e.tagName.toUpperCase()?e.value=e.dataset.example.split("~|~"):e.value=e.dataset.example)})}async onClearRequestData(e){[...e.target.closest(".request-panel").querySelectorAll("input, tag-input, textarea:not(.is-hidden)")].forEach(e=>{e.value=""})}async onTryClick(e){var t;const n=this,r=e.target;let o,i,a="",s="",c="",l="";const u=null===(t=this.closest(".expanded-req-resp-container, .req-resp-container"))||void 0===t?void 0:t.getElementsByTagName("api-response")[0],p=null==u?void 0:u.selectedMimeType,f=e.target.closest(".request-panel"),d=[...f.querySelectorAll("[data-ptype='path']")],h=[...f.querySelectorAll("[data-ptype='query']")],v=[...f.querySelectorAll("[data-ptype='query-object']")],m=[...f.querySelectorAll("[data-ptype='header']")],g=f.querySelector(".request-body-container");o=n.path;const y={method:this.method.toUpperCase(),headers:{}};if(d.map(e=>{o=o.replace("{".concat(e.dataset.pname,"}"),encodeURIComponent(e.value))}),h.length>0){const e=new URLSearchParams;h.forEach(t=>{if("false"===t.dataset.array)""!==t.value&&e.append(t.dataset.pname,t.value);else{const n=t.dataset.paramSerializeStyle,r=t.dataset.paramSerializeExplode,o=t.value&&Array.isArray(t.value)?t.value:[];"spaceDelimited"===n?e.append(t.dataset.pname,o.join(" ")):"pipeDelimited"===n?e.append(t.dataset.pname,o.join("|")):"true"===r?o.forEach(n=>{e.append(t.dataset.pname,n)}):e.append(t.dataset.pname,o.join(","))}}),o="".concat(o).concat(e.toString()?"?":"").concat(e.toString())}if(v.length>0){const e=new URLSearchParams;v.map(t=>{try{let n={};const r=t.dataset.paramSerializeStyle,i=t.dataset.paramSerializeExplode;n=Object.assign(n,JSON.parse(t.value.replace(/\s+/g," ")));for(const t in n)"object"==typeof n[t]?Array.isArray(n[t])&&("spaceDelimited"===r?e.append(t,n[t].join(" ")):"pipeDelimited"===r?e.append(t,n[t].join("|")):"true"===i?n[t].forEach(n=>{e.append(t,n)}):e.append(t,n[t])):e.append(t,n[t]);o="".concat(o).concat(e.toString()?"?":"").concat(e.toString())}catch(e){console.log("RapiDoc: unable to parse %s into object",t.value)}})}if(this.api_keys.filter(e=>"query"===e.in).forEach(e=>{o="".concat(o).concat(o.includes("?")?"&":"?").concat(e.name,"=").concat(encodeURIComponent(e.finalKeyValue))}),o="".concat(this.serverUrl.replace(/\/$/,"")).concat(o),!1===o.startsWith("http")){i=new URL(o,window.location.href).href}else i=o;if(a="curl -X ".concat(this.method.toUpperCase(),' "').concat(i,'" \\\n'),p?(y.headers.Accept=p,s+=' -H "Accept: '.concat(p,'" \\\n')):this.accept&&(y.headers.Accept=this.accept,s+=' -H "Accept: '.concat(this.accept,'" \\\n')),this.api_keys.filter(e=>"header"===e.in).forEach(e=>{y.headers[e.name]=e.finalKeyValue,s+=' -H "'.concat(e.name,": ").concat(e.finalKeyValue,'" \\\n')}),m.map(e=>{e.value&&(y.headers[e.dataset.pname]=e.value,s+=' -H "'.concat(e.dataset.pname,": ").concat(e.value,'" \\\n'))}),g){const e=g.dataset.selectedRequestBodyType;if(e.includes("form-urlencoded")){const e=f.querySelector("[data-ptype='dynamic-form']");if(e){const t=e.value,n=new URLSearchParams;let r,o=!0;if(t)try{r=JSON.parse(t)}catch(e){o=!1,console.warn("RapiDoc: Invalid JSON provided",e)}else o=!1;if(o){for(const e in r)n.append(e,JSON.stringify(r[e]));y.body=n,c=" -d ".concat(n.toString()," \\\n")}}else{const e=[...f.querySelectorAll("[data-ptype='form-urlencode']")],t=new URLSearchParams;e.filter(e=>"file"!==e.type).forEach(e=>{if("false"===e.dataset.array)e.value&&t.append(e.dataset.pname,e.value);else{const n=e.value&&Array.isArray(e.value)?e.value.join(","):"";t.append(e.dataset.pname,n)}}),y.body=t,c=" -d ".concat(t.toString()," \\\n")}}else if(e.includes("form-data")){const e=new FormData;[...f.querySelectorAll("[data-ptype='form-data']")].forEach(t=>{"false"===t.dataset.array?"file"===t.type&&t.files[0]?(e.append(t.dataset.pname,t.files[0],t.files[0].name),l+=' -F "'.concat(t.dataset.pname,"=@").concat(t.files[0].name,'" \\\n')):t.value&&(e.append(t.dataset.pname,t.value),l+=' -F "'.concat(t.dataset.pname,"=").concat(t.value,'" \\\n')):t.value&&Array.isArray(t.value)&&(t.value.forEach(e=>{l="".concat(l,' -F "').concat(t.dataset.pname,"[]=").concat(e,'" \\\n')}),e.append(t.dataset.pname,t.value.join(",")))}),y.body=e}else if(RegExp("^audio/|^image/|^video/|^font/|tar$|zip$|7z$|rtf$|msword$|excel$|/pdf$|/octet-stream$").test(e)){const e=f.querySelector(".request-body-param-file");e&&e.files[0]&&(y.body=e.files[0],c=" --data-binary @".concat(e.files[0].name," \\\n"))}else if(e.includes("json")||e.includes("xml")||e.includes("text")){const e=f.querySelector(".request-body-param-user-input");if(e&&e.value){y.body=e.value;try{c=" -d '".concat(JSON.stringify(JSON.parse(e.value)),"' \\\n")}catch(t){c=" -d '".concat(e.value.replace(/(\r\n|\n|\r)/gm,""),"' \\\n")}}}e.includes("form-data")||(y.headers["Content-Type"]=e),s+=' -H "Content-Type: '.concat(e,'" \\\n')}n.responseUrl="",n.responseHeaders="",n.curlSyntax="",n.responseStatus="success",n.responseIsBlob=!1,n.respContentDisposition="",n.responseBlobUrl&&(URL.revokeObjectURL(n.responseBlobUrl),n.responseBlobUrl=""),n.curlSyntax="".concat(a).concat(s).concat(c).concat(l);try{let e,t,i;r.disabled=!0;const a=await fetch(o,y);r.disabled=!1,n.responseStatus=a.ok?"success":"error",n.responseMessage="".concat(a.statusText,":").concat(a.status),n.responseUrl=a.url,a.headers.forEach((e,t)=>{n.responseHeaders="".concat(n.responseHeaders).concat(t.trim(),": ").concat(e,"\n")});const s=a.headers.get("content-type");if(s){if(s.includes("json")?(t=await a.json(),n.responseText=JSON.stringify(t,null,2)):RegExp("^font/|tar$|zip$|7z$|rtf$|msword$|excel$|/pdf$|/octet-stream$").test(s)?(n.responseIsBlob=!0,n.responseBlobType="download"):RegExp("^audio|^image|^video").test(s)?(n.responseIsBlob=!0,n.responseBlobType="view"):(i=await a.text(),n.responseText=i),n.responseIsBlob){const t=a.headers.get("content-disposition");n.respContentDisposition=t?t.split("filename=")[1]:"filename",e=await a.blob(),n.responseBlobUrl=URL.createObjectURL(e)}}else i=await a.text(),n.responseText=i;this.dispatchEvent(new CustomEvent("after-try",{bubbles:!0,composed:!0,detail:{fetchUrl:o,fetchOptions:y,responseStatus:n.responseStatus,responseContentType:s,responseIsBlob:n.responseIsBlob,response:t||i||e}}))}catch(e){r.disabled=!1,n.responseMessage="".concat(e.message," (CORS or Network Issue)"),document.dispatchEvent(new CustomEvent("after-try",{bubbles:!0,composed:!0,detail:{err:e,url:o,options:y}}))}}onAddRemoveFileInput(e,t,n){if("button"!==e.target.tagName.toLowerCase())return;if(e.target.classList.contains("file-input-remove-btn")){return void e.target.closest(".input-set").remove()}const r=e.target.closest(".file-input-container"),o=document.createElement("div");o.setAttribute("class","input-set row");const i=document.createElement("input");i.type="file",i.style="width:200px; margin-top:2px;",i.setAttribute("data-pname",t),i.setAttribute("data-ptype",n.includes("form-urlencode")?"form-urlencode":"form-data"),i.setAttribute("data-array","false"),i.setAttribute("data-file-array","true");const a=document.createElement("button");a.setAttribute("class","file-input-remove-btn"),a.innerHTML="✕",o.appendChild(i),o.appendChild(a),r.insertBefore(o,e.target)}downloadResponseBlob(){if(this.responseBlobUrl){const e=document.createElement("a");document.body.appendChild(e),e.style="display: none",e.href=this.responseBlobUrl,e.download=this.respContentDisposition,e.click(),e.remove()}}viewResponseBlob(){if(this.responseBlobUrl){const e=document.createElement("a");document.body.appendChild(e),e.style="display: none",e.href=this.responseBlobUrl,e.target="_blank",e.click(),e.remove()}}clearResponseData(){this.responseUrl="",this.responseHeaders="",this.responseText="",this.responseStatus="success",this.responseMessage="",this.responseIsBlob=!1,this.responseBlobType="",this.respContentDisposition="",this.responseBlobUrl&&(URL.revokeObjectURL(this.responseBlobUrl),this.responseBlobUrl="")}disconnectedCallback(){this.responseBlobUrl&&(URL.revokeObjectURL(this.responseBlobUrl),this.responseBlobUrl=""),super.disconnectedCallback()}});function Ml(){const e=mu([' ']);return Ml=function(){return e},e}function Bl(){const e=mu([' ']);return Bl=function(){return e},e}function Dl(){const e=mu([" ",""]);return Dl=function(){return e},e}function ql(){const e=mu(['
     Schema not found
    ']);return ql=function(){return e},e}function Fl(){const e=mu(["
    ","
    "]);return Fl=function(){return e},e}function zl(){const e=mu([' ']);return zl=function(){return e},e}function Ul(){const e=mu(['
    ',"
    "]);return Ul=function(){return e},e}function $l(){const e=mu(['
    ',"
    "]);return $l=function(){return e},e}function Hl(){const e=mu(['
    '," "," ","
    "]);return Hl=function(){return e},e}function Wl(){const e=mu(['"]);return Wl=function(){return e},e}function Vl(){const e=mu([' "," "]);return Vl=function(){return e},e}function Yl(){const e=mu(['
    ',"
    "]);return Yl=function(){return e},e}function Kl(){const e=mu([' ']);return Kl=function(){return e},e}function Gl(){const e=mu([" ",""]);return Gl=function(){return e},e}function Jl(){const e=mu([" "," "]);return Jl=function(){return e},e}function Ql(){const e=mu(['
     No example provided 
    ']);return Ql=function(){return e},e}function Xl(){const e=mu(['"]);return Xl=function(){return e},e}function Zl(){const e=mu([' "]);return Zl=function(){return e},e}function eu(){const e=mu([' ',' ','
    ','
    '," "]);return eu=function(){return e},e}function tu(){const e=mu(['
    Response Headers:
    ',"
    "]);return tu=function(){return e},e}function nu(){const e=mu(['
    ',"
    "]);return nu=function(){return e},e}function ru(){const e=mu(['
    ',"
    "]);return ru=function(){return e},e}function ou(){const e=mu(["",""]);return ou=function(){return e},e}function iu(){const e=mu([' '," "]);return iu=function(){return e},e}function au(){const e=mu(['
    ',"
    ","
    "]);return au=function(){return e},e}function su(){const e=mu(["",""]);return su=function(){return e},e}function cu(){const e=mu(['
    '," ","
    ","
    "]);return cu=function(){return e},e}function lu(){const e=mu(["",""]);return lu=function(){return e},e}function uu(){const e=mu([' "]);return uu=function(){return e},e}function pu(){const e=mu([" ",""]);return pu=function(){return e},e}function fu(){const e=mu(['
    ',"
    "]);return fu=function(){return e},e}function du(){const e=mu([" "," "," "]);return du=function(){return e},e}function hu(){const e=mu(['
    ',"
    ","
    "]);return hu=function(){return e},e}function vu(){const e=mu([".resp-head{vertical-align:middle;padding:16px 0 8px}.resp-head.divider{border-top:1px solid var(--border-color);margin-top:10px}.resp-status{font-weight:700;font-size:calc(var(--font-size-small) + 1px)}.resp-descr{font-size:calc(var(--font-size-small) + 1px);color:var(--light-fg)}.top-gap{margin-top:16px}.example-panel{font-size:var(--font-size-small);margin:0}.focused-mode,.read-mode{padding-top:24px;margin-top:12px;border-top:1px dashed var(--border-color)}"]);return vu=function(){return e},e}function mu(e,t){return t||(t=e.slice(0)),Object.freeze(Object.defineProperties(e,{raw:{value:Object.freeze(t)}}))}customElements.define("schema-table",class extends ie{static get properties(){return{schemaExpandLevel:{type:Number,attribute:"schema-expand-level"},schemaDescriptionExpanded:{type:String,attribute:"schema-description-expanded"},data:{type:Object}}}connectedCallback(){super.connectedCallback(),(!this.schemaExpandLevel||this.schemaExpandLevel<1)&&(this.schemaExpandLevel=99999),this.schemaDescriptionExpanded&&"true false".includes(this.schemaDescriptionExpanded)||(this.schemaDescriptionExpanded="false")}static get styles(){return[pe,ts,re(Ll()),Te]}render(){var e,t;return z(Rl(),"true"===this.schemaDescriptionExpanded?"expanded-descr":"collapsed-descr",()=>{this.schemaDescriptionExpanded="true"===this.schemaDescriptionExpanded?"false":"true"},"true"===this.schemaDescriptionExpanded?"Single line description":"Multiline description",(null===(e=this.data)||void 0===e?void 0:e["::type"])||"",(null===(t=this.data)||void 0===t?void 0:t["::type"])||"",this.data?Ai(se()(this.data["::description"]||"")):"",this.data?z(Pl(),this.generateTree("array"===this.data["::type"]?this.data["::props"]:this.data,this.data["::type"])):"")}generateTree(e,t="object",n="",r="",o=0){var i;const a=16*o;if(!e)return z(Il());if(0===Object.keys(e).length)return z(Cl(),a,n);let s="",c="",l=!1;if(n.startsWith("::ONE~OF")||n.startsWith("::ANY~OF"))s=n.replace("::","").replace("~"," "),l=!0;else if(n.startsWith("::OPTION")){const e=n.split("~");c=e[1],s=e[2]}else s=n;if("object"==typeof e)return z(Tl(),o>0?z(jl(),othis.toggleObjectExpand(e,s),oz(Sl(),["::description","::type","::props","::deprecated"].includes(t)?"":z(kl(),this.generateTree("array"===e[t]["::type"]?e[t]["::props"]:e[t],e[t]["::type"],t,e[t]["::description"],o+1)))));const u=e.split("~|~"),p=u[0].replace("{","").substring(0,4).toLowerCase();return z(wl(),u[8],a,null!==(i=s)&&void 0!==i&&i.endsWith("*")?z(xl(),s.substring(0,s.length-1)):n.startsWith("::OPTION")?z(bl(),c,s):z(yl(),s?z(gl(),s):z(ml(),u[7])),p,"array"===t?"[".concat(u[0],"]"):u[0],u[1],"array"===t?r:"",u[2]?z(vl(),u[4]):"",u[3]?z(hl(),u[3]):"",u[4]?z(dl(),u[4]):"",u[5]?z(fl(),u[5]):"",u[6]?z(pl(),Ai(se()(u[6]))):"")}toggleObjectExpand(e){const t=e.target.closest(".tr");t.classList.contains("expanded")?(t.classList.add("collapsed"),t.classList.remove("expanded"),e.target.innerText="+"):(t.classList.remove("collapsed"),t.classList.add("expanded"),e.target.innerText="-")}});function gu(){const e=Eu(['
    ','
    ','
    ',"
    "]);return gu=function(){return e},e}function yu(){const e=Eu([" "," "]);return yu=function(){return e},e}function bu(){const e=Eu(['
    ',"
    "]);return bu=function(){return e},e}function xu(){const e=Eu(['
    ',' ',"
    "]);return xu=function(){return e},e}function wu(){const e=Eu([' '," ",""]);return wu=function(){return e},e}function ku(){const e=Eu(['

    ',"

    ",""]);return ku=function(){return e},e}function Su(){const e=Eu(['
    DEPRECATED
    ']);return Su=function(){return e},e}function Ou(){const e=Eu(['
    ']);return Ou=function(){return e},e}function Au(){const e=Eu([" ",'
    '," "," "," "," ",'
    ','
    ']);return Au=function(){return e},e}function Eu(e,t){return t||(t=e.slice(0)),Object.freeze(Object.defineProperties(e,{raw:{value:Object.freeze(t)}}))}function _u(e){var t,n;const r=new Set;for(const t in e.responses)for(const n in null===(o=e.responses[t])||void 0===o?void 0:o.content){var o;r.add(n.trim())}const i=[...r].join(", "),a=this.resolvedSpec.securitySchemes.filter(t=>{var n;return t.finalKeyValue&&(null===(n=e.security)||void 0===n?void 0:n.some(e=>t.apiKeyId in e))})||[],s=this.resolvedSpec.securitySchemes.find(e=>"_rapidoc_api_key"===e.apiKeyId&&"-"!==e.value);s&&a.push(s);const c=e.xCodeSamples?Ea.call(this,e.xCodeSamples):"";return z(Au(),"read"===this.renderStyle?z(Ou()):"",e.method,e.deprecated?"deprecated":"",e.method,e.path.replace(Ce,"-"),e.deprecated?z(Su()):"",z(ku(),e.deprecated?"gray-text":"",e.summary||z(wu(),e.deprecated?" method-fg gray-text":e.method,e.method,e.path),e.summary?z(xu(),e.deprecated?" gray-text":" bold-text ".concat(e.method),e.method,e.deprecated?"gray-text":"",e.path):""),e.description?z(bu(),Ai(se()(e.description||""))):"",wa.call(this,e.security),c,e.method,e.path,e.parameters,e.requestBody,a,e.servers,(null===(t=e.servers)||void 0===t||null===(n=t[0])||void 0===n?void 0:n.url)||this.selectedServer.computedUrl,this.fillRequestFieldsWithExample,this.allowTry,i,this.renderStyle,this.schemaStyle,this.defaultSchemaTab,this.schemaExpandLevel,this.schemaDescriptionExpanded,e.callbacks?Pa.call(this,e.callbacks):"",e.responses,this.renderStyle,this.schemaStyle,this.defaultSchemaTab,this.schemaExpandLevel,this.schemaDescriptionExpanded,Object.keys(e.responses||{})[0]||"")}function ju(){return z(yu(),this.resolvedSpec.tags.map(e=>z(gu(),e.name.replace(Ce,"-"),e.name,Ai("
    ".concat(se()(e.description?e.description:""),"
    ")),e.paths.map(e=>_u.call(this,e)))))}function Tu(){const e=Ru(['
    ',"
    "]);return Tu=function(){return e},e}function Cu(){const e=Ru(['
    ',"
    "]);return Cu=function(){return e},e}function Iu(){const e=Ru(['
    ',"
    "]);return Iu=function(){return e},e}function Pu(){const e=Ru(['

    ',"

    "," "]);return Pu=function(){return e},e}function Ru(e,t){return t||(t=e.slice(0)),Object.freeze(Object.defineProperties(e,{raw:{value:Object.freeze(t)}}))}function Lu(e){return z(Pu(),e.name,e.name,e.description?z(Iu(),Ai(se()(e.description||""))):"")}function Nu(){let e="",t={},n={},r=0;if(e=this.selectedContentId?this.selectedContentId:"overview","overview"===e||"authentication"===e||"api-servers"===e)t={},n={};else if(e.startsWith("tag--")){const t=e.replace("tag--","");n=this.resolvedSpec.tags.find(e=>e.name.replace(Ce,"-")===t)}else{for(r=0;r"".concat(t.method,"-").concat(t.path.replace(Ce,"-"))===e),!t);r+=1);var o;if(!t)n=this.resolvedSpec.tags[0],t=null===(o=this.resolvedSpec.tags[0])||void 0===o?void 0:o.paths[0]}return"overview"===e||"authentication"===e||"api-servers"===e?"":e.startsWith("tag--")?z(Cu(),Lu.call(this,n)):z(Tu(),_u.call(this,t))}function Mu(){const e=Hu(['
    '," ","
    "]);return Mu=function(){return e},e}function Bu(){const e=Hu([' "]);return Bu=function(){return e},e}function Du(){const e=Hu([" ",""]);return Du=function(){return e},e}function qu(){const e=Hu(['
    ',"
    "]);return qu=function(){return e},e}function Fu(){const e=Hu(['
    ',"
    "]);return Fu=function(){return e},e}function zu(){const e=Hu(['
    '," "," "," ",'
    ','
    ']);return zu=function(){return e},e}function Uu(){const e=Hu([' deprecated ']);return Uu=function(){return e},e}function $u(){const e=Hu(['
    ','
    ',"
    ",'
    ',"
    "]);return $u=function(){return e},e}function Hu(e,t){return t||(t=e.slice(0)),Object.freeze(Object.defineProperties(e,{raw:{value:Object.freeze(t)}}))}function Wu(e){if(e.expanded)e.expanded=!1,window.history.replaceState(null,null,"".concat(window.location.href.split("#")[0]));else{e.expanded=!0;const t="#".concat(e.method,"-").concat(e.path.replace(Ce,"-"));window.location.hash!==t&&window.history.replaceState(null,null,"".concat(window.location.href.split("#")[0]).concat(t))}this.requestUpdate()}function Vu(e){return z($u(),t=>{Wu.call(this,e,t)},e.method,e.deprecated?"deprecated":"",e.expanded?"expanded":"collapsed",e.method,e.deprecated?"deprecated":"",e.method,e.deprecated?"deprecated":"",e.path,e.deprecated?z(Uu()):"",Ai(se()(e.summary||"")))}function Yu(e){const t=new Set;for(const r in e.responses)for(const o in null===(n=e.responses[r])||void 0===n?void 0:n.content){var n;t.add(o.trim())}const r=[...t].join(", "),o=this.resolvedSpec.securitySchemes.filter(t=>{var n;return t.finalKeyValue&&(null===(n=e.security)||void 0===n?void 0:n.some(e=>t.apiKeyId in e))})||[],i=this.resolvedSpec.securitySchemes.find(e=>"_rapidoc_api_key"===e.apiKeyId&&"-"!==e.value);i&&o.push(i);const a=e.xCodeSamples?Ea(e.xCodeSamples):"";return z(zu(),e.method,e.deprecated?"deprecated":"",e.summary&&e.summary!==e.description?z(Fu(),e.summary):"",e.description?z(qu(),Ai(se()(e.description))):"",wa.call(this,e.security),a,e.method,e.path,e.parameters,e.requestBody,o,e.servers,e.servers&&e.servers.length>0?e.servers[0].url:this.selectedServer.computedUrl,this.defaultSchemaTab,this.fillRequestFieldsWithExample,this.allowTry,r,this.renderStyle,this.schemaStyle,this.schemaExpandLevel,this.schemaDescriptionExpanded,e.callbacks?Pa.call(this,e.callbacks):"",e.responses,this.defaultSchemaTab,this.renderStyle,this.schemaStyle,this.schemaExpandLevel,this.schemaDescriptionExpanded,Object.keys(e.responses||{})[0]||"")}function Ku(){return z(Du(),this.resolvedSpec.tags.map(e=>z(Bu(),e.expanded?"expanded":"collapsed",()=>{e.expanded=!e.expanded,this.requestUpdate()},e.name.replace(Ce,"-"),e.name,Ai(se()(e.description||"")),e.paths.filter(e=>!this.matchPaths||Re(this.matchPaths,e)).map(e=>z(Mu(),e.method,e.path.replace(Ce,"-"),e.method,e.expanded?"expanded":"collapsed",Vu.call(this,e),e.expanded?Yu.call(this,e):"")))))}function Gu(){const e=ap(['- '," "]);return Gu=function(){return e},e}function Ju(){const e=ap(['
    "]);return Ju=function(){return e},e}function Qu(){const e=ap([" "," "]);return Qu=function(){return e},e}function Xu(){const e=ap(['
    API SERVER:
    ','
    SELECTED: ',"
    ","
    "]);return Xu=function(){return e},e}function Zu(){const e=ap([' '," "]);return Zu=function(){return e},e}function ep(){const e=ap([' ']);return ep=function(){return e},e}function tp(){const e=ap(['