]>
git.ipfire.org Git - location/libloc.git/blob - src/python/downloader.py
2 ###############################################################################
4 # libloc - A library to determine the location of someone on the Internet #
6 # Copyright (C) 2020 IPFire Development Team <info@ipfire.org> #
8 # This library is free software; you can redistribute it and/or #
9 # modify it under the terms of the GNU Lesser General Public #
10 # License as published by the Free Software Foundation; either #
11 # version 2.1 of the License, or (at your option) any later version. #
13 # This library is distributed in the hope that it will be useful, #
14 # but WITHOUT ANY WARRANTY; without even the implied warranty of #
15 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU #
16 # Lesser General Public License for more details. #
18 ###############################################################################
31 from _location
import Database
, DATABASE_VERSION_LATEST
33 DATABASE_FILENAME
= "location.db.xz"
35 "https://location.ipfire.org/databases/",
39 log
= logging
.getLogger("location.downloader")
42 class Downloader(object):
43 def __init__(self
, version
=DATABASE_VERSION_LATEST
, mirrors
=None):
44 self
.version
= version
46 # Set mirrors or use defaults
47 self
.mirrors
= list(mirrors
or MIRRORS
)
50 random
.shuffle(self
.mirrors
)
52 # Get proxies from environment
53 self
.proxies
= self
._get
_proxies
()
55 def _get_proxies(self
):
58 for protocol
in ("https", "http"):
59 proxy
= os
.environ
.get("%s_proxy" % protocol
, None)
62 proxies
[protocol
] = proxy
66 def _make_request(self
, url
, baseurl
=None, headers
={}):
68 url
= urllib
.parse
.urljoin(baseurl
, url
)
70 req
= urllib
.request
.Request(url
, method
="GET")
74 "User-Agent" : "location-downloader/@VERSION@",
78 for header
in headers
:
79 req
.add_header(header
, headers
[header
])
82 for protocol
in self
.proxies
:
83 req
.set_proxy(self
.proxies
[protocol
], protocol
)
87 def _send_request(self
, req
, **kwargs
):
89 log
.debug("HTTP %s Request to %s" % (req
.method
, req
.host
))
90 log
.debug(" URL: %s" % req
.full_url
)
91 log
.debug(" Headers:")
92 for k
, v
in req
.header_items():
93 log
.debug(" %s: %s" % (k
, v
))
96 res
= urllib
.request
.urlopen(req
, **kwargs
)
98 except urllib
.error
.HTTPError
as e
:
99 # Log response headers
100 log
.debug("HTTP Response: %s" % e
.code
)
101 log
.debug(" Headers:")
102 for header
in e
.headers
:
103 log
.debug(" %s: %s" % (header
, e
.headers
[header
]))
105 # Raise all other errors
108 # Log response headers
109 log
.debug("HTTP Response: %s" % res
.code
)
110 log
.debug(" Headers:")
111 for k
, v
in res
.getheaders():
112 log
.debug(" %s: %s" % (k
, v
))
116 def download(self
, public_key
, timestamp
=None, tmpdir
=None, **kwargs
):
117 url
= "%s/%s" % (self
.version
, DATABASE_FILENAME
)
121 headers
["If-Modified-Since"] = timestamp
.strftime(
122 "%a, %d %b %Y %H:%M:%S GMT",
125 t
= tempfile
.NamedTemporaryFile(dir=tmpdir
, delete
=False)
128 for mirror
in self
.mirrors
:
129 # Prepare HTTP request
130 req
= self
._make
_request
(url
, baseurl
=mirror
, headers
=headers
)
133 with self
._send
_request
(req
) as res
:
134 decompressor
= lzma
.LZMADecompressor()
143 buf
= decompressor
.decompress(buf
)
147 # Write all data to disk
150 # Catch decompression errors
151 except lzma
.LZMAError
as e
:
152 log
.warning("Could not decompress downloaded file: %s" % e
)
155 except urllib
.error
.HTTPError
as e
:
156 # The file on the server was too old
158 log
.warning("%s is serving an outdated database. Trying next mirror..." % mirror
)
160 # Log any other HTTP errors
162 log
.warning("%s reported: %s" % (mirror
, e
))
164 # Throw away any downloaded content and try again
168 # Check if the downloaded database is recent
169 if not self
._check
_database
(t
, public_key
, timestamp
):
170 log
.warning("Downloaded database is outdated. Trying next mirror...")
172 # Throw away the data and try again
176 # Make the file readable for everyone
177 os
.chmod(t
.name
, stat
.S_IRUSR|stat
.S_IRGRP|stat
.S_IROTH
)
179 # Return temporary file
182 raise FileNotFoundError(url
)
184 def _check_database(self
, f
, public_key
, timestamp
=None):
186 Checks the downloaded database if it can be opened,
187 verified and if it is recent enough
189 log
.debug("Opening downloaded database at %s" % f
.name
)
191 db
= Database(f
.name
)
193 # Database is not recent
194 if timestamp
and db
.created_at
< timestamp
.timestamp():
197 log
.info("Downloaded new database from %s" % (time
.strftime(
198 "%a, %d %b %Y %H:%M:%S GMT", time
.gmtime(db
.created_at
),
201 # Verify the database
202 with
open(public_key
, "r") as f
:
204 log
.error("Could not verify database")