112 lines
4.1 KiB
Python
112 lines
4.1 KiB
Python
from base64 import b64decode
|
|
from cryptography.hazmat.primitives import hashes, serialization
|
|
from cryptography.hazmat.primitives.asymmetric import padding
|
|
from http.server import SimpleHTTPRequestHandler, ThreadingHTTPServer
|
|
from json import dump, load, loads
|
|
from re import search
|
|
from requests import get, post
|
|
from os.path import isfile
|
|
from urllib.parse import quote_plus
|
|
|
|
|
|
domain = 'https://0.exozy.me'
|
|
|
|
|
|
def collection_append(file, item):
|
|
with open(file) as f:
|
|
collection = load(f)
|
|
collection['totalItems'] += 1
|
|
collection['orderedItems'].append(item)
|
|
with open(file, 'w') as f:
|
|
dump(collection, f)
|
|
|
|
|
|
def iri_to_actor(iri):
|
|
if domain in iri:
|
|
name = search(f'^{domain}/users/(.*?)#main-key$', iri).group(1)
|
|
actorfile = f'users/{name}'
|
|
else:
|
|
actorfile = f'users/{quote_plus(iri)}'
|
|
if not isfile(actorfile):
|
|
with open(actorfile, 'w') as f:
|
|
resp = get(iri.removesuffix('#main-key'), headers={'Accept': 'application/activity+json'})
|
|
f.write(resp.text)
|
|
with open(actorfile) as f:
|
|
return load(f)
|
|
|
|
|
|
class fuwuqi(SimpleHTTPRequestHandler):
|
|
def do_POST(self):
|
|
body = self.rfile.read(int(self.headers['Content-Length']))
|
|
activity = loads(body)
|
|
print(activity)
|
|
print(self.headers)
|
|
print(self.path)
|
|
|
|
username = search('^/users/(.*)\.(in|out)box$', self.path).group(1)
|
|
|
|
# Get actor public key
|
|
keyid = search('keyId="(.*?)"', self.headers['Signature']).group(1)
|
|
actor = iri_to_actor(keyid)
|
|
pubkeypem = actor['publicKey']['publicKeyPem'].encode('utf8')
|
|
pubkey = serialization.load_pem_public_key(pubkeypem, None)
|
|
|
|
# Assemble headers
|
|
headers = search('headers="(.*?)"', self.headers['Signature']).group(1)
|
|
message = ''
|
|
for header in headers.split():
|
|
if header == '(request-target)':
|
|
headerval = f'post {self.path}'
|
|
else:
|
|
headerval = self.headers[header]
|
|
message += f'{header}: {headerval}\n'
|
|
|
|
# Verify HTTP signature
|
|
signature = search('signature="(.*?)"', self.headers['Signature']).group(1)
|
|
pubkey.verify(
|
|
b64decode(signature),
|
|
message[:-1].encode('utf8'),
|
|
padding.PKCS1v15(),
|
|
hashes.SHA256()
|
|
)
|
|
|
|
# Make sure activity doer matches HTTP signature
|
|
actor = keyid.removesuffix('#main-key')
|
|
if 'actor' in activity and activity['actor'] != actor:
|
|
self.send_response(401)
|
|
return
|
|
if 'attributedTo' in activity and activity['attributedTo'] != actor:
|
|
self.send_response(401)
|
|
return
|
|
|
|
if self.path.endswith('inbox'):
|
|
# S2S
|
|
collection_append(f'users/{username}.inbox', activity)
|
|
elif self.path.endswith('outbox'):
|
|
# C2S
|
|
collection_append(f'users/{username}.outbox', activity)
|
|
if activity['type'] == 'Create':
|
|
id = activity['id'].split('/')[-1]
|
|
with open(f'users/{username}.statuses/{id}', 'w') as f:
|
|
dump(activity['object'], f)
|
|
# Send to followers
|
|
with open(f'users/{username}.following') as f:
|
|
for followed in load(f)['orderedItems']:
|
|
actor = iri_to_actor(followed)
|
|
self.headers['Host'] = followed.split('/')[2]
|
|
resp = post(actor['inbox'], headers=self.headers, data=body)
|
|
print(resp)
|
|
print(resp.text)
|
|
elif activity['type'] == 'Follow':
|
|
object = iri_to_actor(activity['object'])
|
|
self.headers['Host'] = activity['object'].split('/')[2]
|
|
resp = post(object['inbox'], headers=self.headers, data=body)
|
|
print(resp)
|
|
print(resp.text)
|
|
collection_append(f'users/{username}.following', activity['object'])
|
|
|
|
self.send_response(200)
|
|
self.end_headers()
|
|
|
|
|
|
ThreadingHTTPServer(('localhost', 4200), fuwuqi).serve_forever()
|