epicyon/daemon.py

5514 lines
266 KiB
Python
Raw Normal View History

2019-06-28 18:55:29 +00:00
__filename__ = "daemon.py"
__author__ = "Bob Mottram"
__license__ = "AGPL3+"
2019-08-29 13:35:29 +00:00
__version__ = "1.0.0"
2019-06-28 18:55:29 +00:00
__maintainer__ = "Bob Mottram"
__email__ = "bob@freedombone.net"
__status__ = "Production"
2019-08-15 12:01:56 +00:00
from http.server import BaseHTTPRequestHandler,ThreadingHTTPServer
2019-06-28 18:55:29 +00:00
#import socketserver
import json
2019-07-01 14:30:48 +00:00
import time
2019-07-27 20:30:58 +00:00
import base64
2019-09-07 08:57:52 +00:00
import locale
2019-07-27 20:30:58 +00:00
# used for mime decoding of message POST
import email.parser
# for saving images
from binascii import a2b_base64
from hashlib import sha256
2019-06-28 18:55:29 +00:00
from session import createSession
from webfinger import webfingerMeta
2019-11-13 10:32:12 +00:00
from webfinger import webfingerNodeInfo
2019-06-28 18:55:29 +00:00
from webfinger import webfingerLookup
2019-07-01 21:01:43 +00:00
from webfinger import webfingerHandle
2019-11-13 10:32:12 +00:00
from metadata import metaDataNodeInfo
2019-11-13 12:45:41 +00:00
from metadata import metaDataInstance
from donate import getDonationUrl
from donate import setDonationUrl
from person import activateAccount
from person import deactivateAccount
2019-08-08 13:38:33 +00:00
from person import registerAccount
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-08-13 09:24:55 +00:00
from person import isSuspended
2019-08-13 11:59:38 +00:00
from person import suspendAccount
from person import unsuspendAccount
2019-08-13 13:58:48 +00:00
from person import removeAccount
from person import canRemovePost
2019-11-06 11:39:41 +00:00
from person import personSnooze
from person import personUnsnooze
2019-11-25 22:34:26 +00:00
from posts import createQuestionPost
from posts import outboxMessageCreateWrap
2019-07-04 16:24:23 +00:00
from posts import savePostToBox
2019-11-04 10:43:19 +00:00
from posts import sendToFollowersThread
2019-07-15 17:22:51 +00:00
from posts import postIsAddressedToPublic
2019-07-15 18:20:52 +00:00
from posts import sendToNamedAddresses
2019-07-27 22:48:34 +00:00
from posts import createPublicPost
2019-08-11 11:25:27 +00:00
from posts import createReportPost
2019-07-28 11:08:14 +00:00
from posts import createUnlistedPost
2019-07-27 22:48:34 +00:00
from posts import createFollowersOnlyPost
from posts import createDirectMessagePost
2019-08-02 18:37:23 +00:00
from posts import populateRepliesJson
2019-08-18 09:39:12 +00:00
from posts import addToField
2019-08-20 11:51:29 +00:00
from posts import expireCache
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-09-02 21:52:43 +00:00
from inbox import runInboxQueueWatchdog
2019-07-04 14:36:29 +00:00
from inbox import savePostToInboxQueue
2019-08-02 18:04:31 +00:00
from inbox import populateReplies
2019-09-25 10:22:49 +00:00
from inbox import getPersonPubKey
2019-11-18 17:02:16 +00:00
from inbox import inboxUpdateIndex
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-29 16:13:48 +00:00
from follow import sendFollowRequest
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-11-12 10:59:17 +00:00
from auth import storeBasicCredentials
2019-07-04 12:23:53 +00:00
from threads import threadWithTrace
2019-10-16 18:19:18 +00:00
from threads import removeDormantThreads
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-11-17 14:01:49 +00:00
from bookmarks import outboxBookmark
from bookmarks import outboxUndoBookmark
2019-07-17 21:40:56 +00:00
from blocking import outboxBlock
from blocking import outboxUndoBlock
from blocking import addBlock
from blocking import removeBlock
from blocking import addGlobalBlock
from blocking import removeGlobalBlock
2019-08-14 10:32:15 +00:00
from blocking import isBlockedHashtag
2019-09-09 18:39:46 +00:00
from blocking import isBlockedDomain
2019-07-18 13:10:26 +00:00
from config import setConfigParam
2019-08-12 21:20:47 +00:00
from config import getConfigParam
2019-07-18 15:09:23 +00:00
from roles import outboxDelegate
2019-08-12 21:20:47 +00:00
from roles import setRole
from roles import clearModeratorStatus
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-08-27 12:47:11 +00:00
from webinterface import htmlDeletePost
2019-08-26 16:02:47 +00:00
from webinterface import htmlAbout
from webinterface import htmlRemoveSharedItem
2019-08-25 17:34:09 +00:00
from webinterface import htmlInboxDMs
2019-09-23 20:09:11 +00:00
from webinterface import htmlInboxReplies
2019-09-28 11:29:42 +00:00
from webinterface import htmlInboxMedia
2019-08-24 23:00:03 +00:00
from webinterface import htmlUnblockConfirm
2019-08-24 21:10:20 +00:00
from webinterface import htmlPersonOptions
2019-07-20 21:13:36 +00:00
from webinterface import htmlIndividualPost
from webinterface import htmlProfile
from webinterface import htmlInbox
2019-11-17 14:01:49 +00:00
from webinterface import htmlBookmarks
2019-11-02 14:31:39 +00:00
from webinterface import htmlShares
2019-07-20 21:13:36 +00:00
from webinterface import htmlOutbox
2019-08-12 13:22:17 +00:00
from webinterface import htmlModeration
2019-07-20 21:13:36 +00:00
from webinterface import htmlPostReplies
2019-07-24 22:38:42 +00:00
from webinterface import htmlLogin
2019-08-13 09:24:55 +00:00
from webinterface import htmlSuspended
2019-07-24 22:38:42 +00:00
from webinterface import htmlGetLoginCredentials
2019-07-25 21:39:09 +00:00
from webinterface import htmlNewPost
2019-07-29 09:49:46 +00:00
from webinterface import htmlFollowConfirm
2019-10-10 14:43:21 +00:00
from webinterface import htmlCalendar
2019-07-30 22:34:04 +00:00
from webinterface import htmlSearch
2019-08-19 19:02:28 +00:00
from webinterface import htmlSearchEmoji
2019-08-19 20:01:29 +00:00
from webinterface import htmlSearchEmojiTextEntry
2019-07-29 20:36:26 +00:00
from webinterface import htmlUnfollowConfirm
2019-07-30 22:34:04 +00:00
from webinterface import htmlProfileAfterSearch
2019-08-02 09:52:12 +00:00
from webinterface import htmlEditProfile
2019-08-08 13:38:33 +00:00
from webinterface import htmlTermsOfService
2019-08-27 23:01:40 +00:00
from webinterface import htmlSkillsSearch
2019-08-10 10:54:52 +00:00
from webinterface import htmlHashtagSearch
2019-08-13 17:25:39 +00:00
from webinterface import htmlModerationInfo
2019-08-13 21:32:18 +00:00
from webinterface import htmlSearchSharedItems
2019-08-14 10:32:15 +00:00
from webinterface import htmlHashtagBlocked
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-07-28 08:34:49 +00:00
from shares import addShare
2019-08-26 09:31:45 +00:00
from shares import removeShare
2019-10-17 09:58:30 +00:00
from shares import expireShares
2019-11-25 13:50:39 +00:00
from utils import getCachedPostFilename
from utils import removePostFromCache
2019-07-29 16:13:48 +00:00
from utils import getNicknameFromActor
from utils import getDomainFromActor
2019-08-21 17:13:08 +00:00
from utils import getStatusNumber
2019-09-25 10:22:49 +00:00
from utils import urlPermitted
2019-10-22 11:55:06 +00:00
from utils import loadJson
from utils import saveJson
2019-07-29 19:14:14 +00:00
from manualapprove import manualDenyFollowRequest
from manualapprove import manualApproveFollowRequest
2019-07-31 16:47:45 +00:00
from announce import createAnnounce
from announce import outboxAnnounce
2019-08-09 09:09:21 +00:00
from content import addHtmlTags
2019-11-10 11:37:24 +00:00
from content import extractMediaInFormPOST
from content import saveMediaInFormPOST
from content import extractTextFieldsInPOST
2019-08-02 09:52:12 +00:00
from media import removeMetaData
2019-08-22 15:14:05 +00:00
from cache import storePersonInCache
2019-11-06 23:20:00 +00:00
from cache import getPersonFromCache
2019-09-25 10:22:49 +00:00
from httpsig import verifyPostHeaders
2019-11-23 14:13:25 +00:00
from theme import setTheme
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
2019-10-19 08:42:54 +00:00
maxPostsInFeed=12
2019-06-29 14:35:26 +00:00
2019-09-28 18:06:17 +00:00
# reduced posts for media feed because it can take a while
maxPostsInMediaFeed=6
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-11-03 15:27:29 +00:00
def readFollowList(filename: str) -> None:
2019-06-28 18:55:29 +00:00
"""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-11-10 11:37:24 +00:00
protocol_version = 'HTTP/1.1'
2019-11-25 11:19:03 +00:00
2019-11-25 13:34:44 +00:00
def _sendReplyToQuestion(self,nickname: str,messageId: str,answer: str) -> None:
"""Sends a reply to a question
2019-11-25 12:43:00 +00:00
"""
votesFilename= \
self.server.baseDir+'/accounts/'+ \
2019-11-25 13:34:44 +00:00
nickname+'@'+self.server.domain+'/questions.txt'
2019-11-25 12:43:00 +00:00
# have we already voted on this?
if messageId in open(votesFilename).read():
print('Already voted on message '+messageId)
return
print('Voting on message '+messageId)
print('Vote for: '+answer)
messageJson= \
createPublicPost(self.server.baseDir, \
nickname, \
self.server.domain,self.server.port, \
self.server.httpPrefix, \
answer,False,False,False, \
None,None,None,True, \
messageId,messageId,None, \
None,None,None)
if messageJson:
self.postToNickname=nickname
if self._postToOutbox(messageJson,__version__):
populateReplies(self.server.baseDir, \
self.server.httpPrefix, \
self.server.domainFull, \
messageJson, \
self.server.maxReplies, \
self.server.debug)
# record the vote
votesFile=open(votesFilename,'a+')
if votesFile:
votesFile.write(messageId+'\n')
votesFile.close()
2019-11-25 13:48:07 +00:00
# ensure that the cached post is removed if it exists, so
# that it then will be recreated
cachedPostFilename= \
getCachedPostFilename(self.server.baseDir, \
nickname, \
self.server.domain,messageJson)
if cachedPostFilename:
if os.path.isfile(cachedPostFilename):
os.remove(cachedPostFilename)
2019-11-25 13:48:07 +00:00
# remove from memory cache
removePostFromCache(messageJson,self.server.recentPostsCache)
2019-11-25 12:43:00 +00:00
else:
print('ERROR: unable to post vote to outbox')
else:
print('ERROR: unable to create vote')
2019-11-25 11:19:03 +00:00
def _removePostInteractions(self,postJsonObject: {}) -> None:
"""Removes potentially sensitive interactions from a post
This is the type of thing which would be of interest to marketers
or of saleable value to them. eg. Knowing who likes who or what.
"""
if postJsonObject.get('likes'):
postJsonObject['likes']={'items': []}
if postJsonObject.get('shares'):
postJsonObject['shares']={}
if postJsonObject.get('replies'):
postJsonObject['replies']={}
if postJsonObject.get('bookmarks'):
postJsonObject['bookmarks']={}
if not postJsonObject.get('object'):
return
if not isinstance(postJsonObject['object'], dict):
return
if postJsonObject['object'].get('likes'):
postJsonObject['object']['likes']={'items': []}
if postJsonObject['object'].get('shares'):
postJsonObject['object']['shares']={}
if postJsonObject['object'].get('replies'):
postJsonObject['object']['replies']={}
if postJsonObject['object'].get('bookmarks'):
postJsonObject['object']['bookmarks']={}
2019-08-24 11:23:12 +00:00
def _requestHTTP(self) -> bool:
"""Should a http response be given?
"""
2019-08-31 12:38:57 +00:00
if not self.headers.get('Accept'):
return False
2019-11-26 14:32:09 +00:00
if self.server.debug:
print('ACCEPT: '+self.headers['Accept'])
2019-08-27 16:27:48 +00:00
if 'image/' in self.headers['Accept']:
return False
if self.headers['Accept'].startswith('*'):
2019-08-24 11:28:43 +00:00
return False
2019-08-24 11:23:12 +00:00
if 'json' in self.headers['Accept']:
return False
return True
2019-09-25 09:22:10 +00:00
def _fetchAuthenticated(self) -> bool:
"""http authentication of GET requests for json
"""
if not self.server.authenticatedFetch:
return True
2019-09-25 10:22:49 +00:00
# check that the headers are signed
if not self.headers.get('signature'):
if self.server.debug:
print('WARN: authenticated fetch, GET has no signature in headers')
return False
# get the keyId
keyId=None
signatureParams=self.headers['signature'].split(',')
for signatureItem in signatureParams:
if signatureItem.startswith('keyId='):
if '"' in signatureItem:
keyId=signatureItem.split('"')[1]
break
if not keyId:
if self.server.debug:
print('WARN: authenticated fetch, failed to obtain keyId from signature')
return False
# is the keyId (actor) valid?
if not urlPermitted(keyId,self.server.federationList,"inbox:read"):
if self.server.debug:
print('Authorized fetch failed: '+keyId+' is not permitted')
return False
# make sure we have a session
if not self.server.session:
if self.server.debug:
print('DEBUG: creating new session during authenticated fetch')
self.server.session= \
2019-11-13 10:50:16 +00:00
createSession(self.server.useTor)
2019-09-25 10:22:49 +00:00
# obtain the public key
pubKey= \
getPersonPubKey(self.server.baseDir,self.server.session,keyId, \
self.server.personCache,self.server.debug, \
2019-11-03 15:27:29 +00:00
__version__,self.server.httpPrefix, \
self.server.domain)
2019-09-25 10:22:49 +00:00
if not pubKey:
if self.server.debug:
2019-11-03 15:27:29 +00:00
print('DEBUG: Authenticated fetch failed to obtain public key for '+ \
keyId)
2019-09-25 10:22:49 +00:00
return False
# it is assumed that there will be no message body on authenticated fetches
# and also consequently no digest
GETrequestBody=''
GETrequestDigest=None
# verify the GET request without any digest
if verifyPostHeaders(self.server.httpPrefix, \
pubKey,self.headers, \
self.path,True, \
2019-11-12 15:03:17 +00:00
GETrequestDigest, \
GETrequestBody,debug):
2019-09-25 10:22:49 +00:00
return True
return False
2019-09-25 09:22:10 +00:00
2019-08-15 13:20:09 +00:00
def _login_headers(self,fileFormat: str,length: int) -> None:
2019-06-28 18:55:29 +00:00
self.send_response(200)
self.send_header('Content-type', fileFormat)
2019-08-15 13:20:09 +00:00
self.send_header('Content-Length', str(length))
2019-07-25 09:13:02 +00:00
self.send_header('Host', self.server.domainFull)
2019-11-03 15:27:29 +00:00
self.send_header('WWW-Authenticate', \
'title="Login to Epicyon", Basic realm="epicyon"')
self.send_header('X-Robots-Tag','noindex')
2019-06-28 18:55:29 +00:00
self.end_headers()
def _logout_headers(self,fileFormat: str,length: int) -> None:
self.send_response(200)
self.send_header('Content-type', fileFormat)
self.send_header('Content-Length', str(length))
self.send_header('Set-Cookie', 'epicyon=; SameSite=Strict')
self.send_header('Host', self.server.domainFull)
2019-11-03 15:27:29 +00:00
self.send_header('WWW-Authenticate', \
'title="Login to Epicyon", Basic realm="epicyon"')
self.send_header('X-Robots-Tag','noindex')
self.end_headers()
2019-08-15 13:20:09 +00:00
def _set_headers(self,fileFormat: str,length: int,cookie: str) -> None:
2019-08-15 12:22:34 +00:00
self.send_response(200)
self.send_header('Content-type', fileFormat)
self.send_header('Content-Length', str(length))
if cookie:
self.send_header('Cookie', cookie)
self.send_header('Host', self.server.domainFull)
self.send_header('InstanceID', self.server.instanceId)
self.send_header('X-Robots-Tag','noindex')
2019-08-15 12:22:34 +00:00
self.end_headers()
2019-07-29 16:13:48 +00:00
def _redirect_headers(self,redirect: str,cookie: str) -> None:
2019-07-28 14:09:48 +00:00
self.send_response(303)
#self.send_header('Content-type', 'text/html')
2019-07-29 16:13:48 +00:00
if cookie:
self.send_header('Cookie', cookie)
2019-11-26 15:30:13 +00:00
if '://' not in redirect:
print('REDIRECT ERROR: redirect is not an absolute url '+redirect)
2019-07-28 14:09:48 +00:00
self.send_header('Location', redirect)
self.send_header('Host', self.server.domainFull)
2019-08-02 12:49:34 +00:00
self.send_header('InstanceID', self.server.instanceId)
self.send_header('Content-Length', '0')
self.send_header('X-Robots-Tag','noindex')
2019-07-28 14:09:48 +00:00
self.end_headers()
2019-11-15 14:04:54 +00:00
def _httpReturnCode(self,httpCode: int,httpDescription: str) -> None:
2019-11-15 14:03:43 +00:00
msg="<html><head></head><body><h1>"+str(httpCode)+" "+httpDescription+"</h1></body></html>"
msg=msg.encode('utf-8')
self.send_response(httpCode)
2019-06-28 18:55:29 +00:00
self.send_header('Content-Type', 'text/html; charset=utf-8')
2019-08-15 13:20:09 +00:00
self.send_header('Content-Length', str(len(msg)))
self.send_header('X-Robots-Tag','noindex')
2019-06-28 18:55:29 +00:00
self.end_headers()
2019-08-16 19:45:48 +00:00
try:
self.wfile.write(msg)
except Exception as e:
2019-11-15 14:03:43 +00:00
print('Error when showing '+str(httpCode))
2019-08-16 19:45:48 +00:00
print(e)
2019-06-28 18:55:29 +00:00
2019-11-15 14:03:43 +00:00
def _404(self) -> None:
self._httpReturnCode(404,'Not Found')
def _400(self) -> None:
self._httpReturnCode(400,'Bad Request')
2019-11-15 21:43:20 +00:00
def _503(self) -> None:
self._httpReturnCode(503,'Service Unavailable')
2019-11-15 14:03:43 +00:00
2019-10-22 12:35:51 +00:00
def _write(self,msg) -> None:
2019-10-16 13:45:51 +00:00
tries=0
while tries<5:
try:
self.wfile.write(msg)
break
except Exception as e:
print(e)
time.sleep(1)
tries+=1
2019-10-22 12:35:51 +00:00
def _robotsTxt(self) -> bool:
if not self.path.lower().startswith('/robot'):
return False
msg='User-agent: *\nDisallow: /'
msg=msg.encode('utf-8')
self._set_headers('text/plain; charset=utf-8',len(msg),None)
self._write(msg)
return True
2019-11-13 11:24:27 +00:00
def _mastoApi(self) -> bool:
"""This is a vestigil mastodon API for the purpose
of returning an empty result to sites like
https://mastopeek.app-dist.eu
"""
if not self.path.startswith('/api/v1/'):
return False
if self.server.debug:
print('DEBUG: mastodon api '+self.path)
2019-11-13 12:45:41 +00:00
if self.path=='/api/v1/instance':
adminNickname=getConfigParam(self.server.baseDir,'admin')
instanceDescriptionShort=getConfigParam(self.server.baseDir,'instanceDescriptionShort')
instanceDescription=getConfigParam(self.server.baseDir,'instanceDescription')
instanceTitle=getConfigParam(self.server.baseDir,'instanceTitle')
instanceJson= \
metaDataInstance(instanceTitle, \
instanceDescriptionShort, \
instanceDescription, \
self.server.httpPrefix, \
self.server.baseDir, \
adminNickname, \
self.server.domain,self.server.domainFull, \
self.server.registration, \
self.server.systemLanguage, \
self.server.projectVersion)
msg=json.dumps(instanceJson).encode('utf-8')
2019-11-13 15:41:03 +00:00
if self.headers.get('Accept'):
if 'application/ld+json' in self.headers['Accept']:
self._set_headers('application/ld+json',len(msg),None)
else:
self._set_headers('application/json',len(msg),None)
2019-11-13 14:21:21 +00:00
else:
2019-11-13 15:41:03 +00:00
self._set_headers('application/ld+json',len(msg),None)
2019-11-13 12:45:41 +00:00
self._write(msg)
2019-11-13 14:32:44 +00:00
print('instance metadata sent')
2019-11-13 12:45:41 +00:00
return True
2019-11-13 11:26:02 +00:00
if self.path.startswith('/api/v1/instance/peers'):
2019-11-13 11:24:27 +00:00
# This is just a dummy result.
# Showing the full list of peers would have privacy implications.
# On a large instance you are somewhat lost in the crowd, but on small
# instances a full list of peers would convey a lot of information about
# the interests of a small number of accounts
2019-11-13 11:40:56 +00:00
msg=json.dumps(['mastodon.social',self.server.domainFull]).encode('utf-8')
2019-11-13 15:41:03 +00:00
if self.headers.get('Accept'):
if 'application/ld+json' in self.headers['Accept']:
self._set_headers('application/ld+json',len(msg),None)
else:
self._set_headers('application/json',len(msg),None)
2019-11-13 14:21:21 +00:00
else:
2019-11-13 15:41:03 +00:00
self._set_headers('application/ld+json',len(msg),None)
2019-11-13 11:24:27 +00:00
self._write(msg)
2019-11-13 14:32:44 +00:00
print('instance peers metadata sent')
2019-11-13 11:24:27 +00:00
return True
2019-11-13 11:26:02 +00:00
if self.path.startswith('/api/v1/instance/activity'):
2019-11-13 11:24:27 +00:00
# This is just a dummy result.
msg=json.dumps([]).encode('utf-8')
2019-11-13 15:41:03 +00:00
if self.headers.get('Accept'):
if 'application/ld+json' in self.headers['Accept']:
self._set_headers('application/ld+json',len(msg),None)
else:
self._set_headers('application/json',len(msg),None)
2019-11-13 14:21:21 +00:00
else:
2019-11-13 15:41:03 +00:00
self._set_headers('application/ld+json',len(msg),None)
2019-11-13 11:24:27 +00:00
self._write(msg)
2019-11-13 14:32:44 +00:00
print('instance activity metadata sent')
2019-11-13 11:24:27 +00:00
return True
2019-11-15 13:45:21 +00:00
self._404()
return True
2019-11-13 11:24:27 +00:00
2019-11-13 10:32:12 +00:00
def _nodeinfo(self) -> bool:
if not self.path.startswith('/nodeinfo/2.0'):
return False
if self.server.debug:
2019-11-15 13:45:21 +00:00
print('DEBUG: nodeinfo '+self.path)
info=metaDataNodeInfo(self.server.baseDir,self.server.registration,self.server.projectVersion)
2019-11-13 10:32:12 +00:00
if info:
2019-11-13 10:59:17 +00:00
msg=json.dumps(info).encode('utf-8')
2019-11-13 15:41:03 +00:00
if self.headers.get('Accept'):
if 'application/ld+json' in self.headers['Accept']:
self._set_headers('application/ld+json',len(msg),None)
else:
self._set_headers('application/json',len(msg),None)
2019-11-13 14:21:21 +00:00
else:
2019-11-13 15:41:03 +00:00
self._set_headers('application/ld+json',len(msg),None)
2019-11-13 10:32:12 +00:00
self._write(msg)
2019-11-13 14:32:44 +00:00
print('nodeinfo sent')
2019-11-15 13:45:21 +00:00
return True
self._404()
2019-11-13 10:32:12 +00:00
return True
2019-11-13 12:45:41 +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'):
2019-08-16 20:52:55 +00:00
wfResult=webfingerMeta(self.server.httpPrefix,self.server.domainFull)
2019-06-28 18:55:29 +00:00
if wfResult:
2019-08-15 13:20:09 +00:00
msg=wfResult.encode('utf-8')
2019-11-09 21:39:04 +00:00
self._set_headers('application/xrd+xml',len(msg),None)
2019-10-22 12:35:51 +00:00
self._write(msg)
2019-11-15 13:45:21 +00:00
return True
self._404()
2019-11-13 10:32:12 +00:00
return True
if self.path.startswith('/.well-known/nodeinfo'):
wfResult=webfingerNodeInfo(self.server.httpPrefix,self.server.domainFull)
if wfResult:
2019-11-13 10:59:17 +00:00
msg=json.dumps(wfResult).encode('utf-8')
2019-11-13 15:41:03 +00:00
if self.headers.get('Accept'):
if 'application/ld+json' in self.headers['Accept']:
self._set_headers('application/ld+json',len(msg),None)
else:
self._set_headers('application/json',len(msg),None)
2019-11-13 14:21:21 +00:00
else:
2019-11-13 15:41:03 +00:00
self._set_headers('application/ld+json',len(msg),None)
2019-11-13 10:32:12 +00:00
self._write(msg)
2019-11-15 13:45:21 +00:00
return True
self._404()
2019-11-13 10:32:12 +00:00
return True
2019-06-28 18:55:29 +00:00
2019-07-03 16:14:45 +00:00
if self.server.debug:
print('DEBUG: WEBFINGER lookup '+self.path+' '+str(self.server.baseDir))
wfResult=webfingerLookup(self.path,self.server.baseDir,self.server.port,self.server.debug)
2019-06-28 18:55:29 +00:00
if wfResult:
2019-11-09 21:39:04 +00:00
msg=json.dumps(wfResult).encode('utf-8')
self._set_headers('application/jrd+json',len(msg),None)
2019-10-22 12:35:51 +00:00
self._write(msg)
2019-06-28 18:55:29 +00:00
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
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-09-03 17:07:00 +00:00
2019-09-03 17:16:26 +00:00
def _postToOutbox(self,messageJson: {},version: str) -> bool:
"""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-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
2019-09-09 18:39:46 +00:00
testDomain,testPort=getDomainFromActor(messageJson['actor'])
if testPort:
if testPort!=80 and testPort!=443:
testDomain=testDomain+':'+str(testPort)
if isBlockedDomain(self.server.baseDir,testDomain):
if self.server.debug:
print('DEBUG: domain is blocked: '+messageJson['actor'])
return False
2019-07-03 22:44:03 +00:00
# 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'
2019-11-03 15:27:29 +00:00
mediaTypeStr= \
messageJson['object']['attachment'][attachmentIndex]['mediaType']
if mediaTypeStr.endswith('jpeg'):
2019-07-16 16:08:21 +00:00
fileExtension='jpg'
2019-11-03 15:27:29 +00:00
elif mediaTypeStr.endswith('gif'):
2019-07-16 16:08:21 +00:00
fileExtension='gif'
2019-11-14 15:11:20 +00:00
elif mediaTypeStr.endswith('webp'):
fileExtension='webp'
2019-11-03 15:27:29 +00:00
elif mediaTypeStr.endswith('audio/mpeg'):
2019-08-30 19:29:26 +00:00
fileExtension='mp3'
2019-11-03 15:27:29 +00:00
elif mediaTypeStr.endswith('ogg'):
2019-08-30 19:29:26 +00:00
fileExtension='ogg'
2019-11-03 15:27:29 +00:00
elif mediaTypeStr.endswith('mp4'):
2019-08-30 19:29:26 +00:00
fileExtension='mp4'
2019-11-03 15:27:29 +00:00
elif mediaTypeStr.endswith('webm'):
2019-08-30 19:29:26 +00:00
fileExtension='webm'
2019-11-03 15:27:29 +00:00
elif mediaTypeStr.endswith('ogv'):
2019-08-30 19:29:26 +00:00
fileExtension='ogv'
2019-11-03 15:27:29 +00:00
mediaDir= \
self.server.baseDir+'/accounts/'+ \
self.postToNickname+'@'+self.server.domain
2019-07-16 16:08:21 +00:00
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']= \
2019-11-03 15:27:29 +00:00
self.server.httpPrefix+'://'+self.server.domainFull+ \
'/'+mediaPath
2019-11-07 20:19:19 +00:00
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-11-17 14:01:49 +00:00
'Delegate','Skill','Bookmark'
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')
return False
2019-07-03 22:59:56 +00:00
if messageJson.get('id'):
2019-08-16 19:48:32 +00:00
postId=messageJson['id'].replace('/activity','').replace('/undo','')
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:
2019-07-17 17:16:48 +00:00
print('DEBUG: savePostToBox')
2019-11-07 20:12:43 +00:00
if messageJson['type']!='Upgrade':
2019-11-18 17:02:16 +00:00
savedFilename= \
savePostToBox(self.server.baseDir, \
self.server.httpPrefix, \
postId, \
self.postToNickname, \
self.server.domainFull,messageJson,'outbox')
2019-11-18 17:26:38 +00:00
if messageJson['type']=='Create' or \
2019-11-18 18:39:06 +00:00
messageJson['type']=='Question' or \
2019-11-18 17:26:38 +00:00
messageJson['type']=='Note' or \
messageJson['type']=='Announce':
inboxUpdateIndex('outbox',self.server.baseDir, \
self.postToNickname+'@'+self.server.domain, \
savedFilename,self.server.debug)
2019-11-24 13:46:28 +00:00
if outboxAnnounce(self.server.recentPostsCache, \
self.server.baseDir,messageJson,self.server.debug):
if self.server.debug:
print('DEBUG: Updated announcements (shares) collection for the post associated with the Announce activity')
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= \
2019-11-13 10:50:16 +00:00
createSession(self.server.useTor)
2019-07-15 17:22:51 +00:00
if self.server.debug:
print('DEBUG: sending c2s post to followers')
2019-11-04 11:19:25 +00:00
# remove inactive threads
inactiveFollowerThreads=[]
for th in self.server.followersThreads:
if not th.is_alive():
inactiveFollowerThreads.append(th)
for th in inactiveFollowerThreads:
self.server.followersThreads.remove(th)
2019-11-07 13:44:00 +00:00
if self.server.debug:
print('DEBUG: '+str(len(self.server.followersThreads))+' followers threads active')
2019-11-07 19:59:59 +00:00
# retain up to 20 threads
if len(self.server.followersThreads)>20:
# kill the thread if it is still alive
if self.server.followersThreads[0].is_alive():
self.server.followersThreads[0].kill()
# remove it from the list
self.server.followersThreads.pop(0)
2019-11-04 11:19:25 +00:00
# create a thread to send the post to followers
2019-11-04 11:01:04 +00:00
followersThread= \
sendToFollowersThread(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, \
self.server.projectVersion)
2019-11-04 10:43:19 +00:00
self.server.followersThreads.append(followersThread)
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-11-17 14:01:49 +00:00
2019-07-17 19:31:52 +00:00
if self.server.debug:
print('DEBUG: handle any like requests')
2019-11-24 13:37:34 +00:00
outboxLike(self.server.recentPostsCache, \
self.server.baseDir,self.server.httpPrefix, \
2019-07-17 19:31:52 +00:00
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-11-17 14:01:49 +00:00
if self.server.debug:
print('DEBUG: handle any bookmark requests')
2019-11-24 21:50:18 +00:00
outboxBookmark(self.server.recentPostsCache, \
self.server.baseDir,self.server.httpPrefix, \
2019-11-17 14:01:49 +00:00
self.postToNickname,self.server.domain,self.server.port, \
messageJson,self.server.debug)
if self.server.debug:
print('DEBUG: handle any undo bookmark requests')
2019-11-24 21:50:18 +00:00
outboxUndoBookmark(self.server.recentPostsCache, \
self.server.baseDir,self.server.httpPrefix, \
2019-11-17 14:01:49 +00:00
self.postToNickname,self.server.domain,self.server.port, \
messageJson,self.server.debug)
2019-08-12 18:02:29 +00:00
if self.server.debug:
print('DEBUG: handle delete requests')
outboxDelete(self.server.baseDir,self.server.httpPrefix, \
self.postToNickname,self.server.domain, \
messageJson,self.server.debug, \
self.server.allowDeletion)
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-11-03 15:27:29 +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, \
2019-08-14 20:12:27 +00:00
messageJson,self.server.debug, \
self.server.projectVersion)
return True
2019-09-03 17:16:26 +00:00
def _postToOutboxThread(self,messageJson: {}) -> bool:
"""Creates a thread to send a post
"""
2019-09-03 19:40:44 +00:00
accountOutboxThreadName=self.postToNickname
if not accountOutboxThreadName:
accountOutboxThreadName='*'
if self.server.outboxThread.get(accountOutboxThreadName):
2019-09-03 17:16:26 +00:00
print('Waiting for previous outbox thread to end')
2019-09-03 19:10:50 +00:00
waitCtr=0
2019-09-03 19:40:44 +00:00
while self.server.outboxThread[accountOutboxThreadName].isAlive() and waitCtr<8:
2019-09-03 17:16:26 +00:00
time.sleep(1)
2019-09-03 19:10:50 +00:00
waitCtr+=1
2019-09-03 19:40:44 +00:00
if waitCtr>=8:
self.server.outboxThread[accountOutboxThreadName].kill()
2019-09-03 17:16:26 +00:00
print('Creating outbox thread')
2019-09-03 19:40:44 +00:00
self.server.outboxThread[accountOutboxThreadName]= \
2019-09-03 17:16:26 +00:00
threadWithTrace(target=self._postToOutbox, \
args=(messageJson.copy(),__version__),daemon=True)
print('Starting outbox thread')
2019-09-03 19:40:44 +00:00
self.server.outboxThread[accountOutboxThreadName].start()
2019-09-03 17:16:26 +00:00
return True
2019-11-03 15:27:29 +00:00
def _inboxQueueCleardown(self) -> None:
2019-09-03 09:44:50 +00:00
""" Check if the queue is full and remove oldest items if it is
2019-07-05 11:27:18 +00:00
"""
2019-07-15 12:27:26 +00:00
if len(self.server.inboxQueue)>=self.server.maxQueueLength:
print('Inbox queue is full. Removing oldest items.')
2019-11-15 22:28:40 +00:00
cleardownStartTime=time.time()
2019-08-31 20:31:34 +00:00
while len(self.server.inboxQueue) >= self.server.maxQueueLength-4:
queueFilename=self.server.inboxQueue[0]
2019-08-31 20:34:16 +00:00
if os.path.isfile(queueFilename):
try:
os.remove(queueFilename)
except:
pass
self.server.inboxQueue.pop(0)
2019-11-15 22:28:40 +00:00
timeDiff=str(int((time.time()-cleardownStartTime)*1000))
print('Inbox cleardown took '+timeDiff+' mS')
2019-09-03 09:44:50 +00:00
2019-11-03 15:27:29 +00:00
def _updateInboxQueue(self,nickname: str,messageJson: {}, \
messageBytes: str) -> int:
2019-09-03 09:44:50 +00:00
"""Update the inbox queue
"""
self._inboxQueueCleardown()
2019-08-15 22:12:58 +00:00
2019-08-16 08:39:01 +00:00
# Convert the headers needed for signature verification to dict
2019-08-15 22:12:58 +00:00
headersDict={}
headersDict['host']=self.headers['host']
headersDict['signature']=self.headers['signature']
if self.headers.get('Date'):
headersDict['Date']=self.headers['Date']
if self.headers.get('digest'):
headersDict['digest']=self.headers['digest']
if self.headers.get('Content-type'):
headersDict['Content-type']=self.headers['Content-type']
2019-11-12 17:30:31 +00:00
if self.headers.get('Content-Length'):
2019-11-12 18:32:33 +00:00
headersDict['Content-Length']=self.headers['Content-Length']
elif self.headers.get('content-length'):
2019-11-12 17:30:31 +00:00
headersDict['content-length']=self.headers['content-length']
2019-08-18 09:39:12 +00:00
2019-11-03 15:27:29 +00:00
# For follow activities add a 'to' field, which is a copy
# of the object field
messageJson,toFieldExists= \
addToField('Follow',messageJson,self.server.debug)
2019-08-18 16:49:35 +00:00
2019-11-03 15:27:29 +00:00
# For like activities add a 'to' field, which is a copy of
# the actor within the object field
messageJson,toFieldExists= \
addToField('Like',messageJson,self.server.debug)
2019-08-18 09:39:12 +00:00
2019-11-15 23:03:37 +00:00
beginSaveTime=time.time()
2019-08-15 22:12:58 +00:00
# save the json for later queue processing
2019-07-06 13:49:25 +00:00
queueFilename = \
savePostToInboxQueue(self.server.baseDir,
self.server.httpPrefix,
nickname,
self.server.domainFull,
2019-07-05 11:27:18 +00:00
messageJson,
messageBytes.decode('utf-8'),
2019-08-15 22:12:58 +00:00
headersDict,
2019-08-16 08:39:01 +00:00
self.path,
2019-07-06 13:49:25 +00:00
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-11-15 23:03:37 +00:00
if self.server.debug:
timeDiff=int((time.time()-beginSaveTime)*1000)
if timeDiff>200:
2019-11-16 10:02:35 +00:00
print('SLOW: slow save of inbox queue item '+queueFilename+' took '+str(timeDiff)+' mS')
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:
# token based authenticated used by the web interface
if self.headers.get('Cookie'):
2019-11-15 12:56:07 +00:00
if self.headers['Cookie'].startswith('epicyon='):
tokenStr=self.headers['Cookie'].split('=',1)[1]
if self.server.tokensLookup.get(tokenStr):
nickname=self.server.tokensLookup[tokenStr]
2019-10-23 22:27:52 +00:00
# default to the inbox of the person
if self.path=='/':
self.path='/users/'+nickname+'/inbox'
2019-09-06 09:45:37 +00:00
# check that the path contains the same nickname as the cookie
# otherwise it would be possible to be authorized to use
2019-10-23 22:27:52 +00:00
# an account you don't own
if '/'+nickname+'/' in self.path:
return True
if self.path.endswith('/'+nickname):
return True
2019-11-26 17:19:03 +00:00
print('AUTH: nickname '+nickname+' did not match')
print('AUTH: epicyon cookie authorization failed')
print('AUTH: Header cookie was not authorized')
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
2019-11-26 17:19:03 +00:00
print('AUTH: Basic auth did not authorize')
2019-07-12 11:05:43 +00:00
return False
def _clearLoginDetails(self,nickname: str):
"""Clears login details for the given account
"""
# remove any token
if self.server.tokens.get(nickname):
del self.server.tokensLookup[self.server.tokens[nickname]]
del self.server.tokens[nickname]
self.send_response(303)
self.send_header('Content-Length', '0')
self.send_header('Set-Cookie', 'epicyon=; SameSite=Strict')
2019-11-26 15:30:13 +00:00
self.send_header('Location', \
self.server.httpPrefix+'://'+ \
self.server.domainFull+'/login')
self.send_header('X-Robots-Tag','noindex')
self.end_headers()
2019-11-16 13:25:44 +00:00
def _benchmarkGETtimings(self,GETstartTime,GETtimings: [],getID: int):
"""Updates a list containing how long each segment of GET takes
"""
2019-11-15 18:59:15 +00:00
if self.server.debug:
2019-11-16 13:25:44 +00:00
timeDiff=int((time.time()-GETstartTime)*1000)
logEvent=False
if timeDiff>100:
logEvent=True
if GETtimings:
timeDiff=int(timeDiff-int(GETtimings[-1]))
GETtimings.append(str(timeDiff))
if logEvent:
ctr=1
for timeDiff in GETtimings:
print('GET TIMING|'+str(ctr)+'|'+timeDiff)
ctr+=1
2019-11-15 18:59:15 +00:00
2019-11-16 11:03:02 +00:00
def _benchmarkPOSTtimings(self,POSTstartTime,POSTtimings: [],postID: int):
"""Updates a list containing how long each segment of POST takes
"""
if self.server.debug:
timeDiff=int((time.time()-POSTstartTime)*1000)
logEvent=False
if timeDiff>100:
logEvent=True
if POSTtimings:
timeDiff=int(timeDiff-int(POSTtimings[-1]))
POSTtimings.append(str(timeDiff))
if logEvent:
ctr=1
for timeDiff in POSTtimings:
print('POST TIMING|'+str(ctr)+'|'+timeDiff)
ctr+=1
2019-09-05 11:44:09 +00:00
def do_GET(self):
2019-11-15 18:59:15 +00:00
GETstartTime=time.time()
2019-11-16 13:25:44 +00:00
GETtimings=[]
2019-11-15 18:59:15 +00:00
2019-11-15 13:17:28 +00:00
# Since fediverse crawlers are quite active, make returning info to them high priority
# get nodeinfo endpoint
if self._nodeinfo():
return
2019-11-16 13:25:44 +00:00
self._benchmarkGETtimings(GETstartTime,GETtimings,1)
2019-11-15 13:17:28 +00:00
# minimal mastodon api
if self._mastoApi():
return
2019-11-16 13:25:44 +00:00
self._benchmarkGETtimings(GETstartTime,GETtimings,2)
2019-10-30 11:35:40 +00:00
if self.path=='/logout':
2019-11-03 15:27:29 +00:00
msg=htmlLogin(self.server.translate, \
self.server.baseDir,False).encode('utf-8')
2019-11-09 21:39:04 +00:00
self._logout_headers('text/html',len(msg))
self._write(msg)
2019-10-30 11:35:40 +00:00
return
2019-11-15 12:56:07 +00:00
2019-11-16 13:25:44 +00:00
self._benchmarkGETtimings(GETstartTime,GETtimings,3)
2019-11-06 18:51:51 +00:00
# replace https://domain/@nick with https://domain/users/nick
if self.path.startswith('/@'):
self.path=self.path.replace('/@','/users/')
2019-09-05 11:44:09 +00:00
# redirect music to #nowplaying list
if self.path=='/music' or self.path=='/nowplaying':
self.path='/tags/nowplaying'
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
if self.server.debug:
print(str(self.headers))
2019-07-29 16:13:48 +00:00
cookie=None
if self.headers.get('Cookie'):
cookie=self.headers['Cookie']
2019-11-16 13:25:44 +00:00
self._benchmarkGETtimings(GETstartTime,GETtimings,4)
# check authorization
2019-07-25 11:18:35 +00:00
authorized = self._isAuthorized()
if authorized:
if self.server.debug:
print('GET Authorization granted')
2019-07-25 11:18:35 +00:00
else:
if self.server.debug:
print('GET Not authorized')
2019-07-25 16:03:58 +00:00
2019-11-16 13:25:44 +00:00
self._benchmarkGETtimings(GETstartTime,GETtimings,5)
2019-08-20 11:30:41 +00:00
if not self.server.session:
2019-11-15 11:18:49 +00:00
print('Starting new session')
2019-08-20 11:30:41 +00:00
self.server.session= \
2019-11-13 10:50:16 +00:00
createSession(self.server.useTor)
2019-08-20 11:30:41 +00:00
2019-11-16 13:25:44 +00:00
self._benchmarkGETtimings(GETstartTime,GETtimings,6)
2019-08-24 09:07:05 +00:00
# is this a html request?
htmlGET=False
if self.headers.get('Accept'):
2019-08-24 11:23:12 +00:00
if self._requestHTTP():
2019-08-24 09:07:05 +00:00
htmlGET=True
2019-11-15 11:18:49 +00:00
else:
2019-11-15 14:03:43 +00:00
self._400()
2019-11-15 11:18:49 +00:00
return
2019-08-24 09:07:05 +00:00
2019-11-16 13:25:44 +00:00
self._benchmarkGETtimings(GETstartTime,GETtimings,7)
2019-08-05 11:08:52 +00:00
# treat shared inbox paths consistently
2019-08-23 13:47:29 +00:00
if self.path=='/sharedInbox' or \
self.path=='/users/inbox' or \
2019-08-23 17:06:44 +00:00
self.path=='/actor/inbox' or \
2019-08-23 13:47:29 +00:00
self.path=='/users/'+self.server.domain:
2019-11-15 21:43:20 +00:00
# if shared inbox is not enabled
if not self.server.enableSharedInbox:
self._503()
return
2019-08-05 11:08:52 +00:00
self.path='/inbox'
2019-11-16 13:25:44 +00:00
self._benchmarkGETtimings(GETstartTime,GETtimings,8)
2019-08-24 21:14:33 +00:00
# show the person options screen with view/follow/block/report
if htmlGET and '/users/' in self.path:
if '?options=' in self.path:
optionsStr=self.path.split('?options=')[1]
originPathStr=self.path.split('?options=')[0]
if ';' in optionsStr:
2019-09-04 11:29:44 +00:00
pageNumber=1
2019-08-24 23:00:03 +00:00
optionsList=optionsStr.split(';')
optionsActor=optionsList[0]
2019-09-04 11:29:44 +00:00
optionsPageNumber=optionsList[1]
optionsProfileUrl=optionsList[2]
if optionsPageNumber.isdigit():
pageNumber=int(optionsPageNumber)
2019-08-24 23:00:03 +00:00
optionsLink=None
2019-09-04 11:29:44 +00:00
if len(optionsList)>3:
optionsLink=optionsList[3]
2019-11-06 23:20:00 +00:00
donateUrl=None
actorJson=getPersonFromCache(self.server.baseDir,optionsActor,self.server.personCache)
if actorJson:
donateUrl=getDonationUrl(actorJson)
2019-09-07 08:57:52 +00:00
msg=htmlPersonOptions(self.server.translate, \
self.server.baseDir, \
2019-08-24 23:00:03 +00:00
self.server.domain, \
originPathStr, \
optionsActor, \
2019-09-04 11:29:44 +00:00
optionsProfileUrl, \
optionsLink, \
2019-11-06 23:20:00 +00:00
pageNumber,donateUrl).encode()
2019-11-09 21:39:04 +00:00
self._set_headers('text/html',len(msg),cookie)
2019-10-22 12:35:51 +00:00
self._write(msg)
2019-08-24 21:14:33 +00:00
return
2019-11-26 15:22:45 +00:00
originPathStrAbsolute=self.server.httpPrefix+'://'+self.server.domainFull+originPathStr
self._redirect_headers(originPathStrAbsolute,cookie)
2019-08-24 21:14:33 +00:00
return
2019-11-16 13:25:44 +00:00
self._benchmarkGETtimings(GETstartTime,GETtimings,9)
2019-08-26 09:15:48 +00:00
# remove a shared item
if htmlGET and '?rmshare=' in self.path:
shareName=self.path.split('?rmshare=')[1]
2019-11-03 10:29:01 +00:00
shareName=shareName.replace('%20',' ').replace('%40','@').replace('%3A',':').replace('%2F','/').replace('%23','#').strip()
2019-11-03 15:27:29 +00:00
actor= \
self.server.httpPrefix+'://'+self.server.domainFull+ \
self.path.split('?rmshare=')[0]
msg=htmlRemoveSharedItem(self.server.translate, \
self.server.baseDir, \
actor,shareName).encode()
2019-08-26 09:15:48 +00:00
if not msg:
self._redirect_headers(actor+'/inbox',cookie)
return
2019-11-09 21:39:04 +00:00
self._set_headers('text/html',len(msg),cookie)
2019-10-22 12:35:51 +00:00
self._write(msg)
2019-08-26 09:15:48 +00:00
return
2019-11-16 13:25:44 +00:00
self._benchmarkGETtimings(GETstartTime,GETtimings,10)
if self.path.startswith('/terms'):
2019-08-26 16:07:04 +00:00
msg=htmlTermsOfService(self.server.baseDir, \
self.server.httpPrefix, \
self.server.domainFull).encode()
2019-11-09 21:39:04 +00:00
self._login_headers('text/html',len(msg))
2019-10-22 12:35:51 +00:00
self._write(msg)
2019-08-26 16:07:04 +00:00
return
2019-11-16 13:25:44 +00:00
self._benchmarkGETtimings(GETstartTime,GETtimings,11)
if self.path.startswith('/about'):
2019-08-26 16:07:04 +00:00
msg=htmlAbout(self.server.baseDir, \
self.server.httpPrefix, \
self.server.domainFull).encode()
2019-11-09 21:39:04 +00:00
self._login_headers('text/html',len(msg))
2019-10-22 12:35:51 +00:00
self._write(msg)
2019-08-26 16:07:04 +00:00
return
2019-11-16 13:25:44 +00:00
self._benchmarkGETtimings(GETstartTime,GETtimings,12)
2019-09-03 20:27:49 +00:00
# send robots.txt if asked
if self._robotsTxt():
return
2019-09-06 09:40:57 +00:00
2019-11-16 13:25:44 +00:00
self._benchmarkGETtimings(GETstartTime,GETtimings,13)
2019-08-18 20:11:26 +00:00
# if not authorized then show the login screen
2019-08-26 16:07:04 +00:00
if htmlGET and self.path!='/login' and self.path!='/':
2019-08-18 20:11:26 +00:00
if '/media/' not in self.path and \
'/sharefiles/' not in self.path and \
'/statuses/' not in self.path and \
'/emoji/' not in self.path and \
'/tags/' not in self.path and \
2019-09-14 17:29:55 +00:00
'/avatars/' not in self.path and \
2019-08-18 20:11:26 +00:00
'/icons/' not in self.path:
divertToLoginScreen=True
if self.path.startswith('/users/'):
nickStr=self.path.split('/users/')[1]
if '/' not in nickStr and '?' not in nickStr:
divertToLoginScreen=False
else:
if self.path.endswith('/following') or \
self.path.endswith('/followers') or \
self.path.endswith('/skills') or \
self.path.endswith('/roles') or \
self.path.endswith('/shares'):
2019-07-28 13:30:19 +00:00
divertToLoginScreen=False
2019-08-18 20:11:26 +00:00
if divertToLoginScreen and not authorized:
if self.server.debug:
print('DEBUG: divertToLoginScreen='+str(divertToLoginScreen))
print('DEBUG: authorized='+str(authorized))
2019-08-21 17:05:51 +00:00
print('DEBUG: path='+self.path)
2019-08-18 20:11:26 +00:00
self.send_response(303)
2019-11-26 15:30:13 +00:00
self.send_header('Location', \
self.server.httpPrefix+'://'+ \
self.server.domainFull+'/login')
2019-08-18 20:11:26 +00:00
self.send_header('Content-Length', '0')
self.send_header('X-Robots-Tag','noindex')
2019-08-18 20:11:26 +00:00
self.end_headers()
return
2019-11-15 14:34:11 +00:00
2019-11-16 13:25:44 +00:00
self._benchmarkGETtimings(GETstartTime,GETtimings,14)
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'):
2019-10-11 18:03:58 +00:00
tries=0
while tries<5:
try:
with open('epicyon-profile.css', 'r') as cssfile:
css = cssfile.read()
break
except Exception as e:
print(e)
time.sleep(1)
tries+=1
2019-08-15 13:20:09 +00:00
msg=css.encode('utf-8')
2019-11-09 21:39:04 +00:00
self._set_headers('text/css',len(msg),cookie)
2019-10-22 12:35:51 +00:00
self._write(msg)
2019-07-21 09:09:28 +00:00
return
2019-11-15 14:03:43 +00:00
self._404()
2019-11-15 18:59:15 +00:00
return
2019-10-23 12:04:08 +00:00
2019-11-16 13:25:44 +00:00
self._benchmarkGETtimings(GETstartTime,GETtimings,15)
2019-07-24 22:38:42 +00:00
# image on login screen
if self.path=='/login.png' or \
self.path=='/login.gif' or \
self.path=='/login.webp' or \
self.path=='/login.jpeg' or \
self.path=='/login.jpg':
2019-07-24 22:38:42 +00:00
mediaFilename= \
2019-11-14 13:30:54 +00:00
self.server.baseDir+'/accounts'+self.path
2019-07-24 22:38:42 +00:00
if os.path.isfile(mediaFilename):
2019-10-14 21:38:03 +00:00
tries=0
mediaBinary=None
while tries<5:
try:
with open(mediaFilename, 'rb') as avFile:
mediaBinary = avFile.read()
break
except Exception as e:
print(e)
time.sleep(1)
tries+=1
if mediaBinary:
2019-08-15 13:20:09 +00:00
self._set_headers('image/png',len(mediaBinary),cookie)
2019-10-22 12:35:51 +00:00
self._write(mediaBinary)
2019-10-14 21:29:46 +00:00
return
2019-08-16 22:21:34 +00:00
self._404()
2019-10-23 12:04:08 +00:00
return
2019-11-16 13:25:44 +00:00
self._benchmarkGETtimings(GETstartTime,GETtimings,16)
2019-07-25 19:56:25 +00:00
# login screen background image
if self.path=='/login-background.png':
2019-07-25 19:56:25 +00:00
mediaFilename= \
self.server.baseDir+'/accounts/login-background.png'
if os.path.isfile(mediaFilename):
2019-10-14 21:38:03 +00:00
tries=0
mediaBinary=None
while tries<5:
try:
with open(mediaFilename, 'rb') as avFile:
mediaBinary = avFile.read()
break
except Exception as e:
print(e)
time.sleep(1)
tries+=1
if mediaBinary:
2019-08-15 13:20:09 +00:00
self._set_headers('image/png',len(mediaBinary),cookie)
2019-10-22 12:35:51 +00:00
self._write(mediaBinary)
2019-08-16 22:21:34 +00:00
return
self._404()
return
2019-10-23 12:04:08 +00:00
2019-11-16 13:25:44 +00:00
self._benchmarkGETtimings(GETstartTime,GETtimings,17)
2019-07-29 09:49:46 +00:00
# follow screen background image
if self.path=='/follow-background.png':
2019-07-29 09:49:46 +00:00
mediaFilename= \
self.server.baseDir+'/accounts/follow-background.png'
if os.path.isfile(mediaFilename):
2019-10-14 21:38:03 +00:00
tries=0
mediaBinary=None
while tries<5:
try:
with open(mediaFilename, 'rb') as avFile:
mediaBinary = avFile.read()
break
except Exception as e:
print(e)
time.sleep(1)
tries+=1
if mediaBinary:
2019-08-15 13:20:09 +00:00
self._set_headers('image/png',len(mediaBinary),cookie)
2019-10-22 12:35:51 +00:00
self._write(mediaBinary)
2019-10-14 21:29:46 +00:00
return
2019-08-16 22:21:34 +00:00
self._404()
2019-08-09 12:50:49 +00:00
return
2019-10-23 12:04:08 +00:00
2019-11-16 13:25:44 +00:00
self._benchmarkGETtimings(GETstartTime,GETtimings,18)
2019-08-09 12:50:49 +00:00
# emoji images
if '/emoji/' in self.path:
2019-08-09 12:50:49 +00:00
if self.path.endswith('.png') or \
self.path.endswith('.jpg') or \
self.path.endswith('.gif'):
emojiStr=self.path.split('/emoji/')[1]
emojiFilename= \
self.server.baseDir+'/emoji/'+emojiStr
if os.path.isfile(emojiFilename):
2019-08-15 13:20:09 +00:00
mediaImageType='png'
2019-08-09 12:50:49 +00:00
if emojiFilename.endswith('.png'):
2019-08-15 13:20:09 +00:00
mediaImageType='png'
2019-08-09 12:50:49 +00:00
elif emojiFilename.endswith('.jpg'):
2019-08-15 13:20:09 +00:00
mediaImageType='jpeg'
2019-11-14 15:11:20 +00:00
elif emojiFilename.endswith('.webp'):
mediaImageType='webp'
2019-08-09 12:50:49 +00:00
else:
2019-10-14 21:38:03 +00:00
mediaImageType='gif'
2019-08-09 12:50:49 +00:00
with open(emojiFilename, 'rb') as avFile:
2019-08-15 13:20:09 +00:00
mediaBinary = avFile.read()
self._set_headers('image/'+mediaImageType,len(mediaBinary),cookie)
2019-10-22 12:35:51 +00:00
self._write(mediaBinary)
2019-08-09 17:42:11 +00:00
return
2019-08-09 12:50:49 +00:00
self._404()
return
2019-10-23 12:04:08 +00:00
2019-11-16 13:25:44 +00:00
self._benchmarkGETtimings(GETstartTime,GETtimings,19)
2019-07-12 19:33:34 +00:00
# show media
# Note that this comes before the busy flag to avoid conflicts
if '/media/' in self.path:
2019-07-12 19:33:34 +00:00
if self.path.endswith('.png') or \
self.path.endswith('.jpg') or \
2019-08-30 19:57:24 +00:00
self.path.endswith('.gif') or \
2019-11-14 15:11:20 +00:00
self.path.endswith('.webp') or \
2019-08-30 19:57:24 +00:00
self.path.endswith('.mp4') or \
self.path.endswith('.ogv') or \
self.path.endswith('.mp3') or \
self.path.endswith('.ogg'):
2019-07-12 19:33:34 +00:00
mediaStr=self.path.split('/media/')[1]
mediaFilename= \
self.server.baseDir+'/media/'+mediaStr
if os.path.isfile(mediaFilename):
2019-08-30 19:57:24 +00:00
mediaFileType='image/png'
2019-07-12 19:33:34 +00:00
if mediaFilename.endswith('.png'):
2019-08-30 19:57:24 +00:00
mediaFileType='image/png'
2019-07-12 19:33:34 +00:00
elif mediaFilename.endswith('.jpg'):
2019-08-30 19:57:24 +00:00
mediaFileType='image/jpeg'
elif mediaFilename.endswith('.gif'):
mediaFileType='image/gif'
2019-11-14 15:11:20 +00:00
elif mediaFilename.endswith('.webp'):
mediaFileType='image/webp'
2019-08-30 19:57:24 +00:00
elif mediaFilename.endswith('.mp4'):
mediaFileType='video/mp4'
elif mediaFilename.endswith('.ogv'):
mediaFileType='video/ogv'
elif mediaFilename.endswith('.mp3'):
mediaFileType='audio/mpeg'
elif mediaFilename.endswith('.ogg'):
mediaFileType='audio/ogg'
2019-07-12 19:33:34 +00:00
with open(mediaFilename, 'rb') as avFile:
mediaBinary = avFile.read()
2019-08-30 19:57:24 +00:00
self._set_headers(mediaFileType,len(mediaBinary),cookie)
2019-10-22 12:35:51 +00:00
self._write(mediaBinary)
2019-07-12 19:33:34 +00:00
return
2019-07-16 16:10:52 +00:00
self._404()
return
2019-10-23 12:04:08 +00:00
2019-11-16 13:25:44 +00:00
self._benchmarkGETtimings(GETstartTime,GETtimings,20)
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:
2019-07-23 12:33:09 +00:00
if self.path.endswith('.png') or \
self.path.endswith('.jpg') or \
2019-11-14 15:11:20 +00:00
self.path.endswith('.webp') or \
2019-07-23 12:33:09 +00:00
self.path.endswith('.gif'):
mediaStr=self.path.split('/sharefiles/')[1]
mediaFilename= \
self.server.baseDir+'/sharefiles/'+mediaStr
if os.path.isfile(mediaFilename):
2019-08-15 12:22:34 +00:00
mediaFileType='png'
2019-07-23 12:33:09 +00:00
if mediaFilename.endswith('.png'):
2019-08-15 12:22:34 +00:00
mediaFileType='png'
2019-07-23 12:33:09 +00:00
elif mediaFilename.endswith('.jpg'):
2019-08-15 12:22:34 +00:00
mediaFileType='jpeg'
2019-11-14 15:11:20 +00:00
elif mediaFilename.endswith('.webp'):
mediaFileType='webp'
2019-07-23 12:33:09 +00:00
else:
2019-08-15 12:22:34 +00:00
mediaFileType='gif'
2019-07-23 12:33:09 +00:00
with open(mediaFilename, 'rb') as avFile:
mediaBinary = avFile.read()
2019-08-15 13:20:09 +00:00
self._set_headers('image/'+mediaFileType,len(mediaBinary),cookie)
2019-10-22 12:35:51 +00:00
self._write(mediaBinary)
2019-07-23 12:33:09 +00:00
return
self._404()
return
2019-10-23 12:04:08 +00:00
2019-11-16 13:25:44 +00:00
self._benchmarkGETtimings(GETstartTime,GETtimings,21)
2019-07-26 10:30:13 +00:00
# icon images
# Note that this comes before the busy flag to avoid conflicts
if self.path.startswith('/icons/'):
2019-07-26 10:30:13 +00:00
if self.path.endswith('.png'):
mediaStr=self.path.split('/icons/')[1]
mediaFilename= \
self.server.baseDir+'/img/icons/'+mediaStr
2019-11-24 18:06:54 +00:00
if self.server.iconsCache.get(mediaStr):
2019-11-24 18:11:32 +00:00
mediaBinary=self.server.iconsCache[mediaStr]
2019-11-24 18:06:54 +00:00
self._set_headers('image/png',len(mediaBinary),cookie)
self._write(mediaBinary)
2019-11-18 15:57:51 +00:00
return
2019-11-24 18:06:54 +00:00
else:
if os.path.isfile(mediaFilename):
with open(mediaFilename, 'rb') as avFile:
mediaBinary = avFile.read()
self._set_headers('image/png',len(mediaBinary),cookie)
self._write(mediaBinary)
self.server.iconsCache[mediaStr]=mediaBinary
return
2019-07-26 10:30:13 +00:00
self._404()
return
2019-10-23 12:04:08 +00:00
2019-11-16 13:25:44 +00:00
self._benchmarkGETtimings(GETstartTime,GETtimings,22)
2019-09-14 17:29:55 +00:00
# cached avatar images
# Note that this comes before the busy flag to avoid conflicts
if self.path.startswith('/avatars/'):
2019-09-14 17:29:55 +00:00
mediaFilename= \
self.server.baseDir+'/cache/'+self.path
if os.path.isfile(mediaFilename):
with open(mediaFilename, 'rb') as avFile:
mediaBinary = avFile.read()
if mediaFilename.endswith('.png'):
self._set_headers('image/png',len(mediaBinary),cookie)
elif mediaFilename.endswith('.jpg'):
self._set_headers('image/jpeg',len(mediaBinary),cookie)
2019-09-14 17:55:52 +00:00
elif mediaFilename.endswith('.gif'):
2019-09-14 17:29:55 +00:00
self._set_headers('image/gif',len(mediaBinary),cookie)
else:
2019-10-18 09:00:16 +00:00
# default to jpeg
self._set_headers('image/jpeg',len(mediaBinary),cookie)
#self._404()
2019-09-14 17:29:55 +00:00
return
2019-10-22 12:35:51 +00:00
self._write(mediaBinary)
2019-09-14 17:29:55 +00:00
return
self._404()
return
2019-11-14 14:40:51 +00:00
2019-11-16 13:25:44 +00:00
self._benchmarkGETtimings(GETstartTime,GETtimings,23)
2019-07-12 16:09:25 +00:00
# show avatar or background image
# Note that this comes before the busy flag to avoid conflicts
if '/users/' in self.path:
2019-07-12 16:03:01 +00:00
if self.path.endswith('.png') or \
self.path.endswith('.jpg') or \
2019-11-14 14:40:51 +00:00
self.path.endswith('.webp') or \
2019-07-12 16:03:01 +00:00
self.path.endswith('.gif'):
avatarStr=self.path.split('/users/')[1]
2019-11-14 14:40:51 +00:00
if '/' in avatarStr and '.temp.' not in self.path:
2019-07-12 16:03:01 +00:00
avatarNickname=avatarStr.split('/')[0]
avatarFile=avatarStr.split('/')[1]
avatarFilename= \
self.server.baseDir+'/accounts/'+ \
avatarNickname+'@'+ \
self.server.domain+'/'+avatarFile
if os.path.isfile(avatarFilename):
2019-08-15 13:20:09 +00:00
mediaImageType='png'
2019-07-12 16:03:01 +00:00
if avatarFile.endswith('.png'):
2019-08-15 13:20:09 +00:00
mediaImageType='png'
2019-07-12 16:03:01 +00:00
elif avatarFile.endswith('.jpg'):
2019-08-15 13:20:09 +00:00
mediaImageType='jpeg'
2019-11-14 14:40:51 +00:00
elif avatarFile.endswith('.gif'):
2019-08-15 13:20:09 +00:00
mediaImageType='gif'
2019-11-14 14:40:51 +00:00
else:
mediaImageType='webp'
2019-07-12 16:03:01 +00:00
with open(avatarFilename, 'rb') as avFile:
2019-08-15 13:20:09 +00:00
mediaBinary = avFile.read()
2019-11-03 15:27:29 +00:00
self._set_headers('image/'+mediaImageType, \
len(mediaBinary),cookie)
2019-10-22 12:35:51 +00:00
self._write(mediaBinary)
2019-07-22 15:14:39 +00:00
return
2019-11-16 13:25:44 +00:00
self._benchmarkGETtimings(GETstartTime,GETtimings,24)
2019-07-22 15:14:39 +00:00
# This busy state helps to avoid flooding
# Resources which are expected to be called from a web page
# should be above this
if self.server.GETbusy:
currTimeGET=int(time.time())
if currTimeGET-self.server.lastGET==0:
if self.server.debug:
print('DEBUG: GET Busy')
2019-07-25 16:50:48 +00:00
self.send_response(429)
self.end_headers()
return
self.server.lastGET=currTimeGET
self.server.GETbusy=True
2019-11-16 13:25:44 +00:00
self._benchmarkGETtimings(GETstartTime,GETtimings,25)
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
if self._webfinger():
self.server.GETbusy=False
return
2019-08-08 13:38:33 +00:00
2019-11-16 13:25:44 +00:00
self._benchmarkGETtimings(GETstartTime,GETtimings,26)
if self.path.startswith('/login') or \
2019-10-23 22:02:14 +00:00
(self.path=='/' and not authorized):
2019-07-24 22:38:42 +00:00
# request basic auth
2019-11-03 15:27:29 +00:00
msg=htmlLogin(self.server.translate, \
self.server.baseDir).encode('utf-8')
2019-11-09 21:39:04 +00:00
self._login_headers('text/html',len(msg))
2019-10-22 12:35:51 +00:00
self._write(msg)
2019-07-24 22:38:42 +00:00
self.server.GETbusy=False
2019-07-25 21:39:09 +00:00
return
2019-11-16 13:25:44 +00:00
self._benchmarkGETtimings(GETstartTime,GETtimings,27)
2019-08-10 10:54:52 +00:00
# hashtag search
if self.path.startswith('/tags/'):
2019-08-10 10:54:52 +00:00
pageNumber=1
if '?page=' in self.path:
pageNumberStr=self.path.split('?page=')[1]
if pageNumberStr.isdigit():
pageNumber=int(pageNumberStr)
hashtag=self.path.split('/tags/')[1]
if '?page=' in hashtag:
hashtag=hashtag.split('?page=')[0]
2019-08-14 10:32:15 +00:00
if isBlockedHashtag(self.server.baseDir,hashtag):
2019-08-15 13:20:09 +00:00
msg=htmlHashtagBlocked(self.server.baseDir).encode('utf-8')
2019-11-09 21:39:04 +00:00
self._login_headers('text/html',len(msg))
2019-10-22 12:35:51 +00:00
self._write(msg)
2019-08-14 10:32:15 +00:00
self.server.GETbusy=False
return
2019-08-10 10:54:52 +00:00
hashtagStr= \
htmlHashtagSearch(self.server.recentPostsCache, \
self.server.maxRecentPosts, \
self.server.translate, \
2019-09-07 08:57:52 +00:00
self.server.baseDir,hashtag,pageNumber, \
2019-08-10 10:54:52 +00:00
maxPostsInFeed,self.server.session, \
self.server.cachedWebfingers, \
2019-08-14 20:12:27 +00:00
self.server.personCache, \
self.server.httpPrefix, \
self.server.projectVersion)
2019-08-10 10:54:52 +00:00
if hashtagStr:
2019-08-15 13:20:09 +00:00
msg=hashtagStr.encode()
2019-11-09 21:39:04 +00:00
self._set_headers('text/html',len(msg),cookie)
2019-10-22 12:35:51 +00:00
self._write(msg)
2019-08-10 10:54:52 +00:00
else:
2019-08-15 13:20:09 +00:00
originPathStr=self.path.split('/tags/')[0]
2019-11-26 15:22:45 +00:00
originPathStrAbsolute=self.server.httpPrefix+'://'+self.server.domainFull+originPathStr
self._redirect_headers(originPathStrAbsolute+'/search',cookie)
2019-08-10 10:54:52 +00:00
self.server.GETbusy=False
return
2019-11-16 13:25:44 +00:00
self._benchmarkGETtimings(GETstartTime,GETtimings,28)
2019-11-03 15:27:29 +00:00
# search for a fediverse address, shared item or emoji
# from the web interface by selecting search icon
2019-08-18 20:11:26 +00:00
if htmlGET and '/users/' in self.path:
2019-07-30 22:34:04 +00:00
if self.path.endswith('/search'):
# show the search screen
2019-09-07 08:57:52 +00:00
msg=htmlSearch(self.server.translate, \
self.server.baseDir,self.path).encode()
2019-11-09 21:39:04 +00:00
self._set_headers('text/html',len(msg),cookie)
2019-10-22 12:35:51 +00:00
self._write(msg)
2019-07-30 22:34:04 +00:00
self.server.GETbusy=False
2019-10-10 14:43:21 +00:00
return
2019-11-16 13:25:44 +00:00
self._benchmarkGETtimings(GETstartTime,GETtimings,29)
2019-10-10 14:43:21 +00:00
# Show the calendar for a user
if htmlGET and '/users/' in self.path:
2019-10-10 18:30:25 +00:00
if '/calendar' in self.path:
2019-10-10 14:43:21 +00:00
# show the calendar screen
msg=htmlCalendar(self.server.translate, \
2019-10-11 19:12:30 +00:00
self.server.baseDir,self.path, \
2019-10-11 19:26:08 +00:00
self.server.httpPrefix, \
2019-10-11 19:25:36 +00:00
self.server.domainFull).encode()
2019-11-09 21:39:04 +00:00
self._set_headers('text/html',len(msg),cookie)
2019-10-22 12:35:51 +00:00
self._write(msg)
2019-10-10 14:43:21 +00:00
self.server.GETbusy=False
2019-07-30 22:34:04 +00:00
return
2019-11-16 13:25:44 +00:00
self._benchmarkGETtimings(GETstartTime,GETtimings,30)
2019-08-19 20:01:29 +00:00
# search for emoji by name
if htmlGET and '/users/' in self.path:
if self.path.endswith('/searchemoji'):
# show the search screen
2019-09-07 08:57:52 +00:00
msg=htmlSearchEmojiTextEntry(self.server.translate, \
self.server.baseDir, \
self.path).encode()
2019-11-09 21:39:04 +00:00
self._set_headers('text/html',len(msg),cookie)
2019-10-22 12:35:51 +00:00
self._write(msg)
2019-08-19 20:01:29 +00:00
self.server.GETbusy=False
return
2019-11-16 13:25:44 +00:00
self._benchmarkGETtimings(GETstartTime,GETtimings,31)
2019-08-01 09:05:09 +00:00
# announce/repeat from the web interface
2019-08-18 20:11:26 +00:00
if htmlGET and '?repeat=' in self.path:
2019-09-04 11:29:44 +00:00
pageNumber=1
2019-07-31 16:47:45 +00:00
repeatUrl=self.path.split('?repeat=')[1]
2019-09-04 11:29:44 +00:00
if '?' in repeatUrl:
repeatUrl=repeatUrl.split('?')[0]
2019-11-19 15:27:43 +00:00
timelineBookmark=''
if '?bm=' in self.path:
timelineBookmark=self.path.split('?bm=')[1]
if '?' in timelineBookmark:
timelineBookmark=timelineBookmark.split('?')[0]
timelineBookmark='#'+timelineBookmark
2019-09-04 11:29:44 +00:00
if '?page=' in self.path:
pageNumberStr=self.path.split('?page=')[1]
if '?' in pageNumberStr:
pageNumberStr=pageNumberStr.split('?')[0]
if pageNumberStr.isdigit():
pageNumber=int(pageNumberStr)
timelineStr='inbox'
if '?tl=' in self.path:
timelineStr=self.path.split('?tl=')[1]
if '?' in timelineStr:
timelineStr=timelineStr.split('?')[0]
2019-07-31 16:47:45 +00:00
actor=self.path.split('?repeat=')[0]
self.postToNickname=getNicknameFromActor(actor)
2019-09-02 09:43:43 +00:00
if not self.postToNickname:
print('WARN: unable to find nickname in '+actor)
self.server.GETbusy=False
2019-11-26 15:22:45 +00:00
actorAbsolute=self.server.httpPrefix+'://'+self.server.domainFull+actor
self._redirect_headers(actorAbsolute+'/'+timelineStr+ \
2019-11-03 15:27:29 +00:00
'?page='+str(pageNumber),cookie)
2019-09-02 09:43:43 +00:00
return
2019-07-31 16:47:45 +00:00
if not self.server.session:
self.server.session= \
2019-11-13 10:50:16 +00:00
createSession(self.server.useTor)
2019-11-14 21:09:36 +00:00
self.server.actorRepeat=self.path.split('?actor=')[1]
2019-07-31 16:47:45 +00:00
announceJson= \
createAnnounce(self.server.session, \
self.server.baseDir, \
self.server.federationList, \
self.postToNickname, \
self.server.domain,self.server.port, \
'https://www.w3.org/ns/activitystreams#Public', \
None,self.server.httpPrefix, \
repeatUrl,False,False, \
self.server.sendThreads, \
self.server.postLog, \
self.server.personCache, \
self.server.cachedWebfingers, \
2019-08-14 20:12:27 +00:00
self.server.debug, \
self.server.projectVersion)
2019-11-14 21:12:09 +00:00
if announceJson:
2019-09-03 17:07:00 +00:00
self._postToOutboxThread(announceJson)
2019-07-31 16:47:45 +00:00
self.server.GETbusy=False
2019-11-26 15:22:45 +00:00
actorAbsolute=self.server.httpPrefix+'://'+self.server.domainFull+actor
self._redirect_headers(actorAbsolute+'/'+timelineStr+'?page='+ \
2019-11-19 15:27:43 +00:00
str(pageNumber)+ \
timelineBookmark,cookie)
2019-07-31 16:47:45 +00:00
return
2019-08-01 09:05:09 +00:00
2019-11-16 13:25:44 +00:00
self._benchmarkGETtimings(GETstartTime,GETtimings,32)
2019-08-01 12:18:22 +00:00
# undo an announce/repeat from the web interface
2019-08-18 20:11:26 +00:00
if htmlGET and '?unrepeat=' in self.path:
2019-09-04 11:29:44 +00:00
pageNumber=1
2019-08-01 12:18:22 +00:00
repeatUrl=self.path.split('?unrepeat=')[1]
2019-09-04 11:29:44 +00:00
if '?' in repeatUrl:
repeatUrl=repeatUrl.split('?')[0]
2019-11-19 15:27:43 +00:00
timelineBookmark=''
if '?bm=' in self.path:
timelineBookmark=self.path.split('?bm=')[1]
if '?' in timelineBookmark:
timelineBookmark=timelineBookmark.split('?')[0]
timelineBookmark='#'+timelineBookmark
2019-09-04 11:29:44 +00:00
if '?page=' in self.path:
pageNumberStr=self.path.split('?page=')[1]
if '?' in pageNumberStr:
pageNumberStr=pageNumberStr.split('?')[0]
if pageNumberStr.isdigit():
pageNumber=int(pageNumberStr)
timelineStr='inbox'
if '?tl=' in self.path:
timelineStr=self.path.split('?tl=')[1]
if '?' in timelineStr:
timelineStr=timelineStr.split('?')[0]
2019-08-01 12:18:22 +00:00
actor=self.path.split('?unrepeat=')[0]
self.postToNickname=getNicknameFromActor(actor)
2019-09-02 09:43:43 +00:00
if not self.postToNickname:
print('WARN: unable to find nickname in '+actor)
self.server.GETbusy=False
2019-11-26 15:22:45 +00:00
actorAbsolute=self.server.httpPrefix+'://'+self.server.domainFull+actor
self._redirect_headers(actorAbsolute+'/'+timelineStr+'?page='+ \
2019-11-03 15:27:29 +00:00
str(pageNumber),cookie)
2019-09-02 09:43:43 +00:00
return
2019-08-01 12:18:22 +00:00
if not self.server.session:
self.server.session= \
2019-11-13 10:50:16 +00:00
createSession(self.server.useTor)
2019-11-03 15:27:29 +00:00
undoAnnounceActor= \
self.server.httpPrefix+'://'+self.server.domainFull+ \
'/users/'+self.postToNickname
2019-08-01 12:18:22 +00:00
newUndoAnnounce = {
2019-08-18 11:07:06 +00:00
"@context": "https://www.w3.org/ns/activitystreams",
2019-08-01 12:18:22 +00:00
'actor': undoAnnounceActor,
'type': 'Undo',
'cc': [undoAnnounceActor+'/followers'],
'to': ['https://www.w3.org/ns/activitystreams#Public'],
'object': {
'actor': undoAnnounceActor,
'cc': [undoAnnounceActor+'/followers'],
'object': repeatUrl,
'to': ['https://www.w3.org/ns/activitystreams#Public'],
'type': 'Announce'
}
}
2019-09-03 17:07:00 +00:00
self._postToOutboxThread(newUndoAnnounce)
2019-08-01 12:18:22 +00:00
self.server.GETbusy=False
2019-11-26 15:22:45 +00:00
actorAbsolute=self.server.httpPrefix+'://'+self.server.domainFull+actor
self._redirect_headers(actorAbsolute+'/'+timelineStr+'?page='+ \
2019-11-19 15:27:43 +00:00
str(pageNumber)+ \
timelineBookmark,cookie)
2019-08-01 12:18:22 +00:00
return
2019-11-16 13:25:44 +00:00
self._benchmarkGETtimings(GETstartTime,GETtimings,33)
2019-08-07 11:58:01 +00:00
# send a follow request approval from the web interface
if authorized and '/followapprove=' in self.path and \
2019-11-03 15:27:29 +00:00
self.path.startswith('/users/'):
2019-08-07 11:57:14 +00:00
originPathStr=self.path.split('/followapprove=')[0]
2019-08-07 14:08:32 +00:00
followerNickname=originPathStr.replace('/users/','')
2019-08-07 11:57:14 +00:00
followingHandle=self.path.split('/followapprove=')[1]
if '@' in followingHandle:
2019-08-07 12:50:48 +00:00
if not self.server.session:
self.server.session= \
2019-11-13 10:50:16 +00:00
createSession(self.server.useTor)
2019-08-07 11:57:14 +00:00
manualApproveFollowRequest(self.server.session, \
self.server.baseDir, \
self.server.httpPrefix, \
2019-11-03 15:27:29 +00:00
followerNickname, \
self.server.domain, \
self.server.port, \
2019-08-07 11:57:14 +00:00
followingHandle, \
self.server.federationList, \
self.server.sendThreads, \
self.server.postLog, \
self.server.cachedWebfingers, \
self.server.personCache, \
self.server.acceptedCaps, \
2019-08-14 20:12:27 +00:00
self.server.debug, \
self.server.projectVersion)
2019-11-26 15:22:45 +00:00
originPathStrAbsolute=self.server.httpPrefix+'://'+self.server.domainFull+originPathStr
self._redirect_headers(originPathStrAbsolute,cookie)
2019-08-07 11:57:14 +00:00
self.server.GETbusy=False
return
2019-11-16 13:25:44 +00:00
self._benchmarkGETtimings(GETstartTime,GETtimings,34)
2019-08-07 11:58:01 +00:00
# deny a follow request from the web interface
if authorized and '/followdeny=' in self.path and \
2019-11-03 15:27:29 +00:00
self.path.startswith('/users/'):
2019-08-07 11:57:14 +00:00
originPathStr=self.path.split('/followdeny=')[0]
2019-08-07 14:08:32 +00:00
followerNickname=originPathStr.replace('/users/','')
2019-08-07 11:57:14 +00:00
followingHandle=self.path.split('/followdeny=')[1]
if '@' in followingHandle:
manualDenyFollowRequest(self.server.session, \
self.server.baseDir, \
self.server.httpPrefix, \
2019-11-03 15:27:29 +00:00
followerNickname, \
self.server.domain, \
self.server.port, \
followingHandle, \
self.server.federationList, \
self.server.sendThreads, \
self.server.postLog, \
self.server.cachedWebfingers, \
self.server.personCache, \
self.server.debug, \
self.server.projectVersion)
2019-11-26 15:22:45 +00:00
originPathStrAbsolute=self.server.httpPrefix+'://'+self.server.domainFull+originPathStr
self._redirect_headers(originPathStrAbsolute,cookie)
2019-08-07 11:57:14 +00:00
self.server.GETbusy=False
return
2019-11-16 13:25:44 +00:00
self._benchmarkGETtimings(GETstartTime,GETtimings,35)
2019-08-01 09:05:09 +00:00
# like from the web interface icon
2019-11-14 17:58:46 +00:00
if htmlGET and '?like=' in self.path:
2019-09-04 11:29:44 +00:00
pageNumber=1
2019-08-01 09:05:09 +00:00
likeUrl=self.path.split('?like=')[1]
2019-09-04 11:29:44 +00:00
if '?' in likeUrl:
2019-11-14 17:58:46 +00:00
likeUrl=likeUrl.split('?')[0]
2019-11-19 15:27:43 +00:00
timelineBookmark=''
if '?bm=' in self.path:
timelineBookmark=self.path.split('?bm=')[1]
if '?' in timelineBookmark:
timelineBookmark=timelineBookmark.split('?')[0]
timelineBookmark='#'+timelineBookmark
2019-08-01 09:05:09 +00:00
actor=self.path.split('?like=')[0]
2019-09-04 11:29:44 +00:00
if '?page=' in self.path:
pageNumberStr=self.path.split('?page=')[1]
if '?' in pageNumberStr:
pageNumberStr=pageNumberStr.split('?')[0]
if pageNumberStr.isdigit():
pageNumber=int(pageNumberStr)
timelineStr='inbox'
if '?tl=' in self.path:
timelineStr=self.path.split('?tl=')[1]
if '?' in timelineStr:
timelineStr=timelineStr.split('?')[0]
2019-09-04 11:29:44 +00:00
2019-08-01 09:05:09 +00:00
self.postToNickname=getNicknameFromActor(actor)
2019-09-02 09:43:43 +00:00
if not self.postToNickname:
print('WARN: unable to find nickname in '+actor)
self.server.GETbusy=False
2019-11-26 15:22:45 +00:00
actorAbsolute=self.server.httpPrefix+'://'+self.server.domainFull+actor
self._redirect_headers(actorAbsolute+'/'+timelineStr+ \
2019-11-19 15:27:43 +00:00
'?page='+str(pageNumber)+ \
timelineBookmark,cookie)
2019-09-02 09:43:43 +00:00
return
2019-08-01 09:05:09 +00:00
if not self.server.session:
self.server.session= \
2019-11-13 10:50:16 +00:00
createSession(self.server.useTor)
2019-11-03 15:27:29 +00:00
likeActor= \
self.server.httpPrefix+'://'+ \
2019-11-14 17:58:46 +00:00
self.server.domainFull+'/users/'+self.postToNickname
actorLiked=self.path.split('?actor=')[1]
if '?' in actorLiked:
actorLiked=actorLiked.split('?')[0]
2019-08-01 09:05:09 +00:00
likeJson= {
2019-08-18 11:07:06 +00:00
"@context": "https://www.w3.org/ns/activitystreams",
2019-08-01 09:05:09 +00:00
'type': 'Like',
'actor': likeActor,
'to': [actorLiked],
2019-08-18 16:58:50 +00:00
'object': likeUrl
2019-08-01 09:05:09 +00:00
}
2019-11-15 14:34:11 +00:00
self._postToOutbox(likeJson,self.server.projectVersion)
2019-08-01 09:05:09 +00:00
self.server.GETbusy=False
2019-11-26 15:22:45 +00:00
actorAbsolute=self.server.httpPrefix+'://'+self.server.domainFull+actor
self._redirect_headers(actorAbsolute+'/'+timelineStr+ \
2019-11-19 15:27:43 +00:00
'?page='+str(pageNumber)+ \
timelineBookmark,cookie)
2019-08-01 09:05:09 +00:00
return
2019-11-16 13:25:44 +00:00
self._benchmarkGETtimings(GETstartTime,GETtimings,36)
2019-08-01 09:05:09 +00:00
# undo a like from the web interface icon
2019-11-14 17:58:46 +00:00
if htmlGET and '?unlike=' in self.path:
2019-09-04 11:29:44 +00:00
pageNumber=1
2019-08-01 09:05:09 +00:00
likeUrl=self.path.split('?unlike=')[1]
2019-09-04 11:29:44 +00:00
if '?' in likeUrl:
likeUrl=likeUrl.split('?')[0]
2019-11-19 15:27:43 +00:00
timelineBookmark=''
if '?bm=' in self.path:
timelineBookmark=self.path.split('?bm=')[1]
if '?' in timelineBookmark:
timelineBookmark=timelineBookmark.split('?')[0]
timelineBookmark='#'+timelineBookmark
2019-09-04 11:29:44 +00:00
if '?page=' in self.path:
pageNumberStr=self.path.split('?page=')[1]
if '?' in pageNumberStr:
pageNumberStr=pageNumberStr.split('?')[0]
if pageNumberStr.isdigit():
pageNumber=int(pageNumberStr)
timelineStr='inbox'
if '?tl=' in self.path:
timelineStr=self.path.split('?tl=')[1]
if '?' in timelineStr:
timelineStr=timelineStr.split('?')[0]
2019-08-01 09:05:09 +00:00
actor=self.path.split('?unlike=')[0]
self.postToNickname=getNicknameFromActor(actor)
2019-09-02 09:43:43 +00:00
if not self.postToNickname:
print('WARN: unable to find nickname in '+actor)
self.server.GETbusy=False
2019-11-26 15:22:45 +00:00
actorAbsolute=self.server.httpPrefix+'://'+self.server.domainFull+actor
self._redirect_headers(actorAbsolute+'/'+timelineStr+ \
2019-11-03 15:27:29 +00:00
'?page='+str(pageNumber),cookie)
2019-09-02 09:43:43 +00:00
return
2019-08-01 09:05:09 +00:00
if not self.server.session:
self.server.session= \
2019-11-13 10:50:16 +00:00
createSession(self.server.useTor)
2019-11-03 15:27:29 +00:00
undoActor= \
self.server.httpPrefix+'://'+ \
self.server.domainFull+'/users/'+self.postToNickname
actorLiked=self.path.split('?actor=')[1]
if '?' in actorLiked:
actorLiked=actorLiked.split('?')[0]
2019-08-01 09:05:09 +00:00
undoLikeJson= {
2019-08-18 11:07:06 +00:00
"@context": "https://www.w3.org/ns/activitystreams",
2019-08-01 09:05:09 +00:00
'type': 'Undo',
'actor': undoActor,
'to': [actorLiked],
2019-08-01 09:05:09 +00:00
'object': {
'type': 'Like',
'actor': undoActor,
'to': [actorLiked],
2019-08-18 16:58:50 +00:00
'object': likeUrl
}
2019-08-01 12:49:16 +00:00
}
2019-11-15 14:34:11 +00:00
self._postToOutbox(undoLikeJson,self.server.projectVersion)
2019-08-04 18:29:26 +00:00
self.server.GETbusy=False
2019-11-26 15:22:45 +00:00
actorAbsolute=self.server.httpPrefix+'://'+self.server.domainFull+actor
self._redirect_headers(actorAbsolute+'/'+timelineStr+ \
2019-11-19 15:27:43 +00:00
'?page='+str(pageNumber)+ \
timelineBookmark,cookie)
2019-08-04 18:29:26 +00:00
return
2019-11-17 14:01:49 +00:00
self._benchmarkGETtimings(GETstartTime,GETtimings,36)
# bookmark from the web interface icon
if htmlGET and '?bookmark=' in self.path:
pageNumber=1
bookmarkUrl=self.path.split('?bookmark=')[1]
if '?' in bookmarkUrl:
bookmarkUrl=bookmarkUrl.split('?')[0]
2019-11-19 15:27:43 +00:00
timelineBookmark=''
if '?bm=' in self.path:
timelineBookmark=self.path.split('?bm=')[1]
if '?' in timelineBookmark:
timelineBookmark=timelineBookmark.split('?')[0]
timelineBookmark='#'+timelineBookmark
2019-11-17 14:01:49 +00:00
actor=self.path.split('?bookmark=')[0]
if '?page=' in self.path:
pageNumberStr=self.path.split('?page=')[1]
if '?' in pageNumberStr:
pageNumberStr=pageNumberStr.split('?')[0]
if pageNumberStr.isdigit():
pageNumber=int(pageNumberStr)
timelineStr='inbox'
if '?tl=' in self.path:
timelineStr=self.path.split('?tl=')[1]
if '?' in timelineStr:
timelineStr=timelineStr.split('?')[0]
self.postToNickname=getNicknameFromActor(actor)
if not self.postToNickname:
print('WARN: unable to find nickname in '+actor)
self.server.GETbusy=False
2019-11-26 15:22:45 +00:00
actorAbsolute=self.server.httpPrefix+'://'+self.server.domainFull+actor
self._redirect_headers(actorAbsolute+'/'+timelineStr+ \
2019-11-17 14:01:49 +00:00
'?page='+str(pageNumber),cookie)
return
if not self.server.session:
self.server.session= \
createSession(self.server.useTor)
bookmarkActor= \
self.server.httpPrefix+'://'+ \
self.server.domainFull+'/users/'+self.postToNickname
bookmarkJson= {
"@context": "https://www.w3.org/ns/activitystreams",
'type': 'Bookmark',
'actor': bookmarkActor,
'to': [bookmarkActor],
'object': bookmarkUrl
}
self._postToOutbox(bookmarkJson,self.server.projectVersion)
self.server.GETbusy=False
2019-11-26 15:22:45 +00:00
actorAbsolute=self.server.httpPrefix+'://'+self.server.domainFull+actor
self._redirect_headers(actorAbsolute+'/'+timelineStr+ \
2019-11-19 15:27:43 +00:00
'?page='+str(pageNumber)+ \
timelineBookmark,cookie)
2019-11-17 14:01:49 +00:00
return
# undo a bookmark from the web interface icon
if htmlGET and '?unbookmark=' in self.path:
pageNumber=1
bookmarkUrl=self.path.split('?unbookmark=')[1]
if '?' in bookmarkUrl:
bookmarkUrl=bookmarkUrl.split('?')[0]
2019-11-19 15:27:43 +00:00
timelineBookmark=''
if '?bm=' in self.path:
timelineBookmark=self.path.split('?bm=')[1]
if '?' in timelineBookmark:
timelineBookmark=timelineBookmark.split('?')[0]
timelineBookmark='#'+timelineBookmark
2019-11-17 14:01:49 +00:00
if '?page=' in self.path:
pageNumberStr=self.path.split('?page=')[1]
if '?' in pageNumberStr:
pageNumberStr=pageNumberStr.split('?')[0]
if pageNumberStr.isdigit():
pageNumber=int(pageNumberStr)
timelineStr='inbox'
if '?tl=' in self.path:
timelineStr=self.path.split('?tl=')[1]
if '?' in timelineStr:
timelineStr=timelineStr.split('?')[0]
actor=self.path.split('?unbookmark=')[0]
self.postToNickname=getNicknameFromActor(actor)
if not self.postToNickname:
print('WARN: unable to find nickname in '+actor)
self.server.GETbusy=False
2019-11-26 15:22:45 +00:00
actorAbsolute=self.server.httpPrefix+'://'+self.server.domainFull+actor
self._redirect_headers(actorAbsolute+'/'+timelineStr+ \
2019-11-17 14:01:49 +00:00
'?page='+str(pageNumber),cookie)
return
if not self.server.session:
self.server.session= \
createSession(self.server.useTor)
undoActor= \
self.server.httpPrefix+'://'+ \
self.server.domainFull+'/users/'+self.postToNickname
undoBookmarkJson= {
"@context": "https://www.w3.org/ns/activitystreams",
'type': 'Undo',
'actor': undoActor,
'to': [undoActor],
'object': {
'type': 'Bookmark',
'actor': undoActor,
'to': [undoActor],
'object': bookmarkUrl
}
}
self._postToOutbox(undoBookmarkJson,self.server.projectVersion)
self.server.GETbusy=False
2019-11-26 15:22:45 +00:00
actorAbsolute=self.server.httpPrefix+'://'+self.server.domainFull+actor
self._redirect_headers(actorAbsolute+'/'+timelineStr+ \
2019-11-19 15:27:43 +00:00
'?page='+str(pageNumber)+ \
timelineBookmark,cookie)
2019-11-17 14:01:49 +00:00
return
2019-11-16 13:25:44 +00:00
self._benchmarkGETtimings(GETstartTime,GETtimings,37)
2019-08-04 18:29:26 +00:00
# delete a post from the web interface icon
2019-08-18 20:11:26 +00:00
if htmlGET and '?delete=' in self.path:
2019-09-04 11:29:44 +00:00
pageNumber=1
if '?page=' in self.path:
pageNumberStr=self.path.split('?page=')[1]
if '?' in pageNumberStr:
pageNumberStr=pageNumberStr.split('?')[0]
if pageNumberStr.isdigit():
pageNumber=int(pageNumberStr)
2019-08-04 18:29:26 +00:00
deleteUrl=self.path.split('?delete=')[1]
2019-09-04 11:29:44 +00:00
if '?' in deleteUrl:
deleteUrl=deleteUrl.split('?')[0]
2019-11-03 15:27:29 +00:00
actor= \
self.server.httpPrefix+'://'+ \
self.server.domainFull+self.path.split('?delete=')[0]
2019-08-12 18:02:29 +00:00
if self.server.allowDeletion or \
deleteUrl.startswith(actor):
if self.server.debug:
print('DEBUG: deleteUrl='+deleteUrl)
print('DEBUG: actor='+actor)
if actor not in deleteUrl:
# You can only delete your own posts
self.server.GETbusy=False
2019-11-15 19:44:20 +00:00
self._redirect_headers(actor+'/inbox',cookie)
2019-08-12 18:02:29 +00:00
return
self.postToNickname=getNicknameFromActor(actor)
2019-09-02 09:43:43 +00:00
if not self.postToNickname:
print('WARN: unable to find nickname in '+actor)
self.server.GETbusy=False
2019-11-15 19:44:20 +00:00
self._redirect_headers(actor+'/inbox',cookie)
2019-09-02 09:43:43 +00:00
return
2019-08-12 18:02:29 +00:00
if not self.server.session:
self.server.session= \
2019-11-13 10:50:16 +00:00
createSession(self.server.useTor)
2019-08-27 12:47:11 +00:00
deleteStr= \
htmlDeletePost(self.server.recentPostsCache, \
self.server.maxRecentPosts, \
self.server.translate,pageNumber, \
2019-09-04 11:29:44 +00:00
self.server.session,self.server.baseDir, \
2019-08-27 12:47:11 +00:00
deleteUrl,self.server.httpPrefix, \
__version__,self.server.cachedWebfingers, \
self.server.personCache)
if deleteStr:
2019-11-09 21:39:04 +00:00
self._set_headers('text/html',len(deleteStr),cookie)
2019-10-22 12:35:51 +00:00
self._write(deleteStr.encode())
2019-08-27 12:47:11 +00:00
self.server.GETbusy=False
return
2019-08-01 09:05:09 +00:00
self.server.GETbusy=False
self._redirect_headers(actor+'/inbox',cookie)
return
# reply from the web interface icon
2019-08-05 17:04:43 +00:00
inReplyToUrl=None
replyWithDM=False
2019-08-05 19:13:15 +00:00
replyToList=[]
2019-09-04 11:29:44 +00:00
replyPageNumber=1
2019-08-25 22:12:47 +00:00
shareDescription=None
2019-11-14 21:09:36 +00:00
replytoActor=None
if htmlGET:
# public reply
if '?replyto=' in self.path:
inReplyToUrl=self.path.split('?replyto=')[1]
if '?' in inReplyToUrl:
mentionsList=inReplyToUrl.split('?')
for m in mentionsList:
if m.startswith('mention='):
2019-09-22 17:48:52 +00:00
replyHandle=m.replace('mention=','')
if replyHandle not in replyToList:
replyToList.append(replyHandle)
2019-09-04 11:29:44 +00:00
if m.startswith('page='):
replyPageStr=m.replace('page=','')
if replyPageStr.isdigit():
replyPageNumber=int(replyPageStr)
2019-11-14 21:09:36 +00:00
if m.startswith('actor='):
replytoActor=m.replace('actor=','')
inReplyToUrl=mentionsList[0]
self.path=self.path.split('?replyto=')[0]+'/newpost'
if self.server.debug:
print('DEBUG: replyto path '+self.path)
# reply to followers
if '?replyfollowers=' in self.path:
inReplyToUrl=self.path.split('?replyfollowers=')[1]
if '?' in inReplyToUrl:
mentionsList=inReplyToUrl.split('?')
for m in mentionsList:
if m.startswith('mention='):
2019-09-22 17:48:52 +00:00
replyHandle=m.replace('mention=','')
if m.replace('mention=','') not in replyToList:
replyToList.append(replyHandle)
2019-09-04 11:29:44 +00:00
if m.startswith('page='):
replyPageStr=m.replace('page=','')
if replyPageStr.isdigit():
replyPageNumber=int(replyPageStr)
2019-11-14 21:09:36 +00:00
if m.startswith('actor='):
replytoActor=m.replace('actor=','')
inReplyToUrl=mentionsList[0]
self.path=self.path.split('?replyfollowers=')[0]+'/newfollowers'
if self.server.debug:
print('DEBUG: replyfollowers path '+self.path)
2019-08-02 09:52:12 +00:00
# replying as a direct message, for moderation posts or the dm timeline
if '?replydm=' in self.path:
inReplyToUrl=self.path.split('?replydm=')[1]
if '?' in inReplyToUrl:
mentionsList=inReplyToUrl.split('?')
for m in mentionsList:
if m.startswith('mention='):
2019-09-22 17:48:52 +00:00
replyHandle=m.replace('mention=','')
if m.replace('mention=','') not in replyToList:
replyToList.append(m.replace('mention=',''))
2019-09-04 11:29:44 +00:00
if m.startswith('page='):
replyPageStr=m.replace('page=','')
if replyPageStr.isdigit():
replyPageNumber=int(replyPageStr)
2019-11-14 21:09:36 +00:00
if m.startswith('actor='):
replytoActor=m.replace('actor=','')
inReplyToUrl=mentionsList[0]
if inReplyToUrl.startswith('sharedesc:'):
2019-11-03 15:27:29 +00:00
shareDescription= \
inReplyToUrl.replace('sharedesc:','').replace('%20',' ').replace('%40','@').replace('%3A',':').replace('%2F','/').replace('%23','#')
self.path=self.path.split('?replydm=')[0]+'/newdm'
if self.server.debug:
print('DEBUG: replydm path '+self.path)
# edit profile in web interface
if '/users/' in self.path and self.path.endswith('/editprofile'):
2019-09-07 09:56:14 +00:00
msg=htmlEditProfile(self.server.translate, \
self.server.baseDir, \
self.path,self.server.domain, \
self.server.port).encode()
2019-11-09 21:39:04 +00:00
self._set_headers('text/html',len(msg),cookie)
2019-10-22 12:35:51 +00:00
self._write(msg)
self.server.GETbusy=False
return
# Various types of new post in the web interface
if '/users/' in self.path and \
(self.path.endswith('/newpost') or \
self.path.endswith('/newunlisted') or \
self.path.endswith('/newfollowers') or \
self.path.endswith('/newdm') or \
self.path.endswith('/newreport') or \
2019-11-25 22:34:26 +00:00
self.path.endswith('/newquestion') or \
self.path.endswith('/newshare')):
2019-09-07 08:57:52 +00:00
msg=htmlNewPost(self.server.translate, \
self.server.baseDir, \
self.path,inReplyToUrl, \
replyToList, \
shareDescription, \
replyPageNumber).encode()
2019-11-09 21:39:04 +00:00
self._set_headers('text/html',len(msg),cookie)
2019-10-22 12:35:51 +00:00
self._write(msg)
self.server.GETbusy=False
return
2019-07-24 22:38:42 +00:00
2019-11-16 13:25:44 +00:00
self._benchmarkGETtimings(GETstartTime,GETtimings,38)
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]
2019-09-25 09:22:10 +00:00
if len(statusNumber)>10 and statusNumber.isdigit():
2019-07-25 16:03:58 +00:00
postFilename= \
2019-11-03 15:27:29 +00:00
self.server.baseDir+'/accounts/'+nickname+'@'+ \
self.server.domain+'/outbox/'+ \
self.server.httpPrefix+':##'+ \
self.server.domainFull+'#users#'+ \
nickname+'#statuses#'+statusNumber+'.json'
2019-07-25 16:03:58 +00:00
if os.path.isfile(postFilename):
2019-10-22 11:55:06 +00:00
postJsonObject=loadJson(postFilename)
loadedPost=False
2019-10-22 11:55:06 +00:00
if postJsonObject:
loadedPost=True
else:
postJsonObject={}
if loadedPost:
2019-07-25 16:03:58 +00:00
# Only authorized viewers get to see likes on posts
# Otherwize marketers could gain more social graph info
if not authorized:
2019-11-25 11:19:03 +00:00
self._removePostInteractions(postJsonObject)
2019-08-24 11:23:12 +00:00
if self._requestHTTP():
2019-08-15 13:20:09 +00:00
msg= \
htmlIndividualPost(self.server.recentPostsCache, \
self.server.maxRecentPosts, \
self.server.translate, \
2019-09-07 08:57:52 +00:00
self.server.session, \
2019-11-03 15:27:29 +00:00
self.server.cachedWebfingers, \
self.server.personCache, \
nickname,self.server.domain, \
self.server.port, \
2019-08-15 13:20:09 +00:00
authorized,postJsonObject, \
self.server.httpPrefix, \
self.server.projectVersion).encode('utf-8')
2019-11-09 21:39:04 +00:00
self._set_headers('text/html',len(msg),cookie)
2019-10-22 12:35:51 +00:00
self._write(msg)
2019-07-25 16:03:58 +00:00
else:
2019-09-25 09:22:10 +00:00
if self._fetchAuthenticated():
2019-11-09 21:39:04 +00:00
msg=json.dumps(postJsonObject,ensure_ascii=False).encode('utf-8')
self._set_headers('application/json',len(msg),None)
2019-10-22 12:35:51 +00:00
self._write(msg)
2019-09-25 09:22:10 +00:00
else:
self._404()
2019-07-25 16:03:58 +00:00
self.server.GETbusy=False
return
else:
self._404()
self.server.GETbusy=False
return
2019-09-28 11:29:42 +00:00
2019-11-16 13:25:44 +00:00
self._benchmarkGETtimings(GETstartTime,GETtimings,39)
2019-07-13 19:28:14 +00:00
# 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
2019-07-13 19:34:03 +00:00
boxname='outbox'
2019-11-03 15:27:29 +00:00
postDir= \
self.server.baseDir+'/accounts/'+ \
nickname+'@'+self.server.domain+'/'+boxname
2019-07-13 19:34:03 +00:00
postRepliesFilename= \
postDir+'/'+ \
2019-11-03 15:27:29 +00:00
self.server.httpPrefix+':##'+ \
self.server.domainFull+'#users#'+ \
nickname+'#statuses#'+statusNumber+'.replies'
2019-07-13 19:34:03 +00:00
if not os.path.isfile(postRepliesFilename):
# There are no replies, so show empty collection
repliesJson = {
'@context': 'https://www.w3.org/ns/activitystreams',
2019-07-25 16:03:58 +00:00
'first': self.server.httpPrefix+'://'+self.server.domainFull+'/users/'+nickname+'/statuses/'+statusNumber+'/replies?page=true',
'id': self.server.httpPrefix+'://'+self.server.domainFull+'/users/'+nickname+'/statuses/'+statusNumber+'/replies',
'last': self.server.httpPrefix+'://'+self.server.domainFull+'/users/'+nickname+'/statuses/'+statusNumber+'/replies?page=true',
2019-07-13 19:34:03 +00:00
'totalItems': 0,
'type': 'OrderedCollection'}
2019-08-24 11:23:12 +00:00
if self._requestHTTP():
2019-08-02 16:49:42 +00:00
if not self.server.session:
if self.server.debug:
print('DEBUG: creating new session')
self.server.session= \
2019-11-13 10:50:16 +00:00
createSession(self.server.useTor)
msg=htmlPostReplies(self.server.recentPostsCache, \
self.server.maxRecentPosts, \
self.server.translate, \
2019-09-07 08:57:52 +00:00
self.server.baseDir, \
2019-08-15 13:20:09 +00:00
self.server.session, \
self.server.cachedWebfingers, \
self.server.personCache, \
nickname, \
self.server.domain, \
self.server.port, \
repliesJson, \
self.server.httpPrefix, \
self.server.projectVersion).encode('utf-8')
2019-11-09 21:39:04 +00:00
self._set_headers('text/html',len(msg),cookie)
2019-08-02 18:04:31 +00:00
print('----------------------------------------------------')
2019-10-22 12:35:51 +00:00
self._write(msg)
2019-07-20 21:13:36 +00:00
else:
2019-09-25 09:22:10 +00:00
if self._fetchAuthenticated():
2019-11-09 21:39:04 +00:00
msg=json.dumps(repliesJson,ensure_ascii=False).encode('utf-8')
self._set_headers('application/json',len(msg),None)
2019-10-22 12:35:51 +00:00
self._write(msg)
2019-09-25 09:22:10 +00:00
else:
self._404()
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',
2019-07-25 16:03:58 +00:00
'id': self.server.httpPrefix+'://'+self.server.domainFull+'/users/'+nickname+'/statuses/'+statusNumber+'?page=true',
2019-07-13 19:34:03 +00:00
'orderedItems': [
],
2019-07-25 16:03:58 +00:00
'partOf': self.server.httpPrefix+'://'+self.server.domainFull+'/users/'+nickname+'/statuses/'+statusNumber,
2019-07-13 19:34:03 +00:00
'type': 'OrderedCollectionPage'}
2019-07-25 11:18:35 +00:00
2019-07-13 19:34:03 +00:00
# populate the items list with replies
2019-08-02 18:37:23 +00:00
populateRepliesJson(self.server.baseDir, \
nickname, \
self.server.domain, \
postRepliesFilename, \
authorized, \
repliesJson)
2019-07-13 19:34:03 +00:00
# send the replies json
2019-08-24 11:23:12 +00:00
if self._requestHTTP():
2019-08-02 16:49:42 +00:00
if not self.server.session:
if self.server.debug:
print('DEBUG: creating new session')
self.server.session= \
2019-11-13 10:50:16 +00:00
createSession(self.server.useTor)
msg=htmlPostReplies(self.server.recentPostsCache, \
self.server.maxRecentPosts, \
self.server.translate, \
2019-09-07 08:57:52 +00:00
self.server.baseDir, \
2019-08-15 13:20:09 +00:00
self.server.session, \
self.server.cachedWebfingers, \
self.server.personCache, \
nickname, \
self.server.domain, \
self.server.port, \
repliesJson, \
self.server.httpPrefix, \
self.server.projectVersion).encode('utf-8')
2019-11-09 21:39:04 +00:00
self._set_headers('text/html',len(msg),cookie)
2019-10-22 12:35:51 +00:00
self._write(msg)
2019-07-20 21:13:36 +00:00
else:
2019-09-25 09:22:10 +00:00
if self._fetchAuthenticated():
2019-11-09 21:39:04 +00:00
msg=json.dumps(repliesJson,ensure_ascii=False).encode('utf-8')
self._set_headers('application/json',len(msg),None)
2019-10-22 12:35:51 +00:00
self._write(msg)
2019-09-25 09:22:10 +00:00
else:
self._404()
2019-07-13 19:34:03 +00:00
self.server.GETbusy=False
return
2019-07-13 19:28:14 +00:00
2019-11-16 13:25:44 +00:00
self._benchmarkGETtimings(GETstartTime,GETtimings,40)
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]
2019-11-03 15:27:29 +00:00
actorFilename= \
self.server.baseDir+'/accounts/'+ \
nickname+'@'+self.server.domain+'.json'
2019-07-22 17:21:45 +00:00
if os.path.isfile(actorFilename):
2019-10-22 11:55:06 +00:00
actorJson=loadJson(actorFilename)
if actorJson:
2019-07-22 17:21:45 +00:00
if actorJson.get('roles'):
2019-08-24 11:23:12 +00:00
if self._requestHTTP():
2019-07-22 17:21:45 +00:00
getPerson = \
2019-11-03 15:27:29 +00:00
personLookup(self.server.domain, \
self.path.replace('/roles',''), \
2019-07-22 17:21:45 +00:00
self.server.baseDir)
2019-07-22 20:01:46 +00:00
if getPerson:
msg=htmlProfile(self.server.recentPostsCache, \
self.server.maxRecentPosts, \
self.server.translate, \
2019-09-07 08:57:52 +00:00
self.server.projectVersion, \
2019-08-15 13:20:09 +00:00
self.server.baseDir, \
self.server.httpPrefix, \
True, \
self.server.ocapAlways, \
getPerson,'roles', \
self.server.session, \
self.server.cachedWebfingers, \
self.server.personCache, \
actorJson['roles'], \
None,None).encode('utf-8')
2019-11-09 21:39:04 +00:00
self._set_headers('text/html',len(msg),cookie)
2019-10-22 12:35:51 +00:00
self._write(msg)
2019-07-22 17:21:45 +00:00
else:
2019-09-25 09:22:10 +00:00
if self._fetchAuthenticated():
2019-11-09 21:39:04 +00:00
msg=json.dumps(actorJson['roles'],ensure_ascii=False).encode('utf-8')
self._set_headers('application/json',len(msg),None)
2019-10-22 12:35:51 +00:00
self._write(msg)
2019-09-25 09:22:10 +00:00
else:
self._404()
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
2019-08-27 17:33:11 +00:00
# show skills on the profile page
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]
2019-11-03 15:27:29 +00:00
actorFilename= \
self.server.baseDir+'/accounts/'+ \
nickname+'@'+self.server.domain+'.json'
2019-07-22 20:01:46 +00:00
if os.path.isfile(actorFilename):
2019-10-22 11:55:06 +00:00
actorJson=loadJson(actorFilename)
if actorJson:
2019-07-22 20:01:46 +00:00
if actorJson.get('skills'):
2019-08-24 11:23:12 +00:00
if self._requestHTTP():
2019-07-22 20:01:46 +00:00
getPerson = \
2019-11-03 15:27:29 +00:00
personLookup(self.server.domain, \
self.path.replace('/skills',''), \
2019-07-22 20:01:46 +00:00
self.server.baseDir)
if getPerson:
msg=htmlProfile(self.server.recentPostsCache, \
self.server.maxRecentPosts, \
self.server.translate, \
2019-09-07 08:57:52 +00:00
self.server.projectVersion, \
2019-08-15 13:20:09 +00:00
self.server.baseDir, \
self.server.httpPrefix, \
True, \
self.server.ocapAlways, \
getPerson,'skills', \
self.server.session, \
self.server.cachedWebfingers, \
self.server.personCache, \
actorJson['skills'], \
None,None).encode('utf-8')
2019-11-09 21:39:04 +00:00
self._set_headers('text/html',len(msg),cookie)
2019-10-22 12:35:51 +00:00
self._write(msg)
2019-07-22 20:01:46 +00:00
else:
2019-09-25 09:22:10 +00:00
if self._fetchAuthenticated():
2019-11-09 21:39:04 +00:00
msg=json.dumps(actorJson['skills'],ensure_ascii=False).encode('utf-8')
self._set_headers('application/json',len(msg),None)
2019-10-22 12:35:51 +00:00
self._write(msg)
2019-09-25 09:22:10 +00:00
else:
self._404()
2019-07-22 20:01:46 +00:00
self.server.GETbusy=False
return
2019-08-27 17:35:12 +00:00
actor=self.path.replace('/skills','')
2019-11-26 15:22:45 +00:00
actorAbsolute=self.server.httpPrefix+'://'+self.server.domainFull+actor
self._redirect_headers(actorAbsolute,cookie)
2019-08-27 17:33:11 +00:00
self.server.GETbusy=False
return
2019-07-22 20:01:46 +00:00
2019-11-16 13:25:44 +00:00
self._benchmarkGETtimings(GETstartTime,GETtimings,41)
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-22 17:21:45 +00:00
postFilename= \
2019-11-03 15:27:29 +00:00
self.server.baseDir+'/accounts/'+ \
nickname+'@'+self.server.domain+'/outbox/'+ \
self.server.httpPrefix+':##'+ \
self.server.domainFull+'#users#'+ \
nickname+'#statuses#'+statusNumber+'.json'
2019-07-22 17:21:45 +00:00
if os.path.isfile(postFilename):
2019-10-22 11:55:06 +00:00
postJsonObject=loadJson(postFilename)
if not postJsonObject:
2019-08-31 13:08:01 +00:00
self.send_response(429)
self.end_headers()
self.server.GETbusy=False
return
else:
2019-07-22 17:21:45 +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-11-25 11:19:03 +00:00
self._removePostInteractions(postJsonObject)
2019-08-24 11:23:12 +00:00
if self._requestHTTP():
msg=htmlIndividualPost(self.server.recentPostsCache, \
self.server.maxRecentPosts, \
self.server.translate, \
2019-09-07 08:57:52 +00:00
self.server.baseDir, \
2019-08-15 13:20:09 +00:00
self.server.session, \
2019-11-03 15:27:29 +00:00
self.server.cachedWebfingers, \
self.server.personCache, \
nickname, \
self.server.domain, \
self.server.port, \
2019-08-15 13:20:09 +00:00
authorized,postJsonObject, \
self.server.httpPrefix, \
self.server.projectVersion).encode('utf-8')
2019-11-09 21:39:04 +00:00
self._set_headers('text/html',len(msg),cookie)
2019-10-22 12:35:51 +00:00
self._write(msg)
2019-07-22 17:21:45 +00:00
else:
2019-09-25 09:22:10 +00:00
if self._fetchAuthenticated():
2019-11-09 21:39:04 +00:00
msg=json.dumps(postJsonObject,ensure_ascii=False).encode('utf-8')
self._set_headers('application/json',len(msg),None)
2019-10-22 12:35:51 +00:00
self._write(msg)
2019-09-25 09:22:10 +00:00
else:
self._404()
2019-07-22 17:21:45 +00:00
self.server.GETbusy=False
return
else:
self._404()
self.server.GETbusy=False
return
2019-08-12 13:22:17 +00:00
2019-11-16 13:25:44 +00:00
self._benchmarkGETtimings(GETstartTime,GETtimings,42)
2019-07-03 19:32:07 +00:00
# get the inbox for a given person
2019-08-12 13:22:17 +00:00
if self.path.endswith('/inbox') or '/inbox?page=' in self.path:
2019-07-03 19:32:07 +00:00
if '/users/' in self.path:
2019-07-25 11:18:35 +00:00
if authorized:
2019-11-17 17:20:08 +00:00
inboxFeed= \
personBoxJson(self.server.recentPostsCache, \
self.server.session, \
2019-11-17 17:20:08 +00:00
self.server.baseDir, \
self.server.domain, \
self.server.port, \
self.path, \
self.server.httpPrefix, \
maxPostsInFeed, 'inbox', \
authorized,self.server.ocapAlways)
2019-07-12 11:05:43 +00:00
if inboxFeed:
2019-08-24 11:23:12 +00:00
if self._requestHTTP():
2019-07-24 12:02:28 +00:00
nickname=self.path.replace('/users/','').replace('/inbox','')
2019-07-31 20:37:19 +00:00
pageNumber=1
2019-07-24 12:02:28 +00:00
if '?page=' in nickname:
2019-07-31 20:37:19 +00:00
pageNumber=nickname.split('?page=')[1]
2019-07-24 12:02:28 +00:00
nickname=nickname.split('?page=')[0]
2019-07-31 20:37:19 +00:00
if pageNumber.isdigit():
pageNumber=int(pageNumber)
else:
pageNumber=1
2019-07-24 11:03:56 +00:00
if 'page=' not in self.path:
# if no page was specified then show the first
2019-11-17 17:20:08 +00:00
inboxFeed= \
personBoxJson(self.server.recentPostsCache, \
self.server.session, \
2019-11-17 17:20:08 +00:00
self.server.baseDir, \
self.server.domain, \
self.server.port, \
self.path+'?page=1', \
self.server.httpPrefix, \
maxPostsInFeed, 'inbox', \
authorized,self.server.ocapAlways)
msg=htmlInbox(self.server.recentPostsCache, \
self.server.maxRecentPosts, \
self.server.translate, \
2019-09-07 09:21:10 +00:00
pageNumber,maxPostsInFeed, \
2019-08-15 13:20:09 +00:00
self.server.session, \
self.server.baseDir, \
self.server.cachedWebfingers, \
self.server.personCache, \
nickname, \
self.server.domain, \
self.server.port, \
inboxFeed, \
self.server.allowDeletion, \
self.server.httpPrefix, \
self.server.projectVersion).encode('utf-8')
2019-11-09 21:39:04 +00:00
self._set_headers('text/html',len(msg),cookie)
2019-10-22 12:35:51 +00:00
self._write(msg)
2019-07-20 21:13:36 +00:00
else:
# don't need authenticated fetch here because there is
# already the authorization check
2019-11-09 21:39:04 +00:00
msg=json.dumps(inboxFeed,ensure_ascii=False).encode('utf-8')
self._set_headers('application/json',len(msg),None)
2019-10-22 12:35:51 +00:00
self._write(msg)
2019-07-12 11:05:43 +00:00
self.server.GETbusy=False
return
else:
if self.server.debug:
2019-08-04 21:25:19 +00:00
nickname=self.path.replace('/users/','').replace('/inbox','')
2019-07-12 11:05:43 +00:00
print('DEBUG: '+nickname+ \
' was not authorized to access '+self.path)
2019-08-05 16:05:08 +00:00
if self.path!='/inbox':
# not the shared inbox
if self.server.debug:
print('DEBUG: GET access to inbox is unauthorized')
self.send_response(405)
self.end_headers()
2019-08-12 13:22:17 +00:00
self.server.GETbusy=False
2019-08-05 16:05:08 +00:00
return
2019-08-25 16:09:56 +00:00
2019-11-16 13:25:44 +00:00
self._benchmarkGETtimings(GETstartTime,GETtimings,43)
2019-09-23 20:09:11 +00:00
# get the direct messages for a given person
2019-08-25 16:09:56 +00:00
if self.path.endswith('/dm') or '/dm?page=' in self.path:
if '/users/' in self.path:
if authorized:
2019-11-17 17:20:08 +00:00
inboxDMFeed= \
personBoxJson(self.server.recentPostsCache, \
self.server.session, \
2019-11-17 17:20:08 +00:00
self.server.baseDir, \
self.server.domain, \
self.server.port, \
self.path, \
self.server.httpPrefix, \
maxPostsInFeed, 'dm', \
authorized,self.server.ocapAlways)
2019-08-25 16:09:56 +00:00
if inboxDMFeed:
if self._requestHTTP():
nickname=self.path.replace('/users/','').replace('/dm','')
pageNumber=1
if '?page=' in nickname:
pageNumber=nickname.split('?page=')[1]
nickname=nickname.split('?page=')[0]
if pageNumber.isdigit():
pageNumber=int(pageNumber)
else:
pageNumber=1
if 'page=' not in self.path:
# if no page was specified then show the first
2019-11-17 17:20:08 +00:00
inboxDMFeed= \
personBoxJson(self.server.recentPostsCache, \
self.server.session, \
2019-11-17 17:20:08 +00:00
self.server.baseDir, \
self.server.domain, \
self.server.port, \
self.path+'?page=1', \
self.server.httpPrefix, \
maxPostsInFeed, 'dm', \
authorized,self.server.ocapAlways)
msg=htmlInboxDMs(self.server.recentPostsCache, \
self.server.maxRecentPosts, \
self.server.translate, \
2019-09-07 09:21:10 +00:00
pageNumber,maxPostsInFeed, \
2019-08-25 17:34:09 +00:00
self.server.session, \
self.server.baseDir, \
self.server.cachedWebfingers, \
self.server.personCache, \
nickname, \
self.server.domain, \
self.server.port, \
inboxDMFeed, \
self.server.allowDeletion, \
self.server.httpPrefix, \
self.server.projectVersion).encode('utf-8')
2019-11-09 21:39:04 +00:00
self._set_headers('text/html',len(msg),cookie)
2019-10-22 12:35:51 +00:00
self._write(msg)
2019-08-25 16:09:56 +00:00
else:
# don't need authenticated fetch here because there is
# already the authorization check
2019-11-09 21:39:04 +00:00
msg=json.dumps(inboxDMFeed,ensure_ascii=False).encode('utf-8')
self._set_headers('application/json',len(msg),None)
2019-10-22 12:35:51 +00:00
self._write(msg)
2019-08-25 16:09:56 +00:00
self.server.GETbusy=False
return
else:
if self.server.debug:
nickname=self.path.replace('/users/','').replace('/dm','')
print('DEBUG: '+nickname+ \
' was not authorized to access '+self.path)
if self.path!='/dm':
# not the DM inbox
if self.server.debug:
print('DEBUG: GET access to inbox is unauthorized')
self.send_response(405)
self.end_headers()
self.server.GETbusy=False
return
2019-09-23 20:09:11 +00:00
2019-11-16 13:25:44 +00:00
self._benchmarkGETtimings(GETstartTime,GETtimings,44)
2019-09-23 20:09:11 +00:00
# get the replies for a given person
2019-09-23 20:43:18 +00:00
if self.path.endswith('/tlreplies') or '/tlreplies?page=' in self.path:
2019-09-23 20:09:11 +00:00
if '/users/' in self.path:
if authorized:
inboxRepliesFeed= \
personBoxJson(self.server.recentPostsCache, \
self.server.session, \
2019-09-28 16:21:43 +00:00
self.server.baseDir, \
2019-09-23 20:09:11 +00:00
self.server.domain, \
self.server.port, \
self.path, \
self.server.httpPrefix, \
2019-09-23 20:43:18 +00:00
maxPostsInFeed, 'tlreplies', \
2019-09-23 20:09:11 +00:00
True,self.server.ocapAlways)
2019-09-23 21:02:06 +00:00
if not inboxRepliesFeed:
inboxRepliesFeed=[]
if self._requestHTTP():
nickname=self.path.replace('/users/','').replace('/tlreplies','')
pageNumber=1
if '?page=' in nickname:
pageNumber=nickname.split('?page=')[1]
nickname=nickname.split('?page=')[0]
if pageNumber.isdigit():
pageNumber=int(pageNumber)
else:
pageNumber=1
if 'page=' not in self.path:
# if no page was specified then show the first
inboxRepliesFeed= \
personBoxJson(self.server.recentPostsCache, \
self.server.session, \
2019-09-28 16:21:43 +00:00
self.server.baseDir, \
2019-09-23 21:02:06 +00:00
self.server.domain, \
self.server.port, \
self.path+'?page=1', \
self.server.httpPrefix, \
maxPostsInFeed, 'tlreplies', \
True,self.server.ocapAlways)
msg=htmlInboxReplies(self.server.recentPostsCache, \
self.server.maxRecentPosts, \
self.server.translate, \
2019-09-23 21:02:06 +00:00
pageNumber,maxPostsInFeed, \
self.server.session, \
self.server.baseDir, \
self.server.cachedWebfingers, \
self.server.personCache, \
nickname, \
self.server.domain, \
self.server.port, \
inboxRepliesFeed, \
self.server.allowDeletion, \
self.server.httpPrefix, \
self.server.projectVersion).encode('utf-8')
2019-11-09 21:39:04 +00:00
self._set_headers('text/html',len(msg),cookie)
2019-10-22 12:35:51 +00:00
self._write(msg)
2019-09-23 21:02:06 +00:00
else:
# don't need authenticated fetch here because there is
# already the authorization check
2019-11-09 21:39:04 +00:00
msg=json.dumps(inboxRepliesFeed,ensure_ascii=False).encode('utf-8')
self._set_headers('application/json',len(msg),None)
2019-10-22 12:35:51 +00:00
self._write(msg)
2019-09-23 21:02:06 +00:00
self.server.GETbusy=False
return
2019-09-23 20:09:11 +00:00
else:
if self.server.debug:
2019-09-23 20:43:18 +00:00
nickname=self.path.replace('/users/','').replace('/tlreplies','')
2019-09-23 20:09:11 +00:00
print('DEBUG: '+nickname+ \
' was not authorized to access '+self.path)
2019-09-23 20:43:18 +00:00
if self.path!='/tlreplies':
2019-09-23 20:09:11 +00:00
# not the replies inbox
if self.server.debug:
print('DEBUG: GET access to inbox is unauthorized')
self.send_response(405)
self.end_headers()
2019-09-28 11:29:42 +00:00
self.server.GETbusy=False
return
2019-11-16 13:25:44 +00:00
self._benchmarkGETtimings(GETstartTime,GETtimings,45)
2019-09-28 11:29:42 +00:00
# get the media for a given person
if self.path.endswith('/tlmedia') or '/tlmedia?page=' in self.path:
if '/users/' in self.path:
if authorized:
inboxMediaFeed= \
personBoxJson(self.server.recentPostsCache, \
self.server.session, \
2019-09-28 16:21:43 +00:00
self.server.baseDir, \
2019-09-28 11:29:42 +00:00
self.server.domain, \
self.server.port, \
self.path, \
self.server.httpPrefix, \
2019-09-28 18:06:17 +00:00
maxPostsInMediaFeed, 'tlmedia', \
2019-09-28 11:29:42 +00:00
True,self.server.ocapAlways)
if not inboxMediaFeed:
inboxMediaFeed=[]
if self._requestHTTP():
nickname=self.path.replace('/users/','').replace('/tlmedia','')
pageNumber=1
if '?page=' in nickname:
pageNumber=nickname.split('?page=')[1]
nickname=nickname.split('?page=')[0]
if pageNumber.isdigit():
pageNumber=int(pageNumber)
else:
pageNumber=1
if 'page=' not in self.path:
# if no page was specified then show the first
inboxMediaFeed= \
personBoxJson(self.server.recentPostsCache, \
self.server.session, \
2019-09-28 16:21:43 +00:00
self.server.baseDir, \
2019-09-28 11:29:42 +00:00
self.server.domain, \
self.server.port, \
self.path+'?page=1', \
self.server.httpPrefix, \
2019-09-28 18:06:17 +00:00
maxPostsInMediaFeed, 'tlmedia', \
2019-09-28 11:29:42 +00:00
True,self.server.ocapAlways)
msg=htmlInboxMedia(self.server.recentPostsCache, \
self.server.maxRecentPosts, \
self.server.translate, \
2019-09-28 18:06:17 +00:00
pageNumber,maxPostsInMediaFeed, \
2019-09-28 11:29:42 +00:00
self.server.session, \
self.server.baseDir, \
self.server.cachedWebfingers, \
self.server.personCache, \
nickname, \
self.server.domain, \
self.server.port, \
inboxMediaFeed, \
self.server.allowDeletion, \
self.server.httpPrefix, \
self.server.projectVersion).encode('utf-8')
2019-11-09 21:39:04 +00:00
self._set_headers('text/html',len(msg),cookie)
2019-10-22 12:35:51 +00:00
self._write(msg)
2019-09-28 11:29:42 +00:00
else:
# don't need authenticated fetch here because there is
# already the authorization check
2019-11-09 21:39:04 +00:00
msg=json.dumps(inboxMediaFeed,ensure_ascii=False).encode('utf-8')
self._set_headers('application/json',len(msg),None)
2019-10-22 12:35:51 +00:00
self._write(msg)
2019-09-28 11:29:42 +00:00
self.server.GETbusy=False
return
else:
if self.server.debug:
nickname=self.path.replace('/users/','').replace('/tlmedia','')
print('DEBUG: '+nickname+ \
' was not authorized to access '+self.path)
if self.path!='/tlmedia':
# not the media inbox
if self.server.debug:
print('DEBUG: GET access to inbox is unauthorized')
self.send_response(405)
self.end_headers()
2019-09-23 20:09:11 +00:00
self.server.GETbusy=False
return
2019-08-25 16:09:56 +00:00
2019-11-16 13:25:44 +00:00
self._benchmarkGETtimings(GETstartTime,GETtimings,46)
2019-11-02 14:31:39 +00:00
# get the shared items timeline for a given person
if self.path.endswith('/tlshares') or '/tlshares?page=' in self.path:
if '/users/' in self.path:
if authorized:
if self._requestHTTP():
nickname=self.path.replace('/users/','').replace('/tlshares','')
pageNumber=1
if '?page=' in nickname:
pageNumber=nickname.split('?page=')[1]
nickname=nickname.split('?page=')[0]
if pageNumber.isdigit():
pageNumber=int(pageNumber)
else:
pageNumber=1
msg=htmlShares(self.server.recentPostsCache, \
self.server.maxRecentPosts, \
self.server.translate, \
2019-11-02 14:31:39 +00:00
pageNumber,maxPostsInFeed, \
self.server.session, \
self.server.baseDir, \
self.server.cachedWebfingers, \
self.server.personCache, \
nickname, \
self.server.domain, \
self.server.port, \
self.server.allowDeletion, \
self.server.httpPrefix, \
self.server.projectVersion).encode('utf-8')
2019-11-09 21:39:04 +00:00
self._set_headers('text/html',len(msg),cookie)
2019-11-02 14:31:39 +00:00
self._write(msg)
self.server.GETbusy=False
return
# not the shares timeline
if self.server.debug:
print('DEBUG: GET access to shares timeline is unauthorized')
self.send_response(405)
self.end_headers()
self.server.GETbusy=False
return
2019-11-17 14:01:49 +00:00
# get the bookmarks for a given person
if self.path.endswith('/tlbookmarks') or '/tlbookmarks?page=' in self.path:
if '/users/' in self.path:
if authorized:
bookmarksFeed= \
personBoxJson(self.server.recentPostsCache, \
self.server.session, \
2019-11-17 14:01:49 +00:00
self.server.baseDir, \
self.server.domain, \
self.server.port, \
self.path, \
self.server.httpPrefix, \
maxPostsInFeed, 'tlbookmarks', \
2019-11-17 17:17:17 +00:00
authorized,self.server.ocapAlways)
2019-11-17 14:01:49 +00:00
if bookmarksFeed:
if self._requestHTTP():
2019-11-17 14:27:55 +00:00
nickname=self.path.replace('/users/','').replace('/tlbookmarks','')
2019-11-17 14:01:49 +00:00
pageNumber=1
if '?page=' in nickname:
pageNumber=nickname.split('?page=')[1]
nickname=nickname.split('?page=')[0]
if pageNumber.isdigit():
pageNumber=int(pageNumber)
else:
pageNumber=1
if 'page=' not in self.path:
# if no page was specified then show the first
bookmarksFeed= \
personBoxJson(self.server.recentPostsCache, \
self.server.session, \
2019-11-17 14:01:49 +00:00
self.server.baseDir, \
self.server.domain, \
self.server.port, \
self.path+'?page=1', \
self.server.httpPrefix, \
maxPostsInFeed, 'tlbookmarks', \
2019-11-17 17:17:17 +00:00
authorized,self.server.ocapAlways)
msg=htmlBookmarks(self.server.recentPostsCache, \
self.server.maxRecentPosts, \
self.server.translate, \
2019-11-17 14:47:02 +00:00
pageNumber,maxPostsInFeed, \
self.server.session, \
self.server.baseDir, \
self.server.cachedWebfingers, \
self.server.personCache, \
nickname, \
self.server.domain, \
self.server.port, \
bookmarksFeed, \
self.server.allowDeletion, \
self.server.httpPrefix, \
self.server.projectVersion).encode('utf-8')
2019-11-17 14:01:49 +00:00
self._set_headers('text/html',len(msg),cookie)
self._write(msg)
else:
# don't need authenticated fetch here because there is
# already the authorization check
msg=json.dumps(inboxFeed,ensure_ascii=False).encode('utf-8')
self._set_headers('application/json',len(msg),None)
self._write(msg)
self.server.GETbusy=False
return
else:
if self.server.debug:
2019-11-17 14:27:55 +00:00
nickname=self.path.replace('/users/','').replace('/tlbookmarks','')
2019-11-17 14:01:49 +00:00
print('DEBUG: '+nickname+ \
' was not authorized to access '+self.path)
if self.server.debug:
print('DEBUG: GET access to bookmarks is unauthorized')
self.send_response(405)
self.end_headers()
self.server.GETbusy=False
return
2019-11-16 13:25:44 +00:00
self._benchmarkGETtimings(GETstartTime,GETtimings,47)
2019-06-29 14:35:26 +00:00
# get outbox feed for a person
2019-11-17 17:20:08 +00:00
outboxFeed= \
personBoxJson(self.server.recentPostsCache, \
self.server.session, \
2019-11-17 17:20:08 +00:00
self.server.baseDir,self.server.domain, \
self.server.port,self.path, \
self.server.httpPrefix, \
maxPostsInFeed, 'outbox', \
authorized, \
self.server.ocapAlways)
2019-06-29 14:41:23 +00:00
if outboxFeed:
2019-08-24 11:23:12 +00:00
if self._requestHTTP():
2019-11-03 15:27:29 +00:00
nickname= \
self.path.replace('/users/','').replace('/outbox','')
2019-07-31 20:37:19 +00:00
pageNumber=1
2019-07-24 12:02:28 +00:00
if '?page=' in nickname:
2019-07-31 20:37:19 +00:00
pageNumber=nickname.split('?page=')[1]
2019-07-24 12:02:28 +00:00
nickname=nickname.split('?page=')[0]
2019-07-31 20:37:19 +00:00
if pageNumber.isdigit():
pageNumber=int(pageNumber)
else:
pageNumber=1
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
2019-11-17 17:20:08 +00:00
outboxFeed= \
personBoxJson(self.server.recentPostsCache, \
self.server.session, \
2019-11-17 17:20:08 +00:00
self.server.baseDir, \
self.server.domain, \
self.server.port, \
self.path+'?page=1', \
self.server.httpPrefix, \
maxPostsInFeed, 'outbox', \
authorized, \
self.server.ocapAlways)
msg=htmlOutbox(self.server.recentPostsCache, \
self.server.maxRecentPosts, \
self.server.translate, \
2019-09-07 09:21:10 +00:00
pageNumber,maxPostsInFeed, \
2019-08-15 13:20:09 +00:00
self.server.session, \
self.server.baseDir, \
self.server.cachedWebfingers, \
self.server.personCache, \
nickname, \
self.server.domain, \
self.server.port, \
outboxFeed, \
self.server.allowDeletion, \
self.server.httpPrefix, \
self.server.projectVersion).encode('utf-8')
2019-11-09 21:39:04 +00:00
self._set_headers('text/html',len(msg),cookie)
2019-10-22 12:35:51 +00:00
self._write(msg)
2019-07-20 21:13:36 +00:00
else:
2019-09-25 09:22:10 +00:00
if self._fetchAuthenticated():
2019-11-09 21:39:04 +00:00
msg=json.dumps(outboxFeed,ensure_ascii=False).encode('utf-8')
self._set_headers('application/json',len(msg),None)
2019-10-22 12:35:51 +00:00
self._write(msg)
2019-09-25 09:22:10 +00:00
else:
self._404()
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-11-16 13:25:44 +00:00
self._benchmarkGETtimings(GETstartTime,GETtimings,48)
2019-08-12 13:22:17 +00:00
# get the moderation feed for a moderator
2019-11-03 15:27:29 +00:00
if self.path.endswith('/moderation') or \
'/moderation?page=' in self.path:
2019-08-12 13:22:17 +00:00
if '/users/' in self.path:
if authorized:
moderationFeed= \
personBoxJson(self.server.recentPostsCache, \
self.server.session, \
2019-09-28 16:21:43 +00:00
self.server.baseDir, \
2019-08-12 13:22:17 +00:00
self.server.domain, \
self.server.port, \
self.path, \
self.server.httpPrefix, \
maxPostsInFeed, 'moderation', \
True,self.server.ocapAlways)
if moderationFeed:
2019-08-24 11:23:12 +00:00
if self._requestHTTP():
2019-11-03 15:27:29 +00:00
nickname= \
self.path.replace('/users/','').replace('/moderation','')
2019-08-12 13:22:17 +00:00
pageNumber=1
if '?page=' in nickname:
pageNumber=nickname.split('?page=')[1]
nickname=nickname.split('?page=')[0]
if pageNumber.isdigit():
pageNumber=int(pageNumber)
else:
pageNumber=1
if 'page=' not in self.path:
# if no page was specified then show the first
moderationFeed= \
personBoxJson(self.server.recentPostsCache, \
self.server.session, \
2019-09-28 16:21:43 +00:00
self.server.baseDir, \
2019-08-12 13:22:17 +00:00
self.server.domain, \
self.server.port, \
self.path+'?page=1', \
self.server.httpPrefix, \
maxPostsInFeed, 'moderation', \
True,self.server.ocapAlways)
msg=htmlModeration(self.server.recentPostsCache, \
self.server.maxRecentPosts, \
self.server.translate, \
2019-09-07 09:21:10 +00:00
pageNumber,maxPostsInFeed, \
2019-08-15 13:20:09 +00:00
self.server.session, \
self.server.baseDir, \
self.server.cachedWebfingers, \
self.server.personCache, \
nickname, \
self.server.domain, \
self.server.port, \
moderationFeed, \
True, \
self.server.httpPrefix, \
self.server.projectVersion).encode('utf-8')
2019-11-09 21:39:04 +00:00
self._set_headers('text/html',len(msg),cookie)
2019-10-22 12:35:51 +00:00
self._write(msg)
2019-08-12 13:22:17 +00:00
else:
# don't need authenticated fetch here because there is
# already the authorization check
2019-11-09 21:39:04 +00:00
msg=json.dumps(moderationFeed,ensure_ascii=False).encode('utf-8')
self._set_headers('application/json',len(msg),None)
2019-10-22 12:35:51 +00:00
self._write(msg)
2019-08-12 13:22:17 +00:00
self.server.GETbusy=False
return
else:
if self.server.debug:
nickname=self.path.replace('/users/','').replace('/moderation','')
print('DEBUG: '+nickname+ \
' was not authorized to access '+self.path)
if self.server.debug:
print('DEBUG: GET access to moderation feed is unauthorized')
self.send_response(405)
self.end_headers()
self.server.GETbusy=False
return
2019-11-16 13:25:44 +00:00
self._benchmarkGETtimings(GETstartTime,GETtimings,49)
2019-11-17 17:20:08 +00:00
shares= \
getSharesFeedForPerson(self.server.baseDir, \
self.server.domain, \
self.server.port,self.path, \
self.server.httpPrefix, \
sharesPerPage)
2019-07-23 12:33:09 +00:00
if shares:
2019-08-24 11:23:12 +00:00
if self._requestHTTP():
pageNumber=1
2019-09-05 15:11:21 +00:00
if '?page=' not in self.path:
searchPath=self.path
2019-07-23 12:33:09 +00:00
# get a page of shares, not the summary
2019-11-03 15:27:29 +00:00
shares= \
getSharesFeedForPerson(self.server.baseDir, \
self.server.domain, \
self.server.port, \
self.path+'?page=true', \
self.server.httpPrefix, \
sharesPerPage)
2019-09-05 15:11:21 +00:00
else:
pageNumberStr=self.path.split('?page=')[1]
if pageNumberStr.isdigit():
pageNumber=int(pageNumberStr)
2019-09-05 15:11:21 +00:00
searchPath=self.path.split('?page=')[0]
2019-11-03 15:27:29 +00:00
getPerson= \
personLookup(self.server.domain, \
searchPath.replace('/shares',''), \
self.server.baseDir)
2019-07-23 12:33:09 +00:00
if getPerson:
if not self.server.session:
if self.server.debug:
print('DEBUG: creating new session')
self.server.session= \
2019-11-13 10:50:16 +00:00
createSession(self.server.useTor)
msg=htmlProfile(self.server.recentPostsCache, \
self.server.maxRecentPosts, \
self.server.translate, \
2019-09-07 08:57:52 +00:00
self.server.projectVersion, \
2019-08-15 13:20:09 +00:00
self.server.baseDir, \
self.server.httpPrefix, \
authorized, \
self.server.ocapAlways, \
getPerson,'shares', \
self.server.session, \
self.server.cachedWebfingers, \
self.server.personCache, \
shares, \
pageNumber,sharesPerPage).encode('utf-8')
2019-11-09 21:39:04 +00:00
self._set_headers('text/html',len(msg),cookie)
2019-10-22 12:35:51 +00:00
self._write(msg)
2019-07-23 12:33:09 +00:00
self.server.GETbusy=False
return
else:
2019-09-25 09:22:10 +00:00
if self._fetchAuthenticated():
2019-11-09 21:39:04 +00:00
msg=json.dumps(shares,ensure_ascii=False).encode('utf-8')
self._set_headers('application/json',len(msg),None)
2019-10-22 12:35:51 +00:00
self._write(msg)
2019-09-25 09:22:10 +00:00
else:
self._404()
2019-07-23 12:33:09 +00:00
self.server.GETbusy=False
return
2019-11-16 13:25:44 +00:00
self._benchmarkGETtimings(GETstartTime,GETtimings,50)
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, \
authorized,followsPerPage)
2019-06-29 20:21:37 +00:00
if following:
2019-08-24 11:23:12 +00:00
if self._requestHTTP():
pageNumber=1
2019-09-05 15:07:58 +00:00
if '?page=' not in self.path:
searchPath=self.path
2019-07-22 09:38:02 +00:00
# get a page of following, not the summary
2019-11-17 17:20:08 +00:00
following= \
getFollowingFeed(self.server.baseDir,self.server.domain, \
self.server.port,self.path+'?page=true', \
self.server.httpPrefix, \
authorized,followsPerPage)
2019-09-05 15:07:58 +00:00
else:
2019-09-05 16:18:56 +00:00
pageNumberStr=self.path.split('?page=')[1]
if pageNumberStr.isdigit():
pageNumber=int(pageNumberStr)
2019-09-05 15:07:58 +00:00
searchPath=self.path.split('?page=')[0]
getPerson = personLookup(self.server.domain,searchPath.replace('/following',''), \
2019-07-22 09:38:02 +00:00
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= \
2019-11-13 10:50:16 +00:00
createSession(self.server.useTor)
2019-08-15 13:20:09 +00:00
msg=htmlProfile(self.server.recentPostsCache, \
self.server.maxRecentPosts, \
self.server.translate, \
2019-09-07 08:57:52 +00:00
self.server.projectVersion, \
2019-08-15 13:20:09 +00:00
self.server.baseDir, \
self.server.httpPrefix, \
authorized, \
self.server.ocapAlways, \
getPerson,'following', \
self.server.session, \
self.server.cachedWebfingers, \
self.server.personCache, \
following, \
pageNumber,followsPerPage).encode('utf-8')
2019-11-09 21:39:04 +00:00
self._set_headers('text/html',len(msg),cookie)
2019-10-22 12:35:51 +00:00
self._write(msg)
2019-07-22 09:38:02 +00:00
self.server.GETbusy=False
return
2019-07-20 21:13:36 +00:00
else:
2019-09-25 09:22:10 +00:00
if self._fetchAuthenticated():
2019-11-09 21:39:04 +00:00
msg=json.dumps(following,ensure_ascii=False).encode('utf-8')
self._set_headers('application/json',len(msg),None)
2019-10-22 12:35:51 +00:00
self._write(msg)
2019-09-25 09:22:10 +00:00
else:
self._404()
2019-07-22 09:38:02 +00:00
self.server.GETbusy=False
return
2019-11-16 13:25:44 +00:00
self._benchmarkGETtimings(GETstartTime,GETtimings,51)
2019-11-17 17:20:08 +00:00
followers= \
getFollowingFeed(self.server.baseDir,self.server.domain, \
self.server.port,self.path, \
self.server.httpPrefix, \
authorized,followsPerPage,'followers')
2019-06-29 20:21:37 +00:00
if followers:
2019-08-24 11:23:12 +00:00
if self._requestHTTP():
pageNumber=1
2019-09-05 15:07:58 +00:00
if '?page=' not in self.path:
searchPath=self.path
2019-07-22 09:38:02 +00:00
# get a page of followers, not the summary
2019-11-03 15:27:29 +00:00
followers= \
getFollowingFeed(self.server.baseDir,self.server.domain, \
self.server.port,self.path+'?page=1', \
self.server.httpPrefix, \
authorized,followsPerPage,'followers')
2019-09-05 15:07:58 +00:00
else:
pageNumberStr=self.path.split('?page=')[1]
if pageNumberStr.isdigit():
pageNumber=int(pageNumberStr)
2019-09-05 15:07:58 +00:00
searchPath=self.path.split('?page=')[0]
2019-11-03 15:27:29 +00:00
getPerson= \
personLookup(self.server.domain,searchPath.replace('/followers',''), \
self.server.baseDir)
2019-07-22 09:38:02 +00:00
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= \
2019-11-13 10:50:16 +00:00
createSession(self.server.useTor)
msg=htmlProfile(self.server.recentPostsCache, \
self.server.maxRecentPosts, \
self.server.translate, \
2019-09-07 08:57:52 +00:00
self.server.projectVersion, \
2019-08-15 13:20:09 +00:00
self.server.baseDir, \
self.server.httpPrefix, \
authorized, \
self.server.ocapAlways, \
getPerson,'followers', \
self.server.session, \
self.server.cachedWebfingers, \
self.server.personCache, \
followers, \
pageNumber,followsPerPage).encode('utf-8')
2019-11-09 21:39:04 +00:00
self._set_headers('text/html',len(msg),cookie)
2019-10-22 12:35:51 +00:00
self._write(msg)
2019-07-22 09:38:02 +00:00
self.server.GETbusy=False
return
2019-07-20 21:13:36 +00:00
else:
2019-09-25 09:22:10 +00:00
if self._fetchAuthenticated():
2019-11-09 21:39:04 +00:00
msg=json.dumps(followers,ensure_ascii=False).encode('utf-8')
self._set_headers('application/json',len(msg),None)
2019-10-22 12:35:51 +00:00
self._write(msg)
2019-09-25 09:22:10 +00:00
else:
self._404()
2019-07-01 14:30:48 +00:00
self.server.GETbusy=False
2019-07-04 14:36:29 +00:00
return
2019-11-16 13:25:44 +00:00
self._benchmarkGETtimings(GETstartTime,GETtimings,52)
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-08-24 11:23:12 +00:00
if self._requestHTTP():
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= \
2019-11-13 10:50:16 +00:00
createSession(self.server.useTor)
msg=htmlProfile(self.server.recentPostsCache, \
self.server.maxRecentPosts, \
self.server.translate, \
2019-09-07 08:57:52 +00:00
self.server.projectVersion, \
2019-08-15 13:20:09 +00:00
self.server.baseDir, \
self.server.httpPrefix, \
authorized, \
self.server.ocapAlways, \
getPerson,'posts',
self.server.session, \
self.server.cachedWebfingers, \
self.server.personCache, \
None,None).encode('utf-8')
2019-11-09 21:39:04 +00:00
self._set_headers('text/html',len(msg),cookie)
2019-10-22 12:35:51 +00:00
self._write(msg)
2019-07-20 21:13:36 +00:00
else:
2019-09-25 09:22:10 +00:00
if self._fetchAuthenticated():
2019-11-09 21:39:04 +00:00
msg=json.dumps(getPerson,ensure_ascii=False).encode('utf-8')
self._set_headers('application/json',len(msg),None)
2019-10-22 12:35:51 +00:00
self._write(msg)
2019-09-25 09:22:10 +00:00
else:
self._404()
2019-07-01 14:30:48 +00:00
self.server.GETbusy=False
2019-06-28 18:55:29 +00:00
return
2019-11-16 13:25:44 +00:00
self._benchmarkGETtimings(GETstartTime,GETtimings,53)
2019-06-28 18:55:29 +00:00
# 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
2019-09-25 09:22:10 +00:00
if not self._fetchAuthenticated():
if self.server.debug:
print('WARN: Unauthenticated GET')
self._404()
2019-11-15 18:59:15 +00:00
return
2019-09-25 09:22:10 +00:00
2019-11-16 13:25:44 +00:00
self._benchmarkGETtimings(GETstartTime,GETtimings,54)
2019-06-28 18:55:29 +00:00
# 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-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-11-09 21:39:04 +00:00
msg=json.dumps(contentJson,ensure_ascii=False).encode('utf-8')
self._set_headers('application/json',len(msg),None)
2019-10-22 12:35:51 +00:00
self._write(msg)
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
2019-11-16 13:25:44 +00:00
self._benchmarkGETtimings(GETstartTime,GETtimings,55)
2019-06-28 18:55:29 +00:00
def do_HEAD(self):
2019-11-09 21:39:04 +00:00
self._set_headers('application/json',0,None)
2019-07-01 21:01:43 +00:00
2019-11-03 15:27:29 +00:00
def _receiveNewPostProcess(self,authorized: bool, \
2019-11-16 11:29:57 +00:00
postType: str,path: str,headers: {},
length: int,postBytes,boundary: str) -> int:
2019-11-01 21:23:59 +00:00
# Note: this needs to happen synchronously
2019-07-28 15:16:14 +00:00
# 0 = this is not a new post
# 1 = new post success
# -1 = new post failed
# 2 = new post canceled
2019-11-10 12:00:05 +00:00
if self.server.debug:
print('DEBUG: receiving POST')
2019-09-29 14:16:09 +00:00
if ' boundary=' in headers['Content-Type']:
2019-11-10 12:00:05 +00:00
if self.server.debug:
print('DEBUG: receiving POST headers '+headers['Content-Type'])
2019-09-29 13:30:24 +00:00
nickname=None
nicknameStr=path.split('/users/')[1]
if '/' in nicknameStr:
nickname=nicknameStr.split('/')[0]
else:
return -1
2019-09-29 14:16:09 +00:00
length = int(headers['Content-Length'])
2019-09-29 13:30:24 +00:00
if length>self.server.maxPostLength:
print('POST size too large')
return -1
2019-09-29 14:16:09 +00:00
boundary=headers['Content-Type'].split('boundary=')[1]
2019-09-29 13:30:24 +00:00
if ';' in boundary:
boundary=boundary.split(';')[0]
# Note: we don't use cgi here because it's due to be deprecated
# in Python 3.8/3.10
# Instead we use the multipart mime parser from the email module
2019-11-10 11:54:45 +00:00
if self.server.debug:
print('DEBUG: extracting media from POST')
2019-11-10 11:37:24 +00:00
mediaBytes,postBytes=extractMediaInFormPOST(postBytes,boundary,'attachpic')
2019-11-10 11:54:45 +00:00
if self.server.debug:
if mediaBytes:
print('DEBUG: media was found. '+str(len(mediaBytes))+' bytes')
else:
print('DEBUG: no media was found in POST')
2019-11-10 13:31:55 +00:00
# Note: a .temp extension is used here so that at no time is
# an image with metadata publicly exposed, even for a few mS
filenameBase= \
self.server.baseDir+'/accounts/'+ \
nickname+'@'+self.server.domain+'/upload.temp'
2019-11-10 11:37:24 +00:00
filename,attachmentMediaType= \
2019-11-10 13:31:55 +00:00
saveMediaInFormPOST(mediaBytes,self.server.debug,filenameBase)
2019-11-10 11:54:45 +00:00
if self.server.debug:
if filename:
print('DEBUG: POST media filename is '+filename)
else:
print('DEBUG: no media filename in POST')
2019-11-10 13:31:55 +00:00
if filename:
if filename.endswith('.png') or \
filename.endswith('.jpg') or \
filename.endswith('.gif'):
if self.server.debug:
print('DEBUG: POST media removing metadata')
postImageFilename=filename.replace('.temp','')
removeMetaData(filename,postImageFilename)
if os.path.isfile(postImageFilename):
print('POST media saved to '+postImageFilename)
else:
print('ERROR: POST media could not be saved to '+postImageFilename)
else:
if os.path.isfile(filename):
os.rename(filename,filename.replace('.temp',''))
2019-11-10 11:54:45 +00:00
fields=extractTextFieldsInPOST(postBytes,boundary,self.server.debug)
if self.server.debug:
if fields:
print('DEBUG: text field extracted from POST '+str(fields))
else:
print('WARN: no text fields could be extracted from POST')
2019-11-10 11:37:24 +00:00
# process the received text fields from the POST
2019-09-29 13:30:24 +00:00
if not fields.get('message') and not fields.get('imageDescription'):
return -1
if fields.get('submitPost'):
if fields['submitPost']!='Submit':
return -1
else:
return 2
2019-07-27 20:30:58 +00:00
2019-09-29 13:30:24 +00:00
if not fields.get('imageDescription'):
fields['imageDescription']=None
if not fields.get('subject'):
fields['subject']=None
if not fields.get('replyTo'):
fields['replyTo']=None
2019-10-10 13:12:13 +00:00
if not fields.get('eventDate'):
fields['eventDate']=None
if not fields.get('eventTime'):
fields['eventTime']=None
if not fields.get('location'):
fields['location']=None
2019-07-27 22:48:34 +00:00
2019-11-13 15:32:46 +00:00
# Store a file which contains the time in seconds
# since epoch when an attempt to post something was made.
# This is then used for active monthly users counts
lastUsedFilename= \
self.server.baseDir+'/accounts/'+ \
nickname+'@'+self.server.domain+'/.lastUsed'
try:
lastUsedFile=open(lastUsedFilename,'w')
if lastUsedFile:
lastUsedFile.write(str(int(time.time())))
lastUsedFile.close()
except:
pass
2019-09-29 13:30:24 +00:00
if postType=='newpost':
messageJson= \
createPublicPost(self.server.baseDir, \
nickname, \
self.server.domain,self.server.port, \
self.server.httpPrefix, \
fields['message'],False,False,False, \
2019-11-03 15:27:29 +00:00
filename,attachmentMediaType, \
fields['imageDescription'],True, \
fields['replyTo'],fields['replyTo'], \
fields['subject'], \
fields['eventDate'],fields['eventTime'], \
fields['location'])
2019-09-29 13:30:24 +00:00
if messageJson:
self.postToNickname=nickname
if self._postToOutbox(messageJson,__version__):
populateReplies(self.server.baseDir, \
self.server.httpPrefix, \
self.server.domainFull, \
messageJson, \
self.server.maxReplies, \
self.server.debug)
return 1
else:
2019-11-16 11:29:57 +00:00
return -1
elif postType=='newunlisted':
2019-09-29 13:30:24 +00:00
messageJson= \
createUnlistedPost(self.server.baseDir, \
nickname, \
self.server.domain,self.server.port, \
self.server.httpPrefix, \
fields['message'],False,False,False, \
2019-11-03 15:27:29 +00:00
filename,attachmentMediaType, \
fields['imageDescription'],True, \
fields['replyTo'], fields['replyTo'], \
fields['subject'], \
fields['eventDate'],fields['eventTime'], \
fields['location'])
2019-09-29 13:30:24 +00:00
if messageJson:
self.postToNickname=nickname
if self._postToOutbox(messageJson,__version__):
populateReplies(self.server.baseDir, \
self.server.httpPrefix, \
self.server.domain, \
messageJson, \
self.server.maxReplies, \
self.server.debug)
return 1
else:
return -1
2019-11-16 11:29:57 +00:00
elif postType=='newfollowers':
2019-09-29 13:30:24 +00:00
messageJson= \
createFollowersOnlyPost(self.server.baseDir, \
nickname, \
self.server.domain,self.server.port, \
2019-08-02 18:04:31 +00:00
self.server.httpPrefix, \
2019-09-29 13:30:24 +00:00
fields['message'],True,False,False, \
2019-11-03 15:27:29 +00:00
filename,attachmentMediaType, \
fields['imageDescription'],True, \
fields['replyTo'], fields['replyTo'], \
fields['subject'], \
fields['eventDate'],fields['eventTime'], \
fields['location'])
2019-09-29 13:30:24 +00:00
if messageJson:
self.postToNickname=nickname
if self._postToOutbox(messageJson,__version__):
populateReplies(self.server.baseDir, \
self.server.httpPrefix, \
self.server.domain, \
messageJson, \
self.server.maxReplies, \
self.server.debug)
return 1
else:
return -1
2019-11-16 11:29:57 +00:00
elif postType=='newdm':
2019-09-29 13:30:24 +00:00
messageJson=None
if '@' in fields['message']:
2019-07-27 22:48:34 +00:00
messageJson= \
2019-09-29 13:30:24 +00:00
createDirectMessagePost(self.server.baseDir, \
2019-07-27 22:48:34 +00:00
nickname, \
self.server.domain,self.server.port, \
self.server.httpPrefix, \
fields['message'],True,False,False, \
2019-09-29 13:30:24 +00:00
filename,attachmentMediaType, \
fields['imageDescription'],True, \
fields['replyTo'],fields['replyTo'], \
fields['subject'], \
2019-10-10 13:12:13 +00:00
self.server.debug, \
2019-11-03 15:27:29 +00:00
fields['eventDate'], \
fields['eventTime'], \
fields['location'])
2019-09-29 13:30:24 +00:00
if messageJson:
2019-07-28 13:30:19 +00:00
self.postToNickname=nickname
2019-09-29 13:30:24 +00:00
if self.server.debug:
print('DEBUG: new DM to '+str(messageJson['object']['to']))
if self._postToOutbox(messageJson,__version__):
populateReplies(self.server.baseDir, \
self.server.httpPrefix, \
self.server.domain, \
messageJson, \
self.server.maxReplies, \
self.server.debug)
return 1
else:
return -1
2019-11-16 11:29:57 +00:00
elif postType=='newreport':
2019-09-29 13:30:24 +00:00
if attachmentMediaType:
if attachmentMediaType!='image':
return -1
# So as to be sure that this only goes to moderators
# and not accounts being reported we disable any
# included fediverse addresses by replacing '@' with '-at-'
fields['message']=fields['message'].replace('@','-at-')
messageJson= \
createReportPost(self.server.baseDir, \
nickname, \
self.server.domain,self.server.port, \
self.server.httpPrefix, \
fields['message'],True,False,False, \
filename,attachmentMediaType, \
fields['imageDescription'],True, \
self.server.debug,fields['subject'])
if messageJson:
self.postToNickname=nickname
if self._postToOutbox(messageJson,__version__):
return 1
else:
return -1
2019-11-25 22:34:26 +00:00
elif postType=='newquestion':
if not fields.get('duration'):
return -1
if not fields.get('message'):
return -1
questionStr=fields['message']
qOptions=[]
2019-11-26 12:17:52 +00:00
for questionCtr in range(8):
2019-11-25 22:34:26 +00:00
if fields.get('questionOption'+str(questionCtr)):
qOptions.append(fields['questionOption'+str(questionCtr)])
if not qOptions:
return -1
messageJson= \
createQuestionPost(self.server.baseDir, \
nickname, \
self.server.domain,self.server.port, \
self.server.httpPrefix, \
fields['message'],qOptions, \
False,False,False, \
filename,attachmentMediaType, \
fields['imageDescription'],True, \
fields['subject'],int(fields['duration']))
if messageJson:
self.postToNickname=nickname
if self.server.debug:
print('DEBUG: new Question')
if self._postToOutbox(messageJson,__version__):
return 1
return -1
2019-11-16 11:29:57 +00:00
elif postType=='newshare':
2019-09-29 13:30:24 +00:00
if not fields.get('itemType'):
return -1
if not fields.get('category'):
return -1
if not fields.get('location'):
return -1
if not fields.get('duration'):
return -1
if attachmentMediaType:
if attachmentMediaType!='image':
return -1
2019-11-02 10:46:56 +00:00
durationStr=fields['duration']
if durationStr:
if ' ' not in durationStr:
durationStr=durationStr+' days'
2019-09-29 13:30:24 +00:00
addShare(self.server.baseDir, \
self.server.httpPrefix, \
nickname, \
self.server.domain,self.server.port, \
fields['subject'], \
fields['message'], \
filename, \
fields['itemType'], \
fields['category'], \
fields['location'], \
2019-11-02 10:46:56 +00:00
durationStr,
2019-09-29 13:30:24 +00:00
self.server.debug)
if filename:
if os.path.isfile(filename):
os.remove(filename)
self.postToNickname=nickname
return 1
return -1
2019-09-29 13:43:39 +00:00
def _receiveNewPost(self,authorized: bool,postType: str,path: str) -> int:
2019-09-29 13:30:24 +00:00
"""A new post has been created
This creates a thread to send the new post
"""
pageNumber=1
2019-11-12 13:53:14 +00:00
if not authorized:
print('Not receiving new post for '+path+' because not authorized')
return None
if '/users/' not in path:
print('Not receiving new post for '+path+' because /users/ not in path')
return None
if '?'+postType+'?' not in path:
print('Not receiving new post for '+path+' because ?'+postType+'? not in path')
2019-09-29 13:58:05 +00:00
return None
2019-09-29 14:06:53 +00:00
print('New post begins: '+postType+' '+path)
2019-09-29 13:58:05 +00:00
if '?page=' in path:
pageNumberStr=path.split('?page=')[1]
if '?' in pageNumberStr:
pageNumberStr=pageNumberStr.split('?')[0]
if pageNumberStr.isdigit():
pageNumber=int(pageNumberStr)
path=path.split('?page=')[0]
newPostThreadName=self.postToNickname
if not newPostThreadName:
newPostThreadName='*'
2019-09-29 13:30:24 +00:00
2019-09-29 13:58:05 +00:00
if self.server.newPostThread.get(newPostThreadName):
print('Waiting for previous new post thread to end')
waitCtr=0
while self.server.newPostThread[newPostThreadName].isAlive() and waitCtr<8:
time.sleep(1)
waitCtr+=1
if waitCtr>=8:
self.server.newPostThread[newPostThreadName].kill()
# make a copy of self.headers
headers={}
for dictEntryName,headerLine in self.headers.items():
headers[dictEntryName]=headerLine
print('New post headers: '+str(headers))
2019-11-16 11:29:57 +00:00
length = int(headers['Content-Length'])
if length>self.server.maxPostLength:
print('POST size too large')
2019-11-16 12:49:34 +00:00
return None
2019-11-16 11:29:57 +00:00
2019-11-22 18:37:07 +00:00
if not headers.get('Content-Type'):
if headers.get('Content-type'):
headers['Content-Type']=headers['Content-type']
elif headers.get('content-type'):
headers['Content-Type']=headers['content-type']
if headers.get('Content-Type'):
if ' boundary=' in headers['Content-Type']:
boundary=headers['Content-Type'].split('boundary=')[1]
if ';' in boundary:
boundary=boundary.split(';')[0]
postBytes=self.rfile.read(length)
2019-11-16 11:29:57 +00:00
2019-11-22 18:37:07 +00:00
# second length check from the bytes received
# since Content-Length could be untruthful
length=len(postBytes)
if length>self.server.maxPostLength:
print('POST size too large')
return None
2019-11-16 11:29:57 +00:00
2019-11-22 18:37:07 +00:00
# Note sending new posts needs to be synchronous, otherwise any attachments
# can get mangled if other events happen during their decoding
print('Creating new post: '+newPostThreadName)
self._receiveNewPostProcess(authorized,postType,path,headers,length,postBytes,boundary)
2019-09-29 13:58:05 +00:00
return pageNumber
2019-07-27 20:30:58 +00:00
2019-06-28 18:55:29 +00:00
def do_POST(self):
2019-11-15 18:59:15 +00:00
POSTstartTime=time.time()
2019-11-16 11:03:02 +00:00
POSTtimings=[]
2019-11-15 18:59:15 +00:00
2019-08-20 11:30:41 +00:00
if not self.server.session:
2019-11-15 19:44:20 +00:00
print('Starting new session from POST')
2019-08-20 11:30:41 +00:00
self.server.session= \
2019-11-13 10:50:16 +00:00
createSession(self.server.useTor)
2019-08-20 11:30:41 +00:00
2019-07-04 17:56:25 +00:00
if self.server.debug:
2019-11-12 13:59:16 +00:00
print('DEBUG: POST to '+self.server.baseDir+ \
2019-07-06 17:00:22 +00:00
' 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==0:
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-29 16:13:48 +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
if not self.path.endswith('confirm'):
2019-11-03 15:27:29 +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
2019-11-15 21:43:20 +00:00
if self.path=='/inbox':
if not self.server.enableSharedInbox:
self._503()
return
2019-07-29 16:13:48 +00:00
cookie=None
if self.headers.get('Cookie'):
cookie=self.headers['Cookie']
2019-07-27 20:30:58 +00:00
# check authorization
authorized = self._isAuthorized()
if authorized:
if self.server.debug:
print('POST Authorization granted')
else:
if self.server.debug:
print('POST Not authorized')
2019-07-29 16:13:48 +00:00
print(str(self.headers))
2019-07-27 20:30:58 +00:00
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
2019-11-16 11:03:02 +00:00
self._benchmarkPOSTtimings(POSTstartTime,POSTtimings,1)
2019-11-16 10:40:35 +00:00
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')
2019-11-03 15:27:29 +00:00
loginNickname,loginPassword,register= \
htmlGetLoginCredentials(loginParams,self.server.lastLoginTime)
2019-07-24 22:38:42 +00:00
if loginNickname:
self.server.lastLoginTime=int(time.time())
2019-08-08 13:38:33 +00:00
if register:
2019-11-03 15:27:29 +00:00
if not registerAccount(self.server.baseDir, \
self.server.httpPrefix, \
self.server.domain, \
self.server.port, \
2019-08-08 13:38:33 +00:00
loginNickname,loginPassword):
self.server.POSTbusy=False
2019-11-26 15:22:45 +00:00
self._redirect_headers(self.server.httpPrefix+'://'+self.server.domainFull+'/login',cookie)
2019-08-08 13:38:33 +00:00
return
2019-07-24 22:38:42 +00:00
authHeader=createBasicAuthHeader(loginNickname,loginPassword)
2019-11-03 15:27:29 +00:00
if not authorizeBasic(self.server.baseDir,'/users/'+ \
loginNickname+'/outbox',authHeader,False):
2019-07-25 10:56:24 +00:00
print('Login failed: '+loginNickname)
self._clearLoginDetails(loginNickname)
2019-07-24 22:38:42 +00:00
self.server.POSTbusy=False
return
2019-07-25 10:56:24 +00:00
else:
2019-08-13 09:24:55 +00:00
if isSuspended(self.server.baseDir,loginNickname):
2019-08-15 13:20:09 +00:00
msg=htmlSuspended(self.server.baseDir).encode('utf-8')
2019-11-09 21:39:04 +00:00
self._login_headers('text/html',len(msg))
2019-10-22 12:35:51 +00:00
self._write(msg)
2019-08-13 09:24:55 +00:00
self.server.POSTbusy=False
return
2019-07-25 10:56:24 +00:00
# login success - redirect with authorization
print('Login success: '+loginNickname)
self.send_response(303)
# re-activate account if needed
2019-11-05 12:33:37 +00:00
activateAccount(self.server.baseDir,loginNickname,self.server.domain)
2019-07-25 14:30:12 +00:00
# This produces a deterministic token based on nick+password+salt
2019-11-03 15:27:29 +00:00
saltFilename= \
self.server.baseDir+'/accounts/'+ \
loginNickname+'@'+self.server.domain+'/.salt'
2019-10-25 13:18:29 +00:00
salt=createPassword(32)
2019-10-25 12:52:07 +00:00
if os.path.isfile(saltFilename):
2019-10-25 16:48:53 +00:00
try:
with open(saltFilename, 'r') as fp:
salt = fp.read()
except Exception as e:
2019-11-03 15:27:29 +00:00
print('WARN: Unable to read salt for '+ \
loginNickname+' '+str(e))
2019-10-25 12:52:07 +00:00
else:
2019-10-25 16:48:53 +00:00
try:
with open(saltFilename, 'w') as fp:
fp.write(salt)
except Exception as e:
2019-11-03 15:27:29 +00:00
print('WARN: Unable to save salt for '+ \
loginNickname+' '+str(e))
2019-10-25 16:48:53 +00:00
token=sha256((loginNickname+loginPassword+salt).encode('utf-8')).hexdigest()
self.server.tokens[loginNickname]=token
2019-11-03 15:27:29 +00:00
tokenFilename= \
self.server.baseDir+'/accounts/'+ \
loginNickname+'@'+self.server.domain+'/.token'
2019-10-25 16:48:53 +00:00
try:
with open(tokenFilename, 'w') as fp:
fp.write(token)
except Exception as e:
print('WARN: Unable to save token for '+loginNickname+' '+str(e))
self.server.tokensLookup[self.server.tokens[loginNickname]]=loginNickname
2019-11-03 15:27:29 +00:00
self.send_header('Set-Cookie', \
'epicyon='+self.server.tokens[loginNickname]+'; SameSite=Strict')
self.send_header('Location', \
2019-11-26 15:30:13 +00:00
self.server.httpPrefix+'://'+ \
self.server.domainFull+ \
2019-11-03 15:27:29 +00:00
'/users/'+loginNickname+'/inbox')
self.send_header('Content-Length', '0')
self.send_header('X-Robots-Tag','noindex')
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
2019-11-15 18:59:15 +00:00
return
2019-07-24 22:38:42 +00:00
2019-11-16 11:03:02 +00:00
self._benchmarkPOSTtimings(POSTstartTime,POSTtimings,2)
2019-11-16 10:40:35 +00:00
2019-08-02 09:52:12 +00:00
# update of profile/avatar from web interface
if authorized and self.path.endswith('/profiledata'):
2019-11-26 15:22:45 +00:00
actorStr= \
self.server.httpPrefix+'://'+self.server.domainFull+ \
self.path.replace('/profiledata','').replace('/editprofile','')
2019-08-02 09:52:12 +00:00
if ' boundary=' in self.headers['Content-type']:
boundary=self.headers['Content-type'].split('boundary=')[1]
if ';' in boundary:
boundary=boundary.split(';')[0]
nickname=getNicknameFromActor(actorStr)
if not nickname:
2019-09-02 09:43:43 +00:00
print('WARN: nickname not found in '+actorStr)
2019-08-02 09:52:12 +00:00
self._redirect_headers(actorStr,cookie)
self.server.POSTbusy=False
return
length = int(self.headers['Content-length'])
2019-08-26 21:51:10 +00:00
if length>self.server.maxPostLength:
2019-08-26 19:43:52 +00:00
print('Maximum profile data length exceeded '+str(length))
self._redirect_headers(actorStr,cookie)
self.server.POSTbusy=False
return
2019-08-26 21:15:33 +00:00
2019-11-10 13:31:55 +00:00
# read the bytes of the http form POST
2019-08-26 21:22:23 +00:00
postBytes=self.rfile.read(length)
2019-08-26 21:19:01 +00:00
2019-11-10 13:31:55 +00:00
# extract each image type
actorChanged=True
profileMediaTypes=['avatar','image','banner','instanceLogo']
2019-11-14 13:30:54 +00:00
profileMediaTypesUploaded={}
2019-11-10 13:31:55 +00:00
for mType in profileMediaTypes:
if self.server.debug:
print('DEBUG: profile update extracting '+mType+' image from POST')
mediaBytes,postBytes=extractMediaInFormPOST(postBytes,boundary,mType)
if mediaBytes:
if self.server.debug:
print('DEBUG: profile update '+mType+' image was found. '+str(len(mediaBytes))+' bytes')
else:
if self.server.debug:
print('DEBUG: profile update, no '+mType+' image was found in POST')
2019-08-02 09:52:12 +00:00
continue
2019-11-10 13:31:55 +00:00
# Note: a .temp extension is used here so that at no time is
# an image with metadata publicly exposed, even for a few mS
if mType!='instanceLogo':
filenameBase= \
self.server.baseDir+'/accounts/'+ \
nickname+'@'+self.server.domain+'/'+mType+'.temp'
else:
filenameBase= \
2019-11-13 21:24:34 +00:00
self.server.baseDir+'/accounts/login.temp'
2019-11-10 13:31:55 +00:00
filename,attachmentMediaType= \
saveMediaInFormPOST(mediaBytes,self.server.debug,filenameBase)
if filename:
if self.server.debug:
print('DEBUG: profile update POST '+mType+' media filename is '+filename)
else:
if self.server.debug:
print('DEBUG: profile update, no '+mType+' media filename in POST')
continue
if self.server.debug:
print('DEBUG: POST '+mType+' media removing metadata')
postImageFilename=filename.replace('.temp','')
removeMetaData(filename,postImageFilename)
if os.path.isfile(postImageFilename):
print('profile update POST '+mType+' image saved to '+postImageFilename)
2019-11-14 13:30:54 +00:00
if mType!='instanceLogo':
lastPartOfImageFilename=postImageFilename.split('/')[-1]
profileMediaTypesUploaded[mType]=lastPartOfImageFilename
actorChanged=True
2019-11-10 13:31:55 +00:00
else:
2019-11-12 10:59:17 +00:00
print('ERROR: profile update POST '+mType+' image could not be saved to '+postImageFilename)
2019-11-10 13:31:55 +00:00
fields=extractTextFieldsInPOST(postBytes,boundary,self.server.debug)
if self.server.debug:
if fields:
print('DEBUG: profile update text field extracted from POST '+str(fields))
else:
print('WARN: profile update, no text fields could be extracted from POST')
2019-11-03 15:27:29 +00:00
actorFilename= \
self.server.baseDir+'/accounts/'+ \
nickname+'@'+self.server.domain+'.json'
2019-08-02 09:52:12 +00:00
if os.path.isfile(actorFilename):
2019-10-22 11:55:06 +00:00
actorJson=loadJson(actorFilename)
2019-11-14 13:30:54 +00:00
if actorJson:
# update the avatar/image url file extension
for mType,lastPartOfImageFilename in profileMediaTypesUploaded.items():
if mType=='avatar':
lastPartOfUrl=actorJson['icon']['url'].split('/')[-1]
actorJson['icon']['url']= \
actorJson['icon']['url'].replace('/'+lastPartOfUrl, \
'/'+lastPartOfImageFilename)
elif mType=='image':
lastPartOfUrl=actorJson['image']['url'].split('/')[-1]
actorJson['image']['url']= \
actorJson['image']['url'].replace('/'+lastPartOfUrl, \
'/'+lastPartOfImageFilename)
2019-08-09 08:46:38 +00:00
skillCtr=1
newSkills={}
while skillCtr<10:
skillName=fields.get('skillName'+str(skillCtr))
if not skillName:
skillCtr+=1
continue
skillValue=fields.get('skillValue'+str(skillCtr))
if not skillValue:
skillCtr+=1
continue
if not actorJson['skills'].get(skillName):
actorChanged=True
else:
if actorJson['skills'][skillName]!=int(skillValue):
actorChanged=True
newSkills[skillName]=int(skillValue)
skillCtr+=1
if len(actorJson['skills'].items())!=len(newSkills.items()):
actorChanged=True
actorJson['skills']=newSkills
2019-11-12 10:59:17 +00:00
if fields.get('password'):
if fields.get('passwordconfirm'):
if actorJson['password']==fields['passwordconfirm']:
if len(actorJson['password'])>2:
# set password
storeBasicCredentials(self.server.baseDir,nickname,actorJson['password'])
if fields.get('displayNickname'):
if fields['displayNickname']!=actorJson['name']:
actorJson['name']=fields['displayNickname']
2019-08-02 09:52:12 +00:00
actorChanged=True
2019-11-23 14:13:25 +00:00
if fields.get('themeDropdown'):
setTheme(self.server.baseDir,fields['themeDropdown'])
2019-11-24 18:06:54 +00:00
#self.server.iconsCache={}
2019-11-06 22:56:55 +00:00
if fields.get('donateUrl'):
currentDonateUrl=getDonationUrl(actorJson)
if fields['donateUrl']!=currentDonateUrl:
setDonationUrl(actorJson,fields['donateUrl'])
actorChanged=True
if fields.get('instanceTitle'):
currInstanceTitle=getConfigParam(self.server.baseDir,'instanceTitle')
if fields['instanceTitle']!=currInstanceTitle:
setConfigParam(self.server.baseDir,'instanceTitle',fields['instanceTitle'])
if fields.get('instanceDescriptionShort'):
currInstanceDescriptionShort=getConfigParam(self.server.baseDir,'instanceDescriptionShort')
if fields['instanceDescriptionShort']!=currInstanceDescriptionShort:
setConfigParam(self.server.baseDir,'instanceDescriptionShort',fields['instanceDescriptionShort'])
if fields.get('instanceDescription'):
currInstanceDescription=getConfigParam(self.server.baseDir,'instanceDescription')
if fields['instanceDescription']!=currInstanceDescription:
setConfigParam(self.server.baseDir,'instanceDescription',fields['instanceDescription'])
2019-08-02 09:52:12 +00:00
if fields.get('bio'):
if fields['bio']!=actorJson['summary']:
2019-08-09 16:18:00 +00:00
actorTags={}
2019-08-02 09:52:12 +00:00
actorJson['summary']= \
2019-08-09 09:09:21 +00:00
addHtmlTags(self.server.baseDir, \
2019-08-02 09:52:12 +00:00
self.server.httpPrefix, \
nickname, \
2019-08-09 16:18:00 +00:00
self.server.domainFull, \
fields['bio'],[],actorTags)
if actorTags:
actorJson['tag']=[]
for tagName,tag in actorTags.items():
actorJson['tag'].append(tag)
2019-08-02 09:52:12 +00:00
actorChanged=True
2019-08-12 21:20:47 +00:00
if fields.get('moderators'):
adminNickname=getConfigParam(self.server.baseDir,'admin')
if self.path.startswith('/users/'+adminNickname+'/'):
moderatorsFile=self.server.baseDir+'/accounts/moderators.txt'
clearModeratorStatus(self.server.baseDir)
if ',' in fields['moderators']:
# if the list was given as comma separated
modFile=open(moderatorsFile,"w+")
for modNick in fields['moderators'].split(','):
modNick=modNick.strip()
2019-11-03 15:27:29 +00:00
if os.path.isdir(self.server.baseDir+ \
'/accounts/'+modNick+ \
'@'+self.server.domain):
2019-08-12 21:20:47 +00:00
modFile.write(modNick+'\n')
modFile.close()
for modNick in fields['moderators'].split(','):
modNick=modNick.strip()
2019-11-03 15:27:29 +00:00
if os.path.isdir(self.server.baseDir+ \
'/accounts/'+modNick+ \
'@'+self.server.domain):
setRole(self.server.baseDir, \
modNick,self.server.domain, \
'instance','moderator')
2019-08-12 21:20:47 +00:00
else:
# nicknames on separate lines
modFile=open(moderatorsFile,"w+")
for modNick in fields['moderators'].split('\n'):
modNick=modNick.strip()
2019-11-03 15:27:29 +00:00
if os.path.isdir(self.server.baseDir+ \
'/accounts/'+modNick+ \
'@'+self.server.domain):
2019-08-12 21:20:47 +00:00
modFile.write(modNick+'\n')
modFile.close()
for modNick in fields['moderators'].split('\n'):
modNick=modNick.strip()
2019-11-03 15:27:29 +00:00
if os.path.isdir(self.server.baseDir+ \
'/accounts/'+modNick+ \
'@'+self.server.domain):
setRole(self.server.baseDir, \
modNick,self.server.domain, \
'instance','moderator')
2019-08-12 21:20:47 +00:00
2019-08-07 20:13:44 +00:00
approveFollowers=False
2019-08-02 09:52:12 +00:00
if fields.get('approveFollowers'):
2019-08-07 20:13:44 +00:00
if fields['approveFollowers']=='on':
2019-08-02 09:52:12 +00:00
approveFollowers=True
2019-08-07 20:13:44 +00:00
if approveFollowers!=actorJson['manuallyApprovesFollowers']:
actorJson['manuallyApprovesFollowers']=approveFollowers
actorChanged=True
# only receive DMs from accounts you follow
2019-11-03 15:27:29 +00:00
followDMsFilename= \
self.server.baseDir+'/accounts/'+ \
nickname+'@'+self.server.domain+'/.followDMs'
followDMsActive=False
if fields.get('followDMs'):
if fields['followDMs']=='on':
followDMsActive=True
with open(followDMsFilename, "w") as followDMsFile:
followDMsFile.write('\n')
if not followDMsActive:
if os.path.isfile(followDMsFilename):
os.remove(followDMsFilename)
# this account is a bot
2019-08-07 20:13:44 +00:00
if fields.get('isBot'):
if fields['isBot']=='on':
if actorJson['type']!='Service':
actorJson['type']='Service'
actorChanged=True
else:
# this account is a group
2019-10-04 09:23:38 +00:00
if fields.get('isGroup'):
if fields['isGroup']=='on':
if actorJson['type']!='Group':
actorJson['type']='Group'
actorChanged=True
else:
# this account is a person (default)
2019-10-04 09:23:38 +00:00
if actorJson['type']!='Person':
actorJson['type']='Person'
actorChanged=True
2019-08-02 11:43:14 +00:00
# save filtered words list
2019-11-03 15:27:29 +00:00
filterFilename= \
self.server.baseDir+'/accounts/'+ \
nickname+'@'+self.server.domain+'/filters.txt'
2019-08-02 11:43:14 +00:00
if fields.get('filteredWords'):
with open(filterFilename, "w") as filterfile:
filterfile.write(fields['filteredWords'])
else:
if os.path.isfile(filterFilename):
os.remove(filterFilename)
# save blocked accounts list
2019-11-03 15:27:29 +00:00
blockedFilename= \
self.server.baseDir+'/accounts/'+ \
nickname+'@'+self.server.domain+'/blocking.txt'
2019-08-02 11:43:14 +00:00
if fields.get('blocked'):
with open(blockedFilename, "w") as blockedfile:
blockedfile.write(fields['blocked'])
else:
if os.path.isfile(blockedFilename):
os.remove(blockedFilename)
# save allowed instances list
2019-11-03 15:27:29 +00:00
allowedInstancesFilename= \
self.server.baseDir+'/accounts/'+ \
nickname+'@'+self.server.domain+'/allowedinstances.txt'
if fields.get('allowedInstances'):
with open(allowedInstancesFilename, "w") as allowedInstancesFile:
allowedInstancesFile.write(fields['allowedInstances'])
else:
if os.path.isfile(allowedInstancesFilename):
os.remove(allowedInstancesFilename)
2019-08-02 11:43:14 +00:00
# save actor json file within accounts
2019-08-02 09:52:12 +00:00
if actorChanged:
2019-10-22 11:55:06 +00:00
saveJson(actorJson,actorFilename)
# also copy to the actors cache and personCache in memory
2019-11-03 15:27:29 +00:00
storePersonInCache(self.server.baseDir, \
actorJson['id'],actorJson, \
self.server.personCache)
actorCacheFilename= \
self.server.baseDir+'/cache/actors/'+ \
actorJson['id'].replace('/','#')+'.json'
2019-10-22 11:55:06 +00:00
saveJson(actorJson,actorCacheFilename)
2019-08-22 16:15:02 +00:00
# send actor update to followers
updateActorJson={
'type': 'Update',
'actor': actorJson['id'],
2019-11-07 12:56:00 +00:00
'to': [actorJson['id']+'/followers'],
'cc': [],
2019-08-22 16:15:02 +00:00
'object': actorJson
}
self.postToNickname=nickname
2019-11-06 18:07:00 +00:00
self._postToOutbox(updateActorJson,__version__)
if fields.get('deactivateThisAccount'):
if fields['deactivateThisAccount']=='on':
deactivateAccount(self.server.baseDir,nickname,self.server.domain)
self._clearLoginDetails(nickname)
self.server.POSTbusy=False
return
2019-08-02 09:52:12 +00:00
self._redirect_headers(actorStr,cookie)
self.server.POSTbusy=False
return
2019-11-16 11:03:02 +00:00
self._benchmarkPOSTtimings(POSTstartTime,POSTtimings,3)
2019-11-16 10:40:35 +00:00
2019-08-13 10:48:16 +00:00
# moderator action buttons
if authorized and '/users/' in self.path and \
2019-08-13 11:59:38 +00:00
self.path.endswith('/moderationaction'):
2019-11-26 15:22:45 +00:00
actorStr= \
self.server.httpPrefix+'://'+self.server.domainFull+ \
self.path.replace('/moderationaction','')
2019-08-13 10:48:16 +00:00
length = int(self.headers['Content-length'])
moderationParams=self.rfile.read(length).decode('utf-8')
print('moderationParams: '+moderationParams)
2019-08-13 11:59:38 +00:00
if '&' in moderationParams:
moderationText=None
moderationButton=None
for moderationStr in moderationParams.split('&'):
2019-08-13 13:58:48 +00:00
print('moderationStr: '+moderationStr)
if moderationStr.startswith('moderationAction'):
2019-08-13 11:59:38 +00:00
if '=' in moderationStr:
2019-11-03 15:27:29 +00:00
moderationText= \
moderationStr.split('=')[1].strip()
moderationText= \
moderationText.replace('+',' ').replace('%40','@').replace('%3A',':').replace('%23','#').strip()
2019-08-13 17:25:39 +00:00
elif moderationStr.startswith('submitInfo'):
2019-09-07 09:21:10 +00:00
msg=htmlModerationInfo(self.server.translate, \
self.server.baseDir).encode('utf-8')
2019-11-09 21:39:04 +00:00
self._login_headers('text/html',len(msg))
2019-10-22 12:35:51 +00:00
self._write(msg)
2019-08-13 17:25:39 +00:00
self.server.POSTbusy=False
return
2019-08-13 11:59:38 +00:00
elif moderationStr.startswith('submitBlock'):
moderationButton='block'
elif moderationStr.startswith('submitUnblock'):
moderationButton='unblock'
elif moderationStr.startswith('submitSuspend'):
moderationButton='suspend'
elif moderationStr.startswith('submitUnsuspend'):
moderationButton='unsuspend'
elif moderationStr.startswith('submitRemove'):
moderationButton='remove'
if moderationButton and moderationText:
2019-08-13 13:58:48 +00:00
if self.server.debug:
print('moderationButton: '+moderationButton)
print('moderationText: '+moderationText)
2019-08-13 11:59:38 +00:00
nickname=moderationText
if nickname.startswith('http') or \
nickname.startswith('dat'):
nickname=getNicknameFromActor(nickname)
if '@' in nickname:
nickname=nickname.split('@')[0]
if moderationButton=='suspend':
2019-11-03 15:27:29 +00:00
suspendAccount(self.server.baseDir,nickname, \
self.server.domain)
2019-08-13 11:59:38 +00:00
if moderationButton=='unsuspend':
unsuspendAccount(self.server.baseDir,nickname)
if moderationButton=='block':
2019-08-13 16:39:43 +00:00
fullBlockDomain=None
if moderationText.startswith('http') or \
moderationText.startswith('dat'):
2019-11-03 15:27:29 +00:00
blockDomain,blockPort= \
getDomainFromActor(moderationText)
2019-08-13 16:39:43 +00:00
fullBlockDomain=blockDomain
if blockPort:
if blockPort!=80 and blockPort!=443:
if ':' not in blockDomain:
2019-11-03 15:27:29 +00:00
fullBlockDomain= \
blockDomain+':'+str(blockPort)
2019-08-13 16:39:43 +00:00
if '@' in moderationText:
fullBlockDomain=moderationText.split('@')[1]
2019-08-14 10:32:15 +00:00
if fullBlockDomain or nickname.startswith('#'):
2019-08-13 16:39:43 +00:00
addGlobalBlock(self.server.baseDir, \
nickname,fullBlockDomain)
if moderationButton=='unblock':
2019-08-13 16:39:43 +00:00
fullBlockDomain=None
if moderationText.startswith('http') or \
moderationText.startswith('dat'):
2019-11-03 15:27:29 +00:00
blockDomain,blockPort= \
getDomainFromActor(moderationText)
2019-08-13 16:39:43 +00:00
fullBlockDomain=blockDomain
if blockPort:
if blockPort!=80 and blockPort!=443:
if ':' not in blockDomain:
2019-11-03 15:27:29 +00:00
fullBlockDomain= \
blockDomain+':'+str(blockPort)
2019-08-13 16:39:43 +00:00
if '@' in moderationText:
fullBlockDomain=moderationText.split('@')[1]
2019-08-14 10:32:15 +00:00
if fullBlockDomain or nickname.startswith('#'):
2019-08-13 16:39:43 +00:00
removeGlobalBlock(self.server.baseDir, \
nickname,fullBlockDomain)
2019-08-13 13:58:48 +00:00
if moderationButton=='remove':
if '/statuses/' not in moderationText:
removeAccount(self.server.baseDir, \
nickname, \
self.server.domain, \
self.server.port)
else:
# remove a post or thread
2019-08-13 13:58:48 +00:00
postFilename= \
locatePost(self.server.baseDir, \
nickname,self.server.domain, \
moderationText)
if postFilename:
if canRemovePost(self.server.baseDir, \
nickname, \
self.server.domain, \
self.server.port, \
moderationText):
deletePost(self.server.baseDir, \
self.server.httpPrefix, \
nickname,self.server.omain, \
postFilename, \
self.server.debug)
2019-08-13 10:48:16 +00:00
self._redirect_headers(actorStr+'/moderation',cookie)
self.server.POSTbusy=False
return
2019-11-16 11:03:02 +00:00
self._benchmarkPOSTtimings(POSTstartTime,POSTtimings,4)
2019-11-16 10:40:35 +00:00
2019-08-19 20:01:29 +00:00
searchForEmoji=False
2019-08-19 20:11:38 +00:00
if self.path.endswith('/searchhandleemoji'):
2019-08-19 20:01:29 +00:00
searchForEmoji=True
self.path=self.path.replace('/searchhandleemoji','/searchhandle')
2019-08-19 20:11:38 +00:00
if self.server.debug:
print('DEBUG: searching for emoji')
print('authorized: '+str(authorized))
2019-08-19 20:01:29 +00:00
2019-11-16 11:03:02 +00:00
self._benchmarkPOSTtimings(POSTstartTime,POSTtimings,5)
2019-11-16 10:40:35 +00:00
2019-09-13 19:01:33 +00:00
# a vote/question/poll is posted
if authorized and \
(self.path.endswith('/question') or '/question?page=' in self.path):
pageNumber=1
if '?page=' in self.path:
pageNumberStr=self.path.split('?page=')[1]
if pageNumberStr.isdigit():
pageNumber=int(pageNumberStr)
self.path=self.path.split('?page=')[0]
# the actor who votes
2019-11-03 15:27:29 +00:00
actor= \
self.server.httpPrefix+'://'+ \
self.server.domainFull+self.path.replace('/question','')
2019-09-13 19:01:33 +00:00
nickname=getNicknameFromActor(actor)
if not nickname:
2019-11-03 15:27:29 +00:00
self._redirect_headers(actor+'/inbox?page='+ \
str(pageNumber),cookie)
2019-09-13 19:01:33 +00:00
self.server.POSTbusy=False
return
# get the parameters
length = int(self.headers['Content-length'])
questionParams=self.rfile.read(length).decode('utf-8')
2019-11-03 15:27:29 +00:00
questionParams= \
questionParams.replace('+',' ').replace('%40','@').replace('%3A',':').replace('%23','#').strip()
2019-09-13 19:01:33 +00:00
# post being voted on
messageId=None
if 'messageId=' in questionParams:
2019-10-29 14:58:48 +00:00
messageId=questionParams.split('messageId=')[1]
2019-09-13 19:01:33 +00:00
if '&' in messageId:
messageId=messageId.split('&')[0]
answer=None
if 'answer=' in questionParams:
2019-10-29 14:58:48 +00:00
answer=questionParams.split('answer=')[1]
2019-09-13 19:01:33 +00:00
if '&' in answer:
answer=answer.split('&')[0]
2019-11-25 13:34:44 +00:00
self._sendReplyToQuestion(nickname,messageId,answer)
2019-09-13 19:01:33 +00:00
self._redirect_headers(actor+'/inbox?page='+str(pageNumber),cookie)
self.server.POSTbusy=False
return
2019-11-16 11:03:02 +00:00
self._benchmarkPOSTtimings(POSTstartTime,POSTtimings,6)
2019-11-16 10:40:35 +00:00
2019-08-14 09:45:51 +00:00
# a search was made
2019-08-19 20:13:21 +00:00
if (authorized or searchForEmoji) and \
2019-11-03 15:27:29 +00:00
(self.path.endswith('/searchhandle') or \
'/searchhandle?page=' in self.path):
2019-08-14 09:45:51 +00:00
# get the page number
pageNumber=1
if '/searchhandle?page=' in self.path:
pageNumberStr=self.path.split('/searchhandle?page=')[1]
if pageNumberStr.isdigit():
pageNumber=int(pageNumberStr)
self.path=self.path.split('?page=')[0]
2019-11-03 15:27:29 +00:00
actorStr= \
self.server.httpPrefix+'://'+ \
self.server.domainFull+ \
self.path.replace('/searchhandle','')
2019-07-30 22:34:04 +00:00
length = int(self.headers['Content-length'])
searchParams=self.rfile.read(length).decode('utf-8')
if 'searchtext=' in searchParams:
searchStr=searchParams.split('searchtext=')[1]
if '&' in searchStr:
searchStr=searchStr.split('&')[0]
2019-11-03 15:27:29 +00:00
searchStr= \
searchStr.replace('%20',' ').replace('%40','@').replace('%3A',':').replace('%2F','/').replace('%23','#').strip()
2019-08-21 16:42:00 +00:00
if self.server.debug:
print('searchStr: '+searchStr)
2019-08-19 20:01:29 +00:00
if searchForEmoji:
searchStr=':'+searchStr+':'
2019-08-13 21:32:18 +00:00
if searchStr.startswith('#'):
2019-08-10 10:54:52 +00:00
# hashtag search
hashtagStr= \
htmlHashtagSearch(self.server.recentPostsCache, \
self.server.maxRecentPosts, \
self.server.translate, \
2019-09-07 08:57:52 +00:00
self.server.baseDir,searchStr[1:],1, \
2019-08-10 10:54:52 +00:00
maxPostsInFeed,self.server.session, \
self.server.cachedWebfingers, \
2019-08-14 20:12:27 +00:00
self.server.personCache, \
self.server.httpPrefix, \
self.server.projectVersion)
2019-08-10 10:54:52 +00:00
if hashtagStr:
2019-08-15 13:20:09 +00:00
msg=hashtagStr.encode('utf-8')
2019-11-09 21:39:04 +00:00
self._login_headers('text/html',len(msg))
2019-10-22 12:35:51 +00:00
self._write(msg)
2019-08-10 10:54:52 +00:00
self.server.POSTbusy=False
return
2019-08-27 22:50:40 +00:00
elif searchStr.startswith('*'):
# skill search
searchStr=searchStr.replace('*','').strip()
skillStr= \
2019-09-07 10:08:18 +00:00
htmlSkillsSearch(self.server.translate, \
self.server.baseDir,searchStr, \
self.server.instanceOnlySkillsSearch, \
2019-08-28 10:11:06 +00:00
64)
2019-08-27 22:50:40 +00:00
if skillStr:
msg=skillStr.encode('utf-8')
2019-11-09 21:39:04 +00:00
self._login_headers('text/html',len(msg))
2019-10-22 12:35:51 +00:00
self._write(msg)
2019-08-27 22:50:40 +00:00
self.server.POSTbusy=False
return
2019-08-13 21:32:18 +00:00
elif '@' in searchStr:
2019-08-10 10:54:52 +00:00
# profile search
2019-07-30 22:34:04 +00:00
nickname=getNicknameFromActor(self.path)
if not self.server.session:
self.server.session= \
2019-11-13 10:50:16 +00:00
createSession(self.server.useTor)
2019-07-30 22:34:04 +00:00
profileStr= \
htmlProfileAfterSearch(self.server.recentPostsCache, \
self.server.maxRecentPosts, \
self.server.translate, \
2019-09-07 08:57:52 +00:00
self.server.baseDir, \
2019-07-30 22:34:04 +00:00
self.path.replace('/searchhandle',''), \
self.server.httpPrefix, \
nickname, \
self.server.domain,self.server.port, \
searchStr, \
self.server.session, \
self.server.cachedWebfingers, \
self.server.personCache, \
2019-08-14 20:12:27 +00:00
self.server.debug, \
self.server.projectVersion)
2019-07-30 22:34:04 +00:00
if profileStr:
2019-08-15 13:20:09 +00:00
msg=profileStr.encode('utf-8')
2019-11-09 21:39:04 +00:00
self._login_headers('text/html',len(msg))
2019-10-22 12:35:51 +00:00
self._write(msg)
2019-08-19 19:02:28 +00:00
self.server.POSTbusy=False
return
2019-09-01 12:33:05 +00:00
else:
self._redirect_headers(actorStr+'/search',cookie)
self.server.POSTbusy=False
return
2019-08-19 19:02:28 +00:00
elif searchStr.startswith(':') or \
searchStr.lower().strip('\n').endswith(' emoji'):
# eg. "cat emoji"
if searchStr.lower().strip('\n').endswith(' emoji'):
2019-11-03 15:27:29 +00:00
searchStr= \
searchStr.lower().strip('\n').replace(' emoji','')
2019-08-19 19:02:28 +00:00
# emoji search
emojiStr= \
2019-11-03 15:27:29 +00:00
htmlSearchEmoji(self.server.translate, \
self.server.baseDir,searchStr)
2019-08-19 19:02:28 +00:00
if emojiStr:
2019-08-19 19:06:54 +00:00
msg=emojiStr.encode('utf-8')
2019-11-09 21:39:04 +00:00
self._login_headers('text/html',len(msg))
2019-10-22 12:35:51 +00:00
self._write(msg)
2019-07-30 22:34:04 +00:00
self.server.POSTbusy=False
return
2019-08-13 21:32:18 +00:00
else:
# shared items search
sharedItemsStr= \
2019-09-07 10:13:12 +00:00
htmlSearchSharedItems(self.server.translate, \
self.server.baseDir, \
2019-08-14 09:45:51 +00:00
searchStr,pageNumber, \
2019-08-25 21:59:42 +00:00
maxPostsInFeed, \
self.server.httpPrefix, \
self.server.domainFull, \
actorStr)
2019-08-13 21:32:18 +00:00
if sharedItemsStr:
2019-08-15 13:20:09 +00:00
msg=sharedItemsStr.encode('utf-8')
2019-11-09 21:39:04 +00:00
self._login_headers('text/html',len(msg))
2019-10-22 12:35:51 +00:00
self._write(msg)
2019-08-13 21:32:18 +00:00
self.server.POSTbusy=False
return
2019-08-26 10:36:00 +00:00
self._redirect_headers(actorStr+'/inbox',cookie)
2019-07-30 22:34:04 +00:00
self.server.POSTbusy=False
return
2019-11-16 11:03:02 +00:00
self._benchmarkPOSTtimings(POSTstartTime,POSTtimings,7)
2019-11-16 10:40:35 +00:00
2019-08-26 09:31:45 +00:00
# removes a shared item
if authorized and self.path.endswith('/rmshare'):
2019-11-26 15:22:45 +00:00
originPathStr= \
self.server.httpPrefix+'://'+self.server.domainFull+ \
self.path.split('/rmshare')[0]
2019-08-26 09:31:45 +00:00
length = int(self.headers['Content-length'])
removeShareConfirmParams=self.rfile.read(length).decode('utf-8')
if '&submitYes=' in removeShareConfirmParams:
2019-11-03 09:55:14 +00:00
removeShareConfirmParams= \
2019-11-03 10:20:30 +00:00
removeShareConfirmParams.replace('%20',' ').replace('%40','@').replace('%3A',':').replace('%2F','/').replace('%23','#').replace('+',' ').strip()
2019-08-26 09:31:45 +00:00
shareActor=removeShareConfirmParams.split('actor=')[1]
if '&' in shareActor:
shareActor=shareActor.split('&')[0]
shareName=removeShareConfirmParams.split('shareName=')[1]
if '&' in shareName:
shareName=shareName.split('&')[0]
shareNickname=getNicknameFromActor(shareActor)
2019-09-02 09:43:43 +00:00
if shareNickname:
shareDomain,sharePort=getDomainFromActor(shareActor)
2019-11-03 15:27:29 +00:00
removeShare(self.server.baseDir, \
shareNickname,shareDomain,shareName)
2019-11-03 09:56:35 +00:00
self._redirect_headers(originPathStr+'/tlshares',cookie)
2019-08-26 09:31:45 +00:00
self.server.POSTbusy=False
return
2019-11-16 11:03:02 +00:00
self._benchmarkPOSTtimings(POSTstartTime,POSTtimings,8)
2019-11-16 10:40:35 +00:00
2019-08-27 12:47:11 +00:00
# removes a post
if authorized and self.path.endswith('/rmpost'):
2019-09-04 11:29:44 +00:00
pageNumber=1
2019-11-26 15:22:45 +00:00
originPathStr= \
self.server.httpPrefix+'://'+self.server.domainFull+ \
self.path.split('/rmpost')[0]
2019-08-27 12:47:11 +00:00
length = int(self.headers['Content-length'])
removePostConfirmParams=self.rfile.read(length).decode('utf-8')
if '&submitYes=' in removePostConfirmParams:
2019-11-03 15:27:29 +00:00
removePostConfirmParams= \
removePostConfirmParams.replace('%20',' ').replace('%40','@').replace('%3A',':').replace('%2F','/').replace('%23','#').strip()
removeMessageId= \
removePostConfirmParams.split('messageId=')[1]
2019-08-27 12:47:11 +00:00
if '&' in removeMessageId:
removeMessageId=removeMessageId.split('&')[0]
2019-09-04 11:29:44 +00:00
if 'pageNumber=' in removePostConfirmParams:
pageNumberStr=removePostConfirmParams.split('pageNumber=')[1]
if '&' in pageNumberStr:
pageNumberStr=pageNumberStr.split('&')[0]
if pageNumberStr.isdigit():
pageNumber=int(pageNumberStr)
2019-08-27 12:47:11 +00:00
if '/statuses/' in removeMessageId:
removePostActor=removeMessageId.split('/statuses/')[0]
if originPathStr in removePostActor:
deleteJson= {
"@context": "https://www.w3.org/ns/activitystreams",
'actor': removePostActor,
'object': removeMessageId,
'to': ['https://www.w3.org/ns/activitystreams#Public',removePostActor],
'cc': [removePostActor+'/followers'],
'type': 'Delete'
}
2019-08-27 13:27:46 +00:00
self.postToNickname=getNicknameFromActor(removePostActor)
2019-09-02 09:43:43 +00:00
if self.postToNickname:
2019-09-04 11:29:44 +00:00
self._postToOutboxThread(deleteJson)
if pageNumber==1:
self._redirect_headers(originPathStr+'/outbox',cookie)
else:
2019-11-03 15:27:29 +00:00
self._redirect_headers(originPathStr+'/outbox?page='+ \
str(pageNumber),cookie)
2019-08-27 12:47:11 +00:00
self.server.POSTbusy=False
return
2019-11-16 11:03:02 +00:00
self._benchmarkPOSTtimings(POSTstartTime,POSTtimings,9)
2019-11-16 10:40:35 +00:00
2019-07-29 16:13:48 +00:00
# decision to follow in the web interface is confirmed
2019-07-29 20:36:26 +00:00
if authorized and self.path.endswith('/followconfirm'):
2019-11-26 15:22:45 +00:00
originPathStr= \
self.server.httpPrefix+'://'+self.server.domainFull+ \
self.path.split('/followconfirm')[0]
2019-07-29 16:13:48 +00:00
followerNickname=getNicknameFromActor(originPathStr)
length = int(self.headers['Content-length'])
followConfirmParams=self.rfile.read(length).decode('utf-8')
2019-10-23 15:48:06 +00:00
if '&submitView=' in followConfirmParams:
2019-11-03 15:27:29 +00:00
followingActor= \
followConfirmParams.replace('%3A',':').replace('%2F','/').split('actor=')[1]
2019-10-23 15:48:06 +00:00
if '&' in followingActor:
followingActor=followingActor.split('&')[0]
self._redirect_headers(followingActor,cookie)
self.server.POSTbusy=False
return
2019-07-29 16:13:48 +00:00
if '&submitYes=' in followConfirmParams:
2019-11-03 15:27:29 +00:00
followingActor= \
followConfirmParams.replace('%3A',':').replace('%2F','/').split('actor=')[1]
2019-07-29 16:13:48 +00:00
if '&' in followingActor:
followingActor=followingActor.split('&')[0]
followingNickname=getNicknameFromActor(followingActor)
followingDomain,followingPort=getDomainFromActor(followingActor)
if followerNickname==followingNickname and \
followingDomain==self.server.domain and \
followingPort==self.server.port:
if self.server.debug:
print('You cannot follow yourself!')
else:
if self.server.debug:
2019-11-03 15:27:29 +00:00
print('Sending follow request from '+ \
followerNickname+' to '+followingActor)
2019-07-29 16:13:48 +00:00
sendFollowRequest(self.server.session, \
self.server.baseDir, \
followerNickname, \
self.server.domain,self.server.port, \
self.server.httpPrefix, \
followingNickname, \
followingDomain, \
followingPort,self.server.httpPrefix, \
False,self.server.federationList, \
self.server.sendThreads, \
self.server.postLog, \
self.server.cachedWebfingers, \
self.server.personCache, \
2019-08-14 20:12:27 +00:00
self.server.debug, \
self.server.projectVersion)
2019-07-29 20:36:26 +00:00
self._redirect_headers(originPathStr,cookie)
self.server.POSTbusy=False
return
2019-11-16 11:03:02 +00:00
self._benchmarkPOSTtimings(POSTstartTime,POSTtimings,10)
2019-11-16 10:40:35 +00:00
2019-07-29 20:36:26 +00:00
# decision to unfollow in the web interface is confirmed
if authorized and self.path.endswith('/unfollowconfirm'):
2019-11-26 15:22:45 +00:00
originPathStr= \
self.server.httpPrefix+'://'+self.server.domainFull+ \
self.path.split('/unfollowconfirm')[0]
2019-07-29 20:36:26 +00:00
followerNickname=getNicknameFromActor(originPathStr)
length = int(self.headers['Content-length'])
followConfirmParams=self.rfile.read(length).decode('utf-8')
if '&submitYes=' in followConfirmParams:
2019-11-03 15:27:29 +00:00
followingActor= \
followConfirmParams.replace('%3A',':').replace('%2F','/').split('actor=')[1]
2019-07-29 20:36:26 +00:00
if '&' in followingActor:
followingActor=followingActor.split('&')[0]
followingNickname=getNicknameFromActor(followingActor)
followingDomain,followingPort=getDomainFromActor(followingActor)
if followerNickname==followingNickname and \
followingDomain==self.server.domain and \
followingPort==self.server.port:
if self.server.debug:
print('You cannot unfollow yourself!')
else:
if self.server.debug:
print(followerNickname+' stops following '+followingActor)
2019-11-03 15:27:29 +00:00
followActor= \
self.server.httpPrefix+'://'+ \
self.server.domainFull+ \
'/users/'+followerNickname
2019-08-18 09:39:12 +00:00
statusNumber,published = getStatusNumber()
followId=followActor+'/statuses/'+str(statusNumber)
2019-07-29 20:36:26 +00:00
unfollowJson = {
2019-08-16 21:52:11 +00:00
'@context': 'https://www.w3.org/ns/activitystreams',
'id': followId+'/undo',
2019-07-29 20:36:26 +00:00
'type': 'Undo',
'actor': followActor,
'object': {
2019-08-16 21:52:11 +00:00
'id': followId,
2019-07-29 20:36:26 +00:00
'type': 'Follow',
'actor': followActor,
2019-08-16 21:52:11 +00:00
'object': followingActor
2019-07-29 20:36:26 +00:00
}
}
2019-08-08 08:54:41 +00:00
pathUsersSection=self.path.split('/users/')[1]
self.postToNickname=pathUsersSection.split('/')[0]
2019-09-03 17:07:00 +00:00
self._postToOutboxThread(unfollowJson)
2019-07-29 16:13:48 +00:00
self._redirect_headers(originPathStr,cookie)
self.server.POSTbusy=False
return
2019-11-16 11:03:02 +00:00
self._benchmarkPOSTtimings(POSTstartTime,POSTtimings,11)
2019-11-16 10:40:35 +00:00
# decision to unblock in the web interface is confirmed
if authorized and self.path.endswith('/unblockconfirm'):
2019-11-26 15:22:45 +00:00
originPathStr= \
self.server.httpPrefix+'://'+self.server.domainFull+ \
self.path.split('/unblockconfirm')[0]
blockerNickname=getNicknameFromActor(originPathStr)
2019-09-02 09:43:43 +00:00
if not blockerNickname:
print('WARN: unable to find nickname in '+originPathStr)
self._redirect_headers(originPathStr,cookie)
self.server.POSTbusy=False
return
length = int(self.headers['Content-length'])
blockConfirmParams=self.rfile.read(length).decode('utf-8')
if '&submitYes=' in blockConfirmParams:
2019-11-03 15:27:29 +00:00
blockingActor= \
blockConfirmParams.replace('%3A',':').replace('%2F','/').split('actor=')[1]
if '&' in blockingActor:
blockingActor=blockingActor.split('&')[0]
blockingNickname=getNicknameFromActor(blockingActor)
2019-09-02 09:43:43 +00:00
if not blockingNickname:
print('WARN: unable to find nickname in '+blockingActor)
self._redirect_headers(originPathStr,cookie)
self.server.POSTbusy=False
return
blockingDomain,blockingPort=getDomainFromActor(blockingActor)
blockingDomainFull=blockingDomain
if blockingPort:
if blockingPort!=80 and blockingPort!=443:
if ':' not in blockingDomain:
2019-11-03 15:27:29 +00:00
blockingDomainFull= \
blockingDomain+':'+str(blockingPort)
if blockerNickname==blockingNickname and \
blockingDomain==self.server.domain and \
blockingPort==self.server.port:
if self.server.debug:
print('You cannot unblock yourself!')
else:
if self.server.debug:
print(blockerNickname+' stops blocking '+blockingActor)
removeBlock(self.server.baseDir,blockerNickname,self.server.domain, \
blockingNickname,blockingDomainFull)
self._redirect_headers(originPathStr,cookie)
self.server.POSTbusy=False
return
2019-11-16 11:03:02 +00:00
self._benchmarkPOSTtimings(POSTstartTime,POSTtimings,12)
2019-11-16 10:40:35 +00:00
# decision to block in the web interface is confirmed
if authorized and self.path.endswith('/blockconfirm'):
2019-11-26 15:22:45 +00:00
originPathStr= \
self.server.httpPrefix+'://'+self.server.domainFull+ \
self.path.split('/blockconfirm')[0]
blockerNickname=getNicknameFromActor(originPathStr)
2019-09-02 09:43:43 +00:00
if not blockerNickname:
print('WARN: unable to find nickname in '+originPathStr)
self._redirect_headers(originPathStr,cookie)
self.server.POSTbusy=False
return
length = int(self.headers['Content-length'])
blockConfirmParams=self.rfile.read(length).decode('utf-8')
if '&submitYes=' in blockConfirmParams:
2019-11-03 15:27:29 +00:00
blockingActor= \
blockConfirmParams.replace('%3A',':').replace('%2F','/').split('actor=')[1]
if '&' in blockingActor:
blockingActor=blockingActor.split('&')[0]
blockingNickname=getNicknameFromActor(blockingActor)
2019-09-02 09:43:43 +00:00
if not blockingNickname:
print('WARN: unable to find nickname in '+blockingActor)
self._redirect_headers(originPathStr,cookie)
self.server.POSTbusy=False
return
2019-11-03 15:27:29 +00:00
blockingDomain,blockingPort= \
getDomainFromActor(blockingActor)
blockingDomainFull=blockingDomain
if blockingPort:
if blockingPort!=80 and blockingPort!=443:
if ':' not in blockingDomain:
2019-11-03 15:27:29 +00:00
blockingDomainFull= \
blockingDomain+':'+str(blockingPort)
if blockerNickname==blockingNickname and \
blockingDomain==self.server.domain and \
blockingPort==self.server.port:
if self.server.debug:
print('You cannot block yourself!')
else:
if self.server.debug:
2019-11-03 15:27:29 +00:00
print('Adding block by '+blockerNickname+ \
' of '+blockingActor)
addBlock(self.server.baseDir,blockerNickname, \
self.server.domain, \
blockingNickname,blockingDomainFull)
self._redirect_headers(originPathStr,cookie)
self.server.POSTbusy=False
return
2019-08-24 23:00:03 +00:00
2019-11-16 11:03:02 +00:00
self._benchmarkPOSTtimings(POSTstartTime,POSTtimings,13)
2019-11-16 10:40:35 +00:00
2019-08-24 23:00:03 +00:00
# an option was chosen from person options screen
# view/follow/block/report
if authorized and self.path.endswith('/personoptions'):
2019-09-04 11:29:44 +00:00
pageNumber=1
2019-11-26 15:22:45 +00:00
originPathStr= \
self.server.httpPrefix+'://'+self.server.domainFull+ \
self.path.split('/personoptions')[0]
2019-08-24 23:00:03 +00:00
chooserNickname=getNicknameFromActor(originPathStr)
2019-09-02 09:43:43 +00:00
if not chooserNickname:
print('WARN: unable to find nickname in '+originPathStr)
self._redirect_headers(originPathStr,cookie)
self.server.POSTbusy=False
return
2019-08-24 23:00:03 +00:00
length = int(self.headers['Content-length'])
2019-11-03 15:27:29 +00:00
optionsConfirmParams= \
self.rfile.read(length).decode('utf-8').replace('%3A',':').replace('%2F','/')
2019-09-04 11:29:44 +00:00
# page number to return to
if 'pageNumber=' in optionsConfirmParams:
pageNumberStr=optionsConfirmParams.split('pageNumber=')[1]
if '&' in pageNumberStr:
pageNumberStr=pageNumberStr.split('&')[0]
if pageNumberStr.isdigit():
pageNumber=int(pageNumberStr)
2019-08-25 09:41:57 +00:00
# actor for the person
2019-08-24 23:00:03 +00:00
optionsActor=optionsConfirmParams.split('actor=')[1]
if '&' in optionsActor:
optionsActor=optionsActor.split('&')[0]
2019-08-25 09:41:57 +00:00
# url of the avatar
optionsAvatarUrl=optionsConfirmParams.split('avatarUrl=')[1]
if '&' in optionsAvatarUrl:
optionsAvatarUrl=optionsAvatarUrl.split('&')[0]
# link to a post, which can then be included in reports
postUrl=None
if 'postUrl' in optionsConfirmParams:
postUrl=optionsConfirmParams.split('postUrl=')[1]
if '&' in postUrl:
postUrl=postUrl.split('&')[0]
2019-08-24 23:00:03 +00:00
optionsNickname=getNicknameFromActor(optionsActor)
2019-09-02 09:43:43 +00:00
if not optionsNickname:
print('WARN: unable to find nickname in '+optionsActor)
self._redirect_headers(originPathStr,cookie)
self.server.POSTbusy=False
return
2019-08-24 23:00:03 +00:00
optionsDomain,optionsPort=getDomainFromActor(optionsActor)
optionsDomainFull=optionsDomain
if optionsPort:
if optionsPort!=80 and optionsPort!=443:
if ':' not in optionsDomain:
optionsDomainFull=optionsDomain+':'+str(optionsPort)
if chooserNickname==optionsNickname and \
optionsDomain==self.server.domain and \
optionsPort==self.server.port:
if self.server.debug:
print('You cannot perform an option action on yourself')
if '&submitView=' in optionsConfirmParams:
if self.server.debug:
print('Viewing '+optionsActor)
self._redirect_headers(optionsActor,cookie)
self.server.POSTbusy=False
return
if '&submitBlock=' in optionsConfirmParams:
if self.server.debug:
2019-11-03 15:27:29 +00:00
print('Adding block by '+chooserNickname+ \
' of '+optionsActor)
addBlock(self.server.baseDir,chooserNickname, \
self.server.domain, \
2019-08-24 23:00:03 +00:00
optionsNickname,optionsDomainFull)
if '&submitUnblock=' in optionsConfirmParams:
if self.server.debug:
print('Unblocking '+optionsActor)
2019-09-07 08:57:52 +00:00
msg=htmlUnblockConfirm(self.server.translate, \
self.server.baseDir, \
originPathStr, \
optionsActor, \
optionsAvatarUrl).encode()
2019-11-09 21:39:04 +00:00
self._set_headers('text/html',len(msg),cookie)
2019-10-22 12:35:51 +00:00
self._write(msg)
2019-08-24 23:00:03 +00:00
self.server.POSTbusy=False
return
if '&submitFollow=' in optionsConfirmParams:
if self.server.debug:
print('Following '+optionsActor)
2019-09-07 08:57:52 +00:00
msg=htmlFollowConfirm(self.server.translate, \
self.server.baseDir, \
originPathStr, \
optionsActor, \
optionsAvatarUrl).encode()
2019-11-09 21:39:04 +00:00
self._set_headers('text/html',len(msg),cookie)
2019-10-22 12:35:51 +00:00
self._write(msg)
2019-08-24 23:00:03 +00:00
self.server.POSTbusy=False
return
if '&submitUnfollow=' in optionsConfirmParams:
if self.server.debug:
print('Unfollowing '+optionsActor)
2019-09-07 08:57:52 +00:00
msg=htmlUnfollowConfirm(self.server.translate, \
self.server.baseDir, \
originPathStr, \
optionsActor, \
optionsAvatarUrl).encode()
2019-11-09 21:39:04 +00:00
self._set_headers('text/html',len(msg),cookie)
2019-10-22 12:35:51 +00:00
self._write(msg)
2019-08-24 23:00:03 +00:00
self.server.POSTbusy=False
return
2019-08-25 10:30:39 +00:00
if '&submitDM=' in optionsConfirmParams:
if self.server.debug:
print('Sending DM to '+optionsActor)
reportPath=self.path.replace('/personoptions','')+'/newdm'
2019-09-07 08:57:52 +00:00
msg=htmlNewPost(self.server.translate, \
self.server.baseDir, \
reportPath,None, \
[optionsActor],None, \
pageNumber).encode()
2019-11-09 21:39:04 +00:00
self._set_headers('text/html',len(msg),cookie)
2019-10-22 12:35:51 +00:00
self._write(msg)
2019-08-25 10:30:39 +00:00
self.server.POSTbusy=False
return
2019-11-06 11:39:41 +00:00
if '&submitSnooze=' in optionsConfirmParams:
2019-11-26 15:22:45 +00:00
thisActor= \
self.server.httpPrefix+'://'+self.server.domainFull+ \
self.path.split('/personoptions')[0]
2019-11-06 11:55:22 +00:00
if self.server.debug:
print('Snoozing '+optionsActor+' '+thisActor)
2019-11-06 11:39:41 +00:00
if '/users/' in thisActor:
nickname=thisActor.split('/users/')[1]
personSnooze(self.server.baseDir,nickname,self.server.domain,optionsActor)
self._redirect_headers(thisActor+ \
'/inbox?page='+str(pageNumber),cookie)
self.server.POSTbusy=False
2019-11-15 19:44:20 +00:00
return
2019-11-06 11:39:41 +00:00
if '&submitUnSnooze=' in optionsConfirmParams:
2019-11-26 15:22:45 +00:00
thisActor= \
self.server.httpPrefix+'://'+self.server.domainFull+ \
self.path.split('/personoptions')[0]
2019-11-06 11:55:22 +00:00
if self.server.debug:
print('Unsnoozing '+optionsActor+' '+thisActor)
2019-11-06 11:39:41 +00:00
if '/users/' in thisActor:
nickname=thisActor.split('/users/')[1]
personUnsnooze(self.server.baseDir,nickname,self.server.domain,optionsActor)
self._redirect_headers(thisActor+ \
'/inbox?page='+str(pageNumber),cookie)
self.server.POSTbusy=False
2019-11-15 19:44:20 +00:00
return
2019-08-24 23:00:03 +00:00
if '&submitReport=' in optionsConfirmParams:
if self.server.debug:
print('Reporting '+optionsActor)
2019-08-25 09:49:27 +00:00
reportPath=self.path.replace('/personoptions','')+'/newreport'
2019-09-07 08:57:52 +00:00
msg=htmlNewPost(self.server.translate, \
self.server.baseDir, \
reportPath,None,[], \
postUrl,pageNumber).encode()
2019-11-09 21:39:04 +00:00
self._set_headers('text/html',len(msg),cookie)
2019-10-22 12:35:51 +00:00
self._write(msg)
2019-08-24 23:00:03 +00:00
self.server.POSTbusy=False
return
self._redirect_headers(originPathStr,cookie)
self.server.POSTbusy=False
return
2019-11-16 11:03:02 +00:00
self._benchmarkPOSTtimings(POSTstartTime,POSTtimings,14)
2019-11-16 10:40:35 +00:00
2019-09-29 13:44:56 +00:00
pageNumber=self._receiveNewPost(authorized,'newpost',self.path)
2019-09-29 13:43:39 +00:00
if pageNumber:
2019-07-28 15:16:14 +00:00
nickname=self.path.split('/users/')[1]
if '/' in nickname:
nickname=nickname.split('/')[0]
2019-11-26 15:22:45 +00:00
self._redirect_headers(self.server.httpPrefix+'://'+self.server.domainFull+ \
'/users/'+nickname+ \
2019-11-03 15:27:29 +00:00
'/inbox?page='+str(pageNumber),cookie)
2019-07-27 20:30:58 +00:00
self.server.POSTbusy=False
return
2019-09-29 13:44:56 +00:00
pageNumber=self._receiveNewPost(authorized,'newunlisted',self.path)
2019-09-29 13:43:39 +00:00
if pageNumber:
2019-07-28 15:16:14 +00:00
nickname=self.path.split('/users/')[1]
if '/' in nickname:
nickname=nickname.split('/')[0]
2019-11-26 15:22:45 +00:00
self._redirect_headers(self.server.httpPrefix+'://'+self.server.domainFull+ \
'/users/'+nickname+ \
2019-11-03 15:27:29 +00:00
'/inbox?page='+str(pageNumber),cookie)
2019-07-27 20:30:58 +00:00
self.server.POSTbusy=False
return
2019-09-29 13:44:56 +00:00
pageNumber=self._receiveNewPost(authorized,'newfollowers',self.path)
2019-09-29 13:43:39 +00:00
if pageNumber:
2019-08-11 18:32:29 +00:00
nickname=self.path.split('/users/')[1]
2019-07-28 15:16:14 +00:00
if '/' in nickname:
nickname=nickname.split('/')[0]
2019-11-26 15:22:45 +00:00
self._redirect_headers(self.server.httpPrefix+'://'+self.server.domainFull+ \
'/users/'+nickname+ \
2019-11-03 15:27:29 +00:00
'/inbox?page='+str(pageNumber),cookie)
2019-07-27 20:30:58 +00:00
self.server.POSTbusy=False
return
2019-09-29 13:44:56 +00:00
pageNumber=self._receiveNewPost(authorized,'newdm',self.path)
2019-09-29 13:43:39 +00:00
if pageNumber:
2019-08-11 18:32:29 +00:00
nickname=self.path.split('/users/')[1]
2019-08-11 11:25:27 +00:00
if '/' in nickname:
nickname=nickname.split('/')[0]
2019-11-26 15:22:45 +00:00
self._redirect_headers(self.server.httpPrefix+'://'+self.server.domainFull+ \
'/users/'+nickname+ \
2019-11-03 15:27:29 +00:00
'/inbox?page='+str(pageNumber),cookie)
2019-08-11 11:25:27 +00:00
self.server.POSTbusy=False
return
2019-09-29 13:44:56 +00:00
pageNumber=self._receiveNewPost(authorized,'newreport',self.path)
2019-09-29 13:43:39 +00:00
if pageNumber:
2019-08-11 18:32:29 +00:00
nickname=self.path.split('/users/')[1]
2019-07-28 15:16:14 +00:00
if '/' in nickname:
nickname=nickname.split('/')[0]
2019-11-26 15:22:45 +00:00
self._redirect_headers(self.server.httpPrefix+'://'+self.server.domainFull+ \
'/users/'+nickname+ \
2019-11-03 15:27:29 +00:00
'/inbox?page='+str(pageNumber),cookie)
2019-07-27 20:30:58 +00:00
self.server.POSTbusy=False
return
2019-09-29 13:44:56 +00:00
pageNumber=self._receiveNewPost(authorized,'newshare',self.path)
2019-09-29 13:43:39 +00:00
if pageNumber:
2019-08-11 18:32:29 +00:00
nickname=self.path.split('/users/')[1]
2019-07-28 15:16:14 +00:00
if '/' in nickname:
nickname=nickname.split('/')[0]
2019-11-26 15:22:45 +00:00
self._redirect_headers(self.server.httpPrefix+'://'+self.server.domainFull+ \
'/users/'+nickname+ \
2019-11-03 15:27:29 +00:00
'/shares?page='+str(pageNumber),cookie)
2019-07-27 20:30:58 +00:00
self.server.POSTbusy=False
return
2019-07-29 16:13:48 +00:00
2019-11-16 11:03:02 +00:00
self._benchmarkPOSTtimings(POSTstartTime,POSTtimings,15)
2019-11-16 10:40:35 +00:00
2019-07-23 19:02:26 +00:00
if self.path.endswith('/outbox') or self.path.endswith('/shares'):
if '/users/' in self.path:
2019-07-27 20:30:58 +00:00
if authorized:
2019-07-12 11:05:43 +00:00
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
2019-11-16 11:03:02 +00:00
self._benchmarkPOSTtimings(POSTstartTime,POSTtimings,16)
2019-11-16 10:40:35 +00:00
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-08-13 10:59:38 +00:00
self.path.endswith('/moderationaction') or \
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-11-16 11:03:02 +00:00
self._benchmarkPOSTtimings(POSTstartTime,POSTtimings,17)
2019-11-16 10:40:35 +00:00
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))
if not self.headers['Content-type'].startswith('image/') and \
not self.headers['Content-type'].startswith('video/') and \
not self.headers['Content-type'].startswith('audio/'):
2019-07-16 14:23:06 +00:00
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.maxMediaSize:
print('Maximum media 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:
2019-08-16 18:32:26 +00:00
print('DEBUG: unauthenticated attempt to post image to outbox')
2019-07-16 14:23:06 +00:00
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]
2019-11-03 15:27:29 +00:00
accountsDir= \
self.server.baseDir+'/accounts/'+ \
self.postFromNickname+'@'+self.server.domain
2019-07-16 14:23:06 +00:00
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'
2019-11-14 15:11:20 +00:00
if self.headers['Content-type'].endswith('webp'):
mediaFilename=mediaFilenameBase+'.webp'
2019-07-16 14:23:06 +00:00
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
2019-11-09 21:39:04 +00:00
if self.headers['Content-type'] != 'application/json' and \
self.headers['Content-type'] != 'application/activity+json':
2019-07-16 14:23:06 +00:00
print("POST is not json: "+self.headers['Content-type'])
2019-08-14 22:33:55 +00:00
if self.server.debug:
print(str(self.headers))
length = int(self.headers['Content-length'])
if length<self.server.maxPostLength:
unknownPost=self.rfile.read(length).decode('utf-8')
print(str(unknownPost))
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-11-16 11:03:02 +00:00
self._benchmarkPOSTtimings(POSTstartTime,POSTtimings,18)
2019-11-16 10:40:35 +00:00
2019-11-16 00:07:07 +00:00
# check content length before reading bytes
if self.path == '/sharedInbox' or self.path == '/inbox':
length=0
if self.headers.get('Content-length'):
length = int(self.headers['Content-length'])
2019-11-16 09:53:52 +00:00
elif self.headers.get('Content-Length'):
2019-11-16 00:07:07 +00:00
length = int(self.headers['Content-Length'])
2019-11-16 09:53:52 +00:00
elif self.headers.get('content-length'):
2019-11-16 00:07:07 +00:00
length = int(self.headers['content-length'])
if length>10240:
print('WARN: post to shared inbox is too long '+str(length)+' bytes')
self._400()
self.server.POSTbusy=False
return
2019-07-01 11:48:54 +00:00
messageBytes=self.rfile.read(length)
2019-11-16 00:07:07 +00:00
# check content length after reading bytes
2019-11-16 00:01:00 +00:00
if self.path == '/sharedInbox' or self.path == '/inbox':
lenMessage=len(messageBytes)
if lenMessage>10240:
print('WARN: post to shared inbox is too long '+str(lenMessage)+' bytes')
self._400()
self.server.POSTbusy=False
return
# convert the raw bytes to json
2019-07-16 14:23:06 +00:00
messageJson=json.loads(messageBytes)
2019-07-18 11:35:48 +00:00
2019-11-16 11:03:02 +00:00
self._benchmarkPOSTtimings(POSTstartTime,POSTtimings,19)
2019-11-16 10:40:35 +00:00
2019-07-03 21:37:46 +00:00
# https://www.w3.org/TR/activitypub/#object-without-create
if self.outboxAuthenticated:
2019-09-03 19:46:26 +00:00
if self._postToOutbox(messageJson,__version__):
2019-07-16 19:07:45 +00:00
if messageJson.get('id'):
2019-07-16 10:19:04 +00:00
self.headers['Location']= \
2019-08-16 19:48:32 +00:00
messageJson['id'].replace('/activity','').replace('/undo','')
self.send_response(201)
self.end_headers()
self.server.POSTbusy=False
return
else:
2019-08-16 18:32:26 +00:00
if self.server.debug:
print('Failed to post to outbox')
self.send_response(403)
self.end_headers()
self.server.POSTbusy=False
return
2019-07-03 21:37:46 +00:00
2019-11-16 11:55:14 +00:00
self._benchmarkPOSTtimings(POSTstartTime,POSTtimings,20)
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:
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-11-16 11:55:14 +00:00
self._benchmarkPOSTtimings(POSTstartTime,POSTtimings,21)
2019-11-16 12:07:57 +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
self._benchmarkPOSTtimings(POSTstartTime,POSTtimings,22)
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:
# 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-04 10:02:56 +00:00
2019-11-16 11:55:14 +00:00
self._benchmarkPOSTtimings(POSTstartTime,POSTtimings,23)
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-11-03 15:27:29 +00:00
queueStatus= \
self._updateInboxQueue(self.postToNickname, \
messageJson,messageBytes)
2019-07-15 12:27:26 +00:00
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
2019-08-14 23:04:41 +00:00
return
2019-08-16 18:32:26 +00:00
if self.server.debug:
2019-08-16 19:13:58 +00:00
print('_updateInboxQueue exited without doing anything')
2019-08-14 23:04:41 +00:00
else:
if self.server.debug:
print('self.postToNickname is None')
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-11-03 15:27:29 +00:00
queueStatus= \
self._updateInboxQueue('inbox',messageJson,messageBytes)
2019-07-15 12:27:26 +00:00
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-08-17 15:16:27 +00:00
class PubServerUnitTest(PubServer):
protocol_version = 'HTTP/1.0'
2019-10-16 18:19:18 +00:00
def runPostsQueue(baseDir: str,sendThreads: [],debug: bool) -> None:
"""Manages the threads used to send posts
"""
while True:
time.sleep(1)
removeDormantThreads(baseDir,sendThreads,debug)
2019-10-19 12:40:00 +00:00
def runSharesExpire(versionNumber: str,baseDir: str) -> None:
2019-10-17 09:58:30 +00:00
"""Expires shares as needed
"""
while True:
time.sleep(120)
expireShares(baseDir)
2019-10-16 18:24:06 +00:00
def runPostsWatchdog(projectVersion: str,httpd) -> None:
2019-10-16 18:19:18 +00:00
"""This tries to keep the posts thread running even if it dies
"""
print('Starting posts queue watchdog')
postsQueueOriginal=httpd.thrPostsQueue.clone(runPostsQueue)
httpd.thrPostsQueue.start()
while True:
time.sleep(20)
if not httpd.thrPostsQueue.isAlive():
httpd.thrPostsQueue.kill()
httpd.thrPostsQueue=postsQueueOriginal.clone(runPostsQueue)
httpd.thrPostsQueue.start()
print('Restarting posts queue...')
2019-10-17 09:58:30 +00:00
def runSharesExpireWatchdog(projectVersion: str,httpd) -> None:
"""This tries to keep the shares expiry thread running even if it dies
"""
print('Starting shares expiry watchdog')
sharesExpireOriginal=httpd.thrSharesExpire.clone(runSharesExpire)
httpd.thrSharesExpire.start()
while True:
time.sleep(20)
if not httpd.thrSharesExpire.isAlive():
httpd.thrSharesExpire.kill()
httpd.thrSharesExpire=sharesExpireOriginal.clone(runSharesExpire)
httpd.thrSharesExpire.start()
print('Restarting shares expiry...')
2019-10-25 16:50:22 +00:00
def loadTokens(baseDir: str,tokensDict: {},tokensLookup: {}) -> None:
2019-10-25 16:48:53 +00:00
for subdir, dirs, files in os.walk(baseDir+'/accounts'):
for handle in dirs:
if '@' in handle:
tokenFilename=baseDir+'/accounts/'+handle+'/.token'
if not os.path.isfile(tokenFilename):
continue
nickname=handle.split('@')[0]
token=None
try:
with open(tokenFilename, 'r') as fp:
token = fp.read()
except Exception as e:
print('WARN: Unable to read token for '+nickname+' '+str(e))
if not token:
continue
tokensDict[nickname]=token
tokensLookup[token]=nickname
def runDaemon(maxRecentPosts: int, \
enableSharedInbox: bool,registration: bool, \
2019-11-13 10:32:12 +00:00
language: str,projectVersion: str, \
2019-11-11 17:49:08 +00:00
instanceId: str,clientToServer: bool, \
2019-08-02 12:49:34 +00:00
baseDir: str,domain: str, \
2019-08-14 13:52:19 +00:00
port=80,proxyPort=80,httpPrefix='https', \
2019-11-16 14:49:21 +00:00
fedList=[],maxMentions=10,maxEmoji=10, \
2019-09-30 10:15:20 +00:00
authenticatedFetch=False, \
2019-09-25 09:22:10 +00:00
noreply=False,nolike=False,nopics=False, \
2019-07-09 18:11:23 +00:00
noannounce=False,cw=False,ocapAlways=False, \
2019-07-15 10:22:19 +00:00
useTor=False,maxReplies=64, \
domainMaxPostsPerDay=8640,accountMaxPostsPerDay=8640, \
allowDeletion=False,debug=False,unitTest=False, \
2019-10-16 18:19:18 +00:00
instanceOnlySkillsSearch=False,sendThreads=[]) -> 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-08-14 13:52:19 +00:00
serverAddress = ('', proxyPort)
2019-08-17 15:16:27 +00:00
if unitTest:
httpd = ThreadingHTTPServer(serverAddress, PubServerUnitTest)
else:
httpd = ThreadingHTTPServer(serverAddress, PubServer)
2019-09-07 08:57:52 +00:00
# load translations dictionary
httpd.translate={}
2019-11-13 12:45:41 +00:00
httpd.systemLanguage='en'
2019-09-07 08:57:52 +00:00
if not unitTest:
if not os.path.isdir(baseDir+'/translations'):
print('ERROR: translations directory not found')
return
2019-11-11 17:49:08 +00:00
if not language:
systemLanguage=locale.getdefaultlocale()[0]
else:
systemLanguage=language
2019-11-12 21:38:02 +00:00
if not systemLanguage:
systemLanguage='en'
2019-09-07 08:57:52 +00:00
if '_' in systemLanguage:
systemLanguage=systemLanguage.split('_')[0]
2019-11-11 17:49:08 +00:00
while '/' in systemLanguage:
systemLanguage=systemLanguage.split('/')[1]
2019-09-07 08:57:52 +00:00
if '.' in systemLanguage:
systemLanguage=systemLanguage.split('.')[0]
translationsFile=baseDir+'/translations/'+systemLanguage+'.json'
if not os.path.isfile(translationsFile):
systemLanguage='en'
translationsFile=baseDir+'/translations/'+systemLanguage+'.json'
print('System language: '+systemLanguage)
2019-11-13 12:45:41 +00:00
httpd.systemLanguage=systemLanguage
2019-10-22 11:55:06 +00:00
httpd.translate=loadJson(translationsFile)
2019-09-07 08:57:52 +00:00
2019-11-13 12:49:40 +00:00
if registration=='open':
httpd.registration=True
else:
httpd.registration=False
2019-11-15 21:43:20 +00:00
httpd.enableSharedInbox=enableSharedInbox
2019-09-03 19:40:44 +00:00
httpd.outboxThread={}
2019-09-29 13:30:24 +00:00
httpd.newPostThread={}
2019-08-14 20:12:27 +00:00
httpd.projectVersion=projectVersion
2019-09-25 09:22:10 +00:00
httpd.authenticatedFetch=authenticatedFetch
2019-09-03 19:30:41 +00:00
# max POST size of 30M
httpd.maxPostLength=1024*1024*30
httpd.maxMediaSize=httpd.maxPostLength
httpd.maxMessageLength=5000
2019-10-19 10:23:49 +00:00
httpd.maxPostsInBox=32000
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:
if port!=80 and port!=443:
if ':' not in domain:
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-08-02 12:49:34 +00:00
httpd.instanceId=instanceId
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-10-16 18:19:18 +00:00
httpd.sendThreads=sendThreads
2019-07-05 18:57:19 +00:00
httpd.postLog=[]
2019-07-15 12:27:26 +00:00
httpd.maxQueueLength=16
httpd.ocapAlways=ocapAlways
2019-07-17 19:31:52 +00:00
httpd.allowDeletion=allowDeletion
2019-07-24 22:38:42 +00:00
httpd.lastLoginTime=0
2019-08-02 18:04:31 +00:00
httpd.maxReplies=maxReplies
httpd.tokens={}
httpd.tokensLookup={}
2019-10-25 16:48:53 +00:00
loadTokens(baseDir,httpd.tokens,httpd.tokensLookup)
httpd.instanceOnlySkillsSearch=instanceOnlySkillsSearch
2019-07-09 17:54:08 +00:00
httpd.acceptedCaps=["inbox:write","objects:read"]
2019-11-04 10:43:19 +00:00
# contains threads used to send posts to followers
httpd.followersThreads=[]
2019-07-09 17:54:08 +00:00
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
2019-08-20 10:10:33 +00:00
if not os.path.isdir(baseDir+'/cache'):
os.mkdir(baseDir+'/cache')
if not os.path.isdir(baseDir+'/cache/actors'):
print('Creating actors cache')
os.mkdir(baseDir+'/cache/actors')
2019-08-20 12:39:59 +00:00
if not os.path.isdir(baseDir+'/cache/announce'):
print('Creating announce cache')
os.mkdir(baseDir+'/cache/announce')
2019-09-14 17:12:03 +00:00
if not os.path.isdir(baseDir+'/cache/avatars'):
print('Creating avatars cache')
os.mkdir(baseDir+'/cache/avatars')
2019-08-20 10:10:33 +00:00
2019-08-20 11:51:29 +00:00
archiveDir=baseDir+'/archive'
if not os.path.isdir(archiveDir):
print('Creating archive')
os.mkdir(archiveDir)
2019-08-20 10:28:05 +00:00
print('Creating cache expiry thread')
httpd.thrCache= \
threadWithTrace(target=expireCache, \
2019-08-20 11:51:29 +00:00
args=(baseDir,httpd.personCache, \
httpd.httpPrefix, \
archiveDir, \
2019-08-20 11:52:12 +00:00
httpd.maxPostsInBox),daemon=True)
2019-08-20 10:28:05 +00:00
httpd.thrCache.start()
2019-10-16 18:19:18 +00:00
print('Creating posts queue')
httpd.thrPostsQueue= \
threadWithTrace(target=runPostsQueue, \
args=(baseDir,httpd.sendThreads,debug),daemon=True)
if not unitTest:
httpd.thrPostsWatchdog= \
threadWithTrace(target=runPostsWatchdog, \
2019-10-16 18:24:06 +00:00
args=(projectVersion,httpd),daemon=True)
2019-10-16 18:19:18 +00:00
httpd.thrPostsWatchdog.start()
else:
httpd.thrPostsQueue.start()
2019-10-17 09:58:30 +00:00
print('Creating expire thread for shared items')
httpd.thrSharesExpire= \
threadWithTrace(target=runSharesExpire, \
2019-10-19 12:40:00 +00:00
args=(__version__,baseDir),daemon=True)
2019-10-17 09:58:30 +00:00
if not unitTest:
httpd.thrSharesExpireWatchdog= \
threadWithTrace(target=runSharesExpireWatchdog, \
args=(projectVersion,httpd),daemon=True)
httpd.thrSharesExpireWatchdog.start()
else:
httpd.thrSharesExpire.start()
httpd.recentPostsCache={}
httpd.maxRecentPosts=maxRecentPosts
2019-11-24 18:06:54 +00:00
httpd.iconsCache={}
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=(httpd.recentPostsCache,httpd.maxRecentPosts, \
projectVersion, \
2019-08-14 20:12:27 +00:00
baseDir,httpPrefix,httpd.sendThreads, \
2019-07-06 17:00:22 +00:00
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-11-16 14:49:21 +00:00
allowDeletion,debug,maxMentions,maxEmoji, \
httpd.translate, \
2019-10-19 18:08:47 +00:00
unitTest,httpd.acceptedCaps),daemon=True)
2019-09-05 12:43:59 +00:00
if not unitTest:
httpd.thrWatchdog= \
threadWithTrace(target=runInboxQueueWatchdog, \
args=(projectVersion,httpd),daemon=True)
httpd.thrWatchdog.start()
else:
httpd.thrInboxQueue.start()
2019-07-13 09:37:17 +00:00
if clientToServer:
2019-08-14 13:52:19 +00:00
print('Running ActivityPub client on ' + domain + ' port ' + str(proxyPort))
2019-07-13 09:37:17 +00:00
else:
2019-08-14 13:52:19 +00:00
print('Running ActivityPub server on ' + domain + ' port ' + str(proxyPort))
2019-06-28 18:55:29 +00:00
httpd.serve_forever()