mirror of https://gitlab.com/bashrc2/epicyon
Merge
commit
98300fb5f9
|
|
@ -3,6 +3,6 @@ image: debian:testing
|
|||
test:
|
||||
script:
|
||||
- apt-get update
|
||||
- apt-get install -y python3-crypto python3-dateutil python3-idna python3-numpy python3-pil.imagetk python3-pycryptodome python3-requests python3-socks python3-setuptools python3-pyqrcode
|
||||
- apt-get install -y python3-cryptography python3-dateutil python3-idna python3-numpy python3-pil.imagetk python3-requests python3-socks python3-setuptools python3-pyqrcode
|
||||
- python3 epicyon.py --tests
|
||||
- python3 epicyon.py --testsnetwork
|
||||
|
|
|
|||
103
blocking.py
103
blocking.py
|
|
@ -7,6 +7,9 @@ __email__ = "bob@freedombone.net"
|
|||
__status__ = "Production"
|
||||
|
||||
import os
|
||||
from datetime import datetime
|
||||
from utils import fileLastModified
|
||||
from utils import setConfigParam
|
||||
from utils import hasUsersPath
|
||||
from utils import getFullDomain
|
||||
from utils import removeIdEnding
|
||||
|
|
@ -175,6 +178,9 @@ def isBlockedDomain(baseDir: str, domain: str) -> bool:
|
|||
if noOfSections > 2:
|
||||
shortDomain = domain[noOfSections-2] + '.' + domain[noOfSections-1]
|
||||
|
||||
allowFilename = baseDir + '/accounts/allowedinstances.txt'
|
||||
if not os.path.isfile(allowFilename):
|
||||
# instance block list
|
||||
globalBlockingFilename = baseDir + '/accounts/blocking.txt'
|
||||
if os.path.isfile(globalBlockingFilename):
|
||||
with open(globalBlockingFilename, 'r') as fpBlocked:
|
||||
|
|
@ -184,6 +190,15 @@ def isBlockedDomain(baseDir: str, domain: str) -> bool:
|
|||
if shortDomain:
|
||||
if '*@' + shortDomain in blockedStr:
|
||||
return True
|
||||
else:
|
||||
# instance allow list
|
||||
if not shortDomain:
|
||||
if domain not in open(allowFilename).read():
|
||||
return True
|
||||
else:
|
||||
if shortDomain not in open(allowFilename).read():
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
|
||||
|
|
@ -344,3 +359,91 @@ def outboxUndoBlock(baseDir: str, httpPrefix: str,
|
|||
nicknameBlocked, domainBlockedFull)
|
||||
if debug:
|
||||
print('DEBUG: post undo blocked via c2s - ' + postFilename)
|
||||
|
||||
|
||||
def setBrochMode(baseDir: str, domainFull: str, enabled: bool) -> None:
|
||||
"""Broch mode can be used to lock down the instance during
|
||||
a period of time when it is temporarily under attack.
|
||||
For example, where an adversary is constantly spinning up new
|
||||
instances.
|
||||
It surveys the following lists of all accounts and uses that
|
||||
to construct an instance level allow list. Anything arriving
|
||||
which is then not from one of the allowed domains will be dropped
|
||||
"""
|
||||
allowFilename = baseDir + '/accounts/allowedinstances.txt'
|
||||
|
||||
if not enabled:
|
||||
# remove instance allow list
|
||||
if os.path.isfile(allowFilename):
|
||||
os.remove(allowFilename)
|
||||
print('Broch mode turned off')
|
||||
else:
|
||||
if os.path.isfile(allowFilename):
|
||||
lastModified = fileLastModified(allowFilename)
|
||||
print('Broch mode already activated ' + lastModified)
|
||||
return
|
||||
# generate instance allow list
|
||||
allowedDomains = [domainFull]
|
||||
followFiles = ('following.txt', 'followers.txt')
|
||||
for subdir, dirs, files in os.walk(baseDir + '/accounts'):
|
||||
for acct in dirs:
|
||||
if '@' not in acct:
|
||||
continue
|
||||
if 'inbox@' in acct or 'news@' in acct:
|
||||
continue
|
||||
accountDir = os.path.join(baseDir + '/accounts', acct)
|
||||
for followFileType in followFiles:
|
||||
followingFilename = accountDir + '/' + followFileType
|
||||
if not os.path.isfile(followingFilename):
|
||||
continue
|
||||
with open(followingFilename, "r") as f:
|
||||
followList = f.readlines()
|
||||
for handle in followList:
|
||||
if '@' not in handle:
|
||||
continue
|
||||
handle = handle.replace('\n', '')
|
||||
handleDomain = handle.split('@')[1]
|
||||
if handleDomain not in allowedDomains:
|
||||
allowedDomains.append(handleDomain)
|
||||
break
|
||||
|
||||
# write the allow file
|
||||
allowFile = open(allowFilename, "w+")
|
||||
if allowFile:
|
||||
allowFile.write(domainFull + '\n')
|
||||
for d in allowedDomains:
|
||||
allowFile.write(d + '\n')
|
||||
allowFile.close()
|
||||
print('Broch mode enabled')
|
||||
|
||||
setConfigParam(baseDir, "brochMode", enabled)
|
||||
|
||||
|
||||
def brochModeLapses(baseDir: str, lapseDays=7) -> bool:
|
||||
"""After broch mode is enabled it automatically
|
||||
elapses after a period of time
|
||||
"""
|
||||
allowFilename = baseDir + '/accounts/allowedinstances.txt'
|
||||
if not os.path.isfile(allowFilename):
|
||||
return False
|
||||
lastModified = fileLastModified(allowFilename)
|
||||
modifiedDate = None
|
||||
brochMode = True
|
||||
try:
|
||||
modifiedDate = \
|
||||
datetime.strptime(lastModified, "%Y-%m-%dT%H:%M:%SZ")
|
||||
except BaseException:
|
||||
return brochMode
|
||||
if not modifiedDate:
|
||||
return brochMode
|
||||
currTime = datetime.datetime.utcnow()
|
||||
daysSinceBroch = (currTime - modifiedDate).days
|
||||
if daysSinceBroch >= lapseDays:
|
||||
try:
|
||||
os.remove(allowFilename)
|
||||
brochMode = False
|
||||
setConfigParam(baseDir, "brochMode", brochMode)
|
||||
print('Broch mode has elapsed')
|
||||
except BaseException:
|
||||
pass
|
||||
return brochMode
|
||||
|
|
|
|||
66
daemon.py
66
daemon.py
|
|
@ -109,6 +109,7 @@ from threads import threadWithTrace
|
|||
from threads import removeDormantThreads
|
||||
from media import replaceYouTube
|
||||
from media import attachMedia
|
||||
from blocking import setBrochMode
|
||||
from blocking import addBlock
|
||||
from blocking import removeBlock
|
||||
from blocking import addGlobalBlock
|
||||
|
|
@ -185,6 +186,7 @@ from shares import addShare
|
|||
from shares import removeShare
|
||||
from shares import expireShares
|
||||
from categories import setHashtagCategory
|
||||
from utils import getLocalNetworkAddresses
|
||||
from utils import decodedHost
|
||||
from utils import isPublicPost
|
||||
from utils import getLockedAccount
|
||||
|
|
@ -478,6 +480,10 @@ class PubServer(BaseHTTPRequestHandler):
|
|||
if 'text/html' not in self.headers['Accept']:
|
||||
return False
|
||||
if self.headers['Accept'].startswith('*'):
|
||||
if self.headers.get('User-Agent'):
|
||||
if 'ELinks' in self.headers['User-Agent'] or \
|
||||
'Lynx' in self.headers['User-Agent']:
|
||||
return True
|
||||
return False
|
||||
if 'json' in self.headers['Accept']:
|
||||
return False
|
||||
|
|
@ -1153,7 +1159,38 @@ class PubServer(BaseHTTPRequestHandler):
|
|||
|
||||
# check for blocked domains so that they can be rejected early
|
||||
messageDomain = None
|
||||
if messageJson.get('actor'):
|
||||
if not messageJson.get('actor'):
|
||||
print('Message arriving at inbox queue has no actor')
|
||||
self._400()
|
||||
self.server.POSTbusy = False
|
||||
return 3
|
||||
|
||||
# actor should be a string
|
||||
if not isinstance(messageJson['actor'], str):
|
||||
self._400()
|
||||
self.server.POSTbusy = False
|
||||
return 3
|
||||
|
||||
# actor should look like a url
|
||||
if '://' not in messageJson['actor'] or \
|
||||
'.' not in messageJson['actor']:
|
||||
print('POST actor does not look like a url ' +
|
||||
messageJson['actor'])
|
||||
self._400()
|
||||
self.server.POSTbusy = False
|
||||
return 3
|
||||
|
||||
# sent by an actor on a local network address?
|
||||
if not self.server.allowLocalNetworkAccess:
|
||||
localNetworkPatternList = getLocalNetworkAddresses()
|
||||
for localNetworkPattern in localNetworkPatternList:
|
||||
if localNetworkPattern in messageJson['actor']:
|
||||
print('POST actor contains local network address ' +
|
||||
messageJson['actor'])
|
||||
self._400()
|
||||
self.server.POSTbusy = False
|
||||
return 3
|
||||
|
||||
messageDomain, messagePort = \
|
||||
getDomainFromActor(messageJson['actor'])
|
||||
if isBlockedDomain(self.server.baseDir, messageDomain):
|
||||
|
|
@ -1161,11 +1198,6 @@ class PubServer(BaseHTTPRequestHandler):
|
|||
self._400()
|
||||
self.server.POSTbusy = False
|
||||
return 3
|
||||
else:
|
||||
print('Message arriving at inbox queue has no actor')
|
||||
self._400()
|
||||
self.server.POSTbusy = False
|
||||
return 3
|
||||
|
||||
# if the inbox queue is full then return a busy code
|
||||
if len(self.server.inboxQueue) >= self.server.maxQueueLength:
|
||||
|
|
@ -4512,6 +4544,18 @@ class PubServer(BaseHTTPRequestHandler):
|
|||
setConfigParam(baseDir, "verifyAllSignatures",
|
||||
verifyAllSignatures)
|
||||
|
||||
brochMode = False
|
||||
if fields.get('brochMode'):
|
||||
if fields['brochMode'] == 'on':
|
||||
brochMode = True
|
||||
currBrochMode = \
|
||||
getConfigParam(baseDir, "brochMode")
|
||||
if brochMode != currBrochMode:
|
||||
setBrochMode(self.server.baseDir,
|
||||
self.server.domainFull,
|
||||
brochMode)
|
||||
setConfigParam(baseDir, "brochMode", brochMode)
|
||||
|
||||
# change moderators list
|
||||
if fields.get('moderators'):
|
||||
if path.startswith('/users/' +
|
||||
|
|
@ -10099,9 +10143,13 @@ class PubServer(BaseHTTPRequestHandler):
|
|||
|
||||
# manifest for progressive web apps
|
||||
if '/manifest.json' in self.path:
|
||||
if self._hasAccept(callingDomain):
|
||||
if not self._requestHTTP():
|
||||
self._progressiveWebAppManifest(callingDomain,
|
||||
GETstartTime, GETtimings)
|
||||
return
|
||||
else:
|
||||
self.path = '/'
|
||||
|
||||
# default newswire favicon, for links to sites which
|
||||
# have no favicon
|
||||
|
|
@ -13889,7 +13937,8 @@ def loadTokens(baseDir: str, tokensDict: {}, tokensLookup: {}) -> None:
|
|||
break
|
||||
|
||||
|
||||
def runDaemon(verifyAllSignatures: bool,
|
||||
def runDaemon(brochMode: bool,
|
||||
verifyAllSignatures: bool,
|
||||
sendThreadsTimeoutMins: int,
|
||||
dormantMonths: int,
|
||||
maxNewswirePosts: int,
|
||||
|
|
@ -14142,6 +14191,9 @@ def runDaemon(verifyAllSignatures: bool,
|
|||
# cache to store css files
|
||||
httpd.cssCache = {}
|
||||
|
||||
# whether to enable broch mode, which locks down the instance
|
||||
setBrochMode(baseDir, httpd.domainFull, brochMode)
|
||||
|
||||
if not os.path.isdir(baseDir + '/accounts/inbox@' + domain):
|
||||
print('Creating shared inbox: inbox@' + domain)
|
||||
createSharedInbox(baseDir, 'inbox', domain, port, httpPrefix)
|
||||
|
|
|
|||
|
|
@ -106,11 +106,11 @@
|
|||
--column-left-icons-margin: 0;
|
||||
--column-right-border-width: 0;
|
||||
--column-left-border-color: black;
|
||||
--column-left-icon-size: 20%;
|
||||
--column-left-icon-size: 2.1vw;
|
||||
--column-left-icon-size-mobile: 10%;
|
||||
--column-left-image-width-mobile: 40vw;
|
||||
--column-right-image-width-mobile: 100vw;
|
||||
--column-right-icon-size: 20%;
|
||||
--column-right-icon-size: 2.1vw;
|
||||
--column-right-icon-size-mobile: 10%;
|
||||
--newswire-date-color: white;
|
||||
--newswire-voted-background-color: black;
|
||||
|
|
|
|||
13
epicyon.py
13
epicyon.py
|
|
@ -279,6 +279,11 @@ parser.add_argument("--verifyAllSignatures",
|
|||
const=True, default=False,
|
||||
help="Whether to require that all incoming " +
|
||||
"posts have valid jsonld signatures")
|
||||
parser.add_argument("--brochMode",
|
||||
dest='brochMode',
|
||||
type=str2bool, nargs='?',
|
||||
const=True, default=False,
|
||||
help="Enable broch mode")
|
||||
parser.add_argument("--noapproval", type=str2bool, nargs='?',
|
||||
const=True, default=False,
|
||||
help="Allow followers without approval")
|
||||
|
|
@ -2292,6 +2297,11 @@ verifyAllSignatures = \
|
|||
if verifyAllSignatures is not None:
|
||||
args.verifyAllSignatures = bool(verifyAllSignatures)
|
||||
|
||||
brochMode = \
|
||||
getConfigParam(baseDir, 'brochMode')
|
||||
if brochMode is not None:
|
||||
args.brochMode = bool(brochMode)
|
||||
|
||||
YTDomain = getConfigParam(baseDir, 'youtubedomain')
|
||||
if YTDomain:
|
||||
if '://' in YTDomain:
|
||||
|
|
@ -2305,7 +2315,8 @@ if setTheme(baseDir, themeName, domain, args.allowLocalNetworkAccess):
|
|||
print('Theme set to ' + themeName)
|
||||
|
||||
if __name__ == "__main__":
|
||||
runDaemon(args.verifyAllSignatures,
|
||||
runDaemon(args.brochMode,
|
||||
args.verifyAllSignatures,
|
||||
args.sendThreadsTimeoutMins,
|
||||
args.dormantMonths,
|
||||
args.maxNewswirePosts,
|
||||
|
|
|
|||
3
inbox.py
3
inbox.py
|
|
@ -51,6 +51,7 @@ from bookmarks import updateBookmarksCollection
|
|||
from bookmarks import undoBookmarksCollectionEntry
|
||||
from blocking import isBlocked
|
||||
from blocking import isBlockedDomain
|
||||
from blocking import brochModeLapses
|
||||
from filters import isFiltered
|
||||
from utils import updateAnnounceCollection
|
||||
from utils import undoAnnounceCollectionEntry
|
||||
|
|
@ -2518,6 +2519,8 @@ def runInboxQueue(recentPostsCache: {}, maxRecentPosts: int,
|
|||
# heartbeat to monitor whether the inbox queue is running
|
||||
heartBeatCtr += 5
|
||||
if heartBeatCtr >= 10:
|
||||
# turn off broch mode after it has timed out
|
||||
brochModeLapses(baseDir)
|
||||
print('>>> Heartbeat Q:' + str(len(queue)) + ' ' +
|
||||
'{:%F %T}'.format(datetime.datetime.now()))
|
||||
heartBeatCtr = 0
|
||||
|
|
|
|||
18
outbox.py
18
outbox.py
|
|
@ -14,6 +14,7 @@ from posts import outboxMessageCreateWrap
|
|||
from posts import savePostToBox
|
||||
from posts import sendToFollowersThread
|
||||
from posts import sendToNamedAddresses
|
||||
from utils import getLocalNetworkAddresses
|
||||
from utils import getFullDomain
|
||||
from utils import removeIdEnding
|
||||
from utils import getDomainFromActor
|
||||
|
|
@ -114,6 +115,23 @@ def postMessageToOutbox(messageJson: {}, postToNickname: str,
|
|||
'Create does not have the "to" parameter ' +
|
||||
str(messageJson))
|
||||
return False
|
||||
|
||||
# actor should be a string
|
||||
if not isinstance(messageJson['actor'], str):
|
||||
return False
|
||||
|
||||
# actor should look like a url
|
||||
if '://' not in messageJson['actor'] or \
|
||||
'.' not in messageJson['actor']:
|
||||
return False
|
||||
|
||||
# sent by an actor on a local network address?
|
||||
if not allowLocalNetworkAccess:
|
||||
localNetworkPatternList = getLocalNetworkAddresses()
|
||||
for localNetworkPattern in localNetworkPatternList:
|
||||
if localNetworkPattern in messageJson['actor']:
|
||||
return False
|
||||
|
||||
testDomain, testPort = getDomainFromActor(messageJson['actor'])
|
||||
testDomain = getFullDomain(testDomain, testPort)
|
||||
if isBlockedDomain(baseDir, testDomain):
|
||||
|
|
|
|||
|
|
@ -0,0 +1,6 @@
|
|||
#!/usr/bin/python3
|
||||
|
||||
import setuptools
|
||||
|
||||
if __name__ == "__main__":
|
||||
setuptools.setup()
|
||||
12
tests.py
12
tests.py
|
|
@ -325,8 +325,10 @@ def createServerAlice(path: str, domain: str, port: int,
|
|||
sendThreadsTimeoutMins = 30
|
||||
maxFollowers = 10
|
||||
verifyAllSignatures = True
|
||||
brochMode = False
|
||||
print('Server running: Alice')
|
||||
runDaemon(verifyAllSignatures,
|
||||
runDaemon(brochMode,
|
||||
verifyAllSignatures,
|
||||
sendThreadsTimeoutMins,
|
||||
dormantMonths, maxNewswirePosts,
|
||||
allowLocalNetworkAccess,
|
||||
|
|
@ -420,8 +422,10 @@ def createServerBob(path: str, domain: str, port: int,
|
|||
sendThreadsTimeoutMins = 30
|
||||
maxFollowers = 10
|
||||
verifyAllSignatures = True
|
||||
brochMode = False
|
||||
print('Server running: Bob')
|
||||
runDaemon(verifyAllSignatures,
|
||||
runDaemon(brochMode,
|
||||
verifyAllSignatures,
|
||||
sendThreadsTimeoutMins,
|
||||
dormantMonths, maxNewswirePosts,
|
||||
allowLocalNetworkAccess,
|
||||
|
|
@ -469,8 +473,10 @@ def createServerEve(path: str, domain: str, port: int, federationList: [],
|
|||
sendThreadsTimeoutMins = 30
|
||||
maxFollowers = 10
|
||||
verifyAllSignatures = True
|
||||
brochMode = False
|
||||
print('Server running: Eve')
|
||||
runDaemon(verifyAllSignatures,
|
||||
runDaemon(brochMode,
|
||||
verifyAllSignatures,
|
||||
sendThreadsTimeoutMins,
|
||||
dormantMonths, maxNewswirePosts,
|
||||
allowLocalNetworkAccess,
|
||||
|
|
|
|||
|
|
@ -368,5 +368,6 @@
|
|||
"Skip to Newswire": "انتقل إلى Newswire",
|
||||
"Skip to Links": "تخطي إلى روابط الويب",
|
||||
"Publish a blog article": "نشر مقال بلوق",
|
||||
"Featured writer": "كاتب متميز"
|
||||
"Featured writer": "كاتب متميز",
|
||||
"Broch mode": "وضع الكتيب"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -368,5 +368,6 @@
|
|||
"Skip to Newswire": "Vés a Newswire",
|
||||
"Skip to Links": "Vés als enllaços web",
|
||||
"Publish a blog article": "Publicar un article del bloc",
|
||||
"Featured writer": "Escriptor destacat"
|
||||
"Featured writer": "Escriptor destacat",
|
||||
"Broch mode": "Mode Broch"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -368,5 +368,6 @@
|
|||
"Skip to Newswire": "Neidio i Newswire",
|
||||
"Skip to Links": "Neidio i Dolenni Gwe",
|
||||
"Publish a blog article": "Cyhoeddi erthygl blog",
|
||||
"Featured writer": "Awdur dan sylw"
|
||||
"Featured writer": "Awdur dan sylw",
|
||||
"Broch mode": "Modd Broch"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -368,5 +368,6 @@
|
|||
"Skip to Newswire": "Springe zu Newswire",
|
||||
"Skip to Links": "Springe zu Weblinks",
|
||||
"Publish a blog article": "Veröffentlichen Sie einen Blog-Artikel",
|
||||
"Featured writer": "Ausgewählter Schriftsteller"
|
||||
"Featured writer": "Ausgewählter Schriftsteller",
|
||||
"Broch mode": "Broch-Modus"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -368,5 +368,6 @@
|
|||
"Skip to Newswire": "Skip to Newswire",
|
||||
"Skip to Links": "Skip to Links",
|
||||
"Publish a blog article": "Publish a blog article",
|
||||
"Featured writer": "Featured writer"
|
||||
"Featured writer": "Featured writer",
|
||||
"Broch mode": "Broch mode"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -368,5 +368,6 @@
|
|||
"Skip to Newswire": "Saltar a Newswire",
|
||||
"Skip to Links": "Saltar a enlaces web",
|
||||
"Publish a blog article": "Publica un artículo de blog",
|
||||
"Featured writer": "Escritora destacada"
|
||||
"Featured writer": "Escritora destacada",
|
||||
"Broch mode": "Modo broche"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -368,5 +368,6 @@
|
|||
"Skip to Newswire": "Passer à Newswire",
|
||||
"Skip to Links": "Passer aux liens Web",
|
||||
"Publish a blog article": "Publier un article de blog",
|
||||
"Featured writer": "Écrivain en vedette"
|
||||
"Featured writer": "Écrivain en vedette",
|
||||
"Broch mode": "Mode Broch"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -368,5 +368,6 @@
|
|||
"Skip to Newswire": "Scipeáil chuig Newswire",
|
||||
"Skip to Links": "Scipeáil chuig Naisc Ghréasáin",
|
||||
"Publish a blog article": "Foilsigh alt blagála",
|
||||
"Featured writer": "Scríbhneoir mór le rá"
|
||||
"Featured writer": "Scríbhneoir mór le rá",
|
||||
"Broch mode": "Modh broch"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -368,5 +368,6 @@
|
|||
"Skip to Newswire": "Newswire पर जाएं",
|
||||
"Skip to Links": "वेब लिंक पर जाएं",
|
||||
"Publish a blog article": "एक ब्लॉग लेख प्रकाशित करें",
|
||||
"Featured writer": "फीचर्ड लेखक"
|
||||
"Featured writer": "फीचर्ड लेखक",
|
||||
"Broch mode": "ब्रोच मोड"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -368,5 +368,6 @@
|
|||
"Skip to Newswire": "Passa a Newswire",
|
||||
"Skip to Links": "Passa a collegamenti Web",
|
||||
"Publish a blog article": "Pubblica un articolo sul blog",
|
||||
"Featured writer": "Scrittore in primo piano"
|
||||
"Featured writer": "Scrittore in primo piano",
|
||||
"Broch mode": "Modalità Broch"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -368,5 +368,6 @@
|
|||
"Skip to Newswire": "Newswireにスキップ",
|
||||
"Skip to Links": "Webリンクにスキップ",
|
||||
"Publish a blog article": "ブログ記事を公開する",
|
||||
"Featured writer": "注目の作家"
|
||||
"Featured writer": "注目の作家",
|
||||
"Broch mode": "ブロッホモード"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -364,5 +364,6 @@
|
|||
"Skip to Newswire": "Skip to Newswire",
|
||||
"Skip to Links": "Skip to Links",
|
||||
"Publish a blog article": "Publish a blog article",
|
||||
"Featured writer": "Featured writer"
|
||||
"Featured writer": "Featured writer",
|
||||
"Broch mode": "Broch mode"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -368,5 +368,6 @@
|
|||
"Skip to Newswire": "Pular para Newswire",
|
||||
"Skip to Links": "Pular para links da web",
|
||||
"Publish a blog article": "Publique um artigo de blog",
|
||||
"Featured writer": "Escritor em destaque"
|
||||
"Featured writer": "Escritor em destaque",
|
||||
"Broch mode": "Modo broch"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -368,5 +368,6 @@
|
|||
"Skip to Newswire": "Перейти к ленте новостей",
|
||||
"Skip to Links": "Перейти к веб-ссылкам",
|
||||
"Publish a blog article": "Опубликовать статью в блоге",
|
||||
"Featured writer": "Избранный писатель"
|
||||
"Featured writer": "Избранный писатель",
|
||||
"Broch mode": "Брош режим"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -368,5 +368,6 @@
|
|||
"Skip to Newswire": "跳到新闻专线",
|
||||
"Skip to Links": "跳到网页链接",
|
||||
"Publish a blog article": "发布博客文章",
|
||||
"Featured writer": "特色作家"
|
||||
"Featured writer": "特色作家",
|
||||
"Broch mode": "断点模式"
|
||||
}
|
||||
|
|
|
|||
8
utils.py
8
utils.py
|
|
@ -605,6 +605,12 @@ def urlPermitted(url: str, federationList: []):
|
|||
return False
|
||||
|
||||
|
||||
def getLocalNetworkAddresses() -> []:
|
||||
"""Returns patterns for local network address detection
|
||||
"""
|
||||
return ('localhost', '127.0.', '192.168', '10.0.')
|
||||
|
||||
|
||||
def dangerousMarkup(content: str, allowLocalNetworkAccess: bool) -> bool:
|
||||
"""Returns true if the given content contains dangerous html markup
|
||||
"""
|
||||
|
|
@ -615,7 +621,7 @@ def dangerousMarkup(content: str, allowLocalNetworkAccess: bool) -> bool:
|
|||
contentSections = content.split('<')
|
||||
invalidPartials = ()
|
||||
if not allowLocalNetworkAccess:
|
||||
invalidPartials = ('localhost', '127.0.', '192.168', '10.0.')
|
||||
invalidPartials = getLocalNetworkAddresses()
|
||||
invalidStrings = ('script', 'canvas', 'style', 'abbr',
|
||||
'frame', 'iframe', 'html', 'body',
|
||||
'hr', 'allow-popups', 'allow-scripts')
|
||||
|
|
|
|||
|
|
@ -1278,6 +1278,16 @@ def htmlEditProfile(cssCache: {}, translate: {}, baseDir: str, path: str,
|
|||
' <input type="checkbox" class="profilecheckbox" ' + \
|
||||
'name="verifyallsignatures"> ' + \
|
||||
translate['Verify all signatures'] + '<br>\n'
|
||||
if getConfigParam(baseDir, "brochMode"):
|
||||
instanceStr += \
|
||||
' <input type="checkbox" class="profilecheckbox" ' + \
|
||||
'name="brochMode" checked> ' + \
|
||||
translate['Broch mode'] + '<br>\n'
|
||||
else:
|
||||
instanceStr += \
|
||||
' <input type="checkbox" class="profilecheckbox" ' + \
|
||||
'name="brochMode"> ' + \
|
||||
translate['Broch mode'] + '<br>\n'
|
||||
instanceStr += '</div>'
|
||||
|
||||
moderators = ''
|
||||
|
|
|
|||
|
|
@ -416,19 +416,19 @@ def htmlTimeline(cssCache: {}, defaultTimeline: str,
|
|||
translate['Mod']
|
||||
navLinks = {
|
||||
menuProfile: '/users/' + nickname,
|
||||
menuInbox: usersPath + '/inbox#timeline',
|
||||
menuInbox: usersPath + '/inbox#timelineposts',
|
||||
menuSearch: usersPath + '/search',
|
||||
menuNewPost: usersPath + '/newpost',
|
||||
menuCalendar: usersPath + '/calendar',
|
||||
menuDM: usersPath + '/dm#timeline',
|
||||
menuReplies: usersPath + '/tlreplies#timeline',
|
||||
menuOutbox: usersPath + '/inbox#timeline',
|
||||
menuBookmarks: usersPath + '/tlbookmarks#timeline',
|
||||
menuShares: usersPath + '/tlshares#timeline',
|
||||
menuBlogs: usersPath + '/tlblogs#timeline',
|
||||
# menuEvents: usersPath + '/tlevents#timeline',
|
||||
menuNewswire: '#newswire',
|
||||
menuLinks: '#links'
|
||||
menuDM: usersPath + '/dm#timelineposts',
|
||||
menuReplies: usersPath + '/tlreplies#timelineposts',
|
||||
menuOutbox: usersPath + '/inbox#timelineposts',
|
||||
menuBookmarks: usersPath + '/tlbookmarks#timelineposts',
|
||||
menuShares: usersPath + '/tlshares#timelineposts',
|
||||
menuBlogs: usersPath + '/tlblogs#timelineposts',
|
||||
# menuEvents: usersPath + '/tlevents#timelineposts',
|
||||
menuNewswire: usersPath + '/newswiremobile',
|
||||
menuLinks: usersPath + '/linksmobile'
|
||||
}
|
||||
if moderator:
|
||||
navLinks[menuModeration] = usersPath + '/moderation#modtimeline'
|
||||
|
|
@ -502,7 +502,7 @@ def htmlTimeline(cssCache: {}, defaultTimeline: str,
|
|||
calendarImage, followApprovals,
|
||||
iconsAsButtons)
|
||||
|
||||
tlStr += ' <div id="timeline" class="timeline-posts">\n'
|
||||
tlStr += ' <div id="timelineposts" class="timeline-posts">\n'
|
||||
|
||||
# second row of buttons for moderator actions
|
||||
if moderator and boxName == 'moderation':
|
||||
|
|
|
|||
|
|
@ -892,26 +892,26 @@ def htmlKeyboardNavigation(banner: str, links: {},
|
|||
followApprovals=False) -> str:
|
||||
"""Given a set of links return the html for keyboard navigation
|
||||
"""
|
||||
htmlStr = '<div class="transparent"><ul>'
|
||||
htmlStr = '<div class="transparent"><ul>\n'
|
||||
|
||||
if banner:
|
||||
htmlStr += '<pre aria-label="">' + banner + '<br><br></pre>'
|
||||
htmlStr += '<pre aria-label="">\n' + banner + '\n<br><br></pre>\n'
|
||||
|
||||
if subHeading:
|
||||
htmlStr += '<strong><label class="transparent">' + \
|
||||
subHeading + '</label></strong><br>'
|
||||
subHeading + '</label></strong><br>\n'
|
||||
|
||||
# show new follower approvals
|
||||
if usersPath and translate and followApprovals:
|
||||
htmlStr += '<strong><label class="transparent">' + \
|
||||
'<a href="' + usersPath + '/followers#timeline">' + \
|
||||
translate['Approve follow requests'] + '</a>' + \
|
||||
'</label></strong><br><br>'
|
||||
'</label></strong><br><br>\n'
|
||||
|
||||
# show the list of links
|
||||
for title, url in links.items():
|
||||
htmlStr += '<li><label class="transparent">' + \
|
||||
'<a href="' + str(url) + '">' + \
|
||||
str(title) + '</a></label></li>'
|
||||
htmlStr += '</ul></div>'
|
||||
str(title) + '</a></label></li>\n'
|
||||
htmlStr += '</ul></div>\n'
|
||||
return htmlStr
|
||||
|
|
|
|||
|
|
@ -1,6 +1,9 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<meta charset="utf-8">
|
||||
<meta name="description" content="ActivityPub server written in Python, HTML and CSS, and suitable for self-hosting on single board computers">
|
||||
<meta name="keywords" content="ActivityPub, Fediverse, Python, HTML, CSS">
|
||||
<meta name="author" content="Bob Mottram">
|
||||
<style>
|
||||
@charset "UTF-8";
|
||||
|
||||
|
|
@ -1141,6 +1144,9 @@
|
|||
}
|
||||
|
||||
</style>
|
||||
<head>
|
||||
<title>Epicyon ActivityPub server</title>
|
||||
</head>
|
||||
<body>
|
||||
<div class="timeline-banner"></div>
|
||||
<center>
|
||||
|
|
|
|||
Loading…
Reference in New Issue