diff options
Diffstat (limited to 'apt-pkg/contrib/strutl.cc')
-rw-r--r-- | apt-pkg/contrib/strutl.cc | 1820 |
1 files changed, 1820 insertions, 0 deletions
diff --git a/apt-pkg/contrib/strutl.cc b/apt-pkg/contrib/strutl.cc new file mode 100644 index 0000000..3689dc1 --- /dev/null +++ b/apt-pkg/contrib/strutl.cc @@ -0,0 +1,1820 @@ +// -*- mode: cpp; mode: fold -*- +// SPDX-License-Identifier: GPL-2.0+ +// Description /*{{{*/ +/* ###################################################################### + + String Util - Some useful string functions. + + These have been collected from here and there to do all sorts of useful + things to strings. They are useful in file parsers, URI handlers and + especially in APT methods. + + This file had this historic note, but now includes further changes + under the GPL-2.0+: + + This source is placed in the Public Domain, do with it what you will + It was originally written by Jason Gunthorpe <jgg@gpu.srv.ualberta.ca> + + ##################################################################### */ + /*}}}*/ +// Includes /*{{{*/ +#include <config.h> + +#include <apt-pkg/error.h> +#include <apt-pkg/fileutl.h> +#include <apt-pkg/strutl.h> + +#include <algorithm> +#include <array> +#include <iomanip> +#include <limits> +#include <locale> +#include <sstream> +#include <memory> +#include <sstream> +#include <string> +#include <vector> + +#include <cctype> +#include <cerrno> +#include <cstdarg> +#include <cstddef> +#include <cstdio> +#include <cstdlib> +#include <cstring> +#include <ctime> +#include <cwchar> +#include <iconv.h> +#include <regex.h> +#include <unistd.h> + +#include <apti18n.h> + /*}}}*/ +using namespace std; + +// Strip - Remove white space from the front and back of a string /*{{{*/ +// --------------------------------------------------------------------- +namespace APT { + namespace String { +std::string Strip(const std::string &str) +{ + // ensure we have at least one character + if (str.empty() == true) + return str; + + char const * const s = str.c_str(); + size_t start = 0; + for (; isspace(s[start]) != 0; ++start) + ; // find the first not-space + + // string contains only whitespaces + if (s[start] == '\0') + return ""; + + size_t end = str.length() - 1; + for (; isspace(s[end]) != 0; --end) + ; // find the last not-space + + return str.substr(start, end - start + 1); +} + +bool Endswith(const std::string &s, const std::string &end) +{ + if (end.size() > s.size()) + return false; + return (s.compare(s.size() - end.size(), end.size(), end) == 0); +} + +bool Startswith(const std::string &s, const std::string &start) +{ + if (start.size() > s.size()) + return false; + return (s.compare(0, start.size(), start) == 0); +} + +std::string Join(std::vector<std::string> list, const std::string &sep) +{ + std::ostringstream oss; + for (auto it = list.begin(); it != list.end(); it++) + { + if (it != list.begin()) oss << sep; + oss << *it; + } + return oss.str(); +} + +// Returns string display length honoring multi-byte characters +size_t DisplayLength(StringView str) +{ + size_t len = 0; + + const char *p = str.data(); + const char *const end = str.end(); + + mbstate_t state{}; + while (p < end) + { + wchar_t wch; + size_t res = mbrtowc(&wch, p, end - p, &state); + switch (res) + { + case 0: + // Null wide character (i.e. L'\0') - stop + p = end; + break; + + case static_cast<size_t>(-1): + // Byte sequence is invalid. Assume that it's + // a single-byte single-width character. + len += 1; + p += 1; + + // state is undefined in this case - reset it + state = {}; + + break; + + case static_cast<size_t>(-2): + // Byte sequence is too short. Assume that it's + // an incomplete single-width character and stop. + len += 1; + p = end; + break; + + default: + len += wcwidth(wch); + p += res; + } + } + + return len; +} + +} +} + /*}}}*/ +// UTF8ToCodeset - Convert some UTF-8 string for some codeset /*{{{*/ +// --------------------------------------------------------------------- +/* This is handy to use before display some information for enduser */ +bool UTF8ToCodeset(const char *codeset, const string &orig, string *dest) +{ + iconv_t cd; + const char *inbuf; + char *inptr, *outbuf; + size_t insize, bufsize; + dest->clear(); + + cd = iconv_open(codeset, "UTF-8"); + if (cd == (iconv_t)(-1)) { + // Something went wrong + if (errno == EINVAL) + _error->Error("conversion from 'UTF-8' to '%s' not available", + codeset); + else + perror("iconv_open"); + + return false; + } + + insize = bufsize = orig.size(); + inbuf = orig.data(); + inptr = (char *)inbuf; + outbuf = new char[bufsize]; + size_t lastError = -1; + + while (insize != 0) + { + char *outptr = outbuf; + size_t outsize = bufsize; + size_t const err = iconv(cd, &inptr, &insize, &outptr, &outsize); + dest->append(outbuf, outptr - outbuf); + if (err == (size_t)(-1)) + { + switch (errno) + { + case EILSEQ: + insize--; + inptr++; + // replace a series of unknown multibytes with a single "?" + if (lastError != insize) { + lastError = insize - 1; + dest->append("?"); + } + break; + case EINVAL: + insize = 0; + break; + case E2BIG: + if (outptr == outbuf) + { + bufsize *= 2; + delete[] outbuf; + outbuf = new char[bufsize]; + } + break; + } + } + } + + delete[] outbuf; + + iconv_close(cd); + + return true; +} + /*}}}*/ +// strstrip - Remove white space from the front and back of a string /*{{{*/ +// --------------------------------------------------------------------- +/* This is handy to use when parsing a file. It also removes \n's left + over from fgets and company */ +char *_strstrip(char *String) +{ + for (;*String != 0 && (*String == ' ' || *String == '\t'); String++); + + if (*String == 0) + return String; + return _strrstrip(String); +} + /*}}}*/ +// strrstrip - Remove white space from the back of a string /*{{{*/ +// --------------------------------------------------------------------- +char *_strrstrip(char *String) +{ + char *End = String + strlen(String) - 1; + for (;End != String - 1 && (*End == ' ' || *End == '\t' || *End == '\n' || + *End == '\r'); End--); + End++; + *End = 0; + return String; +} + /*}}}*/ +// strtabexpand - Converts tabs into 8 spaces /*{{{*/ +// --------------------------------------------------------------------- +/* */ +char *_strtabexpand(char *String,size_t Len) +{ + for (char *I = String; I != I + Len && *I != 0; I++) + { + if (*I != '\t') + continue; + if (I + 8 > String + Len) + { + *I = 0; + return String; + } + + /* Assume the start of the string is 0 and find the next 8 char + division */ + int Len; + if (String == I) + Len = 1; + else + Len = 8 - ((String - I) % 8); + Len -= 2; + if (Len <= 0) + { + *I = ' '; + continue; + } + + memmove(I + Len,I + 1,strlen(I) + 1); + for (char *J = I; J + Len != I; *I = ' ', I++); + } + return String; +} + /*}}}*/ +// ParseQuoteWord - Parse a single word out of a string /*{{{*/ +// --------------------------------------------------------------------- +/* This grabs a single word, converts any % escaped characters to their + proper values and advances the pointer. Double quotes are understood + and striped out as well. This is for URI/URL parsing. It also can + understand [] brackets.*/ +bool ParseQuoteWord(const char *&String,string &Res) +{ + // Skip leading whitespace + const char *C = String; + for (; *C == ' '; C++) + ; + if (*C == 0) + return false; + + // Jump to the next word + for (;*C != 0 && isspace(*C) == 0; C++) + { + if (*C == '"') + { + C = strchr(C + 1, '"'); + if (C == NULL) + return false; + } + if (*C == '[') + { + C = strchr(C + 1, ']'); + if (C == NULL) + return false; + } + } + + // Now de-quote characters + Res.clear(); + Res.reserve(C - String); + char Tmp[3]; + const char *Start = String; + while (Start != C) + { + if (*Start == '%' && Start + 2 < C && + isxdigit(Start[1]) && isxdigit(Start[2])) + { + Tmp[0] = Start[1]; + Tmp[1] = Start[2]; + Tmp[2] = 0; + Res.push_back(static_cast<char>(strtol(Tmp, 0, 16))); + Start += 3; + continue; + } + if (*Start != '"') + Res.push_back(*Start); + ++Start; + } + + // Skip ending white space + for (; isspace(*C) != 0; C++) + ; + String = C; + return true; +} + /*}}}*/ +// ParseCWord - Parses a string like a C "" expression /*{{{*/ +// --------------------------------------------------------------------- +/* This expects a series of space separated strings enclosed in ""'s. + It concatenates the ""'s into a single string. */ +bool ParseCWord(const char *&String,string &Res) +{ + // Skip leading whitespace + const char *C = String; + for (; *C == ' '; C++) + ; + if (*C == 0) + return false; + + Res.clear(); + Res.reserve(strlen(String)); + for (; *C != 0; ++C) + { + if (*C == '"') + { + for (C++; *C != 0 && *C != '"'; C++) + Res.push_back(*C); + + if (*C == 0) + return false; + + continue; + } + + if (C != String && isspace(*C) != 0 && isspace(C[-1]) != 0) + continue; + if (isspace(*C) == 0) + return false; + Res.push_back(' '); + } + String = C; + return true; +} + /*}}}*/ +// QuoteString - Convert a string into quoted from /*{{{*/ +// --------------------------------------------------------------------- +/* */ +string QuoteString(const string &Str, const char *Bad) +{ + std::stringstream Res; + for (string::const_iterator I = Str.begin(); I != Str.end(); ++I) + { + if (strchr(Bad,*I) != 0 || isprint(*I) == 0 || + *I == 0x25 || // percent '%' char + *I <= 0x20 || *I >= 0x7F) // control chars + { + ioprintf(Res, "%%%02hhx", *I); + } + else + Res << *I; + } + return Res.str(); +} + /*}}}*/ +// DeQuoteString - Convert a string from quoted from /*{{{*/ +// --------------------------------------------------------------------- +/* This undoes QuoteString */ +string DeQuoteString(const string &Str) +{ + return DeQuoteString(Str.begin(),Str.end()); +} +string DeQuoteString(string::const_iterator const &begin, + string::const_iterator const &end) +{ + string Res; + for (string::const_iterator I = begin; I != end; ++I) + { + if (*I == '%' && I + 2 < end && + isxdigit(I[1]) && isxdigit(I[2])) + { + char Tmp[3]; + Tmp[0] = I[1]; + Tmp[1] = I[2]; + Tmp[2] = 0; + Res += (char)strtol(Tmp,0,16); + I += 2; + continue; + } + else + Res += *I; + } + return Res; +} + + /*}}}*/ +// SizeToStr - Convert a long into a human readable size /*{{{*/ +// --------------------------------------------------------------------- +/* A max of 4 digits are shown before conversion to the next highest unit. + The max length of the string will be 5 chars unless the size is > 10 + YottaBytes (E24) */ +string SizeToStr(double Size) +{ + double ASize; + if (Size >= 0) + ASize = Size; + else + ASize = -1*Size; + + /* bytes, KiloBytes, MegaBytes, GigaBytes, TeraBytes, PetaBytes, + ExaBytes, ZettaBytes, YottaBytes */ + char Ext[] = {'\0','k','M','G','T','P','E','Z','Y'}; + int I = 0; + while (I <= 8) + { + if (ASize < 100 && I != 0) + { + std::string S; + strprintf(S, "%'.1f %c", ASize, Ext[I]); + return S; + } + + if (ASize < 10000) + { + std::string S; + strprintf(S, "%'.0f %c", ASize, Ext[I]); + return S; + } + ASize /= 1000.0; + I++; + } + return ""; +} + /*}}}*/ +// TimeToStr - Convert the time into a string /*{{{*/ +// --------------------------------------------------------------------- +/* Converts a number of seconds to a hms format */ +string TimeToStr(unsigned long Sec) +{ + std::string S; + if (Sec > 60*60*24) + { + //TRANSLATOR: d means days, h means hours, min means minutes, s means seconds + strprintf(S,_("%lid %lih %limin %lis"),Sec/60/60/24,(Sec/60/60) % 24,(Sec/60) % 60,Sec % 60); + } + else if (Sec > 60*60) + { + //TRANSLATOR: h means hours, min means minutes, s means seconds + strprintf(S,_("%lih %limin %lis"),Sec/60/60,(Sec/60) % 60,Sec % 60); + } + else if (Sec > 60) + { + //TRANSLATOR: min means minutes, s means seconds + strprintf(S,_("%limin %lis"),Sec/60,Sec % 60); + } + else + { + //TRANSLATOR: s means seconds + strprintf(S,_("%lis"),Sec); + } + return S; +} + /*}}}*/ +// SubstVar - Substitute a string for another string /*{{{*/ +// --------------------------------------------------------------------- +/* This replaces all occurrences of Subst with Contents in Str. */ +string SubstVar(const string &Str,const string &Subst,const string &Contents) +{ + if (Subst.empty() == true) + return Str; + + string::size_type Pos = 0; + string::size_type OldPos = 0; + string Temp; + + while (OldPos < Str.length() && + (Pos = Str.find(Subst,OldPos)) != string::npos) + { + if (OldPos != Pos) + Temp.append(Str, OldPos, Pos - OldPos); + if (Contents.empty() == false) + Temp.append(Contents); + OldPos = Pos + Subst.length(); + } + + if (OldPos == 0) + return Str; + + if (OldPos >= Str.length()) + return Temp; + + Temp.append(Str, OldPos, string::npos); + return Temp; +} +string SubstVar(string Str,const struct SubstVar *Vars) +{ + for (; Vars->Subst != 0; Vars++) + Str = SubstVar(Str,Vars->Subst,*Vars->Contents); + return Str; +} + /*}}}*/ +// OutputInDepth - return a string with separator multiplied with depth /*{{{*/ +// --------------------------------------------------------------------- +/* Returns a string with the supplied separator depth + 1 times in it */ +std::string OutputInDepth(const unsigned long Depth, const char* Separator) +{ + std::string output = ""; + for(unsigned long d=Depth+1; d > 0; d--) + output.append(Separator); + return output; +} + /*}}}*/ +// URItoFileName - Convert the uri into a unique file name /*{{{*/ +// --------------------------------------------------------------------- +/* This converts a URI into a safe filename. It quotes all unsafe characters + and converts / to _ and removes the scheme identifier. The resulting + file name should be unique and never occur again for a different file */ +string URItoFileName(const string &URI) +{ + // Nuke 'sensitive' items + ::URI U(URI); + U.User.clear(); + U.Password.clear(); + U.Access.clear(); + + // "\x00-\x20{}|\\\\^\\[\\]<>\"\x7F-\xFF"; + string NewURI = QuoteString(U,"\\|{}[]<>\"^~_=!@#$%^&*"); + replace(NewURI.begin(),NewURI.end(),'/','_'); + return NewURI; +} + /*}}}*/ +// Base64Encode - Base64 Encoding routine for short strings /*{{{*/ +// --------------------------------------------------------------------- +/* This routine performs a base64 transformation on a string. It was ripped + from wget and then patched and bug fixed. + + This spec can be found in rfc2045 */ +string Base64Encode(const string &S) +{ + // Conversion table. + static char tbl[64] = {'A','B','C','D','E','F','G','H', + 'I','J','K','L','M','N','O','P', + 'Q','R','S','T','U','V','W','X', + 'Y','Z','a','b','c','d','e','f', + 'g','h','i','j','k','l','m','n', + 'o','p','q','r','s','t','u','v', + 'w','x','y','z','0','1','2','3', + '4','5','6','7','8','9','+','/'}; + + // Pre-allocate some space + string Final; + Final.reserve((4*S.length() + 2)/3 + 2); + + /* Transform the 3x8 bits to 4x6 bits, as required by + base64. */ + for (string::const_iterator I = S.begin(); I < S.end(); I += 3) + { + uint8_t Bits[3] = {0,0,0}; + Bits[0] = I[0]; + if (I + 1 < S.end()) + Bits[1] = I[1]; + if (I + 2 < S.end()) + Bits[2] = I[2]; + + Final += tbl[Bits[0] >> 2]; + Final += tbl[((Bits[0] & 3) << 4) + (Bits[1] >> 4)]; + + if (I + 1 >= S.end()) + break; + + Final += tbl[((Bits[1] & 0xf) << 2) + (Bits[2] >> 6)]; + + if (I + 2 >= S.end()) + break; + + Final += tbl[Bits[2] & 0x3f]; + } + + /* Apply the padding elements, this tells how many bytes the remote + end should discard */ + if (S.length() % 3 == 2) + Final += '='; + if (S.length() % 3 == 1) + Final += "=="; + + return Final; +} + /*}}}*/ +// stringcmp - Arbitrary string compare /*{{{*/ +// --------------------------------------------------------------------- +/* This safely compares two non-null terminated strings of arbitrary + length */ +int stringcmp(const char *A,const char *AEnd,const char *B,const char *BEnd) +{ + for (; A != AEnd && B != BEnd; A++, B++) + if (*A != *B) + break; + + if (A == AEnd && B == BEnd) + return 0; + if (A == AEnd) + return 1; + if (B == BEnd) + return -1; + if (*A < *B) + return -1; + return 1; +} + +#if __GNUC__ >= 3 +int stringcmp(string::const_iterator A,string::const_iterator AEnd, + const char *B,const char *BEnd) +{ + for (; A != AEnd && B != BEnd; A++, B++) + if (*A != *B) + break; + + if (A == AEnd && B == BEnd) + return 0; + if (A == AEnd) + return 1; + if (B == BEnd) + return -1; + if (*A < *B) + return -1; + return 1; +} +int stringcmp(string::const_iterator A,string::const_iterator AEnd, + string::const_iterator B,string::const_iterator BEnd) +{ + for (; A != AEnd && B != BEnd; A++, B++) + if (*A != *B) + break; + + if (A == AEnd && B == BEnd) + return 0; + if (A == AEnd) + return 1; + if (B == BEnd) + return -1; + if (*A < *B) + return -1; + return 1; +} +#endif + /*}}}*/ +// stringcasecmp - Arbitrary case insensitive string compare /*{{{*/ +// --------------------------------------------------------------------- +/* */ +int stringcasecmp(const char *A,const char *AEnd,const char *B,const char *BEnd) +{ + for (; A != AEnd && B != BEnd; A++, B++) + if (tolower_ascii(*A) != tolower_ascii(*B)) + break; + + if (A == AEnd && B == BEnd) + return 0; + if (A == AEnd) + return 1; + if (B == BEnd) + return -1; + if (tolower_ascii(*A) < tolower_ascii(*B)) + return -1; + return 1; +} +#if __GNUC__ >= 3 +int stringcasecmp(string::const_iterator A,string::const_iterator AEnd, + const char *B,const char *BEnd) +{ + for (; A != AEnd && B != BEnd; A++, B++) + if (tolower_ascii(*A) != tolower_ascii(*B)) + break; + + if (A == AEnd && B == BEnd) + return 0; + if (A == AEnd) + return 1; + if (B == BEnd) + return -1; + if (tolower_ascii(*A) < tolower_ascii(*B)) + return -1; + return 1; +} +int stringcasecmp(string::const_iterator A,string::const_iterator AEnd, + string::const_iterator B,string::const_iterator BEnd) +{ + for (; A != AEnd && B != BEnd; A++, B++) + if (tolower_ascii(*A) != tolower_ascii(*B)) + break; + + if (A == AEnd && B == BEnd) + return 0; + if (A == AEnd) + return 1; + if (B == BEnd) + return -1; + if (tolower_ascii(*A) < tolower_ascii(*B)) + return -1; + return 1; +} +#endif + /*}}}*/ +// LookupTag - Lookup the value of a tag in a tagged string /*{{{*/ +// --------------------------------------------------------------------- +/* The format is like those used in package files and the method + communication system */ +std::string LookupTag(const std::string &Message, const char *TagC, const char *Default) +{ + std::string tag = std::string("\n") + TagC + ":"; + if (Default == nullptr) + Default = ""; + if (Message.length() < tag.length()) + return Default; + std::transform(tag.begin(), tag.end(), tag.begin(), tolower_ascii); + auto valuestart = Message.cbegin(); + // maybe the message starts directly with tag + if (Message[tag.length() - 2] == ':') + { + std::string lowstart = std::string("\n") + Message.substr(0, tag.length() - 1); + std::transform(lowstart.begin(), lowstart.end(), lowstart.begin(), tolower_ascii); + if (lowstart == tag) + valuestart = std::next(valuestart, tag.length() - 1); + } + // the tag is somewhere in the message + if (valuestart == Message.cbegin()) + { + auto const tagbegin = std::search(Message.cbegin(), Message.cend(), tag.cbegin(), tag.cend(), + [](char const a, char const b) { return tolower_ascii(a) == b; }); + if (tagbegin == Message.cend()) + return Default; + valuestart = std::next(tagbegin, tag.length()); + } + auto const is_whitespace = [](char const c) { return isspace_ascii(c) != 0 && c != '\n'; }; + auto const is_newline = [](char const c) { return c == '\n'; }; + std::string result; + valuestart = std::find_if_not(valuestart, Message.cend(), is_whitespace); + // is the first line of the value empty? + if (valuestart != Message.cend() && *valuestart == '\n') + { + valuestart = std::next(valuestart); + if (valuestart != Message.cend() && *valuestart == ' ') + valuestart = std::next(valuestart); + } + // extract the value over multiple lines removing trailing whitespace + while (valuestart < Message.cend()) + { + auto const linebreak = std::find_if(valuestart, Message.cend(), is_newline); + auto valueend = std::prev(linebreak); + // skip spaces at the end of the line + while (valueend > valuestart && is_whitespace(*valueend)) + valueend = std::prev(valueend); + // append found line to result + { + std::string tmp(valuestart, std::next(valueend)); + if (tmp != ".") + { + if (result.empty()) + result.assign(std::move(tmp)); + else + result.append(tmp); + } + } + // see if the value is multiline + if (linebreak == Message.cend()) + break; + valuestart = std::next(linebreak); + if (valuestart == Message.cend() || *valuestart != ' ') + break; + result.append("\n"); + // skip the space leading a multiline (Keep all other whitespaces in the value) + valuestart = std::next(valuestart); + } + auto const valueend = result.find_last_not_of("\n"); + if (valueend == std::string::npos) + result.clear(); + else + result.erase(valueend + 1); + return result; +} + /*}}}*/ +// StringToBool - Converts a string into a boolean /*{{{*/ +// --------------------------------------------------------------------- +/* This inspects the string to see if it is true or if it is false and + then returns the result. Several variants on true/false are checked. */ +int StringToBool(const string &Text,int Default) +{ + char *ParseEnd; + int Res = strtol(Text.c_str(),&ParseEnd,0); + // ensure that the entire string was converted by strtol to avoid + // failures on "apt-cache show -a 0ad" where the "0" is converted + const char *TextEnd = Text.c_str()+Text.size(); + if (ParseEnd == TextEnd && Res >= 0 && Res <= 1) + return Res; + + // Check for positives + if (strcasecmp(Text.c_str(),"no") == 0 || + strcasecmp(Text.c_str(),"false") == 0 || + strcasecmp(Text.c_str(),"without") == 0 || + strcasecmp(Text.c_str(),"off") == 0 || + strcasecmp(Text.c_str(),"disable") == 0) + return 0; + + // Check for negatives + if (strcasecmp(Text.c_str(),"yes") == 0 || + strcasecmp(Text.c_str(),"true") == 0 || + strcasecmp(Text.c_str(),"with") == 0 || + strcasecmp(Text.c_str(),"on") == 0 || + strcasecmp(Text.c_str(),"enable") == 0) + return 1; + + return Default; +} + /*}}}*/ +// TimeRFC1123 - Convert a time_t into RFC1123 format /*{{{*/ +// --------------------------------------------------------------------- +/* This converts a time_t into a string time representation that is + year 2000 compliant and timezone neutral */ +string TimeRFC1123(time_t Date, bool const NumericTimezone) +{ + struct tm Conv; + if (gmtime_r(&Date, &Conv) == NULL) + return ""; + + auto const posix = std::locale::classic(); + std::ostringstream datestr; + datestr.imbue(posix); + APT::StringView const fmt("%a, %d %b %Y %H:%M:%S"); + std::use_facet<std::time_put<char>>(posix).put( + std::ostreambuf_iterator<char>(datestr), + datestr, ' ', &Conv, fmt.data(), fmt.data() + fmt.size()); + if (NumericTimezone) + datestr << " +0000"; + else + datestr << " GMT"; + return datestr.str(); +} + /*}}}*/ +// ReadMessages - Read messages from the FD /*{{{*/ +// --------------------------------------------------------------------- +/* This pulls full messages from the input FD into the message buffer. + It assumes that messages will not pause during transit so no + fancy buffering is used. + + In particular: this reads blocks from the input until it believes + that it's run out of input text. Each block is terminated by a + double newline ('\n' followed by '\n'). + */ +bool ReadMessages(int Fd, vector<string> &List) +{ + char Buffer[64000]; + // Represents any left-over from the previous iteration of the + // parse loop. (i.e., if a message is split across the end + // of the buffer, it goes here) + string PartialMessage; + + do { + int const Res = read(Fd, Buffer, sizeof(Buffer)); + if (Res < 0 && errno == EINTR) + continue; + + // process we read from has died + if (Res == 0) + return false; + + // No data +#if EAGAIN != EWOULDBLOCK + if (Res < 0 && (errno == EAGAIN || errno == EWOULDBLOCK)) +#else + if (Res < 0 && errno == EAGAIN) +#endif + return true; + if (Res < 0) + return false; + + // extract the message(s) from the buffer + char const *Start = Buffer; + char const * const End = Buffer + Res; + + char const * NL = (char const *) memchr(Start, '\n', End - Start); + if (NL == NULL) + { + // end of buffer: store what we have so far and read new data in + PartialMessage.append(Start, End - Start); + Start = End; + } + else + ++NL; + + if (PartialMessage.empty() == false && Start < End) + { + // if we start with a new line, see if the partial message we have ended with one + // so that we properly detect records ending between two read() runs + // cases are: \n|\n , \r\n|\r\n and \r\n\r|\n + // the case \r|\n\r\n is handled by the usual double-newline handling + if ((NL - Start) == 1 || ((NL - Start) == 2 && *Start == '\r')) + { + if (APT::String::Endswith(PartialMessage, "\n") || APT::String::Endswith(PartialMessage, "\r\n\r")) + { + PartialMessage.erase(PartialMessage.find_last_not_of("\r\n") + 1); + List.push_back(PartialMessage); + PartialMessage.clear(); + while (NL < End && (*NL == '\n' || *NL == '\r')) ++NL; + Start = NL; + } + } + } + + while (Start < End) { + char const * NL2 = (char const *) memchr(NL, '\n', End - NL); + if (NL2 == NULL) + { + // end of buffer: store what we have so far and read new data in + PartialMessage.append(Start, End - Start); + break; + } + ++NL2; + + // did we find a double newline? + if ((NL2 - NL) == 1 || ((NL2 - NL) == 2 && *NL == '\r')) + { + PartialMessage.append(Start, NL2 - Start); + PartialMessage.erase(PartialMessage.find_last_not_of("\r\n") + 1); + List.push_back(PartialMessage); + PartialMessage.clear(); + while (NL2 < End && (*NL2 == '\n' || *NL2 == '\r')) ++NL2; + Start = NL2; + } + NL = NL2; + } + + // we have read at least one complete message and nothing left + if (PartialMessage.empty() == true) + return true; + + if (WaitFd(Fd) == false) + return false; + } while (true); +} + /*}}}*/ +// MonthConv - Converts a month string into a number /*{{{*/ +// --------------------------------------------------------------------- +/* This was lifted from the boa webserver which lifted it from 'wn-v1.07' + Made it a bit more robust with a few tolower_ascii though. */ +static int MonthConv(char const * const Month) +{ + switch (tolower_ascii(*Month)) + { + case 'a': + return tolower_ascii(Month[1]) == 'p'?3:7; + case 'd': + return 11; + case 'f': + return 1; + case 'j': + if (tolower_ascii(Month[1]) == 'a') + return 0; + return tolower_ascii(Month[2]) == 'n'?5:6; + case 'm': + return tolower_ascii(Month[2]) == 'r'?2:4; + case 'n': + return 10; + case 'o': + return 9; + case 's': + return 8; + + // Pretend it is January.. + default: + return 0; + } +} + /*}}}*/ +// timegm - Internal timegm if the gnu version is not available /*{{{*/ +// --------------------------------------------------------------------- +/* Converts struct tm to time_t, assuming the data in tm is UTC rather + than local timezone (mktime assumes the latter). + + This function is a nonstandard GNU extension that is also present on + the BSDs and maybe other systems. For others we follow the advice of + the manpage of timegm and use his portable replacement. */ +#ifndef HAVE_TIMEGM +static time_t timegm(struct tm *t) +{ + char *tz = getenv("TZ"); + setenv("TZ", "", 1); + tzset(); + time_t ret = mktime(t); + if (tz) + setenv("TZ", tz, 1); + else + unsetenv("TZ"); + tzset(); + return ret; +} +#endif + /*}}}*/ +// RFC1123StrToTime - Converts an HTTP1.1 full date strings into a time_t /*{{{*/ +// --------------------------------------------------------------------- +/* tries to parses a full date as specified in RFC7231 §7.1.1.1 + with one exception: HTTP/1.1 valid dates need to have GMT as timezone. + As we encounter dates from UTC or with a numeric timezone in other places, + we allow them here to to be able to reuse the method. Either way, a date + must be in UTC or parsing will fail. Previous implementations of this + method used to ignore the timezone and assume always UTC. */ +bool RFC1123StrToTime(std::string const &str,time_t &time) +{ + unsigned short day = 0; + signed int year = 0; // yes, Y23K problem – we going to worry then… + std::string weekday, month, datespec, timespec, zone; + std::istringstream ss(str); + auto const &posix = std::locale::classic(); + ss.imbue(posix); + ss >> weekday; + // we only superficially check weekday, mostly to avoid accepting localized + // weekdays here and take only its length to decide which datetime format we + // encounter here. The date isn't stored. + std::transform(weekday.begin(), weekday.end(), weekday.begin(), ::tolower); + std::array<char const * const, 7> c_weekdays = {{ "sun", "mon", "tue", "wed", "thu", "fri", "sat" }}; + if (std::find(c_weekdays.begin(), c_weekdays.end(), weekday.substr(0,3)) == c_weekdays.end()) + return false; + + switch (weekday.length()) + { + case 4: + // Sun, 06 Nov 1994 08:49:37 GMT ; RFC 822, updated by RFC 1123 + if (weekday[3] != ',') + return false; + ss >> day >> month >> year >> timespec >> zone; + break; + case 3: + // Sun Nov 6 08:49:37 1994 ; ANSI C's asctime() format + ss >> month >> day >> timespec >> year; + zone = "UTC"; + break; + case 0: + case 1: + case 2: + return false; + default: + // Sunday, 06-Nov-94 08:49:37 GMT ; RFC 850, obsoleted by RFC 1036 + if (weekday[weekday.length() - 1] != ',') + return false; + ss >> datespec >> timespec >> zone; + auto const expldate = VectorizeString(datespec, '-'); + if (expldate.size() != 3) + return false; + try { + size_t pos; + day = std::stoi(expldate[0], &pos); + if (pos != expldate[0].length()) + return false; + year = 1900 + std::stoi(expldate[2], &pos); + if (pos != expldate[2].length()) + return false; + strprintf(datespec, "%.4d-%.2d-%.2d", year, MonthConv(expldate[1].c_str()) + 1, day); + } catch (...) { + return false; + } + break; + } + + if (ss.fail() || ss.bad() || !ss.eof()) + return false; + + if (zone != "GMT" && zone != "UTC" && zone != "Z") // RFC 822 + { + // numeric timezones as a should of RFC 1123 and generally preferred + try { + size_t pos; + auto const z = std::stoi(zone, &pos); + if (z != 0 || pos != zone.length()) + return false; + } catch (...) { + return false; + } + } + + if (datespec.empty()) + { + if (month.empty()) + return false; + strprintf(datespec, "%.4d-%.2d-%.2d", year, MonthConv(month.c_str()) + 1, day); + } + + std::string const datetime = datespec + ' ' + timespec; + struct tm Tm; + if (strptime(datetime.c_str(), "%Y-%m-%d %H:%M:%S", &Tm) == nullptr) + return false; + time = timegm(&Tm); + return true; +} + /*}}}*/ +// FTPMDTMStrToTime - Converts a ftp modification date into a time_t /*{{{*/ +// --------------------------------------------------------------------- +/* */ +bool FTPMDTMStrToTime(const char* const str,time_t &time) +{ + struct tm Tm; + // MDTM includes no whitespaces but recommend and ignored by strptime + if (strptime(str, "%Y %m %d %H %M %S", &Tm) == NULL) + return false; + + time = timegm(&Tm); + return true; +} + /*}}}*/ +// StrToNum - Convert a fixed length string to a number /*{{{*/ +// --------------------------------------------------------------------- +/* This is used in decoding the crazy fixed length string headers in + tar and ar files. */ +bool StrToNum(const char *Str,unsigned long &Res,unsigned Len,unsigned Base) +{ + unsigned long long BigRes; + if (not StrToNum(Str, BigRes, Len, Base)) + return false; + + if (std::numeric_limits<unsigned long>::max() < BigRes) + return false; + + Res = BigRes; + return true; +} + /*}}}*/ +// StrToNum - Convert a fixed length string to a number /*{{{*/ +// --------------------------------------------------------------------- +/* This is used in decoding the crazy fixed length string headers in + tar and ar files. */ +bool StrToNum(const char *Str,unsigned long long &Res,unsigned Len,unsigned Base) +{ + char S[30]; + if (Len >= sizeof(S)) + return false; + memcpy(S,Str,Len); + S[Len] = 0; + + // All spaces is a zero + Res = 0; + unsigned I; + for (I = 0; S[I] == ' '; ++I); + if (S[I] == 0) + return true; + if (S[I] == '-') + return false; + + char *End; + errno = 0; + Res = strtoull(S,&End,Base); + return not (End == S || errno != 0); +} + /*}}}*/ + +// Base256ToNum - Convert a fixed length binary to a number /*{{{*/ +// --------------------------------------------------------------------- +/* This is used in decoding the 256bit encoded fixed length fields in + tar files */ +bool Base256ToNum(const char *Str,unsigned long long &Res,unsigned int Len) +{ + if ((Str[0] & 0x80) == 0) + return false; + else + { + Res = Str[0] & 0x7F; + for(unsigned int i = 1; i < Len; ++i) + Res = (Res<<8) + Str[i]; + return true; + } +} + /*}}}*/ +// Base256ToNum - Convert a fixed length binary to a number /*{{{*/ +// --------------------------------------------------------------------- +/* This is used in decoding the 256bit encoded fixed length fields in + tar files */ +bool Base256ToNum(const char *Str,unsigned long &Res,unsigned int Len) +{ + unsigned long long Num = 0; + bool rc; + + rc = Base256ToNum(Str, Num, Len); + // rudimentary check for overflow (Res = ulong, Num = ulonglong) + Res = Num; + if (Res != Num) + return false; + + return rc; +} + /*}}}*/ +// HexDigit - Convert a hex character into an integer /*{{{*/ +// --------------------------------------------------------------------- +/* Helper for Hex2Num */ +static int HexDigit(int c) +{ + if (c >= '0' && c <= '9') + return c - '0'; + if (c >= 'a' && c <= 'f') + return c - 'a' + 10; + if (c >= 'A' && c <= 'F') + return c - 'A' + 10; + return -1; +} + /*}}}*/ +// Hex2Num - Convert a long hex number into a buffer /*{{{*/ +// --------------------------------------------------------------------- +/* The length of the buffer must be exactly 1/2 the length of the string. */ +bool Hex2Num(const APT::StringView Str,unsigned char *Num,unsigned int Length) +{ + if (Str.length() != Length*2) + return false; + + // Convert each digit. We store it in the same order as the string + int J = 0; + for (auto I = Str.begin(); I != Str.end();J++, I += 2) + { + int first_half = HexDigit(I[0]); + int second_half; + if (first_half < 0) + return false; + + second_half = HexDigit(I[1]); + if (second_half < 0) + return false; + Num[J] = first_half << 4; + Num[J] += second_half; + } + + return true; +} + /*}}}*/ +// TokSplitString - Split a string up by a given token /*{{{*/ +// --------------------------------------------------------------------- +/* This is intended to be a faster splitter, it does not use dynamic + memories. Input is changed to insert nulls at each token location. */ +bool TokSplitString(char Tok,char *Input,char **List, + unsigned long ListMax) +{ + // Strip any leading spaces + char *Start = Input; + char *Stop = Start + strlen(Start); + for (; *Start != 0 && isspace(*Start) != 0; Start++); + + unsigned long Count = 0; + char *Pos = Start; + while (Pos != Stop) + { + // Skip to the next Token + for (; Pos != Stop && *Pos != Tok; Pos++); + + // Back remove spaces + char *End = Pos; + for (; End > Start && (End[-1] == Tok || isspace(End[-1]) != 0); End--); + *End = 0; + + List[Count++] = Start; + if (Count >= ListMax) + { + List[Count-1] = 0; + return false; + } + + // Advance pos + for (; Pos != Stop && (*Pos == Tok || isspace(*Pos) != 0 || *Pos == 0); Pos++); + Start = Pos; + } + + List[Count] = 0; + return true; +} + /*}}}*/ +// VectorizeString - Split a string up into a vector of strings /*{{{*/ +// --------------------------------------------------------------------- +/* This can be used to split a given string up into a vector, so the + propose is the same as in the method above and this one is a bit slower + also, but the advantage is that we have an iteratable vector */ +vector<string> VectorizeString(string const &haystack, char const &split) +{ + vector<string> exploded; + if (haystack.empty() == true) + return exploded; + string::const_iterator start = haystack.begin(); + string::const_iterator end = start; + do { + for (; end != haystack.end() && *end != split; ++end); + exploded.push_back(string(start, end)); + start = end + 1; + } while (end != haystack.end() && (++end) != haystack.end()); + return exploded; +} + /*}}}*/ +// StringSplit - split a string into a string vector by token /*{{{*/ +// --------------------------------------------------------------------- +/* See header for details. + */ +vector<string> StringSplit(std::string const &s, std::string const &sep, + unsigned int maxsplit) +{ + vector<string> split; + size_t start, pos; + + // no separator given, this is bogus + if(sep.size() == 0) + return split; + + start = pos = 0; + while (pos != string::npos) + { + pos = s.find(sep, start); + split.push_back(s.substr(start, pos-start)); + + // if maxsplit is reached, the remaining string is the last item + if(split.size() >= maxsplit) + { + split[split.size()-1] = s.substr(start); + break; + } + start = pos+sep.size(); + } + return split; +} + /*}}}*/ +// RegexChoice - Simple regex list/list matcher /*{{{*/ +// --------------------------------------------------------------------- +/* */ +unsigned long RegexChoice(RxChoiceList *Rxs,const char **ListBegin, + const char **ListEnd) +{ + for (RxChoiceList *R = Rxs; R->Str != 0; R++) + R->Hit = false; + + unsigned long Hits = 0; + for (; ListBegin < ListEnd; ++ListBegin) + { + // Check if the name is a regex + const char *I; + bool Regex = true; + for (I = *ListBegin; *I != 0; I++) + if (*I == '.' || *I == '?' || *I == '*' || *I == '|') + break; + if (*I == 0) + Regex = false; + + // Compile the regex pattern + regex_t Pattern; + if (Regex == true) + if (regcomp(&Pattern,*ListBegin,REG_EXTENDED | REG_ICASE | + REG_NOSUB) != 0) + Regex = false; + + // Search the list + bool Done = false; + for (RxChoiceList *R = Rxs; R->Str != 0; R++) + { + if (R->Str[0] == 0) + continue; + + if (strcasecmp(R->Str,*ListBegin) != 0) + { + if (Regex == false) + continue; + if (regexec(&Pattern,R->Str,0,0,0) != 0) + continue; + } + Done = true; + + if (R->Hit == false) + Hits++; + + R->Hit = true; + } + + if (Regex == true) + regfree(&Pattern); + + if (Done == false) + _error->Warning(_("Selection %s not found"),*ListBegin); + } + + return Hits; +} + /*}}}*/ +// {str,io}printf - C format string outputter to C++ strings/iostreams /*{{{*/ +// --------------------------------------------------------------------- +/* This is used to make the internationalization strings easier to translate + and to allow reordering of parameters */ +bool iovprintf(std::ostream &out, const char *format, + va_list &args, ssize_t &size) { + auto S = std::unique_ptr<char,decltype(&free)>{static_cast<char*>(malloc(size)), &free}; + ssize_t const n = vsnprintf(S.get(), size, format, args); + if (n > -1 && n < size) { + out << S.get(); + return true; + } else { + if (n > -1) + size = n + 1; + else + size *= 2; + } + return false; +} +void ioprintf(ostream &out,const char *format,...) +{ + va_list args; + ssize_t size = 400; + while (true) { + bool ret; + va_start(args,format); + ret = iovprintf(out, format, args, size); + va_end(args); + if (ret == true) + return; + } +} +void strprintf(string &out,const char *format,...) +{ + va_list args; + ssize_t size = 400; + std::ostringstream outstr; + while (true) { + bool ret; + va_start(args,format); + ret = iovprintf(outstr, format, args, size); + va_end(args); + if (ret == true) + break; + } + out = outstr.str(); +} + /*}}}*/ +// safe_snprintf - Safer snprintf /*{{{*/ +// --------------------------------------------------------------------- +/* This is a snprintf that will never (ever) go past 'End' and returns a + pointer to the end of the new string. The returned string is always null + terminated unless Buffer == end. This is a better alterantive to using + consecutive snprintfs. */ +char *safe_snprintf(char *Buffer,char *End,const char *Format,...) +{ + va_list args; + int Did; + + if (End <= Buffer) + return End; + va_start(args,Format); + Did = vsnprintf(Buffer,End - Buffer,Format,args); + va_end(args); + + if (Did < 0 || Buffer + Did > End) + return End; + return Buffer + Did; +} + /*}}}*/ +// StripEpoch - Remove the version "epoch" from a version string /*{{{*/ +// --------------------------------------------------------------------- +string StripEpoch(const string &VerStr) +{ + size_t i = VerStr.find(":"); + if (i == string::npos) + return VerStr; + return VerStr.substr(i+1); +} + /*}}}*/ + +// tolower_ascii - tolower() function that ignores the locale /*{{{*/ +// --------------------------------------------------------------------- +/* This little function is the most called method we have and tries + therefore to do the absolute minimum - and is notable faster than + standard tolower/toupper and as a bonus avoids problems with different + locales - we only operate on ascii chars anyway. */ +#undef tolower_ascii +int tolower_ascii(int const c) APT_PURE APT_COLD; +int tolower_ascii(int const c) +{ + return tolower_ascii_inline(c); +} + /*}}}*/ + +// isspace_ascii - isspace() function that ignores the locale /*{{{*/ +// --------------------------------------------------------------------- +/* This little function is one of the most called methods we have and tries + therefore to do the absolute minimum - and is notable faster than + standard isspace() and as a bonus avoids problems with different + locales - we only operate on ascii chars anyway. */ +#undef isspace_ascii +int isspace_ascii(int const c) APT_PURE APT_COLD; +int isspace_ascii(int const c) +{ + return isspace_ascii_inline(c); +} + /*}}}*/ + +// CheckDomainList - See if Host is in a , separate list /*{{{*/ +// --------------------------------------------------------------------- +/* The domain list is a comma separate list of domains that are suffix + matched against the argument */ +bool CheckDomainList(const string &Host,const string &List) +{ + string::const_iterator Start = List.begin(); + for (string::const_iterator Cur = List.begin(); Cur <= List.end(); ++Cur) + { + if (Cur < List.end() && *Cur != ',') + continue; + + // Match the end of the string.. + if ((Host.size() >= (unsigned)(Cur - Start)) && + Cur - Start != 0 && + stringcasecmp(Host.end() - (Cur - Start),Host.end(),Start,Cur) == 0) + return true; + + Start = Cur + 1; + } + return false; +} + /*}}}*/ +// strv_length - Return the length of a NULL-terminated string array /*{{{*/ +// --------------------------------------------------------------------- +/* */ +size_t strv_length(const char **str_array) +{ + size_t i; + for (i=0; str_array[i] != NULL; i++) + /* nothing */ + ; + return i; +} + /*}}}*/ +// DeEscapeString - unescape (\0XX and \xXX) from a string /*{{{*/ +// --------------------------------------------------------------------- +/* */ +string DeEscapeString(const string &input) +{ + char tmp[3]; + string::const_iterator it; + string output; + for (it = input.begin(); it != input.end(); ++it) + { + // just copy non-escape chars + if (*it != '\\') + { + output += *it; + continue; + } + + // deal with double escape + if (*it == '\\' && + (it + 1 < input.end()) && it[1] == '\\') + { + // copy + output += *it; + // advance iterator one step further + ++it; + continue; + } + + // ensure we have a char to read + if (it + 1 == input.end()) + continue; + + // read it + ++it; + switch (*it) + { + case '0': + if (it + 2 < input.end()) { + tmp[0] = it[1]; + tmp[1] = it[2]; + tmp[2] = 0; + output += (char)strtol(tmp, 0, 8); + it += 2; + } else { + // FIXME: raise exception here? + } + break; + case 'x': + if (it + 2 < input.end()) { + tmp[0] = it[1]; + tmp[1] = it[2]; + tmp[2] = 0; + output += (char)strtol(tmp, 0, 16); + it += 2; + } else { + // FIXME: raise exception here? + } + break; + default: + // FIXME: raise exception here? + break; + } + } + return output; +} + /*}}}*/ +// URI::CopyFrom - Copy from an object /*{{{*/ +// --------------------------------------------------------------------- +/* This parses the URI into all of its components */ +void URI::CopyFrom(const string &U) +{ + string::const_iterator I = U.begin(); + + // Locate the first colon, this separates the scheme + for (; I < U.end() && *I != ':' ; ++I); + string::const_iterator FirstColon = I; + + /* Determine if this is a host type URI with a leading double // + and then search for the first single / */ + string::const_iterator SingleSlash = I; + if (I + 3 < U.end() && I[1] == '/' && I[2] == '/') + SingleSlash += 3; + + /* Find the / indicating the end of the hostname, ignoring /'s in the + square brackets */ + bool InBracket = false; + for (; SingleSlash < U.end() && (*SingleSlash != '/' || InBracket == true); ++SingleSlash) + { + if (*SingleSlash == '[') + InBracket = true; + if (InBracket == true && *SingleSlash == ']') + InBracket = false; + } + + if (SingleSlash > U.end()) + SingleSlash = U.end(); + + // We can now write the access and path specifiers + Access.assign(U.begin(),FirstColon); + if (SingleSlash != U.end()) + Path.assign(SingleSlash,U.end()); + if (Path.empty() == true) + Path = "/"; + + // Now we attempt to locate a user:pass@host fragment + if (FirstColon + 2 <= U.end() && FirstColon[1] == '/' && FirstColon[2] == '/') + FirstColon += 3; + else + FirstColon += 1; + if (FirstColon >= U.end()) + return; + + if (FirstColon > SingleSlash) + FirstColon = SingleSlash; + + // Find the colon... + I = FirstColon + 1; + if (I > SingleSlash) + I = SingleSlash; + + // Search for the @ separating user:pass from host + auto const RevAt = std::find( + std::string::const_reverse_iterator(SingleSlash), + std::string::const_reverse_iterator(I), '@'); + string::const_iterator const At = RevAt.base() == I ? SingleSlash : std::prev(RevAt.base()); + // and then look for the colon between user and pass + string::const_iterator const SecondColon = std::find(I, At, ':'); + + // Now write the host and user/pass + if (At == SingleSlash) + { + if (FirstColon < SingleSlash) + Host.assign(FirstColon,SingleSlash); + } + else + { + Host.assign(At+1,SingleSlash); + // username and password must be encoded (RFC 3986) + User.assign(DeQuoteString(FirstColon,SecondColon)); + if (SecondColon < At) + Password.assign(DeQuoteString(SecondColon+1,At)); + } + + // Now we parse the RFC 2732 [] hostnames. + unsigned long PortEnd = 0; + InBracket = false; + for (unsigned I = 0; I != Host.length();) + { + if (Host[I] == '[') + { + InBracket = true; + Host.erase(I,1); + continue; + } + + if (InBracket == true && Host[I] == ']') + { + InBracket = false; + Host.erase(I,1); + PortEnd = I; + continue; + } + I++; + } + + // Tsk, weird. + if (InBracket == true) + { + Host.clear(); + return; + } + + // Now we parse off a port number from the hostname + Port = 0; + string::size_type Pos = Host.rfind(':'); + if (Pos == string::npos || Pos < PortEnd) + return; + + Port = atoi(string(Host,Pos+1).c_str()); + Host.assign(Host,0,Pos); +} + /*}}}*/ +// URI::operator string - Convert the URI to a string /*{{{*/ +// --------------------------------------------------------------------- +/* */ +URI::operator string() +{ + std::stringstream Res; + + if (Access.empty() == false) + Res << Access << ':'; + + if (Host.empty() == false) + { + if (Access.empty() == false) + Res << "//"; + + if (User.empty() == false) + { + // FIXME: Technically userinfo is permitted even less + // characters than these, but this is not conveniently + // expressed with a denylist. + Res << QuoteString(User, ":/?#[]@"); + if (Password.empty() == false) + Res << ":" << QuoteString(Password, ":/?#[]@"); + Res << "@"; + } + + // Add RFC 2732 escaping characters + if (Access.empty() == false && Host.find_first_of("/:") != string::npos) + Res << '[' << Host << ']'; + else + Res << Host; + + if (Port != 0) + Res << ':' << std::to_string(Port); + } + + if (Path.empty() == false) + { + if (Path[0] != '/') + Res << "/" << Path; + else + Res << Path; + } + + return Res.str(); +} + /*}}}*/ +// URI::SiteOnly - Return the schema and site for the URI /*{{{*/ +string URI::SiteOnly(const string &URI) +{ + ::URI U(URI); + U.User.clear(); + U.Password.clear(); + U.Path.clear(); + return U; +} + /*}}}*/ +// URI::ArchiveOnly - Return the schema, site and cleaned path for the URI /*{{{*/ +string URI::ArchiveOnly(const string &URI) +{ + ::URI U(URI); + U.User.clear(); + U.Password.clear(); + if (U.Path.empty() == false && U.Path[U.Path.length() - 1] == '/') + U.Path.erase(U.Path.length() - 1); + return U; +} + /*}}}*/ +// URI::NoUserPassword - Return the schema, site and path for the URI /*{{{*/ +string URI::NoUserPassword(const string &URI) +{ + ::URI U(URI); + U.User.clear(); + U.Password.clear(); + return U; +} + /*}}}*/ |