epicyon/daemon.py

232 lines
7.8 KiB
Python
Raw Normal View History

2019-06-28 18:55:29 +00:00
__filename__ = "daemon.py"
__author__ = "Bob Mottram"
__license__ = "AGPL3+"
__version__ = "0.0.1"
__maintainer__ = "Bob Mottram"
__email__ = "bob@freedombone.net"
__status__ = "Production"
from http.server import BaseHTTPRequestHandler, HTTPServer
#import socketserver
import json
import cgi
from pprint import pprint
from session import createSession
from webfinger import webfingerMeta
from webfinger import webfingerLookup
from person import personLookup
from person import personKeyLookup
2019-06-29 14:35:26 +00:00
from person import personOutboxJson
2019-06-28 21:59:54 +00:00
from inbox import inboxPermittedMessage
2019-06-29 20:21:37 +00:00
from follow import getFollowingFeed
2019-06-28 18:55:29 +00:00
import os
2019-06-28 21:06:05 +00:00
import sys
2019-06-28 18:55:29 +00:00
2019-06-28 21:06:05 +00:00
# Avoid giant messages
maxMessageLength=5000
2019-06-29 14:35:26 +00:00
# maximum number of posts to list in outbox feed
maxPostsInFeed=20
2019-06-29 20:21:37 +00:00
# number of follows/followers per page
2019-06-29 20:34:41 +00:00
followsPerPage=12
2019-06-29 20:21:37 +00:00
2019-06-28 18:55:29 +00:00
def readFollowList(filename: str):
"""Returns a list of ActivityPub addresses to follow
"""
followlist=[]
if not os.path.isfile(filename):
return followlist
followUsers = open(filename, "r")
for u in followUsers:
if u not in followlist:
username,domain = parseHandle(u)
if username:
followlist.append(username+'@'+domain)
2019-06-29 20:21:37 +00:00
followUsers.close()
2019-06-28 18:55:29 +00:00
return followlist
class PubServer(BaseHTTPRequestHandler):
def _set_headers(self,fileFormat):
self.send_response(200)
self.send_header('Content-type', fileFormat)
self.end_headers()
def _404(self):
self.send_response(404)
self.send_header('Content-Type', 'text/html; charset=utf-8')
self.end_headers()
self.wfile.write("<html><head></head><body><h1>404 Not Found</h1></body></html>".encode('utf-8'))
def _webfinger(self) -> bool:
if not self.path.startswith('/.well-known'):
return False
if self.path.startswith('/.well-known/host-meta'):
wfResult=webfingerMeta()
if wfResult:
self._set_headers('application/xrd+xml')
self.wfile.write(wfResult.encode('utf-8'))
return
2019-06-30 22:56:37 +00:00
wfResult=webfingerLookup(self.path,self.server.baseDir)
2019-06-28 18:55:29 +00:00
if wfResult:
self._set_headers('application/json')
self.wfile.write(json.dumps(wfResult).encode('utf-8'))
else:
self._404()
return True
2019-06-28 21:59:54 +00:00
def _permittedDir(self,path):
2019-06-28 18:55:29 +00:00
if path.startswith('/wfendpoints') or \
path.startswith('/keys') or \
path.startswith('/accounts'):
return False
return True
2019-06-30 20:03:23 +00:00
def do_GET(self):
2019-06-29 17:25:09 +00:00
try:
if self.GETbusy:
2019-06-29 17:28:43 +00:00
self.send_response(429)
2019-06-29 17:25:09 +00:00
self.end_headers()
return
except:
pass
self.GETbusy=True
2019-06-30 20:03:23 +00:00
2019-06-28 21:59:54 +00:00
if not self._permittedDir(self.path):
2019-06-28 18:55:29 +00:00
self._404()
2019-06-29 17:25:09 +00:00
self.GETbusy=False
2019-06-28 18:55:29 +00:00
return
# get webfinger endpoint for a person
if self._webfinger():
2019-06-29 17:25:09 +00:00
self.GETbusy=False
2019-06-28 18:55:29 +00:00
return
2019-06-29 14:35:26 +00:00
# get outbox feed for a person
2019-06-30 22:56:37 +00:00
outboxFeed=personOutboxJson(self.server.baseDir,self.server.domain,self.server.port,self.path,self.server.https,maxPostsInFeed)
2019-06-29 14:41:23 +00:00
if outboxFeed:
2019-06-29 14:35:26 +00:00
self._set_headers('application/json')
2019-06-29 14:41:23 +00:00
self.wfile.write(json.dumps(outboxFeed).encode('utf-8'))
2019-06-29 17:25:09 +00:00
self.GETbusy=False
2019-06-29 20:21:37 +00:00
return
2019-07-01 10:25:03 +00:00
following=getFollowingFeed(self.server.baseDir,self.server.domain,self.server.port,self.path,self.server.https,followsPerPage)
2019-06-29 20:21:37 +00:00
if following:
self._set_headers('application/json')
self.wfile.write(json.dumps(following).encode('utf-8'))
self.GETbusy=False
return
2019-07-01 10:25:03 +00:00
followers=getFollowingFeed(self.server.baseDir,self.server.domain,self.server.port,self.path,self.server.https,followsPerPage,'followers')
2019-06-29 20:21:37 +00:00
if followers:
self._set_headers('application/json')
self.wfile.write(json.dumps(followers).encode('utf-8'))
self.GETbusy=False
2019-06-29 14:35:26 +00:00
return
2019-06-28 18:55:29 +00:00
# look up a person
2019-06-30 22:56:37 +00:00
getPerson = personLookup(self.server.domain,self.path,self.server.baseDir)
2019-06-28 18:55:29 +00:00
if getPerson:
self._set_headers('application/json')
self.wfile.write(json.dumps(getPerson).encode('utf-8'))
2019-06-29 17:25:09 +00:00
self.GETbusy=False
2019-06-28 18:55:29 +00:00
return
2019-06-30 22:56:37 +00:00
personKey = personKeyLookup(self.server.domain,self.path,self.server.baseDir)
2019-06-30 15:03:26 +00:00
if personKey:
2019-06-28 18:55:29 +00:00
self._set_headers('text/html; charset=utf-8')
2019-06-30 15:03:26 +00:00
self.wfile.write(personKey.encode('utf-8'))
2019-06-29 17:25:09 +00:00
self.GETbusy=False
2019-06-28 18:55:29 +00:00
return
# check that a json file was requested
if not self.path.endswith('.json'):
self._404()
2019-06-29 17:25:09 +00:00
self.GETbusy=False
2019-06-28 18:55:29 +00:00
return
# check that the file exists
2019-06-30 22:56:37 +00:00
filename=self.server.baseDir+self.path
2019-06-28 18:55:29 +00:00
if os.path.isfile(filename):
self._set_headers('application/json')
with open(filename, 'r', encoding='utf8') as File:
content = File.read()
contentJson=json.loads(content)
self.wfile.write(json.dumps(contentJson).encode('utf8'))
else:
self._404()
2019-06-29 17:25:09 +00:00
self.GETbusy=False
2019-06-28 18:55:29 +00:00
def do_HEAD(self):
self._set_headers('application/json')
def do_POST(self):
2019-07-01 11:48:54 +00:00
print('**************** POST recieved!')
2019-06-29 17:27:32 +00:00
try:
if self.POSTbusy:
2019-06-29 17:28:43 +00:00
self.send_response(429)
2019-06-29 17:27:32 +00:00
self.end_headers()
return
except:
pass
2019-07-01 11:48:54 +00:00
print('**************** POST ready to receive')
2019-06-29 17:27:32 +00:00
self.POSTbusy=True
2019-07-01 11:48:54 +00:00
if not self.headers.get('Content-type'):
print('**************** No Content-type')
self.send_response(400)
self.end_headers()
self.POSTbusy=False
return
print('*****************headers: '+str(self.headers))
2019-06-28 18:55:29 +00:00
# refuse to receive non-json content
2019-07-01 11:48:54 +00:00
if self.headers['Content-type'] != 'application/json':
print("**************** That's no Json!")
2019-06-28 18:55:29 +00:00
self.send_response(400)
self.end_headers()
2019-06-29 17:27:32 +00:00
self.POSTbusy=False
2019-06-28 18:55:29 +00:00
return
# read the message and convert it into a python dictionary
2019-07-01 11:48:54 +00:00
length = int(self.headers['Content-length'])
print('**************** content-length: '+str(length))
2019-06-28 21:06:05 +00:00
if length>maxMessageLength:
self.send_response(400)
self.end_headers()
2019-06-29 17:27:32 +00:00
self.POSTbusy=False
2019-06-28 21:06:05 +00:00
return
2019-07-01 11:48:54 +00:00
print('**************** Reading message')
messageBytes=self.rfile.read(length)
messageJson = json.loads(messageBytes)
2019-06-28 20:35:34 +00:00
2019-07-01 11:48:54 +00:00
if not inboxPermittedMessage(self.server.domain,messageJson,self.server.federationList):
print('**************** Ah Ah Ah')
2019-06-28 21:59:54 +00:00
self.send_response(403)
self.end_headers()
2019-07-01 11:48:54 +00:00
self.POSTbusy=False
return
2019-06-28 18:55:29 +00:00
2019-07-01 11:48:54 +00:00
print('**************** POST valid')
pprint(messageJson)
# add a property to the object, just to mess with data
#message['received'] = 'ok'
# send the message back
#self._set_headers('application/json')
#self.wfile.write(json.dumps(message).encode('utf-8'))
self.send_response(200)
self.end_headers()
2019-06-29 17:27:32 +00:00
self.POSTbusy=False
2019-06-28 18:55:29 +00:00
2019-06-30 19:01:43 +00:00
def runDaemon(domain: str,port=80,https=True,fedList=[],useTor=False) -> None:
2019-06-28 18:55:29 +00:00
if len(domain)==0:
domain='127.0.0.1'
if '.' not in domain:
print('Invalid domain: ' + domain)
return
session = createSession(useTor)
serverAddress = ('', port)
httpd = HTTPServer(serverAddress, PubServer)
2019-06-30 20:03:23 +00:00
httpd.domain=domain
httpd.port=port
httpd.https=https
httpd.federationList=fedList.copy()
2019-06-30 22:56:37 +00:00
httpd.baseDir=os.getcwd()
2019-06-30 20:03:23 +00:00
print('Running ActivityPub daemon on ' + domain + ' port ' + str(port))
2019-06-28 18:55:29 +00:00
httpd.serve_forever()