diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 000000000..db8c40a50 --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1 @@ +custom: https://zeronet.io/docs/help_zeronet/donate/ diff --git a/Dockerfile b/Dockerfile index 7913cf1da..7fcd83cae 100644 --- a/Dockerfile +++ b/Dockerfile @@ -4,7 +4,7 @@ FROM alpine:3.8 ENV HOME /root #Install ZeroNet -RUN apk --no-cache --no-progress add musl-dev gcc python python-dev py2-pip tor \ +RUN apk --no-cache --no-progress add musl-dev gcc python python-dev py2-pip tor openssl \ && pip install --no-cache-dir gevent msgpack \ && apk del musl-dev gcc python-dev py2-pip \ && echo "ControlPort 9051" >> /etc/tor/torrc \ diff --git a/plugins/Bigfile/BigfilePlugin.py b/plugins/Bigfile/BigfilePlugin.py index 484d2b6d8..d9b4ff1dc 100644 --- a/plugins/Bigfile/BigfilePlugin.py +++ b/plugins/Bigfile/BigfilePlugin.py @@ -4,6 +4,7 @@ import shutil import collections import math +import json import msgpack import gevent @@ -96,12 +97,12 @@ def actionBigfileUpload(self): site.content_manager.contents.loadItem(file_info["content_inner_path"]) # reload cache - return { + return json.dumps({ "merkle_root": merkle_root, "piece_num": len(piecemap_info["sha512_pieces"]), "piece_size": piece_size, "inner_path": inner_path - } + }) def readMultipartHeaders(self, wsgi_input): for i in range(100): @@ -604,9 +605,10 @@ def isReadable(self, site, inner_path, file, pos): if file.read(10) == "\0" * 10: # Looks empty, but makes sures we don't have that piece file_info = site.content_manager.getFileInfo(inner_path) - piece_i = pos / file_info["piece_size"] - if not site.storage.piecefields[file_info["sha512"]][piece_i]: - return False + if "piece_size" in file_info: + piece_i = pos / file_info["piece_size"] + if not site.storage.piecefields[file_info["sha512"]][piece_i]: + return False # Seek back to position we want to read file.seek(pos) return super(FileRequestPlugin, self).isReadable(site, inner_path, file, pos) diff --git a/plugins/Bigfile/Test/TestBigfile.py b/plugins/Bigfile/Test/TestBigfile.py index 2b71ec8e8..de1266824 100644 --- a/plugins/Bigfile/Test/TestBigfile.py +++ b/plugins/Bigfile/Test/TestBigfile.py @@ -491,3 +491,32 @@ def testFileSize(self, file_server, site, site_temp): site_temp.needFile("%s|%s-%s" % (inner_path, 9 * 1024 * 1024, 10 * 1024 * 1024)) assert site_temp.storage.getSize(inner_path) == site.storage.getSize(inner_path) + + @pytest.mark.parametrize("size", [1024 * 3, 1024 * 1024 * 3, 1024 * 1024 * 30]) + def testNullFileRead(self, file_server, site, site_temp, size): + inner_path = "data/optional.iso" + + f = site.storage.open(inner_path, "w") + f.write("\0" * size) + f.close() + assert site.content_manager.sign("content.json", self.privatekey) + + # Init source server + site.connection_server = file_server + file_server.sites[site.address] = site + + # Init client server + site_temp.connection_server = FileServer(file_server.ip, 1545) + site_temp.connection_server.sites[site_temp.address] = site_temp + site_temp.addPeer(file_server.ip, 1544) + + # Download site + site_temp.download(blind_includes=True).join(timeout=5) + + if "piecemap" in site.content_manager.getFileInfo(inner_path): # Bigfile + site_temp.needFile(inner_path + "|all") + else: + site_temp.needFile(inner_path) + + + assert site_temp.storage.getSize(inner_path) == size diff --git a/plugins/ContentFilter/ContentFilterPlugin.py b/plugins/ContentFilter/ContentFilterPlugin.py index 4c30a1406..05f333765 100644 --- a/plugins/ContentFilter/ContentFilterPlugin.py +++ b/plugins/ContentFilter/ContentFilterPlugin.py @@ -145,7 +145,14 @@ def actionFilterIncludeList(self, to, all_sites=False, filters=False): include_site = filter_storage.site_manager.get(include["address"]) if not include_site: continue - content = include_site.storage.loadJson(include["inner_path"]) + try: + content = include_site.storage.loadJson(include["inner_path"]) + include["error"] = None + except Exception as err: + if include_site.settings["own"]: + include_site.log.warning("Error loading filter %s: %s" % (include["inner_path"], err)) + content = {} + include["error"] = str(err) include["mutes"] = content.get("mutes", {}) include["siteblocks"] = content.get("siteblocks", {}) back.append(include) diff --git a/plugins/Newsfeed/NewsfeedPlugin.py b/plugins/Newsfeed/NewsfeedPlugin.py index 802fa50b3..4e54fae32 100644 --- a/plugins/Newsfeed/NewsfeedPlugin.py +++ b/plugins/Newsfeed/NewsfeedPlugin.py @@ -4,6 +4,7 @@ from Plugin import PluginManager from Db import DbQuery from Debug import Debug +from util import helper @PluginManager.registerTo("UiWebsocket") @@ -66,14 +67,14 @@ def actionFeedQuery(self, to, limit=10, day_limit=3): query = " UNION ".join(query_parts) if ":params" in query: - query = query.replace(":params", ",".join(["?"] * len(params))) - res = site.storage.query(query + " ORDER BY date_added DESC LIMIT %s" % limit, params * query_raw.count(":params")) - else: - res = site.storage.query(query + " ORDER BY date_added DESC LIMIT %s" % limit) + query_params = map(helper.sqlquote, params) + query = query.replace(":params", ",".join(query_params)) + + res = site.storage.query(query + " ORDER BY date_added DESC LIMIT %s" % limit) except Exception as err: # Log error self.log.error("%s feed query %s error: %s" % (address, name, Debug.formatException(err))) - stats.append({"site": site.address, "feed_name": name, "error": str(err), "query": query}) + stats.append({"site": site.address, "feed_name": name, "error": str(err)}) continue for row in res: diff --git a/plugins/OptionalManager/UiWebsocketPlugin.py b/plugins/OptionalManager/UiWebsocketPlugin.py index 94d3f5018..879fb0add 100644 --- a/plugins/OptionalManager/UiWebsocketPlugin.py +++ b/plugins/OptionalManager/UiWebsocketPlugin.py @@ -132,8 +132,12 @@ def actionOptionalFileList(self, to, address=None, orderby="time_downloaded DESC wheres_raw = [] if "bigfile" in filter: wheres["size >"] = 1024 * 1024 * 10 - if "downloaded" in filter: + + if "not_downloaded" in filter: + wheres["is_downloaded"] = 0 + elif "downloaded" in filter: wheres_raw.append("(is_downloaded = 1 OR is_pinned = 1)") + if "pinned" in filter: wheres["is_pinned"] = 1 diff --git a/plugins/Sidebar/ZipStream.py b/plugins/Sidebar/ZipStream.py index 9d7de2416..ea6283e47 100644 --- a/plugins/Sidebar/ZipStream.py +++ b/plugins/Sidebar/ZipStream.py @@ -2,12 +2,12 @@ import os import zipfile - class ZipStream(file): def __init__(self, dir_path): self.dir_path = dir_path self.pos = 0 - self.zf = zipfile.ZipFile(self, 'w', zipfile.ZIP_DEFLATED, allowZip64 = True) + self.buff_pos = 0 + self.zf = zipfile.ZipFile(self, 'w', zipfile.ZIP_DEFLATED, allowZip64=True) self.buff = StringIO.StringIO() self.file_list = self.getFileList() @@ -27,6 +27,8 @@ def read(self, size=60 * 1024): self.buff.seek(0) back = self.buff.read() self.buff.truncate(0) + self.buff.seek(0) + self.buff_pos += len(back) return back def write(self, data): @@ -36,8 +38,22 @@ def write(self, data): def tell(self): return self.pos - def seek(self, pos, type): - pass + def seek(self, pos, whence=0): + if pos >= self.buff_pos: + self.buff.seek(pos - self.buff_pos, whence) + self.pos = pos def flush(self): pass + + +if __name__ == "__main__": + zs = ZipStream(".") + out = open("out.zip", "wb") + while 1: + data = zs.read() + print("Write %s" % len(data)) + if not data: + break + out.write(data) + out.close() diff --git a/plugins/Sidebar/media/Sidebar.css b/plugins/Sidebar/media/Sidebar.css index 58b2582ec..3af7957b1 100644 --- a/plugins/Sidebar/media/Sidebar.css +++ b/plugins/Sidebar/media/Sidebar.css @@ -3,8 +3,8 @@ } .drag-bg { width: 100%; height: 100%; position: fixed; } -.fixbutton.dragging { cursor: -webkit-grabbing; } -.fixbutton-bg:active { cursor: -webkit-grabbing; } +.fixbutton.dragging { cursor: -webkit-grabbing; cusor: grabbing; } +.fixbutton-bg:active { cursor: -webkit-grabbing; cusor: grabbing; } .body-sidebar, .body-internals { background-color: #666 !important; } diff --git a/plugins/TranslateSite/TranslateSitePlugin.py b/plugins/TranslateSite/TranslateSitePlugin.py index 6eefbb779..e049b6923 100644 --- a/plugins/TranslateSite/TranslateSitePlugin.py +++ b/plugins/TranslateSite/TranslateSitePlugin.py @@ -7,11 +7,18 @@ @PluginManager.registerTo("UiRequest") class UiRequestPlugin(object): def actionSiteMedia(self, path, **kwargs): - file_name = path.split("/")[-1] + file_name = path.split("/")[-1].lower() if not file_name: # Path ends with / file_name = "index.html" extension = file_name.split(".")[-1] - if translate.lang != "en" and extension in ["js", "html"]: + if extension == "html": + should_translate = True + elif extension == "js" and translate.lang != "en": + should_translate = True + else: + should_translate = False + + if should_translate: path_parts = self.parsePath(path) kwargs["header_length"] = False file_generator = super(UiRequestPlugin, self).actionSiteMedia(path, **kwargs) diff --git a/plugins/disabled-Multiuser/MultiuserPlugin.py b/plugins/disabled-Multiuser/MultiuserPlugin.py index 8e70d3e31..e3e4b54cc 100644 --- a/plugins/disabled-Multiuser/MultiuserPlugin.py +++ b/plugins/disabled-Multiuser/MultiuserPlugin.py @@ -96,7 +96,7 @@ def getCurrentUser(self): class UiWebsocketPlugin(object): def __init__(self, *args, **kwargs): self.multiuser_denied_cmds = ( - "siteDelete", "configSet", "serverShutdown", "serverUpdate", "siteClone", + "sitePause", "siteResume", "siteDelete", "configSet", "serverShutdown", "serverUpdate", "siteClone", "siteSetOwned", "siteSetAutodownloadoptional", "dbReload", "dbRebuild", "mergerSiteDelete", "siteSetLimit", "siteSetAutodownloadBigfileLimit", "optionalLimitSet", "optionalHelp", "optionalHelpRemove", "optionalHelpAll", "optionalFilePin", "optionalFileUnpin", "optionalFileDelete", diff --git a/src/Config.py b/src/Config.py index fd7b06bc4..aab299fd7 100644 --- a/src/Config.py +++ b/src/Config.py @@ -13,7 +13,7 @@ class Config(object): def __init__(self, argv): self.version = "0.6.5" - self.rev = 3853 + self.rev = 3870 self.argv = argv self.action = None self.pending_changes = {} diff --git a/src/Connection/Connection.py b/src/Connection/Connection.py index b5f7ae70d..4edd33a29 100644 --- a/src/Connection/Connection.py +++ b/src/Connection/Connection.py @@ -1,5 +1,6 @@ import socket import time +import random import gevent import msgpack @@ -172,7 +173,7 @@ def connect(self): self.sock.connect(sock_address) # Detect protocol - self.send({"cmd": "handshake", "req_id": 0, "params": self.getHandshakeInfo()}) + self.send({"cmd": "handshake", "req_id": 0, "params": self.getHandshakeInfo(), "random": "A" * random.randint(0, 1024)}) event_connected = self.event_connected gevent.spawn(self.messageLoop) connect_res = event_connected.get() # Wait for handshake diff --git a/src/Crypt/CryptConnection.py b/src/Crypt/CryptConnection.py index 15009ff45..0897d2aff 100644 --- a/src/Crypt/CryptConnection.py +++ b/src/Crypt/CryptConnection.py @@ -3,6 +3,7 @@ import os import ssl import hashlib +import random from Config import config from util import SslPatch @@ -20,6 +21,12 @@ def __init__(self): self.crypt_supported = [] # Supported cryptos + self.cacert_pem = config.data_dir + "/cacert-rsa.pem" + self.cakey_pem = config.data_dir + "/cakey-rsa.pem" + self.cert_pem = config.data_dir + "/cert-rsa.pem" + self.cert_csr = config.data_dir + "/cert-rsa.csr" + self.key_pem = config.data_dir + "/key-rsa.pem" + # Select crypt that supported by both sides # Return: Name of the crypto def selectCrypt(self, client_supported): @@ -32,12 +39,13 @@ def selectCrypt(self, client_supported): # Return: wrapped socket def wrapSocket(self, sock, crypt, server=False, cert_pin=None): if crypt == "tls-rsa": - ciphers = "ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:AES128-GCM-SHA256:AES128-SHA256:HIGH:" + ciphers = "ECDHE-RSA-CHACHA20-POLY1305:ECDHE-RSA-AES128-GCM-SHA256:AES128-SHA256:AES256-SHA:" ciphers += "!aNULL:!eNULL:!EXPORT:!DSS:!DES:!RC4:!3DES:!MD5:!PSK" if server: sock_wrapped = ssl.wrap_socket( - sock, server_side=server, keyfile='%s/key-rsa.pem' % config.data_dir, - certfile='%s/cert-rsa.pem' % config.data_dir, ciphers=ciphers) + sock, server_side=server, keyfile=self.key_pem, + certfile=self.cert_pem, ciphers=ciphers + ) else: sock_wrapped = ssl.wrap_socket(sock, ciphers=ciphers) if cert_pin: @@ -50,7 +58,7 @@ def wrapSocket(self, sock, crypt, server=False, cert_pin=None): def removeCerts(self): if config.keep_ssl_cert: return False - for file_name in ["cert-rsa.pem", "key-rsa.pem"]: + for file_name in ["cert-rsa.pem", "key-rsa.pem", "cacert-rsa.pem", "cakey-rsa.pem", "cacert-rsa.srl", "cert-rsa.csr"]: file_path = "%s/%s" % (config.data_dir, file_name) if os.path.isfile(file_path): os.unlink(file_path) @@ -66,15 +74,33 @@ def loadCerts(self): # Try to create RSA server cert + sign for connection encryption # Return: True on success def createSslRsaCert(self): - if os.path.isfile("%s/cert-rsa.pem" % config.data_dir) and os.path.isfile("%s/key-rsa.pem" % config.data_dir): + casubjects = [ + "/C=US/O=Amazon/OU=Server CA 1B/CN=Amazon", + "/C=US/O=Let's Encrypt/CN=Let's Encrypt Authority X3", + "/C=US/O=DigiCert Inc/OU=www.digicert.com/CN=DigiCert SHA2 High Assurance Server CA", + "/C=GB/ST=Greater Manchester/L=Salford/O=COMODO CA Limited/CN=COMODO RSA Domain Validation Secure Server CA" + ] + fakedomains = [ + "yahoo.com", "amazon.com", "live.com", "microsoft.com", "mail.ru", "csdn.net", "bing.com", + "amazon.co.jp", "office.com", "imdb.com", "msn.com", "samsung.com", "huawei.com", "ztedevices.com", + "godaddy.com", "w3.org", "gravatar.com", "creativecommons.org", "hatena.ne.jp", + "adobe.com", "opera.com", "apache.org", "rambler.ru", "one.com", "nationalgeographic.com", + "networksolutions.com", "php.net", "python.org", "phoca.cz", "debian.org", "ubuntu.com", + "nazwa.pl", "symantec.com" + ] + self.openssl_env['CN'] = random.choice(fakedomains) + + if os.path.isfile(self.cert_pem) and os.path.isfile(self.key_pem): return True # Files already exits import subprocess - cmd = "%s req -x509 -newkey rsa:2048 -sha256 -batch -keyout %s -out %s -nodes -config %s" % helper.shellquote( + # Generate CAcert and CAkey + cmd = "%s req -new -newkey rsa:2048 -days 3650 -nodes -x509 -subj %s -keyout %s -out %s -batch -config %s" % helper.shellquote( self.openssl_bin, - config.data_dir+"/key-rsa.pem", - config.data_dir+"/cert-rsa.pem", - self.openssl_env["OPENSSL_CONF"] + random.choice(casubjects), + self.cakey_pem, + self.cacert_pem, + self.openssl_env["OPENSSL_CONF"], ) proc = subprocess.Popen( cmd.encode(sys.getfilesystemencoding()), @@ -82,47 +108,50 @@ def createSslRsaCert(self): ) back = proc.stdout.read().strip() proc.wait() - logging.debug("Generating RSA cert and key PEM files...%s" % back) + logging.debug("Generating RSA CAcert and CAkey PEM files...%s" % back) - if os.path.isfile("%s/cert-rsa.pem" % config.data_dir) and os.path.isfile("%s/key-rsa.pem" % config.data_dir): - return True - else: - logging.error("RSA ECC SSL cert generation failed, cert or key files not exist.") + if not (os.path.isfile(self.cacert_pem) and os.path.isfile(self.cakey_pem)): + logging.error("RSA ECC SSL CAcert generation failed, CAcert or CAkey files not exist.") return False - # Not used yet: Missing on some platform - """def createSslEccCert(self): - return False - import subprocess - - # Create ECC privatekey + # Generate certificate key and signing request + cmd = "%s req -new -newkey rsa:2048 -keyout %s -out %s -subj %s -sha256 -nodes -batch -config %s" % helper.shellquote( + self.openssl_bin, + self.key_pem, + self.cert_csr, + "/CN=" + self.openssl_env['CN'], + self.openssl_env["OPENSSL_CONF"], + ) proc = subprocess.Popen( - "%s ecparam -name prime256v1 -genkey -out %s/key-ecc.pem" % (self.openssl_bin, config.data_dir), + cmd.encode(sys.getfilesystemencoding()), shell=True, stderr=subprocess.STDOUT, stdout=subprocess.PIPE, env=self.openssl_env ) back = proc.stdout.read().strip() proc.wait() - self.log.debug("Generating ECC privatekey PEM file...%s" % back) + logging.debug("Generating certificate key and signing request...%s" % back) - # Create ECC cert + # Sign request and generate certificate + cmd = "%s x509 -req -in %s -CA %s -CAkey %s -CAcreateserial -out %s -days 730 -sha256 -extensions x509_ext -extfile %s" % helper.shellquote( + self.openssl_bin, + self.cert_csr, + self.cacert_pem, + self.cakey_pem, + self.cert_pem, + self.openssl_env["OPENSSL_CONF"], + ) proc = subprocess.Popen( - "%s req -new -key %s -x509 -nodes -out %s -config %s" % helper.shellquote( - self.openssl_bin, - config.data_dir+"/key-ecc.pem", - config.data_dir+"/cert-ecc.pem", - self.openssl_env["OPENSSL_CONF"] - ), + cmd.encode(sys.getfilesystemencoding()), shell=True, stderr=subprocess.STDOUT, stdout=subprocess.PIPE, env=self.openssl_env ) back = proc.stdout.read().strip() proc.wait() - self.log.debug("Generating ECC cert PEM file...%s" % back) + logging.debug("Generating RSA cert...%s" % back) - if os.path.isfile("%s/cert-ecc.pem" % config.data_dir) and os.path.isfile("%s/key-ecc.pem" % config.data_dir): + if os.path.isfile(self.cert_pem) and os.path.isfile(self.key_pem): return True else: - self.logging.error("ECC SSL cert generation failed, cert or key files not exits.") + logging.error("RSA ECC SSL cert generation failed, cert or key files not exist.") return False - """ + manager = CryptConnectionManager() diff --git a/src/Db/DbCursor.py b/src/Db/DbCursor.py index 88d898caa..f397ff0cb 100644 --- a/src/Db/DbCursor.py +++ b/src/Db/DbCursor.py @@ -1,5 +1,7 @@ import time import re +from util import helper + # Special sqlite cursor @@ -12,12 +14,6 @@ def __init__(self, conn, db): self.cursor = conn.cursor() self.logging = False - def quoteValue(self, value): - if type(value) is int: - return str(value) - else: - return "'%s'" % value.replace("'", "''") - def execute(self, query, params=None): self.db.last_query_time = time.time() if isinstance(params, dict) and "?" in query: # Make easier select and insert by allowing dict params @@ -35,7 +31,7 @@ def execute(self, query, params=None): operator = "IN" if len(value) > 100: # Embed values in query to avoid "too many SQL variables" error - query_values = ",".join(map(self.quoteValue, value)) + query_values = ",".join(map(helper.sqlquote, value)) else: query_values = ",".join(["?"] * len(value)) values += value diff --git a/src/Debug/DebugHook.py b/src/Debug/DebugHook.py index c3956eed1..1e96125dc 100644 --- a/src/Debug/DebugHook.py +++ b/src/Debug/DebugHook.py @@ -1,5 +1,6 @@ import sys import logging +import signal import gevent import gevent.hub @@ -8,22 +9,22 @@ last_error = None -def shutdown(): - print "Shutting down..." +def shutdown(reason="Unknown"): + logging.info("Shutting down (reason: %s)..." % reason) if "file_server" in dir(sys.modules["main"]) and sys.modules["main"].file_server.running: try: if "file_server" in dir(sys.modules["main"]): gevent.spawn(sys.modules["main"].file_server.stop) if "ui_server" in dir(sys.modules["main"]): gevent.spawn(sys.modules["main"].ui_server.stop) - except Exception, err: + except Exception as err: print "Proper shutdown error: %s" % err sys.exit(0) else: sys.exit(0) # Store last error, ignore notify, allow manual error logging -def handleError(*args): +def handleError(*args, **kwargs): global last_error if not args: # Manual called args = sys.exc_info() @@ -32,22 +33,23 @@ def handleError(*args): silent = False if args[0].__name__ != "Notify": last_error = args + if args[0].__name__ == "KeyboardInterrupt": - shutdown() - return - if not silent and args[0].__name__ != "Notify": + shutdown("Keyboard interrupt") + elif not silent and args[0].__name__ != "Notify": logging.exception("Unhandled exception") if "greenlet.py" not in args[2].tb_frame.f_code.co_filename: # Don't display error twice - sys.__excepthook__(*args) + sys.__excepthook__(*args, **kwargs) # Ignore notify errors -def handleErrorNotify(*args): - if args[0].__name__ == "KeyboardInterrupt": - shutdown() - if args[0].__name__ != "Notify": - logging.exception("Unhandled exception") - sys.__excepthook__(*args) +def handleErrorNotify(*args, **kwargs): + err = args[0] + if err.__name__ == "KeyboardInterrupt": + shutdown("Keyboard interrupt") + elif err.__name__ != "Notify": + logging.error("Unhandled exception: %s" % [args]) + sys.__excepthook__(*args, **kwargs) if config.debug: # Keep last error for /Debug @@ -79,6 +81,12 @@ def handleGreenletError(self, context, type, value, tb): gevent.hub.Hub.handle_error = handleGreenletError +try: + signal.signal(signal.SIGTERM, lambda signum, stack_frame: shutdown("SIGTERM")) +except Exception as err: + logging.debug("Error setting up SIGTERM watcher: %s" % err) + + if __name__ == "__main__": import time from gevent import monkey diff --git a/src/Peer/Peer.py b/src/Peer/Peer.py index b999257e5..536ecf410 100644 --- a/src/Peer/Peer.py +++ b/src/Peer/Peer.py @@ -146,7 +146,7 @@ def request(self, cmd, params={}, stream_to=None): self.log("Send request: %s %s %s %s" % (params.get("site", ""), cmd, params.get("inner_path", ""), params.get("location", ""))) - for retry in range(1, 4): # Retry 3 times + for retry in range(1, 2): # Retry 1 times try: if not self.connection: raise Exception("No connection found") diff --git a/src/Site/Site.py b/src/Site/Site.py index 1126d0655..29b00cab9 100644 --- a/src/Site/Site.py +++ b/src/Site/Site.py @@ -90,7 +90,7 @@ def loadSettings(self, settings=None): self.settings = settings if "cache" not in settings: settings["cache"] = {} - if "size_files_optional" not in settings: + if "size_optional" not in settings: settings["size_optional"] = 0 if "optional_downloaded" not in settings: settings["optional_downloaded"] = 0 diff --git a/src/Site/SiteStorage.py b/src/Site/SiteStorage.py index d901d5fad..6c9d48c1b 100644 --- a/src/Site/SiteStorage.py +++ b/src/Site/SiteStorage.py @@ -362,7 +362,7 @@ def getPath(self, inner_path): if not inner_path: return self.directory - if ".." in inner_path: + if "../" in inner_path: raise Exception(u"File not allowed: %s" % inner_path) return u"%s/%s" % (self.directory, inner_path) diff --git a/src/Translate/languages/it.json b/src/Translate/languages/it.json index 479923282..f3ee5d87a 100644 --- a/src/Translate/languages/it.json +++ b/src/Translate/languages/it.json @@ -39,7 +39,7 @@ " files needs to be downloaded": " i file devono essere scaricati", " downloaded": " scaricati", " download failed": " scaricamento fallito", - "Peers found: ": "Peer trovati: ", + "Peers found: ": "Peers trovati: ", "No peers found": "Nessun peer trovato", "Running out of size limit (": "Superato il limite di spazio (", "Set limit to \" + site_info.next_size_limit + \"MB": "Imposta il limite a \" + site_info.next_size_limit + \"MB", diff --git a/src/Ui/UiRequest.py b/src/Ui/UiRequest.py index 71c380d0f..1a2f4b2a8 100644 --- a/src/Ui/UiRequest.py +++ b/src/Ui/UiRequest.py @@ -103,7 +103,7 @@ def route(self, path): extra_headers = {"Access-Control-Allow-Origin": "null"} - self.sendHeader(content_type=content_type, extra_headers=extra_headers) + self.sendHeader(content_type=content_type, extra_headers=extra_headers, noscript=True) return "" if path == "/": @@ -246,7 +246,13 @@ def sendHeader(self, status=200, content_type="text/html", noscript=False, allow headers["Connection"] = "Keep-Alive" headers["Keep-Alive"] = "max=25, timeout=30" headers["X-Frame-Options"] = "SAMEORIGIN" - if content_type != "text/html" and self.env.get("HTTP_REFERER") and self.isSameOrigin(self.getReferer(), self.getRequestUrl()): + is_referer_allowed = False + if self.env.get("HTTP_REFERER"): + if self.isSameOrigin(self.getReferer(), self.getRequestUrl()): + is_referer_allowed = True + elif self.getReferer() == "%s://%s/" % (self.env["wsgi.url_scheme"], self.env["HTTP_HOST"]): # Origin-only referer + is_referer_allowed = True + if content_type != "text/html" and is_referer_allowed: headers["Access-Control-Allow-Origin"] = "*" # Allow load font files from css if noscript: @@ -287,9 +293,12 @@ def sendHeader(self, status=200, content_type="text/html", noscript=False, allow # Renders a template def render(self, template_path, *args, **kwargs): template = open(template_path).read() - for key, val in kwargs.items(): - template = template.replace("{%s}" % key, "%s" % val) - return template.encode("utf8") + def renderReplacer(m): + return "%s" % kwargs.get(m.group(1), "") + + template_rendered = re.sub("{(.*?)}", renderReplacer, template) + + return template_rendered.encode("utf8") # - Actions - @@ -410,6 +419,9 @@ def renderWrapper(self, site, path, inner_path, title, extra_headers, show_loadi file_url = "/" + address + "/" + inner_path root_url = "/" + address + "/" + if self.isProxyRequest(): + self.server.allowed_ws_origins.add(self.env["HTTP_HOST"]) + # Wrapper variable inits body_style = "" meta_tags = "" @@ -520,7 +532,7 @@ def parsePath(self, path): if path.endswith("/"): path = path + "index.html" - if ".." in path or "./" in path: + if "../" in path or "./" in path: raise SecurityError("Invalid path") match = re.match("/media/(?P
[A-Za-z0-9]+[A-Za-z0-9\._-]+)(?P%s- """ % (title, message, json.dumps(details, indent=4, sort_keys=True)) + """ % (title, cgi.escape(message), cgi.escape(json.dumps(details, indent=4, sort_keys=True))) else: return """