Merge remote-tracking branch 'refs/remotes/HelloZeroNet/master'

This commit is contained in:
lmath 2017-01-28 05:53:19 +08:00
commit a7f21f0680
57 changed files with 917 additions and 184 deletions

View file

@ -70,6 +70,7 @@ Decentralized websites using Bitcoin crypto and the BitTorrent network - https:/
* [Linux 32bit](https://github.com/HelloZeroNet/ZeroBundle/raw/master/dist/ZeroBundle-linux32.tar.gz) * [Linux 32bit](https://github.com/HelloZeroNet/ZeroBundle/raw/master/dist/ZeroBundle-linux32.tar.gz)
* Unpack anywhere * Unpack anywhere
* Run `ZeroNet.cmd` (win), `ZeroNet(.app)` (osx), `ZeroNet.sh` (linux) * Run `ZeroNet.cmd` (win), `ZeroNet(.app)` (osx), `ZeroNet.sh` (linux)
* On OSX you may need to make the application executable via `chmod +x ZeroNet.app`
If you get "classic environment no longer supported" error on OS X: Open a Terminal window and drop ZeroNet.app on it If you get "classic environment no longer supported" error on OS X: Open a Terminal window and drop ZeroNet.app on it
@ -187,4 +188,4 @@ Site:13DNDk..bhC2 Successfuly published to 3 peers
* More info, help, changelog, zeronet sites: https://www.reddit.com/r/zeronet/ * More info, help, changelog, zeronet sites: https://www.reddit.com/r/zeronet/
* Come, chat with us: [#zeronet @ FreeNode](https://kiwiirc.com/client/irc.freenode.net/zeronet) or on [gitter](https://gitter.im/HelloZeroNet/ZeroNet) * Come, chat with us: [#zeronet @ FreeNode](https://kiwiirc.com/client/irc.freenode.net/zeronet) or on [gitter](https://gitter.im/HelloZeroNet/ZeroNet)
* Email: hello@noloop.me * Email: hello@zeronet.io (PGP: CB9613AE)

View file

@ -5,6 +5,7 @@ from Plugin import PluginManager
from Translate import Translate from Translate import Translate
from util import RateLimit from util import RateLimit
from util import helper from util import helper
from Debug import Debug
try: try:
import OptionalManager.UiWebsocketPlugin # To make optioanlFileInfo merger sites compatible import OptionalManager.UiWebsocketPlugin # To make optioanlFileInfo merger sites compatible
except Exception: except Exception:
@ -19,6 +20,7 @@ if "merger_db" not in locals().keys(): # To keep merger_sites between module re
if "_" not in locals(): if "_" not in locals():
_ = Translate("plugins/MergerSite/languages/") _ = Translate("plugins/MergerSite/languages/")
# Check if the site has permission to this merger site # Check if the site has permission to this merger site
def checkMergerPath(address, inner_path): def checkMergerPath(address, inner_path):
merged_match = re.match("^merged-(.*?)/([A-Za-z0-9]{26,35})/", inner_path) merged_match = re.match("^merged-(.*?)/([A-Za-z0-9]{26,35})/", inner_path)
@ -32,7 +34,10 @@ def checkMergerPath(address, inner_path):
inner_path = re.sub("^merged-(.*?)/([A-Za-z0-9]{26,35})/", "", inner_path) inner_path = re.sub("^merged-(.*?)/([A-Za-z0-9]{26,35})/", "", inner_path)
return merged_address, inner_path return merged_address, inner_path
else: else:
raise Exception("Merger site (%s) does not have permission for merged site: %s" % (merger_type, merged_address)) raise Exception(
"Merger site (%s) does not have permission for merged site: %s (%s)" %
(merger_type, merged_address, merged_db.get(merged_address))
)
else: else:
raise Exception("No merger (%s) permission to load: <br>%s (%s not in %s)" % ( raise Exception("No merger (%s) permission to load: <br>%s (%s not in %s)" % (
address, inner_path, merger_type, merger_db.get(address, [])) address, inner_path, merger_type, merger_db.get(address, []))
@ -184,7 +189,8 @@ class UiWebsocketPlugin(object):
def actionPermissionAdd(self, to, permission): def actionPermissionAdd(self, to, permission):
super(UiWebsocketPlugin, self).actionPermissionAdd(to, permission) super(UiWebsocketPlugin, self).actionPermissionAdd(to, permission)
self.site.storage.rebuildDb() if permission.startswith("Merger"):
self.site.storage.rebuildDb()
@PluginManager.registerTo("UiRequest") @PluginManager.registerTo("UiRequest")
@ -269,7 +275,6 @@ class SitePlugin(object):
for ws in merger_site.websockets: for ws in merger_site.websockets:
ws.event("siteChanged", self, {"event": ["file_done", inner_path]}) ws.event("siteChanged", self, {"event": ["file_done", inner_path]})
def fileFailed(self, inner_path): def fileFailed(self, inner_path):
super(SitePlugin, self).fileFailed(inner_path) super(SitePlugin, self).fileFailed(inner_path)
@ -294,7 +299,11 @@ class SiteManagerPlugin(object):
return return
for site in self.sites.itervalues(): for site in self.sites.itervalues():
# Update merged sites # Update merged sites
merged_type = site.content_manager.contents.get("content.json", {}).get("merged_type") try:
merged_type = site.content_manager.contents.get("content.json", {}).get("merged_type")
except Exception, err:
self.log.error("Error loading site %s: %s" % (site.address, Debug.formatException(err)))
continue
if merged_type: if merged_type:
merged_db[site.address] = merged_type merged_db[site.address] = merged_type
@ -303,7 +312,10 @@ class SiteManagerPlugin(object):
if not permission.startswith("Merger:"): if not permission.startswith("Merger:"):
continue continue
if merged_type: if merged_type:
self.log.error("Removing permission %s from %s: Merger and merged at the same time." % (permission, site.address)) self.log.error(
"Removing permission %s from %s: Merger and merged at the same time." %
(permission, site.address)
)
site.settings["permissions"].remove(permission) site.settings["permissions"].remove(permission)
continue continue
merger_type = permission.replace("Merger:", "") merger_type = permission.replace("Merger:", "")

View file

@ -0,0 +1,5 @@
{
"Add <b>%s</b> new site?": "Ajouter le site <b>%s</b> ?",
"Added <b>%s</b> new site": "Site <b>%s</b> ajouté",
"Site deleted: <b>%s</b>": "Site <b>%s</b> supprimé"
}

View file

@ -0,0 +1,5 @@
{
"Add <b>%s</b> new site?": "<b>%s</b> sitesi eklensin mi?",
"Added <b>%s</b> new site": "<b>%s</b> sitesi eklendi",
"Site deleted: <b>%s</b>": "<b>%s</b> sitesi silindi"
}

View file

@ -157,7 +157,10 @@ class ContentDbPlugin(object):
def setContentFilesOptional(self, site, content_inner_path, content, cur=None): def setContentFilesOptional(self, site, content_inner_path, content, cur=None):
if not cur: if not cur:
cur = self cur = self
cur.execute("BEGIN") try:
cur.execute("BEGIN")
except Exception, err:
self.log.warning("Transaction begin error %s %s: %s" % (site, content_inner_path, Debug.formatException(err)))
num = 0 num = 0
site_id = self.site_ids[site.address] site_id = self.site_ids[site.address]
@ -190,8 +193,10 @@ class ContentDbPlugin(object):
num += 1 num += 1
if cur == self: if cur == self:
cur.execute("END") try:
cur.execute("END")
except Exception, err:
self.log.warning("Transaction end error %s %s: %s" % (site, content_inner_path, Debug.formatException(err)))
return num return num
def setContent(self, site, inner_path, content, size=0): def setContent(self, site, inner_path, content, size=0):

View file

@ -0,0 +1,7 @@
{
"Pinned %s files": "Fichiers %s épinglés",
"Removed pin from %s files": "Fichiers %s ne sont plus épinglés",
"You started to help distribute <b>%s</b>.<br><small>Directory: %s</small>": "Vous avez commencé à aider à distribuer <b>%s</b>.<br><small>Dossier : %s</small>",
"Help distribute all new optional files on site <b>%s</b>": "Aider à distribuer tous les fichiers optionnels du site <b>%s</b>",
"Yes, I want to help!": "Oui, je veux aider !"
}

View file

@ -191,7 +191,7 @@ class UiWebsocketPlugin(object):
size = max(0, size_other) size = max(0, size_other)
elif extension == "Image": elif extension == "Image":
size = size_filetypes.get("jpg", 0) + size_filetypes.get("png", 0) + size_filetypes.get("gif", 0) size = size_filetypes.get("jpg", 0) + size_filetypes.get("png", 0) + size_filetypes.get("gif", 0)
elif extension == "total": elif extension == "Total":
size = size_total size = size_total
else: else:
size = size_filetypes.get(extension, 0) size = size_filetypes.get(extension, 0)
@ -411,12 +411,11 @@ class UiWebsocketPlugin(object):
# Choose content you want to sign # Choose content you want to sign
contents = ["content.json"] contents = ["content.json"]
contents += site.content_manager.contents.get("content.json", {}).get("includes", {}).keys() contents += site.content_manager.contents.get("content.json", {}).get("includes", {}).keys()
if len(contents) > 1: body.append(_(u"<div class='contents'>{_[Choose]}: "))
body.append(_(u"<div class='contents'>{_[Choose]}: ")) for content in contents:
for content in contents: content = cgi.escape(content, True)
content = cgi.escape(content, True) body.append(_("<a href='#{content}' onclick='$(\"#input-contents\").val(\"{content}\"); return false'>{content}</a> "))
body.append(_("<a href='#{content}' onclick='$(\"#input-contents\").val(\"{content}\"); return false'>{content}</a> ")) body.append("</div>")
body.append("</div>")
body.append(_(u""" body.append(_(u"""
<div class='flex'> <div class='flex'>
@ -561,7 +560,7 @@ class UiWebsocketPlugin(object):
globe_data += (lat, lon, ping) globe_data += (lat, lon, ping)
# Append myself # Append myself
loc = geodb.get(config.ip_external) loc = geodb.get(config.ip_external)
if loc: if loc and loc.get("location"):
lat, lon = (loc["location"]["latitude"], loc["location"]["longitude"]) lat, lon = (loc["location"]["latitude"], loc["location"]["longitude"])
globe_data += (lat, lon, -0.135) globe_data += (lat, lon, -0.135)

View file

@ -0,0 +1,79 @@
{
"Peers": "Pares",
"Connected": "Conectados",
"Connectable": "Conectables",
"Connectable peers": "Pares conectables",
"Data transfer": "Transferencia de datos",
"Received": "Recibidos",
"Received bytes": "Bytes recibidos",
"Sent": "Enviados",
"Sent bytes": "Bytes envidados",
"Files": "Ficheros",
"Total": "Total",
"Image": "Imagen",
"Other": "Otro",
"User data": "Datos del usuario",
"Size limit": "Límite de tamaño",
"limit used": "Límite utilizado",
"free space": "Espacio libre",
"Set": "Establecer",
"Optional files": "Ficheros opcionales",
"Downloaded": "Descargado",
"Download and help distribute all files": "Descargar y ayudar a distribuir todos los ficheros",
"Total size": "Tamaño total",
"Downloaded files": "Ficheros descargados",
"Database": "Base de datos",
"search feeds": "Fuentes de búsqueda",
"{feeds} query": "{feeds} consulta",
"Reload": "Recargar",
"Rebuild": "Reconstruir",
"No database found": "No se ha encontrado la base de datos",
"Identity address": "Dirección de la identidad",
"Change": "Cambiar",
"Update": "Actualizar",
"Pause": "Pausar",
"Resume": "Reanudar",
"Delete": "Borrar",
"Site address": "Dirección del sitio",
"Donate": "Donar",
"Missing files": "Ficheros perdidos",
"{} try": "{} intento",
"{} tries": "{} intentos",
"+ {num_bad_files} more": "+ {num_bad_files} más",
"This is my site": "Este es mi sitio",
"Site title": "Título del sitio",
"Site description": "Descripción del sitio",
"Save site settings": "Guardar la configuración del sitio",
"Content publishing": "Publicación del contenido",
"Choose": "Elegir",
"Sign": "Firmar",
"Publish": "Publicar",
"This function is disabled on this proxy": "Esta función está deshabilitada en este proxy",
"GeoLite2 City database download error: {}!<br>Please download manually and unpack to data dir:<br>{}": "¡Error de la base de datos GeoLite2 {}!<br>Por favor, descárgalo manualmente y descomprime al directorio de datos<br>{}",
"Downloading GeoLite2 City database (one time only, ~20MB)...": "Descargando la base de datos de GeoLite2 (una única vez, ~20MB...",
"GeoLite2 City database downloaded!": "¡Base de datos de GeoLite2 descargada!",
"Are you sure?": "¿Estás seguro?",
"Site storage limit modified!": "¡Límite de almacenamiento del sitio modificado!",
"Database schema reloaded!": "¡Esquema de la base de datos recargado!",
"Database rebuilding....": "Reconstruyendo la base de datos...",
"Database rebuilt!": "¡Base de datos reconstruida!",
"Site updated!": "¡Sitio actualizado!",
"Delete this site": "Borrar este sitio",
"File write error: ": "Error de escritura de fichero",
"Site settings saved!": "¡Configuración del sitio guardada!",
"Enter your private key:": "Introduce tu clave privada",
" Signed!": " ¡firmado!",
"WebGL not supported": "WebGL no está soportado"
}

View file

@ -0,0 +1,82 @@
{
"Peers": "Użytkownicy równorzędni",
"Connected": "Połączony",
"Connectable": "Możliwy do podłączenia",
"Connectable peers": "Połączeni użytkownicy równorzędni",
"Data transfer": "Transfer danych",
"Received": "Odebrane",
"Received bytes": "Odebrany bajty",
"Sent": "Wysłane",
"Sent bytes": "Wysłane bajty",
"Files": "Pliki",
"Total": "Sumarycznie",
"Image": "Obraz",
"Other": "Inne",
"User data": "Dane użytkownika",
"Size limit": "Rozmiar limitu",
"limit used": "zużyty limit",
"free space": "wolna przestrzeń",
"Set": "Ustaw",
"Optional files": "Pliki opcjonalne",
"Downloaded": "Ściągnięte",
"Download and help distribute all files": "Ściągnij i pomóż rozpowszechniać wszystkie pliki",
"Total size": "Rozmiar sumaryczny",
"Downloaded files": "Ściągnięte pliki",
"Database": "Baza danych",
"search feeds": "przeszukaj zasoby",
"{feeds} query": "{feeds} pytanie",
"Reload": "Odśwież",
"Rebuild": "Odbuduj",
"No database found": "Nie odnaleziono bazy danych",
"Identity address": "Adres identyfikacyjny",
"Change": "Zmień",
"Site control": "Kontrola strony",
"Update": "Zaktualizuj",
"Pause": "Wstrzymaj",
"Resume": "Wznów",
"Delete": "Skasuj",
"Are you sure?": "Jesteś pewien?",
"Site address": "Adres strony",
"Donate": "Wspomóż",
"Missing files": "Brakujące pliki",
"{} try": "{} próba",
"{} tries": "{} próby",
"+ {num_bad_files} more": "+ {num_bad_files} więcej",
"This is my site": "To moja strona",
"Site title": "Tytuł strony",
"Site description": "Opis strony",
"Save site settings": "Zapisz ustawienia strony",
"Content publishing": "Publikowanie treści",
"Choose": "Wybierz",
"Sign": "Podpisz",
"Publish": "Opublikuj",
"This function is disabled on this proxy": "Ta funkcja jest zablokowana w tym proxy",
"GeoLite2 City database download error: {}!<br>Please download manually and unpack to data dir:<br>{}": "Błąd ściągania bazy danych GeoLite2 City: {}!<br>Proszę ściągnąć ją recznie i wypakować do katalogu danych:<br>{}",
"Downloading GeoLite2 City database (one time only, ~20MB)...": "Ściąganie bazy danych GeoLite2 City (tylko jednorazowo, ok. 20MB)...",
"GeoLite2 City database downloaded!": "Baza danych GeoLite2 City ściagnięta!",
"Are you sure?": "Jesteś pewien?",
"Site storage limit modified!": "Limit pamięci strony zmodyfikowany!",
"Database schema reloaded!": "Schemat bazy danych załadowany ponownie!",
"Database rebuilding....": "Przebudowywanie bazy danych...",
"Database rebuilt!": "Baza danych przebudowana!",
"Site updated!": "Strona zaktualizowana!",
"Delete this site": "Usuń tę stronę",
"File write error: ": "Błąd zapisu pliku: ",
"Site settings saved!": "Ustawienia strony zapisane!",
"Enter your private key:": "Wpisz swój prywatny klucz:",
" Signed!": " Podpisane!",
"WebGL not supported": "WebGL nie jest obsługiwany"
}

View file

@ -37,6 +37,8 @@ class Sidebar extends Class
# Detect dragging # Detect dragging
@fixbutton.on "mousedown touchstart", (e) => @fixbutton.on "mousedown touchstart", (e) =>
if e.button > 0 # Right or middle click
return
e.preventDefault() e.preventDefault()
# Disable previous listeners # Disable previous listeners
@ -330,7 +332,7 @@ class Sidebar extends Class
data["title"] = $("#settings-title").val() data["title"] = $("#settings-title").val()
data["description"] = $("#settings-description").val() data["description"] = $("#settings-description").val()
json_raw = unescape(encodeURIComponent(JSON.stringify(data, undefined, '\t'))) json_raw = unescape(encodeURIComponent(JSON.stringify(data, undefined, '\t')))
wrapper.ws.cmd "fileWrite", ["content.json", btoa(json_raw)], (res) => wrapper.ws.cmd "fileWrite", ["content.json", btoa(json_raw), true], (res) =>
if res != "ok" # fileWrite failed if res != "ok" # fileWrite failed
wrapper.notifications.add "file-write", "error", "File write error: #{res}" wrapper.notifications.add "file-write", "error", "File write error: #{res}"
else else

View file

@ -237,6 +237,9 @@ window.initScrollable = function () {
*/ */
this.fixbutton.on("mousedown touchstart", (function(_this) { this.fixbutton.on("mousedown touchstart", (function(_this) {
return function(e) { return function(e) {
if (e.button > 0) {
return;
}
e.preventDefault(); e.preventDefault();
_this.fixbutton.off("click touchstop touchcancel"); _this.fixbutton.off("click touchstop touchcancel");
_this.fixbutton.off("mousemove touchmove"); _this.fixbutton.off("mousemove touchmove");
@ -569,7 +572,7 @@ window.initScrollable = function () {
data["title"] = $("#settings-title").val(); data["title"] = $("#settings-title").val();
data["description"] = $("#settings-description").val(); data["description"] = $("#settings-description").val();
json_raw = unescape(encodeURIComponent(JSON.stringify(data, void 0, '\t'))); json_raw = unescape(encodeURIComponent(JSON.stringify(data, void 0, '\t')));
return wrapper.ws.cmd("fileWrite", ["content.json", btoa(json_raw)], function(res) { return wrapper.ws.cmd("fileWrite", ["content.json", btoa(json_raw), true], function(res) {
if (res !== "ok") { if (res !== "ok") {
return wrapper.notifications.add("file-write", "error", "File write error: " + res); return wrapper.notifications.add("file-write", "error", "File write error: " + res);
} else { } else {

View file

@ -130,7 +130,7 @@ class UiRequestPlugin(object):
# Db # Db
yield "<br><br><b>Db</b>:<br>" yield "<br><br><b>Db</b>:<br>"
for db in sys.modules["Db.Db"].opened_dbs: for db in sys.modules["Db.Db"].opened_dbs:
yield "- %.3fs: %s<br>" % (time.time() - db.last_query_time, db.db_path) yield "- %.3fs: %s<br>" % (time.time() - db.last_query_time, db.db_path.encode("utf8"))
# Sites # Sites
yield "<br><br><b>Sites</b>:" yield "<br><br><b>Sites</b>:"
@ -220,7 +220,7 @@ class UiRequestPlugin(object):
objs = [obj for obj in gc.get_objects() if isinstance(obj, greenlet)] objs = [obj for obj in gc.get_objects() if isinstance(obj, greenlet)]
yield "<br>Greenlets (%s):<br>" % len(objs) yield "<br>Greenlets (%s):<br>" % len(objs)
for obj in objs: for obj in objs:
yield " - %.1fkb: %s<br>" % (self.getObjSize(obj, hpy), cgi.escape(repr(obj))) yield " - %.1fkb: %s<br>" % (self.getObjSize(obj, hpy), cgi.escape(repr(obj).encode("utf8")))
from Worker import Worker from Worker import Worker
objs = [obj for obj in gc.get_objects() if isinstance(obj, Worker)] objs = [obj for obj in gc.get_objects() if isinstance(obj, Worker)]
@ -401,7 +401,10 @@ class UiRequestPlugin(object):
except Exception, err: except Exception, err:
output("<br><b>! Error: %s</b><br>" % err) output("<br><b>! Error: %s</b><br>" % err)
taken = time.time() - s taken = time.time() - s
multipler = standard / taken if taken > 0:
multipler = standard / taken
else:
multipler = 99
if multipler < 0.3: if multipler < 0.3:
speed = "Sloooow" speed = "Sloooow"
elif multipler < 0.5: elif multipler < 0.5:

View file

@ -1,4 +1,3 @@
import time
import os import os
import sys import sys
import atexit import atexit
@ -12,6 +11,7 @@ allow_reload = False # No source reload supported in this plugin
if "_" not in locals(): if "_" not in locals():
_ = Translate("plugins/Trayicon/languages/") _ = Translate("plugins/Trayicon/languages/")
@PluginManager.registerTo("Actions") @PluginManager.registerTo("Actions")
class ActionsPlugin(object): class ActionsPlugin(object):
@ -54,23 +54,26 @@ class ActionsPlugin(object):
(_["ZeroNet Github"], lambda: self.opensite("https://github.com/HelloZeroNet/ZeroNet")), (_["ZeroNet Github"], lambda: self.opensite("https://github.com/HelloZeroNet/ZeroNet")),
(_["Report bug/request feature"], lambda: self.opensite("https://github.com/HelloZeroNet/ZeroNet/issues")), (_["Report bug/request feature"], lambda: self.opensite("https://github.com/HelloZeroNet/ZeroNet/issues")),
"--", "--",
(_["!Open ZeroNet"], lambda: self.opensite("http://%s:%s/%s" % (ui_ip, config.ui_port, config.homepage) )), (_["!Open ZeroNet"], lambda: self.opensite("http://%s:%s/%s" % (ui_ip, config.ui_port, config.homepage))),
"--", "--",
(_["Quit"], self.quit), (_["Quit"], self.quit),
) )
icon.clicked = lambda: self.opensite("http://%s:%s/%s" % (ui_ip, config.ui_port, config.homepage))
icon.clicked = lambda: self.opensite("http://%s:%s/%s" % (ui_ip, config.ui_port, config.homepage) ) self.quit_servers_event = gevent.threadpool.ThreadResult(
lambda res: gevent.spawn_later(0.1, self.quitServers)
) # Fix gevent thread switch error
gevent.threadpool.start_new_thread(icon._run, ()) # Start in real thread (not gevent compatible) gevent.threadpool.start_new_thread(icon._run, ()) # Start in real thread (not gevent compatible)
super(ActionsPlugin, self).main() super(ActionsPlugin, self).main()
icon._die = True icon._die = True
def quit(self): def quit(self):
self.icon.die() self.icon.die()
time.sleep(0.1) self.quit_servers_event.set(True)
sys.exit()
# self.main.ui_server.stop() def quitServers(self):
# self.main.file_server.stop() self.main.ui_server.stop()
self.main.file_server.stop()
def opensite(self, url): def opensite(self, url):
import webbrowser import webbrowser
@ -115,19 +118,33 @@ class ActionsPlugin(object):
def formatAutorun(self): def formatAutorun(self):
args = sys.argv[:] args = sys.argv[:]
args.insert(0, sys.executable)
if not getattr(sys, 'frozen', False): # Not frozen
args.insert(0, sys.executable)
cwd = os.getcwd().decode(sys.getfilesystemencoding())
else:
cwd = os.path.dirname(sys.executable).decode(sys.getfilesystemencoding())
if sys.platform == 'win32': if sys.platform == 'win32':
args = ['"%s"' % arg for arg in args] args = ['"%s"' % arg for arg in args if arg]
cmd = " ".join(args) cmd = " ".join(args)
# Dont open browser on autorun # Dont open browser on autorun
cmd = cmd.replace("start.py", "zeronet.py").replace('"--open_browser"', "").replace('"default_browser"', "").strip() cmd = cmd.replace("start.py", "zeronet.py").replace('"--open_browser"', "").replace('"default_browser"', "").strip()
cmd += ' --open_browser ""'
cmd = cmd.decode(sys.getfilesystemencoding())
return "@echo off\ncd /D %s\n%s" % (os.getcwd(), cmd) return u"""
@echo off
chcp 65001
set PYTHONIOENCODING=utf-8
cd /D \"%s\"
%s
""" % (cwd, cmd)
def isAutorunEnabled(self): def isAutorunEnabled(self):
path = self.getAutorunPath() path = self.getAutorunPath()
return os.path.isfile(path) and open(path).read() == self.formatAutorun() return os.path.isfile(path) and open(path).read().decode("utf8") == self.formatAutorun()
def titleAutorun(self): def titleAutorun(self):
translate = _["Start ZeroNet when Windows starts"] translate = _["Start ZeroNet when Windows starts"]
@ -140,4 +157,4 @@ class ActionsPlugin(object):
if self.isAutorunEnabled(): if self.isAutorunEnabled():
os.unlink(self.getAutorunPath()) os.unlink(self.getAutorunPath())
else: else:
open(self.getAutorunPath(), "w").write(self.formatAutorun()) open(self.getAutorunPath(), "w").write(self.formatAutorun().encode("utf8"))

View file

@ -0,0 +1,14 @@
{
"ZeroNet Twitter": "ZeroNet Twitter",
"ZeroNet Reddit": "ZeroNet Reddit",
"ZeroNet Github": "ZeroNet Github",
"Report bug/request feature": "Rapport d'erreur/Demanger une fonctionnalité",
"!Open ZeroNet": "!Ouvrir ZeroNet",
"Quit": "Quitter",
"(active)": "(actif)",
"(passive)": "(passif)",
"Connections: %s": "Connexions: %s",
"Received: %.2f MB | Sent: %.2f MB": "Reçu: %.2f MB | Envoyé: %.2f MB",
"Show console window": "Afficher la console",
"Start ZeroNet when Windows starts": "Lancer ZeroNet au démarrage de Windows"
}

View file

@ -0,0 +1,14 @@
{
"ZeroNet Twitter": "ZeroNet Twitter",
"ZeroNet Reddit": "ZeroNet Reddit",
"ZeroNet Github": "ZeroNet Github",
"Report bug/request feature": "Hata bildir/geliştirme taleb et",
"!Open ZeroNet": "!ZeroNet'i Aç",
"Quit": "Kapat",
"(active)": "(aktif)",
"(passive)": "(pasif)",
"Connections: %s": "Bağlantı sayısı: %s",
"Received: %.2f MB | Sent: %.2f MB": "Gelen: %.2f MB | Gönderilen: %.2f MB",
"Show console window": "Konsolu aç",
"Start ZeroNet when Windows starts": "ZeroNet'i açılışta otomatik başlat"
}

View file

@ -130,7 +130,7 @@ class BootstrapperDb(Db):
where = "hash_id = :hashid" where = "hash_id = :hashid"
if onions: if onions:
onions_escaped = ["'%s'" % re.sub("[^a-z0-9,]", "", onion) for onion in onions] onions_escaped = ["'%s'" % re.sub("[^a-z0-9,]", "", onion) for onion in onions if type(onion) is str]
where += " AND (onion NOT IN (%s) OR onion IS NULL)" % ",".join(onions_escaped) where += " AND (onion NOT IN (%s) OR onion IS NULL)" % ",".join(onions_escaped)
elif ip4: elif ip4:
where += " AND (NOT (ip4 = :ip4 AND port = :port) OR ip4 IS NULL)" where += " AND (NOT (ip4 = :ip4 AND port = :port) OR ip4 IS NULL)"

View file

@ -2,6 +2,7 @@ import argparse
import sys import sys
import os import os
import locale import locale
import re
import ConfigParser import ConfigParser
@ -9,7 +10,7 @@ class Config(object):
def __init__(self, argv): def __init__(self, argv):
self.version = "0.5.1" self.version = "0.5.1"
self.rev = 1766 self.rev = 1848
self.argv = argv self.argv = argv
self.action = None self.action = None
self.config_file = "zeronet.conf" self.config_file = "zeronet.conf"
@ -37,7 +38,7 @@ class Config(object):
"udp://tracker.coppersurfer.tk:6969", "udp://tracker.coppersurfer.tk:6969",
"udp://tracker.leechers-paradise.org:6969", "udp://tracker.leechers-paradise.org:6969",
"udp://9.rarbg.com:2710", "udp://9.rarbg.com:2710",
"http://tracker.tordb.ml:6881/announce", "http://tracker.opentrackr.org:1337/announce",
"http://explodie.org:6969/announce", "http://explodie.org:6969/announce",
"http://tracker1.wasabii.com.tw:6969/announce" "http://tracker1.wasabii.com.tw:6969/announce"
] ]
@ -55,6 +56,35 @@ class Config(object):
use_openssl = True use_openssl = True
if repr(1483108852.565) != "1483108852.565":
fix_float_decimals = True
else:
fix_float_decimals = False
this_file = os.path.abspath(__file__).replace("\\", "/")
if this_file.endswith("/Contents/Resources/core/src/Config.py"):
# Running as ZeroNet.app
if this_file.startswith("/Application") or this_file.startswith("/private") or this_file.startswith(os.path.expanduser("~/Library")):
# Runnig from non-writeable directory, put data to Application Support
start_dir = os.path.expanduser("~/Library/Application Support/ZeroNet").decode(sys.getfilesystemencoding())
else:
# Running from writeable directory put data next to .app
start_dir = re.sub("/[^/]+/Contents/Resources/core/src/Config.py", "", this_file).decode(sys.getfilesystemencoding())
config_file = start_dir + "/zeronet.conf"
data_dir = start_dir + "/data"
log_dir = start_dir + "/log"
elif this_file.endswith("/core/src/Config.py"):
# Running as exe or source is at Application Support directory, put var files to outside of core dir
start_dir = this_file.replace("/core/src/Config.py", "").decode(sys.getfilesystemencoding())
config_file = start_dir + "/zeronet.conf"
data_dir = start_dir + "/data"
log_dir = start_dir + "/log"
else:
config_file = "zeronet.conf"
data_dir = "data"
log_dir = "log"
# Main # Main
action = self.subparsers.add_parser("main", help='Start UiServer and FileServer (default)') action = self.subparsers.add_parser("main", help='Start UiServer and FileServer (default)')
@ -76,6 +106,7 @@ class Config(object):
action.add_argument('privatekey', help='Private key (default: ask on execute)', nargs='?') action.add_argument('privatekey', help='Private key (default: ask on execute)', nargs='?')
action.add_argument('--inner_path', help='File you want to sign (default: content.json)', action.add_argument('--inner_path', help='File you want to sign (default: content.json)',
default="content.json", metavar="inner_path") default="content.json", metavar="inner_path")
action.add_argument('--remove_missing_optional', help='Remove optional files that is not present in the directory', action='store_true')
action.add_argument('--publish', help='Publish site after the signing', action='store_true') action.add_argument('--publish', help='Publish site after the signing', action='store_true')
# SitePublish # SitePublish
@ -134,9 +165,9 @@ class Config(object):
self.parser.add_argument('--batch', help="Batch mode (No interactive input for commands)", action='store_true') self.parser.add_argument('--batch', help="Batch mode (No interactive input for commands)", action='store_true')
self.parser.add_argument('--config_file', help='Path of config file', default="zeronet.conf", metavar="path") self.parser.add_argument('--config_file', help='Path of config file', default=config_file, metavar="path")
self.parser.add_argument('--data_dir', help='Path of data directory', default="data", metavar="path") self.parser.add_argument('--data_dir', help='Path of data directory', default=data_dir, metavar="path")
self.parser.add_argument('--log_dir', help='Path of logging directory', default="log", metavar="path") self.parser.add_argument('--log_dir', help='Path of logging directory', default=log_dir, metavar="path")
self.parser.add_argument('--language', help='Web interface language', default=language, metavar='language') self.parser.add_argument('--language', help='Web interface language', default=language, metavar='language')
self.parser.add_argument('--ui_ip', help='Web interface bind address', default="127.0.0.1", metavar='ip') self.parser.add_argument('--ui_ip', help='Web interface bind address', default="127.0.0.1", metavar='ip')
@ -149,7 +180,7 @@ class Config(object):
self.parser.add_argument('--updatesite', help='Source code update site', default='1UPDatEDxnvHDo7TXvq6AEBARfNkyfxsp', self.parser.add_argument('--updatesite', help='Source code update site', default='1UPDatEDxnvHDo7TXvq6AEBARfNkyfxsp',
metavar='address') metavar='address')
self.parser.add_argument('--size_limit', help='Default site size limit in MB', default=10, type=int, metavar='size') self.parser.add_argument('--size_limit', help='Default site size limit in MB', default=10, type=int, metavar='size')
self.parser.add_argument('--connected_limit', help='Max connected peer per site', default=10, type=int, metavar='connected_limit') self.parser.add_argument('--connected_limit', help='Max connected peer per site', default=6, type=int, metavar='connected_limit')
self.parser.add_argument('--fileserver_ip', help='FileServer bind address', default="*", metavar='ip') self.parser.add_argument('--fileserver_ip', help='FileServer bind address', default="*", metavar='ip')
self.parser.add_argument('--fileserver_port', help='FileServer bind port', default=15441, type=int, metavar='port') self.parser.add_argument('--fileserver_port', help='FileServer bind port', default=15441, type=int, metavar='port')
@ -173,6 +204,8 @@ class Config(object):
type='bool', choices=[True, False], default=False) type='bool', choices=[True, False], default=False)
self.parser.add_argument("--msgpack_purepython", help='Use less memory, but a bit more CPU power', self.parser.add_argument("--msgpack_purepython", help='Use less memory, but a bit more CPU power',
type='bool', choices=[True, False], default=True) type='bool', choices=[True, False], default=True)
self.parser.add_argument("--fix_float_decimals", help='Fix content.json modification date float precision on verification',
type='bool', choices=[True, False], default=fix_float_decimals)
self.parser.add_argument('--coffeescript_compiler', help='Coffeescript compiler for developing', default=coffeescript, self.parser.add_argument('--coffeescript_compiler', help='Coffeescript compiler for developing', default=coffeescript,
metavar='executable_path') metavar='executable_path')
@ -180,6 +213,7 @@ class Config(object):
self.parser.add_argument('--tor', help='enable: Use only for Tor peers, always: Use Tor for every connection', choices=["disable", "enable", "always"], default='enable') self.parser.add_argument('--tor', help='enable: Use only for Tor peers, always: Use Tor for every connection', choices=["disable", "enable", "always"], default='enable')
self.parser.add_argument('--tor_controller', help='Tor controller address', metavar='ip:port', default='127.0.0.1:9051') self.parser.add_argument('--tor_controller', help='Tor controller address', metavar='ip:port', default='127.0.0.1:9051')
self.parser.add_argument('--tor_proxy', help='Tor proxy address', metavar='ip:port', default='127.0.0.1:9050') self.parser.add_argument('--tor_proxy', help='Tor proxy address', metavar='ip:port', default='127.0.0.1:9050')
self.parser.add_argument('--tor_password', help='Tor controller password', metavar='password')
self.parser.add_argument('--version', action='version', version='ZeroNet %s r%s' % (self.version, self.rev)) self.parser.add_argument('--version', action='version', version='ZeroNet %s r%s' % (self.version, self.rev))

View file

@ -78,6 +78,11 @@ class Connection(object):
def badAction(self, weight=1): def badAction(self, weight=1):
self.bad_actions += weight self.bad_actions += weight
if self.bad_actions > 40:
self.close()
elif self.bad_actions > 20:
time.sleep(5)
def goodAction(self): def goodAction(self):
self.bad_actions = 0 self.bad_actions = 0

View file

@ -55,7 +55,7 @@ class ConnectionServer:
if port: # Listen server on a port if port: # Listen server on a port
self.pool = Pool(1000) # do not accept more than 1000 connections self.pool = Pool(1000) # do not accept more than 1000 connections
self.stream_server = StreamServer( self.stream_server = StreamServer(
(ip.replace("*", ""), port), self.handleIncomingConnection, spawn=self.pool, backlog=500 (ip.replace("*", "0.0.0.0"), port), self.handleIncomingConnection, spawn=self.pool, backlog=500
) )
if request_handler: if request_handler:
self.handleRequest = request_handler self.handleRequest = request_handler

View file

@ -212,7 +212,7 @@ class ContentManager(object):
# Update the content # Update the content
self.contents[content_inner_path] = new_content self.contents[content_inner_path] = new_content
except Exception, err: except Exception, err:
self.log.warning("Content.json parse error: %s" % Debug.formatException(err)) self.log.warning("%s parse error: %s" % (content_inner_path, Debug.formatException(err)))
return [], [] # Content.json parse error return [], [] # Content.json parse error
# Add changed files to bad files # Add changed files to bad files
@ -315,6 +315,7 @@ class ContentManager(object):
if back: if back:
back["content_inner_path"] = content_inner_path back["content_inner_path"] = content_inner_path
back["optional"] = False back["optional"] = False
back["relative_path"] = "/".join(inner_path_parts)
return back return back
# Check in optional files # Check in optional files
@ -323,6 +324,7 @@ class ContentManager(object):
if back: if back:
back["content_inner_path"] = content_inner_path back["content_inner_path"] = content_inner_path
back["optional"] = True back["optional"] = True
back["relative_path"] = "/".join(inner_path_parts)
return back return back
# Return the rules if user dir # Return the rules if user dir
@ -424,7 +426,7 @@ class ContentManager(object):
# Get diffs for changed files # Get diffs for changed files
def getDiffs(self, inner_path, limit=30 * 1024, update_files=True): def getDiffs(self, inner_path, limit=30 * 1024, update_files=True):
if inner_path not in self.contents: if inner_path not in self.contents:
return None return {}
diffs = {} diffs = {}
content_inner_path_dir = helper.getDirname(inner_path) content_inner_path_dir = helper.getDirname(inner_path)
for file_relative_path in self.contents[inner_path].get("files", {}): for file_relative_path in self.contents[inner_path].get("files", {}):
@ -491,7 +493,7 @@ class ContentManager(object):
# Create and sign a content.json # Create and sign a content.json
# Return: The new content if filewrite = False # Return: The new content if filewrite = False
def sign(self, inner_path="content.json", privatekey=None, filewrite=True, update_changed_files=False, extend=None): def sign(self, inner_path="content.json", privatekey=None, filewrite=True, update_changed_files=False, extend=None, remove_missing_optional=False):
if inner_path in self.contents: if inner_path in self.contents:
content = self.contents[inner_path] content = self.contents[inner_path]
if self.contents[inner_path].get("cert_sign", False) is None and self.site.storage.isFile(inner_path): if self.contents[inner_path].get("cert_sign", False) is None and self.site.storage.isFile(inner_path):
@ -523,6 +525,11 @@ class ContentManager(object):
helper.getDirname(inner_path), content.get("ignore"), content.get("optional") helper.getDirname(inner_path), content.get("ignore"), content.get("optional")
) )
if not remove_missing_optional:
for file_inner_path, file_details in content.get("files_optional", {}).iteritems():
if file_inner_path not in files_optional_node:
files_optional_node[file_inner_path] = file_details
# Find changed files # Find changed files
files_merged = files_node.copy() files_merged = files_node.copy()
files_merged.update(files_optional_node) files_merged.update(files_optional_node)
@ -547,7 +554,7 @@ class ContentManager(object):
elif "files_optional" in new_content: elif "files_optional" in new_content:
del new_content["files_optional"] del new_content["files_optional"]
new_content["modified"] = time.time() # Add timestamp new_content["modified"] = int(time.time()) # Add timestamp
if inner_path == "content.json": if inner_path == "content.json":
new_content["zeronet_version"] = config.version new_content["zeronet_version"] = config.version
new_content["signs_required"] = content.get("signs_required", 1) new_content["signs_required"] = content.get("signs_required", 1)
@ -768,8 +775,18 @@ class ContentManager(object):
del(new_content["sign"]) # The file signed without the sign del(new_content["sign"]) # The file signed without the sign
if "signs" in new_content: if "signs" in new_content:
del(new_content["signs"]) # The file signed without the signs del(new_content["signs"]) # The file signed without the signs
sign_content = json.dumps(new_content, sort_keys=True) # Dump the json to string to remove whitepsace sign_content = json.dumps(new_content, sort_keys=True) # Dump the json to string to remove whitepsace
# Fix float representation error on Android
modified = new_content["modified"]
if config.fix_float_decimals and type(modified) is float and not str(modified).endswith(".0"):
modified_fixed = "{:.6f}".format(modified).strip("0.")
sign_content = sign_content.replace(
'"modified": %s' % repr(modified),
'"modified": %s' % modified_fixed
)
if not self.verifyContent(inner_path, new_content): if not self.verifyContent(inner_path, new_content):
return False # Content not valid (files too large, invalid files) return False # Content not valid (files too large, invalid files)

View file

@ -70,13 +70,14 @@ class CryptConnectionManager:
return True # Files already exits return True # Files already exits
import subprocess import subprocess
cmd = "%s req -x509 -newkey rsa:2048 -sha256 -batch -keyout %s -out %s -nodes -config %s" % helper.shellquote(
self.openssl_bin,
config.data_dir+"/key-rsa.pem",
config.data_dir+"/cert-rsa.pem",
self.openssl_env["OPENSSL_CONF"]
)
proc = subprocess.Popen( proc = subprocess.Popen(
"%s req -x509 -newkey rsa:2048 -sha256 -batch -keyout %s -out %s -nodes -config %s" % helper.shellquote( cmd.encode(sys.getfilesystemencoding()),
self.openssl_bin,
config.data_dir+"/key-rsa.pem",
config.data_dir+"/cert-rsa.pem",
self.openssl_env["OPENSSL_CONF"]
),
shell=True, stderr=subprocess.STDOUT, stdout=subprocess.PIPE, env=self.openssl_env shell=True, stderr=subprocess.STDOUT, stdout=subprocess.PIPE, env=self.openssl_env
) )
back = proc.stdout.read().strip() back = proc.stdout.read().strip()

View file

@ -55,7 +55,7 @@ class Db(object):
self.log.debug("Created Db path: %s" % self.db_dir) self.log.debug("Created Db path: %s" % self.db_dir)
if not os.path.isfile(self.db_path): if not os.path.isfile(self.db_path):
self.log.debug("Db file not exist yet: %s" % self.db_path) self.log.debug("Db file not exist yet: %s" % self.db_path)
self.conn = sqlite3.connect(self.db_path) self.conn = sqlite3.connect(self.db_path, check_same_thread=False)
self.conn.row_factory = sqlite3.Row self.conn.row_factory = sqlite3.Row
self.conn.isolation_level = None self.conn.isolation_level = None
self.cur = self.getCursor() self.cur = self.getCursor()

View file

@ -40,9 +40,11 @@ class DebugReloader:
if ( if (
not evt.path or "%s/" % config.data_dir in evt.path or not evt.path or "%s/" % config.data_dir in evt.path or
(not evt.path.endswith("py") and not evt.path.endswith("json")) or (not evt.path.endswith("py") and not evt.path.endswith("json")) or
"Test" in evt.path or
time.time() - self.last_chaged < 1 time.time() - self.last_chaged < 1
): ):
return False # Ignore *.pyc changes and no reload within 1 sec return False # Ignore *.pyc changes and no reload within 1 sec
time.sleep(0.1) # Wait for lock release time.sleep(0.1) # Wait for lock release
logging.debug("Changed: %s, reloading source." % evt.path)
self.callback() self.callback()
self.last_chaged = time.time() self.last_chaged = time.time()

View file

@ -173,7 +173,9 @@ class FileRequest(object):
file.seek(params["location"]) file.seek(params["location"])
file.read_bytes = FILE_BUFF file.read_bytes = FILE_BUFF
file_size = os.fstat(file.fileno()).st_size file_size = os.fstat(file.fileno()).st_size
assert params["location"] <= file_size, "Bad file location" if params["location"] > file_size:
self.connection.badAction(5)
raise Exception("Bad file location")
back = { back = {
"body": file, "body": file,
@ -212,7 +214,9 @@ class FileRequest(object):
file.seek(params["location"]) file.seek(params["location"])
file_size = os.fstat(file.fileno()).st_size file_size = os.fstat(file.fileno()).st_size
stream_bytes = min(FILE_BUFF, file_size - params["location"]) stream_bytes = min(FILE_BUFF, file_size - params["location"])
assert stream_bytes >= 0, "Stream bytes out of range" if stream_bytes < 0:
self.connection.badAction(5)
raise Exception("Bad file location")
back = { back = {
"size": file_size, "size": file_size,

View file

@ -398,7 +398,7 @@ class Site(object):
gevent.joinall(content_threads) gevent.joinall(content_threads)
# Publish worker # Publish worker
def publisher(self, inner_path, peers, published, limit, event_done=None, diffs={}): def publisher(self, inner_path, peers, published, limit, diffs={}, event_done=None, cb_progress=None):
file_size = self.storage.getSize(inner_path) file_size = self.storage.getSize(inner_path)
content_json_modified = self.content_manager.contents[inner_path]["modified"] content_json_modified = self.content_manager.contents[inner_path]["modified"]
body = self.storage.read(inner_path) body = self.storage.read(inner_path)
@ -457,6 +457,8 @@ class Site(object):
if result and "ok" in result: if result and "ok" in result:
published.append(peer) published.append(peer)
if cb_progress and len(published) <= limit:
cb_progress(len(published), limit)
self.log.info("[OK] %s: %s %s/%s" % (peer.key, result["ok"], len(published), limit)) self.log.info("[OK] %s: %s %s/%s" % (peer.key, result["ok"], len(published), limit))
else: else:
if result == {"exception": "Timeout"}: if result == {"exception": "Timeout"}:
@ -466,7 +468,7 @@ class Site(object):
# Update content.json on peers # Update content.json on peers
@util.Noparallel() @util.Noparallel()
def publish(self, limit="default", inner_path="content.json", diffs={}): def publish(self, limit="default", inner_path="content.json", diffs={}, cb_progress=None):
published = [] # Successfully published (Peer) published = [] # Successfully published (Peer)
publishers = [] # Publisher threads publishers = [] # Publisher threads
@ -498,7 +500,7 @@ class Site(object):
event_done = gevent.event.AsyncResult() event_done = gevent.event.AsyncResult()
for i in range(min(len(peers), limit, threads)): for i in range(min(len(peers), limit, threads)):
publisher = gevent.spawn(self.publisher, inner_path, peers, published, limit, event_done, diffs) publisher = gevent.spawn(self.publisher, inner_path, peers, published, limit, diffs, event_done, cb_progress)
publishers.append(publisher) publishers.append(publisher)
event_done.get() # Wait for done event_done.get() # Wait for done
@ -522,7 +524,7 @@ class Site(object):
return len(published) return len(published)
# Copy this site # Copy this site
def clone(self, address, privatekey=None, address_index=None, overwrite=False): def clone(self, address, privatekey=None, address_index=None, root_inner_path="", overwrite=False):
import shutil import shutil
new_site = SiteManager.site_manager.need(address, all_file=False) new_site = SiteManager.site_manager.need(address, all_file=False)
default_dirs = [] # Dont copy these directories (has -default version) default_dirs = [] # Dont copy these directories (has -default version)
@ -530,16 +532,20 @@ class Site(object):
if "-default" in dir_name: if "-default" in dir_name:
default_dirs.append(dir_name.replace("-default", "")) default_dirs.append(dir_name.replace("-default", ""))
self.log.debug("Cloning to %s, ignore dirs: %s" % (address, default_dirs)) self.log.debug("Cloning to %s, ignore dirs: %s, root: %s" % (address, default_dirs, root_inner_path))
# Copy root content.json # Copy root content.json
if not new_site.storage.isFile("content.json") and not overwrite: if not new_site.storage.isFile("content.json") and not overwrite:
# Content.json not exist yet, create a new one from source site # Content.json not exist yet, create a new one from source site
content_json = self.storage.loadJson("content.json") if self.storage.isFile(root_inner_path + "/content.json-default"):
content_json = self.storage.loadJson(root_inner_path + "/content.json-default")
else:
content_json = self.storage.loadJson("content.json")
if "domain" in content_json: if "domain" in content_json:
del content_json["domain"] del content_json["domain"]
content_json["title"] = "my" + content_json["title"] content_json["title"] = "my" + content_json["title"]
content_json["cloned_from"] = self.address content_json["cloned_from"] = self.address
content_json["clone_root"] = root_inner_path
content_json["files"] = {} content_json["files"] = {}
if address_index: if address_index:
content_json["address_index"] = address_index # Site owner's BIP32 index content_json["address_index"] = address_index # Site owner's BIP32 index
@ -553,17 +559,29 @@ class Site(object):
for file_relative_path in sorted(content["files"].keys()): for file_relative_path in sorted(content["files"].keys()):
file_inner_path = helper.getDirname(content_inner_path) + file_relative_path # Relative to content.json file_inner_path = helper.getDirname(content_inner_path) + file_relative_path # Relative to content.json
file_inner_path = file_inner_path.strip("/") # Strip leading / file_inner_path = file_inner_path.strip("/") # Strip leading /
if not file_inner_path.startswith(root_inner_path):
self.log.debug("[SKIP] %s (not in clone root)" % file_inner_path)
continue
if file_inner_path.split("/")[0] in default_dirs: # Dont copy directories that has -default postfixed alternative if file_inner_path.split("/")[0] in default_dirs: # Dont copy directories that has -default postfixed alternative
self.log.debug("[SKIP] %s (has default alternative)" % file_inner_path) self.log.debug("[SKIP] %s (has default alternative)" % file_inner_path)
continue continue
file_path = self.storage.getPath(file_inner_path) file_path = self.storage.getPath(file_inner_path)
# Copy the file normally to keep the -default postfixed dir and file to allow cloning later # Copy the file normally to keep the -default postfixed dir and file to allow cloning later
file_path_dest = new_site.storage.getPath(file_inner_path) if root_inner_path:
file_inner_path_dest = re.sub("^%s/" % re.escape(root_inner_path), "", file_inner_path)
file_path_dest = new_site.storage.getPath(file_inner_path_dest)
else:
file_inner_path_dest = file_inner_path
file_path_dest = new_site.storage.getPath(file_inner_path)
self.log.debug("[COPY] %s to %s..." % (file_inner_path, file_path_dest)) self.log.debug("[COPY] %s to %s..." % (file_inner_path, file_path_dest))
dest_dir = os.path.dirname(file_path_dest) dest_dir = os.path.dirname(file_path_dest)
if not os.path.isdir(dest_dir): if not os.path.isdir(dest_dir):
os.makedirs(dest_dir) os.makedirs(dest_dir)
if file_inner_path_dest == "content.json-default": # Don't copy root content.json-default
continue
shutil.copy(file_path, file_path_dest) shutil.copy(file_path, file_path_dest)
# If -default in path, create a -default less copy of the file # If -default in path, create a -default less copy of the file

View file

@ -36,7 +36,13 @@ class SiteManager(object):
for address, settings in json.load(open("%s/sites.json" % config.data_dir)).iteritems(): for address, settings in json.load(open("%s/sites.json" % config.data_dir)).iteritems():
if address not in self.sites and os.path.isfile("%s/%s/content.json" % (config.data_dir, address)): if address not in self.sites and os.path.isfile("%s/%s/content.json" % (config.data_dir, address)):
s = time.time() s = time.time()
self.sites[address] = Site(address, settings=settings) try:
site = Site(address, settings=settings)
site.content_manager.contents.get("content.json")
except Exception, err:
self.log.debug("Error loading site %s: %s" % (address, err))
continue
self.sites[address] = site
self.log.debug("Loaded site %s in %.3fs" % (address, time.time() - s)) self.log.debug("Loaded site %s in %.3fs" % (address, time.time() - s))
added += 1 added += 1
address_found.append(address) address_found.append(address)

View file

@ -19,8 +19,8 @@ from Plugin import PluginManager
class SiteStorage(object): class SiteStorage(object):
def __init__(self, site, allow_create=True): def __init__(self, site, allow_create=True):
self.site = site self.site = site
self.directory = "%s/%s" % (config.data_dir, self.site.address) # Site data diretory self.directory = u"%s/%s" % (config.data_dir, self.site.address) # Site data diretory
self.allowed_dir = os.path.abspath(self.directory.decode(sys.getfilesystemencoding())) # Only serve file within this dir self.allowed_dir = os.path.abspath(self.directory) # Only serve file within this dir
self.log = site.log self.log = site.log
self.db = None # Db class self.db = None # Db class
self.db_checked = False # Checked db tables since startup self.db_checked = False # Checked db tables since startup
@ -166,8 +166,11 @@ class SiteStorage(object):
with open(file_path, "wb") as file: with open(file_path, "wb") as file:
shutil.copyfileobj(content, file) # Write buff to disk shutil.copyfileobj(content, file) # Write buff to disk
else: # Simple string else: # Simple string
with open(file_path, "wb") as file: if inner_path == "content.json" and os.path.isfile(file_path):
file.write(content) helper.atomicWrite(file_path, content)
else:
with open(file_path, "wb") as file:
file.write(content)
del content del content
self.onUpdated(inner_path) self.onUpdated(inner_path)
@ -275,11 +278,10 @@ class SiteStorage(object):
if not inner_path: if not inner_path:
return self.directory return self.directory
file_path = u"%s/%s" % (self.directory, inner_path) if ".." in inner_path:
raise Exception(u"File not allowed: %s" % inner_path)
if ".." in file_path: return u"%s/%s" % (self.directory, inner_path)
raise Exception(u"File not allowed: %s" % file_path)
return file_path
# Get site dir relative path # Get site dir relative path
def getInnerPath(self, path): def getInnerPath(self, path):
@ -415,8 +417,8 @@ class SiteStorage(object):
os.unlink(path) os.unlink(path)
break break
except Exception, err: except Exception, err:
self.log.error("Error removing %s: %s, try #%s" % (path, err, retry)) self.log.error("Error removing %s: %s, try #%s" % (path, err, retry))
time.sleep(float(retry)/10) time.sleep(float(retry) / 10)
self.onUpdated(inner_path, False) self.onUpdated(inner_path, False)
self.log.debug("Deleting empty dirs...") self.log.debug("Deleting empty dirs...")

View file

@ -108,10 +108,10 @@ class TestContent:
assert len(site.content_manager.hashfield) == 0 assert len(site.content_manager.hashfield) == 0
site.content_manager.contents["content.json"]["optional"] = "((data/img/zero.*))" site.content_manager.contents["content.json"]["optional"] = "((data/img/zero.*))"
content_optional = site.content_manager.sign(privatekey="5KUh3PvNm5HUWoCfSUfcYvfQ2g3PrRNJWr6Q9eqdBGu23mtMntv", filewrite=False) content_optional = site.content_manager.sign(privatekey="5KUh3PvNm5HUWoCfSUfcYvfQ2g3PrRNJWr6Q9eqdBGu23mtMntv", filewrite=False, remove_missing_optional=True)
del site.content_manager.contents["content.json"]["optional"] del site.content_manager.contents["content.json"]["optional"]
content_nooptional = site.content_manager.sign(privatekey="5KUh3PvNm5HUWoCfSUfcYvfQ2g3PrRNJWr6Q9eqdBGu23mtMntv", filewrite=False) content_nooptional = site.content_manager.sign(privatekey="5KUh3PvNm5HUWoCfSUfcYvfQ2g3PrRNJWr6Q9eqdBGu23mtMntv", filewrite=False, remove_missing_optional=True)
assert len(content_nooptional.get("files_optional", {})) == 0 # No optional files if no pattern assert len(content_nooptional.get("files_optional", {})) == 0 # No optional files if no pattern
assert len(content_optional["files_optional"]) > 0 assert len(content_optional["files_optional"]) > 0

View file

@ -1,10 +1,7 @@
import time import time
import gevent
from gevent import monkey
monkey.patch_all()
import util import util
import gevent
class ExampleClass(object): class ExampleClass(object):
def __init__(self): def __init__(self):

View file

@ -1,8 +1,6 @@
import time import time
import gevent import gevent
from gevent import monkey
monkey.patch_all()
from util import RateLimit from util import RateLimit

View file

@ -18,14 +18,14 @@ class WaitForPageLoad(object):
self.old_page = self.browser.find_element_by_tag_name('html') self.old_page = self.browser.find_element_by_tag_name('html')
def __exit__(self, *args): def __exit__(self, *args):
WebDriverWait(self.browser, 20).until(staleness_of(self.old_page)) WebDriverWait(self.browser, 5).until(staleness_of(self.old_page))
@pytest.mark.usefixtures("resetSettings") @pytest.mark.usefixtures("resetSettings")
@pytest.mark.webtest @pytest.mark.webtest
class TestWeb: class TestWeb:
def testFileSecurity(self, site_url): def testFileSecurity(self, site_url):
assert "Forbidden" in urllib.urlopen("%s/media/./sites.json" % site_url).read() assert "Not Found" in urllib.urlopen("%s/media/./sites.json" % site_url).read()
assert "Forbidden" in urllib.urlopen("%s/media/../config.py" % site_url).read() assert "Forbidden" in urllib.urlopen("%s/media/../config.py" % site_url).read()
assert "Forbidden" in urllib.urlopen("%s/media/1EU1tbG9oC1A8jz2ouVwGZyQ5asrNsE4Vr/../sites.json" % site_url).read() assert "Forbidden" in urllib.urlopen("%s/media/1EU1tbG9oC1A8jz2ouVwGZyQ5asrNsE4Vr/../sites.json" % site_url).read()
assert "Forbidden" in urllib.urlopen("%s/media/1EU1tbG9oC1A8jz2ouVwGZyQ5asrNsE4Vr/..//sites.json" % site_url).read() assert "Forbidden" in urllib.urlopen("%s/media/1EU1tbG9oC1A8jz2ouVwGZyQ5asrNsE4Vr/..//sites.json" % site_url).read()
@ -34,10 +34,6 @@ class TestWeb:
assert "Forbidden" in urllib.urlopen("%s/1EU1tbG9oC1A8jz2ouVwGZyQ5asrNsE4Vr/..//sites.json" % site_url).read() assert "Forbidden" in urllib.urlopen("%s/1EU1tbG9oC1A8jz2ouVwGZyQ5asrNsE4Vr/..//sites.json" % site_url).read()
assert "Forbidden" in urllib.urlopen("%s/1EU1tbG9oC1A8jz2ouVwGZyQ5asrNsE4Vr/../../zeronet.py" % site_url).read() assert "Forbidden" in urllib.urlopen("%s/1EU1tbG9oC1A8jz2ouVwGZyQ5asrNsE4Vr/../../zeronet.py" % site_url).read()
def testHomepage(self, browser, site_url):
browser.get("%s/1EU1tbG9oC1A8jz2ouVwGZyQ5asrNsE4Vr" % site_url)
assert browser.title == "ZeroHello - ZeroNet"
def testLinkSecurity(self, browser, site_url): def testLinkSecurity(self, browser, site_url):
browser.get("%s/1EU1tbG9oC1A8jz2ouVwGZyQ5asrNsE4Vr/test/security.html" % site_url) browser.get("%s/1EU1tbG9oC1A8jz2ouVwGZyQ5asrNsE4Vr/test/security.html" % site_url)
assert browser.title == "ZeroHello - ZeroNet" assert browser.title == "ZeroHello - ZeroNet"

View file

@ -49,7 +49,7 @@ if os.path.isfile("%s-temp/content.db" % config.data_dir):
import gevent import gevent
from gevent import monkey from gevent import monkey
monkey.patch_all(thread=False) monkey.patch_all(thread=False, subprocess=False)
from Site import Site from Site import Site
from Site import SiteManager from Site import SiteManager
@ -164,7 +164,9 @@ def user():
def browser(): def browser():
try: try:
from selenium import webdriver from selenium import webdriver
print "Starting phantomjs..."
browser = webdriver.PhantomJS(executable_path=PHANTOMJS_PATH, service_log_path=os.path.devnull) browser = webdriver.PhantomJS(executable_path=PHANTOMJS_PATH, service_log_path=os.path.devnull)
print "Set window size..."
browser.set_window_size(1400, 1000) browser.set_window_size(1400, 1000)
except Exception, err: except Exception, err:
raise pytest.skip("Test requires selenium + phantomjs: %s" % err) raise pytest.skip("Test requires selenium + phantomjs: %s" % err)

View file

@ -14,7 +14,10 @@ from Config import config
from Crypt import CryptRsa from Crypt import CryptRsa
from Site import SiteManager from Site import SiteManager
from lib.PySocks import socks from lib.PySocks import socks
from gevent.coros import RLock try:
from gevent.coros import RLock
except:
from gevent.lock import RLock
from util import helper from util import helper
from Debug import Debug from Debug import Debug
@ -75,7 +78,9 @@ class TorManager:
self.log.info("Starting Tor client %s..." % self.tor_exe) self.log.info("Starting Tor client %s..." % self.tor_exe)
tor_dir = os.path.dirname(self.tor_exe) tor_dir = os.path.dirname(self.tor_exe)
self.tor_process = subprocess.Popen(r"%s -f torrc" % self.tor_exe, cwd=tor_dir, close_fds=True) startupinfo = subprocess.STARTUPINFO()
startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
self.tor_process = subprocess.Popen(r"%s -f torrc" % self.tor_exe, cwd=tor_dir, close_fds=True, startupinfo=startupinfo)
for wait in range(1,10): # Wait for startup for wait in range(1,10): # Wait for startup
time.sleep(wait * 0.5) time.sleep(wait * 0.5)
self.enabled = True self.enabled = True
@ -152,22 +157,26 @@ class TorManager:
try: try:
with self.lock: with self.lock:
conn.connect((self.ip, self.port)) conn.connect((self.ip, self.port))
res_protocol = self.send("PROTOCOLINFO", conn)
version = re.search('Tor="([0-9\.]+)', res_protocol).group(1)
# Version 0.2.7.5 required because ADD_ONION support
assert float(version.replace(".", "0", 2)) >= 207.5, "Tor version >=0.2.7.5 required, found: %s" % version
# Auth cookie file # Auth cookie file
res_protocol = self.send("PROTOCOLINFO", conn)
cookie_match = re.search('COOKIEFILE="(.*?)"', res_protocol) cookie_match = re.search('COOKIEFILE="(.*?)"', res_protocol)
if cookie_match: if cookie_match:
cookie_file = cookie_match.group(1) cookie_file = cookie_match.group(1).decode("string-escape")
auth_hex = binascii.b2a_hex(open(cookie_file, "rb").read()) auth_hex = binascii.b2a_hex(open(cookie_file, "rb").read())
res_auth = self.send("AUTHENTICATE %s" % auth_hex, conn) res_auth = self.send("AUTHENTICATE %s" % auth_hex, conn)
elif config.tor_password:
res_auth = self.send('AUTHENTICATE "%s"' % config.tor_password, conn)
else: else:
res_auth = self.send("AUTHENTICATE", conn) res_auth = self.send("AUTHENTICATE", conn)
assert "250 OK" in res_auth, "Authenticate error %s" % res_auth assert "250 OK" in res_auth, "Authenticate error %s" % res_auth
# Version 0.2.7.5 required because ADD_ONION support
res_version = self.send("GETINFO version", conn)
version = re.search('version=([0-9\.]+)', res_version).group(1)
assert float(version.replace(".", "0", 2)) >= 207.5, "Tor version >=0.2.7.5 required, found: %s" % version
self.status = u"Connected (%s)" % res_auth self.status = u"Connected (%s)" % res_auth
self.conn = conn self.conn = conn
except Exception, err: except Exception, err:
@ -232,10 +241,12 @@ class TorManager:
if not conn: if not conn:
conn = self.conn conn = self.conn
self.log.debug("> %s" % cmd) self.log.debug("> %s" % cmd)
back = ""
for retry in range(2): for retry in range(2):
try: try:
conn.send("%s\r\n" % cmd) conn.sendall("%s\r\n" % cmd)
back = conn.recv(1024 * 64).decode("utf8", "ignore") while not back.endswith("250 OK\r\n"):
back += conn.recv(1024 * 64).decode("utf8", "ignore")
break break
except Exception, err: except Exception, err:
self.log.error("Tor send error: %s, reconnecting..." % err) self.log.error("Tor send error: %s, reconnecting..." % err)

View file

@ -0,0 +1,51 @@
{
"Congratulation, your port <b>{0}</b> is opened.<br>You are full member of ZeroNet network!": "¡Felicidades! tu puerto <b>{0}</b> está abierto.<br>¡Eres un miembro completo de la red Zeronet!",
"Tor mode active, every connection using Onion route.": "Modo Tor activado, cada conexión usa una ruta Onion.",
"Successfully started Tor onion hidden services.": "Tor ha iniciado satisfactoriamente la ocultación de los servicios onion.",
"Unable to start hidden services, please check your config.": "No se puedo iniciar los servicios ocultos, por favor comprueba tu configuración.",
"For faster connections open <b>{0}</b> port on your router.": "Para conexiones más rápidas abre el puerto <b>{0}</b> en tu router.",
"Your connection is restricted. Please, open <b>{0}</b> port on your router": "Tu conexión está limitada. Por favor, abre el puerto <b>{0}</b> en tu router",
"or configure Tor to become full member of ZeroNet network.": "o configura Tor para convertirte en un miembro completo de la red ZeroNet.",
"Select account you want to use in this site:": "Selecciona la cuenta que quieres utilizar en este sitio:",
"currently selected": "actualmente seleccionada",
"Unique to site": "Única para el sitio",
"Content signing failed": "Firma del contenido fallida",
"Content publish queued for {0:.0f} seconds.": "Publicación de contenido en cola durante {0:.0f} segundos.",
"Content published to {0} peers.": "Contenido publicado para {0} pares.",
"No peers found, but your content is ready to access.": "No se ha encontrado pares, pero tu contenido está listo para ser accedido.",
"Your network connection is restricted. Please, open <b>{0}</b> port": "Tu conexión de red está restringida. Por favor, abre el puerto<b>{0}</b>",
"on your router to make your site accessible for everyone.": "en tu router para hacer tu sitio accesible a todo el mundo.",
"Content publish failed.": "Publicación de contenido fallida.",
"This file still in sync, if you write it now, then the previous content may be lost.": "Este archivo está aún sincronizado, si le escribes ahora el contenido previo podría perderse.",
"Write content anyway": "Escribir el contenido de todas formas",
"New certificate added:": "Nuevo certificado añadido:",
"You current certificate:": "Tu certificado actual:",
"Change it to {auth_type}/{auth_user_name}@{domain}": "Cambia esto a {auth_type}/{auth_user_name}@{domain}",
"Certificate changed to: <b>{auth_type}/{auth_user_name}@{domain}</b>.": "Certificado cambiado a: <b>{auth_type}/{auth_user_name}@{domain}</b>.",
"Site cloned": "Sitio clonado",
"You have successfully changed the web interface's language!": "¡Has cambiado con éxito el idioma de la interfaz web!",
"Due to the browser's caching, the full transformation could take some minute.": "Debido a la caché del navegador, la transformación completa podría llevar unos minutos.",
"Connection with <b>UiServer Websocket</b> was lost. Reconnecting...": "Se perdió la conexión con <b>UiServer Websocket</b>. Reconectando...",
"Connection with <b>UiServer Websocket</b> recovered.": "Conexión con <b>UiServer Websocket</b> recuperada.",
"UiServer Websocket error, please reload the page.": "Error de UiServer Websocket, por favor recarga la página.",
"&nbsp;&nbsp;&nbsp;Connecting...": "&nbsp;&nbsp;&nbsp;Conectando...",
"Site size: <b>": "Tamaño del sitio: <b>",
"MB</b> is larger than default allowed ": "MB</b> es más grande de lo permitido por defecto",
"Open site and set size limit to \" + site_info.next_size_limit + \"MB": "Abre tu sitio and establece el límite de tamaño a \" + site_info.next_size_limit + \"MBs",
" files needs to be downloaded": " Los archivos necesitan ser descargados",
" downloaded": " descargados",
" download failed": " descarga fallida",
"Peers found: ": "Pares encontrados: ",
"No peers found": "No se han encontrado pares",
"Running out of size limit (": "Superando el tamaño límite (",
"Set limit to \" + site_info.next_size_limit + \"MB": "Establece ellímite a \" + site_info.next_size_limit + \"MB ändern",
"Site size limit changed to {0}MB": "Límite de tamaño del sitio cambiado a {0}MBs",
" New version of this page has just released.<br>Reload to see the modified content.": " Se ha publicado una nueva versión de esta página .<br>Recarga para ver el contenido modificado.",
"This site requests permission:": "Este sitio solicita permiso:",
"Grant": "Conceder"
}

View file

@ -0,0 +1,51 @@
{
"Congratulation, your port <b>{0}</b> is opened.<br>You are full member of ZeroNet network!": "Gratulacje, twój port <b>{0}</b> jest otwarty.<br>Jesteś pełnoprawnym użytkownikiem sieci ZeroNet!",
"Tor mode active, every connection using Onion route.": "Tryb Tor aktywny, każde połączenie przy użyciu trasy Cebulowej.",
"Successfully started Tor onion hidden services.": "Pomyślnie zainicjowano ukryte usługi cebulowe Tor.",
"Unable to start hidden services, please check your config.": "Niezdolny do uruchomienia ukrytych usług, proszę sprawdź swoją konfigurację.",
"For faster connections open <b>{0}</b> port on your router.": "Dla szybszego połączenia otwórz <b>{0}</b> port w swoim routerze.",
"Your connection is restricted. Please, open <b>{0}</b> port on your router": "Połączenie jest ograniczone. Proszę, otwórz port <b>{0}</b> w swoim routerze",
"or configure Tor to become full member of ZeroNet network.": "bądź skonfiguruj Tora by stać się pełnoprawnym użytkownikiem sieci ZeroNet.",
"Select account you want to use in this site:": "Wybierz konto którego chcesz użyć na tej stronie:",
"currently selected": "aktualnie wybrany",
"Unique to site": "Unikatowy dla strony",
"Content signing failed": "Podpisanie treści zawiodło",
"Content publish queued for {0:.0f} seconds.": "Publikacja treści wstrzymana na {0:.0f} sekund(y).",
"Content published to {0} peers.": "Treść opublikowana do {0} uzytkowników równorzednych.",
"No peers found, but your content is ready to access.": "Nie odnaleziono użytkowników równorzędnych, ale twoja treść jest dostępna.",
"Your network connection is restricted. Please, open <b>{0}</b> port": "Twoje połączenie sieciowe jest ograniczone. Proszę, otwórz port <b>{0}</b>",
"on your router to make your site accessible for everyone.": "w swoim routerze, by twoja strona mogłabyć dostępna dla wszystkich.",
"Content publish failed.": "Publikacja treści zawiodła.",
"This file still in sync, if you write it now, then the previous content may be lost.": "Ten plik wciąż się synchronizuje, jeśli zapiszesz go teraz, poprzednia treść może zostać utracona.",
"Write content anyway": "Zapisz treść mimo wszystko",
"New certificate added:": "Nowy certyfikat dodany:",
"You current certificate:": "Twój aktualny certyfikat: ",
"Change it to {auth_type}/{auth_user_name}@{domain}": "Zmień na {auth_type}/{auth_user_name}@{domain}-ra",
"Certificate changed to: <b>{auth_type}/{auth_user_name}@{domain}</b>.": "Certyfikat zmieniony na <b>{auth_type}/{auth_user_name}@{domain}</b>-ra.",
"Site cloned": "Strona sklonowana",
"You have successfully changed the web interface's language!": "Pomyślnie zmieniono język interfejsu stron!",
"Due to the browser's caching, the full transformation could take some minute.": "Ze względu na buforowanie przeglądarki, pełna zmiana może zająć parę minutę.",
"Connection with <b>UiServer Websocket</b> was lost. Reconnecting...": "Połączenie z <b>UiServer Websocket</b> zostało przerwane. Ponowne łączenie...",
"Connection with <b>UiServer Websocket</b> recovered.": "Połączenie z <b>UiServer Websocket</b> przywrócone.",
"UiServer Websocket error, please reload the page.": "Błąd UiServer Websocket, prosze odświeżyć stronę.",
"&nbsp;&nbsp;&nbsp;Connecting...": "&nbsp;&nbsp;&nbsp;Łączenie...",
"Site size: <b>": "Rozmiar strony: <b>",
"MB</b> is larger than default allowed ": "MB</b> jest większy niż domyślnie dozwolony ",
"Open site and set size limit to \" + site_info.next_size_limit + \"MB": "Otwórz stronę i ustaw limit na \" + site_info.next_size_limit + \"MBów",
" files needs to be downloaded": " pliki muszą zostać ściągnięte",
" downloaded": " ściągnięte",
" download failed": " ściąganie nie powiodło się",
"Peers found: ": "Odnaleziono użytkowników równorzednych: ",
"No peers found": "Nie odnaleziono użytkowników równorzędnych",
"Running out of size limit (": "Limit rozmiaru na wyczerpaniu (",
"Set limit to \" + site_info.next_size_limit + \"MB": "Ustaw limit na \" + site_info.next_size_limit + \"MBów",
"Site size limit changed to {0}MB": "Rozmiar limitu strony zmieniony na {0}MBów",
" New version of this page has just released.<br>Reload to see the modified content.": "Nowa wersja tej strony właśnie została wydana.<br>Odśwież by zobaczyć nową, zmodyfikowaną treść strony.",
"This site requests permission:": "Ta strona wymaga uprawnień:",
"Grant": "Przyznaj uprawnienia"
}

View file

@ -49,7 +49,10 @@ class UiRequest(object):
path = re.sub("^http://", "/", path) # Remove begining http for chrome extension .bit access path = re.sub("^http://", "/", path) # Remove begining http for chrome extension .bit access
if self.env["REQUEST_METHOD"] == "OPTIONS": if self.env["REQUEST_METHOD"] == "OPTIONS":
content_type = self.getContentType(path) if "/" not in path.strip("/"):
content_type = self.getContentType("index.html")
else:
content_type = self.getContentType(path)
self.sendHeader(content_type=content_type) self.sendHeader(content_type=content_type)
return "" return ""
@ -64,8 +67,6 @@ class UiRequest(object):
# uimedia within site dir (for chrome extension) # uimedia within site dir (for chrome extension)
path = re.sub(".*?/uimedia/", "/uimedia/", path) path = re.sub(".*?/uimedia/", "/uimedia/", path)
return self.actionUiMedia(path) return self.actionUiMedia(path)
elif path.startswith("/media"):
return self.actionSiteMedia(path)
# Websocket # Websocket
elif path == "/Websocket": elif path == "/Websocket":
return self.actionWebsocket() return self.actionWebsocket()
@ -93,14 +94,21 @@ class UiRequest(object):
def isProxyRequest(self): def isProxyRequest(self):
return self.env["PATH_INFO"].startswith("http://") return self.env["PATH_INFO"].startswith("http://")
def isWebSocketRequest(self):
return self.env.get("HTTP_UPGRADE") == "websocket"
def isAjaxRequest(self): def isAjaxRequest(self):
return self.env.get("HTTP_X_REQUESTED_WITH") == "XMLHttpRequest" return self.env.get("HTTP_X_REQUESTED_WITH") == "XMLHttpRequest"
# Get mime by filename # Get mime by filename
def getContentType(self, file_name): def getContentType(self, file_name):
content_type = mimetypes.guess_type(file_name)[0] content_type = mimetypes.guess_type(file_name)[0]
if file_name.endswith(".css"): # Force correct css content type
content_type = "text/css"
if not content_type: if not content_type:
if file_name.endswith("json"): # Correct json header if file_name.endswith(".json"): # Correct json header
content_type = "application/json" content_type = "application/json"
else: else:
content_type = "application/octet-stream" content_type = "application/octet-stream"
@ -140,6 +148,7 @@ class UiRequest(object):
headers.append(("Keep-Alive", "max=25, timeout=30")) headers.append(("Keep-Alive", "max=25, timeout=30"))
if content_type != "text/html": if content_type != "text/html":
headers.append(("Access-Control-Allow-Origin", "*")) # Allow json access on non-html files headers.append(("Access-Control-Allow-Origin", "*")) # Allow json access on non-html files
headers.append(("X-Frame-Options", "SAMEORIGIN"))
# headers.append(("Content-Security-Policy", "default-src 'self' data: 'unsafe-inline' ws://127.0.0.1:* http://127.0.0.1:* wss://tracker.webtorrent.io; sandbox allow-same-origin allow-top-navigation allow-scripts")) # Only local connections # headers.append(("Content-Security-Policy", "default-src 'self' data: 'unsafe-inline' ws://127.0.0.1:* http://127.0.0.1:* wss://tracker.webtorrent.io; sandbox allow-same-origin allow-top-navigation allow-scripts")) # Only local connections
if self.env["REQUEST_METHOD"] == "OPTIONS": if self.env["REQUEST_METHOD"] == "OPTIONS":
# Allow json access # Allow json access
@ -191,6 +200,14 @@ class UiRequest(object):
if self.isAjaxRequest(): if self.isAjaxRequest():
return self.error403("Ajax request not allowed to load wrapper") # No ajax allowed on wrapper return self.error403("Ajax request not allowed to load wrapper") # No ajax allowed on wrapper
if self.isWebSocketRequest():
return self.error403("WebSocket request not allowed to load wrapper") # No websocket
if "text/html" not in self.env.get("HTTP_ACCEPT", ""):
return self.error403("Invalid Accept header to load wrapper")
if "prefetch" in self.env.get("HTTP_X_MOZ", "") or "prefetch" in self.env.get("HTTP_PURPOSE", ""):
return self.error403("Prefetch not allowed to load wrapper")
site = SiteManager.site_manager.get(address) site = SiteManager.site_manager.get(address)
if ( if (
@ -339,14 +356,8 @@ class UiRequest(object):
if path_parts: # Looks like a valid path if path_parts: # Looks like a valid path
address = path_parts["address"] address = path_parts["address"]
file_path = "%s/%s/%s" % (config.data_dir, address, path_parts["inner_path"]) file_path = "%s/%s/%s" % (config.data_dir, address, path_parts["inner_path"])
allowed_dir = os.path.abspath("%s/%s" % (config.data_dir, address)) # Only files within data/sitehash allowed if ".." in path_parts["inner_path"]: # File not in allowed path
data_dir = os.path.abspath(config.data_dir) # No files from data/ allowed return self.error403("Invalid file path")
if (
".." in file_path or
not os.path.dirname(os.path.abspath(file_path)).startswith(allowed_dir) or
allowed_dir == data_dir
): # File not in allowed path
return self.error403()
else: else:
if config.debug and file_path.split("/")[-1].startswith("all."): if config.debug and file_path.split("/")[-1].startswith("all."):
# If debugging merge *.css to all.css and *.js to all.js # If debugging merge *.css to all.css and *.js to all.js
@ -359,7 +370,10 @@ class UiRequest(object):
elif os.path.isdir(file_path): # If this is actually a folder, add "/" and redirect elif os.path.isdir(file_path): # If this is actually a folder, add "/" and redirect
return self.actionRedirect("./{0}/".format(path_parts["inner_path"].split("/")[-1])) return self.actionRedirect("./{0}/".format(path_parts["inner_path"].split("/")[-1]))
else: # File not exists, try to download else: # File not exists, try to download
site = SiteManager.site_manager.need(address, all_file=False) if address not in SiteManager.site_manager.sites: # Only in case if site already started downloading
return self.error404(path_parts["inner_path"])
site = SiteManager.site_manager.need(address)
if path_parts["inner_path"].endswith("favicon.ico"): # Default favicon for all sites if path_parts["inner_path"].endswith("favicon.ico"): # Default favicon for all sites
return self.actionFile("src/Ui/media/img/favicon.ico") return self.actionFile("src/Ui/media/img/favicon.ico")

View file

@ -3,6 +3,7 @@ import time
import cgi import cgi
import socket import socket
import sys import sys
import gevent
from gevent.pywsgi import WSGIServer from gevent.pywsgi import WSGIServer
from gevent.pywsgi import WSGIHandler from gevent.pywsgi import WSGIHandler
@ -121,7 +122,8 @@ class UiServer:
browser = webbrowser.get() browser = webbrowser.get()
else: else:
browser = webbrowser.get(config.open_browser) browser = webbrowser.get(config.open_browser)
browser.open("http://%s:%s/%s" % (config.ui_ip if config.ui_ip != "*" else "127.0.0.1", config.ui_port, config.homepage), new=2) url = "http://%s:%s/%s" % (config.ui_ip if config.ui_ip != "*" else "127.0.0.1", config.ui_port, config.homepage)
gevent.spawn_later(0.3, browser.open, url, new=2)
self.server = WSGIServer((self.ip.replace("*", ""), self.port), handler, handler_class=UiWSGIHandler, log=self.log) self.server = WSGIServer((self.ip.replace("*", ""), self.port), handler, handler_class=UiWSGIHandler, log=self.log)
self.server.sockets = {} self.server.sockets = {}

View file

@ -32,6 +32,12 @@ class UiWebsocket(object):
self.channels = [] # Channels joined to self.channels = [] # Channels joined to
self.sending = False # Currently sending to client self.sending = False # Currently sending to client
self.send_queue = [] # Messages to send to client self.send_queue = [] # Messages to send to client
self.admin_commands = (
"sitePause", "siteResume", "siteDelete", "siteList", "siteSetLimit", "siteClone",
"channelJoinAllsite", "serverUpdate", "serverPortcheck", "serverShutdown", "certSet", "configSet",
"actionPermissionAdd", "actionPermissionRemove"
)
self.async_commands = ("fileGet", "fileList")
# Start listener loop # Start listener loop
def start(self): def start(self):
@ -152,6 +158,11 @@ class UiWebsocket(object):
permissions.append("ADMIN") permissions.append("ADMIN")
return permissions return permissions
def asyncWrapper(self, func):
def wrapper(*args, **kwargs):
gevent.spawn(func, *args, **kwargs)
return wrapper
# Handle incoming messages # Handle incoming messages
def handleRequest(self, data): def handleRequest(self, data):
req = json.loads(data) req = json.loads(data)
@ -160,16 +171,10 @@ class UiWebsocket(object):
params = req.get("params") params = req.get("params")
self.permissions = self.getPermissions(req["id"]) self.permissions = self.getPermissions(req["id"])
admin_commands = (
"sitePause", "siteResume", "siteDelete", "siteList", "siteSetLimit", "siteClone",
"channelJoinAllsite", "serverUpdate", "serverPortcheck", "serverShutdown", "certSet", "configSet",
"actionPermissionAdd", "actionPermissionRemove"
)
if cmd == "response": # It's a response to a command if cmd == "response": # It's a response to a command
return self.actionResponse(req["to"], req["result"]) return self.actionResponse(req["to"], req["result"])
elif cmd in admin_commands and "ADMIN" not in self.permissions: # Admin commands elif cmd in self.admin_commands and "ADMIN" not in self.permissions: # Admin commands
return self.response(req["id"], {"error:", "You don't have permission to run %s" % cmd}) return self.response(req["id"], {"error": "You don't have permission to run %s" % cmd})
else: # Normal command else: # Normal command
func_name = "action" + cmd[0].upper() + cmd[1:] func_name = "action" + cmd[0].upper() + cmd[1:]
func = getattr(self, func_name, None) func = getattr(self, func_name, None)
@ -177,6 +182,10 @@ class UiWebsocket(object):
self.response(req["id"], {"error": "Unknown command: %s" % cmd}) self.response(req["id"], {"error": "Unknown command: %s" % cmd})
return return
# Execute in parallel
if cmd in self.async_commands:
func = self.asyncWrapper(func)
# Support calling as named, unnamed parameters and raw first argument too # Support calling as named, unnamed parameters and raw first argument too
if type(params) is dict: if type(params) is dict:
func(req["id"], **params) func(req["id"], **params)
@ -278,7 +287,7 @@ class UiWebsocket(object):
self.response(to, ret) self.response(to, ret)
# Sign content.json # Sign content.json
def actionSiteSign(self, to, privatekey=None, inner_path="content.json", response_ok=True, update_changed_files=False): def actionSiteSign(self, to, privatekey=None, inner_path="content.json", response_ok=True, update_changed_files=False, remove_missing_optional=False):
self.log.debug("Signing: %s" % inner_path) self.log.debug("Signing: %s" % inner_path)
site = self.site site = self.site
extend = {} # Extended info for signing extend = {} # Extended info for signing
@ -311,7 +320,7 @@ class UiWebsocket(object):
# Reload content.json, ignore errors to make it up-to-date # Reload content.json, ignore errors to make it up-to-date
site.content_manager.loadContent(inner_path, add_bad_files=False, force=True) site.content_manager.loadContent(inner_path, add_bad_files=False, force=True)
# Sign using private key sent by user # Sign using private key sent by user
signed = site.content_manager.sign(inner_path, privatekey, extend=extend, update_changed_files=update_changed_files) signed = site.content_manager.sign(inner_path, privatekey, extend=extend, update_changed_files=update_changed_files, remove_missing_optional=remove_missing_optional)
if not signed: if not signed:
self.cmd("notification", ["error", _["Content signing failed"]]) self.cmd("notification", ["error", _["Content signing failed"]])
self.response(to, {"error": "Site sign failed"}) self.response(to, {"error": "Site sign failed"})
@ -346,6 +355,7 @@ class UiWebsocket(object):
thread.linked = True thread.linked = True
if called_instantly: # Allowed to call instantly if called_instantly: # Allowed to call instantly
# At the end callback with request id and thread # At the end callback with request id and thread
self.cmd("progress", ["publish", _["Content published to {0}/{1} peers."].format(0, 5), 0])
thread.link(lambda thread: self.cbSitePublish(to, self.site, thread, notification, callback=notification)) thread.link(lambda thread: self.cbSitePublish(to, self.site, thread, notification, callback=notification))
else: else:
self.cmd( self.cmd(
@ -357,15 +367,27 @@ class UiWebsocket(object):
thread.link(lambda thread: self.cbSitePublish(to, self.site, thread, notification, callback=False)) thread.link(lambda thread: self.cbSitePublish(to, self.site, thread, notification, callback=False))
def doSitePublish(self, site, inner_path): def doSitePublish(self, site, inner_path):
def cbProgress(published, limit):
progress = int(float(published) / limit * 100)
self.cmd("progress", [
"publish",
_["Content published to {0}/{1} peers."].format(published, limit),
progress
])
diffs = site.content_manager.getDiffs(inner_path) diffs = site.content_manager.getDiffs(inner_path)
return site.publish(limit=5, inner_path=inner_path, diffs=diffs) back = site.publish(limit=5, inner_path=inner_path, diffs=diffs, cb_progress=cbProgress)
if back == 0: # Failed to publish to anyone
self.cmd("progress", ["publish", _["Content publish failed."], -100])
else:
cbProgress(back, back)
return back
# Callback of site publish # Callback of site publish
def cbSitePublish(self, to, site, thread, notification=True, callback=True): def cbSitePublish(self, to, site, thread, notification=True, callback=True):
published = thread.value published = thread.value
if published > 0: # Successfully published if published > 0: # Successfully published
if notification: if notification:
self.cmd("notification", ["done", _["Content published to {0} peers."].format(published), 5000]) # self.cmd("notification", ["done", _["Content published to {0} peers."].format(published), 5000])
site.updateWebsocket() # Send updated site data to local websocket clients site.updateWebsocket() # Send updated site data to local websocket clients
if callback: if callback:
self.response(to, "ok") self.response(to, "ok")
@ -388,7 +410,6 @@ class UiWebsocket(object):
else: else:
if notification: if notification:
self.cmd("notification", ["error", _["Content publish failed."]])
self.response(to, {"error": "Content publish failed."}) self.response(to, {"error": "Content publish failed."})
# Write a file to disk # Write a file to disk
@ -448,6 +469,16 @@ class UiWebsocket(object):
): ):
return self.response(to, {"error": "Forbidden, you can only modify your own files"}) return self.response(to, {"error": "Forbidden, you can only modify your own files"})
file_info = self.site.content_manager.getFileInfo(inner_path)
if file_info.get("optional"):
self.log.debug("Deleting optional file: %s" % inner_path)
relative_path = file_info["relative_path"]
content_json = self.site.storage.loadJson(file_info["content_inner_path"])
if relative_path in content_json.get("files_optional", {}):
del content_json["files_optional"][relative_path]
self.site.storage.writeJson(file_info["content_inner_path"], content_json)
self.site.content_manager.loadContent(file_info["content_inner_path"], add_bad_files=False, force=True)
try: try:
self.site.storage.delete(inner_path) self.site.storage.delete(inner_path)
except Exception, err: except Exception, err:
@ -468,6 +499,10 @@ class UiWebsocket(object):
# self.log.debug("FileQuery %s %s done in %s" % (dir_inner_path, query, time.time()-s)) # self.log.debug("FileQuery %s %s done in %s" % (dir_inner_path, query, time.time()-s))
return self.response(to, rows) return self.response(to, rows)
# List files in directory
def actionFileList(self, to, inner_path):
return self.response(to, list(self.site.storage.list(inner_path)))
# Sql query # Sql query
def actionDbQuery(self, to, query, params=None, wait_for=None): def actionDbQuery(self, to, query, params=None, wait_for=None):
if config.debug: if config.debug:
@ -487,14 +522,18 @@ class UiWebsocket(object):
return self.response(to, rows) return self.response(to, rows)
# Return file content # Return file content
def actionFileGet(self, to, inner_path, required=True): def actionFileGet(self, to, inner_path, required=True, format="text", timeout=300):
try: try:
if required or inner_path in self.site.bad_files: if required or inner_path in self.site.bad_files:
self.site.needFile(inner_path, priority=6) with gevent.Timeout(timeout):
self.site.needFile(inner_path, priority=6)
body = self.site.storage.read(inner_path) body = self.site.storage.read(inner_path)
except Exception, err: except Exception, err:
self.log.debug("%s fileGet error: %s" % (inner_path, err)) self.log.debug("%s fileGet error: %s" % (inner_path, err))
body = None body = None
if body and format == "base64":
import base64
body = base64.b64encode(body)
return self.response(to, body) return self.response(to, body)
def actionFileRules(self, to, inner_path): def actionFileRules(self, to, inner_path):
@ -514,13 +553,13 @@ class UiWebsocket(object):
if res is True: if res is True:
self.cmd( self.cmd(
"notification", "notification",
["done", _("{_[New certificate added:]} <b>{auth_type}/{auth_user_name}@{domain}</b>.")] ["done", _("{_[New certificate added]:} <b>{auth_type}/{auth_user_name}@{domain}</b>.")]
) )
self.response(to, "ok") self.response(to, "ok")
elif res is False: elif res is False:
# Display confirmation of change # Display confirmation of change
cert_current = self.user.certs[domain] cert_current = self.user.certs[domain]
body = _("{_[You current certificate:]} <b>{cert_current[auth_type]}/{cert_current[auth_user_name]}@{domain}</b>") body = _("{_[Your current certificate]:} <b>{cert_current[auth_type]}/{cert_current[auth_user_name]}@{domain}</b>")
self.cmd( self.cmd(
"confirm", "confirm",
[body, _("Change it to {auth_type}/{auth_user_name}@{domain}")], [body, _("Change it to {auth_type}/{auth_user_name}@{domain}")],
@ -599,11 +638,13 @@ class UiWebsocket(object):
if permission not in self.site.settings["permissions"]: if permission not in self.site.settings["permissions"]:
self.site.settings["permissions"].append(permission) self.site.settings["permissions"].append(permission)
self.site.saveSettings() self.site.saveSettings()
self.site.updateWebsocket(permission_added=permission)
self.response(to, "ok") self.response(to, "ok")
def actionPermissionRemove(self, to, permission): def actionPermissionRemove(self, to, permission):
self.site.settings["permissions"].remove(permission) self.site.settings["permissions"].remove(permission)
self.site.saveSettings() self.site.saveSettings()
self.site.updateWebsocket(permission_removed=permission)
self.response(to, "ok") self.response(to, "ok")
# Set certificate that used for authenticate user for site # Set certificate that used for authenticate user for site
@ -681,12 +722,12 @@ class UiWebsocket(object):
else: else:
self.response(to, {"error": "Unknown site: %s" % address}) self.response(to, {"error": "Unknown site: %s" % address})
def actionSiteClone(self, to, address): def actionSiteClone(self, to, address, root_inner_path=""):
self.cmd("notification", ["info", "Cloning site..."]) self.cmd("notification", ["info", "Cloning site..."])
site = self.server.sites.get(address) site = self.server.sites.get(address)
# Generate a new site from user's bip32 seed # Generate a new site from user's bip32 seed
new_address, new_address_index, new_site_data = self.user.getNewSiteData() new_address, new_address_index, new_site_data = self.user.getNewSiteData()
new_site = site.clone(new_address, new_site_data["privatekey"], address_index=new_address_index) new_site = site.clone(new_address, new_site_data["privatekey"], address_index=new_address_index, root_inner_path=root_inner_path)
new_site.settings["own"] = True new_site.settings["own"] = True
new_site.saveSettings() new_site.saveSettings()
self.cmd("notification", ["done", _["Site cloned"] + "<script>window.top.location = '/%s'</script>" % new_address]) self.cmd("notification", ["done", _["Site cloned"] + "<script>window.top.location = '/%s'</script>" % new_address])

View file

@ -21,12 +21,16 @@ class Notifications
# Create element # Create element
elem = $(".notification.template", @elem).clone().removeClass("template") elem = $(".notification.template", @elem).clone().removeClass("template")
elem.addClass("notification-#{type}").addClass("notification-#{id}") elem.addClass("notification-#{type}").addClass("notification-#{id}")
if type == "progress"
elem.addClass("notification-done")
# Update text # Update text
if type == "error" if type == "error"
$(".notification-icon", elem).html("!") $(".notification-icon", elem).html("!")
else if type == "done" else if type == "done"
$(".notification-icon", elem).html("<div class='icon-success'></div>") $(".notification-icon", elem).html("<div class='icon-success'></div>")
else if type == "progress"
$(".notification-icon", elem).html("<div class='icon-success'></div>")
else if type == "ask" else if type == "ask"
$(".notification-icon", elem).html("?") $(".notification-icon", elem).html("?")
else else
@ -64,6 +68,8 @@ class Notifications
$(".select", elem).on "click", => $(".select", elem).on "click", =>
@close elem @close elem
return elem
close: (elem) -> close: (elem) ->
elem.stop().animate {"width": 0, "opacity": 0}, 700, "easeInOutCubic" elem.stop().animate {"width": 0, "opacity": 0}, 700, "easeInOutCubic"

View file

@ -53,6 +53,8 @@ class Wrapper
if "-" in message.params[0] # - in first param: message id defined if "-" in message.params[0] # - in first param: message id defined
[id, type] = message.params[0].split("-") [id, type] = message.params[0].split("-")
@notifications.add(id, type, message.params[1], message.params[2]) @notifications.add(id, type, message.params[1], message.params[2])
else if cmd == "progress" # Display notification
@actionProgress(message)
else if cmd == "prompt" # Prompt input else if cmd == "prompt" # Prompt input
@displayPrompt message.params[0], message.params[1], message.params[2], (res) => @displayPrompt message.params[0], message.params[1], message.params[2], (res) =>
@ws.response message.id, res @ws.response message.id, res
@ -109,6 +111,8 @@ class Wrapper
@actionConfirm(message) @actionConfirm(message)
else if cmd == "wrapperPrompt" # Prompt input else if cmd == "wrapperPrompt" # Prompt input
@actionPrompt(message) @actionPrompt(message)
else if cmd == "wrapperProgress" # Progress bar
@actionProgress(message)
else if cmd == "wrapperSetViewport" # Set the viewport else if cmd == "wrapperSetViewport" # Set the viewport
@actionSetViewport(message) @actionSetViewport(message)
else if cmd == "wrapperSetTitle" else if cmd == "wrapperSetTitle"
@ -131,6 +135,8 @@ class Wrapper
@actionOpenWindow(message.params) @actionOpenWindow(message.params)
else if cmd == "wrapperPermissionAdd" else if cmd == "wrapperPermissionAdd"
@actionPermissionAdd(message) @actionPermissionAdd(message)
else if cmd == "wrapperRequestFullscreen"
@actionRequestFullscreen()
else # Send to websocket else # Send to websocket
if message.id < 1000000 if message.id < 1000000
@ws.send(message) # Pass message to websocket @ws.send(message) # Pass message to websocket
@ -141,7 +147,7 @@ class Wrapper
if query == null if query == null
query = window.location.search query = window.location.search
back = window.location.pathname back = window.location.pathname
if back.slice(-1) != "/" if back.match /^\/[^\/]+$/ # Add / after site address if called without it
back += "/" back += "/"
if query.replace("?", "") if query.replace("?", "")
back += "?"+query.replace("?", "") back += "?"+query.replace("?", "")
@ -168,6 +174,21 @@ class Wrapper
w.opener = null w.opener = null
w.location = params[0] w.location = params[0]
actionRequestFullscreen: ->
if "Fullscreen" in @site_info.settings.permissions
elem = document.getElementById("inner-iframe")
request_fullscreen = elem.requestFullScreen || elem.webkitRequestFullscreen || elem.mozRequestFullScreen || elem.msRequestFullScreen
request_fullscreen.call(elem)
setTimeout ( =>
if window.innerHeight != screen.height # Fullscreen failed, probably only allowed on click
@displayConfirm "This site requests permission:" + " <b>Fullscreen</b>", "Grant", =>
request_fullscreen.call(elem)
), 100
else
@displayConfirm "This site requests permission:" + " <b>Fullscreen</b>", "Grant", =>
@site_info.settings.permissions.push("Fullscreen")
@actionRequestFullscreen()
@ws.cmd "permissionAdd", "Fullscreen"
actionPermissionAdd: (message) -> actionPermissionAdd: (message) ->
permission = message.params permission = message.params
@ -180,8 +201,6 @@ class Wrapper
body = $("<span class='message'>"+message.params[1]+"</span>") body = $("<span class='message'>"+message.params[1]+"</span>")
@notifications.add("notification-#{message.id}", message.params[0], body, message.params[2]) @notifications.add("notification-#{message.id}", message.params[0], body, message.params[2])
displayConfirm: (message, caption, cb) -> displayConfirm: (message, caption, cb) ->
body = $("<span class='message'>"+message+"</span>") body = $("<span class='message'>"+message+"</span>")
button = $("<a href='##{caption}' class='button button-#{caption}'>#{caption}</a>") # Add confirm button button = $("<a href='##{caption}' class='button button-#{caption}'>#{caption}</a>") # Add confirm button
@ -232,6 +251,49 @@ class Wrapper
@displayPrompt message.params[0], type, caption, (res) => @displayPrompt message.params[0], type, caption, (res) =>
@sendInner {"cmd": "response", "to": message.id, "result": res} # Response to confirm @sendInner {"cmd": "response", "to": message.id, "result": res} # Response to confirm
actionProgress: (message) ->
message.params = @toHtmlSafe(message.params) # Escape html
percent = Math.min(100, message.params[2])/100
offset = 75-(percent*75)
circle = """
<div class="circle"><svg class="circle-svg" width="30" height="30" viewport="0 0 30 30" version="1.1" xmlns="http://www.w3.org/2000/svg">
<circle r="12" cx="15" cy="15" fill="transparent" class="circle-bg"></circle>
<circle r="12" cx="15" cy="15" fill="transparent" class="circle-fg" style="stroke-dashoffset: #{offset}"></circle>
</svg></div>
"""
body = "<span class='message'>"+message.params[1]+"</span>" + circle
elem = $(".notification-#{message.params[0]}")
if elem.length
width = $(".body .message", elem).outerWidth()
$(".body .message", elem).html(message.params[1])
if $(".body .message", elem).css("width") == ""
$(".body .message", elem).css("width", width)
$(".body .circle-fg", elem).css("stroke-dashoffset", offset)
else
elem = @notifications.add(message.params[0], "progress", $(body))
if percent > 0
$(".body .circle-bg", elem).css {"animation-play-state": "paused", "stroke-dasharray": "180px"}
if $(".notification-icon", elem).data("done")
return false
else if message.params[2] >= 100 # Done
$(".circle-fg", elem).css("transition", "all 0.3s ease-in-out")
setTimeout (->
$(".notification-icon", elem).css {transform: "scale(1)", opacity: 1}
$(".notification-icon .icon-success", elem).css {transform: "rotate(45deg) scale(1)"}
), 300
setTimeout (=>
@notifications.close elem
), 3000
$(".notification-icon", elem).data("done", true)
else if message.params[2] < 0 # Error
$(".body .circle-fg", elem).css("stroke", "#ec6f47").css("transition", "transition: all 0.3s ease-in-out")
setTimeout (=>
$(".notification-icon", elem).css {transform: "scale(1)", opacity: 1}
elem.removeClass("notification-done").addClass("notification-error")
$(".notification-icon .icon-success", elem).removeClass("icon-success").html("!")
), 300
$(".notification-icon", elem).data("done", true)
actionSetViewport: (message) -> actionSetViewport: (message) ->
@ -362,7 +424,7 @@ class Wrapper
if site_info.content if site_info.content
window.document.title = site_info.content.title+" - ZeroNet" window.document.title = site_info.content.title+" - ZeroNet"
@log "Required file done, setting title to", window.document.title @log "Required file done, setting title to", window.document.title
if not $(".loadingscreen").length # Loading screen already removed (loaded +2sec) if not window.show_loadingscreen
@notifications.add("modified", "info", "New version of this page has just released.<br>Reload to see the modified content.") @notifications.add("modified", "info", "New version of this page has just released.<br>Reload to see the modified content.")
# File failed downloading # File failed downloading
else if site_info.event[0] == "file_failed" else if site_info.event[0] == "file_failed"

View file

@ -45,7 +45,7 @@ a { color: black }
color: #4F4F4F; font-family: 'Lucida Grande', 'Segoe UI', Helvetica, Arial, sans-serif; font-size: 14px; line-height: 20px; /*border: 1px solid rgba(210, 206, 205, 0.2)*/ color: #4F4F4F; font-family: 'Lucida Grande', 'Segoe UI', Helvetica, Arial, sans-serif; font-size: 14px; line-height: 20px; /*border: 1px solid rgba(210, 206, 205, 0.2)*/
} }
.notification-icon { .notification-icon {
display: block; width: 50px; height: 50px; position: absolute; float: left; z-index: 1; display: block; width: 50px; height: 50px; position: absolute; float: left; z-index: 2;
text-align: center; background-color: #e74c3c; line-height: 45px; vertical-align: bottom; font-size: 40px; color: white; text-align: center; background-color: #e74c3c; line-height: 45px; vertical-align: bottom; font-size: 40px; color: white;
} }
.notification .body { .notification .body {
@ -82,6 +82,18 @@ a { color: black }
.notification .input { padding: 6px; border: 1px solid #DDD; margin-left: 10px; border-bottom: 2px solid #DDD; border-radius: 1px; margin-right: -11px; transition: all 0.3s } .notification .input { padding: 6px; border: 1px solid #DDD; margin-left: 10px; border-bottom: 2px solid #DDD; border-radius: 1px; margin-right: -11px; transition: all 0.3s }
.notification .input:focus { border-color: #95a5a6; outline: none } .notification .input:focus { border-color: #95a5a6; outline: none }
/* Notification progress */
.notification .circle { width: 50px; height: 50px; position: absolute; left: -50px; top: 0px; background-color: #e2e9ec; z-index: 1; background: linear-gradient(405deg, rgba(226, 233, 236, 0.8), #efefef); }
.notification .circle-svg { margin-left: 10px; margin-top: 10px; transform: rotateZ(-90deg); }
.notification .circle-bg { stroke: #FFF; stroke-width: 2px; animation: rolling 0.4s infinite linear; stroke-dasharray: 40px; transition: all 1s }
.notification .circle-fg { stroke-dashoffset: 200; stroke: #2ecc71; stroke-width: 2px; stroke-dasharray: 75px; transition: all 5s cubic-bezier(0.19, 1, 0.22, 1); }
.notification-progress .notification-icon { opacity: 0; transform: scale(0); transition: all 0.3s ease-in-out }
.notification-progress .icon-success { transform: rotate(45deg) scale(0); transition: all 0.6s cubic-bezier(0.68, -0.55, 0.265, 1.55); }
@keyframes rolling {
0% { stroke-dashoffset: 80px }
100% { stroke-dashoffset: 0px }
}
/* Icons (based on http://nicolasgallagher.com/pure-css-gui-icons/demo/) */ /* Icons (based on http://nicolasgallagher.com/pure-css-gui-icons/demo/) */
.icon-success { left:6px; width:5px; height:12px; border-width:0 5px 5px 0; border-style:solid; border-color:white; margin-left: 20px; margin-top: 15px; transform:rotate(45deg) } .icon-success { left:6px; width:5px; height:12px; border-width:0 5px 5px 0; border-style:solid; border-color:white; margin-left: 20px; margin-top: 15px; transform:rotate(45deg) }

View file

@ -50,7 +50,7 @@ a { color: black }
color: #4F4F4F; font-family: 'Lucida Grande', 'Segoe UI', Helvetica, Arial, sans-serif; font-size: 14px; line-height: 20px; /*border: 1px solid rgba(210, 206, 205, 0.2)*/ color: #4F4F4F; font-family: 'Lucida Grande', 'Segoe UI', Helvetica, Arial, sans-serif; font-size: 14px; line-height: 20px; /*border: 1px solid rgba(210, 206, 205, 0.2)*/
} }
.notification-icon { .notification-icon {
display: block; width: 50px; height: 50px; position: absolute; float: left; z-index: 1; display: block; width: 50px; height: 50px; position: absolute; float: left; z-index: 2;
text-align: center; background-color: #e74c3c; line-height: 45px; vertical-align: bottom; font-size: 40px; color: white; text-align: center; background-color: #e74c3c; line-height: 45px; vertical-align: bottom; font-size: 40px; color: white;
} }
.notification .body { .notification .body {
@ -87,6 +87,27 @@ a { color: black }
.notification .input { padding: 6px; border: 1px solid #DDD; margin-left: 10px; border-bottom: 2px solid #DDD; -webkit-border-radius: 1px; -moz-border-radius: 1px; -o-border-radius: 1px; -ms-border-radius: 1px; border-radius: 1px ; margin-right: -11px; -webkit-transition: all 0.3s ; -moz-transition: all 0.3s ; -o-transition: all 0.3s ; -ms-transition: all 0.3s ; transition: all 0.3s } .notification .input { padding: 6px; border: 1px solid #DDD; margin-left: 10px; border-bottom: 2px solid #DDD; -webkit-border-radius: 1px; -moz-border-radius: 1px; -o-border-radius: 1px; -ms-border-radius: 1px; border-radius: 1px ; margin-right: -11px; -webkit-transition: all 0.3s ; -moz-transition: all 0.3s ; -o-transition: all 0.3s ; -ms-transition: all 0.3s ; transition: all 0.3s }
.notification .input:focus { border-color: #95a5a6; outline: none } .notification .input:focus { border-color: #95a5a6; outline: none }
/* Notification progress */
.notification .circle { width: 50px; height: 50px; position: absolute; left: -50px; top: 0px; background-color: #e2e9ec; z-index: 1; background: -webkit-linear-gradient(405deg, rgba(226, 233, 236, 0.8), #efefef);background: -moz-linear-gradient(405deg, rgba(226, 233, 236, 0.8), #efefef);background: -o-linear-gradient(405deg, rgba(226, 233, 236, 0.8), #efefef);background: -ms-linear-gradient(405deg, rgba(226, 233, 236, 0.8), #efefef);background: linear-gradient(405deg, rgba(226, 233, 236, 0.8), #efefef); }
.notification .circle-svg { margin-left: 10px; margin-top: 10px; -webkit-transform: rotateZ(-90deg); -moz-transform: rotateZ(-90deg); -o-transform: rotateZ(-90deg); -ms-transform: rotateZ(-90deg); transform: rotateZ(-90deg) ; }
.notification .circle-bg { stroke: #FFF; stroke-width: 2px; -webkit-animation: rolling 0.4s infinite linear; -moz-animation: rolling 0.4s infinite linear; -o-animation: rolling 0.4s infinite linear; -ms-animation: rolling 0.4s infinite linear; animation: rolling 0.4s infinite linear ; stroke-dasharray: 40px; -webkit-transition: all 1s ; -moz-transition: all 1s ; -o-transition: all 1s ; -ms-transition: all 1s ; transition: all 1s }
.notification .circle-fg { stroke-dashoffset: 200; stroke: #2ecc71; stroke-width: 2px; stroke-dasharray: 75px; -webkit-transition: all 5s cubic-bezier(0.19, 1, 0.22, 1); -moz-transition: all 5s cubic-bezier(0.19, 1, 0.22, 1); -o-transition: all 5s cubic-bezier(0.19, 1, 0.22, 1); -ms-transition: all 5s cubic-bezier(0.19, 1, 0.22, 1); transition: all 5s cubic-bezier(0.19, 1, 0.22, 1) ; }
.notification-progress .notification-icon { opacity: 0; -webkit-transform: scale(0); -moz-transform: scale(0); -o-transform: scale(0); -ms-transform: scale(0); transform: scale(0) ; -webkit-transition: all 0.3s ease-in-out ; -moz-transition: all 0.3s ease-in-out ; -o-transition: all 0.3s ease-in-out ; -ms-transition: all 0.3s ease-in-out ; transition: all 0.3s ease-in-out }
.notification-progress .icon-success { -webkit-transform: rotate(45deg) scale(0); -moz-transform: rotate(45deg) scale(0); -o-transform: rotate(45deg) scale(0); -ms-transform: rotate(45deg) scale(0); transform: rotate(45deg) scale(0) ; -webkit-transition: all 0.6s cubic-bezier(0.68, -0.55, 0.265, 1.55); -moz-transition: all 0.6s cubic-bezier(0.68, -0.55, 0.265, 1.55); -o-transition: all 0.6s cubic-bezier(0.68, -0.55, 0.265, 1.55); -ms-transition: all 0.6s cubic-bezier(0.68, -0.55, 0.265, 1.55); transition: all 0.6s cubic-bezier(0.68, -0.55, 0.265, 1.55) ; }
@keyframes rolling {
0% { stroke-dashoffset: 80px }
100% { stroke-dashoffset: 0px }
}
@-webkit-keyframes rolling {
0% { stroke-dashoffset: 80px }
100% { stroke-dashoffset: 0px }
}
@-moz-keyframes rolling {
0% { stroke-dashoffset: 80px }
100% { stroke-dashoffset: 0px }
}
/* Icons (based on http://nicolasgallagher.com/pure-css-gui-icons/demo/) */ /* Icons (based on http://nicolasgallagher.com/pure-css-gui-icons/demo/) */
.icon-success { left:6px; width:5px; height:12px; border-width:0 5px 5px 0; border-style:solid; border-color:white; margin-left: 20px; margin-top: 15px; transform:rotate(45deg) } .icon-success { left:6px; width:5px; height:12px; border-width:0 5px 5px 0; border-style:solid; border-color:white; margin-left: 20px; margin-top: 15px; transform:rotate(45deg) }

View file

@ -688,10 +688,15 @@ jQuery.extend( jQuery.easing,
} }
elem = $(".notification.template", this.elem).clone().removeClass("template"); elem = $(".notification.template", this.elem).clone().removeClass("template");
elem.addClass("notification-" + type).addClass("notification-" + id); elem.addClass("notification-" + type).addClass("notification-" + id);
if (type === "progress") {
elem.addClass("notification-done");
}
if (type === "error") { if (type === "error") {
$(".notification-icon", elem).html("!"); $(".notification-icon", elem).html("!");
} else if (type === "done") { } else if (type === "done") {
$(".notification-icon", elem).html("<div class='icon-success'></div>"); $(".notification-icon", elem).html("<div class='icon-success'></div>");
} else if (type === "progress") {
$(".notification-icon", elem).html("<div class='icon-success'></div>");
} else if (type === "ask") { } else if (type === "ask") {
$(".notification-icon", elem).html("?"); $(".notification-icon", elem).html("?");
} else { } else {
@ -735,11 +740,12 @@ jQuery.extend( jQuery.easing,
return false; return false;
}; };
})(this)); })(this));
return $(".select", elem).on("click", (function(_this) { $(".select", elem).on("click", (function(_this) {
return function() { return function() {
return _this.close(elem); return _this.close(elem);
}; };
})(this)); })(this));
return elem;
}; };
Notifications.prototype.close = function(elem) { Notifications.prototype.close = function(elem) {
@ -848,6 +854,8 @@ jQuery.extend( jQuery.easing,
_ref = message.params[0].split("-"), id = _ref[0], type = _ref[1]; _ref = message.params[0].split("-"), id = _ref[0], type = _ref[1];
} }
return this.notifications.add(id, type, message.params[1], message.params[2]); return this.notifications.add(id, type, message.params[1], message.params[2]);
} else if (cmd === "progress") {
return this.actionProgress(message);
} else if (cmd === "prompt") { } else if (cmd === "prompt") {
return this.displayPrompt(message.params[0], message.params[1], message.params[2], (function(_this) { return this.displayPrompt(message.params[0], message.params[1], message.params[2], (function(_this) {
return function(res) { return function(res) {
@ -915,6 +923,8 @@ jQuery.extend( jQuery.easing,
return this.actionConfirm(message); return this.actionConfirm(message);
} else if (cmd === "wrapperPrompt") { } else if (cmd === "wrapperPrompt") {
return this.actionPrompt(message); return this.actionPrompt(message);
} else if (cmd === "wrapperProgress") {
return this.actionProgress(message);
} else if (cmd === "wrapperSetViewport") { } else if (cmd === "wrapperSetViewport") {
return this.actionSetViewport(message); return this.actionSetViewport(message);
} else if (cmd === "wrapperSetTitle") { } else if (cmd === "wrapperSetTitle") {
@ -941,6 +951,8 @@ jQuery.extend( jQuery.easing,
return this.actionOpenWindow(message.params); return this.actionOpenWindow(message.params);
} else if (cmd === "wrapperPermissionAdd") { } else if (cmd === "wrapperPermissionAdd") {
return this.actionPermissionAdd(message); return this.actionPermissionAdd(message);
} else if (cmd === "wrapperRequestFullscreen") {
return this.actionRequestFullscreen();
} else { } else {
if (message.id < 1000000) { if (message.id < 1000000) {
return this.ws.send(message); return this.ws.send(message);
@ -959,7 +971,7 @@ jQuery.extend( jQuery.easing,
query = window.location.search; query = window.location.search;
} }
back = window.location.pathname; back = window.location.pathname;
if (back.slice(-1) !== "/") { if (back.match(/^\/[^\/]+$/)) {
back += "/"; back += "/";
} }
if (query.replace("?", "")) { if (query.replace("?", "")) {
@ -992,6 +1004,32 @@ jQuery.extend( jQuery.easing,
} }
}; };
Wrapper.prototype.actionRequestFullscreen = function() {
var elem, request_fullscreen;
if (__indexOf.call(this.site_info.settings.permissions, "Fullscreen") >= 0) {
elem = document.getElementById("inner-iframe");
request_fullscreen = elem.requestFullScreen || elem.webkitRequestFullscreen || elem.mozRequestFullScreen || elem.msRequestFullScreen;
request_fullscreen.call(elem);
return setTimeout(((function(_this) {
return function() {
if (window.innerHeight !== screen.height) {
return _this.displayConfirm("This site requests permission:" + " <b>Fullscreen</b>", "Grant", function() {
return request_fullscreen.call(elem);
});
}
};
})(this)), 100);
} else {
return this.displayConfirm("This site requests permission:" + " <b>Fullscreen</b>", "Grant", (function(_this) {
return function() {
_this.site_info.settings.permissions.push("Fullscreen");
_this.actionRequestFullscreen();
return _this.ws.cmd("permissionAdd", "Fullscreen");
};
})(this));
}
};
Wrapper.prototype.actionPermissionAdd = function(message) { Wrapper.prototype.actionPermissionAdd = function(message) {
var permission; var permission;
permission = message.params; permission = message.params;
@ -1099,6 +1137,65 @@ jQuery.extend( jQuery.easing,
})(this)); })(this));
}; };
Wrapper.prototype.actionProgress = function(message) {
var body, circle, elem, offset, percent, width;
message.params = this.toHtmlSafe(message.params);
percent = Math.min(100, message.params[2]) / 100;
offset = 75 - (percent * 75);
circle = "<div class=\"circle\"><svg class=\"circle-svg\" width=\"30\" height=\"30\" viewport=\"0 0 30 30\" version=\"1.1\" xmlns=\"http://www.w3.org/2000/svg\">\n <circle r=\"12\" cx=\"15\" cy=\"15\" fill=\"transparent\" class=\"circle-bg\"></circle>\n <circle r=\"12\" cx=\"15\" cy=\"15\" fill=\"transparent\" class=\"circle-fg\" style=\"stroke-dashoffset: " + offset + "\"></circle>\n</svg></div>";
body = "<span class='message'>" + message.params[1] + "</span>" + circle;
elem = $(".notification-" + message.params[0]);
if (elem.length) {
width = $(".body .message", elem).outerWidth();
$(".body .message", elem).html(message.params[1]);
if ($(".body .message", elem).css("width") === "") {
$(".body .message", elem).css("width", width);
}
$(".body .circle-fg", elem).css("stroke-dashoffset", offset);
} else {
elem = this.notifications.add(message.params[0], "progress", $(body));
}
if (percent > 0) {
$(".body .circle-bg", elem).css({
"animation-play-state": "paused",
"stroke-dasharray": "180px"
});
}
if ($(".notification-icon", elem).data("done")) {
return false;
} else if (message.params[2] >= 100) {
$(".circle-fg", elem).css("transition", "all 0.3s ease-in-out");
setTimeout((function() {
$(".notification-icon", elem).css({
transform: "scale(1)",
opacity: 1
});
return $(".notification-icon .icon-success", elem).css({
transform: "rotate(45deg) scale(1)"
});
}), 300);
setTimeout(((function(_this) {
return function() {
return _this.notifications.close(elem);
};
})(this)), 3000);
return $(".notification-icon", elem).data("done", true);
} else if (message.params[2] < 0) {
$(".body .circle-fg", elem).css("stroke", "#ec6f47").css("transition", "transition: all 0.3s ease-in-out");
setTimeout(((function(_this) {
return function() {
$(".notification-icon", elem).css({
transform: "scale(1)",
opacity: 1
});
elem.removeClass("notification-done").addClass("notification-error");
return $(".notification-icon .icon-success", elem).removeClass("icon-success").html("!");
};
})(this)), 300);
return $(".notification-icon", elem).data("done", true);
}
};
Wrapper.prototype.actionSetViewport = function(message) { Wrapper.prototype.actionSetViewport = function(message) {
this.log("actionSetViewport", message); this.log("actionSetViewport", message);
if ($("#viewport").length > 0) { if ($("#viewport").length > 0) {
@ -1273,7 +1370,7 @@ jQuery.extend( jQuery.easing,
window.document.title = site_info.content.title + " - ZeroNet"; window.document.title = site_info.content.title + " - ZeroNet";
this.log("Required file done, setting title to", window.document.title); this.log("Required file done, setting title to", window.document.title);
} }
if (!$(".loadingscreen").length) { if (!window.show_loadingscreen) {
this.notifications.add("modified", "info", "New version of this page has just released.<br>Reload to see the modified content."); this.notifications.add("modified", "info", "New version of this page has just released.<br>Reload to see the modified content.");
} }
} }

Binary file not shown.

View file

@ -12,7 +12,7 @@
--------------- ---------------
/* ==================================================================== /* ====================================================================
* Copyright (c) 1998-2011 The OpenSSL Project. All rights reserved. * Copyright (c) 1998-2016 The OpenSSL Project. All rights reserved.
* *
* Redistribution and use in source and binary forms, with or without * Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions * modification, are permitted provided that the following conditions

View file

@ -1,10 +1,10 @@
============================================================================= =============================================================================
OpenSSL v1.0.2a Precompiled Binaries for Win32 OpenSSL v1.0.2j Precompiled Binaries for Win32
----------------------------------------------------------------------------- -----------------------------------------------------------------------------
*** Release Information *** *** Release Information ***
Release Date: Mrz 20, 2015 Release Date: Okt 01, 2016
Author: Frederik A. Winkelsdorf (opendec.wordpress.com) Author: Frederik A. Winkelsdorf (opendec.wordpress.com)
for the Indy Project (www.indyproject.org) for the Indy Project (www.indyproject.org)
@ -15,7 +15,7 @@ Dependencies: The libraries have no noteworthy dependencies
Installation: Copy both DLL files into your application directory Installation: Copy both DLL files into your application directory
Supported OS: Windows 2000 up to Windows 8 Supported OS: Windows 2000 up to Windows 10
----------------------------------------------------------------------------- -----------------------------------------------------------------------------
@ -42,10 +42,10 @@ SOFTWARE AND/OR PATENTS.
*** Build Information Win32 *** *** Build Information Win32 ***
Built with: Microsoft Visual C++ 2008 Express Edition Built with: Microsoft Visual C++ 2008 Express Edition
The Netwide Assembler (NASM) v2.11.05 Win32 The Netwide Assembler (NASM) v2.11.08 Win32
Strawberry Perl v5.20.0.1 Win32 Portable Strawberry Perl v5.22.0.1 Win32 Portable
Windows PowerShell Windows PowerShell
FinalBuilder 7 Embarcadero Edition FinalBuilder 7
Commands: perl configure VC-WIN32 Commands: perl configure VC-WIN32
ms\do_nasm ms\do_nasm

Binary file not shown.

Binary file not shown.

View file

@ -196,11 +196,13 @@ def openLibrary():
global ssl global ssl
try: try:
if sys.platform.startswith("win"): if sys.platform.startswith("win"):
dll_path = "src/lib/opensslVerify/libeay32.dll" dll_path = os.path.dirname(os.path.abspath(__file__)) + "/" + "libeay32.dll"
elif sys.platform == "cygwin": elif sys.platform == "cygwin":
dll_path = "/bin/cygcrypto-1.0.0.dll" dll_path = "/bin/cygcrypto-1.0.0.dll"
elif os.path.isfile("../lib/libcrypto.so"): # ZeroBundle elif os.path.isfile("../lib/libcrypto.so"): # ZeroBundle OSX
dll_path = "../lib/libcrypto.so" dll_path = "../lib/libcrypto.so"
elif os.path.isfile("/opt/lib/libcrypto.so.1.0.0"): # For optware and entware
dll_path = "/opt/lib/libcrypto.so.1.0.0"
else: else:
dll_path = "/usr/local/ssl/lib/libcrypto.so" dll_path = "/usr/local/ssl/lib/libcrypto.so"
ssl = _OpenSSL(dll_path) ssl = _OpenSSL(dll_path)
@ -456,7 +458,7 @@ if __name__ == "__main__":
sign = btctools.ecdsa_sign("hello", priv) # HGbib2kv9gm9IJjDt1FXbXFczZi35u0rZR3iPUIt5GglDDCeIQ7v8eYXVNIaLoJRI4URGZrhwmsYQ9aVtRTnTfQ= sign = btctools.ecdsa_sign("hello", priv) # HGbib2kv9gm9IJjDt1FXbXFczZi35u0rZR3iPUIt5GglDDCeIQ7v8eYXVNIaLoJRI4URGZrhwmsYQ9aVtRTnTfQ=
s = time.time() s = time.time()
for i in range(100): for i in range(1000):
pubkey = getMessagePubkey("hello", sign) pubkey = getMessagePubkey("hello", sign)
verified = btctools.pubkey_to_address(pubkey) == address verified = btctools.pubkey_to_address(pubkey) == address
print "100x Verified", verified, time.time() - s print "100x Verified", verified, time.time() - s

Binary file not shown.

View file

@ -434,10 +434,10 @@ def openLibrary():
global OpenSSL global OpenSSL
try: try:
if sys.platform.startswith("win"): if sys.platform.startswith("win"):
dll_path = "src/lib/opensslVerify/libeay32.dll" dll_path = os.path.normpath(os.path.dirname(__file__) + "/../opensslVerify/" + "libeay32.dll")
elif sys.platform == "cygwin": elif sys.platform == "cygwin":
dll_path = "/bin/cygcrypto-1.0.0.dll" dll_path = "/bin/cygcrypto-1.0.0.dll"
elif os.path.isfile("../lib/libcrypto.so"): # ZeroBundle elif os.path.isfile("../lib/libcrypto.so"): # ZeroBundle OSX
dll_path = "../lib/libcrypto.so" dll_path = "../lib/libcrypto.so"
else: else:
dll_path = "/usr/local/ssl/lib/libcrypto.so" dll_path = "/usr/local/ssl/lib/libcrypto.so"

View file

@ -30,14 +30,22 @@ if not config.arguments: # Config parse failed, show the help screen and exit
# Create necessary files and dirs # Create necessary files and dirs
if not os.path.isdir(config.log_dir): if not os.path.isdir(config.log_dir):
os.mkdir(config.log_dir) os.mkdir(config.log_dir)
try:
os.chmod(config.log_dir, stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR)
except Exception, err:
print "Can't change permission of %s: %s" % (config.log_dir, err)
if not os.path.isdir(config.data_dir): if not os.path.isdir(config.data_dir):
os.mkdir(config.data_dir) os.mkdir(config.data_dir)
try:
os.chmod(config.data_dir, stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR)
except Exception, err:
print "Can't change permission of %s: %s" % (config.data_dir, err)
if not os.path.isfile("%s/sites.json" % config.data_dir): if not os.path.isfile("%s/sites.json" % config.data_dir):
open("%s/sites.json" % config.data_dir, "w").write("{}") open("%s/sites.json" % config.data_dir, "w").write("{}")
os.chmod("%s/sites.json" % config.data_dir, stat.S_IRUSR | stat.S_IWUSR)
if not os.path.isfile("%s/users.json" % config.data_dir): if not os.path.isfile("%s/users.json" % config.data_dir):
open("%s/users.json" % config.data_dir, "w").write("{}") open("%s/users.json" % config.data_dir, "w").write("{}")
os.chmod("%s/users.json" % config.data_dir, stat.S_IRUSR | stat.S_IWUSR)
# Setup logging # Setup logging
if config.action == "main": if config.action == "main":
@ -180,7 +188,7 @@ class Actions(object):
logging.info("Site created!") logging.info("Site created!")
def siteSign(self, address, privatekey=None, inner_path="content.json", publish=False): def siteSign(self, address, privatekey=None, inner_path="content.json", publish=False, remove_missing_optional=False):
from Site import Site from Site import Site
from Site import SiteManager from Site import SiteManager
SiteManager.site_manager.load() SiteManager.site_manager.load()
@ -200,7 +208,7 @@ class Actions(object):
import getpass import getpass
privatekey = getpass.getpass("Private key (input hidden):") privatekey = getpass.getpass("Private key (input hidden):")
diffs = site.content_manager.getDiffs(inner_path) diffs = site.content_manager.getDiffs(inner_path)
succ = site.content_manager.sign(inner_path=inner_path, privatekey=privatekey, update_changed_files=True) succ = site.content_manager.sign(inner_path=inner_path, privatekey=privatekey, update_changed_files=True, remove_missing_optional=remove_missing_optional)
if succ and publish: if succ and publish:
self.sitePublish(address, inner_path=inner_path, diffs=diffs) self.sitePublish(address, inner_path=inner_path, diffs=diffs)

View file

@ -15,8 +15,8 @@ def setMaxfilesopened(limit):
import resource import resource
soft, hard = resource.getrlimit(resource.RLIMIT_NOFILE) soft, hard = resource.getrlimit(resource.RLIMIT_NOFILE)
if soft < limit: if soft < limit:
logging.debug("Current RLIMIT_NOFILE: %s, changing to %s..." % (soft, limit)) logging.debug("Current RLIMIT_NOFILE: %s (max: %s), changing to %s..." % (soft, hard, limit))
resource.setrlimit(resource.RLIMIT_NOFILE, (soft, hard)) resource.setrlimit(resource.RLIMIT_NOFILE, (limit, hard))
return True return True
except Exception, err: except Exception, err:

View file

@ -4,6 +4,7 @@ import httplib
import logging import logging
from urlparse import urlparse from urlparse import urlparse
from xml.dom.minidom import parseString from xml.dom.minidom import parseString
from xml.parsers.expat import ExpatError
from gevent import socket from gevent import socket
@ -82,7 +83,7 @@ def _retrieve_igd_profile(url):
Retrieve the device's UPnP profile. Retrieve the device's UPnP profile.
""" """
try: try:
return urllib2.urlopen(url.geturl(), timeout=5).read() return urllib2.urlopen(url.geturl(), timeout=5).read().decode('utf-8')
except socket.error: except socket.error:
raise IGDError('IGD profile query timed out') raise IGDError('IGD profile query timed out')
@ -100,7 +101,11 @@ def _parse_igd_profile(profile_xml):
WANIPConnection or WANPPPConnection and return WANIPConnection or WANPPPConnection and return
the 'controlURL' and the service xml schema. the 'controlURL' and the service xml schema.
""" """
dom = parseString(profile_xml) try:
dom = parseString(profile_xml)
except ExpatError as e:
raise IGDError(
'Unable to parse IGD reply: {0} \n\n\n {1}'.format(profile_xml, e))
service_types = dom.getElementsByTagName('serviceType') service_types = dom.getElementsByTagName('serviceType')
for service in service_types: for service in service_types:
@ -268,7 +273,7 @@ def _send_requests(messages, location, upnp_schema, control_path):
raise UpnpError('Sending requests using UPnP failed.') raise UpnpError('Sending requests using UPnP failed.')
def _orchestrate_soap_request(ip, port, msg_fn, desc=None, protos=['TCP', 'UDP']): def _orchestrate_soap_request(ip, port, msg_fn, desc=None, protos=("TCP", "UDP")):
logging.debug("Trying using local ip: %s" % ip) logging.debug("Trying using local ip: %s" % ip)
idg_data = _collect_idg_data(ip) idg_data = _collect_idg_data(ip)
@ -284,7 +289,7 @@ def _communicate_with_igd(port=15441,
desc="UpnpPunch", desc="UpnpPunch",
retries=3, retries=3,
fn=_create_open_message, fn=_create_open_message,
protos=["TCP", "UDP"]): protos=("TCP", "UDP")):
""" """
Manage sending a message generated by 'fn'. Manage sending a message generated by 'fn'.
""" """
@ -310,7 +315,7 @@ def _communicate_with_igd(port=15441,
port, retries)) port, retries))
def ask_to_open_port(port=15441, desc="UpnpPunch", retries=3, protos=["TCP", "UDP"]): def ask_to_open_port(port=15441, desc="UpnpPunch", retries=3, protos=("TCP", "UDP")):
logging.debug("Trying to open port %d." % port) logging.debug("Trying to open port %d." % port)
_communicate_with_igd(port=port, _communicate_with_igd(port=port,
desc=desc, desc=desc,
@ -319,7 +324,7 @@ def ask_to_open_port(port=15441, desc="UpnpPunch", retries=3, protos=["TCP", "UD
protos=protos) protos=protos)
def ask_to_close_port(port=15441, desc="UpnpPunch", retries=3, protos=["TCP", "UDP"]): def ask_to_close_port(port=15441, desc="UpnpPunch", retries=3, protos=("TCP", "UDP")):
logging.debug("Trying to close port %d." % port) logging.debug("Trying to close port %d." % port)
# retries=1 because multiple successes cause 500 response and failure # retries=1 because multiple successes cause 500 response and failure
_communicate_with_igd(port=port, _communicate_with_igd(port=port,

View file

@ -14,17 +14,15 @@ from Config import config
def atomicWrite(dest, content, mode="w"): def atomicWrite(dest, content, mode="w"):
try: try:
permissions = stat.S_IMODE(os.lstat(dest).st_mode) with open(dest + "-tmpnew", mode) as f:
with open(dest + "-new", mode) as f:
f.write(content) f.write(content)
f.flush() f.flush()
os.fsync(f.fileno()) os.fsync(f.fileno())
if os.path.isfile(dest + "-old"): # Previous incomplete write if os.path.isfile(dest + "-tmpold"): # Previous incomplete write
os.rename(dest + "-old", dest + "-old-%s" % time.time()) os.rename(dest + "-tmpold", dest + "-tmpold-%s" % time.time())
os.rename(dest, dest + "-old") os.rename(dest, dest + "-tmpold")
os.rename(dest + "-new", dest) os.rename(dest + "-tmpnew", dest)
os.chmod(dest, permissions) os.unlink(dest + "-tmpold")
os.unlink(dest + "-old")
return True return True
except Exception, err: except Exception, err:
from Debug import Debug from Debug import Debug
@ -32,8 +30,8 @@ def atomicWrite(dest, content, mode="w"):
"File %s write failed: %s, reverting..." % "File %s write failed: %s, reverting..." %
(dest, Debug.formatException(err)) (dest, Debug.formatException(err))
) )
if os.path.isfile(dest + "-old") and not os.path.isfile(dest): if os.path.isfile(dest + "-tmpold") and not os.path.isfile(dest):
os.rename(dest + "-old", dest) os.rename(dest + "-tmpold", dest)
return False return False
@ -54,7 +52,7 @@ def openLocked(path, mode="w"):
def getFreeSpace(): def getFreeSpace():
free_space = -1 free_space = -1
if "statvfs" in dir(os): # Unix if "statvfs" in dir(os): # Unix
statvfs = os.statvfs(config.data_dir) statvfs = os.statvfs(config.data_dir.encode("utf8"))
free_space = statvfs.f_frsize * statvfs.f_bavail free_space = statvfs.f_frsize * statvfs.f_bavail
else: # Windows else: # Windows
try: try:

View file

@ -1,6 +1,7 @@
import urllib import urllib
import zipfile import zipfile
import os import os
import sys
import ssl import ssl
import httplib import httplib
import socket import socket
@ -52,6 +53,11 @@ def download():
def update(): def update():
from Config import config from Config import config
if getattr(sys, 'source_update_dir', False):
if not os.path.isdir(sys.source_update_dir):
os.makedirs(sys.source_update_dir)
os.chdir(sys.source_update_dir) # New source code will be stored in different directory
updatesite_path = config.data_dir + "/" + config.updatesite updatesite_path = config.data_dir + "/" + config.updatesite
sites_json = json.load(open(config.data_dir + "/sites.json")) sites_json = json.load(open(config.data_dir + "/sites.json"))
updatesite_bad_files = sites_json.get(config.updatesite, {}).get("cache", {}).get("bad_files", {}) updatesite_bad_files = sites_json.get(config.updatesite, {}).get("cache", {}).get("bad_files", {})
@ -79,7 +85,7 @@ def update():
plugins_enabled.append(dir) plugins_enabled.append(dir)
print "Plugins enabled:", plugins_enabled, "disabled:", plugins_disabled print "Plugins enabled:", plugins_enabled, "disabled:", plugins_disabled
print "Extracting...", print "Extracting to %s..." % os.getcwd(),
for inner_path in inner_paths: for inner_path in inner_paths:
if ".." in inner_path: if ".." in inner_path:
continue continue

View file

@ -45,11 +45,16 @@ def main():
handler.close() handler.close()
logger.removeHandler(handler) logger.removeHandler(handler)
except Exception, err: # Prevent closing
except (Exception, ): # Prevent closing
import traceback import traceback
traceback.print_exc() try:
traceback.print_exc(file=open("log/error.log", "a")) import logging
logging.exception("Unhandled exception: %s" % err)
except Exception, log_err:
print "Failed to log error:", log_err
traceback.print_exc()
from Config import config
traceback.print_exc(file=open(config.log_dir + "/error.log", "a"))
if main and main.update_after_shutdown: # Updater if main and main.update_after_shutdown: # Updater
# Restart # Restart
@ -58,11 +63,22 @@ def main():
import time import time
time.sleep(1) # Wait files to close time.sleep(1) # Wait files to close
args = sys.argv[:] args = sys.argv[:]
args.insert(0, sys.executable)
sys.executable = sys.executable.replace(".pkg", "") # Frozen mac fix
if not getattr(sys, 'frozen', False):
args.insert(0, sys.executable)
if sys.platform == 'win32': if sys.platform == 'win32':
args = ['"%s"' % arg for arg in args] args = ['"%s"' % arg for arg in args]
os.execv(sys.executable, args)
try:
print "Executing %s %s" % (sys.executable, args)
os.execv(sys.executable, args)
except Exception, err:
print "Execv error: %s" % err
print "Bye." print "Bye."
if __name__ == '__main__': if __name__ == '__main__':
main() main()