ip2cparser.cpp
1 //------------------------------------------------------------------------------
2 // ip2cparser.cpp
3 //------------------------------------------------------------------------------
4 //
5 // This program is free software; you can redistribute it and/or
6 // modify it under the terms of the GNU General Public License
7 // as published by the Free Software Foundation; either version 2
8 // of the License, or (at your option) any later version.
9 //
10 // This program 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
13 // GNU General Public License for more details.
14 //
15 // You should have received a copy of the GNU General Public License
16 // along with this program; 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) 2009 "Blzut3" <admin@maniacsvault.net>
22 //------------------------------------------------------------------------------
23 #include "ip2cparser.h"
24 
25 #include "global.h"
26 #include "log.h"
27 #include "scanner.h"
28 #include <QFile>
29 #include <QMap>
30 #include <QMutexLocker>
31 #include <QTime>
32 
33 IP2CParser::IP2CParser(IP2C* pTargetDatabase)
34 {
35  currentParsingThread = NULL;
36  this->pTargetDatabase = pTargetDatabase;
37  bIsParsing = false;
38 }
39 
40 bool IP2CParser::convertAndSaveDatabase(QByteArray& downloadedData, const QString& outFilePath)
41 {
42  if (downloadedData.isEmpty())
43  return false;
44 
45  Countries countries;
46  readTextDatabase(downloadedData, countries);
47 
48  QByteArray binaryData;
49  convertCountriesIntoBinaryData(countries, binaryData);
50 
51  QFile out(outFilePath);
52  if(out.open(QIODevice::WriteOnly) && out.isWritable())
53  {
54  out.write(binaryData);
55  }
56  else
57  {
58  return false;
59  }
60 
61  return true;
62 }
63 
64 void IP2CParser::convertCountriesIntoBinaryData(const Countries& countries, QByteArray& output)
65 {
66  output.clear();
67  unsigned fileId = MAKEID('I', 'P', '2', 'C');
68  unsigned short version = 2;
69  output += QByteArray((const char*)&fileId, 4);
70  output += QByteArray((const char*)&version, sizeof(unsigned short));
71 
72  CountriesConstIt it;
73  for (it = countries.constBegin(); it != countries.constEnd(); ++it)
74  {
75  // Read the first IP2CData entry for country info
76  if (!it.value().empty())
77  {
78  const IP2C::IP2CData& val = it.value()[0];
79  unsigned ipBlocksNum = it.value().count();
80 
81  output += val.countryFullName;
82  output += QByteArray(1, 0); // array with one null character
83  output += val.country;
84  output += QByteArray(1, 0); // array with one null character
85  output += QByteArray((const char*)&ipBlocksNum, sizeof(ipBlocksNum));
86  }
87 
88  foreach(IP2C::IP2CData val, it.value())
89  {
90  const char ipStart[4] = { WRITEINT32_DIRECT(char,val.ipStart) };
91  const char ipEnd[4] = { WRITEINT32_DIRECT(char,val.ipEnd) };
92 
93  output += QByteArray(ipStart, 4);
94  output += QByteArray(ipEnd, 4);
95  }
96  }
97 }
98 
99 bool IP2CParser::doReadDatabase(const QString& filePath)
100 {
101  QMutexLocker mutexLocker(&thisLock);
102 
103  // This will set proper state whenever and wherever this method finishes.
104  ConstructorDestructorParserStateSetter stateSetter(this);
105 
106  QFile dataBase(filePath);
107  gLog << tr("Parsing IP2C database: %1").arg(dataBase.fileName());
108 
109  if (!dataBase.exists()
110  || !dataBase.open(QIODevice::ReadOnly)
111  || !dataBase.isReadable())
112  {
113  gLog << tr("Unable to open IP2C file.");
114  return false;
115  }
116 
117  // We need to check whether this is a text file or Doomseeker's IP2C
118  // compacted database. To determine this we see if first four bytes are
119  // equal to IP2C. If not, we perform the conversion.
120 
121  QString signature = dataBase.read(4);
122  dataBase.seek(0);
123  if (signature.compare("IP2C") != 0)
124  {
125  gLog << tr("IP2C database is not in compacted format. Performing conversion!");
126  QByteArray contents = dataBase.readAll();
127 
128  QTime time;
129  time.start();
130  if (!convertAndSaveDatabase(contents, filePath))
131  {
132  gLog << tr("IP2C database conversion failed");
133  return false;
134  }
135  else
136  {
137  gLog << tr("IP2C database converted in %1 ms").arg(time.elapsed());
138  gLog << tr("Parsing now compacted IP2C database");
139  }
140  }
141  dataBase.close();
142 
143  dataBase.setFileName(filePath);
144  if (!dataBase.open(QIODevice::ReadOnly))
145  {
146  return false;
147  }
148 
149  QTime time;
150  time.start();
151 
152  QByteArray dataArray = dataBase.readAll();
153 
154  // Read version.
155  int pos = 4;
156  if (pos >= dataArray.size())
157  {
158  return false;
159  }
160 
161  const char* data = dataArray.constData();
162  unsigned short version = READINT16(&data[pos]);
163 
164  bool wasReadSuccessful = false;
165  switch (version)
166  {
167  case 1:
168  wasReadSuccessful = readDatabaseVersion1(dataArray);
169  break;
170 
171  case 2:
172  wasReadSuccessful = readDatabaseVersion2(dataArray);
173  break;
174 
175  default:
176  wasReadSuccessful = false;
177  break;
178  }
179 
180  if (!wasReadSuccessful)
181  {
182  return false;
183  }
184 
185  gLog << tr("IP2C database read in %1 ms. Entries read: %2").arg(time.elapsed()).arg(pTargetDatabase->numKnownEntries());
186  return true;
187 }
188 
189 void IP2CParser::parsingThreadFinished()
190 {
191  bool bSuccessState = currentParsingThread->bSuccessState;
192  gLog << tr("IP2C parsing thread has finished.");
193 
194  delete currentParsingThread;
195  currentParsingThread = NULL;
196 
197  emit parsingFinished(bSuccessState);
198 }
199 
200 bool IP2CParser::readDatabase(const QString& filePath)
201 {
202  bool bSuccess = doReadDatabase(filePath);
203  emit parsingFinished(bSuccess);
204 
205  return bSuccess;
206 }
207 
208 void IP2CParser::readDatabaseThreaded(const QString& filePath)
209 {
210  if (currentParsingThread != NULL)
211  {
212  return;
213  }
214 
215  ParsingThread* pParsingThread = new ParsingThread(this, filePath);
216  connect(pParsingThread, SIGNAL( finished() ), this, SLOT( parsingThreadFinished() ) );
217 
218  currentParsingThread = pParsingThread;
219 
220  pParsingThread->start();
221 }
222 
223 bool IP2CParser::readDatabaseVersion1(const QByteArray& dataArray)
224 {
225  int pos = 6; // skip file tag and version number
226  const char* data = dataArray.constData();
227 
228  while (pos < dataArray.size())
229  {
230  IP2C::IP2CData entry;
231 
232  // Perform error checks at each point. We don't want the app to crash
233  // due to corrupted database.
234  if (pos + 4 > dataArray.size()) return false;
235  entry.ipStart = READINT32(&data[pos]);
236  pos += 4;
237 
238  if (pos + 4 > dataArray.size()) return false;
239  entry.ipEnd = READINT32(&data[pos]);
240  pos += 4;
241 
242  entry.country = &data[pos];
243  pos += entry.country.size() + 1;
244 
246  }
247 
248  return true;
249 }
250 
251 bool IP2CParser::readDatabaseVersion2(const QByteArray& dataArray)
252 {
253  int pos = 6; // skip file tag and version number
254  const char* data = dataArray.constData();
255 
256  // We need to store the addresses in such hash table to make sure they
257  // are ordered in proper, ascending order. Otherwise the whole library
258  // will not work!
259  QMap<unsigned, IP2C::IP2CData> hashTable;
260 
261  while (pos < dataArray.size())
262  {
263  // Base entry for each IP read from the file
264  IP2C::IP2CData baseEntry;
265 
266  baseEntry.countryFullName = &data[pos];
267  pos += baseEntry.countryFullName.size() + 1;
268 
269  baseEntry.country = &data[pos];
270  pos += baseEntry.country.size() + 1;
271 
272  if (pos + 4 > dataArray.size()) return false;
273  unsigned numOfIpBlocks = READINT32(&data[pos]);
274  pos += 4;
275 
276  for (unsigned x = 0; x < numOfIpBlocks; ++x)
277  {
278  // Create new entry from the base.
279  IP2C::IP2CData entry = baseEntry;
280 
281  // Perform error checks at each point. We don't want the app to crash
282  // due to corrupted database.
283  if (pos + 4 > dataArray.size()) return false;
284  entry.ipStart = READINT32(&data[pos]);
285  pos += 4;
286 
287  if (pos + 4 > dataArray.size()) return false;
288  entry.ipEnd = READINT32(&data[pos]);
289  pos += 4;
290 
291  hashTable[entry.ipStart] = entry;
292  }
293  }
294 
295  pTargetDatabase->setDatabase(hashTable.values());
296 
297  return true;
298 }
299 
300 void IP2CParser::readTextDatabase(QByteArray& textDatabase, Countries& countries)
301 {
302  // Skip over the header
303  int indexOfNewLine = -1;
304  while(textDatabase[indexOfNewLine + 1] == '#')
305  {
306  indexOfNewLine = textDatabase.indexOf('\n', indexOfNewLine + 1);
307  }
308 
309  // Trim the header
310  textDatabase = textDatabase.right(textDatabase.size() - indexOfNewLine);
311 
312  Scanner sc(textDatabase.constData(), textDatabase.count());
313  countries.clear();
314  while(sc.tokensLeft())
315  {
316  IP2C::IP2CData entry;
317  bool ok = true;
318 
319  if(!sc.checkToken(TK_StringConst)) break; // ipStart
320  entry.ipStart = sc->str().toUInt(&ok);
321  if(!ok || !sc.checkToken(',')) break;
322  if(!sc.checkToken(TK_StringConst)) break; // ipEnd
323  entry.ipEnd = sc->str().toUInt(&ok);
324  if(!ok || !sc.checkToken(',')) break;
325  if(!sc.checkToken(TK_StringConst)) break; // Register
326  if(!sc.checkToken(',')) break;
327  if(!sc.checkToken(TK_StringConst)) break; // date assigned
328  if(!sc.checkToken(',')) break;
329  if(!sc.checkToken(TK_StringConst)) break; // 2 char country
330  if(!sc.checkToken(',')) break;
331  if(!sc.checkToken(TK_StringConst)) break; // 3 char country
332  entry.country = sc->str();
333  if(!sc.checkToken(',')) break;
334  if(!sc.checkToken(TK_StringConst)) break; // country string
335  entry.countryFullName = sc->str();
336 
337 
338  if (countries.contains(entry.country))
339  {
340  countries[entry.country].append(entry);
341  }
342  else
343  {
344  QList<IP2C::IP2CData> list;
345  list.append(entry);
346  countries[entry.country] = list;
347  }
348  }
349 }
350 
352 
353 IP2CParser::ConstructorDestructorParserStateSetter::ConstructorDestructorParserStateSetter(IP2CParser* pParser)
354 {
355  this->pParser = pParser;
356  pParser->bIsParsing = true;
357 }
358 
359 IP2CParser::ConstructorDestructorParserStateSetter::~ConstructorDestructorParserStateSetter()
360 {
361  pParser->bIsParsing = false;
362 }
363 
365 
366 void IP2CParser::ParsingThread::run()
367 {
368  bSuccessState = pParser->doReadDatabase(filePath);
369 }
void setDatabase(const QList< IP2CData > &sortedCountryData)
Sets database contents to the list specified.
Definition: ip2c.h:121
void appendEntryToDatabase(const IP2CData &entry)
Adds new country entry to the database.
Definition: ip2c.cpp:70
IP2C * pTargetDatabase
Database to which the IP2C parser will save the data it retrieves from IP2C file. ...
Definition: ip2cparser.h:162
void convertCountriesIntoBinaryData(const Countries &countries, QByteArray &output)
Definition: ip2cparser.cpp:64
IP to Country database handler.
Definition: ip2c.h:48
QHash< QString, QList< IP2C::IP2CData > > Countries
Definition: ip2cparser.h:148
void parsingFinished(bool bSuccess)
A signal emitted when parser finishes its job.
void readTextDatabase(QByteArray &textDatabase, Countries &countries)
Definition: ip2cparser.cpp:300
Scanner reads scripts by checking individual tokens.
Definition: scanner.h:75
bool convertAndSaveDatabase(QByteArray &downloadedData, const QString &outFilePath)
Definition: ip2cparser.cpp:40