django-nativemojo 0.1.10__py3-none-any.whl → 0.1.16__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.
- django_nativemojo-0.1.16.dist-info/METADATA +138 -0
- django_nativemojo-0.1.16.dist-info/RECORD +302 -0
- mojo/__init__.py +1 -1
- mojo/apps/account/management/__init__.py +5 -0
- mojo/apps/account/management/commands/__init__.py +6 -0
- mojo/apps/account/management/commands/serializer_admin.py +651 -0
- mojo/apps/account/migrations/0004_user_avatar.py +20 -0
- mojo/apps/account/migrations/0005_group_last_activity.py +18 -0
- mojo/apps/account/migrations/0006_add_device_tracking_models.py +72 -0
- mojo/apps/account/migrations/0007_delete_userdevicelocation.py +16 -0
- mojo/apps/account/migrations/0008_userdevicelocation.py +33 -0
- mojo/apps/account/migrations/0009_geolocatedip_subnet.py +18 -0
- mojo/apps/account/migrations/0010_group_avatar.py +20 -0
- mojo/apps/account/migrations/0011_user_org_registereddevice_pushconfig_and_more.py +118 -0
- mojo/apps/account/migrations/0012_remove_pushconfig_apns_key_file_and_more.py +21 -0
- mojo/apps/account/migrations/0013_pushconfig_test_mode_alter_pushconfig_apns_enabled_and_more.py +28 -0
- mojo/apps/account/migrations/0014_notificationdelivery_data_payload_and_more.py +48 -0
- mojo/apps/account/models/__init__.py +2 -0
- mojo/apps/account/models/device.py +281 -0
- mojo/apps/account/models/group.py +319 -15
- mojo/apps/account/models/member.py +29 -5
- mojo/apps/account/models/push/__init__.py +4 -0
- mojo/apps/account/models/push/config.py +112 -0
- mojo/apps/account/models/push/delivery.py +93 -0
- mojo/apps/account/models/push/device.py +66 -0
- mojo/apps/account/models/push/template.py +99 -0
- mojo/apps/account/models/user.py +369 -19
- mojo/apps/account/rest/__init__.py +2 -0
- mojo/apps/account/rest/device.py +39 -0
- mojo/apps/account/rest/group.py +9 -0
- mojo/apps/account/rest/push.py +187 -0
- mojo/apps/account/rest/user.py +100 -6
- mojo/apps/account/services/__init__.py +1 -0
- mojo/apps/account/services/push.py +363 -0
- mojo/apps/aws/migrations/0001_initial.py +206 -0
- mojo/apps/aws/migrations/0002_emaildomain_can_recv_emaildomain_can_send_and_more.py +28 -0
- mojo/apps/aws/migrations/0003_mailbox_is_domain_default_mailbox_is_system_default_and_more.py +31 -0
- mojo/apps/aws/migrations/0004_s3bucket.py +39 -0
- mojo/apps/aws/migrations/0005_alter_emaildomain_region_delete_s3bucket.py +21 -0
- mojo/apps/aws/models/__init__.py +19 -0
- mojo/apps/aws/models/email_attachment.py +99 -0
- mojo/apps/aws/models/email_domain.py +218 -0
- mojo/apps/aws/models/email_template.py +132 -0
- mojo/apps/aws/models/incoming_email.py +197 -0
- mojo/apps/aws/models/mailbox.py +288 -0
- mojo/apps/aws/models/sent_message.py +175 -0
- mojo/apps/aws/rest/__init__.py +7 -0
- mojo/apps/aws/rest/email.py +33 -0
- mojo/apps/aws/rest/email_ops.py +183 -0
- mojo/apps/aws/rest/messages.py +32 -0
- mojo/apps/aws/rest/s3.py +64 -0
- mojo/apps/aws/rest/send.py +101 -0
- mojo/apps/aws/rest/sns.py +403 -0
- mojo/apps/aws/rest/templates.py +19 -0
- mojo/apps/aws/services/__init__.py +32 -0
- mojo/apps/aws/services/email.py +390 -0
- mojo/apps/aws/services/email_ops.py +548 -0
- mojo/apps/docit/__init__.py +6 -0
- mojo/apps/docit/markdown_plugins/syntax_highlight.py +25 -0
- mojo/apps/docit/markdown_plugins/toc.py +12 -0
- mojo/apps/docit/migrations/0001_initial.py +113 -0
- mojo/apps/docit/migrations/0002_alter_book_modified_by_alter_page_modified_by.py +26 -0
- mojo/apps/docit/migrations/0003_alter_book_group.py +20 -0
- mojo/apps/docit/models/__init__.py +17 -0
- mojo/apps/docit/models/asset.py +231 -0
- mojo/apps/docit/models/book.py +227 -0
- mojo/apps/docit/models/page.py +319 -0
- mojo/apps/docit/models/page_revision.py +203 -0
- mojo/apps/docit/rest/__init__.py +10 -0
- mojo/apps/docit/rest/asset.py +17 -0
- mojo/apps/docit/rest/book.py +22 -0
- mojo/apps/docit/rest/page.py +22 -0
- mojo/apps/docit/rest/page_revision.py +17 -0
- mojo/apps/docit/services/__init__.py +11 -0
- mojo/apps/docit/services/docit.py +315 -0
- mojo/apps/docit/services/markdown.py +44 -0
- mojo/apps/fileman/README.md +8 -8
- mojo/apps/fileman/backends/base.py +76 -70
- mojo/apps/fileman/backends/filesystem.py +86 -86
- mojo/apps/fileman/backends/s3.py +409 -108
- mojo/apps/fileman/migrations/0001_initial.py +106 -0
- mojo/apps/fileman/migrations/0002_filemanager_parent_alter_filemanager_max_file_size.py +24 -0
- mojo/apps/fileman/migrations/0003_remove_file_fileman_fil_upload__c4bc35_idx_and_more.py +25 -0
- mojo/apps/fileman/migrations/0004_remove_file_original_filename_and_more.py +39 -0
- mojo/apps/fileman/migrations/0005_alter_file_upload_token.py +18 -0
- mojo/apps/fileman/migrations/0006_file_download_url_filemanager_forever_urls.py +23 -0
- mojo/apps/fileman/migrations/0007_remove_filemanager_forever_urls_and_more.py +22 -0
- mojo/apps/fileman/migrations/0008_file_category.py +18 -0
- mojo/apps/fileman/migrations/0009_rename_file_path_file_storage_file_path.py +18 -0
- mojo/apps/fileman/migrations/0010_filerendition.py +33 -0
- mojo/apps/fileman/migrations/0011_alter_filerendition_original_file.py +19 -0
- mojo/apps/fileman/models/__init__.py +1 -5
- mojo/apps/fileman/models/file.py +240 -58
- mojo/apps/fileman/models/manager.py +427 -31
- mojo/apps/fileman/models/rendition.py +118 -0
- mojo/apps/fileman/renderer/__init__.py +111 -0
- mojo/apps/fileman/renderer/audio.py +403 -0
- mojo/apps/fileman/renderer/base.py +205 -0
- mojo/apps/fileman/renderer/document.py +404 -0
- mojo/apps/fileman/renderer/image.py +222 -0
- mojo/apps/fileman/renderer/utils.py +297 -0
- mojo/apps/fileman/renderer/video.py +304 -0
- mojo/apps/fileman/rest/__init__.py +1 -18
- mojo/apps/fileman/rest/upload.py +22 -32
- mojo/apps/fileman/signals.py +58 -0
- mojo/apps/fileman/tasks.py +254 -0
- mojo/apps/fileman/utils/__init__.py +40 -16
- mojo/apps/incident/migrations/0005_incidenthistory.py +39 -0
- mojo/apps/incident/migrations/0006_alter_incident_state.py +18 -0
- mojo/apps/incident/migrations/0007_event_uid.py +18 -0
- mojo/apps/incident/migrations/0008_ticket_ticketnote.py +55 -0
- mojo/apps/incident/migrations/0009_incident_status.py +18 -0
- mojo/apps/incident/migrations/0010_event_country_code.py +18 -0
- mojo/apps/incident/migrations/0011_incident_country_code.py +18 -0
- mojo/apps/incident/migrations/0012_alter_incident_status.py +18 -0
- mojo/apps/incident/models/__init__.py +2 -0
- mojo/apps/incident/models/event.py +35 -0
- mojo/apps/incident/models/history.py +36 -0
- mojo/apps/incident/models/incident.py +3 -1
- mojo/apps/incident/models/ticket.py +62 -0
- mojo/apps/incident/reporter.py +21 -1
- mojo/apps/incident/rest/__init__.py +1 -0
- mojo/apps/incident/rest/event.py +7 -1
- mojo/apps/incident/rest/ticket.py +43 -0
- mojo/apps/jobs/__init__.py +489 -0
- mojo/apps/jobs/adapters.py +24 -0
- mojo/apps/jobs/cli.py +616 -0
- mojo/apps/jobs/daemon.py +370 -0
- mojo/apps/jobs/examples/sample_jobs.py +376 -0
- mojo/apps/jobs/examples/webhook_examples.py +203 -0
- mojo/apps/jobs/handlers/__init__.py +5 -0
- mojo/apps/jobs/handlers/webhook.py +317 -0
- mojo/apps/jobs/job_engine.py +734 -0
- mojo/apps/jobs/keys.py +203 -0
- mojo/apps/jobs/local_queue.py +363 -0
- mojo/apps/jobs/management/__init__.py +3 -0
- mojo/apps/jobs/management/commands/__init__.py +3 -0
- mojo/apps/jobs/manager.py +1327 -0
- mojo/apps/jobs/migrations/0001_initial.py +97 -0
- mojo/apps/jobs/migrations/0002_alter_job_max_retries_joblog.py +39 -0
- mojo/apps/jobs/models/__init__.py +6 -0
- mojo/apps/jobs/models/job.py +441 -0
- mojo/apps/jobs/rest/__init__.py +2 -0
- mojo/apps/jobs/rest/control.py +466 -0
- mojo/apps/jobs/rest/jobs.py +421 -0
- mojo/apps/jobs/scheduler.py +571 -0
- mojo/apps/jobs/services/__init__.py +6 -0
- mojo/apps/jobs/services/job_actions.py +465 -0
- mojo/apps/jobs/settings.py +209 -0
- mojo/apps/logit/migrations/0004_alter_log_level.py +18 -0
- mojo/apps/logit/models/log.py +7 -1
- mojo/apps/metrics/__init__.py +8 -1
- mojo/apps/metrics/redis_metrics.py +198 -0
- mojo/apps/metrics/rest/__init__.py +3 -0
- mojo/apps/metrics/rest/categories.py +266 -0
- mojo/apps/metrics/rest/helpers.py +48 -0
- mojo/apps/metrics/rest/permissions.py +99 -0
- mojo/apps/metrics/rest/values.py +277 -0
- mojo/apps/metrics/utils.py +19 -2
- mojo/decorators/auth.py +6 -1
- mojo/decorators/http.py +47 -3
- mojo/helpers/aws/__init__.py +45 -0
- mojo/helpers/aws/ec2.py +804 -0
- mojo/helpers/aws/iam.py +748 -0
- mojo/helpers/aws/inbound_email.py +309 -0
- mojo/helpers/aws/kms.py +413 -0
- mojo/helpers/aws/s3.py +451 -11
- mojo/helpers/aws/ses.py +483 -0
- mojo/helpers/aws/ses_domain.py +959 -0
- mojo/helpers/aws/sns.py +461 -0
- mojo/helpers/crypto/__init__.py +1 -1
- mojo/helpers/crypto/utils.py +15 -0
- mojo/helpers/dates.py +18 -0
- mojo/helpers/location/__init__.py +2 -0
- mojo/helpers/location/countries.py +262 -0
- mojo/helpers/location/geolocation.py +196 -0
- mojo/helpers/logit.py +37 -0
- mojo/helpers/redis/__init__.py +2 -0
- mojo/helpers/redis/adapter.py +606 -0
- mojo/helpers/redis/client.py +48 -0
- mojo/helpers/redis/pool.py +225 -0
- mojo/helpers/request.py +8 -0
- mojo/helpers/response.py +14 -2
- mojo/helpers/settings/__init__.py +2 -0
- mojo/helpers/{settings.py → settings/helper.py} +1 -37
- mojo/helpers/settings/parser.py +132 -0
- mojo/middleware/auth.py +1 -1
- mojo/middleware/cors.py +40 -0
- mojo/middleware/logging.py +131 -12
- mojo/middleware/mojo.py +10 -0
- mojo/models/rest.py +494 -65
- mojo/models/secrets.py +98 -3
- mojo/serializers/__init__.py +106 -0
- mojo/serializers/core/__init__.py +90 -0
- mojo/serializers/core/cache/__init__.py +121 -0
- mojo/serializers/core/cache/backends.py +518 -0
- mojo/serializers/core/cache/base.py +102 -0
- mojo/serializers/core/cache/disabled.py +181 -0
- mojo/serializers/core/cache/memory.py +287 -0
- mojo/serializers/core/cache/redis.py +533 -0
- mojo/serializers/core/cache/utils.py +454 -0
- mojo/serializers/core/manager.py +550 -0
- mojo/serializers/core/serializer.py +475 -0
- mojo/serializers/examples/settings.py +322 -0
- mojo/serializers/formats/csv.py +393 -0
- mojo/serializers/formats/localizers.py +509 -0
- mojo/serializers/{models.py → simple.py} +38 -15
- mojo/serializers/suggested_improvements.md +388 -0
- testit/client.py +1 -1
- testit/helpers.py +35 -4
- testit/runner.py +23 -6
- django_nativemojo-0.1.10.dist-info/METADATA +0 -96
- django_nativemojo-0.1.10.dist-info/RECORD +0 -194
- mojo/apps/metrics/rest/db.py +0 -0
- mojo/apps/notify/README.md +0 -91
- mojo/apps/notify/README_NOTIFICATIONS.md +0 -566
- mojo/apps/notify/admin.py +0 -52
- mojo/apps/notify/handlers/example_handlers.py +0 -516
- mojo/apps/notify/handlers/ses/__init__.py +0 -25
- mojo/apps/notify/handlers/ses/bounce.py +0 -0
- mojo/apps/notify/handlers/ses/complaint.py +0 -25
- mojo/apps/notify/handlers/ses/message.py +0 -86
- mojo/apps/notify/management/commands/__init__.py +0 -1
- mojo/apps/notify/management/commands/process_notifications.py +0 -370
- mojo/apps/notify/mod +0 -0
- mojo/apps/notify/models/__init__.py +0 -12
- mojo/apps/notify/models/account.py +0 -128
- mojo/apps/notify/models/attachment.py +0 -24
- mojo/apps/notify/models/bounce.py +0 -68
- mojo/apps/notify/models/complaint.py +0 -40
- mojo/apps/notify/models/inbox.py +0 -113
- mojo/apps/notify/models/inbox_message.py +0 -173
- mojo/apps/notify/models/outbox.py +0 -129
- mojo/apps/notify/models/outbox_message.py +0 -288
- mojo/apps/notify/models/template.py +0 -30
- mojo/apps/notify/providers/aws.py +0 -73
- mojo/apps/notify/rest/ses.py +0 -0
- mojo/apps/notify/utils/__init__.py +0 -2
- mojo/apps/notify/utils/notifications.py +0 -404
- mojo/apps/notify/utils/parsing.py +0 -202
- mojo/apps/notify/utils/render.py +0 -144
- mojo/apps/tasks/README.md +0 -118
- mojo/apps/tasks/__init__.py +0 -11
- mojo/apps/tasks/manager.py +0 -489
- mojo/apps/tasks/rest/__init__.py +0 -2
- mojo/apps/tasks/rest/hooks.py +0 -0
- mojo/apps/tasks/rest/tasks.py +0 -62
- mojo/apps/tasks/runner.py +0 -174
- mojo/apps/tasks/tq_handlers.py +0 -14
- mojo/helpers/aws/setup_email.py +0 -0
- mojo/helpers/redis.py +0 -10
- mojo/models/meta.py +0 -262
- mojo/ws4redis/README.md +0 -174
- mojo/ws4redis/__init__.py +0 -2
- mojo/ws4redis/client.py +0 -283
- mojo/ws4redis/connection.py +0 -327
- mojo/ws4redis/exceptions.py +0 -32
- mojo/ws4redis/redis.py +0 -183
- mojo/ws4redis/servers/base.py +0 -86
- mojo/ws4redis/servers/django.py +0 -171
- mojo/ws4redis/servers/uwsgi.py +0 -63
- mojo/ws4redis/settings.py +0 -45
- mojo/ws4redis/utf8validator.py +0 -128
- mojo/ws4redis/websocket.py +0 -403
- {django_nativemojo-0.1.10.dist-info → django_nativemojo-0.1.16.dist-info}/LICENSE +0 -0
- {django_nativemojo-0.1.10.dist-info → django_nativemojo-0.1.16.dist-info}/NOTICE +0 -0
- {django_nativemojo-0.1.10.dist-info → django_nativemojo-0.1.16.dist-info}/WHEEL +0 -0
- /mojo/apps/{notify → aws}/__init__.py +0 -0
- /mojo/apps/{notify/handlers → aws/migrations}/__init__.py +0 -0
- /mojo/apps/{notify/management → docit/markdown_plugins}/__init__.py +0 -0
- /mojo/apps/{notify/providers → docit/migrations}/__init__.py +0 -0
- /mojo/apps/{notify/rest → fileman/migrations}/__init__.py +0 -0
- /mojo/{ws4redis/servers → apps/jobs/examples}/__init__.py +0 -0
- /mojo/apps/{fileman/models/render.py → jobs/migrations/__init__.py} +0 -0
- /mojo/{serializers → rest}/openapi.py +0 -0
- /mojo/{apps/fileman/rest/__init__ → serializers/formats/__init__.py} +0 -0
mojo/ws4redis/websocket.py
DELETED
@@ -1,403 +0,0 @@
|
|
1
|
-
# -*- coding: utf-8 -*-
|
2
|
-
# This code was generously pilfered from https://bitbucket.org/Jeffrey/gevent-websocket
|
3
|
-
# written by Jeffrey Gelens (http://noppo.pro/) and licensed under the Apache License, Version 2.0
|
4
|
-
import struct
|
5
|
-
from socket import error as socket_error
|
6
|
-
from mojo.ws4redis.utf8validator import Utf8Validator
|
7
|
-
from mojo.ws4redis.exceptions import WebSocketError, FrameTooLargeException
|
8
|
-
|
9
|
-
from mojo.helpers.logit import get_logger
|
10
|
-
logger = get_logger("async", filename="async.log")
|
11
|
-
|
12
|
-
|
13
|
-
class WebSocket(object):
|
14
|
-
__slots__ = ('_closed', 'stream', 'utf8validator', 'utf8validate_last')
|
15
|
-
|
16
|
-
OPCODE_CONTINUATION = 0x00
|
17
|
-
OPCODE_TEXT = 0x01
|
18
|
-
OPCODE_BINARY = 0x02
|
19
|
-
OPCODE_CLOSE = 0x08
|
20
|
-
OPCODE_PING = 0x09
|
21
|
-
OPCODE_PONG = 0x0a
|
22
|
-
|
23
|
-
def __init__(self, wsgi_input):
|
24
|
-
self._closed = False
|
25
|
-
self.stream = Stream(wsgi_input)
|
26
|
-
self.utf8validator = Utf8Validator()
|
27
|
-
self.utf8validate_last = None
|
28
|
-
|
29
|
-
def __del__(self):
|
30
|
-
try:
|
31
|
-
self.close()
|
32
|
-
except:
|
33
|
-
# close() may fail if __init__ didn't complete
|
34
|
-
pass
|
35
|
-
|
36
|
-
def _decode_bytes(self, bytestring):
|
37
|
-
"""
|
38
|
-
Internal method used to convert the utf-8 encoded bytestring into unicode.
|
39
|
-
If the conversion fails, the socket will be closed.
|
40
|
-
"""
|
41
|
-
if not bytestring:
|
42
|
-
return u''
|
43
|
-
try:
|
44
|
-
return bytestring.decode('utf-8')
|
45
|
-
except UnicodeDecodeError:
|
46
|
-
self.close(1007)
|
47
|
-
raise
|
48
|
-
|
49
|
-
def _encode_bytes(self, text):
|
50
|
-
"""
|
51
|
-
:returns: The utf-8 byte string equivalent of `text`.
|
52
|
-
"""
|
53
|
-
if isinstance(text, bytes):
|
54
|
-
return text
|
55
|
-
if not isinstance(text, bytes):
|
56
|
-
text = str(text or '')
|
57
|
-
return text.encode('utf-8')
|
58
|
-
|
59
|
-
def _is_valid_close_code(self, code):
|
60
|
-
"""
|
61
|
-
:returns: Whether the returned close code is a valid hybi return code.
|
62
|
-
"""
|
63
|
-
if code < 1000:
|
64
|
-
return False
|
65
|
-
if 1004 <= code <= 1006:
|
66
|
-
return False
|
67
|
-
if 1012 <= code <= 1016:
|
68
|
-
return False
|
69
|
-
if code == 1100:
|
70
|
-
# not sure about this one but the autobahn fuzzer requires it.
|
71
|
-
return False
|
72
|
-
if 2000 <= code <= 2999:
|
73
|
-
return False
|
74
|
-
return True
|
75
|
-
|
76
|
-
def get_file_descriptor(self):
|
77
|
-
"""Return the file descriptor for the given websocket"""
|
78
|
-
return self.stream.fileno
|
79
|
-
|
80
|
-
@property
|
81
|
-
def closed(self):
|
82
|
-
return self._closed
|
83
|
-
|
84
|
-
def handle_close(self, header, payload):
|
85
|
-
"""
|
86
|
-
Called when a close frame has been decoded from the stream.
|
87
|
-
|
88
|
-
:param header: The decoded `Header`.
|
89
|
-
:param payload: The bytestring payload associated with the close frame.
|
90
|
-
"""
|
91
|
-
if not payload:
|
92
|
-
self.close(1000, None)
|
93
|
-
return
|
94
|
-
if len(payload) < 2:
|
95
|
-
raise WebSocketError('Invalid close frame: {0} {1}'.format(header, payload))
|
96
|
-
rv = payload[:2]
|
97
|
-
code = struct.unpack('!H', bytes(rv))[0]
|
98
|
-
payload = payload[2:]
|
99
|
-
if payload:
|
100
|
-
validator = Utf8Validator()
|
101
|
-
val = validator.validate(payload)
|
102
|
-
if not val[0]:
|
103
|
-
raise UnicodeError
|
104
|
-
if not self._is_valid_close_code(code):
|
105
|
-
raise WebSocketError('Invalid close code {0}'.format(code))
|
106
|
-
self.close(code, payload)
|
107
|
-
|
108
|
-
def handle_ping(self, header, payload):
|
109
|
-
self.send_frame(payload, self.OPCODE_PONG)
|
110
|
-
|
111
|
-
def handle_pong(self, header, payload):
|
112
|
-
pass
|
113
|
-
|
114
|
-
def read_frame(self):
|
115
|
-
"""
|
116
|
-
Block until a full frame has been read from the socket.
|
117
|
-
|
118
|
-
This is an internal method as calling this will not cleanup correctly
|
119
|
-
if an exception is called. Use `receive` instead.
|
120
|
-
|
121
|
-
:return: The header and payload as a tuple.
|
122
|
-
"""
|
123
|
-
header = Header.decode_header(self.stream)
|
124
|
-
if header.flags:
|
125
|
-
raise WebSocketError
|
126
|
-
if not header.length:
|
127
|
-
return header, ''
|
128
|
-
try:
|
129
|
-
payload = self.stream.read(header.length)
|
130
|
-
except socket_error:
|
131
|
-
payload = ''
|
132
|
-
except Exception as e:
|
133
|
-
logger.debug("{}: {}".format(type(e), str(e)))
|
134
|
-
payload = ''
|
135
|
-
if len(payload) != header.length:
|
136
|
-
raise WebSocketError('Unexpected EOF reading frame payload')
|
137
|
-
if header.mask:
|
138
|
-
payload = header.unmask_payload(payload)
|
139
|
-
return header, payload
|
140
|
-
|
141
|
-
def validate_utf8(self, payload):
|
142
|
-
# Make sure the frames are decodable independently
|
143
|
-
self.utf8validate_last = self.utf8validator.validate(payload)
|
144
|
-
|
145
|
-
if not self.utf8validate_last[0]:
|
146
|
-
raise UnicodeError("Encountered invalid UTF-8 while processing "
|
147
|
-
"text message at payload octet index "
|
148
|
-
"{0:d}".format(self.utf8validate_last[3]))
|
149
|
-
|
150
|
-
def read_message(self):
|
151
|
-
"""
|
152
|
-
Return the next text or binary message from the socket.
|
153
|
-
|
154
|
-
This is an internal method as calling this will not cleanup correctly
|
155
|
-
if an exception is called. Use `receive` instead.
|
156
|
-
"""
|
157
|
-
opcode = None
|
158
|
-
message = None
|
159
|
-
while True:
|
160
|
-
header, payload = self.read_frame()
|
161
|
-
f_opcode = header.opcode
|
162
|
-
if f_opcode in (self.OPCODE_TEXT, self.OPCODE_BINARY):
|
163
|
-
# a new frame
|
164
|
-
if opcode:
|
165
|
-
raise WebSocketError("The opcode in non-fin frame is expected to be zero, got {0!r}".format(f_opcode))
|
166
|
-
# Start reading a new message, reset the validator
|
167
|
-
self.utf8validator.reset()
|
168
|
-
self.utf8validate_last = (True, True, 0, 0)
|
169
|
-
opcode = f_opcode
|
170
|
-
elif f_opcode == self.OPCODE_CONTINUATION:
|
171
|
-
if not opcode:
|
172
|
-
raise WebSocketError("Unexpected frame with opcode=0")
|
173
|
-
elif f_opcode == self.OPCODE_PING:
|
174
|
-
self.handle_ping(header, payload)
|
175
|
-
continue
|
176
|
-
elif f_opcode == self.OPCODE_PONG:
|
177
|
-
self.handle_pong(header, payload)
|
178
|
-
continue
|
179
|
-
elif f_opcode == self.OPCODE_CLOSE:
|
180
|
-
self.handle_close(header, payload)
|
181
|
-
return
|
182
|
-
else:
|
183
|
-
raise WebSocketError("Unexpected opcode={0!r}".format(f_opcode))
|
184
|
-
if opcode == self.OPCODE_TEXT:
|
185
|
-
self.validate_utf8(payload)
|
186
|
-
payload = payload.decode()
|
187
|
-
if message is None:
|
188
|
-
message = str() if opcode == self.OPCODE_TEXT else bytes()
|
189
|
-
message += payload
|
190
|
-
if header.fin:
|
191
|
-
break
|
192
|
-
if opcode == self.OPCODE_TEXT:
|
193
|
-
self.validate_utf8(message.encode())
|
194
|
-
return message
|
195
|
-
else:
|
196
|
-
return bytearray(message)
|
197
|
-
|
198
|
-
def receive(self):
|
199
|
-
"""
|
200
|
-
Read and return a message from the stream. If `None` is returned, then
|
201
|
-
the socket is considered closed/errored.
|
202
|
-
"""
|
203
|
-
if self._closed:
|
204
|
-
raise WebSocketError("Connection is already closed")
|
205
|
-
try:
|
206
|
-
return self.read_message()
|
207
|
-
except UnicodeError as e:
|
208
|
-
logger.info('websocket.receive: UnicodeError {}'.format(e))
|
209
|
-
self.close(1007)
|
210
|
-
except WebSocketError as e:
|
211
|
-
logger.info('websocket.receive: WebSocketError {}'.format(e))
|
212
|
-
self.close(1002)
|
213
|
-
except Exception as e:
|
214
|
-
logger.info('websocket.receive: Unknown error {}'.format(e))
|
215
|
-
raise e
|
216
|
-
|
217
|
-
def flush(self):
|
218
|
-
"""
|
219
|
-
Flush a websocket. In this implementation intentionally it does nothing.
|
220
|
-
"""
|
221
|
-
pass
|
222
|
-
|
223
|
-
def send_frame(self, message, opcode):
|
224
|
-
"""
|
225
|
-
Send a frame over the websocket with message as its payload
|
226
|
-
"""
|
227
|
-
if self._closed:
|
228
|
-
raise WebSocketError("Connection is already closed")
|
229
|
-
if opcode == self.OPCODE_TEXT:
|
230
|
-
message = self._encode_bytes(message)
|
231
|
-
elif opcode == self.OPCODE_BINARY:
|
232
|
-
message = bytes(message)
|
233
|
-
header = Header.encode_header(True, opcode, '', len(message), 0)
|
234
|
-
try:
|
235
|
-
self.stream.write(header + message)
|
236
|
-
except socket_error:
|
237
|
-
raise WebSocketError("Socket is dead")
|
238
|
-
|
239
|
-
def send(self, message, binary=False):
|
240
|
-
"""
|
241
|
-
Send a frame over the websocket with message as its payload
|
242
|
-
"""
|
243
|
-
if binary is None:
|
244
|
-
binary = not isinstance(message, str)
|
245
|
-
opcode = self.OPCODE_BINARY if binary else self.OPCODE_TEXT
|
246
|
-
try:
|
247
|
-
self.send_frame(message, opcode)
|
248
|
-
except WebSocketError:
|
249
|
-
raise WebSocketError("Socket is dead")
|
250
|
-
|
251
|
-
def close(self, code=1000, message=''):
|
252
|
-
"""
|
253
|
-
Close the websocket and connection, sending the specified code and
|
254
|
-
message. The underlying socket object is _not_ closed, that is the
|
255
|
-
responsibility of the initiator.
|
256
|
-
"""
|
257
|
-
try:
|
258
|
-
message = self._encode_bytes(message)
|
259
|
-
self.send_frame(
|
260
|
-
struct.pack('!H%ds' % len(message), code, message),
|
261
|
-
opcode=self.OPCODE_CLOSE)
|
262
|
-
except WebSocketError:
|
263
|
-
# Failed to write the closing frame but it's ok because we're
|
264
|
-
# closing the socket anyway.
|
265
|
-
logger.debug("Failed to write closing frame -> closing socket")
|
266
|
-
finally:
|
267
|
-
logger.debug("Closed WebSocket")
|
268
|
-
self._closed = True
|
269
|
-
self.stream = None
|
270
|
-
|
271
|
-
|
272
|
-
class Stream(object):
|
273
|
-
"""
|
274
|
-
Wraps the handler's socket/rfile attributes and makes it in to a file like
|
275
|
-
object that can be read from/written to by the lower level websocket api.
|
276
|
-
"""
|
277
|
-
|
278
|
-
__slots__ = ('read', 'write', 'fileno')
|
279
|
-
|
280
|
-
def __init__(self, wsgi_input):
|
281
|
-
self.read = wsgi_input.raw._sock.recv
|
282
|
-
self.write = wsgi_input.raw._sock.sendall
|
283
|
-
self.fileno = wsgi_input.fileno()
|
284
|
-
|
285
|
-
|
286
|
-
class Header(object):
|
287
|
-
__slots__ = ('fin', 'mask', 'opcode', 'flags', 'length')
|
288
|
-
|
289
|
-
FIN_MASK = 0x80
|
290
|
-
OPCODE_MASK = 0x0f
|
291
|
-
MASK_MASK = 0x80
|
292
|
-
LENGTH_MASK = 0x7f
|
293
|
-
RSV0_MASK = 0x40
|
294
|
-
RSV1_MASK = 0x20
|
295
|
-
RSV2_MASK = 0x10
|
296
|
-
|
297
|
-
# bitwise mask that will determine the reserved bits for a frame header
|
298
|
-
HEADER_FLAG_MASK = RSV0_MASK | RSV1_MASK | RSV2_MASK
|
299
|
-
|
300
|
-
def __init__(self, fin=0, opcode=0, flags=0, length=0):
|
301
|
-
self.mask = ''
|
302
|
-
self.fin = fin
|
303
|
-
self.opcode = opcode
|
304
|
-
self.flags = flags
|
305
|
-
self.length = length
|
306
|
-
|
307
|
-
def mask_payload(self, payload):
|
308
|
-
payload = bytearray(payload)
|
309
|
-
mask = bytearray(self.mask)
|
310
|
-
for i in range(self.length):
|
311
|
-
payload[i] ^= mask[i % 4]
|
312
|
-
return bytes(payload)
|
313
|
-
|
314
|
-
# it's the same operation
|
315
|
-
unmask_payload = mask_payload
|
316
|
-
|
317
|
-
def __repr__(self):
|
318
|
-
return ("<Header fin={0} opcode={1} length={2} flags={3} at "
|
319
|
-
"0x{4:x}>").format(self.fin, self.opcode, self.length,
|
320
|
-
self.flags, id(self))
|
321
|
-
|
322
|
-
@classmethod
|
323
|
-
def decode_header(cls, stream):
|
324
|
-
"""
|
325
|
-
Decode a WebSocket header.
|
326
|
-
|
327
|
-
:param stream: A file like object that can be 'read' from.
|
328
|
-
:returns: A `Header` instance.
|
329
|
-
"""
|
330
|
-
read = stream.read
|
331
|
-
data = read(2)
|
332
|
-
if len(data) != 2:
|
333
|
-
raise WebSocketError("Unexpected EOF while decoding header")
|
334
|
-
first_byte, second_byte = struct.unpack('!BB', data)
|
335
|
-
header = cls(
|
336
|
-
fin=first_byte & cls.FIN_MASK == cls.FIN_MASK,
|
337
|
-
opcode=first_byte & cls.OPCODE_MASK,
|
338
|
-
flags=first_byte & cls.HEADER_FLAG_MASK,
|
339
|
-
length=second_byte & cls.LENGTH_MASK)
|
340
|
-
has_mask = second_byte & cls.MASK_MASK == cls.MASK_MASK
|
341
|
-
if header.opcode > 0x07:
|
342
|
-
if not header.fin:
|
343
|
-
raise WebSocketError('Received fragmented control frame: {0!r}'.format(data))
|
344
|
-
# Control frames MUST have a payload length of 125 bytes or less
|
345
|
-
if header.length > 125:
|
346
|
-
raise FrameTooLargeException('Control frame cannot be larger than 125 bytes: {0!r}'.format(data))
|
347
|
-
if header.length == 126:
|
348
|
-
# 16 bit length
|
349
|
-
data = read(2)
|
350
|
-
if len(data) != 2:
|
351
|
-
raise WebSocketError('Unexpected EOF while decoding header')
|
352
|
-
header.length = struct.unpack('!H', data)[0]
|
353
|
-
elif header.length == 127:
|
354
|
-
# 64 bit length
|
355
|
-
data = read(8)
|
356
|
-
if len(data) != 8:
|
357
|
-
raise WebSocketError('Unexpected EOF while decoding header')
|
358
|
-
header.length = struct.unpack('!Q', data)[0]
|
359
|
-
if has_mask:
|
360
|
-
mask = read(4)
|
361
|
-
if len(mask) != 4:
|
362
|
-
raise WebSocketError('Unexpected EOF while decoding header')
|
363
|
-
header.mask = mask
|
364
|
-
return header
|
365
|
-
|
366
|
-
@classmethod
|
367
|
-
def encode_header(cls, fin, opcode, mask, length, flags):
|
368
|
-
"""
|
369
|
-
Encodes a WebSocket header.
|
370
|
-
|
371
|
-
:param fin: Whether this is the final frame for this opcode.
|
372
|
-
:param opcode: The opcode of the payload, see `OPCODE_*`
|
373
|
-
:param mask: Whether the payload is masked.
|
374
|
-
:param length: The length of the frame.
|
375
|
-
:param flags: The RSV* flags.
|
376
|
-
:return: A bytestring encoded header.
|
377
|
-
"""
|
378
|
-
first_byte = opcode
|
379
|
-
second_byte = 0
|
380
|
-
extra = b''
|
381
|
-
if fin:
|
382
|
-
first_byte |= cls.FIN_MASK
|
383
|
-
if flags & cls.RSV0_MASK:
|
384
|
-
first_byte |= cls.RSV0_MASK
|
385
|
-
if flags & cls.RSV1_MASK:
|
386
|
-
first_byte |= cls.RSV1_MASK
|
387
|
-
if flags & cls.RSV2_MASK:
|
388
|
-
first_byte |= cls.RSV2_MASK
|
389
|
-
# now deal with length complexities
|
390
|
-
if length < 126:
|
391
|
-
second_byte += length
|
392
|
-
elif length <= 0xffff:
|
393
|
-
second_byte += 126
|
394
|
-
extra = struct.pack('!H', length)
|
395
|
-
elif length <= 0xffffffffffffffff:
|
396
|
-
second_byte += 127
|
397
|
-
extra = struct.pack('!Q', length)
|
398
|
-
else:
|
399
|
-
raise FrameTooLargeException
|
400
|
-
if mask:
|
401
|
-
second_byte |= cls.MASK_MASK
|
402
|
-
extra += mask
|
403
|
-
return bytes([first_byte, second_byte]) + extra
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|