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,68 @@
|
|
|
1
|
+
from google.appengine.runtime.apiproxy_errors import RequestTooLargeError
|
|
2
|
+
from google.appengine.api import mail
|
|
3
|
+
from .util import *
|
|
4
|
+
|
|
5
|
+
envelope = {
|
|
6
|
+
'plain': "\n%s",
|
|
7
|
+
'html': "\n\n<html><head></head><body>%s</body></html>"
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
respond = do_respond
|
|
11
|
+
set_env(lambda html : envelope[html and 'html' or 'plain'])
|
|
12
|
+
|
|
13
|
+
# requests
|
|
14
|
+
def fetch(host, path="/", port=80, asjson=False):
|
|
15
|
+
from google.appengine.api.urlfetch import fetch
|
|
16
|
+
raw = fetch("http://%s:%s%s"%(host, port, path)).content
|
|
17
|
+
if asjson:
|
|
18
|
+
return json.loads(raw)
|
|
19
|
+
return raw
|
|
20
|
+
|
|
21
|
+
def post(host, path="/", port=80, data=None, protocol="http", asjson=False):
|
|
22
|
+
from google.appengine.api.urlfetch import fetch
|
|
23
|
+
raw = fetch("%s://%s:%s%s"%(protocol, host, port, path),
|
|
24
|
+
payload=json.dumps(data), method="POST").content
|
|
25
|
+
if asjson:
|
|
26
|
+
return json.loads(raw)
|
|
27
|
+
return raw
|
|
28
|
+
|
|
29
|
+
# file uploads
|
|
30
|
+
def read_file(data_field):
|
|
31
|
+
try:
|
|
32
|
+
return data_field.file.read()
|
|
33
|
+
except RequestTooLargeError:
|
|
34
|
+
fail("The file you are trying to upload is too large. Please submit something under 1MB. Thank you!", html=True, noenc=True)
|
|
35
|
+
|
|
36
|
+
# emails
|
|
37
|
+
def send_mail(to=None, subject=None, body=None, html=None):
|
|
38
|
+
m = config.mailer
|
|
39
|
+
if not m:
|
|
40
|
+
fail("failed to send email -- no MAILER specified in ct.cfg!")
|
|
41
|
+
if config.mailername:
|
|
42
|
+
m = "%s <%s>"%(config.mailername, m)
|
|
43
|
+
mail.send_mail(to=to, sender=m, subject=subject, body=body, html=html)
|
|
44
|
+
|
|
45
|
+
# memcache stuff
|
|
46
|
+
def getmem(key, tojson=True):
|
|
47
|
+
from google.appengine.api import memcache
|
|
48
|
+
result = memcache.get(key)
|
|
49
|
+
if result is None: return None
|
|
50
|
+
return tojson and json.loads(result) or result
|
|
51
|
+
|
|
52
|
+
def setmem(key, val, fromjson=True):
|
|
53
|
+
from google.appengine.api import memcache
|
|
54
|
+
memcache.set(key, fromjson and json.dumps(val) or val)
|
|
55
|
+
|
|
56
|
+
def delmem(key):
|
|
57
|
+
from google.appengine.api import memcache
|
|
58
|
+
if memcache.get(key) is not None:
|
|
59
|
+
memcache.delete(key)
|
|
60
|
+
|
|
61
|
+
def clearmem():
|
|
62
|
+
from google.appengine.api import memcache
|
|
63
|
+
memcache.flush_all()
|
|
64
|
+
|
|
65
|
+
set_getmem(getmem)
|
|
66
|
+
set_setmem(setmem)
|
|
67
|
+
set_delmem(delmem)
|
|
68
|
+
set_clearmem(clearmem)
|
cantools/web/util.py
ADDED
|
@@ -0,0 +1,552 @@
|
|
|
1
|
+
import re, os, sys, ast, json, time, threading
|
|
2
|
+
try:
|
|
3
|
+
from urllib.parse import quote, unquote, urlencode # py3
|
|
4
|
+
from urllib.request import urlopen, Request
|
|
5
|
+
except:
|
|
6
|
+
from urllib import quote, unquote, urlencode # py2.7
|
|
7
|
+
from urllib2 import urlopen, Request
|
|
8
|
+
from base64 import b64encode, b64decode
|
|
9
|
+
from dez.http.static import StaticStore
|
|
10
|
+
from cantools import config
|
|
11
|
+
from six import string_types
|
|
12
|
+
|
|
13
|
+
DEBUG = True
|
|
14
|
+
|
|
15
|
+
# memcache stuff -- overwrite with setters
|
|
16
|
+
def getmem(key, tojson=True):
|
|
17
|
+
log("memcache getting: %s"%(key,))
|
|
18
|
+
|
|
19
|
+
def setmem(key, val, fromjson=True):
|
|
20
|
+
log("memcache setting: %s -> %s"%(key, val))
|
|
21
|
+
|
|
22
|
+
def delmem(key):
|
|
23
|
+
log("memcache deleting: %s"%(key,))
|
|
24
|
+
|
|
25
|
+
def clearmem():
|
|
26
|
+
log("memcache clearing")
|
|
27
|
+
|
|
28
|
+
def set_getmem(f):
|
|
29
|
+
global getmem
|
|
30
|
+
getmem = f
|
|
31
|
+
|
|
32
|
+
def set_setmem(f):
|
|
33
|
+
global setmem
|
|
34
|
+
setmem = f
|
|
35
|
+
|
|
36
|
+
def set_delmem(f):
|
|
37
|
+
global delmem
|
|
38
|
+
delmem = f
|
|
39
|
+
|
|
40
|
+
def set_clearmem(f):
|
|
41
|
+
global clearmem
|
|
42
|
+
clearmem = f
|
|
43
|
+
|
|
44
|
+
# logging -- overwrite with setlog if ya want
|
|
45
|
+
def log(*args, **kwargs):
|
|
46
|
+
print(args, kwargs)
|
|
47
|
+
|
|
48
|
+
# encoding, decoding -- may overwrite with setenc/setdec, but not _that_ necessary
|
|
49
|
+
_c = config.scrambler
|
|
50
|
+
_cl = len(_c)
|
|
51
|
+
_chl = int(_cl / 2)
|
|
52
|
+
|
|
53
|
+
def flip(c):
|
|
54
|
+
i = _c.find(c)
|
|
55
|
+
if i == -1:
|
|
56
|
+
return c
|
|
57
|
+
return _c[(i + _chl) % _cl]
|
|
58
|
+
|
|
59
|
+
def scramble(s):
|
|
60
|
+
return "".join([flip(c) for c in s])
|
|
61
|
+
|
|
62
|
+
def enc(data):
|
|
63
|
+
return scramble(b64encode(hasattr(data, "encode") and data.encode() or data).decode())
|
|
64
|
+
|
|
65
|
+
def dec(data):
|
|
66
|
+
return data.startswith("{") and data or b64decode(scramble(data)).decode()
|
|
67
|
+
|
|
68
|
+
# setters (see above)
|
|
69
|
+
def setlog(f):
|
|
70
|
+
global log
|
|
71
|
+
log = f
|
|
72
|
+
|
|
73
|
+
def setenc(f):
|
|
74
|
+
global enc
|
|
75
|
+
enc = f
|
|
76
|
+
|
|
77
|
+
def setdec(f):
|
|
78
|
+
global dec
|
|
79
|
+
dec = f
|
|
80
|
+
|
|
81
|
+
# threading
|
|
82
|
+
localvars = threading.local()
|
|
83
|
+
def local(key, fallback=None):
|
|
84
|
+
return getattr(localvars, key, fallback)
|
|
85
|
+
|
|
86
|
+
# request functions
|
|
87
|
+
def deUnicodeDict(d):
|
|
88
|
+
if not isinstance(d, dict):
|
|
89
|
+
return d
|
|
90
|
+
n = {}
|
|
91
|
+
for v in d:
|
|
92
|
+
n[str(v)] = deUnicodeDict(d[v])
|
|
93
|
+
return n
|
|
94
|
+
|
|
95
|
+
def cgi_dump():
|
|
96
|
+
return local("request_string")
|
|
97
|
+
|
|
98
|
+
def cgi_read():
|
|
99
|
+
return local("read", sys.stdin.read)()
|
|
100
|
+
|
|
101
|
+
def set_read(f):
|
|
102
|
+
localvars.read = f
|
|
103
|
+
|
|
104
|
+
def set_redir(f):
|
|
105
|
+
localvars.redir = f
|
|
106
|
+
|
|
107
|
+
def rdec(data):
|
|
108
|
+
bdata = b64decode(data.encode())
|
|
109
|
+
try: # py2
|
|
110
|
+
return unquote(bdata).decode()
|
|
111
|
+
except: #py3
|
|
112
|
+
return unquote(bdata.decode())
|
|
113
|
+
|
|
114
|
+
def renc(data):
|
|
115
|
+
try: # py2
|
|
116
|
+
return b64encode(quote(data).encode()).decode()
|
|
117
|
+
except: #py3
|
|
118
|
+
return b64encode(quote(data.encode())).decode()
|
|
119
|
+
|
|
120
|
+
def rb64(data, de=False): # depped
|
|
121
|
+
log("[DEPRECATION WARNING] Something just called rb64(), which is depped -- use rec_conv()")
|
|
122
|
+
return rec_conv(data, de)
|
|
123
|
+
|
|
124
|
+
def rec_conv(data, de=False):
|
|
125
|
+
if isinstance(data, bytes):
|
|
126
|
+
try:
|
|
127
|
+
data = data.decode()
|
|
128
|
+
except:
|
|
129
|
+
pass
|
|
130
|
+
if isinstance(data, string_types):
|
|
131
|
+
return (de and rdec or renc)(data)
|
|
132
|
+
elif isinstance(data, dict):
|
|
133
|
+
for k, v in list(data.items()):
|
|
134
|
+
data[k] = rec_conv(v, de)
|
|
135
|
+
elif isinstance(data, list):
|
|
136
|
+
return [rec_conv(d, de) for d in data]
|
|
137
|
+
return data
|
|
138
|
+
|
|
139
|
+
def qs_get(x, y):
|
|
140
|
+
val = localvars.request.getvalue(x, y)
|
|
141
|
+
if val:
|
|
142
|
+
val = unquote(val)
|
|
143
|
+
return val
|
|
144
|
+
|
|
145
|
+
def cgi_load(force=False):
|
|
146
|
+
localvars.request_string = cgi_read()
|
|
147
|
+
data = config.encode and dec(localvars.request_string) or localvars.request_string
|
|
148
|
+
try:
|
|
149
|
+
try:
|
|
150
|
+
jdata = json.loads(data)
|
|
151
|
+
except:
|
|
152
|
+
jdata = ast.literal_eval(data)
|
|
153
|
+
try:
|
|
154
|
+
localvars.request = rec_conv(jdata, True)
|
|
155
|
+
except:
|
|
156
|
+
localvars.request = jdata
|
|
157
|
+
except:
|
|
158
|
+
import cgi
|
|
159
|
+
localvars.request = cgi.FieldStorage()
|
|
160
|
+
setattr(localvars.request, "get", qs_get)
|
|
161
|
+
if not localvars.request:
|
|
162
|
+
if force or config.web.server == "dez":
|
|
163
|
+
localvars.request = {}
|
|
164
|
+
else:
|
|
165
|
+
fail('no request data!')
|
|
166
|
+
|
|
167
|
+
def cgi_get(key, choices=None, required=True, default=None, shield=False, decode=False, base64=False):
|
|
168
|
+
request = local("request")
|
|
169
|
+
val = request.get(key, default)
|
|
170
|
+
if val is None:
|
|
171
|
+
required and fail('no value submitted for required field: "%s" [%s]'%(key, request))
|
|
172
|
+
elif shield:
|
|
173
|
+
ip = local("ip")
|
|
174
|
+
shield = config.web.shield
|
|
175
|
+
if shield(val, ip, fspath=True, count=False):
|
|
176
|
+
log('cgi_get() shield bounced "%s" for "%s"'%(ip, shield.ip(ip)["message"]))
|
|
177
|
+
fail()
|
|
178
|
+
if choices and val not in choices:
|
|
179
|
+
fail('invalid value for "%s": "%s"'%(key, val))
|
|
180
|
+
if base64 and val:
|
|
181
|
+
val = b64decode(unquote(val))
|
|
182
|
+
if decode and val:
|
|
183
|
+
val = unquote(val)
|
|
184
|
+
return val
|
|
185
|
+
|
|
186
|
+
# response functions
|
|
187
|
+
def _send(data):
|
|
188
|
+
send = local("send")
|
|
189
|
+
if send:
|
|
190
|
+
send(data)
|
|
191
|
+
else:
|
|
192
|
+
print(data)
|
|
193
|
+
|
|
194
|
+
def set_send(f):
|
|
195
|
+
localvars.send = f
|
|
196
|
+
|
|
197
|
+
def _close():
|
|
198
|
+
local("close", sys.exit)()
|
|
199
|
+
|
|
200
|
+
def set_close(f):
|
|
201
|
+
localvars.close = f
|
|
202
|
+
|
|
203
|
+
def _pre_close():
|
|
204
|
+
pass
|
|
205
|
+
|
|
206
|
+
def set_pre_close(f):
|
|
207
|
+
global _pre_close
|
|
208
|
+
_pre_close = f
|
|
209
|
+
|
|
210
|
+
def _header(hkey, hval):
|
|
211
|
+
header = local("header")
|
|
212
|
+
if header:
|
|
213
|
+
header(hkey, hval)
|
|
214
|
+
else:
|
|
215
|
+
_send("%s: %s"%(hkey, hval))
|
|
216
|
+
|
|
217
|
+
def set_header(f):
|
|
218
|
+
localvars.header = f
|
|
219
|
+
|
|
220
|
+
def _write(data, exit=True, savename=None):
|
|
221
|
+
if savename:
|
|
222
|
+
setmem(savename, data, False)
|
|
223
|
+
# try:
|
|
224
|
+
# data = data.decode('ascii', 'replace').encode('utf-8')
|
|
225
|
+
# except Exception as e:
|
|
226
|
+
# data = data.encode('utf-8')
|
|
227
|
+
_send(data)
|
|
228
|
+
if exit:
|
|
229
|
+
_pre_close()
|
|
230
|
+
_close()
|
|
231
|
+
|
|
232
|
+
def trysavedresponse(key=None):
|
|
233
|
+
key = key or local("request_string")
|
|
234
|
+
response = getmem(key, False)
|
|
235
|
+
response and _write(response, exit=True)
|
|
236
|
+
|
|
237
|
+
def dez_wrap(resp, failure):
|
|
238
|
+
from cantools.db import seshman
|
|
239
|
+
from rel.errors import AbortBranch
|
|
240
|
+
def f():
|
|
241
|
+
try:
|
|
242
|
+
resp()
|
|
243
|
+
except AbortBranch as e:
|
|
244
|
+
seshman.close()
|
|
245
|
+
raise AbortBranch() # handled in rel
|
|
246
|
+
except SystemExit:
|
|
247
|
+
pass
|
|
248
|
+
except Exception as e:
|
|
249
|
+
failure(e)
|
|
250
|
+
return f
|
|
251
|
+
|
|
252
|
+
def gae_wrap(resp, failure):
|
|
253
|
+
def f():
|
|
254
|
+
try:
|
|
255
|
+
resp()
|
|
256
|
+
except SystemExit:
|
|
257
|
+
pass
|
|
258
|
+
except Exception as e:
|
|
259
|
+
failure(e)
|
|
260
|
+
return f
|
|
261
|
+
|
|
262
|
+
resp_wrap = { "dez": dez_wrap, "gae": gae_wrap }
|
|
263
|
+
|
|
264
|
+
def do_respond(responseFunc, failMsg="failed", failHtml=False, failNoEnc=False, noLoad=False, threaded=False, response=None, autowin=True):
|
|
265
|
+
def resp():
|
|
266
|
+
response and response.set_cbs()
|
|
267
|
+
noLoad or cgi_load()
|
|
268
|
+
responseFunc()
|
|
269
|
+
autowin and succeed()
|
|
270
|
+
|
|
271
|
+
def failure(e):
|
|
272
|
+
fail(data=failMsg, html=failHtml, err=e, noenc=failNoEnc)
|
|
273
|
+
|
|
274
|
+
wrapped_response = resp_wrap[config.web.server](resp, failure)
|
|
275
|
+
if threaded: # dez only!!!
|
|
276
|
+
from rel import thread
|
|
277
|
+
thread(wrapped_response)
|
|
278
|
+
else:
|
|
279
|
+
wrapped_response()
|
|
280
|
+
|
|
281
|
+
def redirect(addr, msg="", noscript=False, exit=True, metas=None):
|
|
282
|
+
a = "<script>"
|
|
283
|
+
if msg:
|
|
284
|
+
a += 'alert("%s"); '%(msg,)
|
|
285
|
+
a += "document.location = '%s';</script>"%(addr,)
|
|
286
|
+
if noscript:
|
|
287
|
+
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>'
|
|
288
|
+
if metas:
|
|
289
|
+
a = "<html><head>%s%s</head><body></body></html>"%(metas, a)
|
|
290
|
+
_header("Content-Type", "text/html")
|
|
291
|
+
_write(_env(True)%(a,), exit)
|
|
292
|
+
|
|
293
|
+
def setcachedefault(shouldCache=True):
|
|
294
|
+
# deprecated -- should set via config.memcache.update("requst", [bool])
|
|
295
|
+
config.memcache.update("request", shouldCache)
|
|
296
|
+
|
|
297
|
+
def _env(html):
|
|
298
|
+
return "%s"
|
|
299
|
+
|
|
300
|
+
def set_env(f):
|
|
301
|
+
global _env
|
|
302
|
+
_env = f
|
|
303
|
+
|
|
304
|
+
def processResponse(data, code):
|
|
305
|
+
if code == "1":
|
|
306
|
+
try:
|
|
307
|
+
data = json.dumps(data)
|
|
308
|
+
except:
|
|
309
|
+
data = json.dumps(rec_conv(data))
|
|
310
|
+
code = "3"
|
|
311
|
+
elif code == "0":
|
|
312
|
+
try:
|
|
313
|
+
json.dumps(data)
|
|
314
|
+
except:
|
|
315
|
+
data = rec_conv(data)
|
|
316
|
+
code = "2"
|
|
317
|
+
return "%s%s"%(code, data)
|
|
318
|
+
|
|
319
|
+
def succeed_sync(func, cb):
|
|
320
|
+
d = {}
|
|
321
|
+
def handle(*a, **k):
|
|
322
|
+
d["a"] = a
|
|
323
|
+
d["k"] = k
|
|
324
|
+
func(handle)
|
|
325
|
+
while True:
|
|
326
|
+
time.sleep(0.01)
|
|
327
|
+
if d["a"] or d["k"]:
|
|
328
|
+
succeed(cb(*d["a"], **d["k"]))
|
|
329
|
+
|
|
330
|
+
def succeed(data="", html=False, noenc=False, savename=None, cache=False):
|
|
331
|
+
if cache or config.memcache.request:
|
|
332
|
+
savename = local("request_string")
|
|
333
|
+
_header("Content-Type", "text/%s"%(html and "html" or "plain"))
|
|
334
|
+
draw = processResponse(data, "1")
|
|
335
|
+
dstring = (config.encode and not noenc) and enc(draw) or draw
|
|
336
|
+
_write(_env(html)%(dstring,), savename=savename)
|
|
337
|
+
|
|
338
|
+
def fail(data="failed", html=False, err=None, noenc=False, exit=True):
|
|
339
|
+
if err:
|
|
340
|
+
# log it
|
|
341
|
+
import traceback
|
|
342
|
+
logdata = "%s --- %s --> %s"%(data, repr(err), traceback.format_exc())
|
|
343
|
+
log(logdata, "error")
|
|
344
|
+
if DEBUG:
|
|
345
|
+
# write it
|
|
346
|
+
data = logdata
|
|
347
|
+
resp = local("response")
|
|
348
|
+
reqstring = local("request_string")
|
|
349
|
+
path = resp and resp.request.url or "can't find path!"
|
|
350
|
+
ip = local("ip") or (resp and resp.ip or "can't find ip!")
|
|
351
|
+
edump = "%s\n\n%s\n\n%s\n\n%s"%(path, ip, reqstring, logdata)
|
|
352
|
+
shield = config.web.shield
|
|
353
|
+
if reqstring and shield(reqstring, ip):
|
|
354
|
+
data = "nabra"
|
|
355
|
+
reason = shield.ip(ip)["message"]
|
|
356
|
+
logline = "%s - IP (%s) banned!"%(reason, ip)
|
|
357
|
+
edump = "%s\n\n%s"%(logline, edump)
|
|
358
|
+
log(logline)
|
|
359
|
+
elif config.web.eflags:
|
|
360
|
+
samples = {
|
|
361
|
+
"traceback": logdata
|
|
362
|
+
}
|
|
363
|
+
if reqstring:
|
|
364
|
+
samples["request"] = reqstring
|
|
365
|
+
for sample in samples:
|
|
366
|
+
for ef in config.web.eflags:
|
|
367
|
+
if ef in samples[sample]:
|
|
368
|
+
reason = '"%s" in %s'%(ef, sample)
|
|
369
|
+
logline = "%s - IP (%s) banned!"%(reason, ip)
|
|
370
|
+
edump = "%s\n\n%s"%(logline, edump)
|
|
371
|
+
shield.suss(ip, reason)
|
|
372
|
+
log(logline)
|
|
373
|
+
if config.web.report:
|
|
374
|
+
from cantools.web import email_admins
|
|
375
|
+
email_admins("error encountered", edump)
|
|
376
|
+
_header("Content-Type", "text/%s"%(html and "html" or "plain"))
|
|
377
|
+
draw = processResponse(data, "0")
|
|
378
|
+
dstring = (config.encode and not noenc) and enc(draw) or draw
|
|
379
|
+
_write(_env(html)%(dstring,), exit)
|
|
380
|
+
|
|
381
|
+
def _headers(headers):
|
|
382
|
+
for k, v in list(headers.items()):
|
|
383
|
+
_header(k, v)
|
|
384
|
+
if config.web.server == "gae":
|
|
385
|
+
_send("")
|
|
386
|
+
|
|
387
|
+
def send_pdf(data, title=None):
|
|
388
|
+
if title:
|
|
389
|
+
_headers({
|
|
390
|
+
"Content-Type": 'application/pdf; name="%s.pdf"'%(title,),
|
|
391
|
+
"Content-Disposition": 'attachment; filename="%s.pdf"'%(title,)
|
|
392
|
+
})
|
|
393
|
+
else:
|
|
394
|
+
_headers({"Content-Type": "application/pdf"})
|
|
395
|
+
_send(data)
|
|
396
|
+
_close()
|
|
397
|
+
|
|
398
|
+
def send_image(data):
|
|
399
|
+
_headers({"Content-Type": "image/png"})
|
|
400
|
+
_send(data)
|
|
401
|
+
_close()
|
|
402
|
+
|
|
403
|
+
FILETYPES = {"pdf": "application/pdf", "img": "image/png", "ico": "image/ico", "html": "text/html"}
|
|
404
|
+
|
|
405
|
+
def send_file(data, file_type=None, detect=False, headers={}):
|
|
406
|
+
if detect:
|
|
407
|
+
import magic
|
|
408
|
+
file_type = data and magic.from_buffer(data, True)
|
|
409
|
+
if file_type:
|
|
410
|
+
headers["Content-Type"] = FILETYPES.get(file_type, file_type)
|
|
411
|
+
_headers(headers)
|
|
412
|
+
_send(data)
|
|
413
|
+
_close()
|
|
414
|
+
|
|
415
|
+
def send_text(data, dtype="html", fname=None, exit=True, headers={}):
|
|
416
|
+
headers["Content-Type"] = "text/%s"%(dtype,)
|
|
417
|
+
if fname:
|
|
418
|
+
headers['Content-Disposition'] = 'attachment; filename="%s.%s"'%(fname, dtype)
|
|
419
|
+
_headers(headers)
|
|
420
|
+
_write(data, exit)
|
|
421
|
+
|
|
422
|
+
def send_xml(data):
|
|
423
|
+
send_text(data, "xml")
|
|
424
|
+
|
|
425
|
+
# misc
|
|
426
|
+
def verify_recaptcha(cresponse, pkey):
|
|
427
|
+
verification_result = urlopen(Request(
|
|
428
|
+
url = "https://www.google.com/recaptcha/api/siteverify",
|
|
429
|
+
data = urlencode({
|
|
430
|
+
'secret': pkey,
|
|
431
|
+
'remoteip': os.environ.get('REMOTE_ADDR', os.environ.get('REMOTE_HOST')),
|
|
432
|
+
'response': cresponse
|
|
433
|
+
}).encode(),
|
|
434
|
+
headers = {
|
|
435
|
+
"Content-type": "application/x-www-form-urlencoded"
|
|
436
|
+
}))
|
|
437
|
+
vdata = verification_result.read().decode()
|
|
438
|
+
verification_result.close()
|
|
439
|
+
if "true" not in vdata:
|
|
440
|
+
fail(vdata)
|
|
441
|
+
|
|
442
|
+
def strip_punctuation(s):
|
|
443
|
+
return "".join([c for c in s if c.isalnum() or c.isspace()])
|
|
444
|
+
|
|
445
|
+
def strip_html(s):
|
|
446
|
+
p = re.compile(r'<.*?>')
|
|
447
|
+
return p.sub("", s)
|
|
448
|
+
|
|
449
|
+
def strip_html_carefully(s, besides=[]):
|
|
450
|
+
i = s.find('<');
|
|
451
|
+
while i != -1:
|
|
452
|
+
j = s.find('>', i)
|
|
453
|
+
if s[i+1:j] in besides or s[i+2:j] in besides:
|
|
454
|
+
i = s.find('<', i+1)
|
|
455
|
+
else:
|
|
456
|
+
s = s[:i] + s[j+1:]
|
|
457
|
+
i = s.find('<')
|
|
458
|
+
s = s.replace(" ", " ")
|
|
459
|
+
while " " in s:
|
|
460
|
+
s = s.replace(" ", " ")
|
|
461
|
+
return s
|
|
462
|
+
|
|
463
|
+
# media extraction
|
|
464
|
+
ITYPES = [ # from CT.parse.imgTypes[]
|
|
465
|
+
".png", ".PNG",
|
|
466
|
+
".jpg", ".JPG",
|
|
467
|
+
".gif", ".GIF",
|
|
468
|
+
".img", ".IMG",
|
|
469
|
+
".bmp", ".BMP",
|
|
470
|
+
"jpeg", "JPEG",
|
|
471
|
+
"webp", "WEBP",
|
|
472
|
+
"avif", "AVIF"
|
|
473
|
+
]
|
|
474
|
+
|
|
475
|
+
def vid2thumb(url):
|
|
476
|
+
if "youtube.com" in url and "img.youtube.com" not in url:
|
|
477
|
+
if "shorts" in url:
|
|
478
|
+
token = url.split("/").pop()
|
|
479
|
+
else:
|
|
480
|
+
token = url.split("?v=")[1]
|
|
481
|
+
url = "https://img.youtube.com/vi/%s/0.jpg"%(token,)
|
|
482
|
+
elif "tl.fzn.party" in url:
|
|
483
|
+
url = url.replace("/v/", "/img/v/").replace(".mp4", ".jpg")
|
|
484
|
+
if url[-4:] in ITYPES: # fast
|
|
485
|
+
return url
|
|
486
|
+
for itype in ITYPES: # thorough
|
|
487
|
+
if itype in url:
|
|
488
|
+
return url
|
|
489
|
+
|
|
490
|
+
def text2image(parts, full=False):
|
|
491
|
+
name = []
|
|
492
|
+
while parts:
|
|
493
|
+
part = parts.pop(0)
|
|
494
|
+
if part.startswith("https://"):
|
|
495
|
+
part = vid2thumb(part)
|
|
496
|
+
if not part:
|
|
497
|
+
continue
|
|
498
|
+
if part[-4:] in ITYPES: # fast
|
|
499
|
+
break
|
|
500
|
+
for itype in ITYPES: # thorough
|
|
501
|
+
if itype in part:
|
|
502
|
+
break
|
|
503
|
+
name.append(part)
|
|
504
|
+
if full:
|
|
505
|
+
return " ".join(name), part, " ".join(parts)
|
|
506
|
+
return part
|
|
507
|
+
|
|
508
|
+
def text2parts(text):
|
|
509
|
+
if " http" in text:
|
|
510
|
+
name, rest = text.split(" http", 1)
|
|
511
|
+
if " " in rest:
|
|
512
|
+
image, blurb = rest.split(" ", 1)
|
|
513
|
+
image = vid2thumb("http%s"%(image,))
|
|
514
|
+
if name and image and blurb:
|
|
515
|
+
return name, image, blurb
|
|
516
|
+
return text2image(text.split(" "), True)
|
|
517
|
+
|
|
518
|
+
# metaization
|
|
519
|
+
metastore = StaticStore()
|
|
520
|
+
qcache = {}
|
|
521
|
+
METS = """
|
|
522
|
+
<meta property="og:title" content="%s">
|
|
523
|
+
<meta property="twitter:title" content="%s">
|
|
524
|
+
<meta property="og:image" content="%s">
|
|
525
|
+
<meta property="twitter:image" content="%s">
|
|
526
|
+
<meta property="og:description" content="%s">
|
|
527
|
+
<meta property="twitter:description" content="%s">
|
|
528
|
+
<meta property="description" content="%s">
|
|
529
|
+
"""
|
|
530
|
+
|
|
531
|
+
def metized(m, markup=None):
|
|
532
|
+
n = m["name"]
|
|
533
|
+
i = m["image"]
|
|
534
|
+
b = m["blurb"]
|
|
535
|
+
mets = METS%(n, n, i, i, b, b, b)
|
|
536
|
+
return markup and markup.replace("<head>",
|
|
537
|
+
"<head>%s"%(mets,)) or mets
|
|
538
|
+
|
|
539
|
+
def metize(mextractor):
|
|
540
|
+
qs = local("request_string") # better key?
|
|
541
|
+
p = local("response").request.url
|
|
542
|
+
mdir = config.mode == "dynamic" and "html" or "html-%s"%(config.mode,)
|
|
543
|
+
fp = "%s%s"%(mdir, p)
|
|
544
|
+
if os.path.isdir(fp):
|
|
545
|
+
fp += "/index.html"
|
|
546
|
+
if p not in qcache:
|
|
547
|
+
qcache[p] = {}
|
|
548
|
+
if qs not in qcache[p]:
|
|
549
|
+
markup = metastore.read(fp)[0].decode()
|
|
550
|
+
metas = mextractor(p, markup)
|
|
551
|
+
qcache[p][qs] = metas and metized(metas, markup) or markup
|
|
552
|
+
send_file(qcache[p][qs])
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
MIT License:
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2011 Civil Action Network
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
6
|
+
|
|
7
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
8
|
+
|
|
9
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: ct
|
|
3
|
+
Version: 0.10.8.114
|
|
4
|
+
Summary: Modern minimal web framework
|
|
5
|
+
Author: Mario Balibrera
|
|
6
|
+
Author-email: mario.balibrera@gmail.com
|
|
7
|
+
License: MIT License
|
|
8
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
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: catmail >=0.1.9.1
|
|
17
|
+
Requires-Dist: databae >=0.1.4.6
|
|
18
|
+
Requires-Dist: dez >=0.10.10.37
|
|
19
|
+
Requires-Dist: fyg >=0.1.7.2
|
|
20
|
+
Requires-Dist: rel >=0.4.9.20
|
|
21
|
+
Requires-Dist: requests >=2.3.0
|
|
22
|
+
Requires-Dist: six >=1.12.0
|
|
23
|
+
Requires-Dist: venvr >=0.1.5.3
|
|
24
|
+
|
|
25
|
+
This portable modern web framework is the application-neutral backbone of Civil Action Network. It includes: a pubsub WebSocket server and bot platform; swappable web backends capable of targeting high-concurrency standalone or cloud platforms; a variable-mode application compiler; a broad-spectrum ORM and database migration tools; a built in administrative interface; and a rich modular JavaScript library.
|