rapydscript-ns 0.9.2 → 0.9.3
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.
- package/.agignore +1 -1
- package/.github/workflows/ci.yml +38 -38
- package/=template.pyj +5 -5
- package/CHANGELOG.md +19 -0
- package/HACKING.md +103 -103
- package/LICENSE +24 -24
- package/PYTHON_GAPS.md +420 -0
- package/README.md +153 -29
- package/TODO.md +16 -118
- package/add-toc-to-readme +2 -2
- package/bin/export +75 -75
- package/bin/rapydscript +70 -70
- package/bin/web-repl-export +102 -102
- package/build +2 -2
- package/language-service/index.js +237 -8
- package/memory/project_string_impl.md +43 -0
- package/package.json +1 -1
- package/publish.py +37 -37
- package/release/baselib-plain-pretty.js +248 -38
- package/release/baselib-plain-ugly.js +8 -8
- package/release/compiler.js +778 -277
- package/release/signatures.json +30 -30
- package/session.vim +4 -4
- package/setup.cfg +2 -2
- package/src/ast.pyj +4 -1
- package/src/baselib-builtins.pyj +56 -2
- package/src/baselib-containers.pyj +2 -0
- package/src/baselib-errors.pyj +7 -3
- package/src/baselib-internal.pyj +51 -6
- package/src/baselib-str.pyj +5 -3
- package/src/compiler.pyj +36 -36
- package/src/errors.pyj +30 -30
- package/src/lib/aes.pyj +646 -646
- package/src/lib/asyncio.pyj +534 -0
- package/src/lib/base64.pyj +399 -0
- package/src/lib/bisect.pyj +73 -0
- package/src/lib/collections.pyj +1 -1
- package/src/lib/copy.pyj +120 -120
- package/src/lib/csv.pyj +494 -0
- package/src/lib/elementmaker.pyj +83 -83
- package/src/lib/encodings.pyj +126 -126
- package/src/lib/gettext.pyj +569 -569
- package/src/lib/heapq.pyj +98 -0
- package/src/lib/html.pyj +382 -0
- package/src/lib/http/__init__.pyj +98 -0
- package/src/lib/http/client.pyj +304 -0
- package/src/lib/http/cookies.pyj +236 -0
- package/src/lib/itertools.pyj +580 -580
- package/src/lib/logging.pyj +672 -0
- package/src/lib/math.pyj +193 -193
- package/src/lib/operator.pyj +11 -11
- package/src/lib/pythonize.pyj +20 -20
- package/src/lib/random.pyj +118 -118
- package/src/lib/react.pyj +74 -74
- package/src/lib/string.pyj +357 -0
- package/src/lib/textwrap.pyj +329 -0
- package/src/lib/traceback.pyj +63 -63
- package/src/lib/urllib/__init__.pyj +14 -0
- package/src/lib/urllib/error.pyj +66 -0
- package/src/lib/urllib/parse.pyj +475 -0
- package/src/lib/urllib/request.pyj +86 -0
- package/src/lib/uuid.pyj +77 -77
- package/src/monaco-language-service/analyzer.js +5 -2
- package/src/monaco-language-service/completions.js +26 -0
- package/src/monaco-language-service/diagnostics.js +202 -3
- package/src/monaco-language-service/dts.js +550 -550
- package/src/monaco-language-service/scope.js +1 -0
- package/src/output/comments.pyj +45 -45
- package/src/output/exceptions.pyj +201 -201
- package/src/output/functions.pyj +152 -6
- package/src/output/jsx.pyj +164 -164
- package/src/output/loops.pyj +17 -2
- package/src/output/modules.pyj +1 -1
- package/src/output/operators.pyj +15 -0
- package/src/output/stream.pyj +0 -1
- package/src/output/treeshake.pyj +182 -182
- package/src/output/utils.pyj +72 -72
- package/src/parse.pyj +80 -17
- package/src/string_interpolation.pyj +72 -72
- package/src/tokenizer.pyj +1 -1
- package/src/unicode_aliases.pyj +576 -576
- package/src/utils.pyj +192 -192
- package/test/_import_one.pyj +37 -37
- package/test/_import_two/__init__.pyj +11 -11
- package/test/_import_two/level2/deep.pyj +4 -4
- package/test/_import_two/other.pyj +6 -6
- package/test/_import_two/sub.pyj +13 -13
- package/test/aes_vectors.pyj +421 -421
- package/test/annotations.pyj +80 -80
- package/test/async_generators.pyj +144 -0
- package/test/asyncio.pyj +307 -0
- package/test/base64.pyj +202 -0
- package/test/bisect.pyj +178 -0
- package/test/csv.pyj +405 -0
- package/test/decorators.pyj +77 -77
- package/test/docstrings.pyj +39 -39
- package/test/elementmaker_test.pyj +45 -45
- package/test/float_special.pyj +64 -0
- package/test/functions.pyj +151 -151
- package/test/generators.pyj +41 -41
- package/test/generic.pyj +370 -370
- package/test/heapq.pyj +174 -0
- package/test/html.pyj +212 -0
- package/test/http.pyj +259 -0
- package/test/imports.pyj +79 -72
- package/test/internationalization.pyj +73 -73
- package/test/lint.pyj +164 -164
- package/test/logging.pyj +356 -0
- package/test/long.pyj +130 -0
- package/test/loops.pyj +85 -85
- package/test/numpy.pyj +734 -734
- package/test/parenthesized_with.pyj +141 -0
- package/test/python_compat.pyj +3 -5
- package/test/python_modulo.pyj +76 -0
- package/test/python_modulo_off.pyj +21 -0
- package/test/repl.pyj +121 -121
- package/test/scoped_flags.pyj +76 -76
- package/test/str.pyj +14 -0
- package/test/string.pyj +245 -0
- package/test/textwrap.pyj +172 -0
- package/test/type_display.pyj +48 -0
- package/test/type_enforcement.pyj +164 -0
- package/test/unit/index.js +14 -6
- package/test/unit/language-service-completions.js +119 -0
- package/test/unit/language-service-dts.js +543 -543
- package/test/unit/language-service-hover.js +455 -455
- package/test/unit/language-service-scope.js +32 -0
- package/test/unit/language-service.js +127 -3
- package/test/unit/run-language-service.js +17 -3
- package/test/unit/web-repl.js +2094 -29
- package/test/urllib.pyj +193 -0
- package/tools/compile.js +1 -1
- package/tools/compiler.d.ts +367 -367
- package/tools/completer.js +131 -131
- package/tools/embedded_compiler.js +7 -7
- package/tools/gettext.js +185 -185
- package/tools/ini.js +65 -65
- package/tools/msgfmt.js +187 -187
- package/tools/repl.js +223 -223
- package/tools/test.js +118 -118
- package/tools/utils.js +128 -128
- package/tools/web_repl.js +95 -95
- package/try +41 -41
- package/web-repl/env.js +196 -196
- package/web-repl/index.html +163 -163
- package/web-repl/main.js +1 -1
- package/web-repl/prism.css +139 -139
- package/web-repl/prism.js +113 -113
- package/web-repl/rapydscript.js +224 -224
- package/web-repl/sha1.js +25 -25
- package/test/omit_function_metadata.pyj +0 -20
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
###########################################################
|
|
2
|
+
# RapydScript Standard Library
|
|
3
|
+
# http.client — HTTP/HTTPS connections via the Fetch API
|
|
4
|
+
###########################################################
|
|
5
|
+
#
|
|
6
|
+
# Provides:
|
|
7
|
+
# HTTPConnection(host[, port[, timeout]]) — plain HTTP connection
|
|
8
|
+
# HTTPSConnection(host[, port[, timeout]]) — TLS/HTTPS connection
|
|
9
|
+
# HTTPResponse — response returned by getresponse()
|
|
10
|
+
# HTTPException — base exception
|
|
11
|
+
# NotConnected — no request has been sent yet
|
|
12
|
+
# InvalidURL — malformed URL or host
|
|
13
|
+
# RemoteDisconnected — server closed connection unexpectedly
|
|
14
|
+
# HTTP_PORT = 80
|
|
15
|
+
# HTTPS_PORT = 443
|
|
16
|
+
#
|
|
17
|
+
# Usage (inside an async function):
|
|
18
|
+
# from http.client import HTTPConnection, HTTPSConnection
|
|
19
|
+
#
|
|
20
|
+
# async def fetch_page():
|
|
21
|
+
# conn = HTTPConnection('example.com')
|
|
22
|
+
# conn.request('GET', '/path', headers={'Accept': 'text/html'})
|
|
23
|
+
# resp = await conn.getresponse()
|
|
24
|
+
# print(resp.status, resp.reason)
|
|
25
|
+
# body = await resp.read()
|
|
26
|
+
# conn.close()
|
|
27
|
+
# return body
|
|
28
|
+
#
|
|
29
|
+
# For HTTPS use HTTPSConnection or pass a full https:// URL to HTTPConnection.
|
|
30
|
+
#
|
|
31
|
+
# Note: getresponse() wraps the browser/Node Fetch API and returns a Promise.
|
|
32
|
+
# It must be awaited inside an async function. There is no synchronous version
|
|
33
|
+
# because JavaScript has no synchronous HTTP.
|
|
34
|
+
#
|
|
35
|
+
# Unlike Python's http.client, non-2xx responses do NOT raise an exception —
|
|
36
|
+
# check resp.status yourself (matching the lower-level CPython behaviour).
|
|
37
|
+
|
|
38
|
+
HTTP_PORT = 80
|
|
39
|
+
HTTPS_PORT = 443
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class HTTPException(Exception):
|
|
43
|
+
"""Base class for http.client exceptions."""
|
|
44
|
+
pass
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class NotConnected(HTTPException):
|
|
48
|
+
"""No request has been issued on this connection yet."""
|
|
49
|
+
pass
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class InvalidURL(HTTPException):
|
|
53
|
+
"""The URL or host string is malformed."""
|
|
54
|
+
pass
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
class RemoteDisconnected(HTTPException):
|
|
58
|
+
"""The remote end closed the connection unexpectedly."""
|
|
59
|
+
pass
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
class HTTPResponse:
|
|
63
|
+
"""HTTP response object returned by HTTPConnection.getresponse().
|
|
64
|
+
|
|
65
|
+
Attributes
|
|
66
|
+
----------
|
|
67
|
+
status : int — HTTP status code (e.g. 200)
|
|
68
|
+
reason : str — HTTP reason phrase (e.g. 'OK')
|
|
69
|
+
headers : dict — lowercase header name → value mapping
|
|
70
|
+
url : str — final URL after any redirects
|
|
71
|
+
msg : dict — alias for headers (CPython compat)
|
|
72
|
+
|
|
73
|
+
Methods
|
|
74
|
+
-------
|
|
75
|
+
read([amt]) — Promise → full response body as str (amt ignored)
|
|
76
|
+
json() — Promise → body parsed as JSON
|
|
77
|
+
getheader(name[, default]) — return header value or default
|
|
78
|
+
getheaders() — list of [name, value] pairs
|
|
79
|
+
close() — mark response as closed (no-op in JS)
|
|
80
|
+
"""
|
|
81
|
+
|
|
82
|
+
def __init__(self, status, reason, headers, body_text, url):
|
|
83
|
+
self.status = status
|
|
84
|
+
self.reason = reason
|
|
85
|
+
self.url = url
|
|
86
|
+
self._body = body_text
|
|
87
|
+
self._closed = False
|
|
88
|
+
v"""
|
|
89
|
+
// Normalise headers to a plain null-prototype object so getheader()
|
|
90
|
+
// works with both plain dicts and ρσ_dict (web-repl / dict_literals).
|
|
91
|
+
var _hdrs = Object.create(null);
|
|
92
|
+
if (headers) {
|
|
93
|
+
if (headers.jsmap && typeof headers.jsmap.forEach === 'function') {
|
|
94
|
+
headers.jsmap.forEach(function(v, k) {
|
|
95
|
+
_hdrs[String(k).toLowerCase()] = String(v);
|
|
96
|
+
});
|
|
97
|
+
} else if (typeof headers === 'object') {
|
|
98
|
+
Object.keys(headers).forEach(function(k) {
|
|
99
|
+
if (k !== 'jsmap' && k !== '__class__') {
|
|
100
|
+
_hdrs[k.toLowerCase()] = String(headers[k]);
|
|
101
|
+
}
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
this.headers = _hdrs;
|
|
106
|
+
this.msg = _hdrs;
|
|
107
|
+
"""
|
|
108
|
+
|
|
109
|
+
def read(self, amt=None):
|
|
110
|
+
"""Return the response body as a string (as a resolved Promise)."""
|
|
111
|
+
body = self._body
|
|
112
|
+
return v'Promise.resolve(body)'
|
|
113
|
+
|
|
114
|
+
def json(self):
|
|
115
|
+
"""Parse the response body as JSON (as a resolved Promise)."""
|
|
116
|
+
body = self._body
|
|
117
|
+
return v'Promise.resolve(JSON.parse(body))'
|
|
118
|
+
|
|
119
|
+
def getheader(self, name, dflt=None):
|
|
120
|
+
"""Return the value of header *name* (case-insensitive), or *dflt*."""
|
|
121
|
+
v"""
|
|
122
|
+
var key = name.toLowerCase();
|
|
123
|
+
return Object.prototype.hasOwnProperty.call(this.headers, key)
|
|
124
|
+
? this.headers[key]
|
|
125
|
+
: dflt;
|
|
126
|
+
"""
|
|
127
|
+
|
|
128
|
+
def getheaders(self):
|
|
129
|
+
"""Return a list of [name, value] pairs for all response headers."""
|
|
130
|
+
result = []
|
|
131
|
+
v"""
|
|
132
|
+
var hdrs = this.headers;
|
|
133
|
+
Object.keys(hdrs).forEach(function(k) { result.push([k, hdrs[k]]); });
|
|
134
|
+
"""
|
|
135
|
+
return result
|
|
136
|
+
|
|
137
|
+
def close(self):
|
|
138
|
+
self._closed = True
|
|
139
|
+
|
|
140
|
+
@property
|
|
141
|
+
def closed(self):
|
|
142
|
+
return self._closed
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
class HTTPConnection:
|
|
146
|
+
"""An HTTP connection to *host*.
|
|
147
|
+
|
|
148
|
+
Parameters
|
|
149
|
+
----------
|
|
150
|
+
host : str — hostname (may include port as 'host:port')
|
|
151
|
+
port : int — port number; overrides any port in *host*
|
|
152
|
+
timeout : float — seconds before the request is aborted
|
|
153
|
+
|
|
154
|
+
Call request() to set up a request, then await getresponse() to execute it
|
|
155
|
+
and obtain an HTTPResponse.
|
|
156
|
+
"""
|
|
157
|
+
|
|
158
|
+
_scheme = 'http'
|
|
159
|
+
|
|
160
|
+
def __init__(self, host, port=None, timeout=None, source_address=None):
|
|
161
|
+
self.host = host
|
|
162
|
+
self.port = port
|
|
163
|
+
self.timeout = timeout
|
|
164
|
+
self._method = None
|
|
165
|
+
self._path = None
|
|
166
|
+
self._body = None
|
|
167
|
+
v"""this._headers = Object.create(null);"""
|
|
168
|
+
|
|
169
|
+
def _build_url(self, path):
|
|
170
|
+
"""Construct the full URL from the stored scheme, host, port, and path."""
|
|
171
|
+
v"""
|
|
172
|
+
var scheme = this._scheme;
|
|
173
|
+
var host = this.host;
|
|
174
|
+
var port = this.port;
|
|
175
|
+
// If path is already absolute, return it directly
|
|
176
|
+
if (path.slice(0, 7) === 'http://' || path.slice(0, 8) === 'https://') {
|
|
177
|
+
return path;
|
|
178
|
+
}
|
|
179
|
+
// Append port unless it is the default for the scheme
|
|
180
|
+
if (port != null) {
|
|
181
|
+
var default_port = scheme === 'https' ? 443 : 80;
|
|
182
|
+
if (port !== default_port) {
|
|
183
|
+
host = host + ':' + port;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
return scheme + '://' + host + path;
|
|
187
|
+
"""
|
|
188
|
+
|
|
189
|
+
def set_debuglevel(self, level):
|
|
190
|
+
"""Set debug level (no-op in JS)."""
|
|
191
|
+
pass
|
|
192
|
+
|
|
193
|
+
def connect(self):
|
|
194
|
+
"""Establish the connection (no-op in JS; fetch handles this)."""
|
|
195
|
+
pass
|
|
196
|
+
|
|
197
|
+
def close(self):
|
|
198
|
+
"""Discard any pending request state."""
|
|
199
|
+
self._method = None
|
|
200
|
+
self._path = None
|
|
201
|
+
self._body = None
|
|
202
|
+
v"""this._headers = Object.create(null);"""
|
|
203
|
+
|
|
204
|
+
def putrequest(self, method, url, skip_host=False, skip_accept_encoding=False):
|
|
205
|
+
"""Begin a request; use putheader() + endheaders() to add headers."""
|
|
206
|
+
self._method = method
|
|
207
|
+
self._path = url
|
|
208
|
+
self._body = None
|
|
209
|
+
v"""this._headers = Object.create(null);"""
|
|
210
|
+
|
|
211
|
+
def putheader(self, header, argument):
|
|
212
|
+
"""Add a single header to the pending request."""
|
|
213
|
+
v"""this._headers[header.toLowerCase()] = String(argument);"""
|
|
214
|
+
|
|
215
|
+
def endheaders(self, body=None):
|
|
216
|
+
"""Finalise the request headers and optionally set the body."""
|
|
217
|
+
if body is not None:
|
|
218
|
+
self._body = body
|
|
219
|
+
|
|
220
|
+
def request(self, method, url, body=None, headers={}):
|
|
221
|
+
"""Set up a request; call await getresponse() to execute it.
|
|
222
|
+
|
|
223
|
+
Parameters
|
|
224
|
+
----------
|
|
225
|
+
method : str — HTTP verb ('GET', 'POST', etc.)
|
|
226
|
+
url : str — path (e.g. '/api/items') or full URL
|
|
227
|
+
body : str — request body; also switches method hint to POST
|
|
228
|
+
headers : dict — extra request headers
|
|
229
|
+
"""
|
|
230
|
+
self._method = method
|
|
231
|
+
self._path = url
|
|
232
|
+
self._body = body
|
|
233
|
+
v"""
|
|
234
|
+
this._headers = Object.create(null);
|
|
235
|
+
var h = headers;
|
|
236
|
+
if (h) {
|
|
237
|
+
// ρσ_dict (web-repl / dict_literals mode) stores keys in h.jsmap (a Map)
|
|
238
|
+
if (h.jsmap && typeof h.jsmap.forEach === 'function') {
|
|
239
|
+
h.jsmap.forEach(function(v, k) {
|
|
240
|
+
this._headers[String(k).toLowerCase()] = String(v);
|
|
241
|
+
}.bind(this));
|
|
242
|
+
} else if (typeof h === 'object') {
|
|
243
|
+
Object.keys(h).forEach(function(k) {
|
|
244
|
+
if (k !== 'jsmap' && k !== '__class__') {
|
|
245
|
+
this._headers[k.toLowerCase()] = String(h[k]);
|
|
246
|
+
}
|
|
247
|
+
}.bind(this));
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
"""
|
|
251
|
+
|
|
252
|
+
def getresponse(self):
|
|
253
|
+
"""Execute the pending request and return a Promise → HTTPResponse.
|
|
254
|
+
|
|
255
|
+
Raises HTTPException on network errors.
|
|
256
|
+
Non-2xx responses are returned normally (check resp.status yourself).
|
|
257
|
+
"""
|
|
258
|
+
method = self._method or 'GET'
|
|
259
|
+
path = self._path or '/'
|
|
260
|
+
body = self._body
|
|
261
|
+
headers = self._headers
|
|
262
|
+
timeout = self.timeout
|
|
263
|
+
v"""
|
|
264
|
+
var url = this._build_url(path);
|
|
265
|
+
var _opts = { method: method, headers: headers };
|
|
266
|
+
if (body != null) { _opts.body = body; }
|
|
267
|
+
var _abort = null;
|
|
268
|
+
if (timeout != null && typeof AbortController !== 'undefined') {
|
|
269
|
+
var _ctrl = new AbortController();
|
|
270
|
+
_opts.signal = _ctrl.signal;
|
|
271
|
+
_abort = setTimeout(function() { _ctrl.abort(); }, timeout * 1000);
|
|
272
|
+
}
|
|
273
|
+
return Promise.resolve()
|
|
274
|
+
.then(function() { return fetch(url, _opts); })
|
|
275
|
+
.then(function(resp) {
|
|
276
|
+
if (_abort) { clearTimeout(_abort); _abort = null; }
|
|
277
|
+
var hdrs = {};
|
|
278
|
+
if (resp.headers && typeof resp.headers.forEach === 'function') {
|
|
279
|
+
resp.headers.forEach(function(v, k) { hdrs[k] = v; });
|
|
280
|
+
}
|
|
281
|
+
return resp.text().then(function(body_text) {
|
|
282
|
+
return new HTTPResponse(resp.status, resp.statusText, hdrs, body_text, resp.url);
|
|
283
|
+
});
|
|
284
|
+
})
|
|
285
|
+
.catch(function(e) {
|
|
286
|
+
if (_abort) { clearTimeout(_abort); _abort = null; }
|
|
287
|
+
if (e instanceof HTTPException) throw e;
|
|
288
|
+
throw new HTTPException(String(e));
|
|
289
|
+
});
|
|
290
|
+
"""
|
|
291
|
+
|
|
292
|
+
|
|
293
|
+
class HTTPSConnection(HTTPConnection):
|
|
294
|
+
"""An HTTPS connection to *host* (TLS).
|
|
295
|
+
|
|
296
|
+
Identical to HTTPConnection but uses https:// as the default scheme.
|
|
297
|
+
Parameters are the same as HTTPConnection.
|
|
298
|
+
"""
|
|
299
|
+
|
|
300
|
+
_scheme = 'https'
|
|
301
|
+
|
|
302
|
+
def __init__(self, host, port=None, timeout=None, source_address=None,
|
|
303
|
+
context=None, check_hostname=None):
|
|
304
|
+
HTTPConnection.__init__(self, host, port, timeout, source_address)
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
###########################################################
|
|
2
|
+
# RapydScript Standard Library
|
|
3
|
+
# http.cookies — HTTP cookie parsing and generation
|
|
4
|
+
###########################################################
|
|
5
|
+
#
|
|
6
|
+
# Provides:
|
|
7
|
+
# CookieError — raised when a cookie value cannot be parsed
|
|
8
|
+
# Morsel — represents a single cookie with its attributes
|
|
9
|
+
# SimpleCookie — dict-like container of Morsel objects
|
|
10
|
+
#
|
|
11
|
+
# Usage:
|
|
12
|
+
# from http.cookies import SimpleCookie, CookieError
|
|
13
|
+
#
|
|
14
|
+
# # Parse cookies from a Cookie request header
|
|
15
|
+
# c = SimpleCookie()
|
|
16
|
+
# c.load('session=abc123; user=alice')
|
|
17
|
+
# for name, morsel in c.items():
|
|
18
|
+
# print(name, morsel.value)
|
|
19
|
+
#
|
|
20
|
+
# # Build a Set-Cookie response header
|
|
21
|
+
# c2 = SimpleCookie()
|
|
22
|
+
# c2['token'] = 'xyz'
|
|
23
|
+
# c2['token']['path'] = '/'
|
|
24
|
+
# c2['token']['max-age'] = 3600
|
|
25
|
+
# print(c2.output()) # Set-Cookie: token=xyz; Max-Age=3600; Path=/
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class CookieError(Exception):
|
|
29
|
+
"""Raised when a cookie header cannot be parsed."""
|
|
30
|
+
pass
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
# Attribute keys for Morsel (lowercase, matching the Set-Cookie directive names)
|
|
34
|
+
_MORSEL_ATTRS = ['expires', 'path', 'comment', 'domain', 'max-age',
|
|
35
|
+
'secure', 'httponly', 'version', 'samesite']
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class Morsel:
|
|
39
|
+
"""A single cookie name/value pair plus its Set-Cookie attributes.
|
|
40
|
+
|
|
41
|
+
Attributes
|
|
42
|
+
----------
|
|
43
|
+
key : str — cookie name
|
|
44
|
+
value : str — decoded cookie value
|
|
45
|
+
coded_value : str — value as it appears in the header (same as value here)
|
|
46
|
+
|
|
47
|
+
Cookie attributes (set via morsel['path'] = '/', etc.):
|
|
48
|
+
'expires', 'path', 'comment', 'domain', 'max-age',
|
|
49
|
+
'secure', 'httponly', 'version', 'samesite'
|
|
50
|
+
"""
|
|
51
|
+
|
|
52
|
+
def __init__(self):
|
|
53
|
+
self.key = None
|
|
54
|
+
self.value = None
|
|
55
|
+
self.coded_value = None
|
|
56
|
+
self._attrs = {}
|
|
57
|
+
|
|
58
|
+
def set(self, key, val, coded_val):
|
|
59
|
+
self.key = key
|
|
60
|
+
self.value = val
|
|
61
|
+
self.coded_value = coded_val
|
|
62
|
+
|
|
63
|
+
def __getitem__(self, k):
|
|
64
|
+
v"""
|
|
65
|
+
var lk = k.toLowerCase();
|
|
66
|
+
return Object.prototype.hasOwnProperty.call(this._attrs, lk)
|
|
67
|
+
? this._attrs[lk]
|
|
68
|
+
: '';
|
|
69
|
+
"""
|
|
70
|
+
|
|
71
|
+
def __setitem__(self, k, v):
|
|
72
|
+
v"""
|
|
73
|
+
var lk = k.toLowerCase();
|
|
74
|
+
this._attrs[lk] = v;
|
|
75
|
+
"""
|
|
76
|
+
|
|
77
|
+
def isReservedKey(self, k):
|
|
78
|
+
v"""
|
|
79
|
+
var lk = k.toLowerCase();
|
|
80
|
+
for (var i = 0; i < _MORSEL_ATTRS.length; i++) {
|
|
81
|
+
if (_MORSEL_ATTRS[i] === lk) return true;
|
|
82
|
+
}
|
|
83
|
+
return false;
|
|
84
|
+
"""
|
|
85
|
+
|
|
86
|
+
def OutputString(self):
|
|
87
|
+
"""Return the cookie string (without the 'Set-Cookie:' prefix)."""
|
|
88
|
+
v"""
|
|
89
|
+
var parts = [this.key + '=' + this.coded_value];
|
|
90
|
+
var a = this._attrs;
|
|
91
|
+
if (a['expires']) { parts.push('expires=' + a['expires']); }
|
|
92
|
+
if (a['path']) { parts.push('Path=' + a['path']); }
|
|
93
|
+
if (a['comment']) { parts.push('Comment=' + a['comment']); }
|
|
94
|
+
if (a['domain']) { parts.push('Domain=' + a['domain']); }
|
|
95
|
+
if (a['max-age'] !== undefined && a['max-age'] !== '') {
|
|
96
|
+
parts.push('Max-Age=' + a['max-age']);
|
|
97
|
+
}
|
|
98
|
+
if (a['version']) { parts.push('Version=' + a['version']); }
|
|
99
|
+
if (a['samesite']) { parts.push('SameSite=' + a['samesite']); }
|
|
100
|
+
if (a['secure']) { parts.push('Secure'); }
|
|
101
|
+
if (a['httponly']) { parts.push('HttpOnly'); }
|
|
102
|
+
return parts.join('; ');
|
|
103
|
+
"""
|
|
104
|
+
|
|
105
|
+
def output(self, header='Set-Cookie'):
|
|
106
|
+
return header + ': ' + self.OutputString()
|
|
107
|
+
|
|
108
|
+
def __str__(self):
|
|
109
|
+
return self.OutputString()
|
|
110
|
+
|
|
111
|
+
def __repr__(self):
|
|
112
|
+
return '<Morsel: ' + str(self.key) + '=' + str(self.coded_value) + '>'
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def _parse_cookie_str(rawdata):
|
|
116
|
+
"""Parse a Cookie or Set-Cookie header string into a dict of name→Morsel."""
|
|
117
|
+
result = {}
|
|
118
|
+
v"""
|
|
119
|
+
// Split on ';' to get individual name=value pairs.
|
|
120
|
+
// We only parse the name=value portion here (not Set-Cookie attributes);
|
|
121
|
+
// for SimpleCookie we treat each ';'-separated segment as a cookie if it
|
|
122
|
+
// contains '=', otherwise we skip it (it's a directive like 'HttpOnly').
|
|
123
|
+
var parts = String(rawdata).split(';');
|
|
124
|
+
for (var i = 0; i < parts.length; i++) {
|
|
125
|
+
var part = parts[i].replace(/^\s+|\s+$/g, '');
|
|
126
|
+
if (part.length === 0) continue;
|
|
127
|
+
var eq = part.indexOf('=');
|
|
128
|
+
if (eq < 0) continue;
|
|
129
|
+
var name = part.slice(0, eq).replace(/^\s+|\s+$/g, '');
|
|
130
|
+
var val = part.slice(eq + 1).replace(/^\s+|\s+$/g, '');
|
|
131
|
+
// Strip surrounding quotes if present
|
|
132
|
+
if (val.length >= 2 && val.charAt(0) === '"' && val.charAt(val.length - 1) === '"') {
|
|
133
|
+
val = val.slice(1, val.length - 1);
|
|
134
|
+
}
|
|
135
|
+
if (name.length === 0) continue;
|
|
136
|
+
var m = new Morsel();
|
|
137
|
+
m.set(name, val, val);
|
|
138
|
+
result[name] = m;
|
|
139
|
+
}
|
|
140
|
+
"""
|
|
141
|
+
return result
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
class SimpleCookie:
|
|
145
|
+
"""A dict-like container of Morsel objects parsed from cookie headers.
|
|
146
|
+
|
|
147
|
+
cookie['name'] = 'value' — set a cookie (creates/replaces a Morsel)
|
|
148
|
+
cookie['name'] — get the Morsel for 'name'
|
|
149
|
+
cookie['name']['path'] = '/'— set a cookie attribute
|
|
150
|
+
|
|
151
|
+
load(rawdata) — parse cookie string into this container
|
|
152
|
+
output(header, sep) — format all cookies as Set-Cookie lines
|
|
153
|
+
items() — [(name, morsel), ...]
|
|
154
|
+
keys() — [name, ...]
|
|
155
|
+
values() — [morsel, ...]
|
|
156
|
+
"""
|
|
157
|
+
|
|
158
|
+
def __init__(self, input=None):
|
|
159
|
+
self._cookies = {}
|
|
160
|
+
if input is not None:
|
|
161
|
+
self.load(input)
|
|
162
|
+
|
|
163
|
+
def load(self, rawdata):
|
|
164
|
+
"""Parse *rawdata* (a cookie header string) and merge into this cookie."""
|
|
165
|
+
parsed = _parse_cookie_str(rawdata)
|
|
166
|
+
v"""
|
|
167
|
+
Object.keys(parsed).forEach(function(k) {
|
|
168
|
+
this._cookies[k] = parsed[k];
|
|
169
|
+
}.bind(this));
|
|
170
|
+
"""
|
|
171
|
+
|
|
172
|
+
def __getitem__(self, key):
|
|
173
|
+
v"""
|
|
174
|
+
if (!Object.prototype.hasOwnProperty.call(this._cookies, key)) {
|
|
175
|
+
throw new KeyError(key);
|
|
176
|
+
}
|
|
177
|
+
return this._cookies[key];
|
|
178
|
+
"""
|
|
179
|
+
|
|
180
|
+
def __setitem__(self, key, val):
|
|
181
|
+
v"""
|
|
182
|
+
if (!Object.prototype.hasOwnProperty.call(this._cookies, key)) {
|
|
183
|
+
var m = new Morsel();
|
|
184
|
+
m.set(key, String(val), String(val));
|
|
185
|
+
this._cookies[key] = m;
|
|
186
|
+
} else {
|
|
187
|
+
this._cookies[key].value = String(val);
|
|
188
|
+
this._cookies[key].coded_value = String(val);
|
|
189
|
+
}
|
|
190
|
+
"""
|
|
191
|
+
|
|
192
|
+
def __contains__(self, key):
|
|
193
|
+
v"""return Object.prototype.hasOwnProperty.call(this._cookies, key);"""
|
|
194
|
+
|
|
195
|
+
def keys(self):
|
|
196
|
+
v"""return Object.keys(this._cookies);"""
|
|
197
|
+
|
|
198
|
+
def values(self):
|
|
199
|
+
v"""
|
|
200
|
+
var out = [];
|
|
201
|
+
Object.keys(this._cookies).forEach(function(k) { out.push(this._cookies[k]); }.bind(this));
|
|
202
|
+
return out;
|
|
203
|
+
"""
|
|
204
|
+
|
|
205
|
+
def items(self):
|
|
206
|
+
v"""
|
|
207
|
+
var out = [];
|
|
208
|
+
Object.keys(this._cookies).forEach(function(k) {
|
|
209
|
+
out.push([k, this._cookies[k]]);
|
|
210
|
+
}.bind(this));
|
|
211
|
+
return out;
|
|
212
|
+
"""
|
|
213
|
+
|
|
214
|
+
def output(self, header='Set-Cookie:', sep='\r\n'):
|
|
215
|
+
"""Return all cookies formatted as Set-Cookie header lines."""
|
|
216
|
+
v"""
|
|
217
|
+
var lines = [];
|
|
218
|
+
var ck = this._cookies;
|
|
219
|
+
Object.keys(ck).forEach(function(k) {
|
|
220
|
+
lines.push(ck[k].output(header.replace(/:$/, '')));
|
|
221
|
+
});
|
|
222
|
+
return lines.join(sep);
|
|
223
|
+
"""
|
|
224
|
+
|
|
225
|
+
def __str__(self):
|
|
226
|
+
return self.output()
|
|
227
|
+
|
|
228
|
+
def __repr__(self):
|
|
229
|
+
v"""
|
|
230
|
+
var parts = [];
|
|
231
|
+
var ck = this._cookies;
|
|
232
|
+
Object.keys(ck).forEach(function(k) {
|
|
233
|
+
parts.push(JSON.stringify(k) + ': ' + JSON.stringify(ck[k].coded_value));
|
|
234
|
+
});
|
|
235
|
+
return '<SimpleCookie: ' + parts.join(', ') + '>';
|
|
236
|
+
"""
|