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:
parent
cfe2cfaf09
commit
639b9f4961
44 changed files with 4061 additions and 293 deletions
|
@ -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()
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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; }
|
||||
|
|
|
@ -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; }
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue