babyweb 0.1.0__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.
- babyweb/__init__.py +7 -0
- babyweb/config.py +54 -0
- babyweb/controller.py +89 -0
- babyweb/cron.py +115 -0
- babyweb/daemons.py +45 -0
- babyweb/logger.py +48 -0
- babyweb/mail.py +25 -0
- babyweb/memcache.py +25 -0
- babyweb/requesters.py +98 -0
- babyweb/response.py +65 -0
- babyweb/routes.py +21 -0
- babyweb/server.py +47 -0
- babyweb/shield.py +53 -0
- babyweb/sms.py +11 -0
- babyweb/util/__init__.py +4 -0
- babyweb/util/converters.py +69 -0
- babyweb/util/loaders.py +56 -0
- babyweb/util/responders.py +199 -0
- babyweb/util/setters.py +20 -0
- babyweb/version.py +1 -0
- babyweb-0.1.0.dist-info/LICENSE +21 -0
- babyweb-0.1.0.dist-info/METADATA +19 -0
- babyweb-0.1.0.dist-info/RECORD +25 -0
- babyweb-0.1.0.dist-info/WHEEL +5 -0
- babyweb-0.1.0.dist-info/top_level.txt +1 -0
babyweb/__init__.py
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
from .server import run_dez_webserver
|
|
2
|
+
from .util import succeed, succeed_sync, fail, redirect, local, cgi_get, cgi_dump, trysavedresponse, set_pre_close, send_file, send_text, send_xml, send_image, send_pdf
|
|
3
|
+
from .memcache import getmem, setmem, delmem, clearmem, getcache
|
|
4
|
+
from .requesters import fetch, post
|
|
5
|
+
from .response import read_file
|
|
6
|
+
from .controller import respond
|
|
7
|
+
from .version import __version__
|
babyweb/config.py
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
from fyg import Config, PCache
|
|
2
|
+
|
|
3
|
+
config = Config({
|
|
4
|
+
"cache": PCache(".tw"),
|
|
5
|
+
"encode": False,
|
|
6
|
+
"memcache": False,
|
|
7
|
+
"mempad": 0, # 0 = unset (uses dez's default)
|
|
8
|
+
"webs": {},
|
|
9
|
+
"log": {
|
|
10
|
+
"oflist": False,
|
|
11
|
+
"openfiles": False,
|
|
12
|
+
"tracemalloc": False,
|
|
13
|
+
"allow": ["info", "log", "warn", "error"] # access,info,log,warn,error,detail,db,query,kernel
|
|
14
|
+
},
|
|
15
|
+
"mail": {
|
|
16
|
+
"mailer": None,
|
|
17
|
+
"name": None,
|
|
18
|
+
"html": True,
|
|
19
|
+
"gmailer": False,
|
|
20
|
+
"verbose": False,
|
|
21
|
+
"scantick": 2
|
|
22
|
+
},
|
|
23
|
+
"web": {
|
|
24
|
+
"domain": "your.web.domain",
|
|
25
|
+
"host": "0.0.0.0",
|
|
26
|
+
"port": 8080,
|
|
27
|
+
"protocol": "http",
|
|
28
|
+
"xorigin": False,
|
|
29
|
+
"report": False,
|
|
30
|
+
"shield": False,
|
|
31
|
+
"debug": False,
|
|
32
|
+
"csp": None,
|
|
33
|
+
"log": None,
|
|
34
|
+
"errlog": None,
|
|
35
|
+
"rollz": {},
|
|
36
|
+
"eflags": [],
|
|
37
|
+
"blacklist": [],
|
|
38
|
+
"whitelist": []
|
|
39
|
+
},
|
|
40
|
+
"admin": {
|
|
41
|
+
"contacts": [],
|
|
42
|
+
"reportees": []
|
|
43
|
+
},
|
|
44
|
+
"ssl": {
|
|
45
|
+
"verify": True,
|
|
46
|
+
"certfile": None,
|
|
47
|
+
"keyfile": None,
|
|
48
|
+
"cacerts": None
|
|
49
|
+
},
|
|
50
|
+
"cron": {
|
|
51
|
+
"catchup": False
|
|
52
|
+
},
|
|
53
|
+
"scrambler": "1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ+/=_"
|
|
54
|
+
})
|
babyweb/controller.py
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import os, sys, platform
|
|
2
|
+
from dez.network import SocketController, daemon_wrapper
|
|
3
|
+
from .shield import setShield, writeBlacklist
|
|
4
|
+
from .logger import logger_getter
|
|
5
|
+
from .response import Response
|
|
6
|
+
from .daemons import webs
|
|
7
|
+
from .cron import Cron
|
|
8
|
+
from .version import __version__
|
|
9
|
+
from .util import do_respond, localvars
|
|
10
|
+
from .config import config
|
|
11
|
+
|
|
12
|
+
CTR = None
|
|
13
|
+
|
|
14
|
+
class Controller(SocketController):
|
|
15
|
+
def __init__(self, *args, **kwargs):
|
|
16
|
+
self.logger = logger_getter("Controller")
|
|
17
|
+
SocketController.__init__(self, *args, **kwargs)
|
|
18
|
+
self.handlers = {}
|
|
19
|
+
self.modules = {}
|
|
20
|
+
self.webs = {}
|
|
21
|
+
self.blcount = 0
|
|
22
|
+
self.blchunk = getattr(config.web.shield, "chunk", 5)
|
|
23
|
+
self.logger.info("babyweb: %s"%(__version__,))
|
|
24
|
+
self.logger.info("Python: %s"%(sys.version.split(' ')[0],))
|
|
25
|
+
self.logger.info("System: " + " > ".join([part for part in platform.uname() if part]))
|
|
26
|
+
|
|
27
|
+
def _respond(self, resp, *args, **kwargs):
|
|
28
|
+
if resp == "nothread": # "on start nothread" cron
|
|
29
|
+
kwargs["noLoad"] = True
|
|
30
|
+
elif resp: # regular request
|
|
31
|
+
kwargs["response"] = resp
|
|
32
|
+
localvars.response = resp
|
|
33
|
+
else: # regular cron
|
|
34
|
+
kwargs["noLoad"] = True
|
|
35
|
+
kwargs["threaded"] = True
|
|
36
|
+
do_respond(*args, **kwargs)
|
|
37
|
+
|
|
38
|
+
def register_handler(self, args, kwargs):
|
|
39
|
+
self.logger.info("register handler: %s"%(self.curpath,))
|
|
40
|
+
self.handlers[self.curpath] = lambda resp : self._respond(resp, *args, **kwargs)
|
|
41
|
+
|
|
42
|
+
def trigger_handler(self, rule, target, req=None, option=None):
|
|
43
|
+
self.curpath = rule
|
|
44
|
+
if rule not in self.handlers:
|
|
45
|
+
if target in self.modules:
|
|
46
|
+
self.logger.info("linking module: %s"%(target,))
|
|
47
|
+
self.handlers[rule] = self.modules[target]
|
|
48
|
+
else:
|
|
49
|
+
self.logger.info("importing module: %s"%(target,))
|
|
50
|
+
__import__(target)
|
|
51
|
+
self.modules[target] = self.handlers[rule]
|
|
52
|
+
self.handlers[rule](req and Response(req) or option)
|
|
53
|
+
|
|
54
|
+
def blup(self):
|
|
55
|
+
blen = writeBlacklist()
|
|
56
|
+
self.logger.warn("saved %s IPs in black.list"%(blen,))
|
|
57
|
+
if self.blcount != blen:
|
|
58
|
+
self.blcount = blen
|
|
59
|
+
if config.web.report and not blen % self.blchunk:
|
|
60
|
+
from .mail import email_admins
|
|
61
|
+
email_admins("sketch IPs blacklisted", "sketch count: %s"%(blen,))
|
|
62
|
+
|
|
63
|
+
def web(self, name, cfg, daemon, shield):
|
|
64
|
+
self.webs[name] = self.register_address(cfg.host,
|
|
65
|
+
cfg.port, dclass=daemon_wrapper(daemon, logger_getter, shield, config.mempad))
|
|
66
|
+
self.webs[name].controller = self
|
|
67
|
+
|
|
68
|
+
def getController():
|
|
69
|
+
global CTR
|
|
70
|
+
if not CTR:
|
|
71
|
+
# controller
|
|
72
|
+
CTR = Controller()
|
|
73
|
+
shield = setShield(CTR.blup)
|
|
74
|
+
mempad = config.mempad
|
|
75
|
+
|
|
76
|
+
for web in webs:
|
|
77
|
+
CTR.web(web, config.webs[web], webs[web], shield)
|
|
78
|
+
|
|
79
|
+
# cron
|
|
80
|
+
Cron(CTR, logger_getter)
|
|
81
|
+
|
|
82
|
+
return CTR
|
|
83
|
+
|
|
84
|
+
def dweb(wname="web"):
|
|
85
|
+
return getController().webs.get(wname)
|
|
86
|
+
|
|
87
|
+
def respond(*args, **kwargs):
|
|
88
|
+
getController().register_handler(args, kwargs)
|
|
89
|
+
|
babyweb/cron.py
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import time, os
|
|
2
|
+
from datetime import datetime, timedelta
|
|
3
|
+
from fyg.util import read, write
|
|
4
|
+
from rel import timeout
|
|
5
|
+
from .config import config
|
|
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()
|
babyweb/daemons.py
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
from dez.memcache import get_memcache
|
|
3
|
+
from dez.http.application import HTTPApplication
|
|
4
|
+
from .logger import logger_getter
|
|
5
|
+
from .routes import static, cb
|
|
6
|
+
from .config import config
|
|
7
|
+
sys.path.insert(0, ".") # for dynamically loading modules
|
|
8
|
+
|
|
9
|
+
class WebBase(HTTPApplication):
|
|
10
|
+
def __init__(self, bind_address, port, logger_getter, static=static, cb=cb, whitelist=[], blacklist=[], shield=False, mempad=0):
|
|
11
|
+
isprod = config.mode == "production"
|
|
12
|
+
HTTPApplication.__init__(self, bind_address, port, logger_getter, "dez/cantools",
|
|
13
|
+
config.ssl.certfile, config.ssl.keyfile, config.ssl.cacerts,
|
|
14
|
+
isprod, config.web.rollz, isprod, whitelist, blacklist, shield, mempad, config.web.xorigin)
|
|
15
|
+
self.memcache = get_memcache()
|
|
16
|
+
self.handlers = {}
|
|
17
|
+
for key, val in list(static.items()):
|
|
18
|
+
self.add_static_rule(key, val)
|
|
19
|
+
for key, val in list(cb.items()):
|
|
20
|
+
self.add_cb_rule(key, self._handler(key, val))
|
|
21
|
+
|
|
22
|
+
def _handler(self, rule, target):
|
|
23
|
+
self.logger.info("setting handler: %s %s"%(rule, target))
|
|
24
|
+
def h(req):
|
|
25
|
+
self.logger.info("triggering handler: %s %s"%(rule, target))
|
|
26
|
+
self.controller.trigger_handler(rule, target, req)
|
|
27
|
+
return h
|
|
28
|
+
|
|
29
|
+
class Web(WebBase):
|
|
30
|
+
def __init__(self, bind_address, port, logger_getter, shield, mempad):
|
|
31
|
+
self.logger = logger_getter("Web")
|
|
32
|
+
wcfg = config.web
|
|
33
|
+
WebBase.__init__(self, bind_address, port, logger_getter,
|
|
34
|
+
whitelist=wcfg.whitelist, blacklist=wcfg.blacklist, shield=shield, mempad=mempad)
|
|
35
|
+
|
|
36
|
+
webs = {}
|
|
37
|
+
def addWeb(name, daemon, cfg):
|
|
38
|
+
webs[name] = daemon
|
|
39
|
+
if config.webs[name]:
|
|
40
|
+
for key, val in cfg.items():
|
|
41
|
+
config.webs[name].update(key, val)
|
|
42
|
+
else:
|
|
43
|
+
config.webs.update(name, cfg)
|
|
44
|
+
|
|
45
|
+
addWeb("web", Web, config.web)
|
babyweb/logger.py
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import tracemalloc, psutil, rel
|
|
2
|
+
from fyg.util import log as syslog
|
|
3
|
+
from dez.logging import get_logger_getter
|
|
4
|
+
from .config import config
|
|
5
|
+
|
|
6
|
+
logger_getter = get_logger_getter("httpd", syslog, config.log.allow)
|
|
7
|
+
|
|
8
|
+
def log(*args, **kwargs):
|
|
9
|
+
print(args, kwargs)
|
|
10
|
+
|
|
11
|
+
# setters (see above)
|
|
12
|
+
def setlog(f):
|
|
13
|
+
global log
|
|
14
|
+
log = f
|
|
15
|
+
|
|
16
|
+
TMSNAP = None
|
|
17
|
+
|
|
18
|
+
def log_tracemalloc():
|
|
19
|
+
global TMSNAP
|
|
20
|
+
snapshot = tracemalloc.take_snapshot()
|
|
21
|
+
log("[LINEMALLOC START]", important=True)
|
|
22
|
+
if TMSNAP:
|
|
23
|
+
lines = snapshot.compare_to(TMSNAP, 'lineno')
|
|
24
|
+
else:
|
|
25
|
+
lines = snapshot.statistics("lineno")
|
|
26
|
+
TMSNAP = snapshot
|
|
27
|
+
for line in lines[:10]:
|
|
28
|
+
log(line)
|
|
29
|
+
log("[LINEMALLOC END]", important=True)
|
|
30
|
+
return True
|
|
31
|
+
|
|
32
|
+
PROC = None
|
|
33
|
+
|
|
34
|
+
def log_openfiles():
|
|
35
|
+
global PROC
|
|
36
|
+
if not PROC:
|
|
37
|
+
PROC = psutil.Process(os.getpid())
|
|
38
|
+
ofz = PROC.open_files()
|
|
39
|
+
if config.log.oflist:
|
|
40
|
+
log("OPEN FILES: %s"%(ofz,), important=True)
|
|
41
|
+
else:
|
|
42
|
+
log("OPEN FILE COUNT: %s"%(len(ofz),), important=True)
|
|
43
|
+
return True
|
|
44
|
+
|
|
45
|
+
def log_kernel():
|
|
46
|
+
log(json.dumps(rel.report()), "kernel")
|
|
47
|
+
return True
|
|
48
|
+
|
babyweb/mail.py
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
from catmail import Mailer, Reader, Scanner
|
|
2
|
+
from catmail import config as catfyg
|
|
3
|
+
from .config import config
|
|
4
|
+
|
|
5
|
+
mfg = config.mail
|
|
6
|
+
catfyg.set({
|
|
7
|
+
"html": mfg.html,
|
|
8
|
+
"gmailer": mfg.gmailer,
|
|
9
|
+
"scantick": mfg.scantick,
|
|
10
|
+
"verbose": mfg.verbose,
|
|
11
|
+
"cache": config.cache
|
|
12
|
+
})
|
|
13
|
+
catfyg.admin.update("contacts", config.admin.contacts)
|
|
14
|
+
catfyg.admin.update("reportees", config.admin.reportees)
|
|
15
|
+
|
|
16
|
+
mailer = Mailer(mfg.mailer, mfg.name)
|
|
17
|
+
send_mail = mailer.mail
|
|
18
|
+
email_admins = mailer.admins
|
|
19
|
+
email_reportees = mailer.reportees
|
|
20
|
+
|
|
21
|
+
reader = Reader(config.mailer)
|
|
22
|
+
check_inbox = reader.inbox
|
|
23
|
+
|
|
24
|
+
scanner = Scanner(reader)
|
|
25
|
+
on_mail = scanner.on
|
babyweb/memcache.py
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
from .controller import dweb
|
|
2
|
+
|
|
3
|
+
def getmem(key, tojson=True):
|
|
4
|
+
return dweb().memcache.get(key, tojson)
|
|
5
|
+
|
|
6
|
+
def setmem(key, val, fromjson=True):
|
|
7
|
+
dweb().memcache.set(key, val, fromjson)
|
|
8
|
+
|
|
9
|
+
def delmem(key):
|
|
10
|
+
dweb().memcache.rm(key)
|
|
11
|
+
|
|
12
|
+
def clearmem():
|
|
13
|
+
dweb().memcache.clear()
|
|
14
|
+
|
|
15
|
+
def getcache():
|
|
16
|
+
c = {}
|
|
17
|
+
orig = dweb().memcache.cache
|
|
18
|
+
for k, v in list(orig.items()):
|
|
19
|
+
if v.__class__ == set:
|
|
20
|
+
v = list(v)
|
|
21
|
+
elif v.__class__ == dict and "ttl" in v: # countdown
|
|
22
|
+
v = str(v)
|
|
23
|
+
c[k] = v
|
|
24
|
+
return c
|
|
25
|
+
|
babyweb/requesters.py
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import time, requests, json
|
|
2
|
+
from dez.http import fetch as dfetch, post as dpost
|
|
3
|
+
from fyg.util import log
|
|
4
|
+
from .util import rec_conv
|
|
5
|
+
|
|
6
|
+
def _ctjson(result):
|
|
7
|
+
if hasattr(result, "decode"):
|
|
8
|
+
result = result.decode()
|
|
9
|
+
code = result[0]
|
|
10
|
+
if code not in "0123":
|
|
11
|
+
log("response encoded:")
|
|
12
|
+
log(result)
|
|
13
|
+
log("attempting decode")
|
|
14
|
+
result = dec(result)
|
|
15
|
+
if code in "02":
|
|
16
|
+
log("request failed!! : %s"%(result,), important=True)
|
|
17
|
+
elif code == "3":
|
|
18
|
+
return rec_conv(json.loads(result[1:]), True)
|
|
19
|
+
else:
|
|
20
|
+
return json.loads(result[1:])
|
|
21
|
+
|
|
22
|
+
def parse_url_parts(host, path, port, protocol):
|
|
23
|
+
if "://" in host:
|
|
24
|
+
protocol, host = host.split("://", 1)
|
|
25
|
+
if "/" in host:
|
|
26
|
+
host, path = host.split("/", 1)
|
|
27
|
+
path = "/" + path
|
|
28
|
+
else:
|
|
29
|
+
path = "/"
|
|
30
|
+
if ":" in host:
|
|
31
|
+
host, port = host.split(":")
|
|
32
|
+
port = int(port)
|
|
33
|
+
elif not port:
|
|
34
|
+
port = protocol == "https" and 443 or 80
|
|
35
|
+
return host, path, port, protocol
|
|
36
|
+
|
|
37
|
+
def fetch(host, path="/", port=None, asjson=False, cb=None, timeout=1, asyn=False, protocol="http", ctjson=False, qsp=None, fakeua=False, retries=5):
|
|
38
|
+
host, path, port, protocol = parse_url_parts(host, path, port, protocol)
|
|
39
|
+
if qsp:
|
|
40
|
+
path += "?"
|
|
41
|
+
for k, v in list(qsp.items()):
|
|
42
|
+
path += "%s=%s&"%(k, v)
|
|
43
|
+
path = path[:-1]
|
|
44
|
+
gkwargs = {}
|
|
45
|
+
headers = {}
|
|
46
|
+
if fakeua:
|
|
47
|
+
if type(fakeua) is str:
|
|
48
|
+
headers['User-Agent'] = fakeua
|
|
49
|
+
else:
|
|
50
|
+
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'
|
|
51
|
+
gkwargs["headers"] = headers
|
|
52
|
+
if asyn or cb: # asyn w/o cb works, will just log
|
|
53
|
+
secure = protocol == "https"
|
|
54
|
+
if ctjson:
|
|
55
|
+
orig_cb = cb or log
|
|
56
|
+
cb = lambda v : orig_cb(_ctjson(v))
|
|
57
|
+
return dfetch(host, path, port, secure, headers, cb, timeout, asjson)
|
|
58
|
+
if timeout:
|
|
59
|
+
gkwargs["timeout"] = timeout
|
|
60
|
+
furl = "%s://%s:%s%s"%(protocol, host, port, path)
|
|
61
|
+
log("fetch %s"%(furl,))
|
|
62
|
+
return syncreq(furl, "get", asjson, ctjson, retries, gkwargs)
|
|
63
|
+
|
|
64
|
+
def post(host, path="/", port=80, data=None, protocol="http", asjson=False, ctjson=False, text=None, cb=None):
|
|
65
|
+
if ctjson:
|
|
66
|
+
data = rec_conv(data)
|
|
67
|
+
if cb:
|
|
68
|
+
if ctjson:
|
|
69
|
+
orig_cb = cb
|
|
70
|
+
cb = lambda v : orig_cb(_ctjson(v))
|
|
71
|
+
host, path, port, protocol = parse_url_parts(host, path, port, protocol)
|
|
72
|
+
return dpost(host, path, port, protocol == "https", data=data, text=text, cb=cb)
|
|
73
|
+
url = "://" in host and host or "%s://%s:%s%s"%(protocol, host, port, path)
|
|
74
|
+
log("post %s"%(url,))
|
|
75
|
+
kwargs = {}
|
|
76
|
+
if data:
|
|
77
|
+
kwargs["json"] = data
|
|
78
|
+
elif text:
|
|
79
|
+
kwargs["data"] = text
|
|
80
|
+
return syncreq(url, "post", asjson, ctjson, rekwargs=kwargs)
|
|
81
|
+
|
|
82
|
+
def _dosyncreq(requester, url, asjson, ctjson, rekwargs):
|
|
83
|
+
result = requester(url, **rekwargs).content
|
|
84
|
+
if ctjson:
|
|
85
|
+
return _ctjson(result)
|
|
86
|
+
return asjson and json.loads(result) or result
|
|
87
|
+
|
|
88
|
+
def syncreq(url, method="get", asjson=False, ctjson=False, retries=5, rekwargs={}):
|
|
89
|
+
attempt = 1
|
|
90
|
+
requester = getattr(requests, method)
|
|
91
|
+
while attempt < retries:
|
|
92
|
+
try:
|
|
93
|
+
return _dosyncreq(requester, url, asjson, ctjson, rekwargs)
|
|
94
|
+
except requests.exceptions.ConnectionError:
|
|
95
|
+
log("syncreq(%s %s) attempt #%s failed"%(method, url, attempt))
|
|
96
|
+
time.sleep(1)
|
|
97
|
+
attempt += 1
|
|
98
|
+
return _dosyncreq(requester, url, asjson, ctjson, rekwargs) # final try-less try
|
babyweb/response.py
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import json
|
|
2
|
+
from rel import abort_branch, timeout
|
|
3
|
+
from dez.http.server import HTTPResponse
|
|
4
|
+
from .util import rec_conv, set_read, set_send, set_redir, set_header, set_close
|
|
5
|
+
from .config import config
|
|
6
|
+
|
|
7
|
+
files = {}
|
|
8
|
+
def read_file(data_field):
|
|
9
|
+
return files.pop(data_field)
|
|
10
|
+
|
|
11
|
+
class Response(object):
|
|
12
|
+
def __init__(self, request):
|
|
13
|
+
self.id = request.id
|
|
14
|
+
self.ip = request.real_ip
|
|
15
|
+
self.request = request
|
|
16
|
+
self.response = HTTPResponse(request)
|
|
17
|
+
|
|
18
|
+
def _read(self):
|
|
19
|
+
b = self.request.body or json.dumps(rec_conv(self.request.qs_params))
|
|
20
|
+
# print(b)
|
|
21
|
+
ctype = self.request.headers.get("content-type")
|
|
22
|
+
if ctype and ctype.startswith("multipart/form-data"):
|
|
23
|
+
if type(b) != bytes:
|
|
24
|
+
b = b.encode()
|
|
25
|
+
obj = {}
|
|
26
|
+
splitter, body = b.rsplit(b"\r\n", 2)[0].split(b"\r\n", 1)
|
|
27
|
+
for chunk in body.split(b"\r\n%s\r\n"%(splitter,)):
|
|
28
|
+
nameline, data = chunk.split(b"\r\n\r\n", 1)
|
|
29
|
+
nameline = nameline.decode()
|
|
30
|
+
name = nameline.split("; filename=")[0][:-1].split('name="')[1]
|
|
31
|
+
if "filename=" in nameline:
|
|
32
|
+
signature = "%s%s"%(self.id, name)
|
|
33
|
+
files[signature] = data
|
|
34
|
+
obj[name] = signature
|
|
35
|
+
else:
|
|
36
|
+
obj[name] = data
|
|
37
|
+
b = json.dumps(rec_conv(obj))
|
|
38
|
+
return b
|
|
39
|
+
|
|
40
|
+
def _send(self, *args, **kwargs):
|
|
41
|
+
wcfg = config.web
|
|
42
|
+
if wcfg.xorigin:
|
|
43
|
+
self.response.headers["Access-Control-Allow-Origin"] = "*"
|
|
44
|
+
if wcfg.csp:
|
|
45
|
+
if wcfg.csp.startswith("auto"):
|
|
46
|
+
d = wcfg.domain
|
|
47
|
+
if wcfg.csp != "autosub":
|
|
48
|
+
d = ".".join(d.split(".")[-2:])
|
|
49
|
+
wcfg.update("csp", "default-src 'self' %s *.%s"%(d, d))
|
|
50
|
+
self.response.headers["Content-Security-Policy"] = wcfg.csp
|
|
51
|
+
self.response.write(*args, **kwargs)
|
|
52
|
+
|
|
53
|
+
def _close(self):
|
|
54
|
+
timeout(.001, self.response.dispatch)
|
|
55
|
+
abort_branch()
|
|
56
|
+
|
|
57
|
+
def _header(self, *args, **kwargs):
|
|
58
|
+
self.response.__setitem__(*args, **kwargs)
|
|
59
|
+
|
|
60
|
+
def set_cbs(self):
|
|
61
|
+
set_read(self._read)
|
|
62
|
+
set_header(self._header)
|
|
63
|
+
set_send(self._send)
|
|
64
|
+
set_close(self._close)
|
|
65
|
+
set_redir(self.response.redirect)
|
babyweb/routes.py
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
from fyg.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
|
babyweb/server.py
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import os, time, requests, rel, ssl, sys, json, resource, tracemalloc, psutil
|
|
2
|
+
from fyg.util import log, set_log, set_error
|
|
3
|
+
from .logger import setlog, logger_getter, log_tracemalloc, log_openfiles, log_kernel
|
|
4
|
+
from .controller import getController
|
|
5
|
+
from .util import fail
|
|
6
|
+
from .config import config
|
|
7
|
+
|
|
8
|
+
def fdup():
|
|
9
|
+
log("checking the number of file descriptors allowed on your system", important=True)
|
|
10
|
+
soft, hard = resource.getrlimit(resource.RLIMIT_NOFILE)
|
|
11
|
+
half = int(hard / 2)
|
|
12
|
+
log("soft: %s. hard: %s. half: %s"%(soft, hard, half))
|
|
13
|
+
if soft < half:
|
|
14
|
+
log("increasing soft limit to half of hard limit")
|
|
15
|
+
resource.setrlimit(resource.RLIMIT_NOFILE, (half, hard))
|
|
16
|
+
log("new limits! soft: %s. hard: %s"%resource.getrlimit(resource.RLIMIT_NOFILE))
|
|
17
|
+
|
|
18
|
+
def quit():
|
|
19
|
+
if config.errlog:
|
|
20
|
+
log("closing error log", important=True)
|
|
21
|
+
sys.stderr.close()
|
|
22
|
+
log("quitting - goodbye!", important=True)
|
|
23
|
+
|
|
24
|
+
def run_dez_webserver():
|
|
25
|
+
if not config.ssl.verify and hasattr(ssl, "_https_verify_certificates"):
|
|
26
|
+
ssl._https_verify_certificates(False)
|
|
27
|
+
c = getController()
|
|
28
|
+
setlog(c.webs["web"].logger.simple)
|
|
29
|
+
if config.web.log:
|
|
30
|
+
set_log(os.path.join("logs", config.web.log))
|
|
31
|
+
clog = config.log
|
|
32
|
+
if clog.openfiles:
|
|
33
|
+
rel.timeout(clog.openfiles, log_openfiles)
|
|
34
|
+
if clog.tracemalloc:
|
|
35
|
+
tracemalloc.start()
|
|
36
|
+
rel.timeout(clog.tracemalloc, log_tracemalloc)
|
|
37
|
+
if "kernel" in clog.allow:
|
|
38
|
+
rel.timeout(1, log_kernel)
|
|
39
|
+
set_error(fail)
|
|
40
|
+
if config.fdup:
|
|
41
|
+
fdup()
|
|
42
|
+
if config.web.errlog:
|
|
43
|
+
sys.stderr = open(os.path.join("logs", config.web.errlog), "a")
|
|
44
|
+
c.start(quit)
|
|
45
|
+
|
|
46
|
+
if __name__ == "__main__":
|
|
47
|
+
run_dez_webserver()
|
babyweb/shield.py
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from dez.http.server.shield import Shield
|
|
3
|
+
from fyg.util import read, write
|
|
4
|
+
from .logger import logger_getter
|
|
5
|
+
from .config import config
|
|
6
|
+
|
|
7
|
+
class PaperShield(object):
|
|
8
|
+
def __init__(self):
|
|
9
|
+
self.logger = logger_getter("PaperShield")
|
|
10
|
+
self.default = { "reason": "I'm just a paper shield!" }
|
|
11
|
+
self.ips = {}
|
|
12
|
+
|
|
13
|
+
def __call__(self, path, ip, fspath=False, count=True):
|
|
14
|
+
self.logger.access('NOOP > paperShield("%s", "%s", fspath=%s, count=%s)'%(path, ip, fspath, count))
|
|
15
|
+
|
|
16
|
+
def suss(self, ip, reason):
|
|
17
|
+
self.logger.access("suss(%s) -> %s"%(ip, reason))
|
|
18
|
+
self.ips[ip] = { "reason": reason }
|
|
19
|
+
|
|
20
|
+
def ip(self, ip):
|
|
21
|
+
return self.ips.get(ip, self.default)
|
|
22
|
+
|
|
23
|
+
def setShield(blup=None):
|
|
24
|
+
shield = None
|
|
25
|
+
shfg = config.web.shield
|
|
26
|
+
if shfg: # web/admin share shield and blacklist
|
|
27
|
+
setBlacklist()
|
|
28
|
+
shield = Shield(config.web.blacklist, logger_getter, blup or writeBlacklist,
|
|
29
|
+
getattr(shfg, "limit", 400),
|
|
30
|
+
getattr(shfg, "interval", 2))
|
|
31
|
+
config.web.update("shield", shield or PaperShield())
|
|
32
|
+
return shield
|
|
33
|
+
|
|
34
|
+
def setBlacklist():
|
|
35
|
+
bl = {}
|
|
36
|
+
for preban in config.web.blacklist:
|
|
37
|
+
bl[preban] = "config ban"
|
|
38
|
+
if os.path.isfile("black.list"):
|
|
39
|
+
try:
|
|
40
|
+
bsaved = read("black.list", isjson=True)
|
|
41
|
+
except: # old style
|
|
42
|
+
bsaved = {}
|
|
43
|
+
for line in read("black.list", lines=True):
|
|
44
|
+
bsaved[line.strip()] = "legacy ban"
|
|
45
|
+
bsaved and bl.update(bsaved)
|
|
46
|
+
config.web.update("blacklist", bl)
|
|
47
|
+
|
|
48
|
+
def writeBlacklist():
|
|
49
|
+
wcfg = config.web
|
|
50
|
+
bl = wcfg.blacklist.obj()
|
|
51
|
+
write(bl, "black.list", isjson=True)
|
|
52
|
+
wcfg.blacklister and wcfg.blacklister.update(bl)
|
|
53
|
+
return len(bl.keys())
|
babyweb/sms.py
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
from .mail import send_mail
|
|
2
|
+
|
|
3
|
+
carriers = {
|
|
4
|
+
'at&t': 'mms.att.net',
|
|
5
|
+
'verizon': 'vtext.com',
|
|
6
|
+
'tmobile': 'tmomail.net',
|
|
7
|
+
'sprint': 'page.nextel.com'
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
def send_sms(number, subject="hello", body="goodbye", carrier="at&t"):
|
|
11
|
+
send_mail(to="%s@%s"%(number, carriers[carrier]), subject=subject, body=body)
|
babyweb/util/__init__.py
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
from .setters import local, localvars, set_read, set_send, set_redir, set_header, set_close
|
|
2
|
+
from .converters import rec_conv, rdec, renc, setenc, setdec
|
|
3
|
+
from .responders import set_pre_close, do_respond, succeed, succeed_sync, fail, redirect, trysavedresponse, send_file, send_text, send_xml, send_image, send_pdf
|
|
4
|
+
from .loaders import cgi_get, cgi_dump
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import json
|
|
2
|
+
from urllib.parse import quote, unquote
|
|
3
|
+
from base64 import b64encode, b64decode
|
|
4
|
+
from six import string_types
|
|
5
|
+
from ..config import config
|
|
6
|
+
|
|
7
|
+
_c = config.scrambler
|
|
8
|
+
_cl = len(_c)
|
|
9
|
+
_chl = int(_cl / 2)
|
|
10
|
+
|
|
11
|
+
def flip(c):
|
|
12
|
+
i = _c.find(c)
|
|
13
|
+
if i == -1:
|
|
14
|
+
return c
|
|
15
|
+
return _c[(i + _chl) % _cl]
|
|
16
|
+
|
|
17
|
+
def scramble(s):
|
|
18
|
+
return "".join([flip(c) for c in s])
|
|
19
|
+
|
|
20
|
+
def enc(data):
|
|
21
|
+
return scramble(b64encode(hasattr(data, "encode") and data.encode() or data).decode())
|
|
22
|
+
|
|
23
|
+
def dec(data):
|
|
24
|
+
return data.startswith("{") and data or b64decode(scramble(data)).decode()
|
|
25
|
+
|
|
26
|
+
def setenc(f):
|
|
27
|
+
global enc
|
|
28
|
+
enc = f
|
|
29
|
+
|
|
30
|
+
def setdec(f):
|
|
31
|
+
global dec
|
|
32
|
+
dec = f
|
|
33
|
+
|
|
34
|
+
def rdec(data):
|
|
35
|
+
return unquote(b64decode(data.encode()).decode())
|
|
36
|
+
|
|
37
|
+
def renc(data):
|
|
38
|
+
return b64encode(quote(data).encode()).decode()
|
|
39
|
+
|
|
40
|
+
def rec_conv(data, de=False):
|
|
41
|
+
if isinstance(data, bytes):
|
|
42
|
+
try:
|
|
43
|
+
data = data.decode()
|
|
44
|
+
except:
|
|
45
|
+
pass
|
|
46
|
+
if isinstance(data, string_types):
|
|
47
|
+
return (de and rdec or renc)(data)
|
|
48
|
+
elif isinstance(data, dict):
|
|
49
|
+
for k, v in list(data.items()):
|
|
50
|
+
data[k] = rec_conv(v, de)
|
|
51
|
+
elif isinstance(data, list):
|
|
52
|
+
return [rec_conv(d, de) for d in data]
|
|
53
|
+
return data
|
|
54
|
+
|
|
55
|
+
def processResponse(data, code):
|
|
56
|
+
if code == "1":
|
|
57
|
+
try:
|
|
58
|
+
data = json.dumps(data)
|
|
59
|
+
except:
|
|
60
|
+
data = json.dumps(rec_conv(data))
|
|
61
|
+
code = "3"
|
|
62
|
+
elif code == "0":
|
|
63
|
+
try:
|
|
64
|
+
json.dumps(data)
|
|
65
|
+
except:
|
|
66
|
+
data = rec_conv(data)
|
|
67
|
+
code = "2"
|
|
68
|
+
return "%s%s"%(code, data)
|
|
69
|
+
|
babyweb/util/loaders.py
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import json, cgi, ast, sys
|
|
2
|
+
from base64 import b64decode
|
|
3
|
+
from urllib.parse import unquote
|
|
4
|
+
from .setters import local, localvars
|
|
5
|
+
from .converters import dec, rec_conv
|
|
6
|
+
from ..config import config
|
|
7
|
+
|
|
8
|
+
def qs_get(x, y):
|
|
9
|
+
val = localvars.request.getvalue(x, y)
|
|
10
|
+
if val:
|
|
11
|
+
val = unquote(val)
|
|
12
|
+
return val
|
|
13
|
+
|
|
14
|
+
def cgi_read():
|
|
15
|
+
return local("read", sys.stdin.read)()
|
|
16
|
+
|
|
17
|
+
def cgi_dump():
|
|
18
|
+
return local("request_string")
|
|
19
|
+
|
|
20
|
+
def cgi_load():
|
|
21
|
+
localvars.request_string = cgi_read()
|
|
22
|
+
data = config.encode and dec(localvars.request_string) or localvars.request_string
|
|
23
|
+
try:
|
|
24
|
+
try:
|
|
25
|
+
jdata = json.loads(data)
|
|
26
|
+
except:
|
|
27
|
+
jdata = ast.literal_eval(data)
|
|
28
|
+
try:
|
|
29
|
+
localvars.request = rec_conv(jdata, True)
|
|
30
|
+
except:
|
|
31
|
+
localvars.request = jdata
|
|
32
|
+
except:
|
|
33
|
+
localvars.request = cgi.FieldStorage()
|
|
34
|
+
setattr(localvars.request, "get", qs_get)
|
|
35
|
+
if not localvars.request:
|
|
36
|
+
localvars.request = {}
|
|
37
|
+
|
|
38
|
+
def cgi_get(key, choices=None, required=True, default=None, shield=False, decode=False, base64=False):
|
|
39
|
+
from .responders import fail
|
|
40
|
+
request = local("request")
|
|
41
|
+
val = request.get(key, default)
|
|
42
|
+
if val is None:
|
|
43
|
+
required and fail('no value submitted for required field: "%s" [%s]'%(key, request))
|
|
44
|
+
elif shield:
|
|
45
|
+
ip = local("ip")
|
|
46
|
+
shield = config.web.shield
|
|
47
|
+
if shield(val, ip, fspath=True, count=False):
|
|
48
|
+
log('cgi_get() shield bounced "%s" for "%s"'%(ip, shield.ip(ip)["message"]))
|
|
49
|
+
fail()
|
|
50
|
+
if choices and val not in choices:
|
|
51
|
+
fail('invalid value for "%s": "%s"'%(key, val))
|
|
52
|
+
if base64 and val:
|
|
53
|
+
val = b64decode(unquote(val))
|
|
54
|
+
if decode and val:
|
|
55
|
+
val = unquote(val)
|
|
56
|
+
return val
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
import sys, rel, traceback
|
|
2
|
+
from urllib.parse import unquote
|
|
3
|
+
from fyg.util import basiclog as log
|
|
4
|
+
from .converters import enc, dec, rec_conv, processResponse
|
|
5
|
+
from .setters import local
|
|
6
|
+
from .loaders import cgi_load
|
|
7
|
+
from ..config import config
|
|
8
|
+
|
|
9
|
+
def _send(data):
|
|
10
|
+
send = local("send")
|
|
11
|
+
if send:
|
|
12
|
+
send(data)
|
|
13
|
+
else:
|
|
14
|
+
print(data)
|
|
15
|
+
|
|
16
|
+
def _close():
|
|
17
|
+
local("close", sys.exit)()
|
|
18
|
+
|
|
19
|
+
def _pre_close():
|
|
20
|
+
pass
|
|
21
|
+
|
|
22
|
+
def set_pre_close(f):
|
|
23
|
+
global _pre_close
|
|
24
|
+
_pre_close = f
|
|
25
|
+
|
|
26
|
+
def _env(html):
|
|
27
|
+
return "%s"
|
|
28
|
+
|
|
29
|
+
def set_env(f):
|
|
30
|
+
global _env
|
|
31
|
+
_env = f
|
|
32
|
+
|
|
33
|
+
def _header(hkey, hval):
|
|
34
|
+
header = local("header")
|
|
35
|
+
if header:
|
|
36
|
+
header(hkey, hval)
|
|
37
|
+
else:
|
|
38
|
+
_send("%s: %s"%(hkey, hval))
|
|
39
|
+
|
|
40
|
+
def _headers(headers):
|
|
41
|
+
for k, v in list(headers.items()):
|
|
42
|
+
_header(k, v)
|
|
43
|
+
if config.web.server == "gae":
|
|
44
|
+
_send("")
|
|
45
|
+
|
|
46
|
+
def _write(data, exit=True, savename=None):
|
|
47
|
+
if savename:
|
|
48
|
+
from ..memcache import setmem
|
|
49
|
+
setmem(savename, data, False)
|
|
50
|
+
_send(data)
|
|
51
|
+
if exit:
|
|
52
|
+
_pre_close()
|
|
53
|
+
_close()
|
|
54
|
+
|
|
55
|
+
FILETYPES = {"pdf": "application/pdf", "img": "image/png", "ico": "image/ico", "html": "text/html"}
|
|
56
|
+
|
|
57
|
+
def send_pdf(data, title=None):
|
|
58
|
+
if title:
|
|
59
|
+
_headers({
|
|
60
|
+
"Content-Type": 'application/pdf; name="%s.pdf"'%(title,),
|
|
61
|
+
"Content-Disposition": 'attachment; filename="%s.pdf"'%(title,)
|
|
62
|
+
})
|
|
63
|
+
else:
|
|
64
|
+
_headers({"Content-Type": "application/pdf"})
|
|
65
|
+
_send(data)
|
|
66
|
+
_close()
|
|
67
|
+
|
|
68
|
+
def send_image(data):
|
|
69
|
+
_headers({"Content-Type": "image/png"})
|
|
70
|
+
_send(data)
|
|
71
|
+
_close()
|
|
72
|
+
|
|
73
|
+
def send_file(data, file_type=None, detect=False, headers={}):
|
|
74
|
+
if detect:
|
|
75
|
+
import magic
|
|
76
|
+
file_type = data and magic.from_buffer(data, True)
|
|
77
|
+
if file_type:
|
|
78
|
+
headers["Content-Type"] = FILETYPES.get(file_type, file_type)
|
|
79
|
+
_headers(headers)
|
|
80
|
+
_send(data)
|
|
81
|
+
_close()
|
|
82
|
+
|
|
83
|
+
def send_text(data, dtype="html", fname=None, exit=True, headers={}):
|
|
84
|
+
headers["Content-Type"] = "text/%s"%(dtype,)
|
|
85
|
+
if fname:
|
|
86
|
+
headers['Content-Disposition'] = 'attachment; filename="%s.%s"'%(fname, dtype)
|
|
87
|
+
_headers(headers)
|
|
88
|
+
_write(data, exit)
|
|
89
|
+
|
|
90
|
+
def send_xml(data):
|
|
91
|
+
send_text(data, "xml")
|
|
92
|
+
|
|
93
|
+
def trysavedresponse(key=None):
|
|
94
|
+
from ..memcache import getmem
|
|
95
|
+
key = key or local("request_string")
|
|
96
|
+
response = getmem(key, False)
|
|
97
|
+
response and _write(response, exit=True)
|
|
98
|
+
|
|
99
|
+
def redirect(addr, msg="", noscript=False, exit=True, metas=None):
|
|
100
|
+
a = "<script>"
|
|
101
|
+
if msg:
|
|
102
|
+
a += 'alert("%s"); '%(msg,)
|
|
103
|
+
a += "document.location = '%s';</script>"%(addr,)
|
|
104
|
+
if noscript:
|
|
105
|
+
a += '<noscript>This site requires Javascript to function properly. To enable Javascript in your browser, please follow <a href="http://www.google.com/support/bin/answer.py?answer=23852">these instructions</a>. Thank you, and have a nice day.</noscript>'
|
|
106
|
+
if metas:
|
|
107
|
+
a = "<html><head>%s%s</head><body></body></html>"%(metas, a)
|
|
108
|
+
_header("Content-Type", "text/html")
|
|
109
|
+
_write(_env(True)%(a,), exit)
|
|
110
|
+
|
|
111
|
+
def resp_wrap(resp, failure):
|
|
112
|
+
def f():
|
|
113
|
+
try:
|
|
114
|
+
resp()
|
|
115
|
+
except rel.errors.AbortBranch as e:
|
|
116
|
+
_pre_close()
|
|
117
|
+
raise rel.errors.AbortBranch() # handled in rel
|
|
118
|
+
except SystemExit:
|
|
119
|
+
pass
|
|
120
|
+
except Exception as e:
|
|
121
|
+
failure(e)
|
|
122
|
+
return f
|
|
123
|
+
|
|
124
|
+
def succeed_sync(func, cb):
|
|
125
|
+
d = {}
|
|
126
|
+
def handle(*a, **k):
|
|
127
|
+
d["a"] = a
|
|
128
|
+
d["k"] = k
|
|
129
|
+
func(handle)
|
|
130
|
+
while True:
|
|
131
|
+
time.sleep(0.01)
|
|
132
|
+
if d["a"] or d["k"]:
|
|
133
|
+
succeed(cb(*d["a"], **d["k"]))
|
|
134
|
+
|
|
135
|
+
def succeed(data="", html=False, noenc=False, savename=None, cache=False):
|
|
136
|
+
if cache or config.memcache:
|
|
137
|
+
savename = local("request_string")
|
|
138
|
+
_header("Content-Type", "text/%s"%(html and "html" or "plain"))
|
|
139
|
+
draw = processResponse(data, "1")
|
|
140
|
+
dstring = (config.encode and not noenc) and enc(draw) or draw
|
|
141
|
+
_write(_env(html)%(dstring,), savename=savename)
|
|
142
|
+
|
|
143
|
+
def fail(data="failed", html=False, err=None, noenc=False, exit=True):
|
|
144
|
+
if err:
|
|
145
|
+
# log it
|
|
146
|
+
logdata = "%s --- %s --> %s"%(data, repr(err), traceback.format_exc())
|
|
147
|
+
log("error:", logdata)
|
|
148
|
+
if config.web.debug:
|
|
149
|
+
# write it
|
|
150
|
+
data = logdata
|
|
151
|
+
resp = local("response")
|
|
152
|
+
reqstring = local("request_string")
|
|
153
|
+
path = resp and resp.request.url or "can't find path!"
|
|
154
|
+
ip = local("ip") or (resp and resp.ip or "can't find ip!")
|
|
155
|
+
edump = "%s\n\n%s\n\n%s\n\n%s"%(path, ip, reqstring, logdata)
|
|
156
|
+
shield = config.web.shield
|
|
157
|
+
if reqstring and shield(reqstring, ip):
|
|
158
|
+
data = "nabra"
|
|
159
|
+
reason = shield.ip(ip)["message"]
|
|
160
|
+
logline = "%s - IP (%s) banned!"%(reason, ip)
|
|
161
|
+
edump = "%s\n\n%s"%(logline, edump)
|
|
162
|
+
log(logline)
|
|
163
|
+
elif config.web.eflags:
|
|
164
|
+
samples = {
|
|
165
|
+
"traceback": logdata
|
|
166
|
+
}
|
|
167
|
+
if reqstring:
|
|
168
|
+
samples["request"] = reqstring
|
|
169
|
+
for sample in samples:
|
|
170
|
+
for ef in config.web.eflags:
|
|
171
|
+
if ef in samples[sample]:
|
|
172
|
+
reason = '"%s" in %s'%(ef, sample)
|
|
173
|
+
logline = "%s - IP (%s) banned!"%(reason, ip)
|
|
174
|
+
edump = "%s\n\n%s"%(logline, edump)
|
|
175
|
+
shield.suss(ip, reason)
|
|
176
|
+
log(logline)
|
|
177
|
+
if config.web.report:
|
|
178
|
+
from .mail import email_admins
|
|
179
|
+
email_admins("error encountered", edump)
|
|
180
|
+
_header("Content-Type", "text/%s"%(html and "html" or "plain"))
|
|
181
|
+
draw = processResponse(data, "0")
|
|
182
|
+
dstring = (config.encode and not noenc) and enc(draw) or draw
|
|
183
|
+
_write(_env(html)%(dstring,), exit)
|
|
184
|
+
|
|
185
|
+
def do_respond(responseFunc, failMsg="failed", failHtml=False, failNoEnc=False, noLoad=False, threaded=False, response=None, autowin=True):
|
|
186
|
+
def resp():
|
|
187
|
+
response and response.set_cbs()
|
|
188
|
+
noLoad or cgi_load()
|
|
189
|
+
responseFunc()
|
|
190
|
+
autowin and succeed()
|
|
191
|
+
|
|
192
|
+
def failure(e):
|
|
193
|
+
fail(data=failMsg, html=failHtml, err=e, noenc=failNoEnc)
|
|
194
|
+
|
|
195
|
+
wrapped_response = resp_wrap(resp, failure)
|
|
196
|
+
if threaded:
|
|
197
|
+
rel.thread(wrapped_response)
|
|
198
|
+
else:
|
|
199
|
+
wrapped_response()
|
babyweb/util/setters.py
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import threading
|
|
2
|
+
|
|
3
|
+
localvars = threading.local()
|
|
4
|
+
def local(key, fallback=None):
|
|
5
|
+
return getattr(localvars, key, fallback)
|
|
6
|
+
|
|
7
|
+
def set_read(f):
|
|
8
|
+
localvars.read = f
|
|
9
|
+
|
|
10
|
+
def set_send(f):
|
|
11
|
+
localvars.send = f
|
|
12
|
+
|
|
13
|
+
def set_redir(f):
|
|
14
|
+
localvars.redir = f
|
|
15
|
+
|
|
16
|
+
def set_header(f):
|
|
17
|
+
localvars.header = f
|
|
18
|
+
|
|
19
|
+
def set_close(f):
|
|
20
|
+
localvars.close = f
|
babyweb/version.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.1.0"
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 bubbleboy14
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: babyweb
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Basic Asynchronous weB librarY
|
|
5
|
+
Author: Mario Balibrera
|
|
6
|
+
Author-email: mario.balibrera@gmail.com
|
|
7
|
+
License: MIT License
|
|
8
|
+
Classifier: Development Status :: 3 - Alpha
|
|
9
|
+
Classifier: Environment :: Console
|
|
10
|
+
Classifier: Intended Audience :: Developers
|
|
11
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
12
|
+
Classifier: Operating System :: OS Independent
|
|
13
|
+
Classifier: Programming Language :: Python
|
|
14
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
15
|
+
License-File: LICENSE
|
|
16
|
+
Requires-Dist: dez >=0.10.10.42
|
|
17
|
+
Requires-Dist: fyg >=0.1.7.6
|
|
18
|
+
|
|
19
|
+
repackages async dez components like HTTPApplication and SocketController into a minimalist config-driven web (backend) framework
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
babyweb/__init__.py,sha256=ePOo3Lx3DdhdOrDMae2PHhOLr8SqNh81c2wN0h5JRNQ,404
|
|
2
|
+
babyweb/config.py,sha256=eCvjUDqgRxOg8c8puKdf1rSrJFL87GwZVX3dclSF_ic,1025
|
|
3
|
+
babyweb/controller.py,sha256=zuP_h0y9o2G98QVp_DqtgPDPOJIzGARizQzG5Aozv-g,2734
|
|
4
|
+
babyweb/cron.py,sha256=wQTtY3ZJBeTjvQFbMDmR83c-aQjfEmulzeBWKUob55E,4243
|
|
5
|
+
babyweb/daemons.py,sha256=4rPZyIdjBbYQ4pwSY_W2vkFY3KfvpvBEfEivkwTlE5Y,1840
|
|
6
|
+
babyweb/logger.py,sha256=R8X2gci31CzLx-eWSiCdm8P4RqAYGT0EU683Y3qZf34,1021
|
|
7
|
+
babyweb/mail.py,sha256=A7FL99zDU9Y_LAZPpw9-LWOsxuc1eoiGMN1EYVrwOIo,607
|
|
8
|
+
babyweb/memcache.py,sha256=W6JWcBjJPZx8RPYQXYRbCWMl-ziwy9OtzCvxV9RlCXs,484
|
|
9
|
+
babyweb/requesters.py,sha256=ZrnhYbYkjM7Z0JrwwRrxI5dt7pW9g2Y4EokDtD6A2NY,3197
|
|
10
|
+
babyweb/response.py,sha256=_2hY9ib39MG59nvLDKD37pNCFF9ENuydegBQsd0KV18,2328
|
|
11
|
+
babyweb/routes.py,sha256=V9EQKjWqbwWrR1t4Ren9IGAcmcS5awYCIHdseWoEqVU,483
|
|
12
|
+
babyweb/server.py,sha256=iK0bORQGrO2sZxBcSDl9_ACNGlBVpuKh9XR-TwigGWI,1579
|
|
13
|
+
babyweb/shield.py,sha256=uB4vDKf6jKwWIebAjitm_q9T86iKPRKPNNeKcPgLfrE,1551
|
|
14
|
+
babyweb/sms.py,sha256=lYQFgpFjAas1XIolE0QC4ro2wgXH7hruzV1Dxz72-sE,299
|
|
15
|
+
babyweb/version.py,sha256=Pru0BlFBASFCFo7McHdohtKkUtgMPDwbGfyUZlE2_Vw,21
|
|
16
|
+
babyweb/util/__init__.py,sha256=zia5MMGy8m3KR6HrZ5b3l9lE3kubg4p7r0sr9vAMoPA,352
|
|
17
|
+
babyweb/util/converters.py,sha256=tvAppsy79lrhkOi3E2LkdBpEJ__j0H22RSiKJHssMGs,1579
|
|
18
|
+
babyweb/util/loaders.py,sha256=kMNxQ-hN-oJVgrOU8TQU8LSqp6B3wMoUKOpgS1D5R6Y,1752
|
|
19
|
+
babyweb/util/responders.py,sha256=A8RF3AR32H9z3YAEFCTmIQQuQ_yUoSub7u0Txnv8YYk,6194
|
|
20
|
+
babyweb/util/setters.py,sha256=xIHtdLQgdlyCUC83q3ZW8ab6U5Rj3UU6ZgK70b-TOg4,337
|
|
21
|
+
babyweb-0.1.0.dist-info/LICENSE,sha256=TCGFq2RZnPZed3cKBhfq8EIxNzHcwJWJsBW8p9y1dDk,1068
|
|
22
|
+
babyweb-0.1.0.dist-info/METADATA,sha256=zItkGAOsx5Z4dfMaX8wvI-Y6w7KCtAVQBPXFFl4eqfI,727
|
|
23
|
+
babyweb-0.1.0.dist-info/WHEEL,sha256=oiQVh_5PnQM0E3gPdiz09WCNmwiHDMaGer_elqB3coM,92
|
|
24
|
+
babyweb-0.1.0.dist-info/top_level.txt,sha256=-KcZVMe9-tSS5gwyDZGyLvU5bemV6aWTSofb0B37Mts,8
|
|
25
|
+
babyweb-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
babyweb
|