vdr-plugin-scraper2vdr/lib/common.c
2014-05-09 01:05:21 +02:00

1205 lines
27 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
* common.c:
*
* See the README file for copyright information and how to reach the author.
*
*/
#include <sys/stat.h>
#include <sys/time.h>
#ifdef USEUUID
# include <uuid/uuid.h>
#endif
#include <stdarg.h>
#include <stdio.h>
#include <string.h>
#include <syslog.h>
#include <unistd.h>
#include <zlib.h>
#include <errno.h>
#ifdef USELIBARCHIVE
# include <archive.h>
# include <archive_entry.h>
#endif
#ifdef VDR_PLUGIN
# include <vdr/thread.h>
#endif
#include "common.h"
#include "config.h"
#ifdef VDR_PLUGIN
cMutex logMutex;
#endif
//***************************************************************************
// Debug
//***************************************************************************
void tell(int eloquence, const char* format, ...)
{
if (EPG2VDRConfig.loglevel < eloquence)
return ;
const int sizeBuffer = 100000;
char t[sizeBuffer+100]; *t = 0;
va_list ap;
#ifdef VDR_PLUGIN
cMutexLock lock(&logMutex);
#endif
va_start(ap, format);
#ifdef VDR_PLUGIN
snprintf(t, sizeBuffer, LOG_PREFIX);
#endif
vsnprintf(t+strlen(t), sizeBuffer-strlen(t), format, ap);
if (EPG2VDRConfig.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);
va_end(ap);
}
//***************************************************************************
// 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 = 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 = 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 = max(mblen(&s[ps], lenSrc-ps), 1);
for (int pi = 0; pi < lenIgn; pi += csIgn)
{
csIgn = 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;
csSrc = max(mblen(&s[ps], lenSrc-ps), 1);
for (int pi = 0; pi < lenIgn; pi += csIgn)
{
csIgn = 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);
}
//***************************************************************************
// 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));
}
//***************************************************************************
// 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)
{
char txt[30];
tm* tmp = localtime(&t);
strftime(txt, sizeof(txt), "%d.%m.%Y %T", tmp);
return std::string(txt);
}
//***************************************************************************
// MS to Duration
//***************************************************************************
std::string ms2Dur(uint64_t t)
{
char txt[30];
int s = t / 1000;
int ms = t % 1000;
snprintf(txt, sizeof(txt), "%d.%03d seconds", s, ms);
return std::string(txt);
}
//***************************************************************************
// Char to Char-String
//***************************************************************************
const char* c2s(char c, char* buf)
{
sprintf(buf, "%c", c);
return buf;
}
//***************************************************************************
// 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", 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, "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)
{
if (!str)
return "<null>";
return str;
}
int isZero(const char* str)
{
const char* p = str;
while (p && *p)
{
if (*p != '0')
return no;
p++;
}
return yes;
}
//***************************************************************************
//
//***************************************************************************
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 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
//***************************************************************************
// 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;
if ((res = deflateInit2(&stream, Z_DEFAULT_COMPRESSION, Z_DEFLATED, 15 + 16, 8, Z_DEFAULT_STRATEGY)) != Z_OK)
return res;
res = deflate(&stream, Z_FINISH);
if (res != Z_STREAM_END)
{
deflateEnd(&stream);
return res == Z_OK ? Z_BUF_ERROR : res;
}
*destLen = stream.total_out;
res = deflateEnd(&stream);
return res;
}
int gzip(MemoryStruct* data, MemoryStruct* zippedData)
{
int res;
uLong sizeMax = compressBound(data->size) + 512;
zippedData->clear();
zippedData->memory = (char*)malloc(sizeMax);
if ((res = _gzip((Bytef*)zippedData->memory, &sizeMax, (Bytef*)data->memory, data->size)) != Z_OK)
{
tellZipError(res, " during compression", "");
return fail;
}
zippedData->copyAttributes(data);
zippedData->size = sizeMax;
sprintf(zippedData->contentEncoding, "gzip");
return success;
}
//*************************************************************************
// 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;
}
}
//*************************************************************************
// 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()
{
struct ifaddrs *ifaddr, *ifa;
static char host[NI_MAXHOST] = "";
if (getifaddrs(&ifaddr) == -1)
{
tell(0, "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, "getnameinfo() failed: %s", gai_strerror(res));
return "";
}
// skip loopback interface
if (strcmp(host, "127.0.0.1") == 0)
continue;
tell(5, "%-8s %-15s %s", ifa->ifa_name, host,
ifa->ifa_addr->sa_family == AF_INET ? " (AF_INET)" :
ifa->ifa_addr->sa_family == AF_INET6 ? " (AF_INET6)" : "");
}
}
freeifaddrs(ifaddr);
return host;
}
#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
//***************************************************************************
// Class LogDuration
//***************************************************************************
#ifdef VDR_PLUGIN
# include <vdr/plugin.h>
LogDuration::LogDuration(const char* aMessage, int aLogLevel)
{
logLevel = aLogLevel;
strcpy(message, aMessage);
// at last !
durationStart = cTimeMs::Now();
}
LogDuration::~LogDuration()
{
tell(logLevel, "duration '%s' was (%ldms)",
message, cTimeMs::Now() - durationStart);
}
void LogDuration::show(const char* label)
{
tell(logLevel, "elapsed '%s' at '%s' was (%ldms)",
message, label, cTimeMs::Now() - durationStart);
}
#endif
//***************************************************************************
// 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: Can't access '%s'; %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;
}