Merge branch 'main' of ssh://code.freedombone.net:2222/bashrc/epicyon

main
Bob Mottram 2021-02-09 20:07:46 +00:00
commit e98a5d0d50
20 changed files with 241 additions and 49 deletions

View File

@ -77,7 +77,8 @@ def _acceptFollow(baseDir: str, domain: str, messageJson: {},
if not messageJson['object'].get('type'): if not messageJson['object'].get('type'):
return return
if not messageJson['object']['type'] == 'Follow': if not messageJson['object']['type'] == 'Follow':
return if not messageJson['object']['type'] == 'Join':
return
if debug: if debug:
print('DEBUG: receiving Follow activity') print('DEBUG: receiving Follow activity')
if not messageJson['object'].get('actor'): if not messageJson['object'].get('actor'):

View File

@ -114,7 +114,7 @@ def _validHashtagCategory(category: str) -> bool:
if not category: if not category:
return False return False
invalidChars = (',', ' ', '<', ';', '\\') invalidChars = (',', ' ', '<', ';', '\\', '"', '&', '#')
for ch in invalidChars: for ch in invalidChars:
if ch in category: if ch in category:
return False return False

View File

@ -10,6 +10,7 @@ import os
import email.parser import email.parser
import urllib.parse import urllib.parse
from shutil import copyfile from shutil import copyfile
from utils import isValidLanguage
from utils import getImageExtensions from utils import getImageExtensions
from utils import loadJson from utils import loadJson
from utils import fileLastModified from utils import fileLastModified
@ -377,12 +378,19 @@ def validHashTag(hashtag: str) -> bool:
# long hashtags are not valid # long hashtags are not valid
if len(hashtag) >= 32: if len(hashtag) >= 32:
return False return False
# TODO: this may need to be an international character set
validChars = set('0123456789' + validChars = set('0123456789' +
'abcdefghijklmnopqrstuvwxyz' + 'abcdefghijklmnopqrstuvwxyz' +
'ABCDEFGHIJKLMNOPQRSTUVWXYZ') 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' +
'¡¿ÄäÀàÁáÂâÃãÅåǍǎĄąĂăÆæĀā' +
'ÇçĆćĈĉČčĎđĐďðÈèÉéÊêËëĚěĘęĖėĒē' +
'ĜĝĢģĞğĤĥÌìÍíÎîÏïıĪīĮįĴĵĶķ' +
'ĹĺĻļŁłĽľĿŀÑñŃńŇňŅņÖöÒòÓóÔôÕõŐőØøŒœ' +
'ŔŕŘřẞߌśŜŝŞşŠšȘșŤťŢţÞþȚțÜüÙùÚúÛûŰűŨũŲųŮůŪū' +
'ŴŵÝýŸÿŶŷŹźŽžŻż')
if set(hashtag).issubset(validChars): if set(hashtag).issubset(validChars):
return True return True
if isValidLanguage(hashtag):
return True
return False return False

View File

@ -13,7 +13,8 @@ validContexts = (
"https://w3id.org/security/v1", "https://w3id.org/security/v1",
"*/apschema/v1.9", "*/apschema/v1.9",
"*/apschema/v1.21", "*/apschema/v1.21",
"*/litepub-0.1.jsonld" "*/litepub-0.1.jsonld",
"https://litepub.social/litepub/context.jsonld"
) )
@ -129,6 +130,33 @@ def getApschemaV1_21() -> {}:
} }
def getLitepubSocial() -> {}:
# https://litepub.social/litepub/context.jsonld
return {
'@context': [
'https://www.w3.org/ns/activitystreams',
'https://w3id.org/security/v1',
{
'Emoji': 'toot:Emoji',
'Hashtag': 'as:Hashtag',
'PropertyValue': 'schema:PropertyValue',
'atomUri': 'ostatus:atomUri',
'conversation': {
'@id': 'ostatus:conversation',
'@type': '@id'
},
'manuallyApprovesFollowers': 'as:manuallyApprovesFollowers',
'ostatus': 'http://ostatus.org#',
'schema': 'http://schema.org',
'sensitive': 'as:sensitive',
'toot': 'http://joinmastodon.org/ns#',
'totalItems': 'as:totalItems',
'value': 'schema:value'
}
]
}
def getLitepubV0_1() -> {}: def getLitepubV0_1() -> {}:
# https://domain/schemas/litepub-0.1.jsonld # https://domain/schemas/litepub-0.1.jsonld
return { return {

View File

@ -2344,15 +2344,15 @@ class PubServer(BaseHTTPRequestHandler):
if debug: if debug:
print('You cannot follow the news actor') print('You cannot follow the news actor')
else: else:
if debug: print('Sending follow request from ' +
print('Sending follow request from ' + followerNickname + ' to ' + followingActor)
followerNickname + ' to ' + followingActor)
sendFollowRequest(self.server.session, sendFollowRequest(self.server.session,
baseDir, followerNickname, baseDir, followerNickname,
domain, port, domain, port,
httpPrefix, httpPrefix,
followingNickname, followingNickname,
followingDomain, followingDomain,
followingActor,
followingPort, httpPrefix, followingPort, httpPrefix,
False, self.server.federationList, False, self.server.federationList,
self.server.sendThreads, self.server.sendThreads,
@ -13537,8 +13537,10 @@ class PubServer(BaseHTTPRequestHandler):
return return
# refuse to receive non-json content # refuse to receive non-json content
if self.headers['Content-type'] != 'application/json' and \ contentTypeStr = self.headers['Content-type']
self.headers['Content-type'] != 'application/activity+json': if not contentTypeStr.startswith('application/json') and \
not contentTypeStr.startswith('application/activity+json') and \
not contentTypeStr.startswith('application/ld+json'):
print("POST is not json: " + self.headers['Content-type']) print("POST is not json: " + self.headers['Content-type'])
if self.server.debug: if self.server.debug:
print(str(self.headers)) print(str(self.headers))

BIN
emoji/copyleft.png 100644

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

View File

@ -98,6 +98,7 @@
"confused": "1F615", "confused": "1F615",
"confusedface": "1F615", "confusedface": "1F615",
"copyleftsymbol": "1F12F", "copyleftsymbol": "1F12F",
"copyleft": "1F12F",
"copyright": "00A9", "copyright": "00A9",
"couchandlamp": "1F6CB", "couchandlamp": "1F6CB",
"couplewithheart": "1F491", "couplewithheart": "1F491",

View File

@ -1379,6 +1379,10 @@ if args.actor:
nickname = args.actor.split('/accounts/')[1] nickname = args.actor.split('/accounts/')[1]
nickname = nickname.replace('\n', '').replace('\r', '') nickname = nickname.replace('\n', '').replace('\r', '')
domain = args.actor.split('/accounts/')[0] domain = args.actor.split('/accounts/')[0]
elif '/u/' in args.actor:
nickname = args.actor.split('/u/')[1]
nickname = nickname.replace('\n', '').replace('\r', '')
domain = args.actor.split('/u/')[0]
else: else:
# format: @nick@domain # format: @nick@domain
if '@' not in args.actor: if '@' not in args.actor:
@ -1445,6 +1449,7 @@ if args.actor:
personUrl = personUrl.replace('/accounts/', '/actor/') personUrl = personUrl.replace('/accounts/', '/actor/')
personUrl = personUrl.replace('/channel/', '/actor/') personUrl = personUrl.replace('/channel/', '/actor/')
personUrl = personUrl.replace('/profile/', '/actor/') personUrl = personUrl.replace('/profile/', '/actor/')
personUrl = personUrl.replace('/u/', '/actor/')
if not personUrl: if not personUrl:
# try single user instance # try single user instance
personUrl = httpPrefix + '://' + domain personUrl = httpPrefix + '://' + domain
@ -1508,6 +1513,10 @@ if args.followers:
nickname = args.followers.split('/accounts/')[1] nickname = args.followers.split('/accounts/')[1]
nickname = nickname.replace('\n', '').replace('\r', '') nickname = nickname.replace('\n', '').replace('\r', '')
domain = args.followers.split('/accounts/')[0] domain = args.followers.split('/accounts/')[0]
elif '/u/' in args.followers:
nickname = args.followers.split('/u/')[1]
nickname = nickname.replace('\n', '').replace('\r', '')
domain = args.followers.split('/u/')[0]
else: else:
# format: @nick@domain # format: @nick@domain
if '@' not in args.followers: if '@' not in args.followers:
@ -1572,6 +1581,7 @@ if args.followers:
personUrl = personUrl.replace('/accounts/', '/actor/') personUrl = personUrl.replace('/accounts/', '/actor/')
personUrl = personUrl.replace('/channel/', '/actor/') personUrl = personUrl.replace('/channel/', '/actor/')
personUrl = personUrl.replace('/profile/', '/actor/') personUrl = personUrl.replace('/profile/', '/actor/')
personUrl = personUrl.replace('/u/', '/actor/')
if not personUrl: if not personUrl:
# try single user instance # try single user instance
personUrl = httpPrefix + '://' + domain personUrl = httpPrefix + '://' + domain

View File

@ -204,6 +204,9 @@ def isFollowerOfPerson(baseDir: str, nickname: str, domain: str,
elif '://' + followerDomain + \ elif '://' + followerDomain + \
'/accounts/' + followerNickname in followersStr: '/accounts/' + followerNickname in followersStr:
alreadyFollowing = True alreadyFollowing = True
elif '://' + followerDomain + \
'/u/' + followerNickname in followersStr:
alreadyFollowing = True
return alreadyFollowing return alreadyFollowing
@ -542,6 +545,8 @@ def _storeFollowRequest(baseDir: str,
alreadyFollowing = True alreadyFollowing = True
elif '://' + domainFull + '/accounts/' + nickname in followersStr: elif '://' + domainFull + '/accounts/' + nickname in followersStr:
alreadyFollowing = True alreadyFollowing = True
elif '://' + domainFull + '/u/' + nickname in followersStr:
alreadyFollowing = True
if alreadyFollowing: if alreadyFollowing:
if debug: if debug:
@ -598,7 +603,8 @@ def receiveFollowRequest(session, baseDir: str, httpPrefix: str,
"""Receives a follow request within the POST section of HTTPServer """Receives a follow request within the POST section of HTTPServer
""" """
if not messageJson['type'].startswith('Follow'): if not messageJson['type'].startswith('Follow'):
return False if not messageJson['type'].startswith('Join'):
return False
print('Receiving follow request') print('Receiving follow request')
if not messageJson.get('actor'): if not messageJson.get('actor'):
if debug: if debug:
@ -866,6 +872,7 @@ def followedAccountRejects(session, baseDir: str, httpPrefix: str,
def sendFollowRequest(session, baseDir: str, def sendFollowRequest(session, baseDir: str,
nickname: str, domain: str, port: int, httpPrefix: str, nickname: str, domain: str, port: int, httpPrefix: str,
followNickname: str, followDomain: str, followNickname: str, followDomain: str,
followedActor: str,
followPort: int, followHttpPrefix: str, followPort: int, followHttpPrefix: str,
clientToServer: bool, federationList: [], clientToServer: bool, federationList: [],
sendThreads: [], postLog: [], cachedWebfingers: {}, sendThreads: [], postLog: [], cachedWebfingers: {},
@ -874,6 +881,7 @@ def sendFollowRequest(session, baseDir: str,
"""Gets the json object for sending a follow request """Gets the json object for sending a follow request
""" """
if not domainPermitted(followDomain, federationList): if not domainPermitted(followDomain, federationList):
print('You are not permitted to follow the domain ' + followDomain)
return None return None
fullDomain = getFullDomain(domain, port) fullDomain = getFullDomain(domain, port)
@ -884,8 +892,7 @@ def sendFollowRequest(session, baseDir: str,
statusNumber, published = getStatusNumber() statusNumber, published = getStatusNumber()
if followNickname: if followNickname:
followedId = followHttpPrefix + '://' + \ followedId = followedActor
requestDomain + '/users/' + followNickname
followHandle = followNickname + '@' + requestDomain followHandle = followNickname + '@' + requestDomain
else: else:
if debug: if debug:
@ -1162,7 +1169,8 @@ def outboxUndoFollow(baseDir: str, messageJson: {}, debug: bool) -> None:
if not messageJson['object'].get('type'): if not messageJson['object'].get('type'):
return return
if not messageJson['object']['type'] == 'Follow': if not messageJson['object']['type'] == 'Follow':
return if not messageJson['object']['type'] == 'Join':
return
if not messageJson['object'].get('object'): if not messageJson['object'].get('object'):
return return
if not messageJson['object'].get('actor'): if not messageJson['object'].get('actor'):

Binary file not shown.

Before

Width:  |  Height:  |  Size: 45 KiB

After

Width:  |  Height:  |  Size: 29 KiB

View File

@ -105,6 +105,8 @@ def storeHashTags(baseDir: str, nickname: str, postJsonObject: {}) -> None:
for tag in postJsonObject['object']['tag']: for tag in postJsonObject['object']['tag']:
if not tag.get('type'): if not tag.get('type'):
continue continue
if not isinstance(tag['type'], str):
continue
if tag['type'] != 'Hashtag': if tag['type'] != 'Hashtag':
continue continue
if not tag.get('name'): if not tag.get('name'):
@ -274,8 +276,30 @@ def inboxMessageHasParams(messageJson: {}) -> bool:
# print('inboxMessageHasParams: ' + # print('inboxMessageHasParams: ' +
# param + ' ' + str(messageJson)) # param + ' ' + str(messageJson))
return False return False
# actor should be a string
if not isinstance(messageJson['actor'], str):
print('WARN: actor should be a string, but is actually: ' +
str(messageJson['actor']))
return False
# type should be a string
if not isinstance(messageJson['type'], str):
print('WARN: type from ' + str(messageJson['actor']) +
' should be a string, but is actually: ' +
str(messageJson['type']))
return False
# object should be a dict or a string
if not isinstance(messageJson['object'], dict):
if not isinstance(messageJson['object'], str):
print('WARN: object from ' + str(messageJson['actor']) +
' should be a dict or string, but is actually: ' +
str(messageJson['object']))
return False
if not messageJson.get('to'): if not messageJson.get('to'):
allowedWithoutToParam = ['Like', 'Follow', 'Request', allowedWithoutToParam = ['Like', 'Follow', 'Join', 'Request',
'Accept', 'Capability', 'Undo'] 'Accept', 'Capability', 'Undo']
if messageJson['type'] not in allowedWithoutToParam: if messageJson['type'] not in allowedWithoutToParam:
return False return False
@ -297,7 +321,7 @@ def inboxPermittedMessage(domain: str, messageJson: {},
if not urlPermitted(actor, federationList): if not urlPermitted(actor, federationList):
return False return False
alwaysAllowedTypes = ('Follow', 'Like', 'Delete', 'Announce') alwaysAllowedTypes = ('Follow', 'Join', 'Like', 'Delete', 'Announce')
if messageJson['type'] not in alwaysAllowedTypes: if messageJson['type'] not in alwaysAllowedTypes:
if not messageJson.get('object'): if not messageJson.get('object'):
return True return True
@ -693,7 +717,8 @@ def _receiveUndo(session, baseDir: str, httpPrefix: str,
print('DEBUG: ' + messageJson['type'] + print('DEBUG: ' + messageJson['type'] +
' object within object is not a string') ' object within object is not a string')
return False return False
if messageJson['object']['type'] == 'Follow': if messageJson['object']['type'] == 'Follow' or \
messageJson['object']['type'] == 'Join':
return _receiveUndoFollow(session, baseDir, httpPrefix, return _receiveUndoFollow(session, baseDir, httpPrefix,
port, messageJson, port, messageJson,
federationList, debug) federationList, debug)
@ -731,7 +756,7 @@ def _personReceiveUpdate(baseDir: str,
' ' + str(personJson)) ' ' + str(personJson))
domainFull = getFullDomain(domain, port) domainFull = getFullDomain(domain, port)
updateDomainFull = getFullDomain(updateDomain, updatePort) updateDomainFull = getFullDomain(updateDomain, updatePort)
usersPaths = ('users', 'profile', 'channel', 'accounts') usersPaths = ('users', 'profile', 'channel', 'accounts', 'u')
usersStrFound = False usersStrFound = False
for usersStr in usersPaths: for usersStr in usersPaths:
actor = updateDomainFull + '/' + usersStr + '/' + updateNickname actor = updateDomainFull + '/' + usersStr + '/' + updateNickname

View File

@ -120,6 +120,9 @@ def manualApproveFollowRequest(session, baseDir: str,
elif reqPrefix + '/accounts/' + reqNick in approveFollowsStr: elif reqPrefix + '/accounts/' + reqNick in approveFollowsStr:
exists = True exists = True
approveHandleFull = reqPrefix + '/accounts/' + reqNick approveHandleFull = reqPrefix + '/accounts/' + reqNick
elif reqPrefix + '/u/' + reqNick in approveFollowsStr:
exists = True
approveHandleFull = reqPrefix + '/u/' + reqNick
if not exists: if not exists:
print('Manual follow accept: ' + approveHandleFull + print('Manual follow accept: ' + approveHandleFull +
' not in requests file "' + ' not in requests file "' +

View File

@ -2414,7 +2414,8 @@ def sendToNamedAddresses(session, baseDir: str,
print('DEBUG: ' + print('DEBUG: ' +
'no "to" field when sending to named addresses') 'no "to" field when sending to named addresses')
if postJsonObject['object'].get('type'): if postJsonObject['object'].get('type'):
if postJsonObject['object']['type'] == 'Follow': if postJsonObject['object']['type'] == 'Follow' or \
postJsonObject['object']['type'] == 'Join':
if isinstance(postJsonObject['object']['object'], str): if isinstance(postJsonObject['object']['object'], str):
if debug: if debug:
print('DEBUG: "to" field assigned to Follow') print('DEBUG: "to" field assigned to Follow')

View File

@ -40,6 +40,7 @@ from numbers import Integral, Real
from context import getApschemaV1_9 from context import getApschemaV1_9
from context import getApschemaV1_21 from context import getApschemaV1_21
from context import getLitepubV0_1 from context import getLitepubV0_1
from context import getLitepubSocial
from context import getV1Schema from context import getV1Schema
from context import getV1SecuritySchema from context import getV1SecuritySchema
from context import getActivitystreamsSchema from context import getActivitystreamsSchema
@ -420,6 +421,13 @@ def load_document(url):
'document': getLitepubV0_1() 'document': getLitepubV0_1()
} }
return doc return doc
elif url == 'https://litepub.social/litepub/context.jsonld':
doc = {
'contextUrl': None,
'documentUrl': url,
'document': getLitepubSocial()
}
return doc
return None return None
except JsonLdError as e: except JsonLdError as e:
raise e raise e

View File

@ -1,6 +1,6 @@
[metadata] [metadata]
name = epicyon name = epicyon
version = 1.2.0 version = 1.3.0
author = Bob Mottram author = Bob Mottram
author_email = bob@freedombone.net author_email = bob@freedombone.net
maintainer = Bob Mottram maintainer = Bob Mottram

View File

@ -76,6 +76,7 @@ from inbox import jsonPostAllowsComments
from inbox import validInbox from inbox import validInbox
from inbox import validInboxFilenames from inbox import validInboxFilenames
from categories import guessHashtagCategory from categories import guessHashtagCategory
from content import validHashTag
from content import htmlReplaceEmailQuote from content import htmlReplaceEmailQuote
from content import htmlReplaceQuoteMarks from content import htmlReplaceQuoteMarks
from content import dangerousCSS from content import dangerousCSS
@ -848,10 +849,12 @@ def testFollowBetweenServers():
alicePersonCache = {} alicePersonCache = {}
aliceCachedWebfingers = {} aliceCachedWebfingers = {}
alicePostLog = [] alicePostLog = []
bobActor = httpPrefix + '://' + bobAddress + '/users/bob'
sendResult = \ sendResult = \
sendFollowRequest(sessionAlice, aliceDir, sendFollowRequest(sessionAlice, aliceDir,
'alice', aliceDomain, alicePort, httpPrefix, 'alice', aliceDomain, alicePort, httpPrefix,
'bob', bobDomain, bobPort, httpPrefix, 'bob', bobDomain, bobActor,
bobPort, httpPrefix,
clientToServer, federationList, clientToServer, federationList,
aliceSendThreads, alicePostLog, aliceSendThreads, alicePostLog,
aliceCachedWebfingers, alicePersonCache, aliceCachedWebfingers, alicePersonCache,
@ -3088,9 +3091,25 @@ def testPrepareHtmlPostNickname():
assert result == expectedHtml assert result == expectedHtml
def testValidHashTag():
print('testValidHashTag')
assert validHashTag('ThisIsValid')
assert validHashTag('ThisIsValid12345')
assert validHashTag('ThisIsVälid')
assert validHashTag('यहमान्यहै')
assert not validHashTag('ThisIsNotValid!')
assert not validHashTag('#ThisIsAlsoNotValid')
assert not validHashTag('#यहमान्यहै')
assert not validHashTag('ThisIsAlso&NotValid')
assert not validHashTag('ThisIsAlsoNotValid"')
assert not validHashTag('This Is Also Not Valid"')
assert not validHashTag('This=IsAlsoNotValid"')
def runAllTests(): def runAllTests():
print('Running tests...') print('Running tests...')
testFunctions() testFunctions()
testValidHashTag()
testPrepareHtmlPostNickname() testPrepareHtmlPostNickname()
testDomainHandling() testDomainHandling()
testMastoApi() testMastoApi()

View File

@ -67,7 +67,7 @@ def getLockedAccount(actorJson: {}) -> bool:
def hasUsersPath(pathStr: str) -> bool: def hasUsersPath(pathStr: str) -> bool:
"""Whether there is a /users/ path (or equivalent) in the given string """Whether there is a /users/ path (or equivalent) in the given string
""" """
usersList = ('users', 'accounts', 'channel', 'profile') usersList = ('users', 'accounts', 'channel', 'profile', 'u')
for usersStr in usersList: for usersStr in usersList:
if '/' + usersStr + '/' in pathStr: if '/' + usersStr + '/' in pathStr:
return True return True
@ -656,6 +656,12 @@ def getNicknameFromActor(actor: str) -> str:
return nickStr return nickStr
else: else:
return nickStr.split('/')[0] return nickStr.split('/')[0]
elif '/u/' in actor:
nickStr = actor.split('/u/')[1].replace('@', '')
if '/' not in nickStr:
return nickStr
else:
return nickStr.split('/')[0]
elif '/@' in actor: elif '/@' in actor:
# https://domain/@nick # https://domain/@nick
nickStr = actor.split('/@')[1] nickStr = actor.split('/@')[1]
@ -696,6 +702,10 @@ def getDomainFromActor(actor: str) -> (str, int):
domain = actor.split('/users/')[0] domain = actor.split('/users/')[0]
for prefix in prefixes: for prefix in prefixes:
domain = domain.replace(prefix, '') domain = domain.replace(prefix, '')
elif '/u/' in actor:
domain = actor.split('/u/')[0]
for prefix in prefixes:
domain = domain.replace(prefix, '')
elif '/@' in actor: elif '/@' in actor:
domain = actor.split('/@')[0] domain = actor.split('/@')[0]
for prefix in prefixes: for prefix in prefixes:
@ -1163,14 +1173,58 @@ def deletePost(baseDir: str, httpPrefix: str,
os.remove(postFilename) os.remove(postFilename)
def validNickname(domain: str, nickname: str) -> bool: def isValidLanguage(text: str) -> bool:
forbiddenChars = ('.', ' ', '/', '?', ':', ';', '@', '#') """Returns true if the given text contains a valid
for c in forbiddenChars: natural language string
if c in nickname: """
return False naturalLanguages = {
# this should only apply for the shared inbox "Latin": [65, 866],
if nickname == domain: "Cyrillic": [1024, 1274],
return False "Greek": [880, 1280],
"isArmenian": [1328, 1424],
"isHebrew": [1424, 1536],
"Arabic": [1536, 1792],
"Syriac": [1792, 1872],
"Thaan": [1920, 1984],
"Devanagari": [2304, 2432],
"Bengali": [2432, 2560],
"Gurmukhi": [2560, 2688],
"Gujarati": [2688, 2816],
"Oriya": [2816, 2944],
"Tamil": [2944, 3072],
"Telugu": [3072, 3200],
"Kannada": [3200, 3328],
"Malayalam": [3328, 3456],
"Sinhala": [3456, 3584],
"Thai": [3584, 3712],
"Lao": [3712, 3840],
"Tibetan": [3840, 4096],
"Myanmar": [4096, 4256],
"Georgian": [4256, 4352],
"HangulJamo": [4352, 4608],
"Cherokee": [5024, 5120],
"UCAS": [5120, 5760],
"Ogham": [5760, 5792],
"Runic": [5792, 5888],
"Khmer": [6016, 6144],
"Mongolian": [6144, 6320]
}
for langName, langRange in naturalLanguages.items():
okLang = True
for ch in text:
if ch.isdigit():
continue
if ord(ch) not in range(langRange[0], langRange[1]):
okLang = False
break
if okLang:
return True
return False
def _isReservedName(nickname: str) -> bool:
"""Is the given nickname reserved for some special function?
"""
reservedNames = ('inbox', 'dm', 'outbox', 'following', reservedNames = ('inbox', 'dm', 'outbox', 'following',
'public', 'followers', 'category', 'public', 'followers', 'category',
'channel', 'calendar', 'channel', 'calendar',
@ -1180,10 +1234,27 @@ def validNickname(domain: str, nickname: str) -> bool:
'activity', 'undo', 'pinned', 'activity', 'undo', 'pinned',
'reply', 'replies', 'question', 'like', 'reply', 'replies', 'question', 'like',
'likes', 'users', 'statuses', 'tags', 'likes', 'users', 'statuses', 'tags',
'accounts', 'channels', 'profile', 'accounts', 'channels', 'profile', 'u',
'updates', 'repeat', 'announce', 'updates', 'repeat', 'announce',
'shares', 'fonts', 'icons', 'avatars') 'shares', 'fonts', 'icons', 'avatars')
if nickname in reservedNames: if nickname in reservedNames:
return True
return False
def validNickname(domain: str, nickname: str) -> bool:
"""Is the given nickname valid?
"""
if not isValidLanguage(nickname):
return False
forbiddenChars = ('.', ' ', '/', '?', ':', ';', '@', '#')
for c in forbiddenChars:
if c in nickname:
return False
# this should only apply for the shared inbox
if nickname == domain:
return False
if _isReservedName(nickname):
return False return False
return True return True

View File

@ -136,12 +136,12 @@ def _htmlNewPostDropDown(scopeIcon: str, scopeDescription: str,
'icons/scope_reminder.png"/><b>' + \ 'icons/scope_reminder.png"/><b>' + \
translate['Reminder'] + '</b><br>' + \ translate['Reminder'] + '</b><br>' + \
translate['Scheduled note to yourself'] + '</a></li>\n' translate['Scheduled note to yourself'] + '</a></li>\n'
dropDownContent += \ # dropDownContent += \
'<li><a href="' + pathBase + dropdownEventSuffix + \ # '<li><a href="' + pathBase + dropdownEventSuffix + \
'"><img loading="lazy" alt="" title="" src="/' + \ # '"><img loading="lazy" alt="" title="" src="/' + \
'icons/scope_event.png"/><b>' + \ # 'icons/scope_event.png"/><b>' + \
translate['Event'] + '</b><br>' + \ # translate['Event'] + '</b><br>' + \
translate['Create an event'] + '</a></li>\n' # translate['Create an event'] + '</a></li>\n'
dropDownContent += \ dropDownContent += \
'<li><a href="' + pathBase + dropdownReportSuffix + \ '<li><a href="' + pathBase + dropdownReportSuffix + \
'"><img loading="lazy" alt="" title="" src="/' + \ '"><img loading="lazy" alt="" title="" src="/' + \
@ -198,7 +198,9 @@ def htmlNewPost(cssCache: {}, mediaInstance: bool, translate: {},
newPostText = \ newPostText = \
'<p class="new-post-text">' + \ '<p class="new-post-text">' + \
translate['Write your reply to'] + \ translate['Write your reply to'] + \
' <a href="' + inReplyTo + '">' + \ ' <a href="' + inReplyTo + \
'" rel="nofollow noopener noreferrer" ' + \
'target="_blank">' + \
translate['this post'] + '</a></p>\n' translate['this post'] + '</a></p>\n'
replyStr = '<input type="hidden" ' + \ replyStr = '<input type="hidden" ' + \
'name="replyTo" value="' + inReplyTo + '">\n' 'name="replyTo" value="' + inReplyTo + '">\n'

View File

@ -154,6 +154,11 @@ def htmlHashTagSwarm(baseDir: str, actor: str, translate: {}) -> str:
if len(hashTagName) > maxTagLength: if len(hashTagName) > maxTagLength:
# NoIncrediblyLongAndBoringHashtagsShownHere # NoIncrediblyLongAndBoringHashtagsShownHere
continue continue
if '#' in hashTagName or \
'&' in hashTagName or \
'"' in hashTagName or \
"'" in hashTagName:
continue
if '#' + hashTagName + '\n' in blockedStr: if '#' + hashTagName + '\n' in blockedStr:
continue continue
with open(tagsFilename, 'r') as fp: with open(tagsFilename, 'r') as fp:

View File

@ -158,7 +158,7 @@ def htmlTimeline(cssCache: {}, defaultTimeline: str,
repliesButton = 'buttonhighlighted' repliesButton = 'buttonhighlighted'
mediaButton = 'button' mediaButton = 'button'
bookmarksButton = 'button' bookmarksButton = 'button'
eventsButton = 'button' # eventsButton = 'button'
sentButton = 'button' sentButton = 'button'
sharesButton = 'button' sharesButton = 'button'
if newShare: if newShare:
@ -196,8 +196,8 @@ def htmlTimeline(cssCache: {}, defaultTimeline: str,
sharesButton = 'buttonselectedhighlighted' sharesButton = 'buttonselectedhighlighted'
elif boxName == 'tlbookmarks' or boxName == 'bookmarks': elif boxName == 'tlbookmarks' or boxName == 'bookmarks':
bookmarksButton = 'buttonselected' bookmarksButton = 'buttonselected'
elif boxName == 'tlevents': # elif boxName == 'tlevents':
eventsButton = 'buttonselected' # eventsButton = 'buttonselected'
# get the full domain, including any port number # get the full domain, including any port number
fullDomain = getFullDomain(domain, port) fullDomain = getFullDomain(domain, port)
@ -254,11 +254,11 @@ def htmlTimeline(cssCache: {}, defaultTimeline: str,
'<a href="' + usersPath + '/tlbookmarks"><button class="' + \ '<a href="' + usersPath + '/tlbookmarks"><button class="' + \
bookmarksButton + '"><span>' + translate['Bookmarks'] + \ bookmarksButton + '"><span>' + translate['Bookmarks'] + \
'</span></button></a>' '</span></button></a>'
#
eventsButtonStr = \ # eventsButtonStr = \
'<a href="' + usersPath + '/tlevents"><button class="' + \ # '<a href="' + usersPath + '/tlevents"><button class="' + \
eventsButton + '"><span>' + translate['Events'] + \ # eventsButton + '"><span>' + translate['Events'] + \
'</span></button></a>' # '</span></button></a>'
instanceTitle = \ instanceTitle = \
getConfigParam(baseDir, 'instanceTitle') getConfigParam(baseDir, 'instanceTitle')
@ -400,8 +400,8 @@ def htmlTimeline(cssCache: {}, defaultTimeline: str,
translate['Bookmarks'] translate['Bookmarks']
menuShares = \ menuShares = \
htmlHideFromScreenReader('🤝') + ' ' + sharesStr htmlHideFromScreenReader('🤝') + ' ' + sharesStr
menuEvents = \ # menuEvents = \
htmlHideFromScreenReader('🎫') + ' ' + translate['Events'] # htmlHideFromScreenReader('🎫') + ' ' + translate['Events']
menuBlogs = \ menuBlogs = \
htmlHideFromScreenReader('📝') + ' ' + translate['Blogs'] htmlHideFromScreenReader('📝') + ' ' + translate['Blogs']
menuNewswire = \ menuNewswire = \
@ -426,7 +426,7 @@ def htmlTimeline(cssCache: {}, defaultTimeline: str,
menuBookmarks: usersPath + '/tlbookmarks#timeline', menuBookmarks: usersPath + '/tlbookmarks#timeline',
menuShares: usersPath + '/tlshares#timeline', menuShares: usersPath + '/tlshares#timeline',
menuBlogs: usersPath + '/tlblogs#timeline', menuBlogs: usersPath + '/tlblogs#timeline',
menuEvents: usersPath + '/tlevents#timeline', # menuEvents: usersPath + '/tlevents#timeline',
menuNewswire: '#newswire', menuNewswire: '#newswire',
menuLinks: '#links' menuLinks: '#links'
} }