Vidalia 0.2.15
stringutil.cpp
Go to the documentation of this file.
00001 /*
00002 **  This file is part of Vidalia, and is subject to the license terms in the
00003 **  LICENSE file, found in the top level directory of this distribution. If you
00004 **  did not receive the LICENSE file with this file, you may obtain it from the
00005 **  Vidalia source package distributed by the Vidalia Project at
00006 **  http://www.torproject.org/projects/vidalia.html. No part of Vidalia, 
00007 **  including this file, may be copied, modified, propagated, or distributed 
00008 **  except according to the terms described in the LICENSE file.
00009 */
00010 
00011 /*
00012 ** \file stringutil.cpp
00013 ** \brief Common string manipulation functions
00014 */
00015 
00016 #include "stringutil.h"
00017 
00018 #include <QCoreApplication>
00019 #include <QApplication>
00020 
00021 
00022 /** Create a QStringList from the array of C-style strings. */
00023 QStringList
00024 char_array_to_stringlist(char **arr, int len)
00025 {
00026   QStringList list;
00027   for (int i = 0; i < len; i++) {
00028     list << QString(arr[i]);
00029   }
00030   return list;
00031 }
00032 
00033 /** Conditionally assigns errmsg to str if str is not null and returns false.
00034  * This is a seemingly pointless function, but it saves some messiness in
00035  * methods whose QString *errmsg parameter is optional. */
00036 bool
00037 err(QString *str, const QString &errmsg)
00038 {
00039   if (str) {
00040     *str = errmsg;
00041   }
00042   return false;
00043 }
00044 
00045 /** Ensures all characters in str are in validChars. If a character appears
00046  * in str but not in validChars, it will be removed and the resulting
00047  * string returned. */
00048 QString
00049 ensure_valid_chars(const QString &str, const QString &validChars)
00050 {
00051   QString out = str;
00052   for (int i = 0; i < str.length(); i++) {
00053     QChar c = str.at(i);
00054     if (validChars.indexOf(c) < 0) {
00055       out.remove(c);
00056     }
00057   }
00058   return out;
00059 }
00060 
00061 /** Scrubs an email address by replacing "@" with " at " and "." with " dot ". */
00062 QString
00063 scrub_email_addr(const QString &email)
00064 {
00065   QString scrubbed = email;
00066   scrubbed = scrubbed.replace("@", " at ");
00067   scrubbed = scrubbed.replace(".", " dot ");
00068   return scrubbed;
00069 }
00070 
00071 /** Wraps <b>str</b> at <b>width</b> characters wide, using <b>sep</b> as the
00072  * word separator (" ", for example), and placing the line ending <b>le</b> at
00073  * the end of each line, except the last. */
00074 QString
00075 string_wrap(const QString &str, int width,
00076             const QString &sep, const QString &le)
00077 {
00078   QString wrapped;
00079   int pos, nextsep, wordlen, n;
00080   int seplen = sep.length();
00081  
00082   if (str.length() < width) {
00083     return str;
00084   }
00085 
00086   pos = 0; 
00087   n = width;
00088   while (pos < str.length()) {
00089     /* Get the length of a "word" */
00090     nextsep = str.indexOf(sep, pos);
00091     if (nextsep < 0) {
00092       nextsep = str.length();
00093     }
00094     wordlen = nextsep-pos;
00095 
00096     /* Check if there is room for the word on this line */
00097     if (wordlen > n) {
00098       /* Create a new line */
00099       wrapped.append(le);
00100       n = width;
00101     }
00102 
00103     /* Add the word to the current line */
00104     wrapped.append(str.mid(pos, wordlen+seplen));
00105     n = n - wordlen - seplen;
00106     pos += wordlen + seplen;
00107   }
00108   return wrapped.trimmed();
00109 }
00110 
00111 /** Encodes the bytes in <b>buf</b> as an uppercase hexadecimal string and
00112  * returns the result. This function is derived from base16_encode() in Tor's
00113  * util.c. See LICENSE for details on Tor's license. */
00114 QString
00115 base16_encode(const QByteArray &buf)
00116 {
00117   QString hex;
00118   for (int i = 0; i < buf.size(); i++) {
00119     hex += "0123456789ABCDEF"[((quint8)buf[i]) >>  4];
00120     hex += "0123456789ABCDEF"[((quint8)buf[i]) & 0xf];
00121   }
00122   return hex;
00123 }
00124 
00125 /** Given an ASCII string <b>str</b>, this function returns a quoted string
00126  * with all escaped characters unescaped. Non-ASCII characters in the string
00127  * will be converted to the local 8-bit character encoding and encoded using
00128  * an escaped octal sequence. The returned string will thus contain only
00129  * printable ASCII characters. */
00130 QString
00131 string_escape(const QString &str)
00132 {
00133   QByteArray in;
00134   QByteArray out;
00135   char c;
00136 
00137   in = str.toLocal8Bit();
00138   out.append('\"');
00139   for (int i = 0; i < in.length(); i++) {
00140     c = in[i];
00141     switch (c) {
00142       case '\"':
00143         out.append("\\\"");
00144         break;
00145       case '\\':
00146         out.append("\\\\");
00147         break;
00148       case '\n':
00149         out.append("\\n");
00150         break;
00151       case '\r':
00152         out.append("\\r");
00153         break;
00154       case '\t':
00155         out.append("\\t");
00156         break;
00157       default:
00158         if (QChar(c).isPrint() && c < 127) {
00159           out.append(c);
00160         } else {
00161           out.append('\\');
00162           out.append(QString::number(c, 8).toAscii());
00163         }
00164     }
00165   }
00166   out.append('\"');
00167   return QString::fromAscii(out);
00168 }
00169 
00170 /** Given a quoted string <b>str</b>, this function returns an unquoted,
00171  * unescaped string. <b>str</b> must start and end with an unescaped DQUOTE,
00172  * The input string must contain only ASCII characters; however, non-ASCII
00173  * characters can be included by encoding their byte sequences in either
00174  * escaped hexadecimal (e.g., "\xFF") or octal (e.g., "\301"). The result
00175  * will be converted to a QString using the local 8-bit encoding. */
00176 QString
00177 string_unescape(const QString &str, bool *ok)
00178 {
00179   QByteArray out;
00180   int i;
00181 
00182   /* The string must start and end with an unescaped dquote */
00183   if (str.length() < 2)
00184     goto err;
00185   if (! str.startsWith("\"") || ! str.endsWith("\""))
00186     goto err;
00187   if (str.endsWith("\\\"") && ! str.endsWith("\\\\\""))
00188     goto err;
00189 
00190   i = 1;
00191   while (i < str.length()-1) {
00192     if (str[i] == QLatin1Char('\\')) {
00193       QChar c = str[++i];
00194       if (c == QLatin1Char('n')) {
00195         out.append('\n');
00196       } else if (c == QLatin1Char('r')) {
00197         out.append('\r');
00198       } else if (c == QLatin1Char('t')) {
00199         out.append('\t');
00200       } else if (c == QLatin1Char('x')) {
00201         if (i + 2 >= str.length())
00202           goto err;
00203         bool isHex;
00204         char val = static_cast<char>(str.mid(i+1, 2).toUInt(&isHex, 16));
00205         if (! isHex)
00206           goto err;
00207         out.append(val);
00208         i = i + 2;
00209       } else if (c.isDigit()) {
00210         if (i + 2 >= str.length())
00211           goto err;
00212         bool isOctal;
00213         uint val = str.mid(i, 3).toUInt(&isOctal, 8);
00214         if (! isOctal || val > 255)
00215           goto err;
00216         out.append(static_cast<char>(val));
00217         i = i + 2;
00218       } else {
00219         out.append(str[i].toLatin1());
00220       }
00221     } else if (str[i] == QLatin1Char('\"')) {
00222       /* Unescaped DQUOTE in the middle of the string, so terminate
00223        * processing and return a failure. */
00224       goto err;
00225     } else {
00226       out.append(str[i].toLatin1());
00227     }
00228     i++;
00229   }
00230   if (ok)
00231     *ok = true;
00232   return QString::fromLocal8Bit(out.data());
00233 
00234 err:
00235   if (ok)
00236     *ok = false;
00237   return QString();
00238 }
00239 
00240 /** Parses a series of space-separated key[=value|="value"] tokens from
00241  * <b>str</b> and returns the mappings in a QHash. If <b>str</b> was unable
00242  * to be parsed, <b>ok</b> is set to false. */
00243 QHash<QString,QString>
00244 string_parse_keyvals(const QString &str, bool *ok)
00245 {
00246   int i, len;
00247   bool tmp_ok;
00248   QHash<QString,QString> keyvals;
00249   
00250   i = 0;
00251   len = str.length();
00252   while (i < len && str[i].isSpace())
00253     i++; /* Skip initial whitespace */
00254   while (i < len) {
00255     QString key, val;
00256     
00257     while (i < len && !str[i].isSpace() && str[i] != '=')
00258       key.append(str[i++]);
00259       
00260     if (i < len && str[i] == '=') {
00261       if (++i < len && str[i] == '\"') {
00262         /* The value is wrapped in quotes */
00263         val.append(str[i]);
00264         while (++i < len) {
00265           val.append(str[i]);
00266           if (str[i] == '\\') {
00267             if (++i == len)
00268               goto error;
00269             val.append(str[i]);
00270           } else if (str[i] == '\"') {
00271             i++;
00272             break;
00273           } 
00274         }
00275         val = string_unescape(val, &tmp_ok);
00276         if (!tmp_ok)
00277           goto error;
00278         keyvals.insert(key, val);
00279       } else {
00280         /* The value was not wrapped in quotes */
00281         while (i < len && !str[i].isSpace())
00282           val.append(str[i++]);
00283         keyvals.insert(key, val);
00284       }
00285     } else {
00286       /* The key had no value */
00287       keyvals.insert(key, QString(""));
00288     }
00289     while (i < len && str[i].isSpace())
00290       i++;
00291   }
00292   if (ok)
00293     *ok = true;
00294   return keyvals;
00295 
00296 error:
00297   if (ok)
00298     *ok = false;
00299   return QHash<QString,QString>();
00300 }
00301 
00302 /** Parses a series of command line arguments from <b>str</b>. If <b>str</b>
00303  * was unable to be parsed, <b>ok</b> is set to false. */
00304 QStringList
00305 string_parse_arguments(const QString &str, bool *ok)
00306 {
00307   QStringList args;
00308   int i, len;
00309   bool tmp_ok;
00310 
00311   i = 0;
00312   len = str.length();
00313   while (i < len && str[i].isSpace())
00314     i++; /* Skip initial whitespace */
00315   while (i < len) {
00316     QString arg;
00317     
00318     if (str[i] == '\"') {
00319       /* The value is wrapped in quotes */
00320       arg.append(str[i]);
00321       while (++i < len) {
00322         arg.append(str[i]);
00323         if (str[i] == '\\') {
00324           if (++i == len)
00325             goto error;
00326           arg.append(str[i]);
00327         } else if (str[i] == '\"') {
00328           i++;
00329           break;
00330         } 
00331       }
00332       arg = string_unescape(arg, &tmp_ok);
00333       if (!tmp_ok)
00334         goto error;
00335       args << arg;
00336     } else {
00337       /* The value was not wrapped in quotes */
00338       while (i < len && !str[i].isSpace())
00339         arg.append(str[i++]);
00340       args << arg;
00341     }
00342     while (i < len && str[i].isSpace())
00343       i++;
00344   }
00345 
00346   if (ok)
00347     *ok = true;
00348   return args;
00349 
00350 error:
00351   if (ok)
00352     *ok = false;
00353   return QStringList();
00354 }
00355 
00356 /** Formats the list of command line arguments in <b>args</b> as a string.
00357  * Arguments that contain ' ', '\', or '"' tokens will be escaped and
00358  * wrapped in double quotes. */
00359 QString
00360 string_format_arguments(const QStringList &args)
00361 {
00362   QStringList out;
00363   foreach (QString arg, args) {
00364     if (arg.contains("\"") || arg.contains("\\") || arg.contains(" "))
00365       out << string_escape(arg);
00366     else 
00367       out << arg;
00368   }
00369   return out.join(" ");
00370 }
00371 
00372 /** Returns true if <b>str</b> is a valid hexademical string. Returns false
00373  * otherwise. */
00374 bool
00375 string_is_hex(const QString &str)
00376 {
00377   for (int i = 0; i < str.length(); i++) {
00378     char c = str[i].toUpper().toAscii();
00379     if ((c < 'A' || c > 'F') && (c < '0' || c > '9'))
00380       return false;
00381   }
00382   return true;
00383 }
00384 
00385 /** Returns a human-readable description of the time elapsed given by
00386  * <b>seconds</b>, broken down into days, hours, minutes and seconds. */
00387 QString
00388 string_format_uptime(quint64 seconds)
00389 {
00390   QString uptime;
00391   int secs  = (seconds % 60);
00392   int mins  = (seconds / 60 % 60);
00393   int hours = (seconds / 3600 % 24);
00394   int days  = (seconds / 86400);
00395 
00396   if (days)
00397     uptime += qApp->translate("stringutil.h", "%1 days ").arg(days);
00398   if (hours)
00399     uptime += qApp->translate("stringutil.h", "%1 hours ").arg(hours);
00400   if (mins)
00401     uptime += qApp->translate("stringutil.h", "%1 mins ").arg(mins);
00402   if (secs)
00403     uptime += qApp->translate("stringutil.h", "%1 secs").arg(secs);
00404 
00405   return uptime;
00406 }
00407 
00408 /** Returns a string representation of <b>date</b> formatted according to
00409  * "yyyy-MM-dd HH:mm:ss". */
00410 QString
00411 string_format_datetime(const QDateTime &date)
00412 {
00413   return date.toString("yyyy-MM-dd HH:mm:ss");
00414 }
00415 
00416 /** Returns a string representation of <b>bytes</b> with the appropriate
00417  * suffix of either "B/s", "KB/s", "MB/s" or "GB/s". */
00418 QString
00419 string_format_bandwidth(quint64 bytes)
00420 {
00421   if (bytes < 1024)
00422     return qApp->translate("stringutil.h", "%1 B/s").arg(bytes);
00423   if (bytes < 1048576)
00424     return qApp->translate("stringutil.h", "%1 KB/s").arg(bytes/1024.0, 0, 'f', 2);
00425   if (bytes < 1073741824)
00426     return qApp->translate("stringutil.h", "%1 MB/s").arg(bytes/1048576.0, 0, 'f', 2);
00427 
00428   return qApp->translate("stringutil.h", "%1 GB/s").arg(bytes/1073741824.0, 0, 'f', 2);
00429 }
00430