]> git.saurik.com Git - apt.git/blobdiff - apt-pkg/contrib/strutl.cc
merged from donkult
[apt.git] / apt-pkg / contrib / strutl.cc
index 4882af9227277ee998086e62968ac5e0fbc64e29..8dd05b9c0c8a57ca0dac14acb19648ba3a624fad 100644 (file)
@@ -1,12 +1,12 @@
 // -*- mode: cpp; mode: fold -*-
 // Description                                                         /*{{{*/
-// $Id: strutl.cc,v 1.7 1998/10/23 00:49:59 jgg Exp $
+// $Id: strutl.cc,v 1.48 2003/07/18 14:15:11 mdz Exp $
 /* ######################################################################
 
-   String Util - Some usefull string functions.
+   String Util - Some useful string functions.
 
-   These have been collected from here and there to do all sorts of usefull
-   things to strings. They are usefull in file parsers, URI handlers and
+   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 source is placed in the Public Domain, do with it what you will
    ##################################################################### */
                                                                        /*}}}*/
 // Includes                                                            /*{{{*/
-#include <strutl.h>
+#include <apt-pkg/strutl.h>
 #include <apt-pkg/fileutl.h>
+#include <apt-pkg/error.h>
 
+#include <apti18n.h>
+    
 #include <ctype.h>
 #include <string.h>
 #include <stdio.h>
-#include <time.h>
+#include <algorithm>
+#include <unistd.h>
+#include <regex.h>
+#include <errno.h>
+#include <stdarg.h>
+#include <iconv.h>
+
+#include "config.h"
+
+using namespace std;
                                                                        /*}}}*/
 
+// 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 
@@ -82,7 +164,8 @@ char *_strtabexpand(char *String,size_t Len)
 // ---------------------------------------------------------------------
 /* 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. */
+   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
@@ -92,12 +175,18 @@ bool ParseQuoteWord(const char *&String,string &Res)
       return false;
    
    // Jump to the next word
-   for (;*C != 0 && *C != ' '; C++)
+   for (;*C != 0 && isspace(*C) == 0; C++)
    {
       if (*C == '"')
       {
-        for (C++;*C != 0 && *C != '"'; C++);
-        if (*C == 0)
+        C = strchr(C + 1, '"');
+        if (C == NULL)
+           return false;
+      }
+      if (*C == '[')
+      {
+        C = strchr(C + 1, ']');
+        if (C == NULL)
            return false;
       }
    }
@@ -109,11 +198,12 @@ bool ParseQuoteWord(const char *&String,string &Res)
    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[3] = 0;
+        Tmp[2] = 0;
         *I = (char)strtol(Tmp,0,16);
         Start += 3;
         continue;
@@ -128,16 +218,16 @@ bool ParseQuoteWord(const char *&String,string &Res)
    Res = Buffer;
    
    // Skip ending white space
-   for (;*C != 0 && *C == ' '; C++);
+   for (;*C != 0 && isspace(*C) != 0; C++);
    String = C;
    return true;
 }
                                                                        /*}}}*/
 // ParseCWord - Parses a string like a C "" expression                 /*{{{*/
 // ---------------------------------------------------------------------
-/* This expects a series of space seperated strings enclosed in ""'s. 
+/* 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)
+bool ParseCWord(const char *&String,string &Res)
 {
    // Skip leading whitespace
    const char *C = String;
@@ -168,22 +258,24 @@ bool ParseCWord(const char *String,string &Res)
       if (isspace(*C) == 0)
         return false;
       *Buf++ = ' ';
-   }   
+   }
    *Buf = 0;
    Res = Buffer;
+   String = C;
    return true;
 }
                                                                        /*}}}*/
 // QuoteString - Convert a string into quoted from                     /*{{{*/
 // ---------------------------------------------------------------------
 /* */
-string QuoteString(string Str,const char *Bad)
+string QuoteString(const string &Str, const char *Bad)
 {
    string Res;
-   for (string::iterator I = Str.begin(); I != Str.end(); I++)
+   for (string::const_iterator I = Str.begin(); I != Str.end(); ++I)
    {
       if (strchr(Bad,*I) != 0 || isprint(*I) == 0 || 
-         *I <= 0x20 || *I >= 0x7F)
+         *I == 0x25 || // percent '%' char
+         *I <= 0x20 || *I >= 0x7F) // control chars
       {
         char Buf[10];
         sprintf(Buf,"%%%02x",(int)*I);
@@ -195,6 +287,37 @@ string QuoteString(string Str,const char *Bad)
    return Res;
 }
                                                                        /*}}}*/
+// 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. 
@@ -211,19 +334,19 @@ string SizeToStr(double Size)
    
    /* bytes, KiloBytes, MegaBytes, GigaBytes, TeraBytes, PetaBytes, 
       ExaBytes, ZettaBytes, YottaBytes */
-   char Ext[] = {'b','k','M','G','T','P','E','Z','Y'};
+   char Ext[] = {'\0','k','M','G','T','P','E','Z','Y'};
    int I = 0;
    while (I <= 8)
    {
       if (ASize < 100 && I != 0)
       {
-         sprintf(S,"%.1f%c",ASize,Ext[I]);
+         sprintf(S,"%'.1f %c",ASize,Ext[I]);
         break;
       }
       
       if (ASize < 10000)
       {
-         sprintf(S,"%.0f%c",ASize,Ext[I]);
+         sprintf(S,"%'.0f %c",ASize,Ext[I]);
         break;
       }
       ASize /= 1000.0;
@@ -244,23 +367,27 @@ string TimeToStr(unsigned long Sec)
    {
       if (Sec > 60*60*24)
       {
-        sprintf(S,"%lid %lih%lim%lis",Sec/60/60/24,(Sec/60/60) % 24,(Sec/60) % 60,Sec % 60);
+        //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)
       {
-        sprintf(S,"%lih%lim%lis",Sec/60/60,(Sec/60) % 60,Sec % 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)
       {
-        sprintf(S,"%lim%lis",Sec/60,Sec % 60);
+        //min means minutes, s means seconds
+        sprintf(S,_("%limin %lis"),Sec/60,Sec % 60);
         break;
       }
-      
-      sprintf(S,"%lis",Sec);
+
+      //s means seconds
+      sprintf(S,_("%lis"),Sec);
       break;
    }
    
@@ -270,7 +397,7 @@ string TimeToStr(unsigned long Sec)
 // SubstVar - Substitute a string for another string                   /*{{{*/
 // ---------------------------------------------------------------------
 /* This replaces all occurances of Subst with Contents in Str. */
-string SubstVar(string Str,string Subst,string Contents)
+string SubstVar(const string &Str,const string &Subst,const string &Contents)
 {
    string::size_type Pos = 0;
    string::size_type OldPos = 0;
@@ -288,35 +415,42 @@ string SubstVar(string Str,string Subst,string Contents)
    
    return Temp + string(Str,OldPos);
 }
+
+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(string URI)
+string URItoFileName(const string &URI)
 {
-   string::const_iterator I = URI.begin() + URI.find(':') + 1;
-   for (; I < URI.end() && *I == '/'; I++);
-
+   // Nuke 'sensitive' items
+   ::URI U(URI);
+   U.User.clear();
+   U.Password.clear();
+   U.Access.clear();
+   
    // "\x00-\x20{}|\\\\^\\[\\]<>\"\x7F-\xFF";
-   URI = QuoteString(string(I,URI.end() - I),"\\|{}[]<>\"^~_=!@#$%^&*");
-   string::iterator J = URI.begin();
-   for (; J != URI.end(); J++)
-      if (*J == '/') 
-        *J = '_';
-   return URI;
-}
-                                                                       /*}}}*/
-// URIAccess - Return the access method for the URI                    /*{{{*/
-// ---------------------------------------------------------------------
-/* */
-string URIAccess(string URI)
-{
-   string::size_type Pos = URI.find(':');
-   if (Pos == string::npos)
-      return URI;
-   return string(URI,0,Pos);
+   string NewURI = QuoteString(U,"\\|{}[]<>\"^~_=!@#$%^&*");
+   replace(NewURI.begin(),NewURI.end(),'/','_');
+   return NewURI;
 }
                                                                        /*}}}*/
 // Base64Encode - Base64 Encoding routine for short strings            /*{{{*/
@@ -325,7 +459,7 @@ string URIAccess(string URI)
    from wget and then patched and bug fixed.
  
    This spec can be found in rfc2045 */
-string Base64Encode(string S)
+string Base64Encode(const string &S)
 {
    // Conversion table.
    static char tbl[64] = {'A','B','C','D','E','F','G','H',
@@ -376,9 +510,9 @@ string Base64Encode(string S)
    return Final;
 }
                                                                        /*}}}*/
-// stringcmp - Arbitary string compare                                 /*{{{*/
+// stringcmp - Arbitrary string compare                                        /*{{{*/
 // ---------------------------------------------------------------------
-/* This safely compares two non-null terminated strings of arbitary 
+/* 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)
 {
@@ -396,14 +530,51 @@ int stringcmp(const char *A,const char *AEnd,const char *B,const char *BEnd)
       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 - Arbitary case insensitive string compare            /*{{{*/
+// 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 (toupper(*A) != toupper(*B))
+      if (tolower_ascii(*A) != tolower_ascii(*B))
         break;
 
    if (A == AEnd && B == BEnd)
@@ -412,35 +583,71 @@ int stringcasecmp(const char *A,const char *AEnd,const char *B,const char *BEnd)
       return 1;
    if (B == BEnd)
       return -1;
-   if (toupper(*A) < toupper(*B))
+   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 taged string             /*{{{*/
 // ---------------------------------------------------------------------
 /* The format is like those used in package files and the method 
    communication system */
-string LookupTag(string Message,const char *Tag,const char *Default)
+string LookupTag(const string &Message,const char *Tag,const char *Default)
 {
    // Look for a matching tag.
    int Length = strlen(Tag);
-   for (string::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)
       {
         // Find the end of line and strip the leading/trailing spaces
-        string::iterator J;
+        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-I);
+        return string(I,J);
       }
       
-      for (; *I != '\n' && I < Message.end(); I++);
+      for (; *I != '\n' && I < Message.end(); ++I);
    }   
    
    // Failed to find a match
@@ -453,7 +660,7 @@ string LookupTag(string Message,const char *Tag,const char *Default)
 // ---------------------------------------------------------------------
 /* This inspects the string to see if it is true or if it is false and
    then returns the result. Several varients on true/false are checked. */
-int StringToBool(string Text,int Default = -1)
+int StringToBool(const string &Text,int Default)
 {
    char *End;
    int Res = strtol(Text.c_str(),&End,0);   
@@ -464,6 +671,7 @@ int StringToBool(string Text,int Default = -1)
    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;
    
@@ -471,6 +679,7 @@ int StringToBool(string Text,int Default = -1)
    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;
    
@@ -483,14 +692,16 @@ int StringToBool(string Text,int Default = -1)
    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"};
 
-   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;
@@ -500,34 +711,52 @@ string TimeRFC1123(time_t Date)
 // ---------------------------------------------------------------------
 /* 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. */
+   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').  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.
+ */
 bool ReadMessages(int Fd, vector<string> &List)
 {
-   char Buffer[4000];
+   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));
+      if (Res < 0 && errno == EINTR)
+        continue;
       
       // Process is dead, this is kind of bad..
       if (Res == 0)
         return false;
       
       // No data
-      if (Res <= 0)
+      if (Res < 0 && errno == EAGAIN)
         return true;
-
+      if (Res < 0)
+        return false;
+                             
       End += Res;
       
       // Look for the end of the message
-      for (char *I = Buffer; I < End; I++)
+      for (char *I = Buffer; I + 1 < End; I++)
       {
         if (I[0] != '\n' || I[1] != '\n')
            continue;
         
         // Pull the message out
-        string Message(Buffer,0,I-Buffer);
+        string Message(Buffer,I-Buffer);
+        PartialMessage += Message;
 
         // Fix up the buffer
         for (; I < End && *I == '\n'; I++);
@@ -535,10 +764,32 @@ bool ReadMessages(int Fd, vector<string> &List)
         memmove(Buffer,I,End-Buffer);
         I = Buffer;
         
-        List.push_back(Message);
+        List.push_back(PartialMessage);
+        PartialMessage.clear();
       }
-      if (End == Buffer)
-        return true;
+      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;
+       }
 
       if (WaitFd(Fd) == false)
         return false;
@@ -548,28 +799,28 @@ bool ReadMessages(int Fd, vector<string> &List)
 // 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 touppers though. */
+   Made it a bit more robust with a few tolower_ascii though. */
 static int MonthConv(char *Month)
 {
-   switch (toupper(*Month)) 
+   switch (tolower_ascii(*Month)) 
    {
-      case 'A':
-      return toupper(Month[1]) == 'P'?3:7;
-      case 'D':
+      case 'a':
+      return tolower_ascii(Month[1]) == 'p'?3:7;
+      case 'd':
       return 11;
-      case 'F':
+      case 'f':
       return 1;
-      case 'J':
-      if (toupper(Month[1]) == 'A')
+      case 'j':
+      if (tolower_ascii(Month[1]) == 'a')
         return 0;
-      return toupper(Month[2]) == 'N'?5:6;
-      case 'M':
-      return toupper(Month[2]) == 'R'?2:4;
-      case 'N':
+      return tolower_ascii(Month[2]) == 'n'?5:6;
+      case 'm':
+      return tolower_ascii(Month[2]) == 'r'?2:4;
+      case 'n':
       return 10;
-      case 'O':
+      case 'o':
       return 9;
-      case 'S':
+      case 's':
       return 8;
 
       // Pretend it is January..
@@ -578,22 +829,87 @@ static int MonthConv(char *Month)
    }   
 }
                                                                        /*}}}*/
+// 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
+                                                                       /*}}}*/
+// 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                         /*{{{*/
 // ---------------------------------------------------------------------
 /* This handles all 3 populare 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 :<*/
-bool StrToTime(string Val,time_t &Result)
+   reason the C library does not provide any such function :< This also
+   handles the weird, but unambiguous FTP time format*/
+bool StrToTime(const string &Val,time_t &Result)
 {
    struct tm Tm;
    char Month[10];
-   const char *I = Val.c_str();
-   
+
    // 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;
    if (sscanf(I," %d %3s %d %d:%d:%d GMT",&Tm.tm_mday,Month,&Tm.tm_year,
              &Tm.tm_hour,&Tm.tm_min,&Tm.tm_sec) != 6)
    {
@@ -606,12 +922,19 @@ bool StrToTime(string Val,time_t &Result)
         // asctime format
         if (sscanf(I," %3s %d %d:%d:%d %d",Month,&Tm.tm_mday,
                    &Tm.tm_hour,&Tm.tm_min,&Tm.tm_sec,&Tm.tm_year) != 6)
-           return false;
+        {
+           // 'ftp' time
+           if (sscanf(Val.c_str(),"%4d%2d%2d%2d%2d%2d",&Tm.tm_year,&Tm.tm_mon,
+                      &Tm.tm_mday,&Tm.tm_hour,&Tm.tm_min,&Tm.tm_sec) != 6)
+              return false;
+           Tm.tm_mon--;
+        }       
       }
    }
    
    Tm.tm_isdst = 0;
-   Tm.tm_mon = MonthConv(Month);
+   if (Month[0] != 0)
+      Tm.tm_mon = MonthConv(Month);
    Tm.tm_year -= 1900;
    
    // Convert to local time and then to GMT
@@ -619,3 +942,550 @@ bool StrToTime(string Val,time_t &Result)
    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)
+{
+   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 = strtoul(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 &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;
+   }
+}
+                                                                       /*}}}*/
+// 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 0;
+}
+                                                                       /*}}}*/
+// 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)
+{
+   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)
+   {
+      if (isxdigit(*I) == 0 || isxdigit(I[1]) == 0)
+        return false;
+      
+      Num[J] = HexDigit(I[0]) << 4;
+      Num[J] += HexDigit(I[1]);
+   }
+   
+   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)
+{
+   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));
+      start = end + 1;
+   } while (end != haystack.end() && (++end) != haystack.end());
+   return exploded;
+}
+                                                                       /*}}}*/
+// 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;
+}
+                                                                       /*}}}*/
+// ioprintf - C format string outputter to C++ iostreams               /*{{{*/
+// ---------------------------------------------------------------------
+/* This is used to make the internationalization strings easier to translate
+   and to allow reordering of parameters */
+void ioprintf(ostream &out,const char *format,...) 
+{
+   va_list args;
+   va_start(args,format);
+   
+   // sprintf the description
+   char S[4096];
+   vsnprintf(S,sizeof(S),format,args);
+   out << S;
+}
+                                                                       /*}}}*/
+// 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,...) 
+{
+   va_list args;
+   va_start(args,format);
+   
+   // sprintf the description
+   char S[4096];
+   vsnprintf(S,sizeof(S),format,args);
+   out = string(S);
+}
+                                                                       /*}}}*/
+// 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;
+
+   va_start(args,Format);
+
+   if (End <= Buffer)
+      return End;
+
+   Did = vsnprintf(Buffer,End - Buffer,Format,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 absolut minimum - and is noteable 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)
+{
+   if (c >= 'A' && c <= 'Z')
+      return c + 32;
+   return c;
+}
+                                                                       /*}}}*/
+
+// CheckDomainList - See if Host is in a , seperate list               /*{{{*/
+// ---------------------------------------------------------------------
+/* The domain list is a comma seperate 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;
+}
+                                                                       /*}}}*/
+// DeEscapeString - unescape (\0XX and \xXX) from a string             /*{{{*/
+// ---------------------------------------------------------------------
+/* */
+string DeEscapeString(const string &input)
+{
+   char tmp[3];
+   string::const_iterator it, escape_start;
+   string output, octal, hex;
+   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 */
+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;
+   for (; I < SingleSlash && *I != ':'; ++I);
+   string::const_iterator SecondColon = I;
+   
+   // Search for the @ after the colon
+   for (; I < SingleSlash && *I != '@'; ++I);
+   string::const_iterator At = I;
+   
+   // 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()
+{
+   string Res;
+   
+   if (Access.empty() == false)
+      Res = Access + ':';
+   
+   if (Host.empty() == false)
+   {    
+      if (Access.empty() == false)
+        Res += "//";
+          
+      if (User.empty() == false)
+      {
+        Res +=  User;
+        if (Password.empty() == false)
+           Res += ":" + Password;
+        Res += "@";
+      }
+      
+      // Add RFC 2732 escaping characters
+      if (Access.empty() == false &&
+         (Host.find('/') != string::npos || Host.find(':') != string::npos))
+        Res += '[' + Host + ']';
+      else
+        Res += Host;
+      
+      if (Port != 0)
+      {
+        char S[30];
+        sprintf(S,":%u",Port);
+        Res += S;
+      }         
+   }
+   
+   if (Path.empty() == false)
+   {
+      if (Path[0] != '/')
+        Res += "/" + Path;
+      else
+        Res += Path;
+   }
+   
+   return Res;
+}
+                                                                       /*}}}*/
+// 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();
+   U.Port = 0;
+   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();
+   U.Port = 0;
+   return U;
+}
+                                                                       /*}}}*/