ip2c.cpp
1 //------------------------------------------------------------------------------
2 // ip2c.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 "ip2c.h"
24 
25 #include "configuration/doomseekerconfig.h"
26 #include "serverapi/server.h"
27 
28 #include <QBuffer>
29 #include <QDirIterator>
30 #include <QFile>
31 #include <QFileInfo>
32 #include <QHash>
33 #include <QResource>
34 #include <QTime>
35 #include <QTimeLine>
36 
37 #include "log.h"
38 
39 QMutex IP2C::instanceMutex;
40 IP2C *IP2C::staticInstance = nullptr;
41 
42 static QHash<QString, QPixmap> cacheFlags()
43 {
44  QHash<QString, QPixmap> flags;
45  QDirIterator it(":flags/country/");
46  while (it.hasNext())
47  {
48  QString resName = it.next();
49  QFileInfo resPath(resName);
50  flags[resPath.fileName()] = QPixmap(resName);
51  }
52  return flags;
53 }
54 
55 IP2C *IP2C::instance()
56 {
57  if (staticInstance == nullptr)
58  {
59  QMutexLocker locker(&instanceMutex);
60  if (staticInstance == nullptr)
61  staticInstance = new IP2C();
62  }
63  return staticInstance;
64 }
65 
66 void IP2C::deinstantiate()
67 {
68  if (staticInstance != nullptr)
69  {
70  delete staticInstance;
71  staticInstance = nullptr;
72  }
73 }
74 
75 IP2C::IP2C()
76  : flagLan(":flags/lan-small"), flagLocalhost(":flags/localhost-small"),
77  flagUnknown(":flags/unknown-small"), countries(IP2CCountry::all()),
78  flags(cacheFlags())
79 {
80  for (auto it = countries.begin(); it != countries.end(); ++it)
81  {
82  const QString &code = it.key();
83  auto flagIt = flags.find(code);
84  if (flagIt != flags.end())
85  it->flag = &flagIt.value();
86  }
87 }
88 
89 IP2C::~IP2C()
90 {
91 }
92 
93 const QPixmap &IP2C::flag(const QString &countryCode)
94 {
95  auto it = flags.find(countryCode);
96  if (it != flags.end())
97  {
98  return *it;
99  }
100  else
101  {
102  logUnknownFlag(countryCode);
103  return flagUnknown;
104  }
105 }
106 
107 bool IP2C::hasData() const
108 {
109  return !database.empty();
110 }
111 
112 IP2C::Lookup IP2C::howLookup(const QString &countryCode)
113 {
114  QString code = countryCode.toUpper().trimmed();
115  if (code.isEmpty() || code == "XIP")
116  return LOOKUP_IP;
117  else if (code == "XUN")
118  return LOOKUP_DONT;
119  else
120  return LOOKUP_DIRECT;
121 }
122 
123 const IP2CRange &IP2C::lookupIP(unsigned int ipaddress)
124 {
125  if (database.empty())
126  return invalidRange;
127 
128  unsigned int upper = database.size() - 1;
129  unsigned int lower = 0;
130  unsigned int index = database.size() / 2;
131  unsigned int lastIndex = 0xFFFFFFFF;
132  while (index != lastIndex) // Infinite loop protection.
133  {
134  lastIndex = index;
135 
136  if (ipaddress < database[index].ipStart)
137  {
138  upper = index;
139  index -= (index - lower) >> 1; // If we're concerning ourselves with speed >>1 is the same as /2, but should be faster.
140  continue;
141  }
142  else if (ipaddress > database[index].ipEnd)
143  {
144  lower = index;
145  index += (upper - index) >> 1;
146  continue;
147  }
148  return database[index];
149  }
150 
151  return invalidRange;
152 }
153 
154 IP2CCountry IP2C::countryInfo(const QString &countryCode)
155 {
156  auto it = countries.find(countryCode);
157  if (it != countries.end())
158  {
159  if (it->flag == nullptr)
160  logUnknownFlag(countryCode);
161  return *it;
162  }
163  else
164  {
165  logUnknownCountry(countryCode);
166  return unknownCountry();
167  }
168 }
169 
170 IP2CCountry IP2C::countryInfoForIPv4(unsigned int ipaddress)
171 {
172  if (isLocalhostAddress(ipaddress))
173  return IP2CCountry(&flagLocalhost, tr("Localhost"));
174 
175  if (isLANAddress(ipaddress))
176  return IP2CCountry(&flagLan, tr("LAN"));
177 
178  if (!hasData())
179  return IP2CCountry();
180 
181  const IP2CRange &addressRange = lookupIP(ipaddress);
182 
183  if (!addressRange.isValid())
184  return IP2CCountry();
185  if (addressRange.country.isEmpty())
186  return unknownCountry();
187 
188  return countryInfo(addressRange.country);
189 }
190 
191 IP2CCountry IP2C::countryInfoForServer(const Server &server)
192 {
193  if (gConfig.doomseeker.bHonorServerCountries)
194  {
195  const IP2C::Lookup lookup = howLookup(server.country());
196  if (lookup == IP2C::LOOKUP_DONT)
197  {
198  return unknownCountry();
199  }
200  else if (lookup == IP2C::LOOKUP_DIRECT && !isDataAccessLocked())
201  {
202  return countryInfo(server.country());
203  }
204  }
205 
206  if (!isDataAccessLocked())
207  {
208  return countryInfoForAddress(server.address());
209  }
210 
211  return IP2CCountry();
212 }
213 
214 void IP2C::logUnknownCountry(const QString &countryCode)
215 {
216  if (!unknownCountries.contains(countryCode))
217  {
218  unknownCountries.insert(countryCode);
219  gLog << tr("Unknown country: %1").arg(countryCode);
220  }
221 }
222 
223 void IP2C::logUnknownFlag(const QString &countryCode)
224 {
225  if (!unknownFlags.contains(countryCode))
226  {
227  unknownFlags.insert(countryCode);
228  gLog << tr("No flag for country: %1").arg(countryCode);
229  }
230 }
231 
232 IP2CCountry IP2C::unknownCountry() const
233 {
234  return IP2CCountry(&flagUnknown, tr("Unknown"));
235 }