ip2cupdater.cpp
1 //------------------------------------------------------------------------------
2 // ip2cupdater.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) 2009 Braden "Blzut3" Obrzut <admin@maniacsvault.net>
22 //------------------------------------------------------------------------------
23 #include "ip2cupdater.h"
24 
25 #include <QCryptographicHash>
26 #include <QFile>
27 #include <QNetworkAccessManager>
28 #include <QTemporaryFile>
29 #include <zlib.h>
30 
31 #include "log.h"
32 #include "version.h"
33 
34 IP2CUpdater::IP2CUpdater(QObject *parent)
35  : QObject(parent)
36 {
37  pCurrentNetworkReply = nullptr;
38  pNetworkAccessManager = new QNetworkAccessManager();
39 }
40 
41 IP2CUpdater::~IP2CUpdater()
42 {
43  abort();
44 
45  if (pNetworkAccessManager != nullptr)
46  pNetworkAccessManager->deleteLater();
47 }
48 
49 void IP2CUpdater::abort()
50 {
51  if (pCurrentNetworkReply != nullptr)
52  {
53  pCurrentNetworkReply->disconnect();
54  pCurrentNetworkReply->abort();
55  pCurrentNetworkReply->deleteLater();
56  }
57  pCurrentNetworkReply = nullptr;
58 }
59 
60 void IP2CUpdater::checksumDownloadFinished()
61 {
62  if (handleRedirect(*pCurrentNetworkReply, SLOT(checksumDownloadFinished())))
63  return;
64 
65  if (pCurrentNetworkReply->error() != QNetworkReply::NoError)
66  {
67  gLog << tr("IP2C checksum check network error: %1").arg(pCurrentNetworkReply->errorString());
68  abort();
69  emit updateNeeded(UpdateCheckError);
70  return;
71  }
72 
73  QByteArray remoteMd5 = pCurrentNetworkReply->readAll();
74  remoteMd5 = remoteMd5.trimmed();
75 
76  pCurrentNetworkReply->deleteLater();
77  pCurrentNetworkReply = nullptr;
78 
79  QByteArray localMd5;
80  QFile file(this->lastAsyncCallPath);
81  if (file.open(QIODevice::ReadOnly))
82  {
83  localMd5 = QCryptographicHash::hash(file.readAll(), QCryptographicHash::Md5);
84  localMd5 = localMd5.toHex().toLower();
85  }
86 
87  gLog << tr("Comparing IP2C hashes: local = %1, remote = %2").arg(
88  QString(localMd5)).arg(QString(remoteMd5));
89  bool needed = localMd5 != remoteMd5;
90  if (needed)
91  gLog << tr("IP2C update needed.");
92  emit updateNeeded(needed ? UpdateNeeded : UpToDate);
93 }
94 
95 const QUrl IP2CUpdater::dbChecksumUrl()
96 {
97  return QUrl("https://doomseeker.drdteam.org/ip2c/md5");
98 }
99 
100 const QUrl IP2CUpdater::dbDownloadUrl()
101 {
102  return QUrl("https://doomseeker.drdteam.org/ip2c/geolite2.gz");
103 }
104 
105 void IP2CUpdater::downloadDatabase(const QString &savePath)
106 {
107  this->lastAsyncCallPath = savePath;
108  get(dbDownloadUrl(), SLOT(downloadFinished()));
109 }
110 
111 void IP2CUpdater::downloadFinished()
112 {
113  if (handleRedirect(*pCurrentNetworkReply, SLOT(downloadFinished())))
114  return;
115 
116  QByteArray data = pCurrentNetworkReply->readAll();
117 
118  pCurrentNetworkReply->deleteLater();
119  pCurrentNetworkReply = nullptr;
120 
121  // First we need to write it to a temporary file
122  QTemporaryFile tmpFile;
123  if (tmpFile.open())
124  {
125  tmpFile.write(data);
126 
127  QString tmpFilePath = tmpFile.fileName();
128 
129  QByteArray uncompressedData;
130  gzFile gz = gzopen(tmpFilePath.toUtf8().constData(), "rb");
131  if (gz != nullptr)
132  {
133  static const int CHUNK_SIZE = 128 * 1024;
134  char chunk[CHUNK_SIZE];
135  int bytesRead = 0;
136  while ((bytesRead = gzread(gz, chunk, CHUNK_SIZE)) != 0)
137  {
138  uncompressedData.append(QByteArray(chunk, bytesRead));
139  }
140  gzclose(gz);
141 
142  retrievedData = uncompressedData;
143  }
144  }
145 
146  emit databaseDownloadFinished(retrievedData);
147 }
148 
149 bool IP2CUpdater::handleRedirect(QNetworkReply &reply, const char *finishedSlot)
150 {
151  QUrl possibleRedirectUrl = reply.attribute(QNetworkRequest::RedirectionTargetAttribute).toUrl();
152  QUrl url = reply.request().url();
153  if (!possibleRedirectUrl.isEmpty() && possibleRedirectUrl != url)
154  {
155  // Redirect.
156  if (possibleRedirectUrl.isRelative())
157  possibleRedirectUrl = url.resolved(possibleRedirectUrl);
158  get(possibleRedirectUrl, finishedSlot);
159  return true;
160  }
161  return false;
162 }
163 
164 void IP2CUpdater::get(const QUrl &url, const char *finishedSlot)
165 {
166  abort();
167  retrievedData.clear();
168 
169  QNetworkRequest request;
170  request.setUrl(url);
171  request.setRawHeader("User-Agent", Version::userAgent().toUtf8());
172 
173  pCurrentNetworkReply = pNetworkAccessManager->get(request);
174  this->connect(pCurrentNetworkReply, SIGNAL(downloadProgress(qint64,qint64)),
175  SIGNAL(downloadProgress(qint64,qint64)));
176  this->connect(pCurrentNetworkReply, SIGNAL(finished()), finishedSlot);
177 }
178 
179 bool IP2CUpdater::getRollbackData(const QString &databasePath)
180 {
181  rollbackData.clear();
182  QFile file(databasePath);
183  if (file.open(QIODevice::ReadOnly))
184  {
185  rollbackData = file.readAll();
186  file.close();
187  return true;
188  }
189  return false;
190 }
191 
192 bool IP2CUpdater::isWorking() const
193 {
194  return pCurrentNetworkReply != nullptr;
195 }
196 
197 void IP2CUpdater::needsUpdate(const QString &filePath)
198 {
199  QFile file(filePath);
200  if (!file.exists())
201  {
202  emit updateNeeded(UpdateNeeded);
203  return;
204  }
205 
206  this->lastAsyncCallPath = filePath;
207  gLog << tr("Checking if IP2C database at '%1' needs updating.").arg(filePath);
208  get(dbChecksumUrl(), SLOT(checksumDownloadFinished()));
209 }
210 
211 bool IP2CUpdater::rollback(const QString &savePath)
212 {
213  bool bSuccess = save(rollbackData, savePath);
214  rollbackData.clear();
215 
216  return bSuccess;
217 }
218 
219 bool IP2CUpdater::save(const QByteArray &saveWhat, const QString &savePath)
220 {
221  if (saveWhat.isEmpty())
222  return false;
223 
224  QFile file(savePath);
225  if (file.open(QIODevice::WriteOnly))
226  {
227  file.write(saveWhat);
228  file.close();
229  return true;
230  }
231  return false;
232 }
233 
234 bool IP2CUpdater::saveDownloadedData(const QString &savePath)
235 {
236  return save(retrievedData, savePath);
237 }