First release, remove not used lines from gitignore

This commit is contained in:
HelloZeroNet 2015-01-12 02:03:45 +01:00
parent c0bfb3b062
commit d28e1cb4a6
85 changed files with 7205 additions and 50 deletions

52
.gitignore vendored
View File

@ -2,53 +2,5 @@
__pycache__/
*.py[cod]
# C extensions
*.so
# Distribution / packaging
.Python
env/
build/
develop-eggs/
dist/
downloads/
eggs/
lib/
lib64/
parts/
sdist/
var/
*.egg-info/
.installed.cfg
*.egg
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.coverage
.cache
nosetests.xml
coverage.xml
# Translations
*.mo
*.pot
# Django stuff:
*.log
# Sphinx documentation
docs/_build/
# PyBuilder
target/
# Log files
*.log

2
data/sites.json Normal file
View File

@ -0,0 +1,2 @@
{
}

1
log/empty.txt Normal file
View File

@ -0,0 +1 @@
Place for log files.

129
src/Config.py Normal file
View File

@ -0,0 +1,129 @@
import argparse, sys, os, time
import ConfigParser
class Config(object):
def __init__(self):
self.version = "0.1"
self.parser = self.createArguments()
argv = sys.argv[:] # Copy command line arguments
argv = self.parseConfig(argv) # Add arguments from config file
self.parseCommandline(argv) # Parse argv
self.setAttributes()
def __str__(self):
return str(self.arguments).replace("Namespace", "Config") # Using argparse str output
# Create command line arguments
def createArguments(self):
# Platform specific
if sys.platform.startswith("win"):
upnpc = "tools\\upnpc\\upnpc-static.exe"
coffeescript = "type %s | tools\\coffee\\coffee.cmd"
else:
upnpc = None
coffeescript = None
# Create parser
parser = argparse.ArgumentParser(formatter_class=argparse.ArgumentDefaultsHelpFormatter)
subparsers = parser.add_subparsers(title="Action to perform", dest="action")
# Main
action = subparsers.add_parser("main", help='Start UiServer and FileServer (default)')
# SiteCreate
action = subparsers.add_parser("siteCreate", help='Create a new site')
# SiteSign
action = subparsers.add_parser("siteSign", help='Update and sign content.json: address [privatekey]')
action.add_argument('address', help='Site to sign')
action.add_argument('privatekey', help='Private key (default: ask on execute)', nargs='?')
# SitePublish
action = subparsers.add_parser("sitePublish", help='Publish site to other peers: address')
action.add_argument('address', help='Site to publish')
# SiteVerify
action = subparsers.add_parser("siteVerify", help='Verify site files using md5: address')
action.add_argument('address', help='Site to verify')
# Config parameters
parser.add_argument('--debug', help='Debug mode', action='store_true')
parser.add_argument('--ui_ip', help='Web interface bind address', default="127.0.0.1", metavar='host')
parser.add_argument('--ui_port', help='Web interface bind port', default=43110, metavar='port')
parser.add_argument('--ui_restrict', help='Restrict web access', default=False, metavar='ip')
parser.add_argument('--homepage', help='Web interface Homepage', default='1EU1tbG9oC1A8jz2ouVwGZyQ5asrNsE4Vr', metavar='address')
parser.add_argument('--fileserver_ip', help='FileServer bind address', default="*", metavar='host')
parser.add_argument('--fileserver_port',help='FileServer bind port', default=15441, metavar='port')
parser.add_argument('--ip_external', help='External ip (tested on start if None)', metavar='ip')
parser.add_argument('--upnpc', help='MiniUPnP binary for open port on router', default=upnpc, metavar='executable_path')
parser.add_argument('--coffeescript_compiler', help='Coffeescript compiler for developing', default=coffeescript, metavar='executable_path')
parser.add_argument('--version', action='version', version='ZeroNet %s' % self.version)
return parser
# Find arguments specificed for current action
def getActionArguments(self):
back = {}
arguments = self.parser._subparsers._group_actions[0].choices[self.action]._actions[1:] # First is --version
for argument in arguments:
back[argument.dest] = getattr(self, argument.dest)
return back
# Try to find action from sys.argv
def getAction(self, argv):
actions = [action.choices.keys() for action in self.parser._actions if action.dest == "action"][0] # Valid actions
found_action = False
for action in actions: # See if any in sys.argv
if action in argv:
found_action = action
break
return found_action
# Parse command line arguments
def parseCommandline(self, argv):
# Find out if action is specificed on start
action = self.getAction(argv)
if len(argv) == 1 or not action: # If no action specificed set the main action
argv.append("main")
if "zeronet.py" in argv[0]:
self.arguments = self.parser.parse_args(argv[1:])
else: # Silent errors if not started with zeronet.py
self.arguments = self.parser.parse_args(argv[1:])
# Parse config file
def parseConfig(self, argv):
if os.path.isfile("zeronet.conf"):
config = ConfigParser.ConfigParser(allow_no_value=True)
config.read('zeronet.conf')
for section in config.sections():
for key, val in config.items(section):
if section != "global": # If not global prefix key with section
key = section+"_"+key
if val: argv.insert(1, val)
argv.insert(1, "--%s" % key)
return argv
# Expose arguments as class attributes
def setAttributes(self):
# Set attributes from arguments
args = vars(self.arguments)
for key, val in args.items():
setattr(self, key, val)
config = Config()

26
src/Crypt/CryptBitcoin.py Normal file
View File

@ -0,0 +1,26 @@
from src.lib.BitcoinECC import BitcoinECC
import hashlib
def newPrivatekey(): # Return new private key
bitcoin = BitcoinECC.Bitcoin()
bitcoin.GeneratePrivateKey()
return bitcoin.PrivateEncoding()
def privatekeyToAddress(privatekey): # Return address from private key
bitcoin = BitcoinECC.Bitcoin()
bitcoin.BitcoinAddressFromPrivate(privatekey)
return bitcoin.BitcoinAddresFromPublicKey()
def sign(data, privatekey): # Return sign to data using private key
bitcoin = BitcoinECC.Bitcoin()
bitcoin.BitcoinAddressFromPrivate(privatekey)
sign = bitcoin.SignECDSA(data)
return sign
def verify(data, address, sign): # Verify data using address and sign
bitcoin = BitcoinECC.Bitcoin()
return bitcoin.VerifyMessageFromBitcoinAddress(address, data, sign)

18
src/Crypt/CryptHash.py Normal file
View File

@ -0,0 +1,18 @@
import hashlib
def sha1sum(file, blocksize=65536):
if hasattr(file, "endswith"): # Its a string open it
file = open(file, "rb")
hash = hashlib.sha1()
for block in iter(lambda: file.read(blocksize), ""):
hash.update(block)
return hash.hexdigest()
if __name__ == "__main__":
import cStringIO as StringIO
a = StringIO.StringIO()
a.write("hello!")
a.seek(0)
print hashlib.sha1("hello!").hexdigest()
print sha1sum(a)

0
src/Crypt/__init__.py Normal file
View File

22
src/Debug/DebugHook.py Normal file
View File

@ -0,0 +1,22 @@
import gevent, sys
last_error = None
def handleError(*args):
global last_error
if not args: # Get last error
args = sys.exc_info()
silent = True
else:
silent = False
print "Error catched", args
last_error = args
if not silent: sys.__excepthook__(*args)
OriginalGreenlet = gevent.Greenlet
class ErrorhookedGreenlet(OriginalGreenlet):
def _report_error(self, exc_info):
handleError(exc_info[0], exc_info[1], exc_info[2])
sys.excepthook = handleError
gevent.Greenlet = gevent.greenlet.Greenlet = ErrorhookedGreenlet
reload(gevent)

60
src/Debug/DebugMedia.py Normal file
View File

@ -0,0 +1,60 @@
import os, subprocess, re, logging, time
from Config import config
# Find files with extension in path
def findfiles(path, find_ext):
for root, dirs, files in os.walk(path, topdown = False):
for file in sorted(files):
file_path = root+"/"+file
file_ext = file.split(".")[-1]
if file_ext in find_ext and not file.startswith("all."): yield file_path
# Generates: all.js: merge *.js, compile coffeescript, all.css: merge *.css, vendor prefix features
def merge(merged_path):
merge_dir = os.path.dirname(merged_path)
s = time.time()
ext = merged_path.split(".")[-1]
if ext == "js": # If merging .js find .coffee too
find_ext = ["js", "coffee"]
else:
find_ext = [ext]
# If exits check the other files modification date
if os.path.isfile(merged_path):
merged_mtime = os.path.getmtime(merged_path)
changed = False
for file_path in findfiles(merge_dir, find_ext):
if os.path.getmtime(file_path) > merged_mtime:
changed = True
break
if not changed: return # Assets not changed, nothing to do
# Merge files
parts = []
for file_path in findfiles(merge_dir, find_ext):
parts.append("\n\n/* ---- %s ---- */\n\n" % file_path)
if file_path.endswith(".coffee"): # Compile coffee script
if not config.coffeescript_compiler:
logging.error("No coffeescript compiler definied, skipping compiling %s" % merged_path)
return False # No coffeescript compiler, skip this file
command = config.coffeescript_compiler % file_path.replace("/", "\\")
s = time.time()
compiler = subprocess.Popen(command, shell=True, stderr=subprocess.PIPE, stdout=subprocess.PIPE)
logging.debug("Running: %s (Done in %.2fs)" % (command, time.time()-s))
source = compiler.stdout.read()
if source:
parts.append(source)
else:
error = compiler.stderr.read()
parts.append("alert('%s compile error: %s');" % (file_path, re.escape(error)) )
else: # Add to parts
parts.append(open(file_path).read())
merged = "\n".join(parts)
if ext == "css": # Vendor prefix css
from lib.cssvendor import cssvendor
merged = cssvendor.prefix(merged)
merged = merged.replace("\r", "")
open(merged_path, "wb").write(merged)
logging.debug("Merged %s (%.2fs)" % (merged_path, time.time()-s))

View File

@ -0,0 +1,35 @@
import logging, os, sys, time
import threading
try:
from fs.osfs import OSFS
pyfilesystem = OSFS("src")
except Exception, err:
logging.info("%s: For autoreload please download pyfilesystem (https://code.google.com/p/pyfilesystem/)" % err)
pyfilesystem = False
class DebugReloader:
def __init__ (self, callback, directory = "/"):
if pyfilesystem:
self.directory = directory
self.callback = callback
logging.debug("Adding autoreload: %s, cb: %s" % (directory, callback))
thread = threading.Thread(target=self.addWatcher)
thread.daemon = True
thread.start()
def addWatcher(self, recursive=True):
try:
time.sleep(1) # Wait for .pyc compiles
pyfilesystem.add_watcher(self.changed, path=self.directory, events=None, recursive=recursive)
except Exception, err:
print "File system watcher failed: %s (on linux pyinotify not gevent compatible yet :( )" % err
def changed(self, evt):
if not evt.path or evt.path.endswith("pyc"): return False # Ignore *.pyc changes
#logging.debug("Changed: %s" % evt)
time.sleep(0.1) # Wait for lock release
self.callback()

1
src/Debug/__init__.py Normal file
View File

@ -0,0 +1 @@
from DebugReloader import DebugReloader

98
src/File/FileRequest.py Normal file
View File

@ -0,0 +1,98 @@
import os, msgpack, shutil
from Site import SiteManager
from cStringIO import StringIO
FILE_BUFF = 1024*512
# Request from me
class FileRequest:
def __init__(self, server = None):
if server:
self.server = server
self.log = server.log
self.sites = SiteManager.list()
def send(self, msg):
if not isinstance(msg, dict): # If msg not a dict create a {"body": msg}
msg = {"body": msg}
self.server.socket.send(msgpack.packb(msg, use_bin_type=True))
# Route file requests
def route(self, cmd, params):
if cmd == "getFile":
self.actionGetFile(params)
elif cmd == "update":
self.actionUpdate(params)
elif cmd == "ping":
self.actionPing()
else:
self.actionUnknown(cmd, params)
# Update a site file request
def actionUpdate(self, params):
site = self.sites.get(params["site"])
if not site or not site.settings["serving"]: # Site unknown or not serving
self.send({"error": "Unknown site"})
return False
if site.settings["own"]:
self.log.debug("Someone trying to push a file to own site %s, reload local content.json first" % site.address)
site.loadContent()
buff = StringIO(params["body"])
valid = site.verifyFile(params["inner_path"], buff)
if valid == True: # Valid and changed
buff.seek(0)
file = open(site.getPath(params["inner_path"]), "wb")
shutil.copyfileobj(buff, file) # Write buff to disk
file.close()
if params["inner_path"] == "content.json": # Download every changed file from peer
changed = site.loadContent() # Get changed files
peer = site.addPeer(*params["peer"], return_peer = True) # Add or get peer
self.log.info("%s changed files: %s" % (site.address_short, changed))
for inner_path in changed: # Updated files in content.json
site.needFile(inner_path, peer=peer, update=True, blocking=False) # Download file from peer
site.onComplete.once(lambda: site.publish()) # On complete publish to other peers
self.send({"ok": "Thanks, file %s updated!" % params["inner_path"]})
elif valid == None: # Not changed
peer = site.addPeer(*params["peer"], return_peer = True) # Add or get peer
for task in site.worker_manager.tasks: # New peer add to every ongoing task
site.needFile(task["inner_path"], peer=peer, update=True, blocking=False) # Download file from peer
self.send({"ok": "File file not changed"})
else: # Invalid sign or sha1 hash
self.send({"error": "File invalid"})
# Send file content request
def actionGetFile(self, params):
site = self.sites.get(params["site"])
if not site or not site.settings["serving"]: # Site unknown or not serving
self.send({"error": "Unknown site"})
return False
try:
file = open(site.getPath(params["inner_path"]), "rb")
file.seek(params["location"])
back = {}
back["body"] = file.read(FILE_BUFF)
back["location"] = file.tell()
back["size"] = os.fstat(file.fileno()).st_size
self.send(back)
except Exception, err:
self.send({"error": "File read error: %s" % err})
return False
# Send a simple Pong! answer
def actionPing(self):
self.send("Pong!")
# Unknown command
def actionUnknown(self, cmd, params):
self.send({"error": "Unknown command: %s" % cmd})

165
src/File/FileServer.py Normal file
View File

@ -0,0 +1,165 @@
import os, logging, urllib2, urllib, re, time
import gevent, msgpack
import zmq.green as zmq
from Config import config
from FileRequest import FileRequest
from Site import SiteManager
class FileServer:
def __init__(self):
self.ip = config.fileserver_ip
self.port = config.fileserver_port
self.log = logging.getLogger(__name__)
if config.ip_external: # Ip external definied in arguments
self.port_opened = True
SiteManager.peer_blacklist.append((config.ip_external, self.port)) # Add myself to peer blacklist
else:
self.port_opened = None # Is file server opened on router
self.sites = SiteManager.list()
# Handle request to fileserver
def handleRequest(self, msg):
if "params" in msg:
self.log.debug("FileRequest: %s %s %s" % (msg["cmd"], msg["params"].get("site"), msg["params"].get("inner_path")))
else:
self.log.debug("FileRequest: %s" % msg["cmd"])
req = FileRequest(self)
req.route(msg["cmd"], msg.get("params"))
# Reload the FileRequest class to prevent restarts in debug mode
def reload(self):
global FileRequest
import imp
FileRequest = imp.load_source("FileRequest", "src/File/FileRequest.py").FileRequest
# Try to open the port using upnp
def openport(self, port=None, check=True):
if not port: port = self.port
if self.port_opened: return True # Port already opened
if check: # Check first if its already opened
if self.testOpenport(port)["result"] == True:
return True # Port already opened
if config.upnpc: # If we have upnpc util, try to use it to puch port on our router
self.log.info("Try to open port using upnpc...")
try:
exit = os.system("%s -e ZeroNet -r %s tcp" % (config.upnpc, self.port))
if exit == 0:
upnpc_success = True
else:
upnpc_success = False
except Exception, err:
self.log.error("Upnpc run error: %s" % err)
upnpc_success = False
if upnpc_success and self.testOpenport(port)["result"] == True:
return True
self.log.info("Upnp mapping failed :( Please forward port %s on your router to your ipaddress" % port)
return False
# Test if the port is open
def testOpenport(self, port = None):
time.sleep(1) # Wait for port open
if not port: port = self.port
self.log.info("Checking port %s using canyouseeme.org..." % port)
try:
data = urllib2.urlopen("http://www.canyouseeme.org/", "port=%s" % port, timeout=20.0).read()
message = re.match('.*<p style="padding-left:15px">(.*?)</p>', data, re.DOTALL).group(1)
message = re.sub("<.*?>", "", message.replace("<br>", " ").replace("&nbsp;", " ")) # Strip http tags
except Exception, err:
message = "Error: %s" % err
if "Error" in message:
self.log.info("[BAD :(] Port closed: %s" % message)
if port == self.port:
self.port_opened = False # Self port, update port_opened status
config.ip_external = False
return {"result": False, "message": message}
else:
self.log.info("[OK :)] Port open: %s" % message)
if port == self.port: # Self port, update port_opened status
self.port_opened = True
match = re.match(".*?([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)", message) # Try find my external ip in message
if match: # Found my ip in message
config.ip_external = match.group(1)
SiteManager.peer_blacklist.append((config.ip_external, self.port)) # Add myself to peer blacklist
else:
config.ip_external = False
return {"result": True, "message": message}
# Set external ip without testing
def setIpExternal(self, ip_external):
logging.info("Setting external ip without testing: %s..." % ip_external)
config.ip_external = ip_external
self.port_opened = True
# Check site file integrity
def checkSite(self, site):
if site.settings["serving"]:
site.announce() # Announce site to tracker
site.update() # Update site's content.json and download changed files
# Check sites integrity
def checkSites(self):
if self.port_opened == None: # Test and open port if not tested yet
self.openport()
self.log.debug("Checking sites integrity..")
for address, site in self.sites.items(): # Check sites integrity
gevent.spawn(self.checkSite, site) # Check in new thread
time.sleep(2) # Prevent too quick request
# Announce sites every 10 min
def announceSites(self):
while 1:
time.sleep(10*60) # Announce sites every 10 min
for address, site in self.sites.items():
if site.settings["serving"]:
site.announce() # Announce site to tracker
time.sleep(2) # Prevent too quick request
# Bind and start serving sites
def start(self, check_sites = True):
self.log = logging.getLogger(__name__)
if config.debug:
# Auto reload FileRequest on change
from Debug import DebugReloader
DebugReloader(self.reload)
self.context = zmq.Context()
socket = self.context.socket(zmq.REP)
self.socket = socket
self.socket.setsockopt(zmq.RCVTIMEO, 5000) # Wait for data receive
self.log.info("Binding to tcp://%s:%s" % (self.ip, self.port))
try:
self.socket.bind('tcp://%s:%s' % (self.ip, self.port))
except Exception, err:
self.log.error("Can't bind, FileServer must be running already")
return
if check_sites: # Open port, Update sites, Check files integrity
gevent.spawn(self.checkSites)
gevent.spawn(self.announceSites)
while True:
try:
ret = {}
req = msgpack.unpackb(socket.recv())
self.handleRequest(req)
except Exception, err:
self.log.error(err)
self.socket.send(msgpack.packb({"error": "%s" % err}, use_bin_type=True))
if config.debug: # Raise exception
import sys
sys.excepthook(*sys.exc_info())

2
src/File/__init__.py Normal file
View File

@ -0,0 +1,2 @@
from FileServer import FileServer
from FileRequest import FileRequest

84
src/Peer/Peer.py Normal file
View File

@ -0,0 +1,84 @@
import os, logging, gevent, time, msgpack
import zmq.green as zmq
from cStringIO import StringIO
from Config import config
context = zmq.Context()
# Communicate remote peers
class Peer:
def __init__(self, ip, port):
self.ip = ip
self.port = port
self.socket = None
self.last_found = None
self.added = time.time()
self.hash_failed = 0
self.download_bytes = 0
self.download_time = 0
# Connect to host
def connect(self):
self.log = logging.getLogger("Peer:%s:%s" % (self.ip, self.port))
self.socket = context.socket(zmq.REQ)
self.socket.setsockopt(zmq.SNDTIMEO, 5000) # Wait for data send
self.socket.setsockopt(zmq.LINGER, 500) # Wait for socket close
self.socket.connect('tcp://%s:%s' % (self.ip, self.port))
# Done working with peer
def disconnect(self):
pass
# Found a peer on tracker
def found(self):
self.last_found = time.time()
# Send a command to peer
def sendCmd(self, cmd, params = {}):
if not self.socket: self.connect()
try:
self.socket.send(msgpack.packb({"cmd": cmd, "params": params}, use_bin_type=True))
response = msgpack.unpackb(self.socket.recv())
if "error" in response:
self.log.error("%s %s error: %s" % (cmd, params, response["error"]))
return response
except Exception, err:
self.log.error("%s" % err)
if config.debug:
import traceback
traceback.print_exc()
self.socket.close()
time.sleep(1)
self.connect()
return None
# Get a file content from peer
def getFile(self, site, inner_path):
location = 0
buff = StringIO()
s = time.time()
while 1: # Read in 512k parts
back = self.sendCmd("getFile", {"site": site, "inner_path": inner_path, "location": location}) # Get file content from last location
if "body" not in back: # Error
return False
buff.write(back["body"])
if back["location"] == back["size"]: # End of file
break
else:
location = back["location"]
self.download_bytes += back["location"]
self.download_time += (time.time() - s)
buff.seek(0)
return buff
# Send a ping request
def ping(self):
return self.sendCmd("ping")

1
src/Peer/__init__.py Normal file
View File

@ -0,0 +1 @@
from Peer import Peer

432
src/Site/Site.py Normal file
View File

@ -0,0 +1,432 @@
import os, json, logging, hashlib, re, time, string, random
from lib.subtl.subtl import UdpTrackerClient
import gevent
import util
from Config import config
from Peer import Peer
from Worker import WorkerManager
from Crypt import CryptHash
import SiteManager
class Site:
def __init__(self, address, allow_create=True):
self.address = re.sub("[^A-Za-z0-9]", "", address) # Make sure its correct address
self.address_short = "%s..%s" % (self.address[:6], self.address[-4:]) # Short address for logging
self.directory = "data/%s" % self.address # Site data diretory
self.log = logging.getLogger("Site:%s" % self.address_short)
if not os.path.isdir(self.directory):
if allow_create:
os.mkdir(self.directory) # Create directory if not found
else:
raise Exception("Directory not exists: %s" % self.directory)
self.content = None # Load content.json
self.peers = {} # Key: ip:port, Value: Peer.Peer
self.peer_blacklist = SiteManager.peer_blacklist # Ignore this peers (eg. myself)
self.last_announce = 0 # Last announce time to tracker
self.worker_manager = WorkerManager(self) # Handle site download from other peers
self.bad_files = {} # SHA1 check failed files, need to redownload
self.content_updated = None # Content.js update time
self.last_downloads = [] # Files downloaded in run of self.download()
self.notifications = [] # Pending notifications displayed once on page load [error|ok|info, message, timeout]
self.page_requested = False # Page viewed in browser
self.loadContent(init=True) # Load content.json
self.loadSettings() # Load settings from sites.json
if not self.settings.get("auth_key"):
self.settings["auth_key"] = ''.join(random.choice(string.ascii_uppercase + string.digits) for _ in range(12)) # To auth websocket
self.log.debug("New auth key: %s" % self.settings["auth_key"])
self.saveSettings()
self.websockets = [] # Active site websocket connections
# Add event listeners
self.addEventListeners()
# Load content.json to self.content
def loadContent(self, init=False):
old_content = self.content
content_path = "%s/content.json" % self.directory
if os.path.isfile(content_path):
try:
new_content = json.load(open(content_path))
except Exception, err:
self.log.error("Content.json load error: %s" % err)
return None
else:
return None # Content.json not exits
try:
changed = []
for inner_path, details in new_content["files"].items():
new_sha1 = details["sha1"]
if old_content and old_content["files"].get(inner_path):
old_sha1 = old_content["files"][inner_path]["sha1"]
else:
old_sha1 = None
if old_sha1 != new_sha1: changed.append(inner_path)
self.content = new_content
except Exception, err:
self.log.error("Content.json parse error: %s" % err)
return None # Content.json parse error
# Add to bad files
if not init:
for inner_path in changed:
self.bad_files[inner_path] = True
return changed
# Load site settings from data/sites.json
def loadSettings(self):
sites_settings = json.load(open("data/sites.json"))
if self.address in sites_settings:
self.settings = sites_settings[self.address]
else:
if self.address == config.homepage: # Add admin permissions to homepage
permissions = ["ADMIN"]
else:
permissions = []
self.settings = { "own": False, "serving": True, "permissions": permissions } # Default
return
# Save site settings to data/sites.json
def saveSettings(self):
sites_settings = json.load(open("data/sites.json"))
sites_settings[self.address] = self.settings
open("data/sites.json", "w").write(json.dumps(sites_settings, indent=4, sort_keys=True))
return
# Sercurity check and return path of site's file
def getPath(self, inner_path):
inner_path = inner_path.replace("\\", "/") # Windows separator fix
inner_path = re.sub("^%s/" % re.escape(self.directory), "", inner_path) # Remove site directory if begins with it
file_path = self.directory+"/"+inner_path
allowed_dir = os.path.abspath(self.directory) # Only files within this directory allowed
if ".." in file_path or not os.path.dirname(os.path.abspath(file_path)).startswith(allowed_dir):
raise Exception("File not allowed: %s" % file_path)
return file_path
# Start downloading site
@util.Noparallel(blocking=False)
def download(self):
self.log.debug("Start downloading...")
self.announce()
found = self.needFile("content.json", update=self.bad_files.get("content.json"))
if not found: return False # Could not download content.json
self.loadContent() # Load the content.json
self.log.debug("Got content.json")
evts = []
self.last_downloads = ["content.json"] # Files downloaded in this run
for inner_path in self.content["files"].keys():
res = self.needFile(inner_path, blocking=False, update=self.bad_files.get(inner_path)) # No waiting for finish, return the event
if res != True: # Need downloading
self.last_downloads.append(inner_path)
evts.append(res) # Append evt
self.log.debug("Downloading %s files..." % len(evts))
s = time.time()
gevent.joinall(evts)
self.log.debug("All file downloaded in %.2fs" % (time.time()-s))
# Update content.json from peers and download changed files
@util.Noparallel()
def update(self):
self.loadContent() # Reload content.json
self.content_updated = None
self.needFile("content.json", update=True)
changed_files = self.loadContent()
if changed_files:
for changed_file in changed_files:
self.bad_files[changed_file] = True
self.checkFiles(quick_check=True) # Quick check files based on file size
if self.bad_files:
self.download()
return changed_files
# Update content.json on peers
def publish(self, limit=3):
self.log.info("Publishing to %s/%s peers..." % (limit, len(self.peers)))
published = 0
for key, peer in self.peers.items(): # Send update command to each peer
result = {"exception": "Timeout"}
try:
with gevent.Timeout(2, False): # 2 sec timeout
result = peer.sendCmd("update", {
"site": self.address,
"inner_path": "content.json",
"body": open(self.getPath("content.json")).read(),
"peer": (config.ip_external, config.fileserver_port)
})
except Exception, err:
result = {"exception": err}
if result and "ok" in result:
published += 1
self.log.info("[OK] %s: %s" % (key, result["ok"]))
else:
self.log.info("[ERROR] %s: %s" % (key, result))
if published >= limit: break
self.log.info("Successfuly published to %s peers" % published)
return published
# Check and download if file not exits
def needFile(self, inner_path, update=False, blocking=True, peer=None):
if os.path.isfile(self.getPath(inner_path)) and not update: # File exits, no need to do anything
return True
elif self.settings["serving"] == False: # Site not serving
return False
else: # Wait until file downloaded
if not self.content: # No content.json, download it first!
self.log.debug("Need content.json first")
self.announce()
if inner_path != "content.json": # Prevent double download
task = self.worker_manager.addTask("content.json", peer)
task.get()
self.loadContent()
if not self.content: return False
task = self.worker_manager.addTask(inner_path, peer)
if blocking:
return task.get()
else:
return task
# Add or update a peer to site
def addPeer(self, ip, port, return_peer = False):
key = "%s:%s" % (ip, port)
if key in self.peers: # Already has this ip
self.peers[key].found()
if return_peer: # Always return peer
return self.peers[key]
else:
return False
else: # New peer
peer = Peer(ip, port)
self.peers[key] = peer
return peer
# Add myself and get other peers from tracker
def announce(self, force=False):
if time.time() < self.last_announce+15 and not force: return # No reannouncing within 15 secs
self.last_announce = time.time()
for protocol, ip, port in SiteManager.TRACKERS:
if protocol == "udp":
self.log.debug("Announing to %s://%s:%s..." % (protocol, ip, port))
tracker = UdpTrackerClient(ip, port)
tracker.peer_port = config.fileserver_port
try:
tracker.connect()
tracker.poll_once()
tracker.announce(info_hash=hashlib.sha1(self.address).hexdigest())
back = tracker.poll_once()
except Exception, err:
self.log.error("Tracker error: %s" % err)
continue
if back: # Tracker announce success
peers = back["response"]["peers"]
added = 0
for peer in peers:
if (peer["addr"], peer["port"]) in self.peer_blacklist: # Ignore blacklist (eg. myself)
continue
if self.addPeer(peer["addr"], peer["port"]): added += 1
if added:
self.worker_manager.onPeers()
self.updateWebsocket(peers_added=added)
self.log.debug("Found %s peers, new: %s" % (len(peers), added))
break # Successful announcing, break the list
else:
self.log.error("Tracker bad response, trying next in list...") # Failed to announce, go to next
time.sleep(1)
else:
pass # TODO: http tracker support
# Check and try to fix site files integrity
def checkFiles(self, quick_check=True):
self.log.debug("Checking files... Quick:%s" % quick_check)
bad_files = self.verifyFiles(quick_check)
if bad_files:
for bad_file in bad_files:
self.bad_files[bad_file] = True
# - Events -
# Add event listeners
def addEventListeners(self):
self.onFileStart = util.Event() # If WorkerManager added new task
self.onFileDone = util.Event() # If WorkerManager successfuly downloaded a file
self.onFileFail = util.Event() # If WorkerManager failed to download a file
self.onComplete = util.Event() # All file finished
self.onFileStart.append(lambda inner_path: self.fileStarted()) # No parameters to make Noparallel batching working
self.onFileDone.append(lambda inner_path: self.fileDone(inner_path))
self.onFileFail.append(lambda inner_path: self.fileFailed(inner_path))
# Send site status update to websocket clients
def updateWebsocket(self, **kwargs):
if kwargs:
param = {"event": kwargs.items()[0]}
else:
param = None
for ws in self.websockets:
ws.event("siteChanged", self, param)
# File download started
@util.Noparallel(blocking=False)
def fileStarted(self):
time.sleep(0.001) # Wait for other files adds
self.updateWebsocket(file_started=True)
# File downloaded successful
def fileDone(self, inner_path):
# File downloaded, remove it from bad files
if inner_path in self.bad_files:
self.log.debug("Bad file solved: %s" % inner_path)
del(self.bad_files[inner_path])
# Update content.json last downlad time
if inner_path == "content.json":
self.content_updated = time.time()
self.updateWebsocket(file_done=inner_path)
# File download failed
def fileFailed(self, inner_path):
if inner_path == "content.json":
self.content_updated = False
self.log.error("Can't update content.json")
self.updateWebsocket(file_failed=inner_path)
# - Sign and verify -
# Verify fileobj using sha1 in content.json
def verifyFile(self, inner_path, file, force=False):
if inner_path == "content.json": # Check using sign
from Crypt import CryptBitcoin
try:
content = json.load(file)
if self.content and not force:
if self.content["modified"] == content["modified"]: # Ignore, have the same content.json
return None
elif self.content["modified"] > content["modified"]: # We have newer
return False
if content["modified"] > time.time()+60*60*24: # Content modified in the far future (allow 1 day window)
self.log.error("Content.json modify is in the future!")
return False
# Check sign
sign = content["sign"]
del(content["sign"]) # The file signed without the sign
sign_content = json.dumps(content, sort_keys=True) # Dump the json to string to remove whitepsace
return CryptBitcoin.verify(sign_content, self.address, sign)
except Exception, err:
self.log.error("Verify sign error: %s" % err)
return False
else: # Check using sha1 hash
if self.content and inner_path in self.content["files"]:
return CryptHash.sha1sum(file) == self.content["files"][inner_path]["sha1"]
else: # File not in content.json
self.log.error("File not in content.json: %s" % inner_path)
return False
# Verify all files sha1sum using content.json
def verifyFiles(self, quick_check=False): # Fast = using file size
bad_files = []
if not self.content: # No content.json, download it first
self.needFile("content.json", update=True) # Force update to fix corrupt file
self.loadContent() # Reload content.json
for inner_path in self.content["files"].keys():
file_path = self.getPath(inner_path)
if not os.path.isfile(file_path):
self.log.error("[MISSING] %s" % inner_path)
bad_files.append(inner_path)
continue
if quick_check:
ok = os.path.getsize(file_path) == self.content["files"][inner_path]["size"]
else:
ok = self.verifyFile(inner_path, open(file_path, "rb"))
if ok:
self.log.debug("[OK] %s" % inner_path)
else:
self.log.error("[ERROR] %s" % inner_path)
bad_files.append(inner_path)
return bad_files
# Create and sign content.json using private key
def signContent(self, privatekey=None):
if not self.content: # New site
self.log.info("Site not exits yet, loading default content.json values...")
self.content = {"files": {}, "title": "%s - ZeroNet_" % self.address, "sign": "", "modified": 0.0, "description": "", "address": self.address, "ignore": ""} # Default content.json
self.log.info("Opening site data directory: %s..." % self.directory)
hashed_files = {}
for root, dirs, files in os.walk(self.directory):
for file_name in files:
file_path = self.getPath("%s/%s" % (root, file_name))
if file_name == "content.json" or (self.content["ignore"] and re.match(self.content["ignore"], file_path.replace(self.directory+"/", "") )): # Dont add content.json and ignore regexp pattern definied in content.json
self.log.info("- [SKIPPED] %s" % file_path)
else:
sha1sum = CryptHash.sha1sum(file_path) # Calculate sha sum of file
inner_path = re.sub("^%s/" % re.escape(self.directory), "", file_path)
self.log.info("- %s (SHA1: %s)" % (file_path, sha1sum))
hashed_files[inner_path] = {"sha1": sha1sum, "size": os.path.getsize(file_path)}
# Generate new content.json
self.log.info("Adding timestamp and sha1sums to new content.json...")
import datetime, time
content = self.content.copy() # Create a copy of current content.json
content["address"] = self.address # Add files sha1 hash
content["files"] = hashed_files # Add files sha1 hash
content["modified"] = time.mktime(datetime.datetime.utcnow().utctimetuple()) # Add timestamp
del(content["sign"]) # Delete old site
# Signing content
from Crypt import CryptBitcoin
self.log.info("Verifying private key...")
privatekey_address = CryptBitcoin.privatekeyToAddress(privatekey)
if self.address != privatekey_address:
return self.log.error("Private key invalid! Site address: %s, Private key address: %s" % (self.address, privatekey_address))
self.log.info("Signing modified content.json...")
sign_content = json.dumps(content, sort_keys=True)
self.log.debug("Content: %s" % sign_content)
sign = CryptBitcoin.sign(sign_content, privatekey)
content["sign"] = sign
# Saving modified content.json
self.log.info("Saving to %s/content.json..." % self.directory)
open("%s/content.json" % self.directory, "w").write(json.dumps(content, indent=4, sort_keys=True))
self.log.info("Site signed!")

62
src/Site/SiteManager.py Normal file
View File

@ -0,0 +1,62 @@
import json, logging, time, re, os
import gevent
TRACKERS = [
("udp", "sugoi.pomf.se", 2710),
("udp", "open.demonii.com", 1337), # Retry 3 times
("udp", "open.demonii.com", 1337),
("udp", "open.demonii.com", 1337),
("udp", "bigfoot1942.sektori.org", 6969),
("udp", "tracker.coppersurfer.tk", 80),
("udp", "tracker.leechers-paradise.org", 6969),
("udp", "tracker.blazing.de", 80),
]
# Load all sites from data/sites.json
def load():
from Site import Site
global sites
if not sites: sites = {}
address_found = []
added = 0
# Load new adresses
for address in json.load(open("data/sites.json")):
if address not in sites and os.path.isfile("data/%s/content.json" % address):
sites[address] = Site(address)
added += 1
address_found.append(address)
# Remove deleted adresses
for address in sites.keys():
if address not in address_found:
del(sites[address])
logging.debug("Removed site: %s" % address)
if added: logging.debug("SiteManager added %s sites" % added)
# Checks if its a valid address
def isAddress(address):
return re.match("^[A-Za-z0-9]{34}$", address)
# Return site and start download site files
def need(address, all_file=True):
from Site import Site
if address not in sites: # Site not exits yet
if not isAddress(address): raise Exception("Not address: %s" % address)
sites[address] = Site(address)
site = sites[address]
if all_file: site.download()
return site
# Lazy load sites
def list():
if sites == None: # Not loaded yet
load()
return sites
sites = None
peer_blacklist = [] # Dont download from this peers

1
src/Site/__init__.py Normal file
View File

@ -0,0 +1 @@
from Site import Site

0
src/Test/__init__.py Normal file
View File

46
src/Test/test.py Normal file
View File

@ -0,0 +1,46 @@
import sys, os, unittest, urllib, time
sys.path.append(os.path.abspath("src")) # Imports relative to src dir
from Crypt import CryptBitcoin
from Ui import UiRequest
class TestCase(unittest.TestCase):
def testMediaRoute(self):
try:
urllib.urlopen("http://127.0.0.1:43110").read()
except Exception, err:
raise unittest.SkipTest(err)
self.assertIn("Not Found", urllib.urlopen("http://127.0.0.1:43110/media//sites.json").read())
self.assertIn("Not Found", urllib.urlopen("http://127.0.0.1:43110/media/./sites.json").read())
self.assertIn("Not Found", urllib.urlopen("http://127.0.0.1:43110/media/../config.py").read())
self.assertIn("Forbidden", urllib.urlopen("http://127.0.0.1:43110/media/1P2rJhkQjYSHdHpWDDwxfRGYXaoWE8u1vV/../sites.json").read())
self.assertIn("Forbidden", urllib.urlopen("http://127.0.0.1:43110/media/1P2rJhkQjYSHdHpWDDwxfRGYXaoWE8u1vV/..//sites.json").read())
self.assertIn("Forbidden", urllib.urlopen("http://127.0.0.1:43110/media/1P2rJhkQjYSHdHpWDDwxfRGYXaoWE8u1vV/../../config.py").read())
def testBitcoinSign(self):
s = time.time()
privatekey = "23DKQpDz7bXM7w5KN5Wnmz7bwRNqNHcdQjb2WwrdB1QtTf5gM3pFdf"
privatekey_bad = "23DKQpDz7bXM7w5KN5Wnmz6bwRNqNHcdQjb2WwrdB1QtTf5gM3pFdf"
address = CryptBitcoin.privatekeyToAddress(privatekey)
self.assertEqual(address, "12vTsjscg4hYPewUL2onma5pgQmWPMs3ez")
address_bad = CryptBitcoin.privatekeyToAddress(privatekey_bad)
self.assertNotEqual(address_bad, "12vTsjscg4hYPewUL2onma5pgQmWPMs3ez")
sign = CryptBitcoin.sign("hello", privatekey)
self.assertTrue(CryptBitcoin.verify("hello", address, sign))
self.assertFalse(CryptBitcoin.verify("not hello", address, sign))
sign_bad = CryptBitcoin.sign("hello", privatekey_bad)
self.assertFalse(CryptBitcoin.verify("hello", address, sign_bad))
print "Taken: %.3fs, " % (time.time()-s),
if __name__ == "__main__":
unittest.main(verbosity=2)

286
src/Ui/UiRequest.py Normal file
View File

@ -0,0 +1,286 @@
import time, re, os, mimetypes, json
from Config import config
from Site import SiteManager
from Ui.UiWebsocket import UiWebsocket
status_texts = {
200: "200 OK",
400: "400 Bad Request",
403: "403 Forbidden",
404: "404 Not Found",
}
class UiRequest:
def __init__(self, server = None):
if server:
self.server = server
self.log = server.log
self.get = {} # Get parameters
self.env = {} # Enviroment settings
self.start_response = None # Start response function
# Call the request handler function base on path
def route(self, path):
if config.ui_restrict and self.env['REMOTE_ADDR'] != config.ui_restrict: # Restict Ui access by ip
return self.error403()
if path == "/":
return self.actionIndex()
elif path == "/favicon.ico":
return self.actionFile("src/Ui/media/img/favicon.ico")
# Media
elif path.startswith("/uimedia/"):
return self.actionUiMedia(path)
elif path.startswith("/media"):
return self.actionSiteMedia(path)
# Websocket
elif path == "/Websocket":
return self.actionWebsocket()
# Debug
elif path == "/Debug" and config.debug:
return self.actionDebug()
elif path == "/Console" and config.debug:
return self.actionConsole()
# Test
elif path == "/Test/Websocket":
return self.actionFile("Data/temp/ws_test.html")
elif path == "/Test/Stream":
return self.actionTestStream()
# Site media wrapper
else:
return self.actionWrapper(path)
# Get mime by filename
def getContentType(self, file_name):
content_type = mimetypes.guess_type(file_name)[0]
if not content_type:
if file_name.endswith("json"): # Correct json header
content_type = "application/json"
else:
content_type = "application/octet-stream"
return content_type
# Send response headers
def sendHeader(self, status=200, content_type="text/html; charset=utf-8", extra_headers=[]):
headers = []
headers.append(("Version", "HTTP/1.1"))
headers.append(("Access-Control-Allow-Origin", "*")) # Allow json access
headers.append(("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept")) # Allow json access
headers.append(("Cache-Control", "no-cache, no-store, private, must-revalidate, max-age=0")) # No caching at all
#headers.append(("Cache-Control", "public, max-age=604800")) # Cache 1 week
headers.append(("Content-Type", content_type))
for extra_header in extra_headers:
headers.append(extra_header)
self.start_response(status_texts[status], headers)
# Renders a template
def render(self, template_path, *args, **kwargs):
#template = SimpleTemplate(open(template_path), lookup=[os.path.dirname(template_path)])
#yield str(template.render(*args, **kwargs).encode("utf8"))
template = open(template_path).read()
yield template.format(**kwargs)
# - Actions -
# Redirect to an url
def actionRedirect(self, url):
self.start_response('301 Redirect', [('Location', url)])
yield "Location changed: %s" % url
def actionIndex(self):
return self.actionRedirect("/"+config.homepage)
# Render a file from media with iframe site wrapper
def actionWrapper(self, path):
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)
if match:
inner_path = match.group("inner_path").lstrip("/")
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: # Its downloaded
title = site.content["title"]
else:
title = "Loading %s..." % match.group("site")
site = SiteManager.need(match.group("site")) # Start download site
if not site: self.error404()
self.sendHeader(extra_headers=[("X-Frame-Options", "DENY")])
return self.render("src/Ui/template/wrapper.html",
inner_path=inner_path,
address=match.group("site"),
title=title,
auth_key=site.settings["auth_key"],
permissions=json.dumps(site.settings["permissions"]),
show_loadingscreen=json.dumps(not os.path.isfile(site.getPath(inner_path))),
homepage=config.homepage
)
else: # Bad url
return self.error404(path)
# Serve a media for site
def actionSiteMedia(self, path):
match = re.match("/media/(?P<site>[A-Za-z0-9]+)/(?P<inner_path>.*)", path)
referer = self.env.get("HTTP_REFERER")
if referer: # Only allow same site to receive media
referer = re.sub("http://.*?/", "/", referer) # Remove server address
referer = referer.replace("/media", "") # Media
if not referer.startswith("/"+match.group("site")): return self.error403() # Referer not starts same address as requested path
if match: # Looks like a valid path
file_path = "data/%s/%s" % (match.group("site"), match.group("inner_path"))
allowed_dir = os.path.abspath("data/%s" % match.group("site")) # Only files within data/sitehash allowed
if ".." in file_path or not os.path.dirname(os.path.abspath(file_path)).startswith(allowed_dir): # File not in allowed path
return self.error403()
else:
if config.debug and file_path.split("/")[-1].startswith("all."): # When debugging merge *.css to all.css and *.js to all.js
site = self.server.sites.get(match.group("site"))
if site.settings["own"]:
from Debug import DebugMedia
DebugMedia.merge(file_path)
if os.path.isfile(file_path): # File exits
return self.actionFile(file_path)
else: # File not exits, try to download
site = SiteManager.need(match.group("site"), all_file=False)
self.sendHeader(content_type=self.getContentType(file_path)) # ?? Get Exception without this
result = site.needFile(match.group("inner_path")) # Wait until file downloads
return self.actionFile(file_path)
else: # Bad url
return self.error404(path)
# Serve a media for ui
def actionUiMedia(self, path):
match = re.match("/uimedia/(?P<inner_path>.*)", path)
if match: # Looks like a valid path
file_path = "src/Ui/media/%s" % match.group("inner_path")
allowed_dir = os.path.abspath("src/Ui/media") # Only files within data/sitehash allowed
if ".." in file_path or not os.path.dirname(os.path.abspath(file_path)).startswith(allowed_dir): # File not in allowed path
return self.error403()
else:
if config.debug and match.group("inner_path").startswith("all."): # When debugging merge *.css to all.css and *.js to all.js
from Debug import DebugMedia
DebugMedia.merge(file_path)
return self.actionFile(file_path)
else: # Bad url
return self.error400()
# Stream a file to client
def actionFile(self, file_path, block_size = 64*1024):
if os.path.isfile(file_path):
# Try to figure out content type by extension
content_type = self.getContentType(file_path)
self.sendHeader(content_type = content_type) # TODO: Dont allow external access: extra_headers=[("Content-Security-Policy", "default-src 'unsafe-inline' data: http://localhost:43110 ws://localhost:43110")]
if self.env["REQUEST_METHOD"] != "OPTIONS":
file = open(file_path, "rb")
while 1:
try:
block = file.read(block_size)
if block:
yield block
else:
raise StopIteration
except StopIteration:
file.close()
break
else: # File not exits
yield self.error404(file_path)
# On websocket connection
def actionWebsocket(self):
ws = self.env.get("wsgi.websocket")
if ws:
auth_key = self.get["auth_key"]
# Find site by auth_key
site = None
for site_check in self.server.sites.values():
if site_check.settings["auth_key"] == auth_key: site = site_check
if site: # Correct auth key
ui_websocket = UiWebsocket(ws, site, self.server)
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)
if ui_websocket in site_check.websockets:
site_check.websockets.remove(ui_websocket)
return "Bye."
else: # No site found by auth key
self.log.error("Auth key not found: %s" % auth_key)
return self.error403()
else:
start_response("400 Bad Request", [])
return "Not a websocket!"
# Debug last error
def actionDebug(self):
# Raise last error from DebugHook
import sys
last_error = sys.modules["src.main"].DebugHook.last_error
if last_error:
raise last_error[0], last_error[1], last_error[2]
else:
self.sendHeader()
yield "No error! :)"
# Just raise an error to get console
def actionConsole(self):
raise Exception("Here is your console")
# - Tests -
def actionTestStream(self):
self.sendHeader()
yield " "*1080 # Overflow browser's buffer
yield "He"
time.sleep(1)
yield "llo!"
yield "Running websockets: %s" % len(self.server.websockets)
self.server.sendMessage("Hello!")
# - Errors -
# Send bad request error
def error400(self):
self.sendHeader(400)
return "Bad Request"
# You are not allowed to access this
def error403(self):
self.sendHeader(403)
return "Forbidden"
# Send file not found error
def error404(self, path = None):
self.sendHeader(404)
return "Not Found: %s" % path
# - Reload for eaiser developing -
def reload(self):
import imp
global UiWebsocket
UiWebsocket = imp.load_source("UiWebsocket", "src/Ui/UiWebsocket.py").UiWebsocket

93
src/Ui/UiServer.py Normal file
View File

@ -0,0 +1,93 @@
from gevent import monkey; monkey.patch_all(thread = False)
import logging, time, cgi, string, random
from gevent.pywsgi import WSGIServer
from gevent.pywsgi import WSGIHandler
from lib.geventwebsocket.handler import WebSocketHandler
from Ui import UiRequest
from Site import SiteManager
from Config import config
# Skip websocket handler if not necessary
class UiWSGIHandler(WSGIHandler):
def __init__(self, *args, **kwargs):
super(UiWSGIHandler, self).__init__(*args, **kwargs)
self.ws_handler = WebSocketHandler(*args, **kwargs)
def run_application(self):
if "HTTP_UPGRADE" in self.environ: # Websocket request
self.ws_handler.__dict__ = self.__dict__ # Match class variables
self.ws_handler.run_application()
else: # Standard HTTP request
#print self.application.__class__.__name__
return super(UiWSGIHandler, self).run_application()
class UiServer:
def __init__(self):
self.ip = config.ui_ip
self.port = config.ui_port
if self.ip == "*": self.ip = "" # Bind all
#self.sidebar_websockets = [] # Sidebar websocket connections
#self.auth_key = ''.join(random.choice(string.ascii_uppercase + string.digits) for _ in range(12)) # Global admin auth key
self.sites = SiteManager.list()
self.log = logging.getLogger(__name__)
self.ui_request = UiRequest(self)
# Handle WSGI request
def handleRequest(self, env, start_response):
path = env["PATH_INFO"]
self.ui_request.env = env
self.ui_request.start_response = start_response
if env.get("QUERY_STRING"):
self.ui_request.get = dict(cgi.parse_qsl(env['QUERY_STRING']))
else:
self.ui_request.get = {}
return self.ui_request.route(path)
# Send a message to all connected client
def sendMessage(self, message):
sent = 0
for ws in self.websockets:
try:
ws.send(message)
sent += 1
except Exception, err:
self.log.error("addMessage error: %s" % err)
self.server.websockets.remove(ws)
return sent
# Reload the UiRequest class to prevent restarts in debug mode
def reload(self):
import imp
self.ui_request = imp.load_source("UiRequest", "src/Ui/UiRequest.py").UiRequest(self)
self.ui_request.reload()
# Bind and run the server
def start(self):
handler = self.handleRequest
if config.debug:
# Auto reload UiRequest on change
from Debug import DebugReloader
DebugReloader(self.reload)
# Werkzeug Debugger
try:
from werkzeug.debug import DebuggedApplication
handler = DebuggedApplication(self.handleRequest, evalex=True)
except Exception, err:
self.log.info("%s: For debugging please download Werkzeug (http://werkzeug.pocoo.org/)" % err)
from Debug import DebugReloader
self.log.write = lambda msg: self.log.debug(msg.strip()) # For Wsgi access.log
self.log.info("--------------------------------------")
self.log.info("Web interface: http://%s:%s/" % (config.ui_ip, config.ui_port))
self.log.info("--------------------------------------")
WSGIServer((self.ip, self.port), handler, handler_class=UiWSGIHandler, log=self.log).serve_forever()

217
src/Ui/UiWebsocket.py Normal file
View File

@ -0,0 +1,217 @@
import json, gevent, time, sys, hashlib
from Config import config
from Site import SiteManager
class UiWebsocket:
def __init__(self, ws, site, server):
self.ws = ws
self.site = site
self.server = server
self.next_message_id = 1
self.waiting_cb = {} # Waiting for callback. Key: message_id, Value: function pointer
self.channels = [] # Channels joined to
# Start listener loop
def start(self):
ws = self.ws
if self.site.address == config.homepage and not self.site.page_requested: # Add open fileserver port message or closed port error to homepage at first request after start
if config.ip_external:
self.site.notifications.append(["done", "Congratulation, your port <b>"+str(config.fileserver_port)+"</b> is opened. <br>You are full member of ZeroNet network!", 10000])
elif config.ip_external == False:
self.site.notifications.append(["error", "Your network connection is restricted. Please, open <b>"+str(config.fileserver_port)+"</b> port <br>on your router to become full member of ZeroNet network.", 0])
self.site.page_requested = True # Dont add connection notification anymore
for notification in self.site.notifications: # Send pending notification messages
self.cmd("notification", notification)
self.site.notifications = []
while True:
try:
message = ws.receive()
if message:
self.handleRequest(message)
except Exception, err:
if err.message != 'Connection is already closed':
if config.debug: # Allow websocket errors to appear on /Debug
import sys
sys.modules["src.main"].DebugHook.handleError()
self.site.log.error("WebSocket error: %s" % err)
return "Bye."
# Event in a channel
def event(self, channel, *params):
if channel in self.channels: # We are joined to channel
if channel == "siteChanged":
site = params[0] # Triggerer site
site_info = self.siteInfo(site)
if len(params) > 1 and params[1]: # Extra data
site_info.update(params[1])
self.cmd("setSiteInfo", site_info)
# Send response to client (to = message.id)
def response(self, to, result):
self.send({"cmd": "response", "to": to, "result": result})
# Send a command
def cmd(self, cmd, params={}, cb = None):
self.send({"cmd": cmd, "params": params}, cb)
# Encode to json and send message
def send(self, message, cb = None):
message["id"] = self.next_message_id # Add message id to allow response
self.next_message_id += 1
self.ws.send(json.dumps(message))
if cb: # Callback after client responsed
self.waiting_cb[message["id"]] = cb
# Handle incoming messages
def handleRequest(self, data):
req = json.loads(data)
cmd = req["cmd"]
permissions = self.site.settings["permissions"]
if cmd == "response":
self.actionResponse(req)
elif cmd == "ping":
self.actionPing(req["id"])
elif cmd == "channelJoin":
self.actionChannelJoin(req["id"], req["params"])
elif cmd == "siteInfo":
self.actionSiteInfo(req["id"], req["params"])
elif cmd == "serverInfo":
self.actionServerInfo(req["id"], req["params"])
elif cmd == "siteUpdate":
self.actionSiteUpdate(req["id"], req["params"])
# Admin commands
elif cmd == "sitePause" and "ADMIN" in permissions:
self.actionSitePause(req["id"], req["params"])
elif cmd == "siteResume" and "ADMIN" in permissions:
self.actionSiteResume(req["id"], req["params"])
elif cmd == "siteList" and "ADMIN" in permissions:
self.actionSiteList(req["id"], req["params"])
elif cmd == "channelJoinAllsite" and "ADMIN" in permissions:
self.actionChannelJoinAllsite(req["id"], req["params"])
# Unknown command
else:
self.response(req["id"], "Unknown command: %s" % cmd)
# - Actions -
# Do callback on response {"cmd": "response", "to": message_id, "result": result}
def actionResponse(self, req):
if req["to"] in self.waiting_cb:
self.waiting_cb(req["result"]) # Call callback function
else:
self.site.log.error("Websocket callback not found: %s" % req)
# Send a simple pong answer
def actionPing(self, to):
self.response(to, "pong")
# Format site info
def siteInfo(self, site):
ret = {
"auth_id": self.site.settings["auth_key"][0:10],
"auth_id_md5": hashlib.md5(self.site.settings["auth_key"][0:10]).hexdigest(),
"address": site.address,
"settings": site.settings,
"content_updated": site.content_updated,
"bad_files": site.bad_files.keys(),
"last_downloads": site.last_downloads,
"peers": len(site.peers),
"tasks": [task["inner_path"] for task in site.worker_manager.tasks],
"content": site.content
}
if site.settings["serving"] and site.content: ret["peers"] += 1 # Add myself if serving
return ret
# Send site details
def actionSiteInfo(self, to, params):
ret = self.siteInfo(self.site)
self.response(to, ret)
# Join to an event channel
def actionChannelJoin(self, to, params):
if params["channel"] not in self.channels:
self.channels.append(params["channel"])
# Server variables
def actionServerInfo(self, to, params):
ret = {
"ip_external": config.ip_external,
"platform": sys.platform,
"fileserver_ip": config.fileserver_ip,
"fileserver_port": config.fileserver_port,
"ui_ip": config.ui_ip,
"ui_port": config.ui_port,
"debug": config.debug
}
self.response(to, ret)
# - Admin actions -
# List all site info
def actionSiteList(self, to, params):
ret = []
SiteManager.load() # Reload sites
for site in self.server.sites.values():
if not site.content: continue # Broken site
ret.append(self.siteInfo(site))
self.response(to, ret)
# Join to an event channel on all sites
def actionChannelJoinAllsite(self, to, params):
if params["channel"] not in self.channels: # Add channel to channels
self.channels.append(params["channel"])
for site in self.server.sites.values(): # Add websocket to every channel
if self not in site.websockets:
site.websockets.append(self)
# Update site content.json
def actionSiteUpdate(self, to, params):
address = params.get("address")
site = self.server.sites.get(address)
if site and (site.address == self.site.address or "ADMIN" in self.site.settings["permissions"]):
gevent.spawn(site.update)
else:
self.response(to, {"error": "Unknown site: %s" % address})
# Pause site serving
def actionSitePause(self, to, params):
address = params.get("address")
site = self.server.sites.get(address)
if site:
site.settings["serving"] = False
site.saveSettings()
site.updateWebsocket()
else:
self.response(to, {"error": "Unknown site: %s" % address})
# Resume site serving
def actionSiteResume(self, to, params):
address = params.get("address")
site = self.server.sites.get(address)
if site:
site.settings["serving"] = True
site.saveSettings()
gevent.spawn(site.update)
time.sleep(0.001) # Wait for update thread starting
site.updateWebsocket()
else:
self.response(to, {"error": "Unknown site: %s" % address})

3
src/Ui/__init__.py Normal file
View File

@ -0,0 +1,3 @@
from UiServer import UiServer
from UiRequest import UiRequest
from UiWebsocket import UiWebsocket

View File

@ -0,0 +1,40 @@
class Loading
constructor: ->
if window.show_loadingscreen then @showScreen()
showScreen: ->
$(".loadingscreen").css("display", "block").addClassLater("ready")
@screen_visible = true
@printLine "&nbsp;&nbsp;&nbsp;Connecting..."
# We dont need loadingscreen anymore
hideScreen: ->
if @screen_visible # Hide with animate
$(".loadingscreen").addClass("done").removeLater(2000)
else # Not visible, just remove
$(".loadingscreen").remove()
@screen_visible = false
# Append text to last line of loadingscreen
print: (text, type="normal") ->
if not @screen_visible then return false
$(".loadingscreen .console .cursor").remove() # Remove previous cursor
last_line = $(".loadingscreen .console .console-line:last-child")
if type == "error" then text = "<span class='console-error'>#{text}</span>"
last_line.html(last_line.html()+text)
# Add line to loading screen
printLine: (text, type="normal") ->
if not @screen_visible then return false
$(".loadingscreen .console .cursor").remove() # Remove previous cursor
if type == "error" then text = "<span class='console-error'>#{text}</span>" else text = text+"<span class='cursor'> </span>"
$(".loadingscreen .console").append("<div class='console-line'>#{text}</div>")
window.Loading = Loading

View File

@ -0,0 +1,68 @@
class Notifications
constructor: (@elem) ->
@
test: ->
setTimeout (=>
@add("connection", "error", "Connection lost to <b>UiServer</b> on <b>localhost</b>!")
@add("message-Anyone", "info", "New from <b>Anyone</b>.")
), 1000
setTimeout (=>
@add("connection", "done", "<b>UiServer</b> connection recovered.", 5000)
), 3000
add: (id, type, body, timeout=0) ->
@log id, type, body, timeout
# Close notifications with same id
for elem in $(".notification-#{id}")
@close $(elem)
# Create element
elem = $(".notification.template", @elem).clone().removeClass("template")
elem.addClass("notification-#{type}").addClass("notification-#{id}")
# Update text
if type == "error"
$(".notification-icon", elem).html("!")
else if type == "done"
$(".notification-icon", elem).html("<div class='icon-success'></div>")
else
$(".notification-icon", elem).html("i")
$(".body", elem).html(body)
elem.appendTo(@elem)
# Timeout
if timeout
$(".close", elem).remove() # No need of close button
setTimeout (=>
@close elem
), timeout
# Animate
width = elem.outerWidth()
if not timeout then width += 20 # Add space for close button
elem.css({"width": "50px", "transform": "scale(0.01)"})
elem.animate({"scale": 1}, 800, "easeOutElastic")
elem.animate({"width": width}, 700, "easeInOutCubic")
# Close button
$(".close", elem).on "click", =>
@close elem
return false
@
close: (elem) ->
elem.stop().animate {"width": 0, "opacity": 0}, 700, "easeInOutCubic"
elem.slideUp 300, (-> elem.remove())
log: (args...) ->
console.log "[Notifications]", args...
window.Notifications = Notifications

View File

@ -0,0 +1,33 @@
class Sidebar
constructor: ->
@initFixbutton()
initFixbutton: ->
$(".fixbutton-bg").on "mouseover", ->
$(@).stop().animate({"scale": 0.7}, 800, "easeOutElastic")
$(".fixbutton-burger").stop().animate({"opacity": 1.5, "left": 0}, 800, "easeOutElastic")
$(".fixbutton-text").stop().animate({"opacity": 0, "left": 20}, 300, "easeOutCubic")
$(".fixbutton-bg").on "mouseout", ->
$(@).stop().animate({"scale": 0.6}, 300, "easeOutCubic")
$(".fixbutton-burger").stop().animate({"opacity": 0, "left": -20}, 300, "easeOutCubic")
$(".fixbutton-text").stop().animate({"opacity": 1, "left": 0}, 300, "easeOutBack")
###$(".fixbutton-bg").on "click", ->
return false
###
$(".fixbutton-bg").on "mousedown", ->
$(".fixbutton-burger").stop().animate({"scale": 0.7, "left": 0}, 300, "easeOutCubic")
#$("#inner-iframe").toggleClass("back")
#$(".wrapper-iframe").stop().animate({"scale": 0.9}, 600, "easeOutCubic")
#$("body").addClass("back")
$(".fixbutton-bg").on "mouseup", ->
$(".fixbutton-burger").stop().animate({"scale": 1, "left": 0}, 600, "easeOutElastic")
window.Sidebar = Sidebar

152
src/Ui/media/Wrapper.coffee Normal file
View File

@ -0,0 +1,152 @@
class Wrapper
constructor: (ws_url) ->
@log "Created!"
@loading = new Loading()
@notifications = new Notifications($(".notifications"))
@sidebar = new Sidebar()
window.addEventListener("message", @onMessageInner, false)
@inner = document.getElementById("inner-iframe").contentWindow
@ws = new ZeroWebsocket(ws_url)
@ws.next_message_id = 1000000 # Avoid messageid collision :)
@ws.onOpen = @onOpenWebsocket
@ws.onClose = @onCloseWebsocket
@ws.onMessage = @onMessageWebsocket
@ws.connect()
@ws_error = null # Ws error message
@site_info = null # Hold latest site info
@inner_loaded = false # If iframe loaded or not
@inner_ready = false # Inner frame ready to receive messages
@wrapperWsInited = false # Wrapper notified on websocket open
@site_error = null # Latest failed file download
window.onload = @onLoad # On iframe loaded
@
# Incoming message from UiServer websocket
onMessageWebsocket: (e) =>
message = JSON.parse(e.data)
cmd = message.cmd
if cmd == "response"
if @ws.waiting_cb[message.to]? # We are waiting for response
@ws.waiting_cb[message.to](message.result)
else
@sendInner message # Pass message to inner frame
else if cmd == "notification" # Display notification
@notifications.add("notification-#{message.id}", message.params[0], message.params[1], message.params[2])
else if cmd == "setSiteInfo"
@sendInner message # Pass to inner frame
if message.params.address == window.address # Current page
@setSiteInfo message.params
else
@sendInner message # Pass message to inner frame
# Incoming message from inner frame
onMessageInner: (e) =>
message = e.data
cmd = message.cmd
if cmd == "innerReady"
@inner_ready = true
@log "innerReady", @ws.ws.readyState, @wrapperWsInited
if @ws.ws.readyState == 1 and not @wrapperWsInited # If ws already opened
@sendInner {"cmd": "wrapperOpenedWebsocket"}
@wrapperWsInited = true
else if cmd == "wrapperNotification"
@notifications.add("notification-#{message.id}", message.params[0], message.params[1], message.params[2])
else # Send to websocket
@ws.send(message) # Pass message to websocket
onOpenWebsocket: (e) =>
@ws.cmd "channelJoin", {"channel": "siteChanged"} # Get info on modifications
@log "onOpenWebsocket", @inner_ready, @wrapperWsInited
if not @wrapperWsInited and @inner_ready
@sendInner {"cmd": "wrapperOpenedWebsocket"} # Send to inner frame
@wrapperWsInited = true
if @inner_loaded # Update site info
@reloadSiteInfo()
# If inner frame not loaded for 2 sec show peer informations on loading screen by loading site info
setTimeout (=>
if not @site_info then @reloadSiteInfo()
), 2000
if @ws_error
@notifications.add("connection", "done", "Connection with <b>UiServer Websocket</b> recovered.", 6000)
@ws_error = null
onCloseWebsocket: (e) =>
@wrapperWsInited = false
setTimeout (=> # Wait a bit, maybe its page closing
@sendInner {"cmd": "wrapperClosedWebsocket"} # Send to inner frame
if e.code == 1000 # Server error please reload page
@ws_error = @notifications.add("connection", "error", "UiServer Websocket error, please reload the page.")
else if not @ws_error
@ws_error = @notifications.add("connection", "error", "Connection with <b>UiServer Websocket</b> was lost. Reconnecting...")
), 500
# Iframe loaded
onLoad: (e) =>
@log "onLoad", e
@inner_loaded = true
if not @inner_ready then @sendInner {"cmd": "wrapperReady"} # Inner frame loaded before wrapper
if not @site_error then @loading.hideScreen() # Hide loading screen
if @ws.ws.readyState == 1 and not @site_info # Ws opened
@reloadSiteInfo()
# Send message to innerframe
sendInner: (message) ->
@inner.postMessage(message, '*')
# Get site info from UiServer
reloadSiteInfo: ->
@ws.cmd "siteInfo", {}, (site_info) =>
@setSiteInfo site_info
window.document.title = site_info.content.title+" - ZeroNet"
@log "Setting title to", window.document.title
# Got setSiteInfo from websocket UiServer
setSiteInfo: (site_info) ->
if site_info.event? # If loading screen visible add event to it
# File started downloading
if site_info.event[0] == "file_added" and site_info.bad_files.length
@loading.printLine("#{site_info.bad_files.length} files needs to be downloaded")
# File finished downloading
else if site_info.event[0] == "file_done"
@loading.printLine("#{site_info.event[1]} downloaded")
if site_info.event[1] == window.inner_path # File downloaded we currently on
@loading.hideScreen()
if not $(".loadingscreen").length # Loading screen already removed (loaded +2sec)
@notifications.add("modified", "info", "New version of this page has just released.<br>Reload to see the modified content.")
# File failed downloading
else if site_info.event[0] == "file_failed"
@site_error = site_info.event[1]
@loading.printLine("#{site_info.event[1]} download failed", "error")
# New peers found
else if site_info.event[0] == "peers_added"
@loading.printLine("Peers found: #{site_info.peers}")
if @loading.screen_visible and not @site_info # First site info display current peers
if site_info.peers > 1
@loading.printLine "Peers found: #{site_info.peers}"
else
@site_error = "No peers found"
@loading.printLine "No peers found"
@site_info = site_info
log: (args...) ->
console.log "[Wrapper]", args...
ws_url = "ws://#{window.location.hostname}:#{window.location.port}/Websocket?auth_key=#{window.auth_key}"
window.wrapper = new Wrapper(ws_url)

102
src/Ui/media/Wrapper.css Normal file
View File

@ -0,0 +1,102 @@
body { margin: 0px; padding: 0px; height: 100%; background-color: #D2CECD; overflow: hidden }
body.back { background-color: #090909 }
a { color: black }
.template { display: none !important }
#inner-iframe { width: 100%; height: 100%; position: absolute; border: 0px; transition: all 0.8s cubic-bezier(0.68, -0.55, 0.265, 1.55), opacity 0.8s ease-in-out }
#inner-iframe.back { transform: scale(0.95) translate(-300px, 0px); opacity: 0.4 }
/* Fixbutton */
.fixbutton {
position: absolute; right: 35px; top: 15px; width: 40px; z-index: 999;
text-align: center; color: white; font-family: Consolas; font-size: 25px; line-height: 40px;
}
.fixbutton-bg {
border-radius: 80px; background-color: rgba(180, 180, 180, 0.5); cursor: pointer;
display: block; width: 80px; height: 80px; transition: background-color 0.2s, box-shadow 0.5s; transform: scale(0.6); margin-left: -20px; margin-top: -20px; /* 2x size to prevent blur on anim */
/*box-shadow: inset 105px 260px 0px -200px rgba(0,0,0,0.1);*/ /* box-shadow: inset -75px 183px 0px -200px rgba(0,0,0,0.1); */
}
.fixbutton-text { pointer-events: none; position: absolute; z-index: 999; width: 40px; backface-visibility: hidden; perspective: 1000px }
.fixbutton-burger { pointer-events: none; position: absolute; z-index: 999; width: 40px; opacity: 0; left: -20px }
.fixbutton-bg:hover { background-color: #AF3BFF }
/* Notification */
.notifications { position: absolute; top: 0px; right: 85px; display: inline-block; z-index: 999; white-space: nowrap }
.notification {
position: relative; float: right; clear: both; margin: 10px; height: 50px; box-sizing: border-box; overflow: hidden; backface-visibility: hidden; perspective: 1000px;
background-color: white; color: #4F4F4F; font-family: 'Helvetica Neue', 'Segoe UI', Helvetica, Arial, sans-serif; font-size: 14px; line-height: 20px
}
.notification-icon {
display: block; width: 50px; height: 50px; position: absolute; float: left; z-index: 1;
text-align: center; background-color: #e74c3c; line-height: 45px; vertical-align: bottom; font-size: 40px; color: white;
}
.notification .body { max-width: 420px; padding-left: 68px; padding-right: 17px; height: 50px; vertical-align: middle; display: table-cell }
.notification.visible { max-width: 350px }
.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 }
/* Notification types */
.notification-info .notification-icon { font-size: 22px; font-weight: bold; background-color: #2980b9; line-height: 48px }
.notification-done .notification-icon { font-size: 22px; background-color: #27ae60 }
/* 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) }
/* Loading screen */
.loadingscreen { width: 100%; height: 100%; position: absolute; background-color: #EEE; z-index: 1; overflow: hidden; display: none }
.loading-text { text-align: center; vertical-align: middle; top: 50%; position: absolute; margin-top: 39px; width: 100% }
/* Console */
.console { line-height: 24px; font-family: monospace; font-size: 14px; color: #ADADAD; text-transform: uppercase; opacity: 0; transform: translateY(-20px); }
.console-line:last-child { color: #6C6767 }
.console .cursor {
background-color: #999; color: #999; animation: pulse 1.5s infinite ease-in-out; margin-right: -9px;
display: inline-block; width: 9px; height: 19px; vertical-align: -4px;
}
.console .console-error { color: #e74c3c; font-weight: bold; animation: pulse 2s infinite linear }
/* Flipper loading anim */
.flipper-container { width: 40px; height: 40px; position: absolute; top: 0%; left: 50%; transform: translate3d(-50%, -50%, 0); perspective: 1200; opacity: 0 }
.flipper { position: relative; display: block; height: inherit; width: inherit; animation: flip 1.2s infinite ease-in-out; -webkit-transform-style: preserve-3d; }
.flipper .front, .flipper .back {
position: absolute; top: 0; left: 0; backface-visibility: hidden; /*transform-style: preserve-3d;*/ display: block;
background-color: #d50000; height: 100%; width: 100%; /*outline: 1px solid transparent; /* FF AA fix */
}
.flipper .back { background-color: white; z-index: 800; transform: rotateY(-180deg) }
/* Loading ready */
.loadingscreen.ready .console { opacity: 1; transform: translateY(0px); transition: all 0.3s }
.loadingscreen.ready .flipper-container { top: 50%; opacity: 1; transition: all 1s cubic-bezier(1, 0, 0, 1); }
/* Loading done */
.loadingscreen.done { height: 0%; transition: all 1s cubic-bezier(0.6, -0.28, 0.735, 0.045); }
.loadingscreen.done .console { transform: translateY(300px); opacity: 0; transition: all 1.5s }
.loadingscreen.done .flipper-container { opacity: 0; transition: all 1.5s }
/* Animations */
@keyframes flip {
0% { transform: perspective(120px) rotateX(0deg) rotateY(0deg); }
50% { transform: perspective(120px) rotateX(-180.1deg) rotateY(0deg) }
100% { transform: perspective(120px) rotateX(-180deg) rotateY(-179.9deg); }
}
@keyframes pulse {
0% { opacity: 0 }
5% { opacity: 1 }
30% { opacity: 1 }
70% { opacity: 0 }
100% { opacity: 0 }
}

133
src/Ui/media/all.css Normal file
View File

@ -0,0 +1,133 @@
/* ---- src/Ui/media/Wrapper.css ---- */
body { margin: 0px; padding: 0px; height: 100%; background-color: #D2CECD; overflow: hidden }
body.back { background-color: #090909 }
a { color: black }
.template { display: none !important }
#inner-iframe { width: 100%; height: 100%; position: absolute; border: 0px; -webkit-transition: all 0.8s cubic-bezier(0.68, -0.55, 0.265, 1.55), opacity 0.8s ease-in-out ; -moz-transition: all 0.8s cubic-bezier(0.68, -0.55, 0.265, 1.55), opacity 0.8s ease-in-out ; -o-transition: all 0.8s cubic-bezier(0.68, -0.55, 0.265, 1.55), opacity 0.8s ease-in-out ; -ms-transition: all 0.8s cubic-bezier(0.68, -0.55, 0.265, 1.55), opacity 0.8s ease-in-out ; transition: all 0.8s cubic-bezier(0.68, -0.55, 0.265, 1.55), opacity 0.8s ease-in-out }
#inner-iframe.back { -webkit-transform: scale(0.95) translate(-300px, 0px); -moz-transform: scale(0.95) translate(-300px, 0px); -o-transform: scale(0.95) translate(-300px, 0px); -ms-transform: scale(0.95) translate(-300px, 0px); transform: scale(0.95) translate(-300px, 0px) ; opacity: 0.4 }
/* Fixbutton */
.fixbutton {
position: absolute; right: 35px; top: 15px; width: 40px; z-index: 999;
text-align: center; color: white; font-family: Consolas; font-size: 25px; line-height: 40px;
}
.fixbutton-bg {
-webkit-border-radius: 80px; -moz-border-radius: 80px; -o-border-radius: 80px; -ms-border-radius: 80px; border-radius: 80px ; background-color: rgba(180, 180, 180, 0.5); cursor: pointer;
display: block; width: 80px; height: 80px; -webkit-transition: background-color 0.2s, box-shadow 0.5s; -moz-transition: background-color 0.2s, box-shadow 0.5s; -o-transition: background-color 0.2s, box-shadow 0.5s; -ms-transition: background-color 0.2s, box-shadow 0.5s; transition: background-color 0.2s, box-shadow 0.5s ; -webkit-transform: scale(0.6); -moz-transform: scale(0.6); -o-transform: scale(0.6); -ms-transform: scale(0.6); transform: scale(0.6) ; margin-left: -20px; margin-top: -20px; /* 2x size to prevent blur on anim */
/*box-shadow: inset 105px 260px 0px -200px rgba(0,0,0,0.1);*/ /* -webkit-box-shadow: inset -75px 183px 0px -200px rgba(0,0,0,0.1); -moz-box-shadow: inset -75px 183px 0px -200px rgba(0,0,0,0.1); -o-box-shadow: inset -75px 183px 0px -200px rgba(0,0,0,0.1); -ms-box-shadow: inset -75px 183px 0px -200px rgba(0,0,0,0.1); box-shadow: inset -75px 183px 0px -200px rgba(0,0,0,0.1) ; */
}
.fixbutton-text { pointer-events: none; position: absolute; z-index: 999; width: 40px; backface-visibility: hidden; -webkit-perspective: 1000px ; -moz-perspective: 1000px ; -o-perspective: 1000px ; -ms-perspective: 1000px ; perspective: 1000px }
.fixbutton-burger { pointer-events: none; position: absolute; z-index: 999; width: 40px; opacity: 0; left: -20px }
.fixbutton-bg:hover { background-color: #AF3BFF }
/* Notification */
.notifications { position: absolute; top: 0px; right: 85px; display: inline-block; z-index: 999; white-space: nowrap }
.notification {
position: relative; float: right; clear: both; margin: 10px; height: 50px; -webkit-box-sizing: border-box; -moz-box-sizing: border-box; -o-box-sizing: border-box; -ms-box-sizing: border-box; box-sizing: border-box ; overflow: hidden; backface-visibility: hidden; -webkit-perspective: 1000px; -moz-perspective: 1000px; -o-perspective: 1000px; -ms-perspective: 1000px; perspective: 1000px ;
background-color: white; color: #4F4F4F; font-family: 'Helvetica Neue', 'Segoe UI', Helvetica, Arial, sans-serif; font-size: 14px; line-height: 20px
}
.notification-icon {
display: block; width: 50px; height: 50px; position: absolute; float: left; z-index: 1;
text-align: center; background-color: #e74c3c; line-height: 45px; vertical-align: bottom; font-size: 40px; color: white;
}
.notification .body { max-width: 420px; padding-left: 68px; padding-right: 17px; height: 50px; vertical-align: middle; display: table-cell }
.notification.visible { max-width: 350px }
.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 }
/* Notification types */
.notification-info .notification-icon { font-size: 22px; font-weight: bold; background-color: #2980b9; line-height: 48px }
.notification-done .notification-icon { font-size: 22px; background-color: #27ae60 }
/* 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) }
/* Loading screen */
.loadingscreen { width: 100%; height: 100%; position: absolute; background-color: #EEE; z-index: 1; overflow: hidden; display: none }
.loading-text { text-align: center; vertical-align: middle; top: 50%; position: absolute; margin-top: 39px; width: 100% }
/* Console */
.console { line-height: 24px; font-family: monospace; font-size: 14px; color: #ADADAD; text-transform: uppercase; opacity: 0; -webkit-transform: translateY(-20px); -moz-transform: translateY(-20px); -o-transform: translateY(-20px); -ms-transform: translateY(-20px); transform: translateY(-20px) ; }
.console-line:last-child { color: #6C6767 }
.console .cursor {
background-color: #999; color: #999; -webkit-animation: pulse 1.5s infinite ease-in-out; -moz-animation: pulse 1.5s infinite ease-in-out; -o-animation: pulse 1.5s infinite ease-in-out; -ms-animation: pulse 1.5s infinite ease-in-out; animation: pulse 1.5s infinite ease-in-out ; margin-right: -9px;
display: inline-block; width: 9px; height: 19px; vertical-align: -4px;
}
.console .console-error { color: #e74c3c; font-weight: bold; -webkit-animation: pulse 2s infinite linear ; -moz-animation: pulse 2s infinite linear ; -o-animation: pulse 2s infinite linear ; -ms-animation: pulse 2s infinite linear ; animation: pulse 2s infinite linear }
/* Flipper loading anim */
.flipper-container { width: 40px; height: 40px; position: absolute; top: 0%; left: 50%; -webkit-transform: translate3d(-50%, -50%, 0); -moz-transform: translate3d(-50%, -50%, 0); -o-transform: translate3d(-50%, -50%, 0); -ms-transform: translate3d(-50%, -50%, 0); transform: translate3d(-50%, -50%, 0) ; -webkit-perspective: 1200; -moz-perspective: 1200; -o-perspective: 1200; -ms-perspective: 1200; perspective: 1200 ; opacity: 0 }
.flipper { position: relative; display: block; height: inherit; width: inherit; -webkit-animation: flip 1.2s infinite ease-in-out; -moz-animation: flip 1.2s infinite ease-in-out; -o-animation: flip 1.2s infinite ease-in-out; -ms-animation: flip 1.2s infinite ease-in-out; animation: flip 1.2s infinite ease-in-out ; -webkit-transform-style: preserve-3d; }
.flipper .front, .flipper .back {
position: absolute; top: 0; left: 0; backface-visibility: hidden; /*transform-style: preserve-3d;*/ display: block;
background-color: #d50000; height: 100%; width: 100%; /*outline: 1px solid transparent; /* FF AA fix */
}
.flipper .back { background-color: white; z-index: 800; -webkit-transform: rotateY(-180deg) ; -moz-transform: rotateY(-180deg) ; -o-transform: rotateY(-180deg) ; -ms-transform: rotateY(-180deg) ; transform: rotateY(-180deg) }
/* Loading ready */
.loadingscreen.ready .console { opacity: 1; -webkit-transform: translateY(0px); -moz-transform: translateY(0px); -o-transform: translateY(0px); -ms-transform: translateY(0px); transform: translateY(0px) ; -webkit-transition: all 0.3s ; -moz-transition: all 0.3s ; -o-transition: all 0.3s ; -ms-transition: all 0.3s ; transition: all 0.3s }
.loadingscreen.ready .flipper-container { top: 50%; opacity: 1; -webkit-transition: all 1s cubic-bezier(1, 0, 0, 1); -moz-transition: all 1s cubic-bezier(1, 0, 0, 1); -o-transition: all 1s cubic-bezier(1, 0, 0, 1); -ms-transition: all 1s cubic-bezier(1, 0, 0, 1); transition: all 1s cubic-bezier(1, 0, 0, 1) ; }
/* Loading done */
.loadingscreen.done { height: 0%; -webkit-transition: all 1s cubic-bezier(0.6, -0.28, 0.735, 0.045); -moz-transition: all 1s cubic-bezier(0.6, -0.28, 0.735, 0.045); -o-transition: all 1s cubic-bezier(0.6, -0.28, 0.735, 0.045); -ms-transition: all 1s cubic-bezier(0.6, -0.28, 0.735, 0.045); transition: all 1s cubic-bezier(0.6, -0.28, 0.735, 0.045) ; }
.loadingscreen.done .console { -webkit-transform: translateY(300px); -moz-transform: translateY(300px); -o-transform: translateY(300px); -ms-transform: translateY(300px); transform: translateY(300px) ; opacity: 0; -webkit-transition: all 1.5s ; -moz-transition: all 1.5s ; -o-transition: all 1.5s ; -ms-transition: all 1.5s ; transition: all 1.5s }
.loadingscreen.done .flipper-container { opacity: 0; -webkit-transition: all 1.5s ; -moz-transition: all 1.5s ; -o-transition: all 1.5s ; -ms-transition: all 1.5s ; transition: all 1.5s }
/* Animations */
@keyframes flip {
0% { -webkit-transform: perspective(120px) rotateX(0deg) rotateY(0deg); -moz-transform: perspective(120px) rotateX(0deg) rotateY(0deg); -o-transform: perspective(120px) rotateX(0deg) rotateY(0deg); -ms-transform: perspective(120px) rotateX(0deg) rotateY(0deg); transform: perspective(120px) rotateX(0deg) rotateY(0deg) ; }
50% { -webkit-transform: perspective(120px) rotateX(-180.1deg) rotateY(0deg) ; -moz-transform: perspective(120px) rotateX(-180.1deg) rotateY(0deg) ; -o-transform: perspective(120px) rotateX(-180.1deg) rotateY(0deg) ; -ms-transform: perspective(120px) rotateX(-180.1deg) rotateY(0deg) ; transform: perspective(120px) rotateX(-180.1deg) rotateY(0deg) }
100% { -webkit-transform: perspective(120px) rotateX(-180deg) rotateY(-179.9deg); -moz-transform: perspective(120px) rotateX(-180deg) rotateY(-179.9deg); -o-transform: perspective(120px) rotateX(-180deg) rotateY(-179.9deg); -ms-transform: perspective(120px) rotateX(-180deg) rotateY(-179.9deg); transform: perspective(120px) rotateX(-180deg) rotateY(-179.9deg) ; }
}
@-webkit-keyframes flip {
0% { -webkit-transform: perspective(120px) rotateX(0deg) rotateY(0deg); -moz-transform: perspective(120px) rotateX(0deg) rotateY(0deg); -o-transform: perspective(120px) rotateX(0deg) rotateY(0deg); -ms-transform: perspective(120px) rotateX(0deg) rotateY(0deg); transform: perspective(120px) rotateX(0deg) rotateY(0deg) ; }
50% { -webkit-transform: perspective(120px) rotateX(-180.1deg) rotateY(0deg) ; -moz-transform: perspective(120px) rotateX(-180.1deg) rotateY(0deg) ; -o-transform: perspective(120px) rotateX(-180.1deg) rotateY(0deg) ; -ms-transform: perspective(120px) rotateX(-180.1deg) rotateY(0deg) ; transform: perspective(120px) rotateX(-180.1deg) rotateY(0deg) }
100% { -webkit-transform: perspective(120px) rotateX(-180deg) rotateY(-179.9deg); -moz-transform: perspective(120px) rotateX(-180deg) rotateY(-179.9deg); -o-transform: perspective(120px) rotateX(-180deg) rotateY(-179.9deg); -ms-transform: perspective(120px) rotateX(-180deg) rotateY(-179.9deg); transform: perspective(120px) rotateX(-180deg) rotateY(-179.9deg) ; }
}
@-moz-keyframes flip {
0% { -webkit-transform: perspective(120px) rotateX(0deg) rotateY(0deg); -moz-transform: perspective(120px) rotateX(0deg) rotateY(0deg); -o-transform: perspective(120px) rotateX(0deg) rotateY(0deg); -ms-transform: perspective(120px) rotateX(0deg) rotateY(0deg); transform: perspective(120px) rotateX(0deg) rotateY(0deg) ; }
50% { -webkit-transform: perspective(120px) rotateX(-180.1deg) rotateY(0deg) ; -moz-transform: perspective(120px) rotateX(-180.1deg) rotateY(0deg) ; -o-transform: perspective(120px) rotateX(-180.1deg) rotateY(0deg) ; -ms-transform: perspective(120px) rotateX(-180.1deg) rotateY(0deg) ; transform: perspective(120px) rotateX(-180.1deg) rotateY(0deg) }
100% { -webkit-transform: perspective(120px) rotateX(-180deg) rotateY(-179.9deg); -moz-transform: perspective(120px) rotateX(-180deg) rotateY(-179.9deg); -o-transform: perspective(120px) rotateX(-180deg) rotateY(-179.9deg); -ms-transform: perspective(120px) rotateX(-180deg) rotateY(-179.9deg); transform: perspective(120px) rotateX(-180deg) rotateY(-179.9deg) ; }
}
@keyframes pulse {
0% { opacity: 0 }
5% { opacity: 1 }
30% { opacity: 1 }
70% { opacity: 0 }
100% { opacity: 0 }
}
@-webkit-keyframes pulse {
0% { opacity: 0 }
5% { opacity: 1 }
30% { opacity: 1 }
70% { opacity: 0 }
100% { opacity: 0 }
}
@-moz-keyframes pulse {
0% { opacity: 0 }
5% { opacity: 1 }
30% { opacity: 1 }
70% { opacity: 0 }
100% { opacity: 0 }
}

895
src/Ui/media/all.js Normal file

File diff suppressed because one or more lines are too long

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

4
src/Ui/media/lib/00-jquery.min.js vendored Normal file

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,81 @@
class ZeroWebsocket
constructor: (url) ->
@url = url
@next_message_id = 1
@waiting_cb = {}
@init()
init: ->
@
connect: ->
@ws = new WebSocket(@url)
@ws.onmessage = @onMessage
@ws.onopen = @onOpenWebsocket
@ws.onerror = @onErrorWebsocket
@ws.onclose = @onCloseWebsocket
onMessage: (e) =>
message = JSON.parse(e.data)
cmd = message.cmd
if cmd == "response"
if @waiting_cb[message.to]?
@waiting_cb[message.to](message.result)
else
@log "Websocket callback not found:", message
else if cmd == "ping"
@response message.id, "pong"
else
@route cmd, message
route: (cmd, message) =>
@log "Unknown command", message
response: (to, result) ->
@send {"cmd": "response", "to": to, "result": result}
cmd: (cmd, params={}, cb=null) ->
@send {"cmd": cmd, "params": params}, cb
send: (message, cb=null) ->
if not message.id?
message.id = @next_message_id
@next_message_id += 1
@ws.send(JSON.stringify(message))
if cb
@waiting_cb[message.id] = cb
log: (args...) =>
console.log "[ZeroWebsocket]", args...
onOpenWebsocket: (e) =>
@log "Open", e
if @onOpen? then @onOpen(e)
onErrorWebsocket: (e) =>
@log "Error", e
if @onError? then @onError(e)
onCloseWebsocket: (e) =>
@log "Closed", e
if e.code == 1000
@log "Server error, please reload the page"
else # Connection error
setTimeout (=>
@log "Reconnecting..."
@connect()
), 10000
if @onClose? then @onClose(e)
window.ZeroWebsocket = ZeroWebsocket

View File

@ -0,0 +1,27 @@
jQuery.cssHooks['scale'] = {
get: function(elem, computed, extra) {
var match = window.getComputedStyle(elem).transform.match("[0-9\.]+")
if (match) {
var scale = parseFloat(match[0])
return scale
} else {
return 1.0
}
},
set: function(elem, val) {
//var transforms = $(elem).css("transform").match(/[0-9\.]+/g)
var transforms = window.getComputedStyle(elem).transform.match(/[0-9\.]+/g)
if (transforms) {
transforms[0] = val
transforms[3] = val
//$(elem).css("transform", 'matrix('+transforms.join(", ")+")")
elem.style.transform = 'matrix('+transforms.join(", ")+')'
} else {
elem.style.transform = "scale("+val+")"
}
}
}
jQuery.fx.step.scale = function(fx) {
jQuery.cssHooks['scale'].set(fx.elem, fx.now)
};

View File

@ -0,0 +1,35 @@
jQuery.fn.readdClass = (class_name) ->
elem = @
elem.removeClass class_name
setTimeout ( ->
elem.addClass class_name
), 1
return @
jQuery.fn.removeLater = (time = 500) ->
elem = @
setTimeout ( ->
elem.remove()
), time
return @
jQuery.fn.hideLater = (time = 500) ->
elem = @
setTimeout ( ->
elem.css("display", "none")
), time
return @
jQuery.fn.addClassLater = (class_name, time = 5) ->
elem = @
setTimeout ( ->
elem.addClass(class_name)
), time
return @
jQuery.fn.cssLater = (name, val, time = 500) ->
elem = @
setTimeout ( ->
elem.css name, val
), time
return @

View File

@ -0,0 +1,205 @@
/*
* jQuery Easing v1.3 - http://gsgd.co.uk/sandbox/jquery/easing/
*
* Uses the built in easing capabilities added In jQuery 1.1
* to offer multiple easing options
*
* TERMS OF USE - jQuery Easing
*
* Open source under the BSD License.
*
* Copyright © 2008 George McGinley Smith
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification,
* are permitted provided that the following conditions are met:
*
* Redistributions of source code must retain the above copyright notice, this list of
* conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice, this list
* of conditions and the following disclaimer in the documentation and/or other materials
* provided with the distribution.
*
* Neither the name of the author nor the names of contributors may be used to endorse
* or promote products derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
* GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
* OF THE POSSIBILITY OF SUCH DAMAGE.
*
*/
// t: current time, b: begInnIng value, c: change In value, d: duration
jQuery.easing['jswing'] = jQuery.easing['swing'];
jQuery.extend( jQuery.easing,
{
def: 'easeOutQuad',
swing: function (x, t, b, c, d) {
//alert(jQuery.easing.default);
return jQuery.easing[jQuery.easing.def](x, t, b, c, d);
},
easeInQuad: function (x, t, b, c, d) {
return c*(t/=d)*t + b;
},
easeOutQuad: function (x, t, b, c, d) {
return -c *(t/=d)*(t-2) + b;
},
easeInOutQuad: function (x, t, b, c, d) {
if ((t/=d/2) < 1) return c/2*t*t + b;
return -c/2 * ((--t)*(t-2) - 1) + b;
},
easeInCubic: function (x, t, b, c, d) {
return c*(t/=d)*t*t + b;
},
easeOutCubic: function (x, t, b, c, d) {
return c*((t=t/d-1)*t*t + 1) + b;
},
easeInOutCubic: function (x, t, b, c, d) {
if ((t/=d/2) < 1) return c/2*t*t*t + b;
return c/2*((t-=2)*t*t + 2) + b;
},
easeInQuart: function (x, t, b, c, d) {
return c*(t/=d)*t*t*t + b;
},
easeOutQuart: function (x, t, b, c, d) {
return -c * ((t=t/d-1)*t*t*t - 1) + b;
},
easeInOutQuart: function (x, t, b, c, d) {
if ((t/=d/2) < 1) return c/2*t*t*t*t + b;
return -c/2 * ((t-=2)*t*t*t - 2) + b;
},
easeInQuint: function (x, t, b, c, d) {
return c*(t/=d)*t*t*t*t + b;
},
easeOutQuint: function (x, t, b, c, d) {
return c*((t=t/d-1)*t*t*t*t + 1) + b;
},
easeInOutQuint: function (x, t, b, c, d) {
if ((t/=d/2) < 1) return c/2*t*t*t*t*t + b;
return c/2*((t-=2)*t*t*t*t + 2) + b;
},
easeInSine: function (x, t, b, c, d) {
return -c * Math.cos(t/d * (Math.PI/2)) + c + b;
},
easeOutSine: function (x, t, b, c, d) {
return c * Math.sin(t/d * (Math.PI/2)) + b;
},
easeInOutSine: function (x, t, b, c, d) {
return -c/2 * (Math.cos(Math.PI*t/d) - 1) + b;
},
easeInExpo: function (x, t, b, c, d) {
return (t==0) ? b : c * Math.pow(2, 10 * (t/d - 1)) + b;
},
easeOutExpo: function (x, t, b, c, d) {
return (t==d) ? b+c : c * (-Math.pow(2, -10 * t/d) + 1) + b;
},
easeInOutExpo: function (x, t, b, c, d) {
if (t==0) return b;
if (t==d) return b+c;
if ((t/=d/2) < 1) return c/2 * Math.pow(2, 10 * (t - 1)) + b;
return c/2 * (-Math.pow(2, -10 * --t) + 2) + b;
},
easeInCirc: function (x, t, b, c, d) {
return -c * (Math.sqrt(1 - (t/=d)*t) - 1) + b;
},
easeOutCirc: function (x, t, b, c, d) {
return c * Math.sqrt(1 - (t=t/d-1)*t) + b;
},
easeInOutCirc: function (x, t, b, c, d) {
if ((t/=d/2) < 1) return -c/2 * (Math.sqrt(1 - t*t) - 1) + b;
return c/2 * (Math.sqrt(1 - (t-=2)*t) + 1) + b;
},
easeInElastic: function (x, t, b, c, d) {
var s=1.70158;var p=0;var a=c;
if (t==0) return b; if ((t/=d)==1) return b+c; if (!p) p=d*.3;
if (a < Math.abs(c)) { a=c; var s=p/4; }
else var s = p/(2*Math.PI) * Math.asin (c/a);
return -(a*Math.pow(2,10*(t-=1)) * Math.sin( (t*d-s)*(2*Math.PI)/p )) + b;
},
easeOutElastic: function (x, t, b, c, d) {
var s=1.70158;var p=0;var a=c;
if (t==0) return b; if ((t/=d)==1) return b+c; if (!p) p=d*.3;
if (a < Math.abs(c)) { a=c; var s=p/4; }
else var s = p/(2*Math.PI) * Math.asin (c/a);
return a*Math.pow(2,-10*t) * Math.sin( (t*d-s)*(2*Math.PI)/p ) + c + b;
},
easeInOutElastic: function (x, t, b, c, d) {
var s=1.70158;var p=0;var a=c;
if (t==0) return b; if ((t/=d/2)==2) return b+c; if (!p) p=d*(.3*1.5);
if (a < Math.abs(c)) { a=c; var s=p/4; }
else var s = p/(2*Math.PI) * Math.asin (c/a);
if (t < 1) return -.5*(a*Math.pow(2,10*(t-=1)) * Math.sin( (t*d-s)*(2*Math.PI)/p )) + b;
return a*Math.pow(2,-10*(t-=1)) * Math.sin( (t*d-s)*(2*Math.PI)/p )*.5 + c + b;
},
easeInBack: function (x, t, b, c, d, s) {
if (s == undefined) s = 1.70158;
return c*(t/=d)*t*((s+1)*t - s) + b;
},
easeOutBack: function (x, t, b, c, d, s) {
if (s == undefined) s = 1.70158;
return c*((t=t/d-1)*t*((s+1)*t + s) + 1) + b;
},
easeInOutBack: function (x, t, b, c, d, s) {
if (s == undefined) s = 1.70158;
if ((t/=d/2) < 1) return c/2*(t*t*(((s*=(1.525))+1)*t - s)) + b;
return c/2*((t-=2)*t*(((s*=(1.525))+1)*t + s) + 2) + b;
},
easeInBounce: function (x, t, b, c, d) {
return c - jQuery.easing.easeOutBounce (x, d-t, 0, c, d) + b;
},
easeOutBounce: function (x, t, b, c, d) {
if ((t/=d) < (1/2.75)) {
return c*(7.5625*t*t) + b;
} else if (t < (2/2.75)) {
return c*(7.5625*(t-=(1.5/2.75))*t + .75) + b;
} else if (t < (2.5/2.75)) {
return c*(7.5625*(t-=(2.25/2.75))*t + .9375) + b;
} else {
return c*(7.5625*(t-=(2.625/2.75))*t + .984375) + b;
}
},
easeInOutBounce: function (x, t, b, c, d) {
if (t < d/2) return jQuery.easing.easeInBounce (x, t*2, 0, c, d) * .5 + b;
return jQuery.easing.easeOutBounce (x, t*2-d, 0, c, d) * .5 + c*.5 + b;
}
});
/*
*
* TERMS OF USE - EASING EQUATIONS
*
* Open source under the BSD License.
*
* Copyright © 2001 Robert Penner
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification,
* are permitted provided that the following conditions are met:
*
* Redistributions of source code must retain the above copyright notice, this list of
* conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice, this list
* of conditions and the following disclaimer in the documentation and/or other materials
* provided with the distribution.
*
* Neither the name of the author nor the names of contributors may be used to endorse
* or promote products derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
* GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
* OF THE POSSIBILITY OF SUCH DAMAGE.
*
*/

View File

@ -0,0 +1,50 @@
<!DOCTYPE html>
<html>
<head>
<title>{title} - ZeroNet</title>
<meta charset="utf-8">
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
<link rel="stylesheet" href="/uimedia/all.css" />
</head>
<body>
<!-- Fixed button -->
<div class='fixbutton'>
<div class='fixbutton-text'>0</div>
<div class='fixbutton-burger'>&#9776;</div>
<a class='fixbutton-bg' href="/{homepage}"></a>
</div>
<!-- Notifications -->
<div class='notifications'>
<div class='notification template'><span class='notification-icon'>!</span> <span class='body'>Test notification</span><a class="close" href="#Close">&times;</a><div style="clear: both"></div></div>
</div>
<!-- Loadingscreen -->
<div class='loadingscreen'>
<div class='loading-text console'>
</div>
<div class="flipper-container">
<div class="flipper"> <div class="front"></div><div class="back"></div> </div>
</div>
</div>
<!-- Site Iframe -->
<iframe src='/media/{address}/{inner_path}#auth_key={auth_key}' id='inner-iframe' sandbox="allow-forms allow-scripts allow-top-navigation"></iframe>
<!-- Site info -->
<script>address = "{address}"</script>
<script>auth_key = "{auth_key}"</script>
<script>inner_path = "{inner_path}"</script>
<script>permissions = {permissions}</script>
<script>show_loadingscreen = {show_loadingscreen}</script>
<script type="text/javascript" src="/uimedia/all.js" asyc></script>
</body>
</html>

68
src/Worker/Worker.py Normal file
View File

@ -0,0 +1,68 @@
import gevent, time, logging, shutil, os
from Peer import Peer
class Worker:
def __init__(self, manager, peer):
self.manager = manager
self.peer = peer
self.task = None
self.key = None
self.running = False
self.thread = None
# Downloader thread
def downloader(self):
while self.running:
# Try to pickup free file download task
task = self.manager.getTask(self.peer)
if not task: # Die, no more task
self.manager.log.debug("%s: No task found, stopping" % self.key)
break
if task["workers_num"] > 0: # Wait a bit if someone already working on it
self.manager.log.debug("%s: Someone already working on %s, sleeping 1 sec..." % (self.key, task["inner_path"]))
time.sleep(1)
if task["done"] == False:
self.task = task
task["workers_num"] += 1
buff = self.peer.getFile(task["site"].address, task["inner_path"])
if buff: # Download ok
correct = task["site"].verifyFile(task["inner_path"], buff)
else: # Download error
correct = False
if correct == True or correct == None: # Hash ok or same file
self.manager.log.debug("%s: Hash correct: %s" % (self.key, task["inner_path"]))
if task["done"] == False: # Task not done yet
buff.seek(0)
file_path = task["site"].getPath(task["inner_path"])
file_dir = os.path.dirname(file_path)
if not os.path.isdir(file_dir): os.makedirs(file_dir) # Make directory for files
file = open(file_path, "wb")
shutil.copyfileobj(buff, file) # Write buff to disk
file.close()
task["workers_num"] -= 1
self.manager.doneTask(task)
self.task = None
else: # Hash failed
self.task = None
self.peer.hash_failed += 1
if self.peer.hash_failed > 5: # Broken peer
break
task["workers_num"] -= 1
self.manager.log.error("%s: Hash failed: %s" % (self.key, task["inner_path"]))
time.sleep(1)
self.running = False
self.peer.disconnect()
self.manager.removeWorker(self)
# Start the worker
def start(self):
self.running = True
self.thread = gevent.spawn(self.downloader)
def stop(self):
self.running = False
self.manager.removeWorker(self)

131
src/Worker/WorkerManager.py Normal file
View File

@ -0,0 +1,131 @@
from Worker import Worker
import gevent, time, logging
MAX_WORKERS = 10
# Worker manager for site
class WorkerManager:
def __init__(self, site):
self.site = site
self.workers = {} # Key: ip:port, Value: Worker.Worker
self.tasks = [] # {"evt": evt, "workers_num": 0, "site": self.site, "inner_path": inner_path, "done": False, "time_start": time.time(), "peers": peers}
self.log = logging.getLogger("WorkerManager:%s" % self.site.address_short)
self.process_taskchecker = gevent.spawn(self.checkTasks)
# Check expired tasks
def checkTasks(self):
while 1:
time.sleep(15) # Check every 30 sec
if not self.tasks: continue
tasks = self.tasks[:] # Copy it so removing elements wont cause any problem
for task in tasks:
if time.time() >= task["time_start"]+60: # Task timed out
self.log.debug("Cleaning up task: %s" % task)
# Clean up workers
workers = self.findWorkers(task)
for worker in workers:
worker.stop()
# Remove task
self.failTask(task)
elif time.time() >= task["time_start"]+15: # Task taking long time
self.log.debug("Task taking long time, find more peers: %s" % task["inner_path"])
task["site"].announce() # Find more peers
if task["peers"]: # Release the peer olck
self.log.debug("Task peer lock release: %s" % task["inner_path"])
task["peers"] = []
self.startWorkers()
continue # One reannounce per loop
# Returns the next free or less worked task
def getTask(self, peer, only_free=False):
best_task = None
for task in self.tasks: # Find out the task with lowest worker number
if task["peers"] and peer not in task["peers"]: continue # This peer not allowed to pick this task
if task["inner_path"] == "content.json": return task # Content.json always prority
if not best_task or task["workers_num"] < best_task["workers_num"]: # If task has lower worker number then its better
best_task = task
return best_task
# New peers added to site
def onPeers(self):
self.startWorkers()
# Start workers to process tasks
def startWorkers(self):
if len(self.workers) >= MAX_WORKERS: return False # Workers number already maxed
if not self.tasks: return False # No task for workers
for key, peer in self.site.peers.iteritems(): # One worker for every peer
if key not in self.workers and len(self.workers) < MAX_WORKERS: # We dont have worker for that peer and workers num less than max
worker = Worker(self, peer)
self.workers[key] = worker
worker.key = key
worker.start()
self.log.debug("Added worker: %s, workers: %s/%s" % (key, len(self.workers), MAX_WORKERS))
# Find workers by task
def findWorkers(self, task):
workers = []
for worker in self.workers.values():
if worker.task == task: workers.append(worker)
return workers
# Ends and remove a worker
def removeWorker(self, worker):
worker.running = False
del(self.workers[worker.key])
self.log.debug("Removed worker, workers: %s/%s" % (len(self.workers), MAX_WORKERS))
# Create new task and return asyncresult
def addTask(self, inner_path, peer=None):
self.site.onFileStart(inner_path) # First task, trigger site download started
task = self.findTask(inner_path)
if task: # Already has task for that file
if peer and task["peers"]: # This peer has new version too
task["peers"].append(peer)
self.startWorkers()
return task["evt"]
else: # No task for that file yet
evt = gevent.event.AsyncResult()
if peer:
peers = [peer] # Only download from this peer
else:
peers = None
task = {"evt": evt, "workers_num": 0, "site": self.site, "inner_path": inner_path, "done": False, "time_start": time.time(), "peers": peers}
self.tasks.append(task)
self.log.debug("New task: %s" % task)
self.startWorkers()
return evt
# Find a task using inner_path
def findTask(self, inner_path):
for task in self.tasks:
if task["inner_path"] == inner_path:
return task
return None # Not found
# Mark a task failed
def failTask(self, task):
task["done"] = True
self.tasks.remove(task) # Remove from queue
self.site.onFileFail(task["inner_path"])
task["evt"].set(False)
# Mark a task done
def doneTask(self, task):
task["done"] = True
self.tasks.remove(task) # Remove from queue
self.site.onFileDone(task["inner_path"])
task["evt"].set(True)
if not self.tasks: self.site.onComplete() # No more task trigger site compelte

2
src/Worker/__init__.py Normal file
View File

@ -0,0 +1,2 @@
from Worker import Worker
from WorkerManager import WorkerManager

0
src/__init__.py Normal file
View File

View File

@ -0,0 +1,466 @@
# By: HurlSly
# Source: https://github.com/HurlSly/Python/blob/master/BitcoinECC.py
# Modified: random number generator in def GeneratePrivateKey(self):
import random
import hashlib
import os
class GaussInt:
#A class for the Gauss integers of the form a + b sqrt(n) where a,b are integers.
#n can be positive or negative.
def __init__(self,x,y,n,p=0):
if p:
self.x=x%p
self.y=y%p
self.n=n%p
else:
self.x=x
self.y=y
self.n=n
self.p=p
def __add__(self,b):
return GaussInt(self.x+b.x,self.y+b.y,self.n,self.p)
def __sub__(self,b):
return GaussInt(self.x-b.x,self.y-b.y,self.n,self.p)
def __mul__(self,b):
return GaussInt(self.x*b.x+self.n*self.y*b.y,self.x*b.y+self.y*b.x,self.n,self.p)
def __div__(self,b):
return GaussInt((self.x*b.x-self.n*self.y*b.y)/(b.x*b.x-self.n*b.y*b.y),(-self.x*b.y+self.y*b.x)/(b.x*b.x-self.n*b.y*b.y),self.n,self.p)
def __eq__(self,b):
return self.x==b.x and self.y==b.y
def __repr__(self):
if self.p:
return "%s+%s (%d,%d)"%(self.x,self.y,self.n,self.p)
else:
return "%s+%s (%d)"%(self.x,self.y,self.n)
def __pow__(self,n):
b=Base(n,2)
t=GaussInt(1,0,self.n)
while b:
t=t*t
if b.pop():
t=self*t
return t
def Inv(self):
return GaussInt(self.x/(self.x*self.x-self.n*self.y*self.y),-self.y/(self.x*self.x-self.n*self.y*self.y),self.n,self.p)
def Cipolla(a,p):
#Find a square root of a modulo p using the algorithm of Cipolla
b=0
while pow((b*b-a)%p,(p-1)/2,p)==1:
b+=1
return (GaussInt(b,1,b**2-a,p)**((p+1)/2)).x
def Base(n,b):
#Decompose n in base b
l=[]
while n:
l.append(n%b)
n/=b
return l
def InvMod(a,n):
#Find the inverse mod n of a.
#Use the Extended Euclides Algorithm.
m=[]
s=n
while n:
m.append(a/n)
(a,n)=(n,a%n)
u=1
v=0
while m:
(u,v)=(v,u-m.pop()*v)
return u%s
def b58encode(v):
#Encode a byte string to the Base58
digit="123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"
base=len(digit)
val=0
for c in v:
val*=256
val+=ord(c)
result=""
while val:
(val,mod)=divmod(val,base)
result=digit[mod]+result
pad=0
for c in v:
if c=="\0":
pad+=1
else:
break
return (digit[0]*pad)+result
def b58decode(v):
#Decode a Base58 string to byte string
digit="123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"
base=len(digit)
val=0
for c in v:
val*=base
val+=digit.find(c)
result=""
while val:
(val,mod)=divmod(val,256)
result=chr(mod)+result
pad=0
for c in v:
if c==digit[0]:
pad+=1
else:
break
result="\0"*pad+result
return result
def Byte2Hex(b):
#Convert a byte string to hex number
out=""
for x in b:
y=hex(ord(x))[2:]
if len(y)==1:
y="0"+y
out+="%2s"%y
return out
def Int2Byte(n,b):
#Convert a integer to a byte string of length b
out=""
for i in range(b):
(n,m)=divmod(n,256)
out=chr(m)+out
return out
class EllipticCurvePoint:
#Main class
#It is an point on an Elliptic Curve
def __init__(self,x,a,b,p,n=0):
#We store the coordinate in x and the elliptic curbe parameter.
#x is of length 3. This is the 3 projective coordinates of the point.
self.x=x[:]
self.a=a
self.b=b
self.p=p
self.n=n
def EqualProj(self,y):
#Does y equals self ?
#It computes self cross product with y and check if the result is 0.
return self.x[0]*y.x[1]==self.x[1]*y.x[0] and self.x[1]*y.x[2]==self.x[2]*y.x[1] and self.x[2]*y.x[0]==self.x[0]*y.x[2]
def __add__(self,y):
#The main function to add self and y
#It uses the formulas I derived in projective coordinates.
#Projectives coordinates are more performant than the usual (x,y) coordinates
#because it we don't need to compute inverse mod p, which is faster.
z=EllipticCurvePoint([0,0,0],self.a,self.b,self.p)
if self.EqualProj(y):
d=(2*self.x[1]*self.x[2])%self.p
d3=pow(d,3,self.p)
n=(3*pow(self.x[0],2,self.p)+self.a*pow(self.x[2],2,self.p))%self.p
z.x[0]=(pow(n,2,self.p)*d*self.x[2]-2*d3*self.x[0])%self.p
z.x[1]=(3*self.x[0]*n*pow(d,2,self.p)-pow(n,3,self.p)*self.x[2]-self.x[1]*d3)%self.p
z.x[2]=(self.x[2]*d3)%self.p
else:
d=(y.x[0]*self.x[2]-y.x[2]*self.x[0])%self.p
d3=pow(d,3,self.p)
n=(y.x[1]*self.x[2]-self.x[1]*y.x[2])%self.p
z.x[0]=(y.x[2]*self.x[2]*pow(n,2,self.p)*d-d3*(y.x[2]*self.x[0]+y.x[0]*self.x[2]))%self.p
z.x[1]=(pow(d,2,self.p)*n*(2*self.x[0]*y.x[2]+y.x[0]*self.x[2])-pow(n,3,self.p)*self.x[2]*y.x[2]-self.x[1]*d3*y.x[2])%self.p
z.x[2]=(self.x[2]*d3*y.x[2])%self.p
return z
def __mul__(self,n):
#The fast multiplication of point n times by itself.
b=Base(n,2)
t=EllipticCurvePoint(self.x,self.a,self.b,self.p)
b.pop()
while b:
t+=t
if b.pop():
t+=self
return t
def __repr__(self):
#print a point in (x,y) coordinate.
return "x=%d\ny=%d\n"%((self.x[0]*InvMod(self.x[2],self.p))%self.p,(self.x[1]*InvMod(self.x[2],self.p))%self.p)
def __eq__(self,x):
#Does self==x ?
return self.x==x.x and self.a==x.a and self.b==x.b and self.p==x.p
def __ne__(self,x):
#Does self!=x ?
return self.x!=x.x or self.a!=x.a or self.b!=x.b or self.p!=x.p
def Check(self):
#Is self on the curve ?
return (self.x[0]**3+self.a*self.x[0]*self.x[2]**2+self.b*self.x[2]**3-self.x[1]**2*self.x[2])%self.p==0
def GeneratePrivateKey(self):
#Generate a private key. It's just a random number between 1 and n-1.
#Of course, this function isn't cryptographically secure.
#Don't use it to generate your key. Use a cryptographically secure source of randomness instead.
#self.d = random.randint(1,self.n-1)
self.d = int(os.urandom(32).encode("hex"), 16) # Better random fix
def SignECDSA(self,m):
#Sign a message. The private key is self.d .
h=hashlib.new("SHA256")
h.update(m)
z=int(h.hexdigest(),16)
r=0
s=0
while not r or not s:
k=random.randint(1,self.n-1)
R=self*k
R.Normalize()
r=R.x[0]%self.n
s=(InvMod(k,self.n)*(z+r*self.d))%self.n
return (r,s)
def CheckECDSA(self,sig,m):
#Check a signature (r,s) of the message m using the public key self.Q
# and the generator which is self.
#This is not the one used by Bitcoin because the public key isn't known;
# only a hash of the public key is known. See the next function.
(r,s)=sig
h=hashlib.new("SHA256")
h.update(m)
z=int(h.hexdigest(),16)
if self.Q.x[2]==0:
return False
if not self.Q.Check():
return False
if (self.Q*self.n).x[2]!=0:
return False
if r<1 or r>self.n-1 or s<1 or s>self.n-1:
return False
w=InvMod(s,self.n)
u1=(z*w)%self.n
u2=(r*w)%self.n
R=self*u1+self.Q*u2
R.Normalize()
return (R.x[0]-r)%self.n==0
def VerifyMessageFromBitcoinAddress(self,adresse,m,sig):
#Check a signature (r,s) for the message m signed by the Bitcoin
# address "addresse".
h=hashlib.new("SHA256")
h.update(m)
z=int(h.hexdigest(),16)
(r,s)=sig
x=r
y2=(pow(x,3,self.p)+self.a*x+self.b)%self.p
y=Cipolla(y2,self.p)
for i in range(2):
kG=EllipticCurvePoint([x,y,1],self.a,self.b,self.p,self.n)
mzG=self*((-z)%self.n)
self.Q=(kG*s+mzG)*InvMod(r,self.n)
adr=self.BitcoinAddresFromPublicKey()
if adr==adresse:
break
y=(-y)%self.p
if adr!=adresse:
return False
return True
def BitcoinAddressFromPrivate(self,pri=None):
#Transform a private key in base58 encoding to a bitcoin address.
#normal means "uncompressed".
if not pri:
print "Private Key :",
pri=raw_input()
normal=(len(pri)==51)
pri=b58decode(pri)
if normal:
pri=pri[1:-4]
else:
pri=pri[1:-5]
self.d=int(Byte2Hex(pri),16)
return self.BitcoinAddress(normal)
def PrivateEncoding(self,normal=True):
#Encode a private key self.d to base58 encoding.
p=Int2Byte(self.d,32)
p="\80"+p
if not normal:
p+=chr(1)
h=hashlib.new("SHA256")
h.update(p)
s=h.digest()
h=hashlib.new("SHA256")
h.update(s)
s=h.digest()
cs=s[:4]
p+=cs
p=b58encode(p)
return p
def BitcoinAddresFromPublicKey(self,normal=True):
#Find the bitcoin address from the public key self.Q
#We do normalization to go from the projective coordinates to the usual
# (x,y) coordinates.
self.Q.Normalize()
if normal:
pk=chr(4)+Int2Byte(self.Q.x[0],32)+Int2Byte((self.Q.x[1])%self.p,32)
else:
if self.Q.x[1]%2==0:
pk=chr(2)+Int2Byte(self.Q.x[0],32)
else:
pk=chr(3)+Int2Byte(self.Q.x[0],32)
version=chr(0)
h=hashlib.new("SHA256")
h.update(pk)
s=h.digest()
h=hashlib.new("RIPEMD160")
h.update(s)
kh=version+h.digest()
h=hashlib.new("SHA256")
h.update(kh)
cs=h.digest()
h=hashlib.new("SHA256")
h.update(cs)
cs=h.digest()[:4]
adr=b58encode(kh+cs)
return adr
def BitcoinAddress(self,normal=True):
#Computes a bitcoin address given the private key self.d.
self.Q=self*self.d
return self.BitcoinAddresFromPublicKey(normal)
def BitcoinAddressGenerator(self,k,filename):
#Generate Bitcoin address and write them in the filename in the multibit format.
#Change the date as you like.
f=open(filename,"w")
for i in range(k):
self.GeneratePrivateKey()
adr=self.BitcoinAddress()
p=self.PrivateEncoding()
f.write("#%s\n%s 2014-01-30T12:00:00Z\n"%(adr,p))
#print hex(self.d)
print adr,p
f.close()
def TestSign(self):
#Test signature
self.GeneratePrivateKey()
self.Q=self*self.d
m="Hello World"
adresse=self.BitcoinAddresFromPublicKey()
(r,s)=self.SignECDSA(m)
m="Hello World"
print self.VerifyMessageFromBitcoinAddress(adresse,m,r,s)
def Normalize(self):
#Transform projective coordinates of self to the usual (x,y) coordinates.
if self.x[2]:
self.x[0]=(self.x[0]*InvMod(self.x[2],self.p))%self.p
self.x[1]=(self.x[1]*InvMod(self.x[2],self.p))%self.p
self.x[2]=1
elif self.x[1]:
self.x[0]=(self.x[0]*InvMod(self.x[1],self.p))%self.p
self.x[1]=1
elif self.x[0]:
self.x[0]=1
else:
raise Exception
def Bitcoin():
#Create the Bitcoin elliptiv curve
a=0
b=7
p=2**256-2**32-2**9-2**8-2**7-2**6-2**4-1
#Create the generator G of the Bitcoin elliptic curve, with is order n.
Gx=int("79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798",16)
Gy=int("483ADA7726A3C4655DA4FBFC0E1108A8FD17B448A68554199C47D08FFB10D4B8",16)
n =int("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141",16)
#Create the generator
return EllipticCurvePoint([Gx,Gy,1],a,b,p,n)
if __name__ == "__main__":
bitcoin=Bitcoin()
#Generate the public key from the private one
print bitcoin.BitcoinAddressFromPrivate("23DKRBLkeDbcSaddsMYLAHXhanPmGwkWAhSPVGbspAkc72Hw9BdrDF")
print bitcoin.BitcoinAddress()
#Print the bitcoin address of the public key generated at the previous line
adr=bitcoin.BitcoinAddresFromPublicKey()
print adr
#Sign a message with the current address
m="Hello World"
sig=bitcoin.SignECDSA("Hello World")
#Verify the message using only the bitcoin adress, the signature and the message.
#Not using the public key as it is not needed.
print bitcoin.VerifyMessageFromBitcoinAddress(adr,m,sig)

View File

0
src/lib/__init__.py Normal file
View File

View File

View File

@ -0,0 +1,29 @@
import re
def prefix(content):
content = re.sub("@keyframes (.*? {.*?[^ ]})", "@keyframes \\1\n@-webkit-keyframes \\1\n@-moz-keyframes \\1\n", content, flags=re.DOTALL)
content = re.sub('([^-\*])(border-radius|box-shadow|transition|animation|box-sizing|transform|filter|perspective|animation-[a-z-]+): (.*?)([;}])', '\\1-webkit-\\2: \\3; -moz-\\2: \\3; -o-\\2: \\3; -ms-\\2: \\3; \\2: \\3 \\4', content)
content = re.sub('(?<=[^a-zA-Z0-9-])([a-zA-Z0-9-]+): {0,1}(linear-gradient)\((.*?)(\)[;\n])',
'\\1: -webkit-\\2(\\3);'+
'\\1: -moz-\\2(\\3);'+
'\\1: -o-\\2(\\3);'+
'\\1: -ms-\\2(\\3);'+
'\\1: \\2(\\3);', content)
return content
if __name__ == "__main__":
print prefix("""
.test {
border-radius: 5px;
background: linear-gradient(red, blue);
}
@keyframes flip {
0% { transform: perspective(120px) rotateX(0deg) rotateY(0deg); }
50% { transform: perspective(120px) rotateX(-180.1deg) rotateY(0deg) }
100% { transform: perspective(120px) rotateX(-180deg) rotateY(-179.9deg); }
}
""")

View File

@ -0,0 +1,21 @@
VERSION = (0, 9, 3, 'final', 0)
__all__ = [
'WebSocketApplication',
'Resource',
'WebSocketServer',
'WebSocketError',
'get_version'
]
def get_version(*args, **kwargs):
from .utils import get_version
return get_version(*args, **kwargs)
try:
from .resource import WebSocketApplication, Resource
from .server import WebSocketServer
from .exceptions import WebSocketError
except ImportError:
pass

View File

@ -0,0 +1,19 @@
from socket import error as socket_error
class WebSocketError(socket_error):
"""
Base class for all websocket errors.
"""
class ProtocolError(WebSocketError):
"""
Raised if an error occurs when de/encoding the websocket protocol.
"""
class FrameTooLargeException(ProtocolError):
"""
Raised if a frame is received that is too large.
"""

View File

@ -0,0 +1,6 @@
from geventwebsocket.handler import WebSocketHandler
from gunicorn.workers.ggevent import GeventPyWSGIWorker
class GeventWebSocketWorker(GeventPyWSGIWorker):
wsgi_handler = WebSocketHandler

View File

@ -0,0 +1,283 @@
# Modified: Werkzeug Debugger workaround in run_websocket(self):
import base64
import hashlib
import warnings
from gevent.pywsgi import WSGIHandler
from .websocket import WebSocket, Stream
from .logging import create_logger
class Client(object):
def __init__(self, address, ws):
self.address = address
self.ws = ws
class WebSocketHandler(WSGIHandler):
"""
Automatically upgrades the connection to a websocket.
To prevent the WebSocketHandler to call the underlying WSGI application,
but only setup the WebSocket negotiations, do:
mywebsockethandler.prevent_wsgi_call = True
before calling run_application(). This is useful if you want to do more
things before calling the app, and want to off-load the WebSocket
negotiations to this library. Socket.IO needs this for example, to send
the 'ack' before yielding the control to your WSGI app.
"""
SUPPORTED_VERSIONS = ('13', '8', '7')
GUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
def run_websocket(self):
"""
Called when a websocket has been created successfully.
"""
if getattr(self, 'prevent_wsgi_call', False):
return
# In case WebSocketServer is not used
if not hasattr(self.server, 'clients'):
self.server.clients = {}
# Since we're now a websocket connection, we don't care what the
# application actually responds with for the http response
try:
self.server.clients[self.client_address] = Client(
self.client_address, self.websocket)
if self.application.__class__.__name__ == "DebuggedApplication": # Modified: Werkzeug Debugger workaround (https://bitbucket.org/Jeffrey/gevent-websocket/issue/53/if-the-application-returns-a-generator-we)
list(self.application(self.environ, lambda s, h: []))
else:
self.application(self.environ, lambda s, h: [])
finally:
del self.server.clients[self.client_address]
if not self.websocket.closed:
self.websocket.close()
self.environ.update({
'wsgi.websocket': None
})
self.websocket = None
def run_application(self):
if (hasattr(self.server, 'pre_start_hook')
and self.server.pre_start_hook):
self.logger.debug("Calling pre-start hook")
if self.server.pre_start_hook(self):
return super(WebSocketHandler, self).run_application()
self.logger.debug("Initializing WebSocket")
self.result = self.upgrade_websocket()
if hasattr(self, 'websocket'):
if self.status and not self.headers_sent:
self.write('')
self.run_websocket()
else:
if self.status:
# A status was set, likely an error so just send the response
if not self.result:
self.result = []
self.process_result()
return
# This handler did not handle the request, so defer it to the
# underlying application object
return super(WebSocketHandler, self).run_application()
def upgrade_websocket(self):
"""
Attempt to upgrade the current environ into a websocket enabled
connection. If successful, the environ dict with be updated with two
new entries, `wsgi.websocket` and `wsgi.websocket_version`.
:returns: Whether the upgrade was successful.
"""
# Some basic sanity checks first
self.logger.debug("Validating WebSocket request")
if self.environ.get('REQUEST_METHOD', '') != 'GET':
# This is not a websocket request, so we must not handle it
self.logger.debug('Can only upgrade connection if using GET method.')
return
upgrade = self.environ.get('HTTP_UPGRADE', '').lower()
if upgrade == 'websocket':
connection = self.environ.get('HTTP_CONNECTION', '').lower()
if 'upgrade' not in connection:
# This is not a websocket request, so we must not handle it
self.logger.warning("Client didn't ask for a connection "
"upgrade")
return
else:
# This is not a websocket request, so we must not handle it
return
if self.request_version != 'HTTP/1.1':
self.start_response('402 Bad Request', [])
self.logger.warning("Bad server protocol in headers")
return ['Bad protocol version']
if self.environ.get('HTTP_SEC_WEBSOCKET_VERSION'):
return self.upgrade_connection()
else:
self.logger.warning("No protocol defined")
self.start_response('426 Upgrade Required', [
('Sec-WebSocket-Version', ', '.join(self.SUPPORTED_VERSIONS))])
return ['No Websocket protocol version defined']
def upgrade_connection(self):
"""
Validate and 'upgrade' the HTTP request to a WebSocket request.
If an upgrade succeeded then then handler will have `start_response`
with a status of `101`, the environ will also be updated with
`wsgi.websocket` and `wsgi.websocket_version` keys.
:param environ: The WSGI environ dict.
:param start_response: The callable used to start the response.
:param stream: File like object that will be read from/written to by
the underlying WebSocket object, if created.
:return: The WSGI response iterator is something went awry.
"""
self.logger.debug("Attempting to upgrade connection")
version = self.environ.get("HTTP_SEC_WEBSOCKET_VERSION")
if version not in self.SUPPORTED_VERSIONS:
msg = "Unsupported WebSocket Version: {0}".format(version)
self.logger.warning(msg)
self.start_response('400 Bad Request', [
('Sec-WebSocket-Version', ', '.join(self.SUPPORTED_VERSIONS))
])
return [msg]
key = self.environ.get("HTTP_SEC_WEBSOCKET_KEY", '').strip()
if not key:
# 5.2.1 (3)
msg = "Sec-WebSocket-Key header is missing/empty"
self.logger.warning(msg)
self.start_response('400 Bad Request', [])
return [msg]
try:
key_len = len(base64.b64decode(key))
except TypeError:
msg = "Invalid key: {0}".format(key)
self.logger.warning(msg)
self.start_response('400 Bad Request', [])
return [msg]
if key_len != 16:
# 5.2.1 (3)
msg = "Invalid key: {0}".format(key)
self.logger.warning(msg)
self.start_response('400 Bad Request', [])
return [msg]
# Check for WebSocket Protocols
requested_protocols = self.environ.get(
'HTTP_SEC_WEBSOCKET_PROTOCOL', '')
protocol = None
if hasattr(self.application, 'app_protocol'):
allowed_protocol = self.application.app_protocol(
self.environ['PATH_INFO'])
if allowed_protocol and allowed_protocol in requested_protocols:
protocol = allowed_protocol
self.logger.debug("Protocol allowed: {0}".format(protocol))
self.websocket = WebSocket(self.environ, Stream(self), self)
self.environ.update({
'wsgi.websocket_version': version,
'wsgi.websocket': self.websocket
})
headers = [
("Upgrade", "websocket"),
("Connection", "Upgrade"),
("Sec-WebSocket-Accept", base64.b64encode(
hashlib.sha1(key + self.GUID).digest())),
]
if protocol:
headers.append(("Sec-WebSocket-Protocol", protocol))
self.logger.debug("WebSocket request accepted, switching protocols")
self.start_response("101 Switching Protocols", headers)
@property
def logger(self):
if not hasattr(self.server, 'logger'):
self.server.logger = create_logger(__name__)
return self.server.logger
def log_request(self):
if '101' not in self.status:
self.logger.info(self.format_request())
@property
def active_client(self):
return self.server.clients[self.client_address]
def start_response(self, status, headers, exc_info=None):
"""
Called when the handler is ready to send a response back to the remote
endpoint. A websocket connection may have not been created.
"""
writer = super(WebSocketHandler, self).start_response(
status, headers, exc_info=exc_info)
self._prepare_response()
return writer
def _prepare_response(self):
"""
Sets up the ``pywsgi.Handler`` to work with a websocket response.
This is used by other projects that need to support WebSocket
connections as part of a larger effort.
"""
assert not self.headers_sent
if not self.environ.get('wsgi.websocket'):
# a WebSocket connection is not established, do nothing
return
# So that `finalize_headers` doesn't write a Content-Length header
self.provided_content_length = False
# The websocket is now controlling the response
self.response_use_chunked = False
# Once the request is over, the connection must be closed
self.close_connection = True
# Prevents the Date header from being written
self.provided_date = True

View File

@ -0,0 +1,31 @@
from __future__ import absolute_import
from logging import getLogger, StreamHandler, getLoggerClass, Formatter, DEBUG
def create_logger(name, debug=False, format=None):
Logger = getLoggerClass()
class DebugLogger(Logger):
def getEffectiveLevel(x):
if x.level == 0 and debug:
return DEBUG
else:
return Logger.getEffectiveLevel(x)
class DebugHandler(StreamHandler):
def emit(x, record):
StreamHandler.emit(x, record) if debug else None
handler = DebugHandler()
handler.setLevel(DEBUG)
if format:
handler.setFormatter(Formatter(format))
logger = getLogger(name)
del logger.handlers[:]
logger.__class__ = DebugLogger
logger.addHandler(handler)
return logger

View File

@ -0,0 +1,35 @@
class BaseProtocol(object):
PROTOCOL_NAME = ''
def __init__(self, app):
self._app = app
def on_open(self):
self.app.on_open()
def on_message(self, message):
self.app.on_message(message)
def on_close(self, reason=None):
self.app.on_close(reason)
@property
def app(self):
if self._app:
return self._app
else:
raise Exception("No application coupled")
@property
def server(self):
if not hasattr(self.app, 'ws'):
return None
return self.app.ws.handler.server
@property
def handler(self):
if not hasattr(self.app, 'ws'):
return None
return self.app.ws.handler

View File

@ -0,0 +1,234 @@
import inspect
import random
import string
import types
try:
import ujson as json
except ImportError:
try:
import simplejson as json
except ImportError:
import json
from ..exceptions import WebSocketError
from .base import BaseProtocol
def export_rpc(arg=None):
if isinstance(arg, types.FunctionType):
arg._rpc = arg.__name__
return arg
def serialize(data):
return json.dumps(data)
class Prefixes(object):
def __init__(self):
self.prefixes = {}
def add(self, prefix, uri):
self.prefixes[prefix] = uri
def resolve(self, curie_or_uri):
if "http://" in curie_or_uri:
return curie_or_uri
elif ':' in curie_or_uri:
prefix, proc = curie_or_uri.split(':', 1)
return self.prefixes[prefix] + proc
else:
raise Exception(curie_or_uri)
class RemoteProcedures(object):
def __init__(self):
self.calls = {}
def register_procedure(self, uri, proc):
self.calls[uri] = proc
def register_object(self, uri, obj):
for k in inspect.getmembers(obj, inspect.ismethod):
if '_rpc' in k[1].__dict__:
proc_uri = uri + k[1]._rpc
self.calls[proc_uri] = (obj, k[1])
def call(self, uri, args):
if uri in self.calls:
proc = self.calls[uri]
# Do the correct call whether it's a function or instance method.
if isinstance(proc, tuple):
if proc[1].__self__ is None:
# Create instance of object and call method
return proc[1](proc[0](), *args)
else:
# Call bound method on instance
return proc[1](*args)
else:
return self.calls[uri](*args)
else:
raise Exception("no such uri '{}'".format(uri))
class Channels(object):
def __init__(self):
self.channels = {}
def create(self, uri, prefix_matching=False):
if uri not in self.channels:
self.channels[uri] = []
# TODO: implement prefix matching
def subscribe(self, uri, client):
if uri in self.channels:
self.channels[uri].append(client)
def unsubscribe(self, uri, client):
if uri not in self.channels:
return
client_index = self.channels[uri].index(client)
self.channels[uri].pop(client_index)
if len(self.channels[uri]) == 0:
del self.channels[uri]
def publish(self, uri, event, exclude=None, eligible=None):
if uri not in self.channels:
return
# TODO: exclude & eligible
msg = [WampProtocol.MSG_EVENT, uri, event]
for client in self.channels[uri]:
try:
client.ws.send(serialize(msg))
except WebSocketError:
# Seems someone didn't unsubscribe before disconnecting
self.channels[uri].remove(client)
class WampProtocol(BaseProtocol):
MSG_WELCOME = 0
MSG_PREFIX = 1
MSG_CALL = 2
MSG_CALL_RESULT = 3
MSG_CALL_ERROR = 4
MSG_SUBSCRIBE = 5
MSG_UNSUBSCRIBE = 6
MSG_PUBLISH = 7
MSG_EVENT = 8
PROTOCOL_NAME = "wamp"
def __init__(self, *args, **kwargs):
self.procedures = RemoteProcedures()
self.prefixes = Prefixes()
self.session_id = ''.join(
[random.choice(string.digits + string.letters)
for i in xrange(16)])
super(WampProtocol, self).__init__(*args, **kwargs)
def register_procedure(self, *args, **kwargs):
self.procedures.register_procedure(*args, **kwargs)
def register_object(self, *args, **kwargs):
self.procedures.register_object(*args, **kwargs)
def register_pubsub(self, *args, **kwargs):
if not hasattr(self.server, 'channels'):
self.server.channels = Channels()
self.server.channels.create(*args, **kwargs)
def do_handshake(self):
from geventwebsocket import get_version
welcome = [
self.MSG_WELCOME,
self.session_id,
1,
'gevent-websocket/' + get_version()
]
self.app.ws.send(serialize(welcome))
def _get_exception_info(self, e):
uri = 'http://TODO#generic'
desc = str(type(e))
details = str(e)
return [uri, desc, details]
def rpc_call(self, data):
call_id, curie_or_uri = data[1:3]
args = data[3:]
if not isinstance(call_id, (str, unicode)):
raise Exception()
if not isinstance(curie_or_uri, (str, unicode)):
raise Exception()
uri = self.prefixes.resolve(curie_or_uri)
try:
result = self.procedures.call(uri, args)
result_msg = [self.MSG_CALL_RESULT, call_id, result]
except Exception, e:
result_msg = [self.MSG_CALL_ERROR,
call_id] + self._get_exception_info(e)
self.app.on_message(serialize(result_msg))
def pubsub_action(self, data):
action = data[0]
curie_or_uri = data[1]
if not isinstance(action, int):
raise Exception()
if not isinstance(curie_or_uri, (str, unicode)):
raise Exception()
uri = self.prefixes.resolve(curie_or_uri)
if action == self.MSG_SUBSCRIBE and len(data) == 2:
self.server.channels.subscribe(data[1], self.handler.active_client)
elif action == self.MSG_UNSUBSCRIBE and len(data) == 2:
self.server.channels.unsubscribe(
data[1], self.handler.active_client)
elif action == self.MSG_PUBLISH and len(data) >= 3:
payload = data[2] if len(data) >= 3 else None
exclude = data[3] if len(data) >= 4 else None
eligible = data[4] if len(data) >= 5 else None
self.server.channels.publish(uri, payload, exclude, eligible)
def on_open(self):
self.app.on_open()
self.do_handshake()
def on_message(self, message):
data = json.loads(message)
if not isinstance(data, list):
raise Exception('incoming data is no list')
if data[0] == self.MSG_PREFIX and len(data) == 3:
prefix, uri = data[1:3]
self.prefixes.add(prefix, uri)
elif data[0] == self.MSG_CALL and len(data) >= 3:
return self.rpc_call(data)
elif data[0] in (self.MSG_SUBSCRIBE, self.MSG_UNSUBSCRIBE,
self.MSG_PUBLISH):
return self.pubsub_action(data)
else:
raise Exception("Unknown call")

View File

@ -0,0 +1,74 @@
import re
from .protocols.base import BaseProtocol
from .exceptions import WebSocketError
class WebSocketApplication(object):
protocol_class = BaseProtocol
def __init__(self, ws):
self.protocol = self.protocol_class(self)
self.ws = ws
def handle(self):
self.protocol.on_open()
while True:
try:
message = self.ws.receive()
except WebSocketError:
self.protocol.on_close()
break
self.protocol.on_message(message)
def on_open(self, *args, **kwargs):
pass
def on_close(self, *args, **kwargs):
pass
def on_message(self, message, *args, **kwargs):
self.ws.send(message, **kwargs)
@classmethod
def protocol_name(cls):
return cls.protocol_class.PROTOCOL_NAME
class Resource(object):
def __init__(self, apps=None):
self.apps = apps if apps else []
def _app_by_path(self, environ_path):
# Which app matched the current path?
for path, app in self.apps.iteritems():
if re.match(path, environ_path):
return app
def app_protocol(self, path):
app = self._app_by_path(path)
if hasattr(app, 'protocol_name'):
return app.protocol_name()
else:
return ''
def __call__(self, environ, start_response):
environ = environ
current_app = self._app_by_path(environ['PATH_INFO'])
if current_app is None:
raise Exception("No apps defined")
if 'wsgi.websocket' in environ:
ws = environ['wsgi.websocket']
current_app = current_app(ws)
current_app.ws = ws # TODO: needed?
current_app.handle()
return None
else:
return current_app(environ, start_response)

View File

@ -0,0 +1,34 @@
from gevent.pywsgi import WSGIServer
from .handler import WebSocketHandler
from .logging import create_logger
class WebSocketServer(WSGIServer):
debug_log_format = (
'-' * 80 + '\n' +
'%(levelname)s in %(module)s [%(pathname)s:%(lineno)d]:\n' +
'%(message)s\n' +
'-' * 80
)
def __init__(self, *args, **kwargs):
self.debug = kwargs.pop('debug', False)
self.pre_start_hook = kwargs.pop('pre_start_hook', None)
self._logger = None
self.clients = {}
kwargs['handler_class'] = WebSocketHandler
super(WebSocketServer, self).__init__(*args, **kwargs)
def handle(self, socket, address):
handler = self.handler_class(socket, address, self)
handler.handle()
@property
def logger(self):
if not self._logger:
self._logger = create_logger(
__name__, self.debug, self.debug_log_format)
return self._logger

View File

@ -0,0 +1,128 @@
###############################################################################
##
## Copyright 2011-2013 Tavendo GmbH
##
## Note:
##
## This code is a Python implementation of the algorithm
##
## "Flexible and Economical UTF-8 Decoder"
##
## by Bjoern Hoehrmann
##
## bjoern@hoehrmann.de
## http://bjoern.hoehrmann.de/utf-8/decoder/dfa/
##
## Licensed under the Apache License, Version 2.0 (the "License");
## you may not use this file except in compliance with the License.
## You may obtain a copy of the License at
##
## http://www.apache.org/licenses/LICENSE-2.0
##
## Unless required by applicable law or agreed to in writing, software
## distributed under the License is distributed on an "AS IS" BASIS,
## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
## See the License for the specific language governing permissions and
## limitations under the License.
##
###############################################################################
## use Cython implementation of UTF8 validator if available
##
try:
from wsaccel.utf8validator import Utf8Validator
except:
## fallback to pure Python implementation
class Utf8Validator:
"""
Incremental UTF-8 validator with constant memory consumption (minimal
state).
Implements the algorithm "Flexible and Economical UTF-8 Decoder" by
Bjoern Hoehrmann (http://bjoern.hoehrmann.de/utf-8/decoder/dfa/).
"""
## DFA transitions
UTF8VALIDATOR_DFA = [
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, # 00..1f
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, # 20..3f
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, # 40..5f
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, # 60..7f
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, # 80..9f
7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, # a0..bf
8,8,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, # c0..df
0xa,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x4,0x3,0x3, # e0..ef
0xb,0x6,0x6,0x6,0x5,0x8,0x8,0x8,0x8,0x8,0x8,0x8,0x8,0x8,0x8,0x8, # f0..ff
0x0,0x1,0x2,0x3,0x5,0x8,0x7,0x1,0x1,0x1,0x4,0x6,0x1,0x1,0x1,0x1, # s0..s0
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,1,1,1,1,0,1,0,1,1,1,1,1,1, # s1..s2
1,2,1,1,1,1,1,2,1,2,1,1,1,1,1,1,1,1,1,1,1,1,1,2,1,1,1,1,1,1,1,1, # s3..s4
1,2,1,1,1,1,1,1,1,2,1,1,1,1,1,1,1,1,1,1,1,1,1,3,1,3,1,1,1,1,1,1, # s5..s6
1,3,1,1,1,1,1,3,1,3,1,1,1,1,1,1,1,3,1,1,1,1,1,1,1,1,1,1,1,1,1,1, # s7..s8
]
UTF8_ACCEPT = 0
UTF8_REJECT = 1
def __init__(self):
self.reset()
def decode(self, b):
"""
Eat one UTF-8 octet, and validate on the fly.
Returns UTF8_ACCEPT when enough octets have been consumed, in which case
self.codepoint contains the decoded Unicode code point.
Returns UTF8_REJECT when invalid UTF-8 was encountered.
Returns some other positive integer when more octets need to be eaten.
"""
type = Utf8Validator.UTF8VALIDATOR_DFA[b]
if self.state != Utf8Validator.UTF8_ACCEPT:
self.codepoint = (b & 0x3f) | (self.codepoint << 6)
else:
self.codepoint = (0xff >> type) & b
self.state = Utf8Validator.UTF8VALIDATOR_DFA[256 + self.state * 16 + type]
return self.state
def reset(self):
"""
Reset validator to start new incremental UTF-8 decode/validation.
"""
self.state = Utf8Validator.UTF8_ACCEPT
self.codepoint = 0
self.i = 0
def validate(self, ba):
"""
Incrementally validate a chunk of bytes provided as string.
Will return a quad (valid?, endsOnCodePoint?, currentIndex, totalIndex).
As soon as an octet is encountered which renders the octet sequence
invalid, a quad with valid? == False is returned. currentIndex returns
the index within the currently consumed chunk, and totalIndex the
index within the total consumed sequence that was the point of bail out.
When valid? == True, currentIndex will be len(ba) and totalIndex the
total amount of consumed bytes.
"""
l = len(ba)
for i in xrange(l):
## optimized version of decode(), since we are not interested in actual code points
self.state = Utf8Validator.UTF8VALIDATOR_DFA[256 + (self.state << 4) + Utf8Validator.UTF8VALIDATOR_DFA[ord(ba[i])]]
if self.state == Utf8Validator.UTF8_REJECT:
self.i += i
return False, False, i, self.i
self.i += l
return True, self.state == Utf8Validator.UTF8_ACCEPT, l, self.i

View File

@ -0,0 +1,45 @@
import subprocess
def get_version(version=None):
"Returns a PEP 386-compliant version number from VERSION."
if version is None:
from geventwebsocket import VERSION as version
else:
assert len(version) == 5
assert version[3] in ('alpha', 'beta', 'rc', 'final')
# Now build the two parts of the version number:
# main = X.Y[.Z]
# sub = .devN - for pre-alpha releases
# | {a|b|c}N - for alpha, beta and rc releases
parts = 2 if version[2] == 0 else 3
main = '.'.join(str(x) for x in version[:parts])
sub = ''
if version[3] == 'alpha' and version[4] == 0:
hg_changeset = get_hg_changeset()
if hg_changeset:
sub = '.dev{0}'.format(hg_changeset)
elif version[3] != 'final':
mapping = {'alpha': 'a', 'beta': 'b', 'rc': 'c'}
sub = mapping[version[3]] + str(version[4])
return str(main + sub)
def get_hg_changeset():
rev, err = subprocess.Popen(
'hg id -i',
shell=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE
).communicate()
if err:
return None
else:
return rev.strip().replace('+', '')

View File

@ -0,0 +1,543 @@
import struct
from socket import error
from .exceptions import ProtocolError
from .exceptions import WebSocketError
from .exceptions import FrameTooLargeException
from .utf8validator import Utf8Validator
MSG_SOCKET_DEAD = "Socket is dead"
MSG_ALREADY_CLOSED = "Connection is already closed"
MSG_CLOSED = "Connection closed"
class WebSocket(object):
"""
Base class for supporting websocket operations.
:ivar environ: The http environment referenced by this connection.
:ivar closed: Whether this connection is closed/closing.
:ivar stream: The underlying file like object that will be read from /
written to by this WebSocket object.
"""
__slots__ = ('utf8validator', 'utf8validate_last', 'environ', 'closed',
'stream', 'raw_write', 'raw_read', 'handler')
OPCODE_CONTINUATION = 0x00
OPCODE_TEXT = 0x01
OPCODE_BINARY = 0x02
OPCODE_CLOSE = 0x08
OPCODE_PING = 0x09
OPCODE_PONG = 0x0a
def __init__(self, environ, stream, handler):
self.environ = environ
self.closed = False
self.stream = stream
self.raw_write = stream.write
self.raw_read = stream.read
self.utf8validator = Utf8Validator()
self.handler = handler
def __del__(self):
try:
self.close()
except:
# close() may fail if __init__ didn't complete
pass
def _decode_bytes(self, bytestring):
"""
Internal method used to convert the utf-8 encoded bytestring into
unicode.
If the conversion fails, the socket will be closed.
"""
if not bytestring:
return u''
try:
return bytestring.decode('utf-8')
except UnicodeDecodeError:
self.close(1007)
raise
def _encode_bytes(self, text):
"""
:returns: The utf-8 byte string equivalent of `text`.
"""
if isinstance(text, str):
return text
if not isinstance(text, unicode):
text = unicode(text or '')
return text.encode('utf-8')
def _is_valid_close_code(self, code):
"""
:returns: Whether the returned close code is a valid hybi return code.
"""
if code < 1000:
return False
if 1004 <= code <= 1006:
return False
if 1012 <= code <= 1016:
return False
if code == 1100:
# not sure about this one but the autobahn fuzzer requires it.
return False
if 2000 <= code <= 2999:
return False
return True
@property
def current_app(self):
if hasattr(self.handler.server.application, 'current_app'):
return self.handler.server.application.current_app
else:
# For backwards compatibility reasons
class MockApp():
def on_close(self, *args):
pass
return MockApp()
@property
def origin(self):
if not self.environ:
return
return self.environ.get('HTTP_ORIGIN')
@property
def protocol(self):
if not self.environ:
return
return self.environ.get('HTTP_SEC_WEBSOCKET_PROTOCOL')
@property
def version(self):
if not self.environ:
return
return self.environ.get('HTTP_SEC_WEBSOCKET_VERSION')
@property
def path(self):
if not self.environ:
return
return self.environ.get('PATH_INFO')
@property
def logger(self):
return self.handler.logger
def handle_close(self, header, payload):
"""
Called when a close frame has been decoded from the stream.
:param header: The decoded `Header`.
:param payload: The bytestring payload associated with the close frame.
"""
if not payload:
self.close(1000, None)
return
if len(payload) < 2:
raise ProtocolError('Invalid close frame: {0} {1}'.format(
header, payload))
code = struct.unpack('!H', str(payload[:2]))[0]
payload = payload[2:]
if payload:
validator = Utf8Validator()
val = validator.validate(payload)
if not val[0]:
raise UnicodeError
if not self._is_valid_close_code(code):
raise ProtocolError('Invalid close code {0}'.format(code))
self.close(code, payload)
def handle_ping(self, header, payload):
self.send_frame(payload, self.OPCODE_PONG)
def handle_pong(self, header, payload):
pass
def read_frame(self):
"""
Block until a full frame has been read from the socket.
This is an internal method as calling this will not cleanup correctly
if an exception is called. Use `receive` instead.
:return: The header and payload as a tuple.
"""
header = Header.decode_header(self.stream)
if header.flags:
raise ProtocolError
if not header.length:
return header, ''
try:
payload = self.raw_read(header.length)
except error:
payload = ''
except Exception:
# TODO log out this exception
payload = ''
if len(payload) != header.length:
raise WebSocketError('Unexpected EOF reading frame payload')
if header.mask:
payload = header.unmask_payload(payload)
return header, payload
def validate_utf8(self, payload):
# Make sure the frames are decodable independently
self.utf8validate_last = self.utf8validator.validate(payload)
if not self.utf8validate_last[0]:
raise UnicodeError("Encountered invalid UTF-8 while processing "
"text message at payload octet index "
"{0:d}".format(self.utf8validate_last[3]))
def read_message(self):
"""
Return the next text or binary message from the socket.
This is an internal method as calling this will not cleanup correctly
if an exception is called. Use `receive` instead.
"""
opcode = None
message = ""
while True:
header, payload = self.read_frame()
f_opcode = header.opcode
if f_opcode in (self.OPCODE_TEXT, self.OPCODE_BINARY):
# a new frame
if opcode:
raise ProtocolError("The opcode in non-fin frame is "
"expected to be zero, got "
"{0!r}".format(f_opcode))
# Start reading a new message, reset the validator
self.utf8validator.reset()
self.utf8validate_last = (True, True, 0, 0)
opcode = f_opcode
elif f_opcode == self.OPCODE_CONTINUATION:
if not opcode:
raise ProtocolError("Unexpected frame with opcode=0")
elif f_opcode == self.OPCODE_PING:
self.handle_ping(header, payload)
continue
elif f_opcode == self.OPCODE_PONG:
self.handle_pong(header, payload)
continue
elif f_opcode == self.OPCODE_CLOSE:
self.handle_close(header, payload)
return
else:
raise ProtocolError("Unexpected opcode={0!r}".format(f_opcode))
if opcode == self.OPCODE_TEXT:
self.validate_utf8(payload)
message += payload
if header.fin:
break
if opcode == self.OPCODE_TEXT:
self.validate_utf8(message)
return message
else:
return bytearray(message)
def receive(self):
"""
Read and return a message from the stream. If `None` is returned, then
the socket is considered closed/errored.
"""
if self.closed:
self.current_app.on_close(MSG_ALREADY_CLOSED)
raise WebSocketError(MSG_ALREADY_CLOSED)
try:
return self.read_message()
except UnicodeError:
self.close(1007)
except ProtocolError:
self.close(1002)
except error:
self.close()
self.current_app.on_close(MSG_CLOSED)
return None
def send_frame(self, message, opcode):
"""
Send a frame over the websocket with message as its payload
"""
if self.closed:
self.current_app.on_close(MSG_ALREADY_CLOSED)
raise WebSocketError(MSG_ALREADY_CLOSED)
if opcode == self.OPCODE_TEXT:
message = self._encode_bytes(message)
elif opcode == self.OPCODE_BINARY:
message = str(message)
header = Header.encode_header(True, opcode, '', len(message), 0)
try:
self.raw_write(header + message)
except error:
raise WebSocketError(MSG_SOCKET_DEAD)
def send(self, message, binary=None):
"""
Send a frame over the websocket with message as its payload
"""
if binary is None:
binary = not isinstance(message, (str, unicode))
opcode = self.OPCODE_BINARY if binary else self.OPCODE_TEXT
try:
self.send_frame(message, opcode)
except WebSocketError:
self.current_app.on_close(MSG_SOCKET_DEAD)
raise WebSocketError(MSG_SOCKET_DEAD)
def close(self, code=1000, message=''):
"""
Close the websocket and connection, sending the specified code and
message. The underlying socket object is _not_ closed, that is the
responsibility of the initiator.
"""
if self.closed:
self.current_app.on_close(MSG_ALREADY_CLOSED)
try:
message = self._encode_bytes(message)
self.send_frame(
struct.pack('!H%ds' % len(message), code, message),
opcode=self.OPCODE_CLOSE)
except WebSocketError:
# Failed to write the closing frame but it's ok because we're
# closing the socket anyway.
self.logger.debug("Failed to write closing frame -> closing socket")
finally:
self.logger.debug("Closed WebSocket")
self.closed = True
self.stream = None
self.raw_write = None
self.raw_read = None
self.environ = None
#self.current_app.on_close(MSG_ALREADY_CLOSED)
class Stream(object):
"""
Wraps the handler's socket/rfile attributes and makes it in to a file like
object that can be read from/written to by the lower level websocket api.
"""
__slots__ = ('handler', 'read', 'write')
def __init__(self, handler):
self.handler = handler
self.read = handler.rfile.read
self.write = handler.socket.sendall
class Header(object):
__slots__ = ('fin', 'mask', 'opcode', 'flags', 'length')
FIN_MASK = 0x80
OPCODE_MASK = 0x0f
MASK_MASK = 0x80
LENGTH_MASK = 0x7f
RSV0_MASK = 0x40
RSV1_MASK = 0x20
RSV2_MASK = 0x10
# bitwise mask that will determine the reserved bits for a frame header
HEADER_FLAG_MASK = RSV0_MASK | RSV1_MASK | RSV2_MASK
def __init__(self, fin=0, opcode=0, flags=0, length=0):
self.mask = ''
self.fin = fin
self.opcode = opcode
self.flags = flags
self.length = length
def mask_payload(self, payload):
payload = bytearray(payload)
mask = bytearray(self.mask)
for i in xrange(self.length):
payload[i] ^= mask[i % 4]
return str(payload)
# it's the same operation
unmask_payload = mask_payload
def __repr__(self):
return ("<Header fin={0} opcode={1} length={2} flags={3} at "
"0x{4:x}>").format(self.fin, self.opcode, self.length,
self.flags, id(self))
@classmethod
def decode_header(cls, stream):
"""
Decode a WebSocket header.
:param stream: A file like object that can be 'read' from.
:returns: A `Header` instance.
"""
read = stream.read
data = read(2)
if len(data) != 2:
raise WebSocketError("Unexpected EOF while decoding header")
first_byte, second_byte = struct.unpack('!BB', data)
header = cls(
fin=first_byte & cls.FIN_MASK == cls.FIN_MASK,
opcode=first_byte & cls.OPCODE_MASK,
flags=first_byte & cls.HEADER_FLAG_MASK,
length=second_byte & cls.LENGTH_MASK)
has_mask = second_byte & cls.MASK_MASK == cls.MASK_MASK
if header.opcode > 0x07:
if not header.fin:
raise ProtocolError(
"Received fragmented control frame: {0!r}".format(data))
# Control frames MUST have a payload length of 125 bytes or less
if header.length > 125:
raise FrameTooLargeException(
"Control frame cannot be larger than 125 bytes: "
"{0!r}".format(data))
if header.length == 126:
# 16 bit length
data = read(2)
if len(data) != 2:
raise WebSocketError('Unexpected EOF while decoding header')
header.length = struct.unpack('!H', data)[0]
elif header.length == 127:
# 64 bit length
data = read(8)
if len(data) != 8:
raise WebSocketError('Unexpected EOF while decoding header')
header.length = struct.unpack('!Q', data)[0]
if has_mask:
mask = read(4)
if len(mask) != 4:
raise WebSocketError('Unexpected EOF while decoding header')
header.mask = mask
return header
@classmethod
def encode_header(cls, fin, opcode, mask, length, flags):
"""
Encodes a WebSocket header.
:param fin: Whether this is the final frame for this opcode.
:param opcode: The opcode of the payload, see `OPCODE_*`
:param mask: Whether the payload is masked.
:param length: The length of the frame.
:param flags: The RSV* flags.
:return: A bytestring encoded header.
"""
first_byte = opcode
second_byte = 0
extra = ''
if fin:
first_byte |= cls.FIN_MASK
if flags & cls.RSV0_MASK:
first_byte |= cls.RSV0_MASK
if flags & cls.RSV1_MASK:
first_byte |= cls.RSV1_MASK
if flags & cls.RSV2_MASK:
first_byte |= cls.RSV2_MASK
# now deal with length complexities
if length < 126:
second_byte += length
elif length <= 0xffff:
second_byte += 126
extra = struct.pack('!H', length)
elif length <= 0xffffffffffffffff:
second_byte += 127
extra = struct.pack('!Q', length)
else:
raise FrameTooLargeException
if mask:
second_byte |= cls.MASK_MASK
extra += mask
return chr(first_byte) + chr(second_byte) + extra

23
src/lib/subtl/LICENCE Normal file
View File

@ -0,0 +1,23 @@
Copyright (c) 2012, Packetloop. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* Neither the name of Packetloop nor the names of its contributors may be
used to endorse or promote products derived from this software without
specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

28
src/lib/subtl/README.md Normal file
View File

@ -0,0 +1,28 @@
# subtl
## Overview
SUBTL is a **s**imple **U**DP **B**itTorrent **t**racker **l**ibrary for Python, licenced under the modified BSD license.
## Example
This short example will list a few IP Addresses from a certain hash:
from subtl import UdpTrackerClient
utc = UdpTrackerClient('tracker.openbittorrent.com', 80)
utc.connect()
if not utc.poll_once():
raise Exception('Could not connect')
print('Success!')
utc.announce(info_hash='089184ED52AA37F71801391C451C5D5ADD0D9501')
data = utc.poll_once()
if not data:
raise Exception('Could not announce')
for a in data['response']['peers']:
print(a)
## Caveats
* There is no automatic retrying of sending packets yet.
* This library won't download torrent files--it is simply a tracker client.

View File

220
src/lib/subtl/subtl.py Normal file
View File

@ -0,0 +1,220 @@
'''
Based on the specification at http://bittorrent.org/beps/bep_0015.html
'''
import random
import struct
import time
import socket
from collections import defaultdict
__version__ = '0.0.1'
CONNECT = 0
ANNOUNCE = 1
SCRAPE = 2
ERROR = 3
def norm_info_hash(info_hash):
if len(info_hash) == 40:
info_hash = info_hash.decode('hex')
if len(info_hash) != 20:
raise UdpTrackerClientException(
'info_hash length is not 20: {}'.format(len(info_hash)))
return info_hash
def info_hash_to_str(info_hash):
return binascii.hexlify(info_hash)
class UdpTrackerClientException(Exception):
pass
class UdpTrackerClient:
def __init__(self, host, port):
self.host = host
self.port = port
self.peer_port = 6881
self.sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
self.conn_id = 0x41727101980
self.transactions = {}
self.peer_id = self._generate_peer_id()
self.timeout = 2
def connect(self):
return self._send(CONNECT)
def announce(self, **kwargs):
if not kwargs:
raise UdpTrackerClientException('arguments missing')
args = {
'peer_id': self.peer_id,
'downloaded': 0,
'left': 0,
'uploaded': 0,
'event': 0,
'key': 0,
'num_want': 10,
'ip_address': 0,
'port': self.peer_port,
}
args.update(kwargs)
fields = 'info_hash peer_id downloaded left uploaded event ' \
'ip_address key num_want port'
# Check and raise if missing fields
self._check_fields(args, fields)
# Humans tend to use hex representations of the hash. Wasteful humans.
args['info_hash'] = norm_info_hash(args['info_hash'])
values = [args[a] for a in fields.split()]
payload = struct.pack('!20s20sQQQLLLLH', *values)
return self._send(ANNOUNCE, payload)
def scrape(self, info_hash_list):
if len(info_hash_list) > 74:
raise UdpTrackerClientException('Max info_hashes is 74')
payload = ''
for info_hash in info_hash_list:
info_hash = norm_info_hash(info_hash)
payload += info_hash
trans = self._send(SCRAPE, payload)
trans['sent_hashes'] = info_hash_list
return trans
def poll_once(self):
self.sock.settimeout(self.timeout)
try:
response = self.sock.recv(10240)
except socket.timeout:
return
header = response[:8]
payload = response[8:]
action, trans_id = struct.unpack('!LL', header)
try:
trans = self.transactions[trans_id]
except KeyError:
self.error('transaction_id not found')
return
trans['response'] = self._process_response(action, payload, trans)
trans['completed'] = True
del self.transactions[trans_id]
return trans
def error(self, message):
print('error: {}'.format(message))
def _send(self, action, payload=None):
if not payload:
payload = ''
trans_id, header = self._request_header(action)
self.transactions[trans_id] = trans = {
'action': action,
'time': time.time(),
'payload': payload,
'completed': False,
}
self.sock.sendto(header + payload, (self.host, self.port))
return trans
def _request_header(self, action):
trans_id = random.randint(0, (1 << 32) - 1)
return trans_id, struct.pack('!QLL', self.conn_id, action, trans_id)
def _process_response(self, action, payload, trans):
if action == CONNECT:
return self._process_connect(payload, trans)
elif action == ANNOUNCE:
return self._process_announce(payload, trans)
elif action == SCRAPE:
return self._process_scrape(payload, trans)
elif action == ERROR:
return self._proecss_error(payload, trans)
else:
raise UdpTrackerClientException(
'Unknown action response: {}'.format(action))
def _process_connect(self, payload, trans):
self.conn_id = struct.unpack('!Q', payload)[0]
return self.conn_id
def _process_announce(self, payload, trans):
response = {}
info_struct = '!LLL'
info_size = struct.calcsize(info_struct)
info = payload[:info_size]
interval, leechers, seeders = struct.unpack(info_struct, info)
peer_data = payload[info_size:]
peer_struct = '!LH'
peer_size = struct.calcsize(peer_struct)
peer_count = len(peer_data) / peer_size
peers = []
for peer_offset in xrange(peer_count):
off = peer_size * peer_offset
peer = peer_data[off:off + peer_size]
addr, port = struct.unpack(peer_struct, peer)
peers.append({
'addr': socket.inet_ntoa(struct.pack('!L', addr)),
'port': port,
})
return {
'interval': interval,
'leechers': leechers,
'seeders': seeders,
'peers': peers,
}
def _process_scrape(self, payload, trans):
info_struct = '!LLL'
info_size = struct.calcsize(info_struct)
info_count = len(payload) / info_size
hashes = trans['sent_hashes']
response = {}
for info_offset in xrange(info_count):
off = info_size * info_offset
info = payload[off:off + info_size]
seeders, completed, leechers = struct.unpack(info_struct, info)
response[hashes[info_offset]] = {
'seeders': seeders,
'completed': completed,
'leechers': leechers,
}
return response
def _process_error(self, payload, trans):
'''
I haven't seen this action type be sent from a tracker, but I've left
it here for the possibility.
'''
self.error(payload)
return payload
def _generate_peer_id(self):
'''http://www.bittorrent.org/beps/bep_0020.html'''
peer_id = '-PU' + __version__.replace('.', '-') + '-'
remaining = 20 - len(peer_id)
numbers = [str(random.randint(0, 9)) for _ in xrange(remaining)]
peer_id += ''.join(numbers)
assert(len(peer_id) == 20)
return peer_id
def _check_fields(self, args, fields):
for f in fields:
try:
args.get(f)
except KeyError:
raise UdpTrackerClientException('field missing: {}'.format(f))

175
src/main.py Normal file
View File

@ -0,0 +1,175 @@
import os, sys
sys.path.append(os.path.dirname(__file__)) # Imports relative to main.py
# Load config
from Config import config
# Init logging
import logging
if config.action == "main":
if os.path.isfile("log/debug.log"): # Simple logrotate
if os.path.isfile("log/debug-last.log"): os.unlink("log/debug-last.log")
os.rename("log/debug.log", "log/debug-last.log")
logging.basicConfig(format='[%(asctime)s] %(levelname)-8s %(name)s %(message)s', level=logging.DEBUG, filename="log/debug.log")
else:
logging.basicConfig(level=logging.DEBUG, stream=open(os.devnull,"w")) # No file logging if action is not main
console_log = logging.StreamHandler()
console_log.setFormatter(logging.Formatter('%(name)s %(message)s', "%H:%M:%S"))
logging.getLogger('').addHandler(console_log) # Add console logger
logging.getLogger('').name = "-" # Remove root prefix
# Debug dependent configuration
if config.debug:
console_log.setLevel(logging.DEBUG)
from Debug import DebugHook
from gevent import monkey; monkey.patch_all(thread=False) # thread=False because of pyfilesystem
else:
console_log.setLevel(logging.INFO)
from gevent import monkey; monkey.patch_all()
import gevent
import time
logging.debug("Starting... %s" % config)
# Start serving UiServer and PeerServer
def main():
from File import FileServer
from Ui import UiServer
logging.info("Creating UiServer....")
ui_server = UiServer()
logging.info("Creating FileServer....")
file_server = FileServer()
logging.info("Starting servers....")
gevent.joinall([gevent.spawn(ui_server.start), gevent.spawn(file_server.start)])
# Site commands
def siteCreate():
logging.info("Generating new privatekey...")
from src.Crypt import CryptBitcoin
privatekey = CryptBitcoin.newPrivatekey()
logging.info("-----------------------------------------------------------")
logging.info("Site private key: %s (save it, required to modify the site)" % privatekey)
address = CryptBitcoin.privatekeyToAddress(privatekey)
logging.info("Site address: %s" % address)
logging.info("-----------------------------------------------------------")
logging.info("Creating directory structure...")
from Site import Site
os.mkdir("data/%s" % address)
open("data/%s/index.html" % address, "w").write("Hello %s!" % address)
logging.info("Creating content.json...")
site = Site(address)
site.signContent(privatekey)
logging.info("Site created!")
def siteSign(address, privatekey=None):
from Site import Site
logging.info("Signing site: %s..." % address)
site = Site(address, allow_create = False)
if not privatekey: # If no privatekey in args then ask it now
import getpass
privatekey = getpass.getpass("Private key (input hidden):")
site.signContent(privatekey)
def siteVerify(address):
from Site import Site
logging.info("Verifing site: %s..." % address)
site = Site(address)
logging.info("Verifing content.json signature...")
if site.verifyFile("content.json", open(site.getPath("content.json"), "rb"), force=True) != False: # Force check the sign
logging.info("[OK] content.json signed by address %s!" % address)
else:
logging.error("[ERROR] Content.json not signed by address %s!" % address)
logging.info("Verifying site files...")
bad_files = site.verifyFiles()
if not bad_files:
logging.info("[OK] All file sha1sum matches!")
else:
logging.error("[ERROR] Error during verifying site files!")
def siteAnnounce(address):
from Site.Site import Site
logging.info("Announcing site %s to tracker..." % address)
site = Site(address)
s = time.time()
site.announce()
print "Response time: %.3fs" % (time.time()-s)
print site.peers
def siteNeedFile(address, inner_path):
from Site import Site
site = Site(address)
site.announce()
print site.needFile(inner_path, update=True)
def sitePublish(address):
from Site import Site
from File import FileServer # We need fileserver to handle incoming file requests
logging.info("Creating FileServer....")
file_server = FileServer()
file_server_thread = gevent.spawn(file_server.start, check_sites=False) # Dont check every site integrity
file_server.openport()
if file_server.port_opened == False:
logging.info("Port not opened, passive publishing not supported yet :(")
return
site = file_server.sites[address]
site.settings["serving"] = True # Serving the site even if its disabled
site.announce() # Gather peers
site.publish(10) # Push to 10 peers
logging.info("Serving files....")
gevent.joinall([file_server_thread])
# Crypto commands
def cryptoPrivatekeyToAddress(privatekey=None):
from src.Crypt import CryptBitcoin
if not privatekey: # If no privatekey in args then ask it now
import getpass
privatekey = getpass.getpass("Private key (input hidden):")
print CryptBitcoin.privatekeyToAddress(privatekey)
# Peer
def peerPing(ip, port):
from Peer import Peer
logging.info("Pinging 5 times peer: %s:%s..." % (ip, port))
peer = Peer(ip, port)
for i in range(5):
s = time.time()
print peer.ping(),
print "Response time: %.3fs" % (time.time()-s)
time.sleep(1)
def peerGetFile(ip, port, site, filename=None):
from Peer import Peer
if not site: site = config.homepage
if not filename: filename = "content.json"
logging.info("Getting %s/%s from peer: %s:%s..." % (site, filename, ip, port))
peer = Peer(ip, port)
s = time.time()
print peer.getFile(site, filename).read()
print "Response time: %.3fs" % (time.time()-s)

29
src/util/Event.py Normal file
View File

@ -0,0 +1,29 @@
# Based on http://stackoverflow.com/a/2022629
class Event(list):
def __call__(self, *args, **kwargs):
for f in self[:]:
if "once" in dir(f):
self.remove(f)
f(*args, **kwargs)
def __repr__(self):
return "Event(%s)" % list.__repr__(self)
def once(self, func):
func.once = True
self.append(func)
return self
if __name__ == "__main__":
def say(pre, text):
print "%s Say: %s" % (pre, text)
onChanged = Event()
onChanged.once(lambda pre: say(pre, "once"))
onChanged.append(lambda pre: say(pre, "always"))
onChanged("#1")
onChanged("#2")
onChanged("#3")

114
src/util/Noparallel.py Normal file
View File

@ -0,0 +1,114 @@
import gevent, time
class Noparallel(object): # Only allow function running once in same time
def __init__(self,blocking=True):
self.threads = {}
self.blocking = blocking # Blocking: Acts like normal function else thread returned
def __call__(self, func):
def wrapper(*args, **kwargs):
key = (func, tuple(args), tuple(kwargs)) # Unique key for function including parameters
if key in self.threads: # Thread already running (if using blocking mode)
thread = self.threads[key]
if self.blocking:
thread.join() # Blocking until its finished
return thread.value # Return the value
else: # No blocking
if thread.ready(): # Its finished, create a new
thread = gevent.spawn(func, *args, **kwargs)
self.threads[key] = thread
return thread
else: # Still running
return thread
else: # Thread not running
thread = gevent.spawn(func, *args, **kwargs) # Spawning new thread
self.threads[key] = thread
if self.blocking: # Wait for finish
thread.join()
ret = thread.value
if key in self.threads: del(self.threads[key]) # Allowing it to run again
return ret
else: # No blocking just return the thread
return thread
wrapper.func_name = func.func_name
return wrapper
class Test():
@Noparallel()
def count(self):
for i in range(5):
print self, i
time.sleep(1)
return "%s return:%s" % (self, i)
class TestNoblock():
@Noparallel(blocking=False)
def count(self):
for i in range(5):
print self, i
time.sleep(1)
return "%s return:%s" % (self, i)
def testBlocking():
test = Test()
test2 = Test()
print "Counting..."
print "Creating class1/thread1"
thread1 = gevent.spawn(test.count)
print "Creating class1/thread2 (ignored)"
thread2 = gevent.spawn(test.count)
print "Creating class2/thread3"
thread3 = gevent.spawn(test2.count)
print "Joining class1/thread1"
thread1.join()
print "Joining class1/thread2"
thread2.join()
print "Joining class2/thread3"
thread3.join()
print "Creating class1/thread4 (its finished, allowed again)"
thread4 = gevent.spawn(test.count)
print "Joining thread4"
thread4.join()
print thread1.value, thread2.value, thread3.value, thread4.value
print "Done."
def testNoblocking():
test = TestNoblock()
test2 = TestNoblock()
print "Creating class1/thread1"
thread1 = test.count()
print "Creating class1/thread2 (ignored)"
thread2 = test.count()
print "Creating class2/thread3"
thread3 = test2.count()
print "Joining class1/thread1"
thread1.join()
print "Joining class1/thread2"
thread2.join()
print "Joining class2/thread3"
thread3.join()
print "Creating class1/thread4 (its finished, allowed again)"
thread4 = test.count()
print "Joining thread4"
thread4.join()
print thread1.value, thread2.value, thread3.value, thread4.value
print "Done."
if __name__ == "__main__":
from gevent import monkey
monkey.patch_all()
print "Testing blocking mode..."
testBlocking()
print "Testing noblocking mode..."
testNoblocking()

2
src/util/__init__.py Normal file
View File

@ -0,0 +1,2 @@
from Event import Event
from Noparallel import Noparallel

19
tools/coffee/README.md Normal file
View File

@ -0,0 +1,19 @@
# CoffeeScript compiler for Windows
A simple command-line utilty for Windows that will compile `*.coffee` files to JavaScript `*.js` files using [CoffeeScript](http://jashkenas.github.com/coffee-script/) and the venerable Windows Script Host, ubiquitous on Windows since the 90s.
## Usage
To use it, invoke `coffee.cmd` like so:
coffee input.coffee output.js
If an output is not specified, it is written to `stdout`. In neither an input or output are specified then data is assumed to be on `stdin`. For example:
type input.coffee | coffee > output.js
Errors are written to `stderr`.
In the `test` directory there's a version of the standard CoffeeScript tests which can be kicked off using `test.cmd`. The test just attempts to compile the *.coffee files but doesn't execute them.
To upgrade to the latest CoffeeScript simply replace `coffee-script.js` from the upstream https://github.com/jashkenas/coffee-script/blob/master/extras/coffee-script.js (the tests will likely need updating as well, if you want to run them).

File diff suppressed because one or more lines are too long

2
tools/coffee/coffee.cmd Normal file
View File

@ -0,0 +1,2 @@
::For convenience
@cscript //nologo "%~dp0coffee.wsf" %*

93
tools/coffee/coffee.wsf Normal file
View File

@ -0,0 +1,93 @@
<job>
<!-- https://github.com/jashkenas/coffee-script/raw/master/extras/coffee-script.js -->
<script src="coffee-script.js" language="JScript" />
<script language="JScript">
(function() {
var args = [];
for (var i = 0; i < WScript.Arguments.Length; i++) {
args.push(WScript.Arguments.Item(i));
}
// FileSystemObject: http://msdn.microsoft.com/en-us/library/bkx696eh.aspx
var fso = new ActiveXObject("Scripting.FileSystemObject");
var isfolder = (args[0] && fso.folderExists(args[0]));
if (isfolder) {
f = fso.getFolder(args[0]);
e = new Enumerator(f.files);
for (; !e.atEnd(); e.moveNext()) {
if (e.item().path.toLowerCase().lastIndexOf('.coffee') != -1) {
convert(e.item(), args[1]);
}
}
}
else {
convert(args[0], args[1])
}
})();
function convert(input, output) {
var fso = new ActiveXObject("Scripting.FileSystemObject");
if (output) {
// if output specifies a folder name, output filename is same as input filename with .coffee extension
if (fso.folderExists(output)) {
output = output + '\\' + fso.getFile(input).name.replace('\.coffee', '.js')
}
}
var coffee;
if (!input) {
// Read all input data from STDIN
var chunks = [];
while (!WScript.StdIn.AtEndOfStream)
chunks.push(WScript.StdIn.ReadAll());
coffee = chunks.join('');
}
else {
coffee = readUtf8(input);
}
try {
var js = CoffeeScript.compile(coffee);
if (!output) {
WScript.StdOut.Write(js);
}
else {
writeUtf8(output, js);
}
}
catch (err) {
WScript.StdErr.WriteLine(err.message);
WScript.Quit(1);
}
}
function readUtf8(filename) {
var stream = new ActiveXObject("ADODB.Stream");
stream.Open();
stream.Type = 2; // Text
stream.Charset = 'utf-8';
stream.LoadFromFile(filename);
var text = stream.ReadText();
stream.Close();
return text;
}
function writeUtf8(filename, text) {
var stream = new ActiveXObject("ADODB.Stream");
stream.Open();
stream.Type = 2; // Text
stream.Charset = 'utf-8';
stream.WriteText(text);
stream.SaveToFile(filename, 2);
stream.Close();
}
</script>
</job>

585
tools/upnpc/Changelog.txt Normal file
View File

@ -0,0 +1,585 @@
$Id: Changelog.txt,v 1.193 2014/02/05 17:26:45 nanard Exp $
miniUPnP client Changelog.
2014/02/05:
handle EINPROGRESS after connect()
2014/02/03:
minixml now handle XML comments
VERSION 1.9 : released 2014/01/31
2014/01/31:
added argument remoteHost to UPNP_GetSpecificPortMappingEntry()
increment API_VERSION to 10
2013/12/09:
--help and -h arguments in upnpc.c
2013/10/07:
fixed potential buffer overrun in miniwget.c
Modified UPNP_GetValidIGD() to check for ExternalIpAddress
2013/08/01:
define MAXHOSTNAMELEN if not already done
2013/06/06:
update upnpreplyparse to allow larger values (128 chars instead of 64)
2013/05/14:
Update upnpreplyparse to take into account "empty" elements
validate upnpreplyparse.c code with "make check"
2013/05/03:
Fix Solaris build thanks to Maciej Małecki
2013/04/27:
Fix testminiwget.sh for BSD
2013/03/23:
Fixed Makefile for *BSD
2013/03/11:
Update Makefile to use JNAerator version 0.11
2013/02/11:
Fix testminiwget.sh for use with dash
Use $(DESTDIR) in Makefile
VERSION 1.8 : released 2013/02/06
2012/10/16:
fix testminiwget with no IPv6 support
2012/09/27:
Rename all include guards to not clash with C99
(7.1.3 Reserved identifiers).
2012/08/30:
Added -e option to upnpc program (set description for port mappings)
2012/08/29:
Python 3 support (thanks to Christopher Foo)
2012/08/11:
Fix a memory link in UPNP_GetValidIGD()
Try to handle scope id in link local IPv6 URL under MS Windows
2012/07/20:
Disable HAS_IP_MREQN on DragonFly BSD
2012/06/28:
GetUPNPUrls() now inserts scope into link-local IPv6 addresses
2012/06/23:
More error return checks in upnpc.c
#define MINIUPNPC_GET_SRC_ADDR enables receivedata() to get scope_id
parseURL() now parses IPv6 addresses scope
new parameter for miniwget() : IPv6 address scope
increment API_VERSION to 9
2012/06/20:
fixed CMakeLists.txt
2012/05/29
Improvements in testminiwget.sh
VERSION 1.7 : released 2012/05/24
2012/05/01:
Cleanup settings of CFLAGS in Makefile
Fix signed/unsigned integer comparaisons
2012/04/20:
Allow to specify protocol with TCP or UDP for -A option
2012/04/09:
Only try to fetch XML description once in UPNP_GetValidIGD()
Added -ansi flag to compilation, and fixed C++ comments to ANSI C comments.
2012/04/05:
minor improvements to minihttptestserver.c
2012/03/15:
upnperrors.c returns valid error string for unrecognized error codes
2012/03/08:
make minihttptestserver listen on loopback interface instead of 0.0.0.0
2012/01/25:
Maven installation thanks to Alexey Kuznetsov
2012/01/21:
Replace WIN32 macro by _WIN32
2012/01/19:
Fixes in java wrappers thanks to Alexey Kuznetsov :
https://github.com/axet/miniupnp/tree/fix-javatest/miniupnpc
Make and install .deb packages (python) thanks to Alexey Kuznetsov :
https://github.com/axet/miniupnp/tree/feature-debbuild/miniupnpc
2012/01/07:
The multicast interface can now be specified by name with IPv4.
2012/01/02:
Install man page
2011/11/25:
added header to Port Mappings list in upnpc.c
2011/10/09:
Makefile : make clean now removes jnaerator generated files.
MINIUPNPC_VERSION in miniupnpc.h (updated by make)
2011/09/12:
added rootdescURL to UPNPUrls structure.
VERSION 1.6 : released 2011/07/25
2011/07/25:
Update doc for version 1.6 release
2011/06/18:
Fix for windows in miniwget.c
2011/06/04:
display remote host in port mapping listing
2011/06/03:
Fix in make install : there were missing headers
2011/05/26:
Fix the socket leak in miniwget thanks to Richard Marsh.
Permit to add leaseduration in -a command. Display lease duration.
2011/05/15:
Try both LinkLocal and SiteLocal multicast address for SSDP in IPv6
2011/05/09:
add a test in testminiwget.sh.
more error checking in miniwget.c
2011/05/06:
Adding some tool to test and validate miniwget.c
simplified and debugged miniwget.c
2011/04/11:
moving ReceiveData() to a receivedata.c file.
parsing presentation url
adding IGD v2 WANIPv6FirewallControl commands
2011/04/10:
update of miniupnpcmodule.c
comments in miniwget.c, update in testminiwget
Adding errors codes from IGD v2
new functions in upnpc.c for IGD v2
2011/04/09:
Support for litteral ip v6 address in miniwget
2011/04/08:
Adding support for urn:schemas-upnp-org:service:WANIPv6FirewallControl:1
Updating APIVERSION
Supporting IPV6 in upnpDiscover()
Adding a -6 option to upnpc command line tool
2011/03/18:
miniwget/parseURL() : return an error when url param is null.
fixing GetListOfPortMappings()
2011/03/14:
upnpDiscover() now reporting an error code.
improvements in comments.
2011/03/11:
adding miniupnpcstrings.h.cmake and CMakeLists.txt files.
2011/02/15:
Implementation of GetListOfPortMappings()
2011/02/07:
updates to minixml to support character data starting with spaces
minixml now support CDATA
upnpreplyparse treats <NewPortListing> specificaly
change in simpleUPnPcommand to return the buffer (simplification)
2011/02/06:
Added leaseDuration argument to AddPortMapping()
Starting to implement GetListOfPortMappings()
2011/01/11:
updating wingenminiupnpcstrings.c
2011/01/04:
improving updateminiupnpcstrings.sh
VERSION 1.5 : released 2011/01/01
2010/12/21:
use NO_GETADDRINFO macro to disable the use of getaddrinfo/freeaddrinfo
2010/12/11:
Improvements on getHTTPResponse() code.
2010/12/09:
new code for miniwget that handle Chunked transfer encoding
using getHTTPResponse() in SOAP call code
Adding MANIFEST.in for 'python setup.py bdist_rpm'
2010/11/25:
changes to minissdpc.c to compile under Win32.
see http://miniupnp.tuxfamily.org/forum/viewtopic.php?t=729
2010/09/17:
Various improvement to Makefile from Michał Górny
2010/08/05:
Adding the script "external-ip.sh" from Reuben Hawkins
2010/06/09:
update to python module to match modification made on 2010/04/05
update to Java test code to match modification made on 2010/04/05
all UPNP_* function now return an error if the SOAP request failed
at HTTP level.
2010/04/17:
Using GetBestRoute() under win32 in order to find the
right interface to use.
2010/04/12:
Retrying with HTTP/1.1 if HTTP/1.0 failed. see
http://miniupnp.tuxfamily.org/forum/viewtopic.php?p=1703
2010/04/07:
avoid returning duplicates in upnpDiscover()
2010/04/05:
Create a connecthostport.h/.c with connecthostport() function
and use it in miniwget and miniupnpc.
Use getnameinfo() instead of inet_ntop or inet_ntoa
Work to make miniupnpc IPV6 compatible...
Add java test code.
Big changes in order to support device having both WANIPConnection
and WANPPPConnection.
2010/04/04:
Use getaddrinfo() instead of gethostbyname() in miniwget.
2010/01/06:
#define _DARWIN_C_SOURCE for Mac OS X
2009/12/19:
Improve MinGW32 build
2009/12/11:
adding a MSVC9 project to build the static library and executable
2009/12/10:
Fixing some compilation stuff for Windows/MinGW
2009/12/07:
adaptations in Makefile and updateminiupnpcstring.sh for AmigaOS
some fixes for Windows when using virtual ethernet adapters (it is the
case with VMWare installed).
2009/12/04:
some fixes for AmigaOS compilation
Changed HTTP version to HTTP/1.0 for Soap too (to prevent chunked
transfer encoding)
2009/12/03:
updating printIDG and testigddescparse.c for debug.
modifications to compile under AmigaOS
adding a testminiwget program
Changed miniwget to advertise itself as HTTP/1.0 to prevent chunked
transfer encoding
2009/11/26:
fixing updateminiupnpcstrings.sh to take into account
which command that does not return an error code.
VERSION 1.4 : released 2009/10/30
2009/10/16:
using Py_BEGIN_ALLOW_THREADS and Py_END_ALLOW_THREADS in python module.
2009/10/10:
Some fixes for compilation under Solaris
compilation fixes : http://miniupnp.tuxfamily.org/forum/viewtopic.php?p=1464
2009/09/21:
fixing the code to ignore EINTR during connect() calls.
2009/08/07:
Set socket timeout for connect()
Some cleanup in miniwget.c
2009/08/04:
remove multiple redirections with -d in upnpc.c
Print textual error code in upnpc.c
Ignore EINTR during the connect() and poll() calls.
2009/07/29:
fix in updateminiupnpcstrings.sh if OS name contains "/"
Sending a correct value for MX: field in SSDP request
2009/07/20:
Change the Makefile to compile under Mac OS X
Fixed a stackoverflow in getDevicesFromMiniSSDPD()
2009/07/09:
Compile under Haiku
generate miniupnpcstrings.h.in from miniupnpcstrings.h
2009/06/04:
patching to compile under CygWin and cross compile for minGW
VERSION 1.3 :
2009/04/17:
updating python module
Use strtoull() when using C99
2009/02/28:
Fixed miniwget.c for compiling under sun
2008/12/18:
cleanup in Makefile (thanks to Paul de Weerd)
minissdpc.c : win32 compatibility
miniupnpc.c : changed xmlns prefix from 'm' to 'u'
Removed NDEBUG (using DEBUG)
2008/10/14:
Added the ExternalHost argument to DeletePortMapping()
2008/10/11:
Added the ExternalHost argument to AddPortMapping()
Put a correct User-Agent: header in HTTP requests.
VERSION 1.2 :
2008/10/07:
Update docs
2008/09/25:
Integrated sameport patch from Dario Meloni : Added a "sameport"
argument to upnpDiscover().
2008/07/18:
small modif to make Clang happy :)
2008/07/17:
#define SOAPPREFIX "s" in miniupnpc.c in order to remove SOAP-ENV...
2008/07/14:
include declspec.h in installation (to /usr/include/miniupnpc)
VERSION 1.1 :
2008/07/04:
standard options for install/ln instead of gnu-specific stuff.
2008/07/03:
now builds a .dll and .lib with win32. (mingw32)
2008/04/28:
make install now install the binary of the upnpc tool
2008/04/27:
added testupnpigd.py
added error strings for miniupnpc "internal" errors
improved python module error/exception reporting.
2008/04/23:
Completely rewrite igd_desc_parse.c in order to be compatible with
Linksys WAG200G
Added testigddescparse
updated python module
VERSION 1.0 :
2008/02/21:
put some #ifdef DEBUG around DisplayNameValueList()
2008/02/18:
Improved error reporting in upnpcommands.c
UPNP_GetStatusInfo() returns LastConnectionError
2008/02/16:
better error handling in minisoap.c
improving display of "valid IGD found" in upnpc.c
2008/02/03:
Fixing UPNP_GetValidIGD()
improved make install :)
2007/12/22:
Adding upnperrors.c/h to provide a strupnperror() function
used to translate UPnP error codes to string.
2007/12/19:
Fixing getDevicesFromMiniSSDPD()
improved error reporting of UPnP functions
2007/12/18:
It is now possible to specify a different location for MiniSSDPd socket.
working with MiniSSDPd is now more efficient.
python module improved.
2007/12/16:
improving error reporting
2007/12/13:
Try to improve compatibility by using HTTP/1.0 instead of 1.1 and
XML a bit different for SOAP.
2007/11/25:
fixed select() call for linux
2007/11/15:
Added -fPIC to CFLAG for better shared library code.
2007/11/02:
Fixed a potential socket leak in miniwget2()
2007/10/16:
added a parameter to upnpDiscover() in order to allow the use of another
interface than the default multicast interface.
2007/10/12:
Fixed the creation of symbolic link in Makefile
2007/10/08:
Added man page
2007/10/02:
fixed memory bug in GetUPNPUrls()
2007/10/01:
fixes in the Makefile
Added UPNP_GetIGDFromUrl() and adapted the sample program accordingly.
Added SONAME in the shared library to please debian :)
fixed MS Windows compilation (minissdpd is not available under MS Windows).
2007/09/25:
small change to Makefile to be able to install in a different location
(default is /usr)
2007/09/24:
now compiling both shared and static library
2007/09/19:
Cosmetic changes on upnpc.c
2007/09/02:
adapting to new miniSSDPd (release version ?)
2007/08/31:
Usage of miniSSDPd to skip discovery process.
2007/08/27:
fixed python module to allow compilation with Python older than Python 2.4
2007/06/12:
Added a python module.
2007/05/19:
Fixed compilation under MinGW
2007/05/15:
fixed a memory leak in AddPortMapping()
Added testupnpreplyparse executable to check the parsing of
upnp soap messages
minixml now ignore namespace prefixes.
2007/04/26:
upnpc now displays external ip address with -s or -l
2007/04/11:
changed MINIUPNPC_URL_MAXSIZE to 128 to accomodate the "BT Voyager 210"
2007/03/19:
cleanup in miniwget.c
2007/03/01:
Small typo fix...
2007/01/30:
Now parsing the HTTP header from SOAP responses in order to
get content-length value.
2007/01/29:
Fixed the Soap Query to speedup the HTTP request.
added some Win32 DLL stuff...
2007/01/27:
Fixed some WIN32 compatibility issues
2006/12/14:
Added UPNPIGD_IsConnected() function in miniupnp.c/.h
Added UPNP_GetValidIGD() in miniupnp.c/.h
cleaned upnpc.c main(). now using UPNP_GetValidIGD()
2006/12/07:
Version 1.0-RC1 released
2006/12/03:
Minor changes to compile under SunOS/Solaris
2006/11/30:
made a minixml parser validator program
updated minixml to handle attributes correctly
2006/11/22:
Added a -r option to the upnpc sample thanks to Alexander Hubmann.
2006/11/19:
Cleanup code to make it more ANSI C compliant
2006/11/10:
detect and display local lan address.
2006/11/04:
Packets and Bytes Sent/Received are now unsigned int.
2006/11/01:
Bug fix thanks to Giuseppe D'Angelo
2006/10/31:
C++ compatibility for .h files.
Added a way to get ip Address on the LAN used to reach the IGD.
2006/10/25:
Added M-SEARCH to the services in the discovery process.
2006/10/22:
updated the Makefile to use makedepend, added a "make install"
update Makefile
2006/10/20:
fixing the description url parsing thanks to patch sent by
Wayne Dawe.
Fixed/translated some comments.
Implemented a better discover process, first looking
for IGD then for root devices (as some devices only reply to
M-SEARCH for root devices).
2006/09/02:
added freeUPNPDevlist() function.
2006/08/04:
More command line arguments checking
2006/08/01:
Added the .bat file to compile under Win32 with minGW32
2006/07/31:
Fixed the rootdesc parser (igd_desc_parse.c)
2006/07/20:
parseMSEARCHReply() is now returning the ST: line as well
starting changes to detect several UPnP devices on the network
2006/07/19:
using GetCommonLinkProperties to get down/upload bitrate

27
tools/upnpc/LICENSE Normal file
View File

@ -0,0 +1,27 @@
MiniUPnPc
Copyright (c) 2005-2011, Thomas BERNARD
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
* The name of the author may not be used to endorse or promote products
derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.

66
tools/upnpc/README Normal file
View File

@ -0,0 +1,66 @@
Project: miniupnp
Project web page: http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/
github: https://github.com/miniupnp/miniupnp
freecode: http://freecode.com/projects/miniupnp
Author: Thomas Bernard
Copyright (c) 2005-2012 Thomas Bernard
This software is subject to the conditions detailed in the
LICENSE file provided within this distribution.
For the comfort of Win32 users, bsdqueue.h is included in the distribution.
Its licence is included in the header of the file.
bsdqueue.h is a copy of the sys/queue.h of an OpenBSD system.
* miniUPnP Client - miniUPnPc *
To compile, simply run 'gmake' (could be 'make' on your system).
Under win32, to compile with MinGW, type "mingw32make.bat".
MS Visual C solution and project files are supplied in the msvc/ subdirectory.
The compilation is known to work under linux, FreeBSD,
OpenBSD, MacOS X, AmigaOS and cygwin.
The official AmigaOS4.1 SDK was used for AmigaOS4 and GeekGadgets for AmigaOS3.
upx (http://upx.sourceforge.net) is used to compress the win32 .exe files.
To install the library and headers on the system use :
> su
> make install
> exit
alternatively, to install into a specific location, use :
> INSTALLPREFIX=/usr/local make install
upnpc.c is a sample client using the libminiupnpc.
To use the libminiupnpc in your application, link it with
libminiupnpc.a (or .so) and use the following functions found in miniupnpc.h,
upnpcommands.h and miniwget.h :
- upnpDiscover()
- miniwget()
- parserootdesc()
- GetUPNPUrls()
- UPNP_* (calling UPNP methods)
Note : use #include <miniupnpc/miniupnpc.h> etc... for the includes
and -lminiupnpc for the link
Discovery process is speeded up when MiniSSDPd is running on the machine.
* Python module *
you can build a python module with 'make pythonmodule'
and install it with 'make installpythonmodule'.
setup.py (and setupmingw32.py) are included in the distribution.
Feel free to contact me if you have any problem :
e-mail : miniupnp@free.fr
If you are using libminiupnpc in your application, please
send me an email !
For any question, you can use the web forum :
http://miniupnp.tuxfamily.org/forum/

BIN
tools/upnpc/libminiupnpc.a Normal file

Binary file not shown.

43
tools/upnpc/miniupnpc.def Normal file
View File

@ -0,0 +1,43 @@
LIBRARY
; miniupnpc library
miniupnpc
EXPORTS
; miniupnpc
upnpDiscover
freeUPNPDevlist
parserootdesc
UPNP_GetValidIGD
UPNP_GetIGDFromUrl
GetUPNPUrls
FreeUPNPUrls
; miniwget
miniwget
miniwget_getaddr
; upnpcommands
UPNP_GetTotalBytesSent
UPNP_GetTotalBytesReceived
UPNP_GetTotalPacketsSent
UPNP_GetTotalPacketsReceived
UPNP_GetStatusInfo
UPNP_GetConnectionTypeInfo
UPNP_GetExternalIPAddress
UPNP_GetLinkLayerMaxBitRates
UPNP_AddPortMapping
UPNP_DeletePortMapping
UPNP_GetPortMappingNumberOfEntries
UPNP_GetSpecificPortMappingEntry
UPNP_GetGenericPortMappingEntry
UPNP_GetListOfPortMappings
UPNP_AddPinhole
UPNP_CheckPinholeWorking
UPNP_UpdatePinhole
UPNP_GetPinholePackets
UPNP_DeletePinhole
UPNP_GetFirewallStatus
UPNP_GetOutboundPinholeTimeout
; upnperrors
strupnperror
; portlistingparse
ParsePortListing
FreePortListing

BIN
tools/upnpc/miniupnpc.dll Normal file

Binary file not shown.

BIN
tools/upnpc/miniupnpc.lib Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

7
zeronet.py Normal file
View File

@ -0,0 +1,7 @@
from src import main
action_func = getattr(main, main.config.action)
action_kwargs = main.config.getActionArguments()
action_func(**action_kwargs)