Vidalia  0.2.21
stringutil.cpp
Go to the documentation of this file.
1 /*
2 ** This file is part of Vidalia, and is subject to the license terms in the
3 ** LICENSE file, found in the top level directory of this distribution. If you
4 ** did not receive the LICENSE file with this file, you may obtain it from the
5 ** Vidalia source package distributed by the Vidalia Project at
6 ** http://www.torproject.org/projects/vidalia.html. No part of Vidalia,
7 ** including this file, may be copied, modified, propagated, or distributed
8 ** except according to the terms described in the LICENSE file.
9 */
10 
11 /*
12 ** \file stringutil.cpp
13 ** \brief Common string manipulation functions
14 */
15 
16 #include "stringutil.h"
17 
18 #include <QCoreApplication>
19 #include <QApplication>
20 
21 
22 /** Create a QStringList from the array of C-style strings. */
23 QStringList
24 char_array_to_stringlist(char **arr, int len)
25 {
26  QStringList list;
27  for (int i = 0; i < len; i++) {
28  list << QString(arr[i]);
29  }
30  return list;
31 }
32 
33 /** Conditionally assigns errmsg to str if str is not null and returns false.
34  * This is a seemingly pointless function, but it saves some messiness in
35  * methods whose QString *errmsg parameter is optional. */
36 bool
37 err(QString *str, const QString &errmsg)
38 {
39  if (str) {
40  *str = errmsg;
41  }
42  return false;
43 }
44 
45 /** Ensures all characters in str are in validChars. If a character appears
46  * in str but not in validChars, it will be removed and the resulting
47  * string returned. */
48 QString
49 ensure_valid_chars(const QString &str, const QString &validChars)
50 {
51  QString out = str;
52  for (int i = 0; i < str.length(); i++) {
53  QChar c = str.at(i);
54  if (validChars.indexOf(c) < 0) {
55  out.remove(c);
56  }
57  }
58  return out;
59 }
60 
61 /** Scrubs an email address by replacing "@" with " at " and "." with " dot ". */
62 QString
63 scrub_email_addr(const QString &email)
64 {
65  QString scrubbed = email;
66  scrubbed = scrubbed.replace("@", " at ");
67  scrubbed = scrubbed.replace(".", " dot ");
68  return scrubbed;
69 }
70 
71 /** Wraps <b>str</b> at <b>width</b> characters wide, using <b>sep</b> as the
72  * word separator (" ", for example), and placing the line ending <b>le</b> at
73  * the end of each line, except the last. */
74 QString
75 string_wrap(const QString &str, int width,
76  const QString &sep, const QString &le)
77 {
78  QString wrapped;
79  int pos, nextsep, wordlen, n;
80  int seplen = sep.length();
81 
82  if (str.length() < width) {
83  return str;
84  }
85 
86  pos = 0;
87  n = width;
88  while (pos < str.length()) {
89  /* Get the length of a "word" */
90  nextsep = str.indexOf(sep, pos);
91  if (nextsep < 0) {
92  nextsep = str.length();
93  }
94  wordlen = nextsep-pos;
95 
96  /* Check if there is room for the word on this line */
97  if (wordlen > n) {
98  /* Create a new line */
99  wrapped.append(le);
100  n = width;
101  }
102 
103  /* Add the word to the current line */
104  wrapped.append(str.mid(pos, wordlen+seplen));
105  n = n - wordlen - seplen;
106  pos += wordlen + seplen;
107  }
108  return wrapped.trimmed();
109 }
110 
111 /** Encodes the bytes in <b>buf</b> as an uppercase hexadecimal string and
112  * returns the result. This function is derived from base16_encode() in Tor's
113  * util.c. See LICENSE for details on Tor's license. */
114 QString
115 base16_encode(const QByteArray &buf)
116 {
117  QString hex;
118  for (int i = 0; i < buf.size(); i++) {
119  hex += "0123456789ABCDEF"[((quint8)buf[i]) >> 4];
120  hex += "0123456789ABCDEF"[((quint8)buf[i]) & 0xf];
121  }
122  return hex;
123 }
124 
125 /** Given an ASCII string <b>str</b>, this function returns a quoted string
126  * with all escaped characters unescaped. Non-ASCII characters in the string
127  * will be converted to the local 8-bit character encoding and encoded using
128  * an escaped octal sequence. The returned string will thus contain only
129  * printable ASCII characters. */
130 QString
131 string_escape(const QString &str)
132 {
133  QByteArray in;
134  QByteArray out;
135  char c;
136 
137  in = str.toLocal8Bit();
138  out.append('\"');
139  for (int i = 0; i < in.length(); i++) {
140  c = in[i];
141  switch (c) {
142  case '\"':
143  out.append("\\\"");
144  break;
145  case '\\':
146  out.append("\\\\");
147  break;
148  case '\n':
149  out.append("\\n");
150  break;
151  case '\r':
152  out.append("\\r");
153  break;
154  case '\t':
155  out.append("\\t");
156  break;
157  default:
158  if (QChar(c).isPrint() && c < 127) {
159  out.append(c);
160  } else {
161  out.append('\\');
162  out.append(QString::number(c, 8).toAscii());
163  }
164  }
165  }
166  out.append('\"');
167  return QString::fromAscii(out);
168 }
169 
170 /** Given a quoted string <b>str</b>, this function returns an unquoted,
171  * unescaped string. <b>str</b> must start and end with an unescaped DQUOTE,
172  * The input string must contain only ASCII characters; however, non-ASCII
173  * characters can be included by encoding their byte sequences in either
174  * escaped hexadecimal (e.g., "\xFF") or octal (e.g., "\301"). The result
175  * will be converted to a QString using the local 8-bit encoding. */
176 QString
177 string_unescape(const QString &str, bool *ok)
178 {
179  QByteArray out;
180  int i;
181 
182  /* The string must start and end with an unescaped dquote */
183  if (str.length() < 2)
184  goto err;
185  if (! str.startsWith("\"") || ! str.endsWith("\""))
186  goto err;
187  if (str.endsWith("\\\"") && ! str.endsWith("\\\\\""))
188  goto err;
189 
190  i = 1;
191  while (i < str.length()-1) {
192  if (str[i] == QLatin1Char('\\')) {
193  QChar c = str[++i];
194  if (c == QLatin1Char('n')) {
195  out.append('\n');
196  } else if (c == QLatin1Char('r')) {
197  out.append('\r');
198  } else if (c == QLatin1Char('t')) {
199  out.append('\t');
200  } else if (c == QLatin1Char('x')) {
201  if (i + 2 >= str.length())
202  goto err;
203  bool isHex;
204  char val = static_cast<char>(str.mid(i+1, 2).toUInt(&isHex, 16));
205  if (! isHex)
206  goto err;
207  out.append(val);
208  i = i + 2;
209  } else if (c.isDigit()) {
210  if (i + 2 >= str.length())
211  goto err;
212  bool isOctal;
213  uint val = str.mid(i, 3).toUInt(&isOctal, 8);
214  if (! isOctal || val > 255)
215  goto err;
216  out.append(static_cast<char>(val));
217  i = i + 2;
218  } else {
219  out.append(str[i].toLatin1());
220  }
221  } else if (str[i] == QLatin1Char('\"')) {
222  /* Unescaped DQUOTE in the middle of the string, so terminate
223  * processing and return a failure. */
224  goto err;
225  } else {
226  out.append(str[i].toLatin1());
227  }
228  i++;
229  }
230  if (ok)
231  *ok = true;
232  return QString::fromLocal8Bit(out.data());
233 
234 err:
235  if (ok)
236  *ok = false;
237  return QString();
238 }
239 
240 /** Parses a series of space-separated key[=value|="value"] tokens from
241  * <b>str</b> and returns the mappings in a QHash. If <b>str</b> was unable
242  * to be parsed, <b>ok</b> is set to false. */
243 QHash<QString,QString>
244 string_parse_keyvals(const QString &str, bool *ok)
245 {
246  int i, len;
247  bool tmp_ok;
248  QHash<QString,QString> keyvals;
249 
250  i = 0;
251  len = str.length();
252  while (i < len && str[i].isSpace())
253  i++; /* Skip initial whitespace */
254  while (i < len) {
255  QString key, val;
256 
257  while (i < len && !str[i].isSpace() && str[i] != '=')
258  key.append(str[i++]);
259 
260  if (i < len && str[i] == '=') {
261  if (++i < len && str[i] == '\"') {
262  /* The value is wrapped in quotes */
263  val.append(str[i]);
264  while (++i < len) {
265  val.append(str[i]);
266  if (str[i] == '\\') {
267  if (++i == len)
268  goto error;
269  val.append(str[i]);
270  } else if (str[i] == '\"') {
271  i++;
272  break;
273  }
274  }
275  val = string_unescape(val, &tmp_ok);
276  if (!tmp_ok)
277  goto error;
278  keyvals.insert(key, val);
279  } else {
280  /* The value was not wrapped in quotes */
281  while (i < len && !str[i].isSpace())
282  val.append(str[i++]);
283  keyvals.insert(key, val);
284  }
285  } else {
286  /* The key had no value */
287  keyvals.insert(key, QString(""));
288  }
289  while (i < len && str[i].isSpace())
290  i++;
291  }
292  if (ok)
293  *ok = true;
294  return keyvals;
295 
296 error:
297  if (ok)
298  *ok = false;
299  return QHash<QString,QString>();
300 }
301 
302 /** Parses a series of command line arguments from <b>str</b>. If <b>str</b>
303  * was unable to be parsed, <b>ok</b> is set to false. */
304 QStringList
305 string_parse_arguments(const QString &str, bool *ok)
306 {
307  QStringList args;
308  int i, len;
309  bool tmp_ok;
310 
311  i = 0;
312  len = str.length();
313  while (i < len && str[i].isSpace())
314  i++; /* Skip initial whitespace */
315  while (i < len) {
316  QString arg;
317 
318  if (str[i] == '\"') {
319  /* The value is wrapped in quotes */
320  arg.append(str[i]);
321  while (++i < len) {
322  arg.append(str[i]);
323  if (str[i] == '\\') {
324  if (++i == len)
325  goto error;
326  arg.append(str[i]);
327  } else if (str[i] == '\"') {
328  i++;
329  break;
330  }
331  }
332  arg = string_unescape(arg, &tmp_ok);
333  if (!tmp_ok)
334  goto error;
335  args << arg;
336  } else {
337  /* The value was not wrapped in quotes */
338  while (i < len && !str[i].isSpace())
339  arg.append(str[i++]);
340  args << arg;
341  }
342  while (i < len && str[i].isSpace())
343  i++;
344  }
345 
346  if (ok)
347  *ok = true;
348  return args;
349 
350 error:
351  if (ok)
352  *ok = false;
353  return QStringList();
354 }
355 
356 /** Formats the list of command line arguments in <b>args</b> as a string.
357  * Arguments that contain ' ', '\', or '"' tokens will be escaped and
358  * wrapped in double quotes. */
359 QString
360 string_format_arguments(const QStringList &args)
361 {
362  QStringList out;
363  foreach (QString arg, args) {
364  if (arg.contains("\"") || arg.contains("\\") || arg.contains(" "))
365  out << string_escape(arg);
366  else
367  out << arg;
368  }
369  return out.join(" ");
370 }
371 
372 /** Returns true if <b>str</b> is a valid hexademical string. Returns false
373  * otherwise. */
374 bool
375 string_is_hex(const QString &str)
376 {
377  for (int i = 0; i < str.length(); i++) {
378  char c = str[i].toUpper().toAscii();
379  if ((c < 'A' || c > 'F') && (c < '0' || c > '9'))
380  return false;
381  }
382  return true;
383 }
384 
385 /** Returns a human-readable description of the time elapsed given by
386  * <b>seconds</b>, broken down into days, hours, minutes and seconds. */
387 QString
388 string_format_uptime(quint64 seconds)
389 {
390  QString uptime;
391  int secs = (seconds % 60);
392  int mins = (seconds / 60 % 60);
393  int hours = (seconds / 3600 % 24);
394  int days = (seconds / 86400);
395 
396  if (days)
397  uptime += qApp->translate("stringutil.h", "%1 days ").arg(days);
398  if (hours)
399  uptime += qApp->translate("stringutil.h", "%1 hours ").arg(hours);
400  if (mins)
401  uptime += qApp->translate("stringutil.h", "%1 mins ").arg(mins);
402  if (secs)
403  uptime += qApp->translate("stringutil.h", "%1 secs").arg(secs);
404 
405  return uptime;
406 }
407 
408 /** Returns a string representation of <b>date</b> formatted according to
409  * "yyyy-MM-dd HH:mm:ss". */
410 QString
411 string_format_datetime(const QDateTime &date)
412 {
413  return date.toString("yyyy-MM-dd HH:mm:ss");
414 }
415 
416 /** Returns a string representation of <b>bytes</b> with the appropriate
417  * suffix of either "B/s", "KB/s", "MB/s" or "GB/s". */
418 QString
420 {
421  if (bytes < 1024)
422  return qApp->translate("stringutil.h", "%1 B/s").arg(bytes);
423  if (bytes < 1048576)
424  return qApp->translate("stringutil.h", "%1 KB/s").arg(bytes/1024.0, 0, 'f', 2);
425  if (bytes < 1073741824)
426  return qApp->translate("stringutil.h", "%1 MB/s").arg(bytes/1048576.0, 0, 'f', 2);
427 
428  return qApp->translate("stringutil.h", "%1 GB/s").arg(bytes/1073741824.0, 0, 'f', 2);
429 }
430