strings.cpp
1 //------------------------------------------------------------------------------
2 // strings.cpp
3 //------------------------------------------------------------------------------
4 //
5 // This library is free software; you can redistribute it and/or
6 // modify it under the terms of the GNU Lesser General Public
7 // License as published by the Free Software Foundation; either
8 // version 2.1 of the License, or (at your option) any later version.
9 //
10 // This library is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 // Lesser General Public License for more details.
14 //
15 // You should have received a copy of the GNU Lesser General Public
16 // License along with this library; if not, write to the Free Software
17 // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
18 // 02110-1301 USA
19 //
20 //------------------------------------------------------------------------------
21 // Copyright (C) 2010 "Zalewa" <zalewapl@gmail.com>
22 //------------------------------------------------------------------------------
23 #include "strings.hpp"
24 
25 #include "random.h"
26 
27 #include "datastreamoperatorwrapper.h"
28 #include "plugins/engineplugin.h"
29 #include "plugins/pluginloader.h"
30 
31 #include <cassert>
32 #include <cmath>
33 
34 #include <QDataStream>
35 #include <QDateTime>
36 #include <QDir>
37 #include <QRegularExpression>
38 #include <QStringList>
39 #include <QUrl>
40 
41 const char Strings::RANDOM_CHAR_POOL[RANDOM_CHAR_POOL_SIZE] =
42 {
43  'a', 'b', 'c', 'd', 'e', 'f', 'g',
44  'h', 'i', 'j', 'k', 'l', 'm', 'n',
45  'o', 'p', 'q', 'r', 's', 't', 'u',
46  'v', 'w', 'x', 'y', 'z', '0', '1',
47  '2', '3', '4', '5', '6', '7', '8',
48  '9'
49 };
50 
51 QString Strings::colorizeString(const QString &str, int current)
52 {
53  static const char colorChart[22][7] =
54  {
55  "FF91A4", //a
56  "D2B48C", //b
57  "808080", //c
58  "32CD32", //d
59  "918151", //e
60  "F4C430", //f
61  "E32636", //g
62  "0000FF", //h
63  "FF8C00", //i
64  "C0C0C0", //j
65  "FFD700", //k
66  "E34234", //l
67  "000000", //m
68  "4169E1", //n
69  "FFDEAD", //o
70  "465945", //p
71  "228b22", //q
72  "800000", //r
73  "704214", //s
74  "A020F0", //t
75  "404040", //u
76  "007F7F", //v
77  };
78 
79  QString ret;
80  bool colored = false;
81  for (int i = 0; i < str.length(); i++)
82  {
83  if (str[i] == ESCAPE_COLOR_CHAR)
84  {
85  i++;
86  if (i >= str.length())
87  break;
88  QChar colorChar = str[i].toLower();
89  int color = colorChar.toLatin1() - 97;
90 
91  // special cases
92  if (colorChar == '+')
93  color = current == 0 ? 19 : current - 1; // + is the current minus one, wrap if needed.
94  else if (colorChar == '*')
95  color = 3; // Chat color which is usally green
96  else if (colorChar == '!')
97  color = 16; // Team char (usually green, but made dark green here for distinction)
98  else if (colorChar == '[') // Named!
99  {
100  int end = str.indexOf(']', i);
101  if (end == -1)
102  break;
103  QString colorName = str.mid(i + 1, end - i - 1);
104  if (colorName.indexOf('"') == -1) // Just in case there's a security problem.
105  ret += QString("<span style=\"color: " + colorName + "\">");
106  i += colorName.length() + 1;
107  colored = true;
108  continue;
109  }
110  else if (colorChar == '-')
111  {
112  if (colored)
113  ret += "</span>";
114  colored = false;
115  continue;
116  }
117 
118  if (colored)
119  {
120  ret += "</span>";
121  colored = false;
122  }
123 
124  if (color >= 0 && color < 22)
125  {
126  ret += QString("<span style=\"color: #") + colorChart[color] + "\">";
127  colored = true;
128  }
129  continue;
130  }
131  ret += str[i];
132  }
133  if (colored)
134  ret += "</span>";
135  return ret;
136 }
137 
138 QStringList Strings::combineManyPaths(const QStringList &fronts, const QString &pathEnd)
139 {
140  QStringList result;
141  for (const QString &s : fronts)
142  {
143  result << combinePaths(s, pathEnd);
144  }
145  return result;
146 }
147 
148 QString Strings::combinePaths(QString pathFront, QString pathEnd)
149 {
150  QString combinedPath;
151 
152  // One of them is nullptr
153  if (pathFront.isEmpty())
154  {
155  return pathEnd;
156  }
157 
158  if (pathEnd.isEmpty())
159  {
160  return pathFront;
161  }
162 
163  pathFront = Strings::trimr(pathFront, "/\\");
164  pathEnd = Strings::triml(pathEnd, "/\\");
165 
166  combinedPath = pathFront + "/" + pathEnd;
167  combinedPath = normalizePath(combinedPath);
168 
169  return combinedPath;
170 }
171 
172 QString Strings::createRandomAlphaNumericString(unsigned numChars)
173 {
174  QString generatedString = "";
175  for (unsigned i = 0; i < numChars; ++i)
176  {
177  auto index = (unsigned) Random::nextUShort(RANDOM_CHAR_POOL_SIZE);
178  generatedString += RANDOM_CHAR_POOL[index];
179  }
180 
181  return generatedString;
182 }
183 
184 QString Strings::createRandomAlphaNumericStringWithNewLines(unsigned numCharsPerLine, unsigned numLines)
185 {
186  QString generatedString = "";
187  for (unsigned i = 0; i < numLines; ++i)
188  {
189  generatedString += createRandomAlphaNumericString(numCharsPerLine) + "\n";
190  }
191 
192  return generatedString;
193 }
194 
195 // NOTE: Be sure that '\\' is the first thing in the array otherwise it will re-escape.
196 static char escapeCharacters[] = {'\\', '"', 0};
197 const QString &Strings::escape(QString &str)
198 {
199  for (unsigned int i = 0; escapeCharacters[i] != 0; i++)
200  {
201  // += 2 because we'll be inserting 1 character.
202  for (int p = 0; p < str.length() && (p = str.indexOf(escapeCharacters[i], p)) != -1; p += 2)
203  {
204  str.insert(p, '\\');
205  }
206  }
207  return str;
208 }
209 const QString &Strings::unescape(QString &str)
210 {
211  for (unsigned int i = 0; escapeCharacters[i] != 0; i++)
212  {
213  QString sequence = "\\" + QString(escapeCharacters[i]);
214  for (int p = 0; p < str.length() && (p = str.indexOf(sequence, p)) != -1; p++)
215  str.replace(str.indexOf(sequence, p), 2, escapeCharacters[i]);
216  }
217  return str;
218 }
219 
220 QString Strings::formatDataAmount(qint64 bytes)
221 {
222  DataUnit dataUnit;
223 
224  auto fBytes = (float)bytes;
225  fBytes = scaleDataUnit(fBytes, dataUnit);
226 
227  QString formattedString = QString("%1 ").arg(fBytes, 0, 'f', 2);
228  switch (dataUnit)
229  {
230  case Byte:
231  formattedString += "B";
232  break;
233 
234  case Kilobyte:
235  formattedString += "kB";
236  break;
237 
238  case Megabyte:
239  formattedString += "MB";
240  break;
241 
242  case Gigabyte:
243  formattedString += "GB";
244  break;
245 
246  default:
247  // Shouldn't really happen.
248  return "#ERR: Formatting data amount error.";
249  }
250 
251  return formattedString;
252 }
253 
254 QString Strings::formatDataSpeed(float speedInBytesPerSecond)
255 {
256  DataUnit dataUnit;
257 
258  speedInBytesPerSecond = scaleDataUnit(speedInBytesPerSecond, dataUnit);
259 
260  QString formattedString = QString("%1 ").arg(speedInBytesPerSecond, 0, 'f', 2);
261  switch (dataUnit)
262  {
263  case Byte:
264  formattedString += "B/s";
265  break;
266 
267  case Kilobyte:
268  formattedString += "kB/s";
269  break;
270 
271  case Megabyte:
272  formattedString += "MB/s";
273  break;
274 
275  case Gigabyte:
276  formattedString += "GB/s";
277  break;
278 
279  default:
280  // Shouldn't really happen.
281  return "#ERR: Formatting speed error.";
282  }
283 
284  return formattedString;
285 }
286 
287 QString Strings::formatTime(float seconds)
288 {
289  if (seconds < 0.0f)
290  {
291  return "#ERR: Formatting time error.";
292  }
293 
294  seconds = ceil(seconds);
295 
296  // QTime is a 24-hour clock. It cannot be used here since seconds input
297  // can be larger than that.
298 
299  int hours = 0;
300  int minutes = 0;
301  int remainingSeconds = 0;
302 
303  if (seconds >= 3600.0f)
304  {
305  // An hour or more.
306  hours = seconds / 3600.0f;
307  seconds -= hours * 3600.0f;
308  }
309 
310  if (seconds >= 60.0f)
311  {
312  // A minute or more.
313  minutes = seconds / 60.0f;
314  seconds -= minutes * 60.0f;
315  }
316 
317  remainingSeconds = (int)seconds;
318 
319  QString formattedString;
320  if (hours > 0)
321  {
322  formattedString += QString("%1h ").arg(hours);
323  }
324 
325  if (hours > 0 || minutes > 0)
326  {
327  formattedString += QString("%1min. ").arg(minutes);
328  }
329 
330  formattedString += QString("%1s").arg(remainingSeconds);
331 
332  return formattedString;
333 }
334 
335 bool Strings::isCharOnCharList(char c, const QString &charList)
336 {
337  for (const auto &candidate : charList)
338  {
339  if (candidate == c)
340  return true;
341  }
342 
343  return false;
344 }
345 
346 bool Strings::isUrlSafe(const QString &url)
347 {
348  QUrl urlObject = url;
349 
350  QString scheme = urlObject.scheme();
351 
352  bool bIsSafe1 = scheme.isEmpty();
353  bool bIsSafe2 = (scheme.compare("http", Qt::CaseInsensitive) == 0);
354  bool bIsSafe3 = (scheme.compare("ftp", Qt::CaseInsensitive) == 0);
355  bool bIsSafe4 = (scheme.compare("https", Qt::CaseInsensitive) == 0);
356 
357 
358  return bIsSafe1 || bIsSafe2 || bIsSafe3 || bIsSafe4;
359 }
360 
361 QString Strings::middleEllipsis(const QString &text, unsigned left, unsigned right, const QString &ellipsis)
362 {
363  if (text.length() <= left + right) {
364  return text;
365  }
366  QString filtered;
367  filtered += text.left(left);
368  filtered += ellipsis;
369  filtered += text.right(text.length() - right);
370  return filtered;
371 }
372 
373 QString Strings::normalizePath(QString path)
374 {
375  path = QDir::fromNativeSeparators(path);
376  path = QDir::cleanPath(path);
377 
378  return path;
379 }
380 
381 QByteArray Strings::readUntilByte(QDataStream &stream, unsigned char stopByte)
382 {
383  DataStreamOperatorWrapper reader(&stream);
384  return reader.readRawUntilByte(stopByte);
385 }
386 
387 float Strings::scaleDataUnit(float bytes, DataUnit &outUnit)
388 {
389  const static float UPPER_BOUNDARY = 900.0f;
390  outUnit = Byte;
391 
392  while (bytes > UPPER_BOUNDARY && outUnit != Gigabyte)
393  {
394  bytes /= 1024.0f;
395  outUnit = (DataUnit)((int)outUnit + 1);
396  }
397 
398  return bytes;
399 }
400 
401 QString Strings::timestamp(const QString &format)
402 {
403  return QDateTime::currentDateTime().toString(format);
404 }
405 
406 void Strings::translateServerAddress(const QString &addressString, QString &hostname, unsigned short &port, const QString &defaultAddress)
407 {
408  port = 0;
409  QStringList addressAndPort = addressString.split(":");
410  QStringList defaultAddressAndPort = defaultAddress.split(":");
411 
412  if (addressAndPort.size() >= 1 && addressAndPort.size() <= 2)
413  {
414  hostname = addressAndPort[0];
415  if (addressAndPort.size() == 2)
416  {
417  port = addressAndPort[1].toUShort();
418  }
419  }
420  else
421  {
422  // if something is not right set default settings
423  if (defaultAddressAndPort.size() >= 1)
424  {
425  hostname = defaultAddressAndPort[0];
426  }
427  }
428 
429  if (port == 0 && defaultAddressAndPort.size() >= 2)
430  {
431  port = defaultAddressAndPort[1].toUShort();
432  }
433 }
434 
435 QString &Strings::trimr(QString &str, const QString &charList)
436 {
437  int i;
438  for (i = str.length() - 1; i >= 0; --i)
439  {
440  if (!isCharOnCharList(str[i].toLatin1(), charList))
441  break;
442  }
443  ++i;
444 
445  return str.remove(i, str.length() - i);
446 }
447 
448 QString &Strings::triml(QString &str, const QString &charList)
449 {
450  int i;
451  for (i = 0; i < str.length(); ++i)
452  {
453  if (!isCharOnCharList(str[i].toLatin1(), charList))
454  break;
455  }
456 
457  return str.remove(0, i);
458 }
459 
460 QString Strings::wrapUrlsWithHtmlATags(const QString &str)
461 {
462  static QString pluginSchemes;
463  if (pluginSchemes.isEmpty())
464  {
465  pluginSchemes = "zds";
466 
467  for (unsigned int i = 0; i < gPlugins->numPlugins(); ++i)
468  pluginSchemes = QString("%1|%2").arg(pluginSchemes)
469  .arg(gPlugins->plugin(i)->info()->data()->scheme);
470  }
471 
472  QRegularExpression pattern(QString("("
473  "("
474  "(http|https|ftp|%1)://"
475  "|(www\\.)"
476  ")[\\w\\-\\.,@?^=%&amp;:/~\\+#\\(\\)]+"
477  ")").arg(pluginSchemes), QRegularExpression::CaseInsensitiveOption);
478  QString newString = str;
479 
480  int offset = 0;
481  for (;;)
482  {
483  auto match = pattern.match(newString, offset);
484  if (!match.hasMatch())
485  break;
486 
487  int index = match.capturedStart(1);
488  QString cap = match.captured(1);
489  int capLength = cap.length();
490 
491  QString replacement = cap;
492  if (cap.startsWith("www.", Qt::CaseInsensitive))
493  {
494  replacement = "http://" + cap;
495  }
496 
497  replacement = QString("<a href=\"%1\">%2</a>").arg(replacement, cap);
498 
499  newString.replace(index, capLength, replacement);
500  offset = index + replacement.length();
501  }
502 
503  return newString;
504 }