version 0.2.0, new lib for bitcoin ecc, dont display or track notify errors, dont reload again within 1 sec, null peer ip fix, signingmoved to ContentManager, content.json include support, content.json multisig ready, content.json proper bitcoincore compatible signing, content.json include permissions, multithreaded publish, publish timeout 60s, no exception on invalid bitcoin address, testcase for new lib, bip32 based persite privatekey generation, multiuser ready, simple json database query command, websocket api fileGet, wrapper loading title stuck bugfix

This commit is contained in:
HelloZeroNet 2015-02-09 02:09:02 +01:00
parent cfe2cfaf09
commit 639b9f4961
44 changed files with 4061 additions and 293 deletions

View file

@ -1,6 +1,7 @@
import time, re, os, mimetypes, json
from Config import config
from Site import SiteManager
from User import UserManager
from Ui.UiWebsocket import UiWebsocket
status_texts = {
@ -19,6 +20,7 @@ class UiRequest:
self.log = server.log
self.get = {} # Get parameters
self.env = {} # Enviroment settings
self.user = UserManager.getCurrent()
self.start_response = None # Start response function
@ -103,6 +105,7 @@ class UiRequest:
# Render a file from media with iframe site wrapper
def actionWrapper(self, path):
if "." in path and not path.endswith(".html"): return self.actionSiteMedia("/media"+path) # Only serve html files with frame
if self.get.get("wrapper") == "False": return self.actionSiteMedia("/media"+path) # Only serve html files with frame
if self.env.get("HTTP_X_REQUESTED_WITH"): return self.error403() # No ajax allowed on wrapper
match = re.match("/(?P<site>[A-Za-z0-9]+)(?P<inner_path>/.*|$)", path)
@ -111,22 +114,22 @@ class UiRequest:
if not inner_path: inner_path = "index.html" # If inner path defaults to index.html
site = self.server.sites.get(match.group("site"))
if site and site.content and (not site.bad_files or site.settings["own"]): # Its downloaded or own
title = site.content["title"]
if site and site.content_manager.contents.get("content.json") and (not site.bad_files or site.settings["own"]): # Its downloaded or own
title = site.content_manager.contents["content.json"]["title"]
else:
title = "Loading %s..." % match.group("site")
site = SiteManager.need(match.group("site")) # Start download site
if not site: self.error404()
if not site: return self.error404(path)
self.sendHeader(extra_headers=[("X-Frame-Options", "DENY")])
# Wrapper variable inits
if self.env.get("QUERY_STRING"):
query_string = "?"+self.env["QUERY_STRING"]
else:
query_string = "?"+self.env["QUERY_STRING"]
else:
query_string = ""
body_style = ""
if site.content and site.content.get("background-color"): body_style += "background-color: "+site.content["background-color"]+";"
if site.content_manager.contents.get("content.json") and site.content_manager.contents["content.json"].get("background-color"): body_style += "background-color: "+site.content_manager.contents["content.json"]["background-color"]+";"
return self.render("src/Ui/template/wrapper.html",
inner_path=inner_path,
@ -146,6 +149,8 @@ class UiRequest:
# Serve a media for site
def actionSiteMedia(self, path):
path = path.replace("/index.html/", "/") # Base Backward compatibility fix
match = re.match("/media/(?P<site>[A-Za-z0-9]+)/(?P<inner_path>.*)", path)
referer = self.env.get("HTTP_REFERER")
@ -228,7 +233,7 @@ class UiRequest:
if site_check.settings["wrapper_key"] == wrapper_key: site = site_check
if site: # Correct wrapper key
ui_websocket = UiWebsocket(ws, site, self.server)
ui_websocket = UiWebsocket(ws, site, self.server, self.user)
site.websockets.append(ui_websocket) # Add to site websockets to allow notify on events
ui_websocket.start()
for site_check in self.server.sites.values(): # Remove websocket from every site (admin sites allowed to join other sites event channels)
@ -297,3 +302,5 @@ class UiRequest:
import imp
global UiWebsocket
UiWebsocket = imp.load_source("UiWebsocket", "src/Ui/UiWebsocket.py").UiWebsocket
UserManager.reload()
self.user = UserManager.getCurrent()

View file

@ -2,11 +2,14 @@ import json, gevent, time, sys, hashlib
from Config import config
from Site import SiteManager
from Debug import Debug
from util import QueryJson
class UiWebsocket:
def __init__(self, ws, site, server):
def __init__(self, ws, site, server, user):
self.ws = ws
self.site = site
self.user = user
self.log = site.log
self.server = server
self.next_message_id = 1
@ -98,6 +101,10 @@ class UiWebsocket:
func = self.actionSitePublish
elif cmd == "fileWrite":
func = self.actionFileWrite
elif cmd == "fileGet":
func = self.actionFileGet
elif cmd == "fileQuery":
func = self.actionFileQuery
# Admin commands
elif cmd == "sitePause" and "ADMIN" in permissions:
func = self.actionSitePause
@ -140,15 +147,18 @@ class UiWebsocket:
# Format site info
def formatSiteInfo(self, site):
content = site.content
if content and "files" in content: # Remove unnecessary data transfer
content = site.content.copy()
content["files"] = len(content["files"])
del(content["sign"])
content = site.content_manager.contents.get("content.json")
if content: # Remove unnecessary data transfer
content = content.copy()
content["files"] = len(content.get("files", {}))
content["includes"] = len(content.get("includes", {}))
if "sign" in content: del(content["sign"])
if "signs" in content: del(content["signs"])
ret = {
"auth_key": self.site.settings["auth_key"],
"auth_key_sha512": hashlib.sha512(self.site.settings["auth_key"]).hexdigest()[0:64],
"auth_key": self.site.settings["auth_key"], # Obsolete, will be removed
"auth_key_sha512": hashlib.sha512(self.site.settings["auth_key"]).hexdigest()[0:64], # Obsolete, will be removed
"auth_address": self.user.getAuthAddress(site.address),
"address": site.address,
"settings": site.settings,
"content_updated": site.content_updated,
@ -158,7 +168,7 @@ class UiWebsocket:
"tasks": len([task["inner_path"] for task in site.worker_manager.tasks]),
"content": content
}
if site.settings["serving"] and site.content: ret["peers"] += 1 # Add myself if serving
if site.settings["serving"] and content: ret["peers"] += 1 # Add myself if serving
return ret
@ -189,20 +199,26 @@ class UiWebsocket:
self.response(to, ret)
def actionSitePublish(self, to, privatekey):
def actionSitePublish(self, to, privatekey=None, inner_path="content.json"):
site = self.site
if not site.settings["own"]: return self.response(to, "Forbidden, you can only modify your own sites")
if not inner_path.endswith("content.json"): # Find the content.json first
inner_path = site.content_manager.getFileInfo(inner_path)["content_inner_path"]
if not site.settings["own"] and self.user.getAuthAddress(self.site.address) not in self.site.content_manager.getValidSigners(inner_path):
return self.response(to, "Forbidden, you can only modify your own sites")
if not privatekey: # Get privatekey from users.json
privatekey = self.user.getAuthPrivatekey(self.site.address)
# Signing
site.loadContent(True) # Reload content.json, ignore errors to make it up-to-date
signed = site.signContent(privatekey) # Sign using private key sent by user
site.content_manager.loadContent(add_bad_files=False) # Reload content.json, ignore errors to make it up-to-date
signed = site.content_manager.sign(inner_path, privatekey) # Sign using private key sent by user
if signed:
self.cmd("notification", ["done", "Private key correct, site signed!", 5000]) # Display message for 5 sec
if inner_path == "content_json": self.cmd("notification", ["done", "Private key correct, content signed!", 5000]) # Display message for 5 sec
else:
self.cmd("notification", ["error", "Site sign failed: invalid private key."])
self.cmd("notification", ["error", "Content sign failed: invalid private key."])
self.response(to, "Site sign failed")
return
site.loadContent(True) # Load new content.json, ignore errors
site.content_manager.loadContent(add_bad_files=False) # Load new content.json, ignore errors
# Publishing
if not site.settings["serving"]: # Enable site if paused
@ -210,27 +226,26 @@ class UiWebsocket:
site.saveSettings()
site.announce()
published = site.publish(5) # Publish to 5 peer
published = site.publish(5, inner_path) # Publish to 5 peer
if published>0: # Successfuly published
self.cmd("notification", ["done", "Site published to %s peers." % published, 5000])
self.cmd("notification", ["done", "Content published to %s peers." % published, 5000])
self.response(to, "ok")
site.updateWebsocket() # Send updated site data to local websocket clients
else:
if len(site.peers) == 0:
self.cmd("notification", ["info", "No peers found, but your site is ready to access."])
self.response(to, "No peers found, but your site is ready to access.")
self.cmd("notification", ["info", "No peers found, but your content is ready to access."])
self.response(to, "No peers found, but your content is ready to access.")
else:
self.cmd("notification", ["error", "Site publish failed."])
self.response(to, "Site publish failed.")
self.cmd("notification", ["error", "Content publish failed."])
self.response(to, "Content publish failed.")
# Write a file to disk
def actionFileWrite(self, to, inner_path, content_base64):
if not self.site.settings["own"]: return self.response(to, "Forbidden, you can only modify your own sites")
if not self.site.settings["own"] and self.user.getAuthAddress(self.site.address) not in self.site.content_manager.getValidSigners(inner_path):
return self.response(to, "Forbidden, you can only modify your own files")
try:
import base64
content = base64.b64decode(content_base64)
@ -238,12 +253,27 @@ class UiWebsocket:
except Exception, err:
return self.response(to, "Write error: %s" % err)
if inner_path == "content.json":
self.site.loadContent(True)
if inner_path.endswith("content.json"):
self.site.content_manager.loadContent(inner_path, add_bad_files=False)
return self.response(to, "ok")
# Find data in json files
def actionFileQuery(self, to, dir_inner_path, query):
dir_path = self.site.getPath(dir_inner_path)
rows = list(QueryJson.query(dir_path, query))
return self.response(to, rows)
# Return file content
def actionFileGet(self, to, inner_path):
try:
self.site.needFile(inner_path, priority=1)
body = open(self.site.getPath(inner_path)).read()
except:
body = None
return self.response(to, body)
# - Admin actions -
@ -253,7 +283,7 @@ class UiWebsocket:
ret = []
SiteManager.load() # Reload sites
for site in self.server.sites.values():
if not site.content: continue # Broken site
if not site.content_manager.contents.get("content.json"): continue # Broken site
ret.append(self.formatSiteInfo(site))
self.response(to, ret)

View file

@ -25,8 +25,9 @@ class Wrapper
window.onload = @onLoad # On iframe loaded
$(window).on "hashchange", => # On hash change
@log "Hashchange", window.location.hash
src = $("#inner-iframe").attr("src").replace(/#.*/, "")+window.location.hash
$("#inner-iframe").attr("src", src)
if window.location.hash
src = $("#inner-iframe").attr("src").replace(/#.*/, "")+window.location.hash
$("#inner-iframe").attr("src", src)
@
@ -97,7 +98,8 @@ class Wrapper
input = $("<input type='#{type}' class='input button-#{type}'/>") # Add input
input.on "keyup", (e) => # Send on enter
if e.keyCode == 13
@sendInner {"cmd": "response", "to": message.id, "result": input.val()} # Response to confirm
button.trigger "click" # Response to confirm
body.append(input)
button = $("<a href='##{caption}' class='button button-#{caption}'>#{caption}</a>") # Add confirm button
@ -149,6 +151,9 @@ class Wrapper
if window.location.hash then $("#inner-iframe")[0].src += window.location.hash # Hash tag
if @ws.ws.readyState == 1 and not @site_info # Ws opened
@reloadSiteInfo()
else if @site_info
window.document.title = @site_info.content.title+" - ZeroNet"
@log "Setting title to", window.document.title
# Send message to innerframe

View file

@ -47,6 +47,7 @@ a { color: black }
.notification .close { position: absolute; top: 0px; right: 0px; font-size: 19px; line-height: 13px; color: #DDD; padding: 7px; text-decoration: none }
.notification .close:hover { color: black }
.notification .close:active, .notification .close:focus { color: #AF3BFF }
.body-white .notification { box-shadow: 0px 1px 9px rgba(0,0,0,0.1) }
/* Notification types */
.notification-ask .notification-icon { background-color: #f39c12; }

View file

@ -52,6 +52,7 @@ a { color: black }
.notification .close { position: absolute; top: 0px; right: 0px; font-size: 19px; line-height: 13px; color: #DDD; padding: 7px; text-decoration: none }
.notification .close:hover { color: black }
.notification .close:active, .notification .close:focus { color: #AF3BFF }
.body-white .notification { -webkit-box-shadow: 0px 1px 9px rgba(0,0,0,0.1) ; -moz-box-shadow: 0px 1px 9px rgba(0,0,0,0.1) ; -o-box-shadow: 0px 1px 9px rgba(0,0,0,0.1) ; -ms-box-shadow: 0px 1px 9px rgba(0,0,0,0.1) ; box-shadow: 0px 1px 9px rgba(0,0,0,0.1) }
/* Notification types */
.notification-ask .notification-icon { background-color: #f39c12; }

View file

@ -748,8 +748,10 @@ jQuery.extend( jQuery.easing,
return function() {
var src;
_this.log("Hashchange", window.location.hash);
src = $("#inner-iframe").attr("src").replace(/#.*/, "") + window.location.hash;
return $("#inner-iframe").attr("src", src);
if (window.location.hash) {
src = $("#inner-iframe").attr("src").replace(/#.*/, "") + window.location.hash;
return $("#inner-iframe").attr("src", src);
}
};
})(this));
this;
@ -840,11 +842,7 @@ jQuery.extend( jQuery.easing,
input.on("keyup", (function(_this) {
return function(e) {
if (e.keyCode === 13) {
return _this.sendInner({
"cmd": "response",
"to": message.id,
"result": input.val()
});
return button.trigger("click");
}
};
})(this));
@ -923,6 +921,9 @@ jQuery.extend( jQuery.easing,
}
if (this.ws.ws.readyState === 1 && !this.site_info) {
return this.reloadSiteInfo();
} else if (this.site_info) {
window.document.title = this.site_info.content.title + " - ZeroNet";
return this.log("Setting title to", window.document.title);
}
};