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"
|
|
|
|
|
2019-07-07 19:25:38 +00:00
|
|
|
from http.server import BaseHTTPRequestHandler,ThreadingHTTPServer
|
2019-06-28 18:55:29 +00:00
|
|
|
#import socketserver
|
2019-07-06 21:24:47 +00:00
|
|
|
import commentjson
|
2019-06-28 18:55:29 +00:00
|
|
|
import json
|
2019-07-01 14:30:48 +00:00
|
|
|
import time
|
2019-07-25 14:24:42 +00:00
|
|
|
from hashlib import sha256
|
2019-06-28 18:55:29 +00:00
|
|
|
from pprint import pprint
|
|
|
|
from session import createSession
|
|
|
|
from webfinger import webfingerMeta
|
|
|
|
from webfinger import webfingerLookup
|
2019-07-01 21:01:43 +00:00
|
|
|
from webfinger import webfingerHandle
|
2019-06-28 18:55:29 +00:00
|
|
|
from person import personLookup
|
2019-07-04 16:24:23 +00:00
|
|
|
from person import personBoxJson
|
2019-07-11 12:29:31 +00:00
|
|
|
from person import createSharedInbox
|
2019-07-03 22:16:03 +00:00
|
|
|
from posts import outboxMessageCreateWrap
|
2019-07-04 16:24:23 +00:00
|
|
|
from posts import savePostToBox
|
2019-07-15 17:22:51 +00:00
|
|
|
from posts import sendToFollowers
|
|
|
|
from posts import postIsAddressedToPublic
|
2019-07-15 18:20:52 +00:00
|
|
|
from posts import sendToNamedAddresses
|
2019-06-28 21:59:54 +00:00
|
|
|
from inbox import inboxPermittedMessage
|
2019-07-02 15:07:27 +00:00
|
|
|
from inbox import inboxMessageHasParams
|
2019-07-04 12:23:53 +00:00
|
|
|
from inbox import runInboxQueue
|
2019-07-04 14:36:29 +00:00
|
|
|
from inbox import savePostToInboxQueue
|
2019-06-29 20:21:37 +00:00
|
|
|
from follow import getFollowingFeed
|
2019-07-17 10:34:00 +00:00
|
|
|
from follow import outboxUndoFollow
|
2019-07-03 18:24:44 +00:00
|
|
|
from auth import authorize
|
2019-07-16 16:08:21 +00:00
|
|
|
from auth import createPassword
|
2019-07-24 22:38:42 +00:00
|
|
|
from auth import createBasicAuthHeader
|
|
|
|
from auth import authorizeBasic
|
2019-07-04 12:23:53 +00:00
|
|
|
from threads import threadWithTrace
|
2019-07-16 16:08:21 +00:00
|
|
|
from media import getMediaPath
|
|
|
|
from media import createMediaDirs
|
2019-07-17 17:16:48 +00:00
|
|
|
from delete import outboxDelete
|
2019-07-17 19:31:52 +00:00
|
|
|
from like import outboxLike
|
2019-07-17 19:55:24 +00:00
|
|
|
from like import outboxUndoLike
|
2019-07-17 21:40:56 +00:00
|
|
|
from blocking import outboxBlock
|
|
|
|
from blocking import outboxUndoBlock
|
2019-07-18 13:10:26 +00:00
|
|
|
from config import setConfigParam
|
2019-07-18 15:09:23 +00:00
|
|
|
from roles import outboxDelegate
|
2019-07-19 10:01:24 +00:00
|
|
|
from skills import outboxSkills
|
2019-07-19 11:38:37 +00:00
|
|
|
from availability import outboxAvailability
|
2019-07-20 21:13:36 +00:00
|
|
|
from webinterface import htmlIndividualPost
|
|
|
|
from webinterface import htmlProfile
|
|
|
|
from webinterface import htmlInbox
|
|
|
|
from webinterface import htmlOutbox
|
|
|
|
from webinterface import htmlPostReplies
|
2019-07-24 22:38:42 +00:00
|
|
|
from webinterface import htmlLogin
|
|
|
|
from webinterface import htmlGetLoginCredentials
|
2019-07-23 12:33:09 +00:00
|
|
|
from shares import getSharesFeedForPerson
|
2019-07-23 20:00:17 +00:00
|
|
|
from shares import outboxShareUpload
|
2019-07-23 21:14:16 +00:00
|
|
|
from shares import outboxUndoShareUpload
|
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-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-07-23 12:33:09 +00:00
|
|
|
# number of item shares per page
|
|
|
|
sharesPerPage=12
|
|
|
|
|
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:
|
2019-07-03 09:40:27 +00:00
|
|
|
nickname,domain = parseHandle(u)
|
|
|
|
if nickname:
|
|
|
|
followlist.append(nickname+'@'+domain)
|
2019-06-29 20:21:37 +00:00
|
|
|
followUsers.close()
|
2019-06-28 18:55:29 +00:00
|
|
|
return followlist
|
|
|
|
|
|
|
|
class PubServer(BaseHTTPRequestHandler):
|
2019-07-25 09:13:02 +00:00
|
|
|
def _login_headers(self,fileFormat: str) -> None:
|
2019-06-28 18:55:29 +00:00
|
|
|
self.send_response(200)
|
|
|
|
self.send_header('Content-type', fileFormat)
|
2019-07-25 09:13:02 +00:00
|
|
|
self.send_header('Host', self.server.domainFull)
|
2019-07-25 10:56:24 +00:00
|
|
|
self.send_header('WWW-Authenticate', 'title="Login to Epicyon", Basic realm="epicyon"')
|
2019-06-28 18:55:29 +00:00
|
|
|
self.end_headers()
|
|
|
|
|
2019-07-25 14:24:42 +00:00
|
|
|
def _set_headers(self,fileFormat: str) -> None:
|
2019-07-25 09:13:02 +00:00
|
|
|
self.send_response(200)
|
|
|
|
self.send_header('Content-type', fileFormat)
|
|
|
|
self.send_header('Host', self.server.domainFull)
|
|
|
|
self.end_headers()
|
|
|
|
|
2019-07-03 22:16:03 +00:00
|
|
|
def _404(self) -> None:
|
2019-06-28 18:55:29 +00:00
|
|
|
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'))
|
|
|
|
|
2019-07-25 14:24:42 +00:00
|
|
|
def _webfinger(self) -> bool:
|
2019-06-28 18:55:29 +00:00
|
|
|
if not self.path.startswith('/.well-known'):
|
|
|
|
return False
|
2019-07-04 14:36:29 +00:00
|
|
|
if self.server.debug:
|
|
|
|
print('DEBUG: WEBFINGER well-known')
|
2019-06-28 18:55:29 +00:00
|
|
|
|
2019-07-03 16:14:45 +00:00
|
|
|
if self.server.debug:
|
|
|
|
print('DEBUG: WEBFINGER host-meta')
|
2019-06-28 18:55:29 +00:00
|
|
|
if self.path.startswith('/.well-known/host-meta'):
|
|
|
|
wfResult=webfingerMeta()
|
|
|
|
if wfResult:
|
2019-07-25 14:24:42 +00:00
|
|
|
self._set_headers('application/xrd+xml')
|
2019-06-28 18:55:29 +00:00
|
|
|
self.wfile.write(wfResult.encode('utf-8'))
|
|
|
|
return
|
|
|
|
|
2019-07-03 16:14:45 +00:00
|
|
|
if self.server.debug:
|
|
|
|
print('DEBUG: WEBFINGER lookup '+self.path+' '+str(self.server.baseDir))
|
2019-07-19 14:41:32 +00:00
|
|
|
wfResult=webfingerLookup(self.path,self.server.baseDir,self.server.port,self.server.debug)
|
2019-06-28 18:55:29 +00:00
|
|
|
if wfResult:
|
2019-07-25 14:24:42 +00:00
|
|
|
self._set_headers('application/jrd+json')
|
2019-06-28 18:55:29 +00:00
|
|
|
self.wfile.write(json.dumps(wfResult).encode('utf-8'))
|
|
|
|
else:
|
2019-07-03 16:14:45 +00:00
|
|
|
if self.server.debug:
|
|
|
|
print('DEBUG: WEBFINGER lookup 404 '+self.path)
|
2019-06-28 18:55:29 +00:00
|
|
|
self._404()
|
|
|
|
return True
|
|
|
|
|
2019-07-03 22:16:03 +00:00
|
|
|
def _permittedDir(self,path: str) -> bool:
|
|
|
|
"""These are special paths which should not be accessible
|
|
|
|
directly via GET or POST
|
|
|
|
"""
|
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-07-05 11:41:09 +00:00
|
|
|
def _postToOutbox(self,messageJson: {}) -> bool:
|
2019-07-03 22:16:03 +00:00
|
|
|
"""post is received by the outbox
|
|
|
|
Client to server message post
|
|
|
|
https://www.w3.org/TR/activitypub/#client-to-server-outbox-delivery
|
|
|
|
"""
|
2019-07-03 22:59:56 +00:00
|
|
|
if not messageJson.get('type'):
|
2019-07-04 10:02:56 +00:00
|
|
|
if self.server.debug:
|
|
|
|
print('DEBUG: POST to outbox has no "type" parameter')
|
2019-07-03 22:59:56 +00:00
|
|
|
return False
|
|
|
|
if not messageJson.get('object') and messageJson.get('content'):
|
|
|
|
if messageJson['type']!='Create':
|
|
|
|
# https://www.w3.org/TR/activitypub/#object-without-create
|
|
|
|
if self.server.debug:
|
2019-07-04 10:02:56 +00:00
|
|
|
print('DEBUG: POST to outbox - adding Create wrapper')
|
2019-07-03 22:59:56 +00:00
|
|
|
messageJson= \
|
|
|
|
outboxMessageCreateWrap(self.server.httpPrefix, \
|
|
|
|
self.postToNickname, \
|
2019-07-16 10:19:04 +00:00
|
|
|
self.server.domain, \
|
|
|
|
self.server.port, \
|
|
|
|
messageJson)
|
2019-07-03 22:44:03 +00:00
|
|
|
if messageJson['type']=='Create':
|
|
|
|
if not (messageJson.get('id') and \
|
|
|
|
messageJson.get('type') and \
|
|
|
|
messageJson.get('actor') and \
|
|
|
|
messageJson.get('object') and \
|
|
|
|
messageJson.get('to')):
|
|
|
|
if self.server.debug:
|
2019-07-16 10:19:04 +00:00
|
|
|
pprint(messageJson)
|
2019-07-04 10:02:56 +00:00
|
|
|
print('DEBUG: POST to outbox - Create does not have the required parameters')
|
2019-07-03 22:44:03 +00:00
|
|
|
return False
|
|
|
|
# https://www.w3.org/TR/activitypub/#create-activity-outbox
|
|
|
|
messageJson['object']['attributedTo']=messageJson['actor']
|
2019-07-16 16:08:21 +00:00
|
|
|
if messageJson['object'].get('attachment'):
|
|
|
|
attachmentIndex=0
|
|
|
|
if messageJson['object']['attachment'][attachmentIndex].get('mediaType'):
|
|
|
|
fileExtension='png'
|
|
|
|
if messageJson['object']['attachment'][attachmentIndex]['mediaType'].endswith('jpeg'):
|
|
|
|
fileExtension='jpg'
|
|
|
|
if messageJson['object']['attachment'][attachmentIndex]['mediaType'].endswith('gif'):
|
|
|
|
fileExtension='gif'
|
|
|
|
mediaDir=self.server.baseDir+'/accounts/'+self.postToNickname+'@'+self.server.domain
|
|
|
|
uploadMediaFilename=mediaDir+'/upload.'+fileExtension
|
|
|
|
if not os.path.isfile(uploadMediaFilename):
|
|
|
|
del messageJson['object']['attachment']
|
|
|
|
else:
|
|
|
|
# generate a path for the uploaded image
|
|
|
|
mPath=getMediaPath()
|
|
|
|
mediaPath=mPath+'/'+createPassword(32)+'.'+fileExtension
|
|
|
|
createMediaDirs(self.server.baseDir,mPath)
|
|
|
|
mediaFilename=self.server.baseDir+'/'+mediaPath
|
|
|
|
# move the uploaded image to its new path
|
|
|
|
os.rename(uploadMediaFilename,mediaFilename)
|
|
|
|
# change the url of the attachment
|
|
|
|
messageJson['object']['attachment'][attachmentIndex]['url']= \
|
|
|
|
self.server.httpPrefix+'://'+self.server.domain+'/'+mediaPath
|
|
|
|
|
2019-07-06 17:00:22 +00:00
|
|
|
permittedOutboxTypes=[
|
|
|
|
'Create','Announce','Like','Follow','Undo', \
|
2019-07-18 15:09:23 +00:00
|
|
|
'Update','Add','Remove','Block','Delete', \
|
2019-07-19 10:01:24 +00:00
|
|
|
'Delegate','Skill'
|
2019-07-06 17:00:22 +00:00
|
|
|
]
|
2019-07-03 22:44:03 +00:00
|
|
|
if messageJson['type'] not in permittedOutboxTypes:
|
|
|
|
if self.server.debug:
|
2019-07-06 17:00:22 +00:00
|
|
|
print('DEBUG: POST to outbox - '+messageJson['type']+ \
|
|
|
|
' is not a permitted activity type')
|
2019-07-03 22:16:03 +00:00
|
|
|
return False
|
2019-07-03 22:59:56 +00:00
|
|
|
if messageJson.get('id'):
|
2019-07-16 21:38:06 +00:00
|
|
|
postId=messageJson['id'].replace('/activity','')
|
2019-07-18 11:35:48 +00:00
|
|
|
if self.server.debug:
|
|
|
|
print('DEBUG: id attribute exists within POST to outbox')
|
2019-07-03 22:59:56 +00:00
|
|
|
else:
|
2019-07-18 11:35:48 +00:00
|
|
|
if self.server.debug:
|
|
|
|
print('DEBUG: No id attribute within POST to outbox')
|
2019-07-03 22:59:56 +00:00
|
|
|
postId=None
|
2019-07-16 10:19:04 +00:00
|
|
|
if self.server.debug:
|
|
|
|
pprint(messageJson)
|
2019-07-17 17:16:48 +00:00
|
|
|
print('DEBUG: savePostToBox')
|
2019-07-18 11:35:48 +00:00
|
|
|
domainFull=self.server.domain
|
|
|
|
if self.server.port!=80 and self.server.port!=443:
|
|
|
|
domainFull=self.server.domain+':'+str(self.server.port)
|
2019-07-16 10:19:04 +00:00
|
|
|
savePostToBox(self.server.baseDir, \
|
|
|
|
self.server.httpPrefix, \
|
|
|
|
postId, \
|
2019-07-15 18:20:52 +00:00
|
|
|
self.postToNickname, \
|
2019-07-18 11:35:48 +00:00
|
|
|
domainFull,messageJson,'outbox')
|
2019-07-16 10:19:04 +00:00
|
|
|
if not self.server.session:
|
2019-07-16 10:20:03 +00:00
|
|
|
if self.server.debug:
|
|
|
|
print('DEBUG: creating new session for c2s')
|
2019-07-16 10:19:04 +00:00
|
|
|
self.server.session= \
|
|
|
|
createSession(self.server.domain,self.server.port,self.server.useTor)
|
2019-07-15 17:22:51 +00:00
|
|
|
if self.server.debug:
|
|
|
|
print('DEBUG: sending c2s post to followers')
|
|
|
|
sendToFollowers(self.server.session,self.server.baseDir, \
|
2019-07-16 10:19:04 +00:00
|
|
|
self.postToNickname,self.server.domain, \
|
|
|
|
self.server.port, \
|
|
|
|
self.server.httpPrefix, \
|
|
|
|
self.server.federationList, \
|
|
|
|
self.server.sendThreads, \
|
|
|
|
self.server.postLog, \
|
|
|
|
self.server.cachedWebfingers, \
|
|
|
|
self.server.personCache, \
|
|
|
|
messageJson,self.server.debug)
|
2019-07-17 10:34:00 +00:00
|
|
|
if self.server.debug:
|
|
|
|
print('DEBUG: handle any unfollow requests')
|
|
|
|
outboxUndoFollow(self.server.baseDir,messageJson,self.server.debug)
|
2019-07-18 15:09:23 +00:00
|
|
|
if self.server.debug:
|
|
|
|
print('DEBUG: handle delegation requests')
|
2019-07-19 10:01:24 +00:00
|
|
|
outboxDelegate(self.server.baseDir,self.postToNickname,messageJson,self.server.debug)
|
|
|
|
if self.server.debug:
|
2019-07-19 11:38:37 +00:00
|
|
|
print('DEBUG: handle skills changes requests')
|
2019-07-19 10:01:24 +00:00
|
|
|
outboxSkills(self.server.baseDir,self.postToNickname,messageJson,self.server.debug)
|
2019-07-19 11:38:37 +00:00
|
|
|
if self.server.debug:
|
|
|
|
print('DEBUG: handle availability changes requests')
|
|
|
|
outboxAvailability(self.server.baseDir,self.postToNickname,messageJson,self.server.debug)
|
2019-07-17 19:31:52 +00:00
|
|
|
if self.server.debug:
|
|
|
|
print('DEBUG: handle any like requests')
|
|
|
|
outboxLike(self.server.baseDir,self.server.httpPrefix, \
|
|
|
|
self.postToNickname,self.server.domain,self.server.port, \
|
|
|
|
messageJson,self.server.debug)
|
2019-07-17 19:55:24 +00:00
|
|
|
if self.server.debug:
|
|
|
|
print('DEBUG: handle any undo like requests')
|
|
|
|
outboxUndoLike(self.server.baseDir,self.server.httpPrefix, \
|
|
|
|
self.postToNickname,self.server.domain,self.server.port, \
|
|
|
|
messageJson,self.server.debug)
|
2019-07-17 17:58:08 +00:00
|
|
|
if self.server.debug:
|
|
|
|
print('DEBUG: handle delete requests')
|
2019-07-17 18:05:07 +00:00
|
|
|
outboxDelete(self.server.baseDir,self.server.httpPrefix, \
|
|
|
|
self.postToNickname,self.server.domain, \
|
|
|
|
messageJson,self.server.debug)
|
2019-07-17 21:40:56 +00:00
|
|
|
if self.server.debug:
|
|
|
|
print('DEBUG: handle block requests')
|
|
|
|
outboxBlock(self.server.baseDir,self.server.httpPrefix, \
|
|
|
|
self.postToNickname,self.server.domain, \
|
|
|
|
self.server.port,
|
|
|
|
messageJson,self.server.debug)
|
|
|
|
if self.server.debug:
|
|
|
|
print('DEBUG: handle undo block requests')
|
|
|
|
outboxUndoBlock(self.server.baseDir,self.server.httpPrefix, \
|
|
|
|
self.postToNickname,self.server.domain, \
|
|
|
|
self.server.port,
|
|
|
|
messageJson,self.server.debug)
|
2019-07-23 20:00:17 +00:00
|
|
|
if self.server.debug:
|
|
|
|
print('DEBUG: handle share uploads')
|
|
|
|
outboxShareUpload(self.server.baseDir,self.server.httpPrefix, \
|
|
|
|
self.postToNickname,self.server.domain, \
|
|
|
|
self.server.port,
|
|
|
|
messageJson,self.server.debug)
|
2019-07-23 21:14:16 +00:00
|
|
|
if self.server.debug:
|
|
|
|
print('DEBUG: handle undo share uploads')
|
|
|
|
outboxUndoShareUpload(self.server.baseDir,self.server.httpPrefix, \
|
|
|
|
self.postToNickname,self.server.domain, \
|
|
|
|
self.server.port,
|
|
|
|
messageJson,self.server.debug)
|
2019-07-15 18:20:52 +00:00
|
|
|
if self.server.debug:
|
|
|
|
print('DEBUG: sending c2s post to named addresses')
|
2019-07-16 10:19:04 +00:00
|
|
|
print('c2s sender: '+self.postToNickname+'@'+self.server.domain+':'+str(self.server.port))
|
2019-07-15 18:20:52 +00:00
|
|
|
sendToNamedAddresses(self.server.session,self.server.baseDir, \
|
|
|
|
self.postToNickname,self.server.domain, \
|
|
|
|
self.server.port, \
|
|
|
|
self.server.httpPrefix, \
|
|
|
|
self.server.federationList, \
|
|
|
|
self.server.sendThreads, \
|
|
|
|
self.server.postLog, \
|
|
|
|
self.server.cachedWebfingers, \
|
|
|
|
self.server.personCache, \
|
|
|
|
messageJson,self.server.debug)
|
2019-07-03 22:16:03 +00:00
|
|
|
return True
|
|
|
|
|
2019-07-15 12:27:26 +00:00
|
|
|
def _updateInboxQueue(self,nickname: str,messageJson: {}) -> int:
|
2019-07-05 11:27:18 +00:00
|
|
|
"""Update the inbox queue
|
|
|
|
"""
|
2019-07-15 12:27:26 +00:00
|
|
|
# Check if the queue is full
|
|
|
|
if len(self.server.inboxQueue)>=self.server.maxQueueLength:
|
|
|
|
return 1
|
|
|
|
|
2019-07-18 11:35:48 +00:00
|
|
|
domainFull=self.server.domain
|
|
|
|
if self.server.port!=80 and self.server.port!=443:
|
|
|
|
domainFull=self.server.domain+':'+str(self.server.port)
|
|
|
|
|
|
|
|
# save the json for later queue processing
|
2019-07-06 13:49:25 +00:00
|
|
|
queueFilename = \
|
2019-07-05 11:27:18 +00:00
|
|
|
savePostToInboxQueue(self.server.baseDir, \
|
|
|
|
self.server.httpPrefix, \
|
|
|
|
nickname, \
|
2019-07-18 11:35:48 +00:00
|
|
|
domainFull, \
|
2019-07-05 11:27:18 +00:00
|
|
|
messageJson,
|
|
|
|
self.headers['host'],
|
2019-07-05 22:13:20 +00:00
|
|
|
self.headers['signature'],
|
2019-07-06 13:49:25 +00:00
|
|
|
'/'+self.path.split('/')[-1],
|
|
|
|
self.server.debug)
|
|
|
|
if queueFilename:
|
2019-07-15 12:27:26 +00:00
|
|
|
# add json to the queue
|
2019-07-06 13:49:25 +00:00
|
|
|
if queueFilename not in self.server.inboxQueue:
|
|
|
|
self.server.inboxQueue.append(queueFilename)
|
2019-07-05 11:27:18 +00:00
|
|
|
self.send_response(201)
|
|
|
|
self.end_headers()
|
|
|
|
self.server.POSTbusy=False
|
2019-07-15 12:27:26 +00:00
|
|
|
return 0
|
|
|
|
return 2
|
2019-07-05 11:27:18 +00:00
|
|
|
|
2019-07-12 11:05:43 +00:00
|
|
|
def _isAuthorized(self) -> bool:
|
2019-07-25 14:24:42 +00:00
|
|
|
# token based authenticated used by the web interface
|
|
|
|
if self.headers.get('Cookie'):
|
|
|
|
if '=' in self.headers['Cookie']:
|
|
|
|
tokenStr=self.headers['Cookie'].split('=',1)[1]
|
|
|
|
if self.server.tokensLookup.get(tokenStr):
|
|
|
|
nickname=self.server.tokensLookup[tokenStr]
|
|
|
|
if '/'+nickname+'/' in self.path:
|
|
|
|
return True
|
|
|
|
if self.path.endswith('/'+nickname):
|
|
|
|
return True
|
|
|
|
return False
|
|
|
|
# basic auth
|
2019-07-12 11:05:43 +00:00
|
|
|
if self.headers.get('Authorization'):
|
|
|
|
if authorize(self.server.baseDir,self.path, \
|
|
|
|
self.headers['Authorization'], \
|
|
|
|
self.server.debug):
|
|
|
|
return True
|
|
|
|
return False
|
|
|
|
|
2019-07-25 11:18:35 +00:00
|
|
|
def do_GET(self):
|
2019-07-03 16:14:45 +00:00
|
|
|
if self.server.debug:
|
2019-07-06 17:00:22 +00:00
|
|
|
print('DEBUG: GET from '+self.server.baseDir+ \
|
|
|
|
' path: '+self.path+' busy: '+ \
|
|
|
|
str(self.server.GETbusy))
|
2019-07-25 11:18:35 +00:00
|
|
|
|
2019-07-25 14:24:42 +00:00
|
|
|
if self.server.debug:
|
|
|
|
print(str(self.headers))
|
|
|
|
|
|
|
|
# check authorization
|
2019-07-25 11:18:35 +00:00
|
|
|
authorized = self._isAuthorized()
|
|
|
|
if authorized:
|
|
|
|
if self.server.debug:
|
2019-07-25 14:24:42 +00:00
|
|
|
print('GET Authorization granted')
|
2019-07-25 11:18:35 +00:00
|
|
|
else:
|
|
|
|
if self.server.debug:
|
2019-07-25 14:24:42 +00:00
|
|
|
print('GET Not authorized')
|
2019-07-25 11:18:35 +00:00
|
|
|
|
2019-07-21 09:09:28 +00:00
|
|
|
# get css
|
|
|
|
# Note that this comes before the busy flag to avoid conflicts
|
|
|
|
if self.path.endswith('.css'):
|
2019-07-24 11:03:56 +00:00
|
|
|
if os.path.isfile('epicyon-profile.css'):
|
|
|
|
with open('epicyon-profile.css', 'r') as cssfile:
|
2019-07-21 09:09:28 +00:00
|
|
|
css = cssfile.read()
|
2019-07-25 14:24:42 +00:00
|
|
|
self._set_headers('text/css')
|
2019-07-21 09:09:28 +00:00
|
|
|
self.wfile.write(css.encode('utf-8'))
|
|
|
|
return
|
2019-07-24 22:38:42 +00:00
|
|
|
# image on login screen
|
|
|
|
if self.path=='/login.png':
|
|
|
|
mediaFilename= \
|
|
|
|
self.server.baseDir+'/accounts/login.png'
|
|
|
|
if os.path.isfile(mediaFilename):
|
2019-07-25 14:24:42 +00:00
|
|
|
self._set_headers('image/png')
|
2019-07-24 22:38:42 +00:00
|
|
|
with open(mediaFilename, 'rb') as avFile:
|
|
|
|
mediaBinary = avFile.read()
|
|
|
|
self.wfile.write(mediaBinary)
|
|
|
|
return
|
2019-07-12 19:33:34 +00:00
|
|
|
# show media
|
2019-07-22 09:52:43 +00:00
|
|
|
# Note that this comes before the busy flag to avoid conflicts
|
2019-07-12 19:33:34 +00:00
|
|
|
if '/media/' in self.path:
|
|
|
|
if self.path.endswith('.png') or \
|
|
|
|
self.path.endswith('.jpg') or \
|
|
|
|
self.path.endswith('.gif'):
|
|
|
|
mediaStr=self.path.split('/media/')[1]
|
|
|
|
mediaFilename= \
|
|
|
|
self.server.baseDir+'/media/'+mediaStr
|
|
|
|
if os.path.isfile(mediaFilename):
|
|
|
|
if mediaFilename.endswith('.png'):
|
2019-07-25 14:24:42 +00:00
|
|
|
self._set_headers('image/png')
|
2019-07-12 19:33:34 +00:00
|
|
|
elif mediaFilename.endswith('.jpg'):
|
2019-07-25 14:24:42 +00:00
|
|
|
self._set_headers('image/jpeg')
|
2019-07-12 19:33:34 +00:00
|
|
|
else:
|
2019-07-25 14:24:42 +00:00
|
|
|
self._set_headers('image/gif')
|
2019-07-12 19:33:34 +00:00
|
|
|
with open(mediaFilename, 'rb') as avFile:
|
|
|
|
mediaBinary = avFile.read()
|
|
|
|
self.wfile.write(mediaBinary)
|
|
|
|
return
|
2019-07-16 16:10:52 +00:00
|
|
|
self._404()
|
|
|
|
return
|
2019-07-23 12:33:09 +00:00
|
|
|
# show shared item images
|
|
|
|
# Note that this comes before the busy flag to avoid conflicts
|
|
|
|
if '/sharefiles/' in self.path:
|
|
|
|
if self.path.endswith('.png') or \
|
|
|
|
self.path.endswith('.jpg') or \
|
|
|
|
self.path.endswith('.gif'):
|
|
|
|
mediaStr=self.path.split('/sharefiles/')[1]
|
|
|
|
mediaFilename= \
|
|
|
|
self.server.baseDir+'/sharefiles/'+mediaStr
|
|
|
|
if os.path.isfile(mediaFilename):
|
|
|
|
if mediaFilename.endswith('.png'):
|
2019-07-25 14:24:42 +00:00
|
|
|
self._set_headers('image/png')
|
2019-07-23 12:33:09 +00:00
|
|
|
elif mediaFilename.endswith('.jpg'):
|
2019-07-25 14:24:42 +00:00
|
|
|
self._set_headers('image/jpeg')
|
2019-07-23 12:33:09 +00:00
|
|
|
else:
|
2019-07-25 14:24:42 +00:00
|
|
|
self._set_headers('image/gif')
|
2019-07-23 12:33:09 +00:00
|
|
|
with open(mediaFilename, 'rb') as avFile:
|
|
|
|
mediaBinary = avFile.read()
|
|
|
|
self.wfile.write(mediaBinary)
|
|
|
|
return
|
|
|
|
self._404()
|
|
|
|
return
|
2019-07-12 16:09:25 +00:00
|
|
|
# show avatar or background image
|
2019-07-22 09:52:43 +00:00
|
|
|
# Note that this comes before the busy flag to avoid conflicts
|
2019-07-12 16:03:01 +00:00
|
|
|
if '/users/' in self.path:
|
|
|
|
if self.path.endswith('.png') or \
|
|
|
|
self.path.endswith('.jpg') or \
|
|
|
|
self.path.endswith('.gif'):
|
|
|
|
avatarStr=self.path.split('/users/')[1]
|
|
|
|
if '/' in avatarStr:
|
|
|
|
avatarNickname=avatarStr.split('/')[0]
|
|
|
|
avatarFile=avatarStr.split('/')[1]
|
|
|
|
avatarFilename= \
|
|
|
|
self.server.baseDir+'/accounts/'+ \
|
|
|
|
avatarNickname+'@'+ \
|
|
|
|
self.server.domain+'/'+avatarFile
|
|
|
|
if os.path.isfile(avatarFilename):
|
|
|
|
if avatarFile.endswith('.png'):
|
2019-07-25 14:24:42 +00:00
|
|
|
self._set_headers('image/png')
|
2019-07-12 16:03:01 +00:00
|
|
|
elif avatarFile.endswith('.jpg'):
|
2019-07-25 14:24:42 +00:00
|
|
|
self._set_headers('image/jpeg')
|
2019-07-12 16:03:01 +00:00
|
|
|
else:
|
2019-07-25 14:24:42 +00:00
|
|
|
self._set_headers('image/gif')
|
2019-07-12 16:03:01 +00:00
|
|
|
with open(avatarFilename, 'rb') as avFile:
|
2019-07-12 19:33:34 +00:00
|
|
|
avBinary = avFile.read()
|
|
|
|
self.wfile.write(avBinary)
|
2019-07-22 15:14:39 +00:00
|
|
|
return
|
|
|
|
|
|
|
|
# This busy state helps to avoid flooding
|
|
|
|
# Resources which are expected to be called from a web page
|
|
|
|
# should be above this
|
2019-07-22 09:52:43 +00:00
|
|
|
if self.server.GETbusy:
|
|
|
|
currTimeGET=int(time.time())
|
|
|
|
if currTimeGET-self.server.lastGET<10:
|
|
|
|
if self.server.debug:
|
|
|
|
print('DEBUG: GET Busy')
|
|
|
|
self.send_response(429)
|
2019-07-25 11:18:35 +00:00
|
|
|
if authorized:
|
2019-07-25 14:24:42 +00:00
|
|
|
self.send_header('Authorization')
|
2019-07-22 09:52:43 +00:00
|
|
|
self.end_headers()
|
|
|
|
return
|
|
|
|
self.server.lastGET=currTimeGET
|
|
|
|
self.server.GETbusy=True
|
|
|
|
|
|
|
|
if not self._permittedDir(self.path):
|
|
|
|
if self.server.debug:
|
|
|
|
print('DEBUG: GET Not permitted')
|
|
|
|
self._404()
|
|
|
|
self.server.GETbusy=False
|
|
|
|
return
|
|
|
|
# get webfinger endpoint for a person
|
2019-07-25 14:24:42 +00:00
|
|
|
if self._webfinger():
|
2019-07-22 09:52:43 +00:00
|
|
|
self.server.GETbusy=False
|
|
|
|
return
|
2019-07-24 22:38:42 +00:00
|
|
|
|
|
|
|
if self.path.startswith('/login'):
|
|
|
|
# request basic auth
|
2019-07-25 09:13:02 +00:00
|
|
|
self._login_headers('text/html')
|
2019-07-24 22:38:42 +00:00
|
|
|
self.wfile.write(htmlLogin(self.server.baseDir).encode('utf-8'))
|
|
|
|
self.server.GETbusy=False
|
|
|
|
return
|
|
|
|
|
2019-07-06 21:33:46 +00:00
|
|
|
# get an individual post from the path /@nickname/statusnumber
|
|
|
|
if '/@' in self.path:
|
|
|
|
namedStatus=self.path.split('/@')[1]
|
2019-07-19 13:32:58 +00:00
|
|
|
if '/' not in namedStatus:
|
|
|
|
# show actor
|
|
|
|
nickname=namedStatus
|
|
|
|
else:
|
2019-07-06 21:33:46 +00:00
|
|
|
postSections=namedStatus.split('/')
|
|
|
|
if len(postSections)==2:
|
|
|
|
nickname=postSections[0]
|
|
|
|
statusNumber=postSections[1]
|
|
|
|
if len(statusNumber)>10 and statusNumber.isdigit():
|
|
|
|
domainFull=self.server.domain
|
|
|
|
if self.server.port!=80 and self.server.port!=443:
|
|
|
|
domainFull=self.server.domain+':'+str(self.server.port)
|
|
|
|
postFilename= \
|
|
|
|
self.server.baseDir+'/accounts/'+nickname+'@'+self.server.domain+'/outbox/'+ \
|
|
|
|
self.server.httpPrefix+':##'+domainFull+'#users#'+nickname+'#statuses#'+statusNumber+'.json'
|
|
|
|
if os.path.isfile(postFilename):
|
2019-07-14 16:57:06 +00:00
|
|
|
postJsonObject={}
|
2019-07-06 21:33:46 +00:00
|
|
|
with open(postFilename, 'r') as fp:
|
2019-07-14 16:57:06 +00:00
|
|
|
postJsonObject=commentjson.load(fp)
|
2019-07-12 11:20:59 +00:00
|
|
|
# Only authorized viewers get to see likes on posts
|
|
|
|
# Otherwize marketers could gain more social graph info
|
2019-07-25 11:18:35 +00:00
|
|
|
if not authorized:
|
2019-07-14 16:57:06 +00:00
|
|
|
if postJsonObject.get('likes'):
|
|
|
|
postJsonObject['likes']={}
|
2019-07-20 21:13:36 +00:00
|
|
|
if 'text/html' in self.headers['Accept']:
|
2019-07-25 14:24:42 +00:00
|
|
|
self._set_headers('text/html')
|
2019-07-25 11:18:35 +00:00
|
|
|
if authorized:
|
2019-07-25 14:24:42 +00:00
|
|
|
self.send_header('Authorization')
|
2019-07-20 21:13:36 +00:00
|
|
|
self.wfile.write(htmlIndividualPost(postJsonObject).encode('utf-8'))
|
|
|
|
else:
|
2019-07-25 14:24:42 +00:00
|
|
|
self._set_headers('application/json')
|
2019-07-25 11:18:35 +00:00
|
|
|
if authorized:
|
2019-07-25 14:24:42 +00:00
|
|
|
self.send_header('Authorization')
|
2019-07-20 21:13:36 +00:00
|
|
|
self.wfile.write(json.dumps(postJsonObject).encode('utf-8'))
|
2019-07-06 21:33:46 +00:00
|
|
|
self.server.GETbusy=False
|
|
|
|
return
|
|
|
|
else:
|
|
|
|
self._404()
|
|
|
|
self.server.GETbusy=False
|
2019-07-13 19:28:14 +00:00
|
|
|
return
|
|
|
|
# get replies to a post /users/nickname/statuses/number/replies
|
2019-07-13 20:23:42 +00:00
|
|
|
if self.path.endswith('/replies') or '/replies?page=' in self.path:
|
2019-07-13 19:28:14 +00:00
|
|
|
if '/statuses/' in self.path and '/users/' in self.path:
|
|
|
|
namedStatus=self.path.split('/users/')[1]
|
|
|
|
if '/' in namedStatus:
|
|
|
|
postSections=namedStatus.split('/')
|
|
|
|
if len(postSections)>=4:
|
|
|
|
if postSections[3].startswith('replies'):
|
|
|
|
nickname=postSections[0]
|
|
|
|
statusNumber=postSections[2]
|
|
|
|
if len(statusNumber)>10 and statusNumber.isdigit():
|
|
|
|
#get the replies file
|
|
|
|
domainFull=self.server.domain
|
|
|
|
if self.server.port!=80 and self.server.port!=443:
|
|
|
|
domainFull=self.server.domain+':'+str(self.server.port)
|
2019-07-13 19:34:03 +00:00
|
|
|
boxname='outbox'
|
|
|
|
postDir=self.server.baseDir+'/accounts/'+nickname+'@'+self.server.domain+'/'+boxname
|
|
|
|
postRepliesFilename= \
|
|
|
|
postDir+'/'+ \
|
|
|
|
self.server.httpPrefix+':##'+domainFull+'#users#'+nickname+'#statuses#'+statusNumber+'.replies'
|
|
|
|
if not os.path.isfile(postRepliesFilename):
|
|
|
|
# There are no replies, so show empty collection
|
|
|
|
repliesJson = {
|
|
|
|
'@context': 'https://www.w3.org/ns/activitystreams',
|
2019-07-13 20:23:42 +00:00
|
|
|
'first': self.server.httpPrefix+'://'+domainFull+'/users/'+nickname+'/statuses/'+statusNumber+'/replies?page=true',
|
|
|
|
'id': self.server.httpPrefix+'://'+domainFull+'/users/'+nickname+'/statuses/'+statusNumber+'/replies',
|
|
|
|
'last': self.server.httpPrefix+'://'+domainFull+'/users/'+nickname+'/statuses/'+statusNumber+'/replies?page=true',
|
2019-07-13 19:34:03 +00:00
|
|
|
'totalItems': 0,
|
|
|
|
'type': 'OrderedCollection'}
|
2019-07-20 21:13:36 +00:00
|
|
|
if 'text/html' in self.headers['Accept']:
|
2019-07-25 14:24:42 +00:00
|
|
|
self._set_headers('text/html')
|
2019-07-20 21:13:36 +00:00
|
|
|
self.wfile.write(htmlPostReplies(repliesJson).encode('utf-8'))
|
|
|
|
else:
|
2019-07-25 14:24:42 +00:00
|
|
|
self._set_headers('application/json')
|
2019-07-20 21:13:36 +00:00
|
|
|
self.wfile.write(json.dumps(repliesJson).encode('utf-8'))
|
2019-07-13 19:34:03 +00:00
|
|
|
self.server.GETbusy=False
|
|
|
|
return
|
|
|
|
else:
|
|
|
|
# replies exist. Itterate through the text file containing message ids
|
|
|
|
repliesJson = {
|
|
|
|
'@context': 'https://www.w3.org/ns/activitystreams',
|
|
|
|
'id': self.server.httpPrefix+'://'+domainFull+'/users/'+nickname+'/statuses/'+statusNumber+'?page=true',
|
|
|
|
'orderedItems': [
|
|
|
|
],
|
|
|
|
'partOf': self.server.httpPrefix+'://'+domainFull+'/users/'+nickname+'/statuses/'+statusNumber,
|
|
|
|
'type': 'OrderedCollectionPage'}
|
2019-07-25 11:18:35 +00:00
|
|
|
|
2019-07-13 19:34:03 +00:00
|
|
|
# populate the items list with replies
|
|
|
|
repliesBoxes=['outbox','inbox']
|
|
|
|
with open(postRepliesFilename,'r') as repliesFile:
|
|
|
|
for messageId in repliesFile:
|
|
|
|
replyFound=False
|
|
|
|
# examine inbox and outbox
|
|
|
|
for boxname in repliesBoxes:
|
|
|
|
searchFilename= \
|
|
|
|
self.server.baseDir+ \
|
|
|
|
'/accounts/'+nickname+'@'+ \
|
|
|
|
self.server.domain+'/'+ \
|
|
|
|
boxname+'/'+ \
|
|
|
|
messageId.replace('\n','').replace('/','#')+'.json'
|
|
|
|
if os.path.isfile(searchFilename):
|
|
|
|
if authorized or \
|
|
|
|
'https://www.w3.org/ns/activitystreams#Public' in open(searchFilename).read():
|
|
|
|
with open(searchFilename, 'r') as fp:
|
2019-07-14 16:57:06 +00:00
|
|
|
postJsonObject=commentjson.load(fp)
|
|
|
|
if postJsonObject['object'].get('cc'):
|
2019-07-13 20:16:07 +00:00
|
|
|
if authorized or \
|
2019-07-14 16:57:06 +00:00
|
|
|
('https://www.w3.org/ns/activitystreams#Public' in postJsonObject['object']['to'] or \
|
|
|
|
'https://www.w3.org/ns/activitystreams#Public' in postJsonObject['object']['cc']):
|
|
|
|
repliesJson['orderedItems'].append(postJsonObject)
|
2019-07-13 20:16:07 +00:00
|
|
|
replyFound=True
|
|
|
|
else:
|
|
|
|
if authorized or \
|
2019-07-14 16:57:06 +00:00
|
|
|
'https://www.w3.org/ns/activitystreams#Public' in postJsonObject['object']['to']:
|
|
|
|
repliesJson['orderedItems'].append(postJsonObject)
|
2019-07-13 20:16:07 +00:00
|
|
|
replyFound=True
|
2019-07-13 19:34:03 +00:00
|
|
|
break
|
|
|
|
# if not in either inbox or outbox then examine the shared inbox
|
|
|
|
if not replyFound:
|
|
|
|
searchFilename= \
|
|
|
|
self.server.baseDir+ \
|
|
|
|
'/accounts/inbox@'+ \
|
|
|
|
self.server.domain+'/inbox/'+ \
|
|
|
|
messageId.replace('\n','').replace('/','#')+'.json'
|
|
|
|
if os.path.isfile(searchFilename):
|
|
|
|
if authorized or \
|
|
|
|
'https://www.w3.org/ns/activitystreams#Public' in open(searchFilename).read():
|
|
|
|
# get the json of the reply and append it to the collection
|
|
|
|
with open(searchFilename, 'r') as fp:
|
2019-07-14 16:57:06 +00:00
|
|
|
postJsonObject=commentjson.load(fp)
|
|
|
|
if postJsonObject['object'].get('cc'):
|
2019-07-13 20:16:07 +00:00
|
|
|
if authorized or \
|
2019-07-14 16:57:06 +00:00
|
|
|
('https://www.w3.org/ns/activitystreams#Public' in postJsonObject['object']['to'] or \
|
|
|
|
'https://www.w3.org/ns/activitystreams#Public' in postJsonObject['object']['cc']):
|
|
|
|
repliesJson['orderedItems'].append(postJsonObject)
|
2019-07-13 20:16:07 +00:00
|
|
|
else:
|
|
|
|
if authorized or \
|
2019-07-14 16:57:06 +00:00
|
|
|
'https://www.w3.org/ns/activitystreams#Public' in postJsonObject['object']['to']:
|
|
|
|
repliesJson['orderedItems'].append(postJsonObject)
|
2019-07-13 19:34:03 +00:00
|
|
|
# send the replies json
|
2019-07-20 21:13:36 +00:00
|
|
|
if 'text/html' in self.headers['Accept']:
|
2019-07-25 14:24:42 +00:00
|
|
|
self._set_headers('text/html')
|
2019-07-20 21:13:36 +00:00
|
|
|
self.wfile.write(htmlPostReplies(repliesJson).encode('utf-8'))
|
|
|
|
else:
|
2019-07-25 14:24:42 +00:00
|
|
|
self._set_headers('application/json')
|
2019-07-20 21:13:36 +00:00
|
|
|
self.wfile.write(json.dumps(repliesJson).encode('utf-8'))
|
2019-07-13 19:34:03 +00:00
|
|
|
self.server.GETbusy=False
|
|
|
|
return
|
2019-07-13 19:28:14 +00:00
|
|
|
|
2019-07-22 17:21:45 +00:00
|
|
|
if self.path.endswith('/roles') and '/users/' in self.path:
|
|
|
|
namedStatus=self.path.split('/users/')[1]
|
|
|
|
if '/' in namedStatus:
|
|
|
|
postSections=namedStatus.split('/')
|
|
|
|
nickname=postSections[0]
|
|
|
|
actorFilename=self.server.baseDir+'/accounts/'+nickname+'@'+self.server.domain+'.json'
|
|
|
|
if os.path.isfile(actorFilename):
|
|
|
|
with open(actorFilename, 'r') as fp:
|
|
|
|
actorJson=commentjson.load(fp)
|
|
|
|
if actorJson.get('roles'):
|
|
|
|
if 'text/html' in self.headers['Accept']:
|
|
|
|
getPerson = \
|
|
|
|
personLookup(self.server.domain,self.path.replace('/roles',''), \
|
|
|
|
self.server.baseDir)
|
2019-07-22 20:01:46 +00:00
|
|
|
if getPerson:
|
2019-07-25 14:24:42 +00:00
|
|
|
self._set_headers('text/html')
|
2019-07-22 17:21:45 +00:00
|
|
|
self.wfile.write(htmlProfile(self.server.baseDir, \
|
|
|
|
self.server.httpPrefix, \
|
|
|
|
True, \
|
|
|
|
self.server.ocapAlways, \
|
|
|
|
getPerson,'roles', \
|
|
|
|
self.server.session, \
|
|
|
|
self.server.cachedWebfingers, \
|
|
|
|
self.server.personCache, \
|
|
|
|
actorJson['roles']).encode('utf-8'))
|
|
|
|
else:
|
2019-07-25 14:24:42 +00:00
|
|
|
self._set_headers('application/json')
|
2019-07-22 17:21:45 +00:00
|
|
|
self.wfile.write(json.dumps(actorJson['roles']).encode('utf-8'))
|
2019-07-22 20:01:46 +00:00
|
|
|
self.server.GETbusy=False
|
2019-07-22 17:21:45 +00:00
|
|
|
return
|
2019-07-22 20:01:46 +00:00
|
|
|
|
|
|
|
if self.path.endswith('/skills') and '/users/' in self.path:
|
|
|
|
namedStatus=self.path.split('/users/')[1]
|
|
|
|
if '/' in namedStatus:
|
|
|
|
postSections=namedStatus.split('/')
|
|
|
|
nickname=postSections[0]
|
|
|
|
actorFilename=self.server.baseDir+'/accounts/'+nickname+'@'+self.server.domain+'.json'
|
|
|
|
if os.path.isfile(actorFilename):
|
|
|
|
with open(actorFilename, 'r') as fp:
|
|
|
|
actorJson=commentjson.load(fp)
|
|
|
|
if actorJson.get('skills'):
|
|
|
|
if 'text/html' in self.headers['Accept']:
|
|
|
|
getPerson = \
|
|
|
|
personLookup(self.server.domain,self.path.replace('/skills',''), \
|
|
|
|
self.server.baseDir)
|
|
|
|
if getPerson:
|
2019-07-25 14:24:42 +00:00
|
|
|
self._set_headers('text/html')
|
2019-07-22 20:01:46 +00:00
|
|
|
self.wfile.write(htmlProfile(self.server.baseDir, \
|
|
|
|
self.server.httpPrefix, \
|
|
|
|
True, \
|
|
|
|
self.server.ocapAlways, \
|
|
|
|
getPerson,'skills', \
|
|
|
|
self.server.session, \
|
|
|
|
self.server.cachedWebfingers, \
|
|
|
|
self.server.personCache, \
|
|
|
|
actorJson['skills']).encode('utf-8'))
|
|
|
|
else:
|
2019-07-25 14:24:42 +00:00
|
|
|
self._set_headers('application/json')
|
2019-07-22 20:01:46 +00:00
|
|
|
self.wfile.write(json.dumps(actorJson['skills']).encode('utf-8'))
|
|
|
|
self.server.GETbusy=False
|
|
|
|
return
|
|
|
|
|
2019-07-06 21:33:46 +00:00
|
|
|
# get an individual post from the path /users/nickname/statuses/number
|
2019-07-06 21:24:47 +00:00
|
|
|
if '/statuses/' in self.path and '/users/' in self.path:
|
|
|
|
namedStatus=self.path.split('/users/')[1]
|
|
|
|
if '/' in namedStatus:
|
|
|
|
postSections=namedStatus.split('/')
|
2019-07-13 19:28:14 +00:00
|
|
|
if len(postSections)>=3:
|
2019-07-06 21:24:47 +00:00
|
|
|
nickname=postSections[0]
|
|
|
|
statusNumber=postSections[2]
|
2019-07-06 21:33:46 +00:00
|
|
|
if len(statusNumber)>10 and statusNumber.isdigit():
|
2019-07-06 21:24:47 +00:00
|
|
|
domainFull=self.server.domain
|
|
|
|
if self.server.port!=80 and self.server.port!=443:
|
|
|
|
domainFull=self.server.domain+':'+str(self.server.port)
|
2019-07-22 17:21:45 +00:00
|
|
|
postFilename= \
|
|
|
|
self.server.baseDir+'/accounts/'+nickname+'@'+self.server.domain+'/outbox/'+ \
|
|
|
|
self.server.httpPrefix+':##'+domainFull+'#users#'+nickname+'#statuses#'+statusNumber+'.json'
|
|
|
|
if os.path.isfile(postFilename):
|
|
|
|
postJsonObject={}
|
|
|
|
with open(postFilename, 'r') as fp:
|
|
|
|
postJsonObject=commentjson.load(fp)
|
|
|
|
# Only authorized viewers get to see likes on posts
|
|
|
|
# Otherwize marketers could gain more social graph info
|
2019-07-25 11:18:35 +00:00
|
|
|
if not authorized:
|
2019-07-22 17:21:45 +00:00
|
|
|
if postJsonObject.get('likes'):
|
|
|
|
postJsonObject['likes']={}
|
|
|
|
if 'text/html' in self.headers['Accept']:
|
2019-07-25 14:24:42 +00:00
|
|
|
self._set_headers('text/html')
|
2019-07-22 17:21:45 +00:00
|
|
|
self.wfile.write(htmlIndividualPost(postJsonObject).encode('utf-8'))
|
|
|
|
else:
|
2019-07-25 14:24:42 +00:00
|
|
|
self._set_headers('application/json')
|
2019-07-22 17:21:45 +00:00
|
|
|
self.wfile.write(json.dumps(postJsonObject).encode('utf-8'))
|
|
|
|
self.server.GETbusy=False
|
|
|
|
return
|
|
|
|
else:
|
|
|
|
self._404()
|
|
|
|
self.server.GETbusy=False
|
|
|
|
return
|
2019-07-03 19:32:07 +00:00
|
|
|
# get the inbox for a given person
|
|
|
|
if self.path.endswith('/inbox'):
|
|
|
|
if '/users/' in self.path:
|
2019-07-25 11:18:35 +00:00
|
|
|
if authorized:
|
2019-07-12 11:05:43 +00:00
|
|
|
inboxFeed=personBoxJson(self.server.baseDir, \
|
|
|
|
self.server.domain, \
|
|
|
|
self.server.port, \
|
|
|
|
self.path, \
|
|
|
|
self.server.httpPrefix, \
|
2019-07-14 11:15:28 +00:00
|
|
|
maxPostsInFeed, 'inbox', \
|
|
|
|
True,self.server.ocapAlways)
|
2019-07-12 11:05:43 +00:00
|
|
|
if inboxFeed:
|
2019-07-20 21:13:36 +00:00
|
|
|
if 'text/html' in self.headers['Accept']:
|
2019-07-24 12:02:28 +00:00
|
|
|
nickname=self.path.replace('/users/','').replace('/inbox','')
|
|
|
|
if '?page=' in nickname:
|
|
|
|
nickname=nickname.split('?page=')[0]
|
2019-07-24 11:03:56 +00:00
|
|
|
if 'page=' not in self.path:
|
|
|
|
# if no page was specified then show the first
|
|
|
|
inboxFeed=personBoxJson(self.server.baseDir, \
|
|
|
|
self.server.domain, \
|
|
|
|
self.server.port, \
|
|
|
|
self.path+'?page=1', \
|
|
|
|
self.server.httpPrefix, \
|
|
|
|
maxPostsInFeed, 'inbox', \
|
2019-07-24 12:02:28 +00:00
|
|
|
True,self.server.ocapAlways)
|
2019-07-25 14:24:42 +00:00
|
|
|
self._set_headers('text/html')
|
2019-07-24 11:03:56 +00:00
|
|
|
self.wfile.write(htmlInbox(self.server.session, \
|
2019-07-24 12:02:28 +00:00
|
|
|
self.server.baseDir, \
|
2019-07-24 11:03:56 +00:00
|
|
|
self.server.cachedWebfingers, \
|
|
|
|
self.server.personCache, \
|
2019-07-24 12:02:28 +00:00
|
|
|
nickname, \
|
2019-07-24 11:03:56 +00:00
|
|
|
self.server.domain, \
|
|
|
|
inboxFeed).encode('utf-8'))
|
2019-07-20 21:13:36 +00:00
|
|
|
else:
|
2019-07-25 14:24:42 +00:00
|
|
|
self._set_headers('application/json')
|
2019-07-20 21:13:36 +00:00
|
|
|
self.wfile.write(json.dumps(inboxFeed).encode('utf-8'))
|
2019-07-12 11:05:43 +00:00
|
|
|
self.server.GETbusy=False
|
|
|
|
return
|
|
|
|
else:
|
|
|
|
if self.server.debug:
|
|
|
|
print('DEBUG: '+nickname+ \
|
|
|
|
' was not authorized to access '+self.path)
|
2019-07-04 08:56:15 +00:00
|
|
|
if self.server.debug:
|
|
|
|
print('DEBUG: GET access to inbox is unauthorized')
|
2019-07-03 20:32:30 +00:00
|
|
|
self.send_response(405)
|
|
|
|
self.end_headers()
|
|
|
|
self.server.POSTbusy=False
|
|
|
|
return
|
2019-07-03 19:32:07 +00:00
|
|
|
|
2019-06-29 14:35:26 +00:00
|
|
|
# get outbox feed for a person
|
2019-07-04 16:24:23 +00:00
|
|
|
outboxFeed=personBoxJson(self.server.baseDir,self.server.domain, \
|
|
|
|
self.server.port,self.path, \
|
|
|
|
self.server.httpPrefix, \
|
2019-07-12 11:20:59 +00:00
|
|
|
maxPostsInFeed, 'outbox', \
|
2019-07-25 11:18:35 +00:00
|
|
|
authorized, \
|
2019-07-14 11:15:28 +00:00
|
|
|
self.server.ocapAlways)
|
2019-06-29 14:41:23 +00:00
|
|
|
if outboxFeed:
|
2019-07-20 21:13:36 +00:00
|
|
|
if 'text/html' in self.headers['Accept']:
|
2019-07-24 12:02:28 +00:00
|
|
|
nickname=self.path.replace('/users/','').replace('/outbox','')
|
|
|
|
if '?page=' in nickname:
|
|
|
|
nickname=nickname.split('?page=')[0]
|
2019-07-24 11:03:56 +00:00
|
|
|
if 'page=' not in self.path:
|
|
|
|
# if a page wasn't specified then show the first one
|
|
|
|
outboxFeed=personBoxJson(self.server.baseDir,self.server.domain, \
|
|
|
|
self.server.port,self.path+'?page=1', \
|
|
|
|
self.server.httpPrefix, \
|
|
|
|
maxPostsInFeed, 'outbox', \
|
2019-07-25 11:18:35 +00:00
|
|
|
authorized, \
|
2019-07-24 11:03:56 +00:00
|
|
|
self.server.ocapAlways)
|
|
|
|
|
2019-07-25 14:24:42 +00:00
|
|
|
self._set_headers('text/html')
|
2019-07-24 11:03:56 +00:00
|
|
|
self.wfile.write(htmlOutbox(self.server.session, \
|
2019-07-24 12:02:28 +00:00
|
|
|
self.server.baseDir, \
|
2019-07-24 11:03:56 +00:00
|
|
|
self.server.cachedWebfingers, \
|
|
|
|
self.server.personCache, \
|
2019-07-24 12:02:28 +00:00
|
|
|
nickname, \
|
2019-07-24 11:03:56 +00:00
|
|
|
self.server.domain, \
|
|
|
|
outboxFeed).encode('utf-8'))
|
2019-07-20 21:13:36 +00:00
|
|
|
else:
|
2019-07-25 14:24:42 +00:00
|
|
|
self._set_headers('application/json')
|
2019-07-20 21:13:36 +00:00
|
|
|
self.wfile.write(json.dumps(outboxFeed).encode('utf-8'))
|
2019-07-01 14:30:48 +00:00
|
|
|
self.server.GETbusy=False
|
2019-06-29 20:21:37 +00:00
|
|
|
return
|
2019-07-23 12:33:09 +00:00
|
|
|
|
2019-07-24 09:53:07 +00:00
|
|
|
shares=getSharesFeedForPerson(self.server.baseDir, \
|
|
|
|
self.server.domain, \
|
2019-07-23 12:33:09 +00:00
|
|
|
self.server.port,self.path, \
|
|
|
|
self.server.httpPrefix, \
|
|
|
|
sharesPerPage)
|
|
|
|
if shares:
|
|
|
|
if 'text/html' in self.headers['Accept']:
|
|
|
|
if 'page=' not in self.path:
|
|
|
|
# get a page of shares, not the summary
|
|
|
|
shares=getSharesFeedForPerson(self.server.baseDir,self.server.domain, \
|
|
|
|
self.server.port,self.path+'?page=true', \
|
|
|
|
self.server.httpPrefix, \
|
|
|
|
sharesPerPage)
|
|
|
|
getPerson = personLookup(self.server.domain,self.path.replace('/shares',''), \
|
|
|
|
self.server.baseDir)
|
|
|
|
if getPerson:
|
|
|
|
if not self.server.session:
|
|
|
|
if self.server.debug:
|
|
|
|
print('DEBUG: creating new session')
|
|
|
|
self.server.session= \
|
|
|
|
createSession(self.server.domain,self.server.port,self.server.useTor)
|
|
|
|
|
2019-07-25 14:24:42 +00:00
|
|
|
self._set_headers('text/html')
|
2019-07-23 12:33:09 +00:00
|
|
|
self.wfile.write(htmlProfile(self.server.baseDir, \
|
|
|
|
self.server.httpPrefix, \
|
|
|
|
authorized, \
|
|
|
|
self.server.ocapAlways, \
|
|
|
|
getPerson,'shares', \
|
|
|
|
self.server.session, \
|
|
|
|
self.server.cachedWebfingers, \
|
|
|
|
self.server.personCache, \
|
|
|
|
shares).encode('utf-8'))
|
|
|
|
self.server.GETbusy=False
|
|
|
|
return
|
|
|
|
else:
|
2019-07-25 14:24:42 +00:00
|
|
|
self._set_headers('application/json')
|
2019-07-23 12:33:09 +00:00
|
|
|
self.wfile.write(json.dumps(shares).encode('utf-8'))
|
|
|
|
self.server.GETbusy=False
|
|
|
|
return
|
|
|
|
|
2019-07-02 20:54:22 +00:00
|
|
|
following=getFollowingFeed(self.server.baseDir,self.server.domain, \
|
|
|
|
self.server.port,self.path, \
|
2019-07-22 09:38:02 +00:00
|
|
|
self.server.httpPrefix, \
|
2019-07-19 08:40:51 +00:00
|
|
|
authorized,followsPerPage)
|
2019-06-29 20:21:37 +00:00
|
|
|
if following:
|
2019-07-20 21:13:36 +00:00
|
|
|
if 'text/html' in self.headers['Accept']:
|
2019-07-22 09:38:02 +00:00
|
|
|
if 'page=' not in self.path:
|
|
|
|
# get a page of following, not the summary
|
|
|
|
following=getFollowingFeed(self.server.baseDir,self.server.domain, \
|
2019-07-22 11:44:31 +00:00
|
|
|
self.server.port,self.path+'?page=true', \
|
2019-07-22 09:38:02 +00:00
|
|
|
self.server.httpPrefix, \
|
|
|
|
authorized,followsPerPage)
|
|
|
|
getPerson = personLookup(self.server.domain,self.path.replace('/following',''), \
|
|
|
|
self.server.baseDir)
|
|
|
|
if getPerson:
|
2019-07-22 14:09:21 +00:00
|
|
|
if not self.server.session:
|
|
|
|
if self.server.debug:
|
|
|
|
print('DEBUG: creating new session')
|
|
|
|
self.server.session= \
|
|
|
|
createSession(self.server.domain,self.server.port,self.server.useTor)
|
2019-07-23 12:33:09 +00:00
|
|
|
|
2019-07-25 14:24:42 +00:00
|
|
|
self._set_headers('text/html')
|
2019-07-22 09:38:02 +00:00
|
|
|
self.wfile.write(htmlProfile(self.server.baseDir, \
|
|
|
|
self.server.httpPrefix, \
|
|
|
|
authorized, \
|
|
|
|
self.server.ocapAlways, \
|
|
|
|
getPerson,'following', \
|
2019-07-22 14:09:21 +00:00
|
|
|
self.server.session, \
|
|
|
|
self.server.cachedWebfingers, \
|
|
|
|
self.server.personCache, \
|
2019-07-22 09:38:02 +00:00
|
|
|
following).encode('utf-8'))
|
|
|
|
self.server.GETbusy=False
|
|
|
|
return
|
2019-07-20 21:13:36 +00:00
|
|
|
else:
|
2019-07-25 14:24:42 +00:00
|
|
|
self._set_headers('application/json')
|
2019-07-20 21:13:36 +00:00
|
|
|
self.wfile.write(json.dumps(following).encode('utf-8'))
|
2019-07-22 09:38:02 +00:00
|
|
|
self.server.GETbusy=False
|
2019-07-22 11:44:31 +00:00
|
|
|
return
|
2019-07-02 20:54:22 +00:00
|
|
|
followers=getFollowingFeed(self.server.baseDir,self.server.domain, \
|
|
|
|
self.server.port,self.path, \
|
2019-07-06 17:00:22 +00:00
|
|
|
self.server.httpPrefix, \
|
2019-07-19 08:40:51 +00:00
|
|
|
authorized,followsPerPage,'followers')
|
2019-06-29 20:21:37 +00:00
|
|
|
if followers:
|
2019-07-20 21:13:36 +00:00
|
|
|
if 'text/html' in self.headers['Accept']:
|
2019-07-22 09:38:02 +00:00
|
|
|
if 'page=' not in self.path:
|
|
|
|
# get a page of followers, not the summary
|
|
|
|
followers=getFollowingFeed(self.server.baseDir,self.server.domain, \
|
|
|
|
self.server.port,self.path+'?page=1', \
|
|
|
|
self.server.httpPrefix, \
|
|
|
|
authorized,followsPerPage,'followers')
|
|
|
|
getPerson = personLookup(self.server.domain,self.path.replace('/followers',''), \
|
|
|
|
self.server.baseDir)
|
|
|
|
if getPerson:
|
2019-07-22 14:09:21 +00:00
|
|
|
if not self.server.session:
|
|
|
|
if self.server.debug:
|
|
|
|
print('DEBUG: creating new session')
|
|
|
|
self.server.session= \
|
|
|
|
createSession(self.server.domain,self.server.port,self.server.useTor)
|
2019-07-25 11:18:35 +00:00
|
|
|
self._set_headers('text/html',authheader)
|
2019-07-22 09:38:02 +00:00
|
|
|
self.wfile.write(htmlProfile(self.server.baseDir, \
|
|
|
|
self.server.httpPrefix, \
|
|
|
|
authorized, \
|
|
|
|
self.server.ocapAlways, \
|
|
|
|
getPerson,'followers', \
|
2019-07-22 14:09:21 +00:00
|
|
|
self.server.session, \
|
|
|
|
self.server.cachedWebfingers, \
|
|
|
|
self.server.personCache, \
|
2019-07-22 09:38:02 +00:00
|
|
|
followers).encode('utf-8'))
|
|
|
|
self.server.GETbusy=False
|
|
|
|
return
|
2019-07-20 21:13:36 +00:00
|
|
|
else:
|
2019-07-25 14:24:42 +00:00
|
|
|
self._set_headers('application/json')
|
2019-07-20 21:13:36 +00:00
|
|
|
self.wfile.write(json.dumps(followers).encode('utf-8'))
|
2019-07-01 14:30:48 +00:00
|
|
|
self.server.GETbusy=False
|
2019-07-04 14:36:29 +00:00
|
|
|
return
|
2019-06-28 18:55:29 +00:00
|
|
|
# look up a person
|
2019-07-02 20:54:22 +00:00
|
|
|
getPerson = personLookup(self.server.domain,self.path, \
|
|
|
|
self.server.baseDir)
|
2019-06-28 18:55:29 +00:00
|
|
|
if getPerson:
|
2019-07-20 21:13:36 +00:00
|
|
|
if 'text/html' in self.headers['Accept']:
|
2019-07-22 14:09:21 +00:00
|
|
|
if not self.server.session:
|
|
|
|
if self.server.debug:
|
|
|
|
print('DEBUG: creating new session')
|
|
|
|
self.server.session= \
|
|
|
|
createSession(self.server.domain,self.server.port,self.server.useTor)
|
2019-07-25 14:24:42 +00:00
|
|
|
self._set_headers('text/html')
|
2019-07-21 22:38:44 +00:00
|
|
|
self.wfile.write(htmlProfile(self.server.baseDir, \
|
|
|
|
self.server.httpPrefix, \
|
|
|
|
authorized, \
|
|
|
|
self.server.ocapAlways, \
|
2019-07-22 14:09:21 +00:00
|
|
|
getPerson,'posts',
|
|
|
|
self.server.session, \
|
|
|
|
self.server.cachedWebfingers, \
|
|
|
|
self.server.personCache).encode('utf-8'))
|
2019-07-20 21:13:36 +00:00
|
|
|
else:
|
2019-07-25 14:24:42 +00:00
|
|
|
self._set_headers('application/json')
|
2019-07-20 21:13:36 +00:00
|
|
|
self.wfile.write(json.dumps(getPerson).encode('utf-8'))
|
2019-07-01 14:30:48 +00:00
|
|
|
self.server.GETbusy=False
|
2019-06-28 18:55:29 +00:00
|
|
|
return
|
|
|
|
# check that a json file was requested
|
|
|
|
if not self.path.endswith('.json'):
|
2019-07-03 16:14:45 +00:00
|
|
|
if self.server.debug:
|
|
|
|
print('DEBUG: GET Not json: '+self.path+' '+self.server.baseDir)
|
2019-06-28 18:55:29 +00:00
|
|
|
self._404()
|
2019-07-01 14:30:48 +00:00
|
|
|
self.server.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):
|
2019-07-25 14:24:42 +00:00
|
|
|
self._set_headers('application/json')
|
2019-07-03 19:15:42 +00:00
|
|
|
with open(filename, 'r', encoding='utf-8') as File:
|
2019-06-28 18:55:29 +00:00
|
|
|
content = File.read()
|
|
|
|
contentJson=json.loads(content)
|
2019-07-03 19:15:42 +00:00
|
|
|
self.wfile.write(json.dumps(contentJson).encode('utf-8'))
|
2019-06-28 18:55:29 +00:00
|
|
|
else:
|
2019-07-03 16:14:45 +00:00
|
|
|
if self.server.debug:
|
|
|
|
print('DEBUG: GET Unknown file')
|
2019-06-28 18:55:29 +00:00
|
|
|
self._404()
|
2019-07-01 14:30:48 +00:00
|
|
|
self.server.GETbusy=False
|
2019-06-28 18:55:29 +00:00
|
|
|
|
|
|
|
def do_HEAD(self):
|
2019-07-25 11:18:35 +00:00
|
|
|
self._set_headers('application/json',None)
|
2019-07-01 21:01:43 +00:00
|
|
|
|
2019-06-28 18:55:29 +00:00
|
|
|
def do_POST(self):
|
2019-07-04 17:56:25 +00:00
|
|
|
if self.server.debug:
|
2019-07-06 17:00:22 +00:00
|
|
|
print('DEBUG: POST to from '+self.server.baseDir+ \
|
|
|
|
' path: '+self.path+' busy: '+ \
|
|
|
|
str(self.server.POSTbusy))
|
2019-07-01 14:30:48 +00:00
|
|
|
if self.server.POSTbusy:
|
|
|
|
currTimePOST=int(time.time())
|
|
|
|
if currTimePOST-self.server.lastPOST<10:
|
2019-06-29 17:28:43 +00:00
|
|
|
self.send_response(429)
|
2019-06-29 17:27:32 +00:00
|
|
|
self.end_headers()
|
2019-07-01 14:30:48 +00:00
|
|
|
return
|
|
|
|
self.server.lastPOST=currTimePOST
|
2019-07-03 16:14:45 +00:00
|
|
|
|
2019-07-01 14:30:48 +00:00
|
|
|
self.server.POSTbusy=True
|
2019-07-01 11:48:54 +00:00
|
|
|
if not self.headers.get('Content-type'):
|
2019-07-03 16:14:45 +00:00
|
|
|
print('Content-type header missing')
|
2019-07-01 11:48:54 +00:00
|
|
|
self.send_response(400)
|
|
|
|
self.end_headers()
|
2019-07-01 14:30:48 +00:00
|
|
|
self.server.POSTbusy=False
|
2019-07-01 11:48:54 +00:00
|
|
|
return
|
2019-07-03 16:14:45 +00:00
|
|
|
|
2019-07-03 21:37:46 +00:00
|
|
|
# remove any trailing slashes from the path
|
2019-07-23 19:02:26 +00:00
|
|
|
self.path=self.path.replace('/outbox/','/outbox').replace('/inbox/','/inbox').replace('/shares/','/shares').replace('/sharedInbox/','/sharedInbox')
|
2019-07-03 21:37:46 +00:00
|
|
|
|
|
|
|
# if this is a POST to teh outbox then check authentication
|
|
|
|
self.outboxAuthenticated=False
|
|
|
|
self.postToNickname=None
|
2019-07-24 22:38:42 +00:00
|
|
|
|
|
|
|
if self.path.startswith('/login'):
|
2019-07-25 10:56:24 +00:00
|
|
|
# get the contents of POST containing login credentials
|
|
|
|
length = int(self.headers['Content-length'])
|
|
|
|
if length>512:
|
|
|
|
print('Login failed - credentials too long')
|
|
|
|
self.send_response(401)
|
|
|
|
self.end_headers()
|
|
|
|
self.server.POSTbusy=False
|
|
|
|
return
|
|
|
|
loginParams=self.rfile.read(length).decode('utf-8')
|
|
|
|
loginNickname,loginPassword=htmlGetLoginCredentials(loginParams,self.server.lastLoginTime)
|
2019-07-24 22:38:42 +00:00
|
|
|
if loginNickname:
|
|
|
|
self.server.lastLoginTime=int(time.time())
|
|
|
|
authHeader=createBasicAuthHeader(loginNickname,loginPassword)
|
|
|
|
if not authorizeBasic(self.server.baseDir,'/users/'+loginNickname+'/outbox',authHeader,False):
|
2019-07-25 10:56:24 +00:00
|
|
|
print('Login failed: '+loginNickname)
|
2019-07-25 14:24:42 +00:00
|
|
|
# remove any token
|
|
|
|
if self.server.tokens.get(loginNickname):
|
|
|
|
del self.server.tokensLookup[self.server.tokens[loginNickname]]
|
|
|
|
del self.server.tokens[loginNickname]
|
|
|
|
del self.server.salts[loginNickname]
|
2019-07-24 22:38:42 +00:00
|
|
|
self.send_response(401)
|
2019-07-25 14:24:42 +00:00
|
|
|
self.send_header('Content-type', 'text/html; charset=utf-8')
|
|
|
|
self.send_header('Set-Cookie', 'epicyon=; SameSite=Strict')
|
2019-07-24 22:38:42 +00:00
|
|
|
self.end_headers()
|
|
|
|
self.server.POSTbusy=False
|
|
|
|
return
|
2019-07-25 10:56:24 +00:00
|
|
|
else:
|
|
|
|
# login success - redirect with authorization
|
|
|
|
print('Login success: '+loginNickname)
|
|
|
|
self.send_response(303)
|
2019-07-25 14:30:12 +00:00
|
|
|
# This produces a deterministic token based on nick+password+salt
|
|
|
|
# But notice that the salt is ephemeral, so a server reboot changes them.
|
|
|
|
# This allows you to be logged in on two or more devices with the
|
|
|
|
# same token, but also ensures that if an adversary obtains the token
|
|
|
|
# then rebooting the server is sufficient to thwart them, without
|
|
|
|
# any password changes.
|
2019-07-25 14:24:42 +00:00
|
|
|
if not self.server.salts.get(loginNickname):
|
|
|
|
self.server.salts[loginNickname]=createPassword(32)
|
|
|
|
self.server.tokens[loginNickname]=str(sha256((loginNickname+loginPassword+self.server.salts[loginNickname]).encode('utf-8')))
|
|
|
|
self.server.tokensLookup[self.server.tokens[loginNickname]]=loginNickname
|
|
|
|
self.send_header('Set-Cookie', 'epicyon='+self.server.tokens[loginNickname]+'; SameSite=Strict')
|
|
|
|
self.send_header('Location', '/users/'+loginNickname+'/outbox')
|
2019-07-25 10:56:24 +00:00
|
|
|
self.end_headers()
|
|
|
|
self.server.POSTbusy=False
|
|
|
|
return
|
2019-07-24 22:38:42 +00:00
|
|
|
self.send_response(200)
|
|
|
|
self.end_headers()
|
|
|
|
self.server.POSTbusy=False
|
|
|
|
return
|
|
|
|
|
2019-07-23 19:02:26 +00:00
|
|
|
if self.path.endswith('/outbox') or self.path.endswith('/shares'):
|
2019-07-03 20:03:38 +00:00
|
|
|
if '/users/' in self.path:
|
2019-07-12 11:05:43 +00:00
|
|
|
if self._isAuthorized():
|
|
|
|
self.outboxAuthenticated=True
|
2019-07-16 10:19:04 +00:00
|
|
|
pathUsersSection=self.path.split('/users/')[1]
|
2019-07-12 11:05:43 +00:00
|
|
|
self.postToNickname=pathUsersSection.split('/')[0]
|
2019-07-03 21:37:46 +00:00
|
|
|
if not self.outboxAuthenticated:
|
|
|
|
self.send_response(405)
|
|
|
|
self.end_headers()
|
|
|
|
self.server.POSTbusy=False
|
|
|
|
return
|
2019-07-03 16:14:45 +00:00
|
|
|
|
|
|
|
# check that the post is to an expected path
|
2019-07-05 10:21:10 +00:00
|
|
|
if not (self.path.endswith('/outbox') or \
|
|
|
|
self.path.endswith('/inbox') or \
|
2019-07-23 19:02:26 +00:00
|
|
|
self.path.endswith('/shares') or \
|
2019-07-05 21:24:16 +00:00
|
|
|
self.path.endswith('/caps/new') or \
|
2019-07-05 10:21:10 +00:00
|
|
|
self.path=='/sharedInbox'):
|
2019-07-03 16:14:45 +00:00
|
|
|
print('Attempt to POST to invalid path '+self.path)
|
|
|
|
self.send_response(400)
|
|
|
|
self.end_headers()
|
|
|
|
self.server.POSTbusy=False
|
|
|
|
return
|
|
|
|
|
2019-06-28 18:55:29 +00:00
|
|
|
# read the message and convert it into a python dictionary
|
2019-07-01 11:48:54 +00:00
|
|
|
length = int(self.headers['Content-length'])
|
2019-07-03 16:14:45 +00:00
|
|
|
if self.server.debug:
|
|
|
|
print('DEBUG: content-length: '+str(length))
|
2019-07-16 14:23:06 +00:00
|
|
|
if not self.headers['Content-type'].startswith('image/'):
|
|
|
|
if length>self.server.maxMessageLength:
|
2019-07-19 15:22:04 +00:00
|
|
|
print('Maximum message length exceeded '+str(length))
|
2019-07-16 14:23:06 +00:00
|
|
|
self.send_response(400)
|
|
|
|
self.end_headers()
|
|
|
|
self.server.POSTbusy=False
|
|
|
|
return
|
|
|
|
else:
|
|
|
|
if length>self.server.maxImageSize:
|
2019-07-19 15:22:04 +00:00
|
|
|
print('Maximum image size exceeded '+str(length))
|
2019-07-16 14:23:06 +00:00
|
|
|
self.send_response(400)
|
|
|
|
self.end_headers()
|
|
|
|
self.server.POSTbusy=False
|
|
|
|
return
|
|
|
|
|
|
|
|
# receive images to the outbox
|
|
|
|
if self.headers['Content-type'].startswith('image/') and \
|
|
|
|
'/users/' in self.path:
|
|
|
|
if not self.outboxAuthenticated:
|
|
|
|
if self.server.debug:
|
|
|
|
print('DEBUG: unathenticated attempt to post image to outbox')
|
|
|
|
self.send_response(403)
|
|
|
|
self.end_headers()
|
|
|
|
self.server.POSTbusy=False
|
|
|
|
return
|
|
|
|
pathUsersSection=self.path.split('/users/')[1]
|
|
|
|
if '/' not in pathUsersSection:
|
|
|
|
self.send_response(404)
|
|
|
|
self.end_headers()
|
|
|
|
self.server.POSTbusy=False
|
|
|
|
return
|
|
|
|
self.postFromNickname=pathUsersSection.split('/')[0]
|
|
|
|
accountsDir=self.server.baseDir+'/accounts/'+self.postFromNickname+'@'+self.server.domain
|
|
|
|
if not os.path.isdir(accountsDir):
|
|
|
|
self.send_response(404)
|
|
|
|
self.end_headers()
|
|
|
|
self.server.POSTbusy=False
|
|
|
|
return
|
|
|
|
mediaBytes=self.rfile.read(length)
|
|
|
|
mediaFilenameBase=accountsDir+'/upload'
|
|
|
|
mediaFilename=mediaFilenameBase+'.png'
|
|
|
|
if self.headers['Content-type'].endswith('jpeg'):
|
|
|
|
mediaFilename=mediaFilenameBase+'.jpg'
|
|
|
|
if self.headers['Content-type'].endswith('gif'):
|
|
|
|
mediaFilename=mediaFilenameBase+'.gif'
|
|
|
|
with open(mediaFilename, 'wb') as avFile:
|
|
|
|
avFile.write(mediaBytes)
|
|
|
|
if self.server.debug:
|
|
|
|
print('DEBUG: image saved to '+mediaFilename)
|
|
|
|
self.send_response(201)
|
|
|
|
self.end_headers()
|
|
|
|
self.server.POSTbusy=False
|
|
|
|
return
|
|
|
|
|
|
|
|
# refuse to receive non-json content
|
|
|
|
if self.headers['Content-type'] != 'application/json':
|
|
|
|
print("POST is not json: "+self.headers['Content-type'])
|
2019-06-28 21:06:05 +00:00
|
|
|
self.send_response(400)
|
|
|
|
self.end_headers()
|
2019-07-01 14:30:48 +00:00
|
|
|
self.server.POSTbusy=False
|
2019-06-28 21:06:05 +00:00
|
|
|
return
|
2019-07-03 16:14:45 +00:00
|
|
|
|
|
|
|
if self.server.debug:
|
|
|
|
print('DEBUG: Reading message')
|
|
|
|
|
2019-07-01 11:48:54 +00:00
|
|
|
messageBytes=self.rfile.read(length)
|
2019-07-16 14:23:06 +00:00
|
|
|
messageJson=json.loads(messageBytes)
|
2019-07-18 11:35:48 +00:00
|
|
|
|
2019-07-03 21:37:46 +00:00
|
|
|
# https://www.w3.org/TR/activitypub/#object-without-create
|
|
|
|
if self.outboxAuthenticated:
|
2019-07-16 10:19:04 +00:00
|
|
|
if self._postToOutbox(messageJson):
|
2019-07-16 19:07:45 +00:00
|
|
|
if messageJson.get('id'):
|
2019-07-16 10:19:04 +00:00
|
|
|
self.headers['Location']= \
|
2019-07-16 19:07:45 +00:00
|
|
|
messageJson['id'].replace('/activity','')
|
2019-07-03 22:16:03 +00:00
|
|
|
self.send_response(201)
|
|
|
|
self.end_headers()
|
|
|
|
self.server.POSTbusy=False
|
|
|
|
return
|
|
|
|
else:
|
|
|
|
self.send_response(403)
|
|
|
|
self.end_headers()
|
|
|
|
self.server.POSTbusy=False
|
|
|
|
return
|
2019-07-03 21:37:46 +00:00
|
|
|
|
2019-07-02 15:07:27 +00:00
|
|
|
# check the necessary properties are available
|
2019-07-03 16:14:45 +00:00
|
|
|
if self.server.debug:
|
|
|
|
print('DEBUG: Check message has params')
|
|
|
|
|
2019-07-05 15:10:21 +00:00
|
|
|
if self.path.endswith('/inbox') or \
|
|
|
|
self.path=='/sharedInbox':
|
2019-07-03 21:37:46 +00:00
|
|
|
if not inboxMessageHasParams(messageJson):
|
2019-07-06 13:49:25 +00:00
|
|
|
if self.server.debug:
|
2019-07-16 10:19:04 +00:00
|
|
|
pprint(messageJson)
|
2019-07-06 13:49:25 +00:00
|
|
|
print("DEBUG: inbox message doesn't have the required parameters")
|
2019-07-03 21:37:46 +00:00
|
|
|
self.send_response(403)
|
|
|
|
self.end_headers()
|
|
|
|
self.server.POSTbusy=False
|
|
|
|
return
|
2019-07-02 15:07:27 +00:00
|
|
|
|
2019-07-06 17:00:22 +00:00
|
|
|
if not inboxPermittedMessage(self.server.domain, \
|
|
|
|
messageJson, \
|
2019-07-09 14:20:23 +00:00
|
|
|
self.server.federationList):
|
2019-07-03 16:14:45 +00:00
|
|
|
if self.server.debug:
|
2019-07-05 13:26:54 +00:00
|
|
|
# https://www.youtube.com/watch?v=K3PrSj9XEu4
|
2019-07-03 16:14:45 +00:00
|
|
|
print('DEBUG: Ah Ah Ah')
|
2019-06-28 21:59:54 +00:00
|
|
|
self.send_response(403)
|
|
|
|
self.end_headers()
|
2019-07-01 14:30:48 +00:00
|
|
|
self.server.POSTbusy=False
|
2019-07-01 11:48:54 +00:00
|
|
|
return
|
2019-07-01 14:30:48 +00:00
|
|
|
|
2019-07-05 10:21:10 +00:00
|
|
|
if self.server.debug:
|
|
|
|
pprint(messageJson)
|
2019-07-02 17:11:59 +00:00
|
|
|
|
2019-07-04 14:36:29 +00:00
|
|
|
if not self.headers.get('signature'):
|
|
|
|
if 'keyId=' not in self.headers['signature']:
|
|
|
|
if self.server.debug:
|
|
|
|
print('DEBUG: POST to inbox has no keyId in header signature parameter')
|
|
|
|
self.send_response(403)
|
|
|
|
self.end_headers()
|
|
|
|
self.server.POSTbusy=False
|
|
|
|
return
|
2019-07-04 10:02:56 +00:00
|
|
|
|
2019-07-03 16:14:45 +00:00
|
|
|
if self.server.debug:
|
2019-07-06 13:49:25 +00:00
|
|
|
print('DEBUG: POST saving to inbox queue')
|
2019-07-04 10:02:56 +00:00
|
|
|
if '/users/' in self.path:
|
|
|
|
pathUsersSection=self.path.split('/users/')[1]
|
|
|
|
if '/' not in pathUsersSection:
|
|
|
|
if self.server.debug:
|
|
|
|
print('DEBUG: This is not a users endpoint')
|
|
|
|
else:
|
|
|
|
self.postToNickname=pathUsersSection.split('/')[0]
|
|
|
|
if self.postToNickname:
|
2019-07-15 12:27:26 +00:00
|
|
|
queueStatus=self._updateInboxQueue(self.postToNickname,messageJson)
|
|
|
|
if queueStatus==0:
|
|
|
|
self.send_response(200)
|
|
|
|
self.end_headers()
|
|
|
|
self.server.POSTbusy=False
|
2019-07-04 12:23:53 +00:00
|
|
|
return
|
2019-07-15 12:27:26 +00:00
|
|
|
if queueStatus==1:
|
2019-07-15 12:28:41 +00:00
|
|
|
self.send_response(503)
|
2019-07-15 12:27:26 +00:00
|
|
|
self.end_headers()
|
|
|
|
self.server.POSTbusy=False
|
|
|
|
return
|
2019-07-04 12:23:53 +00:00
|
|
|
self.send_response(403)
|
2019-07-01 14:30:48 +00:00
|
|
|
self.end_headers()
|
|
|
|
self.server.POSTbusy=False
|
|
|
|
return
|
2019-07-04 12:23:53 +00:00
|
|
|
else:
|
2019-07-05 15:10:21 +00:00
|
|
|
if self.path == '/sharedInbox' or self.path == '/inbox':
|
2019-07-05 10:24:20 +00:00
|
|
|
print('DEBUG: POST to shared inbox')
|
2019-07-15 12:27:26 +00:00
|
|
|
queueStatus-_updateInboxQueue('inbox',messageJson)
|
|
|
|
if queueStatus==0:
|
|
|
|
self.send_response(200)
|
|
|
|
self.end_headers()
|
|
|
|
self.server.POSTbusy=False
|
2019-07-05 11:27:18 +00:00
|
|
|
return
|
2019-07-15 12:27:26 +00:00
|
|
|
if queueStatus==1:
|
2019-07-15 12:28:41 +00:00
|
|
|
self.send_response(503)
|
2019-07-15 12:27:26 +00:00
|
|
|
self.end_headers()
|
|
|
|
self.server.POSTbusy=False
|
|
|
|
return
|
2019-07-05 10:24:20 +00:00
|
|
|
self.send_response(200)
|
|
|
|
self.end_headers()
|
|
|
|
self.server.POSTbusy=False
|
2019-07-03 16:14:45 +00:00
|
|
|
|
2019-07-13 09:37:17 +00:00
|
|
|
def runDaemon(clientToServer: bool,baseDir: str,domain: str, \
|
|
|
|
port=80,httpPrefix='https', \
|
2019-07-09 18:11:23 +00:00
|
|
|
fedList=[],noreply=False,nolike=False,nopics=False, \
|
|
|
|
noannounce=False,cw=False,ocapAlways=False, \
|
2019-07-15 10:22:19 +00:00
|
|
|
useTor=False,maxReplies=64, \
|
2019-07-15 10:27:53 +00:00
|
|
|
domainMaxPostsPerDay=8640,accountMaxPostsPerDay=8640, \
|
2019-07-17 18:13:45 +00:00
|
|
|
allowDeletion=False,debug=False) -> None:
|
2019-06-28 18:55:29 +00:00
|
|
|
if len(domain)==0:
|
2019-07-03 09:24:55 +00:00
|
|
|
domain='localhost'
|
2019-06-28 18:55:29 +00:00
|
|
|
if '.' not in domain:
|
2019-07-03 12:24:54 +00:00
|
|
|
if domain != 'localhost':
|
|
|
|
print('Invalid domain: ' + domain)
|
|
|
|
return
|
2019-06-28 18:55:29 +00:00
|
|
|
|
2019-07-03 09:24:55 +00:00
|
|
|
serverAddress = ('', port)
|
2019-07-01 21:01:43 +00:00
|
|
|
httpd = ThreadingHTTPServer(serverAddress, PubServer)
|
2019-06-30 20:03:23 +00:00
|
|
|
httpd.domain=domain
|
|
|
|
httpd.port=port
|
2019-07-25 09:13:02 +00:00
|
|
|
httpd.domainFull=domain
|
|
|
|
if port!=80 and port!=443:
|
|
|
|
httpd.domainFull=domain+':'+str(port)
|
2019-07-03 19:00:03 +00:00
|
|
|
httpd.httpPrefix=httpPrefix
|
2019-07-03 16:14:45 +00:00
|
|
|
httpd.debug=debug
|
2019-06-30 20:03:23 +00:00
|
|
|
httpd.federationList=fedList.copy()
|
2019-07-04 20:25:19 +00:00
|
|
|
httpd.baseDir=baseDir
|
2019-07-01 14:30:48 +00:00
|
|
|
httpd.personCache={}
|
|
|
|
httpd.cachedWebfingers={}
|
|
|
|
httpd.useTor=useTor
|
2019-07-02 17:11:59 +00:00
|
|
|
httpd.session = None
|
|
|
|
httpd.sessionLastUpdate=0
|
2019-07-01 14:30:48 +00:00
|
|
|
httpd.lastGET=0
|
|
|
|
httpd.lastPOST=0
|
|
|
|
httpd.GETbusy=False
|
|
|
|
httpd.POSTbusy=False
|
2019-07-01 21:01:43 +00:00
|
|
|
httpd.receivedMessage=False
|
2019-07-04 10:02:56 +00:00
|
|
|
httpd.inboxQueue=[]
|
2019-07-05 18:57:19 +00:00
|
|
|
httpd.sendThreads=[]
|
|
|
|
httpd.postLog=[]
|
2019-07-15 12:27:26 +00:00
|
|
|
httpd.maxQueueLength=16
|
2019-07-07 17:47:37 +00:00
|
|
|
httpd.ocapAlways=ocapAlways
|
2019-07-16 14:23:06 +00:00
|
|
|
httpd.maxMessageLength=5000
|
|
|
|
httpd.maxImageSize=10*1024*1024
|
2019-07-17 19:31:52 +00:00
|
|
|
httpd.allowDeletion=allowDeletion
|
2019-07-24 22:38:42 +00:00
|
|
|
httpd.lastLoginTime=0
|
2019-07-25 14:24:42 +00:00
|
|
|
httpd.salts={}
|
|
|
|
httpd.tokens={}
|
|
|
|
httpd.tokensLookup={}
|
2019-07-09 17:54:08 +00:00
|
|
|
httpd.acceptedCaps=["inbox:write","objects:read"]
|
|
|
|
if noreply:
|
|
|
|
httpd.acceptedCaps.append('inbox:noreply')
|
|
|
|
if nolike:
|
|
|
|
httpd.acceptedCaps.append('inbox:nolike')
|
2019-07-09 18:11:23 +00:00
|
|
|
if nopics:
|
|
|
|
httpd.acceptedCaps.append('inbox:nopics')
|
|
|
|
if noannounce:
|
|
|
|
httpd.acceptedCaps.append('inbox:noannounce')
|
|
|
|
if cw:
|
|
|
|
httpd.acceptedCaps.append('inbox:cw')
|
2019-07-11 12:29:31 +00:00
|
|
|
|
2019-07-18 13:10:26 +00:00
|
|
|
if not os.path.isdir(baseDir+'/accounts/inbox@'+domain):
|
|
|
|
print('Creating shared inbox: inbox@'+domain)
|
|
|
|
createSharedInbox(baseDir,'inbox',domain,port,httpPrefix)
|
2019-07-12 09:52:06 +00:00
|
|
|
|
|
|
|
print('Creating inbox queue')
|
2019-07-06 17:00:22 +00:00
|
|
|
httpd.thrInboxQueue= \
|
|
|
|
threadWithTrace(target=runInboxQueue, \
|
|
|
|
args=(baseDir,httpPrefix,httpd.sendThreads, \
|
|
|
|
httpd.postLog,httpd.cachedWebfingers, \
|
|
|
|
httpd.personCache,httpd.inboxQueue, \
|
|
|
|
domain,port,useTor,httpd.federationList, \
|
2019-07-13 21:00:12 +00:00
|
|
|
httpd.ocapAlways,maxReplies, \
|
2019-07-15 10:22:19 +00:00
|
|
|
domainMaxPostsPerDay,accountMaxPostsPerDay, \
|
2019-07-17 18:13:45 +00:00
|
|
|
allowDeletion,debug,httpd.acceptedCaps),daemon=True)
|
2019-07-04 12:23:53 +00:00
|
|
|
httpd.thrInboxQueue.start()
|
2019-07-13 09:37:17 +00:00
|
|
|
if clientToServer:
|
|
|
|
print('Running ActivityPub client on ' + domain + ' port ' + str(port))
|
|
|
|
else:
|
|
|
|
print('Running ActivityPub server on ' + domain + ' port ' + str(port))
|
2019-06-28 18:55:29 +00:00
|
|
|
httpd.serve_forever()
|