2017-04-15 21:04:05 -04:00
|
|
|
# -*- coding: utf-8 -*-
|
|
|
|
"""
|
|
|
|
OnionShare | https://onionshare.org/
|
|
|
|
|
|
|
|
Copyright (C) 2017 Micah Lee <micah@micahflee.com>
|
|
|
|
|
|
|
|
This program is free software: you can redistribute it and/or modify
|
|
|
|
it under the terms of the GNU General Public License as published by
|
|
|
|
the Free Software Foundation, either version 3 of the License, or
|
|
|
|
(at your option) any later version.
|
|
|
|
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
GNU General Public License for more details.
|
|
|
|
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
|
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
"""
|
2017-04-15 21:55:41 -04:00
|
|
|
from PyQt5 import QtCore
|
2017-04-19 12:06:01 -04:00
|
|
|
import datetime, time, socket, re, platform
|
2017-04-15 21:04:05 -04:00
|
|
|
|
2017-04-19 12:06:01 -04:00
|
|
|
from onionshare import socks
|
2017-04-15 21:04:05 -04:00
|
|
|
from onionshare.settings import Settings
|
|
|
|
from onionshare.onion import Onion
|
|
|
|
|
2017-05-16 14:05:48 -04:00
|
|
|
from . import strings, common
|
2017-04-15 22:07:02 -04:00
|
|
|
|
2017-05-14 22:54:12 -04:00
|
|
|
class UpdateCheckerCheckError(Exception):
|
2017-04-15 21:04:05 -04:00
|
|
|
"""
|
2017-05-14 22:54:12 -04:00
|
|
|
Error checking for updates because of some Tor connection issue, or because
|
|
|
|
the OnionShare website is down.
|
2017-04-15 21:04:05 -04:00
|
|
|
"""
|
|
|
|
pass
|
|
|
|
|
|
|
|
class UpdateCheckerInvalidLatestVersion(Exception):
|
|
|
|
"""
|
|
|
|
Successfully downloaded the latest version, but it doesn't appear to be a
|
|
|
|
valid version string.
|
|
|
|
"""
|
|
|
|
def __init__(self, latest_version):
|
|
|
|
self.latest_version = latest_version
|
|
|
|
|
2017-04-15 21:55:41 -04:00
|
|
|
class UpdateChecker(QtCore.QObject):
|
2017-04-15 21:04:05 -04:00
|
|
|
"""
|
|
|
|
Load http://elx57ue5uyfplgva.onion/latest-version.txt to see what the latest
|
|
|
|
version of OnionShare is. If the latest version is newer than the
|
|
|
|
installed version, alert the user.
|
|
|
|
|
|
|
|
Only check at most once per day, unless force is True.
|
|
|
|
"""
|
2017-04-15 21:55:41 -04:00
|
|
|
update_available = QtCore.pyqtSignal(str, str, str)
|
|
|
|
update_not_available = QtCore.pyqtSignal()
|
2018-01-23 00:51:13 -05:00
|
|
|
update_error = QtCore.pyqtSignal()
|
|
|
|
update_invalid_version = QtCore.pyqtSignal()
|
2017-04-15 21:55:41 -04:00
|
|
|
|
2017-06-01 03:35:27 -04:00
|
|
|
def __init__(self, onion, config=False):
|
2017-04-15 21:55:41 -04:00
|
|
|
super(UpdateChecker, self).__init__()
|
2017-05-16 20:29:02 -04:00
|
|
|
common.log('UpdateChecker', '__init__')
|
2017-05-14 22:54:12 -04:00
|
|
|
self.onion = onion
|
2017-06-01 03:35:27 -04:00
|
|
|
self.config = config
|
2017-04-15 21:55:41 -04:00
|
|
|
|
2017-06-01 03:35:27 -04:00
|
|
|
def check(self, force=False, config=False):
|
2017-05-16 20:29:02 -04:00
|
|
|
common.log('UpdateChecker', 'check', 'force={}'.format(force))
|
2017-04-15 21:55:41 -04:00
|
|
|
# Load the settings
|
2017-06-01 03:35:27 -04:00
|
|
|
settings = Settings(config)
|
2017-04-15 21:55:41 -04:00
|
|
|
settings.load()
|
|
|
|
|
2017-04-17 16:03:16 -04:00
|
|
|
# If force=True, then definitely check
|
|
|
|
if force:
|
|
|
|
check_for_updates = True
|
|
|
|
else:
|
|
|
|
check_for_updates = False
|
|
|
|
|
|
|
|
# See if it's been 1 day since the last check
|
2017-04-15 21:55:41 -04:00
|
|
|
autoupdate_timestamp = settings.get('autoupdate_timestamp')
|
|
|
|
if autoupdate_timestamp:
|
|
|
|
last_checked = datetime.datetime.fromtimestamp(autoupdate_timestamp)
|
|
|
|
now = datetime.datetime.now()
|
|
|
|
|
|
|
|
one_day = datetime.timedelta(days=1)
|
|
|
|
if now - last_checked > one_day:
|
2017-04-17 16:03:16 -04:00
|
|
|
check_for_updates = True
|
2017-04-15 21:55:41 -04:00
|
|
|
else:
|
2017-04-17 16:03:16 -04:00
|
|
|
check_for_updates = True
|
2017-04-15 21:55:41 -04:00
|
|
|
|
|
|
|
# Check for updates
|
2017-04-17 16:03:16 -04:00
|
|
|
if check_for_updates:
|
2017-05-16 20:29:02 -04:00
|
|
|
common.log('UpdateChecker', 'check', 'checking for updates')
|
2017-04-15 21:55:41 -04:00
|
|
|
# Download the latest-version file over Tor
|
|
|
|
try:
|
2017-04-17 16:03:16 -04:00
|
|
|
# User agent string includes OnionShare version and platform
|
2017-05-16 14:05:48 -04:00
|
|
|
user_agent = 'OnionShare {}, {}'.format(common.get_version(), platform.system())
|
2017-04-17 16:03:16 -04:00
|
|
|
|
|
|
|
# If the update is forced, add '?force=1' to the URL, to more
|
|
|
|
# accurately measure daily users
|
|
|
|
path = '/latest-version.txt'
|
|
|
|
if force:
|
|
|
|
path += '?force=1'
|
|
|
|
|
2017-05-16 20:29:02 -04:00
|
|
|
onion_domain = 'elx57ue5uyfplgva.onion'
|
|
|
|
|
2017-05-18 20:33:55 -04:00
|
|
|
common.log('UpdateChecker', 'check', 'loading http://{}{}'.format(onion_domain, path))
|
2017-05-16 20:29:02 -04:00
|
|
|
|
2017-05-14 22:54:12 -04:00
|
|
|
(socks_address, socks_port) = self.onion.get_tor_socks_port()
|
2017-04-15 21:55:41 -04:00
|
|
|
socks.set_default_proxy(socks.SOCKS5, socks_address, socks_port)
|
|
|
|
|
|
|
|
s = socks.socksocket()
|
|
|
|
s.settimeout(15) # 15 second timeout
|
2017-05-16 20:29:02 -04:00
|
|
|
s.connect((onion_domain, 80))
|
2017-04-15 21:55:41 -04:00
|
|
|
|
2017-04-17 16:03:16 -04:00
|
|
|
http_request = 'GET {} HTTP/1.0\r\n'.format(path)
|
2017-05-16 20:29:02 -04:00
|
|
|
http_request += 'Host: {}\r\n'.format(onion_domain)
|
2017-04-17 16:03:16 -04:00
|
|
|
http_request += 'User-Agent: {}\r\n'.format(user_agent)
|
2017-04-15 21:55:41 -04:00
|
|
|
http_request += '\r\n'
|
|
|
|
s.sendall(http_request.encode('utf-8'))
|
|
|
|
|
|
|
|
http_response = s.recv(1024)
|
|
|
|
latest_version = http_response[http_response.find(b'\r\n\r\n'):].strip().decode('utf-8')
|
2017-05-16 20:29:02 -04:00
|
|
|
|
|
|
|
common.log('UpdateChecker', 'check', 'latest OnionShare version: {}'.format(latest_version))
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
common.log('UpdateChecker', 'check', '{}'.format(e))
|
2018-01-23 00:51:13 -05:00
|
|
|
self.update_error.emit()
|
2017-05-14 22:54:12 -04:00
|
|
|
raise UpdateCheckerCheckError
|
2017-04-15 21:55:41 -04:00
|
|
|
|
|
|
|
# Validate that latest_version looks like a version string
|
|
|
|
# This regex is: 1-3 dot-separated numeric components
|
|
|
|
version_re = r"^(\d+\.)?(\d+\.)?(\d+)$"
|
|
|
|
if not re.match(version_re, latest_version):
|
2018-01-23 00:51:13 -05:00
|
|
|
self.update_invalid_version.emit()
|
2017-04-15 21:55:41 -04:00
|
|
|
raise UpdateCheckerInvalidLatestVersion(latest_version)
|
|
|
|
|
|
|
|
# Update the last checked timestamp (dropping the seconds and milliseconds)
|
|
|
|
timestamp = datetime.datetime.now().replace(microsecond=0).replace(second=0).timestamp()
|
2018-01-17 16:43:14 -05:00
|
|
|
# Re-load the settings first before saving, just in case they've changed since we started our thread
|
|
|
|
settings.load()
|
2017-04-15 21:55:41 -04:00
|
|
|
settings.set('autoupdate_timestamp', timestamp)
|
|
|
|
settings.save()
|
|
|
|
|
|
|
|
# Do we need to update?
|
|
|
|
update_url = 'https://github.com/micahflee/onionshare/releases/tag/v{}'.format(latest_version)
|
2017-05-16 14:05:48 -04:00
|
|
|
installed_version = common.get_version()
|
2017-04-15 21:55:41 -04:00
|
|
|
if installed_version < latest_version:
|
|
|
|
self.update_available.emit(update_url, installed_version, latest_version)
|
|
|
|
return
|
|
|
|
|
|
|
|
# No updates are available
|
|
|
|
self.update_not_available.emit()
|
2017-04-15 22:07:02 -04:00
|
|
|
|
|
|
|
class UpdateThread(QtCore.QThread):
|
2017-04-17 16:22:33 -04:00
|
|
|
update_available = QtCore.pyqtSignal(str, str, str)
|
2017-05-14 22:54:12 -04:00
|
|
|
update_not_available = QtCore.pyqtSignal()
|
2018-01-23 00:51:13 -05:00
|
|
|
update_error = QtCore.pyqtSignal()
|
|
|
|
update_invalid_version = QtCore.pyqtSignal()
|
2017-04-17 16:22:33 -04:00
|
|
|
|
2018-01-23 00:32:14 -05:00
|
|
|
def __init__(self, onion, config=False, force=False):
|
2017-04-15 22:07:02 -04:00
|
|
|
super(UpdateThread, self).__init__()
|
2017-05-16 20:29:02 -04:00
|
|
|
common.log('UpdateThread', '__init__')
|
2017-05-14 22:54:12 -04:00
|
|
|
self.onion = onion
|
2017-06-01 03:35:27 -04:00
|
|
|
self.config = config
|
2018-01-23 00:32:14 -05:00
|
|
|
self.force = force
|
2017-04-15 22:07:02 -04:00
|
|
|
|
|
|
|
def run(self):
|
2017-05-16 20:29:02 -04:00
|
|
|
common.log('UpdateThread', 'run')
|
|
|
|
|
2017-06-01 03:35:27 -04:00
|
|
|
u = UpdateChecker(self.onion, self.config)
|
2017-04-17 16:22:33 -04:00
|
|
|
u.update_available.connect(self._update_available)
|
2017-05-14 22:54:12 -04:00
|
|
|
u.update_not_available.connect(self._update_not_available)
|
2018-01-23 00:51:13 -05:00
|
|
|
u.update_error.connect(self._update_error)
|
|
|
|
u.update_invalid_version.connect(self._update_invalid_version)
|
2017-05-16 20:29:02 -04:00
|
|
|
|
2017-04-15 22:07:02 -04:00
|
|
|
try:
|
2018-01-23 00:32:14 -05:00
|
|
|
u.check(config=self.config,force=self.force)
|
2017-05-16 20:29:02 -04:00
|
|
|
except Exception as e:
|
2017-04-15 22:07:02 -04:00
|
|
|
# If update check fails, silently ignore
|
2017-05-16 20:29:02 -04:00
|
|
|
common.log('UpdateThread', 'run', '{}'.format(e))
|
2017-04-15 22:07:02 -04:00
|
|
|
pass
|
|
|
|
|
2017-04-17 16:22:33 -04:00
|
|
|
def _update_available(self, update_url, installed_version, latest_version):
|
2017-05-16 20:29:02 -04:00
|
|
|
common.log('UpdateThread', '_update_available')
|
|
|
|
self.active = False
|
2017-04-17 16:22:33 -04:00
|
|
|
self.update_available.emit(update_url, installed_version, latest_version)
|
|
|
|
|
2017-05-14 22:54:12 -04:00
|
|
|
def _update_not_available(self):
|
2017-05-16 20:29:02 -04:00
|
|
|
common.log('UpdateThread', '_update_not_available')
|
|
|
|
self.active = False
|
2017-05-14 22:54:12 -04:00
|
|
|
self.update_not_available.emit()
|
2018-01-23 00:51:13 -05:00
|
|
|
|
|
|
|
def _update_error(self):
|
|
|
|
common.log('UpdateThread', '_update_error')
|
|
|
|
self.active = False
|
|
|
|
self.update_error.emit()
|
|
|
|
|
|
|
|
def _update_invalid_version(self):
|
|
|
|
common.log('UpdateThread', '_update_invalid_version')
|
|
|
|
self.active = False
|
|
|
|
self.update_invalid_version.emit()
|