ct 0.10.8.114__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- cantools/__init__.py +24 -0
- cantools/_db.py +142 -0
- cantools/_memcache.py +76 -0
- cantools/_pay.py +46 -0
- cantools/admin.py +31 -0
- cantools/cfg.py +347 -0
- cantools/config.py +131 -0
- cantools/db/__init__.py +18 -0
- cantools/db/admin.py +27 -0
- cantools/db/gae/__init__.py +0 -0
- cantools/db/gae/model.py +127 -0
- cantools/db/gae/properties.py +35 -0
- cantools/db/wp.py +99 -0
- cantools/geo.py +188 -0
- cantools/hooks.py +13 -0
- cantools/scripts/__init__.py +0 -0
- cantools/scripts/bench.py +167 -0
- cantools/scripts/builder.py +272 -0
- cantools/scripts/deploy.py +154 -0
- cantools/scripts/doc.py +239 -0
- cantools/scripts/index.py +226 -0
- cantools/scripts/init.py +345 -0
- cantools/scripts/migrate.py +593 -0
- cantools/scripts/pubsub/__init__.py +28 -0
- cantools/scripts/pubsub/actor.py +13 -0
- cantools/scripts/pubsub/bots.py +143 -0
- cantools/scripts/pubsub/channel.py +85 -0
- cantools/scripts/pubsub/ps.py +145 -0
- cantools/scripts/pubsub/user.py +51 -0
- cantools/scripts/start.py +53 -0
- cantools/scripts/util.py +24 -0
- cantools/util/__init__.py +78 -0
- cantools/util/admin.py +620 -0
- cantools/util/data.py +109 -0
- cantools/util/media.py +303 -0
- cantools/util/package.py +125 -0
- cantools/util/system.py +73 -0
- cantools/web/__init__.py +9 -0
- cantools/web/dez_server/__init__.py +1 -0
- cantools/web/dez_server/controller.py +129 -0
- cantools/web/dez_server/cron.py +115 -0
- cantools/web/dez_server/daemons.py +64 -0
- cantools/web/dez_server/mail.py +24 -0
- cantools/web/dez_server/response.py +63 -0
- cantools/web/dez_server/routes.py +21 -0
- cantools/web/dez_server/server.py +229 -0
- cantools/web/dez_server/sms.py +12 -0
- cantools/web/gae_server.py +68 -0
- cantools/web/util.py +552 -0
- ct-0.10.8.114.dist-info/LICENSE +9 -0
- ct-0.10.8.114.dist-info/METADATA +25 -0
- ct-0.10.8.114.dist-info/RECORD +55 -0
- ct-0.10.8.114.dist-info/WHEEL +5 -0
- ct-0.10.8.114.dist-info/entry_points.txt +10 -0
- ct-0.10.8.114.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import os, sys, platform
|
|
2
|
+
from dez.network import SocketController, daemon_wrapper
|
|
3
|
+
from dez.http.server.shield import Shield
|
|
4
|
+
from dez.logging import get_logger_getter
|
|
5
|
+
from cantools import config
|
|
6
|
+
from .daemons import Web, Admin
|
|
7
|
+
from .response import Response
|
|
8
|
+
from .cron import Cron
|
|
9
|
+
from cantools import config, __version__
|
|
10
|
+
from ..util import *
|
|
11
|
+
from ...util import read, write, log as syslog
|
|
12
|
+
|
|
13
|
+
logger_getter = get_logger_getter("httpd", syslog, config.log.allow)
|
|
14
|
+
CTR = None
|
|
15
|
+
|
|
16
|
+
class DController(SocketController):
|
|
17
|
+
def __init__(self, *args, **kwargs):
|
|
18
|
+
self.logger = logger_getter("Controller")
|
|
19
|
+
SocketController.__init__(self, *args, **kwargs)
|
|
20
|
+
self.handlers = {}
|
|
21
|
+
self.modules = {}
|
|
22
|
+
self.blcount = 0
|
|
23
|
+
self.blchunk = getattr(config.web.shield, "chunk", 5)
|
|
24
|
+
self.logger.info("cantools: %s"%(__version__,))
|
|
25
|
+
self.logger.info("Python: %s"%(sys.version.split(' ')[0],))
|
|
26
|
+
self.logger.info("System: " + " > ".join([part for part in platform.uname() if part]))
|
|
27
|
+
|
|
28
|
+
def _respond(self, resp, *args, **kwargs):
|
|
29
|
+
if resp == "nothread": # "on start nothread" cron
|
|
30
|
+
kwargs["noLoad"] = True
|
|
31
|
+
elif resp: # regular request
|
|
32
|
+
kwargs["response"] = resp
|
|
33
|
+
localvars.response = resp
|
|
34
|
+
else: # regular cron
|
|
35
|
+
kwargs["noLoad"] = True
|
|
36
|
+
kwargs["threaded"] = True
|
|
37
|
+
do_respond(*args, **kwargs)
|
|
38
|
+
|
|
39
|
+
def register_handler(self, args, kwargs):
|
|
40
|
+
self.logger.info("register handler: %s"%(self.curpath,))
|
|
41
|
+
self.handlers[self.curpath] = lambda resp : self._respond(resp, *args, **kwargs)
|
|
42
|
+
|
|
43
|
+
def trigger_handler(self, rule, target, req=None, option=None):
|
|
44
|
+
self.curpath = rule
|
|
45
|
+
if rule not in self.handlers:
|
|
46
|
+
if target in self.modules:
|
|
47
|
+
self.logger.info("linking module: %s"%(target,))
|
|
48
|
+
self.handlers[rule] = self.modules[target]
|
|
49
|
+
else:
|
|
50
|
+
self.logger.info("importing module: %s"%(target,))
|
|
51
|
+
__import__(target)
|
|
52
|
+
self.modules[target] = self.handlers[rule]
|
|
53
|
+
self.handlers[rule](req and Response(req) or option)
|
|
54
|
+
|
|
55
|
+
def blup(self):
|
|
56
|
+
wcfg = config.web
|
|
57
|
+
bl = wcfg.blacklist.obj()
|
|
58
|
+
blen = len(bl.keys())
|
|
59
|
+
self.logger.warn("saving %s IPs in black.list"%(blen,))
|
|
60
|
+
write(bl, "black.list", isjson=True)
|
|
61
|
+
if wcfg.report and self.blcount != blen:
|
|
62
|
+
self.blcount = blen
|
|
63
|
+
if not blen % self.blchunk:
|
|
64
|
+
from cantools.web import email_admins
|
|
65
|
+
email_admins("sketch IPs blacklisted", "sketch count: %s"%(blen,))
|
|
66
|
+
wcfg.blacklister and wcfg.blacklister.update(bl)
|
|
67
|
+
|
|
68
|
+
class PaperShield(object):
|
|
69
|
+
def __init__(self):
|
|
70
|
+
self.logger = logger_getter("PaperShield")
|
|
71
|
+
self.default = { "reason": "I'm just a paper shield!" }
|
|
72
|
+
self.ips = {}
|
|
73
|
+
|
|
74
|
+
def __call__(self, path, ip, fspath=False, count=True):
|
|
75
|
+
self.logger.access('NOOP > paperShield("%s", "%s", fspath=%s, count=%s)'%(path, ip, fspath, count))
|
|
76
|
+
|
|
77
|
+
def suss(self, ip, reason):
|
|
78
|
+
self.logger.access("suss(%s) -> %s"%(ip, reason))
|
|
79
|
+
self.ips[ip] = { "reason": reason }
|
|
80
|
+
|
|
81
|
+
def ip(self, ip):
|
|
82
|
+
return self.ips.get(ip, self.default)
|
|
83
|
+
|
|
84
|
+
def setBlacklist():
|
|
85
|
+
bl = {}
|
|
86
|
+
for preban in config.web.blacklist:
|
|
87
|
+
bl[preban] = "config ban"
|
|
88
|
+
if os.path.isfile("black.list"):
|
|
89
|
+
try:
|
|
90
|
+
bsaved = read("black.list", isjson=True)
|
|
91
|
+
except: # old style
|
|
92
|
+
bsaved = {}
|
|
93
|
+
for line in read("black.list", lines=True):
|
|
94
|
+
bsaved[line.strip()] = "legacy ban"
|
|
95
|
+
bsaved and bl.update(bsaved)
|
|
96
|
+
config.web.update("blacklist", bl)
|
|
97
|
+
|
|
98
|
+
def getController():
|
|
99
|
+
global CTR
|
|
100
|
+
if not CTR:
|
|
101
|
+
# controller
|
|
102
|
+
CTR = DController()
|
|
103
|
+
|
|
104
|
+
shield = None
|
|
105
|
+
shfg = config.web.shield
|
|
106
|
+
if shfg: # web/admin share shield and blacklist
|
|
107
|
+
setBlacklist()
|
|
108
|
+
shield = Shield(config.web.blacklist, logger_getter, CTR.blup,
|
|
109
|
+
getattr(shfg, "limit", 400),
|
|
110
|
+
getattr(shfg, "interval", 2))
|
|
111
|
+
config.web.update("shield", shield or PaperShield())
|
|
112
|
+
mempad = config.mempad
|
|
113
|
+
|
|
114
|
+
# web
|
|
115
|
+
CTR.web = CTR.register_address(config.web.host,
|
|
116
|
+
config.web.port, dclass=daemon_wrapper(Web, logger_getter, shield, mempad))
|
|
117
|
+
CTR.web.controller = CTR
|
|
118
|
+
|
|
119
|
+
# admin
|
|
120
|
+
config.admin.update("pw",
|
|
121
|
+
config.cache("admin password? ", overwrite=config.newpass))
|
|
122
|
+
CTR.admin = CTR.register_address(config.admin.host,
|
|
123
|
+
config.admin.port, dclass=daemon_wrapper(Admin, logger_getter, shield, mempad))
|
|
124
|
+
CTR.admin.controller = CTR
|
|
125
|
+
|
|
126
|
+
# cron
|
|
127
|
+
Cron(CTR, logger_getter)
|
|
128
|
+
|
|
129
|
+
return CTR
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import time, os
|
|
2
|
+
from datetime import datetime, timedelta
|
|
3
|
+
from rel import timeout
|
|
4
|
+
from cantools import config
|
|
5
|
+
from cantools.util import read, write
|
|
6
|
+
from ..util import do_respond
|
|
7
|
+
|
|
8
|
+
secsPerUnit = {
|
|
9
|
+
"days": 60 * 60 * 24,
|
|
10
|
+
"hours": 60 * 60,
|
|
11
|
+
"minutes": 60,
|
|
12
|
+
"mins": 60,
|
|
13
|
+
"seconds": 1
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
class Rule(object):
|
|
17
|
+
def __init__(self, controller, scheduler, url, rule, logger_getter):
|
|
18
|
+
self.logger = logger_getter("Rule(%s -> %s)"%(rule, url))
|
|
19
|
+
self.controller = controller
|
|
20
|
+
self.scheduler = scheduler
|
|
21
|
+
self.url = url
|
|
22
|
+
self.rule = rule
|
|
23
|
+
self.exact = len(rule) == 5 # 17:00
|
|
24
|
+
self.words = rule.split(" ")
|
|
25
|
+
self.timer = timeout(None, self.trigger)
|
|
26
|
+
self.parse()
|
|
27
|
+
|
|
28
|
+
def trigger(self):
|
|
29
|
+
self.logger.info("trigger: %s (%s)"%(self.url, getattr(self, "seconds", self.rule)))
|
|
30
|
+
self.controller.trigger_handler(self.url, self.url[1:],
|
|
31
|
+
option=self.rule.endswith("nothread") and "nothread")
|
|
32
|
+
if not self.exact and not self.rule.startswith("on start"):
|
|
33
|
+
self.scheduler.update(self)
|
|
34
|
+
return True
|
|
35
|
+
|
|
36
|
+
def start(self):
|
|
37
|
+
if self.rule.startswith("on start"):
|
|
38
|
+
return
|
|
39
|
+
self.logger.info("start (%s seconds)"%(self.seconds,))
|
|
40
|
+
self.timer.add(self.seconds)
|
|
41
|
+
if self.exact:
|
|
42
|
+
self.timer.delay = 60 * 60 * 24
|
|
43
|
+
else:
|
|
44
|
+
ff = self.scheduler.ff(self)
|
|
45
|
+
if ff:
|
|
46
|
+
self.logger.info("rescheduled for %s seconds"%(self.timer.delay - ff,))
|
|
47
|
+
self.timer.expiration -= ff
|
|
48
|
+
|
|
49
|
+
def parse(self):
|
|
50
|
+
self.logger.info("parse")
|
|
51
|
+
if self.exact:
|
|
52
|
+
hours, mins = self.rule.split(":")
|
|
53
|
+
n = datetime.now()
|
|
54
|
+
t = datetime(n.year, n.month, n.day, int(hours), int(mins))
|
|
55
|
+
self.seconds = (t - n).seconds
|
|
56
|
+
elif self.rule.startswith("on start"):
|
|
57
|
+
self.logger.info("triggering start script")
|
|
58
|
+
self.trigger()
|
|
59
|
+
elif len(self.words) == 3 and self.words[0] == "every": # every [NUMBER] [UNIT]
|
|
60
|
+
num = int(self.words[1])
|
|
61
|
+
unit = self.words[2]
|
|
62
|
+
self.seconds = num * secsPerUnit[unit]
|
|
63
|
+
else: # we can implement more later...
|
|
64
|
+
self.logger.error("can't parse: %s"%(self.rule,))
|
|
65
|
+
|
|
66
|
+
class Scheduler(object):
|
|
67
|
+
def __init__(self, logger_getter):
|
|
68
|
+
self.logger_getter = logger_getter
|
|
69
|
+
self.logger = logger_getter("Scheduler")
|
|
70
|
+
self.tsfile = os.path.join(os.path.abspath("."), "cron.ts")
|
|
71
|
+
self.lasts = read(self.tsfile, isjson=True, default={})
|
|
72
|
+
self.logger.info("initialized with %s timestamps"%(len(self.lasts.keys()),))
|
|
73
|
+
self.logger.info("saving state to timestamp file at %s"%(self.tsfile,))
|
|
74
|
+
|
|
75
|
+
def update(self, rule):
|
|
76
|
+
self.lasts[rule.url] = time.time()
|
|
77
|
+
self.logger.info("updating %s timestamp to %s"%(rule.url, self.lasts[rule.url]))
|
|
78
|
+
write(self.lasts, self.tsfile, isjson=True)
|
|
79
|
+
|
|
80
|
+
def ff(self, rule):
|
|
81
|
+
if rule.url in self.lasts:
|
|
82
|
+
ff = min(rule.seconds, time.time() - self.lasts[rule.url])
|
|
83
|
+
self.logger.info("fast-forwarding %s %s seconds"%(rule.url, ff))
|
|
84
|
+
return ff
|
|
85
|
+
elif config.cron.catchup:
|
|
86
|
+
self.logger.info("catching up %s"%(rule.url,))
|
|
87
|
+
return rule.seconds
|
|
88
|
+
|
|
89
|
+
class Cron(object):
|
|
90
|
+
def __init__(self, controller, logger_getter):
|
|
91
|
+
self.logger_getter = logger_getter
|
|
92
|
+
self.logger = logger_getter("Cron")
|
|
93
|
+
self.scheduler = Scheduler(logger_getter)
|
|
94
|
+
self.controller = controller
|
|
95
|
+
self.timers = {}
|
|
96
|
+
self.parse()
|
|
97
|
+
self.start()
|
|
98
|
+
|
|
99
|
+
def parse(self):
|
|
100
|
+
self.logger.info("parse")
|
|
101
|
+
url = None
|
|
102
|
+
for line in read("cron.yaml", True):
|
|
103
|
+
if line.startswith("- description: "):
|
|
104
|
+
self.logger.info("initializing %s"%(line[15:],))
|
|
105
|
+
elif line.startswith(" url: "):
|
|
106
|
+
url = line[7:].strip()
|
|
107
|
+
elif url:
|
|
108
|
+
self.timers[url] = Rule(self.controller, self.scheduler,
|
|
109
|
+
url, line[12:].strip(), self.logger_getter)
|
|
110
|
+
url = None
|
|
111
|
+
|
|
112
|
+
def start(self):
|
|
113
|
+
self.logger.info("start")
|
|
114
|
+
for rule in list(self.timers.values()):
|
|
115
|
+
rule.start()
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import sys, json, gc, os, inspect, threading
|
|
2
|
+
try:
|
|
3
|
+
import psutil
|
|
4
|
+
except ImportError as e:
|
|
5
|
+
pass # google crap engine (get it if you need it!)
|
|
6
|
+
from dez.memcache import get_memcache
|
|
7
|
+
from dez.http.application import HTTPApplication
|
|
8
|
+
from .routes import static, cb
|
|
9
|
+
from cantools import config
|
|
10
|
+
sys.path.insert(0, ".") # for dynamically loading modules
|
|
11
|
+
|
|
12
|
+
A_STATIC = {
|
|
13
|
+
"dynamic": { "/": "_/dynamic", "/css/": "_/css", "/js/CT/": "js/CT", "/logs/": "logs", "/logs": "logs" },
|
|
14
|
+
"static": { "/": "_/static", "/css/": "_/css", "/js/CT/": "js/CT", "/logs/": "logs", "/logs": "logs" },
|
|
15
|
+
"production": { "/": "_/production", "/css/": "_/css", "/logs/": "logs", "/logs": "logs" },
|
|
16
|
+
}
|
|
17
|
+
A_CB = { "/admin": "admin", "/_db": "_db" }
|
|
18
|
+
|
|
19
|
+
class CTWebBase(HTTPApplication):
|
|
20
|
+
def __init__(self, bind_address, port, logger_getter, static=static, cb=cb, whitelist=[], blacklist=[], shield=False, mempad=0):
|
|
21
|
+
isprod = config.mode == "production"
|
|
22
|
+
HTTPApplication.__init__(self, bind_address, port, logger_getter, "dez/cantools",
|
|
23
|
+
config.ssl.certfile, config.ssl.keyfile, config.ssl.cacerts,
|
|
24
|
+
isprod, config.web.rollz, isprod, whitelist, blacklist, shield, mempad, config.web.xorigin)
|
|
25
|
+
self.memcache = get_memcache()
|
|
26
|
+
self.handlers = {}
|
|
27
|
+
for key, val in list(static.items()):
|
|
28
|
+
self.add_static_rule(key, val)
|
|
29
|
+
for key, val in list(cb.items()):
|
|
30
|
+
self.add_cb_rule(key, self._handler(key, val))
|
|
31
|
+
|
|
32
|
+
def _handler(self, rule, target):
|
|
33
|
+
self.logger.info("setting handler: %s %s"%(rule, target))
|
|
34
|
+
def h(req):
|
|
35
|
+
self.logger.info("triggering handler: %s %s"%(rule, target))
|
|
36
|
+
self.controller.trigger_handler(rule, target, req)
|
|
37
|
+
return h
|
|
38
|
+
|
|
39
|
+
class Web(CTWebBase):
|
|
40
|
+
def __init__(self, bind_address, port, logger_getter, shield, mempad):
|
|
41
|
+
self.logger = logger_getter("Web")
|
|
42
|
+
wcfg = config.web
|
|
43
|
+
CTWebBase.__init__(self, bind_address, port, logger_getter,
|
|
44
|
+
whitelist=wcfg.whitelist, blacklist=wcfg.blacklist, shield=shield, mempad=mempad)
|
|
45
|
+
|
|
46
|
+
class Admin(CTWebBase):
|
|
47
|
+
def __init__(self, bind_address, port, logger_getter, shield, mempad):
|
|
48
|
+
self.logger = logger_getter("Admin")
|
|
49
|
+
acfg = config.admin
|
|
50
|
+
CTWebBase.__init__(self, bind_address, port, logger_getter, # share shield/blacklist
|
|
51
|
+
A_STATIC[config.mode], A_CB, acfg.whitelist, config.web.blacklist, shield, mempad)
|
|
52
|
+
self.add_cb_rule("/_report", self.report)
|
|
53
|
+
|
|
54
|
+
def report(self, req):
|
|
55
|
+
report = json.dumps({
|
|
56
|
+
"threads": threading.active_count(),
|
|
57
|
+
"stack_frames": len(inspect.stack()),
|
|
58
|
+
"web": self.controller.web.daemon.counter.report(),
|
|
59
|
+
"admin": self.daemon.counter.report(),
|
|
60
|
+
"gc": len(gc.get_objects()),
|
|
61
|
+
"mem": psutil.Process(os.getpid()).memory_percent()
|
|
62
|
+
})
|
|
63
|
+
self.logger.info(report)
|
|
64
|
+
self.daemon.respond(req, report)
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
from catmail import Mailer, Reader, Scanner
|
|
2
|
+
from catmail import config as catfyg
|
|
3
|
+
from ..util import config
|
|
4
|
+
|
|
5
|
+
catfyg.set({
|
|
6
|
+
"html": config.mailhtml,
|
|
7
|
+
"gmailer": config.gmailer,
|
|
8
|
+
"scantick": config.mailscantick,
|
|
9
|
+
"verbose": config.mailoud,
|
|
10
|
+
"cache": config.cache
|
|
11
|
+
})
|
|
12
|
+
catfyg.admin.update("contacts", config.admin.contacts)
|
|
13
|
+
catfyg.admin.update("reportees", config.admin.reportees)
|
|
14
|
+
|
|
15
|
+
mailer = Mailer(config.mailer, config.mailername)
|
|
16
|
+
send_mail = mailer.mail
|
|
17
|
+
email_admins = mailer.admins
|
|
18
|
+
email_reportees = mailer.reportees
|
|
19
|
+
|
|
20
|
+
reader = Reader(config.mailer)
|
|
21
|
+
check_inbox = reader.inbox
|
|
22
|
+
|
|
23
|
+
scanner = Scanner(reader)
|
|
24
|
+
on_mail = scanner.on
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import json
|
|
2
|
+
from rel import abort_branch, timeout
|
|
3
|
+
from dez.http.server import HTTPResponse
|
|
4
|
+
from cantools import config
|
|
5
|
+
from ..util import *
|
|
6
|
+
|
|
7
|
+
files = {}
|
|
8
|
+
|
|
9
|
+
class Response(object):
|
|
10
|
+
def __init__(self, request):
|
|
11
|
+
self.id = request.id
|
|
12
|
+
self.ip = request.real_ip
|
|
13
|
+
self.request = request
|
|
14
|
+
self.response = HTTPResponse(request)
|
|
15
|
+
|
|
16
|
+
def _read(self):
|
|
17
|
+
b = self.request.body or json.dumps(rec_conv(self.request.qs_params))
|
|
18
|
+
# print(b)
|
|
19
|
+
ctype = self.request.headers.get("content-type")
|
|
20
|
+
if ctype and ctype.startswith("multipart/form-data"):
|
|
21
|
+
if type(b) != bytes:
|
|
22
|
+
b = b.encode()
|
|
23
|
+
obj = {}
|
|
24
|
+
splitter, body = b.rsplit(b"\r\n", 2)[0].split(b"\r\n", 1)
|
|
25
|
+
for chunk in body.split(b"\r\n%s\r\n"%(splitter,)):
|
|
26
|
+
nameline, data = chunk.split(b"\r\n\r\n", 1)
|
|
27
|
+
nameline = nameline.decode()
|
|
28
|
+
name = nameline.split("; filename=")[0][:-1].split('name="')[1]
|
|
29
|
+
if "filename=" in nameline:
|
|
30
|
+
signature = "%s%s"%(self.id, name)
|
|
31
|
+
files[signature] = data
|
|
32
|
+
obj[name] = signature
|
|
33
|
+
else:
|
|
34
|
+
obj[name] = data
|
|
35
|
+
b = json.dumps(rec_conv(obj))
|
|
36
|
+
return b
|
|
37
|
+
|
|
38
|
+
def _send(self, *args, **kwargs):
|
|
39
|
+
wcfg = config.web
|
|
40
|
+
if wcfg.xorigin:
|
|
41
|
+
self.response.headers["Access-Control-Allow-Origin"] = "*"
|
|
42
|
+
if wcfg.csp:
|
|
43
|
+
if wcfg.csp.startswith("auto"):
|
|
44
|
+
d = wcfg.domain
|
|
45
|
+
if wcfg.csp != "autosub":
|
|
46
|
+
d = ".".join(d.split(".")[-2:])
|
|
47
|
+
wcfg.update("csp", "default-src 'self' %s *.%s"%(d, d))
|
|
48
|
+
self.response.headers["Content-Security-Policy"] = wcfg.csp
|
|
49
|
+
self.response.write(*args, **kwargs)
|
|
50
|
+
|
|
51
|
+
def _close(self):
|
|
52
|
+
timeout(.001, self.response.dispatch)
|
|
53
|
+
abort_branch()
|
|
54
|
+
|
|
55
|
+
def _header(self, *args, **kwargs):
|
|
56
|
+
self.response.__setitem__(*args, **kwargs)
|
|
57
|
+
|
|
58
|
+
def set_cbs(self):
|
|
59
|
+
set_read(self._read)
|
|
60
|
+
set_header(self._header)
|
|
61
|
+
set_send(self._send)
|
|
62
|
+
set_close(self._close)
|
|
63
|
+
set_redir(self.response.redirect)
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
from ...util import read
|
|
2
|
+
|
|
3
|
+
url = None
|
|
4
|
+
static = {}
|
|
5
|
+
cb = {}
|
|
6
|
+
|
|
7
|
+
for line in read("app.yaml", True):
|
|
8
|
+
if line.startswith("- url: "):
|
|
9
|
+
url = line[7:].strip()
|
|
10
|
+
elif url:
|
|
11
|
+
if line.startswith(" static_dir: "):
|
|
12
|
+
target = line[14:].strip()
|
|
13
|
+
if "*" in url: # bad regex detection...
|
|
14
|
+
static[url] = target
|
|
15
|
+
else:
|
|
16
|
+
url = url.rstrip("/")
|
|
17
|
+
static[url] = static[url + "/"] = target
|
|
18
|
+
url = None
|
|
19
|
+
elif line.startswith(" script: "):
|
|
20
|
+
cb[url] = line[10:].split(".")[0]
|
|
21
|
+
url = None
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
import os, rel, ssl, sys, json
|
|
2
|
+
from dez.http import fetch as dfetch, post as dpost
|
|
3
|
+
from ..util import *
|
|
4
|
+
from ...util import set_log, set_error, init_rel
|
|
5
|
+
from .mail import send_mail, email_admins, email_reportees, mailer, reader, check_inbox, scanner, on_mail
|
|
6
|
+
from .sms import send_sms
|
|
7
|
+
from .controller import getController
|
|
8
|
+
from cantools import config
|
|
9
|
+
|
|
10
|
+
def fdup():
|
|
11
|
+
import resource
|
|
12
|
+
from cantools.util import log # gives us logger set in run_dez_webserver()
|
|
13
|
+
log("checking the number of file descriptors allowed on your system", important=True)
|
|
14
|
+
soft, hard = resource.getrlimit(resource.RLIMIT_NOFILE)
|
|
15
|
+
half = int(hard / 2)
|
|
16
|
+
log("soft: %s. hard: %s. half: %s"%(soft, hard, half))
|
|
17
|
+
if soft < half:
|
|
18
|
+
log("increasing soft limit to half of hard limit")
|
|
19
|
+
resource.setrlimit(resource.RLIMIT_NOFILE, (half, hard))
|
|
20
|
+
log("new limits! soft: %s. hard: %s"%resource.getrlimit(resource.RLIMIT_NOFILE))
|
|
21
|
+
|
|
22
|
+
def log_kernel():
|
|
23
|
+
from cantools.web.util import log
|
|
24
|
+
log(json.dumps(rel.report()), "kernel")
|
|
25
|
+
return True
|
|
26
|
+
|
|
27
|
+
TMSNAP = None
|
|
28
|
+
|
|
29
|
+
def log_tracemalloc():
|
|
30
|
+
global TMSNAP
|
|
31
|
+
import tracemalloc
|
|
32
|
+
from cantools.util import log
|
|
33
|
+
snapshot = tracemalloc.take_snapshot()
|
|
34
|
+
log("[LINEMALLOC START]", important=True)
|
|
35
|
+
if TMSNAP:
|
|
36
|
+
lines = snapshot.compare_to(TMSNAP, 'lineno')
|
|
37
|
+
else:
|
|
38
|
+
lines = snapshot.statistics("lineno")
|
|
39
|
+
TMSNAP = snapshot
|
|
40
|
+
for line in lines[:10]:
|
|
41
|
+
log(line)
|
|
42
|
+
log("[LINEMALLOC END]", important=True)
|
|
43
|
+
return True
|
|
44
|
+
|
|
45
|
+
PROC = None
|
|
46
|
+
|
|
47
|
+
def log_openfiles():
|
|
48
|
+
global PROC
|
|
49
|
+
from cantools.util import log
|
|
50
|
+
if not PROC:
|
|
51
|
+
import psutil
|
|
52
|
+
PROC = psutil.Process(os.getpid())
|
|
53
|
+
ofz = PROC.open_files()
|
|
54
|
+
if config.log.oflist:
|
|
55
|
+
log("OPEN FILES: %s"%(ofz,), important=True)
|
|
56
|
+
else:
|
|
57
|
+
log("OPEN FILE COUNT: %s"%(len(ofz),), important=True)
|
|
58
|
+
return True
|
|
59
|
+
|
|
60
|
+
def quit():
|
|
61
|
+
from cantools.util import log
|
|
62
|
+
if config.web.errlog:
|
|
63
|
+
log("closing error log", important=True)
|
|
64
|
+
sys.stderr.close()
|
|
65
|
+
log("quitting - goodbye!", important=True)
|
|
66
|
+
|
|
67
|
+
def run_dez_webserver():
|
|
68
|
+
if not config.ssl.verify and hasattr(ssl, "_https_verify_certificates"):
|
|
69
|
+
ssl._https_verify_certificates(False)
|
|
70
|
+
c = getController()
|
|
71
|
+
setlog(c.web.logger.simple)
|
|
72
|
+
if config.web.log:
|
|
73
|
+
set_log(os.path.join("logs", config.web.log))
|
|
74
|
+
init_rel()
|
|
75
|
+
clog = config.log
|
|
76
|
+
if clog.openfiles:
|
|
77
|
+
rel.timeout(clog.openfiles, log_openfiles)
|
|
78
|
+
if clog.tracemalloc:
|
|
79
|
+
import tracemalloc
|
|
80
|
+
tracemalloc.start()
|
|
81
|
+
rel.timeout(clog.tracemalloc, log_tracemalloc)
|
|
82
|
+
if "kernel" in clog.allow:
|
|
83
|
+
rel.timeout(1, log_kernel)
|
|
84
|
+
set_error(fail)
|
|
85
|
+
if config.fdup:
|
|
86
|
+
fdup()
|
|
87
|
+
if config.web.errlog:
|
|
88
|
+
sys.stderr = open(os.path.join("logs", config.web.errlog), "a")
|
|
89
|
+
c.start(quit)
|
|
90
|
+
|
|
91
|
+
def dweb():
|
|
92
|
+
return getController().web
|
|
93
|
+
|
|
94
|
+
def respond(*args, **kwargs):
|
|
95
|
+
getController().register_handler(args, kwargs)
|
|
96
|
+
|
|
97
|
+
def _ctjson(result):
|
|
98
|
+
from cantools.util import log # gives us logger set in run_dez_webserver()
|
|
99
|
+
result = result.decode()
|
|
100
|
+
code = result[0]
|
|
101
|
+
if code not in "0123":
|
|
102
|
+
log("response encoded:")
|
|
103
|
+
log(result)
|
|
104
|
+
log("attempting decode")
|
|
105
|
+
result = dec(result)
|
|
106
|
+
if code in "02":
|
|
107
|
+
from cantools.util import log
|
|
108
|
+
log("request failed!! : %s"%(result,), important=True)
|
|
109
|
+
elif code == "3":
|
|
110
|
+
return rec_conv(json.loads(result[1:]), True)
|
|
111
|
+
else:
|
|
112
|
+
return json.loads(result[1:])
|
|
113
|
+
|
|
114
|
+
def parse_url_parts(host, path, port, protocol):
|
|
115
|
+
if "://" in host:
|
|
116
|
+
protocol, host = host.split("://", 1)
|
|
117
|
+
if "/" in host:
|
|
118
|
+
host, path = host.split("/", 1)
|
|
119
|
+
path = "/" + path
|
|
120
|
+
else:
|
|
121
|
+
path = "/"
|
|
122
|
+
if ":" in host:
|
|
123
|
+
host, port = host.split(":")
|
|
124
|
+
port = int(port)
|
|
125
|
+
elif not port:
|
|
126
|
+
port = protocol == "https" and 443 or 80
|
|
127
|
+
return host, path, port, protocol
|
|
128
|
+
|
|
129
|
+
def fetch(host, path="/", port=None, asjson=False, cb=None, timeout=1, asyn=False, protocol="http", ctjson=False, qsp=None, fakeua=False, retries=5):
|
|
130
|
+
from cantools.util import log # gives us logger set in run_dez_webserver()
|
|
131
|
+
host, path, port, protocol = parse_url_parts(host, path, port, protocol)
|
|
132
|
+
if qsp:
|
|
133
|
+
path += "?"
|
|
134
|
+
for k, v in list(qsp.items()):
|
|
135
|
+
path += "%s=%s&"%(k, v)
|
|
136
|
+
path = path[:-1]
|
|
137
|
+
gkwargs = {}
|
|
138
|
+
headers = {}
|
|
139
|
+
if fakeua:
|
|
140
|
+
if type(fakeua) is str:
|
|
141
|
+
headers['User-Agent'] = fakeua
|
|
142
|
+
else:
|
|
143
|
+
headers['User-Agent'] = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.75 Safari/537.36'
|
|
144
|
+
gkwargs["headers"] = headers
|
|
145
|
+
if asyn or cb: # asyn w/o cb works, will just log
|
|
146
|
+
secure = protocol == "https"
|
|
147
|
+
if ctjson:
|
|
148
|
+
orig_cb = cb or log
|
|
149
|
+
cb = lambda v : orig_cb(_ctjson(v))
|
|
150
|
+
return dfetch(host, path, port, secure, headers, cb, timeout, asjson)
|
|
151
|
+
if timeout:
|
|
152
|
+
gkwargs["timeout"] = timeout
|
|
153
|
+
furl = "%s://%s:%s%s"%(protocol, host, port, path)
|
|
154
|
+
log("fetch %s"%(furl,))
|
|
155
|
+
return syncreq(furl, "get", asjson, ctjson, retries, gkwargs)
|
|
156
|
+
|
|
157
|
+
def post(host, path="/", port=80, data=None, protocol="http", asjson=False, ctjson=False, text=None, cb=None):
|
|
158
|
+
if ctjson:
|
|
159
|
+
data = rec_conv(data)
|
|
160
|
+
if cb:
|
|
161
|
+
if ctjson:
|
|
162
|
+
orig_cb = cb
|
|
163
|
+
cb = lambda v : orig_cb(_ctjson(v))
|
|
164
|
+
host, path, port, protocol = parse_url_parts(host, path, port, protocol)
|
|
165
|
+
return dpost(host, path, port, protocol == "https", data=data, text=text, cb=cb)
|
|
166
|
+
url = "://" in host and host or "%s://%s:%s%s"%(protocol, host, port, path)
|
|
167
|
+
log("post %s"%(url,))
|
|
168
|
+
kwargs = {}
|
|
169
|
+
if data:
|
|
170
|
+
kwargs["json"] = data
|
|
171
|
+
elif text:
|
|
172
|
+
kwargs["data"] = text
|
|
173
|
+
return syncreq(url, "post", asjson, ctjson, rekwargs=kwargs)
|
|
174
|
+
|
|
175
|
+
def _dosyncreq(requester, url, asjson, ctjson, rekwargs):
|
|
176
|
+
result = requester(url, **rekwargs).content
|
|
177
|
+
if ctjson:
|
|
178
|
+
return _ctjson(result)
|
|
179
|
+
return asjson and json.loads(result) or result
|
|
180
|
+
|
|
181
|
+
def syncreq(url, method="get", asjson=False, ctjson=False, retries=5, rekwargs={}):
|
|
182
|
+
import time, requests
|
|
183
|
+
attempt = 1
|
|
184
|
+
requester = getattr(requests, method)
|
|
185
|
+
while attempt < retries:
|
|
186
|
+
try:
|
|
187
|
+
return _dosyncreq(requester, url, asjson, ctjson, rekwargs)
|
|
188
|
+
except requests.exceptions.ConnectionError:
|
|
189
|
+
log("syncreq(%s %s) attempt #%s failed"%(method, url, attempt))
|
|
190
|
+
time.sleep(1)
|
|
191
|
+
attempt += 1
|
|
192
|
+
return _dosyncreq(requester, url, asjson, ctjson, rekwargs) # final try-less try
|
|
193
|
+
|
|
194
|
+
# file uploads
|
|
195
|
+
def read_file(data_field):
|
|
196
|
+
from .response import files
|
|
197
|
+
return files.pop(data_field)
|
|
198
|
+
|
|
199
|
+
# memcache stuff
|
|
200
|
+
def getmem(key, tojson=True):
|
|
201
|
+
return dweb().memcache.get(key, tojson)
|
|
202
|
+
|
|
203
|
+
def setmem(key, val, fromjson=True):
|
|
204
|
+
dweb().memcache.set(key, val, fromjson)
|
|
205
|
+
|
|
206
|
+
def delmem(key):
|
|
207
|
+
dweb().memcache.rm(key)
|
|
208
|
+
|
|
209
|
+
def clearmem():
|
|
210
|
+
dweb().memcache.clear()
|
|
211
|
+
|
|
212
|
+
def getcache():
|
|
213
|
+
c = {}
|
|
214
|
+
orig = dweb().memcache.cache
|
|
215
|
+
for k, v in list(orig.items()):
|
|
216
|
+
if v.__class__ == set:
|
|
217
|
+
v = list(v)
|
|
218
|
+
elif v.__class__ == dict and "ttl" in v: # countdown
|
|
219
|
+
v = str(v)
|
|
220
|
+
c[k] = v
|
|
221
|
+
return c
|
|
222
|
+
|
|
223
|
+
set_getmem(getmem)
|
|
224
|
+
set_setmem(setmem)
|
|
225
|
+
set_delmem(delmem)
|
|
226
|
+
set_clearmem(clearmem)
|
|
227
|
+
|
|
228
|
+
if __name__ == "__main__":
|
|
229
|
+
run_dez_webserver()
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
from .mail import send_mail
|
|
2
|
+
from cantools import config
|
|
3
|
+
|
|
4
|
+
carriers = {
|
|
5
|
+
'at&t': 'mms.att.net',
|
|
6
|
+
'verizon': 'vtext.com',
|
|
7
|
+
'tmobile': 'tmomail.net',
|
|
8
|
+
'sprint': 'page.nextel.com'
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
def send_sms(number, subject="hello", body="goodbye", carrier="at&t"):
|
|
12
|
+
send_mail(to="%s@%s"%(number, carriers[carrier]), subject=subject, body=body)
|