X-Git-Url: https://git.saurik.com/apt.git/blobdiff_plain/5b63d2a9a2e088bb7df7c703e9452af7efc88210..0cfec3ab589c6309bf284438d2148c7742cdaf10:/apt-pkg/contrib/strutl.cc?ds=sidebyside diff --git a/apt-pkg/contrib/strutl.cc b/apt-pkg/contrib/strutl.cc index f4dd3407d..fba1e3306 100644 --- a/apt-pkg/contrib/strutl.cc +++ b/apt-pkg/contrib/strutl.cc @@ -21,11 +21,19 @@ #include #include +#include +#include +#include +#include +#include +#include + +#include +#include +#include #include #include -#include #include -#include #include #include #include @@ -33,10 +41,52 @@ #include #include - -using namespace std; /*}}}*/ +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); +} +} +} + /*}}}*/ // UTF8ToCodeset - Convert some UTF-8 string for some codeset /*{{{*/ // --------------------------------------------------------------------- /* This is handy to use before display some information for enduser */ @@ -130,7 +180,7 @@ char *_strrstrip(char *String) End++; *End = 0; return String; -}; +} /*}}}*/ // strtabexpand - Converts tabs into 8 spaces /*{{{*/ // --------------------------------------------------------------------- @@ -277,21 +327,19 @@ bool ParseCWord(const char *&String,string &Res) /* */ string QuoteString(const string &Str, const char *Bad) { - string Res; + std::stringstream Res; for (string::const_iterator I = Str.begin(); I != Str.end(); ++I) { - if (strchr(Bad,*I) != 0 || isprint(*I) == 0 || + if (strchr(Bad,*I) != 0 || isprint(*I) == 0 || *I == 0x25 || // percent '%' char *I <= 0x20 || *I >= 0x7F) // control chars { - char Buf[10]; - sprintf(Buf,"%%%02x",(int)*I); - Res += Buf; + ioprintf(Res, "%%%02hhx", *I); } else - Res += *I; + Res << *I; } - return Res; + return Res.str(); } /*}}}*/ // DeQuoteString - Convert a string from quoted from /*{{{*/ @@ -332,13 +380,12 @@ string DeQuoteString(string::const_iterator const &begin, YottaBytes (E24) */ string SizeToStr(double Size) { - char S[300]; 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'}; @@ -347,20 +394,21 @@ string SizeToStr(double Size) { if (ASize < 100 && I != 0) { - sprintf(S,"%'.1f %c",ASize,Ext[I]); - break; + std::string S; + strprintf(S, "%'.1f %c", ASize, Ext[I]); + return S; } - + if (ASize < 10000) { - sprintf(S,"%'.0f %c",ASize,Ext[I]); - break; + std::string S; + strprintf(S, "%'.0f %c", ASize, Ext[I]); + return S; } ASize /= 1000.0; I++; } - - return S; + return ""; } /*}}}*/ // TimeToStr - Convert the time into a string /*{{{*/ @@ -368,61 +416,61 @@ string SizeToStr(double Size) /* Converts a number of seconds to a hms format */ string TimeToStr(unsigned long Sec) { - char S[300]; - - while (1) + std::string S; + if (Sec > 60*60*24) { - if (Sec > 60*60*24) - { - //d means days, h means hours, min means minutes, s means seconds - sprintf(S,_("%lid %lih %limin %lis"),Sec/60/60/24,(Sec/60/60) % 24,(Sec/60) % 60,Sec % 60); - break; - } - - if (Sec > 60*60) - { - //h means hours, min means minutes, s means seconds - sprintf(S,_("%lih %limin %lis"),Sec/60/60,(Sec/60) % 60,Sec % 60); - break; - } - - if (Sec > 60) - { - //min means minutes, s means seconds - sprintf(S,_("%limin %lis"),Sec/60,Sec % 60); - break; - } - - //s means seconds - sprintf(S,_("%lis"),Sec); - break; + //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 occurances of Subst with Contents in Str. */ +/* 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() && + + while (OldPos < Str.length() && (Pos = Str.find(Subst,OldPos)) != string::npos) { - Temp += string(Str,OldPos,Pos) + Contents; - OldPos = Pos + Subst.length(); + 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; - - return Temp + string(Str,OldPos); -} + 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++) @@ -647,9 +695,9 @@ string LookupTag(const string &Message,const char *Tag,const char *Default) // Find the end of line and strip the leading/trailing spaces string::const_iterator J; I += Length + 1; - for (; isspace(*I) != 0 && I < Message.end(); ++I); + for (; isspace_ascii(*I) != 0 && I < Message.end(); ++I); for (J = I; *J != '\n' && J < Message.end(); ++J); - for (; J > I && isspace(J[-1]) != 0; --J); + for (; J > I && isspace_ascii(J[-1]) != 0; --J); return string(I,J); } @@ -669,9 +717,12 @@ string LookupTag(const string &Message,const char *Tag,const char *Default) then returns the result. Several varients on true/false are checked. */ int StringToBool(const string &Text,int Default) { - char *End; - int Res = strtol(Text.c_str(),&End,0); - if (End != Text.c_str() && Res >= 0 && Res <= 1) + 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 @@ -703,15 +754,11 @@ string TimeRFC1123(time_t Date) if (gmtime_r(&Date, &Conv) == NULL) return ""; - char Buf[300]; - const char *Day[] = {"Sun","Mon","Tue","Wed","Thu","Fri","Sat"}; - const char *Month[] = {"Jan","Feb","Mar","Apr","May","Jun","Jul", - "Aug","Sep","Oct","Nov","Dec"}; - - snprintf(Buf, sizeof(Buf), "%s, %02i %s %i %02i:%02i:%02i GMT",Day[Conv.tm_wday], - Conv.tm_mday,Month[Conv.tm_mon],Conv.tm_year+1900,Conv.tm_hour, - Conv.tm_min,Conv.tm_sec); - return Buf; + auto const posix = std::locale("C.UTF-8"); + std::ostringstream datestr; + datestr.imbue(posix); + datestr << std::put_time(&Conv, "%a, %d %b %Y %H:%M:%S GMT"); + return datestr.str(); } /*}}}*/ // ReadMessages - Read messages from the FD /*{{{*/ @@ -722,85 +769,98 @@ string TimeRFC1123(time_t Date) 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'). As noted below, there is a - bug in this code: it assumes that all the blocks have been read if - it doesn't see additional text in the buffer after the last one is - parsed, which will cause it to lose blocks if the last block - coincides with the end of the buffer. + double newline ('\n' followed by '\n'). */ bool ReadMessages(int Fd, vector &List) { char Buffer[64000]; - char *End = Buffer; // 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; - - while (1) - { - int Res = read(Fd,End,sizeof(Buffer) - (End-Buffer)); + + do { + int const Res = read(Fd, Buffer, sizeof(Buffer)); if (Res < 0 && errno == EINTR) continue; - - // Process is dead, this is kind of bad.. + + // 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; - - End += Res; - - // Look for the end of the message - for (char *I = Buffer; I + 1 < End; I++) + + // 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) { - if (I[0] != '\n' || I[1] != '\n') - continue; - - // Pull the message out - string Message(Buffer,I-Buffer); - PartialMessage += Message; - - // Fix up the buffer - for (; I < End && *I == '\n'; I++); - End -= I-Buffer; - memmove(Buffer,I,End-Buffer); - I = Buffer; - - List.push_back(PartialMessage); - PartialMessage.clear(); + // end of buffer: store what we have so far and read new data in + PartialMessage.append(Start, End - Start); + Start = End; } - if (End != Buffer) - { - // If there's text left in the buffer, store it - // in PartialMessage and throw the rest of the buffer - // away. This allows us to handle messages that - // are longer than the static buffer size. - PartialMessage += string(Buffer, End); - End = Buffer; - } else - { - // BUG ALERT: if a message block happens to end at a - // multiple of 64000 characters, this will cause it to - // terminate early, leading to a badly formed block and - // probably crashing the method. However, this is the only - // way we have to find the end of the message block. I have - // an idea of how to fix this, but it will require changes - // to the protocol (essentially to mark the beginning and - // end of the block). - // - // -- dburrows 2008-04-02 - return true; - } + ++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 /*{{{*/ @@ -860,29 +920,56 @@ static time_t timegm(struct tm *t) } #endif /*}}}*/ -// FullDateToTime - Converts a HTTP1.1 full date strings into a time_t /*{{{*/ +// RFC1123StrToTime - Converts a HTTP1.1 full date strings into a time_t /*{{{*/ // --------------------------------------------------------------------- -/* tries to parses a full date as specified in RFC2616 Section 3.3.1 - with one exception: All timezones (%Z) are accepted but the protocol - says that it MUST be GMT, but this one is equal to UTC which we will - encounter from time to time (e.g. in Release files) so we accept all - here and just assume it is GMT (or UTC) later on */ +/* 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(const char* const str,time_t &time) { - struct tm Tm; - setlocale (LC_ALL,"C"); - bool const invalid = - // Sun, 06 Nov 1994 08:49:37 GMT ; RFC 822, updated by RFC 1123 - (strptime(str, "%a, %d %b %Y %H:%M:%S %Z", &Tm) == NULL && - // Sunday, 06-Nov-94 08:49:37 GMT ; RFC 850, obsoleted by RFC 1036 - strptime(str, "%A, %d-%b-%y %H:%M:%S %Z", &Tm) == NULL && - // Sun Nov 6 08:49:37 1994 ; ANSI C's asctime() format - strptime(str, "%a %b %d %H:%M:%S %Y", &Tm) == NULL); - setlocale (LC_ALL,""); - if (invalid == true) + struct tm t; + auto const &posix = std::locale("C.UTF-8"); + auto const parse_time = [&](char const * const s, bool const has_timezone) { + std::istringstream ss(str); + ss.imbue(posix); + ss >> std::get_time(&t, s); + if (has_timezone && ss.fail() == false) + { + std::string timezone; + ss >> timezone; + if (timezone.empty()) + return false; + if (timezone != "GMT" && timezone != "UTC" && timezone != "Z") // RFC 822 + { + // numeric timezones as a should of RFC 1123 and generally preferred + try { + size_t pos; + auto const zone = std::stoi(timezone, &pos); + if (zone != 0 || pos != timezone.length()) + return false; + } catch (...) { + return false; + } + } + } + t.tm_isdst = 0; + return ss.fail() == false; + }; + + bool const good = + // Sun, 06 Nov 1994 08:49:37 GMT ; RFC 822, updated by RFC 1123 + parse_time("%a, %d %b %Y %H:%M:%S", true) || + // Sunday, 06-Nov-94 08:49:37 GMT ; RFC 850, obsoleted by RFC 1036 + parse_time("%A, %d-%b-%y %H:%M:%S", true) || + // Sun Nov 6 08:49:37 1994 ; ANSI C's asctime() format + parse_time("%c", false); // "%a %b %d %H:%M:%S %Y" + if (good == false) return false; - time = timegm(&Tm); + time = timegm(&t); return true; } /*}}}*/ @@ -902,7 +989,7 @@ bool FTPMDTMStrToTime(const char* const str,time_t &time) /*}}}*/ // StrToTime - Converts a string into a time_t /*{{{*/ // --------------------------------------------------------------------- -/* This handles all 3 populare time formats including RFC 1123, RFC 1036 +/* This handles all 3 popular time formats including RFC 1123, RFC 1036 and the C library asctime format. It requires the GNU library function 'timegm' to convert a struct tm in UTC to a time_t. For some bizzar reason the C library does not provide any such function :< This also @@ -942,6 +1029,8 @@ bool StrToTime(const string &Val,time_t &Result) Tm.tm_isdst = 0; if (Month[0] != 0) Tm.tm_mon = MonthConv(Month); + else + Tm.tm_mon = 0; // we don't have a month, so pick something Tm.tm_year -= 1900; // Convert to local time and then to GMT @@ -1008,7 +1097,7 @@ bool StrToNum(const char *Str,unsigned long long &Res,unsigned Len,unsigned Base // --------------------------------------------------------------------- /* 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) +bool Base256ToNum(const char *Str,unsigned long long &Res,unsigned int Len) { if ((Str[0] & 0x80) == 0) return false; @@ -1021,6 +1110,23 @@ bool Base256ToNum(const char *Str,unsigned long &Res,unsigned int Len) } } /*}}}*/ +// 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; + bool rc; + + rc = Base256ToNum(Str, Num, Len); + Res = Num; + if (Res != Num) + return false; + + return rc; +} + /*}}}*/ // HexDigit - Convert a hex character into an integer /*{{{*/ // --------------------------------------------------------------------- /* Helper for Hex2Num */ @@ -1032,26 +1138,36 @@ static int HexDigit(int c) return c - 'a' + 10; if (c >= 'A' && c <= 'F') return c - 'A' + 10; - return 0; + 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 string &Str,unsigned char *Num,unsigned int Length) +{ + return Hex2Num(APT::StringView(Str), Num, Length); +} + +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 (string::const_iterator I = Str.begin(); I != Str.end();J++, I += 2) + for (auto I = Str.begin(); I != Str.end();J++, I += 2) { - if (isxdigit(*I) == 0 || isxdigit(I[1]) == 0) + int first_half = HexDigit(I[0]); + int second_half; + if (first_half < 0) return false; - Num[J] = HexDigit(I[0]) << 4; - Num[J] += HexDigit(I[1]); + second_half = HexDigit(I[1]); + if (second_half < 0) + return false; + Num[J] = first_half << 4; + Num[J] += second_half; } return true; @@ -1104,9 +1220,11 @@ bool TokSplitString(char Tok,char *Input,char **List, also, but the advantage is that we have an iteratable vector */ vector VectorizeString(string const &haystack, char const &split) { + vector exploded; + if (haystack.empty() == true) + return exploded; string::const_iterator start = haystack.begin(); string::const_iterator end = start; - vector exploded; do { for (; end != haystack.end() && *end != split; ++end); exploded.push_back(string(start, end)); @@ -1115,6 +1233,37 @@ vector VectorizeString(string const &haystack, char const &split) return exploded; } /*}}}*/ +// StringSplit - split a string into a string vector by token /*{{{*/ +// --------------------------------------------------------------------- +/* See header for details. + */ +vector StringSplit(std::string const &s, std::string const &sep, + unsigned int maxsplit) +{ + vector 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 /*{{{*/ // --------------------------------------------------------------------- /* */ @@ -1125,7 +1274,7 @@ unsigned long RegexChoice(RxChoiceList *Rxs,const char **ListBegin, R->Hit = false; unsigned long Hits = 0; - for (; ListBegin != ListEnd; ListBegin++) + for (; ListBegin < ListEnd; ++ListBegin) { // Check if the name is a regex const char *I; @@ -1201,10 +1350,12 @@ void ioprintf(ostream &out,const char *format,...) va_list args; ssize_t size = 400; while (true) { + bool ret; va_start(args,format); - if (iovprintf(out, format, args, size) == true) - return; + ret = iovprintf(out, format, args, size); va_end(args); + if (ret == true) + return; } } void strprintf(string &out,const char *format,...) @@ -1213,10 +1364,12 @@ void strprintf(string &out,const char *format,...) ssize_t size = 400; std::ostringstream outstr; while (true) { + bool ret; va_start(args,format); - if (iovprintf(outstr, format, args, size) == true) - break; + ret = iovprintf(outstr, format, args, size); va_end(args); + if (ret == true) + break; } out = outstr.str(); } @@ -1232,12 +1385,12 @@ char *safe_snprintf(char *Buffer,char *End,const char *Format,...) va_list args; int Did; - va_start(args,Format); - 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; @@ -1253,23 +1406,38 @@ string StripEpoch(const string &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 absolut minimum - and is noteable faster than + therefore to do the absolut 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_CONST APT_COLD; int tolower_ascii(int const c) { - if (c >= 'A' && c <= 'Z') - return c + 32; - return 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 absolut 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_CONST APT_COLD; +int isspace_ascii(int const c) +{ + return isspace_ascii_inline(c); } /*}}}*/ -// CheckDomainList - See if Host is in a , seperate list /*{{{*/ +// CheckDomainList - See if Host is in a , separate list /*{{{*/ // --------------------------------------------------------------------- -/* The domain list is a comma seperate list of domains that are suffix +/* 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) { @@ -1290,6 +1458,18 @@ bool CheckDomainList(const string &Host,const string &List) 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 /*{{{*/ // --------------------------------------------------------------------- /* */ @@ -1471,56 +1651,49 @@ void URI::CopyFrom(const string &U) /* */ URI::operator string() { - string Res; - + std::stringstream Res; + if (Access.empty() == false) - Res = Access + ':'; - + Res << Access << ':'; + if (Host.empty() == false) - { + { if (Access.empty() == false) - Res += "//"; - + Res << "//"; + if (User.empty() == false) { // FIXME: Technically userinfo is permitted even less // characters than these, but this is not conveniently // expressed with a blacklist. - Res += QuoteString(User, ":/?#[]@"); + Res << QuoteString(User, ":/?#[]@"); if (Password.empty() == false) - Res += ":" + QuoteString(Password, ":/?#[]@"); - Res += "@"; + Res << ":" << QuoteString(Password, ":/?#[]@"); + Res << "@"; } - + // Add RFC 2732 escaping characters - if (Access.empty() == false && - (Host.find('/') != string::npos || Host.find(':') != string::npos)) - Res += '[' + Host + ']'; + if (Access.empty() == false && Host.find_first_of("/:") != string::npos) + Res << '[' << Host << ']'; else - Res += Host; - + Res << Host; + if (Port != 0) - { - char S[30]; - sprintf(S,":%u",Port); - Res += S; - } + Res << ':' << std::to_string(Port); } - + if (Path.empty() == false) { if (Path[0] != '/') - Res += "/" + Path; + Res << "/" << Path; else - Res += Path; + Res << Path; } - - return Res; + + return Res.str(); } /*}}}*/ // URI::SiteOnly - Return the schema and site for the URI /*{{{*/ -// --------------------------------------------------------------------- -/* */ string URI::SiteOnly(const string &URI) { ::URI U(URI); @@ -1530,9 +1703,18 @@ string URI::SiteOnly(const string &URI) 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);