summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.editorconfig17
-rw-r--r--.gitignore5
-rw-r--r--Makefile125
-rw-r--r--README.txt68
-rw-r--r--mingw64-include/regex.h6
-rw-r--r--src/array/array.c25
-rw-r--r--src/array/array.h46
-rw-r--r--src/common/commonbase.c0
-rw-r--r--src/common/commonbase.h18
-rw-r--r--src/entrypoint/gateleenResclone.c16
-rw-r--r--src/gateleen_resclone/gateleen_resclone.c926
-rw-r--r--src/gateleen_resclone/gateleen_resclone.h16
-rw-r--r--src/log/log.c31
-rw-r--r--src/log/log.h23
-rw-r--r--src/mime/mime.c260
-rw-r--r--src/mime/mime.h13
-rw-r--r--src/util_string/util_string.h13
-rw-r--r--src/util_term/util_term.c45
-rw-r--r--src/util_term/util_term.h8
19 files changed, 1661 insertions, 0 deletions
diff --git a/.editorconfig b/.editorconfig
new file mode 100644
index 0000000..b6cc8e0
--- /dev/null
+++ b/.editorconfig
@@ -0,0 +1,17 @@
+
+[*.{c,h}]
+indent_style=space
+indent_size=4
+
+[Makefile]
+indent_style=tab
+
+[*.{xml,html,htm}]
+indent_style=space
+indent_size=2
+charset=utf-8
+
+[*.{json}]
+indent_style=space
+indent_size=2
+charset=utf-8
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..9c87afb
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,5 @@
+
+/build
+/dist
+/external
+
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..062e8a3
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,125 @@
+
+CC ?=gcc
+LD ?=ld
+BINEXT ?= .elf
+TOOLCHAIN ?= lxGcc64
+
+ifndef PROJECT_VERSION
+ PROJECT_VERSION := $(shell git describe | sed 's;^v;;')
+endif
+
+CFLAGS ?= --std=c99 \
+ -Wall -Wextra -Werror -fmax-errors=3 \
+ -Wno-error=unused-function -Wno-error=unused-label -Wno-error=unused-variable \
+ -Wno-error=unused-parameter -Wno-error=sign-compare \
+ -Wno-error=unused-const-variable -Wno-error=pointer-sign \
+ -Werror=implicit-fallthrough=1 \
+ -Wno-error=unused-but-set-variable \
+ -Wno-unused-function -Wno-unused-parameter \
+ -DPROJECT_VERSION=$(PROJECT_VERSION)
+
+LDFLAGS ?= -Wl,--no-demangle,--fatal-warnings
+
+ifndef NDEBUG
+ CFLAGS := $(CFLAGS) -ggdb -O0 -g3
+else
+ CFLAGS := $(CFLAGS) -ffunction-sections -fdata-sections -O2 "-DNDEBUG=1"
+ LDFLAGS := $(LDFLAGS) -Wl,--gc-sections,--as-needed
+endif
+
+OBJFILES ?= $(shell find src -name "*\.c" | sed -re "s;^src/(.*).c$$;build/\1.o;")
+
+INCDIRS ?= -Iinclude \
+ $(shell find src -type d | sed "s;^;-I;") \
+ -Iexternal/$(TOOLCHAIN)/include
+
+LIBSDIR ?= -Lexternal/$(TOOLCHAIN)/lib
+
+ifeq ($(BINEXT),.exe)
+ INCDIRS := $(INCDIRS) -Imingw64-include
+ LPCRE := -lpcre
+ LPCREPOSIX := -lpcreposix
+endif
+
+
+default: dist
+
+link: \
+ build/entrypoint/gateleenResclone$(BINEXT) \
+
+.PHONY: clean
+clean:
+ @echo "\n[\033[34mINFO \033[0m] Clean"
+ rm -rf build dist
+
+compile: $(OBJFILES)
+
+build/%.o: src/%.c
+ @echo "\n[\033[34mINFO \033[0m] Compile '$@'"
+ @mkdir -p $(shell dirname build/$*)
+ $(CC) -c -o $@ $< $(CFLAGS) $(INCDIRS) \
+
+build/entrypoint/gateleenResclone$(BINEXT): \
+ build/array/array.o \
+ build/entrypoint/gateleenResclone.o \
+ build/gateleen_resclone/gateleen_resclone.o \
+ build/log/log.o \
+ build/mime/mime.o \
+ build/util_term/util_term.o
+ @echo "\n[\033[34mINFO \033[0m] Linking '$@'"
+ @mkdir -p $(shell dirname $@)
+ $(CC) -o $@ $(LDFLAGS) $^ $(LIBSDIR) \
+ -larchive -lcurl -lyajl $(LPCREPOSIX) $(LPCRE) \
+
+
+.PHONY: dist
+dist: clean link
+ @echo "\n[\033[34mINFO \033[0m] Package"
+ @mkdir -p build dist
+ @rm -rf build/GateleenResclone-$(PROJECT_VERSION)
+ @echo
+ @bash -c 'if [[ -n `git status --porcelain` ]]; then echo "[ERROR] Worktree not clean as it should be (see: git status)"; exit 1; fi'
+ # Source bundle.
+ git archive --format=tar "--prefix=GateleenResclone-$(PROJECT_VERSION)/" HEAD | tar -C build -x
+ @echo
+ rm -f build/GateleenResclone-$(PROJECT_VERSION)/MANIFEST.INI
+ echo "version=$(PROJECT_VERSION)" >> build/GateleenResclone-$(PROJECT_VERSION)/MANIFEST.INI
+ echo "builtAt=$(shell date -Is)" >> build/GateleenResclone-$(PROJECT_VERSION)/MANIFEST.INI
+ git log -n1 HEAD | sed -re "s,^,; ," >> build/GateleenResclone-$(PROJECT_VERSION)/MANIFEST.INI
+ @echo
+ (cd build/GateleenResclone-$(PROJECT_VERSION) && find . -type f -exec md5sum -b {} \;) > build/checksums.md5
+ mv build/checksums.md5 build/GateleenResclone-$(PROJECT_VERSION)/checksums.md5
+ tar --owner=0 --group=0 -czf dist/GateleenResclone-$(PROJECT_VERSION).tgz -C build GateleenResclone-$(PROJECT_VERSION)
+ @echo
+ @# Executable bundle.
+ rm -rf build/GateleenResclone-$(PROJECT_VERSION)-$(TOOLCHAIN)
+ mkdir -p build/GateleenResclone-$(PROJECT_VERSION)-$(TOOLCHAIN)
+ mv -t build/GateleenResclone-$(PROJECT_VERSION)-$(TOOLCHAIN) \
+ build/GateleenResclone-$(PROJECT_VERSION)/README* \
+ build/GateleenResclone-$(PROJECT_VERSION)/MANIFEST.INI
+ mkdir build/GateleenResclone-$(PROJECT_VERSION)-$(TOOLCHAIN)/bin
+ mv -t build/GateleenResclone-$(PROJECT_VERSION)-$(TOOLCHAIN)/bin \
+ $(shell find build -name "*$(BINEXT)")
+ (cd build/GateleenResclone-$(PROJECT_VERSION)-$(TOOLCHAIN) && find . -type f -exec md5sum -b {} \;) > build/checksums.md5
+ mv build/checksums.md5 build/GateleenResclone-$(PROJECT_VERSION)-$(TOOLCHAIN)/checksums.md5
+ tar --owner=0 --group=0 -czf dist/GateleenResclone-$(PROJECT_VERSION)-$(TOOLCHAIN).tgz \
+ -C build GateleenResclone-$(PROJECT_VERSION)-$(TOOLCHAIN)
+ @echo "\n[\033[34mINFO \033[0m] DONE: Artifacts created and placed in 'dist'."
+ @# Dependency Bundle.
+ $(eval PCKROOT := build/GateleenResclone-$(PROJECT_VERSION)-$(TOOLCHAIN)-rt)
+ @bash -c 'if [ ".exe" = "$(BINEXT)" ]; then \
+ rm -rf ./$(PCKROOT); \
+ mkdir -p ./$(PCKROOT)/bin; \
+ cp external/$(TOOLCHAIN)/rt/bin/libarchive-13.dll $(PCKROOT)/bin/; \
+ cp external/$(TOOLCHAIN)/rt/bin/libcurl-4.dll $(PCKROOT)/bin/; \
+ cp external/$(TOOLCHAIN)/rt/bin/libiconv-2.dll $(PCKROOT)/bin/; \
+ cp external/$(TOOLCHAIN)/rt/bin/libpcreposix-0.dll $(PCKROOT)/bin/; \
+ cp external/$(TOOLCHAIN)/rt/bin/libpcre-1.dll $(PCKROOT)/bin/; \
+ cp external/$(TOOLCHAIN)/rt/bin/*pthread*.dll $(PCKROOT)/bin/libwinpthread-1.dll; \
+ (cd $(PCKROOT) && find . -type f -exec md5sum -b {} \;) > build/checksums.md5; \
+ mv build/checksums.md5 $(PCKROOT)/checksums.md5; \
+ tar --owner=0 --group=0 -czf dist/GateleenResclone-$(PROJECT_VERSION)-$(TOOLCHAIN)-rt.tgz \
+ -C build GateleenResclone-$(PROJECT_VERSION)-$(TOOLCHAIN)-rt; \
+ fi'
+ @echo
+ @echo See './dist/' for result.
diff --git a/README.txt b/README.txt
new file mode 100644
index 0000000..e70f632
--- /dev/null
+++ b/README.txt
@@ -0,0 +1,68 @@
+
+Gateleen Resclone
+============
+
+Commandline utility to clone subtrees from gateleen instances.
+
+From "gateleenResclone --help":
++------------------------------------------------------------------------------
+| Options:
+|
+| --pull|--push
+| Choose to download or upload.
+|
+| --url <url>
+| Root node of remote tree.
+|
+| --filter-part <path-filter>
+| Regex pattern applied as predicate to the path starting after the path
+| specified in '--url'. Each path segment will be handled as its
+| individual pattern. If there are longer paths to process, they will be
+| accepted, as long they at least start-with specified filter.
+| Example: /foo/[0-9]+/bar
+|
+| --filter-full <path-filter>
+| Nearly same as '--filter-part'. But paths with more segments than the
+| pattern, will be rejected.
+|
+| --file <path.tar>
+| (optional) Path to the archive file to read/write. Defaults to
+| stdin/stdout if ommitted.
+|
++------------------------------------------------------------------------------
+
+
+## Build
+
+1. Make sure dependencies are in place.
+2. Run "make dist"
+3. Result gets placed in 'dist' directory. There are multiple tarballs:
+ - Source.
+ - Binaries.
+ - Dependencies (will only contain those which got placed in external subdir)
+
+
+## Dependencies
+
+- libcurl 7.67.0 "http://curl.haxx.se/libcurl"
+- libyajl 2.1.0 "http://lloyd.github.io/yajl"
+- libarchive 3.3.3 "https://www.libarchive.org/"
+
+If you prefer to NOT cluttering your system with tons of never again used
+packages, you have the option to provide the dependencies from within the
+project tree itself. For this, place the files as described below.
+
++------------------------------------------------------------------------------
+| external/
+| '- lxGcc64/
+| |- include/
+| | |- curl/
+| | | '- {curl.h, easy.h, multi.h, ...}
+| | |- yajl/
+| | | '- {yajl_gen.h, yajl_parse.h, ...}
+| | |- archive.h
+| | '- archive_entry.h
+| '- lib/
+| '- {libarchive.a, libcurl.a, libyajl.a, libz.a}
++------------------------------------------------------------------------------
+
diff --git a/mingw64-include/regex.h b/mingw64-include/regex.h
new file mode 100644
index 0000000..d4c993d
--- /dev/null
+++ b/mingw64-include/regex.h
@@ -0,0 +1,6 @@
+#ifndef INCGUARD_4271049675ab4024740579a2e97a9704
+#define INCGUARD_4271049675ab4024740579a2e97a9704
+
+#include <pcreposix.h>
+
+#endif /* INCGUARD_4271049675ab4024740579a2e97a9704 */
diff --git a/src/array/array.c b/src/array/array.c
new file mode 100644
index 0000000..f657df5
--- /dev/null
+++ b/src/array/array.c
@@ -0,0 +1,25 @@
+
+/* This */
+#include "array.h"
+
+/* System */
+#include <stdlib.h>
+#include <string.h>
+
+
+ssize_t
+array_add( void*arr_ , size_t*count , size_t*capacity , void*elem , unsigned int elemSize , unsigned int allocStep )
+{
+ void** arr = arr_;
+ if( *count >= *capacity || *arr==NULL ){
+ size_t newLen = *count + allocStep;
+ void *newArr = realloc( *arr , newLen*elemSize );
+ if( ! newArr ){ return -1; }
+ *arr = newArr;
+ *capacity = newLen;
+ }
+ void *dstAddr = ((char*)*arr) + (*count)++ * elemSize;
+ memcpy( dstAddr , (char*)elem , elemSize );
+ return 0;
+}
+
diff --git a/src/array/array.h b/src/array/array.h
new file mode 100644
index 0000000..1fdd570
--- /dev/null
+++ b/src/array/array.h
@@ -0,0 +1,46 @@
+#ifndef INCGUARD_6dff7e511be9d0848804e8d1e2dc23a4
+#define INCGUARD_6dff7e511be9d0848804e8d1e2dc23a4
+
+#include <commonbase.h>
+
+#include <stdlib.h>
+#include <sys/types.h>
+
+
+#define TPL_ARRAY( name , type , allocStep ) \
+ static inline type* STR_CAT(array_malloc_,name)( size_t count ){ \
+ return malloc( count * sizeof(type) ); \
+ } \
+ static inline type* STR_CAT(array_calloc_,name)( size_t count ){ \
+ return calloc( count , sizeof(type) ); \
+ } \
+ static inline int STR_CAT(array_add_,name)( type**arr , size_t*arrCount , size_t*arrLen , type elem ){ \
+ return array_add( arr , arrCount , arrLen , &elem , sizeof(type) , allocStep ); \
+ } \
+
+
+/**
+ * @param arr
+ * WARN: Must be ptr to the array ptr. Not the array itself!
+ * @param arr_count
+ * Count of elements already present in the array.
+ * @param arr_capacity
+ * Count of elements where space is allocated for in this array.
+ * @param elem
+ * A pointer to the element to add.
+ * @param elemSize
+ * Size of the element to add. MUST always be the same size for all
+ * elements in this array.
+ * @param allocStep
+ * Count of elements to additionally realloc in case array runs out of
+ * space.
+ * @return
+ * =0: OK.
+ * <0: Error.
+ * >0: Reserved.
+ */
+ssize_t
+array_add( void*arr , size_t*arr_count , size_t*arr_capacity , void*elem , unsigned int elemSize , unsigned int allocStep );
+
+
+#endif /* INCGUARD_6dff7e511be9d0848804e8d1e2dc23a4 */
diff --git a/src/common/commonbase.c b/src/common/commonbase.c
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/src/common/commonbase.c
diff --git a/src/common/commonbase.h b/src/common/commonbase.h
new file mode 100644
index 0000000..0cc509b
--- /dev/null
+++ b/src/common/commonbase.h
@@ -0,0 +1,18 @@
+#ifndef INCGUARD_0835dec38b8927b0daeba484a1eb21e7
+#define INCGUARD_0835dec38b8927b0daeba484a1eb21e7
+
+
+#define _POSIX_C_SOURCE 200809L
+
+
+#ifdef _WIN32
+ /* Include stuff from strange systems here to prevent errors as:
+ * winsock2.h: error: #warning Please include winsock2.h before windows.h */
+ #include <winsock2.h>
+#endif
+
+
+typedef unsigned int uint_t;
+
+
+#endif /* INCGUARD_0835dec38b8927b0daeba484a1eb21e7 */
diff --git a/src/entrypoint/gateleenResclone.c b/src/entrypoint/gateleenResclone.c
new file mode 100644
index 0000000..ea5d920
--- /dev/null
+++ b/src/entrypoint/gateleenResclone.c
@@ -0,0 +1,16 @@
+
+/* Project */
+#include "gateleen_resclone.h"
+#include "util_term.h"
+
+
+int
+main( int argc, char**argv )
+{
+ int err;
+ util_term_init();
+ err = gateleenResclone_run( argc, argv );
+ if( err<0 ){ err = 0-err; }
+ return err>127 ? 1 : err;
+}
+
diff --git a/src/gateleen_resclone/gateleen_resclone.c b/src/gateleen_resclone/gateleen_resclone.c
new file mode 100644
index 0000000..6f03b64
--- /dev/null
+++ b/src/gateleen_resclone/gateleen_resclone.c
@@ -0,0 +1,926 @@
+
+/* This Unit */
+#include "gateleen_resclone.h"
+
+/* System */
+#include <assert.h>
+#include <errno.h>
+#include <libgen.h>
+#include <regex.h>
+#include <stdbool.h>
+#include <string.h>
+
+/* Libs */
+#include "archive.h"
+#include "archive_entry.h"
+#include <curl/curl.h>
+#include <yajl/yajl_parse.h>
+
+/* Project */
+#include "array.h"
+#include "log.h"
+#include "mime.h"
+#include "util_string.h"
+
+
+/* Types *********************************************************************/
+
+#define ERR_PARSE_DIR_LIST -2
+
+
+
+/** Operation mode. */
+typedef enum opMode {
+ MODE_NULL =0,
+ MODE_FETCH=1,
+ MODE_PUSH =2
+} opMode_t;
+
+
+/** Main module handle representing an instance. */
+typedef struct this {
+ enum opMode mode;
+ /** Base URL where to upload to / download from. */
+ char *url;
+ /** Array of regex patterns to use per segment. */
+ regex_t *filter;
+ size_t filter_len;
+ /** Says if only start or whole path needs to match the pattern. */
+ bool isFilterFull;
+ /* Path to archive file to use. Using stdin/stdout if NULL. */
+ char *file;
+} this_t;
+
+
+/** Closure for a download instructed by external caller. */
+typedef struct cls_dload {
+ struct this *this;
+ char *rootUrl;
+ struct archive *dstArchive;
+ struct archive_entry *tmpEntry;
+ char *archiveFile;
+ CURL *curl;
+} cls_dload_t;
+
+
+/** Closure for a collection (directory) download. */
+typedef struct cls_resourceDir {
+ struct cls_dload *dload;
+ yajl_handle yajl;
+ struct cls_resourceDir *parentDir;
+ short rspCode;
+ char *name;
+ char **childNames;
+ size_t childNames_count;
+ size_t childNames_size;
+} cls_resourceDir_t;
+
+
+/** Closure for a file download. */
+typedef struct cls_resourceFile {
+ struct cls_dload *dload;
+ size_t srcChunkIdx;
+ char *url;
+ char *buf;
+ int buf_len;
+ int buf_memSz;
+} cls_resourceFile_t;
+
+
+/** Closure for an upload instructed by external caller. */
+typedef struct cls_upload {
+ struct this *this;
+ char *rootUrl;
+ char *archiveFile;
+ struct archive *srcArchive;
+ CURL *curl;
+} cls_upload_t;
+
+
+/** Closure for a PUT of a single resource. */
+typedef struct cls_put {
+ struct cls_upload *upload;
+ /* Path (relative to rootUrl) of the resource to be uploaded. */
+ char *name;
+} cls_put_t;
+
+
+
+/* Operations ****************************************************************/
+
+
+TPL_ARRAY( str, char*, 16 );
+
+
+static void
+printHelp( void )
+{
+ char filename[sizeof(__FILE__)];
+ strcpy( filename, __FILE__ );
+ printf("%s%s%s",
+ "\n"
+ " ", basename(filename), " - " STR_QUOT(PROJECT_VERSION) "\n"
+ "\n"
+ "Options:\n"
+ "\n"
+ " --pull|--push\n"
+ " Choose to download or upload.\n"
+ "\n"
+ " --url <url>\n"
+ " Root node of remote tree.\n"
+ "\n"
+ " --filter-part <path-filter>\n"
+ " Regex pattern applied as predicate to the path starting after the path\n"
+ " specified in '--url'. Each path segment will be handled as its\n"
+ " individual pattern. If there are longer paths to process, they will be\n"
+ " accepted, as long they at least start-with specified filter.\n"
+ " Example: /foo/[0-9]+/bar\n"
+ "\n"
+ " --filter-full <path-filter>\n"
+ " Nearly same as '--filter-part'. But paths with more segments than the\n"
+ " pattern, will be rejected.\n"
+ "\n"
+ " --file <path.tar>\n"
+ " (optional) Path to the archive file to read/write. Defaults to\n"
+ " stdin/stdout if ommitted.\n"
+ "\n"
+ );
+}
+
+
+static int
+parseArgs( int argc, char**argv, opMode_t*mode, char**url, regex_t**filter, size_t*filter_cnt,
+ bool*isFilterFull, char**file )
+{
+ ssize_t err, ret=0;
+ char *filterRaw = NULL;
+ if( argc == -1 ){ // -1 indicates the call to free our resources. So simply jump
+ goto fail; // to 'fail' because that has the same effect.
+ }
+ *mode = 0;
+ *url = NULL;
+ *filter = NULL;
+ *filter_cnt = 0;
+ *isFilterFull = false;
+ *file = NULL;
+
+ for( int i=1 ; i<argc ; ++i ){
+ char *arg = argv[i];
+ if( !strcmp(arg,"--help") ){
+ printHelp();
+ ret = -1; goto fail;
+ }else if( !strcmp(arg,"--pull") ){
+ if( *mode ){
+ printf("%s\n","ERROR: Mode already specified. Won't set '--pull'.");
+ ret = -1; goto fail;
+ }
+ *mode = MODE_FETCH;
+ }else if( !strcmp(arg,"--push") ){
+ if( *mode ){
+ printf("%s\n","ERROR: Mode already specified. Won't set '--push'.");
+ ret = -1; goto fail;
+ }
+ *mode = MODE_PUSH;
+ }else if( !strcmp(arg,"--url") ){
+ if(!( arg=argv[++i]) ){
+ printf("%s\n","ERROR: Arg '--url' needs a value.");
+ ret = -1; goto fail;
+ }
+ *url = arg;
+ }else if( !strcmp(arg,"--filter-full") ){
+ if(!( arg=argv[++i] )){
+ printf("%s\n","ERROR: Arg '--filter-full' needs a value.");
+ ret = -1; goto fail; }
+ if( filterRaw ){
+ printf("%s\n","ERROR: Cannot use '--filter-full' because a filter is already set.");
+ ret=-1; goto fail; }
+ filterRaw = arg;
+ *isFilterFull = true;
+ }else if( !strcmp(arg,"--filter-part") ){
+ if(!( arg=argv[++i] )){
+ printf("%s\n","ERROR: Arg '--filter-part' needs a value.");
+ ret = -1; goto fail; }
+ if( filterRaw ){
+ printf("%s\n","ERROR: Cannot use '--filter-part' because a filter is already set.");
+ ret=-1; goto fail; }
+ filterRaw = arg;
+ *isFilterFull = false;
+ }else if( !strcmp(arg,"--file") ){
+ if(!( arg=argv[++i]) ){
+ printf("%s\n","ERROR: Arg '--file' needs a value.");
+ ret = -1; goto fail;
+ }
+ *file = arg;
+ }else{
+ printf("%s%s\n", "ERROR: Unknown arg ",arg);
+ ret = -1; goto fail;
+ }
+ }
+
+ if( *mode==0 ){
+ printf("%s\n", "ERROR: One of --push or --pull required.");
+ ret = -1; goto fail;
+ }
+
+ if( *url==NULL ){
+ printf("%s\n", "ERROR: Arg --url missing.");
+ ret = -1; goto fail;
+ }
+ uint_t urlFromArgs_len = strlen( *url );
+ if( ((*url)[urlFromArgs_len-1]) != '/' ){
+ char *urlFromArgs = *url;
+ uint_t url_len = urlFromArgs_len + 1;
+ *url = malloc(url_len+1); /* TODO: Should we free this? */
+ memcpy( *url, urlFromArgs, urlFromArgs_len );
+ (*url)[url_len-1] = '/';
+ (*url)[url_len] = '\0';
+ }else{
+ *url = strdup( *url ); // Make our own string (so we've no fear to call
+ // free on it later)
+ }
+
+ if( filterRaw ){
+ uint_t buf_len = strlen( filterRaw );
+ char *buf = malloc( 1+ buf_len +2 );
+ buf[0] = '_'; // <- Match whole segment.
+ memcpy( buf+1, filterRaw, buf_len+1 );
+ char *beg, *end;
+ size_t filter_cap = 0;
+ end = buf+1; // <- Initialize at begin for 1st iteration.
+ for( uint_t iSegm=0 ;; ++iSegm ){
+ for( beg=end ; *beg=='/' ; ++beg ); // <- Search for begin and
+ for( end=beg ; *end!='/' && *end!='\0' ; ++end ); // <- end of current segment.
+ char origBeg = beg[-1];
+ char origSep = *end;
+ char origNext = end[1];
+ beg[-1] = '^'; // <- Add 'match-start' so we MUST match whole segment.
+ *end = '$'; // <- Add 'match-end' so we must match whole segment.
+ end[1] = '\0'; // <- Temporary terminate to compile segment only.
+ if( iSegm >= filter_cap ){
+ filter_cap += 8;
+ *filter = realloc( *filter, filter_cap*sizeof(**filter) );
+ //LOG_DEBUG("%s%u%s%p\n", "realloc( NULL, ", filter_cap*sizeof(**filter)," ) -> ", *filter );
+ if( !*filter ){ LOG_ERROR("%s%d%s\n","realloc(",filter_cap*sizeof(**filter),")"); ret=-ENOMEM; goto fail; }
+ }
+ //LOG_DEBUG("%s%d%s%s%s\n", "filter[", iSegm, "] -> '", beg-1, "'");
+ err = regcomp( (*filter)+iSegm, beg-1, REG_EXTENDED);
+ if( err ){ LOG_ERROR("%s%s%s%d\n","regcomp(",beg,") -> ", err); return -1; }
+ // Restore surrounding stuff.
+ beg[-1] = origBeg;
+ *end = origSep; // <- Restore tmp 'end-of-match' ($).
+ end[1] = origNext; // <- Restore tmp termination.
+ if( *end=='\0' ){ // EOF
+ *filter_cnt = iSegm +1;
+ *filter = realloc( *filter, *filter_cnt *sizeof(**filter) ); // Trim result.
+ free( buf ); buf=NULL;
+ break;
+ }
+ }
+ }
+
+ if( *mode==MODE_PUSH && *filter ){
+ printf("%s\n", "ERROR: Filtering not supported for push mode.");
+ ret = -1; goto fail;
+ }
+
+ return 0;
+ fail:
+ free( *url ); *url=NULL;
+ for( uint_t i=0 ; i<*filter_cnt ; ++i ){
+ regfree( &(filter[0][i]) );
+ }
+ *filter_cnt = 0;
+ free( *filter ); *filter=NULL;
+ return ret;
+}
+
+
+static int
+onYajlString(void*cls_resourceDir_, const unsigned char*str_, size_t str_len)
+{
+ ssize_t err, ret=str_len;
+ cls_resourceDir_t *resourceDir = cls_resourceDir_;
+ const char *str = (void*)str_;
+
+ //LOG_DEBUG("%s%s%.*s%s\n", __func__, "(): Add dirent '",(int)str_len,str,"'");
+
+ char *newStr = malloc( str_len +1 );
+ memcpy( newStr, str, str_len );
+ newStr[str_len] = '\0';
+ err = array_add_str( &resourceDir->childNames, &resourceDir->childNames_count, &resourceDir->childNames_size, newStr );
+ //err = arrStr_add( &resourceDir->childNames , &resourceDir->childNames_count , &resourceDir->childNames_size , &newStr );
+ if( err ){ assert(!err); ret=-1; goto endFn; }
+
+ endFn:
+ return ret;
+}
+
+
+static size_t
+onCurlDirRsp( char*buf , size_t size , size_t nmemb , void*cls_resourceDir_ )
+{
+ ssize_t err, ret = size*nmemb;
+ cls_resourceDir_t *resourceDir = cls_resourceDir_;
+ cls_dload_t *dload = resourceDir->dload;
+ CURL *curl = dload->curl;
+ const size_t buf_len = size * nmemb;
+ unsigned char *bufUnsigned = (void*)buf;
+
+ long rspCode;
+ curl_easy_getinfo( curl, CURLINFO_RESPONSE_CODE, &rspCode );
+ resourceDir->rspCode = rspCode;
+ if( rspCode != 200 ){
+ goto endFn;
+ }
+
+ err = yajl_parse(resourceDir->yajl, bufUnsigned, buf_len);
+ if( err ){
+ unsigned char *errMsg = yajl_get_error(resourceDir->yajl, /*verbose=*/1, bufUnsigned, buf_len);
+ LOG_WARN("%s%ld%s%s\n", "Failed to parse dir listing (code ",err,"): ", errMsg);
+ yajl_free_error( resourceDir->yajl , errMsg );
+ resourceDir->rspCode = ERR_PARSE_DIR_LIST;
+ goto endFn;
+ }
+
+ endFn:
+ return ret;
+}
+
+
+static size_t
+onResourceChunk( char*buf , size_t size , size_t nmemb , void*cls_resourceFile_ )
+{
+ int buf_len = size * nmemb;
+ int ret = buf_len;
+ cls_resourceFile_t *resourceFile = cls_resourceFile_;
+
+ int avail = resourceFile->buf_memSz - resourceFile->buf_len;
+ if( avail <= buf_len ){
+ resourceFile->buf_memSz += buf_len - avail +1;
+ resourceFile->buf = realloc( resourceFile->buf , resourceFile->buf_memSz );
+ }
+ char *it = resourceFile->buf + resourceFile->buf_len;
+ memcpy( it , buf , buf_len ); it+=buf_len;
+ *it = '\0';
+ resourceFile->buf_len = it - resourceFile->buf;
+
+ return ret;
+}
+
+
+static ssize_t
+collectResourceIntoMemory( cls_resourceFile_t*resourceFile , char*url )
+{
+ ssize_t err, ret=0;
+ cls_dload_t *dload = resourceFile->dload;
+ CURL *curl = dload->curl;
+
+ err = CURLE_OK!= curl_easy_setopt(curl, CURLOPT_URL, url)
+ || CURLE_OK!= curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 0L )
+ || CURLE_OK!= curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, onResourceChunk)
+ || CURLE_OK!= curl_easy_setopt(curl, CURLOPT_WRITEDATA, resourceFile)
+ ;
+ if( err ){ assert(!err); ret=-1; goto finally; }
+
+ err = curl_easy_perform( curl );
+ if( err!=CURLE_OK ){
+ LOG_ERROR("%s%s%s%s%ld%s%s\n", __func__, "(): '",url,"' (code ", err, "): ", curl_easy_strerror(err));
+ ret=-1; goto finally;
+ }
+
+ finally:
+ return ret;
+}
+
+
+static ssize_t
+copyBufToArchive( cls_resourceFile_t*resourceFile )
+{
+ ssize_t err, ret=0;
+ cls_dload_t *dload = resourceFile->dload;
+ char *fileName = resourceFile->url + strlen(resourceFile->dload->rootUrl);
+
+ if( ! dload->dstArchive ){
+ // Setup archive if not setup yet.
+ dload->dstArchive = archive_write_new();
+ err = archive_write_set_format_pax_restricted( dload->dstArchive )
+ || archive_write_open_filename( dload->dstArchive , dload->archiveFile )
+ ;
+ if( err ){
+ LOG_ERROR("%s%s%s\n", __func__, "(): Failed to setup tar output: ", archive_error_string(dload->dstArchive));
+ ret = -1; goto endFn;
+ }
+ }
+
+ if( ! dload->tmpEntry ){
+ dload->tmpEntry = archive_entry_new();
+ }else{
+ dload->tmpEntry = archive_entry_clear( dload->tmpEntry );
+ }
+ archive_entry_set_pathname(dload->tmpEntry, fileName);
+ archive_entry_set_filetype(dload->tmpEntry, AE_IFREG);
+ archive_entry_set_size(dload->tmpEntry, resourceFile->buf_len);
+ archive_entry_set_perm(dload->tmpEntry, 0644);
+ err = archive_write_header(dload->dstArchive, dload->tmpEntry);
+ if( err ){ ret=-1; goto endFn; }
+
+ ssize_t written = archive_write_data( dload->dstArchive , resourceFile->buf , resourceFile->buf_len );
+ if( written < 0 ){
+ LOG_ERROR("%s%s%s\n", __func__, "Failed to archive_write_data: ", archive_error_string(dload->dstArchive));
+ ret = -1; goto endFn;
+ }else if( written != resourceFile->buf_len ){
+ LOG_ERROR("%s%s%u%s%lu\n", __func__, "(): archive_write_data failed to write all ", resourceFile->buf_len, " bytes. Instead it wrote ", written);
+ ret = -1; goto endFn;
+ }
+ resourceFile->buf_len = 0;
+
+ endFn:
+ return ret;
+}
+
+
+/** @return 0:Reject, 1:Accept, <0:ERROR */
+static ssize_t
+pathFilterAcceptsEntry( cls_dload_t*dload, cls_resourceDir_t*resourceDir, size_t childIdx )
+{
+ ssize_t err, ret=1; // <- Accept per default.
+ char *name = resourceDir->childNames[childIdx];
+ uint_t name_len = strlen( name );
+
+ if( dload->this->filter ){
+ // Count parents to find correct regex to apply.
+ uint_t idx = 0;
+ for( cls_resourceDir_t*it=resourceDir->parentDir ; it ; it=it->parentDir ){ ++idx; }
+ // Check if we even have such a long filter at all.
+ if( idx >= dload->this->filter_len ){
+ if( dload->this->isFilterFull ){
+ //LOG_DEBUG("%s\n", "Path longer than --filter-full -> reject.");
+ ret = 0; goto finally;
+ }else{
+ //LOG_DEBUG("%s\n", "Path longer than --filter-part -> accept.");
+ ret = 1; goto finally;
+ }
+ }
+ // We have a regex. Setup the check.
+ bool restoreEndSlash = false;
+ if( name[name_len-1]=='/' ){
+ restoreEndSlash = true;
+ name[name_len-1] = '\0';
+ }
+ //LOG_DEBUG("%s%u%s%s%s\n", "idx=",idx," name='", name, "'");
+ regex_t *filterArr = dload->this->filter;
+ regex_t *r = filterArr + (idx);
+ err = regexec( r, name, 0, 0, 0 );
+ if( ! err ){
+ //LOG_DEBUG("%s\n", "Segment accepted by filter.");
+ ret = 1;
+ }else if( err==REG_NOMATCH ){
+ //LOG_DEBUG("%s\n", "Segment rejected by filter.");
+ ret = 0;
+ }else{
+ LOG_ERROR("%s%.*s%s%d\n", "regexec(rgx, '",(int)name_len,name,"') -> ", err );
+ ret = -1;
+ }
+ if( restoreEndSlash ){
+ name[name_len-1] = '/';
+ }
+ goto finally;
+ }
+ finally: return ret;
+}
+
+
+/** Gets called for every resource to scan/download.
+ * HINT: Gets called recursively. */
+static ssize_t
+gateleenResclone_download( cls_dload_t*dload , cls_resourceDir_t*parentResourceDir , char*entryName )
+{
+ ssize_t err, ret=0;
+ char *url = NULL;
+ int url_len = 0;
+ int resUrl_len = 0;
+ cls_resourceDir_t *resourceDir = NULL;
+ cls_resourceFile_t *resourceFile = NULL;
+
+ if( !entryName ){
+ // Is the case when its the root call and not a recursive one.
+ entryName = dload->rootUrl;
+ }
+
+ // Stack-Alloc resourceDir-closure.
+ cls_resourceDir_t _1={0}; resourceDir =&_1;
+ resourceDir->dload = dload;
+ resourceDir->parentDir = parentResourceDir;
+ resourceDir->name = strdup( entryName );
+
+ static yajl_callbacks yajlCbacks = {0};
+ yajlCbacks.yajl_string = onYajlString;
+ resourceDir->yajl = yajl_alloc( &yajlCbacks , NULL , resourceDir );
+
+ // Configure client
+ {
+ url_len = 0;
+ for( cls_resourceDir_t*d=resourceDir ; d ; d=d->parentDir ){
+ int len = strlen(d->name) - strspn(d->name, "/");
+ url_len += len;
+ }
+ url = malloc( url_len +1 /*MightPreventReallocLaterForName*/+24 );
+ char *u = url + url_len;
+ for( cls_resourceDir_t*d=resourceDir ; d ; d=d->parentDir ){
+ char *name = d->name + strspn( d->name , "/" );
+ int name_len = strlen(name);
+ memcpy( u-name_len , name , name_len ); u-=name_len;
+ }
+ url[url_len] = '\0';
+ //LOG_DEBUG("%s%s%s%s\n", __func__, "(): URL '",url,"'");
+ err = CURLE_OK != curl_easy_setopt(dload->curl, CURLOPT_URL, url)
+ || CURLE_OK != curl_easy_setopt(dload->curl, CURLOPT_FOLLOWLOCATION, 0L )
+ || CURLE_OK != curl_easy_setopt(dload->curl, CURLOPT_WRITEFUNCTION, onCurlDirRsp)
+ || CURLE_OK != curl_easy_setopt(dload->curl, CURLOPT_WRITEDATA, resourceDir)
+ ;
+ if( err ){ assert(!err); ret=-1; goto finally; }
+ }
+
+ err = curl_easy_perform( dload->curl );
+ if( err!=CURLE_OK ){
+ LOG_ERROR("%s%s%s%s%ld%s%s\n", __func__, "(): '",url,"' (code ", err, "): ", curl_easy_strerror(err));
+ ret=-1; goto finally;
+ }
+
+ if( resourceDir->rspCode == ERR_PARSE_DIR_LIST ){
+ goto finally; // Already logged by sub-ctxt. Simply skip to next entry.
+ }
+ if( resourceDir->rspCode != 200 ){
+ // Ugh? Just one request earlier, server said there's a directory on
+ // that URL. Never mind. Just skip it and at least download the other
+ // stuff.
+ LOG_WARN("%s%d%s%s%s\n", "Skip HTTP ", resourceDir->rspCode ," -> '",url,"'");
+ goto finally;
+ }
+
+ err = yajl_complete_parse( resourceDir->yajl );
+ if( err ){
+ LOG_ERROR("%s\n", "Failed to parse response json.");
+ assert( !err );
+ ret=-1; goto finally;
+ }
+
+ cls_resourceFile_t _2={0}; resourceFile =&_2;
+ resourceFile->dload = dload;
+ for( unsigned i=0 ; i<resourceDir->childNames_count ; ++i ){
+ char *name = resourceDir->childNames[i];
+ int name_len = strlen( name );
+
+ err = pathFilterAcceptsEntry( dload, resourceDir, i );
+ if( err<0 ){ // ERROR
+ ret = err; goto finally;
+ }else if( err==0 ){ // REJECT
+ LOG_INFO("%s%s%s%s\n", "Skip '", url,resourceDir->childNames[i],"' (filtered)");
+ continue;
+ }else{ // ACCEPT
+ // Go ahead.
+ }
+
+ if( name[name_len-1]=='/' ){
+ LOG_DEBUG("%s%s%s%s\n", "Scan '", url, name,"'");
+ err = gateleenResclone_download( dload , resourceDir , name );
+ if( err ){ ret=err; goto finally; }
+ }
+ else{
+ int requiredLen = url_len + 1/*slash*/ + name_len;
+ if( resUrl_len < requiredLen ){
+ resourceFile->url = realloc( resourceFile->url , requiredLen +1 );
+ resUrl_len = requiredLen;
+ }
+ sprintf(resourceFile->url, "%s%s", url , name );
+ LOG_INFO("%s%s%s\n", "Download '", resourceFile->url, "'");
+ resourceFile->buf_len = 0; // <- Reset before use.
+ collectResourceIntoMemory( resourceFile , resourceFile->url );
+ copyBufToArchive( resourceFile );
+ }
+ }
+
+ finally:
+ if( resourceFile ){
+ free(resourceFile->buf); resourceFile->buf = NULL;
+ free( resourceFile->url ); resourceFile->url = NULL;
+ }
+ if( resourceDir ){
+ yajl_free( resourceDir->yajl ); resourceDir->yajl = NULL;
+ free( resourceDir->name ); resourceDir->name = NULL;
+ for( size_t i=0 ; i<resourceDir->childNames_count ; ++i ){
+ free( resourceDir->childNames[i] ); resourceDir->childNames[i] = NULL;
+ }
+ free( resourceDir->childNames ); resourceDir->childNames = NULL;
+ }
+ free( url );
+ return ret;
+}
+
+
+static void
+this_free( this_t*this )
+{
+ if( this==NULL ) return;
+
+ //for( dirEntry_t*dirent=this->dirStack ; dirent ;){
+ // for( size_t iChild=0 ; iChild<dirent->names_count ; ++iChild ){
+ // free( dirent->names[iChild] ); dirent->names[iChild] = NULL;
+ // }
+ // free( dirent->names ); dirent->names = NULL;
+ // free( dirent->name ); dirent->name = NULL;
+ // { void*n=dirent->parent; free(dirent); dirent=n; }
+ //}
+
+ free( this );
+}
+
+static this_t*
+this_alloc()
+{
+ ssize_t err;
+ this_t *this = NULL;
+
+ err = curl_global_init( CURL_GLOBAL_ALL );
+ if( err ){ assert(!err); goto fail; }
+
+ this = calloc(1, sizeof(*this) );
+
+ return this;
+ fail:
+ this_free( this );
+ return NULL;
+}
+
+
+static size_t
+onUploadChunkRequested( char*buf , size_t size , size_t count , void*cls_put_ )
+{
+ cls_put_t *put = cls_put_;
+ cls_upload_t *upload = put->upload;
+ const size_t buf_len = size * count;
+ ssize_t ret = buf_len;
+
+ ssize_t readLen = archive_read_data(upload->srcArchive, buf, buf_len);
+ //LOG_DEBUG("%s%lu%s\n", "Cpy ",readLen," bytes.");
+ if( readLen<0 ){
+ LOG_ERROR("%s%ld%s%s\n", "Failed to read from archive (code ",readLen,"): ", archive_error_string(upload->srcArchive));
+ assert(0); ret=-1; goto endFn;
+ }
+ else if( readLen>0 ){
+ // Regular read. Data already written to 'buf'. Only need to adjust
+ // return val.
+ ret = readLen;
+ }
+ else{ // readLen==0 -> EOF
+ ret = 0; // Nothing more to read.
+ }
+
+
+ endFn:
+ //LOG_DEBUG("%s%s%ld\n", __func__, "() -> ", ret);
+ return ret>=0 ? ret : CURL_READFUNC_ABORT;
+}
+
+
+static ssize_t
+addContentTypeHeader( cls_put_t*put , struct curl_slist *reqHdrs )
+{
+ ssize_t err, ret=0;
+ char *contentTypeHdr = NULL;
+ cls_upload_t *upload = put->upload;
+ const char *name = put->name;
+
+ uint_t name_len = strlen( put->name );
+ // Find file extension.
+ const char *ext = name + name_len;
+ for(; ext>name && *ext!='.' && *ext!='/' ; --ext );
+ // Convert it to mime type.
+ const char *mimeType;
+ if( *ext=='.' ){
+ mimeType = fileExtToMime( ext+1 ); // <- +1, to skip the (useless) dot.
+ if( mimeType ){
+ LOG_DEBUG("%s%s%s%s%s\n", "Resolved file ext '", ext+1,"' to mime '", mimeType?mimeType:"<null>", "'.");
+ }
+ }
+ else if( *ext=='/' || ext==name || *ext=='\0' ){ // TODO Explain why 0x00.
+ mimeType = "application/json";
+ LOG_DEBUG("%s\n", "No file extension. Fallback to json (gateleen default)");
+ }
+ else{
+ mimeType = NULL;
+ }
+ if( mimeType==NULL ){
+ LOG_DEBUG("%s%s%s\n", "Unknown file extension '", ext+1, "'. Will NOT add Content-Type header.");
+ mimeType = ""; // <- Need to 'remove' header. To do this, pass an empty value to curl.
+ }
+ uint_t mimeType_len = strlen( mimeType );
+ static const char contentTypePrefix[] = "Content-Type: ";
+ static const uint_t contentTypePrefix_len = sizeof(contentTypePrefix)-1;
+ contentTypeHdr = malloc( contentTypePrefix_len + mimeType_len +1 );
+ memcpy( contentTypeHdr , contentTypePrefix , contentTypePrefix_len );
+ memcpy( contentTypeHdr+contentTypePrefix_len , mimeType , mimeType_len+1 );
+ reqHdrs = curl_slist_append( reqHdrs, contentTypeHdr );
+ err = curl_easy_setopt( upload->curl, CURLOPT_HTTPHEADER, reqHdrs );
+ if( err ){ assert(!err); ret=-1; goto endFn; }
+
+ endFn:
+ free( contentTypeHdr );
+ return ret;
+}
+
+
+static ssize_t
+httpPutEntry( cls_put_t*put )
+{
+ ssize_t err, ret = 0;
+ cls_upload_t *upload = put->upload;
+ char *url = NULL;
+ struct curl_slist *reqHdrs = NULL;
+
+ int rootUrl_len = strlen(upload->rootUrl);
+ if( upload->rootUrl[rootUrl_len-1]=='/' ){
+ rootUrl_len -= 1;
+ }
+ int url_len = strlen(upload->rootUrl) + strlen(put->name);
+ url = malloc( url_len +2 );
+ sprintf(url, "%.*s/%s", rootUrl_len,upload->rootUrl, put->name);
+ err = CURLE_OK != curl_easy_setopt(upload->curl, CURLOPT_URL, url )
+ || addContentTypeHeader( put , reqHdrs )
+ ;
+ if( err ){ assert(!err); ret=-1; goto endFn; }
+
+ LOG_INFO("%s%s%s\n", "Upload '",url,"'");
+ err = curl_easy_perform( upload->curl );
+ if( err!=CURLE_OK ){
+ LOG_ERROR("%s%s%s%ld%s%s\n", "PUT '",url,"' (code ", err, "): ", curl_easy_strerror(err));
+ ret=-1; goto endFn;
+ }
+ long rspCode;
+ curl_easy_getinfo(upload->curl, CURLINFO_RESPONSE_CODE, &rspCode);
+ if( rspCode<=199 || rspCode>=300 ){
+ LOG_WARN("%s%ld%s%s%s\n", "Got RspCode ", rspCode, " for 'PUT ", url, "'");
+ }else{
+ //LOG_DEBUG("%s%ld%s%s%s\n", "Got RspCode ", rspCode, " for 'PUT ", url, "'");
+ }
+
+ endFn:
+ curl_slist_free_all( reqHdrs );
+ free( url );
+ return ret;
+}
+
+
+static ssize_t
+readArchive( cls_upload_t*upload )
+{
+ ssize_t err, ret=0;
+ cls_put_t *put = NULL;
+
+ upload->srcArchive = archive_read_new();
+ if( ! upload->srcArchive ){ assert(upload->srcArchive); ret=-1; goto endFn; }
+
+ const int blockSize = 16384; // <- Because curl examples use this value too.
+ err = archive_read_support_format_all( upload->srcArchive )
+ || archive_read_open_filename( upload->srcArchive , upload->archiveFile , blockSize )
+ ;
+ if( err ){
+ LOG_ERROR("%s%ld%s%s\n", "Failed to open src archive (code ",err,"): ", curl_easy_strerror(err) );
+ ret=-1; goto endFn;
+ }
+
+ err = curl_easy_setopt( upload->curl, CURLOPT_UPLOAD, 1L)
+ || curl_easy_setopt( upload->curl, CURLOPT_READFUNCTION, onUploadChunkRequested )
+ ;
+ if( err ){ assert(!err); ret=-1; goto endFn; }
+ for( struct archive_entry*entry ; archive_read_next_header(upload->srcArchive,&entry)==ARCHIVE_OK ;){
+ const char *name = archive_entry_pathname( entry );
+ int ftype = archive_entry_filetype( entry );
+ if( ftype == AE_IFDIR ){
+ continue; // Ignore dirs because gateleen doesn't know 'dirs' as such.
+ }
+ if( ftype != AE_IFREG ){
+ LOG_WARN("%s%s%s\n", "Ignore non-regular file '", name, "'");
+ continue;
+ }
+ //LOG_DEBUG("%s%s%s\n", "Reading '",name,"'");
+ cls_put_t _1={
+ .upload = upload,
+ .name = (char*)name
+ }; put =&_1;
+ err = curl_easy_setopt( upload->curl, CURLOPT_READDATA, put )
+ || httpPutEntry( put );
+ //curl = upload->curl; // Sync back. TODO: Still needed?
+ if( err ){ ret=-1; goto endFn; }
+ }
+
+ endFn:
+ return ret;
+}
+
+
+static ssize_t
+pull( this_t*this )
+{
+ ssize_t err, ret = 0;
+ cls_dload_t *dload = NULL;
+
+ if( this->file==NULL && isatty(1) ){
+ LOG_ERROR("%s\n", "Are you sure you wanna write binary content to tty?");
+ ret = -1; goto finally;
+ }
+
+ cls_dload_t _1={0}; dload =&_1;
+ dload->this = this;
+ dload->rootUrl = this->url;
+ dload->archiveFile = this->file;
+ dload->curl = curl_easy_init();
+ if( ! dload->curl ){
+ LOG_ERROR("%s\n", "curl_easy_init() -> NULL");
+ ret = -1; goto finally;
+ }
+
+ err = gateleenResclone_download( dload , NULL , NULL );
+ if( err ){ ret=-1; goto finally; }
+
+ if( dload->dstArchive && archive_write_close(dload->dstArchive) ){
+ LOG_ERROR("%s%s%ld%s%s\n", __func__, "(): archive_write_close failed (code ", err, "): ", archive_error_string(dload->dstArchive));
+ ret = -1; goto finally;
+ }
+
+ finally:
+ if( dload ){
+ curl_easy_cleanup( dload->curl );
+ archive_entry_free( dload->tmpEntry ); dload->tmpEntry = NULL;
+ archive_write_free( dload->dstArchive ); dload->dstArchive = NULL;
+ }
+ return ret;
+}
+
+
+static ssize_t
+push( this_t*this )
+{
+ ssize_t err, ret = 0;
+ cls_upload_t *upload = NULL;
+
+ cls_upload_t _1={0}; upload =&_1;
+ upload->this = this;
+ upload->archiveFile = this->file;
+ upload->rootUrl = this->url;
+ upload->curl = curl_easy_init();
+ if( ! upload->curl ){
+ LOG_ERROR("%s\n", "curl_easy_init() -> NULL" );
+ ret = -1; goto finally;
+ }
+
+ err = readArchive( upload );
+ if( err ){ ret=-1; goto finally; }
+
+ finally:
+ if( upload ){
+ curl_easy_cleanup( upload->curl );
+ archive_read_free( upload->srcArchive );
+ }
+ return ret;
+}
+
+
+ssize_t
+gateleenResclone_run( int argc , char**argv )
+{
+ ssize_t err, ret=0;
+ this_t *this = NULL;
+
+ this = this_alloc();
+ if( this==NULL ){ ret=-1; goto finally; }
+
+ err = parseArgs( argc, argv, &this->mode, &this->url, &this->filter, &this->filter_len,
+ &this->isFilterFull, &this->file );
+ if( err ){ ret=-1; goto finally; }
+
+ if( this->mode == MODE_FETCH ){
+ ret = pull(this); goto finally;
+ }else if( this->mode == MODE_PUSH ){
+ ret = push(this); goto finally;
+ }else{
+ ret = -1; goto finally;
+ }
+
+ finally:
+ parseArgs( -1, argv, &this->mode, &this->url, &this->filter, &this->filter_len,
+ &this->isFilterFull, &this->file );
+ this->mode=MODE_NULL; this->url=NULL; this->file=NULL;
+ this_free( this );
+ return ret;
+}
+
+
+int
+gateleenResclone_main( int argc , char**argv )
+{
+ int ret;
+ ret = gateleenResclone_run( argc , argv );
+ return ret<0||ret>127 ? 1 : ret;
+}
+
diff --git a/src/gateleen_resclone/gateleen_resclone.h b/src/gateleen_resclone/gateleen_resclone.h
new file mode 100644
index 0000000..43eebcb
--- /dev/null
+++ b/src/gateleen_resclone/gateleen_resclone.h
@@ -0,0 +1,16 @@
+#ifndef INCGUARD_d16bcf26aca7174fb8aae7641c828007
+#define INCGUARD_d16bcf26aca7174fb8aae7641c828007
+
+#include "commonbase.h"
+
+#include <sys/types.h>
+
+
+/** @return
+ * Zero on success, negative values otherwise. Positive values are
+ * reserved. */
+ssize_t
+gateleenResclone_run( int argc , char**argv );
+
+
+#endif /* INCGUARD_d16bcf26aca7174fb8aae7641c828007 */
diff --git a/src/log/log.c b/src/log/log.c
new file mode 100644
index 0000000..a390421
--- /dev/null
+++ b/src/log/log.c
@@ -0,0 +1,31 @@
+
+#include "log.h"
+
+/* System */
+#include <stdarg.h>
+#include <stdio.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+
+
+void log_asdfghjklqwertzu( const char*level, const char*cLvl, const char*file, int line, const char*fmt, ... )
+{
+ va_list args;
+ va_start( args, fmt );
+ int isTTY = isatty(2);
+ const char *cRst = isTTY ? "\033[0m" : "";
+ char *cTxt = isTTY ? "\033[90m" : "";
+ cLvl = isTTY ? cLvl : "";
+ char tBuf[20];
+ const time_t t = time(0);
+ const char *tfmt = "%Y-%m-%d_%H:%M:%S";
+ if( isTTY ){ tfmt += 9; }
+ strftime( tBuf,sizeof(tBuf), tfmt, localtime(&t) );
+ const char *fileOnly = strrchr(file, '/') +1;
+ fprintf( stderr, "[%s%s%s %s%s%s %s%s:%d%s] ",
+ cTxt,tBuf,cRst, cLvl,level,cRst , cTxt,fileOnly,line,cRst );
+ vfprintf( stderr, fmt, args );
+ va_end( args );
+}
+
diff --git a/src/log/log.h b/src/log/log.h
new file mode 100644
index 0000000..82c3f34
--- /dev/null
+++ b/src/log/log.h
@@ -0,0 +1,23 @@
+#ifndef INCGUARD_540e2cd36c1ba21909b922d45a94b7f7
+#define INCGUARD_540e2cd36c1ba21909b922d45a94b7f7
+
+#include "commonbase.h"
+
+
+#define LOG_FATAL( ... ) log_asdfghjklqwertzu("FATAL","\033[31m",__FILE__,__LINE__,__VA_ARGS__)
+#define LOG_ERROR( ... ) log_asdfghjklqwertzu("ERROR","\033[31m",__FILE__,__LINE__,__VA_ARGS__)
+#define LOG_WARN( ... ) log_asdfghjklqwertzu("WARN ","\033[33m",__FILE__,__LINE__,__VA_ARGS__)
+#define LOG_INFO( ... ) log_asdfghjklqwertzu("INFO ","\033[36m",__FILE__,__LINE__,__VA_ARGS__)
+#ifndef NDEBUG
+ #define LOG_DEBUG( ... ) log_asdfghjklqwertzu("DEBUG","\033[35m",__FILE__,__LINE__,__VA_ARGS__ )
+ #define LOG_TRACE( ... ) log_asdfghjklqwertzu("TRACE","\033[94m",__FILE__,__LINE__,__VA_ARGS__ )
+#else
+ #define LOG_DEBUG( ... ) /* Debugging not enabled. Therefore ignore that log level. */
+ #define LOG_TRACE( ... ) /* Debugging not enabled. Therefore ignore that log level. */
+#endif
+
+
+void log_asdfghjklqwertzu( const char*level, const char*cLvl, const char*file, int line, const char*fmt, ... );
+
+
+#endif /* INCGUARD_540e2cd36c1ba21909b922d45a94b7f7 */
diff --git a/src/mime/mime.c b/src/mime/mime.c
new file mode 100644
index 0000000..e476eea
--- /dev/null
+++ b/src/mime/mime.c
@@ -0,0 +1,260 @@
+
+#include "mime.h"
+
+#include <stdlib.h>
+
+
+const char* fileExtToMime( const char*ext )
+{
+ if( ext[0]=='7' ){
+ if( ext[1]=='z' ){
+ if( ext[2]=='\0' ){
+ return "application/x-7z-compressed";
+ }
+ }
+ }
+ if( ext[0]=='b' ){
+ if( ext[1]=='i' ){
+ if( ext[2]=='n' ){
+ if( ext[3]==0 ){
+ return "application/octet-stream";
+ }
+ }
+ }
+ }
+ if( ext[0]=='c' ){
+ if( ext[1]=='s' ){
+ if( ext[2]=='s' ){
+ if( ext[3]==0 ){
+ return "text/css";
+ }
+ }
+ if( ext[2]=='v' ){
+ if( ext[3]==0 ){
+ return "text/csv";
+ }
+ }
+ }
+ }
+ if( ext[0]=='g' ){
+ if( ext[1]=='i' ){
+ if( ext[2]=='f' ){
+ if( ext[3]==0 ){
+ return "image/gif";
+ }
+ }
+ }
+ if( ext[1]=='z' ){
+ if( ext[2]==0 ){
+ return "application/gzip";
+ }
+ }
+ }
+ if( ext[0]=='h' ){
+ if( ext[1]=='t' ){
+ if( ext[2]=='m' ){
+ if( ext[3]==0 ){
+ return "text/html";
+ }
+ if( ext[3]=='l' ){
+ return "text/html";
+ }
+ }
+ }
+ }
+ if( ext[0]=='i' ){
+ if( ext[1]=='c' ){
+ if( ext[2]=='o' ){
+ if( ext[3]==0 ){
+ return "image/vnd.microsoft.icon";
+ }
+ }
+ }
+ }
+ if( ext[0]=='j' ){
+ if( ext[1]=='a' ){
+ if( ext[2]=='r' ){
+ if( ext[3]==0 ){
+ return "application/java-archive";
+ }
+ }
+ }
+ if( ext[1]=='p' ){
+ if( ext[2]=='e' ){
+ if( ext[3]=='g' ){
+ if( ext[4]==0 ){
+ return "image/jpeg";
+ }
+ }
+ }
+ if( ext[2]=='g' ){
+ if( ext[3]==0 ){
+ return "image/jpeg";
+ }
+ }
+ }
+ }
+ if( ext[0]=='m' ){
+ if( ext[1]=='p' ){
+ if( ext[2]=='3' ){
+ if( ext[3]==0 ){
+ return "audio/mpeg";
+ }
+ }
+ if( ext[2]=='e' ){
+ if( ext[3]=='g' ){
+ if( ext[4]==0 ){
+ return "video/mpeg";
+ }
+ }
+ }
+ }
+ }
+ if( ext[0]=='o' ){
+ if( ext[1]=='d' ){
+ if( ext[2]=='p' ){
+ if( ext[3]==0 ){
+ return "application/vnd.oasis.opendocument.presentation";
+ }
+ }
+ if( ext[2]=='s' ){
+ if( ext[3]==0 ){
+ return "application/vnd.oasis.opendocument.spreadsheet";
+ }
+ }
+ if( ext[2]=='t' ){
+ if( ext[3]==0 ){
+ return "application/vnd.oasis.opendocument.text";
+ }
+ }
+ }
+ }
+ if( ext[0]=='p' ){
+ if( ext[1]=='d' ){
+ if( ext[2]=='f' ){
+ if( ext[3]==0 ){
+ return "application/pdf";
+ }
+ }
+ }
+ if( ext[1]=='n' ){
+ if( ext[2]=='g' ){
+ if( ext[3]==0 ){
+ return "image/png";
+ }
+ }
+ }
+ }
+ if( ext[0]=='s' ){
+ if( ext[1]=='v' ){
+ if( ext[2]=='g' ){
+ if( ext[3]==0 ){
+ return "image/svg+xml";
+ }
+ }
+ }
+ }
+ if( ext[0]=='t' ){
+ if( ext[1]=='a' ){
+ if( ext[2]=='r' ){
+ if( ext[3]==0 ){
+ return "application/x-tar";
+ }
+ }
+ }
+ if( ext[1]=='x' ){
+ if( ext[2]=='t' ){
+ if( ext[3]==0 ){
+ return "text/plain";
+ }
+ }
+ }
+ }
+ if( ext[0]=='j' ){
+ if( ext[1]=='s' ){
+ if( ext[2]==0 ){
+ return "text/javascript";
+ }
+ if( ext[2]=='o' ){
+ if( ext[3]=='n' ){
+ if( ext[4]==0 ){
+ return "application/json";
+ }
+ }
+ }
+ }
+ }
+ if( ext[0]=='w' ){
+ if( ext[1]=='a' ){
+ if( ext[2]=='v' ){
+ if( ext[3]==0 ){
+ return "audio/wav";
+ }
+ }
+ }
+ if( ext[1]=='e' ){
+ if( ext[2]=='b' ){
+ if( ext[3]=='a' ){
+ if( ext[4]==0 ){
+ return "audio/webm";
+ }
+ }
+ if( ext[3]=='m' ){
+ if( ext[4]==0 ){
+ return "video/webm";
+ }
+ }
+ if( ext[3]=='p' ){
+ if( ext[4]==0 ){
+ return "image/webp";
+ }
+ }
+ }
+ }
+ if( ext[1]=='o' ){
+ if( ext[2]=='f' ){
+ if( ext[3]=='f' ){
+ if( ext[4]=='\0' ){
+ return "font/woff";
+ }
+ if( ext[4]=='2' ){
+ if( ext[5]=='\0' ){
+ return "font/woff2";
+ }
+ }
+ }
+ }
+ }
+ }
+ if( ext[0]=='x' ){
+ if( ext[1]=='h' ){
+ if( ext[2]=='t' ){
+ if( ext[3]=='m' ){
+ if( ext[4]=='l' ){
+ if( ext[5]=='\0' ){
+ return "application/xhtml+xml";
+ }
+ }
+ }
+ }
+ }
+ if( ext[1]=='m' ){
+ if( ext[2]=='l' ){
+ if( ext[3]==0 ){
+ return "text/xml";
+ }
+ }
+ }
+ }
+ if( ext[0]=='z' ){
+ if( ext[1]=='i' ){
+ if( ext[2]=='p' ){
+ if( ext[3]=='\0' ){
+ return "application/zip";
+ }
+ }
+ }
+ }
+ return NULL;
+}
+
diff --git a/src/mime/mime.h b/src/mime/mime.h
new file mode 100644
index 0000000..171290a
--- /dev/null
+++ b/src/mime/mime.h
@@ -0,0 +1,13 @@
+#ifndef INCGUARD_569e6cc3cc7a72f544dd00fb071f0a17
+#define INCGUARD_569e6cc3cc7a72f544dd00fb071f0a17
+
+#include "commonbase.h"
+
+
+/**
+ * Returns ptr to statically allocated mimetype.
+ */
+const char* fileExtToMime( const char*ext );
+
+
+#endif /* INCGUARD_569e6cc3cc7a72f544dd00fb071f0a17 */
diff --git a/src/util_string/util_string.h b/src/util_string/util_string.h
new file mode 100644
index 0000000..45f07fd
--- /dev/null
+++ b/src/util_string/util_string.h
@@ -0,0 +1,13 @@
+#ifndef INCGUARD_7af1eceb805a30c68adec1fd0ccc15c4
+#define INCGUARD_7af1eceb805a30c68adec1fd0ccc15c4
+
+#include "commonbase.h"
+
+
+#define STR_QUOT_IAHGEWIH( s ) #s
+#define STR_QUOT( s ) STR_QUOT_IAHGEWIH(s)
+
+#define STR_CAT(a,b) a ## b
+
+
+#endif /* INCGUARD_7af1eceb805a30c68adec1fd0ccc15c4 */
diff --git a/src/util_term/util_term.c b/src/util_term/util_term.c
new file mode 100644
index 0000000..6ea6a12
--- /dev/null
+++ b/src/util_term/util_term.c
@@ -0,0 +1,45 @@
+
+#include "commonbase.h"
+
+/* System */
+#include <assert.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <unistd.h>
+
+
+#ifdef _WIN32
+static HANDLE
+fdToHandle( int fd ){
+ switch( fd ){
+ case 1: return GetStdHandle(STD_OUTPUT_HANDLE);
+ case 2: return GetStdHandle(STD_ERROR_HANDLE );
+ default: assert(0); return NULL;
+ }
+}
+#endif
+
+
+#ifdef _WIN32
+static void
+termColorsEnable( int fd )
+{
+ HANDLE hOut = fdToHandle( fd );
+ if( hOut==INVALID_HANDLE_VALUE ){ return; }
+ DWORD dwMode = 0;
+ if( ! GetConsoleMode(hOut,&dwMode) ){ return; }
+ dwMode |= 0x0004; /* <- ENABLE_VIRTUAL_TERMINAL_PROCESSING */
+ if( ! SetConsoleMode(hOut, dwMode) ){ return; }
+}
+#endif
+
+
+void
+util_term_init()
+{
+ #ifdef _WIN32
+ if( isatty(1) ){ termColorsEnable(1); }
+ if( isatty(2) ){ termColorsEnable(2); }
+ #endif
+}
+
diff --git a/src/util_term/util_term.h b/src/util_term/util_term.h
new file mode 100644
index 0000000..be53d7c
--- /dev/null
+++ b/src/util_term/util_term.h
@@ -0,0 +1,8 @@
+#ifndef INCGUARD_559e64ee2a3c96a09c7c046f15fe0b1b
+#define INCGUARD_559e64ee2a3c96a09c7c046f15fe0b1b
+
+void
+util_term_init();
+
+
+#endif /* INCGUARD_559e64ee2a3c96a09c7c046f15fe0b1b */