a
This commit is contained in:
parent
b798d5504d
commit
7a9d98b844
|
@ -25,6 +25,10 @@ object_types = [
|
||||||
'Profile', 'Relationship', 'Tombstone', 'Video'
|
'Profile', 'Relationship', 'Tombstone', 'Video'
|
||||||
]
|
]
|
||||||
|
|
||||||
|
url_keys = [
|
||||||
|
'attributedTo', 'url', 'href', 'object', 'id', 'actor', 'partOf', 'target'
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
def parse_privacy_level(to: list=[], cc: list=[]):
|
def parse_privacy_level(to: list=[], cc: list=[]):
|
||||||
if to == [pubstr] and len(cc) == 1:
|
if to == [pubstr] and len(cc) == 1:
|
||||||
|
@ -48,58 +52,46 @@ def generate_privacy_fields(privacy='public'):
|
||||||
|
|
||||||
|
|
||||||
class Object(DotDict):
|
class Object(DotDict):
|
||||||
@property
|
def __setitem__(self, key, value):
|
||||||
def privacy_level(self):
|
if type(key) == str and key in url_keys:
|
||||||
return parse_privacy_level(self.get('to', []), self.get('cc', []))
|
value = Url(value)
|
||||||
|
|
||||||
|
elif key == 'object' and isinstance(key, dict):
|
||||||
|
value = Object(value)
|
||||||
|
|
||||||
|
super().__setitem__(key, value)
|
||||||
|
|
||||||
|
|
||||||
@property
|
@classmethod
|
||||||
def shared_inbox(self):
|
def new_activity(cls, *args, **kwargs):
|
||||||
try: return self.endpoints.shared_inbox
|
return Activity.new(*args, **kwargs)
|
||||||
except KeyError: pass
|
|
||||||
|
|
||||||
|
|
||||||
@property
|
|
||||||
def pubkey(self):
|
|
||||||
try: return self.publicKey.publicKeyPem
|
|
||||||
except KeyError: pass
|
|
||||||
|
|
||||||
|
|
||||||
@property
|
|
||||||
def handle(self):
|
|
||||||
return self.get('preferredUsername')
|
|
||||||
|
|
||||||
|
|
||||||
@property
|
|
||||||
def display_name(self):
|
|
||||||
return self.get('name')
|
|
||||||
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def new_note(cls, id, url, actor, content, **kwargs):
|
def new_note(cls, id, url, actor, content, **kwargs):
|
||||||
assert False not in map(isinstance, [id, actor, url], [Url])
|
assert False not in map(isinstance, [id, actor, url], [Url])
|
||||||
|
|
||||||
date = kwargs.get('date', DateString.now('activitypub'))
|
if kwargs.get('date'):
|
||||||
|
date = DateString.from_datetime(kwargs['date'], 'activitypub')
|
||||||
|
|
||||||
|
else:
|
||||||
|
date = DateString.now('activitypub')
|
||||||
|
|
||||||
return cls({
|
return cls({
|
||||||
"@context": [
|
"@context": [
|
||||||
"https://www.w3.org/ns/activitystreams",
|
"https://www.w3.org/ns/activitystreams",
|
||||||
{
|
{
|
||||||
"ostatus": "http://ostatus.org#",
|
|
||||||
"atomUri": "ostatus:atomUri",
|
|
||||||
"inReplyToAtomUri": "ostatus:inReplyToAtomUri",
|
|
||||||
"conversation": "ostatus:conversation",
|
|
||||||
"sensitive": "as:sensitive",
|
"sensitive": "as:sensitive",
|
||||||
"toot": "http://joinmastodon.org/ns#",
|
"toot": "http://joinmastodon.org/ns#",
|
||||||
"votersCount": "toot:votersCount",
|
#"votersCount": "toot:votersCount",
|
||||||
"litepub": "http://litepub.social/ns#",
|
#"litepub": "http://litepub.social/ns#",
|
||||||
"directMessage": "litepub:directMessage"
|
#"directMessage": "litepub:directMessage"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"id": id,
|
"id": id,
|
||||||
"type": "Note",
|
"type": "Note",
|
||||||
"summary": kwargs.get('summary'),
|
"summary": kwargs.get('summary'),
|
||||||
"inReplyTo": kwargs.get('replyto'),
|
#"inReplyTo": kwargs.get('replyto'),
|
||||||
"published": date,
|
"published": date,
|
||||||
"url": url,
|
"url": url,
|
||||||
"attributedTo": actor,
|
"attributedTo": actor,
|
||||||
|
@ -110,31 +102,130 @@ class Object(DotDict):
|
||||||
f'{actor}/followers'
|
f'{actor}/followers'
|
||||||
],
|
],
|
||||||
"sensitive": kwargs.get('sensitive', False),
|
"sensitive": kwargs.get('sensitive', False),
|
||||||
"atomUri": id,
|
"content": f'{content}',
|
||||||
"inReplyToAtomUri": kwargs.get('replyto_id'),
|
#"contentMap": {
|
||||||
"conversation": f'tag:{actor.host},{date.dump_to_string("activitypub-date")}:objectId=490:objectType=Conversation',
|
#"en": content
|
||||||
"content": content,
|
#},
|
||||||
"contentMap": {
|
#"attachment": [],
|
||||||
"en": content
|
#"tag": [],
|
||||||
},
|
#"replies": {
|
||||||
"attachment": [],
|
#"id": f"{id}/replies",
|
||||||
"tag": [],
|
#"type": "Collection",
|
||||||
"replies": {
|
#"first": {
|
||||||
"id": f"{id}/replies",
|
#"type": "CollectionPage",
|
||||||
"type": "Collection",
|
#"next": f"{id}/replies?only_other_accounts=true&page=true",
|
||||||
"first": {
|
#"partOf": f"{id}/replies",
|
||||||
"type": "CollectionPage",
|
#"items": []
|
||||||
"next": f"{id}/replies?only_other_accounts=true&page=true",
|
#}
|
||||||
"partOf": f"{id}/replies",
|
#}
|
||||||
"items": []
|
})
|
||||||
|
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def new_actor(cls, actor, handle, pubkey, published=None, table={}, full=True, **kwargs):
|
||||||
|
actor_type = kwargs.get('type', 'Person').title()
|
||||||
|
|
||||||
|
assert actor_type in actor_types
|
||||||
|
|
||||||
|
actor = Url(actor)
|
||||||
|
data = cls({
|
||||||
|
'@context': [
|
||||||
|
'https://www.w3.org/ns/activitystreams',
|
||||||
|
'https://w3id.org/security/v1',
|
||||||
|
{
|
||||||
|
'schema': 'http://schema.org',
|
||||||
|
'toot': 'https://joinmastodon.org/ns#',
|
||||||
|
'manuallyApprovesFollowers': 'as:manuallyApprovesFollowers',
|
||||||
|
'PropertyValue': 'schema:PropertyValue',
|
||||||
|
'value': 'schema:value',
|
||||||
|
#'IdentityProof': 'toot:IdentityProof',
|
||||||
|
'discoverable': 'toot:discoverable',
|
||||||
|
#'Device': 'toot:Device',
|
||||||
|
#'Ed25519Signature': 'toot:Ed25519Signature',
|
||||||
|
#'Ed25519Key': 'toot:Ed25519Key',
|
||||||
|
#'Curve25519Key': 'toot:Curve25519Key',
|
||||||
|
#'EncryptedMessage': 'toot:EncryptedMessage',
|
||||||
|
#'publicKeyBase64': 'toot:publicKeyBase64',
|
||||||
|
#'deviceId': 'toot:deviceId',
|
||||||
|
#'messageFranking': 'toot:messageFranking',
|
||||||
|
'messageType': 'toot:messageType',
|
||||||
|
#'cipherText': 'toot:cipherText',
|
||||||
|
#'suspended': 'toot:suspended',
|
||||||
|
'Emoji': 'toot:Emoji',
|
||||||
|
"alsoKnownAs": {
|
||||||
|
"@id": "as:alsoKnownAs",
|
||||||
|
"@type": "@id"
|
||||||
|
},
|
||||||
|
"movedTo": {
|
||||||
|
"@id": "as:movedTo",
|
||||||
|
"@type": "@id"
|
||||||
|
},
|
||||||
|
"claim": {
|
||||||
|
"@type": "@id",
|
||||||
|
"@id": "toot:claim"
|
||||||
|
},
|
||||||
|
"fingerprintKey": {
|
||||||
|
"@type": "@id",
|
||||||
|
"@id": "toot:fingerprintKey"
|
||||||
|
},
|
||||||
|
"focalPoint": {
|
||||||
|
"@container": "@list",
|
||||||
|
"@id": "toot:focalPoint"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
],
|
||||||
|
'id': actor,
|
||||||
|
'type': actor_type,
|
||||||
|
'inbox': kwargs.get('inbox', f'{actor}'),
|
||||||
|
'preferredUsername': handle,
|
||||||
|
'name': kwargs.get('display_name', handle),
|
||||||
|
'summary': kwargs.get('bio'),
|
||||||
|
'url': kwargs.get('url', actor),
|
||||||
|
'manuallyApprovesFollowers': kwargs.get('locked', False),
|
||||||
|
'discoverable': kwargs.get('discoverable', False),
|
||||||
|
'published': published or DateString.now('activitypub'),
|
||||||
|
'publicKey': {
|
||||||
|
'id': f'{actor}#main-key',
|
||||||
|
'owner': actor,
|
||||||
|
'publicKeyPem': pubkey
|
||||||
|
},
|
||||||
|
'tag': [],
|
||||||
|
'attachment': [],
|
||||||
|
'endpoints': {
|
||||||
|
'sharedInbox': kwargs.get('shared_inbox', f'https://{actor.host}/inbox')
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
for key, value in table.items():
|
||||||
|
data.attachment.append(PropertyValue(key, value))
|
||||||
|
|
||||||
|
if kwargs.get('avatar_url'):
|
||||||
|
data.icon = Object.new_image(kwargs.get('avatar_url'), kwargs.get('avatar_type'))
|
||||||
|
|
||||||
|
if full:
|
||||||
|
data.featured = f'{actor}/collections/featured'
|
||||||
|
data.tags = f'{actor}/collections/tags'
|
||||||
|
data.following = f'{actor}/following'
|
||||||
|
data.followers = f'{actor}/followers'
|
||||||
|
data.outbox = f'{actor}/outbox'
|
||||||
|
|
||||||
|
data['@context'][2].update({
|
||||||
|
"featured": {
|
||||||
|
"@id": "toot:featured",
|
||||||
|
"@type": "@id"
|
||||||
|
},
|
||||||
|
"featuredTags": {
|
||||||
|
"@id": "toot:featuredTags",
|
||||||
|
"@type": "@id"
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return data
|
||||||
|
|
||||||
|
|
||||||
# not complete
|
# not complete
|
||||||
@classmethod
|
@classmethod
|
||||||
def new_actor(cls, actor, handle, pubkey, published=None, table={}, full=True, **kwargs):
|
def new_actor_old(cls, actor, handle, pubkey, published=None, table={}, full=True, **kwargs):
|
||||||
actor_type = kwargs.get('type', 'Person').title()
|
actor_type = kwargs.get('type', 'Person').title()
|
||||||
|
|
||||||
assert actor_type in actor_types
|
assert actor_type in actor_types
|
||||||
|
@ -249,17 +340,13 @@ class Object(DotDict):
|
||||||
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def new_activity(cls, id: str, type: str, actor_src: Union[str, dict], object: Union[str, dict], to: list=[pubstr], cc: list=[]):
|
def new_follow(cls, id, actor, target):
|
||||||
assert type in activity_types
|
return cls({
|
||||||
|
|
||||||
cls({
|
|
||||||
'@context': 'https://www.w3.org/ns/activitystreams',
|
'@context': 'https://www.w3.org/ns/activitystreams',
|
||||||
'type': type,
|
|
||||||
'to': to,
|
|
||||||
'cc': cc,
|
|
||||||
'object': object,
|
|
||||||
'id': id,
|
'id': id,
|
||||||
'actor': actor_src
|
'type': 'Follow',
|
||||||
|
'actor': actor,
|
||||||
|
'object': target
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
|
@ -274,6 +361,100 @@ class Object(DotDict):
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
|
@property
|
||||||
|
def privacy_level(self):
|
||||||
|
return parse_privacy_level(self.get('to', []), self.get('cc', []))
|
||||||
|
|
||||||
|
|
||||||
|
@property
|
||||||
|
def shared_inbox(self):
|
||||||
|
try: return self.endpoints.shared_inbox
|
||||||
|
except KeyError: pass
|
||||||
|
|
||||||
|
|
||||||
|
@property
|
||||||
|
def pubkey(self):
|
||||||
|
try: return self.publicKey.publicKeyPem
|
||||||
|
except KeyError: pass
|
||||||
|
|
||||||
|
|
||||||
|
@property
|
||||||
|
def handle(self):
|
||||||
|
return self.get('preferredUsername')
|
||||||
|
|
||||||
|
|
||||||
|
@property
|
||||||
|
def display_name(self):
|
||||||
|
return self.get('name')
|
||||||
|
|
||||||
|
|
||||||
|
@property
|
||||||
|
def type(self):
|
||||||
|
return self['type'].capitalize()
|
||||||
|
|
||||||
|
|
||||||
|
class Activity(Object):
|
||||||
|
@classmethod
|
||||||
|
def new(cls, id: str, type: str, actor_src: Union[str, dict], object: Union[str, dict], to: list=[pubstr], cc: list=[]):
|
||||||
|
assert type in activity_types
|
||||||
|
|
||||||
|
activity = cls({
|
||||||
|
'@context': 'https://www.w3.org/ns/activitystreams',
|
||||||
|
'id': id,
|
||||||
|
'object': object,
|
||||||
|
'type': type,
|
||||||
|
'actor': actor_src
|
||||||
|
})
|
||||||
|
|
||||||
|
if to:
|
||||||
|
activity.to = to
|
||||||
|
|
||||||
|
if cc:
|
||||||
|
activity.cc = cc
|
||||||
|
|
||||||
|
return activity
|
||||||
|
|
||||||
|
|
||||||
|
class Collection(Object):
|
||||||
|
@classmethod
|
||||||
|
def new_replies(cls, id):
|
||||||
|
{
|
||||||
|
"id": f"{id}/replies",
|
||||||
|
"type": "Collection",
|
||||||
|
"first": {
|
||||||
|
"type": "CollectionPage",
|
||||||
|
"next": f"{id}/replies?only_other_accounts=true&page=true",
|
||||||
|
"partOf": f"{id}/replies",
|
||||||
|
"items": []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
### sub-objects ###
|
||||||
|
|
||||||
|
class PropertyValue(DotDict):
|
||||||
|
def __init__(self, key, value):
|
||||||
|
super().__init__({
|
||||||
|
'type': 'PropertyValue',
|
||||||
|
'name': key,
|
||||||
|
'value': value
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
def __setitem__(self, key, value):
|
||||||
|
key = key.lower()
|
||||||
|
|
||||||
|
assert key in ['type', 'name', 'value']
|
||||||
|
assert type(value) == str
|
||||||
|
|
||||||
|
super().__setitem__(key, value)
|
||||||
|
|
||||||
|
|
||||||
|
def set_pair(self, key, value):
|
||||||
|
self.name = key
|
||||||
|
self.value = value
|
||||||
|
|
||||||
|
|
||||||
class Media(Object):
|
class Media(Object):
|
||||||
@classmethod
|
@classmethod
|
||||||
def new(cls, type, url, mime=None):
|
def new(cls, type, url, mime=None):
|
||||||
|
@ -299,64 +480,18 @@ class Media(Object):
|
||||||
return cls.new('Audio', url, mime)
|
return cls.new('Audio', url, mime)
|
||||||
|
|
||||||
|
|
||||||
class Activity(DotDict):
|
class Emoji(DotDict):
|
||||||
@property
|
|
||||||
def privacy_level(self):
|
|
||||||
return parse_privacy_level(self.get('to', []), self.get('cc', []))
|
|
||||||
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def new_activity(cls, id: str, type: str, actor_src: Union[str, dict], object: Union[str, dict], to: list=[pubstr], cc: list=[]):
|
def new(cls, id, name, image):
|
||||||
assert type in activity_types
|
return cls({
|
||||||
|
|
||||||
cls({
|
|
||||||
'@context': 'https://www.w3.org/ns/activitystreams',
|
|
||||||
'id': id,
|
'id': id,
|
||||||
'object': object,
|
'type': Emoji,
|
||||||
'type': type,
|
'name': f':{name}:',
|
||||||
'to': to,
|
'icon': image
|
||||||
'cc': cc,
|
|
||||||
'actor': actor_src
|
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
class Collection(Object):
|
### Not activitypub objects, but related ###
|
||||||
@classmethod
|
|
||||||
def new_replies(cls, id):
|
|
||||||
{
|
|
||||||
"id": f"{id}/replies",
|
|
||||||
"type": "Collection",
|
|
||||||
"first": {
|
|
||||||
"type": "CollectionPage",
|
|
||||||
"next": f"{id}/replies?only_other_accounts=true&page=true",
|
|
||||||
"partOf": f"{id}/replies",
|
|
||||||
"items": []
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class PropertyValue(DotDict):
|
|
||||||
def __init__(self, key, value):
|
|
||||||
super().__init__({
|
|
||||||
'type': 'PropertyValue',
|
|
||||||
'name': key,
|
|
||||||
'value': value
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
def __setitem__(self, key, value):
|
|
||||||
key = key.lower()
|
|
||||||
|
|
||||||
assert key in ['type', 'name', 'value']
|
|
||||||
assert type(value) == str
|
|
||||||
|
|
||||||
super().__setitem__(key, value)
|
|
||||||
|
|
||||||
|
|
||||||
def set_pair(self, key, value):
|
|
||||||
self.name = key
|
|
||||||
self.value = value
|
|
||||||
|
|
||||||
|
|
||||||
class Nodeinfo(DotDict):
|
class Nodeinfo(DotDict):
|
||||||
@property
|
@property
|
||||||
|
|
|
@ -1,12 +1,9 @@
|
||||||
http_methods = ['CONNECT', 'DELETE', 'GET', 'HEAD', 'OPTIONS', 'PATCH', 'POST', 'PUT', 'TRACE']
|
http_methods = ['CONNECT', 'DELETE', 'GET', 'HEAD', 'OPTIONS', 'PATCH', 'POST', 'PUT', 'TRACE']
|
||||||
applications = {}
|
applications = {}
|
||||||
|
|
||||||
def get_app(name='default'):
|
|
||||||
try:
|
|
||||||
return applications.get(name)
|
|
||||||
|
|
||||||
except KeyError:
|
def get_app(name='default'):
|
||||||
return set_app(Application(appname=name))
|
return applications[name]
|
||||||
|
|
||||||
|
|
||||||
def set_app(app):
|
def set_app(app):
|
||||||
|
|
|
@ -33,6 +33,7 @@ class ApplicationBase:
|
||||||
self.db = None
|
self.db = None
|
||||||
self.router = Router(trim_last_slash=True)
|
self.router = Router(trim_last_slash=True)
|
||||||
self.middleware = DotDict({'request': [], 'response': []})
|
self.middleware = DotDict({'request': [], 'response': []})
|
||||||
|
self.routes = {}
|
||||||
|
|
||||||
for view in views:
|
for view in views:
|
||||||
self.add_view(view)
|
self.add_view(view)
|
||||||
|
@ -61,6 +62,19 @@ class ApplicationBase:
|
||||||
|
|
||||||
def add_route(self, handler, path, method='GET'):
|
def add_route(self, handler, path, method='GET'):
|
||||||
self.router.bind(handler, path, methods=[method.upper()])
|
self.router.bind(handler, path, methods=[method.upper()])
|
||||||
|
self.routes[f'{method.upper()}:{path}'] = handler
|
||||||
|
|
||||||
|
|
||||||
|
def compare_routes(self, route, path, method='GET'):
|
||||||
|
try:
|
||||||
|
return self.get_route(path, method) == self.routes[f'{method.upper()}:{route}']
|
||||||
|
except:
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
async def run_handler(self, request, response, path, method=None, **kwargs):
|
||||||
|
handler = self.get_route(path, method or request.method)
|
||||||
|
return await handler.target(request, response, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
def add_view(self, view):
|
def add_view(self, view):
|
||||||
|
@ -115,9 +129,10 @@ class ApplicationBase:
|
||||||
|
|
||||||
async def handle_request(self, request, response, path=None):
|
async def handle_request(self, request, response, path=None):
|
||||||
if request.host not in self.cfg.hosts and not request.path.startswith('/framework'):
|
if request.host not in self.cfg.hosts and not request.path.startswith('/framework'):
|
||||||
raise error.NotFound(f'Host not handled on this server: {request.host}')
|
raise error.BadRequest(f'Host not handled on this server: {request.host}')
|
||||||
|
|
||||||
handler = self.get_route(path or request.path, request.method)
|
handler = self.get_route(path or request.path, request.method)
|
||||||
|
request._params = handler.params
|
||||||
|
|
||||||
await self.handle_middleware(request)
|
await self.handle_middleware(request)
|
||||||
|
|
||||||
|
|
|
@ -82,10 +82,10 @@ class Config(BaseConfig):
|
||||||
elif key == 'tpl_context' and not getattr(value, '__call__', None):
|
elif key == 'tpl_context' and not getattr(value, '__call__', None):
|
||||||
raise TypeError(f'{key} must be a callable')
|
raise TypeError(f'{key} must be a callable')
|
||||||
|
|
||||||
elif key == 'request_class' and not isinstance(value, Request):
|
elif key == 'request_class' and not issubclass(value, Request):
|
||||||
raise TypeError(f'{key} must be a subclass of izzylib.http_server_async.Request')
|
raise TypeError(f'{key} must be a subclass of izzylib.http_server_async.Request')
|
||||||
|
|
||||||
elif key == 'response_class' and not isinstance(value, Response):
|
elif key == 'response_class' and not issubclass(value, Response):
|
||||||
raise TypeError(f'{key} must be a subclass of izzylib.http_server_async.Response')
|
raise TypeError(f'{key} must be a subclass of izzylib.http_server_async.Response')
|
||||||
|
|
||||||
return value
|
return value
|
||||||
|
|
|
@ -17,8 +17,8 @@ LocalTime = datetime.now(UtcTime).astimezone().tzinfo
|
||||||
|
|
||||||
class Request:
|
class Request:
|
||||||
__slots__ = [
|
__slots__ = [
|
||||||
'_body', '_form', '_reader', '_method', '_app', 'address',
|
'_body', '_form', '_reader', '_method', '_app', '_params',
|
||||||
'path', 'version', 'headers', 'cookies',
|
'address', 'path', 'version', 'headers', 'cookies',
|
||||||
'query', 'raw_query'
|
'query', 'raw_query'
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -32,6 +32,7 @@ class Request:
|
||||||
self._body = b''
|
self._body = b''
|
||||||
self._form = DotDict()
|
self._form = DotDict()
|
||||||
self._method = None
|
self._method = None
|
||||||
|
self._params = None
|
||||||
|
|
||||||
self.headers = Headers()
|
self.headers = Headers()
|
||||||
self.cookies = Cookies()
|
self.cookies = Cookies()
|
||||||
|
@ -41,6 +42,7 @@ class Request:
|
||||||
self.path = None
|
self.path = None
|
||||||
self.version = None
|
self.version = None
|
||||||
self.raw_query = None
|
self.raw_query = None
|
||||||
|
self.log = True
|
||||||
|
|
||||||
|
|
||||||
def __getitem__(self, key):
|
def __getitem__(self, key):
|
||||||
|
@ -84,6 +86,11 @@ class Request:
|
||||||
return self.headers.getone('User-Agent', 'no agent')
|
return self.headers.getone('User-Agent', 'no agent')
|
||||||
|
|
||||||
|
|
||||||
|
@property
|
||||||
|
def accept(self):
|
||||||
|
return self.headers.getone('Accept', '')
|
||||||
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def content_type(self):
|
def content_type(self):
|
||||||
return self.headers.getone('Content-Type', '')
|
return self.headers.getone('Content-Type', '')
|
||||||
|
@ -127,6 +134,11 @@ class Request:
|
||||||
self._method = data.upper()
|
self._method = data.upper()
|
||||||
|
|
||||||
|
|
||||||
|
@property
|
||||||
|
def params(self):
|
||||||
|
return self._params
|
||||||
|
|
||||||
|
|
||||||
async def read(self, length=2048, timeout=None):
|
async def read(self, length=2048, timeout=None):
|
||||||
try: return await asyncio.wait_for(self._reader.read(length), timeout or self.app.cfg.timeout)
|
try: return await asyncio.wait_for(self._reader.read(length), timeout or self.app.cfg.timeout)
|
||||||
except: return
|
except: return
|
||||||
|
@ -205,9 +217,9 @@ class Request:
|
||||||
raise ImportError('Failed to import verify_headers from izzylib.http_signatures.')
|
raise ImportError('Failed to import verify_headers from izzylib.http_signatures.')
|
||||||
|
|
||||||
return verify_headers(
|
return verify_headers(
|
||||||
headers = {k: self.headers.getone(k) for k in request.headers.keys()},
|
headers = {k: self.headers.getone(k) for k in self.headers.keys()},
|
||||||
method = self.method,
|
method = self.method,
|
||||||
path = self.path,
|
path = self.path,
|
||||||
actor = actor,
|
actor = actor,
|
||||||
body = await self.body
|
body = await self.body()
|
||||||
)
|
)
|
||||||
|
|
|
@ -34,10 +34,10 @@ def parse_signature(signature: str):
|
||||||
key, value = part.split('=', 1)
|
key, value = part.split('=', 1)
|
||||||
sig[key.lower()] = value.replace('"', '')
|
sig[key.lower()] = value.replace('"', '')
|
||||||
|
|
||||||
|
sig.actor = Url(sig.keyid.split('#')[0])
|
||||||
sig.headers = sig.headers.split()
|
sig.headers = sig.headers.split()
|
||||||
sig.domain = Url(sig.keyid).host
|
sig.domain = sig.actor.host
|
||||||
sig.top_domain = '.'.join(extract(sig.domain)[1:])
|
sig.top_domain = '.'.join(extract(sig.domain)[1:])
|
||||||
sig.actor = sig.keyid.split('#')[0]
|
|
||||||
|
|
||||||
return sig
|
return sig
|
||||||
|
|
||||||
|
@ -55,7 +55,7 @@ def verify_headers(headers: dict, method: str, path: str, actor: dict, body=None
|
||||||
|
|
||||||
headers = {k.lower(): headers[k] for k in headers}
|
headers = {k.lower(): headers[k] for k in headers}
|
||||||
headers['(request-target)'] = f'{method.lower()} {path}'
|
headers['(request-target)'] = f'{method.lower()} {path}'
|
||||||
signature = parse_signature(headers.get('signature'))
|
signature = Signature(headers.get('signature'))
|
||||||
digest = headers.get('digest')
|
digest = headers.get('digest')
|
||||||
missing_headers = [k for k in headers if k in ['date', 'host'] if headers.get(k) == None]
|
missing_headers = [k for k in headers if k in ['date', 'host'] if headers.get(k) == None]
|
||||||
|
|
||||||
|
@ -181,3 +181,54 @@ def verify_string(string, enc_string, alg='SHA256', fail=False):
|
||||||
|
|
||||||
else:
|
else:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
class Signature(str):
|
||||||
|
__parts = {}
|
||||||
|
|
||||||
|
def __init__(self, signature: str):
|
||||||
|
if not signature:
|
||||||
|
raise AssertionError('Missing signature header')
|
||||||
|
|
||||||
|
split_sig = signature.split(',')
|
||||||
|
|
||||||
|
for part in split_sig:
|
||||||
|
key, value = part.split('=', 1)
|
||||||
|
value = value.replace('"', '')
|
||||||
|
|
||||||
|
self.__parts[key.lower()] = value.split() if key == 'headers' else value
|
||||||
|
|
||||||
|
|
||||||
|
def __new__(cls, signature: str):
|
||||||
|
return str.__new__(cls, signature)
|
||||||
|
|
||||||
|
|
||||||
|
def __new2__(cls, signature: str):
|
||||||
|
data = str.__new__(cls, signature)
|
||||||
|
data.__init__(signature)
|
||||||
|
|
||||||
|
return
|
||||||
|
|
||||||
|
|
||||||
|
def __getattr__(self, key):
|
||||||
|
return self.__parts[key]
|
||||||
|
|
||||||
|
|
||||||
|
@property
|
||||||
|
def sig(self):
|
||||||
|
return self.__parts['signature']
|
||||||
|
|
||||||
|
|
||||||
|
@property
|
||||||
|
def actor(self):
|
||||||
|
return Url(self.keyid.split('#')[0])
|
||||||
|
|
||||||
|
|
||||||
|
@property
|
||||||
|
def domain(self):
|
||||||
|
return self.actor.host
|
||||||
|
|
||||||
|
|
||||||
|
@property
|
||||||
|
def top_domain(self):
|
||||||
|
return '.'.join(extract(self.domain)[1:])
|
||||||
|
|
|
@ -667,6 +667,9 @@ class Url(str):
|
||||||
def __init__(self, url):
|
def __init__(self, url):
|
||||||
parsed = urlparse(url)
|
parsed = urlparse(url)
|
||||||
|
|
||||||
|
if not all([parsed.scheme, parsed.netloc]):
|
||||||
|
raise ValueError('Not a valid url')
|
||||||
|
|
||||||
self.proto = parsed.scheme
|
self.proto = parsed.scheme
|
||||||
self.host = parsed.netloc
|
self.host = parsed.netloc
|
||||||
self.port = self.protocols.get(self.proto) if not parsed.port else None
|
self.port = self.protocols.get(self.proto) if not parsed.port else None
|
||||||
|
|
Loading…
Reference in a new issue