/* * common.c: * * See the README file for copyright information and how to reach the author. * */ #include #include #ifdef USEUUID # include #endif #include #include #include #include #include #include #include #ifdef USELIBARCHIVE # include # include #endif #include "common.h" #include "config.h" cMyMutex logMutex; //*************************************************************************** // Debug //*************************************************************************** const char* getLogPrefix() { return logPrefix; } 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; logMutex.Lock(); va_start(ap, format); if (getLogPrefix()) snprintf(t, sizeBuffer, "%s", getLogPrefix()); 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); logMutex.Unlock(); 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 ""; 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 #include #include 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 and get data of first content which name matches //*************************************************************************** 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, "cMyTimeMs: clock_gettime(CLOCK_MONOTONIC) failed"); } else tell(0, "cMyTimeMs: not using monotonic clock - resolution is too bad (%ld s %ld ns)", tp.tv_sec, tp.tv_nsec); } else tell(0, "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, "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; } //*************************************************************************** // 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, cMyTimeMs::Now() - durationStart); } void LogDuration::show(const char* label) { tell(logLevel, "elapsed '%s' at '%s' was (%ldms)", message, label, 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: 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; }