summaryrefslogtreecommitdiff
path: root/lib/common.c
diff options
context:
space:
mode:
Diffstat (limited to 'lib/common.c')
-rw-r--r--lib/common.c1921
1 files changed, 1921 insertions, 0 deletions
diff --git a/lib/common.c b/lib/common.c
new file mode 100644
index 0000000..8a0e9ac
--- /dev/null
+++ b/lib/common.c
@@ -0,0 +1,1921 @@
+/*
+ * common.c:
+ *
+ * See the README file for copyright information and how to reach the author.
+ *
+ */
+
+#include <sys/stat.h>
+#include <sys/time.h>
+
+#include <sys/ioctl.h>
+#include <net/if.h>
+#include <arpa/inet.h>
+
+#ifdef USEUUID
+# include <uuid/uuid.h>
+#endif
+
+#include <stdarg.h>
+#include <stdio.h>
+#include <string.h>
+#include <syslog.h>
+#include <unistd.h>
+#include <errno.h>
+#include <regex.h>
+#include <limits.h>
+
+#ifdef USELIBARCHIVE
+# include <archive.h>
+# include <archive_entry.h>
+#endif
+
+#include "common.h"
+#include "config.h"
+
+cMyMutex logMutex;
+
+//***************************************************************************
+// Tell
+//***************************************************************************
+
+const char* getLogPrefix()
+{
+ return logPrefix;
+}
+
+void tell(int eloquence, const char* format, ...)
+{
+ if (cEpgConfig::loglevel < eloquence)
+ return ;
+
+ logMutex.Lock();
+
+#ifndef VDR_PLUGIN
+ static int init = no;
+
+ if (!init)
+ {
+ init = yes;
+ openlog(cEpgConfig::logName, LOG_CONS, cEpgConfig::logFacility);
+ }
+#endif
+
+ const int sizeBuffer = 100000;
+ char t[sizeBuffer+100]; *t = 0;
+ va_list ap;
+
+ va_start(ap, format);
+
+ if (getLogPrefix())
+ snprintf(t, sizeBuffer, "%s", getLogPrefix());
+
+ vsnprintf(t+strlen(t), sizeBuffer-strlen(t), format, ap);
+
+ strReplace(t, '\n', '$');
+
+ if (cEpgConfig::logstdout)
+ {
+ char buf[50+TB];
+ timeval tp;
+
+ gettimeofday(&tp, 0);
+
+ tm* tm = localtime(&tp.tv_sec);
+
+ sprintf(buf,"%2.2d:%2.2d:%2.2d,%3.3ld ",
+ tm->tm_hour, tm->tm_min, tm->tm_sec,
+ tp.tv_usec / 1000);
+
+ printf("%s %s\n", buf, t);
+ }
+ else
+ syslog(LOG_ERR, "%s", t);
+
+ logMutex.Unlock();
+
+ va_end(ap);
+}
+
+//***************************************************************************
+// Syslog Facilities
+//***************************************************************************
+
+const Syslog::Facilities Syslog::facilities[] =
+{
+ { "auth", LOG_AUTH },
+ { "cron", LOG_CRON },
+ { "daemon", LOG_DAEMON },
+ { "kern", LOG_KERN },
+ { "lpr", LOG_LPR },
+ { "mail", LOG_MAIL },
+ { "news", LOG_NEWS },
+ { "security", LOG_AUTH },
+ { "syslog", LOG_SYSLOG },
+ { "user", LOG_USER },
+ { "uucp", LOG_UUCP },
+ { "local0", LOG_LOCAL0 },
+ { "local1", LOG_LOCAL1 },
+ { "local2", LOG_LOCAL2 },
+ { "local3", LOG_LOCAL3 },
+ { "local4", LOG_LOCAL4 },
+ { "local5", LOG_LOCAL5 },
+ { "local6", LOG_LOCAL6 },
+ { "local7", LOG_LOCAL7 },
+ { 0, 0 }
+};
+
+//***************************************************************************
+// To Name
+//***************************************************************************
+
+char* Syslog::toName(int code)
+{
+ for (int i = 0; facilities[i].name; i++)
+ if (facilities[i].code == code)
+ return (char*) facilities[i].name; // #83
+
+ return 0;
+}
+
+//***************************************************************************
+// To Code
+//***************************************************************************
+
+int Syslog::toCode(const char* name)
+{
+ for (int i = 0; facilities[i].name; i++)
+ if (strcmp(facilities[i].name, name) == 0)
+ return facilities[i].code;
+
+ return na;
+}
+
+//***************************************************************************
+// Save Realloc
+//***************************************************************************
+
+char* srealloc(void* ptr, size_t size)
+{
+ void* n = realloc(ptr, size);
+
+ if (!n)
+ {
+ free(ptr);
+ ptr = 0;
+ }
+
+ return (char*)n;
+}
+
+//***************************************************************************
+// us now
+//***************************************************************************
+
+double usNow()
+{
+ struct timeval tp;
+
+ gettimeofday(&tp, 0);
+
+ return tp.tv_sec * 1000000.0 + tp.tv_usec;
+}
+
+//***************************************************************************
+// Host ID
+//***************************************************************************
+
+unsigned int getHostId()
+{
+ static unsigned int id = gethostid() & 0xFFFFFFFF;
+ return id;
+}
+
+//***************************************************************************
+// String Operations
+//***************************************************************************
+
+void toUpper(std::string& str)
+{
+ const char* s = str.c_str();
+ int lenSrc = str.length();
+
+ char* dest = (char*)malloc(lenSrc+TB); *dest = 0;
+ char* d = dest;
+
+ int csSrc; // size of character
+
+ for (int ps = 0; ps < lenSrc; ps += csSrc)
+ {
+ csSrc = std::max(mblen(&s[ps], lenSrc-ps), 1);
+
+ if (csSrc == 1)
+ *d++ = toupper(s[ps]);
+ else if (csSrc == 2 && s[ps] == (char)0xc3 && s[ps+1] >= (char)0xa0)
+ {
+ *d++ = s[ps];
+ *d++ = s[ps+1] - 32;
+ }
+ else
+ {
+ for (int i = 0; i < csSrc; i++)
+ *d++ = s[ps+i];
+ }
+ }
+
+ *d = 0;
+
+ str = dest;
+ free(dest);
+}
+
+//***************************************************************************
+// To Case (UTF-8 save)
+//***************************************************************************
+
+const char* toCase(Case cs, char* str)
+{
+ char* s = str;
+ int lenSrc = strlen(str);
+
+ int csSrc; // size of character
+
+ for (int ps = 0; ps < lenSrc; ps += csSrc)
+ {
+ csSrc = std::max(mblen(&s[ps], lenSrc-ps), 1);
+
+ if (csSrc == 1)
+ s[ps] = cs == cUpper ? toupper(s[ps]) : tolower(s[ps]);
+ else if (csSrc == 2 && s[ps] == (char)0xc3 && s[ps+1] >= (char)0xa0)
+ {
+ s[ps] = s[ps];
+ s[ps+1] = cs == cUpper ? toupper(s[ps+1]) : tolower(s[ps+1]);
+ }
+ else
+ {
+ for (int i = 0; i < csSrc; i++)
+ s[ps+i] = s[ps+i];
+ }
+ }
+
+ return str;
+}
+
+void removeChars(std::string& str, const char* ignore)
+{
+ const char* s = str.c_str();
+ int lenSrc = str.length();
+ int lenIgn = strlen(ignore);
+
+ char* dest = (char*)malloc(lenSrc+TB); *dest = 0;
+ char* d = dest;
+
+ int csSrc; // size of character
+ int csIgn; //
+
+ for (int ps = 0; ps < lenSrc; ps += csSrc)
+ {
+ int skip = no;
+
+ csSrc = std::max(mblen(&s[ps], lenSrc-ps), 1);
+
+ for (int pi = 0; pi < lenIgn; pi += csIgn)
+ {
+ csIgn = std::max(mblen(&ignore[pi], lenIgn-pi), 1);
+
+ if (csSrc == csIgn && strncmp(&s[ps], &ignore[pi], csSrc) == 0)
+ {
+ skip = yes;
+ break;
+ }
+ }
+
+ if (!skip)
+ {
+ for (int i = 0; i < csSrc; i++)
+ *d++ = s[ps+i];
+ }
+ }
+
+ *d = 0;
+
+ str = dest;
+ free(dest);
+}
+
+void removeCharsExcept(std::string& str, const char* except)
+{
+ const char* s = str.c_str();
+ int lenSrc = str.length();
+ int lenIgn = strlen(except);
+
+ char* dest = (char*)malloc(lenSrc+TB); *dest = 0;
+ char* d = dest;
+
+ int csSrc; // size of character
+ int csIgn; //
+
+ for (int ps = 0; ps < lenSrc; ps += csSrc)
+ {
+ int skip = yes;
+
+ mblen(0,0);
+ csSrc = std::max(mblen(&s[ps], lenSrc-ps), 1);
+
+ for (int pi = 0; pi < lenIgn; pi += csIgn)
+ {
+ csIgn = std::max(mblen(&except[pi], lenIgn-pi), 1);
+
+ if (csSrc == csIgn && strncmp(&s[ps], &except[pi], csSrc) == 0)
+ {
+ skip = no;
+ break;
+ }
+ }
+
+ if (!skip)
+ {
+ for (int i = 0; i < csSrc; i++)
+ *d++ = s[ps+i];
+ }
+ }
+
+ *d = 0;
+
+ str = dest;
+ free(dest);
+}
+
+void removeWord(std::string& pattern, std::string word)
+{
+ size_t pos;
+
+ if ((pos = pattern.find(word)) != std::string::npos)
+ pattern.swap(pattern.erase(pos, word.length()));
+}
+
+//***************************************************************************
+// String Manipulation
+//***************************************************************************
+
+void prepareCompressed(std::string& pattern)
+{
+ // const char* ignore = " (),.;:-_+*!#?=&%$<>§/'`´@~\"[]{}";
+ const char* notignore = "ABCDEFGHIJKLMNOPQRSTUVWXYZßÖÄÜöäü0123456789";
+
+ toUpper(pattern);
+ removeWord(pattern, " TEIL ");
+ removeWord(pattern, " FOLGE ");
+ removeCharsExcept(pattern, notignore);
+}
+
+//***************************************************************************
+// Get Range Parts of String like '33-123' or '-123' or '21-'
+//***************************************************************************
+
+int rangeFrom(const char* s)
+{
+ return atoi(s);
+}
+
+int rangeTo(const char* s)
+{
+ int to = INT_MAX;
+
+ const char* p = strchr(s, '-');
+
+ if (p && *(p+1))
+ to = atoi(p+1);
+
+ return to;
+}
+
+//***************************************************************************
+// Left Trim
+//***************************************************************************
+
+char* lTrim(char* buf)
+{
+ if (buf)
+ {
+ char *tp = buf;
+
+ while (*tp && strchr("\n\r\t ",*tp))
+ tp++;
+
+ memmove(buf, tp, strlen(tp) +1);
+ }
+
+ return buf;
+}
+
+//*************************************************************************
+// Right Trim
+//*************************************************************************
+
+char* rTrim(char* buf)
+{
+ if (buf)
+ {
+ char *tp = buf + strlen(buf);
+
+ while (tp >= buf && strchr("\n\r\t ",*tp))
+ tp--;
+
+ *(tp+1) = 0;
+ }
+
+ return buf;
+}
+
+//*************************************************************************
+// All Trim
+//*************************************************************************
+
+char* allTrim(char* buf)
+{
+ return lTrim(rTrim(buf));
+}
+
+std::string strReplace(const std::string& what, const std::string& with, const std::string& subject)
+{
+ std::string str = subject;
+ size_t pos = 0;
+
+ while ((pos = str.find(what, pos)) != std::string::npos)
+ {
+ str.replace(pos, what.length(), with);
+ pos += with.length();
+ }
+
+ return str;
+}
+
+std::string strReplace(const std::string& what, long with, const std::string& subject)
+{
+ char swith[100];
+
+ sprintf(swith, "%ld", with);
+
+ return strReplace(what, swith, subject);
+}
+
+std::string strReplace(const std::string& what, double with, const std::string& subject)
+{
+ char swith[100];
+
+ sprintf(swith, "%.2f", with);
+
+ return strReplace(what, swith, subject);
+}
+
+char* strReplace(char* buffer, char from, char to)
+{
+ char* p = buffer;
+
+ while (*p)
+ {
+ if (*p == from)
+ *p = to;
+
+ p++;
+ }
+
+ return buffer;
+}
+
+//***************************************************************************
+// Number to String
+//***************************************************************************
+
+std::string num2Str(int num)
+{
+ char txt[16];
+
+ snprintf(txt, sizeof(txt), "%d", num);
+
+ return std::string(txt);
+}
+
+//***************************************************************************
+// Long to Pretty Time
+//***************************************************************************
+
+std::string l2pTime(time_t t, const char* format)
+{
+ char txt[100];
+ tm* tmp = localtime(&t);
+
+ strftime(txt, sizeof(txt), format, tmp);
+
+ return std::string(txt);
+}
+
+std::string l2pDate(time_t t)
+{
+ char txt[30];
+ tm* tmp = localtime(&t);
+
+ strftime(txt, sizeof(txt), "%d.%m.%Y", tmp);
+
+ return std::string(txt);
+}
+
+//***************************************************************************
+// To HTTP Header Date Format
+//***************************************************************************
+
+std::string l2HttpTime(time_t t)
+{
+ char date[128+TB];
+ tm now;
+
+ static const char *const days[] =
+ { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" };
+
+ static const char *const mons[] =
+ { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" };
+
+ gmtime_r(&t, &now);
+
+ sprintf(date, "%3s, %02u %3s %04u %02u:%02u:%02u GMT",
+ days[now.tm_wday % 7],
+ (unsigned int)now.tm_mday,
+ mons[now.tm_mon % 12],
+ (unsigned int)(1900 + now.tm_year),
+ (unsigned int)now.tm_hour,
+ (unsigned int)now.tm_min,
+ (unsigned int)now.tm_sec);
+
+ return std::string(date);
+}
+
+//***************************************************************************
+// Is Daylight Saving Time
+//***************************************************************************
+
+int isDST(time_t t)
+{
+ struct tm tm;
+
+ if (!t)
+ t = time(0);
+
+ localtime_r(&t, &tm);
+ tm.tm_isdst = -1; // force DST auto detect
+ mktime(&tm);
+
+ return tm.tm_isdst;
+}
+
+//***************************************************************************
+// Time of
+//***************************************************************************
+
+time_t timeOf(time_t t)
+{
+ struct tm tm;
+
+ localtime_r(&t, &tm);
+
+ tm.tm_year = 0;
+ tm.tm_mon = 0;
+ tm.tm_mday = 0;
+ tm.tm_wday = 0;
+ tm.tm_yday = 0;
+ tm.tm_isdst = -1; // force DST auto detect
+
+ return mktime(&tm);
+}
+
+//***************************************************************************
+// Weekday Of
+//***************************************************************************
+
+int weekdayOf(time_t t)
+{
+ struct tm tm_r;
+ int weekday = localtime_r(&t, &tm_r)->tm_wday;
+
+ return weekday == 0 ? 6 : weekday - 1; // Monday is 0
+}
+
+//***************************************************************************
+// To Weekday Name
+//***************************************************************************
+
+const char* toWeekdayName(uint day)
+{
+ const char* dayNames[] =
+ {
+ "Monday",
+ "Tuesday",
+ "Wednesday",
+ "Thursday",
+ "Friday",
+ "Saturday",
+ "Sunday",
+ 0
+ };
+
+ if (day > 6)
+ return "<unknown>";
+
+ return dayNames[day];
+}
+
+//***************************************************************************
+// hhmm of
+//***************************************************************************
+
+time_t hhmmOf(time_t t)
+{
+ struct tm tm;
+
+ localtime_r(&t, &tm);
+
+ tm.tm_year = 0;
+ tm.tm_mon = 0;
+ tm.tm_mday = 0;
+ tm.tm_wday = 0;
+ tm.tm_yday = 0;
+ tm.tm_sec = 0;
+ tm.tm_isdst = -1; // force DST auto detect
+
+ return mktime(&tm);
+}
+
+//***************************************************************************
+// time_t to hhmm like '2015'
+//***************************************************************************
+
+int l2hhmm(time_t t)
+{
+ struct tm tm;
+
+ localtime_r(&t, &tm);
+
+ return tm.tm_hour * 100 + tm.tm_min;
+}
+
+//***************************************************************************
+// HHMM to Pretty Time
+//***************************************************************************
+
+std::string hhmm2pTime(int hhmm)
+{
+ char txt[100];
+
+ sprintf(txt, "%02d:%02d", hhmm / 100, hhmm % 100);
+
+ return std::string(txt);
+}
+
+//***************************************************************************
+// Midnight of
+//***************************************************************************
+
+time_t midnightOf(time_t t)
+{
+ struct tm tm;
+
+ localtime_r(&t, &tm);
+
+ tm.tm_hour = 0;
+ tm.tm_min = 0;
+ tm.tm_sec = 0;
+ tm.tm_isdst = -1; // force DST auto detect
+
+ return mktime(&tm);
+}
+
+//***************************************************************************
+// MS to Duration
+//***************************************************************************
+
+std::string ms2Dur(uint64_t t)
+{
+ char txt[30];
+
+ int s = t / 1000;
+ int ms = t % 1000;
+
+ if (s != 0)
+ snprintf(txt, sizeof(txt), "%d.%03d seconds", s, ms);
+ else
+ snprintf(txt, sizeof(txt), "%d ms", ms);
+
+ return std::string(txt);
+}
+
+//***************************************************************************
+// Char to Char-String
+//***************************************************************************
+
+const char* c2s(char c, char* buf)
+{
+ sprintf(buf, "%c", c);
+
+ return buf;
+}
+
+//***************************************************************************
+// End Of String
+//***************************************************************************
+
+char* eos(char* s)
+{
+ if (!s)
+ return 0;
+
+ return s + strlen(s);
+}
+
+//***************************************************************************
+// Store To File
+//***************************************************************************
+
+int storeToFile(const char* filename, const char* data, int size)
+{
+ FILE* fout;
+
+ if ((fout = fopen(filename, "w+")))
+ {
+ fwrite(data, sizeof(char), size, fout);
+ fclose(fout);
+ }
+ else
+ {
+ tell(0, "Error, can't store '%s' to filesystem '%s'", filename, strerror(errno));
+ return fail;
+ }
+
+ return success;
+}
+
+//***************************************************************************
+// Load From File
+//***************************************************************************
+
+int loadFromFile(const char* infile, MemoryStruct* data)
+{
+ FILE* fin;
+ struct stat sb;
+
+ data->clear();
+
+ if (!fileExists(infile))
+ {
+ tell(0, "File '%s' not found'", infile);
+ return fail;
+ }
+
+ if (stat(infile, &sb) < 0)
+ {
+ tell(0, "Can't get info of '%s', error was '%s'", infile, strerror(errno));
+ return fail;
+ }
+
+ if ((fin = fopen(infile, "r")))
+ {
+ const char* sfx = suffixOf(infile);
+
+ data->size = sb.st_size;
+ data->modTime = sb.st_mtime;
+ data->memory = (char*)malloc(data->size);
+ fread(data->memory, sizeof(char), data->size, fin);
+ fclose(fin);
+ sprintf(data->tag, "%ld", (long int)data->size);
+
+ if (strcmp(sfx, "gz") == 0)
+ sprintf(data->contentEncoding, "gzip");
+
+ if (strcmp(sfx, "js") == 0)
+ sprintf(data->contentType, "application/javascript");
+
+ else if (strcmp(sfx, "png") == 0 || strcmp(sfx, "jpg") == 0 || strcmp(sfx, "gif") == 0)
+ sprintf(data->contentType, "image/%s", sfx);
+ else if (strcmp(sfx, "svg") == 0)
+ sprintf(data->contentType, "image/%s+xml", sfx);
+ else if (strcmp(sfx, "ico") == 0)
+ strcpy(data->contentType, "image/x-icon");
+
+ else
+ sprintf(data->contentType, "text/%s", sfx);
+ }
+ else
+ {
+ tell(0, "Error, can't open '%s' for reading, error was '%s'", infile, strerror(errno));
+ return fail;
+ }
+
+ return success;
+}
+
+//***************************************************************************
+// TOOLS
+//***************************************************************************
+
+int isEmpty(const char* str)
+{
+ return !str || !*str;
+}
+
+const char* notNull(const char* str, const char* def)
+{
+ if (!str)
+ return def;
+
+ return str;
+}
+
+int isZero(const char* str)
+{
+ const char* p = str;
+
+ while (p && *p)
+ {
+ if (*p != '0')
+ return no;
+
+ p++;
+ }
+
+ return yes;
+}
+
+//***************************************************************************
+// Is Member
+//***************************************************************************
+
+int isMember(const char** list, const char* item)
+{
+ const char** p;
+ int i;
+
+ for (i = 0, p = list; *p; i++, p++)
+ if (strcmp(*p, item) == 0)
+ return i;
+
+ return na;
+}
+
+//***************************************************************************
+//
+//***************************************************************************
+
+char* sstrcpy(char* dest, const char* src, int max)
+{
+ if (!dest || !src)
+ return 0;
+
+ strncpy(dest, src, max);
+ dest[max-1] = 0;
+
+ return dest;
+}
+
+int isLink(const char* path)
+{
+ struct stat sb;
+
+ if (lstat(path, &sb) == 0)
+ return S_ISLNK(sb.st_mode);
+
+ tell(0, "Error: Detecting state for '%s' failed, error was '%s'", path, strerror(errno));
+
+ return false;
+}
+
+const char* suffixOf(const char* path)
+{
+ const char* p;
+
+ if (path && (p = strrchr(path, '.')))
+ return p+1;
+
+ return "";
+}
+
+int fileSize(const char* path)
+{
+ struct stat sb;
+
+ if (lstat(path, &sb) == 0)
+ return sb.st_size;
+
+ tell(0, "Error: Detecting state for '%s' failed, error was '%s'", path, strerror(errno));
+
+ return 0;
+}
+
+time_t fileModTime(const char* path)
+{
+ struct stat sb;
+
+ if (lstat(path, &sb) == 0)
+ return sb.st_mtime;
+
+ tell(0, "Error: Detecting state for '%s' failed, error was '%s'", path, strerror(errno));
+
+ return 0;
+}
+
+int folderExists(const char* path)
+{
+ struct stat fs;
+
+ return stat(path, &fs) == 0 && S_ISDIR(fs.st_mode);
+}
+
+int fileExists(const char* path)
+{
+ return access(path, F_OK) == 0;
+}
+
+int createLink(const char* link, const char* dest, int force)
+{
+ if (!fileExists(link) || force)
+ {
+ // may be the link exists and point to a wrong or already deleted destination ...
+ // .. therefore we delete the link at first
+
+ unlink(link);
+
+ if (symlink(dest, link) != 0)
+ {
+ tell(0, "Failed to create symlink '%s', error was '%s'", link, strerror(errno));
+ return fail;
+ }
+ }
+
+ return success;
+}
+
+//***************************************************************************
+// Remove File
+//***************************************************************************
+
+int removeFile(const char* filename)
+{
+ int lnk = isLink(filename);
+
+ if (unlink(filename) != 0)
+ {
+ tell(0, "Can't remove file '%s', '%s'", filename, strerror(errno));
+
+ return 1;
+ }
+
+ tell(3, "Removed %s '%s'", lnk ? "link" : "file", filename);
+
+ return 0;
+}
+
+//***************************************************************************
+// Check Dir
+//***************************************************************************
+
+int chkDir(const char* path)
+{
+ struct stat fs;
+
+ if (stat(path, &fs) != 0 || !S_ISDIR(fs.st_mode))
+ {
+ tell(0, "Creating directory '%s'", path);
+
+ if (mkdir(path, ACCESSPERMS) == -1)
+ {
+ tell(0, "Can't create directory '%s'", strerror(errno));
+ return fail;
+ }
+ }
+
+ return success;
+}
+
+#ifdef USELIBXML
+
+//***************************************************************************
+// Load XSLT
+//***************************************************************************
+
+xsltStylesheetPtr loadXSLT(const char* name, const char* path, int utf8)
+{
+ xsltStylesheetPtr stylesheet;
+ char* xsltfile;
+
+ asprintf(&xsltfile, "%s/%s-%s.xsl", path, name, utf8 ? "utf-8" : "iso-8859-1");
+
+ if ((stylesheet = xsltParseStylesheetFile((const xmlChar*)xsltfile)) == 0)
+ tell(0, "Error: Can't load xsltfile %s", xsltfile);
+ else
+ tell(0, "Info: Stylesheet '%s' loaded", xsltfile);
+
+ free(xsltfile);
+ return stylesheet;
+}
+#endif
+
+#ifdef USEGUNZIP
+
+//***************************************************************************
+// Gnu Unzip
+//***************************************************************************
+
+int gunzip(MemoryStruct* zippedData, MemoryStruct* unzippedData)
+{
+ const int growthStep = 1024;
+
+ z_stream stream = {0,0,0,0,0,0,0,0,0,0,0,Z_NULL,Z_NULL,Z_NULL};
+ unsigned int resultSize = 0;
+ int res = 0;
+
+ unzippedData->clear();
+
+ // determining the size in this way is taken from the sources of the gzip utility.
+
+ memcpy(&unzippedData->size, zippedData->memory + zippedData->size -4, 4);
+ unzippedData->memory = (char*)malloc(unzippedData->size);
+
+ // zlib initialisation
+
+ stream.avail_in = zippedData->size;
+ stream.next_in = (Bytef*)zippedData->memory;
+ stream.avail_out = unzippedData->size;
+ stream.next_out = (Bytef*)unzippedData->memory;
+
+ // The '+ 32' tells zlib to process zlib&gzlib headers
+
+ res = inflateInit2(&stream, MAX_WBITS + 32);
+
+ if (res != Z_OK)
+ {
+ tellZipError(res, " during zlib initialisation", stream.msg);
+ inflateEnd(&stream);
+ return fail;
+ }
+
+ // skip the header
+
+ res = inflate(&stream, Z_BLOCK);
+
+ if (res != Z_OK)
+ {
+ tellZipError(res, " while skipping the header", stream.msg);
+ inflateEnd(&stream);
+ return fail;
+ }
+
+ while (res == Z_OK)
+ {
+ if (stream.avail_out == 0)
+ {
+ unzippedData->size += growthStep;
+ unzippedData->memory = (char*)realloc(unzippedData->memory, unzippedData->size);
+
+ // Set the stream pointers to the potentially changed buffer!
+
+ stream.avail_out = resultSize - stream.total_out;
+ stream.next_out = (Bytef*)(unzippedData + stream.total_out);
+ }
+
+ res = inflate(&stream, Z_SYNC_FLUSH);
+ resultSize = stream.total_out;
+ }
+
+ if (res != Z_STREAM_END)
+ {
+ tellZipError(res, " during inflating", stream.msg);
+ inflateEnd(&stream);
+ return fail;
+ }
+
+ unzippedData->size = resultSize;
+ inflateEnd(&stream);
+
+ return success;
+}
+
+//***************************************************************************
+// gzip
+//***************************************************************************
+
+int gzip(Bytef* dest, uLongf* destLen, const Bytef* source, uLong sourceLen)
+{
+ z_stream stream;
+ int res;
+
+ stream.next_in = (Bytef *)source;
+ stream.avail_in = (uInt)sourceLen;
+ stream.next_out = dest;
+ stream.avail_out = (uInt)*destLen;
+ if ((uLong)stream.avail_out != *destLen) return Z_BUF_ERROR;
+
+ stream.zalloc = (alloc_func)0;
+ stream.zfree = (free_func)0;
+ stream.opaque = (voidpf)0;
+
+ res = deflateInit2(&stream, Z_DEFAULT_COMPRESSION, Z_DEFLATED, 15 + 16, 8, Z_DEFAULT_STRATEGY);
+
+ if (res == Z_OK)
+ {
+ res = deflate(&stream, Z_FINISH);
+
+ if (res != Z_STREAM_END)
+ {
+ deflateEnd(&stream);
+ res = res == Z_OK ? Z_BUF_ERROR : res;
+ }
+ }
+
+ if (res == Z_STREAM_END)
+ {
+ *destLen = stream.total_out;
+ res = deflateEnd(&stream);
+ }
+
+ if (res != Z_OK)
+ tellZipError(res, " during compression", "");
+
+ return res == Z_OK ? success : fail;
+}
+
+ulong gzipBound(ulong size)
+{
+ return compressBound(size);
+}
+
+//*************************************************************************
+// tellZipError
+//*************************************************************************
+
+void tellZipError(int errorCode, const char* op, const char* msg)
+{
+ if (!op) op = "";
+ if (!msg) msg = "None";
+
+ switch (errorCode)
+ {
+ case Z_OK: return;
+ case Z_STREAM_END: return;
+ case Z_MEM_ERROR: tell(0, "Error: Not enough memory to zip/unzip file%s!\n", op); return;
+ case Z_BUF_ERROR: tell(0, "Error: Couldn't zip/unzip data due to output buffer size problem%s!\n", op); return;
+ case Z_DATA_ERROR: tell(0, "Error: Zipped input data corrupted%s! Details: %s\n", op, msg); return;
+ case Z_STREAM_ERROR: tell(0, "Error: Invalid stream structure%s. Details: %s\n", op, msg); return;
+ default: tell(0, "Error: Couldn't zip/unzip data for unknown reason (%6d)%s!\n", errorCode, op); return;
+ }
+}
+
+#endif // USEGUNZIP
+
+//*************************************************************************
+// Host Data
+//*************************************************************************
+
+#include <sys/utsname.h>
+#include <netdb.h>
+#include <ifaddrs.h>
+
+static struct utsname info;
+
+const char* getHostName()
+{
+ // get info from kernel
+
+ if (uname(&info) == -1)
+ return "";
+
+ return info.nodename;
+}
+
+const char* getFirstIp(int skipLo)
+{
+ struct ifaddrs *ifaddr, *ifa;
+ static char host[NI_MAXHOST] = "";
+ static char netMask[INET6_ADDRSTRLEN] = "";
+
+ if (getifaddrs(&ifaddr) == -1)
+ {
+ tell(0, "Error: getifaddrs() failed");
+ return "";
+ }
+
+ // walk through linked interface list
+
+ for (ifa = ifaddr; ifa; ifa = ifa->ifa_next)
+ {
+ if (!ifa->ifa_addr)
+ continue;
+
+ // For an AF_INET interfaces
+
+ if (ifa->ifa_addr->sa_family == AF_INET) // || ifa->ifa_addr->sa_family == AF_INET6)
+ {
+ int res = getnameinfo(ifa->ifa_addr,
+ (ifa->ifa_addr->sa_family == AF_INET) ? sizeof(struct sockaddr_in) :
+ sizeof(struct sockaddr_in6),
+ host, NI_MAXHOST, 0, 0, NI_NUMERICHOST);
+
+ if (res)
+ {
+ tell(0, "Error: getnameinfo() for '%s' failed: %s", gai_strerror(res), ifa->ifa_name);
+ continue;
+ }
+
+ // skip loopback interface
+
+ if (skipLo && strcmp(host, "127.0.0.1") == 0)
+ continue;
+
+ if (ifa->ifa_netmask && ifa->ifa_netmask->sa_family == AF_INET)
+ {
+ void* p = &((struct sockaddr_in*)ifa->ifa_netmask)->sin_addr;
+ inet_ntop(ifa->ifa_netmask->sa_family, p, netMask, sizeof(netMask));
+ }
+
+ tell(1, "%-8s %-15s %s; netmask '%s'", ifa->ifa_name, host,
+ ifa->ifa_addr->sa_family == AF_INET ? " (AF_INET)" :
+ ifa->ifa_addr->sa_family == AF_INET6 ? " (AF_INET6)" : "",
+ netMask);
+ }
+ }
+
+ freeifaddrs(ifaddr);
+
+ return host;
+}
+
+//***************************************************************************
+// Get Interfaces
+//***************************************************************************
+
+const char* getInterfaces()
+{
+ static char buffer[1000+TB] = "";
+ static char host[NI_MAXHOST] = "";
+ struct ifaddrs *ifaddr, *ifa;
+
+ *buffer = 0;
+
+ if (getifaddrs(&ifaddr) == -1)
+ {
+ tell(0, "Error: getifaddrs() failed");
+ return "";
+ }
+
+ // walk through linked interface list
+
+ for (ifa = ifaddr; ifa; ifa = ifa->ifa_next)
+ {
+ if (!ifa->ifa_addr)
+ continue;
+
+ // For an AF_INET interfaces
+
+ if (ifa->ifa_addr->sa_family == AF_INET) // || ifa->ifa_addr->sa_family == AF_INET6)
+ {
+ int res = getnameinfo(ifa->ifa_addr,
+ (ifa->ifa_addr->sa_family == AF_INET) ? sizeof(struct sockaddr_in) :
+ sizeof(struct sockaddr_in6),
+ host, NI_MAXHOST, 0, 0, NI_NUMERICHOST);
+
+ if (res)
+ {
+ tell(0, "Error: getnameinfo() failed: %s, skipping interface '%s'", gai_strerror(res), ifa->ifa_name);
+ continue;
+ }
+
+ sprintf(eos(buffer), "%s:%s ", ifa->ifa_name, host);
+ }
+ }
+
+ freeifaddrs(ifaddr);
+
+ return buffer;
+}
+
+//***************************************************************************
+// Get First Interface
+//***************************************************************************
+
+const char* getFirstInterface()
+{
+ static char buffer[1000+TB] = "";
+ static char host[NI_MAXHOST] = "";
+ struct ifaddrs *ifaddr, *ifa;
+
+ *buffer = 0;
+
+ if (getifaddrs(&ifaddr) == -1)
+ {
+ tell(0, "Error: getifaddrs() failed");
+ return "";
+ }
+
+ // walk through linked interface list
+
+ for (ifa = ifaddr; ifa; ifa = ifa->ifa_next)
+ {
+ if (!ifa->ifa_addr)
+ continue;
+
+ // For an AF_INET interfaces
+
+ if (ifa->ifa_addr->sa_family == AF_INET) // || ifa->ifa_addr->sa_family == AF_INET6)
+ {
+ int res = getnameinfo(ifa->ifa_addr,
+ (ifa->ifa_addr->sa_family == AF_INET) ? sizeof(struct sockaddr_in) :
+ sizeof(struct sockaddr_in6),
+ host, NI_MAXHOST, 0, 0, NI_NUMERICHOST);
+
+ if (res)
+ {
+ tell(0, "Error: getnameinfo() failed: %s, skipping interface '%s'", gai_strerror(res), ifa->ifa_name);
+ continue;
+ }
+
+ if (strcasecmp(ifa->ifa_name, "lo") != 0)
+ sprintf(buffer, "%s", ifa->ifa_name);
+ }
+ }
+
+ freeifaddrs(ifaddr);
+
+ return buffer;
+}
+
+//***************************************************************************
+// Get IP Of
+//***************************************************************************
+
+const char* getIpOf(const char* device)
+{
+ struct ifreq ifr;
+ int fd;
+
+ if (isEmpty(device))
+ return getFirstIp();
+
+ fd = socket(AF_INET, SOCK_DGRAM, 0);
+
+ ifr.ifr_addr.sa_family = AF_INET;
+ strncpy(ifr.ifr_name, device, IFNAMSIZ-1);
+
+ ioctl(fd, SIOCGIFADDR, &ifr);
+ close(fd);
+
+ // caller has to copy the result string 'before' calling again!
+
+ return inet_ntoa(((struct sockaddr_in*)&ifr.ifr_addr)->sin_addr);
+}
+
+//***************************************************************************
+// Get Mask Of
+//***************************************************************************
+
+const char* getMaskOf(const char* device)
+{
+ struct ifreq ifr;
+ static char netMask[INET6_ADDRSTRLEN] = "";
+ int fd;
+
+ if (isEmpty(device))
+ device = getFirstInterface();
+
+ fd = socket(AF_INET, SOCK_DGRAM, 0);
+
+ ifr.ifr_addr.sa_family = AF_INET;
+ strncpy(ifr.ifr_name, device, IFNAMSIZ-1);
+ ioctl(fd, SIOCGIFNETMASK, &ifr);
+ close(fd);
+
+ if (ifr.ifr_netmask.sa_family == AF_INET)
+ {
+ void* p = &((struct sockaddr_in*)(&ifr.ifr_netmask))->sin_addr;
+ inet_ntop(ifr.ifr_netmask.sa_family, p, netMask, sizeof(netMask));
+
+ tell(5, "netmask for device '%s' is '%s'", device, netMask);
+ }
+
+ return netMask;
+}
+
+//***************************************************************************
+// Broadcast Address Of
+//***************************************************************************
+
+const char* bcastAddressOf(const char* ipStr, const char* maskStr)
+{
+ struct in_addr host, mask, broadcast;
+ static char bcastAddress[INET_ADDRSTRLEN] = "";
+
+ if (isEmpty(maskStr))
+ maskStr = "255.255.255.0";
+
+ if (inet_pton(AF_INET, ipStr, &host) == 1 && inet_pton(AF_INET, maskStr, &mask) == 1)
+ {
+ broadcast.s_addr = host.s_addr | ~mask.s_addr;
+
+ if (inet_ntop(AF_INET, &broadcast, bcastAddress, INET_ADDRSTRLEN))
+ tell(5, "Bcast for '%s' is '%s'", ipStr, bcastAddress);
+ else
+ tell(0, "Error: Failed converting number to string");
+ }
+ else
+ {
+ tell(0, "Error: Failed converting strings to numbers");
+ }
+
+ return bcastAddress;
+}
+
+//***************************************************************************
+// MAC Address Of
+//***************************************************************************
+
+const char* getMacOf(const char* device)
+{
+ enum { macTuppel = 6 };
+
+ static char mac[20+TB];
+ struct ifreq ifr;
+ int s;
+
+ s = socket(AF_INET, SOCK_DGRAM, 0);
+ strcpy(ifr.ifr_name, "eth0");
+ ioctl(s, SIOCGIFHWADDR, &ifr);
+
+ for (int i = 0; i < macTuppel; i++)
+ sprintf(&mac[i*3],"%02x:",((unsigned char*)ifr.ifr_hwaddr.sa_data)[i]);
+
+ mac[17] = 0;
+
+ close(s);
+
+ return mac;
+}
+
+#ifdef USELIBARCHIVE
+
+//***************************************************************************
+// unzip <file> and get data of first content which name matches <filter>
+//***************************************************************************
+
+int unzip(const char* file, const char* filter, char*& buffer, int& size, char* entryName)
+{
+ const int step = 1024*10;
+
+ int bufSize = 0;
+ int r;
+ int res;
+
+ struct archive_entry* entry;
+ struct archive* a = archive_read_new();
+
+ *entryName = 0;
+ buffer = 0;
+ size = 0;
+
+ archive_read_support_filter_all(a);
+ archive_read_support_format_all(a);
+
+ r = archive_read_open_filename(a, file, 10204);
+
+ if (r != ARCHIVE_OK)
+ {
+ tell(0, "Error: Open '%s' failed - %s", file, strerror(errno));
+ return 1;
+ }
+
+ while (archive_read_next_header(a, &entry) == ARCHIVE_OK)
+ {
+ strcpy(entryName, archive_entry_pathname(entry));
+
+ if (strstr(entryName, filter))
+ {
+ bufSize = step;
+ buffer = (char*)malloc(bufSize+1);
+
+ while ((res = archive_read_data(a, buffer+size, step)) > 0)
+ {
+ size += res;
+ bufSize += step;
+
+ buffer = (char*)realloc(buffer, bufSize+1);
+ }
+
+ buffer[size] = 0;
+
+ break;
+ }
+ }
+
+ r = archive_read_free(a);
+
+ if (r != ARCHIVE_OK)
+ {
+ size = 0;
+ free(buffer);
+ return fail;
+ }
+
+ return size > 0 ? success : fail;
+}
+
+#endif
+
+//***************************************************************************
+// cMyMutex
+//***************************************************************************
+
+cMyMutex::cMyMutex (void)
+{
+ locked = 0;
+ pthread_mutexattr_t attr;
+ pthread_mutexattr_init(&attr);
+ pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_ERRORCHECK_NP);
+ pthread_mutex_init(&mutex, &attr);
+}
+
+cMyMutex::~cMyMutex()
+{
+ pthread_mutex_destroy(&mutex);
+}
+
+void cMyMutex::Lock(void)
+{
+ pthread_mutex_lock(&mutex);
+ locked++;
+}
+
+void cMyMutex::Unlock(void)
+{
+ if (!--locked)
+ pthread_mutex_unlock(&mutex);
+}
+
+//***************************************************************************
+// cMyTimeMs
+//***************************************************************************
+
+cMyTimeMs::cMyTimeMs(int Ms)
+{
+ if (Ms >= 0)
+ Set(Ms);
+ else
+ begin = 0;
+}
+
+uint64_t cMyTimeMs::Now(void)
+{
+#define MIN_RESOLUTION 5 // ms
+ static bool initialized = false;
+ static bool monotonic = false;
+ struct timespec tp;
+ if (!initialized) {
+ // check if monotonic timer is available and provides enough accurate resolution:
+ if (clock_getres(CLOCK_MONOTONIC, &tp) == 0) {
+ // long Resolution = tp.tv_nsec;
+ // require a minimum resolution:
+ if (tp.tv_sec == 0 && tp.tv_nsec <= MIN_RESOLUTION * 1000000) {
+ if (clock_gettime(CLOCK_MONOTONIC, &tp) == 0) {
+ monotonic = true;
+ }
+ else
+ tell(0, "Error: cMyTimeMs: clock_gettime(CLOCK_MONOTONIC) failed");
+ }
+ else
+ tell(0, "Info: cMyTimeMs: not using monotonic clock - resolution is too bad (%ld s %ld ns)", tp.tv_sec, tp.tv_nsec);
+ }
+ else
+ tell(0, "Error: cMyTimeMs: clock_getres(CLOCK_MONOTONIC) failed");
+ initialized = true;
+ }
+ if (monotonic) {
+ if (clock_gettime(CLOCK_MONOTONIC, &tp) == 0)
+ return (uint64_t(tp.tv_sec)) * 1000 + tp.tv_nsec / 1000000;
+ tell(0, "Error: cMyTimeMs: clock_gettime(CLOCK_MONOTONIC) failed");
+ monotonic = false;
+ // fall back to gettimeofday()
+ }
+ struct timeval t;
+ if (gettimeofday(&t, NULL) == 0)
+ return (uint64_t(t.tv_sec)) * 1000 + t.tv_usec / 1000;
+ return 0;
+}
+
+void cMyTimeMs::Set(int Ms)
+{
+ begin = Now() + Ms;
+}
+
+bool cMyTimeMs::TimedOut(void)
+{
+ return Now() >= begin;
+}
+
+uint64_t cMyTimeMs::Elapsed(void)
+{
+ return Now() - begin;
+}
+
+//**************************************************************************
+// Regular Expression Searching
+//**************************************************************************
+
+int rep(const char* string, const char* expression, Option options)
+{
+ const char* tmpA;
+ const char* tmpB;
+
+ return rep(string, expression, tmpA, tmpB, options);
+}
+
+int rep(const char* string, const char* expression, const char*& s_location,
+ Option options)
+{
+ const char* tmpA;
+
+ return rep(string, expression, s_location, tmpA, options);
+}
+
+
+int rep(const char* string, const char* expression, const char*& s_location,
+ const char*& e_location, Option options)
+{
+ regex_t reg;
+ regmatch_t rm;
+ int status;
+ int opt = 0;
+
+ // Vorbereiten von reg fuer die Expressionsuche mit regexec
+ // Flags: REG_EXTENDED = Use Extended Regular Expressions
+ // REG_ICASE = Ignore case in match.
+
+ reg.re_nsub = 0;
+
+ // Options umwandeln
+ if (options & repUseRegularExpression)
+ opt = opt | REG_EXTENDED;
+ if (options & repIgnoreCase)
+ opt = opt | REG_ICASE;
+
+ if (regcomp( &reg, expression, opt) != 0)
+ return fail;
+
+ // Suchen des ersten Vorkommens von reg in string
+
+ status = regexec(&reg, string, 1, &rm, 0);
+ regfree(&reg);
+
+ if (status != 0)
+ return fail;
+
+ // Suche erfolgreich =>
+ // Setzen der ermittelten Start- und Endpositionen
+
+ s_location = (char*)(string + rm.rm_so);
+ e_location = (char*)(string + rm.rm_eo);
+
+ return success;
+}
+
+//***************************************************************************
+// Class LogDuration
+//***************************************************************************
+
+LogDuration::LogDuration(const char* aMessage, int aLogLevel)
+{
+ logLevel = aLogLevel;
+ strcpy(message, aMessage);
+
+ // at last !
+
+ durationStart = cMyTimeMs::Now();
+}
+
+LogDuration::~LogDuration()
+{
+ tell(logLevel, "duration '%s' was (%ldms)",
+ message, (long)(cMyTimeMs::Now() - durationStart));
+}
+
+void LogDuration::show(const char* label)
+{
+ tell(logLevel, "elapsed '%s' at '%s' was (%ldms)",
+ message, label, (long)(cMyTimeMs::Now() - durationStart));
+}
+
+//***************************************************************************
+// Get Unique ID
+//***************************************************************************
+
+#ifdef USEUUID
+const char* getUniqueId()
+{
+ static char uuid[sizeUuid+TB] = "";
+
+ uuid_t id;
+ uuid_generate(id);
+ uuid_unparse_upper(id, uuid);
+
+ return uuid;
+}
+#endif // USEUUID
+
+//***************************************************************************
+// Create MD5
+//***************************************************************************
+
+#ifdef USEMD5
+
+int createMd5(const char* buf, md5* md5)
+{
+ MD5_CTX c;
+ unsigned char out[MD5_DIGEST_LENGTH];
+
+ MD5_Init(&c);
+ MD5_Update(&c, buf, strlen(buf));
+ MD5_Final(out, &c);
+
+ for (int n = 0; n < MD5_DIGEST_LENGTH; n++)
+ sprintf(md5+2*n, "%02x", out[n]);
+
+ md5[sizeMd5] = 0;
+
+ return done;
+}
+
+int createMd5OfFile(const char* path, const char* name, md5* md5)
+{
+ FILE* f;
+ char buffer[1000];
+ int nread = 0;
+ MD5_CTX c;
+ unsigned char out[MD5_DIGEST_LENGTH];
+ char* file = 0;
+
+ asprintf(&file, "%s/%s", path, name);
+
+ if (!(f = fopen(file, "r")))
+ {
+ tell(0, "Fatal: Cannot build MD5 of '%s'; Error was '%s'", file, strerror(errno));
+ free(file);
+ return fail;
+ }
+
+ free(file);
+
+ MD5_Init(&c);
+
+ while ((nread = fread(buffer, 1, 1000, f)) > 0)
+ MD5_Update(&c, buffer, nread);
+
+ fclose(f);
+
+ MD5_Final(out, &c);
+
+ for (int n = 0; n < MD5_DIGEST_LENGTH; n++)
+ sprintf(md5+2*n, "%02x", out[n]);
+
+ md5[sizeMd5] = 0;
+
+ return success;
+}
+
+#endif // USEMD5
+
+//***************************************************************************
+// Url Unescape
+//***************************************************************************
+/*
+ * The buffer pointed to by @dst must be at least strlen(@src) bytes.
+ * Decoding stops at the first character from @src that decodes to null.
+
+ * Path normalization will remove redundant slashes and slash+dot sequences,
+ * as well as removing path components when slash+dot+dot is found. It will
+ * keep the root slash (if one was present) and will stop normalization
+ * at the first questionmark found (so query parameters won't be normalized).
+ *
+ * @param dst destination buffer
+ * @param src source buffer
+ * @param normalize perform path normalization if nonzero
+ * @return number of valid characters in @dst
+ */
+
+int urlUnescape(char* dst, const char* src, int normalize)
+{
+// CURL* curl;
+// int resultSize;
+
+// if (curl_global_init(CURL_GLOBAL_ALL) != 0)
+// {
+// tell(0, "Error, something went wrong with curl_global_init()");
+
+// return fail;
+// }
+
+// curl = curl_easy_init();
+
+// if (!curl)
+// {
+// tell(0, "Error, unable to get handle from curl_easy_init()");
+
+// return fail;
+// }
+
+// dst = curl_easy_unescape(curl, src, strlen(src), &resultSize);
+
+// tell(0, " [%.40s]", src);
+
+// tell(0, "res size %d [%.40s]", resultSize, dst);
+// return resultSize;
+
+ char* org_dst = dst;
+ int slash_dot_dot = 0;
+ char ch, a, b;
+
+ a = 0;
+
+ do {
+ ch = *src++;
+
+ if (ch == '%' && isxdigit(a = src[0]) && isxdigit(b = src[1]))
+ {
+ if (a < 'A')
+ a -= '0';
+ else if
+ (a < 'a') a -= 'A' - 10;
+ else
+ a -= 'a' - 10;
+
+ if (b < 'A')
+ b -= '0';
+ else if (b < 'a')
+ b -= 'A' - 10;
+ else
+ b -= 'a' - 10;
+
+ ch = 16 * a + b;
+ src += 2;
+ }
+
+ if (normalize)
+ {
+ switch (ch)
+ {
+ case '/': // compress consecutive slashes and remove slash-dot
+ if (slash_dot_dot < 3)
+ {
+
+ dst -= slash_dot_dot;
+ slash_dot_dot = 1;
+ break;
+ }
+ // fall-through
+
+ case '?': // at start of query, stop normalizing
+ if (ch == '?')
+ normalize = 0;
+
+ // fall-through
+
+ case '\0': // remove trailing slash-dot-(dot)
+ if (slash_dot_dot > 1)
+ {
+ dst -= slash_dot_dot;
+
+ // remove parent directory if it was two dots
+
+ if (slash_dot_dot == 3)
+ while (dst > org_dst && *--dst != '/')
+ ; // empty body
+ slash_dot_dot = (ch == '/') ? 1 : 0;
+
+ // keep the root slash if any
+
+ if (!slash_dot_dot && dst == org_dst && *dst == '/')
+ ++dst;
+
+ }
+ break;
+
+ case '.':
+ if (slash_dot_dot == 1 || slash_dot_dot == 2)
+ {
+ ++slash_dot_dot;
+ break;
+ }
+ // fall-through
+
+ default:
+ slash_dot_dot = 0;
+ }
+ }
+
+ *dst++ = ch;
+ } while(ch);
+
+ return (dst - org_dst) - 1;
+}