]> git.saurik.com Git - apt.git/blobdiff - apt-pkg/contrib/strutl.cc
test framework: Correctly generate new paths in noopchroot
[apt.git] / apt-pkg / contrib / strutl.cc
index ab47cdede0d93abeb305f1c2c8f3438de24af2c7..396cb7898c0e5dc26f31bbbbe030aa4c7cb90ca9 100644 (file)
    ##################################################################### */
                                                                        /*}}}*/
 // Includes                                                            /*{{{*/
    ##################################################################### */
                                                                        /*}}}*/
 // Includes                                                            /*{{{*/
+#include <config.h>
+
 #include <apt-pkg/strutl.h>
 #include <apt-pkg/fileutl.h>
 #include <apt-pkg/error.h>
 
 #include <apt-pkg/strutl.h>
 #include <apt-pkg/fileutl.h>
 #include <apt-pkg/error.h>
 
-#include <apti18n.h>
-    
+#include <stddef.h>
+#include <stdlib.h>
+#include <time.h>
+#include <string>
+#include <vector>
 #include <ctype.h>
 #include <string.h>
 #include <ctype.h>
 #include <string.h>
+#include <sstream>
 #include <stdio.h>
 #include <algorithm>
 #include <unistd.h>
 #include <stdio.h>
 #include <algorithm>
 #include <unistd.h>
 #include <stdarg.h>
 #include <iconv.h>
 
 #include <stdarg.h>
 #include <iconv.h>
 
-#include "config.h"
-
-using namespace std;
+#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.substr(s.size() - end.size(), s.size()) == end);
+}
+
+bool Startswith(const std::string &s, const std::string &start)
+{
+   if (start.size() > s.size())
+      return false;
+   return (s.substr(0, start.size()) == start);
+}
 
 
+}
+}
+                                                                       /*}}}*/
 // UTF8ToCodeset - Convert some UTF-8 string for some codeset          /*{{{*/
 // ---------------------------------------------------------------------
 /* This is handy to use before display some information for enduser  */
 // UTF8ToCodeset - Convert some UTF-8 string for some codeset          /*{{{*/
 // ---------------------------------------------------------------------
 /* This is handy to use before display some information for enduser  */
@@ -116,14 +164,20 @@ char *_strstrip(char *String)
 
    if (*String == 0)
       return 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;
    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                          /*{{{*/
 // ---------------------------------------------------------------------
                                                                        /*}}}*/
 // strtabexpand - Converts tabs into 8 spaces                          /*{{{*/
 // ---------------------------------------------------------------------
@@ -179,14 +233,14 @@ bool ParseQuoteWord(const char *&String,string &Res)
    {
       if (*C == '"')
       {
    {
       if (*C == '"')
       {
-        for (C++; *C != 0 && *C != '"'; C++);
-        if (*C == 0)
+        C = strchr(C + 1, '"');
+        if (C == NULL)
            return false;
       }
       if (*C == '[')
       {
            return false;
       }
       if (*C == '[')
       {
-        for (C++; *C != 0 && *C != ']'; C++);
-        if (*C == 0)
+        C = strchr(C + 1, ']');
+        if (C == NULL)
            return false;
       }
    }
            return false;
       }
    }
@@ -198,7 +252,8 @@ bool ParseQuoteWord(const char *&String,string &Res)
    char *I;
    for (I = Buffer; I < Buffer + sizeof(Buffer) && Start != C; I++)
    {
    char *I;
    for (I = Buffer; I < Buffer + sizeof(Buffer) && Start != C; I++)
    {
-      if (*Start == '%' && Start + 2 < C)
+      if (*Start == '%' && Start + 2 < C &&
+         isxdigit(Start[1]) && isxdigit(Start[2]))
       {
         Tmp[0] = Start[1];
         Tmp[1] = Start[2];
       {
         Tmp[0] = Start[1];
         Tmp[1] = Start[2];
@@ -269,31 +324,36 @@ bool ParseCWord(const char *&String,string &Res)
 /* */
 string QuoteString(const string &Str, const char *Bad)
 {
 /* */
 string QuoteString(const string &Str, const char *Bad)
 {
-   string Res;
-   for (string::const_iterator I = Str.begin(); I != Str.end(); I++)
+   std::stringstream Res;
+   for (string::const_iterator I = Str.begin(); I != Str.end(); ++I)
    {
    {
-      if (strchr(Bad,*I) != 0 || isprint(*I) == 0 || 
-         *I <= 0x20 || *I >= 0x7F)
+      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
       }
       else
-        Res += *I;
+        Res << *I;
    }
    }
-   return Res;
+   return Res.str();
 }
                                                                        /*}}}*/
 // DeQuoteString - Convert a string from quoted from                    /*{{{*/
 // ---------------------------------------------------------------------
 /* This undoes QuoteString */
 string DeQuoteString(const string &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;
 {
    string Res;
-   for (string::const_iterator I = Str.begin(); I != Str.end(); I++)
+   for (string::const_iterator I = begin; I != end; ++I)
    {
    {
-      if (*I == '%' && I + 2 < Str.end())
+      if (*I == '%' && I + 2 < end &&
+         isxdigit(I[1]) && isxdigit(I[2]))
       {
         char Tmp[3];
         Tmp[0] = I[1];
       {
         char Tmp[3];
         Tmp[0] = I[1];
@@ -317,13 +377,12 @@ string DeQuoteString(const string &Str)
    YottaBytes (E24) */
 string SizeToStr(double Size)
 {
    YottaBytes (E24) */
 string SizeToStr(double Size)
 {
-   char S[300];
    double ASize;
    if (Size >= 0)
       ASize = Size;
    else
       ASize = -1*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'};
    /* bytes, KiloBytes, MegaBytes, GigaBytes, TeraBytes, PetaBytes, 
       ExaBytes, ZettaBytes, YottaBytes */
    char Ext[] = {'\0','k','M','G','T','P','E','Z','Y'};
@@ -332,20 +391,21 @@ string SizeToStr(double Size)
    {
       if (ASize < 100 && I != 0)
       {
    {
       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)
       {
       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++;
    }
       }
       ASize /= 1000.0;
       I++;
    }
-   
-   return S;
+   return "";
 }
                                                                        /*}}}*/
 // TimeToStr - Convert the time into a string                          /*{{{*/
 }
                                                                        /*}}}*/
 // TimeToStr - Convert the time into a string                          /*{{{*/
@@ -353,61 +413,59 @@ string SizeToStr(double Size)
 /* Converts a number of seconds to a hms format */
 string TimeToStr(unsigned long Sec)
 {
 /* 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                   /*{{{*/
 // ---------------------------------------------------------------------
    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)
 {
 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;
    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)
    {
          (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;
    if (OldPos == 0)
       return Str;
-   
+
+   if (OldPos >= Str.length())
+      return Temp;
    return Temp + string(Str,OldPos);
 }
    return Temp + string(Str,OldPos);
 }
-
 string SubstVar(string Str,const struct SubstVar *Vars)
 {
    for (; Vars->Subst != 0; Vars++)
 string SubstVar(string Str,const struct SubstVar *Vars)
 {
    for (; Vars->Subst != 0; Vars++)
@@ -624,7 +682,7 @@ string LookupTag(const string &Message,const char *Tag,const char *Default)
 {
    // Look for a matching tag.
    int Length = strlen(Tag);
 {
    // Look for a matching tag.
    int Length = strlen(Tag);
-   for (string::const_iterator I = Message.begin(); I + Length < Message.end(); I++)
+   for (string::const_iterator I = Message.begin(); I + Length < Message.end(); ++I)
    {
       // Found the tag
       if (I[Length] == ':' && stringcasecmp(I,I+Length,Tag) == 0)
    {
       // Found the tag
       if (I[Length] == ':' && stringcasecmp(I,I+Length,Tag) == 0)
@@ -632,14 +690,14 @@ 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;
         // 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 (J = I; *J != '\n' && J < Message.end(); J++);
-        for (; J > I && isspace(J[-1]) != 0; J--);
+        for (; isspace(*I) != 0 && I < Message.end(); ++I);
+        for (J = I; *J != '\n' && J < Message.end(); ++J);
+        for (; J > I && isspace(J[-1]) != 0; --J);
         
         return string(I,J);
       }
       
         
         return string(I,J);
       }
       
-      for (; *I != '\n' && I < Message.end(); I++);
+      for (; *I != '\n' && I < Message.end(); ++I);
    }   
    
    // Failed to find a match
    }   
    
    // Failed to find a match
@@ -654,9 +712,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)
 {
    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
       return Res;
    
    // Check for positives
@@ -684,14 +745,16 @@ int StringToBool(const string &Text,int Default)
    year 2000 complient and timezone neutral */
 string TimeRFC1123(time_t Date)
 {
    year 2000 complient and timezone neutral */
 string TimeRFC1123(time_t Date)
 {
-   struct tm Conv = *gmtime(&Date);
-   char Buf[300];
+   struct tm Conv;
+   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"};
 
    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"};
 
-   sprintf(Buf,"%s, %02i %s %i %02i:%02i:%02i GMT",Day[Conv.tm_wday],
+   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;
           Conv.tm_mday,Month[Conv.tm_mon],Conv.tm_year+1900,Conv.tm_hour,
           Conv.tm_min,Conv.tm_sec);
    return Buf;
@@ -705,85 +768,94 @@ 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
 
    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<string> &List)
 {
    char Buffer[64000];
  */
 bool ReadMessages(int Fd, vector<string> &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;
    // 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;
       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;
       if (Res == 0)
         return false;
-      
+
       // No data
       // No data
-      if (Res < 0 && errno == EAGAIN)
+      if (Res < 0 && (errno == EAGAIN || errno == EWOULDBLOCK))
         return true;
       if (Res < 0)
         return false;
         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
       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;
 
       if (WaitFd(Fd) == false)
         return false;
-   }   
+   } while (true);
 }
                                                                        /*}}}*/
 // MonthConv - Converts a month string into a number                   /*{{{*/
 }
                                                                        /*}}}*/
 // MonthConv - Converts a month string into a number                   /*{{{*/
@@ -819,37 +891,73 @@ static int MonthConv(char *Month)
    }   
 }
                                                                        /*}}}*/
    }   
 }
                                                                        /*}}}*/
-// timegm - Internal timegm function if gnu is not available           /*{{{*/
+// timegm - Internal timegm if the gnu version is not available                /*{{{*/
 // ---------------------------------------------------------------------
 // ---------------------------------------------------------------------
-/* Ripped this evil little function from wget - I prefer the use of 
-   GNU timegm if possible as this technique will have interesting problems
-   with leap seconds, timezones and other.
-   
-   Converts struct tm to time_t, assuming the data in tm is UTC rather
+/* Converts struct tm to time_t, assuming the data in tm is UTC rather
    than local timezone (mktime assumes the latter).
    than local timezone (mktime assumes the latter).
-   
-   Contributed by Roger Beeman <beeman@cisco.com>, with the help of
-   Mark Baushke <mdb@cisco.com> and the rest of the Gurus at CISCO. */
 
 
-/* Turned it into an autoconf check, because GNU is not the only thing which
-   can provide timegm. -- 2002-09-22, Joel Baker */
-
-#ifndef HAVE_TIMEGM // Now with autoconf!
+   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)
 {
 static time_t timegm(struct tm *t)
 {
-   time_t tl, tb;
-   
-   tl = mktime (t);
-   if (tl == -1)
-      return -1;
-   tb = mktime (gmtime (&tl));
-   return (tl <= tb ? (tl + (tl - tb)) : (tl - (tb - tl)));
+   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
                                                                        /*}}}*/
 }
 #endif
                                                                        /*}}}*/
+// FullDateToTime - 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 */
+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)
+      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;
+}
+                                                                       /*}}}*/
 // StrToTime - Converts a string into a time_t                         /*{{{*/
 // ---------------------------------------------------------------------
 // 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
    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
@@ -858,24 +966,23 @@ bool StrToTime(const string &Val,time_t &Result)
 {
    struct tm Tm;
    char Month[10];
 {
    struct tm Tm;
    char Month[10];
-   const char *I = Val.c_str();
-   
+
    // Skip the day of the week
    // Skip the day of the week
-   for (;*I != 0  && *I != ' '; I++);
-   
+   const char *I = strchr(Val.c_str(), ' ');
+
    // Handle RFC 1123 time
    Month[0] = 0;
    // Handle RFC 1123 time
    Month[0] = 0;
-   if (sscanf(I," %d %3s %d %d:%d:%d GMT",&Tm.tm_mday,Month,&Tm.tm_year,
+   if (sscanf(I," %2d %3s %4d %2d:%2d:%2d GMT",&Tm.tm_mday,Month,&Tm.tm_year,
              &Tm.tm_hour,&Tm.tm_min,&Tm.tm_sec) != 6)
    {
       // Handle RFC 1036 time
              &Tm.tm_hour,&Tm.tm_min,&Tm.tm_sec) != 6)
    {
       // Handle RFC 1036 time
-      if (sscanf(I," %d-%3s-%d %d:%d:%d GMT",&Tm.tm_mday,Month,
+      if (sscanf(I," %2d-%3s-%3d %2d:%2d:%2d GMT",&Tm.tm_mday,Month,
                 &Tm.tm_year,&Tm.tm_hour,&Tm.tm_min,&Tm.tm_sec) == 6)
         Tm.tm_year += 1900;
       else
       {
         // asctime format
                 &Tm.tm_year,&Tm.tm_hour,&Tm.tm_min,&Tm.tm_sec) == 6)
         Tm.tm_year += 1900;
       else
       {
         // asctime format
-        if (sscanf(I," %3s %d %d:%d:%d %d",Month,&Tm.tm_mday,
+        if (sscanf(I," %3s %2d %2d:%2d:%2d %4d",Month,&Tm.tm_mday,
                    &Tm.tm_hour,&Tm.tm_min,&Tm.tm_sec,&Tm.tm_year) != 6)
         {
            // 'ftp' time
                    &Tm.tm_hour,&Tm.tm_min,&Tm.tm_sec,&Tm.tm_year) != 6)
         {
            // 'ftp' time
@@ -890,6 +997,8 @@ bool StrToTime(const string &Val,time_t &Result)
    Tm.tm_isdst = 0;
    if (Month[0] != 0)
       Tm.tm_mon = MonthConv(Month);
    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
    Tm.tm_year -= 1900;
    
    // Convert to local time and then to GMT
@@ -924,6 +1033,68 @@ bool StrToNum(const char *Str,unsigned long &Res,unsigned Len,unsigned Base)
    return true;
 }
                                                                        /*}}}*/
    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;
+   
+   char *End;
+   Res = strtoull(S,&End,Base);
+   if (End == S)
+      return false;
+   
+   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 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;
+   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 */
 // HexDigit - Convert a hex character into an integer                  /*{{{*/
 // ---------------------------------------------------------------------
 /* Helper for Hex2Num */
@@ -1000,16 +1171,18 @@ bool TokSplitString(char Tok,char *Input,char **List,
    return true;
 }
                                                                        /*}}}*/
    return true;
 }
                                                                        /*}}}*/
-// ExplodeString - Split a string up into a vector                     /*{{{*/
+// 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
 // ---------------------------------------------------------------------
 /* 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 an iteratable vector */
-vector<string> ExplodeString(string const &haystack, char const &split)
+   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;
    string::const_iterator start = haystack.begin();
    string::const_iterator end = start;
-   vector<string> exploded;
    do {
       for (; end != haystack.end() && *end != split; ++end);
       exploded.push_back(string(start, end));
    do {
       for (; end != haystack.end() && *end != split; ++end);
       exploded.push_back(string(start, end));
@@ -1018,6 +1191,37 @@ vector<string> ExplodeString(string const &haystack, char const &split)
    return exploded;
 }
                                                                        /*}}}*/
    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 seperator 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                                /*{{{*/
 // ---------------------------------------------------------------------
 /* */
 // RegexChoice - Simple regex list/list matcher                                /*{{{*/
 // ---------------------------------------------------------------------
 /* */
@@ -1028,7 +1232,7 @@ unsigned long RegexChoice(RxChoiceList *Rxs,const char **ListBegin,
       R->Hit = false;
 
    unsigned long Hits = 0;
       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;
    {
       // Check if the name is a regex
       const char *I;
@@ -1078,34 +1282,54 @@ unsigned long RegexChoice(RxChoiceList *Rxs,const char **ListBegin,
    return Hits;
 }
                                                                        /*}}}*/
    return Hits;
 }
                                                                        /*}}}*/
-// ioprintf - C format string outputter to C++ iostreams               /*{{{*/
+// {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 */
 // ---------------------------------------------------------------------
 /* This is used to make the internationalization strings easier to translate
    and to allow reordering of parameters */
-void ioprintf(ostream &out,const char *format,...) 
+static bool iovprintf(ostream &out, const char *format,
+                     va_list &args, ssize_t &size) {
+   char *S = (char*)malloc(size);
+   ssize_t const n = vsnprintf(S, size, format, args);
+   if (n > -1 && n < size) {
+      out << S;
+      free(S);
+      return true;
+   } else {
+      if (n > -1)
+        size = n + 1;
+      else
+        size *= 2;
+   }
+   free(S);
+   return false;
+}
+void ioprintf(ostream &out,const char *format,...)
 {
    va_list args;
 {
    va_list args;
-   va_start(args,format);
-   
-   // sprintf the description
-   char S[4096];
-   vsnprintf(S,sizeof(S),format,args);
-   out << S;
+   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;
+   }
 }
 }
-                                                                       /*}}}*/
-// strprintf - C format string outputter to C++ strings                /*{{{*/
-// ---------------------------------------------------------------------
-/* This is used to make the internationalization strings easier to translate
-   and to allow reordering of parameters */
-void strprintf(string &out,const char *format,...) 
+void strprintf(string &out,const char *format,...)
 {
    va_list args;
 {
    va_list args;
-   va_start(args,format);
-   
-   // sprintf the description
-   char S[4096];
-   vsnprintf(S,sizeof(S),format,args);
-   out = string(S);
+   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                                      /*{{{*/
 }
                                                                        /*}}}*/
 // safe_snprintf - Safer snprintf                                      /*{{{*/
@@ -1117,24 +1341,33 @@ void strprintf(string &out,const char *format,...)
 char *safe_snprintf(char *Buffer,char *End,const char *Format,...)
 {
    va_list args;
 char *safe_snprintf(char *Buffer,char *End,const char *Format,...)
 {
    va_list args;
-   unsigned long Did;
-
-   va_start(args,Format);
+   int Did;
 
    if (End <= Buffer)
       return End;
 
    if (End <= Buffer)
       return End;
-
+   va_start(args,Format);
    Did = vsnprintf(Buffer,End - Buffer,Format,args);
    Did = vsnprintf(Buffer,End - Buffer,Format,args);
+   va_end(args);
+
    if (Did < 0 || Buffer + Did > End)
       return End;
    return Buffer + Did;
 }
                                                                        /*}}}*/
    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
 // 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. */
 int tolower_ascii(int const c)
    standard tolower/toupper and as a bonus avoids problems with different
    locales - we only operate on ascii chars anyway. */
 int tolower_ascii(int const c)
@@ -1145,14 +1378,14 @@ int tolower_ascii(int const 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)
 {
    string::const_iterator Start = List.begin();
    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++)
+   for (string::const_iterator Cur = List.begin(); Cur <= List.end(); ++Cur)
    {
       if (Cur < List.end() && *Cur != ',')
         continue;
    {
       if (Cur < List.end() && *Cur != ',')
         continue;
@@ -1168,7 +1401,80 @@ bool CheckDomainList(const string &Host,const string &List)
    return false;
 }
                                                                        /*}}}*/
    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;
+            }
+            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;
+            }
+            break;
+         default:
+            // FIXME: raise exception here?
+            break;
+      }
+   }
+   return output;
+}
+                                                                       /*}}}*/
 // URI::CopyFrom - Copy from an object                                 /*{{{*/
 // ---------------------------------------------------------------------
 /* This parses the URI into all of its components */
 // URI::CopyFrom - Copy from an object                                 /*{{{*/
 // ---------------------------------------------------------------------
 /* This parses the URI into all of its components */
@@ -1177,7 +1483,7 @@ void URI::CopyFrom(const string &U)
    string::const_iterator I = U.begin();
 
    // Locate the first colon, this separates the scheme
    string::const_iterator I = U.begin();
 
    // Locate the first colon, this separates the scheme
-   for (; I < U.end() && *I != ':' ; I++);
+   for (; I < U.end() && *I != ':' ; ++I);
    string::const_iterator FirstColon = I;
 
    /* Determine if this is a host type URI with a leading double //
    string::const_iterator FirstColon = I;
 
    /* Determine if this is a host type URI with a leading double //
@@ -1189,7 +1495,7 @@ void URI::CopyFrom(const string &U)
    /* Find the / indicating the end of the hostname, ignoring /'s in the
       square brackets */
    bool InBracket = false;
    /* 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++)
+   for (; SingleSlash < U.end() && (*SingleSlash != '/' || InBracket == true); ++SingleSlash)
    {
       if (*SingleSlash == '[')
         InBracket = true;
    {
       if (*SingleSlash == '[')
         InBracket = true;
@@ -1222,11 +1528,11 @@ void URI::CopyFrom(const string &U)
    I = FirstColon + 1;
    if (I > SingleSlash)
       I = SingleSlash;
    I = FirstColon + 1;
    if (I > SingleSlash)
       I = SingleSlash;
-   for (; I < SingleSlash && *I != ':'; I++);
+   for (; I < SingleSlash && *I != ':'; ++I);
    string::const_iterator SecondColon = I;
    
    // Search for the @ after the colon
    string::const_iterator SecondColon = I;
    
    // Search for the @ after the colon
-   for (; I < SingleSlash && *I != '@'; I++);
+   for (; I < SingleSlash && *I != '@'; ++I);
    string::const_iterator At = I;
    
    // Now write the host and user/pass
    string::const_iterator At = I;
    
    // Now write the host and user/pass
@@ -1238,9 +1544,10 @@ void URI::CopyFrom(const string &U)
    else
    {
       Host.assign(At+1,SingleSlash);
    else
    {
       Host.assign(At+1,SingleSlash);
-      User.assign(FirstColon,SecondColon);
+      // username and password must be encoded (RFC 3986)
+      User.assign(DeQuoteString(FirstColon,SecondColon));
       if (SecondColon < At)
       if (SecondColon < At)
-        Password.assign(SecondColon+1,At);
+        Password.assign(DeQuoteString(SecondColon+1,At));
    }   
    
    // Now we parse the RFC 2732 [] hostnames.
    }   
    
    // Now we parse the RFC 2732 [] hostnames.
@@ -1287,72 +1594,75 @@ void URI::CopyFrom(const string &U)
 /* */
 URI::operator string()
 {
 /* */
 URI::operator string()
 {
-   string Res;
-   
+   std::stringstream Res;
+
    if (Access.empty() == false)
    if (Access.empty() == false)
-      Res = Access + ':';
-   
+      Res << Access << ':';
+
    if (Host.empty() == false)
    if (Host.empty() == false)
-   {    
+   {
       if (Access.empty() == false)
       if (Access.empty() == false)
-        Res += "//";
-          
+        Res << "//";
+
       if (User.empty() == false)
       {
       if (User.empty() == false)
       {
-        Res +=  User;
+        // FIXME: Technically userinfo is permitted even less
+        // characters than these, but this is not conveniently
+        // expressed with a blacklist.
+        Res << QuoteString(User, ":/?#[]@");
         if (Password.empty() == false)
         if (Password.empty() == false)
-           Res += ":" + Password;
-        Res += "@";
+           Res << ":" << QuoteString(Password, ":/?#[]@");
+        Res << "@";
       }
       }
-      
+
       // Add RFC 2732 escaping characters
       // 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
       else
-        Res += Host;
-      
+        Res << Host;
+
       if (Port != 0)
       if (Port != 0)
-      {
-        char S[30];
-        sprintf(S,":%u",Port);
-        Res += S;
-      }         
+        Res << ':' << Port;
    }
    }
-   
+
    if (Path.empty() == false)
    {
       if (Path[0] != '/')
    if (Path.empty() == false)
    {
       if (Path[0] != '/')
-        Res += "/" + Path;
+        Res << "/" << Path;
       else
       else
-        Res += Path;
+        Res << Path;
    }
    }
-   
-   return Res;
+
+   return Res.str();
 }
                                                                        /*}}}*/
 // URI::SiteOnly - Return the schema and site for the URI              /*{{{*/
 }
                                                                        /*}}}*/
 // 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();
 string URI::SiteOnly(const string &URI)
 {
    ::URI U(URI);
    U.User.clear();
    U.Password.clear();
    U.Path.clear();
-   U.Port = 0;
+   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  /*{{{*/
    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();
 string URI::NoUserPassword(const string &URI)
 {
    ::URI U(URI);
    U.User.clear();
    U.Password.clear();
-   U.Port = 0;
    return U;
 }
                                                                        /*}}}*/
    return U;
 }
                                                                        /*}}}*/