qBittorrent
FilterParserThread Class Referencefinal

#include <filterparserthread.h>

Inheritance diagram for FilterParserThread:
Collaboration diagram for FilterParserThread:

Signals

void IPFilterParsed (int ruleCount)
 
void IPFilterError ()
 

Public Member Functions

 FilterParserThread (QObject *parent=nullptr)
 
 ~FilterParserThread ()
 
void processFilterFile (const QString &filePath)
 
lt::ip_filter IPfilter ()
 

Protected Member Functions

void run () override
 

Private Member Functions

int findAndNullDelimiter (char *const data, char delimiter, int start, int end, bool reverse=false)
 
int trim (char *const data, int start, int end)
 
int parseDATFilterFile ()
 
int parseP2PFilterFile ()
 
int getlineInStream (QDataStream &stream, std::string &name, char delim)
 
int parseP2BFilterFile ()
 

Private Attributes

bool m_abort
 
QString m_filePath
 
lt::ip_filter m_filter
 

Detailed Description

Definition at line 37 of file filterparserthread.h.

Constructor & Destructor Documentation

◆ FilterParserThread()

FilterParserThread::FilterParserThread ( QObject *  parent = nullptr)

Definition at line 111 of file filterparserthread.cpp.

112  : QThread(parent)
113  , m_abort(false)
114 {
115 }

◆ ~FilterParserThread()

FilterParserThread::~FilterParserThread ( )

Definition at line 117 of file filterparserthread.cpp.

118 {
119  m_abort = true;
120  wait();
121 }

References m_abort.

Member Function Documentation

◆ findAndNullDelimiter()

int FilterParserThread::findAndNullDelimiter ( char *const  data,
char  delimiter,
int  start,
int  end,
bool  reverse = false 
)
private

Definition at line 650 of file filterparserthread.cpp.

651 {
652  if (!reverse)
653  {
654  for (int i = start; i <= end; ++i)
655  {
656  if (data[i] == delimiter)
657  {
658  data[i] = '\0';
659  return i;
660  }
661  }
662  }
663  else
664  {
665  for (int i = end; i >= start; --i)
666  {
667  if (data[i] == delimiter)
668  {
669  data[i] = '\0';
670  return i;
671  }
672  }
673  }
674 
675  return -1;
676 }

Referenced by parseDATFilterFile(), and parseP2PFilterFile().

Here is the caller graph for this function:

◆ getlineInStream()

int FilterParserThread::getlineInStream ( QDataStream &  stream,
std::string &  name,
char  delim 
)
private

Definition at line 441 of file filterparserthread.cpp.

442 {
443  char c;
444  int totalRead = 0;
445  int read;
446  do
447  {
448  read = stream.readRawData(&c, 1);
449  totalRead += read;
450  if (read > 0)
451  {
452  if (c != delim)
453  {
454  name += c;
455  }
456  else
457  {
458  // Delim found
459  return totalRead;
460  }
461  }
462  }
463  while (read > 0);
464 
465  return totalRead;
466 }

Referenced by parseP2BFilterFile().

Here is the caller graph for this function:

◆ IPfilter()

lt::ip_filter FilterParserThread::IPfilter ( )

Definition at line 611 of file filterparserthread.cpp.

612 {
613  return m_filter;
614 }
lt::ip_filter m_filter

References m_filter.

◆ IPFilterError

void FilterParserThread::IPFilterError ( )
signal

Referenced by BitTorrent::Session::enableIPFilter(), and run().

Here is the caller graph for this function:

◆ IPFilterParsed

void FilterParserThread::IPFilterParsed ( int  ruleCount)
signal

Referenced by BitTorrent::Session::enableIPFilter(), and run().

Here is the caller graph for this function:

◆ parseDATFilterFile()

int FilterParserThread::parseDATFilterFile ( )
private

Definition at line 124 of file filterparserthread.cpp.

125 {
126  int ruleCount = 0;
127  QFile file(m_filePath);
128  if (!file.exists()) return ruleCount;
129 
130  if (!file.open(QIODevice::ReadOnly | QIODevice::Text))
131  {
132  LogMsg(tr("I/O Error: Could not open IP filter file in read mode."), Log::CRITICAL);
133  return ruleCount;
134  }
135 
136  std::vector<char> buffer(BUFFER_SIZE, 0); // seems a bit faster than QVector
137  qint64 bytesRead = 0;
138  int offset = 0;
139  int start = 0;
140  int endOfLine = -1;
141  int nbLine = 0;
142  int parseErrorCount = 0;
143  const auto addLog = [&parseErrorCount](const QString &msg)
144  {
145  if (parseErrorCount <= MAX_LOGGED_ERRORS)
146  LogMsg(msg, Log::CRITICAL);
147  };
148 
149  while (true)
150  {
151  bytesRead = file.read(buffer.data() + offset, BUFFER_SIZE - offset - 1);
152  if (bytesRead < 0)
153  break;
154  const int dataSize = bytesRead + offset;
155  if ((bytesRead == 0) && (dataSize == 0))
156  break;
157 
158  for (start = 0; start < dataSize; ++start)
159  {
160  endOfLine = -1;
161  // The file might have ended without the last line having a newline
162  if (!((bytesRead == 0) && (dataSize > 0)))
163  {
164  for (int i = start; i < dataSize; ++i)
165  {
166  if (buffer[i] == '\n')
167  {
168  endOfLine = i;
169  // We need to NULL the newline in case the line has only an IP range.
170  // In that case the parser won't work for the end IP, because it ends
171  // with the newline and not with a number.
172  buffer[i] = '\0';
173  break;
174  }
175  }
176  }
177  else
178  {
179  endOfLine = dataSize;
180  buffer[dataSize] = '\0';
181  }
182 
183  if (endOfLine == -1)
184  {
185  // read the next chunk from file
186  // but first move(copy) the leftover data to the front of the buffer
187  offset = dataSize - start;
188  memmove(buffer.data(), buffer.data() + start, offset);
189  break;
190  }
191 
192  ++nbLine;
193 
194  if ((buffer[start] == '#')
195  || ((buffer[start] == '/') && ((start + 1 < dataSize) && (buffer[start + 1] == '/'))))
196  {
197  start = endOfLine;
198  continue;
199  }
200 
201  // Each line should follow this format:
202  // 001.009.096.105 - 001.009.096.105 , 000 , Some organization
203  // The 3rd entry is access level and if above 127 the IP range isn't blocked.
204  const int firstComma = findAndNullDelimiter(buffer.data(), ',', start, endOfLine);
205  if (firstComma != -1)
206  findAndNullDelimiter(buffer.data(), ',', firstComma + 1, endOfLine);
207 
208  // Check if there is an access value (apparently not mandatory)
209  if (firstComma != -1)
210  {
211  // There is possibly one
212  const long int nbAccess = strtol(buffer.data() + firstComma + 1, nullptr, 10);
213  // Ignoring this rule because access value is too high
214  if (nbAccess > 127L)
215  {
216  start = endOfLine;
217  continue;
218  }
219  }
220 
221  // IP Range should be split by a dash
222  const int endOfIPRange = ((firstComma == -1) ? (endOfLine - 1) : (firstComma - 1));
223  const int delimIP = findAndNullDelimiter(buffer.data(), '-', start, endOfIPRange);
224  if (delimIP == -1)
225  {
226  ++parseErrorCount;
227  addLog(tr("IP filter line %1 is malformed.").arg(nbLine));
228  start = endOfLine;
229  continue;
230  }
231 
232  lt::address startAddr;
233  int newStart = trim(buffer.data(), start, delimIP - 1);
234  if (!parseIPAddress(buffer.data() + newStart, startAddr))
235  {
236  ++parseErrorCount;
237  addLog(tr("IP filter line %1 is malformed. Start IP of the range is malformed.").arg(nbLine));
238  start = endOfLine;
239  continue;
240  }
241 
242  lt::address endAddr;
243  newStart = trim(buffer.data(), delimIP + 1, endOfIPRange);
244  if (!parseIPAddress(buffer.data() + newStart, endAddr))
245  {
246  ++parseErrorCount;
247  addLog(tr("IP filter line %1 is malformed. End IP of the range is malformed.").arg(nbLine));
248  start = endOfLine;
249  continue;
250  }
251 
252  if ((startAddr.is_v4() != endAddr.is_v4())
253  || (startAddr.is_v6() != endAddr.is_v6()))
254  {
255  ++parseErrorCount;
256  addLog(tr("IP filter line %1 is malformed. One IP is IPv4 and the other is IPv6!").arg(nbLine));
257  start = endOfLine;
258  continue;
259  }
260 
261  start = endOfLine;
262 
263  // Now Add to the filter
264  try
265  {
266  m_filter.add_rule(startAddr, endAddr, lt::ip_filter::blocked);
267  ++ruleCount;
268  }
269  catch (const std::exception &e)
270  {
271  ++parseErrorCount;
272  addLog(tr("IP filter exception thrown for line %1. Exception is: %2")
273  .arg(nbLine).arg(QString::fromLocal8Bit(e.what())));
274  }
275  }
276 
277  if (start >= dataSize)
278  offset = 0;
279  }
280 
281  if (parseErrorCount > MAX_LOGGED_ERRORS)
282  LogMsg(tr("%1 extra IP filter parsing errors occurred.", "513 extra IP filter parsing errors occurred.")
283  .arg(parseErrorCount - MAX_LOGGED_ERRORS), Log::CRITICAL);
284  return ruleCount;
285 }
int findAndNullDelimiter(char *const data, char delimiter, int start, int end, bool reverse=false)
int trim(char *const data, int start, int end)
void LogMsg(const QString &message, const Log::MsgType &type)
Definition: logger.cpp:125
@ CRITICAL
Definition: logger.h:48
bool parseIPAddress(const char *data, lt::address &address)
file(GLOB QBT_TS_FILES "${qBittorrent_SOURCE_DIR}/src/lang/*.ts") set_source_files_properties($
Definition: CMakeLists.txt:5

References anonymous_namespace{filterparserthread.cpp}::BUFFER_SIZE, Log::CRITICAL, nova3.nova2dl::e, file(), findAndNullDelimiter(), LogMsg(), m_filePath, m_filter, anonymous_namespace{filterparserthread.cpp}::MAX_LOGGED_ERRORS, anonymous_namespace{filterparserthread.cpp}::parseIPAddress(), and trim().

Referenced by run().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ parseP2BFilterFile()

int FilterParserThread::parseP2BFilterFile ( )
private

Definition at line 469 of file filterparserthread.cpp.

470 {
471  int ruleCount = 0;
472  QFile file(m_filePath);
473  if (!file.exists()) return ruleCount;
474 
475  if (!file.open(QIODevice::ReadOnly))
476  {
477  LogMsg(tr("I/O Error: Could not open IP filter file in read mode."), Log::CRITICAL);
478  return ruleCount;
479  }
480 
481  QDataStream stream(&file);
482  // Read header
483  char buf[7];
484  unsigned char version;
485  if (!stream.readRawData(buf, sizeof(buf))
486  || memcmp(buf, "\xFF\xFF\xFF\xFFP2B", 7)
487  || !stream.readRawData(reinterpret_cast<char*>(&version), sizeof(version)))
488  {
489  LogMsg(tr("Parsing Error: The filter file is not a valid PeerGuardian P2B file."), Log::CRITICAL);
490  return ruleCount;
491  }
492 
493  if ((version == 1) || (version == 2))
494  {
495  qDebug ("p2b version 1 or 2");
496  unsigned int start, end;
497 
498  std::string name;
499  while (getlineInStream(stream, name, '\0') && !m_abort)
500  {
501  if (!stream.readRawData(reinterpret_cast<char*>(&start), sizeof(start))
502  || !stream.readRawData(reinterpret_cast<char*>(&end), sizeof(end)))
503  {
504  LogMsg(tr("Parsing Error: The filter file is not a valid PeerGuardian P2B file."), Log::CRITICAL);
505  return ruleCount;
506  }
507 
508  // Network byte order to Host byte order
509  // asio address_v4 constructor expects it
510  // that way
511  const lt::address_v4 first(ntohl(start));
512  const lt::address_v4 last(ntohl(end));
513  // Apply to bittorrent session
514  try
515  {
516  m_filter.add_rule(first, last, lt::ip_filter::blocked);
517  ++ruleCount;
518  }
519  catch (const std::exception &) {}
520  }
521  }
522  else if (version == 3)
523  {
524  qDebug ("p2b version 3");
525  unsigned int namecount;
526  if (!stream.readRawData(reinterpret_cast<char*>(&namecount), sizeof(namecount)))
527  {
528  LogMsg(tr("Parsing Error: The filter file is not a valid PeerGuardian P2B file."), Log::CRITICAL);
529  return ruleCount;
530  }
531 
532  namecount = ntohl(namecount);
533  // Reading names although, we don't really care about them
534  for (unsigned int i = 0; i < namecount; ++i)
535  {
536  std::string name;
537  if (!getlineInStream(stream, name, '\0'))
538  {
539  LogMsg(tr("Parsing Error: The filter file is not a valid PeerGuardian P2B file."), Log::CRITICAL);
540  return ruleCount;
541  }
542 
543  if (m_abort) return ruleCount;
544  }
545 
546  // Reading the ranges
547  unsigned int rangecount;
548  if (!stream.readRawData(reinterpret_cast<char*>(&rangecount), sizeof(rangecount)))
549  {
550  LogMsg(tr("Parsing Error: The filter file is not a valid PeerGuardian P2B file."), Log::CRITICAL);
551  return ruleCount;
552  }
553 
554  rangecount = ntohl(rangecount);
555  unsigned int name, start, end;
556  for (unsigned int i = 0; i < rangecount; ++i)
557  {
558  if (!stream.readRawData(reinterpret_cast<char*>(&name), sizeof(name))
559  || !stream.readRawData(reinterpret_cast<char*>(&start), sizeof(start))
560  || !stream.readRawData(reinterpret_cast<char*>(&end), sizeof(end)))
561  {
562  LogMsg(tr("Parsing Error: The filter file is not a valid PeerGuardian P2B file."), Log::CRITICAL);
563  return ruleCount;
564  }
565 
566  // Network byte order to Host byte order
567  // asio address_v4 constructor expects it
568  // that way
569  const lt::address_v4 first(ntohl(start));
570  const lt::address_v4 last(ntohl(end));
571  // Apply to bittorrent session
572  try
573  {
574  m_filter.add_rule(first, last, lt::ip_filter::blocked);
575  ++ruleCount;
576  }
577  catch (const std::exception &) {}
578 
579  if (m_abort) return ruleCount;
580  }
581  }
582  else
583  {
584  LogMsg(tr("Parsing Error: The filter file is not a valid PeerGuardian P2B file."), Log::CRITICAL);
585  }
586 
587  return ruleCount;
588 }
int getlineInStream(QDataStream &stream, std::string &name, char delim)

References Log::CRITICAL, file(), getlineInStream(), LogMsg(), m_abort, m_filePath, and m_filter.

Referenced by run().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ parseP2PFilterFile()

int FilterParserThread::parseP2PFilterFile ( )
private

Definition at line 288 of file filterparserthread.cpp.

289 {
290  int ruleCount = 0;
291  QFile file(m_filePath);
292  if (!file.exists()) return ruleCount;
293 
294  if (!file.open(QIODevice::ReadOnly | QIODevice::Text))
295  {
296  LogMsg(tr("I/O Error: Could not open IP filter file in read mode."), Log::CRITICAL);
297  return ruleCount;
298  }
299 
300  std::vector<char> buffer(BUFFER_SIZE, 0); // seems a bit faster than QVector
301  qint64 bytesRead = 0;
302  int offset = 0;
303  int start = 0;
304  int endOfLine = -1;
305  int nbLine = 0;
306  int parseErrorCount = 0;
307  const auto addLog = [&parseErrorCount](const QString &msg)
308  {
309  if (parseErrorCount <= MAX_LOGGED_ERRORS)
310  LogMsg(msg, Log::CRITICAL);
311  };
312 
313  while (true)
314  {
315  bytesRead = file.read(buffer.data() + offset, BUFFER_SIZE - offset - 1);
316  if (bytesRead < 0)
317  break;
318  const int dataSize = bytesRead + offset;
319  if ((bytesRead == 0) && (dataSize == 0))
320  break;
321 
322  for (start = 0; start < dataSize; ++start)
323  {
324  endOfLine = -1;
325  // The file might have ended without the last line having a newline
326  if (!((bytesRead == 0) && (dataSize > 0)))
327  {
328  for (int i = start; i < dataSize; ++i)
329  {
330  if (buffer[i] == '\n')
331  {
332  endOfLine = i;
333  // We need to NULL the newline in case the line has only an IP range.
334  // In that case the parser won't work for the end IP, because it ends
335  // with the newline and not with a number.
336  buffer[i] = '\0';
337  break;
338  }
339  }
340  }
341  else
342  {
343  endOfLine = dataSize;
344  buffer[dataSize] = '\0';
345  }
346 
347  if (endOfLine == -1)
348  {
349  // read the next chunk from file
350  // but first move(copy) the leftover data to the front of the buffer
351  offset = dataSize - start;
352  memmove(buffer.data(), buffer.data() + start, offset);
353  break;
354  }
355 
356  ++nbLine;
357 
358  if ((buffer[start] == '#')
359  || ((buffer[start] == '/') && ((start + 1 < dataSize) && (buffer[start + 1] == '/'))))
360  {
361  start = endOfLine;
362  continue;
363  }
364 
365  // Each line should follow this format:
366  // Some organization:1.0.0.0-1.255.255.255
367  // The "Some organization" part might contain a ':' char itself so we find the last occurrence
368  const int partsDelimiter = findAndNullDelimiter(buffer.data(), ':', start, endOfLine, true);
369  if (partsDelimiter == -1)
370  {
371  ++parseErrorCount;
372  addLog(tr("IP filter line %1 is malformed.").arg(nbLine));
373  start = endOfLine;
374  continue;
375  }
376 
377  // IP Range should be split by a dash
378  const int delimIP = findAndNullDelimiter(buffer.data(), '-', partsDelimiter + 1, endOfLine);
379  if (delimIP == -1)
380  {
381  ++parseErrorCount;
382  addLog(tr("IP filter line %1 is malformed.").arg(nbLine));
383  start = endOfLine;
384  continue;
385  }
386 
387  lt::address startAddr;
388  int newStart = trim(buffer.data(), partsDelimiter + 1, delimIP - 1);
389  if (!parseIPAddress(buffer.data() + newStart, startAddr))
390  {
391  ++parseErrorCount;
392  addLog(tr("IP filter line %1 is malformed. Start IP of the range is malformed.").arg(nbLine));
393  start = endOfLine;
394  continue;
395  }
396 
397  lt::address endAddr;
398  newStart = trim(buffer.data(), delimIP + 1, endOfLine);
399  if (!parseIPAddress(buffer.data() + newStart, endAddr))
400  {
401  ++parseErrorCount;
402  addLog(tr("IP filter line %1 is malformed. End IP of the range is malformed.").arg(nbLine));
403  start = endOfLine;
404  continue;
405  }
406 
407  if ((startAddr.is_v4() != endAddr.is_v4())
408  || (startAddr.is_v6() != endAddr.is_v6()))
409  {
410  ++parseErrorCount;
411  addLog(tr("IP filter line %1 is malformed. One IP is IPv4 and the other is IPv6!").arg(nbLine));
412  start = endOfLine;
413  continue;
414  }
415 
416  start = endOfLine;
417 
418  try
419  {
420  m_filter.add_rule(startAddr, endAddr, lt::ip_filter::blocked);
421  ++ruleCount;
422  }
423  catch (const std::exception &e)
424  {
425  ++parseErrorCount;
426  addLog(tr("IP filter exception thrown for line %1. Exception is: %2")
427  .arg(nbLine).arg(QString::fromLocal8Bit(e.what())));
428  }
429  }
430 
431  if (start >= dataSize)
432  offset = 0;
433  }
434 
435  if (parseErrorCount > MAX_LOGGED_ERRORS)
436  LogMsg(tr("%1 extra IP filter parsing errors occurred.", "513 extra IP filter parsing errors occurred.")
437  .arg(parseErrorCount - MAX_LOGGED_ERRORS), Log::CRITICAL);
438  return ruleCount;
439 }

References anonymous_namespace{filterparserthread.cpp}::BUFFER_SIZE, Log::CRITICAL, nova3.nova2dl::e, file(), findAndNullDelimiter(), LogMsg(), m_filePath, m_filter, anonymous_namespace{filterparserthread.cpp}::MAX_LOGGED_ERRORS, anonymous_namespace{filterparserthread.cpp}::parseIPAddress(), and trim().

Referenced by run().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ processFilterFile()

void FilterParserThread::processFilterFile ( const QString &  filePath)

Definition at line 595 of file filterparserthread.cpp.

596 {
597  if (isRunning())
598  {
599  // Already parsing a filter, m_abort first
600  m_abort = true;
601  wait();
602  }
603 
604  m_abort = false;
605  m_filePath = filePath;
606  m_filter = lt::ip_filter();
607  // Run it
608  start();
609 }

References m_abort, m_filePath, and m_filter.

◆ run()

void FilterParserThread::run ( )
overrideprotected

Definition at line 616 of file filterparserthread.cpp.

617 {
618  qDebug("Processing filter file");
619  int ruleCount = 0;
620  if (m_filePath.endsWith(".p2p", Qt::CaseInsensitive))
621  {
622  // PeerGuardian p2p file
623  ruleCount = parseP2PFilterFile();
624  }
625  else if (m_filePath.endsWith(".p2b", Qt::CaseInsensitive))
626  {
627  // PeerGuardian p2b file
628  ruleCount = parseP2BFilterFile();
629  }
630  else if (m_filePath.endsWith(".dat", Qt::CaseInsensitive))
631  {
632  // eMule DAT format
633  ruleCount = parseDATFilterFile();
634  }
635 
636  if (m_abort) return;
637 
638  try
639  {
640  emit IPFilterParsed(ruleCount);
641  }
642  catch (const std::exception &)
643  {
644  emit IPFilterError();
645  }
646 
647  qDebug("IP Filter thread: finished parsing, filter applied");
648 }
void IPFilterParsed(int ruleCount)

References IPFilterError(), IPFilterParsed(), m_abort, m_filePath, parseDATFilterFile(), parseP2BFilterFile(), and parseP2PFilterFile().

Here is the call graph for this function:

◆ trim()

int FilterParserThread::trim ( char *const  data,
int  start,
int  end 
)
private

Definition at line 678 of file filterparserthread.cpp.

679 {
680  if (start >= end) return start;
681  int newStart = start;
682 
683  for (int i = start; i <= end; ++i)
684  {
685  if (isspace(data[i]) != 0)
686  {
687  data[i] = '\0';
688  }
689  else
690  {
691  newStart = i;
692  break;
693  }
694  }
695 
696  for (int i = end; i >= start; --i)
697  {
698  if (isspace(data[i]) != 0)
699  data[i] = '\0';
700  else
701  break;
702  }
703 
704  return newStart;
705 }

Referenced by parseDATFilterFile(), and parseP2PFilterFile().

Here is the caller graph for this function:

Member Data Documentation

◆ m_abort

bool FilterParserThread::m_abort
private

◆ m_filePath

QString FilterParserThread::m_filePath
private

◆ m_filter

lt::ip_filter FilterParserThread::m_filter
private

The documentation for this class was generated from the following files: