Merge branch 'develop' of https://github.com/micahflee/onionshare into develop
6
BUILD.md
|
@ -85,7 +85,7 @@ pip3 install -r install\requirements-windows.txt
|
||||||
|
|
||||||
Download and install pywin32 (build 221, x86, for python 3.6) from https://sourceforge.net/projects/pywin32/files/pywin32/Build%20221/. I downloaded `pywin32-221.win32-py3.6.exe`.
|
Download and install pywin32 (build 221, x86, for python 3.6) from https://sourceforge.net/projects/pywin32/files/pywin32/Build%20221/. I downloaded `pywin32-221.win32-py3.6.exe`.
|
||||||
|
|
||||||
Download and install Qt5 from https://www.qt.io/download-open-source/. I downloaded `qt-unified-windows-x86-3.0.2-online.exe`. There's no need to login to a Qt account during installation. Make sure you install the latest Qt 5.x. I installed Qt 5.10.0.
|
Download and install Qt5 from https://www.qt.io/download-open-source/. I downloaded `qt-unified-windows-x86-3.0.4-online.exe`. There's no need to login to a Qt account during installation. Make sure you install the latest Qt 5.x. I installed Qt 5.11.0. You only need to install the `MSVC 2015 32-bit` component, as well as all of the the `Qt` components, for that that version.
|
||||||
|
|
||||||
After that you can try both the CLI and the GUI version of OnionShare:
|
After that you can try both the CLI and the GUI version of OnionShare:
|
||||||
|
|
||||||
|
@ -100,7 +100,7 @@ These instructions include adding folders to the path in Windows. To do this, go
|
||||||
|
|
||||||
Download and install the 32-bit [Visual C++ Redistributable for Visual Studio 2015](https://www.microsoft.com/en-US/download/details.aspx?id=48145). I downloaded `vc_redist.x86.exe`.
|
Download and install the 32-bit [Visual C++ Redistributable for Visual Studio 2015](https://www.microsoft.com/en-US/download/details.aspx?id=48145). I downloaded `vc_redist.x86.exe`.
|
||||||
|
|
||||||
Download and install 7-Zip from http://www.7-zip.org/download.html. I downloaded `7z1800.exe`.
|
Download and install 7-Zip from http://www.7-zip.org/download.html. I downloaded `7z1805.exe`.
|
||||||
|
|
||||||
Download and install the standalone [Windows 10 SDK](https://dev.windows.com/en-us/downloads/windows-10-sdk). Note that you may not need this if you already have Visual Studio.
|
Download and install the standalone [Windows 10 SDK](https://dev.windows.com/en-us/downloads/windows-10-sdk). Note that you may not need this if you already have Visual Studio.
|
||||||
|
|
||||||
|
@ -113,7 +113,7 @@ Add the following directories to the path:
|
||||||
|
|
||||||
If you want to build the installer:
|
If you want to build the installer:
|
||||||
|
|
||||||
* Go to http://nsis.sourceforge.net/Download and download the latest NSIS. I downloaded `nsis-3.02.1-setup.exe`.
|
* Go to http://nsis.sourceforge.net/Download and download the latest NSIS. I downloaded `nsis-3.03-setup.exe`.
|
||||||
* Add `C:\Program Files (x86)\NSIS` to the path.
|
* Add `C:\Program Files (x86)\NSIS` to the path.
|
||||||
|
|
||||||
If you want to sign binaries with Authenticode:
|
If you want to sign binaries with Authenticode:
|
||||||
|
|
|
@ -1,5 +1,10 @@
|
||||||
# OnionShare Changelog
|
# OnionShare Changelog
|
||||||
|
|
||||||
|
## 1.3.1
|
||||||
|
|
||||||
|
* Updated Tor to 0.2.3.10
|
||||||
|
* Windows and Mac binaries are now distributed with licenses for tor and obfs4
|
||||||
|
|
||||||
## 1.3
|
## 1.3
|
||||||
|
|
||||||
* Major UI redesign, introducing many UX improvements
|
* Major UI redesign, introducing many UX improvements
|
||||||
|
|
6
LICENSE
|
@ -1,7 +1,7 @@
|
||||||
OnionShare
|
(Note: Third-party licenses can be found under install/licenses/.)
|
||||||
|
|
||||||
Copyright © 2016
|
OnionShare
|
||||||
Micah Lee <micah@micahflee.com>
|
Copyright © 2018 Micah Lee <micah@micahflee.com>
|
||||||
|
|
||||||
GNU GENERAL PUBLIC LICENSE
|
GNU GENERAL PUBLIC LICENSE
|
||||||
Version 3, 29 June 2007
|
Version 3, 29 June 2007
|
||||||
|
|
|
@ -4,7 +4,8 @@ include BUILD.md
|
||||||
include share/*
|
include share/*
|
||||||
include share/images/*
|
include share/images/*
|
||||||
include share/locale/*
|
include share/locale/*
|
||||||
include share/html/*
|
include share/templates/*
|
||||||
|
include share/static/*
|
||||||
include install/onionshare.desktop
|
include install/onionshare.desktop
|
||||||
include install/onionshare.appdata.xml
|
include install/onionshare.appdata.xml
|
||||||
include install/onionshare80.xpm
|
include install/onionshare80.xpm
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
"""
|
"""
|
||||||
OnionShare | https://onionshare.org/
|
OnionShare | https://onionshare.org/
|
||||||
|
|
||||||
Copyright (C) 2017 Micah Lee <micah@micahflee.com>
|
Copyright (C) 2018 Micah Lee <micah@micahflee.com>
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
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
|
it under the terms of the GNU General Public License as published by
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
"""
|
"""
|
||||||
OnionShare | https://onionshare.org/
|
OnionShare | https://onionshare.org/
|
||||||
|
|
||||||
Copyright (C) 2017 Micah Lee <micah@micahflee.com>
|
Copyright (C) 2018 Micah Lee <micah@micahflee.com>
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
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
|
it under the terms of the GNU General Public License as published by
|
||||||
|
|
|
@ -8,10 +8,10 @@ REM download tor
|
||||||
python install\get-tor-windows.py
|
python install\get-tor-windows.py
|
||||||
|
|
||||||
REM sign onionshare-gui.exe
|
REM sign onionshare-gui.exe
|
||||||
signtool.exe sign /v /d "OnionShare" /a /tr http://time.certum.pl/ /fd sha256 dist\onionshare\onionshare-gui.exe
|
signtool.exe sign /v /d "OnionShare" /a /tr http://time.certum.pl/ dist\onionshare\onionshare-gui.exe
|
||||||
|
|
||||||
REM build an installer, dist\onionshare-setup.exe
|
REM build an installer, dist\onionshare-setup.exe
|
||||||
makensis.exe install\onionshare.nsi
|
makensis.exe install\onionshare.nsi
|
||||||
|
|
||||||
REM sign onionshare-setup.exe
|
REM sign onionshare-setup.exe
|
||||||
signtool.exe sign /v /d "OnionShare" /a /tr http://time.certum.pl/ /fd sha256 dist\onionshare-setup.exe
|
signtool.exe sign /v /d "OnionShare" /a /tr http://time.certum.pl/ dist\onionshare-setup.exe
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
"""
|
"""
|
||||||
OnionShare | https://onionshare.org/
|
OnionShare | https://onionshare.org/
|
||||||
|
|
||||||
Copyright (C) 2017 Micah Lee <micah@micahflee.com>
|
Copyright (C) 2018 Micah Lee <micah@micahflee.com>
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
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
|
it under the terms of the GNU General Public License as published by
|
||||||
|
@ -24,13 +24,20 @@ In order to avoid a Mac gnupg dependency, I manually verify the signature
|
||||||
and hard-code the sha256 hash.
|
and hard-code the sha256 hash.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import inspect, os, sys, hashlib, zipfile, io, shutil, subprocess
|
import inspect
|
||||||
import urllib.request
|
import os
|
||||||
|
import sys
|
||||||
|
import hashlib
|
||||||
|
import zipfile
|
||||||
|
import io
|
||||||
|
import shutil
|
||||||
|
import subprocess
|
||||||
|
import requests
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
dmg_url = 'https://archive.torproject.org/tor-package-archive/torbrowser/7.5/TorBrowser-7.5-osx64_en-US.dmg'
|
dmg_url = 'https://archive.torproject.org/tor-package-archive/torbrowser/7.5.5/TorBrowser-7.5.5-osx64_en-US.dmg'
|
||||||
dmg_filename = 'TorBrowser-7.5-osx64_en-US.dmg'
|
dmg_filename = 'TorBrowser-7.5.5-osx64_en-US.dmg'
|
||||||
expected_dmg_sha256 = '43a8dc0afd0a77e42766311eb54ad9fc8714f67fcd2d3582a3bcb98b22c2e629'
|
expected_dmg_sha256 = '2b445e4237cdd9be0e71e65f76db5d36f0d6c37532982d642803b57e388e4636'
|
||||||
|
|
||||||
# Build paths
|
# Build paths
|
||||||
root_path = os.path.dirname(os.path.dirname(os.path.abspath(inspect.getfile(inspect.currentframe()))))
|
root_path = os.path.dirname(os.path.dirname(os.path.abspath(inspect.getfile(inspect.currentframe()))))
|
||||||
|
@ -46,10 +53,9 @@ def main():
|
||||||
# Make sure the zip is downloaded
|
# Make sure the zip is downloaded
|
||||||
if not os.path.exists(dmg_path):
|
if not os.path.exists(dmg_path):
|
||||||
print("Downloading {}".format(dmg_url))
|
print("Downloading {}".format(dmg_url))
|
||||||
response = urllib.request.urlopen(dmg_url)
|
r = requests.get(dmg_url)
|
||||||
dmg_data = response.read()
|
open(dmg_path, 'wb').write(r.content)
|
||||||
open(dmg_path, 'wb').write(dmg_data)
|
dmg_sha256 = hashlib.sha256(r.content).hexdigest()
|
||||||
dmg_sha256 = hashlib.sha256(dmg_data).hexdigest()
|
|
||||||
else:
|
else:
|
||||||
dmg_data = open(dmg_path, 'rb').read()
|
dmg_data = open(dmg_path, 'rb').read()
|
||||||
dmg_sha256 = hashlib.sha256(dmg_data).hexdigest()
|
dmg_sha256 = hashlib.sha256(dmg_data).hexdigest()
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
"""
|
"""
|
||||||
OnionShare | https://onionshare.org/
|
OnionShare | https://onionshare.org/
|
||||||
|
|
||||||
Copyright (C) 2017 Micah Lee <micah@micahflee.com>
|
Copyright (C) 2018 Micah Lee <micah@micahflee.com>
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
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
|
it under the terms of the GNU General Public License as published by
|
||||||
|
@ -24,13 +24,18 @@ In order to avoid a Windows gnupg dependency, I manually verify the signature
|
||||||
and hard-code the sha256 hash.
|
and hard-code the sha256 hash.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import inspect, os, sys, hashlib, shutil, subprocess
|
import inspect
|
||||||
import urllib.request
|
import os
|
||||||
|
import sys
|
||||||
|
import hashlib
|
||||||
|
import shutil
|
||||||
|
import subprocess
|
||||||
|
import requests
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
exe_url = 'https://archive.torproject.org/tor-package-archive/torbrowser/7.5/torbrowser-install-7.5_en-US.exe'
|
exe_url = 'https://archive.torproject.org/tor-package-archive/torbrowser/7.5.5/torbrowser-install-7.5.5_en-US.exe'
|
||||||
exe_filename = 'torbrowser-install-7.5_en-US.exe'
|
exe_filename = 'torbrowser-install-7.5.5_en-US.exe'
|
||||||
expected_exe_sha256 = '81ccb9456118cf8fa755a3eafb5c514665fc69599cdd41e9eb36baa335ebe233'
|
expected_exe_sha256 = '992f9a6658001c3419ed3695a908eef4fb7feb1cd549389bdacbadb7f8cb08a7'
|
||||||
# Build paths
|
# Build paths
|
||||||
root_path = os.path.dirname(os.path.dirname(os.path.abspath(inspect.getfile(inspect.currentframe()))))
|
root_path = os.path.dirname(os.path.dirname(os.path.abspath(inspect.getfile(inspect.currentframe()))))
|
||||||
working_path = os.path.join(os.path.join(root_path, 'build'), 'tor')
|
working_path = os.path.join(os.path.join(root_path, 'build'), 'tor')
|
||||||
|
@ -44,10 +49,9 @@ def main():
|
||||||
# Make sure the zip is downloaded
|
# Make sure the zip is downloaded
|
||||||
if not os.path.exists(exe_path):
|
if not os.path.exists(exe_path):
|
||||||
print("Downloading {}".format(exe_url))
|
print("Downloading {}".format(exe_url))
|
||||||
response = urllib.request.urlopen(exe_url)
|
r = requests.get(exe_url)
|
||||||
exe_data = response.read()
|
open(exe_path, 'wb').write(r.content)
|
||||||
open(exe_path, 'wb').write(exe_data)
|
exe_sha256 = hashlib.sha256(r.content).hexdigest()
|
||||||
exe_sha256 = hashlib.sha256(exe_data).hexdigest()
|
|
||||||
else:
|
else:
|
||||||
exe_data = open(exe_path, 'rb').read()
|
exe_data = open(exe_path, 'rb').read()
|
||||||
exe_sha256 = hashlib.sha256(exe_data).hexdigest()
|
exe_sha256 = hashlib.sha256(exe_data).hexdigest()
|
||||||
|
|
55
install/licenses/license-obfs4.txt
Normal file
|
@ -0,0 +1,55 @@
|
||||||
|
Copyright (c) 2014, Yawning Angel <yawning at torproject dot org>
|
||||||
|
All rights reserved.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions are met:
|
||||||
|
|
||||||
|
* Redistributions of source code must retain the above copyright notice,
|
||||||
|
this list of conditions and the following disclaimer.
|
||||||
|
|
||||||
|
* Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
this list of conditions and the following disclaimer in the documentation
|
||||||
|
and/or other materials provided with the distribution.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
|
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
|
||||||
|
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||||
|
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||||
|
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||||
|
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||||
|
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||||
|
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||||
|
POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
|
==============================================================================
|
||||||
|
|
||||||
|
Copyright (c) 2012 The Go Authors. All rights reserved.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions are
|
||||||
|
met:
|
||||||
|
|
||||||
|
* Redistributions of source code must retain the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer.
|
||||||
|
* Redistributions in binary form must reproduce the above
|
||||||
|
copyright notice, this list of conditions and the following disclaimer
|
||||||
|
in the documentation and/or other materials provided with the
|
||||||
|
distribution.
|
||||||
|
* Neither the name of Google Inc. nor the names of its
|
||||||
|
contributors may be used to endorse or promote products derived from
|
||||||
|
this software without specific prior written permission.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
|
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||||
|
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
|
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
Copyright (C) 2017 Micah Lee <micah@micahflee.com>
|
OnionShare
|
||||||
|
Copyright © 2018 Micah Lee <micah@micahflee.com>
|
||||||
|
|
||||||
GNU GENERAL PUBLIC LICENSE
|
GNU GENERAL PUBLIC LICENSE
|
||||||
Version 3, 29 June 2007
|
Version 3, 29 June 2007
|
381
install/licenses/license-tor.txt
Normal file
|
@ -0,0 +1,381 @@
|
||||||
|
This file contains the license for Tor,
|
||||||
|
a free software project to provide anonymity on the Internet.
|
||||||
|
|
||||||
|
It also lists the licenses for other components used by Tor.
|
||||||
|
|
||||||
|
For more information about Tor, see https://www.torproject.org/.
|
||||||
|
|
||||||
|
If you got this file as a part of a larger bundle,
|
||||||
|
there may be other license terms that you should be aware of.
|
||||||
|
|
||||||
|
===============================================================================
|
||||||
|
Tor is distributed under this license:
|
||||||
|
|
||||||
|
Copyright (c) 2001-2004, Roger Dingledine
|
||||||
|
Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson
|
||||||
|
Copyright (c) 2007-2017, The Tor Project, Inc.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions are
|
||||||
|
met:
|
||||||
|
|
||||||
|
* Redistributions of source code must retain the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer.
|
||||||
|
|
||||||
|
* Redistributions in binary form must reproduce the above
|
||||||
|
copyright notice, this list of conditions and the following disclaimer
|
||||||
|
in the documentation and/or other materials provided with the
|
||||||
|
distribution.
|
||||||
|
|
||||||
|
* Neither the names of the copyright owners nor the names of its
|
||||||
|
contributors may be used to endorse or promote products derived from
|
||||||
|
this software without specific prior written permission.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
|
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||||
|
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
|
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
===============================================================================
|
||||||
|
src/ext/strlcat.c and src/ext/strlcpy.c by Todd C. Miller are licensed
|
||||||
|
under the following license:
|
||||||
|
|
||||||
|
* Copyright (c) 1998 Todd C. Miller <Todd.Miller@courtesan.com>
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
* modification, are permitted provided that the following conditions
|
||||||
|
* are met:
|
||||||
|
* 1. Redistributions of source code must retain the above copyright
|
||||||
|
* notice, this list of conditions and the following disclaimer.
|
||||||
|
* 2. Redistributions in binary form must reproduce the above copyright
|
||||||
|
* notice, this list of conditions and the following disclaimer in the
|
||||||
|
* documentation and/or other materials provided with the distribution.
|
||||||
|
* 3. The name of the author may not be used to endorse or promote products
|
||||||
|
* derived from this software without specific prior written permission.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
|
||||||
|
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||||
|
* AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
|
||||||
|
* THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||||
|
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||||
|
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
|
||||||
|
* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
||||||
|
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
|
||||||
|
* OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
|
||||||
|
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
===============================================================================
|
||||||
|
src/ext/tor_queue.h is licensed under the following license:
|
||||||
|
|
||||||
|
* Copyright (c) 1991, 1993
|
||||||
|
* The Regents of the University of California. All rights reserved.
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
* modification, are permitted provided that the following conditions
|
||||||
|
* are met:
|
||||||
|
* 1. Redistributions of source code must retain the above copyright
|
||||||
|
* notice, this list of conditions and the following disclaimer.
|
||||||
|
* 2. Redistributions in binary form must reproduce the above copyright
|
||||||
|
* notice, this list of conditions and the following disclaimer in the
|
||||||
|
* documentation and/or other materials provided with the distribution.
|
||||||
|
* 3. Neither the name of the University nor the names of its contributors
|
||||||
|
* may be used to endorse or promote products derived from this software
|
||||||
|
* without specific prior written permission.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
|
||||||
|
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
* ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
|
||||||
|
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||||
|
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
||||||
|
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||||
|
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||||
|
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
||||||
|
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
||||||
|
* SUCH DAMAGE.
|
||||||
|
|
||||||
|
===============================================================================
|
||||||
|
src/ext/csiphash.c is licensed under the following license:
|
||||||
|
|
||||||
|
Copyright (c) 2013 Marek Majkowski <marek@popcount.org>
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in
|
||||||
|
all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
THE SOFTWARE.
|
||||||
|
===============================================================================
|
||||||
|
Trunnel is distributed under this license:
|
||||||
|
|
||||||
|
Copyright 2014 The Tor Project, Inc.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions are
|
||||||
|
met:
|
||||||
|
|
||||||
|
* Redistributions of source code must retain the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer.
|
||||||
|
|
||||||
|
* Redistributions in binary form must reproduce the above
|
||||||
|
copyright notice, this list of conditions and the following disclaimer
|
||||||
|
in the documentation and/or other materials provided with the
|
||||||
|
distribution.
|
||||||
|
|
||||||
|
* Neither the names of the copyright owners nor the names of its
|
||||||
|
contributors may be used to endorse or promote products derived from
|
||||||
|
this software without specific prior written permission.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
|
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||||
|
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
|
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
|
===============================================================================
|
||||||
|
src/config/geoip is licensed under the following license:
|
||||||
|
|
||||||
|
OPEN DATA LICENSE (GeoLite Country and GeoLite City databases)
|
||||||
|
|
||||||
|
Copyright (c) 2008 MaxMind, Inc. All Rights Reserved.
|
||||||
|
|
||||||
|
All advertising materials and documentation mentioning features or use of
|
||||||
|
this database must display the following acknowledgment:
|
||||||
|
"This product includes GeoLite data created by MaxMind, available from
|
||||||
|
http://maxmind.com/"
|
||||||
|
|
||||||
|
Redistribution and use with or without modification, are permitted provided
|
||||||
|
that the following conditions are met:
|
||||||
|
1. Redistributions must retain the above copyright notice, this list of
|
||||||
|
conditions and the following disclaimer in the documentation and/or other
|
||||||
|
materials provided with the distribution.
|
||||||
|
2. All advertising materials and documentation mentioning features or use of
|
||||||
|
this database must display the following acknowledgement:
|
||||||
|
"This product includes GeoLite data created by MaxMind, available from
|
||||||
|
http://maxmind.com/"
|
||||||
|
3. "MaxMind" may not be used to endorse or promote products derived from this
|
||||||
|
database without specific prior written permission.
|
||||||
|
|
||||||
|
THIS DATABASE IS PROVIDED BY MAXMIND, INC ``AS IS'' AND ANY
|
||||||
|
EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||||
|
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
DISCLAIMED. IN NO EVENT SHALL MAXMIND BE LIABLE FOR ANY
|
||||||
|
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||||
|
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||||
|
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||||
|
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
|
DATABASE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
===============================================================================
|
||||||
|
m4/pc_from_ucontext.m4 is available under the following license. Note that
|
||||||
|
it is *not* built into the Tor software.
|
||||||
|
|
||||||
|
Copyright (c) 2005, Google Inc.
|
||||||
|
All rights reserved.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions are
|
||||||
|
met:
|
||||||
|
|
||||||
|
* Redistributions of source code must retain the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer.
|
||||||
|
* Redistributions in binary form must reproduce the above
|
||||||
|
copyright notice, this list of conditions and the following disclaimer
|
||||||
|
in the documentation and/or other materials provided with the
|
||||||
|
distribution.
|
||||||
|
* Neither the name of Google Inc. nor the names of its
|
||||||
|
contributors may be used to endorse or promote products derived from
|
||||||
|
this software without specific prior written permission.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
|
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||||
|
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
|
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
|
===============================================================================
|
||||||
|
m4/pkg.m4 is available under the following license. Note that
|
||||||
|
it is *not* built into the Tor software.
|
||||||
|
|
||||||
|
pkg.m4 - Macros to locate and utilise pkg-config. -*- Autoconf -*-
|
||||||
|
serial 1 (pkg-config-0.24)
|
||||||
|
|
||||||
|
Copyright © 2004 Scott James Remnant <scott@netsplit.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 2 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, write to the Free Software
|
||||||
|
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||||
|
|
||||||
|
As a special exception to the GNU General Public License, if you
|
||||||
|
distribute this file as part of a program that contains a
|
||||||
|
configuration script generated by Autoconf, you may include it under
|
||||||
|
the same distribution terms that you use for the rest of that program.
|
||||||
|
===============================================================================
|
||||||
|
src/ext/readpassphrase.[ch] are distributed under this license:
|
||||||
|
|
||||||
|
Copyright (c) 2000-2002, 2007 Todd C. Miller <Todd.Miller@courtesan.com>
|
||||||
|
|
||||||
|
Permission to use, copy, modify, and distribute this software for any
|
||||||
|
purpose with or without fee is hereby granted, provided that the above
|
||||||
|
copyright notice and this permission notice appear in all copies.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||||
|
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||||
|
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||||
|
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||||
|
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||||
|
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||||
|
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
|
||||||
|
Sponsored in part by the Defense Advanced Research Projects
|
||||||
|
Agency (DARPA) and Air Force Research Laboratory, Air Force
|
||||||
|
Materiel Command, USAF, under agreement number F39502-99-1-0512.
|
||||||
|
|
||||||
|
===============================================================================
|
||||||
|
src/ext/mulodi4.c is distributed under this license:
|
||||||
|
|
||||||
|
=========================================================================
|
||||||
|
compiler_rt License
|
||||||
|
=========================================================================
|
||||||
|
|
||||||
|
The compiler_rt library is dual licensed under both the
|
||||||
|
University of Illinois "BSD-Like" license and the MIT license.
|
||||||
|
As a user of this code you may choose to use it under either
|
||||||
|
license. As a contributor, you agree to allow your code to be
|
||||||
|
used under both.
|
||||||
|
|
||||||
|
Full text of the relevant licenses is included below.
|
||||||
|
|
||||||
|
=========================================================================
|
||||||
|
|
||||||
|
University of Illinois/NCSA
|
||||||
|
Open Source License
|
||||||
|
|
||||||
|
Copyright (c) 2009-2016 by the contributors listed in CREDITS.TXT
|
||||||
|
|
||||||
|
All rights reserved.
|
||||||
|
|
||||||
|
Developed by:
|
||||||
|
|
||||||
|
LLVM Team
|
||||||
|
|
||||||
|
University of Illinois at Urbana-Champaign
|
||||||
|
|
||||||
|
http://llvm.org
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person
|
||||||
|
obtaining a copy of this software and associated documentation
|
||||||
|
files (the "Software"), to deal with the Software without
|
||||||
|
restriction, including without limitation the rights to use,
|
||||||
|
copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the
|
||||||
|
Software is furnished to do so, subject to the following
|
||||||
|
conditions:
|
||||||
|
|
||||||
|
* Redistributions of source code must retain the above
|
||||||
|
copyright notice, this list of conditions and the following
|
||||||
|
disclaimers.
|
||||||
|
|
||||||
|
* Redistributions in binary form must reproduce the above
|
||||||
|
copyright notice, this list of conditions and the following
|
||||||
|
disclaimers in the documentation and/or other materials
|
||||||
|
provided with the distribution.
|
||||||
|
|
||||||
|
* Neither the names of the LLVM Team, University of Illinois
|
||||||
|
at Urbana-Champaign, nor the names of its contributors may
|
||||||
|
be used to endorse or promote products derived from this
|
||||||
|
Software without specific prior written permission.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
||||||
|
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||||
|
NONINFRINGEMENT. IN NO EVENT SHALL THE CONTRIBUTORS OR COPYRIGHT
|
||||||
|
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||||
|
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||||
|
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||||
|
OTHER DEALINGS WITH THE SOFTWARE.
|
||||||
|
|
||||||
|
=========================================================================
|
||||||
|
|
||||||
|
Copyright (c) 2009-2015 by the contributors listed in CREDITS.TXT
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person
|
||||||
|
obtaining a copy of this software and associated documentation
|
||||||
|
files (the "Software"), to deal in the Software without
|
||||||
|
restriction, including without limitation the rights to use,
|
||||||
|
copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the
|
||||||
|
Software is furnished to do so, subject to the following
|
||||||
|
conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be
|
||||||
|
included in all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
||||||
|
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||||
|
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
||||||
|
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||||
|
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||||
|
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||||
|
OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
|
||||||
|
=========================================================================
|
||||||
|
Copyrights and Licenses for Third Party Software Distributed with LLVM:
|
||||||
|
=========================================================================
|
||||||
|
|
||||||
|
The LLVM software contains code written by third parties. Such
|
||||||
|
software will have its own individual LICENSE.TXT file in the
|
||||||
|
directory in which it appears. This file will describe the
|
||||||
|
copyrights, license, and restrictions which apply to that code.
|
||||||
|
|
||||||
|
The disclaimer of warranty in the University of Illinois Open
|
||||||
|
Source License applies to all code in the LLVM Distribution, and
|
||||||
|
nothing in any of the other licenses gives permission to use the
|
||||||
|
names of the LLVM Team or the University of Illinois to endorse
|
||||||
|
or promote products derived from this Software.
|
||||||
|
|
||||||
|
===============================================================================
|
||||||
|
If you got Tor as a static binary with OpenSSL included, then you should know:
|
||||||
|
"This product includes software developed by the OpenSSL Project
|
||||||
|
for use in the OpenSSL Toolkit (http://www.openssl.org/)"
|
||||||
|
===============================================================================
|
1
install/licenses/readme.txt
Normal file
|
@ -0,0 +1 @@
|
||||||
|
This folder contains the software licenses for 3rd-party binaries included with OnionShare.
|
|
@ -6,7 +6,7 @@
|
||||||
!define INSTALLSIZE 66537
|
!define INSTALLSIZE 66537
|
||||||
!define VERSIONMAJOR 1
|
!define VERSIONMAJOR 1
|
||||||
!define VERSIONMINOR 3
|
!define VERSIONMINOR 3
|
||||||
!define VERSIONSTRING "1.3"
|
!define VERSIONSTRING "1.3.1"
|
||||||
|
|
||||||
RequestExecutionLevel admin
|
RequestExecutionLevel admin
|
||||||
|
|
||||||
|
@ -39,7 +39,7 @@ ${EndIf}
|
||||||
!echo "Creating normal installer"
|
!echo "Creating normal installer"
|
||||||
!system "makensis.exe /DINNER onionshare.nsi" = 0
|
!system "makensis.exe /DINNER onionshare.nsi" = 0
|
||||||
!system "$%TEMP%\tempinstaller.exe" = 2
|
!system "$%TEMP%\tempinstaller.exe" = 2
|
||||||
!system "signtool.exe sign /v /d $\"Uninstall OnionShare$\" /a /tr http://time.certum.pl/ /fd sha256 $%TEMP%\uninstall.exe" = 0
|
!system "signtool.exe sign /v /d $\"Uninstall OnionShare$\" /a /tr http://time.certum.pl/ $%TEMP%\uninstall.exe" = 0
|
||||||
|
|
||||||
# all done, now we can build the real installer
|
# all done, now we can build the real installer
|
||||||
OutFile "..\dist\onionshare-setup.exe"
|
OutFile "..\dist\onionshare-setup.exe"
|
||||||
|
@ -162,6 +162,12 @@ Section "install"
|
||||||
SetOutPath "$INSTDIR\lib2to3\tests\data"
|
SetOutPath "$INSTDIR\lib2to3\tests\data"
|
||||||
File "${BINPATH}\lib2to3\tests\data\README"
|
File "${BINPATH}\lib2to3\tests\data\README"
|
||||||
|
|
||||||
|
SetOutPath "$INSTDIR\licenses"
|
||||||
|
File "${BINPATH}\licenses\license-obfs4.txt"
|
||||||
|
File "${BINPATH}\licenses\license-onionshare.txt"
|
||||||
|
File "${BINPATH}\licenses\license-tor.txt"
|
||||||
|
File "${BINPATH}\licenses\readme.txt"
|
||||||
|
|
||||||
SetOutPath "$INSTDIR\PyQt5\Qt\bin"
|
SetOutPath "$INSTDIR\PyQt5\Qt\bin"
|
||||||
File "${BINPATH}\PyQt5\Qt\bin\qt.conf"
|
File "${BINPATH}\PyQt5\Qt\bin\qt.conf"
|
||||||
|
|
||||||
|
@ -188,7 +194,6 @@ Section "install"
|
||||||
File "${BINPATH}\PyQt5\Qt\plugins\printsupport\windowsprintersupport.dll"
|
File "${BINPATH}\PyQt5\Qt\plugins\printsupport\windowsprintersupport.dll"
|
||||||
|
|
||||||
SetOutPath "$INSTDIR\share"
|
SetOutPath "$INSTDIR\share"
|
||||||
File "${BINPATH}\share\license.txt"
|
|
||||||
File "${BINPATH}\share\torrc_template"
|
File "${BINPATH}\share\torrc_template"
|
||||||
File "${BINPATH}\share\torrc_template-windows"
|
File "${BINPATH}\share\torrc_template-windows"
|
||||||
File "${BINPATH}\share\torrc_template-obfs4"
|
File "${BINPATH}\share\torrc_template-obfs4"
|
||||||
|
@ -207,6 +212,8 @@ Section "install"
|
||||||
File "${BINPATH}\share\images\download_completed_none.png"
|
File "${BINPATH}\share\images\download_completed_none.png"
|
||||||
File "${BINPATH}\share\images\download_in_progress.png"
|
File "${BINPATH}\share\images\download_in_progress.png"
|
||||||
File "${BINPATH}\share\images\download_in_progress_none.png"
|
File "${BINPATH}\share\images\download_in_progress_none.png"
|
||||||
|
File "${BINPATH}\share\images\download_window_gray.png"
|
||||||
|
File "${BINPATH}\share\images\download_window_green.png"
|
||||||
File "${BINPATH}\share\images\favicon.ico"
|
File "${BINPATH}\share\images\favicon.ico"
|
||||||
File "${BINPATH}\share\images\file_delete.png"
|
File "${BINPATH}\share\images\file_delete.png"
|
||||||
File "${BINPATH}\share\images\info.png"
|
File "${BINPATH}\share\images\info.png"
|
||||||
|
@ -351,6 +358,10 @@ FunctionEnd
|
||||||
Delete "$INSTDIR\lib2to3\tests"
|
Delete "$INSTDIR\lib2to3\tests"
|
||||||
Delete "$INSTDIR\lib2to3\tests\data"
|
Delete "$INSTDIR\lib2to3\tests\data"
|
||||||
Delete "$INSTDIR\lib2to3\tests\data\README"
|
Delete "$INSTDIR\lib2to3\tests\data\README"
|
||||||
|
Delete "$INSTDIR\licenses\license-obfs4.txt"
|
||||||
|
Delete "$INSTDIR\licenses\license-onionshare.txt"
|
||||||
|
Delete "$INSTDIR\licenses\license-tor.txt"
|
||||||
|
Delete "$INSTDIR\licenses\readme.txt"
|
||||||
Delete "$INSTDIR\mfc140u.dll"
|
Delete "$INSTDIR\mfc140u.dll"
|
||||||
Delete "$INSTDIR\MSVCP140.dll"
|
Delete "$INSTDIR\MSVCP140.dll"
|
||||||
Delete "$INSTDIR\onionshare-gui.exe"
|
Delete "$INSTDIR\onionshare-gui.exe"
|
||||||
|
@ -393,6 +404,8 @@ FunctionEnd
|
||||||
Delete "$INSTDIR\share\images\download_completed_none.png"
|
Delete "$INSTDIR\share\images\download_completed_none.png"
|
||||||
Delete "$INSTDIR\share\images\download_in_progress.png"
|
Delete "$INSTDIR\share\images\download_in_progress.png"
|
||||||
Delete "$INSTDIR\share\images\download_in_progress_none.png"
|
Delete "$INSTDIR\share\images\download_in_progress_none.png"
|
||||||
|
Delete "$INSTDIR\share\images\download_window_gray.png"
|
||||||
|
Delete "$INSTDIR\share\images\download_window_green.png"
|
||||||
Delete "$INSTDIR\share\images\favicon.ico"
|
Delete "$INSTDIR\share\images\favicon.ico"
|
||||||
Delete "$INSTDIR\share\images\file_delete.png"
|
Delete "$INSTDIR\share\images\file_delete.png"
|
||||||
Delete "$INSTDIR\share\images\info.png"
|
Delete "$INSTDIR\share\images\info.png"
|
||||||
|
@ -405,7 +418,6 @@ FunctionEnd
|
||||||
Delete "$INSTDIR\share\images\settings.png"
|
Delete "$INSTDIR\share\images\settings.png"
|
||||||
Delete "$INSTDIR\share\images\web_file.png"
|
Delete "$INSTDIR\share\images\web_file.png"
|
||||||
Delete "$INSTDIR\share\images\web_folder.png"
|
Delete "$INSTDIR\share\images\web_folder.png"
|
||||||
Delete "$INSTDIR\share\license.txt"
|
|
||||||
Delete "$INSTDIR\share\locale\cs.json"
|
Delete "$INSTDIR\share\locale\cs.json"
|
||||||
Delete "$INSTDIR\share\locale\de.json"
|
Delete "$INSTDIR\share\locale\de.json"
|
||||||
Delete "$INSTDIR\share\locale\en.json"
|
Delete "$INSTDIR\share\locale\en.json"
|
||||||
|
@ -466,6 +478,7 @@ FunctionEnd
|
||||||
rmDir "$INSTDIR\lib2to3\tests\data"
|
rmDir "$INSTDIR\lib2to3\tests\data"
|
||||||
rmDir "$INSTDIR\lib2to3\tests"
|
rmDir "$INSTDIR\lib2to3\tests"
|
||||||
rmDir "$INSTDIR\lib2to3"
|
rmDir "$INSTDIR\lib2to3"
|
||||||
|
rmDir "$INSTDIR\licenses"
|
||||||
rmDir "$INSTDIR\PyQt5\Qt\bin"
|
rmDir "$INSTDIR\PyQt5\Qt\bin"
|
||||||
rmDir "$INSTDIR\PyQt5\Qt\plugins\iconengines"
|
rmDir "$INSTDIR\PyQt5\Qt\plugins\iconengines"
|
||||||
rmDir "$INSTDIR\PyQt5\Qt\plugins\imageformats"
|
rmDir "$INSTDIR\PyQt5\Qt\plugins\imageformats"
|
||||||
|
|
|
@ -10,7 +10,6 @@ a = Analysis(
|
||||||
pathex=['.'],
|
pathex=['.'],
|
||||||
binaries=None,
|
binaries=None,
|
||||||
datas=[
|
datas=[
|
||||||
('../share/license.txt', 'share'),
|
|
||||||
('../share/version.txt', 'share'),
|
('../share/version.txt', 'share'),
|
||||||
('../share/wordlist.txt', 'share'),
|
('../share/wordlist.txt', 'share'),
|
||||||
('../share/torrc_template', 'share'),
|
('../share/torrc_template', 'share'),
|
||||||
|
@ -20,7 +19,9 @@ a = Analysis(
|
||||||
('../share/torrc_template-windows', 'share'),
|
('../share/torrc_template-windows', 'share'),
|
||||||
('../share/images/*', 'share/images'),
|
('../share/images/*', 'share/images'),
|
||||||
('../share/locale/*', 'share/locale'),
|
('../share/locale/*', 'share/locale'),
|
||||||
('../share/html/*', 'share/html')
|
('../share/static/*', 'share/static'),
|
||||||
|
('../share/templates/*', 'share/templates'),
|
||||||
|
('../install/licenses/*', 'licenses')
|
||||||
],
|
],
|
||||||
hiddenimports=[],
|
hiddenimports=[],
|
||||||
hookspath=[],
|
hookspath=[],
|
||||||
|
|
|
@ -8,6 +8,7 @@ pefile==2017.11.5
|
||||||
PyInstaller==3.3.1
|
PyInstaller==3.3.1
|
||||||
PyQt5==5.9.2
|
PyQt5==5.9.2
|
||||||
PySocks==1.6.7
|
PySocks==1.6.7
|
||||||
|
requests==2.19.1
|
||||||
sip==4.19.6
|
sip==4.19.6
|
||||||
stem==1.6.0
|
stem==1.6.0
|
||||||
Werkzeug==0.14.1
|
Werkzeug==0.14.1
|
||||||
|
|
|
@ -6,6 +6,7 @@ MarkupSafe==1.0
|
||||||
PyInstaller==3.3.1
|
PyInstaller==3.3.1
|
||||||
PyQt5==5.9.2
|
PyQt5==5.9.2
|
||||||
PySocks==1.6.7
|
PySocks==1.6.7
|
||||||
|
requests==2.19.1
|
||||||
sip==4.19.6
|
sip==4.19.6
|
||||||
stem==1.6.0
|
stem==1.6.0
|
||||||
Werkzeug==0.14.1
|
Werkzeug==0.14.1
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
"""
|
"""
|
||||||
OnionShare | https://onionshare.org/
|
OnionShare | https://onionshare.org/
|
||||||
|
|
||||||
Copyright (C) 2017 Micah Lee <micah@micahflee.com>
|
Copyright (C) 2018 Micah Lee <micah@micahflee.com>
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
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
|
it under the terms of the GNU General Public License as published by
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
"""
|
"""
|
||||||
OnionShare | https://onionshare.org/
|
OnionShare | https://onionshare.org/
|
||||||
|
|
||||||
Copyright (C) 2017 Micah Lee <micah@micahflee.com>
|
Copyright (C) 2018 Micah Lee <micah@micahflee.com>
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
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
|
it under the terms of the GNU General Public License as published by
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
"""
|
"""
|
||||||
OnionShare | https://onionshare.org/
|
OnionShare | https://onionshare.org/
|
||||||
|
|
||||||
Copyright (C) 2017 Micah Lee <micah@micahflee.com>
|
Copyright (C) 2018 Micah Lee <micah@micahflee.com>
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
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
|
it under the terms of the GNU General Public License as published by
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
"""
|
"""
|
||||||
OnionShare | https://onionshare.org/
|
OnionShare | https://onionshare.org/
|
||||||
|
|
||||||
Copyright (C) 2017 Micah Lee <micah@micahflee.com>
|
Copyright (C) 2018 Micah Lee <micah@micahflee.com>
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
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
|
it under the terms of the GNU General Public License as published by
|
||||||
|
@ -20,21 +20,24 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
import os, sys, time, argparse, threading
|
import os, sys, time, argparse, threading
|
||||||
|
|
||||||
from . import strings, common, web
|
from . import strings
|
||||||
|
from .common import Common
|
||||||
|
from .web import Web
|
||||||
from .onion import *
|
from .onion import *
|
||||||
from .onionshare import OnionShare
|
from .onionshare import OnionShare
|
||||||
from .settings import Settings
|
|
||||||
|
|
||||||
def main(cwd=None):
|
def main(cwd=None):
|
||||||
"""
|
"""
|
||||||
The main() function implements all of the logic that the command-line version of
|
The main() function implements all of the logic that the command-line version of
|
||||||
onionshare uses.
|
onionshare uses.
|
||||||
"""
|
"""
|
||||||
|
common = Common()
|
||||||
|
|
||||||
strings.load_strings(common)
|
strings.load_strings(common)
|
||||||
print(strings._('version_string').format(common.get_version()))
|
print(strings._('version_string').format(common.version))
|
||||||
|
|
||||||
# OnionShare CLI in OSX needs to change current working directory (#132)
|
# OnionShare CLI in OSX needs to change current working directory (#132)
|
||||||
if common.get_platform() == 'Darwin':
|
if common.platform == 'Darwin':
|
||||||
if cwd:
|
if cwd:
|
||||||
os.chdir(cwd)
|
os.chdir(cwd)
|
||||||
|
|
||||||
|
@ -44,9 +47,10 @@ def main(cwd=None):
|
||||||
parser.add_argument('--stay-open', action='store_true', dest='stay_open', help=strings._("help_stay_open"))
|
parser.add_argument('--stay-open', action='store_true', dest='stay_open', help=strings._("help_stay_open"))
|
||||||
parser.add_argument('--shutdown-timeout', metavar='<int>', dest='shutdown_timeout', default=0, help=strings._("help_shutdown_timeout"))
|
parser.add_argument('--shutdown-timeout', metavar='<int>', dest='shutdown_timeout', default=0, help=strings._("help_shutdown_timeout"))
|
||||||
parser.add_argument('--stealth', action='store_true', dest='stealth', help=strings._("help_stealth"))
|
parser.add_argument('--stealth', action='store_true', dest='stealth', help=strings._("help_stealth"))
|
||||||
parser.add_argument('--debug', action='store_true', dest='debug', help=strings._("help_debug"))
|
parser.add_argument('--receive', action='store_true', dest='receive', help=strings._("help_receive"))
|
||||||
parser.add_argument('--config', metavar='config', default=False, help=strings._('help_config'))
|
parser.add_argument('--config', metavar='config', default=False, help=strings._('help_config'))
|
||||||
parser.add_argument('filename', metavar='filename', nargs='+', help=strings._('help_filename'))
|
parser.add_argument('--debug', action='store_true', dest='debug', help=strings._("help_debug"))
|
||||||
|
parser.add_argument('filename', metavar='filename', nargs='*', help=strings._('help_filename'))
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
|
||||||
filenames = args.filename
|
filenames = args.filename
|
||||||
|
@ -58,14 +62,16 @@ def main(cwd=None):
|
||||||
stay_open = bool(args.stay_open)
|
stay_open = bool(args.stay_open)
|
||||||
shutdown_timeout = int(args.shutdown_timeout)
|
shutdown_timeout = int(args.shutdown_timeout)
|
||||||
stealth = bool(args.stealth)
|
stealth = bool(args.stealth)
|
||||||
|
receive = bool(args.receive)
|
||||||
config = args.config
|
config = args.config
|
||||||
|
|
||||||
# Debug mode?
|
# Make sure filenames given if not using receiver mode
|
||||||
if debug:
|
if not receive and len(filenames) == 0:
|
||||||
common.set_debug(debug)
|
print(strings._('no_filenames'))
|
||||||
web.debug_mode()
|
sys.exit()
|
||||||
|
|
||||||
# Validation
|
# Validate filenames
|
||||||
|
if not receive:
|
||||||
valid = True
|
valid = True
|
||||||
for filename in filenames:
|
for filename in filenames:
|
||||||
if not os.path.isfile(filename) and not os.path.isdir(filename):
|
if not os.path.isfile(filename) and not os.path.isdir(filename):
|
||||||
|
@ -77,13 +83,34 @@ def main(cwd=None):
|
||||||
if not valid:
|
if not valid:
|
||||||
sys.exit()
|
sys.exit()
|
||||||
|
|
||||||
|
# Load settings
|
||||||
|
common.load_settings(config)
|
||||||
|
|
||||||
settings = Settings(config)
|
# Debug mode?
|
||||||
|
common.debug = debug
|
||||||
|
|
||||||
|
# In receive mode, validate downloads dir
|
||||||
|
if receive:
|
||||||
|
valid = True
|
||||||
|
if not os.path.isdir(common.settings.get('downloads_dir')):
|
||||||
|
try:
|
||||||
|
os.mkdir(common.settings.get('downloads_dir'), 0o700)
|
||||||
|
except:
|
||||||
|
print(strings._('error_cannot_create_downloads_dir').format(common.settings.get('downloads_dir')))
|
||||||
|
valid = False
|
||||||
|
if valid and not os.access(common.settings.get('downloads_dir'), os.W_OK):
|
||||||
|
print(strings._('error_downloads_dir_not_writable').format(common.settings.get('downloads_dir')))
|
||||||
|
valid = False
|
||||||
|
if not valid:
|
||||||
|
sys.exit()
|
||||||
|
|
||||||
|
# Create the Web object
|
||||||
|
web = Web(common, stay_open, False, receive)
|
||||||
|
|
||||||
# Start the Onion object
|
# Start the Onion object
|
||||||
onion = Onion()
|
onion = Onion(common)
|
||||||
try:
|
try:
|
||||||
onion.connect(settings=False, config=config)
|
onion.connect(custom_settings=False, config=config)
|
||||||
except (TorTooOld, TorErrorInvalidSetting, TorErrorAutomatic, TorErrorSocketPort, TorErrorSocketFile, TorErrorMissingPassword, TorErrorUnreadableCookieFile, TorErrorAuthError, TorErrorProtocolError, BundledTorNotSupported, BundledTorTimeout) as e:
|
except (TorTooOld, TorErrorInvalidSetting, TorErrorAutomatic, TorErrorSocketPort, TorErrorSocketFile, TorErrorMissingPassword, TorErrorUnreadableCookieFile, TorErrorAuthError, TorErrorProtocolError, BundledTorNotSupported, BundledTorTimeout) as e:
|
||||||
sys.exit(e.args[0])
|
sys.exit(e.args[0])
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
|
@ -92,7 +119,7 @@ def main(cwd=None):
|
||||||
|
|
||||||
# Start the onionshare app
|
# Start the onionshare app
|
||||||
try:
|
try:
|
||||||
app = OnionShare(onion, local_only, stay_open, shutdown_timeout)
|
app = OnionShare(common, onion, local_only, stay_open, shutdown_timeout)
|
||||||
app.set_stealth(stealth)
|
app.set_stealth(stealth)
|
||||||
app.start_onion_service()
|
app.start_onion_service()
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
|
@ -115,8 +142,7 @@ def main(cwd=None):
|
||||||
print('')
|
print('')
|
||||||
|
|
||||||
# Start OnionShare http service in new thread
|
# Start OnionShare http service in new thread
|
||||||
settings.load()
|
t = threading.Thread(target=web.start, args=(app.port, app.stay_open, common.settings.get('slug')))
|
||||||
t = threading.Thread(target=web.start, args=(app.port, app.stay_open, settings.get('slug')))
|
|
||||||
t.daemon = True
|
t.daemon = True
|
||||||
t.start()
|
t.start()
|
||||||
|
|
||||||
|
@ -129,12 +155,27 @@ def main(cwd=None):
|
||||||
app.shutdown_timer.start()
|
app.shutdown_timer.start()
|
||||||
|
|
||||||
# Save the web slug if we are using a persistent private key
|
# Save the web slug if we are using a persistent private key
|
||||||
if settings.get('save_private_key'):
|
if common.settings.get('save_private_key'):
|
||||||
if not settings.get('slug'):
|
if not common.settings.get('slug'):
|
||||||
settings.set('slug', web.slug)
|
common.settings.set('slug', web.slug)
|
||||||
settings.save()
|
common.settings.save()
|
||||||
|
|
||||||
if(stealth):
|
print('')
|
||||||
|
if receive:
|
||||||
|
print(strings._('receive_mode_downloads_dir').format(common.settings.get('downloads_dir')))
|
||||||
|
print('')
|
||||||
|
print(strings._('receive_mode_warning'))
|
||||||
|
print('')
|
||||||
|
|
||||||
|
if stealth:
|
||||||
|
print(strings._("give_this_url_receive_stealth"))
|
||||||
|
print('http://{0:s}/{1:s}'.format(app.onion_host, web.slug))
|
||||||
|
print(app.auth_string)
|
||||||
|
else:
|
||||||
|
print(strings._("give_this_url_receive"))
|
||||||
|
print('http://{0:s}/{1:s}'.format(app.onion_host, web.slug))
|
||||||
|
else:
|
||||||
|
if stealth:
|
||||||
print(strings._("give_this_url_stealth"))
|
print(strings._("give_this_url_stealth"))
|
||||||
print('http://{0:s}/{1:s}'.format(app.onion_host, web.slug))
|
print('http://{0:s}/{1:s}'.format(app.onion_host, web.slug))
|
||||||
print(app.auth_string)
|
print(app.auth_string)
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
"""
|
"""
|
||||||
OnionShare | https://onionshare.org/
|
OnionShare | https://onionshare.org/
|
||||||
|
|
||||||
Copyright (C) 2017 Micah Lee <micah@micahflee.com>
|
Copyright (C) 2018 Micah Lee <micah@micahflee.com>
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
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
|
it under the terms of the GNU General Public License as published by
|
||||||
|
@ -28,17 +28,37 @@ import sys
|
||||||
import tempfile
|
import tempfile
|
||||||
import threading
|
import threading
|
||||||
import time
|
import time
|
||||||
import zipfile
|
|
||||||
|
|
||||||
debug = False
|
from .settings import Settings
|
||||||
|
|
||||||
|
class Common(object):
|
||||||
|
"""
|
||||||
|
The Common object is shared amongst all parts of OnionShare.
|
||||||
|
"""
|
||||||
|
def __init__(self, debug=False):
|
||||||
|
self.debug = debug
|
||||||
|
|
||||||
def log(module, func, msg=None):
|
# The platform OnionShare is running on
|
||||||
|
self.platform = platform.system()
|
||||||
|
if self.platform.endswith('BSD'):
|
||||||
|
self.platform = 'BSD'
|
||||||
|
|
||||||
|
# The current version of OnionShare
|
||||||
|
with open(self.get_resource_path('version.txt')) as f:
|
||||||
|
self.version = f.read().strip()
|
||||||
|
|
||||||
|
def load_settings(self, config=None):
|
||||||
|
"""
|
||||||
|
Loading settings, optionally from a custom config json file.
|
||||||
|
"""
|
||||||
|
self.settings = Settings(self, config)
|
||||||
|
self.settings.load()
|
||||||
|
|
||||||
|
def log(self, module, func, msg=None):
|
||||||
"""
|
"""
|
||||||
If debug mode is on, log error messages to stdout
|
If debug mode is on, log error messages to stdout
|
||||||
"""
|
"""
|
||||||
global debug
|
if self.debug:
|
||||||
if debug:
|
|
||||||
timestamp = time.strftime("%b %d %Y %X")
|
timestamp = time.strftime("%b %d %Y %X")
|
||||||
|
|
||||||
final_msg = "[{}] {}.{}".format(timestamp, module, func)
|
final_msg = "[{}] {}.{}".format(timestamp, module, func)
|
||||||
|
@ -46,31 +66,13 @@ def log(module, func, msg=None):
|
||||||
final_msg = '{}: {}'.format(final_msg, msg)
|
final_msg = '{}: {}'.format(final_msg, msg)
|
||||||
print(final_msg)
|
print(final_msg)
|
||||||
|
|
||||||
|
def get_resource_path(self, filename):
|
||||||
def set_debug(new_debug):
|
|
||||||
global debug
|
|
||||||
debug = new_debug
|
|
||||||
|
|
||||||
|
|
||||||
def get_platform():
|
|
||||||
"""
|
|
||||||
Returns the platform OnionShare is running on.
|
|
||||||
"""
|
|
||||||
plat = platform.system()
|
|
||||||
if plat.endswith('BSD'):
|
|
||||||
plat = 'BSD'
|
|
||||||
return plat
|
|
||||||
|
|
||||||
|
|
||||||
def get_resource_path(filename):
|
|
||||||
"""
|
"""
|
||||||
Returns the absolute path of a resource, regardless of whether OnionShare is installed
|
Returns the absolute path of a resource, regardless of whether OnionShare is installed
|
||||||
systemwide, and whether regardless of platform
|
systemwide, and whether regardless of platform
|
||||||
"""
|
"""
|
||||||
p = get_platform()
|
|
||||||
|
|
||||||
# On Windows, and in Windows dev mode, switch slashes in incoming filename to backslackes
|
# On Windows, and in Windows dev mode, switch slashes in incoming filename to backslackes
|
||||||
if p == 'Windows':
|
if self.platform == 'Windows':
|
||||||
filename = filename.replace('/', '\\')
|
filename = filename.replace('/', '\\')
|
||||||
|
|
||||||
if getattr(sys, 'onionshare_dev_mode', False):
|
if getattr(sys, 'onionshare_dev_mode', False):
|
||||||
|
@ -80,41 +82,39 @@ def get_resource_path(filename):
|
||||||
# While running tests during stdeb bdist_deb, look 3 directories up for the share folder
|
# While running tests during stdeb bdist_deb, look 3 directories up for the share folder
|
||||||
prefix = os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(prefix)))), 'share')
|
prefix = os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(prefix)))), 'share')
|
||||||
|
|
||||||
elif p == 'BSD' or p == 'Linux':
|
elif self.platform == 'BSD' or self.platform == 'Linux':
|
||||||
# Assume OnionShare is installed systemwide in Linux, since we're not running in dev mode
|
# Assume OnionShare is installed systemwide in Linux, since we're not running in dev mode
|
||||||
prefix = os.path.join(sys.prefix, 'share/onionshare')
|
prefix = os.path.join(sys.prefix, 'share/onionshare')
|
||||||
|
|
||||||
elif getattr(sys, 'frozen', False):
|
elif getattr(sys, 'frozen', False):
|
||||||
# Check if app is "frozen"
|
# Check if app is "frozen"
|
||||||
# https://pythonhosted.org/PyInstaller/#run-time-information
|
# https://pythonhosted.org/PyInstaller/#run-time-information
|
||||||
if p == 'Darwin':
|
if self.platform == 'Darwin':
|
||||||
prefix = os.path.join(sys._MEIPASS, 'share')
|
prefix = os.path.join(sys._MEIPASS, 'share')
|
||||||
elif p == 'Windows':
|
elif self.platform == 'Windows':
|
||||||
prefix = os.path.join(os.path.dirname(sys.executable), 'share')
|
prefix = os.path.join(os.path.dirname(sys.executable), 'share')
|
||||||
|
|
||||||
return os.path.join(prefix, filename)
|
return os.path.join(prefix, filename)
|
||||||
|
|
||||||
|
def get_tor_paths(self):
|
||||||
def get_tor_paths():
|
if self.platform == 'Linux':
|
||||||
p = get_platform()
|
|
||||||
if p == 'Linux':
|
|
||||||
tor_path = '/usr/bin/tor'
|
tor_path = '/usr/bin/tor'
|
||||||
tor_geo_ip_file_path = '/usr/share/tor/geoip'
|
tor_geo_ip_file_path = '/usr/share/tor/geoip'
|
||||||
tor_geo_ipv6_file_path = '/usr/share/tor/geoip6'
|
tor_geo_ipv6_file_path = '/usr/share/tor/geoip6'
|
||||||
obfs4proxy_file_path = '/usr/bin/obfs4proxy'
|
obfs4proxy_file_path = '/usr/bin/obfs4proxy'
|
||||||
elif p == 'Windows':
|
elif self.platform == 'Windows':
|
||||||
base_path = os.path.join(os.path.dirname(os.path.dirname(get_resource_path(''))), 'tor')
|
base_path = os.path.join(os.path.dirname(os.path.dirname(self.get_resource_path(''))), 'tor')
|
||||||
tor_path = os.path.join(os.path.join(base_path, 'Tor'), 'tor.exe')
|
tor_path = os.path.join(os.path.join(base_path, 'Tor'), 'tor.exe')
|
||||||
obfs4proxy_file_path = os.path.join(os.path.join(base_path, 'Tor'), 'obfs4proxy.exe')
|
obfs4proxy_file_path = os.path.join(os.path.join(base_path, 'Tor'), 'obfs4proxy.exe')
|
||||||
tor_geo_ip_file_path = os.path.join(os.path.join(os.path.join(base_path, 'Data'), 'Tor'), 'geoip')
|
tor_geo_ip_file_path = os.path.join(os.path.join(os.path.join(base_path, 'Data'), 'Tor'), 'geoip')
|
||||||
tor_geo_ipv6_file_path = os.path.join(os.path.join(os.path.join(base_path, 'Data'), 'Tor'), 'geoip6')
|
tor_geo_ipv6_file_path = os.path.join(os.path.join(os.path.join(base_path, 'Data'), 'Tor'), 'geoip6')
|
||||||
elif p == 'Darwin':
|
elif self.platform == 'Darwin':
|
||||||
base_path = os.path.dirname(os.path.dirname(os.path.dirname(get_resource_path(''))))
|
base_path = os.path.dirname(os.path.dirname(os.path.dirname(self.get_resource_path(''))))
|
||||||
tor_path = os.path.join(base_path, 'Resources', 'Tor', 'tor')
|
tor_path = os.path.join(base_path, 'Resources', 'Tor', 'tor')
|
||||||
tor_geo_ip_file_path = os.path.join(base_path, 'Resources', 'Tor', 'geoip')
|
tor_geo_ip_file_path = os.path.join(base_path, 'Resources', 'Tor', 'geoip')
|
||||||
tor_geo_ipv6_file_path = os.path.join(base_path, 'Resources', 'Tor', 'geoip6')
|
tor_geo_ipv6_file_path = os.path.join(base_path, 'Resources', 'Tor', 'geoip6')
|
||||||
obfs4proxy_file_path = os.path.join(base_path, 'Resources', 'Tor', 'obfs4proxy')
|
obfs4proxy_file_path = os.path.join(base_path, 'Resources', 'Tor', 'obfs4proxy')
|
||||||
elif p == 'BSD':
|
elif self.platform == 'BSD':
|
||||||
tor_path = '/usr/local/bin/tor'
|
tor_path = '/usr/local/bin/tor'
|
||||||
tor_geo_ip_file_path = '/usr/local/share/tor/geoip'
|
tor_geo_ip_file_path = '/usr/local/share/tor/geoip'
|
||||||
tor_geo_ipv6_file_path = '/usr/local/share/tor/geoip6'
|
tor_geo_ipv6_file_path = '/usr/local/share/tor/geoip6'
|
||||||
|
@ -122,16 +122,17 @@ def get_tor_paths():
|
||||||
|
|
||||||
return (tor_path, tor_geo_ip_file_path, tor_geo_ipv6_file_path, obfs4proxy_file_path)
|
return (tor_path, tor_geo_ip_file_path, tor_geo_ipv6_file_path, obfs4proxy_file_path)
|
||||||
|
|
||||||
|
def build_slug(self):
|
||||||
def get_version():
|
|
||||||
"""
|
"""
|
||||||
Returns the version of OnionShare that is running.
|
Returns a random string made from two words from the wordlist, such as "deter-trig".
|
||||||
"""
|
"""
|
||||||
with open(get_resource_path('version.txt')) as f:
|
with open(self.get_resource_path('wordlist.txt')) as f:
|
||||||
version = f.read().strip()
|
wordlist = f.read().split()
|
||||||
return version
|
|
||||||
|
|
||||||
|
r = random.SystemRandom()
|
||||||
|
return '-'.join(r.choice(wordlist) for _ in range(2))
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
def random_string(num_bytes, output_len=None):
|
def random_string(num_bytes, output_len=None):
|
||||||
"""
|
"""
|
||||||
Returns a random string with a specified number of bytes.
|
Returns a random string with a specified number of bytes.
|
||||||
|
@ -143,18 +144,7 @@ def random_string(num_bytes, output_len=None):
|
||||||
return s
|
return s
|
||||||
return s[:output_len]
|
return s[:output_len]
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
def build_slug():
|
|
||||||
"""
|
|
||||||
Returns a random string made from two words from the wordlist, such as "deter-trig".
|
|
||||||
"""
|
|
||||||
with open(get_resource_path('wordlist.txt')) as f:
|
|
||||||
wordlist = f.read().split()
|
|
||||||
|
|
||||||
r = random.SystemRandom()
|
|
||||||
return '-'.join(r.choice(wordlist) for _ in range(2))
|
|
||||||
|
|
||||||
|
|
||||||
def human_readable_filesize(b):
|
def human_readable_filesize(b):
|
||||||
"""
|
"""
|
||||||
Returns filesize in a human readable format.
|
Returns filesize in a human readable format.
|
||||||
|
@ -170,7 +160,7 @@ def human_readable_filesize(b):
|
||||||
u += 1
|
u += 1
|
||||||
return '{:.1f} {}'.format(b, units[u])
|
return '{:.1f} {}'.format(b, units[u])
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
def format_seconds(seconds):
|
def format_seconds(seconds):
|
||||||
"""Return a human-readable string of the format 1d2h3m4s"""
|
"""Return a human-readable string of the format 1d2h3m4s"""
|
||||||
days, seconds = divmod(seconds, 86400)
|
days, seconds = divmod(seconds, 86400)
|
||||||
|
@ -188,16 +178,16 @@ def format_seconds(seconds):
|
||||||
human_readable.append("{:.0f}s".format(seconds))
|
human_readable.append("{:.0f}s".format(seconds))
|
||||||
return ''.join(human_readable)
|
return ''.join(human_readable)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
def estimated_time_remaining(bytes_downloaded, total_bytes, started):
|
def estimated_time_remaining(bytes_downloaded, total_bytes, started):
|
||||||
now = time.time()
|
now = time.time()
|
||||||
time_elapsed = now - started # in seconds
|
time_elapsed = now - started # in seconds
|
||||||
download_rate = bytes_downloaded / time_elapsed
|
download_rate = bytes_downloaded / time_elapsed
|
||||||
remaining_bytes = total_bytes - bytes_downloaded
|
remaining_bytes = total_bytes - bytes_downloaded
|
||||||
eta = remaining_bytes / download_rate
|
eta = remaining_bytes / download_rate
|
||||||
return format_seconds(eta)
|
return Common.format_seconds(eta)
|
||||||
|
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
def get_available_port(min_port, max_port):
|
def get_available_port(min_port, max_port):
|
||||||
"""
|
"""
|
||||||
Find a random available port within the given range.
|
Find a random available port within the given range.
|
||||||
|
@ -212,7 +202,7 @@ def get_available_port(min_port, max_port):
|
||||||
_, port = tmpsock.getsockname()
|
_, port = tmpsock.getsockname()
|
||||||
return port
|
return port
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
def dir_size(start_path):
|
def dir_size(start_path):
|
||||||
"""
|
"""
|
||||||
Calculates the total size, in bytes, of all of the files in a directory.
|
Calculates the total size, in bytes, of all of the files in a directory.
|
||||||
|
@ -226,64 +216,19 @@ def dir_size(start_path):
|
||||||
return total_size
|
return total_size
|
||||||
|
|
||||||
|
|
||||||
class ZipWriter(object):
|
class ShutdownTimer(threading.Thread):
|
||||||
"""
|
|
||||||
ZipWriter accepts files and directories and compresses them into a zip file
|
|
||||||
with. If a zip_filename is not passed in, it will use the default onionshare
|
|
||||||
filename.
|
|
||||||
"""
|
|
||||||
def __init__(self, zip_filename=None, processed_size_callback=None):
|
|
||||||
if zip_filename:
|
|
||||||
self.zip_filename = zip_filename
|
|
||||||
else:
|
|
||||||
self.zip_filename = '{0:s}/onionshare_{1:s}.zip'.format(tempfile.mkdtemp(), random_string(4, 6))
|
|
||||||
|
|
||||||
self.z = zipfile.ZipFile(self.zip_filename, 'w', allowZip64=True)
|
|
||||||
self.processed_size_callback = processed_size_callback
|
|
||||||
if self.processed_size_callback is None:
|
|
||||||
self.processed_size_callback = lambda _: None
|
|
||||||
self._size = 0
|
|
||||||
self.processed_size_callback(self._size)
|
|
||||||
|
|
||||||
def add_file(self, filename):
|
|
||||||
"""
|
|
||||||
Add a file to the zip archive.
|
|
||||||
"""
|
|
||||||
self.z.write(filename, os.path.basename(filename), zipfile.ZIP_DEFLATED)
|
|
||||||
self._size += os.path.getsize(filename)
|
|
||||||
self.processed_size_callback(self._size)
|
|
||||||
|
|
||||||
def add_dir(self, filename):
|
|
||||||
"""
|
|
||||||
Add a directory, and all of its children, to the zip archive.
|
|
||||||
"""
|
|
||||||
dir_to_strip = os.path.dirname(filename.rstrip('/'))+'/'
|
|
||||||
for dirpath, dirnames, filenames in os.walk(filename):
|
|
||||||
for f in filenames:
|
|
||||||
full_filename = os.path.join(dirpath, f)
|
|
||||||
if not os.path.islink(full_filename):
|
|
||||||
arc_filename = full_filename[len(dir_to_strip):]
|
|
||||||
self.z.write(full_filename, arc_filename, zipfile.ZIP_DEFLATED)
|
|
||||||
self._size += os.path.getsize(full_filename)
|
|
||||||
self.processed_size_callback(self._size)
|
|
||||||
|
|
||||||
def close(self):
|
|
||||||
"""
|
|
||||||
Close the zip archive.
|
|
||||||
"""
|
|
||||||
self.z.close()
|
|
||||||
|
|
||||||
|
|
||||||
class close_after_seconds(threading.Thread):
|
|
||||||
"""
|
"""
|
||||||
Background thread sleeps t hours and returns.
|
Background thread sleeps t hours and returns.
|
||||||
"""
|
"""
|
||||||
def __init__(self, time):
|
def __init__(self, common, time):
|
||||||
threading.Thread.__init__(self)
|
threading.Thread.__init__(self)
|
||||||
|
|
||||||
|
self.common = common
|
||||||
|
|
||||||
self.setDaemon(True)
|
self.setDaemon(True)
|
||||||
self.time = time
|
self.time = time
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
log('Shutdown Timer', 'Server will shut down after {} seconds'.format(self.time))
|
self.common.log('Shutdown Timer', 'Server will shut down after {} seconds'.format(self.time))
|
||||||
time.sleep(self.time)
|
time.sleep(self.time)
|
||||||
return 1
|
return 1
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
"""
|
"""
|
||||||
OnionShare | https://onionshare.org/
|
OnionShare | https://onionshare.org/
|
||||||
|
|
||||||
Copyright (C) 2017 Micah Lee <micah@micahflee.com>
|
Copyright (C) 2018 Micah Lee <micah@micahflee.com>
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
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
|
it under the terms of the GNU General Public License as published by
|
||||||
|
@ -125,22 +125,22 @@ class Onion(object):
|
||||||
call this function and pass in a status string while connecting to tor. This
|
call this function and pass in a status string while connecting to tor. This
|
||||||
is necessary for status updates to reach the GUI.
|
is necessary for status updates to reach the GUI.
|
||||||
"""
|
"""
|
||||||
def __init__(self):
|
def __init__(self, common):
|
||||||
common.log('Onion', '__init__')
|
self.common = common
|
||||||
|
|
||||||
|
self.common.log('Onion', '__init__')
|
||||||
|
|
||||||
self.stealth = False
|
self.stealth = False
|
||||||
self.service_id = None
|
self.service_id = None
|
||||||
|
|
||||||
self.system = common.get_platform()
|
|
||||||
|
|
||||||
# Is bundled tor supported?
|
# Is bundled tor supported?
|
||||||
if (self.system == 'Windows' or self.system == 'Darwin') and getattr(sys, 'onionshare_dev_mode', False):
|
if (self.common.platform == 'Windows' or self.common.platform == 'Darwin') and getattr(sys, 'onionshare_dev_mode', False):
|
||||||
self.bundle_tor_supported = False
|
self.bundle_tor_supported = False
|
||||||
else:
|
else:
|
||||||
self.bundle_tor_supported = True
|
self.bundle_tor_supported = True
|
||||||
|
|
||||||
# Set the path of the tor binary, for bundled tor
|
# Set the path of the tor binary, for bundled tor
|
||||||
(self.tor_path, self.tor_geo_ip_file_path, self.tor_geo_ipv6_file_path, self.obfs4proxy_file_path) = common.get_tor_paths()
|
(self.tor_path, self.tor_geo_ip_file_path, self.tor_geo_ipv6_file_path, self.obfs4proxy_file_path) = self.common.get_tor_paths()
|
||||||
|
|
||||||
# The tor process
|
# The tor process
|
||||||
self.tor_proc = None
|
self.tor_proc = None
|
||||||
|
@ -148,15 +148,14 @@ class Onion(object):
|
||||||
# Start out not connected to Tor
|
# Start out not connected to Tor
|
||||||
self.connected_to_tor = False
|
self.connected_to_tor = False
|
||||||
|
|
||||||
def connect(self, settings=False, config=False, tor_status_update_func=None):
|
def connect(self, custom_settings=False, config=False, tor_status_update_func=None):
|
||||||
common.log('Onion', 'connect')
|
self.common.log('Onion', 'connect')
|
||||||
|
|
||||||
# Either use settings that are passed in, or load them from disk
|
# Either use settings that are passed in, or use them from common
|
||||||
if settings:
|
if custom_settings:
|
||||||
self.settings = settings
|
self.settings = custom_settings
|
||||||
else:
|
else:
|
||||||
self.settings = Settings(config)
|
self.settings = self.common.settings
|
||||||
self.settings.load()
|
|
||||||
|
|
||||||
# The Tor controller
|
# The Tor controller
|
||||||
self.c = None
|
self.c = None
|
||||||
|
@ -168,29 +167,29 @@ class Onion(object):
|
||||||
# Create a torrc for this session
|
# Create a torrc for this session
|
||||||
self.tor_data_directory = tempfile.TemporaryDirectory()
|
self.tor_data_directory = tempfile.TemporaryDirectory()
|
||||||
|
|
||||||
if self.system == 'Windows':
|
if self.common.platform == 'Windows':
|
||||||
# Windows needs to use network ports, doesn't support unix sockets
|
# Windows needs to use network ports, doesn't support unix sockets
|
||||||
torrc_template = open(common.get_resource_path('torrc_template-windows')).read()
|
torrc_template = open(self.common.get_resource_path('torrc_template-windows')).read()
|
||||||
try:
|
try:
|
||||||
self.tor_control_port = common.get_available_port(1000, 65535)
|
self.tor_control_port = self.common.get_available_port(1000, 65535)
|
||||||
except:
|
except:
|
||||||
raise OSError(strings._('no_available_port'))
|
raise OSError(strings._('no_available_port'))
|
||||||
self.tor_control_socket = None
|
self.tor_control_socket = None
|
||||||
self.tor_cookie_auth_file = os.path.join(self.tor_data_directory.name, 'cookie')
|
self.tor_cookie_auth_file = os.path.join(self.tor_data_directory.name, 'cookie')
|
||||||
try:
|
try:
|
||||||
self.tor_socks_port = common.get_available_port(1000, 65535)
|
self.tor_socks_port = self.common.get_available_port(1000, 65535)
|
||||||
except:
|
except:
|
||||||
raise OSError(strings._('no_available_port'))
|
raise OSError(strings._('no_available_port'))
|
||||||
self.tor_torrc = os.path.join(self.tor_data_directory.name, 'torrc')
|
self.tor_torrc = os.path.join(self.tor_data_directory.name, 'torrc')
|
||||||
else:
|
else:
|
||||||
# Linux, Mac and BSD can use unix sockets
|
# Linux, Mac and BSD can use unix sockets
|
||||||
with open(common.get_resource_path('torrc_template')) as f:
|
with open(self.common.get_resource_path('torrc_template')) as f:
|
||||||
torrc_template = f.read()
|
torrc_template = f.read()
|
||||||
self.tor_control_port = None
|
self.tor_control_port = None
|
||||||
self.tor_control_socket = os.path.join(self.tor_data_directory.name, 'control_socket')
|
self.tor_control_socket = os.path.join(self.tor_data_directory.name, 'control_socket')
|
||||||
self.tor_cookie_auth_file = os.path.join(self.tor_data_directory.name, 'cookie')
|
self.tor_cookie_auth_file = os.path.join(self.tor_data_directory.name, 'cookie')
|
||||||
try:
|
try:
|
||||||
self.tor_socks_port = common.get_available_port(1000, 65535)
|
self.tor_socks_port = self.common.get_available_port(1000, 65535)
|
||||||
except:
|
except:
|
||||||
raise OSError(strings._('no_available_port'))
|
raise OSError(strings._('no_available_port'))
|
||||||
self.tor_torrc = os.path.join(self.tor_data_directory.name, 'torrc')
|
self.tor_torrc = os.path.join(self.tor_data_directory.name, 'torrc')
|
||||||
|
@ -208,17 +207,17 @@ class Onion(object):
|
||||||
# Bridge support
|
# Bridge support
|
||||||
if self.settings.get('tor_bridges_use_obfs4'):
|
if self.settings.get('tor_bridges_use_obfs4'):
|
||||||
f.write('ClientTransportPlugin obfs4 exec {}\n'.format(self.obfs4proxy_file_path))
|
f.write('ClientTransportPlugin obfs4 exec {}\n'.format(self.obfs4proxy_file_path))
|
||||||
with open(common.get_resource_path('torrc_template-obfs4')) as o:
|
with open(self.common.get_resource_path('torrc_template-obfs4')) as o:
|
||||||
for line in o:
|
for line in o:
|
||||||
f.write(line)
|
f.write(line)
|
||||||
elif self.settings.get('tor_bridges_use_meek_lite_amazon'):
|
elif self.settings.get('tor_bridges_use_meek_lite_amazon'):
|
||||||
f.write('ClientTransportPlugin meek_lite exec {}\n'.format(self.obfs4proxy_file_path))
|
f.write('ClientTransportPlugin meek_lite exec {}\n'.format(self.obfs4proxy_file_path))
|
||||||
with open(common.get_resource_path('torrc_template-meek_lite_amazon')) as o:
|
with open(self.common.get_resource_path('torrc_template-meek_lite_amazon')) as o:
|
||||||
for line in o:
|
for line in o:
|
||||||
f.write(line)
|
f.write(line)
|
||||||
elif self.settings.get('tor_bridges_use_meek_lite_azure'):
|
elif self.settings.get('tor_bridges_use_meek_lite_azure'):
|
||||||
f.write('ClientTransportPlugin meek_lite exec {}\n'.format(self.obfs4proxy_file_path))
|
f.write('ClientTransportPlugin meek_lite exec {}\n'.format(self.obfs4proxy_file_path))
|
||||||
with open(common.get_resource_path('torrc_template-meek_lite_azure')) as o:
|
with open(self.common.get_resource_path('torrc_template-meek_lite_azure')) as o:
|
||||||
for line in o:
|
for line in o:
|
||||||
f.write(line)
|
f.write(line)
|
||||||
|
|
||||||
|
@ -232,7 +231,7 @@ class Onion(object):
|
||||||
|
|
||||||
# Execute a tor subprocess
|
# Execute a tor subprocess
|
||||||
start_ts = time.time()
|
start_ts = time.time()
|
||||||
if self.system == 'Windows':
|
if self.common.platform == 'Windows':
|
||||||
# In Windows, hide console window when opening tor.exe subprocess
|
# In Windows, hide console window when opening tor.exe subprocess
|
||||||
startupinfo = subprocess.STARTUPINFO()
|
startupinfo = subprocess.STARTUPINFO()
|
||||||
startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
|
startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
|
||||||
|
@ -245,7 +244,7 @@ class Onion(object):
|
||||||
|
|
||||||
# Connect to the controller
|
# Connect to the controller
|
||||||
try:
|
try:
|
||||||
if self.system == 'Windows':
|
if self.common.platform == 'Windows':
|
||||||
self.c = Controller.from_port(port=self.tor_control_port)
|
self.c = Controller.from_port(port=self.tor_control_port)
|
||||||
self.c.authenticate()
|
self.c.authenticate()
|
||||||
else:
|
else:
|
||||||
|
@ -270,7 +269,7 @@ class Onion(object):
|
||||||
if callable(tor_status_update_func):
|
if callable(tor_status_update_func):
|
||||||
if not tor_status_update_func(progress, summary):
|
if not tor_status_update_func(progress, summary):
|
||||||
# If the dialog was canceled, stop connecting to Tor
|
# If the dialog was canceled, stop connecting to Tor
|
||||||
common.log('Onion', 'connect', 'tor_status_update_func returned false, canceling connecting to Tor')
|
self.common.log('Onion', 'connect', 'tor_status_update_func returned false, canceling connecting to Tor')
|
||||||
print()
|
print()
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
@ -322,7 +321,7 @@ class Onion(object):
|
||||||
socket_file_path = ''
|
socket_file_path = ''
|
||||||
if not found_tor:
|
if not found_tor:
|
||||||
try:
|
try:
|
||||||
if self.system == 'Darwin':
|
if self.common.platform == 'Darwin':
|
||||||
socket_file_path = os.path.expanduser('~/Library/Application Support/TorBrowser-Data/Tor/control.socket')
|
socket_file_path = os.path.expanduser('~/Library/Application Support/TorBrowser-Data/Tor/control.socket')
|
||||||
|
|
||||||
self.c = Controller.from_socket_file(path=socket_file_path)
|
self.c = Controller.from_socket_file(path=socket_file_path)
|
||||||
|
@ -334,11 +333,11 @@ class Onion(object):
|
||||||
# guessing the socket file name next
|
# guessing the socket file name next
|
||||||
if not found_tor:
|
if not found_tor:
|
||||||
try:
|
try:
|
||||||
if self.system == 'Linux' or self.system == 'BSD':
|
if self.common.platform == 'Linux' or self.common.platform == 'BSD':
|
||||||
socket_file_path = '/run/user/{}/Tor/control.socket'.format(os.geteuid())
|
socket_file_path = '/run/user/{}/Tor/control.socket'.format(os.geteuid())
|
||||||
elif self.system == 'Darwin':
|
elif self.common.platform == 'Darwin':
|
||||||
socket_file_path = '/run/user/{}/Tor/control.socket'.format(os.geteuid())
|
socket_file_path = '/run/user/{}/Tor/control.socket'.format(os.geteuid())
|
||||||
elif self.system == 'Windows':
|
elif self.common.platform == 'Windows':
|
||||||
# Windows doesn't support unix sockets
|
# Windows doesn't support unix sockets
|
||||||
raise TorErrorAutomatic(strings._('settings_error_automatic'))
|
raise TorErrorAutomatic(strings._('settings_error_automatic'))
|
||||||
|
|
||||||
|
@ -424,7 +423,7 @@ class Onion(object):
|
||||||
Start a onion service on port 80, pointing to the given port, and
|
Start a onion service on port 80, pointing to the given port, and
|
||||||
return the onion hostname.
|
return the onion hostname.
|
||||||
"""
|
"""
|
||||||
common.log('Onion', 'start_onion_service')
|
self.common.log('Onion', 'start_onion_service')
|
||||||
|
|
||||||
self.auth_string = None
|
self.auth_string = None
|
||||||
if not self.supports_ephemeral:
|
if not self.supports_ephemeral:
|
||||||
|
@ -447,11 +446,11 @@ class Onion(object):
|
||||||
if self.settings.get('private_key'):
|
if self.settings.get('private_key'):
|
||||||
key_type = "RSA1024"
|
key_type = "RSA1024"
|
||||||
key_content = self.settings.get('private_key')
|
key_content = self.settings.get('private_key')
|
||||||
common.log('Onion', 'Starting a hidden service with a saved private key')
|
self.common.log('Onion', 'start_onion_service', 'Starting a hidden service with a saved private key')
|
||||||
else:
|
else:
|
||||||
key_type = "NEW"
|
key_type = "NEW"
|
||||||
key_content = "RSA1024"
|
key_content = "RSA1024"
|
||||||
common.log('Onion', 'Starting a hidden service with a new private key')
|
self.common.log('Onion', 'start_onion_service', 'Starting a hidden service with a new private key')
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if basic_auth != None:
|
if basic_auth != None:
|
||||||
|
@ -498,17 +497,17 @@ class Onion(object):
|
||||||
"""
|
"""
|
||||||
Stop onion services that were created earlier. If there's a tor subprocess running, kill it.
|
Stop onion services that were created earlier. If there's a tor subprocess running, kill it.
|
||||||
"""
|
"""
|
||||||
common.log('Onion', 'cleanup')
|
self.common.log('Onion', 'cleanup')
|
||||||
|
|
||||||
# Cleanup the ephemeral onion services, if we have any
|
# Cleanup the ephemeral onion services, if we have any
|
||||||
try:
|
try:
|
||||||
onions = self.c.list_ephemeral_hidden_services()
|
onions = self.c.list_ephemeral_hidden_services()
|
||||||
for onion in onions:
|
for onion in onions:
|
||||||
try:
|
try:
|
||||||
common.log('Onion', 'cleanup', 'trying to remove onion {}'.format(onion))
|
self.common.log('Onion', 'cleanup', 'trying to remove onion {}'.format(onion))
|
||||||
self.c.remove_ephemeral_hidden_service(onion)
|
self.c.remove_ephemeral_hidden_service(onion)
|
||||||
except:
|
except:
|
||||||
common.log('Onion', 'cleanup', 'could not remove onion {}.. moving on anyway'.format(onion))
|
self.common.log('Onion', 'cleanup', 'could not remove onion {}.. moving on anyway'.format(onion))
|
||||||
pass
|
pass
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
|
@ -545,7 +544,7 @@ class Onion(object):
|
||||||
"""
|
"""
|
||||||
Returns a (address, port) tuple for the Tor SOCKS port
|
Returns a (address, port) tuple for the Tor SOCKS port
|
||||||
"""
|
"""
|
||||||
common.log('Onion', 'get_tor_socks_port')
|
self.common.log('Onion', 'get_tor_socks_port')
|
||||||
|
|
||||||
if self.settings.get('connection_type') == 'bundled':
|
if self.settings.get('connection_type') == 'bundled':
|
||||||
return ('127.0.0.1', self.tor_socks_port)
|
return ('127.0.0.1', self.tor_socks_port)
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
"""
|
"""
|
||||||
OnionShare | https://onionshare.org/
|
OnionShare | https://onionshare.org/
|
||||||
|
|
||||||
Copyright (C) 2017 Micah Lee <micah@micahflee.com>
|
Copyright (C) 2018 Micah Lee <micah@micahflee.com>
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
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
|
it under the terms of the GNU General Public License as published by
|
||||||
|
@ -21,14 +21,17 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
import os, shutil
|
import os, shutil
|
||||||
|
|
||||||
from . import common, strings
|
from . import common, strings
|
||||||
|
from .common import ShutdownTimer
|
||||||
|
|
||||||
class OnionShare(object):
|
class OnionShare(object):
|
||||||
"""
|
"""
|
||||||
OnionShare is the main application class. Pass in options and run
|
OnionShare is the main application class. Pass in options and run
|
||||||
start_onion_service and it will do the magic.
|
start_onion_service and it will do the magic.
|
||||||
"""
|
"""
|
||||||
def __init__(self, onion, local_only=False, stay_open=False, shutdown_timeout=0):
|
def __init__(self, common, onion, local_only=False, stay_open=False, shutdown_timeout=0):
|
||||||
common.log('OnionShare', '__init__')
|
self.common = common
|
||||||
|
|
||||||
|
self.common.log('OnionShare', '__init__')
|
||||||
|
|
||||||
# The Onion object
|
# The Onion object
|
||||||
self.onion = onion
|
self.onion = onion
|
||||||
|
@ -52,7 +55,7 @@ class OnionShare(object):
|
||||||
self.shutdown_timer = None
|
self.shutdown_timer = None
|
||||||
|
|
||||||
def set_stealth(self, stealth):
|
def set_stealth(self, stealth):
|
||||||
common.log('OnionShare', 'set_stealth', 'stealth={}'.format(stealth))
|
self.common.log('OnionShare', 'set_stealth', 'stealth={}'.format(stealth))
|
||||||
|
|
||||||
self.stealth = stealth
|
self.stealth = stealth
|
||||||
self.onion.stealth = stealth
|
self.onion.stealth = stealth
|
||||||
|
@ -61,11 +64,11 @@ class OnionShare(object):
|
||||||
"""
|
"""
|
||||||
Start the onionshare onion service.
|
Start the onionshare onion service.
|
||||||
"""
|
"""
|
||||||
common.log('OnionShare', 'start_onion_service')
|
self.common.log('OnionShare', 'start_onion_service')
|
||||||
|
|
||||||
# Choose a random port
|
# Choose a random port
|
||||||
try:
|
try:
|
||||||
self.port = common.get_available_port(17600, 17650)
|
self.port = self.common.get_available_port(17600, 17650)
|
||||||
except:
|
except:
|
||||||
raise OSError(strings._('no_available_port'))
|
raise OSError(strings._('no_available_port'))
|
||||||
|
|
||||||
|
@ -74,7 +77,7 @@ class OnionShare(object):
|
||||||
return
|
return
|
||||||
|
|
||||||
if self.shutdown_timeout > 0:
|
if self.shutdown_timeout > 0:
|
||||||
self.shutdown_timer = common.close_after_seconds(self.shutdown_timeout)
|
self.shutdown_timer = ShutdownTimer(self.common, self.shutdown_timeout)
|
||||||
|
|
||||||
self.onion_host = self.onion.start_onion_service(self.port)
|
self.onion_host = self.onion.start_onion_service(self.port)
|
||||||
|
|
||||||
|
@ -85,7 +88,7 @@ class OnionShare(object):
|
||||||
"""
|
"""
|
||||||
Shut everything down and clean up temporary files, etc.
|
Shut everything down and clean up temporary files, etc.
|
||||||
"""
|
"""
|
||||||
common.log('OnionShare', 'cleanup')
|
self.common.log('OnionShare', 'cleanup')
|
||||||
|
|
||||||
# cleanup files
|
# cleanup files
|
||||||
for filename in self.cleanup_filenames:
|
for filename in self.cleanup_filenames:
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
"""
|
"""
|
||||||
OnionShare | https://onionshare.org/
|
OnionShare | https://onionshare.org/
|
||||||
|
|
||||||
Copyright (C) 2017 Micah Lee <micah@micahflee.com>
|
Copyright (C) 2018 Micah Lee <micah@micahflee.com>
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
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
|
it under the terms of the GNU General Public License as published by
|
||||||
|
@ -22,7 +22,7 @@ import json
|
||||||
import os
|
import os
|
||||||
import platform
|
import platform
|
||||||
|
|
||||||
from . import strings, common
|
from . import strings
|
||||||
|
|
||||||
|
|
||||||
class Settings(object):
|
class Settings(object):
|
||||||
|
@ -32,8 +32,10 @@ class Settings(object):
|
||||||
which is to attempt to connect automatically using default Tor Browser
|
which is to attempt to connect automatically using default Tor Browser
|
||||||
settings.
|
settings.
|
||||||
"""
|
"""
|
||||||
def __init__(self, config=False):
|
def __init__(self, common, config=False):
|
||||||
common.log('Settings', '__init__')
|
self.common = common
|
||||||
|
|
||||||
|
self.common.log('Settings', '__init__')
|
||||||
|
|
||||||
# Default config
|
# Default config
|
||||||
self.filename = self.build_filename()
|
self.filename = self.build_filename()
|
||||||
|
@ -43,11 +45,11 @@ class Settings(object):
|
||||||
if os.path.isfile(config):
|
if os.path.isfile(config):
|
||||||
self.filename = config
|
self.filename = config
|
||||||
else:
|
else:
|
||||||
common.log('Settings', '__init__', 'Supplied config does not exist or is unreadable. Falling back to default location')
|
self.common.log('Settings', '__init__', 'Supplied config does not exist or is unreadable. Falling back to default location')
|
||||||
|
|
||||||
# These are the default settings. They will get overwritten when loading from disk
|
# These are the default settings. They will get overwritten when loading from disk
|
||||||
self.default_settings = {
|
self.default_settings = {
|
||||||
'version': common.get_version(),
|
'version': self.common.version,
|
||||||
'connection_type': 'bundled',
|
'connection_type': 'bundled',
|
||||||
'control_port_address': '127.0.0.1',
|
'control_port_address': '127.0.0.1',
|
||||||
'control_port_port': 9051,
|
'control_port_port': 9051,
|
||||||
|
@ -70,7 +72,8 @@ class Settings(object):
|
||||||
'save_private_key': False,
|
'save_private_key': False,
|
||||||
'private_key': '',
|
'private_key': '',
|
||||||
'slug': '',
|
'slug': '',
|
||||||
'hidservauth_string': ''
|
'hidservauth_string': '',
|
||||||
|
'downloads_dir': self.build_default_downloads_dir()
|
||||||
}
|
}
|
||||||
self._settings = {}
|
self._settings = {}
|
||||||
self.fill_in_defaults()
|
self.fill_in_defaults()
|
||||||
|
@ -97,16 +100,24 @@ class Settings(object):
|
||||||
else:
|
else:
|
||||||
return os.path.expanduser('~/.config/onionshare/onionshare.json')
|
return os.path.expanduser('~/.config/onionshare/onionshare.json')
|
||||||
|
|
||||||
|
def build_default_downloads_dir(self):
|
||||||
|
"""
|
||||||
|
Returns the path of the default Downloads directory for receive mode.
|
||||||
|
"""
|
||||||
|
# TODO: Test in Windows, though it looks like it should work
|
||||||
|
# https://docs.python.org/3/library/os.path.html#os.path.expanduser
|
||||||
|
return os.path.expanduser('~/OnionShare')
|
||||||
|
|
||||||
def load(self):
|
def load(self):
|
||||||
"""
|
"""
|
||||||
Load the settings from file.
|
Load the settings from file.
|
||||||
"""
|
"""
|
||||||
common.log('Settings', 'load')
|
self.common.log('Settings', 'load')
|
||||||
|
|
||||||
# If the settings file exists, load it
|
# If the settings file exists, load it
|
||||||
if os.path.exists(self.filename):
|
if os.path.exists(self.filename):
|
||||||
try:
|
try:
|
||||||
common.log('Settings', 'load', 'Trying to load {}'.format(self.filename))
|
self.common.log('Settings', 'load', 'Trying to load {}'.format(self.filename))
|
||||||
with open(self.filename, 'r') as f:
|
with open(self.filename, 'r') as f:
|
||||||
self._settings = json.load(f)
|
self._settings = json.load(f)
|
||||||
self.fill_in_defaults()
|
self.fill_in_defaults()
|
||||||
|
@ -117,7 +128,7 @@ class Settings(object):
|
||||||
"""
|
"""
|
||||||
Save settings to file.
|
Save settings to file.
|
||||||
"""
|
"""
|
||||||
common.log('Settings', 'save')
|
self.common.log('Settings', 'save')
|
||||||
|
|
||||||
try:
|
try:
|
||||||
os.makedirs(os.path.dirname(self.filename))
|
os.makedirs(os.path.dirname(self.filename))
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
"""
|
"""
|
||||||
OnionShare | https://onionshare.org/
|
OnionShare | https://onionshare.org/
|
||||||
|
|
||||||
Copyright (C) 2017 Micah Lee <micah@micahflee.com>
|
Copyright (C) 2018 Micah Lee <micah@micahflee.com>
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
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
|
it under the terms of the GNU General Public License as published by
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
"""
|
"""
|
||||||
OnionShare | https://onionshare.org/
|
OnionShare | https://onionshare.org/
|
||||||
|
|
||||||
Copyright (C) 2017 Micah Lee <micah@micahflee.com>
|
Copyright (C) 2018 Micah Lee <micah@micahflee.com>
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
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
|
it under the terms of the GNU General Public License as published by
|
||||||
|
@ -26,22 +26,50 @@ import queue
|
||||||
import socket
|
import socket
|
||||||
import sys
|
import sys
|
||||||
import tempfile
|
import tempfile
|
||||||
import base64
|
import zipfile
|
||||||
|
import re
|
||||||
|
import io
|
||||||
from distutils.version import LooseVersion as Version
|
from distutils.version import LooseVersion as Version
|
||||||
from urllib.request import urlopen
|
from urllib.request import urlopen
|
||||||
|
|
||||||
from flask import (
|
from flask import (
|
||||||
Flask, Response, request, render_template_string, abort, make_response,
|
Flask, Response, Request, request, render_template, abort, make_response,
|
||||||
__version__ as flask_version
|
flash, redirect, __version__ as flask_version
|
||||||
)
|
)
|
||||||
|
from werkzeug.utils import secure_filename
|
||||||
|
|
||||||
from . import strings, common
|
from . import strings, common
|
||||||
|
|
||||||
|
class Web(object):
|
||||||
|
"""
|
||||||
|
The Web object is the OnionShare web server, powered by flask
|
||||||
|
"""
|
||||||
|
def __init__(self, common, stay_open, gui_mode, receive_mode=False):
|
||||||
|
self.common = common
|
||||||
|
|
||||||
def _safe_select_jinja_autoescape(self, filename):
|
# The flask app
|
||||||
if filename is None:
|
self.app = Flask(__name__,
|
||||||
return True
|
static_folder=common.get_resource_path('static'),
|
||||||
return filename.endswith(('.html', '.htm', '.xml', '.xhtml'))
|
template_folder=common.get_resource_path('templates'))
|
||||||
|
self.app.secret_key = self.common.random_string(8)
|
||||||
|
|
||||||
|
# Debug mode?
|
||||||
|
if self.common.debug:
|
||||||
|
self.debug_mode()
|
||||||
|
|
||||||
|
# Stay open after the first download?
|
||||||
|
self.stay_open = stay_open
|
||||||
|
|
||||||
|
# Are we running in GUI mode?
|
||||||
|
self.gui_mode = gui_mode
|
||||||
|
|
||||||
|
# Are we using receive mode?
|
||||||
|
self.receive_mode = receive_mode
|
||||||
|
if self.receive_mode:
|
||||||
|
# Use custom WSGI middleware, to modify environ
|
||||||
|
self.app.wsgi_app = ReceiveModeWSGIMiddleware(self.app.wsgi_app, self)
|
||||||
|
# Use a custom Request class to track upload progess
|
||||||
|
self.app.request_class = ReceiveModeRequest
|
||||||
|
|
||||||
# Starting in Flask 0.11, render_template_string autoescapes template variables
|
# Starting in Flask 0.11, render_template_string autoescapes template variables
|
||||||
# by default. To prevent content injection through template variables in
|
# by default. To prevent content injection through template variables in
|
||||||
|
@ -49,17 +77,15 @@ def _safe_select_jinja_autoescape(self, filename):
|
||||||
# engine if we detect a Flask version with insecure default behavior.
|
# engine if we detect a Flask version with insecure default behavior.
|
||||||
if Version(flask_version) < Version('0.11'):
|
if Version(flask_version) < Version('0.11'):
|
||||||
# Monkey-patch in the fix from https://github.com/pallets/flask/commit/99c99c4c16b1327288fd76c44bc8635a1de452bc
|
# Monkey-patch in the fix from https://github.com/pallets/flask/commit/99c99c4c16b1327288fd76c44bc8635a1de452bc
|
||||||
Flask.select_jinja_autoescape = _safe_select_jinja_autoescape
|
Flask.select_jinja_autoescape = self._safe_select_jinja_autoescape
|
||||||
|
|
||||||
app = Flask(__name__)
|
# Information about the file
|
||||||
|
self.file_info = []
|
||||||
|
self.zip_filename = None
|
||||||
|
self.zip_filesize = None
|
||||||
|
|
||||||
# information about the file
|
self.security_headers = [
|
||||||
file_info = []
|
('Content-Security-Policy', 'default-src \'self\'; style-src \'self\'; script-src \'self\'; img-src \'self\' data:;'),
|
||||||
zip_filename = None
|
|
||||||
zip_filesize = None
|
|
||||||
|
|
||||||
security_headers = [
|
|
||||||
('Content-Security-Policy', 'default-src \'self\'; style-src \'unsafe-inline\'; script-src \'unsafe-inline\'; img-src \'self\' data:;'),
|
|
||||||
('X-Frame-Options', 'DENY'),
|
('X-Frame-Options', 'DENY'),
|
||||||
('X-Xss-Protection', '1; mode=block'),
|
('X-Xss-Protection', '1; mode=block'),
|
||||||
('X-Content-Type-Options', 'nosniff'),
|
('X-Content-Type-Options', 'nosniff'),
|
||||||
|
@ -67,209 +93,87 @@ security_headers = [
|
||||||
('Server', 'OnionShare')
|
('Server', 'OnionShare')
|
||||||
]
|
]
|
||||||
|
|
||||||
|
self.REQUEST_LOAD = 0
|
||||||
|
self.REQUEST_DOWNLOAD = 1
|
||||||
|
self.REQUEST_PROGRESS = 2
|
||||||
|
self.REQUEST_OTHER = 3
|
||||||
|
self.REQUEST_CANCELED = 4
|
||||||
|
self.REQUEST_RATE_LIMIT = 5
|
||||||
|
self.q = queue.Queue()
|
||||||
|
|
||||||
def set_file_info(filenames, processed_size_callback=None):
|
self.slug = None
|
||||||
"""
|
|
||||||
Using the list of filenames being shared, fill in details that the web
|
|
||||||
page will need to display. This includes zipping up the file in order to
|
|
||||||
get the zip file's name and size.
|
|
||||||
"""
|
|
||||||
global file_info, zip_filename, zip_filesize
|
|
||||||
|
|
||||||
# build file info list
|
|
||||||
file_info = {'files': [], 'dirs': []}
|
|
||||||
for filename in filenames:
|
|
||||||
info = {
|
|
||||||
'filename': filename,
|
|
||||||
'basename': os.path.basename(filename.rstrip('/'))
|
|
||||||
}
|
|
||||||
if os.path.isfile(filename):
|
|
||||||
info['size'] = os.path.getsize(filename)
|
|
||||||
info['size_human'] = common.human_readable_filesize(info['size'])
|
|
||||||
file_info['files'].append(info)
|
|
||||||
if os.path.isdir(filename):
|
|
||||||
info['size'] = common.dir_size(filename)
|
|
||||||
info['size_human'] = common.human_readable_filesize(info['size'])
|
|
||||||
file_info['dirs'].append(info)
|
|
||||||
file_info['files'] = sorted(file_info['files'], key=lambda k: k['basename'])
|
|
||||||
file_info['dirs'] = sorted(file_info['dirs'], key=lambda k: k['basename'])
|
|
||||||
|
|
||||||
# zip up the files and folders
|
|
||||||
z = common.ZipWriter(processed_size_callback=processed_size_callback)
|
|
||||||
for info in file_info['files']:
|
|
||||||
z.add_file(info['filename'])
|
|
||||||
for info in file_info['dirs']:
|
|
||||||
z.add_dir(info['filename'])
|
|
||||||
z.close()
|
|
||||||
zip_filename = z.zip_filename
|
|
||||||
zip_filesize = os.path.getsize(zip_filename)
|
|
||||||
|
|
||||||
|
|
||||||
REQUEST_LOAD = 0
|
|
||||||
REQUEST_DOWNLOAD = 1
|
|
||||||
REQUEST_PROGRESS = 2
|
|
||||||
REQUEST_OTHER = 3
|
|
||||||
REQUEST_CANCELED = 4
|
|
||||||
REQUEST_RATE_LIMIT = 5
|
|
||||||
q = queue.Queue()
|
|
||||||
|
|
||||||
|
|
||||||
def add_request(request_type, path, data=None):
|
|
||||||
"""
|
|
||||||
Add a request to the queue, to communicate with the GUI.
|
|
||||||
"""
|
|
||||||
global q
|
|
||||||
q.put({
|
|
||||||
'type': request_type,
|
|
||||||
'path': path,
|
|
||||||
'data': data
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
# Load and base64 encode images to pass into templates
|
|
||||||
favicon_b64 = base64.b64encode(open(common.get_resource_path('images/favicon.ico'), 'rb').read()).decode()
|
|
||||||
logo_b64 = base64.b64encode(open(common.get_resource_path('images/logo.png'), 'rb').read()).decode()
|
|
||||||
folder_b64 = base64.b64encode(open(common.get_resource_path('images/web_folder.png'), 'rb').read()).decode()
|
|
||||||
file_b64 = base64.b64encode(open(common.get_resource_path('images/web_file.png'), 'rb').read()).decode()
|
|
||||||
|
|
||||||
slug = None
|
|
||||||
|
|
||||||
|
|
||||||
def generate_slug(persistent_slug=''):
|
|
||||||
global slug
|
|
||||||
if persistent_slug:
|
|
||||||
slug = persistent_slug
|
|
||||||
else:
|
|
||||||
slug = common.build_slug()
|
|
||||||
|
|
||||||
download_count = 0
|
|
||||||
error404_count = 0
|
|
||||||
|
|
||||||
stay_open = False
|
|
||||||
|
|
||||||
|
|
||||||
def set_stay_open(new_stay_open):
|
|
||||||
"""
|
|
||||||
Set stay_open variable.
|
|
||||||
"""
|
|
||||||
global stay_open
|
|
||||||
stay_open = new_stay_open
|
|
||||||
|
|
||||||
|
|
||||||
def get_stay_open():
|
|
||||||
"""
|
|
||||||
Get stay_open variable.
|
|
||||||
"""
|
|
||||||
return stay_open
|
|
||||||
|
|
||||||
|
|
||||||
# Are we running in GUI mode?
|
|
||||||
gui_mode = False
|
|
||||||
|
|
||||||
|
|
||||||
def set_gui_mode():
|
|
||||||
"""
|
|
||||||
Tell the web service that we're running in GUI mode
|
|
||||||
"""
|
|
||||||
global gui_mode
|
|
||||||
gui_mode = True
|
|
||||||
|
|
||||||
|
|
||||||
def debug_mode():
|
|
||||||
"""
|
|
||||||
Turn on debugging mode, which will log flask errors to a debug file.
|
|
||||||
"""
|
|
||||||
temp_dir = tempfile.gettempdir()
|
|
||||||
log_handler = logging.FileHandler(
|
|
||||||
os.path.join(temp_dir, 'onionshare_server.log'))
|
|
||||||
log_handler.setLevel(logging.WARNING)
|
|
||||||
app.logger.addHandler(log_handler)
|
|
||||||
|
|
||||||
|
|
||||||
def check_slug_candidate(slug_candidate, slug_compare=None):
|
|
||||||
if not slug_compare:
|
|
||||||
slug_compare = slug
|
|
||||||
if not hmac.compare_digest(slug_compare, slug_candidate):
|
|
||||||
abort(404)
|
|
||||||
|
|
||||||
|
self.download_count = 0
|
||||||
|
self.error404_count = 0
|
||||||
|
|
||||||
# If "Stop After First Download" is checked (stay_open == False), only allow
|
# If "Stop After First Download" is checked (stay_open == False), only allow
|
||||||
# one download at a time.
|
# one download at a time.
|
||||||
download_in_progress = False
|
self.download_in_progress = False
|
||||||
|
|
||||||
done = False
|
|
||||||
|
|
||||||
@app.route("/<slug_candidate>")
|
|
||||||
def index(slug_candidate):
|
|
||||||
"""
|
|
||||||
Render the template for the onionshare landing page.
|
|
||||||
"""
|
|
||||||
check_slug_candidate(slug_candidate)
|
|
||||||
|
|
||||||
add_request(REQUEST_LOAD, request.path)
|
|
||||||
|
|
||||||
# Deny new downloads if "Stop After First Download" is checked and there is
|
|
||||||
# currently a download
|
|
||||||
global stay_open, download_in_progress
|
|
||||||
deny_download = not stay_open and download_in_progress
|
|
||||||
if deny_download:
|
|
||||||
r = make_response(render_template_string(
|
|
||||||
open(common.get_resource_path('html/denied.html')).read(),
|
|
||||||
favicon_b64=favicon_b64
|
|
||||||
))
|
|
||||||
for header, value in security_headers:
|
|
||||||
r.headers.set(header, value)
|
|
||||||
return r
|
|
||||||
|
|
||||||
# If download is allowed to continue, serve download page
|
|
||||||
|
|
||||||
r = make_response(render_template_string(
|
|
||||||
open(common.get_resource_path('html/index.html')).read(),
|
|
||||||
favicon_b64=favicon_b64,
|
|
||||||
logo_b64=logo_b64,
|
|
||||||
folder_b64=folder_b64,
|
|
||||||
file_b64=file_b64,
|
|
||||||
slug=slug,
|
|
||||||
file_info=file_info,
|
|
||||||
filename=os.path.basename(zip_filename),
|
|
||||||
filesize=zip_filesize,
|
|
||||||
filesize_human=common.human_readable_filesize(zip_filesize)))
|
|
||||||
for header, value in security_headers:
|
|
||||||
r.headers.set(header, value)
|
|
||||||
return r
|
|
||||||
|
|
||||||
|
self.done = False
|
||||||
|
|
||||||
# If the client closes the OnionShare window while a download is in progress,
|
# If the client closes the OnionShare window while a download is in progress,
|
||||||
# it should immediately stop serving the file. The client_cancel global is
|
# it should immediately stop serving the file. The client_cancel global is
|
||||||
# used to tell the download function that the client is canceling the download.
|
# used to tell the download function that the client is canceling the download.
|
||||||
client_cancel = False
|
self.client_cancel = False
|
||||||
|
|
||||||
|
# shutting down the server only works within the context of flask, so the easiest way to do it is over http
|
||||||
|
self.shutdown_slug = self.common.random_string(16)
|
||||||
|
|
||||||
@app.route("/<slug_candidate>/download")
|
# Define the ewb app routes
|
||||||
|
self.common_routes()
|
||||||
|
if self.receive_mode:
|
||||||
|
self.receive_routes()
|
||||||
|
else:
|
||||||
|
self.send_routes()
|
||||||
|
|
||||||
|
def send_routes(self):
|
||||||
|
"""
|
||||||
|
The web app routes for sharing files
|
||||||
|
"""
|
||||||
|
@self.app.route("/<slug_candidate>")
|
||||||
|
def index(slug_candidate):
|
||||||
|
"""
|
||||||
|
Render the template for the onionshare landing page.
|
||||||
|
"""
|
||||||
|
self.check_slug_candidate(slug_candidate)
|
||||||
|
|
||||||
|
self.add_request(self.REQUEST_LOAD, request.path)
|
||||||
|
|
||||||
|
# Deny new downloads if "Stop After First Download" is checked and there is
|
||||||
|
# currently a download
|
||||||
|
deny_download = not self.stay_open and self.download_in_progress
|
||||||
|
if deny_download:
|
||||||
|
r = make_response(render_template('denied.html'))
|
||||||
|
return self.add_security_headers(r)
|
||||||
|
|
||||||
|
# If download is allowed to continue, serve download page
|
||||||
|
r = make_response(render_template(
|
||||||
|
'send.html',
|
||||||
|
slug=self.slug,
|
||||||
|
file_info=self.file_info,
|
||||||
|
filename=os.path.basename(self.zip_filename),
|
||||||
|
filesize=self.zip_filesize,
|
||||||
|
filesize_human=self.common.human_readable_filesize(self.zip_filesize)))
|
||||||
|
return self.add_security_headers(r)
|
||||||
|
|
||||||
|
@self.app.route("/<slug_candidate>/download")
|
||||||
def download(slug_candidate):
|
def download(slug_candidate):
|
||||||
"""
|
"""
|
||||||
Download the zip file.
|
Download the zip file.
|
||||||
"""
|
"""
|
||||||
check_slug_candidate(slug_candidate)
|
self.check_slug_candidate(slug_candidate)
|
||||||
|
|
||||||
# Deny new downloads if "Stop After First Download" is checked and there is
|
# Deny new downloads if "Stop After First Download" is checked and there is
|
||||||
# currently a download
|
# currently a download
|
||||||
global stay_open, download_in_progress, done
|
deny_download = not self.stay_open and self.download_in_progress
|
||||||
deny_download = not stay_open and download_in_progress
|
|
||||||
if deny_download:
|
if deny_download:
|
||||||
r = make_response(render_template_string(
|
r = make_response(render_template('denied.html'))
|
||||||
open(common.get_resource_path('html/denied.html')).read(),
|
return self.add_security_headers(r)
|
||||||
favicon_b64=favicon_b64
|
|
||||||
))
|
|
||||||
for header,value in security_headers:
|
|
||||||
r.headers.set(header, value)
|
|
||||||
return r
|
|
||||||
|
|
||||||
global download_count
|
|
||||||
|
|
||||||
# each download has a unique id
|
# each download has a unique id
|
||||||
download_id = download_count
|
download_id = self.download_count
|
||||||
download_count += 1
|
self.download_count += 1
|
||||||
|
|
||||||
# prepare some variables to use inside generate() function below
|
# prepare some variables to use inside generate() function below
|
||||||
# which is outside of the request context
|
# which is outside of the request context
|
||||||
|
@ -277,127 +181,267 @@ def download(slug_candidate):
|
||||||
path = request.path
|
path = request.path
|
||||||
|
|
||||||
# tell GUI the download started
|
# tell GUI the download started
|
||||||
add_request(REQUEST_DOWNLOAD, path, {'id': download_id})
|
self.add_request(self.REQUEST_DOWNLOAD, path, {'id': download_id})
|
||||||
|
|
||||||
dirname = os.path.dirname(zip_filename)
|
dirname = os.path.dirname(self.zip_filename)
|
||||||
basename = os.path.basename(zip_filename)
|
basename = os.path.basename(self.zip_filename)
|
||||||
|
|
||||||
def generate():
|
def generate():
|
||||||
# The user hasn't canceled the download
|
# The user hasn't canceled the download
|
||||||
global client_cancel, gui_mode
|
self.client_cancel = False
|
||||||
client_cancel = False
|
|
||||||
|
|
||||||
# Starting a new download
|
# Starting a new download
|
||||||
global stay_open, download_in_progress, done
|
if not self.stay_open:
|
||||||
if not stay_open:
|
self.download_in_progress = True
|
||||||
download_in_progress = True
|
|
||||||
|
|
||||||
chunk_size = 102400 # 100kb
|
chunk_size = 102400 # 100kb
|
||||||
|
|
||||||
fp = open(zip_filename, 'rb')
|
fp = open(self.zip_filename, 'rb')
|
||||||
done = False
|
self.done = False
|
||||||
canceled = False
|
canceled = False
|
||||||
while not done:
|
while not self.done:
|
||||||
# The user has canceled the download, so stop serving the file
|
# The user has canceled the download, so stop serving the file
|
||||||
if client_cancel:
|
if self.client_cancel:
|
||||||
add_request(REQUEST_CANCELED, path, {'id': download_id})
|
self.add_request(self.REQUEST_CANCELED, path, {'id': download_id})
|
||||||
break
|
break
|
||||||
|
|
||||||
chunk = fp.read(chunk_size)
|
chunk = fp.read(chunk_size)
|
||||||
if chunk == b'':
|
if chunk == b'':
|
||||||
done = True
|
self.done = True
|
||||||
else:
|
else:
|
||||||
try:
|
try:
|
||||||
yield chunk
|
yield chunk
|
||||||
|
|
||||||
# tell GUI the progress
|
# tell GUI the progress
|
||||||
downloaded_bytes = fp.tell()
|
downloaded_bytes = fp.tell()
|
||||||
percent = (1.0 * downloaded_bytes / zip_filesize) * 100
|
percent = (1.0 * downloaded_bytes / self.zip_filesize) * 100
|
||||||
|
|
||||||
# only output to stdout if running onionshare in CLI mode, or if using Linux (#203, #304)
|
# only output to stdout if running onionshare in CLI mode, or if using Linux (#203, #304)
|
||||||
plat = common.get_platform()
|
if not self.gui_mode or self.common.platform == 'Linux' or self.common.platform == 'BSD':
|
||||||
if not gui_mode or plat == 'Linux' or plat == 'BSD':
|
|
||||||
sys.stdout.write(
|
sys.stdout.write(
|
||||||
"\r{0:s}, {1:.2f}% ".format(common.human_readable_filesize(downloaded_bytes), percent))
|
"\r{0:s}, {1:.2f}% ".format(self.common.human_readable_filesize(downloaded_bytes), percent))
|
||||||
sys.stdout.flush()
|
sys.stdout.flush()
|
||||||
|
|
||||||
add_request(REQUEST_PROGRESS, path, {'id': download_id, 'bytes': downloaded_bytes})
|
self.add_request(self.REQUEST_PROGRESS, path, {'id': download_id, 'bytes': downloaded_bytes})
|
||||||
done = False
|
self.done = False
|
||||||
except:
|
except:
|
||||||
# looks like the download was canceled
|
# looks like the download was canceled
|
||||||
done = True
|
self.done = True
|
||||||
canceled = True
|
canceled = True
|
||||||
|
|
||||||
# tell the GUI the download has canceled
|
# tell the GUI the download has canceled
|
||||||
add_request(REQUEST_CANCELED, path, {'id': download_id})
|
self.add_request(self.REQUEST_CANCELED, path, {'id': download_id})
|
||||||
|
|
||||||
fp.close()
|
fp.close()
|
||||||
|
|
||||||
if common.get_platform() != 'Darwin':
|
if self.common.platform != 'Darwin':
|
||||||
sys.stdout.write("\n")
|
sys.stdout.write("\n")
|
||||||
|
|
||||||
# Download is finished
|
# Download is finished
|
||||||
if not stay_open:
|
if not self.stay_open:
|
||||||
download_in_progress = False
|
self.download_in_progress = False
|
||||||
|
|
||||||
# Close the server, if necessary
|
# Close the server, if necessary
|
||||||
if not stay_open and not canceled:
|
if not self.stay_open and not canceled:
|
||||||
print(strings._("closing_automatically"))
|
print(strings._("closing_automatically"))
|
||||||
if shutdown_func is None:
|
if shutdown_func is None:
|
||||||
raise RuntimeError('Not running with the Werkzeug Server')
|
raise RuntimeError('Not running with the Werkzeug Server')
|
||||||
shutdown_func()
|
shutdown_func()
|
||||||
|
|
||||||
r = Response(generate())
|
r = Response(generate())
|
||||||
r.headers.set('Content-Length', zip_filesize)
|
r.headers.set('Content-Length', self.zip_filesize)
|
||||||
r.headers.set('Content-Disposition', 'attachment', filename=basename)
|
r.headers.set('Content-Disposition', 'attachment', filename=basename)
|
||||||
for header,value in security_headers:
|
r = self.add_security_headers(r)
|
||||||
r.headers.set(header, value)
|
|
||||||
# guess content type
|
# guess content type
|
||||||
(content_type, _) = mimetypes.guess_type(basename, strict=False)
|
(content_type, _) = mimetypes.guess_type(basename, strict=False)
|
||||||
if content_type is not None:
|
if content_type is not None:
|
||||||
r.headers.set('Content-Type', content_type)
|
r.headers.set('Content-Type', content_type)
|
||||||
return r
|
return r
|
||||||
|
|
||||||
|
def receive_routes(self):
|
||||||
|
"""
|
||||||
|
The web app routes for sharing files
|
||||||
|
"""
|
||||||
|
@self.app.route("/<slug_candidate>")
|
||||||
|
def index(slug_candidate):
|
||||||
|
self.check_slug_candidate(slug_candidate)
|
||||||
|
|
||||||
@app.errorhandler(404)
|
r = make_response(render_template(
|
||||||
|
'receive.html',
|
||||||
|
slug=self.slug))
|
||||||
|
return self.add_security_headers(r)
|
||||||
|
|
||||||
|
@self.app.route("/<slug_candidate>/upload", methods=['POST'])
|
||||||
|
def upload(slug_candidate):
|
||||||
|
self.check_slug_candidate(slug_candidate)
|
||||||
|
|
||||||
|
files = request.files.getlist('file[]')
|
||||||
|
filenames = []
|
||||||
|
for f in files:
|
||||||
|
if f.filename != '':
|
||||||
|
# Automatically rename the file, if a file of the same name already exists
|
||||||
|
filename = secure_filename(f.filename)
|
||||||
|
filenames.append(filename)
|
||||||
|
local_path = os.path.join(self.common.settings.get('downloads_dir'), filename)
|
||||||
|
if os.path.exists(local_path):
|
||||||
|
if '.' in filename:
|
||||||
|
# Add "-i", e.g. change "foo.txt" to "foo-2.txt"
|
||||||
|
parts = filename.split('.')
|
||||||
|
name = parts[:-1]
|
||||||
|
ext = parts[-1]
|
||||||
|
|
||||||
|
i = 2
|
||||||
|
valid = False
|
||||||
|
while not valid:
|
||||||
|
new_filename = '{}-{}.{}'.format('.'.join(name), i, ext)
|
||||||
|
local_path = os.path.join(self.common.settings.get('downloads_dir'), new_filename)
|
||||||
|
if os.path.exists(local_path):
|
||||||
|
i += 1
|
||||||
|
else:
|
||||||
|
valid = True
|
||||||
|
else:
|
||||||
|
# If no extension, just add "-i", e.g. change "foo" to "foo-2"
|
||||||
|
i = 2
|
||||||
|
valid = False
|
||||||
|
while not valid:
|
||||||
|
new_filename = '{}-{}'.format(filename, i)
|
||||||
|
local_path = os.path.join(self.common.settings.get('downloads_dir'), new_filename)
|
||||||
|
if os.path.exists(local_path):
|
||||||
|
i += 1
|
||||||
|
else:
|
||||||
|
valid = True
|
||||||
|
|
||||||
|
self.common.log('Web', 'receive_routes', '/upload, uploaded {}, saving to {}'.format(f.filename, local_path))
|
||||||
|
print(strings._('receive_mode_received_file').format(local_path))
|
||||||
|
f.save(local_path)
|
||||||
|
|
||||||
|
# Note that flash strings are on English, and not translated, on purpose,
|
||||||
|
# to avoid leaking the locale of the OnionShare user
|
||||||
|
if len(filenames) == 0:
|
||||||
|
flash('No files uploaded')
|
||||||
|
else:
|
||||||
|
for filename in filenames:
|
||||||
|
flash('Uploaded {}'.format(filename))
|
||||||
|
|
||||||
|
return redirect('/{}'.format(slug_candidate))
|
||||||
|
|
||||||
|
@self.app.route("/<slug_candidate>/close", methods=['POST'])
|
||||||
|
def close(slug_candidate):
|
||||||
|
self.check_slug_candidate(slug_candidate)
|
||||||
|
self.force_shutdown()
|
||||||
|
r = make_response(render_template('closed.html'))
|
||||||
|
return self.add_security_headers(r)
|
||||||
|
|
||||||
|
def common_routes(self):
|
||||||
|
"""
|
||||||
|
Common web app routes between sending and receiving
|
||||||
|
"""
|
||||||
|
@self.app.errorhandler(404)
|
||||||
def page_not_found(e):
|
def page_not_found(e):
|
||||||
"""
|
"""
|
||||||
404 error page.
|
404 error page.
|
||||||
"""
|
"""
|
||||||
add_request(REQUEST_OTHER, request.path)
|
self.add_request(self.REQUEST_OTHER, request.path)
|
||||||
|
|
||||||
global error404_count
|
|
||||||
if request.path != '/favicon.ico':
|
if request.path != '/favicon.ico':
|
||||||
error404_count += 1
|
self.error404_count += 1
|
||||||
if error404_count == 20:
|
if self.error404_count == 20:
|
||||||
add_request(REQUEST_RATE_LIMIT, request.path)
|
self.add_request(self.REQUEST_RATE_LIMIT, request.path)
|
||||||
force_shutdown()
|
self.force_shutdown()
|
||||||
print(strings._('error_rate_limit'))
|
print(strings._('error_rate_limit'))
|
||||||
|
|
||||||
r = make_response(render_template_string(
|
r = make_response(render_template('404.html'), 404)
|
||||||
open(common.get_resource_path('html/404.html')).read(),
|
return self.add_security_headers(r)
|
||||||
favicon_b64=favicon_b64
|
|
||||||
), 404)
|
|
||||||
for header, value in security_headers:
|
|
||||||
r.headers.set(header, value)
|
|
||||||
return r
|
|
||||||
|
|
||||||
|
@self.app.route("/<slug_candidate>/shutdown")
|
||||||
# shutting down the server only works within the context of flask, so the easiest way to do it is over http
|
|
||||||
shutdown_slug = common.random_string(16)
|
|
||||||
|
|
||||||
|
|
||||||
@app.route("/<slug_candidate>/shutdown")
|
|
||||||
def shutdown(slug_candidate):
|
def shutdown(slug_candidate):
|
||||||
"""
|
"""
|
||||||
Stop the flask web server, from the context of an http request.
|
Stop the flask web server, from the context of an http request.
|
||||||
"""
|
"""
|
||||||
check_slug_candidate(slug_candidate, shutdown_slug)
|
self.check_slug_candidate(slug_candidate, self.shutdown_slug)
|
||||||
force_shutdown()
|
self.force_shutdown()
|
||||||
return ""
|
return ""
|
||||||
|
|
||||||
|
def add_security_headers(self, r):
|
||||||
|
"""
|
||||||
|
Add security headers to a request
|
||||||
|
"""
|
||||||
|
for header, value in self.security_headers:
|
||||||
|
r.headers.set(header, value)
|
||||||
|
return r
|
||||||
|
|
||||||
def force_shutdown():
|
def set_file_info(self, filenames, processed_size_callback=None):
|
||||||
|
"""
|
||||||
|
Using the list of filenames being shared, fill in details that the web
|
||||||
|
page will need to display. This includes zipping up the file in order to
|
||||||
|
get the zip file's name and size.
|
||||||
|
"""
|
||||||
|
# build file info list
|
||||||
|
self.file_info = {'files': [], 'dirs': []}
|
||||||
|
for filename in filenames:
|
||||||
|
info = {
|
||||||
|
'filename': filename,
|
||||||
|
'basename': os.path.basename(filename.rstrip('/'))
|
||||||
|
}
|
||||||
|
if os.path.isfile(filename):
|
||||||
|
info['size'] = os.path.getsize(filename)
|
||||||
|
info['size_human'] = self.common.human_readable_filesize(info['size'])
|
||||||
|
self.file_info['files'].append(info)
|
||||||
|
if os.path.isdir(filename):
|
||||||
|
info['size'] = self.common.dir_size(filename)
|
||||||
|
info['size_human'] = self.common.human_readable_filesize(info['size'])
|
||||||
|
self.file_info['dirs'].append(info)
|
||||||
|
self.file_info['files'] = sorted(self.file_info['files'], key=lambda k: k['basename'])
|
||||||
|
self.file_info['dirs'] = sorted(self.file_info['dirs'], key=lambda k: k['basename'])
|
||||||
|
|
||||||
|
# zip up the files and folders
|
||||||
|
z = ZipWriter(self.common, processed_size_callback=processed_size_callback)
|
||||||
|
for info in self.file_info['files']:
|
||||||
|
z.add_file(info['filename'])
|
||||||
|
for info in self.file_info['dirs']:
|
||||||
|
z.add_dir(info['filename'])
|
||||||
|
z.close()
|
||||||
|
self.zip_filename = z.zip_filename
|
||||||
|
self.zip_filesize = os.path.getsize(self.zip_filename)
|
||||||
|
|
||||||
|
def _safe_select_jinja_autoescape(self, filename):
|
||||||
|
if filename is None:
|
||||||
|
return True
|
||||||
|
return filename.endswith(('.html', '.htm', '.xml', '.xhtml'))
|
||||||
|
|
||||||
|
def add_request(self, request_type, path, data=None):
|
||||||
|
"""
|
||||||
|
Add a request to the queue, to communicate with the GUI.
|
||||||
|
"""
|
||||||
|
self.q.put({
|
||||||
|
'type': request_type,
|
||||||
|
'path': path,
|
||||||
|
'data': data
|
||||||
|
})
|
||||||
|
|
||||||
|
def generate_slug(self, persistent_slug=''):
|
||||||
|
if persistent_slug:
|
||||||
|
self.slug = persistent_slug
|
||||||
|
else:
|
||||||
|
self.slug = self.common.build_slug()
|
||||||
|
|
||||||
|
def debug_mode(self):
|
||||||
|
"""
|
||||||
|
Turn on debugging mode, which will log flask errors to a debug file.
|
||||||
|
"""
|
||||||
|
temp_dir = tempfile.gettempdir()
|
||||||
|
log_handler = logging.FileHandler(
|
||||||
|
os.path.join(temp_dir, 'onionshare_server.log'))
|
||||||
|
log_handler.setLevel(logging.WARNING)
|
||||||
|
self.app.logger.addHandler(log_handler)
|
||||||
|
|
||||||
|
def check_slug_candidate(self, slug_candidate, slug_compare=None):
|
||||||
|
if not slug_compare:
|
||||||
|
slug_compare = self.slug
|
||||||
|
if not hmac.compare_digest(slug_compare, slug_candidate):
|
||||||
|
abort(404)
|
||||||
|
|
||||||
|
def force_shutdown(self):
|
||||||
"""
|
"""
|
||||||
Stop the flask web server, from the context of the flask app.
|
Stop the flask web server, from the context of the flask app.
|
||||||
"""
|
"""
|
||||||
|
@ -407,14 +451,13 @@ def force_shutdown():
|
||||||
raise RuntimeError('Not running with the Werkzeug Server')
|
raise RuntimeError('Not running with the Werkzeug Server')
|
||||||
func()
|
func()
|
||||||
|
|
||||||
|
def start(self, port, stay_open=False, persistent_slug=''):
|
||||||
def start(port, stay_open=False, persistent_slug=''):
|
|
||||||
"""
|
"""
|
||||||
Start the flask web server.
|
Start the flask web server.
|
||||||
"""
|
"""
|
||||||
generate_slug(persistent_slug)
|
self.generate_slug(persistent_slug)
|
||||||
|
|
||||||
set_stay_open(stay_open)
|
self.stay_open = stay_open
|
||||||
|
|
||||||
# In Whonix, listen on 0.0.0.0 instead of 127.0.0.1 (#220)
|
# In Whonix, listen on 0.0.0.0 instead of 127.0.0.1 (#220)
|
||||||
if os.path.exists('/usr/share/anon-ws-base-files/workstation'):
|
if os.path.exists('/usr/share/anon-ws-base-files/workstation'):
|
||||||
|
@ -422,26 +465,154 @@ def start(port, stay_open=False, persistent_slug=''):
|
||||||
else:
|
else:
|
||||||
host = '127.0.0.1'
|
host = '127.0.0.1'
|
||||||
|
|
||||||
app.run(host=host, port=port, threaded=True)
|
self.app.run(host=host, port=port, threaded=True)
|
||||||
|
|
||||||
|
def stop(self, port):
|
||||||
def stop(port):
|
|
||||||
"""
|
"""
|
||||||
Stop the flask web server by loading /shutdown.
|
Stop the flask web server by loading /shutdown.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# If the user cancels the download, let the download function know to stop
|
# If the user cancels the download, let the download function know to stop
|
||||||
# serving the file
|
# serving the file
|
||||||
global client_cancel
|
self.client_cancel = True
|
||||||
client_cancel = True
|
|
||||||
|
|
||||||
# to stop flask, load http://127.0.0.1:<port>/<shutdown_slug>/shutdown
|
# to stop flask, load http://127.0.0.1:<port>/<shutdown_slug>/shutdown
|
||||||
try:
|
try:
|
||||||
s = socket.socket()
|
s = socket.socket()
|
||||||
s.connect(('127.0.0.1', port))
|
s.connect(('127.0.0.1', port))
|
||||||
s.sendall('GET /{0:s}/shutdown HTTP/1.1\r\n\r\n'.format(shutdown_slug))
|
s.sendall('GET /{0:s}/shutdown HTTP/1.1\r\n\r\n'.format(self.shutdown_slug))
|
||||||
except:
|
except:
|
||||||
try:
|
try:
|
||||||
urlopen('http://127.0.0.1:{0:d}/{1:s}/shutdown'.format(port, shutdown_slug)).read()
|
urlopen('http://127.0.0.1:{0:d}/{1:s}/shutdown'.format(port, self.shutdown_slug)).read()
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class ZipWriter(object):
|
||||||
|
"""
|
||||||
|
ZipWriter accepts files and directories and compresses them into a zip file
|
||||||
|
with. If a zip_filename is not passed in, it will use the default onionshare
|
||||||
|
filename.
|
||||||
|
"""
|
||||||
|
def __init__(self, common, zip_filename=None, processed_size_callback=None):
|
||||||
|
self.common = common
|
||||||
|
|
||||||
|
if zip_filename:
|
||||||
|
self.zip_filename = zip_filename
|
||||||
|
else:
|
||||||
|
self.zip_filename = '{0:s}/onionshare_{1:s}.zip'.format(tempfile.mkdtemp(), self.common.random_string(4, 6))
|
||||||
|
|
||||||
|
self.z = zipfile.ZipFile(self.zip_filename, 'w', allowZip64=True)
|
||||||
|
self.processed_size_callback = processed_size_callback
|
||||||
|
if self.processed_size_callback is None:
|
||||||
|
self.processed_size_callback = lambda _: None
|
||||||
|
self._size = 0
|
||||||
|
self.processed_size_callback(self._size)
|
||||||
|
|
||||||
|
def add_file(self, filename):
|
||||||
|
"""
|
||||||
|
Add a file to the zip archive.
|
||||||
|
"""
|
||||||
|
self.z.write(filename, os.path.basename(filename), zipfile.ZIP_DEFLATED)
|
||||||
|
self._size += os.path.getsize(filename)
|
||||||
|
self.processed_size_callback(self._size)
|
||||||
|
|
||||||
|
def add_dir(self, filename):
|
||||||
|
"""
|
||||||
|
Add a directory, and all of its children, to the zip archive.
|
||||||
|
"""
|
||||||
|
dir_to_strip = os.path.dirname(filename.rstrip('/'))+'/'
|
||||||
|
for dirpath, dirnames, filenames in os.walk(filename):
|
||||||
|
for f in filenames:
|
||||||
|
full_filename = os.path.join(dirpath, f)
|
||||||
|
if not os.path.islink(full_filename):
|
||||||
|
arc_filename = full_filename[len(dir_to_strip):]
|
||||||
|
self.z.write(full_filename, arc_filename, zipfile.ZIP_DEFLATED)
|
||||||
|
self._size += os.path.getsize(full_filename)
|
||||||
|
self.processed_size_callback(self._size)
|
||||||
|
|
||||||
|
def close(self):
|
||||||
|
"""
|
||||||
|
Close the zip archive.
|
||||||
|
"""
|
||||||
|
self.z.close()
|
||||||
|
|
||||||
|
|
||||||
|
class ReceiveModeWSGIMiddleware(object):
|
||||||
|
"""
|
||||||
|
Custom WSGI middleware in order to attach the Web object to environ, so
|
||||||
|
ReceiveModeRequest can access it.
|
||||||
|
"""
|
||||||
|
def __init__(self, app, web):
|
||||||
|
self.app = app
|
||||||
|
self.web = web
|
||||||
|
|
||||||
|
def __call__(self, environ, start_response):
|
||||||
|
environ['web'] = self.web
|
||||||
|
return self.app(environ, start_response)
|
||||||
|
|
||||||
|
class ReceiveModeTemporaryFile(object):
|
||||||
|
"""
|
||||||
|
A custom TemporaryFile that tells ReceiveModeRequest every time data gets
|
||||||
|
written to it, in order to track the progress of uploads.
|
||||||
|
"""
|
||||||
|
def __init__(self, filename, update_func):
|
||||||
|
self.onionshare_filename = filename
|
||||||
|
self.onionshare_update_func = update_func
|
||||||
|
|
||||||
|
# Create a temporary file
|
||||||
|
self.f = tempfile.TemporaryFile('wb+')
|
||||||
|
|
||||||
|
# Make all the file-like methods and attributes actually access the
|
||||||
|
# TemporaryFile, except for write
|
||||||
|
attrs = ['close', 'closed', 'detach', 'fileno', 'flush', 'isatty', 'mode',
|
||||||
|
'name', 'peek', 'raw', 'read', 'read1', 'readable', 'readinto',
|
||||||
|
'readinto1', 'readline', 'readlines', 'seek', 'seekable', 'tell',
|
||||||
|
'truncate', 'writable', 'writelines']
|
||||||
|
for attr in attrs:
|
||||||
|
setattr(self, attr, getattr(self.f, attr))
|
||||||
|
|
||||||
|
def write(self, b):
|
||||||
|
"""
|
||||||
|
Custom write method that calls out to onionshare_update_func
|
||||||
|
"""
|
||||||
|
bytes_written = self.f.write(b)
|
||||||
|
self.onionshare_update_func(self.onionshare_filename, bytes_written)
|
||||||
|
|
||||||
|
|
||||||
|
class ReceiveModeRequest(Request):
|
||||||
|
"""
|
||||||
|
A custom flask Request object that keeps track of how much data has been
|
||||||
|
uploaded for each file, for receive mode.
|
||||||
|
"""
|
||||||
|
def __init__(self, environ, populate_request=True, shallow=False):
|
||||||
|
super(ReceiveModeRequest, self).__init__(environ, populate_request, shallow)
|
||||||
|
self.web = environ['web']
|
||||||
|
|
||||||
|
# A dictionary that maps filenames to the bytes uploaded so far
|
||||||
|
self.onionshare_progress = {}
|
||||||
|
|
||||||
|
def _get_file_stream(self, total_content_length, content_type, filename=None, content_length=None):
|
||||||
|
"""
|
||||||
|
This gets called for each file that gets uploaded, and returns an file-like
|
||||||
|
writable stream.
|
||||||
|
"""
|
||||||
|
if len(self.onionshare_progress) > 0:
|
||||||
|
print('')
|
||||||
|
self.onionshare_progress[filename] = 0
|
||||||
|
return ReceiveModeTemporaryFile(filename, self.onionshare_update_func)
|
||||||
|
|
||||||
|
def close(self):
|
||||||
|
"""
|
||||||
|
When closing the request, print a newline if this was a file upload.
|
||||||
|
"""
|
||||||
|
super(ReceiveModeRequest, self).close()
|
||||||
|
if len(self.onionshare_progress) > 0:
|
||||||
|
print('')
|
||||||
|
|
||||||
|
def onionshare_update_func(self, filename, length):
|
||||||
|
"""
|
||||||
|
Keep track of the bytes uploaded so far for all files.
|
||||||
|
"""
|
||||||
|
self.onionshare_progress[filename] += length
|
||||||
|
print('{} - {} '.format(self.web.common.human_readable_filesize(self.onionshare_progress[filename]), filename), end='\r')
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
"""
|
"""
|
||||||
OnionShare | https://onionshare.org/
|
OnionShare | https://onionshare.org/
|
||||||
|
|
||||||
Copyright (C) 2017 Micah Lee <micah@micahflee.com>
|
Copyright (C) 2018 Micah Lee <micah@micahflee.com>
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
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
|
it under the terms of the GNU General Public License as published by
|
||||||
|
@ -22,10 +22,11 @@ import os, sys, platform, argparse
|
||||||
from .alert import Alert
|
from .alert import Alert
|
||||||
from PyQt5 import QtCore, QtWidgets
|
from PyQt5 import QtCore, QtWidgets
|
||||||
|
|
||||||
from onionshare import strings, common, web
|
from onionshare import strings
|
||||||
|
from onionshare.common import Common
|
||||||
|
from onionshare.web import Web
|
||||||
from onionshare.onion import Onion
|
from onionshare.onion import Onion
|
||||||
from onionshare.onionshare import OnionShare
|
from onionshare.onionshare import OnionShare
|
||||||
from onionshare.settings import Settings
|
|
||||||
|
|
||||||
from .onionshare_gui import OnionShareGui
|
from .onionshare_gui import OnionShareGui
|
||||||
|
|
||||||
|
@ -34,9 +35,8 @@ class Application(QtWidgets.QApplication):
|
||||||
This is Qt's QApplication class. It has been overridden to support threads
|
This is Qt's QApplication class. It has been overridden to support threads
|
||||||
and the quick keyboard shortcut.
|
and the quick keyboard shortcut.
|
||||||
"""
|
"""
|
||||||
def __init__(self):
|
def __init__(self, common):
|
||||||
system = common.get_platform()
|
if common.platform == 'Linux' or common.platform == 'BSD':
|
||||||
if system == 'Linux' or system == 'BSD':
|
|
||||||
self.setAttribute(QtCore.Qt.AA_X11InitThreads, True)
|
self.setAttribute(QtCore.Qt.AA_X11InitThreads, True)
|
||||||
QtWidgets.QApplication.__init__(self, sys.argv)
|
QtWidgets.QApplication.__init__(self, sys.argv)
|
||||||
self.installEventFilter(self)
|
self.installEventFilter(self)
|
||||||
|
@ -53,12 +53,14 @@ def main():
|
||||||
"""
|
"""
|
||||||
The main() function implements all of the logic that the GUI version of onionshare uses.
|
The main() function implements all of the logic that the GUI version of onionshare uses.
|
||||||
"""
|
"""
|
||||||
|
common = Common()
|
||||||
|
|
||||||
strings.load_strings(common)
|
strings.load_strings(common)
|
||||||
print(strings._('version_string').format(common.get_version()))
|
print(strings._('version_string').format(common.version))
|
||||||
|
|
||||||
# Start the Qt app
|
# Start the Qt app
|
||||||
global qtapp
|
global qtapp
|
||||||
qtapp = Application()
|
qtapp = Application(common)
|
||||||
|
|
||||||
# Parse arguments
|
# Parse arguments
|
||||||
parser = argparse.ArgumentParser(formatter_class=lambda prog: argparse.HelpFormatter(prog,max_help_position=48))
|
parser = argparse.ArgumentParser(formatter_class=lambda prog: argparse.HelpFormatter(prog,max_help_position=48))
|
||||||
|
@ -83,32 +85,32 @@ def main():
|
||||||
debug = bool(args.debug)
|
debug = bool(args.debug)
|
||||||
|
|
||||||
# Debug mode?
|
# Debug mode?
|
||||||
if debug:
|
common.debug = debug
|
||||||
common.set_debug(debug)
|
|
||||||
web.debug_mode()
|
|
||||||
|
|
||||||
# Validation
|
# Validation
|
||||||
if filenames:
|
if filenames:
|
||||||
valid = True
|
valid = True
|
||||||
for filename in filenames:
|
for filename in filenames:
|
||||||
if not os.path.isfile(filename) and not os.path.isdir(filename):
|
if not os.path.isfile(filename) and not os.path.isdir(filename):
|
||||||
Alert(strings._("not_a_file", True).format(filename))
|
Alert(common, strings._("not_a_file", True).format(filename))
|
||||||
valid = False
|
valid = False
|
||||||
if not os.access(filename, os.R_OK):
|
if not os.access(filename, os.R_OK):
|
||||||
Alert(strings._("not_a_readable_file", True).format(filename))
|
Alert(common, strings._("not_a_readable_file", True).format(filename))
|
||||||
valid = False
|
valid = False
|
||||||
if not valid:
|
if not valid:
|
||||||
sys.exit()
|
sys.exit()
|
||||||
|
|
||||||
|
# Create the Web object
|
||||||
|
web = Web(common, stay_open, True)
|
||||||
|
|
||||||
# Start the Onion
|
# Start the Onion
|
||||||
onion = Onion()
|
onion = Onion(common)
|
||||||
|
|
||||||
# Start the OnionShare app
|
# Start the OnionShare app
|
||||||
web.set_stay_open(stay_open)
|
app = OnionShare(common, onion, local_only, stay_open, shutdown_timeout)
|
||||||
app = OnionShare(onion, local_only, stay_open, shutdown_timeout)
|
|
||||||
|
|
||||||
# Launch the gui
|
# Launch the gui
|
||||||
gui = OnionShareGui(onion, qtapp, app, filenames, config)
|
gui = OnionShareGui(common, web, onion, qtapp, app, filenames, config, local_only)
|
||||||
|
|
||||||
# Clean up when app quits
|
# Clean up when app quits
|
||||||
def shutdown():
|
def shutdown():
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
"""
|
"""
|
||||||
OnionShare | https://onionshare.org/
|
OnionShare | https://onionshare.org/
|
||||||
|
|
||||||
Copyright (C) 2017 Micah Lee <micah@micahflee.com>
|
Copyright (C) 2018 Micah Lee <micah@micahflee.com>
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
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
|
it under the terms of the GNU General Public License as published by
|
||||||
|
@ -19,18 +19,19 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
"""
|
"""
|
||||||
from PyQt5 import QtCore, QtWidgets, QtGui
|
from PyQt5 import QtCore, QtWidgets, QtGui
|
||||||
|
|
||||||
from onionshare import common
|
|
||||||
|
|
||||||
class Alert(QtWidgets.QMessageBox):
|
class Alert(QtWidgets.QMessageBox):
|
||||||
"""
|
"""
|
||||||
An alert box dialog.
|
An alert box dialog.
|
||||||
"""
|
"""
|
||||||
def __init__(self, message, icon=QtWidgets.QMessageBox.NoIcon, buttons=QtWidgets.QMessageBox.Ok, autostart=True):
|
def __init__(self, common, message, icon=QtWidgets.QMessageBox.NoIcon, buttons=QtWidgets.QMessageBox.Ok, autostart=True):
|
||||||
super(Alert, self).__init__(None)
|
super(Alert, self).__init__(None)
|
||||||
common.log('Alert', '__init__')
|
|
||||||
|
self.common = common
|
||||||
|
|
||||||
|
self.common.log('Alert', '__init__')
|
||||||
|
|
||||||
self.setWindowTitle("OnionShare")
|
self.setWindowTitle("OnionShare")
|
||||||
self.setWindowIcon(QtGui.QIcon(common.get_resource_path('images/logo.png')))
|
self.setWindowIcon(QtGui.QIcon(self.common.get_resource_path('images/logo.png')))
|
||||||
self.setText(message)
|
self.setText(message)
|
||||||
self.setIcon(icon)
|
self.setIcon(icon)
|
||||||
self.setStandardButtons(buttons)
|
self.setStandardButtons(buttons)
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
"""
|
"""
|
||||||
OnionShare | https://onionshare.org/
|
OnionShare | https://onionshare.org/
|
||||||
|
|
||||||
Copyright (C) 2017 Micah Lee <micah@micahflee.com>
|
Copyright (C) 2018 Micah Lee <micah@micahflee.com>
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
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
|
it under the terms of the GNU General Public License as published by
|
||||||
|
@ -19,13 +19,15 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
"""
|
"""
|
||||||
import time
|
import time
|
||||||
|
|
||||||
from PyQt5 import QtCore, QtWidgets
|
from PyQt5 import QtCore, QtWidgets, QtGui
|
||||||
|
|
||||||
from onionshare import strings, common
|
from onionshare import strings
|
||||||
|
|
||||||
class Download(object):
|
class Download(object):
|
||||||
|
|
||||||
def __init__(self, download_id, total_bytes):
|
def __init__(self, common, download_id, total_bytes):
|
||||||
|
self.common = common
|
||||||
|
|
||||||
self.download_id = download_id
|
self.download_id = download_id
|
||||||
self.started = time.time()
|
self.started = time.time()
|
||||||
self.total_bytes = total_bytes
|
self.total_bytes = total_bytes
|
||||||
|
@ -64,7 +66,7 @@ class Download(object):
|
||||||
self.progress_bar.setValue(downloaded_bytes)
|
self.progress_bar.setValue(downloaded_bytes)
|
||||||
if downloaded_bytes == self.progress_bar.total_bytes:
|
if downloaded_bytes == self.progress_bar.total_bytes:
|
||||||
pb_fmt = strings._('gui_download_progress_complete').format(
|
pb_fmt = strings._('gui_download_progress_complete').format(
|
||||||
common.format_seconds(time.time() - self.started))
|
self.common.format_seconds(time.time() - self.started))
|
||||||
else:
|
else:
|
||||||
elapsed = time.time() - self.started
|
elapsed = time.time() - self.started
|
||||||
if elapsed < 10:
|
if elapsed < 10:
|
||||||
|
@ -72,10 +74,10 @@ class Download(object):
|
||||||
# This prevents a "Windows copy dialog"-esque experience at
|
# This prevents a "Windows copy dialog"-esque experience at
|
||||||
# the beginning of the download.
|
# the beginning of the download.
|
||||||
pb_fmt = strings._('gui_download_progress_starting').format(
|
pb_fmt = strings._('gui_download_progress_starting').format(
|
||||||
common.human_readable_filesize(downloaded_bytes))
|
self.common.human_readable_filesize(downloaded_bytes))
|
||||||
else:
|
else:
|
||||||
pb_fmt = strings._('gui_download_progress_eta').format(
|
pb_fmt = strings._('gui_download_progress_eta').format(
|
||||||
common.human_readable_filesize(downloaded_bytes),
|
self.common.human_readable_filesize(downloaded_bytes),
|
||||||
self.estimated_time_remaining)
|
self.estimated_time_remaining)
|
||||||
|
|
||||||
self.progress_bar.setFormat(pb_fmt)
|
self.progress_bar.setFormat(pb_fmt)
|
||||||
|
@ -85,7 +87,7 @@ class Download(object):
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def estimated_time_remaining(self):
|
def estimated_time_remaining(self):
|
||||||
return common.estimated_time_remaining(self.downloaded_bytes,
|
return self.common.estimated_time_remaining(self.downloaded_bytes,
|
||||||
self.total_bytes,
|
self.total_bytes,
|
||||||
self.started)
|
self.started)
|
||||||
|
|
||||||
|
@ -95,22 +97,45 @@ class Downloads(QtWidgets.QWidget):
|
||||||
The downloads chunk of the GUI. This lists all of the active download
|
The downloads chunk of the GUI. This lists all of the active download
|
||||||
progress bars.
|
progress bars.
|
||||||
"""
|
"""
|
||||||
def __init__(self):
|
def __init__(self, common):
|
||||||
super(Downloads, self).__init__()
|
super(Downloads, self).__init__()
|
||||||
|
|
||||||
|
self.common = common
|
||||||
|
|
||||||
self.downloads = {}
|
self.downloads = {}
|
||||||
|
|
||||||
|
self.downloads_container = QtWidgets.QScrollArea()
|
||||||
|
self.downloads_container.setWidget(self)
|
||||||
|
self.downloads_container.setWindowTitle(strings._('gui_downloads', True))
|
||||||
|
self.downloads_container.setWidgetResizable(True)
|
||||||
|
self.downloads_container.setMaximumHeight(600)
|
||||||
|
self.downloads_container.setMinimumHeight(150)
|
||||||
|
self.downloads_container.setMinimumWidth(350)
|
||||||
|
self.downloads_container.setWindowIcon(QtGui.QIcon(common.get_resource_path('images/logo.png')))
|
||||||
|
self.downloads_container.setWindowFlags(QtCore.Qt.Sheet | QtCore.Qt.WindowTitleHint | QtCore.Qt.WindowSystemMenuHint | QtCore.Qt.CustomizeWindowHint)
|
||||||
|
self.downloads_container.vbar = self.downloads_container.verticalScrollBar()
|
||||||
|
|
||||||
|
self.downloads_label = QtWidgets.QLabel(strings._('gui_downloads', True))
|
||||||
|
self.downloads_label.setStyleSheet('QLabel { font-weight: bold; font-size 14px; text-align: center; }')
|
||||||
|
self.no_downloads_label = QtWidgets.QLabel(strings._('gui_no_downloads', True))
|
||||||
|
|
||||||
|
self.downloads_layout = QtWidgets.QVBoxLayout()
|
||||||
|
|
||||||
self.layout = QtWidgets.QVBoxLayout()
|
self.layout = QtWidgets.QVBoxLayout()
|
||||||
|
self.layout.addWidget(self.downloads_label)
|
||||||
|
self.layout.addWidget(self.no_downloads_label)
|
||||||
|
self.layout.addLayout(self.downloads_layout)
|
||||||
|
self.layout.addStretch()
|
||||||
self.setLayout(self.layout)
|
self.setLayout(self.layout)
|
||||||
|
|
||||||
def add_download(self, download_id, total_bytes):
|
def add_download(self, download_id, total_bytes):
|
||||||
"""
|
"""
|
||||||
Add a new download progress bar.
|
Add a new download progress bar.
|
||||||
"""
|
"""
|
||||||
self.parent().show()
|
|
||||||
|
|
||||||
# add it to the list
|
# add it to the list
|
||||||
download = Download(download_id, total_bytes)
|
download = Download(self.common, download_id, total_bytes)
|
||||||
self.downloads[download_id] = download
|
self.downloads[download_id] = download
|
||||||
self.layout.insertWidget(-1, download.progress_bar)
|
self.downloads_layout.addWidget(download.progress_bar)
|
||||||
|
|
||||||
def update_download(self, download_id, downloaded_bytes):
|
def update_download(self, download_id, downloaded_bytes):
|
||||||
"""
|
"""
|
||||||
|
@ -129,6 +154,6 @@ class Downloads(QtWidgets.QWidget):
|
||||||
Reset the downloads back to zero
|
Reset the downloads back to zero
|
||||||
"""
|
"""
|
||||||
for download in self.downloads.values():
|
for download in self.downloads.values():
|
||||||
self.layout.removeWidget(download.progress_bar)
|
self.downloads_layout.removeWidget(download.progress_bar)
|
||||||
download.progress_bar.close()
|
download.progress_bar.close()
|
||||||
self.downloads = {}
|
self.downloads = {}
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
"""
|
"""
|
||||||
OnionShare | https://onionshare.org/
|
OnionShare | https://onionshare.org/
|
||||||
|
|
||||||
Copyright (C) 2017 Micah Lee <micah@micahflee.com>
|
Copyright (C) 2018 Micah Lee <micah@micahflee.com>
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
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
|
it under the terms of the GNU General Public License as published by
|
||||||
|
@ -21,21 +21,24 @@ import os
|
||||||
from PyQt5 import QtCore, QtWidgets, QtGui
|
from PyQt5 import QtCore, QtWidgets, QtGui
|
||||||
from .alert import Alert
|
from .alert import Alert
|
||||||
|
|
||||||
from onionshare import strings, common
|
from onionshare import strings
|
||||||
|
|
||||||
class DropHereLabel(QtWidgets.QLabel):
|
class DropHereLabel(QtWidgets.QLabel):
|
||||||
"""
|
"""
|
||||||
When there are no files or folders in the FileList yet, display the
|
When there are no files or folders in the FileList yet, display the
|
||||||
'drop files here' message and graphic.
|
'drop files here' message and graphic.
|
||||||
"""
|
"""
|
||||||
def __init__(self, parent, image=False):
|
def __init__(self, common, parent, image=False):
|
||||||
self.parent = parent
|
self.parent = parent
|
||||||
super(DropHereLabel, self).__init__(parent=parent)
|
super(DropHereLabel, self).__init__(parent=parent)
|
||||||
|
|
||||||
|
self.common = common
|
||||||
|
|
||||||
self.setAcceptDrops(True)
|
self.setAcceptDrops(True)
|
||||||
self.setAlignment(QtCore.Qt.AlignCenter)
|
self.setAlignment(QtCore.Qt.AlignCenter)
|
||||||
|
|
||||||
if image:
|
if image:
|
||||||
self.setPixmap(QtGui.QPixmap.fromImage(QtGui.QImage(common.get_resource_path('images/logo_transparent.png'))))
|
self.setPixmap(QtGui.QPixmap.fromImage(QtGui.QImage(self.common.get_resource_path('images/logo_transparent.png'))))
|
||||||
else:
|
else:
|
||||||
self.setText(strings._('gui_drag_and_drop', True))
|
self.setText(strings._('gui_drag_and_drop', True))
|
||||||
self.setStyleSheet('color: #999999;')
|
self.setStyleSheet('color: #999999;')
|
||||||
|
@ -53,9 +56,12 @@ class DropCountLabel(QtWidgets.QLabel):
|
||||||
While dragging files over the FileList, this counter displays the
|
While dragging files over the FileList, this counter displays the
|
||||||
number of files you're dragging.
|
number of files you're dragging.
|
||||||
"""
|
"""
|
||||||
def __init__(self, parent):
|
def __init__(self, common, parent):
|
||||||
self.parent = parent
|
self.parent = parent
|
||||||
super(DropCountLabel, self).__init__(parent=parent)
|
super(DropCountLabel, self).__init__(parent=parent)
|
||||||
|
|
||||||
|
self.common = common
|
||||||
|
|
||||||
self.setAcceptDrops(True)
|
self.setAcceptDrops(True)
|
||||||
self.setAlignment(QtCore.Qt.AlignCenter)
|
self.setAlignment(QtCore.Qt.AlignCenter)
|
||||||
self.setText(strings._('gui_drag_and_drop', True))
|
self.setText(strings._('gui_drag_and_drop', True))
|
||||||
|
@ -74,16 +80,19 @@ class FileList(QtWidgets.QListWidget):
|
||||||
files_dropped = QtCore.pyqtSignal()
|
files_dropped = QtCore.pyqtSignal()
|
||||||
files_updated = QtCore.pyqtSignal()
|
files_updated = QtCore.pyqtSignal()
|
||||||
|
|
||||||
def __init__(self, parent=None):
|
def __init__(self, common, parent=None):
|
||||||
super(FileList, self).__init__(parent)
|
super(FileList, self).__init__(parent)
|
||||||
|
|
||||||
|
self.common = common
|
||||||
|
|
||||||
self.setAcceptDrops(True)
|
self.setAcceptDrops(True)
|
||||||
self.setIconSize(QtCore.QSize(32, 32))
|
self.setIconSize(QtCore.QSize(32, 32))
|
||||||
self.setSortingEnabled(True)
|
self.setSortingEnabled(True)
|
||||||
self.setMinimumHeight(205)
|
self.setMinimumHeight(205)
|
||||||
self.setSelectionMode(QtWidgets.QAbstractItemView.ExtendedSelection)
|
self.setSelectionMode(QtWidgets.QAbstractItemView.ExtendedSelection)
|
||||||
self.drop_here_image = DropHereLabel(self, True)
|
self.drop_here_image = DropHereLabel(self.common, self, True)
|
||||||
self.drop_here_text = DropHereLabel(self, False)
|
self.drop_here_text = DropHereLabel(self.common, self, False)
|
||||||
self.drop_count = DropCountLabel(self)
|
self.drop_count = DropCountLabel(self.common, self)
|
||||||
self.resizeEvent(None)
|
self.resizeEvent(None)
|
||||||
self.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOn)
|
self.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOn)
|
||||||
|
|
||||||
|
@ -206,7 +215,7 @@ class FileList(QtWidgets.QListWidget):
|
||||||
|
|
||||||
if filename not in filenames:
|
if filename not in filenames:
|
||||||
if not os.access(filename, os.R_OK):
|
if not os.access(filename, os.R_OK):
|
||||||
Alert(strings._("not_a_readable_file", True).format(filename))
|
Alert(self.common, strings._("not_a_readable_file", True).format(filename))
|
||||||
return
|
return
|
||||||
|
|
||||||
fileinfo = QtCore.QFileInfo(filename)
|
fileinfo = QtCore.QFileInfo(filename)
|
||||||
|
@ -215,10 +224,10 @@ class FileList(QtWidgets.QListWidget):
|
||||||
|
|
||||||
if os.path.isfile(filename):
|
if os.path.isfile(filename):
|
||||||
size_bytes = fileinfo.size()
|
size_bytes = fileinfo.size()
|
||||||
size_readable = common.human_readable_filesize(size_bytes)
|
size_readable = self.common.human_readable_filesize(size_bytes)
|
||||||
else:
|
else:
|
||||||
size_bytes = common.dir_size(filename)
|
size_bytes = self.common.dir_size(filename)
|
||||||
size_readable = common.human_readable_filesize(size_bytes)
|
size_readable = self.common.human_readable_filesize(size_bytes)
|
||||||
|
|
||||||
# Create a new item
|
# Create a new item
|
||||||
item = QtWidgets.QListWidgetItem()
|
item = QtWidgets.QListWidgetItem()
|
||||||
|
@ -245,7 +254,7 @@ class FileList(QtWidgets.QListWidget):
|
||||||
item.item_button = QtWidgets.QPushButton()
|
item.item_button = QtWidgets.QPushButton()
|
||||||
item.item_button.setDefault(False)
|
item.item_button.setDefault(False)
|
||||||
item.item_button.setFlat(True)
|
item.item_button.setFlat(True)
|
||||||
item.item_button.setIcon( QtGui.QIcon(common.get_resource_path('images/file_delete.png')) )
|
item.item_button.setIcon( QtGui.QIcon(self.common.get_resource_path('images/file_delete.png')) )
|
||||||
item.item_button.clicked.connect(delete_item)
|
item.item_button.clicked.connect(delete_item)
|
||||||
item.item_button.setSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed)
|
item.item_button.setSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed)
|
||||||
|
|
||||||
|
@ -277,12 +286,15 @@ class FileSelection(QtWidgets.QVBoxLayout):
|
||||||
The list of files and folders in the GUI, as well as buttons to add and
|
The list of files and folders in the GUI, as well as buttons to add and
|
||||||
delete the files and folders.
|
delete the files and folders.
|
||||||
"""
|
"""
|
||||||
def __init__(self):
|
def __init__(self, common):
|
||||||
super(FileSelection, self).__init__()
|
super(FileSelection, self).__init__()
|
||||||
|
|
||||||
|
self.common = common
|
||||||
|
|
||||||
self.server_on = False
|
self.server_on = False
|
||||||
|
|
||||||
# File list
|
# File list
|
||||||
self.file_list = FileList()
|
self.file_list = FileList(self.common)
|
||||||
self.file_list.itemSelectionChanged.connect(self.update)
|
self.file_list.itemSelectionChanged.connect(self.update)
|
||||||
self.file_list.files_dropped.connect(self.update)
|
self.file_list.files_dropped.connect(self.update)
|
||||||
self.file_list.files_updated.connect(self.update)
|
self.file_list.files_updated.connect(self.update)
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
"""
|
"""
|
||||||
OnionShare | https://onionshare.org/
|
OnionShare | https://onionshare.org/
|
||||||
|
|
||||||
Copyright (C) 2017 Micah Lee <micah@micahflee.com>
|
Copyright (C) 2018 Micah Lee <micah@micahflee.com>
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
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
|
it under the terms of the GNU General Public License as published by
|
||||||
|
@ -17,11 +17,14 @@ GNU General Public License for more details.
|
||||||
You should have received a copy of the GNU General Public License
|
You should have received a copy of the GNU General Public License
|
||||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
"""
|
"""
|
||||||
import os, threading, time
|
import os
|
||||||
|
import threading
|
||||||
|
import time
|
||||||
|
import queue
|
||||||
from PyQt5 import QtCore, QtWidgets, QtGui
|
from PyQt5 import QtCore, QtWidgets, QtGui
|
||||||
|
|
||||||
from onionshare import strings, common, web
|
from onionshare import strings, common
|
||||||
from onionshare.settings import Settings
|
from onionshare.common import Common, ShutdownTimer
|
||||||
from onionshare.onion import *
|
from onionshare.onion import *
|
||||||
|
|
||||||
from .tor_connection_dialog import TorConnectionDialog
|
from .tor_connection_dialog import TorConnectionDialog
|
||||||
|
@ -43,34 +46,36 @@ class OnionShareGui(QtWidgets.QMainWindow):
|
||||||
starting_server_step3 = QtCore.pyqtSignal()
|
starting_server_step3 = QtCore.pyqtSignal()
|
||||||
starting_server_error = QtCore.pyqtSignal(str)
|
starting_server_error = QtCore.pyqtSignal(str)
|
||||||
|
|
||||||
def __init__(self, onion, qtapp, app, filenames, config=False):
|
def __init__(self, common, web, onion, qtapp, app, filenames, config=False, local_only=False):
|
||||||
super(OnionShareGui, self).__init__()
|
super(OnionShareGui, self).__init__()
|
||||||
|
|
||||||
|
self.common = common
|
||||||
|
self.common.log('OnionShareGui', '__init__')
|
||||||
|
|
||||||
self._initSystemTray()
|
self._initSystemTray()
|
||||||
|
|
||||||
common.log('OnionShareGui', '__init__')
|
self.web = web
|
||||||
|
|
||||||
self.onion = onion
|
self.onion = onion
|
||||||
self.qtapp = qtapp
|
self.qtapp = qtapp
|
||||||
self.app = app
|
self.app = app
|
||||||
|
self.local_only = local_only
|
||||||
|
|
||||||
self.setWindowTitle('OnionShare')
|
self.setWindowTitle('OnionShare')
|
||||||
self.setWindowIcon(QtGui.QIcon(common.get_resource_path('images/logo.png')))
|
self.setWindowIcon(QtGui.QIcon(self.common.get_resource_path('images/logo.png')))
|
||||||
self.setMinimumWidth(430)
|
self.setMinimumWidth(430)
|
||||||
|
|
||||||
# Load settings
|
# Load settings
|
||||||
self.config = config
|
self.config = config
|
||||||
self.settings = Settings(self.config)
|
self.common.load_settings(self.config)
|
||||||
self.settings.load()
|
|
||||||
|
|
||||||
# File selection
|
# File selection
|
||||||
self.file_selection = FileSelection()
|
self.file_selection = FileSelection(self.common)
|
||||||
if filenames:
|
if filenames:
|
||||||
for filename in filenames:
|
for filename in filenames:
|
||||||
self.file_selection.file_list.add_file(filename)
|
self.file_selection.file_list.add_file(filename)
|
||||||
|
|
||||||
# Server status
|
# Server status
|
||||||
self.server_status = ServerStatus(self.qtapp, self.app, web, self.file_selection, self.settings)
|
self.server_status = ServerStatus(self.common, self.qtapp, self.app, self.web, self.file_selection)
|
||||||
self.server_status.server_started.connect(self.file_selection.server_started)
|
self.server_status.server_started.connect(self.file_selection.server_started)
|
||||||
self.server_status.server_started.connect(self.start_server)
|
self.server_status.server_started.connect(self.start_server)
|
||||||
self.server_status.server_started.connect(self.update_server_status_indicator)
|
self.server_status.server_started.connect(self.update_server_status_indicator)
|
||||||
|
@ -102,14 +107,7 @@ class OnionShareGui(QtWidgets.QMainWindow):
|
||||||
self.filesize_warning.hide()
|
self.filesize_warning.hide()
|
||||||
|
|
||||||
# Downloads
|
# Downloads
|
||||||
self.downloads = Downloads()
|
self.downloads = Downloads(self.common)
|
||||||
self.downloads_container = QtWidgets.QScrollArea()
|
|
||||||
self.downloads_container.setWidget(self.downloads)
|
|
||||||
self.downloads_container.setWidgetResizable(True)
|
|
||||||
self.downloads_container.setMaximumHeight(200)
|
|
||||||
self.downloads_container.setMinimumHeight(75)
|
|
||||||
self.vbar = self.downloads_container.verticalScrollBar()
|
|
||||||
self.downloads_container.hide() # downloads start out hidden
|
|
||||||
self.new_download = False
|
self.new_download = False
|
||||||
self.downloads_in_progress = 0
|
self.downloads_in_progress = 0
|
||||||
self.downloads_completed = 0
|
self.downloads_completed = 0
|
||||||
|
@ -119,6 +117,12 @@ class OnionShareGui(QtWidgets.QMainWindow):
|
||||||
self.info_label = QtWidgets.QLabel()
|
self.info_label = QtWidgets.QLabel()
|
||||||
self.info_label.setStyleSheet('QLabel { font-size: 12px; color: #666666; }')
|
self.info_label.setStyleSheet('QLabel { font-size: 12px; color: #666666; }')
|
||||||
|
|
||||||
|
self.info_show_downloads = QtWidgets.QToolButton()
|
||||||
|
self.info_show_downloads.setIcon(QtGui.QIcon(self.common.get_resource_path('images/download_window_gray.png')))
|
||||||
|
self.info_show_downloads.setCheckable(True)
|
||||||
|
self.info_show_downloads.toggled.connect(self.downloads_toggled)
|
||||||
|
self.info_show_downloads.setToolTip(strings._('gui_downloads_window_tooltip', True))
|
||||||
|
|
||||||
self.info_in_progress_downloads_count = QtWidgets.QLabel()
|
self.info_in_progress_downloads_count = QtWidgets.QLabel()
|
||||||
self.info_in_progress_downloads_count.setStyleSheet('QLabel { font-size: 12px; color: #666666; }')
|
self.info_in_progress_downloads_count.setStyleSheet('QLabel { font-size: 12px; color: #666666; }')
|
||||||
|
|
||||||
|
@ -132,6 +136,7 @@ class OnionShareGui(QtWidgets.QMainWindow):
|
||||||
self.info_layout.addStretch()
|
self.info_layout.addStretch()
|
||||||
self.info_layout.addWidget(self.info_in_progress_downloads_count)
|
self.info_layout.addWidget(self.info_in_progress_downloads_count)
|
||||||
self.info_layout.addWidget(self.info_completed_downloads_count)
|
self.info_layout.addWidget(self.info_completed_downloads_count)
|
||||||
|
self.info_layout.addWidget(self.info_show_downloads)
|
||||||
|
|
||||||
self.info_widget = QtWidgets.QWidget()
|
self.info_widget = QtWidgets.QWidget()
|
||||||
self.info_widget.setLayout(self.info_layout)
|
self.info_widget.setLayout(self.info_layout)
|
||||||
|
@ -142,13 +147,13 @@ class OnionShareGui(QtWidgets.QMainWindow):
|
||||||
self.settings_button.setDefault(False)
|
self.settings_button.setDefault(False)
|
||||||
self.settings_button.setFlat(True)
|
self.settings_button.setFlat(True)
|
||||||
self.settings_button.setFixedWidth(40)
|
self.settings_button.setFixedWidth(40)
|
||||||
self.settings_button.setIcon( QtGui.QIcon(common.get_resource_path('images/settings.png')) )
|
self.settings_button.setIcon( QtGui.QIcon(self.common.get_resource_path('images/settings.png')) )
|
||||||
self.settings_button.clicked.connect(self.open_settings)
|
self.settings_button.clicked.connect(self.open_settings)
|
||||||
|
|
||||||
# Server status indicator on the status bar
|
# Server status indicator on the status bar
|
||||||
self.server_status_image_stopped = QtGui.QImage(common.get_resource_path('images/server_stopped.png'))
|
self.server_status_image_stopped = QtGui.QImage(self.common.get_resource_path('images/server_stopped.png'))
|
||||||
self.server_status_image_working = QtGui.QImage(common.get_resource_path('images/server_working.png'))
|
self.server_status_image_working = QtGui.QImage(self.common.get_resource_path('images/server_working.png'))
|
||||||
self.server_status_image_started = QtGui.QImage(common.get_resource_path('images/server_started.png'))
|
self.server_status_image_started = QtGui.QImage(self.common.get_resource_path('images/server_started.png'))
|
||||||
self.server_status_image_label = QtWidgets.QLabel()
|
self.server_status_image_label = QtWidgets.QLabel()
|
||||||
self.server_status_image_label.setFixedWidth(20)
|
self.server_status_image_label.setFixedWidth(20)
|
||||||
self.server_status_label = QtWidgets.QLabel()
|
self.server_status_label = QtWidgets.QLabel()
|
||||||
|
@ -189,7 +194,6 @@ class OnionShareGui(QtWidgets.QMainWindow):
|
||||||
primary_action_layout = QtWidgets.QVBoxLayout()
|
primary_action_layout = QtWidgets.QVBoxLayout()
|
||||||
primary_action_layout.addWidget(self.server_status)
|
primary_action_layout.addWidget(self.server_status)
|
||||||
primary_action_layout.addWidget(self.filesize_warning)
|
primary_action_layout.addWidget(self.filesize_warning)
|
||||||
primary_action_layout.addWidget(self.downloads_container)
|
|
||||||
self.primary_action = QtWidgets.QWidget()
|
self.primary_action = QtWidgets.QWidget()
|
||||||
self.primary_action.setLayout(primary_action_layout)
|
self.primary_action.setLayout(primary_action_layout)
|
||||||
self.primary_action.hide()
|
self.primary_action.hide()
|
||||||
|
@ -216,9 +220,10 @@ class OnionShareGui(QtWidgets.QMainWindow):
|
||||||
self.timer.timeout.connect(self.check_for_requests)
|
self.timer.timeout.connect(self.check_for_requests)
|
||||||
|
|
||||||
# Start the "Connecting to Tor" dialog, which calls onion.connect()
|
# Start the "Connecting to Tor" dialog, which calls onion.connect()
|
||||||
tor_con = TorConnectionDialog(self.qtapp, self.settings, self.onion)
|
tor_con = TorConnectionDialog(self.common, self.qtapp, self.onion)
|
||||||
tor_con.canceled.connect(self._tor_connection_canceled)
|
tor_con.canceled.connect(self._tor_connection_canceled)
|
||||||
tor_con.open_settings.connect(self._tor_connection_open_settings)
|
tor_con.open_settings.connect(self._tor_connection_open_settings)
|
||||||
|
if not self.local_only:
|
||||||
tor_con.start()
|
tor_con.start()
|
||||||
|
|
||||||
# Start the timer
|
# Start the timer
|
||||||
|
@ -239,7 +244,7 @@ class OnionShareGui(QtWidgets.QMainWindow):
|
||||||
for index in range(self.file_selection.file_list.count()):
|
for index in range(self.file_selection.file_list.count()):
|
||||||
item = self.file_selection.file_list.item(index)
|
item = self.file_selection.file_list.item(index)
|
||||||
total_size_bytes += item.size_bytes
|
total_size_bytes += item.size_bytes
|
||||||
total_size_readable = common.human_readable_filesize(total_size_bytes)
|
total_size_readable = self.common.human_readable_filesize(total_size_bytes)
|
||||||
|
|
||||||
if file_count > 1:
|
if file_count > 1:
|
||||||
self.info_label.setText(strings._('gui_file_info', True).format(file_count, total_size_readable))
|
self.info_label.setText(strings._('gui_file_info', True).format(file_count, total_size_readable))
|
||||||
|
@ -254,7 +259,7 @@ class OnionShareGui(QtWidgets.QMainWindow):
|
||||||
self.adjustSize()
|
self.adjustSize()
|
||||||
|
|
||||||
def update_server_status_indicator(self):
|
def update_server_status_indicator(self):
|
||||||
common.log('OnionShareGui', 'update_server_status_indicator')
|
self.common.log('OnionShareGui', 'update_server_status_indicator')
|
||||||
|
|
||||||
# Set the status image
|
# Set the status image
|
||||||
if self.server_status.status == self.server_status.STATUS_STOPPED:
|
if self.server_status.status == self.server_status.STATUS_STOPPED:
|
||||||
|
@ -268,8 +273,6 @@ class OnionShareGui(QtWidgets.QMainWindow):
|
||||||
self.server_status_label.setText(strings._('gui_status_indicator_started', True))
|
self.server_status_label.setText(strings._('gui_status_indicator_started', True))
|
||||||
|
|
||||||
def _initSystemTray(self):
|
def _initSystemTray(self):
|
||||||
system = common.get_platform()
|
|
||||||
|
|
||||||
menu = QtWidgets.QMenu()
|
menu = QtWidgets.QMenu()
|
||||||
self.settingsAction = menu.addAction(strings._('gui_settings_window_title', True))
|
self.settingsAction = menu.addAction(strings._('gui_settings_window_title', True))
|
||||||
self.settingsAction.triggered.connect(self.open_settings)
|
self.settingsAction.triggered.connect(self.open_settings)
|
||||||
|
@ -280,10 +283,10 @@ class OnionShareGui(QtWidgets.QMainWindow):
|
||||||
|
|
||||||
self.systemTray = QtWidgets.QSystemTrayIcon(self)
|
self.systemTray = QtWidgets.QSystemTrayIcon(self)
|
||||||
# The convention is Mac systray icons are always grayscale
|
# The convention is Mac systray icons are always grayscale
|
||||||
if system == 'Darwin':
|
if self.common.platform == 'Darwin':
|
||||||
self.systemTray.setIcon(QtGui.QIcon(common.get_resource_path('images/logo_grayscale.png')))
|
self.systemTray.setIcon(QtGui.QIcon(self.common.get_resource_path('images/logo_grayscale.png')))
|
||||||
else:
|
else:
|
||||||
self.systemTray.setIcon(QtGui.QIcon(common.get_resource_path('images/logo.png')))
|
self.systemTray.setIcon(QtGui.QIcon(self.common.get_resource_path('images/logo.png')))
|
||||||
self.systemTray.setContextMenu(menu)
|
self.systemTray.setContextMenu(menu)
|
||||||
self.systemTray.show()
|
self.systemTray.show()
|
||||||
|
|
||||||
|
@ -292,10 +295,10 @@ class OnionShareGui(QtWidgets.QMainWindow):
|
||||||
If the user cancels before Tor finishes connecting, ask if they want to
|
If the user cancels before Tor finishes connecting, ask if they want to
|
||||||
quit, or open settings.
|
quit, or open settings.
|
||||||
"""
|
"""
|
||||||
common.log('OnionShareGui', '_tor_connection_canceled')
|
self.common.log('OnionShareGui', '_tor_connection_canceled')
|
||||||
|
|
||||||
def ask():
|
def ask():
|
||||||
a = Alert(strings._('gui_tor_connection_ask', True), QtWidgets.QMessageBox.Question, buttons=QtWidgets.QMessageBox.NoButton, autostart=False)
|
a = Alert(self.common, strings._('gui_tor_connection_ask', True), QtWidgets.QMessageBox.Question, buttons=QtWidgets.QMessageBox.NoButton, autostart=False)
|
||||||
settings_button = QtWidgets.QPushButton(strings._('gui_tor_connection_ask_open_settings', True))
|
settings_button = QtWidgets.QPushButton(strings._('gui_tor_connection_ask_open_settings', True))
|
||||||
quit_button = QtWidgets.QPushButton(strings._('gui_tor_connection_ask_quit', True))
|
quit_button = QtWidgets.QPushButton(strings._('gui_tor_connection_ask_quit', True))
|
||||||
a.addButton(settings_button, QtWidgets.QMessageBox.AcceptRole)
|
a.addButton(settings_button, QtWidgets.QMessageBox.AcceptRole)
|
||||||
|
@ -305,12 +308,12 @@ class OnionShareGui(QtWidgets.QMainWindow):
|
||||||
|
|
||||||
if a.clickedButton() == settings_button:
|
if a.clickedButton() == settings_button:
|
||||||
# Open settings
|
# Open settings
|
||||||
common.log('OnionShareGui', '_tor_connection_canceled', 'Settings button clicked')
|
self.common.log('OnionShareGui', '_tor_connection_canceled', 'Settings button clicked')
|
||||||
self.open_settings()
|
self.open_settings()
|
||||||
|
|
||||||
if a.clickedButton() == quit_button:
|
if a.clickedButton() == quit_button:
|
||||||
# Quit
|
# Quit
|
||||||
common.log('OnionShareGui', '_tor_connection_canceled', 'Quit button clicked')
|
self.common.log('OnionShareGui', '_tor_connection_canceled', 'Quit button clicked')
|
||||||
|
|
||||||
# Wait 1ms for the event loop to finish, then quit
|
# Wait 1ms for the event loop to finish, then quit
|
||||||
QtCore.QTimer.singleShot(1, self.qtapp.quit)
|
QtCore.QTimer.singleShot(1, self.qtapp.quit)
|
||||||
|
@ -322,7 +325,7 @@ class OnionShareGui(QtWidgets.QMainWindow):
|
||||||
"""
|
"""
|
||||||
The TorConnectionDialog wants to open the Settings dialog
|
The TorConnectionDialog wants to open the Settings dialog
|
||||||
"""
|
"""
|
||||||
common.log('OnionShareGui', '_tor_connection_open_settings')
|
self.common.log('OnionShareGui', '_tor_connection_open_settings')
|
||||||
|
|
||||||
# Wait 1ms for the event loop to finish closing the TorConnectionDialog
|
# Wait 1ms for the event loop to finish closing the TorConnectionDialog
|
||||||
QtCore.QTimer.singleShot(1, self.open_settings)
|
QtCore.QTimer.singleShot(1, self.open_settings)
|
||||||
|
@ -331,27 +334,29 @@ class OnionShareGui(QtWidgets.QMainWindow):
|
||||||
"""
|
"""
|
||||||
Open the SettingsDialog.
|
Open the SettingsDialog.
|
||||||
"""
|
"""
|
||||||
common.log('OnionShareGui', 'open_settings')
|
self.common.log('OnionShareGui', 'open_settings')
|
||||||
|
|
||||||
def reload_settings():
|
def reload_settings():
|
||||||
common.log('OnionShareGui', 'open_settings', 'settings have changed, reloading')
|
self.common.log('OnionShareGui', 'open_settings', 'settings have changed, reloading')
|
||||||
self.settings.load()
|
self.common.settings.load()
|
||||||
# We might've stopped the main requests timer if a Tor connection failed.
|
# We might've stopped the main requests timer if a Tor connection failed.
|
||||||
# If we've reloaded settings, we probably succeeded in obtaining a new
|
# If we've reloaded settings, we probably succeeded in obtaining a new
|
||||||
# connection. If so, restart the timer.
|
# connection. If so, restart the timer.
|
||||||
|
if not self.local_only:
|
||||||
if self.onion.is_authenticated():
|
if self.onion.is_authenticated():
|
||||||
if not self.timer.isActive():
|
if not self.timer.isActive():
|
||||||
self.timer.start(500)
|
self.timer.start(500)
|
||||||
# If there were some files listed for sharing, we should be ok to
|
# If there were some files listed for sharing, we should be ok to
|
||||||
# re-enable the 'Start Sharing' button now.
|
# re-enable the 'Start Sharing' button now.
|
||||||
if self.server_status.file_selection.get_num_files() > 0:
|
if self.server_status.file_selection.get_num_files() > 0:
|
||||||
self.server_status.server_button.setEnabled(True)
|
self.primary_action.show()
|
||||||
|
self.info_widget.show()
|
||||||
self.status_bar.clearMessage()
|
self.status_bar.clearMessage()
|
||||||
# If we switched off the shutdown timeout setting, ensure the widget is hidden.
|
# If we switched off the shutdown timeout setting, ensure the widget is hidden.
|
||||||
if not self.settings.get('shutdown_timeout'):
|
if not self.common.settings.get('shutdown_timeout'):
|
||||||
self.server_status.shutdown_timeout_container.hide()
|
self.server_status.shutdown_timeout_container.hide()
|
||||||
|
|
||||||
d = SettingsDialog(self.onion, self.qtapp, self.config)
|
d = SettingsDialog(self.common, self.onion, self.qtapp, self.config, self.local_only)
|
||||||
d.settings_saved.connect(reload_settings)
|
d.settings_saved.connect(reload_settings)
|
||||||
d.exec_()
|
d.exec_()
|
||||||
|
|
||||||
|
@ -363,23 +368,21 @@ class OnionShareGui(QtWidgets.QMainWindow):
|
||||||
Start the onionshare server. This uses multiple threads to start the Tor onion
|
Start the onionshare server. This uses multiple threads to start the Tor onion
|
||||||
server and the web app.
|
server and the web app.
|
||||||
"""
|
"""
|
||||||
common.log('OnionShareGui', 'start_server')
|
self.common.log('OnionShareGui', 'start_server')
|
||||||
|
|
||||||
self.set_server_active(True)
|
self.set_server_active(True)
|
||||||
|
|
||||||
self.app.set_stealth(self.settings.get('use_stealth'))
|
self.app.set_stealth(self.common.settings.get('use_stealth'))
|
||||||
|
|
||||||
# Hide and reset the downloads if we have previously shared
|
# Hide and reset the downloads if we have previously shared
|
||||||
self.downloads_container.hide()
|
|
||||||
self.downloads.reset_downloads()
|
self.downloads.reset_downloads()
|
||||||
self.reset_info_counters()
|
self.reset_info_counters()
|
||||||
self.status_bar.clearMessage()
|
self.status_bar.clearMessage()
|
||||||
self.server_share_status_label.setText('')
|
self.server_share_status_label.setText('')
|
||||||
|
|
||||||
# Reset web counters
|
# Reset web counters
|
||||||
web.download_count = 0
|
self.web.download_count = 0
|
||||||
web.error404_count = 0
|
self.web.error404_count = 0
|
||||||
web.set_gui_mode()
|
|
||||||
|
|
||||||
# start the onion service in a new thread
|
# start the onion service in a new thread
|
||||||
def start_onion_service(self):
|
def start_onion_service(self):
|
||||||
|
@ -392,17 +395,17 @@ class OnionShareGui(QtWidgets.QMainWindow):
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|
||||||
self.app.stay_open = not self.settings.get('close_after_first_download')
|
self.app.stay_open = not self.common.settings.get('close_after_first_download')
|
||||||
|
|
||||||
# start onionshare http service in new thread
|
# start onionshare http service in new thread
|
||||||
t = threading.Thread(target=web.start, args=(self.app.port, self.app.stay_open, self.settings.get('slug')))
|
t = threading.Thread(target=self.web.start, args=(self.app.port, self.app.stay_open, self.common.settings.get('slug')))
|
||||||
t.daemon = True
|
t.daemon = True
|
||||||
t.start()
|
t.start()
|
||||||
# wait for modules in thread to load, preventing a thread-related cx_Freeze crash
|
# wait for modules in thread to load, preventing a thread-related cx_Freeze crash
|
||||||
time.sleep(0.2)
|
time.sleep(0.2)
|
||||||
|
|
||||||
common.log('OnionshareGui', 'start_server', 'Starting an onion thread')
|
self.common.log('OnionshareGui', 'start_server', 'Starting an onion thread')
|
||||||
self.t = OnionThread(function=start_onion_service, kwargs={'self': self})
|
self.t = OnionThread(self.common, function=start_onion_service, kwargs={'self': self})
|
||||||
self.t.daemon = True
|
self.t.daemon = True
|
||||||
self.t.start()
|
self.t.start()
|
||||||
|
|
||||||
|
@ -410,7 +413,7 @@ class OnionShareGui(QtWidgets.QMainWindow):
|
||||||
"""
|
"""
|
||||||
Step 2 in starting the onionshare server. Zipping up files.
|
Step 2 in starting the onionshare server. Zipping up files.
|
||||||
"""
|
"""
|
||||||
common.log('OnionShareGui', 'start_server_step2')
|
self.common.log('OnionShareGui', 'start_server_step2')
|
||||||
|
|
||||||
# add progress bar to the status bar, indicating the compressing of files.
|
# add progress bar to the status bar, indicating the compressing of files.
|
||||||
self._zip_progress_bar = ZipProgressBar(0)
|
self._zip_progress_bar = ZipProgressBar(0)
|
||||||
|
@ -428,8 +431,8 @@ class OnionShareGui(QtWidgets.QMainWindow):
|
||||||
if self._zip_progress_bar != None:
|
if self._zip_progress_bar != None:
|
||||||
self._zip_progress_bar.update_processed_size_signal.emit(x)
|
self._zip_progress_bar.update_processed_size_signal.emit(x)
|
||||||
try:
|
try:
|
||||||
web.set_file_info(self.filenames, processed_size_callback=_set_processed_size)
|
self.web.set_file_info(self.filenames, processed_size_callback=_set_processed_size)
|
||||||
self.app.cleanup_filenames.append(web.zip_filename)
|
self.app.cleanup_filenames.append(self.web.zip_filename)
|
||||||
self.starting_server_step3.emit()
|
self.starting_server_step3.emit()
|
||||||
|
|
||||||
# done
|
# done
|
||||||
|
@ -447,7 +450,7 @@ class OnionShareGui(QtWidgets.QMainWindow):
|
||||||
Step 3 in starting the onionshare server. This displays the large filesize
|
Step 3 in starting the onionshare server. This displays the large filesize
|
||||||
warning, if applicable.
|
warning, if applicable.
|
||||||
"""
|
"""
|
||||||
common.log('OnionShareGui', 'start_server_step3')
|
self.common.log('OnionShareGui', 'start_server_step3')
|
||||||
|
|
||||||
# Remove zip progress bar
|
# Remove zip progress bar
|
||||||
if self._zip_progress_bar is not None:
|
if self._zip_progress_bar is not None:
|
||||||
|
@ -455,17 +458,17 @@ class OnionShareGui(QtWidgets.QMainWindow):
|
||||||
self._zip_progress_bar = None
|
self._zip_progress_bar = None
|
||||||
|
|
||||||
# warn about sending large files over Tor
|
# warn about sending large files over Tor
|
||||||
if web.zip_filesize >= 157286400: # 150mb
|
if self.web.zip_filesize >= 157286400: # 150mb
|
||||||
self.filesize_warning.setText(strings._("large_filesize", True))
|
self.filesize_warning.setText(strings._("large_filesize", True))
|
||||||
self.filesize_warning.show()
|
self.filesize_warning.show()
|
||||||
|
|
||||||
if self.settings.get('shutdown_timeout'):
|
if self.common.settings.get('shutdown_timeout'):
|
||||||
# Convert the date value to seconds between now and then
|
# Convert the date value to seconds between now and then
|
||||||
now = QtCore.QDateTime.currentDateTime()
|
now = QtCore.QDateTime.currentDateTime()
|
||||||
self.timeout = now.secsTo(self.server_status.timeout)
|
self.timeout = now.secsTo(self.server_status.timeout)
|
||||||
# Set the shutdown timeout value
|
# Set the shutdown timeout value
|
||||||
if self.timeout > 0:
|
if self.timeout > 0:
|
||||||
self.app.shutdown_timer = common.close_after_seconds(self.timeout)
|
self.app.shutdown_timer = ShutdownTimer(self.common, self.timeout)
|
||||||
self.app.shutdown_timer.start()
|
self.app.shutdown_timer.start()
|
||||||
# The timeout has actually already passed since the user clicked Start. Probably the Onion service took too long to start.
|
# The timeout has actually already passed since the user clicked Start. Probably the Onion service took too long to start.
|
||||||
else:
|
else:
|
||||||
|
@ -476,11 +479,11 @@ class OnionShareGui(QtWidgets.QMainWindow):
|
||||||
"""
|
"""
|
||||||
If there's an error when trying to start the onion service
|
If there's an error when trying to start the onion service
|
||||||
"""
|
"""
|
||||||
common.log('OnionShareGui', 'start_server_error')
|
self.common.log('OnionShareGui', 'start_server_error')
|
||||||
|
|
||||||
self.set_server_active(False)
|
self.set_server_active(False)
|
||||||
|
|
||||||
Alert(error, QtWidgets.QMessageBox.Warning)
|
Alert(self.common, error, QtWidgets.QMessageBox.Warning)
|
||||||
self.server_status.stop_server()
|
self.server_status.stop_server()
|
||||||
if self._zip_progress_bar is not None:
|
if self._zip_progress_bar is not None:
|
||||||
self.status_bar.removeWidget(self._zip_progress_bar)
|
self.status_bar.removeWidget(self._zip_progress_bar)
|
||||||
|
@ -499,11 +502,11 @@ class OnionShareGui(QtWidgets.QMainWindow):
|
||||||
"""
|
"""
|
||||||
Stop the onionshare server.
|
Stop the onionshare server.
|
||||||
"""
|
"""
|
||||||
common.log('OnionShareGui', 'stop_server')
|
self.common.log('OnionShareGui', 'stop_server')
|
||||||
|
|
||||||
if self.server_status.status != self.server_status.STATUS_STOPPED:
|
if self.server_status.status != self.server_status.STATUS_STOPPED:
|
||||||
try:
|
try:
|
||||||
web.stop(self.app.port)
|
self.web.stop(self.app.port)
|
||||||
except:
|
except:
|
||||||
# Probably we had no port to begin with (Onion service didn't start)
|
# Probably we had no port to begin with (Onion service didn't start)
|
||||||
pass
|
pass
|
||||||
|
@ -523,13 +526,12 @@ class OnionShareGui(QtWidgets.QMainWindow):
|
||||||
"""
|
"""
|
||||||
Check for updates in a new thread, if enabled.
|
Check for updates in a new thread, if enabled.
|
||||||
"""
|
"""
|
||||||
system = common.get_platform()
|
if self.common.platform == 'Windows' or self.common.platform == 'Darwin':
|
||||||
if system == 'Windows' or system == 'Darwin':
|
if self.common.settings.get('use_autoupdate'):
|
||||||
if self.settings.get('use_autoupdate'):
|
|
||||||
def update_available(update_url, installed_version, latest_version):
|
def update_available(update_url, installed_version, latest_version):
|
||||||
Alert(strings._("update_available", True).format(update_url, installed_version, latest_version))
|
Alert(self.common, strings._("update_available", True).format(update_url, installed_version, latest_version))
|
||||||
|
|
||||||
self.update_thread = UpdateThread(self.onion, self.config)
|
self.update_thread = UpdateThread(self.common, self.onion, self.config)
|
||||||
self.update_thread.update_available.connect(update_available)
|
self.update_thread.update_available.connect(update_available)
|
||||||
self.update_thread.start()
|
self.update_thread.start()
|
||||||
|
|
||||||
|
@ -540,7 +542,7 @@ class OnionShareGui(QtWidgets.QMainWindow):
|
||||||
if os.path.isfile(filename):
|
if os.path.isfile(filename):
|
||||||
total_size += os.path.getsize(filename)
|
total_size += os.path.getsize(filename)
|
||||||
if os.path.isdir(filename):
|
if os.path.isdir(filename):
|
||||||
total_size += common.dir_size(filename)
|
total_size += Common.dir_size(filename)
|
||||||
return total_size
|
return total_size
|
||||||
|
|
||||||
def check_for_requests(self):
|
def check_for_requests(self):
|
||||||
|
@ -549,12 +551,14 @@ class OnionShareGui(QtWidgets.QMainWindow):
|
||||||
"""
|
"""
|
||||||
self.update()
|
self.update()
|
||||||
|
|
||||||
|
if not self.local_only:
|
||||||
# Have we lost connection to Tor somehow?
|
# Have we lost connection to Tor somehow?
|
||||||
if not self.onion.is_authenticated():
|
if not self.onion.is_authenticated():
|
||||||
self.timer.stop()
|
self.timer.stop()
|
||||||
if self.server_status.status != self.server_status.STATUS_STOPPED:
|
if self.server_status.status != self.server_status.STATUS_STOPPED:
|
||||||
self.server_status.stop_server()
|
self.server_status.stop_server()
|
||||||
self.server_status.server_button.setEnabled(False)
|
self.primary_action.hide()
|
||||||
|
self.info_widget.hide()
|
||||||
self.status_bar.showMessage(strings._('gui_tor_connection_lost', True))
|
self.status_bar.showMessage(strings._('gui_tor_connection_lost', True))
|
||||||
if self.systemTray.supportsMessages() and self.settings.get('systray_notifications'):
|
if self.systemTray.supportsMessages() and self.settings.get('systray_notifications'):
|
||||||
self.systemTray.showMessage(strings._('gui_tor_connection_lost', True), strings._('gui_tor_connection_error_settings', True))
|
self.systemTray.showMessage(strings._('gui_tor_connection_lost', True), strings._('gui_tor_connection_error_settings', True))
|
||||||
|
@ -562,7 +566,7 @@ class OnionShareGui(QtWidgets.QMainWindow):
|
||||||
# scroll to the bottom of the dl progress bar log pane
|
# scroll to the bottom of the dl progress bar log pane
|
||||||
# if a new download has been added
|
# if a new download has been added
|
||||||
if self.new_download:
|
if self.new_download:
|
||||||
self.vbar.setValue(self.vbar.maximum())
|
self.downloads.downloads_container.vbar.setValue(self.downloads.downloads_container.vbar.maximum())
|
||||||
self.new_download = False
|
self.new_download = False
|
||||||
|
|
||||||
events = []
|
events = []
|
||||||
|
@ -570,34 +574,34 @@ class OnionShareGui(QtWidgets.QMainWindow):
|
||||||
done = False
|
done = False
|
||||||
while not done:
|
while not done:
|
||||||
try:
|
try:
|
||||||
r = web.q.get(False)
|
r = self.web.q.get(False)
|
||||||
events.append(r)
|
events.append(r)
|
||||||
except web.queue.Empty:
|
except queue.Empty:
|
||||||
done = True
|
done = True
|
||||||
|
|
||||||
for event in events:
|
for event in events:
|
||||||
if event["type"] == web.REQUEST_LOAD:
|
if event["type"] == self.web.REQUEST_LOAD:
|
||||||
self.status_bar.showMessage(strings._('download_page_loaded', True))
|
self.status_bar.showMessage(strings._('download_page_loaded', True))
|
||||||
|
|
||||||
elif event["type"] == web.REQUEST_DOWNLOAD:
|
elif event["type"] == self.web.REQUEST_DOWNLOAD:
|
||||||
self.downloads_container.show() # show the downloads layout
|
self.downloads.no_downloads_label.hide()
|
||||||
self.downloads.add_download(event["data"]["id"], web.zip_filesize)
|
self.downloads.add_download(event["data"]["id"], web.zip_filesize)
|
||||||
self.new_download = True
|
self.new_download = True
|
||||||
self.downloads_in_progress += 1
|
self.downloads_in_progress += 1
|
||||||
self.update_downloads_in_progress(self.downloads_in_progress)
|
self.update_downloads_in_progress(self.downloads_in_progress)
|
||||||
if self.systemTray.supportsMessages() and self.settings.get('systray_notifications'):
|
if self.systemTray.supportsMessages() and self.common.settings.get('systray_notifications'):
|
||||||
self.systemTray.showMessage(strings._('systray_download_started_title', True), strings._('systray_download_started_message', True))
|
self.systemTray.showMessage(strings._('systray_download_started_title', True), strings._('systray_download_started_message', True))
|
||||||
|
|
||||||
elif event["type"] == web.REQUEST_RATE_LIMIT:
|
elif event["type"] == self.web.REQUEST_RATE_LIMIT:
|
||||||
self.stop_server()
|
self.stop_server()
|
||||||
Alert(strings._('error_rate_limit'), QtWidgets.QMessageBox.Critical)
|
Alert(self.common, strings._('error_rate_limit'), QtWidgets.QMessageBox.Critical)
|
||||||
|
|
||||||
elif event["type"] == web.REQUEST_PROGRESS:
|
elif event["type"] == self.web.REQUEST_PROGRESS:
|
||||||
self.downloads.update_download(event["data"]["id"], event["data"]["bytes"])
|
self.downloads.update_download(event["data"]["id"], event["data"]["bytes"])
|
||||||
|
|
||||||
# is the download complete?
|
# is the download complete?
|
||||||
if event["data"]["bytes"] == web.zip_filesize:
|
if event["data"]["bytes"] == self.web.zip_filesize:
|
||||||
if self.systemTray.supportsMessages() and self.settings.get('systray_notifications'):
|
if self.systemTray.supportsMessages() and self.common.settings.get('systray_notifications'):
|
||||||
self.systemTray.showMessage(strings._('systray_download_completed_title', True), strings._('systray_download_completed_message', True))
|
self.systemTray.showMessage(strings._('systray_download_completed_title', True), strings._('systray_download_completed_message', True))
|
||||||
# Update the total 'completed downloads' info
|
# Update the total 'completed downloads' info
|
||||||
self.downloads_completed += 1
|
self.downloads_completed += 1
|
||||||
|
@ -607,7 +611,7 @@ class OnionShareGui(QtWidgets.QMainWindow):
|
||||||
self.update_downloads_in_progress(self.downloads_in_progress)
|
self.update_downloads_in_progress(self.downloads_in_progress)
|
||||||
|
|
||||||
# close on finish?
|
# close on finish?
|
||||||
if not web.get_stay_open():
|
if not self.web.stay_open:
|
||||||
self.server_status.stop_server()
|
self.server_status.stop_server()
|
||||||
self.status_bar.clearMessage()
|
self.status_bar.clearMessage()
|
||||||
self.server_share_status_label.setText(strings._('closing_automatically', True))
|
self.server_share_status_label.setText(strings._('closing_automatically', True))
|
||||||
|
@ -618,27 +622,27 @@ class OnionShareGui(QtWidgets.QMainWindow):
|
||||||
self.update_downloads_in_progress(self.downloads_in_progress)
|
self.update_downloads_in_progress(self.downloads_in_progress)
|
||||||
|
|
||||||
|
|
||||||
elif event["type"] == web.REQUEST_CANCELED:
|
elif event["type"] == self.web.REQUEST_CANCELED:
|
||||||
self.downloads.cancel_download(event["data"]["id"])
|
self.downloads.cancel_download(event["data"]["id"])
|
||||||
# Update the 'in progress downloads' info
|
# Update the 'in progress downloads' info
|
||||||
self.downloads_in_progress -= 1
|
self.downloads_in_progress -= 1
|
||||||
self.update_downloads_in_progress(self.downloads_in_progress)
|
self.update_downloads_in_progress(self.downloads_in_progress)
|
||||||
if self.systemTray.supportsMessages() and self.settings.get('systray_notifications'):
|
if self.systemTray.supportsMessages() and self.common.settings.get('systray_notifications'):
|
||||||
self.systemTray.showMessage(strings._('systray_download_canceled_title', True), strings._('systray_download_canceled_message', True))
|
self.systemTray.showMessage(strings._('systray_download_canceled_title', True), strings._('systray_download_canceled_message', True))
|
||||||
|
|
||||||
elif event["path"] != '/favicon.ico':
|
elif event["path"] != '/favicon.ico':
|
||||||
self.status_bar.showMessage('[#{0:d}] {1:s}: {2:s}'.format(web.error404_count, strings._('other_page_loaded', True), event["path"]))
|
self.status_bar.showMessage('[#{0:d}] {1:s}: {2:s}'.format(self.web.error404_count, strings._('other_page_loaded', True), event["path"]))
|
||||||
|
|
||||||
# If the auto-shutdown timer has stopped, stop the server
|
# If the auto-shutdown timer has stopped, stop the server
|
||||||
if self.server_status.status == self.server_status.STATUS_STARTED:
|
if self.server_status.status == self.server_status.STATUS_STARTED:
|
||||||
if self.app.shutdown_timer and self.settings.get('shutdown_timeout'):
|
if self.app.shutdown_timer and self.common.settings.get('shutdown_timeout'):
|
||||||
if self.timeout > 0:
|
if self.timeout > 0:
|
||||||
now = QtCore.QDateTime.currentDateTime()
|
now = QtCore.QDateTime.currentDateTime()
|
||||||
seconds_remaining = now.secsTo(self.server_status.timeout)
|
seconds_remaining = now.secsTo(self.server_status.timeout)
|
||||||
self.server_status.server_button.setText(strings._('gui_stop_server_shutdown_timeout', True).format(seconds_remaining))
|
self.server_status.server_button.setText(strings._('gui_stop_server_shutdown_timeout', True).format(seconds_remaining))
|
||||||
if not self.app.shutdown_timer.is_alive():
|
if not self.app.shutdown_timer.is_alive():
|
||||||
# If there were no attempts to download the share, or all downloads are done, we can stop
|
# If there were no attempts to download the share, or all downloads are done, we can stop
|
||||||
if web.download_count == 0 or web.done:
|
if self.web.download_count == 0 or self.web.done:
|
||||||
self.server_status.stop_server()
|
self.server_status.stop_server()
|
||||||
self.status_bar.clearMessage()
|
self.status_bar.clearMessage()
|
||||||
self.server_share_status_label.setText(strings._('close_on_timeout', True))
|
self.server_share_status_label.setText(strings._('close_on_timeout', True))
|
||||||
|
@ -647,20 +651,30 @@ class OnionShareGui(QtWidgets.QMainWindow):
|
||||||
self.status_bar.clearMessage()
|
self.status_bar.clearMessage()
|
||||||
self.server_share_status_label.setText(strings._('timeout_download_still_running', True))
|
self.server_share_status_label.setText(strings._('timeout_download_still_running', True))
|
||||||
|
|
||||||
|
def downloads_toggled(self, checked):
|
||||||
|
"""
|
||||||
|
When the 'Show/hide downloads' button is toggled, show or hide the downloads window.
|
||||||
|
"""
|
||||||
|
self.common.log('OnionShareGui', 'toggle_downloads')
|
||||||
|
if checked:
|
||||||
|
self.downloads.downloads_container.show()
|
||||||
|
else:
|
||||||
|
self.downloads.downloads_container.hide()
|
||||||
|
|
||||||
def copy_url(self):
|
def copy_url(self):
|
||||||
"""
|
"""
|
||||||
When the URL gets copied to the clipboard, display this in the status bar.
|
When the URL gets copied to the clipboard, display this in the status bar.
|
||||||
"""
|
"""
|
||||||
common.log('OnionShareGui', 'copy_url')
|
self.common.log('OnionShareGui', 'copy_url')
|
||||||
if self.systemTray.supportsMessages() and self.settings.get('systray_notifications'):
|
if self.systemTray.supportsMessages() and self.common.settings.get('systray_notifications'):
|
||||||
self.systemTray.showMessage(strings._('gui_copied_url_title', True), strings._('gui_copied_url', True))
|
self.systemTray.showMessage(strings._('gui_copied_url_title', True), strings._('gui_copied_url', True))
|
||||||
|
|
||||||
def copy_hidservauth(self):
|
def copy_hidservauth(self):
|
||||||
"""
|
"""
|
||||||
When the stealth onion service HidServAuth gets copied to the clipboard, display this in the status bar.
|
When the stealth onion service HidServAuth gets copied to the clipboard, display this in the status bar.
|
||||||
"""
|
"""
|
||||||
common.log('OnionShareGui', 'copy_hidservauth')
|
self.common.log('OnionShareGui', 'copy_hidservauth')
|
||||||
if self.systemTray.supportsMessages() and self.settings.get('systray_notifications'):
|
if self.systemTray.supportsMessages() and self.common.settings.get('systray_notifications'):
|
||||||
self.systemTray.showMessage(strings._('gui_copied_hidservauth_title', True), strings._('gui_copied_hidservauth', True))
|
self.systemTray.showMessage(strings._('gui_copied_hidservauth_title', True), strings._('gui_copied_hidservauth', True))
|
||||||
|
|
||||||
def clear_message(self):
|
def clear_message(self):
|
||||||
|
@ -687,15 +701,18 @@ class OnionShareGui(QtWidgets.QMainWindow):
|
||||||
"""
|
"""
|
||||||
self.update_downloads_completed(0)
|
self.update_downloads_completed(0)
|
||||||
self.update_downloads_in_progress(0)
|
self.update_downloads_in_progress(0)
|
||||||
|
self.info_show_downloads.setIcon(QtGui.QIcon(self.common.get_resource_path('images/download_window_gray.png')))
|
||||||
|
self.downloads.no_downloads_label.show()
|
||||||
|
self.downloads.downloads_container.resize(self.downloads.downloads_container.sizeHint())
|
||||||
|
|
||||||
def update_downloads_completed(self, count):
|
def update_downloads_completed(self, count):
|
||||||
"""
|
"""
|
||||||
Update the 'Downloads completed' info widget.
|
Update the 'Downloads completed' info widget.
|
||||||
"""
|
"""
|
||||||
if count == 0:
|
if count == 0:
|
||||||
self.info_completed_downloads_image = common.get_resource_path('images/download_completed_none.png')
|
self.info_completed_downloads_image = self.common.get_resource_path('images/download_completed_none.png')
|
||||||
else:
|
else:
|
||||||
self.info_completed_downloads_image = common.get_resource_path('images/download_completed.png')
|
self.info_completed_downloads_image = self.common.get_resource_path('images/download_completed.png')
|
||||||
self.info_completed_downloads_count.setText('<img src="{0:s}" /> {1:d}'.format(self.info_completed_downloads_image, count))
|
self.info_completed_downloads_count.setText('<img src="{0:s}" /> {1:d}'.format(self.info_completed_downloads_image, count))
|
||||||
self.info_completed_downloads_count.setToolTip(strings._('info_completed_downloads_tooltip', True).format(count))
|
self.info_completed_downloads_count.setToolTip(strings._('info_completed_downloads_tooltip', True).format(count))
|
||||||
|
|
||||||
|
@ -704,17 +721,18 @@ class OnionShareGui(QtWidgets.QMainWindow):
|
||||||
Update the 'Downloads in progress' info widget.
|
Update the 'Downloads in progress' info widget.
|
||||||
"""
|
"""
|
||||||
if count == 0:
|
if count == 0:
|
||||||
self.info_in_progress_downloads_image = common.get_resource_path('images/download_in_progress_none.png')
|
self.info_in_progress_downloads_image = self.common.get_resource_path('images/download_in_progress_none.png')
|
||||||
else:
|
else:
|
||||||
self.info_in_progress_downloads_image = common.get_resource_path('images/download_in_progress.png')
|
self.info_in_progress_downloads_image = self.common.get_resource_path('images/download_in_progress.png')
|
||||||
|
self.info_show_downloads.setIcon(QtGui.QIcon(self.common.get_resource_path('images/download_window_green.png')))
|
||||||
self.info_in_progress_downloads_count.setText('<img src="{0:s}" /> {1:d}'.format(self.info_in_progress_downloads_image, count))
|
self.info_in_progress_downloads_count.setText('<img src="{0:s}" /> {1:d}'.format(self.info_in_progress_downloads_image, count))
|
||||||
self.info_in_progress_downloads_count.setToolTip(strings._('info_in_progress_downloads_tooltip', True).format(count))
|
self.info_in_progress_downloads_count.setToolTip(strings._('info_in_progress_downloads_tooltip', True).format(count))
|
||||||
|
|
||||||
def closeEvent(self, e):
|
def closeEvent(self, e):
|
||||||
common.log('OnionShareGui', 'closeEvent')
|
self.common.log('OnionShareGui', 'closeEvent')
|
||||||
try:
|
try:
|
||||||
if self.server_status.status != self.server_status.STATUS_STOPPED:
|
if self.server_status.status != self.server_status.STATUS_STOPPED:
|
||||||
common.log('OnionShareGui', 'closeEvent, opening warning dialog')
|
self.common.log('OnionShareGui', 'closeEvent, opening warning dialog')
|
||||||
dialog = QtWidgets.QMessageBox()
|
dialog = QtWidgets.QMessageBox()
|
||||||
dialog.setWindowTitle(strings._('gui_quit_title', True))
|
dialog.setWindowTitle(strings._('gui_quit_title', True))
|
||||||
dialog.setText(strings._('gui_quit_warning', True))
|
dialog.setText(strings._('gui_quit_warning', True))
|
||||||
|
@ -799,9 +817,12 @@ class OnionThread(QtCore.QThread):
|
||||||
decided to cancel (in which case do not proceed with obtaining
|
decided to cancel (in which case do not proceed with obtaining
|
||||||
the Onion address and starting the web server).
|
the Onion address and starting the web server).
|
||||||
"""
|
"""
|
||||||
def __init__(self, function, kwargs=None):
|
def __init__(self, common, function, kwargs=None):
|
||||||
super(OnionThread, self).__init__()
|
super(OnionThread, self).__init__()
|
||||||
common.log('OnionThread', '__init__')
|
|
||||||
|
self.common = common
|
||||||
|
|
||||||
|
self.common.log('OnionThread', '__init__')
|
||||||
self.function = function
|
self.function = function
|
||||||
if not kwargs:
|
if not kwargs:
|
||||||
self.kwargs = {}
|
self.kwargs = {}
|
||||||
|
@ -809,6 +830,6 @@ class OnionThread(QtCore.QThread):
|
||||||
self.kwargs = kwargs
|
self.kwargs = kwargs
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
common.log('OnionThread', 'run')
|
self.common.log('OnionThread', 'run')
|
||||||
|
|
||||||
self.function(**self.kwargs)
|
self.function(**self.kwargs)
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
"""
|
"""
|
||||||
OnionShare | https://onionshare.org/
|
OnionShare | https://onionshare.org/
|
||||||
|
|
||||||
Copyright (C) 2017 Micah Lee <micah@micahflee.com>
|
Copyright (C) 2018 Micah Lee <micah@micahflee.com>
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
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
|
it under the terms of the GNU General Public License as published by
|
||||||
|
@ -21,7 +21,7 @@ import platform
|
||||||
from .alert import Alert
|
from .alert import Alert
|
||||||
from PyQt5 import QtCore, QtWidgets, QtGui
|
from PyQt5 import QtCore, QtWidgets, QtGui
|
||||||
|
|
||||||
from onionshare import strings, common, settings
|
from onionshare import strings
|
||||||
|
|
||||||
class ServerStatus(QtWidgets.QWidget):
|
class ServerStatus(QtWidgets.QWidget):
|
||||||
"""
|
"""
|
||||||
|
@ -38,8 +38,11 @@ class ServerStatus(QtWidgets.QWidget):
|
||||||
STATUS_WORKING = 1
|
STATUS_WORKING = 1
|
||||||
STATUS_STARTED = 2
|
STATUS_STARTED = 2
|
||||||
|
|
||||||
def __init__(self, qtapp, app, web, file_selection, settings):
|
def __init__(self, common, qtapp, app, web, file_selection):
|
||||||
super(ServerStatus, self).__init__()
|
super(ServerStatus, self).__init__()
|
||||||
|
|
||||||
|
self.common = common
|
||||||
|
|
||||||
self.status = self.STATUS_STOPPED
|
self.status = self.STATUS_STOPPED
|
||||||
|
|
||||||
self.qtapp = qtapp
|
self.qtapp = qtapp
|
||||||
|
@ -47,8 +50,6 @@ class ServerStatus(QtWidgets.QWidget):
|
||||||
self.web = web
|
self.web = web
|
||||||
self.file_selection = file_selection
|
self.file_selection = file_selection
|
||||||
|
|
||||||
self.settings = settings
|
|
||||||
|
|
||||||
# Shutdown timeout layout
|
# Shutdown timeout layout
|
||||||
self.shutdown_timeout_label = QtWidgets.QLabel(strings._('gui_settings_shutdown_timeout', True))
|
self.shutdown_timeout_label = QtWidgets.QLabel(strings._('gui_settings_shutdown_timeout', True))
|
||||||
self.shutdown_timeout = QtWidgets.QDateTimeEdit()
|
self.shutdown_timeout = QtWidgets.QDateTimeEdit()
|
||||||
|
@ -129,16 +130,16 @@ class ServerStatus(QtWidgets.QWidget):
|
||||||
if self.status == self.STATUS_STARTED:
|
if self.status == self.STATUS_STARTED:
|
||||||
self.url_description.show()
|
self.url_description.show()
|
||||||
|
|
||||||
info_image = common.get_resource_path('images/info.png')
|
info_image = self.common.get_resource_path('images/info.png')
|
||||||
self.url_description.setText(strings._('gui_url_description', True).format(info_image))
|
self.url_description.setText(strings._('gui_url_description', True).format(info_image))
|
||||||
# Show a Tool Tip explaining the lifecycle of this URL
|
# Show a Tool Tip explaining the lifecycle of this URL
|
||||||
if self.settings.get('save_private_key'):
|
if self.common.settings.get('save_private_key'):
|
||||||
if self.settings.get('close_after_first_download'):
|
if self.common.settings.get('close_after_first_download'):
|
||||||
self.url_description.setToolTip(strings._('gui_url_label_onetime_and_persistent', True))
|
self.url_description.setToolTip(strings._('gui_url_label_onetime_and_persistent', True))
|
||||||
else:
|
else:
|
||||||
self.url_description.setToolTip(strings._('gui_url_label_persistent', True))
|
self.url_description.setToolTip(strings._('gui_url_label_persistent', True))
|
||||||
else:
|
else:
|
||||||
if self.settings.get('close_after_first_download'):
|
if self.common.settings.get('close_after_first_download'):
|
||||||
self.url_description.setToolTip(strings._('gui_url_label_onetime', True))
|
self.url_description.setToolTip(strings._('gui_url_label_onetime', True))
|
||||||
else:
|
else:
|
||||||
self.url_description.setToolTip(strings._('gui_url_label_stay_open', True))
|
self.url_description.setToolTip(strings._('gui_url_label_stay_open', True))
|
||||||
|
@ -148,12 +149,12 @@ class ServerStatus(QtWidgets.QWidget):
|
||||||
|
|
||||||
self.copy_url_button.show()
|
self.copy_url_button.show()
|
||||||
|
|
||||||
if self.settings.get('save_private_key'):
|
if self.common.settings.get('save_private_key'):
|
||||||
if not self.settings.get('slug'):
|
if not self.common.settings.get('slug'):
|
||||||
self.settings.set('slug', self.web.slug)
|
self.common.settings.set('slug', self.web.slug)
|
||||||
self.settings.save()
|
self.common.settings.save()
|
||||||
|
|
||||||
if self.settings.get('shutdown_timeout'):
|
if self.common.settings.get('shutdown_timeout'):
|
||||||
self.shutdown_timeout_container.hide()
|
self.shutdown_timeout_container.hide()
|
||||||
|
|
||||||
if self.app.stealth:
|
if self.app.stealth:
|
||||||
|
@ -180,26 +181,26 @@ class ServerStatus(QtWidgets.QWidget):
|
||||||
self.server_button.setEnabled(True)
|
self.server_button.setEnabled(True)
|
||||||
self.server_button.setText(strings._('gui_start_server', True))
|
self.server_button.setText(strings._('gui_start_server', True))
|
||||||
self.server_button.setToolTip('')
|
self.server_button.setToolTip('')
|
||||||
if self.settings.get('shutdown_timeout'):
|
if self.common.settings.get('shutdown_timeout'):
|
||||||
self.shutdown_timeout_container.show()
|
self.shutdown_timeout_container.show()
|
||||||
elif self.status == self.STATUS_STARTED:
|
elif self.status == self.STATUS_STARTED:
|
||||||
self.server_button.setStyleSheet(button_started_style)
|
self.server_button.setStyleSheet(button_started_style)
|
||||||
self.server_button.setEnabled(True)
|
self.server_button.setEnabled(True)
|
||||||
self.server_button.setText(strings._('gui_stop_server', True))
|
self.server_button.setText(strings._('gui_stop_server', True))
|
||||||
if self.settings.get('shutdown_timeout'):
|
if self.common.settings.get('shutdown_timeout'):
|
||||||
self.shutdown_timeout_container.hide()
|
self.shutdown_timeout_container.hide()
|
||||||
self.server_button.setToolTip(strings._('gui_stop_server_shutdown_timeout_tooltip', True).format(self.timeout))
|
self.server_button.setToolTip(strings._('gui_stop_server_shutdown_timeout_tooltip', True).format(self.timeout))
|
||||||
elif self.status == self.STATUS_WORKING:
|
elif self.status == self.STATUS_WORKING:
|
||||||
self.server_button.setStyleSheet(button_working_style)
|
self.server_button.setStyleSheet(button_working_style)
|
||||||
self.server_button.setEnabled(True)
|
self.server_button.setEnabled(True)
|
||||||
self.server_button.setText(strings._('gui_please_wait'))
|
self.server_button.setText(strings._('gui_please_wait'))
|
||||||
if self.settings.get('shutdown_timeout'):
|
if self.common.settings.get('shutdown_timeout'):
|
||||||
self.shutdown_timeout_container.hide()
|
self.shutdown_timeout_container.hide()
|
||||||
else:
|
else:
|
||||||
self.server_button.setStyleSheet(button_working_style)
|
self.server_button.setStyleSheet(button_working_style)
|
||||||
self.server_button.setEnabled(False)
|
self.server_button.setEnabled(False)
|
||||||
self.server_button.setText(strings._('gui_please_wait'))
|
self.server_button.setText(strings._('gui_please_wait'))
|
||||||
if self.settings.get('shutdown_timeout'):
|
if self.common.settings.get('shutdown_timeout'):
|
||||||
self.shutdown_timeout_container.hide()
|
self.shutdown_timeout_container.hide()
|
||||||
|
|
||||||
def server_button_clicked(self):
|
def server_button_clicked(self):
|
||||||
|
@ -207,12 +208,12 @@ class ServerStatus(QtWidgets.QWidget):
|
||||||
Toggle starting or stopping the server.
|
Toggle starting or stopping the server.
|
||||||
"""
|
"""
|
||||||
if self.status == self.STATUS_STOPPED:
|
if self.status == self.STATUS_STOPPED:
|
||||||
if self.settings.get('shutdown_timeout'):
|
if self.common.settings.get('shutdown_timeout'):
|
||||||
# Get the timeout chosen, stripped of its seconds. This prevents confusion if the share stops at (say) 37 seconds past the minute chosen
|
# Get the timeout chosen, stripped of its seconds. This prevents confusion if the share stops at (say) 37 seconds past the minute chosen
|
||||||
self.timeout = self.shutdown_timeout.dateTime().toPyDateTime().replace(second=0, microsecond=0)
|
self.timeout = self.shutdown_timeout.dateTime().toPyDateTime().replace(second=0, microsecond=0)
|
||||||
# If the timeout has actually passed already before the user hit Start, refuse to start the server.
|
# If the timeout has actually passed already before the user hit Start, refuse to start the server.
|
||||||
if QtCore.QDateTime.currentDateTime().toPyDateTime() > self.timeout:
|
if QtCore.QDateTime.currentDateTime().toPyDateTime() > self.timeout:
|
||||||
Alert(strings._('gui_server_timeout_expired', QtWidgets.QMessageBox.Warning))
|
Alert(self.common, strings._('gui_server_timeout_expired', QtWidgets.QMessageBox.Warning))
|
||||||
else:
|
else:
|
||||||
self.start_server()
|
self.start_server()
|
||||||
else:
|
else:
|
||||||
|
@ -252,7 +253,7 @@ class ServerStatus(QtWidgets.QWidget):
|
||||||
"""
|
"""
|
||||||
Cancel the server.
|
Cancel the server.
|
||||||
"""
|
"""
|
||||||
common.log('ServerStatus', 'cancel_server', 'Canceling the server mid-startup')
|
self.common.log('ServerStatus', 'cancel_server', 'Canceling the server mid-startup')
|
||||||
self.status = self.STATUS_WORKING
|
self.status = self.STATUS_WORKING
|
||||||
self.shutdown_timeout_reset()
|
self.shutdown_timeout_reset()
|
||||||
self.update()
|
self.update()
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
"""
|
"""
|
||||||
OnionShare | https://onionshare.org/
|
OnionShare | https://onionshare.org/
|
||||||
|
|
||||||
Copyright (C) 2017 Micah Lee <micah@micahflee.com>
|
Copyright (C) 2018 Micah Lee <micah@micahflee.com>
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
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
|
it under the terms of the GNU General Public License as published by
|
||||||
|
@ -34,17 +34,21 @@ class SettingsDialog(QtWidgets.QDialog):
|
||||||
"""
|
"""
|
||||||
settings_saved = QtCore.pyqtSignal()
|
settings_saved = QtCore.pyqtSignal()
|
||||||
|
|
||||||
def __init__(self, onion, qtapp, config=False):
|
def __init__(self, common, onion, qtapp, config=False, local_only=False):
|
||||||
super(SettingsDialog, self).__init__()
|
super(SettingsDialog, self).__init__()
|
||||||
common.log('SettingsDialog', '__init__')
|
|
||||||
|
self.common = common
|
||||||
|
|
||||||
|
self.common.log('SettingsDialog', '__init__')
|
||||||
|
|
||||||
self.onion = onion
|
self.onion = onion
|
||||||
self.qtapp = qtapp
|
self.qtapp = qtapp
|
||||||
self.config = config
|
self.config = config
|
||||||
|
self.local_only = local_only
|
||||||
|
|
||||||
self.setModal(True)
|
self.setModal(True)
|
||||||
self.setWindowTitle(strings._('gui_settings_window_title', True))
|
self.setWindowTitle(strings._('gui_settings_window_title', True))
|
||||||
self.setWindowIcon(QtGui.QIcon(common.get_resource_path('images/logo.png')))
|
self.setWindowIcon(QtGui.QIcon(self.common.get_resource_path('images/logo.png')))
|
||||||
|
|
||||||
self.system = platform.system()
|
self.system = platform.system()
|
||||||
|
|
||||||
|
@ -156,7 +160,7 @@ class SettingsDialog(QtWidgets.QDialog):
|
||||||
|
|
||||||
# obfs4 option radio
|
# obfs4 option radio
|
||||||
# if the obfs4proxy binary is missing, we can't use obfs4 transports
|
# if the obfs4proxy binary is missing, we can't use obfs4 transports
|
||||||
(self.tor_path, self.tor_geo_ip_file_path, self.tor_geo_ipv6_file_path, self.obfs4proxy_file_path) = common.get_tor_paths()
|
(self.tor_path, self.tor_geo_ip_file_path, self.tor_geo_ipv6_file_path, self.obfs4proxy_file_path) = self.common.get_tor_paths()
|
||||||
if not os.path.isfile(self.obfs4proxy_file_path):
|
if not os.path.isfile(self.obfs4proxy_file_path):
|
||||||
self.tor_bridges_use_obfs4_radio = QtWidgets.QRadioButton(strings._('gui_settings_tor_bridges_obfs4_radio_option_no_obfs4proxy', True))
|
self.tor_bridges_use_obfs4_radio = QtWidgets.QRadioButton(strings._('gui_settings_tor_bridges_obfs4_radio_option_no_obfs4proxy', True))
|
||||||
self.tor_bridges_use_obfs4_radio.setEnabled(False)
|
self.tor_bridges_use_obfs4_radio.setEnabled(False)
|
||||||
|
@ -166,7 +170,7 @@ class SettingsDialog(QtWidgets.QDialog):
|
||||||
|
|
||||||
# meek_lite-amazon option radio
|
# meek_lite-amazon option radio
|
||||||
# if the obfs4proxy binary is missing, we can't use meek_lite-amazon transports
|
# if the obfs4proxy binary is missing, we can't use meek_lite-amazon transports
|
||||||
(self.tor_path, self.tor_geo_ip_file_path, self.tor_geo_ipv6_file_path, self.obfs4proxy_file_path) = common.get_tor_paths()
|
(self.tor_path, self.tor_geo_ip_file_path, self.tor_geo_ipv6_file_path, self.obfs4proxy_file_path) = self.common.get_tor_paths()
|
||||||
if not os.path.isfile(self.obfs4proxy_file_path):
|
if not os.path.isfile(self.obfs4proxy_file_path):
|
||||||
self.tor_bridges_use_meek_lite_amazon_radio = QtWidgets.QRadioButton(strings._('gui_settings_tor_bridges_meek_lite_amazon_radio_option_no_obfs4proxy', True))
|
self.tor_bridges_use_meek_lite_amazon_radio = QtWidgets.QRadioButton(strings._('gui_settings_tor_bridges_meek_lite_amazon_radio_option_no_obfs4proxy', True))
|
||||||
self.tor_bridges_use_meek_lite_amazon_radio.setEnabled(False)
|
self.tor_bridges_use_meek_lite_amazon_radio.setEnabled(False)
|
||||||
|
@ -176,7 +180,7 @@ class SettingsDialog(QtWidgets.QDialog):
|
||||||
|
|
||||||
# meek_lite-azure option radio
|
# meek_lite-azure option radio
|
||||||
# if the obfs4proxy binary is missing, we can't use meek_lite-azure transports
|
# if the obfs4proxy binary is missing, we can't use meek_lite-azure transports
|
||||||
(self.tor_path, self.tor_geo_ip_file_path, self.tor_geo_ipv6_file_path, self.obfs4proxy_file_path) = common.get_tor_paths()
|
(self.tor_path, self.tor_geo_ip_file_path, self.tor_geo_ipv6_file_path, self.obfs4proxy_file_path) = self.common.get_tor_paths()
|
||||||
if not os.path.isfile(self.obfs4proxy_file_path):
|
if not os.path.isfile(self.obfs4proxy_file_path):
|
||||||
self.tor_bridges_use_meek_lite_azure_radio = QtWidgets.QRadioButton(strings._('gui_settings_tor_bridges_meek_lite_azure_radio_option_no_obfs4proxy', True))
|
self.tor_bridges_use_meek_lite_azure_radio = QtWidgets.QRadioButton(strings._('gui_settings_tor_bridges_meek_lite_azure_radio_option_no_obfs4proxy', True))
|
||||||
self.tor_bridges_use_meek_lite_azure_radio.setEnabled(False)
|
self.tor_bridges_use_meek_lite_azure_radio.setEnabled(False)
|
||||||
|
@ -290,10 +294,6 @@ class SettingsDialog(QtWidgets.QDialog):
|
||||||
self.authenticate_group = QtWidgets.QGroupBox(strings._("gui_settings_authenticate_label", True))
|
self.authenticate_group = QtWidgets.QGroupBox(strings._("gui_settings_authenticate_label", True))
|
||||||
self.authenticate_group.setLayout(authenticate_group_layout)
|
self.authenticate_group.setLayout(authenticate_group_layout)
|
||||||
|
|
||||||
# Test tor settings button
|
|
||||||
self.connection_type_test_button = QtWidgets.QPushButton(strings._('gui_settings_connection_type_test_button', True))
|
|
||||||
self.connection_type_test_button.clicked.connect(self.test_tor_clicked)
|
|
||||||
|
|
||||||
# Put the radios into their own group so they are exclusive
|
# Put the radios into their own group so they are exclusive
|
||||||
connection_type_radio_group_layout = QtWidgets.QVBoxLayout()
|
connection_type_radio_group_layout = QtWidgets.QVBoxLayout()
|
||||||
connection_type_radio_group_layout.addWidget(self.connection_type_bundled_radio)
|
connection_type_radio_group_layout.addWidget(self.connection_type_bundled_radio)
|
||||||
|
@ -303,16 +303,6 @@ class SettingsDialog(QtWidgets.QDialog):
|
||||||
connection_type_radio_group = QtWidgets.QGroupBox(strings._("gui_settings_connection_type_label", True))
|
connection_type_radio_group = QtWidgets.QGroupBox(strings._("gui_settings_connection_type_label", True))
|
||||||
connection_type_radio_group.setLayout(connection_type_radio_group_layout)
|
connection_type_radio_group.setLayout(connection_type_radio_group_layout)
|
||||||
|
|
||||||
# Connection type layout
|
|
||||||
connection_type_group_layout = QtWidgets.QVBoxLayout()
|
|
||||||
connection_type_group_layout.addWidget(self.connection_type_control_port_extras)
|
|
||||||
connection_type_group_layout.addWidget(self.connection_type_socket_file_extras)
|
|
||||||
connection_type_group_layout.addWidget(self.connection_type_socks)
|
|
||||||
connection_type_group_layout.addWidget(self.authenticate_group)
|
|
||||||
connection_type_group_layout.addWidget(self.connection_type_test_button)
|
|
||||||
connection_type_group = QtWidgets.QGroupBox()
|
|
||||||
connection_type_group.setLayout(connection_type_group_layout)
|
|
||||||
|
|
||||||
# The Bridges options are not exclusive (enabling Bridges offers obfs4 or custom bridges)
|
# The Bridges options are not exclusive (enabling Bridges offers obfs4 or custom bridges)
|
||||||
connection_type_bridges_radio_group_layout = QtWidgets.QVBoxLayout()
|
connection_type_bridges_radio_group_layout = QtWidgets.QVBoxLayout()
|
||||||
connection_type_bridges_radio_group_layout.addWidget(self.bridges)
|
connection_type_bridges_radio_group_layout.addWidget(self.bridges)
|
||||||
|
@ -320,12 +310,28 @@ class SettingsDialog(QtWidgets.QDialog):
|
||||||
self.connection_type_bridges_radio_group.setLayout(connection_type_bridges_radio_group_layout)
|
self.connection_type_bridges_radio_group.setLayout(connection_type_bridges_radio_group_layout)
|
||||||
self.connection_type_bridges_radio_group.hide()
|
self.connection_type_bridges_radio_group.hide()
|
||||||
|
|
||||||
|
# Test tor settings button
|
||||||
|
self.connection_type_test_button = QtWidgets.QPushButton(strings._('gui_settings_connection_type_test_button', True))
|
||||||
|
self.connection_type_test_button.clicked.connect(self.test_tor_clicked)
|
||||||
|
connection_type_test_button_layout = QtWidgets.QHBoxLayout()
|
||||||
|
connection_type_test_button_layout.addWidget(self.connection_type_test_button)
|
||||||
|
connection_type_test_button_layout.addStretch()
|
||||||
|
|
||||||
|
# Connection type layout
|
||||||
|
connection_type_layout = QtWidgets.QVBoxLayout()
|
||||||
|
connection_type_layout.addWidget(self.connection_type_control_port_extras)
|
||||||
|
connection_type_layout.addWidget(self.connection_type_socket_file_extras)
|
||||||
|
connection_type_layout.addWidget(self.connection_type_socks)
|
||||||
|
connection_type_layout.addWidget(self.authenticate_group)
|
||||||
|
connection_type_layout.addWidget(self.connection_type_bridges_radio_group)
|
||||||
|
connection_type_layout.addLayout(connection_type_test_button_layout)
|
||||||
|
|
||||||
# Buttons
|
# Buttons
|
||||||
self.save_button = QtWidgets.QPushButton(strings._('gui_settings_button_save', True))
|
self.save_button = QtWidgets.QPushButton(strings._('gui_settings_button_save', True))
|
||||||
self.save_button.clicked.connect(self.save_clicked)
|
self.save_button.clicked.connect(self.save_clicked)
|
||||||
self.cancel_button = QtWidgets.QPushButton(strings._('gui_settings_button_cancel', True))
|
self.cancel_button = QtWidgets.QPushButton(strings._('gui_settings_button_cancel', True))
|
||||||
self.cancel_button.clicked.connect(self.cancel_clicked)
|
self.cancel_button.clicked.connect(self.cancel_clicked)
|
||||||
version_label = QtWidgets.QLabel('OnionShare {0:s}'.format(common.get_version()))
|
version_label = QtWidgets.QLabel('OnionShare {0:s}'.format(self.common.version))
|
||||||
version_label.setStyleSheet('color: #666666')
|
version_label.setStyleSheet('color: #666666')
|
||||||
self.help_button = QtWidgets.QPushButton(strings._('gui_settings_button_help', True))
|
self.help_button = QtWidgets.QPushButton(strings._('gui_settings_button_help', True))
|
||||||
self.help_button.clicked.connect(self.help_clicked)
|
self.help_button.clicked.connect(self.help_clicked)
|
||||||
|
@ -350,8 +356,7 @@ class SettingsDialog(QtWidgets.QDialog):
|
||||||
|
|
||||||
right_col_layout = QtWidgets.QVBoxLayout()
|
right_col_layout = QtWidgets.QVBoxLayout()
|
||||||
right_col_layout.addWidget(connection_type_radio_group)
|
right_col_layout.addWidget(connection_type_radio_group)
|
||||||
right_col_layout.addWidget(connection_type_group)
|
right_col_layout.addLayout(connection_type_layout)
|
||||||
right_col_layout.addWidget(self.connection_type_bridges_radio_group)
|
|
||||||
right_col_layout.addWidget(self.tor_status)
|
right_col_layout.addWidget(self.tor_status)
|
||||||
right_col_layout.addStretch()
|
right_col_layout.addStretch()
|
||||||
|
|
||||||
|
@ -367,7 +372,7 @@ class SettingsDialog(QtWidgets.QDialog):
|
||||||
self.cancel_button.setFocus()
|
self.cancel_button.setFocus()
|
||||||
|
|
||||||
# Load settings, and fill them in
|
# Load settings, and fill them in
|
||||||
self.old_settings = Settings(self.config)
|
self.old_settings = Settings(self.common, self.config)
|
||||||
self.old_settings.load()
|
self.old_settings.load()
|
||||||
|
|
||||||
close_after_first_download = self.old_settings.get('close_after_first_download')
|
close_after_first_download = self.old_settings.get('close_after_first_download')
|
||||||
|
@ -465,7 +470,7 @@ class SettingsDialog(QtWidgets.QDialog):
|
||||||
"""
|
"""
|
||||||
Connection type bundled was toggled. If checked, hide authentication fields.
|
Connection type bundled was toggled. If checked, hide authentication fields.
|
||||||
"""
|
"""
|
||||||
common.log('SettingsDialog', 'connection_type_bundled_toggled')
|
self.common.log('SettingsDialog', 'connection_type_bundled_toggled')
|
||||||
if checked:
|
if checked:
|
||||||
self.authenticate_group.hide()
|
self.authenticate_group.hide()
|
||||||
self.connection_type_socks.hide()
|
self.connection_type_socks.hide()
|
||||||
|
@ -491,6 +496,9 @@ class SettingsDialog(QtWidgets.QDialog):
|
||||||
"""
|
"""
|
||||||
if checked:
|
if checked:
|
||||||
self.tor_bridges_use_custom_textbox_options.hide()
|
self.tor_bridges_use_custom_textbox_options.hide()
|
||||||
|
# Alert the user about meek's costliness if it looks like they're turning it on
|
||||||
|
if not self.old_settings.get('tor_bridges_use_meek_lite_amazon'):
|
||||||
|
Alert(strings._('gui_settings_meek_lite_expensive_warning', True), QtWidgets.QMessageBox.Warning)
|
||||||
|
|
||||||
def tor_bridges_use_meek_lite_azure_radio_toggled(self, checked):
|
def tor_bridges_use_meek_lite_azure_radio_toggled(self, checked):
|
||||||
"""
|
"""
|
||||||
|
@ -498,6 +506,9 @@ class SettingsDialog(QtWidgets.QDialog):
|
||||||
"""
|
"""
|
||||||
if checked:
|
if checked:
|
||||||
self.tor_bridges_use_custom_textbox_options.hide()
|
self.tor_bridges_use_custom_textbox_options.hide()
|
||||||
|
# Alert the user about meek's costliness if it looks like they're turning it on
|
||||||
|
if not self.old_settings.get('tor_bridges_use_meek_lite_azure'):
|
||||||
|
Alert(strings._('gui_settings_meek_lite_expensive_warning', True), QtWidgets.QMessageBox.Warning)
|
||||||
|
|
||||||
def tor_bridges_use_custom_radio_toggled(self, checked):
|
def tor_bridges_use_custom_radio_toggled(self, checked):
|
||||||
"""
|
"""
|
||||||
|
@ -510,7 +521,7 @@ class SettingsDialog(QtWidgets.QDialog):
|
||||||
"""
|
"""
|
||||||
Connection type automatic was toggled. If checked, hide authentication fields.
|
Connection type automatic was toggled. If checked, hide authentication fields.
|
||||||
"""
|
"""
|
||||||
common.log('SettingsDialog', 'connection_type_automatic_toggled')
|
self.common.log('SettingsDialog', 'connection_type_automatic_toggled')
|
||||||
if checked:
|
if checked:
|
||||||
self.authenticate_group.hide()
|
self.authenticate_group.hide()
|
||||||
self.connection_type_socks.hide()
|
self.connection_type_socks.hide()
|
||||||
|
@ -521,7 +532,7 @@ class SettingsDialog(QtWidgets.QDialog):
|
||||||
Connection type control port was toggled. If checked, show extra fields
|
Connection type control port was toggled. If checked, show extra fields
|
||||||
for Tor control address and port. If unchecked, hide those extra fields.
|
for Tor control address and port. If unchecked, hide those extra fields.
|
||||||
"""
|
"""
|
||||||
common.log('SettingsDialog', 'connection_type_control_port_toggled')
|
self.common.log('SettingsDialog', 'connection_type_control_port_toggled')
|
||||||
if checked:
|
if checked:
|
||||||
self.authenticate_group.show()
|
self.authenticate_group.show()
|
||||||
self.connection_type_control_port_extras.show()
|
self.connection_type_control_port_extras.show()
|
||||||
|
@ -536,7 +547,7 @@ class SettingsDialog(QtWidgets.QDialog):
|
||||||
Connection type socket file was toggled. If checked, show extra fields
|
Connection type socket file was toggled. If checked, show extra fields
|
||||||
for socket file. If unchecked, hide those extra fields.
|
for socket file. If unchecked, hide those extra fields.
|
||||||
"""
|
"""
|
||||||
common.log('SettingsDialog', 'connection_type_socket_file_toggled')
|
self.common.log('SettingsDialog', 'connection_type_socket_file_toggled')
|
||||||
if checked:
|
if checked:
|
||||||
self.authenticate_group.show()
|
self.authenticate_group.show()
|
||||||
self.connection_type_socket_file_extras.show()
|
self.connection_type_socket_file_extras.show()
|
||||||
|
@ -549,14 +560,14 @@ class SettingsDialog(QtWidgets.QDialog):
|
||||||
"""
|
"""
|
||||||
Authentication option no authentication was toggled.
|
Authentication option no authentication was toggled.
|
||||||
"""
|
"""
|
||||||
common.log('SettingsDialog', 'authenticate_no_auth_toggled')
|
self.common.log('SettingsDialog', 'authenticate_no_auth_toggled')
|
||||||
|
|
||||||
def authenticate_password_toggled(self, checked):
|
def authenticate_password_toggled(self, checked):
|
||||||
"""
|
"""
|
||||||
Authentication option password was toggled. If checked, show extra fields
|
Authentication option password was toggled. If checked, show extra fields
|
||||||
for password auth. If unchecked, hide those extra fields.
|
for password auth. If unchecked, hide those extra fields.
|
||||||
"""
|
"""
|
||||||
common.log('SettingsDialog', 'authenticate_password_toggled')
|
self.common.log('SettingsDialog', 'authenticate_password_toggled')
|
||||||
if checked:
|
if checked:
|
||||||
self.authenticate_password_extras.show()
|
self.authenticate_password_extras.show()
|
||||||
else:
|
else:
|
||||||
|
@ -567,7 +578,7 @@ class SettingsDialog(QtWidgets.QDialog):
|
||||||
Toggle the 'Copy HidServAuth' button
|
Toggle the 'Copy HidServAuth' button
|
||||||
to copy the saved HidServAuth to clipboard.
|
to copy the saved HidServAuth to clipboard.
|
||||||
"""
|
"""
|
||||||
common.log('SettingsDialog', 'hidservauth_copy_button_clicked', 'HidServAuth was copied to clipboard')
|
self.common.log('SettingsDialog', 'hidservauth_copy_button_clicked', 'HidServAuth was copied to clipboard')
|
||||||
clipboard = self.qtapp.clipboard()
|
clipboard = self.qtapp.clipboard()
|
||||||
clipboard.setText(self.old_settings.get('hidservauth_string'))
|
clipboard.setText(self.old_settings.get('hidservauth_string'))
|
||||||
|
|
||||||
|
@ -576,7 +587,7 @@ class SettingsDialog(QtWidgets.QDialog):
|
||||||
Test Tor Settings button clicked. With the given settings, see if we can
|
Test Tor Settings button clicked. With the given settings, see if we can
|
||||||
successfully connect and authenticate to Tor.
|
successfully connect and authenticate to Tor.
|
||||||
"""
|
"""
|
||||||
common.log('SettingsDialog', 'test_tor_clicked')
|
self.common.log('SettingsDialog', 'test_tor_clicked')
|
||||||
settings = self.settings_from_fields()
|
settings = self.settings_from_fields()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
@ -591,17 +602,17 @@ class SettingsDialog(QtWidgets.QDialog):
|
||||||
else:
|
else:
|
||||||
tor_status_update_func = None
|
tor_status_update_func = None
|
||||||
|
|
||||||
onion = Onion()
|
onion = Onion(self.common)
|
||||||
onion.connect(settings=settings, config=self.config, tor_status_update_func=tor_status_update_func)
|
onion.connect(custom_settings=settings, config=self.config, tor_status_update_func=tor_status_update_func)
|
||||||
|
|
||||||
# If an exception hasn't been raised yet, the Tor settings work
|
# If an exception hasn't been raised yet, the Tor settings work
|
||||||
Alert(strings._('settings_test_success', True).format(onion.tor_version, onion.supports_ephemeral, onion.supports_stealth))
|
Alert(self.common, strings._('settings_test_success', True).format(onion.tor_version, onion.supports_ephemeral, onion.supports_stealth))
|
||||||
|
|
||||||
# Clean up
|
# Clean up
|
||||||
onion.cleanup()
|
onion.cleanup()
|
||||||
|
|
||||||
except (TorErrorInvalidSetting, TorErrorAutomatic, TorErrorSocketPort, TorErrorSocketFile, TorErrorMissingPassword, TorErrorUnreadableCookieFile, TorErrorAuthError, TorErrorProtocolError, BundledTorNotSupported, BundledTorTimeout) as e:
|
except (TorErrorInvalidSetting, TorErrorAutomatic, TorErrorSocketPort, TorErrorSocketFile, TorErrorMissingPassword, TorErrorUnreadableCookieFile, TorErrorAuthError, TorErrorProtocolError, BundledTorNotSupported, BundledTorTimeout) as e:
|
||||||
Alert(e.args[0], QtWidgets.QMessageBox.Warning)
|
Alert(self.common, e.args[0], QtWidgets.QMessageBox.Warning)
|
||||||
if settings.get('connection_type') == 'bundled':
|
if settings.get('connection_type') == 'bundled':
|
||||||
self.tor_status.hide()
|
self.tor_status.hide()
|
||||||
self._enable_buttons()
|
self._enable_buttons()
|
||||||
|
@ -610,14 +621,14 @@ class SettingsDialog(QtWidgets.QDialog):
|
||||||
"""
|
"""
|
||||||
Check for Updates button clicked. Manually force an update check.
|
Check for Updates button clicked. Manually force an update check.
|
||||||
"""
|
"""
|
||||||
common.log('SettingsDialog', 'check_for_updates')
|
self.common.log('SettingsDialog', 'check_for_updates')
|
||||||
# Disable buttons
|
# Disable buttons
|
||||||
self._disable_buttons()
|
self._disable_buttons()
|
||||||
self.qtapp.processEvents()
|
self.qtapp.processEvents()
|
||||||
|
|
||||||
def update_timestamp():
|
def update_timestamp():
|
||||||
# Update the last checked label
|
# Update the last checked label
|
||||||
settings = Settings(self.config)
|
settings = Settings(self.common, self.config)
|
||||||
settings.load()
|
settings.load()
|
||||||
autoupdate_timestamp = settings.get('autoupdate_timestamp')
|
autoupdate_timestamp = settings.get('autoupdate_timestamp')
|
||||||
self._update_autoupdate_timestamp(autoupdate_timestamp)
|
self._update_autoupdate_timestamp(autoupdate_timestamp)
|
||||||
|
@ -631,22 +642,22 @@ class SettingsDialog(QtWidgets.QDialog):
|
||||||
|
|
||||||
# Check for updates
|
# Check for updates
|
||||||
def update_available(update_url, installed_version, latest_version):
|
def update_available(update_url, installed_version, latest_version):
|
||||||
Alert(strings._("update_available", True).format(update_url, installed_version, latest_version))
|
Alert(self.common, strings._("update_available", True).format(update_url, installed_version, latest_version))
|
||||||
close_forced_update_thread()
|
close_forced_update_thread()
|
||||||
|
|
||||||
def update_not_available():
|
def update_not_available():
|
||||||
Alert(strings._('update_not_available', True))
|
Alert(self.common, strings._('update_not_available', True))
|
||||||
close_forced_update_thread()
|
close_forced_update_thread()
|
||||||
|
|
||||||
def update_error():
|
def update_error():
|
||||||
Alert(strings._('update_error_check_error', True), QtWidgets.QMessageBox.Warning)
|
Alert(self.common, strings._('update_error_check_error', True), QtWidgets.QMessageBox.Warning)
|
||||||
close_forced_update_thread()
|
close_forced_update_thread()
|
||||||
|
|
||||||
def update_invalid_version():
|
def update_invalid_version():
|
||||||
Alert(strings._('update_error_invalid_latest_version', True).format(e.latest_version), QtWidgets.QMessageBox.Warning)
|
Alert(self.common, strings._('update_error_invalid_latest_version', True).format(e.latest_version), QtWidgets.QMessageBox.Warning)
|
||||||
close_forced_update_thread()
|
close_forced_update_thread()
|
||||||
|
|
||||||
forced_update_thread = UpdateThread(self.onion, self.config, force=True)
|
forced_update_thread = UpdateThread(self.common, self.onion, self.config, force=True)
|
||||||
forced_update_thread.update_available.connect(update_available)
|
forced_update_thread.update_available.connect(update_available)
|
||||||
forced_update_thread.update_not_available.connect(update_not_available)
|
forced_update_thread.update_not_available.connect(update_not_available)
|
||||||
forced_update_thread.update_error.connect(update_error)
|
forced_update_thread.update_error.connect(update_error)
|
||||||
|
@ -657,7 +668,7 @@ class SettingsDialog(QtWidgets.QDialog):
|
||||||
"""
|
"""
|
||||||
Save button clicked. Save current settings to disk.
|
Save button clicked. Save current settings to disk.
|
||||||
"""
|
"""
|
||||||
common.log('SettingsDialog', 'save_clicked')
|
self.common.log('SettingsDialog', 'save_clicked')
|
||||||
|
|
||||||
settings = self.settings_from_fields()
|
settings = self.settings_from_fields()
|
||||||
if settings:
|
if settings:
|
||||||
|
@ -666,8 +677,9 @@ class SettingsDialog(QtWidgets.QDialog):
|
||||||
# If Tor isn't connected, or if Tor settings have changed, Reinitialize
|
# If Tor isn't connected, or if Tor settings have changed, Reinitialize
|
||||||
# the Onion object
|
# the Onion object
|
||||||
reboot_onion = False
|
reboot_onion = False
|
||||||
|
if not self.local_only:
|
||||||
if self.onion.is_authenticated():
|
if self.onion.is_authenticated():
|
||||||
common.log('SettingsDialog', 'save_clicked', 'Connected to Tor')
|
self.common.log('SettingsDialog', 'save_clicked', 'Connected to Tor')
|
||||||
def changed(s1, s2, keys):
|
def changed(s1, s2, keys):
|
||||||
"""
|
"""
|
||||||
Compare the Settings objects s1 and s2 and return true if any values
|
Compare the Settings objects s1 and s2 and return true if any values
|
||||||
|
@ -689,20 +701,20 @@ class SettingsDialog(QtWidgets.QDialog):
|
||||||
reboot_onion = True
|
reboot_onion = True
|
||||||
|
|
||||||
else:
|
else:
|
||||||
common.log('SettingsDialog', 'save_clicked', 'Not connected to Tor')
|
self.common.log('SettingsDialog', 'save_clicked', 'Not connected to Tor')
|
||||||
# Tor isn't connected, so try connecting
|
# Tor isn't connected, so try connecting
|
||||||
reboot_onion = True
|
reboot_onion = True
|
||||||
|
|
||||||
# Do we need to reinitialize Tor?
|
# Do we need to reinitialize Tor?
|
||||||
if reboot_onion:
|
if reboot_onion:
|
||||||
# Reinitialize the Onion object
|
# Reinitialize the Onion object
|
||||||
common.log('SettingsDialog', 'save_clicked', 'rebooting the Onion')
|
self.common.log('SettingsDialog', 'save_clicked', 'rebooting the Onion')
|
||||||
self.onion.cleanup()
|
self.onion.cleanup()
|
||||||
|
|
||||||
tor_con = TorConnectionDialog(self.qtapp, settings, self.onion)
|
tor_con = TorConnectionDialog(self.qtapp, settings, self.onion)
|
||||||
tor_con.start()
|
tor_con.start()
|
||||||
|
|
||||||
common.log('SettingsDialog', 'save_clicked', 'Onion done rebooting, connected to Tor: {}'.format(self.onion.connected_to_tor))
|
self.common.log('SettingsDialog', 'save_clicked', 'Onion done rebooting, connected to Tor: {}'.format(self.onion.connected_to_tor))
|
||||||
|
|
||||||
if self.onion.is_authenticated() and not tor_con.wasCanceled():
|
if self.onion.is_authenticated() and not tor_con.wasCanceled():
|
||||||
self.settings_saved.emit()
|
self.settings_saved.emit()
|
||||||
|
@ -711,14 +723,17 @@ class SettingsDialog(QtWidgets.QDialog):
|
||||||
else:
|
else:
|
||||||
self.settings_saved.emit()
|
self.settings_saved.emit()
|
||||||
self.close()
|
self.close()
|
||||||
|
else:
|
||||||
|
self.settings_saved.emit()
|
||||||
|
self.close()
|
||||||
|
|
||||||
def cancel_clicked(self):
|
def cancel_clicked(self):
|
||||||
"""
|
"""
|
||||||
Cancel button clicked.
|
Cancel button clicked.
|
||||||
"""
|
"""
|
||||||
common.log('SettingsDialog', 'cancel_clicked')
|
self.common.log('SettingsDialog', 'cancel_clicked')
|
||||||
if not self.onion.is_authenticated():
|
if not self.onion.is_authenticated():
|
||||||
Alert(strings._('gui_tor_connection_canceled', True), QtWidgets.QMessageBox.Warning)
|
Alert(self.common, strings._('gui_tor_connection_canceled', True), QtWidgets.QMessageBox.Warning)
|
||||||
sys.exit()
|
sys.exit()
|
||||||
else:
|
else:
|
||||||
self.close()
|
self.close()
|
||||||
|
@ -727,7 +742,7 @@ class SettingsDialog(QtWidgets.QDialog):
|
||||||
"""
|
"""
|
||||||
Help button clicked.
|
Help button clicked.
|
||||||
"""
|
"""
|
||||||
common.log('SettingsDialog', 'help_clicked')
|
self.common.log('SettingsDialog', 'help_clicked')
|
||||||
help_site = 'https://github.com/micahflee/onionshare/wiki'
|
help_site = 'https://github.com/micahflee/onionshare/wiki'
|
||||||
QtGui.QDesktopServices.openUrl(QtCore.QUrl(help_site))
|
QtGui.QDesktopServices.openUrl(QtCore.QUrl(help_site))
|
||||||
|
|
||||||
|
@ -735,8 +750,8 @@ class SettingsDialog(QtWidgets.QDialog):
|
||||||
"""
|
"""
|
||||||
Return a Settings object that's full of values from the settings dialog.
|
Return a Settings object that's full of values from the settings dialog.
|
||||||
"""
|
"""
|
||||||
common.log('SettingsDialog', 'settings_from_fields')
|
self.common.log('SettingsDialog', 'settings_from_fields')
|
||||||
settings = Settings(self.config)
|
settings = Settings(self.common, self.config)
|
||||||
settings.load() # To get the last update timestamp
|
settings.load() # To get the last update timestamp
|
||||||
|
|
||||||
settings.set('close_after_first_download', self.close_after_first_download_checkbox.isChecked())
|
settings.set('close_after_first_download', self.close_after_first_download_checkbox.isChecked())
|
||||||
|
@ -839,24 +854,25 @@ class SettingsDialog(QtWidgets.QDialog):
|
||||||
new_bridges = ''.join(new_bridges)
|
new_bridges = ''.join(new_bridges)
|
||||||
settings.set('tor_bridges_use_custom_bridges', new_bridges)
|
settings.set('tor_bridges_use_custom_bridges', new_bridges)
|
||||||
else:
|
else:
|
||||||
Alert(strings._('gui_settings_tor_bridges_invalid', True))
|
Alert(self.common, strings._('gui_settings_tor_bridges_invalid', True))
|
||||||
settings.set('no_bridges', True)
|
settings.set('no_bridges', True)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
return settings
|
return settings
|
||||||
|
|
||||||
def closeEvent(self, e):
|
def closeEvent(self, e):
|
||||||
common.log('SettingsDialog', 'closeEvent')
|
self.common.log('SettingsDialog', 'closeEvent')
|
||||||
|
|
||||||
# On close, if Tor isn't connected, then quit OnionShare altogether
|
# On close, if Tor isn't connected, then quit OnionShare altogether
|
||||||
|
if not self.local_only:
|
||||||
if not self.onion.is_authenticated():
|
if not self.onion.is_authenticated():
|
||||||
common.log('SettingsDialog', 'closeEvent', 'Closing while not connected to Tor')
|
self.common.log('SettingsDialog', 'closeEvent', 'Closing while not connected to Tor')
|
||||||
|
|
||||||
# Wait 1ms for the event loop to finish, then quit
|
# Wait 1ms for the event loop to finish, then quit
|
||||||
QtCore.QTimer.singleShot(1, self.qtapp.quit)
|
QtCore.QTimer.singleShot(1, self.qtapp.quit)
|
||||||
|
|
||||||
def _update_autoupdate_timestamp(self, autoupdate_timestamp):
|
def _update_autoupdate_timestamp(self, autoupdate_timestamp):
|
||||||
common.log('SettingsDialog', '_update_autoupdate_timestamp')
|
self.common.log('SettingsDialog', '_update_autoupdate_timestamp')
|
||||||
|
|
||||||
if autoupdate_timestamp:
|
if autoupdate_timestamp:
|
||||||
dt = datetime.datetime.fromtimestamp(autoupdate_timestamp)
|
dt = datetime.datetime.fromtimestamp(autoupdate_timestamp)
|
||||||
|
@ -873,7 +889,7 @@ class SettingsDialog(QtWidgets.QDialog):
|
||||||
self._enable_buttons()
|
self._enable_buttons()
|
||||||
|
|
||||||
def _disable_buttons(self):
|
def _disable_buttons(self):
|
||||||
common.log('SettingsDialog', '_disable_buttons')
|
self.common.log('SettingsDialog', '_disable_buttons')
|
||||||
|
|
||||||
self.check_for_updates_button.setEnabled(False)
|
self.check_for_updates_button.setEnabled(False)
|
||||||
self.connection_type_test_button.setEnabled(False)
|
self.connection_type_test_button.setEnabled(False)
|
||||||
|
@ -881,7 +897,7 @@ class SettingsDialog(QtWidgets.QDialog):
|
||||||
self.cancel_button.setEnabled(False)
|
self.cancel_button.setEnabled(False)
|
||||||
|
|
||||||
def _enable_buttons(self):
|
def _enable_buttons(self):
|
||||||
common.log('SettingsDialog', '_enable_buttons')
|
self.common.log('SettingsDialog', '_enable_buttons')
|
||||||
# We can't check for updates if we're still not connected to Tor
|
# We can't check for updates if we're still not connected to Tor
|
||||||
if not self.onion.connected_to_tor:
|
if not self.onion.connected_to_tor:
|
||||||
self.check_for_updates_button.setEnabled(False)
|
self.check_for_updates_button.setEnabled(False)
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
"""
|
"""
|
||||||
OnionShare | https://onionshare.org/
|
OnionShare | https://onionshare.org/
|
||||||
|
|
||||||
Copyright (C) 2017 Micah Lee <micah@micahflee.com>
|
Copyright (C) 2018 Micah Lee <micah@micahflee.com>
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
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
|
it under the terms of the GNU General Public License as published by
|
||||||
|
@ -19,7 +19,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
"""
|
"""
|
||||||
from PyQt5 import QtCore, QtWidgets, QtGui
|
from PyQt5 import QtCore, QtWidgets, QtGui
|
||||||
|
|
||||||
from onionshare import strings, common
|
from onionshare import strings
|
||||||
from onionshare.onion import *
|
from onionshare.onion import *
|
||||||
|
|
||||||
from .alert import Alert
|
from .alert import Alert
|
||||||
|
@ -30,16 +30,23 @@ class TorConnectionDialog(QtWidgets.QProgressDialog):
|
||||||
"""
|
"""
|
||||||
open_settings = QtCore.pyqtSignal()
|
open_settings = QtCore.pyqtSignal()
|
||||||
|
|
||||||
def __init__(self, qtapp, settings, onion):
|
def __init__(self, common, qtapp, onion, custom_settings=False):
|
||||||
super(TorConnectionDialog, self).__init__(None)
|
super(TorConnectionDialog, self).__init__(None)
|
||||||
common.log('TorConnectionDialog', '__init__')
|
|
||||||
|
self.common = common
|
||||||
|
|
||||||
|
if custom_settings:
|
||||||
|
self.settings = custom_settings
|
||||||
|
else:
|
||||||
|
self.settings = self.common.settings
|
||||||
|
|
||||||
|
self.common.log('TorConnectionDialog', '__init__')
|
||||||
|
|
||||||
self.qtapp = qtapp
|
self.qtapp = qtapp
|
||||||
self.settings = settings
|
|
||||||
self.onion = onion
|
self.onion = onion
|
||||||
|
|
||||||
self.setWindowTitle("OnionShare")
|
self.setWindowTitle("OnionShare")
|
||||||
self.setWindowIcon(QtGui.QIcon(common.get_resource_path('images/logo.png')))
|
self.setWindowIcon(QtGui.QIcon(self.common.get_resource_path('images/logo.png')))
|
||||||
self.setModal(True)
|
self.setModal(True)
|
||||||
self.setFixedSize(400, 150)
|
self.setFixedSize(400, 150)
|
||||||
|
|
||||||
|
@ -55,9 +62,9 @@ class TorConnectionDialog(QtWidgets.QProgressDialog):
|
||||||
self._tor_status_update(0, '')
|
self._tor_status_update(0, '')
|
||||||
|
|
||||||
def start(self):
|
def start(self):
|
||||||
common.log('TorConnectionDialog', 'start')
|
self.common.log('TorConnectionDialog', 'start')
|
||||||
|
|
||||||
t = TorConnectionThread(self, self.settings, self.onion)
|
t = TorConnectionThread(self.common, self.settings, self, self.onion)
|
||||||
t.tor_status_update.connect(self._tor_status_update)
|
t.tor_status_update.connect(self._tor_status_update)
|
||||||
t.connected_to_tor.connect(self._connected_to_tor)
|
t.connected_to_tor.connect(self._connected_to_tor)
|
||||||
t.canceled_connecting_to_tor.connect(self._canceled_connecting_to_tor)
|
t.canceled_connecting_to_tor.connect(self._canceled_connecting_to_tor)
|
||||||
|
@ -77,14 +84,14 @@ class TorConnectionDialog(QtWidgets.QProgressDialog):
|
||||||
self.setLabelText("<strong>{}</strong><br>{}".format(strings._('connecting_to_tor', True), summary))
|
self.setLabelText("<strong>{}</strong><br>{}".format(strings._('connecting_to_tor', True), summary))
|
||||||
|
|
||||||
def _connected_to_tor(self):
|
def _connected_to_tor(self):
|
||||||
common.log('TorConnectionDialog', '_connected_to_tor')
|
self.common.log('TorConnectionDialog', '_connected_to_tor')
|
||||||
self.active = False
|
self.active = False
|
||||||
|
|
||||||
# Close the dialog after connecting
|
# Close the dialog after connecting
|
||||||
self.setValue(self.maximum())
|
self.setValue(self.maximum())
|
||||||
|
|
||||||
def _canceled_connecting_to_tor(self):
|
def _canceled_connecting_to_tor(self):
|
||||||
common.log('TorConnectionDialog', '_canceled_connecting_to_tor')
|
self.common.log('TorConnectionDialog', '_canceled_connecting_to_tor')
|
||||||
self.active = False
|
self.active = False
|
||||||
self.onion.cleanup()
|
self.onion.cleanup()
|
||||||
|
|
||||||
|
@ -92,12 +99,12 @@ class TorConnectionDialog(QtWidgets.QProgressDialog):
|
||||||
QtCore.QTimer.singleShot(1, self.cancel)
|
QtCore.QTimer.singleShot(1, self.cancel)
|
||||||
|
|
||||||
def _error_connecting_to_tor(self, msg):
|
def _error_connecting_to_tor(self, msg):
|
||||||
common.log('TorConnectionDialog', '_error_connecting_to_tor')
|
self.common.log('TorConnectionDialog', '_error_connecting_to_tor')
|
||||||
self.active = False
|
self.active = False
|
||||||
|
|
||||||
def alert_and_open_settings():
|
def alert_and_open_settings():
|
||||||
# Display the exception in an alert box
|
# Display the exception in an alert box
|
||||||
Alert("{}\n\n{}".format(msg, strings._('gui_tor_connection_error_settings', True)), QtWidgets.QMessageBox.Warning)
|
Alert(self.common, "{}\n\n{}".format(msg, strings._('gui_tor_connection_error_settings', True)), QtWidgets.QMessageBox.Warning)
|
||||||
|
|
||||||
# Open settings
|
# Open settings
|
||||||
self.open_settings.emit()
|
self.open_settings.emit()
|
||||||
|
@ -113,16 +120,20 @@ class TorConnectionThread(QtCore.QThread):
|
||||||
canceled_connecting_to_tor = QtCore.pyqtSignal()
|
canceled_connecting_to_tor = QtCore.pyqtSignal()
|
||||||
error_connecting_to_tor = QtCore.pyqtSignal(str)
|
error_connecting_to_tor = QtCore.pyqtSignal(str)
|
||||||
|
|
||||||
def __init__(self, dialog, settings, onion):
|
def __init__(self, common, settings, dialog, onion):
|
||||||
super(TorConnectionThread, self).__init__()
|
super(TorConnectionThread, self).__init__()
|
||||||
common.log('TorConnectionThread', '__init__')
|
|
||||||
|
self.common = common
|
||||||
|
|
||||||
|
self.common.log('TorConnectionThread', '__init__')
|
||||||
|
|
||||||
|
self.settings = settings
|
||||||
|
|
||||||
self.dialog = dialog
|
self.dialog = dialog
|
||||||
self.settings = settings
|
|
||||||
self.onion = onion
|
self.onion = onion
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
common.log('TorConnectionThread', 'run')
|
self.common.log('TorConnectionThread', 'run')
|
||||||
|
|
||||||
# Connect to the Onion
|
# Connect to the Onion
|
||||||
try:
|
try:
|
||||||
|
@ -133,11 +144,11 @@ class TorConnectionThread(QtCore.QThread):
|
||||||
self.canceled_connecting_to_tor.emit()
|
self.canceled_connecting_to_tor.emit()
|
||||||
|
|
||||||
except BundledTorCanceled as e:
|
except BundledTorCanceled as e:
|
||||||
common.log('TorConnectionThread', 'run', 'caught exception: BundledTorCanceled')
|
self.common.log('TorConnectionThread', 'run', 'caught exception: BundledTorCanceled')
|
||||||
self.canceled_connecting_to_tor.emit()
|
self.canceled_connecting_to_tor.emit()
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
common.log('TorConnectionThread', 'run', 'caught exception: {}'.format(e.args[0]))
|
self.common.log('TorConnectionThread', 'run', 'caught exception: {}'.format(e.args[0]))
|
||||||
self.error_connecting_to_tor.emit(str(e.args[0]))
|
self.error_connecting_to_tor.emit(str(e.args[0]))
|
||||||
|
|
||||||
def _tor_status_update(self, progress, summary):
|
def _tor_status_update(self, progress, summary):
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
"""
|
"""
|
||||||
OnionShare | https://onionshare.org/
|
OnionShare | https://onionshare.org/
|
||||||
|
|
||||||
Copyright (C) 2017 Micah Lee <micah@micahflee.com>
|
Copyright (C) 2018 Micah Lee <micah@micahflee.com>
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
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
|
it under the terms of the GNU General Public License as published by
|
||||||
|
@ -25,7 +25,7 @@ from onionshare import socks
|
||||||
from onionshare.settings import Settings
|
from onionshare.settings import Settings
|
||||||
from onionshare.onion import Onion
|
from onionshare.onion import Onion
|
||||||
|
|
||||||
from . import strings, common
|
from . import strings
|
||||||
|
|
||||||
class UpdateCheckerCheckError(Exception):
|
class UpdateCheckerCheckError(Exception):
|
||||||
"""
|
"""
|
||||||
|
@ -55,16 +55,19 @@ class UpdateChecker(QtCore.QObject):
|
||||||
update_error = QtCore.pyqtSignal()
|
update_error = QtCore.pyqtSignal()
|
||||||
update_invalid_version = QtCore.pyqtSignal()
|
update_invalid_version = QtCore.pyqtSignal()
|
||||||
|
|
||||||
def __init__(self, onion, config=False):
|
def __init__(self, common, onion, config=False):
|
||||||
super(UpdateChecker, self).__init__()
|
super(UpdateChecker, self).__init__()
|
||||||
common.log('UpdateChecker', '__init__')
|
|
||||||
|
self.common = common
|
||||||
|
|
||||||
|
self.common.log('UpdateChecker', '__init__')
|
||||||
self.onion = onion
|
self.onion = onion
|
||||||
self.config = config
|
self.config = config
|
||||||
|
|
||||||
def check(self, force=False, config=False):
|
def check(self, force=False, config=False):
|
||||||
common.log('UpdateChecker', 'check', 'force={}'.format(force))
|
self.common.log('UpdateChecker', 'check', 'force={}'.format(force))
|
||||||
# Load the settings
|
# Load the settings
|
||||||
settings = Settings(config)
|
settings = Settings(self.common, config)
|
||||||
settings.load()
|
settings.load()
|
||||||
|
|
||||||
# If force=True, then definitely check
|
# If force=True, then definitely check
|
||||||
|
@ -87,11 +90,11 @@ class UpdateChecker(QtCore.QObject):
|
||||||
|
|
||||||
# Check for updates
|
# Check for updates
|
||||||
if check_for_updates:
|
if check_for_updates:
|
||||||
common.log('UpdateChecker', 'check', 'checking for updates')
|
self.common.log('UpdateChecker', 'check', 'checking for updates')
|
||||||
# Download the latest-version file over Tor
|
# Download the latest-version file over Tor
|
||||||
try:
|
try:
|
||||||
# User agent string includes OnionShare version and platform
|
# User agent string includes OnionShare version and platform
|
||||||
user_agent = 'OnionShare {}, {}'.format(common.get_version(), platform.system())
|
user_agent = 'OnionShare {}, {}'.format(self.common.version, self.common.platform)
|
||||||
|
|
||||||
# If the update is forced, add '?force=1' to the URL, to more
|
# If the update is forced, add '?force=1' to the URL, to more
|
||||||
# accurately measure daily users
|
# accurately measure daily users
|
||||||
|
@ -104,7 +107,7 @@ class UpdateChecker(QtCore.QObject):
|
||||||
else:
|
else:
|
||||||
onion_domain = 'elx57ue5uyfplgva.onion'
|
onion_domain = 'elx57ue5uyfplgva.onion'
|
||||||
|
|
||||||
common.log('UpdateChecker', 'check', 'loading http://{}{}'.format(onion_domain, path))
|
self.common.log('UpdateChecker', 'check', 'loading http://{}{}'.format(onion_domain, path))
|
||||||
|
|
||||||
(socks_address, socks_port) = self.onion.get_tor_socks_port()
|
(socks_address, socks_port) = self.onion.get_tor_socks_port()
|
||||||
socks.set_default_proxy(socks.SOCKS5, socks_address, socks_port)
|
socks.set_default_proxy(socks.SOCKS5, socks_address, socks_port)
|
||||||
|
@ -122,10 +125,10 @@ class UpdateChecker(QtCore.QObject):
|
||||||
http_response = s.recv(1024)
|
http_response = s.recv(1024)
|
||||||
latest_version = http_response[http_response.find(b'\r\n\r\n'):].strip().decode('utf-8')
|
latest_version = http_response[http_response.find(b'\r\n\r\n'):].strip().decode('utf-8')
|
||||||
|
|
||||||
common.log('UpdateChecker', 'check', 'latest OnionShare version: {}'.format(latest_version))
|
self.common.log('UpdateChecker', 'check', 'latest OnionShare version: {}'.format(latest_version))
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
common.log('UpdateChecker', 'check', '{}'.format(e))
|
self.common.log('UpdateChecker', 'check', '{}'.format(e))
|
||||||
self.update_error.emit()
|
self.update_error.emit()
|
||||||
raise UpdateCheckerCheckError
|
raise UpdateCheckerCheckError
|
||||||
|
|
||||||
|
@ -145,7 +148,7 @@ class UpdateChecker(QtCore.QObject):
|
||||||
|
|
||||||
# Do we need to update?
|
# Do we need to update?
|
||||||
update_url = 'https://github.com/micahflee/onionshare/releases/tag/v{}'.format(latest_version)
|
update_url = 'https://github.com/micahflee/onionshare/releases/tag/v{}'.format(latest_version)
|
||||||
installed_version = common.get_version()
|
installed_version = self.common.version
|
||||||
if installed_version < latest_version:
|
if installed_version < latest_version:
|
||||||
self.update_available.emit(update_url, installed_version, latest_version)
|
self.update_available.emit(update_url, installed_version, latest_version)
|
||||||
return
|
return
|
||||||
|
@ -159,17 +162,20 @@ class UpdateThread(QtCore.QThread):
|
||||||
update_error = QtCore.pyqtSignal()
|
update_error = QtCore.pyqtSignal()
|
||||||
update_invalid_version = QtCore.pyqtSignal()
|
update_invalid_version = QtCore.pyqtSignal()
|
||||||
|
|
||||||
def __init__(self, onion, config=False, force=False):
|
def __init__(self, common, onion, config=False, force=False):
|
||||||
super(UpdateThread, self).__init__()
|
super(UpdateThread, self).__init__()
|
||||||
common.log('UpdateThread', '__init__')
|
|
||||||
|
self.common = common
|
||||||
|
|
||||||
|
self.common.log('UpdateThread', '__init__')
|
||||||
self.onion = onion
|
self.onion = onion
|
||||||
self.config = config
|
self.config = config
|
||||||
self.force = force
|
self.force = force
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
common.log('UpdateThread', 'run')
|
self.common.log('UpdateThread', 'run')
|
||||||
|
|
||||||
u = UpdateChecker(self.onion, self.config)
|
u = UpdateChecker(self.common, self.onion, self.config)
|
||||||
u.update_available.connect(self._update_available)
|
u.update_available.connect(self._update_available)
|
||||||
u.update_not_available.connect(self._update_not_available)
|
u.update_not_available.connect(self._update_not_available)
|
||||||
u.update_error.connect(self._update_error)
|
u.update_error.connect(self._update_error)
|
||||||
|
@ -179,25 +185,25 @@ class UpdateThread(QtCore.QThread):
|
||||||
u.check(config=self.config,force=self.force)
|
u.check(config=self.config,force=self.force)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
# If update check fails, silently ignore
|
# If update check fails, silently ignore
|
||||||
common.log('UpdateThread', 'run', '{}'.format(e))
|
self.common.log('UpdateThread', 'run', '{}'.format(e))
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def _update_available(self, update_url, installed_version, latest_version):
|
def _update_available(self, update_url, installed_version, latest_version):
|
||||||
common.log('UpdateThread', '_update_available')
|
self.common.log('UpdateThread', '_update_available')
|
||||||
self.active = False
|
self.active = False
|
||||||
self.update_available.emit(update_url, installed_version, latest_version)
|
self.update_available.emit(update_url, installed_version, latest_version)
|
||||||
|
|
||||||
def _update_not_available(self):
|
def _update_not_available(self):
|
||||||
common.log('UpdateThread', '_update_not_available')
|
self.common.log('UpdateThread', '_update_not_available')
|
||||||
self.active = False
|
self.active = False
|
||||||
self.update_not_available.emit()
|
self.update_not_available.emit()
|
||||||
|
|
||||||
def _update_error(self):
|
def _update_error(self):
|
||||||
common.log('UpdateThread', '_update_error')
|
self.common.log('UpdateThread', '_update_error')
|
||||||
self.active = False
|
self.active = False
|
||||||
self.update_error.emit()
|
self.update_error.emit()
|
||||||
|
|
||||||
def _update_invalid_version(self):
|
def _update_invalid_version(self):
|
||||||
common.log('UpdateThread', '_update_invalid_version')
|
self.common.log('UpdateThread', '_update_invalid_version')
|
||||||
self.active = False
|
self.active = False
|
||||||
self.update_invalid_version.emit()
|
self.update_invalid_version.emit()
|
||||||
|
|
7
setup.py
|
@ -3,7 +3,7 @@
|
||||||
"""
|
"""
|
||||||
OnionShare | https://onionshare.org/
|
OnionShare | https://onionshare.org/
|
||||||
|
|
||||||
Copyright (C) 2017 Micah Lee <micah@micahflee.com>
|
Copyright (C) 2018 Micah Lee <micah@micahflee.com>
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
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
|
it under the terms of the GNU General Public License as published by
|
||||||
|
@ -52,7 +52,10 @@ data_files=[
|
||||||
(os.path.join(sys.prefix, 'share/onionshare'), file_list('share')),
|
(os.path.join(sys.prefix, 'share/onionshare'), file_list('share')),
|
||||||
(os.path.join(sys.prefix, 'share/onionshare/images'), file_list('share/images')),
|
(os.path.join(sys.prefix, 'share/onionshare/images'), file_list('share/images')),
|
||||||
(os.path.join(sys.prefix, 'share/onionshare/locale'), file_list('share/locale')),
|
(os.path.join(sys.prefix, 'share/onionshare/locale'), file_list('share/locale')),
|
||||||
(os.path.join(sys.prefix, 'share/onionshare/html'), file_list('share/html')),
|
(os.path.join(sys.prefix, 'share/onionshare/templates'), file_list('share/templates')),
|
||||||
|
(os.path.join(sys.prefix, 'share/onionshare/static/css'), file_list('share/static/css')),
|
||||||
|
(os.path.join(sys.prefix, 'share/onionshare/static/img'), file_list('share/static/img')),
|
||||||
|
(os.path.join(sys.prefix, 'share/onionshare/static/js'), file_list('share/static/js'))
|
||||||
]
|
]
|
||||||
if platform.system() != 'OpenBSD':
|
if platform.system() != 'OpenBSD':
|
||||||
data_files.append(('/usr/share/nautilus-python/extensions/', ['install/scripts/onionshare-nautilus.py']))
|
data_files.append(('/usr/share/nautilus-python/extensions/', ['install/scripts/onionshare-nautilus.py']))
|
||||||
|
|
|
@ -1,16 +0,0 @@
|
||||||
<!DOCTYPE html>
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<title>Error 404</title>
|
|
||||||
<link href="data:image/x-icon;base64,{{favicon_b64}}" rel="icon" type="image/x-icon" />
|
|
||||||
<style type="text/css">
|
|
||||||
body {
|
|
||||||
background-color: #FFC4D5;
|
|
||||||
color: #FF0048;
|
|
||||||
text-align: center;
|
|
||||||
font-size: 20em;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>404</body>
|
|
||||||
</html>
|
|
|
@ -1,19 +0,0 @@
|
||||||
<!DOCTYPE html>
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<title>OnionShare</title>
|
|
||||||
<link href="data:image/x-icon;base64,{{favicon_b64}}" rel="icon" type="image/x-icon" />
|
|
||||||
<style>
|
|
||||||
body {
|
|
||||||
background-color: #222222;
|
|
||||||
color: #ffffff;
|
|
||||||
text-align: center;
|
|
||||||
font-family: sans-serif;
|
|
||||||
padding: 5em 1em;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<p>OnionShare download in progress</p>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
|
@ -1,208 +0,0 @@
|
||||||
<!DOCTYPE html>
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<title>OnionShare</title>
|
|
||||||
<link href="data:image/x-icon;base64,{{favicon_b64}}" rel="icon" type="image/x-icon" />
|
|
||||||
<style type="text/css">
|
|
||||||
.clearfix:after {
|
|
||||||
content: ".";
|
|
||||||
display: block;
|
|
||||||
clear: both;
|
|
||||||
visibility: hidden;
|
|
||||||
line-height: 0;
|
|
||||||
height: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
body {
|
|
||||||
margin: 0;
|
|
||||||
font-family: Helvetica;
|
|
||||||
}
|
|
||||||
|
|
||||||
header {
|
|
||||||
border-bottom: 1px solid rgba(0, 0, 0, 0.1);
|
|
||||||
background: #fcfcfc;
|
|
||||||
background: -webkit-linear-gradient(top, #fcfcfc 0%, #f2f2f2 100%);
|
|
||||||
padding: 0.8rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
header .logo {
|
|
||||||
vertical-align: middle;
|
|
||||||
width: 45px;
|
|
||||||
height: 45px;
|
|
||||||
}
|
|
||||||
|
|
||||||
header h1 {
|
|
||||||
display: inline-block;
|
|
||||||
margin: 0 0 0 0.5rem;
|
|
||||||
vertical-align: middle;
|
|
||||||
font-weight: normal;
|
|
||||||
font-size: 1.5rem;
|
|
||||||
color: #666666;
|
|
||||||
}
|
|
||||||
|
|
||||||
header .right {
|
|
||||||
float: right;
|
|
||||||
font-size: .75rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
header .right ul li {
|
|
||||||
display: inline;
|
|
||||||
margin: 0 0 0 .5rem;
|
|
||||||
font-size: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
header .button {
|
|
||||||
color: #ffffff;
|
|
||||||
background-color: #4e064f;
|
|
||||||
padding: 10px;
|
|
||||||
border-radius: 5px;
|
|
||||||
text-decoration: none;
|
|
||||||
margin-left: 1rem;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
table.file-list {
|
|
||||||
width: 100%;
|
|
||||||
margin: 0 auto;
|
|
||||||
border-collapse: collapse;
|
|
||||||
}
|
|
||||||
|
|
||||||
table.file-list th {
|
|
||||||
text-align: left;
|
|
||||||
text-transform: uppercase;
|
|
||||||
font-weight: normal;
|
|
||||||
color: #666666;
|
|
||||||
padding: 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
table.file-list tr {
|
|
||||||
border-bottom: 1px solid #e0e0e0;
|
|
||||||
}
|
|
||||||
|
|
||||||
table.file-list td {
|
|
||||||
white-space: nowrap;
|
|
||||||
padding: 0.5rem 10rem 0.5rem 0.8rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
table.file-list td img {
|
|
||||||
vertical-align: middle;
|
|
||||||
margin-right: 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
table.file-list td:last-child {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
<meta name="onionshare-filename" content="{{ filename }}">
|
|
||||||
<meta name="onionshare-filesize" content="{{ filesize }}">
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
|
|
||||||
<header class="clearfix">
|
|
||||||
<div class="right">
|
|
||||||
<ul>
|
|
||||||
<li>Total size: <strong>{{ filesize_human }}</strong> (compressed)</li>
|
|
||||||
<li><a class="button" href='/{{ slug }}/download'>Download Files</a></li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
<img class="logo" src="data:image/png;base64,{{logo_b64}}" title="OnionShare">
|
|
||||||
<h1>OnionShare</h1>
|
|
||||||
</header>
|
|
||||||
|
|
||||||
<table class="file-list" id="file-list">
|
|
||||||
<tr>
|
|
||||||
<th onclick="sortTable(0)">Filename</th>
|
|
||||||
<th onclick="sortTable(1)">Size</th>
|
|
||||||
<th></th>
|
|
||||||
</tr>
|
|
||||||
{% for info in file_info.dirs %}
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
<img width="30" height="30" title="" alt="" src="data:image/png;base64,{{ folder_b64 }}" />
|
|
||||||
{{ info.basename }}
|
|
||||||
</td>
|
|
||||||
<td>{{ info.size_human }}</td>
|
|
||||||
<td></td>
|
|
||||||
</tr>
|
|
||||||
{% endfor %}
|
|
||||||
{% for info in file_info.files %}
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
<img width="30" height="30" title="" alt="" src="data:image/png;base64,{{ file_b64 }}" />
|
|
||||||
{{ info.basename }}
|
|
||||||
</td>
|
|
||||||
<td>{{ info.size_human }}</td>
|
|
||||||
<td></td>
|
|
||||||
</tr>
|
|
||||||
{% endfor %}
|
|
||||||
</table>
|
|
||||||
<script>
|
|
||||||
// Function to convert human-readable sizes back to bytes, for sorting
|
|
||||||
function unhumanize(text) {
|
|
||||||
var powers = {'b': 0, 'k': 1, 'm': 2, 'g': 3, 't': 4};
|
|
||||||
var regex = /(\d+(?:\.\d+)?)\s?(B|K|M|G|T)?/i;
|
|
||||||
var res = regex.exec(text);
|
|
||||||
if(res[2] === undefined) {
|
|
||||||
// Account for alphabetical words (file/dir names)
|
|
||||||
return text;
|
|
||||||
} else {
|
|
||||||
return res[1] * Math.pow(1024, powers[res[2].toLowerCase()]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
function sortTable(n) {
|
|
||||||
var table, rows, switching, i, x, y, shouldSwitch, dir, switchcount = 0;
|
|
||||||
table = document.getElementById("file-list");
|
|
||||||
switching = true;
|
|
||||||
// Set the sorting direction to ascending:
|
|
||||||
dir = "asc";
|
|
||||||
/* Make a loop that will continue until
|
|
||||||
no switching has been done: */
|
|
||||||
while (switching) {
|
|
||||||
// Start by saying: no switching is done:
|
|
||||||
switching = false;
|
|
||||||
rows = table.getElementsByTagName("TR");
|
|
||||||
/* Loop through all table rows (except the
|
|
||||||
first, which contains table headers): */
|
|
||||||
for (i = 1; i < (rows.length - 1); i++) {
|
|
||||||
// Start by saying there should be no switching:
|
|
||||||
shouldSwitch = false;
|
|
||||||
/* Get the two elements you want to compare,
|
|
||||||
one from current row and one from the next: */
|
|
||||||
x = rows[i].getElementsByTagName("TD")[n];
|
|
||||||
y = rows[i + 1].getElementsByTagName("TD")[n];
|
|
||||||
/* Check if the two rows should switch place,
|
|
||||||
based on the direction, asc or desc: */
|
|
||||||
if (dir == "asc") {
|
|
||||||
if (unhumanize(x.innerHTML.toLowerCase()) > unhumanize(y.innerHTML.toLowerCase())) {
|
|
||||||
// If so, mark as a switch and break the loop:
|
|
||||||
shouldSwitch= true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
} else if (dir == "desc") {
|
|
||||||
if (unhumanize(x.innerHTML.toLowerCase()) < unhumanize(y.innerHTML.toLowerCase())) {
|
|
||||||
// If so, mark as a switch and break the loop:
|
|
||||||
shouldSwitch= true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (shouldSwitch) {
|
|
||||||
/* If a switch has been marked, make the switch
|
|
||||||
and mark that a switch has been done: */
|
|
||||||
rows[i].parentNode.insertBefore(rows[i + 1], rows[i]);
|
|
||||||
switching = true;
|
|
||||||
// Each time a switch is done, increase this count by 1:
|
|
||||||
switchcount ++;
|
|
||||||
} else {
|
|
||||||
/* If no switching has been done AND the direction is "asc",
|
|
||||||
set the direction to "desc" and run the while loop again. */
|
|
||||||
if (switchcount == 0 && dir == "asc") {
|
|
||||||
dir = "desc";
|
|
||||||
switching = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
BIN
share/images/download_window_gray.png
Normal file
After Width: | Height: | Size: 440 B |
BIN
share/images/download_window_green.png
Normal file
After Width: | Height: | Size: 761 B |
|
@ -2,14 +2,17 @@
|
||||||
"config_onion_service": "Configuring onion service on port {0:d}.",
|
"config_onion_service": "Configuring onion service on port {0:d}.",
|
||||||
"preparing_files": "Preparing files to share.",
|
"preparing_files": "Preparing files to share.",
|
||||||
"wait_for_hs": "Waiting for HS to be ready:",
|
"wait_for_hs": "Waiting for HS to be ready:",
|
||||||
"wait_for_hs_trying": "Trying...",
|
"wait_for_hs_trying": "Trying…",
|
||||||
"wait_for_hs_nope": "Not ready yet.",
|
"wait_for_hs_nope": "Not ready yet.",
|
||||||
"wait_for_hs_yup": "Ready!",
|
"wait_for_hs_yup": "Ready!",
|
||||||
"give_this_url": "Give this address to the person you're sending the file to:",
|
"give_this_url": "Give this address to the person you're sending the file to:",
|
||||||
"give_this_url_stealth": "Give this address and HidServAuth line to the person you're sending the file to:",
|
"give_this_url_stealth": "Give this address and HidServAuth line to the person you're sending the file to:",
|
||||||
"ctrlc_to_stop": "Press Ctrl-C to stop server",
|
"give_this_url_receive": "Give this address to the people sending you files:",
|
||||||
|
"give_this_url_receive_stealth": "Give this address and HidServAuth line to the people sending you files:",
|
||||||
|
"ctrlc_to_stop": "Press Ctrl+C to stop the server",
|
||||||
"not_a_file": "{0:s} is not a valid file.",
|
"not_a_file": "{0:s} is not a valid file.",
|
||||||
"not_a_readable_file": "{0:s} is not a readable file.",
|
"not_a_readable_file": "{0:s} is not a readable file.",
|
||||||
|
"no_filenames": "You must specify a list of files to share.",
|
||||||
"no_available_port": "Could not start the Onion service as there was no available port.",
|
"no_available_port": "Could not start the Onion service as there was no available port.",
|
||||||
"download_page_loaded": "Download page loaded",
|
"download_page_loaded": "Download page loaded",
|
||||||
"other_page_loaded": "Address loaded",
|
"other_page_loaded": "Address loaded",
|
||||||
|
@ -17,7 +20,7 @@
|
||||||
"closing_automatically": "Stopped because download finished",
|
"closing_automatically": "Stopped because download finished",
|
||||||
"timeout_download_still_running": "Waiting for download to complete",
|
"timeout_download_still_running": "Waiting for download to complete",
|
||||||
"large_filesize": "Warning: Sending large files could take hours",
|
"large_filesize": "Warning: Sending large files could take hours",
|
||||||
"error_tails_invalid_port": "Invalid value, port must be an integer",
|
"error_tails_invalid_port": "Invalid value, port must be a regular number",
|
||||||
"error_tails_unknown_root": "Unknown error with Tails root process",
|
"error_tails_unknown_root": "Unknown error with Tails root process",
|
||||||
"systray_menu_exit": "Quit",
|
"systray_menu_exit": "Quit",
|
||||||
"systray_download_started_title": "OnionShare Download Started",
|
"systray_download_started_title": "OnionShare Download Started",
|
||||||
|
@ -26,11 +29,11 @@
|
||||||
"systray_download_completed_message": "The user finished downloading your files",
|
"systray_download_completed_message": "The user finished downloading your files",
|
||||||
"systray_download_canceled_title": "OnionShare Download Canceled",
|
"systray_download_canceled_title": "OnionShare Download Canceled",
|
||||||
"systray_download_canceled_message": "The user canceled the download",
|
"systray_download_canceled_message": "The user canceled the download",
|
||||||
"help_local_only": "Do not attempt to use tor: for development only",
|
"help_local_only": "Do not attempt to use Tor: For development only",
|
||||||
"help_stay_open": "Keep onion service running after download has finished",
|
"help_stay_open": "Keep onion service running after download has finished",
|
||||||
"help_shutdown_timeout": "Shut down the onion service after N seconds",
|
"help_shutdown_timeout": "Shut down the onion service after N seconds",
|
||||||
"help_transparent_torification": "My system is transparently torified",
|
|
||||||
"help_stealth": "Create stealth onion service (advanced)",
|
"help_stealth": "Create stealth onion service (advanced)",
|
||||||
|
"help_receive": "Receive files instead of sending them",
|
||||||
"help_debug": "Log application errors to stdout, and log web errors to disk",
|
"help_debug": "Log application errors to stdout, and log web errors to disk",
|
||||||
"help_filename": "List of files or folders to share",
|
"help_filename": "List of files or folders to share",
|
||||||
"help_config": "Path to a custom JSON config file (optional)",
|
"help_config": "Path to a custom JSON config file (optional)",
|
||||||
|
@ -41,25 +44,27 @@
|
||||||
"gui_start_server": "Start Sharing",
|
"gui_start_server": "Start Sharing",
|
||||||
"gui_stop_server": "Stop Sharing",
|
"gui_stop_server": "Stop Sharing",
|
||||||
"gui_stop_server_shutdown_timeout": "Stop Sharing ({}s remaining)",
|
"gui_stop_server_shutdown_timeout": "Stop Sharing ({}s remaining)",
|
||||||
"gui_stop_server_shutdown_timeout_tooltip": "Share will stop automatically at {}",
|
"gui_stop_server_shutdown_timeout_tooltip": "Share will expire automatically at {}",
|
||||||
"gui_copy_url": "Copy Address",
|
"gui_copy_url": "Copy Address",
|
||||||
"gui_copy_hidservauth": "Copy HidServAuth",
|
"gui_copy_hidservauth": "Copy HidServAuth",
|
||||||
"gui_downloads": "Downloads:",
|
"gui_downloads": "Download History",
|
||||||
|
"gui_downloads_window_tooltip": "Show/hide downloads",
|
||||||
|
"gui_no_downloads": "No downloads yet.",
|
||||||
"gui_canceled": "Canceled",
|
"gui_canceled": "Canceled",
|
||||||
"gui_copied_url_title": "Copied OnionShare address",
|
"gui_copied_url_title": "Copied OnionShare address",
|
||||||
"gui_copied_url": "The OnionShare address has been copied to clipboard",
|
"gui_copied_url": "The OnionShare address has been copied to clipboard",
|
||||||
"gui_copied_hidservauth_title": "Copied HidServAuth",
|
"gui_copied_hidservauth_title": "Copied HidServAuth",
|
||||||
"gui_copied_hidservauth": "The HidServAuth line has been copied to clipboard",
|
"gui_copied_hidservauth": "The HidServAuth line has been copied to clipboard",
|
||||||
"gui_starting_server1": "Starting Tor onion service...",
|
"gui_starting_server1": "Starting Tor onion service…",
|
||||||
"gui_starting_server2": "Compressing files...",
|
"gui_starting_server2": "Compressing files…",
|
||||||
"gui_please_wait": "Starting... Click to cancel",
|
"gui_please_wait": "Starting… Click to cancel",
|
||||||
"error_hs_dir_cannot_create": "Cannot create onion service dir {0:s}",
|
"error_hs_dir_cannot_create": "Cannot create onion service dir {0:s}",
|
||||||
"error_hs_dir_not_writable": "onion service dir {0:s} is not writable",
|
"error_hs_dir_not_writable": "onion service dir {0:s} is not writable",
|
||||||
"using_ephemeral": "Starting ephemeral Tor onion service and awaiting publication",
|
"using_ephemeral": "Starting ephemeral Tor onion service and awaiting publication",
|
||||||
"gui_download_progress_complete": "%p%, Time Elapsed: {0:s}",
|
"gui_download_progress_complete": "%p%, Time Elapsed: {0:s}",
|
||||||
"gui_download_progress_starting": "{0:s}, %p% (Computing ETA)",
|
"gui_download_progress_starting": "{0:s}, %p% (Computing ETA)",
|
||||||
"gui_download_progress_eta": "{0:s}, ETA: {1:s}, %p%",
|
"gui_download_progress_eta": "{0:s}, ETA: {1:s}, %p%",
|
||||||
"version_string": "Onionshare {0:s} | https://onionshare.org/",
|
"version_string": "OnionShare {0:s} | https://onionshare.org/",
|
||||||
"gui_quit_title": "Transfer in Progress",
|
"gui_quit_title": "Transfer in Progress",
|
||||||
"gui_quit_warning": "You're in the process of sending files. Are you sure you want to quit OnionShare?",
|
"gui_quit_warning": "You're in the process of sending files. Are you sure you want to quit OnionShare?",
|
||||||
"gui_quit_warning_quit": "Quit",
|
"gui_quit_warning_quit": "Quit",
|
||||||
|
@ -71,18 +76,18 @@
|
||||||
"gui_settings_window_title": "Settings",
|
"gui_settings_window_title": "Settings",
|
||||||
"gui_settings_stealth_label": "Stealth (advanced)",
|
"gui_settings_stealth_label": "Stealth (advanced)",
|
||||||
"gui_settings_stealth_option": "Create stealth onion services",
|
"gui_settings_stealth_option": "Create stealth onion services",
|
||||||
"gui_settings_stealth_option_details": "This makes OnionShare more secure, but also more difficult for the recipient to connect to it.<br><a href=\"https://github.com/micahflee/onionshare/wiki/Stealth-Onion-Services\">More information</a>.",
|
"gui_settings_stealth_option_details": "This makes OnionShare more secure, but also more difficult for the recipient to connect to.<br><a href=\"https://github.com/micahflee/onionshare/wiki/Stealth-Onion-Services\">More information</a>.",
|
||||||
"gui_settings_stealth_hidservauth_string": "You have saved the private key for reuse, so your HidServAuth string is also reused.\nClick below to copy the HidServAuth.",
|
"gui_settings_stealth_hidservauth_string": "You have saved the private key for reuse, so your HidServAuth string is also reused.\nClick below to copy the HidServAuth.",
|
||||||
"gui_settings_autoupdate_label": "Check for updates",
|
"gui_settings_autoupdate_label": "Check for upgrades",
|
||||||
"gui_settings_autoupdate_option": "Notify me when updates are available",
|
"gui_settings_autoupdate_option": "Notify me when upgrades are available",
|
||||||
"gui_settings_autoupdate_timestamp": "Last checked: {}",
|
"gui_settings_autoupdate_timestamp": "Last checked: {}",
|
||||||
"gui_settings_autoupdate_timestamp_never": "Never",
|
"gui_settings_autoupdate_timestamp_never": "Never",
|
||||||
"gui_settings_autoupdate_check_button": "Check For Updates",
|
"gui_settings_autoupdate_check_button": "Check For Upgrades",
|
||||||
"gui_settings_sharing_label": "Sharing options",
|
"gui_settings_sharing_label": "Sharing options",
|
||||||
"gui_settings_close_after_first_download_option": "Stop sharing after first download",
|
"gui_settings_close_after_first_download_option": "Stop sharing after first download",
|
||||||
"gui_settings_systray_notifications": "Show desktop notifications",
|
"gui_settings_systray_notifications": "Show desktop notifications",
|
||||||
"gui_settings_connection_type_label": "How should OnionShare connect to Tor?",
|
"gui_settings_connection_type_label": "How should OnionShare connect to Tor?",
|
||||||
"gui_settings_connection_type_bundled_option": "Use Tor that is bundled with OnionShare",
|
"gui_settings_connection_type_bundled_option": "Use the Tor version that is bundled with OnionShare",
|
||||||
"gui_settings_connection_type_automatic_option": "Attempt automatic configuration with Tor Browser",
|
"gui_settings_connection_type_automatic_option": "Attempt automatic configuration with Tor Browser",
|
||||||
"gui_settings_connection_type_control_port_option": "Connect using control port",
|
"gui_settings_connection_type_control_port_option": "Connect using control port",
|
||||||
"gui_settings_connection_type_socket_file_option": "Connect using socket file",
|
"gui_settings_connection_type_socket_file_option": "Connect using socket file",
|
||||||
|
@ -104,9 +109,10 @@
|
||||||
"gui_settings_tor_bridges_meek_lite_amazon_radio_option_no_obfs4proxy": "Use built-in meek_lite (Amazon) pluggable transports (requires obfs4proxy)",
|
"gui_settings_tor_bridges_meek_lite_amazon_radio_option_no_obfs4proxy": "Use built-in meek_lite (Amazon) pluggable transports (requires obfs4proxy)",
|
||||||
"gui_settings_tor_bridges_meek_lite_azure_radio_option": "Use built-in meek_lite (Azure) pluggable transports",
|
"gui_settings_tor_bridges_meek_lite_azure_radio_option": "Use built-in meek_lite (Azure) pluggable transports",
|
||||||
"gui_settings_tor_bridges_meek_lite_azure_radio_option_no_obfs4proxy": "Use built-in meek_lite (Azure) pluggable transports (requires obfs4proxy)",
|
"gui_settings_tor_bridges_meek_lite_azure_radio_option_no_obfs4proxy": "Use built-in meek_lite (Azure) pluggable transports (requires obfs4proxy)",
|
||||||
|
"gui_settings_meek_lite_expensive_warning": "Warning: the meek_lite bridges are very costly for the Tor Project to run!<br><br>You should only use meek_lite bridges if you are having trouble connecting to Tor directly, via obfs4 transports or other normal bridges.",
|
||||||
"gui_settings_tor_bridges_custom_radio_option": "Use custom bridges",
|
"gui_settings_tor_bridges_custom_radio_option": "Use custom bridges",
|
||||||
"gui_settings_tor_bridges_custom_label": "You can get bridges from <a href=\"https://bridges.torproject.org/options\">https://bridges.torproject.org</a>",
|
"gui_settings_tor_bridges_custom_label": "You can get bridges from <a href=\"https://bridges.torproject.org/options\">https://bridges.torproject.org</a>",
|
||||||
"gui_settings_tor_bridges_invalid": "None of the bridges you supplied seem to be valid.\nPlease try again with valid bridges.",
|
"gui_settings_tor_bridges_invalid": "None of the bridges you supplied seem to work.\nPlease try again by double-checking them or adding other ones.",
|
||||||
"gui_settings_button_save": "Save",
|
"gui_settings_button_save": "Save",
|
||||||
"gui_settings_button_cancel": "Cancel",
|
"gui_settings_button_cancel": "Cancel",
|
||||||
"gui_settings_button_help": "Help",
|
"gui_settings_button_help": "Help",
|
||||||
|
@ -119,15 +125,15 @@
|
||||||
"settings_error_socket_file": "Can't connect to Tor controller using socket file {}.",
|
"settings_error_socket_file": "Can't connect to Tor controller using socket file {}.",
|
||||||
"settings_error_auth": "Connected to {}:{}, but can't authenticate. Maybe this isn't a Tor controller?",
|
"settings_error_auth": "Connected to {}:{}, but can't authenticate. Maybe this isn't a Tor controller?",
|
||||||
"settings_error_missing_password": "Connected to Tor controller, but it requires a password to authenticate.",
|
"settings_error_missing_password": "Connected to Tor controller, but it requires a password to authenticate.",
|
||||||
"settings_error_unreadable_cookie_file": "Connected to Tor controller, but can't authenticate because your password may be wrong, and your user doesn't have permission to read the cookie file.",
|
"settings_error_unreadable_cookie_file": "Connected to Tor controller, but can't authenticate because your password may be wrong, and your user lacks permission to read the cookie file.",
|
||||||
"settings_error_bundled_tor_not_supported": "Bundled Tor is not supported when not using developer mode in Windows or macOS.",
|
"settings_error_bundled_tor_not_supported": "Use of the Tor version bundled with OnionShare is not supported when using developer mode on Windows or macOS.",
|
||||||
"settings_error_bundled_tor_timeout": "Connecting to Tor is taking too long. Maybe your computer is offline, or your clock isn't accurate.",
|
"settings_error_bundled_tor_timeout": "Connecting to Tor is taking too long. Maybe your computer is offline, or your system clock isn't accurate.",
|
||||||
"settings_error_bundled_tor_canceled": "The Tor process closed before it could finish connecting.",
|
"settings_error_bundled_tor_canceled": "The Tor process closed before it could finish connecting.",
|
||||||
"settings_error_bundled_tor_broken": "Something is wrong with OnionShare connecting to Tor in the background:\n{}",
|
"settings_error_bundled_tor_broken": "OnionShare could not connect to Tor in the background:\n{}",
|
||||||
"settings_test_success": "Congratulations, OnionShare can connect to the Tor controller.\n\nTor version: {}\nSupports ephemeral onion services: {}\nSupports stealth onion services: {}",
|
"settings_test_success": "Congratulations, OnionShare can connect to the Tor controller.\n\nTor version: {}\nSupports ephemeral onion services: {}\nSupports stealth onion services: {}",
|
||||||
"error_tor_protocol_error": "Error talking to the Tor controller.\nIf you're using Whonix, check out https://www.whonix.org/wiki/onionshare to make OnionShare work.",
|
"error_tor_protocol_error": "Could not communicate with the Tor controller.\nIf you're using Whonix, check out https://www.whonix.org/wiki/onionshare to make OnionShare work.",
|
||||||
"connecting_to_tor": "Connecting to the Tor network",
|
"connecting_to_tor": "Connecting to the Tor network",
|
||||||
"update_available": "There is an OnionShare update available. <a href='{}'>Click here</a> to download it.<br><br>Installed version: {}<br>Latest version: {}",
|
"update_available": "A new version of OnionShare is available. <a href='{}'>Click here</a> to download it.<br><br>Installed version: {}<br>Latest version: {}",
|
||||||
"update_error_check_error": "Error checking for updates: Maybe you're not connected to Tor, or maybe the OnionShare website is down.",
|
"update_error_check_error": "Error checking for updates: Maybe you're not connected to Tor, or maybe the OnionShare website is down.",
|
||||||
"update_error_invalid_latest_version": "Error checking for updates: The OnionShare website responded saying the latest version is '{}', but that doesn't appear to be a valid version string.",
|
"update_error_invalid_latest_version": "Error checking for updates: The OnionShare website responded saying the latest version is '{}', but that doesn't appear to be a valid version string.",
|
||||||
"update_not_available": "You are running the latest version of OnionShare.",
|
"update_not_available": "You are running the latest version of OnionShare.",
|
||||||
|
@ -135,22 +141,27 @@
|
||||||
"gui_tor_connection_ask_open_settings": "Open Settings",
|
"gui_tor_connection_ask_open_settings": "Open Settings",
|
||||||
"gui_tor_connection_ask_quit": "Quit",
|
"gui_tor_connection_ask_quit": "Quit",
|
||||||
"gui_tor_connection_error_settings": "Try adjusting how OnionShare connects to the Tor network in Settings.",
|
"gui_tor_connection_error_settings": "Try adjusting how OnionShare connects to the Tor network in Settings.",
|
||||||
"gui_tor_connection_canceled": "OnionShare cannot connect to Tor.\n\nMake sure you're connected to the internet, then re-open OnionShare to configure the Tor connection.",
|
"gui_tor_connection_canceled": "OnionShare could not connect to Tor.\n\nMake sure you're connected to the Internet, then re-open OnionShare to set up the Tor connection.",
|
||||||
"gui_tor_connection_lost": "Disconnected from Tor.",
|
"gui_tor_connection_lost": "Disconnected from Tor.",
|
||||||
"gui_server_started_after_timeout": "The server started after your chosen auto-timeout.\nPlease start a new share.",
|
"gui_server_started_after_timeout": "The server started after your chosen auto-timeout.\nPlease start a new share.",
|
||||||
"gui_server_timeout_expired": "The chosen timeout has already expired.\nPlease update the timeout and then you may start sharing.",
|
"gui_server_timeout_expired": "The chosen timeout has already expired.\nPlease update the timeout and then you may start sharing.",
|
||||||
"share_via_onionshare": "Share via OnionShare",
|
"share_via_onionshare": "Share via OnionShare",
|
||||||
"gui_save_private_key_checkbox": "Use a persistent address\n(unchecking will delete any saved address)",
|
"gui_save_private_key_checkbox": "Use a persistent address\n(unchecking will delete any saved addresses)",
|
||||||
"gui_url_description": "<b>Anyone</b> with this link can <b>download</b> your files using <b>Tor Browser</b>: <img src='{}' />",
|
"gui_url_description": "<b>Anyone</b> with this link can <b>download</b> your files using the <b>Tor Browser</b>: <img src='{}' />",
|
||||||
"gui_url_label_persistent": "This share will not stop automatically unless a timer is set.<br><br>Every share will have the same address (to use one-time addresses, disable persistence in the Settings)",
|
"gui_url_label_persistent": "This share will not expire automatically unless a timer is set.<br><br>Every share will have the same address (to use one-time addresses, disable persistence in Settings)",
|
||||||
"gui_url_label_stay_open": "This share will not stop automatically unless a timer is set.",
|
"gui_url_label_stay_open": "This share will not expire automatically unless a timer is set.",
|
||||||
"gui_url_label_onetime": "This share will stop after the first download",
|
"gui_url_label_onetime": "This share will expire after the first download",
|
||||||
"gui_url_label_onetime_and_persistent": "This share will stop after the first download<br><br>Every share will have the same address (to use one-time addresses, disable persistence in the Settings)",
|
"gui_url_label_onetime_and_persistent": "This share will expire after the first download<br><br>Every share will have the same address (to use one-time addresses, disable persistence in the Settings)",
|
||||||
"gui_status_indicator_stopped": "Ready to Share",
|
"gui_status_indicator_stopped": "Ready to Share",
|
||||||
"gui_status_indicator_working": "Starting...",
|
"gui_status_indicator_working": "Starting…",
|
||||||
"gui_status_indicator_started": "Sharing",
|
"gui_status_indicator_started": "Sharing",
|
||||||
"gui_file_info": "{} Files, {}",
|
"gui_file_info": "{} Files, {}",
|
||||||
"gui_file_info_single": "{} File, {}",
|
"gui_file_info_single": "{} File, {}",
|
||||||
"info_in_progress_downloads_tooltip": "{} download(s) in progress",
|
"info_in_progress_downloads_tooltip": "{} download(s) in progress",
|
||||||
"info_completed_downloads_tooltip": "{} download(s) completed"
|
"info_completed_downloads_tooltip": "{} download(s) completed",
|
||||||
|
"error_cannot_create_downloads_dir": "Error creating downloads folder: {}",
|
||||||
|
"error_downloads_dir_not_writable": "The downloads folder isn't writable: {}",
|
||||||
|
"receive_mode_downloads_dir": "Files people send you will appear in this folder: {}",
|
||||||
|
"receive_mode_warning": "Warning: Some files can hack your computer if you open them! Only open files from people you trust, or if you know what you're doing.",
|
||||||
|
"receive_mode_received_file": "Received file: {}"
|
||||||
}
|
}
|
||||||
|
|
144
share/static/css/style.css
Normal file
|
@ -0,0 +1,144 @@
|
||||||
|
.clearfix:after {
|
||||||
|
content: ".";
|
||||||
|
display: block;
|
||||||
|
clear: both;
|
||||||
|
visibility: hidden;
|
||||||
|
line-height: 0;
|
||||||
|
height: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
font-family: Helvetica;
|
||||||
|
}
|
||||||
|
|
||||||
|
header {
|
||||||
|
border-bottom: 1px solid rgba(0, 0, 0, 0.1);
|
||||||
|
background: #fcfcfc;
|
||||||
|
background: -webkit-linear-gradient(top, #fcfcfc 0%, #f2f2f2 100%);
|
||||||
|
padding: 0.8rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
header .logo {
|
||||||
|
vertical-align: middle;
|
||||||
|
width: 45px;
|
||||||
|
height: 45px;
|
||||||
|
}
|
||||||
|
|
||||||
|
header h1 {
|
||||||
|
display: inline-block;
|
||||||
|
margin: 0 0 0 0.5rem;
|
||||||
|
vertical-align: middle;
|
||||||
|
font-weight: normal;
|
||||||
|
font-size: 1.5rem;
|
||||||
|
color: #666666;
|
||||||
|
}
|
||||||
|
|
||||||
|
header .right {
|
||||||
|
float: right;
|
||||||
|
font-size: .75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
header .right ul li {
|
||||||
|
display: inline;
|
||||||
|
margin: 0 0 0 .5rem;
|
||||||
|
font-size: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button {
|
||||||
|
color: #ffffff;
|
||||||
|
background-color: #4e064f;
|
||||||
|
padding: 10px;
|
||||||
|
border: 0;
|
||||||
|
border-radius: 5px;
|
||||||
|
text-decoration: none;
|
||||||
|
margin-left: 1rem;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.close-button {
|
||||||
|
color: #ffffff;
|
||||||
|
background-color: #c90c0c;
|
||||||
|
padding: 10px;
|
||||||
|
border: 0;
|
||||||
|
border-radius: 5px;
|
||||||
|
text-decoration: none;
|
||||||
|
margin-left: 1rem;
|
||||||
|
cursor: pointer;
|
||||||
|
position: absolute;
|
||||||
|
right: 10px;
|
||||||
|
bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
table.file-list {
|
||||||
|
width: 100%;
|
||||||
|
margin: 0 auto;
|
||||||
|
border-collapse: collapse;
|
||||||
|
}
|
||||||
|
|
||||||
|
table.file-list th {
|
||||||
|
text-align: left;
|
||||||
|
text-transform: uppercase;
|
||||||
|
font-weight: normal;
|
||||||
|
color: #666666;
|
||||||
|
padding: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
table.file-list tr {
|
||||||
|
border-bottom: 1px solid #e0e0e0;
|
||||||
|
}
|
||||||
|
|
||||||
|
table.file-list td {
|
||||||
|
white-space: nowrap;
|
||||||
|
padding: 0.5rem 10rem 0.5rem 0.8rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
table.file-list td img {
|
||||||
|
vertical-align: middle;
|
||||||
|
margin-right: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
table.file-list td:last-child {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.upload-wrapper {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
min-height: 400px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.upload {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.upload img {
|
||||||
|
width: 120px;
|
||||||
|
height: 120px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.upload .upload-header {
|
||||||
|
font-size: 30px;
|
||||||
|
font-weight: normal;
|
||||||
|
color: #666666;
|
||||||
|
margin: 0 0 10px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.upload .upload-description {
|
||||||
|
color: #666666;
|
||||||
|
margin: 0 0 20px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
ul.flashes {
|
||||||
|
list-style: none;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
color: #cc0000;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
ul.flashes li {
|
||||||
|
margin: 0;
|
||||||
|
padding: 10px;
|
||||||
|
}
|
Before Width: | Height: | Size: 4.2 KiB After Width: | Height: | Size: 4.2 KiB |
BIN
share/static/img/logo.png
Normal file
After Width: | Height: | Size: 3.7 KiB |
BIN
share/static/img/logo_large.png
Normal file
After Width: | Height: | Size: 9.4 KiB |
Before Width: | Height: | Size: 251 B After Width: | Height: | Size: 251 B |
Before Width: | Height: | Size: 338 B After Width: | Height: | Size: 338 B |
75
share/static/js/send.js
Normal file
|
@ -0,0 +1,75 @@
|
||||||
|
// Function to convert human-readable sizes back to bytes, for sorting
|
||||||
|
function unhumanize(text) {
|
||||||
|
var powers = {'b': 0, 'k': 1, 'm': 2, 'g': 3, 't': 4};
|
||||||
|
var regex = /(\d+(?:\.\d+)?)\s?(B|K|M|G|T)?/i;
|
||||||
|
var res = regex.exec(text);
|
||||||
|
if(res[2] === undefined) {
|
||||||
|
// Account for alphabetical words (file/dir names)
|
||||||
|
return text;
|
||||||
|
} else {
|
||||||
|
return res[1] * Math.pow(1024, powers[res[2].toLowerCase()]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function sortTable(n) {
|
||||||
|
var table, rows, switching, i, x, y, shouldSwitch, dir, switchcount = 0;
|
||||||
|
table = document.getElementById("file-list");
|
||||||
|
switching = true;
|
||||||
|
// Set the sorting direction to ascending:
|
||||||
|
dir = "asc";
|
||||||
|
/* Make a loop that will continue until
|
||||||
|
no switching has been done: */
|
||||||
|
while (switching) {
|
||||||
|
// Start by saying: no switching is done:
|
||||||
|
switching = false;
|
||||||
|
rows = table.getElementsByTagName("TR");
|
||||||
|
/* Loop through all table rows (except the
|
||||||
|
first, which contains table headers): */
|
||||||
|
for (i = 1; i < (rows.length - 1); i++) {
|
||||||
|
// Start by saying there should be no switching:
|
||||||
|
shouldSwitch = false;
|
||||||
|
/* Get the two elements you want to compare,
|
||||||
|
one from current row and one from the next: */
|
||||||
|
x = rows[i].getElementsByTagName("TD")[n];
|
||||||
|
y = rows[i + 1].getElementsByTagName("TD")[n];
|
||||||
|
/* Check if the two rows should switch place,
|
||||||
|
based on the direction, asc or desc: */
|
||||||
|
if (dir == "asc") {
|
||||||
|
if (unhumanize(x.innerHTML.toLowerCase()) > unhumanize(y.innerHTML.toLowerCase())) {
|
||||||
|
// If so, mark as a switch and break the loop:
|
||||||
|
shouldSwitch= true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else if (dir == "desc") {
|
||||||
|
if (unhumanize(x.innerHTML.toLowerCase()) < unhumanize(y.innerHTML.toLowerCase())) {
|
||||||
|
// If so, mark as a switch and break the loop:
|
||||||
|
shouldSwitch= true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (shouldSwitch) {
|
||||||
|
/* If a switch has been marked, make the switch
|
||||||
|
and mark that a switch has been done: */
|
||||||
|
rows[i].parentNode.insertBefore(rows[i + 1], rows[i]);
|
||||||
|
switching = true;
|
||||||
|
// Each time a switch is done, increase this count by 1:
|
||||||
|
switchcount ++;
|
||||||
|
} else {
|
||||||
|
/* If no switching has been done AND the direction is "asc",
|
||||||
|
set the direction to "desc" and run the while loop again. */
|
||||||
|
if (switchcount == 0 && dir == "asc") {
|
||||||
|
dir = "desc";
|
||||||
|
switching = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Set click handlers
|
||||||
|
document.getElementById("filename-header").addEventListener("click", function(){
|
||||||
|
sortTable(0);
|
||||||
|
});
|
||||||
|
document.getElementById("size-header").addEventListener("click", function(){
|
||||||
|
sortTable(1);
|
||||||
|
});
|
10
share/templates/404.html
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>OnionShare: Error 404</title>
|
||||||
|
<link href="/static/img/favicon.ico" rel="icon" type="image/x-icon" />
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<p>Error 404: You probably typed the OnionShare address wrong</p>
|
||||||
|
</body>
|
||||||
|
</html>
|
10
share/templates/closed.html
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>OnionShare is closed</title>
|
||||||
|
<link href="/static/img/favicon.ico" rel="icon" type="image/x-icon" />
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<p>Thank you for using OnionShare</p>
|
||||||
|
</body>
|
||||||
|
</html>
|
10
share/templates/denied.html
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>OnionShare</title>
|
||||||
|
<link href="/static/img/favicon.ico" rel="icon" type="image/x-icon" />
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<p>OnionShare download in progress</p>
|
||||||
|
</body>
|
||||||
|
</html>
|
43
share/templates/receive.html
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>OnionShare</title>
|
||||||
|
<link href="/static/img/favicon.ico" rel="icon" type="image/x-icon" />
|
||||||
|
<link href="/static/css/style.css" rel="stylesheet" type="text/css" />
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<header class="clearfix">
|
||||||
|
<img class="logo" src="/static/img/logo.png" title="OnionShare">
|
||||||
|
<h1>OnionShare</h1>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<div class="upload-wrapper">
|
||||||
|
<div class="upload">
|
||||||
|
<p><img class="logo" src="/static/img/logo_large.png" title="OnionShare"></p>
|
||||||
|
<p class="upload-header">Send Files</p>
|
||||||
|
<p class="upload-description">Select the files you want to send, then click "Send Files"...</p>
|
||||||
|
<form method="post" enctype="multipart/form-data" action="/{{ slug }}/upload">
|
||||||
|
<p><input type="file" name="file[]" multiple /></p>
|
||||||
|
<p><input type="submit" class="button" value="Send Files" /></p>
|
||||||
|
<div>
|
||||||
|
{% with messages = get_flashed_messages() %}
|
||||||
|
{% if messages %}
|
||||||
|
<ul class=flashes>
|
||||||
|
{% for message in messages %}
|
||||||
|
<li>{{ message }}</li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
{% endif %}
|
||||||
|
{% endwith %}
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<form method="post" action="/{{ slug }}/close">
|
||||||
|
<input type="submit" class="close-button" value="I'm Finished Uploading" />
|
||||||
|
</form>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
52
share/templates/send.html
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>OnionShare</title>
|
||||||
|
<link href="/static/img/favicon.ico" rel="icon" type="image/x-icon" />
|
||||||
|
<link href="/static/css/style.css" rel="stylesheet" type="text/css" />
|
||||||
|
<meta name="onionshare-filename" content="{{ filename }}">
|
||||||
|
<meta name="onionshare-filesize" content="{{ filesize }}">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<header class="clearfix">
|
||||||
|
<div class="right">
|
||||||
|
<ul>
|
||||||
|
<li>Total size: <strong>{{ filesize_human }}</strong> (compressed)</li>
|
||||||
|
<li><a class="button" href='/{{ slug }}/download'>Download Files</a></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<img class="logo" src="/static/img/logo.png" title="OnionShare">
|
||||||
|
<h1>OnionShare</h1>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<table class="file-list" id="file-list">
|
||||||
|
<tr>
|
||||||
|
<th id="filename-header">Filename</th>
|
||||||
|
<th id="size-header">Size</th>
|
||||||
|
<th></th>
|
||||||
|
</tr>
|
||||||
|
{% for info in file_info.dirs %}
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<img width="30" height="30" title="" alt="" src="/static/img/web_folder.png" />
|
||||||
|
{{ info.basename }}
|
||||||
|
</td>
|
||||||
|
<td>{{ info.size_human }}</td>
|
||||||
|
<td></td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
{% for info in file_info.files %}
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<img width="30" height="30" title="" alt="" src="/static/img/web_file.png" />
|
||||||
|
{{ info.basename }}
|
||||||
|
</td>
|
||||||
|
<td>{{ info.size_human }}</td>
|
||||||
|
<td></td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</table>
|
||||||
|
<script src="/static/js/send.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -1 +1 @@
|
||||||
1.3
|
1.3.1
|
||||||
|
|
|
@ -8,7 +8,7 @@ import tempfile
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from onionshare import common
|
from onionshare import common, web
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def temp_dir_1024():
|
def temp_dir_1024():
|
||||||
|
@ -64,8 +64,9 @@ def temp_file_1024_delete():
|
||||||
# pytest > 2.9 only needs @pytest.fixture
|
# pytest > 2.9 only needs @pytest.fixture
|
||||||
@pytest.yield_fixture(scope='session')
|
@pytest.yield_fixture(scope='session')
|
||||||
def custom_zw():
|
def custom_zw():
|
||||||
zw = common.ZipWriter(
|
zw = web.ZipWriter(
|
||||||
zip_filename=common.random_string(4, 6),
|
common.Common(),
|
||||||
|
zip_filename=common.Common.random_string(4, 6),
|
||||||
processed_size_callback=lambda _: 'custom_callback'
|
processed_size_callback=lambda _: 'custom_callback'
|
||||||
)
|
)
|
||||||
yield zw
|
yield zw
|
||||||
|
@ -76,7 +77,7 @@ def custom_zw():
|
||||||
# pytest > 2.9 only needs @pytest.fixture
|
# pytest > 2.9 only needs @pytest.fixture
|
||||||
@pytest.yield_fixture(scope='session')
|
@pytest.yield_fixture(scope='session')
|
||||||
def default_zw():
|
def default_zw():
|
||||||
zw = common.ZipWriter()
|
zw = web.ZipWriter(common.Common())
|
||||||
yield zw
|
yield zw
|
||||||
zw.close()
|
zw.close()
|
||||||
tmp_dir = os.path.dirname(zw.zip_filename)
|
tmp_dir = os.path.dirname(zw.zip_filename)
|
||||||
|
@ -118,16 +119,6 @@ def platform_windows(monkeypatch):
|
||||||
monkeypatch.setattr('platform.system', lambda: 'Windows')
|
monkeypatch.setattr('platform.system', lambda: 'Windows')
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
|
||||||
def set_debug_false(monkeypatch):
|
|
||||||
monkeypatch.setattr('onionshare.common.debug', False)
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
|
||||||
def set_debug_true(monkeypatch):
|
|
||||||
monkeypatch.setattr('onionshare.common.debug', True)
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def sys_argv_sys_prefix(monkeypatch):
|
def sys_argv_sys_prefix(monkeypatch):
|
||||||
monkeypatch.setattr('sys.argv', [sys.prefix])
|
monkeypatch.setattr('sys.argv', [sys.prefix])
|
||||||
|
@ -157,3 +148,7 @@ def time_time_100(monkeypatch):
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def time_strftime(monkeypatch):
|
def time_strftime(monkeypatch):
|
||||||
monkeypatch.setattr('time.strftime', lambda _: 'Jun 06 2013 11:05:00')
|
monkeypatch.setattr('time.strftime', lambda _: 'Jun 06 2013 11:05:00')
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def common_obj():
|
||||||
|
return common.Common()
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
"""
|
"""
|
||||||
OnionShare | https://onionshare.org/
|
OnionShare | https://onionshare.org/
|
||||||
|
|
||||||
Copyright (C) 2017 Micah Lee <micah@micahflee.com>
|
Copyright (C) 2018 Micah Lee <micah@micahflee.com>
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
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
|
it under the terms of the GNU General Public License as published by
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
"""
|
"""
|
||||||
OnionShare | https://onionshare.org/
|
OnionShare | https://onionshare.org/
|
||||||
|
|
||||||
Copyright (C) 2017 Micah Lee <micah@micahflee.com>
|
Copyright (C) 2018 Micah Lee <micah@micahflee.com>
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
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
|
it under the terms of the GNU General Public License as published by
|
||||||
|
@ -22,6 +22,7 @@ import os
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from onionshare import OnionShare
|
from onionshare import OnionShare
|
||||||
|
from onionshare.common import Common
|
||||||
|
|
||||||
|
|
||||||
class MyOnion:
|
class MyOnion:
|
||||||
|
@ -37,7 +38,8 @@ class MyOnion:
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def onionshare_obj():
|
def onionshare_obj():
|
||||||
return OnionShare(MyOnion())
|
common = Common()
|
||||||
|
return OnionShare(common, MyOnion())
|
||||||
|
|
||||||
|
|
||||||
class TestOnionShare:
|
class TestOnionShare:
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
"""
|
"""
|
||||||
OnionShare | https://onionshare.org/
|
OnionShare | https://onionshare.org/
|
||||||
|
|
||||||
Copyright (C) 2017 Micah Lee <micah@micahflee.com>
|
Copyright (C) 2018 Micah Lee <micah@micahflee.com>
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
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
|
it under the terms of the GNU General Public License as published by
|
||||||
|
@ -29,17 +29,16 @@ import zipfile
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from onionshare import common
|
|
||||||
|
|
||||||
DEFAULT_ZW_FILENAME_REGEX = re.compile(r'^onionshare_[a-z2-7]{6}.zip$')
|
|
||||||
LOG_MSG_REGEX = re.compile(r"""
|
LOG_MSG_REGEX = re.compile(r"""
|
||||||
^\[Jun\ 06\ 2013\ 11:05:00\]
|
^\[Jun\ 06\ 2013\ 11:05:00\]
|
||||||
\ TestModule\.<function\ TestLog\.test_output\.<locals>\.dummy_func
|
\ TestModule\.<function\ TestLog\.test_output\.<locals>\.dummy_func
|
||||||
\ at\ 0x[a-f0-9]+>(:\ TEST_MSG)?$""", re.VERBOSE)
|
\ at\ 0x[a-f0-9]+>(:\ TEST_MSG)?$""", re.VERBOSE)
|
||||||
RANDOM_STR_REGEX = re.compile(r'^[a-z2-7]+$')
|
|
||||||
SLUG_REGEX = re.compile(r'^([a-z]+)(-[a-z]+)?-([a-z]+)(-[a-z]+)?$')
|
SLUG_REGEX = re.compile(r'^([a-z]+)(-[a-z]+)?-([a-z]+)(-[a-z]+)?$')
|
||||||
|
|
||||||
|
|
||||||
|
# TODO: Improve the Common tests to test it all as a single class
|
||||||
|
|
||||||
|
|
||||||
class TestBuildSlug:
|
class TestBuildSlug:
|
||||||
@pytest.mark.parametrize('test_input,expected', (
|
@pytest.mark.parametrize('test_input,expected', (
|
||||||
# VALID, two lowercase words, separated by a hyphen
|
# VALID, two lowercase words, separated by a hyphen
|
||||||
|
@ -79,17 +78,17 @@ class TestBuildSlug:
|
||||||
|
|
||||||
assert bool(SLUG_REGEX.match(test_input)) == expected
|
assert bool(SLUG_REGEX.match(test_input)) == expected
|
||||||
|
|
||||||
def test_build_slug_unique(self, sys_onionshare_dev_mode):
|
def test_build_slug_unique(self, common_obj, sys_onionshare_dev_mode):
|
||||||
assert common.build_slug() != common.build_slug()
|
assert common_obj.build_slug() != common_obj.build_slug()
|
||||||
|
|
||||||
|
|
||||||
class TestDirSize:
|
class TestDirSize:
|
||||||
def test_temp_dir_size(self, temp_dir_1024_delete):
|
def test_temp_dir_size(self, common_obj, temp_dir_1024_delete):
|
||||||
""" dir_size() should return the total size (in bytes) of all files
|
""" dir_size() should return the total size (in bytes) of all files
|
||||||
in a particular directory.
|
in a particular directory.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
assert common.dir_size(temp_dir_1024_delete) == 1024
|
assert common_obj.dir_size(temp_dir_1024_delete) == 1024
|
||||||
|
|
||||||
|
|
||||||
class TestEstimatedTimeRemaining:
|
class TestEstimatedTimeRemaining:
|
||||||
|
@ -103,16 +102,16 @@ class TestEstimatedTimeRemaining:
|
||||||
((971, 1009, 83), '1s')
|
((971, 1009, 83), '1s')
|
||||||
))
|
))
|
||||||
def test_estimated_time_remaining(
|
def test_estimated_time_remaining(
|
||||||
self, test_input, expected, time_time_100):
|
self, common_obj, test_input, expected, time_time_100):
|
||||||
assert common.estimated_time_remaining(*test_input) == expected
|
assert common_obj.estimated_time_remaining(*test_input) == expected
|
||||||
|
|
||||||
@pytest.mark.parametrize('test_input', (
|
@pytest.mark.parametrize('test_input', (
|
||||||
(10, 20, 100), # if `time_elapsed == 0`
|
(10, 20, 100), # if `time_elapsed == 0`
|
||||||
(0, 37, 99) # if `download_rate == 0`
|
(0, 37, 99) # if `download_rate == 0`
|
||||||
))
|
))
|
||||||
def test_raises_zero_division_error(self, test_input, time_time_100):
|
def test_raises_zero_division_error(self, common_obj, test_input, time_time_100):
|
||||||
with pytest.raises(ZeroDivisionError):
|
with pytest.raises(ZeroDivisionError):
|
||||||
common.estimated_time_remaining(*test_input)
|
common_obj.estimated_time_remaining(*test_input)
|
||||||
|
|
||||||
|
|
||||||
class TestFormatSeconds:
|
class TestFormatSeconds:
|
||||||
|
@ -131,16 +130,16 @@ class TestFormatSeconds:
|
||||||
(129674, '1d12h1m14s'),
|
(129674, '1d12h1m14s'),
|
||||||
(56404.12, '15h40m4s')
|
(56404.12, '15h40m4s')
|
||||||
))
|
))
|
||||||
def test_format_seconds(self, test_input, expected):
|
def test_format_seconds(self, common_obj, test_input, expected):
|
||||||
assert common.format_seconds(test_input) == expected
|
assert common_obj.format_seconds(test_input) == expected
|
||||||
|
|
||||||
# TODO: test negative numbers?
|
# TODO: test negative numbers?
|
||||||
@pytest.mark.parametrize('test_input', (
|
@pytest.mark.parametrize('test_input', (
|
||||||
'string', lambda: None, [], {}, set()
|
'string', lambda: None, [], {}, set()
|
||||||
))
|
))
|
||||||
def test_invalid_input_types(self, test_input):
|
def test_invalid_input_types(self, common_obj, test_input):
|
||||||
with pytest.raises(TypeError):
|
with pytest.raises(TypeError):
|
||||||
common.format_seconds(test_input)
|
common_obj.format_seconds(test_input)
|
||||||
|
|
||||||
|
|
||||||
class TestGetAvailablePort:
|
class TestGetAvailablePort:
|
||||||
|
@ -148,29 +147,29 @@ class TestGetAvailablePort:
|
||||||
(random.randint(1024, 1500),
|
(random.randint(1024, 1500),
|
||||||
random.randint(1800, 2048)) for _ in range(50)
|
random.randint(1800, 2048)) for _ in range(50)
|
||||||
))
|
))
|
||||||
def test_returns_an_open_port(self, port_min, port_max):
|
def test_returns_an_open_port(self, common_obj, port_min, port_max):
|
||||||
""" get_available_port() should return an open port within the range """
|
""" get_available_port() should return an open port within the range """
|
||||||
|
|
||||||
port = common.get_available_port(port_min, port_max)
|
port = common_obj.get_available_port(port_min, port_max)
|
||||||
assert port_min <= port <= port_max
|
assert port_min <= port <= port_max
|
||||||
with socket.socket() as tmpsock:
|
with socket.socket() as tmpsock:
|
||||||
tmpsock.bind(('127.0.0.1', port))
|
tmpsock.bind(('127.0.0.1', port))
|
||||||
|
|
||||||
|
|
||||||
class TestGetPlatform:
|
class TestGetPlatform:
|
||||||
def test_darwin(self, platform_darwin):
|
def test_darwin(self, platform_darwin, common_obj):
|
||||||
assert common.get_platform() == 'Darwin'
|
assert common_obj.platform == 'Darwin'
|
||||||
|
|
||||||
def test_linux(self, platform_linux):
|
def test_linux(self, platform_linux, common_obj):
|
||||||
assert common.get_platform() == 'Linux'
|
assert common_obj.platform == 'Linux'
|
||||||
|
|
||||||
def test_windows(self, platform_windows):
|
def test_windows(self, platform_windows, common_obj):
|
||||||
assert common.get_platform() == 'Windows'
|
assert common_obj.platform == 'Windows'
|
||||||
|
|
||||||
|
|
||||||
# TODO: double-check these tests
|
# TODO: double-check these tests
|
||||||
class TestGetResourcePath:
|
class TestGetResourcePath:
|
||||||
def test_onionshare_dev_mode(self, sys_onionshare_dev_mode):
|
def test_onionshare_dev_mode(self, common_obj, sys_onionshare_dev_mode):
|
||||||
prefix = os.path.join(
|
prefix = os.path.join(
|
||||||
os.path.dirname(
|
os.path.dirname(
|
||||||
os.path.dirname(
|
os.path.dirname(
|
||||||
|
@ -178,29 +177,29 @@ class TestGetResourcePath:
|
||||||
inspect.getfile(
|
inspect.getfile(
|
||||||
inspect.currentframe())))), 'share')
|
inspect.currentframe())))), 'share')
|
||||||
assert (
|
assert (
|
||||||
common.get_resource_path(os.path.join(prefix, 'test_filename')) ==
|
common_obj.get_resource_path(os.path.join(prefix, 'test_filename')) ==
|
||||||
os.path.join(prefix, 'test_filename'))
|
os.path.join(prefix, 'test_filename'))
|
||||||
|
|
||||||
def test_linux(self, platform_linux, sys_argv_sys_prefix):
|
def test_linux(self, common_obj, platform_linux, sys_argv_sys_prefix):
|
||||||
prefix = os.path.join(sys.prefix, 'share/onionshare')
|
prefix = os.path.join(sys.prefix, 'share/onionshare')
|
||||||
assert (
|
assert (
|
||||||
common.get_resource_path(os.path.join(prefix, 'test_filename')) ==
|
common_obj.get_resource_path(os.path.join(prefix, 'test_filename')) ==
|
||||||
os.path.join(prefix, 'test_filename'))
|
os.path.join(prefix, 'test_filename'))
|
||||||
|
|
||||||
def test_frozen_darwin(self, platform_darwin, sys_frozen, sys_meipass):
|
def test_frozen_darwin(self, common_obj, platform_darwin, sys_frozen, sys_meipass):
|
||||||
prefix = os.path.join(sys._MEIPASS, 'share')
|
prefix = os.path.join(sys._MEIPASS, 'share')
|
||||||
assert (
|
assert (
|
||||||
common.get_resource_path(os.path.join(prefix, 'test_filename')) ==
|
common_obj.get_resource_path(os.path.join(prefix, 'test_filename')) ==
|
||||||
os.path.join(prefix, 'test_filename'))
|
os.path.join(prefix, 'test_filename'))
|
||||||
|
|
||||||
|
|
||||||
class TestGetTorPaths:
|
class TestGetTorPaths:
|
||||||
# @pytest.mark.skipif(sys.platform != 'Darwin', reason='requires MacOS') ?
|
# @pytest.mark.skipif(sys.platform != 'Darwin', reason='requires MacOS') ?
|
||||||
def test_get_tor_paths_darwin(self, platform_darwin, sys_frozen, sys_meipass):
|
def test_get_tor_paths_darwin(self, platform_darwin, common_obj, sys_frozen, sys_meipass):
|
||||||
base_path = os.path.dirname(
|
base_path = os.path.dirname(
|
||||||
os.path.dirname(
|
os.path.dirname(
|
||||||
os.path.dirname(
|
os.path.dirname(
|
||||||
common.get_resource_path(''))))
|
common_obj.get_resource_path(''))))
|
||||||
tor_path = os.path.join(
|
tor_path = os.path.join(
|
||||||
base_path, 'Resources', 'Tor', 'tor')
|
base_path, 'Resources', 'Tor', 'tor')
|
||||||
tor_geo_ip_file_path = os.path.join(
|
tor_geo_ip_file_path = os.path.join(
|
||||||
|
@ -209,20 +208,20 @@ class TestGetTorPaths:
|
||||||
base_path, 'Resources', 'Tor', 'geoip6')
|
base_path, 'Resources', 'Tor', 'geoip6')
|
||||||
obfs4proxy_file_path = os.path.join(
|
obfs4proxy_file_path = os.path.join(
|
||||||
base_path, 'Resources', 'Tor', 'obfs4proxy')
|
base_path, 'Resources', 'Tor', 'obfs4proxy')
|
||||||
assert (common.get_tor_paths() ==
|
assert (common_obj.get_tor_paths() ==
|
||||||
(tor_path, tor_geo_ip_file_path, tor_geo_ipv6_file_path, obfs4proxy_file_path))
|
(tor_path, tor_geo_ip_file_path, tor_geo_ipv6_file_path, obfs4proxy_file_path))
|
||||||
|
|
||||||
# @pytest.mark.skipif(sys.platform != 'Linux', reason='requires Linux') ?
|
# @pytest.mark.skipif(sys.platform != 'Linux', reason='requires Linux') ?
|
||||||
def test_get_tor_paths_linux(self, platform_linux):
|
def test_get_tor_paths_linux(self, platform_linux, common_obj):
|
||||||
assert (common.get_tor_paths() ==
|
assert (common_obj.get_tor_paths() ==
|
||||||
('/usr/bin/tor', '/usr/share/tor/geoip', '/usr/share/tor/geoip6', '/usr/bin/obfs4proxy'))
|
('/usr/bin/tor', '/usr/share/tor/geoip', '/usr/share/tor/geoip6', '/usr/bin/obfs4proxy'))
|
||||||
|
|
||||||
# @pytest.mark.skipif(sys.platform != 'Windows', reason='requires Windows') ?
|
# @pytest.mark.skipif(sys.platform != 'Windows', reason='requires Windows') ?
|
||||||
def test_get_tor_paths_windows(self, platform_windows, sys_frozen):
|
def test_get_tor_paths_windows(self, platform_windows, common_obj, sys_frozen):
|
||||||
base_path = os.path.join(
|
base_path = os.path.join(
|
||||||
os.path.dirname(
|
os.path.dirname(
|
||||||
os.path.dirname(
|
os.path.dirname(
|
||||||
common.get_resource_path(''))), 'tor')
|
common_obj.get_resource_path(''))), 'tor')
|
||||||
tor_path = os.path.join(
|
tor_path = os.path.join(
|
||||||
os.path.join(base_path, 'Tor'), 'tor.exe')
|
os.path.join(base_path, 'Tor'), 'tor.exe')
|
||||||
obfs4proxy_file_path = os.path.join(
|
obfs4proxy_file_path = os.path.join(
|
||||||
|
@ -233,18 +232,10 @@ class TestGetTorPaths:
|
||||||
tor_geo_ipv6_file_path = os.path.join(
|
tor_geo_ipv6_file_path = os.path.join(
|
||||||
os.path.join(
|
os.path.join(
|
||||||
os.path.join(base_path, 'Data'), 'Tor'), 'geoip6')
|
os.path.join(base_path, 'Data'), 'Tor'), 'geoip6')
|
||||||
assert (common.get_tor_paths() ==
|
assert (common_obj.get_tor_paths() ==
|
||||||
(tor_path, tor_geo_ip_file_path, tor_geo_ipv6_file_path, obfs4proxy_file_path))
|
(tor_path, tor_geo_ip_file_path, tor_geo_ipv6_file_path, obfs4proxy_file_path))
|
||||||
|
|
||||||
|
|
||||||
class TestGetVersion:
|
|
||||||
def test_get_version(self, sys_onionshare_dev_mode):
|
|
||||||
with open(common.get_resource_path('version.txt')) as f:
|
|
||||||
version = f.read().strip()
|
|
||||||
|
|
||||||
assert version == common.get_version()
|
|
||||||
|
|
||||||
|
|
||||||
class TestHumanReadableFilesize:
|
class TestHumanReadableFilesize:
|
||||||
@pytest.mark.parametrize('test_input,expected', (
|
@pytest.mark.parametrize('test_input,expected', (
|
||||||
(1024 ** 0, '1.0 B'),
|
(1024 ** 0, '1.0 B'),
|
||||||
|
@ -257,8 +248,8 @@ class TestHumanReadableFilesize:
|
||||||
(1024 ** 7, '1.0 ZiB'),
|
(1024 ** 7, '1.0 ZiB'),
|
||||||
(1024 ** 8, '1.0 YiB')
|
(1024 ** 8, '1.0 YiB')
|
||||||
))
|
))
|
||||||
def test_human_readable_filesize(self, test_input, expected):
|
def test_human_readable_filesize(self, common_obj, test_input, expected):
|
||||||
assert common.human_readable_filesize(test_input) == expected
|
assert common_obj.human_readable_filesize(test_input) == expected
|
||||||
|
|
||||||
|
|
||||||
class TestLog:
|
class TestLog:
|
||||||
|
@ -273,82 +264,18 @@ class TestLog:
|
||||||
def test_log_msg_regex(self, test_input):
|
def test_log_msg_regex(self, test_input):
|
||||||
assert bool(LOG_MSG_REGEX.match(test_input))
|
assert bool(LOG_MSG_REGEX.match(test_input))
|
||||||
|
|
||||||
def test_output(self, set_debug_true, time_strftime):
|
def test_output(self, common_obj, time_strftime):
|
||||||
def dummy_func():
|
def dummy_func():
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
common_obj.debug = True
|
||||||
|
|
||||||
# From: https://stackoverflow.com/questions/1218933
|
# From: https://stackoverflow.com/questions/1218933
|
||||||
with io.StringIO() as buf, contextlib.redirect_stdout(buf):
|
with io.StringIO() as buf, contextlib.redirect_stdout(buf):
|
||||||
common.log('TestModule', dummy_func)
|
common_obj.log('TestModule', dummy_func)
|
||||||
common.log('TestModule', dummy_func, 'TEST_MSG')
|
common_obj.log('TestModule', dummy_func, 'TEST_MSG')
|
||||||
output = buf.getvalue()
|
output = buf.getvalue()
|
||||||
|
|
||||||
line_one, line_two, _ = output.split('\n')
|
line_one, line_two, _ = output.split('\n')
|
||||||
assert LOG_MSG_REGEX.match(line_one)
|
assert LOG_MSG_REGEX.match(line_one)
|
||||||
assert LOG_MSG_REGEX.match(line_two)
|
assert LOG_MSG_REGEX.match(line_two)
|
||||||
|
|
||||||
|
|
||||||
class TestSetDebug:
|
|
||||||
def test_debug_true(self, set_debug_false):
|
|
||||||
common.set_debug(True)
|
|
||||||
assert common.debug is True
|
|
||||||
|
|
||||||
def test_debug_false(self, set_debug_true):
|
|
||||||
common.set_debug(False)
|
|
||||||
assert common.debug is False
|
|
||||||
|
|
||||||
|
|
||||||
class TestZipWriterDefault:
|
|
||||||
@pytest.mark.parametrize('test_input', (
|
|
||||||
'onionshare_{}.zip'.format(''.join(
|
|
||||||
random.choice('abcdefghijklmnopqrstuvwxyz234567') for _ in range(6)
|
|
||||||
)) for _ in range(50)
|
|
||||||
))
|
|
||||||
def test_default_zw_filename_regex(self, test_input):
|
|
||||||
assert bool(DEFAULT_ZW_FILENAME_REGEX.match(test_input))
|
|
||||||
|
|
||||||
def test_zw_filename(self, default_zw):
|
|
||||||
zw_filename = os.path.basename(default_zw.zip_filename)
|
|
||||||
assert bool(DEFAULT_ZW_FILENAME_REGEX.match(zw_filename))
|
|
||||||
|
|
||||||
def test_zipfile_filename_matches_zipwriter_filename(self, default_zw):
|
|
||||||
assert default_zw.z.filename == default_zw.zip_filename
|
|
||||||
|
|
||||||
def test_zipfile_allow_zip64(self, default_zw):
|
|
||||||
assert default_zw.z._allowZip64 is True
|
|
||||||
|
|
||||||
def test_zipfile_mode(self, default_zw):
|
|
||||||
assert default_zw.z.mode == 'w'
|
|
||||||
|
|
||||||
def test_callback(self, default_zw):
|
|
||||||
assert default_zw.processed_size_callback(None) is None
|
|
||||||
|
|
||||||
def test_add_file(self, default_zw, temp_file_1024_delete):
|
|
||||||
default_zw.add_file(temp_file_1024_delete)
|
|
||||||
zipfile_info = default_zw.z.getinfo(
|
|
||||||
os.path.basename(temp_file_1024_delete))
|
|
||||||
|
|
||||||
assert zipfile_info.compress_type == zipfile.ZIP_DEFLATED
|
|
||||||
assert zipfile_info.file_size == 1024
|
|
||||||
|
|
||||||
def test_add_directory(self, temp_dir_1024_delete, default_zw):
|
|
||||||
previous_size = default_zw._size # size before adding directory
|
|
||||||
default_zw.add_dir(temp_dir_1024_delete)
|
|
||||||
assert default_zw._size == previous_size + 1024
|
|
||||||
|
|
||||||
|
|
||||||
class TestZipWriterCustom:
|
|
||||||
@pytest.mark.parametrize('test_input', (
|
|
||||||
common.random_string(
|
|
||||||
random.randint(2, 50),
|
|
||||||
random.choice((None, random.randint(2, 50)))
|
|
||||||
) for _ in range(50)
|
|
||||||
))
|
|
||||||
def test_random_string_regex(self, test_input):
|
|
||||||
assert bool(RANDOM_STR_REGEX.match(test_input))
|
|
||||||
|
|
||||||
def test_custom_filename(self, custom_zw):
|
|
||||||
assert bool(RANDOM_STR_REGEX.match(custom_zw.zip_filename))
|
|
||||||
|
|
||||||
def test_custom_callback(self, custom_zw):
|
|
||||||
assert custom_zw.processed_size_callback(None) == 'custom_callback'
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
"""
|
"""
|
||||||
OnionShare | https://onionshare.org/
|
OnionShare | https://onionshare.org/
|
||||||
|
|
||||||
Copyright (C) 2017 Micah Lee <micah@micahflee.com>
|
Copyright (C) 2018 Micah Lee <micah@micahflee.com>
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
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
|
it under the terms of the GNU General Public License as published by
|
||||||
|
@ -26,19 +26,16 @@ import pytest
|
||||||
from onionshare import common, settings, strings
|
from onionshare import common, settings, strings
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
|
||||||
def custom_version(monkeypatch):
|
|
||||||
monkeypatch.setattr(common, 'get_version', lambda: 'DUMMY_VERSION_1.2.3')
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def os_path_expanduser(monkeypatch):
|
def os_path_expanduser(monkeypatch):
|
||||||
monkeypatch.setattr('os.path.expanduser', lambda path: path)
|
monkeypatch.setattr('os.path.expanduser', lambda path: path)
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def settings_obj(custom_version, sys_onionshare_dev_mode, platform_linux):
|
def settings_obj(sys_onionshare_dev_mode, platform_linux):
|
||||||
return settings.Settings()
|
_common = common.Common()
|
||||||
|
_common.version = 'DUMMY_VERSION_1.2.3'
|
||||||
|
return settings.Settings(_common)
|
||||||
|
|
||||||
|
|
||||||
class TestSettings:
|
class TestSettings:
|
||||||
|
@ -67,7 +64,8 @@ class TestSettings:
|
||||||
'save_private_key': False,
|
'save_private_key': False,
|
||||||
'private_key': '',
|
'private_key': '',
|
||||||
'slug': '',
|
'slug': '',
|
||||||
'hidservauth_string': ''
|
'hidservauth_string': '',
|
||||||
|
'downloads_dir': os.path.expanduser('~/OnionShare')
|
||||||
}
|
}
|
||||||
|
|
||||||
def test_fill_in_defaults(self, settings_obj):
|
def test_fill_in_defaults(self, settings_obj):
|
||||||
|
@ -153,30 +151,27 @@ class TestSettings:
|
||||||
|
|
||||||
def test_filename_darwin(
|
def test_filename_darwin(
|
||||||
self,
|
self,
|
||||||
custom_version,
|
|
||||||
monkeypatch,
|
monkeypatch,
|
||||||
os_path_expanduser,
|
os_path_expanduser,
|
||||||
platform_darwin):
|
platform_darwin):
|
||||||
obj = settings.Settings()
|
obj = settings.Settings(common.Common())
|
||||||
assert (obj.filename ==
|
assert (obj.filename ==
|
||||||
'~/Library/Application Support/OnionShare/onionshare.json')
|
'~/Library/Application Support/OnionShare/onionshare.json')
|
||||||
|
|
||||||
def test_filename_linux(
|
def test_filename_linux(
|
||||||
self,
|
self,
|
||||||
custom_version,
|
|
||||||
monkeypatch,
|
monkeypatch,
|
||||||
os_path_expanduser,
|
os_path_expanduser,
|
||||||
platform_linux):
|
platform_linux):
|
||||||
obj = settings.Settings()
|
obj = settings.Settings(common.Common())
|
||||||
assert obj.filename == '~/.config/onionshare/onionshare.json'
|
assert obj.filename == '~/.config/onionshare/onionshare.json'
|
||||||
|
|
||||||
def test_filename_windows(
|
def test_filename_windows(
|
||||||
self,
|
self,
|
||||||
custom_version,
|
|
||||||
monkeypatch,
|
monkeypatch,
|
||||||
platform_windows):
|
platform_windows):
|
||||||
monkeypatch.setenv('APPDATA', 'C:')
|
monkeypatch.setenv('APPDATA', 'C:')
|
||||||
obj = settings.Settings()
|
obj = settings.Settings(common.Common())
|
||||||
assert obj.filename == 'C:\\OnionShare\\onionshare.json'
|
assert obj.filename == 'C:\\OnionShare\\onionshare.json'
|
||||||
|
|
||||||
def test_set_custom_bridge(self, settings_obj):
|
def test_set_custom_bridge(self, settings_obj):
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
"""
|
"""
|
||||||
OnionShare | https://onionshare.org/
|
OnionShare | https://onionshare.org/
|
||||||
|
|
||||||
Copyright (C) 2017 Micah Lee <micah@micahflee.com>
|
Copyright (C) 2018 Micah Lee <micah@micahflee.com>
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
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
|
it under the terms of the GNU General Public License as published by
|
||||||
|
@ -22,7 +22,7 @@ import types
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from onionshare import common, strings
|
from onionshare import strings
|
||||||
|
|
||||||
|
|
||||||
# # Stub get_resource_path so it finds the correct path while running tests
|
# # Stub get_resource_path so it finds the correct path while running tests
|
||||||
|
@ -44,28 +44,28 @@ def test_underscore_is_function():
|
||||||
|
|
||||||
class TestLoadStrings:
|
class TestLoadStrings:
|
||||||
def test_load_strings_defaults_to_english(
|
def test_load_strings_defaults_to_english(
|
||||||
self, locale_en, sys_onionshare_dev_mode):
|
self, common_obj, locale_en, sys_onionshare_dev_mode):
|
||||||
""" load_strings() loads English by default """
|
""" load_strings() loads English by default """
|
||||||
strings.load_strings(common)
|
strings.load_strings(common_obj)
|
||||||
assert strings._('wait_for_hs') == "Waiting for HS to be ready:"
|
assert strings._('wait_for_hs') == "Waiting for HS to be ready:"
|
||||||
|
|
||||||
|
|
||||||
def test_load_strings_loads_other_languages(
|
def test_load_strings_loads_other_languages(
|
||||||
self, locale_fr, sys_onionshare_dev_mode):
|
self, common_obj, locale_fr, sys_onionshare_dev_mode):
|
||||||
""" load_strings() loads other languages in different locales """
|
""" load_strings() loads other languages in different locales """
|
||||||
strings.load_strings(common, "fr")
|
strings.load_strings(common_obj, "fr")
|
||||||
assert strings._('wait_for_hs') == "En attente du HS:"
|
assert strings._('wait_for_hs') == "En attente du HS:"
|
||||||
|
|
||||||
def test_load_partial_strings(
|
def test_load_partial_strings(
|
||||||
self, locale_ru, sys_onionshare_dev_mode):
|
self, common_obj, locale_ru, sys_onionshare_dev_mode):
|
||||||
strings.load_strings(common)
|
strings.load_strings(common_obj)
|
||||||
assert strings._("give_this_url") == (
|
assert strings._("give_this_url") == (
|
||||||
"Отправьте эту ссылку тому человеку, "
|
"Отправьте эту ссылку тому человеку, "
|
||||||
"которому вы хотите передать файл:")
|
"которому вы хотите передать файл:")
|
||||||
assert strings._('wait_for_hs') == "Waiting for HS to be ready:"
|
assert strings._('wait_for_hs') == "Waiting for HS to be ready:"
|
||||||
|
|
||||||
def test_load_invalid_locale(
|
def test_load_invalid_locale(
|
||||||
self, locale_invalid, sys_onionshare_dev_mode):
|
self, common_obj, locale_invalid, sys_onionshare_dev_mode):
|
||||||
""" load_strings() raises a KeyError for an invalid locale """
|
""" load_strings() raises a KeyError for an invalid locale """
|
||||||
with pytest.raises(KeyError):
|
with pytest.raises(KeyError):
|
||||||
strings.load_strings(common, 'XX')
|
strings.load_strings(common_obj, 'XX')
|
||||||
|
|
90
test/test_onionshare_web.py
Normal file
|
@ -0,0 +1,90 @@
|
||||||
|
"""
|
||||||
|
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/>.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import contextlib
|
||||||
|
import inspect
|
||||||
|
import io
|
||||||
|
import os
|
||||||
|
import random
|
||||||
|
import re
|
||||||
|
import socket
|
||||||
|
import sys
|
||||||
|
import zipfile
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from onionshare.common import Common
|
||||||
|
|
||||||
|
DEFAULT_ZW_FILENAME_REGEX = re.compile(r'^onionshare_[a-z2-7]{6}.zip$')
|
||||||
|
RANDOM_STR_REGEX = re.compile(r'^[a-z2-7]+$')
|
||||||
|
|
||||||
|
class TestZipWriterDefault:
|
||||||
|
@pytest.mark.parametrize('test_input', (
|
||||||
|
'onionshare_{}.zip'.format(''.join(
|
||||||
|
random.choice('abcdefghijklmnopqrstuvwxyz234567') for _ in range(6)
|
||||||
|
)) for _ in range(50)
|
||||||
|
))
|
||||||
|
def test_default_zw_filename_regex(self, test_input):
|
||||||
|
assert bool(DEFAULT_ZW_FILENAME_REGEX.match(test_input))
|
||||||
|
|
||||||
|
def test_zw_filename(self, default_zw):
|
||||||
|
zw_filename = os.path.basename(default_zw.zip_filename)
|
||||||
|
assert bool(DEFAULT_ZW_FILENAME_REGEX.match(zw_filename))
|
||||||
|
|
||||||
|
def test_zipfile_filename_matches_zipwriter_filename(self, default_zw):
|
||||||
|
assert default_zw.z.filename == default_zw.zip_filename
|
||||||
|
|
||||||
|
def test_zipfile_allow_zip64(self, default_zw):
|
||||||
|
assert default_zw.z._allowZip64 is True
|
||||||
|
|
||||||
|
def test_zipfile_mode(self, default_zw):
|
||||||
|
assert default_zw.z.mode == 'w'
|
||||||
|
|
||||||
|
def test_callback(self, default_zw):
|
||||||
|
assert default_zw.processed_size_callback(None) is None
|
||||||
|
|
||||||
|
def test_add_file(self, default_zw, temp_file_1024_delete):
|
||||||
|
default_zw.add_file(temp_file_1024_delete)
|
||||||
|
zipfile_info = default_zw.z.getinfo(
|
||||||
|
os.path.basename(temp_file_1024_delete))
|
||||||
|
|
||||||
|
assert zipfile_info.compress_type == zipfile.ZIP_DEFLATED
|
||||||
|
assert zipfile_info.file_size == 1024
|
||||||
|
|
||||||
|
def test_add_directory(self, temp_dir_1024_delete, default_zw):
|
||||||
|
previous_size = default_zw._size # size before adding directory
|
||||||
|
default_zw.add_dir(temp_dir_1024_delete)
|
||||||
|
assert default_zw._size == previous_size + 1024
|
||||||
|
|
||||||
|
|
||||||
|
class TestZipWriterCustom:
|
||||||
|
@pytest.mark.parametrize('test_input', (
|
||||||
|
Common.random_string(
|
||||||
|
random.randint(2, 50),
|
||||||
|
random.choice((None, random.randint(2, 50)))
|
||||||
|
) for _ in range(50)
|
||||||
|
))
|
||||||
|
def test_random_string_regex(self, test_input):
|
||||||
|
assert bool(RANDOM_STR_REGEX.match(test_input))
|
||||||
|
|
||||||
|
def test_custom_filename(self, custom_zw):
|
||||||
|
assert bool(RANDOM_STR_REGEX.match(custom_zw.zip_filename))
|
||||||
|
|
||||||
|
def test_custom_callback(self, custom_zw):
|
||||||
|
assert custom_zw.processed_size_callback(None) == 'custom_callback'
|