2015-06-17 23:44:20 +02:00
|
|
|
# Included modules
|
|
|
|
import os
|
Rev536, Fix stats page, Support ranged http requests for better video browser compatibility, setHashfield command, One by one send hashfield to connected peers if changed, Keep count hashfield changetime, PeerHashfield optimalizations, Wait for peers on checkmodification, Give more time to query trackers, Do not count udp trackers as error if udp disabled, Test hashfield push
2015-10-30 02:08:02 +01:00
|
|
|
import time
|
2016-04-20 23:38:22 +02:00
|
|
|
import json
|
2019-01-20 16:22:32 +01:00
|
|
|
import collections
|
2016-11-07 23:20:08 +01:00
|
|
|
import itertools
|
2015-06-17 23:44:20 +02:00
|
|
|
|
|
|
|
# Third party modules
|
|
|
|
import gevent
|
|
|
|
|
2015-01-17 18:50:56 +01:00
|
|
|
from Debug import Debug
|
2015-01-21 12:58:26 +01:00
|
|
|
from Config import config
|
2015-09-27 02:08:53 +02:00
|
|
|
from util import RateLimit
|
2019-03-16 02:43:07 +01:00
|
|
|
from util import Msgpack
|
2015-09-27 02:08:53 +02:00
|
|
|
from util import helper
|
Version 0.3.5, Rev830, Full Tor mode support with hidden services, Onion stats in Sidebar, GeoDB download fix using Tor, Gray out disabled sites in Stats page, Tor hidden service status in stat page, Benchmark sha256, Skyts tracker out expodie in, 2 new tracker using ZeroNet protocol, Keep SSL cert option between restarts, SSL Certificate pinning support for connections, Site lock support for connections, Certificate pinned connections using implicit SSL, Flood protection whitelist support, Foreign keys support for DB layer, Not support for SQL query helper, 0 length file get bugfix, Pex onion address support, Faster port testing, Faster uPnP port opening, Need connections more often on owned sites, Delay ZeroHello startup message if port check or Tor manager not ready yet, Use lockfiles to avoid double start, Save original socket on proxy monkey patching to get ability to connect localhost directly, Handle atomic write errors, Broken gevent https workaround helper, Rsa crypt functions, Plugin to Bootstrap using ZeroNet protocol
2016-01-05 00:20:52 +01:00
|
|
|
from Plugin import PluginManager
|
2017-08-15 19:17:42 +02:00
|
|
|
from contextlib import closing
|
2015-01-12 02:03:45 +01:00
|
|
|
|
2015-07-12 20:36:46 +02:00
|
|
|
FILE_BUFF = 1024 * 512
|
|
|
|
|
2015-01-12 02:03:45 +01:00
|
|
|
|
2017-06-19 16:01:41 +02:00
|
|
|
class RequestError(Exception):
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
Version 0.3.5, Rev830, Full Tor mode support with hidden services, Onion stats in Sidebar, GeoDB download fix using Tor, Gray out disabled sites in Stats page, Tor hidden service status in stat page, Benchmark sha256, Skyts tracker out expodie in, 2 new tracker using ZeroNet protocol, Keep SSL cert option between restarts, SSL Certificate pinning support for connections, Site lock support for connections, Certificate pinned connections using implicit SSL, Flood protection whitelist support, Foreign keys support for DB layer, Not support for SQL query helper, 0 length file get bugfix, Pex onion address support, Faster port testing, Faster uPnP port opening, Need connections more often on owned sites, Delay ZeroHello startup message if port check or Tor manager not ready yet, Use lockfiles to avoid double start, Save original socket on proxy monkey patching to get ability to connect localhost directly, Handle atomic write errors, Broken gevent https workaround helper, Rsa crypt functions, Plugin to Bootstrap using ZeroNet protocol
2016-01-05 00:20:52 +01:00
|
|
|
# Incoming requests
|
|
|
|
@PluginManager.acceptPlugins
|
rev125, Class statistics, OpenSSL disabled on OSX by default because of possible segfault, --disable_openssl command line parameter, Save memory on Connection, Peer and FileRequest objects using slots, Dont store modification time from the far future, Able to query modified files from peer, Allow reannounce in 30secs, Use with command in SiteStorage, Always create dir before write file, PeerCmd shell command to query specific command from peer
2015-04-29 23:12:45 +02:00
|
|
|
class FileRequest(object):
|
2015-06-17 23:44:20 +02:00
|
|
|
__slots__ = ("server", "connection", "req_id", "sites", "log", "responded")
|
|
|
|
|
|
|
|
def __init__(self, server, connection):
|
|
|
|
self.server = server
|
|
|
|
self.connection = connection
|
|
|
|
|
|
|
|
self.req_id = None
|
|
|
|
self.sites = self.server.sites
|
|
|
|
self.log = server.log
|
|
|
|
self.responded = False # Responded to the request
|
|
|
|
|
|
|
|
def send(self, msg, streaming=False):
|
|
|
|
if not self.connection.closed:
|
|
|
|
self.connection.send(msg, streaming)
|
|
|
|
|
2015-07-25 13:38:58 +02:00
|
|
|
def sendRawfile(self, file, read_bytes):
|
|
|
|
if not self.connection.closed:
|
|
|
|
self.connection.sendRawfile(file, read_bytes)
|
|
|
|
|
2015-06-17 23:44:20 +02:00
|
|
|
def response(self, msg, streaming=False):
|
|
|
|
if self.responded:
|
2016-03-06 00:55:50 +01:00
|
|
|
if config.verbose:
|
|
|
|
self.log.debug("Req id %s already responded" % self.req_id)
|
2015-06-17 23:44:20 +02:00
|
|
|
return
|
|
|
|
if not isinstance(msg, dict): # If msg not a dict create a {"body": msg}
|
|
|
|
msg = {"body": msg}
|
|
|
|
msg["cmd"] = "response"
|
|
|
|
msg["to"] = self.req_id
|
|
|
|
self.responded = True
|
|
|
|
self.send(msg, streaming=streaming)
|
|
|
|
|
|
|
|
# Route file requests
|
|
|
|
def route(self, cmd, req_id, params):
|
|
|
|
self.req_id = req_id
|
Version 0.3.5, Rev830, Full Tor mode support with hidden services, Onion stats in Sidebar, GeoDB download fix using Tor, Gray out disabled sites in Stats page, Tor hidden service status in stat page, Benchmark sha256, Skyts tracker out expodie in, 2 new tracker using ZeroNet protocol, Keep SSL cert option between restarts, SSL Certificate pinning support for connections, Site lock support for connections, Certificate pinned connections using implicit SSL, Flood protection whitelist support, Foreign keys support for DB layer, Not support for SQL query helper, 0 length file get bugfix, Pex onion address support, Faster port testing, Faster uPnP port opening, Need connections more often on owned sites, Delay ZeroHello startup message if port check or Tor manager not ready yet, Use lockfiles to avoid double start, Save original socket on proxy monkey patching to get ability to connect localhost directly, Handle atomic write errors, Broken gevent https workaround helper, Rsa crypt functions, Plugin to Bootstrap using ZeroNet protocol
2016-01-05 00:20:52 +01:00
|
|
|
# Don't allow other sites than locked
|
2017-04-09 12:03:25 +02:00
|
|
|
if "site" in params and self.connection.target_onion:
|
|
|
|
valid_sites = self.connection.getValidSites()
|
2018-03-14 22:30:08 +01:00
|
|
|
if params["site"] not in valid_sites and valid_sites != ["global"]:
|
2017-04-09 12:03:25 +02:00
|
|
|
self.response({"error": "Invalid site"})
|
2017-04-10 12:18:21 +02:00
|
|
|
self.connection.log(
|
2017-06-13 14:12:13 +02:00
|
|
|
"Site lock violation: %s not in %s, target onion: %s" %
|
2017-04-10 12:18:21 +02:00
|
|
|
(params["site"], valid_sites, self.connection.target_onion)
|
|
|
|
)
|
2017-04-09 12:03:25 +02:00
|
|
|
self.connection.badAction(5)
|
|
|
|
return False
|
2015-06-17 23:44:20 +02:00
|
|
|
|
Version 0.3.5, Rev830, Full Tor mode support with hidden services, Onion stats in Sidebar, GeoDB download fix using Tor, Gray out disabled sites in Stats page, Tor hidden service status in stat page, Benchmark sha256, Skyts tracker out expodie in, 2 new tracker using ZeroNet protocol, Keep SSL cert option between restarts, SSL Certificate pinning support for connections, Site lock support for connections, Certificate pinned connections using implicit SSL, Flood protection whitelist support, Foreign keys support for DB layer, Not support for SQL query helper, 0 length file get bugfix, Pex onion address support, Faster port testing, Faster uPnP port opening, Need connections more often on owned sites, Delay ZeroHello startup message if port check or Tor manager not ready yet, Use lockfiles to avoid double start, Save original socket on proxy monkey patching to get ability to connect localhost directly, Handle atomic write errors, Broken gevent https workaround helper, Rsa crypt functions, Plugin to Bootstrap using ZeroNet protocol
2016-01-05 00:20:52 +01:00
|
|
|
if cmd == "update":
|
2015-06-17 23:44:20 +02:00
|
|
|
event = "%s update %s %s" % (self.connection.id, params["site"], params["inner_path"])
|
2016-11-07 23:19:17 +01:00
|
|
|
# If called more than once within 15 sec only keep the last update
|
|
|
|
RateLimit.callAsync(event, max(self.connection.bad_actions, 15), self.actionUpdate, params)
|
2015-06-17 23:44:20 +02:00
|
|
|
else:
|
Version 0.3.5, Rev830, Full Tor mode support with hidden services, Onion stats in Sidebar, GeoDB download fix using Tor, Gray out disabled sites in Stats page, Tor hidden service status in stat page, Benchmark sha256, Skyts tracker out expodie in, 2 new tracker using ZeroNet protocol, Keep SSL cert option between restarts, SSL Certificate pinning support for connections, Site lock support for connections, Certificate pinned connections using implicit SSL, Flood protection whitelist support, Foreign keys support for DB layer, Not support for SQL query helper, 0 length file get bugfix, Pex onion address support, Faster port testing, Faster uPnP port opening, Need connections more often on owned sites, Delay ZeroHello startup message if port check or Tor manager not ready yet, Use lockfiles to avoid double start, Save original socket on proxy monkey patching to get ability to connect localhost directly, Handle atomic write errors, Broken gevent https workaround helper, Rsa crypt functions, Plugin to Bootstrap using ZeroNet protocol
2016-01-05 00:20:52 +01:00
|
|
|
func_name = "action" + cmd[0].upper() + cmd[1:]
|
|
|
|
func = getattr(self, func_name, None)
|
2016-11-07 23:19:17 +01:00
|
|
|
if cmd not in ["getFile", "streamFile"]: # Skip IO bound functions
|
|
|
|
if self.connection.cpu_time > 0.5:
|
2017-04-10 12:18:21 +02:00
|
|
|
self.log.debug(
|
|
|
|
"Delay %s %s, cpu_time used by connection: %.3fs" %
|
|
|
|
(self.connection.ip, cmd, self.connection.cpu_time)
|
|
|
|
)
|
2016-11-07 23:19:17 +01:00
|
|
|
time.sleep(self.connection.cpu_time)
|
|
|
|
if self.connection.cpu_time > 5:
|
2017-02-27 00:02:24 +01:00
|
|
|
self.connection.close("Cpu time: %.3fs" % self.connection.cpu_time)
|
2017-10-03 15:19:09 +02:00
|
|
|
s = time.time()
|
Version 0.3.5, Rev830, Full Tor mode support with hidden services, Onion stats in Sidebar, GeoDB download fix using Tor, Gray out disabled sites in Stats page, Tor hidden service status in stat page, Benchmark sha256, Skyts tracker out expodie in, 2 new tracker using ZeroNet protocol, Keep SSL cert option between restarts, SSL Certificate pinning support for connections, Site lock support for connections, Certificate pinned connections using implicit SSL, Flood protection whitelist support, Foreign keys support for DB layer, Not support for SQL query helper, 0 length file get bugfix, Pex onion address support, Faster port testing, Faster uPnP port opening, Need connections more often on owned sites, Delay ZeroHello startup message if port check or Tor manager not ready yet, Use lockfiles to avoid double start, Save original socket on proxy monkey patching to get ability to connect localhost directly, Handle atomic write errors, Broken gevent https workaround helper, Rsa crypt functions, Plugin to Bootstrap using ZeroNet protocol
2016-01-05 00:20:52 +01:00
|
|
|
if func:
|
|
|
|
func(params)
|
|
|
|
else:
|
|
|
|
self.actionUnknown(cmd, params)
|
2015-06-17 23:44:20 +02:00
|
|
|
|
2016-11-07 23:19:17 +01:00
|
|
|
if cmd not in ["getFile", "streamFile"]:
|
|
|
|
taken = time.time() - s
|
2017-10-03 15:19:09 +02:00
|
|
|
taken_sent = self.connection.last_sent_time - self.connection.last_send_time
|
|
|
|
self.connection.cpu_time += taken - taken_sent
|
2016-11-07 23:19:17 +01:00
|
|
|
|
2015-06-17 23:44:20 +02:00
|
|
|
# Update a site file request
|
|
|
|
def actionUpdate(self, params):
|
|
|
|
site = self.sites.get(params["site"])
|
2019-04-15 15:06:25 +02:00
|
|
|
if not site or not site.isServing(): # Site unknown or not serving
|
2015-06-17 23:44:20 +02:00
|
|
|
self.response({"error": "Unknown site"})
|
2017-02-09 01:54:04 +01:00
|
|
|
self.connection.badAction(1)
|
2018-11-26 00:12:54 +01:00
|
|
|
self.connection.badAction(5)
|
2015-06-17 23:44:20 +02:00
|
|
|
return False
|
2016-04-20 23:38:22 +02:00
|
|
|
|
2017-06-19 16:02:52 +02:00
|
|
|
inner_path = params.get("inner_path", "")
|
2019-05-30 04:26:41 +02:00
|
|
|
current_content_modified = site.content_manager.contents.get(inner_path, {}).get("modified", 0)
|
|
|
|
body = params["body"]
|
2017-06-19 16:02:52 +02:00
|
|
|
|
|
|
|
if not inner_path.endswith("content.json"):
|
2016-04-20 23:35:51 +02:00
|
|
|
self.response({"error": "Only content.json update allowed"})
|
2017-02-09 01:54:04 +01:00
|
|
|
self.connection.badAction(5)
|
2016-04-20 23:35:51 +02:00
|
|
|
return
|
2016-04-20 23:38:22 +02:00
|
|
|
|
2019-05-30 04:26:41 +02:00
|
|
|
should_validate_content = True
|
|
|
|
if "modified" in params and params["modified"] <= current_content_modified:
|
|
|
|
should_validate_content = False
|
|
|
|
valid = None # Same or earlier content as we have
|
|
|
|
elif not body: # No body sent, we have to download it first
|
2019-12-19 02:17:00 +01:00
|
|
|
site.log.debug("Missing body from update for file %s, downloading ..." % inner_path)
|
2019-05-30 04:26:41 +02:00
|
|
|
peer = site.addPeer(self.connection.ip, self.connection.port, return_peer=True, source="update") # Add or get peer
|
|
|
|
try:
|
|
|
|
body = peer.getFile(site.address, inner_path).read()
|
|
|
|
except Exception as err:
|
2019-12-19 02:17:00 +01:00
|
|
|
site.log.debug("Can't download updated file %s: %s" % (inner_path, err))
|
2019-05-30 04:26:41 +02:00
|
|
|
self.response({"error": "File invalid update: Can't download updaed file"})
|
|
|
|
self.connection.badAction(5)
|
|
|
|
return
|
2016-04-20 23:38:22 +02:00
|
|
|
|
2019-05-30 04:26:41 +02:00
|
|
|
if should_validate_content:
|
2017-06-19 16:07:36 +02:00
|
|
|
try:
|
2019-05-30 04:26:41 +02:00
|
|
|
content = json.loads(body.decode())
|
2019-03-15 21:06:59 +01:00
|
|
|
except Exception as err:
|
2019-12-19 02:17:00 +01:00
|
|
|
site.log.debug("Update for %s is invalid JSON: %s" % (inner_path, err))
|
2019-05-30 04:26:41 +02:00
|
|
|
self.response({"error": "File invalid JSON"})
|
|
|
|
self.connection.badAction(5)
|
|
|
|
return
|
|
|
|
|
|
|
|
file_uri = "%s/%s:%s" % (site.address, inner_path, content["modified"])
|
|
|
|
|
|
|
|
if self.server.files_parsing.get(file_uri): # Check if we already working on it
|
|
|
|
valid = None # Same file
|
|
|
|
else:
|
|
|
|
try:
|
|
|
|
valid = site.content_manager.verifyFile(inner_path, content)
|
|
|
|
except Exception as err:
|
2019-12-19 02:17:00 +01:00
|
|
|
site.log.debug("Update for %s is invalid: %s" % (inner_path, err))
|
2019-05-30 04:26:41 +02:00
|
|
|
error = err
|
|
|
|
valid = False
|
2016-04-20 23:38:22 +02:00
|
|
|
|
2015-07-12 20:36:46 +02:00
|
|
|
if valid is True: # Valid and changed
|
2017-06-19 16:04:17 +02:00
|
|
|
site.log.info("Update for %s looks valid, saving..." % inner_path)
|
2016-04-20 23:38:22 +02:00
|
|
|
self.server.files_parsing[file_uri] = True
|
2019-05-30 04:26:41 +02:00
|
|
|
site.storage.write(inner_path, body)
|
2016-04-20 23:38:22 +02:00
|
|
|
del params["body"]
|
2015-06-17 23:44:20 +02:00
|
|
|
|
2017-06-19 16:02:52 +02:00
|
|
|
site.onFileDone(inner_path) # Trigger filedone
|
2015-07-12 20:36:46 +02:00
|
|
|
|
2017-06-19 16:02:52 +02:00
|
|
|
if inner_path.endswith("content.json"): # Download every changed file from peer
|
2018-02-08 17:57:26 +01:00
|
|
|
peer = site.addPeer(self.connection.ip, self.connection.port, return_peer=True, source="update") # Add or get peer
|
2015-07-12 20:36:46 +02:00
|
|
|
# On complete publish to other peers
|
2016-08-15 13:54:00 +02:00
|
|
|
diffs = params.get("diffs", {})
|
2017-07-27 16:30:21 +02:00
|
|
|
site.onComplete.once(lambda: site.publish(inner_path=inner_path, diffs=diffs, limit=3), "publish_%s" % inner_path)
|
2015-06-17 23:44:20 +02:00
|
|
|
|
2015-07-12 20:36:46 +02:00
|
|
|
# Load new content file and download changed files in new thread
|
2016-04-20 23:38:22 +02:00
|
|
|
def downloader():
|
2017-06-19 16:02:52 +02:00
|
|
|
site.downloadContent(inner_path, peer=peer, diffs=params.get("diffs", {}))
|
2016-04-20 23:38:22 +02:00
|
|
|
del self.server.files_parsing[file_uri]
|
|
|
|
|
|
|
|
gevent.spawn(downloader)
|
|
|
|
else:
|
|
|
|
del self.server.files_parsing[file_uri]
|
2015-06-17 23:44:20 +02:00
|
|
|
|
2017-06-19 16:02:52 +02:00
|
|
|
self.response({"ok": "Thanks, file %s updated!" % inner_path})
|
2016-03-12 23:09:26 +01:00
|
|
|
self.connection.goodAction()
|
2015-06-17 23:44:20 +02:00
|
|
|
|
2015-07-12 20:36:46 +02:00
|
|
|
elif valid is None: # Not changed
|
2018-02-08 17:57:26 +01:00
|
|
|
peer = site.addPeer(self.connection.ip, self.connection.port, return_peer=True, source="update old") # Add or get peer
|
2015-06-17 23:44:20 +02:00
|
|
|
if peer:
|
2016-04-25 02:23:06 +02:00
|
|
|
if not peer.connection:
|
|
|
|
peer.connect(self.connection) # Assign current connection to peer
|
2017-06-19 16:04:17 +02:00
|
|
|
if inner_path in site.content_manager.contents:
|
|
|
|
peer.last_content_json_update = site.content_manager.contents[inner_path]["modified"]
|
2016-03-06 00:55:50 +01:00
|
|
|
if config.verbose:
|
2019-12-19 02:17:00 +01:00
|
|
|
site.log.debug(
|
2016-03-06 00:55:50 +01:00
|
|
|
"Same version, adding new peer for locked files: %s, tasks: %s" %
|
|
|
|
(peer.key, len(site.worker_manager.tasks))
|
|
|
|
)
|
2015-07-12 20:36:46 +02:00
|
|
|
for task in site.worker_manager.tasks: # New peer add to every ongoing task
|
2016-03-16 00:38:26 +01:00
|
|
|
if task["peers"] and not task["optional_hash_id"]:
|
2015-07-12 20:36:46 +02:00
|
|
|
# Download file from this peer too if its peer locked
|
|
|
|
site.needFile(task["inner_path"], peer=peer, update=True, blocking=False)
|
2015-06-17 23:44:20 +02:00
|
|
|
|
|
|
|
self.response({"ok": "File not changed"})
|
2016-03-12 23:09:26 +01:00
|
|
|
self.connection.badAction()
|
2015-06-17 23:44:20 +02:00
|
|
|
|
2016-03-30 23:05:43 +02:00
|
|
|
else: # Invalid sign or sha hash
|
file: set error message before using it
Fixes this exception:
Unhandled exception: [(<class 'UnboundLocalError'>,
UnboundLocalError("local variable 'err' referenced before assignm>
Traceback (most recent call last):
File "src/gevent/greenlet.py", line 766, in gevent._greenlet.Greenlet.run
File "/opt/zeronet/src/util/RateLimit.py", line 57, in <lambda>
thread = gevent.spawn_later(time_left, lambda: callQueue(event)) # Call this function later
File "/opt/zeronet/src/util/RateLimit.py", line 42, in callQueue
return func(*args, **kwargs)
File "/opt/zeronet/src/File/FileRequest.py", line 185, in actionUpdate
self.response({"error": "File invalid: %s" % err})
UnboundLocalError: local variable 'err' referenced before assignment
2019-03-31 22:25:26 +02:00
|
|
|
self.response({"error": "File %s invalid: %s" % (inner_path, error)})
|
2016-03-12 23:09:26 +01:00
|
|
|
self.connection.badAction(5)
|
2015-06-17 23:44:20 +02:00
|
|
|
|
2017-10-03 15:03:56 +02:00
|
|
|
def isReadable(self, site, inner_path, file, pos):
|
|
|
|
return True
|
|
|
|
|
2015-06-17 23:44:20 +02:00
|
|
|
# Send file content request
|
2017-10-03 15:03:56 +02:00
|
|
|
def handleGetFile(self, params, streaming=False):
|
2015-06-17 23:44:20 +02:00
|
|
|
site = self.sites.get(params["site"])
|
2019-04-15 15:06:25 +02:00
|
|
|
if not site or not site.isServing(): # Site unknown or not serving
|
2015-06-17 23:44:20 +02:00
|
|
|
self.response({"error": "Unknown site"})
|
2018-11-26 00:12:54 +01:00
|
|
|
self.connection.badAction(5)
|
2015-06-17 23:44:20 +02:00
|
|
|
return False
|
|
|
|
try:
|
|
|
|
file_path = site.storage.getPath(params["inner_path"])
|
2017-10-03 15:03:56 +02:00
|
|
|
if streaming:
|
|
|
|
file_obj = site.storage.open(params["inner_path"])
|
|
|
|
else:
|
2019-03-16 02:43:07 +01:00
|
|
|
file_obj = Msgpack.FilePart(file_path, "rb")
|
2017-10-03 15:03:56 +02:00
|
|
|
|
|
|
|
with file_obj as file:
|
2015-06-17 23:44:20 +02:00
|
|
|
file.seek(params["location"])
|
2017-10-03 15:03:56 +02:00
|
|
|
read_bytes = params.get("read_bytes", FILE_BUFF)
|
2015-08-16 11:51:00 +02:00
|
|
|
file_size = os.fstat(file.fileno()).st_size
|
2017-10-03 15:03:56 +02:00
|
|
|
|
|
|
|
if file_size > read_bytes: # Check if file is readable at current position (for big files)
|
|
|
|
if not self.isReadable(site, params["inner_path"], file, params["location"]):
|
|
|
|
raise RequestError("File not readable at position: %s" % params["location"])
|
2018-07-16 01:35:35 +02:00
|
|
|
else:
|
|
|
|
if params.get("file_size") and params["file_size"] != file_size:
|
|
|
|
self.connection.badAction(2)
|
|
|
|
raise RequestError("File size does not match: %sB != %sB" % (params["file_size"], file_size))
|
2017-10-03 15:03:56 +02:00
|
|
|
|
|
|
|
if not streaming:
|
|
|
|
file.read_bytes = read_bytes
|
|
|
|
|
2017-01-05 02:25:06 +01:00
|
|
|
if params["location"] > file_size:
|
|
|
|
self.connection.badAction(5)
|
2017-06-19 16:01:41 +02:00
|
|
|
raise RequestError("Bad file location")
|
2015-09-28 00:22:27 +02:00
|
|
|
|
2017-10-03 15:03:56 +02:00
|
|
|
if streaming:
|
|
|
|
back = {
|
|
|
|
"size": file_size,
|
|
|
|
"location": min(file.tell() + read_bytes, file_size),
|
|
|
|
"stream_bytes": min(read_bytes, file_size - params["location"])
|
|
|
|
}
|
|
|
|
self.response(back)
|
|
|
|
self.sendRawfile(file, read_bytes=read_bytes)
|
|
|
|
else:
|
|
|
|
back = {
|
|
|
|
"body": file,
|
|
|
|
"size": file_size,
|
|
|
|
"location": min(file.tell() + file.read_bytes, file_size)
|
|
|
|
}
|
|
|
|
self.response(back, streaming=True)
|
|
|
|
|
|
|
|
bytes_sent = min(read_bytes, file_size - params["location"]) # Number of bytes we going to send
|
2015-08-16 11:51:00 +02:00
|
|
|
site.settings["bytes_sent"] = site.settings.get("bytes_sent", 0) + bytes_sent
|
2015-06-17 23:44:20 +02:00
|
|
|
if config.debug_socket:
|
2015-08-16 11:51:00 +02:00
|
|
|
self.log.debug("File %s at position %s sent %s bytes" % (file_path, params["location"], bytes_sent))
|
2015-06-17 23:44:20 +02:00
|
|
|
|
|
|
|
# Add peer to site if not added before
|
2018-02-08 17:57:26 +01:00
|
|
|
connected_peer = site.addPeer(self.connection.ip, self.connection.port, source="request")
|
2015-07-12 20:36:46 +02:00
|
|
|
if connected_peer: # Just added
|
2015-06-17 23:44:20 +02:00
|
|
|
connected_peer.connect(self.connection) # Assign current connection to peer
|
|
|
|
|
2016-11-07 23:19:33 +01:00
|
|
|
return {"bytes_sent": bytes_sent, "file_size": file_size, "location": params["location"]}
|
|
|
|
|
2019-03-15 21:06:59 +01:00
|
|
|
except RequestError as err:
|
2019-12-31 12:46:01 +01:00
|
|
|
self.log.debug("GetFile %s %s %s request error: %s" % (self.connection, params["site"], params["inner_path"], Debug.formatException(err)))
|
2017-06-19 16:01:41 +02:00
|
|
|
self.response({"error": "File read error: %s" % err})
|
2019-06-06 02:27:35 +02:00
|
|
|
except OSError as err:
|
2017-08-09 14:19:56 +02:00
|
|
|
if config.verbose:
|
|
|
|
self.log.debug("GetFile read error: %s" % Debug.formatException(err))
|
2017-04-14 00:26:20 +02:00
|
|
|
self.response({"error": "File read error"})
|
2015-06-17 23:44:20 +02:00
|
|
|
return False
|
2019-06-06 02:27:35 +02:00
|
|
|
except Exception as err:
|
|
|
|
self.log.error("GetFile exception: %s" % Debug.formatException(err))
|
2019-06-06 03:17:42 +02:00
|
|
|
self.response({"error": "File read exception"})
|
2019-06-06 02:27:35 +02:00
|
|
|
return False
|
2015-06-17 23:44:20 +02:00
|
|
|
|
2017-10-03 15:03:56 +02:00
|
|
|
def actionGetFile(self, params):
|
|
|
|
return self.handleGetFile(params)
|
2016-11-07 23:19:33 +01:00
|
|
|
|
2017-10-03 15:03:56 +02:00
|
|
|
def actionStreamFile(self, params):
|
|
|
|
return self.handleGetFile(params, streaming=True)
|
2015-07-25 13:38:58 +02:00
|
|
|
|
2015-06-17 23:44:20 +02:00
|
|
|
# Peer exchange request
|
|
|
|
def actionPex(self, params):
|
|
|
|
site = self.sites.get(params["site"])
|
2019-04-15 15:06:25 +02:00
|
|
|
if not site or not site.isServing(): # Site unknown or not serving
|
2015-06-17 23:44:20 +02:00
|
|
|
self.response({"error": "Unknown site"})
|
2018-11-26 00:12:54 +01:00
|
|
|
self.connection.badAction(5)
|
2015-06-17 23:44:20 +02:00
|
|
|
return False
|
|
|
|
|
|
|
|
got_peer_keys = []
|
|
|
|
added = 0
|
2015-09-28 00:22:27 +02:00
|
|
|
|
|
|
|
# Add requester peer to site
|
2018-02-08 17:57:26 +01:00
|
|
|
connected_peer = site.addPeer(self.connection.ip, self.connection.port, source="request")
|
|
|
|
|
2015-09-28 00:22:27 +02:00
|
|
|
if connected_peer: # It was not registered before
|
2015-06-17 23:44:20 +02:00
|
|
|
added += 1
|
|
|
|
connected_peer.connect(self.connection) # Assign current connection to peer
|
|
|
|
|
2015-09-28 00:22:27 +02:00
|
|
|
# Add sent peers to site
|
2019-01-20 16:22:32 +01:00
|
|
|
for packed_address in itertools.chain(params.get("peers", []), params.get("peers_ipv6", [])):
|
2015-09-27 12:42:53 +02:00
|
|
|
address = helper.unpackAddress(packed_address)
|
2015-06-17 23:44:20 +02:00
|
|
|
got_peer_keys.append("%s:%s" % address)
|
2018-02-08 17:57:26 +01:00
|
|
|
if site.addPeer(*address, source="pex"):
|
2015-07-12 20:36:46 +02:00
|
|
|
added += 1
|
2015-09-28 00:22:27 +02:00
|
|
|
|
2019-01-20 16:22:32 +01:00
|
|
|
# Add sent onion peers to site
|
Version 0.3.5, Rev830, Full Tor mode support with hidden services, Onion stats in Sidebar, GeoDB download fix using Tor, Gray out disabled sites in Stats page, Tor hidden service status in stat page, Benchmark sha256, Skyts tracker out expodie in, 2 new tracker using ZeroNet protocol, Keep SSL cert option between restarts, SSL Certificate pinning support for connections, Site lock support for connections, Certificate pinned connections using implicit SSL, Flood protection whitelist support, Foreign keys support for DB layer, Not support for SQL query helper, 0 length file get bugfix, Pex onion address support, Faster port testing, Faster uPnP port opening, Need connections more often on owned sites, Delay ZeroHello startup message if port check or Tor manager not ready yet, Use lockfiles to avoid double start, Save original socket on proxy monkey patching to get ability to connect localhost directly, Handle atomic write errors, Broken gevent https workaround helper, Rsa crypt functions, Plugin to Bootstrap using ZeroNet protocol
2016-01-05 00:20:52 +01:00
|
|
|
for packed_address in params.get("peers_onion", []):
|
|
|
|
address = helper.unpackOnionAddress(packed_address)
|
|
|
|
got_peer_keys.append("%s:%s" % address)
|
2018-02-08 17:57:26 +01:00
|
|
|
if site.addPeer(*address, source="pex"):
|
Version 0.3.5, Rev830, Full Tor mode support with hidden services, Onion stats in Sidebar, GeoDB download fix using Tor, Gray out disabled sites in Stats page, Tor hidden service status in stat page, Benchmark sha256, Skyts tracker out expodie in, 2 new tracker using ZeroNet protocol, Keep SSL cert option between restarts, SSL Certificate pinning support for connections, Site lock support for connections, Certificate pinned connections using implicit SSL, Flood protection whitelist support, Foreign keys support for DB layer, Not support for SQL query helper, 0 length file get bugfix, Pex onion address support, Faster port testing, Faster uPnP port opening, Need connections more often on owned sites, Delay ZeroHello startup message if port check or Tor manager not ready yet, Use lockfiles to avoid double start, Save original socket on proxy monkey patching to get ability to connect localhost directly, Handle atomic write errors, Broken gevent https workaround helper, Rsa crypt functions, Plugin to Bootstrap using ZeroNet protocol
2016-01-05 00:20:52 +01:00
|
|
|
added += 1
|
|
|
|
|
2015-06-17 23:44:20 +02:00
|
|
|
# Send back peers that is not in the sent list and connectable (not port 0)
|
2019-01-20 16:22:32 +01:00
|
|
|
packed_peers = helper.packPeers(site.getConnectablePeers(params["need"], ignore=got_peer_keys, allow_private=False))
|
Version 0.3.5, Rev830, Full Tor mode support with hidden services, Onion stats in Sidebar, GeoDB download fix using Tor, Gray out disabled sites in Stats page, Tor hidden service status in stat page, Benchmark sha256, Skyts tracker out expodie in, 2 new tracker using ZeroNet protocol, Keep SSL cert option between restarts, SSL Certificate pinning support for connections, Site lock support for connections, Certificate pinned connections using implicit SSL, Flood protection whitelist support, Foreign keys support for DB layer, Not support for SQL query helper, 0 length file get bugfix, Pex onion address support, Faster port testing, Faster uPnP port opening, Need connections more often on owned sites, Delay ZeroHello startup message if port check or Tor manager not ready yet, Use lockfiles to avoid double start, Save original socket on proxy monkey patching to get ability to connect localhost directly, Handle atomic write errors, Broken gevent https workaround helper, Rsa crypt functions, Plugin to Bootstrap using ZeroNet protocol
2016-01-05 00:20:52 +01:00
|
|
|
|
2015-06-17 23:44:20 +02:00
|
|
|
if added:
|
|
|
|
site.worker_manager.onPeers()
|
2016-03-06 00:55:50 +01:00
|
|
|
if config.verbose:
|
|
|
|
self.log.debug(
|
|
|
|
"Added %s peers to %s using pex, sending back %s" %
|
2019-03-15 21:06:59 +01:00
|
|
|
(added, site, {key: len(val) for key, val in packed_peers.items()})
|
2016-03-06 00:55:50 +01:00
|
|
|
)
|
Version 0.3.5, Rev830, Full Tor mode support with hidden services, Onion stats in Sidebar, GeoDB download fix using Tor, Gray out disabled sites in Stats page, Tor hidden service status in stat page, Benchmark sha256, Skyts tracker out expodie in, 2 new tracker using ZeroNet protocol, Keep SSL cert option between restarts, SSL Certificate pinning support for connections, Site lock support for connections, Certificate pinned connections using implicit SSL, Flood protection whitelist support, Foreign keys support for DB layer, Not support for SQL query helper, 0 length file get bugfix, Pex onion address support, Faster port testing, Faster uPnP port opening, Need connections more often on owned sites, Delay ZeroHello startup message if port check or Tor manager not ready yet, Use lockfiles to avoid double start, Save original socket on proxy monkey patching to get ability to connect localhost directly, Handle atomic write errors, Broken gevent https workaround helper, Rsa crypt functions, Plugin to Bootstrap using ZeroNet protocol
2016-01-05 00:20:52 +01:00
|
|
|
|
2019-01-20 16:22:32 +01:00
|
|
|
back = {
|
|
|
|
"peers": packed_peers["ipv4"],
|
|
|
|
"peers_ipv6": packed_peers["ipv6"],
|
|
|
|
"peers_onion": packed_peers["onion"]
|
|
|
|
}
|
Version 0.3.5, Rev830, Full Tor mode support with hidden services, Onion stats in Sidebar, GeoDB download fix using Tor, Gray out disabled sites in Stats page, Tor hidden service status in stat page, Benchmark sha256, Skyts tracker out expodie in, 2 new tracker using ZeroNet protocol, Keep SSL cert option between restarts, SSL Certificate pinning support for connections, Site lock support for connections, Certificate pinned connections using implicit SSL, Flood protection whitelist support, Foreign keys support for DB layer, Not support for SQL query helper, 0 length file get bugfix, Pex onion address support, Faster port testing, Faster uPnP port opening, Need connections more often on owned sites, Delay ZeroHello startup message if port check or Tor manager not ready yet, Use lockfiles to avoid double start, Save original socket on proxy monkey patching to get ability to connect localhost directly, Handle atomic write errors, Broken gevent https workaround helper, Rsa crypt functions, Plugin to Bootstrap using ZeroNet protocol
2016-01-05 00:20:52 +01:00
|
|
|
|
|
|
|
self.response(back)
|
2015-06-17 23:44:20 +02:00
|
|
|
|
|
|
|
# Get modified content.json files since
|
|
|
|
def actionListModified(self, params):
|
|
|
|
site = self.sites.get(params["site"])
|
2019-04-15 15:06:25 +02:00
|
|
|
if not site or not site.isServing(): # Site unknown or not serving
|
2015-06-17 23:44:20 +02:00
|
|
|
self.response({"error": "Unknown site"})
|
2018-11-26 00:12:54 +01:00
|
|
|
self.connection.badAction(5)
|
2015-06-17 23:44:20 +02:00
|
|
|
return False
|
2016-09-04 17:50:29 +02:00
|
|
|
modified_files = site.content_manager.listModified(params["since"])
|
2015-06-17 23:44:20 +02:00
|
|
|
|
|
|
|
# Add peer to site if not added before
|
2018-02-08 17:57:26 +01:00
|
|
|
connected_peer = site.addPeer(self.connection.ip, self.connection.port, source="request")
|
2015-06-17 23:44:20 +02:00
|
|
|
if connected_peer: # Just added
|
|
|
|
connected_peer.connect(self.connection) # Assign current connection to peer
|
|
|
|
|
|
|
|
self.response({"modified_files": modified_files})
|
|
|
|
|
2015-10-11 02:22:53 +02:00
|
|
|
def actionGetHashfield(self, params):
|
|
|
|
site = self.sites.get(params["site"])
|
2019-04-15 15:06:25 +02:00
|
|
|
if not site or not site.isServing(): # Site unknown or not serving
|
2015-10-11 02:22:53 +02:00
|
|
|
self.response({"error": "Unknown site"})
|
2018-11-26 00:12:54 +01:00
|
|
|
self.connection.badAction(5)
|
2015-10-11 02:22:53 +02:00
|
|
|
return False
|
|
|
|
|
|
|
|
# Add peer to site if not added before
|
2018-02-08 17:57:26 +01:00
|
|
|
peer = site.addPeer(self.connection.ip, self.connection.port, return_peer=True, source="request")
|
Rev536, Fix stats page, Support ranged http requests for better video browser compatibility, setHashfield command, One by one send hashfield to connected peers if changed, Keep count hashfield changetime, PeerHashfield optimalizations, Wait for peers on checkmodification, Give more time to query trackers, Do not count udp trackers as error if udp disabled, Test hashfield push
2015-10-30 02:08:02 +01:00
|
|
|
if not peer.connection: # Just added
|
|
|
|
peer.connect(self.connection) # Assign current connection to peer
|
|
|
|
|
|
|
|
peer.time_my_hashfield_sent = time.time() # Don't send again if not changed
|
2015-10-11 02:22:53 +02:00
|
|
|
|
2019-03-16 02:43:07 +01:00
|
|
|
self.response({"hashfield_raw": site.content_manager.hashfield.tobytes()})
|
2015-10-11 02:22:53 +02:00
|
|
|
|
2016-11-07 23:20:08 +01:00
|
|
|
def findHashIds(self, site, hash_ids, limit=100):
|
2019-01-20 16:23:37 +01:00
|
|
|
back = collections.defaultdict(lambda: collections.defaultdict(list))
|
2016-11-07 23:20:08 +01:00
|
|
|
found = site.worker_manager.findOptionalHashIds(hash_ids, limit=limit)
|
|
|
|
|
2019-03-15 21:06:59 +01:00
|
|
|
for hash_id, peers in found.items():
|
2019-01-20 16:23:37 +01:00
|
|
|
for peer in peers:
|
|
|
|
ip_type = helper.getIpType(peer.ip)
|
|
|
|
if len(back[ip_type][hash_id]) < 20:
|
|
|
|
back[ip_type][hash_id].append(peer.packMyAddress())
|
|
|
|
return back
|
2016-11-07 23:20:08 +01:00
|
|
|
|
2015-10-22 11:42:55 +02:00
|
|
|
def actionFindHashIds(self, params):
|
|
|
|
site = self.sites.get(params["site"])
|
2016-11-07 23:20:08 +01:00
|
|
|
s = time.time()
|
2019-04-15 15:06:25 +02:00
|
|
|
if not site or not site.isServing(): # Site unknown or not serving
|
2015-10-22 11:42:55 +02:00
|
|
|
self.response({"error": "Unknown site"})
|
2016-03-12 23:09:26 +01:00
|
|
|
self.connection.badAction(5)
|
2015-10-22 11:42:55 +02:00
|
|
|
return False
|
|
|
|
|
2016-11-07 23:20:08 +01:00
|
|
|
event_key = "%s_findHashIds_%s_%s" % (self.connection.ip, params["site"], len(params["hash_ids"]))
|
|
|
|
if self.connection.cpu_time > 0.5 or not RateLimit.isAllowed(event_key, 60 * 5):
|
|
|
|
time.sleep(0.1)
|
2019-01-20 16:23:37 +01:00
|
|
|
back = self.findHashIds(site, params["hash_ids"], limit=10)
|
2016-11-07 23:20:08 +01:00
|
|
|
else:
|
2019-01-20 16:23:37 +01:00
|
|
|
back = self.findHashIds(site, params["hash_ids"])
|
2016-11-07 23:20:08 +01:00
|
|
|
RateLimit.called(event_key)
|
Rev957, Sidebar displays onion peers in graph, Sidebar display bad file retry number, Sidebar site Update/Pause/Delete, Ratelimit sidebar update, Encoded typo, Fix onion findHashId, More retry for bad files, Log file path errors, Testcase for self findhashIds, Testcase for Tor findHashId, Better Tor version parse, UiWebsocket callback on update/pause/resume/delete, Skip invalid postMessage messages
2016-03-09 00:48:57 +01:00
|
|
|
|
2019-01-24 15:18:12 +01:00
|
|
|
my_hashes = []
|
2016-11-07 23:20:08 +01:00
|
|
|
my_hashfield_set = set(site.content_manager.hashfield)
|
2015-10-28 01:28:29 +01:00
|
|
|
for hash_id in params["hash_ids"]:
|
2016-11-07 23:20:08 +01:00
|
|
|
if hash_id in my_hashfield_set:
|
2019-01-24 15:18:12 +01:00
|
|
|
my_hashes.append(hash_id)
|
Rev957, Sidebar displays onion peers in graph, Sidebar display bad file retry number, Sidebar site Update/Pause/Delete, Ratelimit sidebar update, Encoded typo, Fix onion findHashId, More retry for bad files, Log file path errors, Testcase for self findhashIds, Testcase for Tor findHashId, Better Tor version parse, UiWebsocket callback on update/pause/resume/delete, Skip invalid postMessage messages
2016-03-09 00:48:57 +01:00
|
|
|
|
2016-03-06 00:55:50 +01:00
|
|
|
if config.verbose:
|
|
|
|
self.log.debug(
|
2019-01-20 16:23:37 +01:00
|
|
|
"Found: %s for %s hashids in %.3fs" %
|
2019-03-15 21:06:59 +01:00
|
|
|
({key: len(val) for key, val in back.items()}, len(params["hash_ids"]), time.time() - s)
|
2016-03-06 00:55:50 +01:00
|
|
|
)
|
2019-01-24 15:18:12 +01:00
|
|
|
self.response({"peers": back["ipv4"], "peers_onion": back["onion"], "peers_ipv6": back["ipv6"], "my": my_hashes})
|
2015-10-11 02:22:53 +02:00
|
|
|
|
Rev536, Fix stats page, Support ranged http requests for better video browser compatibility, setHashfield command, One by one send hashfield to connected peers if changed, Keep count hashfield changetime, PeerHashfield optimalizations, Wait for peers on checkmodification, Give more time to query trackers, Do not count udp trackers as error if udp disabled, Test hashfield push
2015-10-30 02:08:02 +01:00
|
|
|
def actionSetHashfield(self, params):
|
|
|
|
site = self.sites.get(params["site"])
|
2019-04-15 15:06:25 +02:00
|
|
|
if not site or not site.isServing(): # Site unknown or not serving
|
Rev536, Fix stats page, Support ranged http requests for better video browser compatibility, setHashfield command, One by one send hashfield to connected peers if changed, Keep count hashfield changetime, PeerHashfield optimalizations, Wait for peers on checkmodification, Give more time to query trackers, Do not count udp trackers as error if udp disabled, Test hashfield push
2015-10-30 02:08:02 +01:00
|
|
|
self.response({"error": "Unknown site"})
|
2016-03-12 23:09:26 +01:00
|
|
|
self.connection.badAction(5)
|
Rev536, Fix stats page, Support ranged http requests for better video browser compatibility, setHashfield command, One by one send hashfield to connected peers if changed, Keep count hashfield changetime, PeerHashfield optimalizations, Wait for peers on checkmodification, Give more time to query trackers, Do not count udp trackers as error if udp disabled, Test hashfield push
2015-10-30 02:08:02 +01:00
|
|
|
return False
|
|
|
|
|
2016-04-20 23:38:22 +02:00
|
|
|
# Add or get peer
|
2018-02-08 17:57:26 +01:00
|
|
|
peer = site.addPeer(self.connection.ip, self.connection.port, return_peer=True, connection=self.connection, source="request")
|
Rev536, Fix stats page, Support ranged http requests for better video browser compatibility, setHashfield command, One by one send hashfield to connected peers if changed, Keep count hashfield changetime, PeerHashfield optimalizations, Wait for peers on checkmodification, Give more time to query trackers, Do not count udp trackers as error if udp disabled, Test hashfield push
2015-10-30 02:08:02 +01:00
|
|
|
if not peer.connection:
|
|
|
|
peer.connect(self.connection)
|
2019-03-16 02:43:07 +01:00
|
|
|
peer.hashfield.replaceFromBytes(params["hashfield_raw"])
|
Rev536, Fix stats page, Support ranged http requests for better video browser compatibility, setHashfield command, One by one send hashfield to connected peers if changed, Keep count hashfield changetime, PeerHashfield optimalizations, Wait for peers on checkmodification, Give more time to query trackers, Do not count udp trackers as error if udp disabled, Test hashfield push
2015-10-30 02:08:02 +01:00
|
|
|
self.response({"ok": "Updated"})
|
|
|
|
|
2015-06-17 23:44:20 +02:00
|
|
|
# Send a simple Pong! answer
|
Version 0.3.5, Rev830, Full Tor mode support with hidden services, Onion stats in Sidebar, GeoDB download fix using Tor, Gray out disabled sites in Stats page, Tor hidden service status in stat page, Benchmark sha256, Skyts tracker out expodie in, 2 new tracker using ZeroNet protocol, Keep SSL cert option between restarts, SSL Certificate pinning support for connections, Site lock support for connections, Certificate pinned connections using implicit SSL, Flood protection whitelist support, Foreign keys support for DB layer, Not support for SQL query helper, 0 length file get bugfix, Pex onion address support, Faster port testing, Faster uPnP port opening, Need connections more often on owned sites, Delay ZeroHello startup message if port check or Tor manager not ready yet, Use lockfiles to avoid double start, Save original socket on proxy monkey patching to get ability to connect localhost directly, Handle atomic write errors, Broken gevent https workaround helper, Rsa crypt functions, Plugin to Bootstrap using ZeroNet protocol
2016-01-05 00:20:52 +01:00
|
|
|
def actionPing(self, params):
|
2019-03-15 21:06:59 +01:00
|
|
|
self.response(b"Pong!")
|
2015-06-17 23:44:20 +02:00
|
|
|
|
2017-08-15 19:17:42 +02:00
|
|
|
# Check requested port of the other peer
|
|
|
|
def actionCheckport(self, params):
|
2019-01-20 16:23:53 +01:00
|
|
|
if helper.getIpType(self.connection.ip) == "ipv6":
|
|
|
|
sock_address = (self.connection.ip, params["port"], 0, 0)
|
|
|
|
else:
|
|
|
|
sock_address = (self.connection.ip, params["port"])
|
|
|
|
|
|
|
|
with closing(helper.createSocket(self.connection.ip)) as sock:
|
2017-08-15 19:17:42 +02:00
|
|
|
sock.settimeout(5)
|
2019-01-20 16:23:53 +01:00
|
|
|
if sock.connect_ex(sock_address) == 0:
|
2017-08-18 10:29:41 +02:00
|
|
|
self.response({"status": "open", "ip_external": self.connection.ip})
|
2017-08-15 19:17:42 +02:00
|
|
|
else:
|
2017-08-18 10:29:41 +02:00
|
|
|
self.response({"status": "closed", "ip_external": self.connection.ip})
|
2017-08-15 19:17:42 +02:00
|
|
|
|
2015-06-17 23:44:20 +02:00
|
|
|
# Unknown command
|
|
|
|
def actionUnknown(self, cmd, params):
|
|
|
|
self.response({"error": "Unknown command: %s" % cmd})
|
2016-03-12 23:09:26 +01:00
|
|
|
self.connection.badAction(5)
|