diff --git a/.gitignore b/.gitignore
new file mode 100644
index 000000000..25aacffde
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,3 @@
+build/
+dist/
+*.egg-info/
diff --git a/content.py b/content.py
index fef7d66e9..4f20267e8 100644
--- a/content.py
+++ b/content.py
@@ -14,6 +14,8 @@ from utils import getImageExtensions
from utils import loadJson
from utils import fileLastModified
from utils import getLinkPrefixes
+from utils import dangerousMarkup
+from petnames import getPetName
def removeHtmlTag(htmlStr: str, tag: str) -> str:
@@ -153,38 +155,6 @@ def htmlReplaceQuoteMarks(content: str) -> str:
return newContent
-def dangerousMarkup(content: str, allowLocalNetworkAccess: bool) -> bool:
- """Returns true if the given content contains dangerous html markup
- """
- if '<' not in content:
- return False
- if '>' not in content:
- return False
- contentSections = content.split('<')
- invalidPartials = ()
- if not allowLocalNetworkAccess:
- invalidPartials = ('localhost', '127.0.', '192.168', '10.0.')
- invalidStrings = ('script', 'canvas', 'style', 'abbr',
- 'frame', 'iframe', 'html', 'body',
- 'hr', 'allow-popups', 'allow-scripts')
- for markup in contentSections:
- if '>' not in markup:
- continue
- markup = markup.split('>')[0].strip()
- for partialMatch in invalidPartials:
- if partialMatch in markup:
- return True
- if ' ' not in markup:
- for badStr in invalidStrings:
- if badStr in markup:
- return True
- else:
- for badStr in invalidStrings:
- if badStr + ' ' in markup:
- return True
- return False
-
-
def dangerousCSS(filename: str, allowLocalNetworkAccess: bool) -> bool:
"""Returns true is the css file contains code which
can create security problems
@@ -489,7 +459,7 @@ def tagExists(tagType: str, tagName: str, tags: {}) -> bool:
return False
-def _addMention(wordStr: str, httpPrefix: str, following: str,
+def _addMention(wordStr: str, httpPrefix: str, following: str, petnames: str,
replaceMentions: {}, recipients: [], tags: {}) -> bool:
"""Detects mentions and adds them to the replacements dict and
recipients list
@@ -501,9 +471,12 @@ def _addMention(wordStr: str, httpPrefix: str, following: str,
# if no domain was specified. eg. @nick
possibleNickname = possibleHandle
for follow in following:
- if follow.startswith(possibleNickname + '@'):
- replaceDomain = \
- follow.replace('\n', '').replace('\r', '').split('@')[1]
+ if '@' not in follow:
+ continue
+ followNick = follow.split('@')[0]
+ if possibleNickname == followNick:
+ followStr = follow.replace('\n', '').replace('\r', '')
+ replaceDomain = followStr.split('@')[1]
recipientActor = httpPrefix + "://" + \
replaceDomain + "/users/" + possibleNickname
if recipientActor not in recipients:
@@ -519,6 +492,34 @@ def _addMention(wordStr: str, httpPrefix: str, following: str,
"\" class=\"u-url mention\">@" + possibleNickname + \
""
return True
+ # try replacing petnames with mentions
+ followCtr = 0
+ for follow in following:
+ if '@' not in follow:
+ followCtr += 1
+ continue
+ pet = petnames[followCtr].replace('\n', '')
+ if pet:
+ if possibleNickname == pet:
+ followStr = follow.replace('\n', '').replace('\r', '')
+ replaceNickname = followStr.split('@')[0]
+ replaceDomain = followStr.split('@')[1]
+ recipientActor = httpPrefix + "://" + \
+ replaceDomain + "/users/" + replaceNickname
+ if recipientActor not in recipients:
+ recipients.append(recipientActor)
+ tags[wordStr] = {
+ 'href': recipientActor,
+ 'name': wordStr,
+ 'type': 'Mention'
+ }
+ replaceMentions[wordStr] = \
+ "@" + \
+ replaceNickname + ""
+ return True
+ followCtr += 1
return False
possibleNickname = None
possibleDomain = None
@@ -752,10 +753,14 @@ def addHtmlTags(baseDir: str, httpPrefix: str,
# read the following list so that we can detect just @nick
# in addition to @nick@domain
following = None
+ petnames = None
if '@' in words:
if os.path.isfile(followingFilename):
with open(followingFilename, "r") as f:
following = f.readlines()
+ for handle in following:
+ pet = getPetName(baseDir, nickname, domain, handle)
+ petnames.append(pet + '\n')
# extract mentions and tags from words
longWordsList = []
@@ -769,7 +774,7 @@ def addHtmlTags(baseDir: str, httpPrefix: str,
longWordsList.append(wordStr)
firstChar = wordStr[0]
if firstChar == '@':
- if _addMention(wordStr, httpPrefix, following,
+ if _addMention(wordStr, httpPrefix, following, petnames,
replaceMentions, recipients, hashtags):
prevWordStr = ''
continue
diff --git a/daemon.py b/daemon.py
index 5ad42d349..0c70a77fb 100644
--- a/daemon.py
+++ b/daemon.py
@@ -217,10 +217,10 @@ from utils import urlPermitted
from utils import loadJson
from utils import saveJson
from utils import isSuspended
+from utils import dangerousMarkup
from manualapprove import manualDenyFollowRequest
from manualapprove import manualApproveFollowRequest
from announce import createAnnounce
-from content import dangerousMarkup
from content import replaceEmojiFromTags
from content import addHtmlTags
from content import extractMediaInFormPOST
@@ -1136,7 +1136,7 @@ class PubServer(BaseHTTPRequestHandler):
"""
if self.server.restartInboxQueueInProgress:
self._503()
- print('Message arrrived but currently restarting inbox queue')
+ print('Message arrived but currently restarting inbox queue')
self.server.POSTbusy = False
return 2
@@ -2614,7 +2614,8 @@ class PubServer(BaseHTTPRequestHandler):
self.server.projectVersion,
self.server.YTReplacementDomain,
self.server.showPublishedDateOnly,
- self.server.peertubeInstances)
+ self.server.peertubeInstances,
+ self.server.allowLocalNetworkAccess)
if hashtagStr:
msg = hashtagStr.encode('utf-8')
msglen = len(msg)
@@ -2666,7 +2667,8 @@ class PubServer(BaseHTTPRequestHandler):
port,
self.server.YTReplacementDomain,
self.server.showPublishedDateOnly,
- self.server.peertubeInstances)
+ self.server.peertubeInstances,
+ self.server.allowLocalNetworkAccess)
if historyStr:
msg = historyStr.encode('utf-8')
msglen = len(msg)
@@ -2733,6 +2735,8 @@ class PubServer(BaseHTTPRequestHandler):
return
else:
showPublishedDateOnly = self.server.showPublishedDateOnly
+ allowLocalNetworkAccess = \
+ self.server.allowLocalNetworkAccess
profileStr = \
htmlProfileAfterSearch(self.server.cssCache,
self.server.recentPostsCache,
@@ -2753,7 +2757,8 @@ class PubServer(BaseHTTPRequestHandler):
self.server.YTReplacementDomain,
showPublishedDateOnly,
self.server.defaultTimeline,
- self.server.peertubeInstances)
+ self.server.peertubeInstances,
+ allowLocalNetworkAccess)
if profileStr:
msg = profileStr.encode('utf-8')
msglen = len(msg)
@@ -5674,7 +5679,8 @@ class PubServer(BaseHTTPRequestHandler):
self.server.projectVersion,
self.server.YTReplacementDomain,
self.server.showPublishedDateOnly,
- self.server.peertubeInstances)
+ self.server.peertubeInstances,
+ self.server.allowLocalNetworkAccess)
if hashtagStr:
msg = hashtagStr.encode('utf-8')
msglen = len(msg)
@@ -6636,7 +6642,8 @@ class PubServer(BaseHTTPRequestHandler):
self.server.personCache, callingDomain,
self.server.YTReplacementDomain,
self.server.showPublishedDateOnly,
- self.server.peertubeInstances)
+ self.server.peertubeInstances,
+ self.server.allowLocalNetworkAccess)
if deleteStr:
deleteStrLen = len(deleteStr)
self._set_headers('text/html', deleteStrLen,
@@ -6840,7 +6847,8 @@ class PubServer(BaseHTTPRequestHandler):
projectVersion,
ytDomain,
self.server.showPublishedDateOnly,
- peertubeInstances)
+ peertubeInstances,
+ self.server.allowLocalNetworkAccess)
msg = msg.encode('utf-8')
msglen = len(msg)
self._set_headers('text/html', msglen,
@@ -6926,7 +6934,8 @@ class PubServer(BaseHTTPRequestHandler):
projectVersion,
ytDomain,
self.server.showPublishedDateOnly,
- peertubeInstances)
+ peertubeInstances,
+ self.server.allowLocalNetworkAccess)
msg = msg.encode('utf-8')
msglen = len(msg)
self._set_headers('text/html', msglen,
@@ -7013,6 +7022,7 @@ class PubServer(BaseHTTPRequestHandler):
self.server.themeName,
self.server.dormantMonths,
self.server.peertubeInstances,
+ self.server.allowLocalNetworkAccess,
actorJson['roles'],
None, None)
msg = msg.encode('utf-8')
@@ -7077,6 +7087,8 @@ class PubServer(BaseHTTPRequestHandler):
self.server.showPublishedDateOnly
iconsAsButtons = \
self.server.iconsAsButtons
+ allowLocalNetworkAccess = \
+ self.server.allowLocalNetworkAccess
msg = \
htmlProfile(self.server.rssIconAtTop,
self.server.cssCache,
@@ -7097,6 +7109,7 @@ class PubServer(BaseHTTPRequestHandler):
self.server.themeName,
self.server.dormantMonths,
self.server.peertubeInstances,
+ allowLocalNetworkAccess,
actorJson['skills'],
None, None)
msg = msg.encode('utf-8')
@@ -7208,6 +7221,8 @@ class PubServer(BaseHTTPRequestHandler):
peertubeInstances = \
self.server.peertubeInstances
cssCache = self.server.cssCache
+ allowLocalNetworkAccess = \
+ self.server.allowLocalNetworkAccess
msg = \
htmlIndividualPost(cssCache,
recentPostsCache,
@@ -7227,7 +7242,8 @@ class PubServer(BaseHTTPRequestHandler):
likedBy,
ytDomain,
showPublishedDateOnly,
- peertubeInstances)
+ peertubeInstances,
+ allowLocalNetworkAccess)
msg = msg.encode('utf-8')
msglen = len(msg)
self._set_headers('text/html', msglen,
@@ -7329,6 +7345,8 @@ class PubServer(BaseHTTPRequestHandler):
self.server.showPublishedDateOnly
peertubeInstances = \
self.server.peertubeInstances
+ allowLocalNetworkAccess = \
+ self.server.allowLocalNetworkAccess
msg = \
htmlIndividualPost(self.server.cssCache,
recentPostsCache,
@@ -7348,7 +7366,8 @@ class PubServer(BaseHTTPRequestHandler):
likedBy,
ytDomain,
showPublishedDateOnly,
- peertubeInstances)
+ peertubeInstances,
+ allowLocalNetworkAccess)
msg = msg.encode('utf-8')
msglen = len(msg)
self._set_headers('text/html', msglen,
@@ -7481,7 +7500,8 @@ class PubServer(BaseHTTPRequestHandler):
self.server.publishButtonAtTop,
authorized,
self.server.themeName,
- self.server.peertubeInstances)
+ self.server.peertubeInstances,
+ self.server.allowLocalNetworkAccess)
if GETstartTime:
self._benchmarkGETtimings(GETstartTime, GETtimings,
'show status done',
@@ -7608,7 +7628,8 @@ class PubServer(BaseHTTPRequestHandler):
self.server.rssIconAtTop,
self.server.publishButtonAtTop,
authorized, self.server.themeName,
- self.server.peertubeInstances)
+ self.server.peertubeInstances,
+ self.server.allowLocalNetworkAccess)
msg = msg.encode('utf-8')
msglen = len(msg)
self._set_headers('text/html', msglen,
@@ -7728,7 +7749,8 @@ class PubServer(BaseHTTPRequestHandler):
self.server.rssIconAtTop,
self.server.publishButtonAtTop,
authorized, self.server.themeName,
- self.server.peertubeInstances)
+ self.server.peertubeInstances,
+ self.server.allowLocalNetworkAccess)
msg = msg.encode('utf-8')
msglen = len(msg)
self._set_headers('text/html', msglen,
@@ -7849,7 +7871,8 @@ class PubServer(BaseHTTPRequestHandler):
self.server.publishButtonAtTop,
authorized,
self.server.themeName,
- self.server.peertubeInstances)
+ self.server.peertubeInstances,
+ self.server.allowLocalNetworkAccess)
msg = msg.encode('utf-8')
msglen = len(msg)
self._set_headers('text/html', msglen,
@@ -7970,7 +7993,8 @@ class PubServer(BaseHTTPRequestHandler):
self.server.publishButtonAtTop,
authorized,
self.server.themeName,
- self.server.peertubeInstances)
+ self.server.peertubeInstances,
+ self.server.allowLocalNetworkAccess)
msg = msg.encode('utf-8')
msglen = len(msg)
self._set_headers('text/html', msglen,
@@ -8100,7 +8124,8 @@ class PubServer(BaseHTTPRequestHandler):
self.server.publishButtonAtTop,
authorized,
self.server.themeName,
- self.server.peertubeInstances)
+ self.server.peertubeInstances,
+ self.server.allowLocalNetworkAccess)
msg = msg.encode('utf-8')
msglen = len(msg)
self._set_headers('text/html', msglen,
@@ -8226,7 +8251,8 @@ class PubServer(BaseHTTPRequestHandler):
self.server.publishButtonAtTop,
authorized,
self.server.themeName,
- self.server.peertubeInstances)
+ self.server.peertubeInstances,
+ self.server.allowLocalNetworkAccess)
msg = msg.encode('utf-8')
msglen = len(msg)
self._set_headers('text/html', msglen,
@@ -8313,7 +8339,8 @@ class PubServer(BaseHTTPRequestHandler):
self.server.rssIconAtTop,
self.server.publishButtonAtTop,
authorized, self.server.themeName,
- self.server.peertubeInstances)
+ self.server.peertubeInstances,
+ self.server.allowLocalNetworkAccess)
msg = msg.encode('utf-8')
msglen = len(msg)
self._set_headers('text/html', msglen,
@@ -8417,7 +8444,8 @@ class PubServer(BaseHTTPRequestHandler):
self.server.publishButtonAtTop,
authorized,
self.server.themeName,
- self.server.peertubeInstances)
+ self.server.peertubeInstances,
+ self.server.allowLocalNetworkAccess)
msg = msg.encode('utf-8')
msglen = len(msg)
self._set_headers('text/html', msglen,
@@ -8541,7 +8569,8 @@ class PubServer(BaseHTTPRequestHandler):
self.server.publishButtonAtTop,
authorized,
self.server.themeName,
- self.server.peertubeInstances)
+ self.server.peertubeInstances,
+ self.server.allowLocalNetworkAccess)
msg = msg.encode('utf-8')
msglen = len(msg)
self._set_headers('text/html', msglen,
@@ -8657,7 +8686,8 @@ class PubServer(BaseHTTPRequestHandler):
self.server.publishButtonAtTop,
authorized,
self.server.themeName,
- self.server.peertubeInstances)
+ self.server.peertubeInstances,
+ self.server.allowLocalNetworkAccess)
msg = msg.encode('utf-8')
msglen = len(msg)
self._set_headers('text/html', msglen,
@@ -8763,7 +8793,8 @@ class PubServer(BaseHTTPRequestHandler):
self.server.publishButtonAtTop,
authorized, moderationActionStr,
self.server.themeName,
- self.server.peertubeInstances)
+ self.server.peertubeInstances,
+ self.server.allowLocalNetworkAccess)
msg = msg.encode('utf-8')
msglen = len(msg)
self._set_headers('text/html', msglen,
@@ -8863,6 +8894,7 @@ class PubServer(BaseHTTPRequestHandler):
self.server.themeName,
self.server.dormantMonths,
self.server.peertubeInstances,
+ self.server.allowLocalNetworkAccess,
shares,
pageNumber, sharesPerPage)
msg = msg.encode('utf-8')
@@ -8959,6 +8991,7 @@ class PubServer(BaseHTTPRequestHandler):
self.server.themeName,
self.server.dormantMonths,
self.server.peertubeInstances,
+ self.server.allowLocalNetworkAccess,
following,
pageNumber,
followsPerPage).encode('utf-8')
@@ -9055,6 +9088,7 @@ class PubServer(BaseHTTPRequestHandler):
self.server.themeName,
self.server.dormantMonths,
self.server.peertubeInstances,
+ self.server.allowLocalNetworkAccess,
followers,
pageNumber,
followsPerPage).encode('utf-8')
@@ -9174,6 +9208,7 @@ class PubServer(BaseHTTPRequestHandler):
self.server.themeName,
self.server.dormantMonths,
self.server.peertubeInstances,
+ self.server.allowLocalNetworkAccess,
None, None).encode('utf-8')
msglen = len(msg)
self._set_headers('text/html', msglen,
diff --git a/deploy/onion b/deploy/onion
index cf277b4e6..ea99ffea4 100755
--- a/deploy/onion
+++ b/deploy/onion
@@ -16,10 +16,10 @@ if [[ "$1" == 'remove' ]]; then
rm "/etc/nginx/sites-availale/${username}"
rm -rf ${install_destination}
if [ -d /var/www/cache ]; then
- rm -rf /var/www/cache
+ rm -rf /var/www/cache
fi
if [ -d /srv/http/cache ]; then
- rm -rf /srv/http/cache
+ rm -rf /srv/http/cache
fi
userdel -r ${username}
echo 'Epicyon onion instance removed'
@@ -37,18 +37,36 @@ if [ -f /usr/bin/pacman ]; then
pacman -Syy
pacman -S --noconfirm tor python-pip python-pysocks python-pycryptodome \
imagemagick python-pillow python-requests \
- perl-image-exiftool python-numpy python-dateutil \
- certbot flake8 git qrencode bandit
+ perl-image-exiftool python-numpy python-dateutil \
+ certbot flake8 git qrencode bandit
pip3 install pyLD pyqrcode pypng
else
apt-get update
apt-get -y install imagemagick python3-crypto python3-pycryptodome \
- python3-dateutil python3-idna python3-requests \
- python3-numpy python3-pil.imagetk python3-pip \
- python3-setuptools python3-socks python3-idna \
- libimage-exiftool-perl python3-flake8 python3-pyld \
- python3-django-timezone-field tor nginx git qrencode \
- python3-pyqrcode python3-png python3-bandit
+ python3-dateutil python3-idna python3-requests \
+ python3-numpy python3-pil.imagetk python3-pip \
+ python3-setuptools python3-socks python3-idna \
+ libimage-exiftool-perl python3-flake8 python3-pyld \
+ python3-django-timezone-field tor nginx git qrencode \
+ python3-pyqrcode python3-png python3-bandit
+fi
+
+if [[ "$(uname -a)" == *'Debian'* ]]; then
+ echo 'Fixing the tor daemon'
+ { echo '[Unit]';
+ echo 'Description=Anonymizing overlay network for TCP (multi-instance-master)';
+ echo '';
+ echo '[Service]';
+ echo 'Type=simple';
+ echo 'User=root';
+ echo 'Group=debian-tor';
+ echo 'ExecStart=/usr/bin/tor --defaults-torrc /usr/share/tor/tor-service-defaults-torrc -f /etc/tor/torrc --RunAsDaemon 0';
+ echo '';
+ echo '[Install]';
+ echo 'WantedBy=multi-user.target'; } > /lib/systemd/system/tor.service
+ cp /lib/systemd/system/tor.service /root/tor.service
+ systemctl daemon-reload
+ systemctl restart tor
fi
echo 'Cloning the epicyon repo'
@@ -56,8 +74,8 @@ if [ ! -d ${install_destination} ]; then
git clone https://gitlab.com/bashrc2/epicyon ${install_destination}
if [ ! -d ${install_destination} ]; then
- echo 'Epicyon repo failed to clone'
- exit 3
+ echo 'Epicyon repo failed to clone'
+ exit 3
fi
fi
@@ -79,6 +97,7 @@ if [ ! -d /etc/torrc.d ]; then
fi
if ! grep -q '%include /etc/torrc.d' /etc/tor/torrc; then
echo '%include /etc/torrc.d' >> /etc/tor/torrc
+ systemctl restart tor
fi
if [ ! -f /etc/torrc.d/epicyon ]; then
@@ -185,7 +204,7 @@ if [ ! -f /etc/nginx/nginx.conf ]; then
echo '}'; } > /etc/nginx/nginx.conf
else
if ! grep -q 'include /etc/nginx/sites-enabled' /etc/nginx/nginx.conf; then
- echo 'include /etc/nginx/sites-enabled/*.conf;' >> /etc/nginx/nginx.conf
+ echo 'include /etc/nginx/sites-enabled/*.conf;' >> /etc/nginx/nginx.conf
fi
fi
if [ ! -d /etc/nginx/conf.d ]; then
@@ -200,25 +219,25 @@ fi
if [ -f /usr/bin/pacman ]; then
if [ ! -f /lib/systemd/system/nginx.service ]; then
- echo 'Creating nginx daemon'
- { echo '[Unit]';
- echo 'Description=A high performance web server and a reverse proxy server';
- echo 'Documentation=man:nginx(8)';
- echo 'After=network.target nss-lookup.target';
- echo ''
- echo '[Service]';
- echo 'Type=forking';
- echo 'PIDFile=/run/nginx.pid';
- echo "ExecStartPre=$(which nginx) -t -q -g 'daemon on; master_process on;'";
- echo "ExecStart=$(which nginx) -g 'daemon on; master_process on;'";
- echo "ExecReload=$(which nginx) -g 'daemon on; master_process on;' -s reload";
- echo 'ExecStop=-/sbin/start-stop-daemon --quiet --stop --retry QUIT/5 --pidfile /run/nginx.pid';
- echo 'TimeoutStopSec=5';
- echo 'KillMode=mixed';
- echo '';
- echo '[Install]';
- echo 'WantedBy=multi-user.target'; } > /etc/systemd/system/nginx.service
- systemctl enable nginx
+ echo 'Creating nginx daemon'
+ { echo '[Unit]';
+ echo 'Description=A high performance web server and a reverse proxy server';
+ echo 'Documentation=man:nginx(8)';
+ echo 'After=network.target nss-lookup.target';
+ echo ''
+ echo '[Service]';
+ echo 'Type=forking';
+ echo 'PIDFile=/run/nginx.pid';
+ echo "ExecStartPre=$(which nginx) -t -q -g 'daemon on; master_process on;'";
+ echo "ExecStart=$(which nginx) -g 'daemon on; master_process on;'";
+ echo "ExecReload=$(which nginx) -g 'daemon on; master_process on;' -s reload";
+ echo 'ExecStop=-/sbin/start-stop-daemon --quiet --stop --retry QUIT/5 --pidfile /run/nginx.pid';
+ echo 'TimeoutStopSec=5';
+ echo 'KillMode=mixed';
+ echo '';
+ echo '[Install]';
+ echo 'WantedBy=multi-user.target'; } > /etc/systemd/system/nginx.service
+ systemctl enable nginx
fi
fi
@@ -257,7 +276,7 @@ echo "Creating nginx virtual host for ${ONION_DOMAIN}"
echo ' index index.html;';
echo '';
echo ' location /newsmirror {';
- echo ' root /var/www/${ONION_DOMAIN}/htdocs;';
+ echo " root /var/www/${ONION_DOMAIN}/htdocs;";
echo ' try_files $uri =404;';
echo ' }';
echo '';
diff --git a/epicyon-profile.css b/epicyon-profile.css
index 7e1fd85e4..f97f70677 100644
--- a/epicyon-profile.css
+++ b/epicyon-profile.css
@@ -51,8 +51,8 @@
--font-size-tox: 16px;
--font-size-tox2: 18px;
--time-color: #aaa;
- --time-vertical-align: 4px;
- --time-vertical-align-mobile: 25px;
+ --time-vertical-align: 0%;
+ --time-vertical-align-mobile: 1.5%;
--publish-button-text: #FFFFFF;
--button-margin: 5px;
--button-left-margin: none;
@@ -96,6 +96,7 @@
--column-right-width: 10vw;
--column-left-mobile-margin: 2%;
--column-left-top-margin: 0;
+ --column-right-top-margin: 0;
--column-left-header-style: uppercase;
--column-left-header-background: #555;
--column-left-header-color: #fff;
@@ -1156,7 +1157,7 @@ div.container {
.col-right img.rightColImg {
background: var(--column-left-color);
width: 100%;
- margin: 0 0;
+ margin-top: var(--column-right-top-margin);
padding: 0 0;
}
.likesCount {
diff --git a/epicyon.py b/epicyon.py
index e5f3ecbf4..b776e0bbd 100644
--- a/epicyon.py
+++ b/epicyon.py
@@ -868,7 +868,11 @@ configPort = getConfigParam(baseDir, 'port')
if configPort:
port = configPort
else:
- port = 8085
+ if domain.endswith('.onion') or \
+ domain.endswith('.i2p'):
+ port = 80
+ else:
+ port = 443
configProxyPort = getConfigParam(baseDir, 'proxyPort')
if configProxyPort:
@@ -1613,6 +1617,10 @@ if args.addaccount:
if os.path.isdir(baseDir + '/deactivated/' + nickname + '@' + domain):
print('Account is deactivated')
sys.exit()
+ if domain.endswith('.onion') or \
+ domain.endswith('.i2p'):
+ port = 80
+ httpPrefix = 'http'
createPerson(baseDir, nickname, domain, port, httpPrefix,
True, not args.noapproval, args.password.strip())
if os.path.isdir(baseDir + '/accounts/' + nickname + '@' + domain):
diff --git a/inbox.py b/inbox.py
index b572a3043..c213e0d22 100644
--- a/inbox.py
+++ b/inbox.py
@@ -54,6 +54,7 @@ from blocking import isBlockedDomain
from filters import isFiltered
from utils import updateAnnounceCollection
from utils import undoAnnounceCollectionEntry
+from utils import dangerousMarkup
from httpsig import messageContentDigest
from posts import validContentWarning
from posts import downloadAnnounce
@@ -69,7 +70,6 @@ from media import replaceYouTube
from git import isGitPatch
from git import receiveGitPatch
from followingCalendar import receivingCalendarEvents
-from content import dangerousMarkup
from happening import saveEventPost
from delete import removeOldHashtags
from follow import isFollowingActor
@@ -151,7 +151,8 @@ def _inboxStorePostToHtmlCache(recentPostsCache: {}, maxRecentPosts: int,
postJsonObject: {},
allowDeletion: bool, boxname: str,
showPublishedDateOnly: bool,
- peertubeInstances: []) -> None:
+ peertubeInstances: [],
+ allowLocalNetworkAccess: bool) -> None:
"""Converts the json post into html and stores it in a cache
This enables the post to be quickly displayed later
"""
@@ -168,7 +169,7 @@ def _inboxStorePostToHtmlCache(recentPostsCache: {}, maxRecentPosts: int,
avatarUrl, True, allowDeletion,
httpPrefix, __version__, boxname, None,
showPublishedDateOnly,
- peertubeInstances,
+ peertubeInstances, allowLocalNetworkAccess,
not isDM(postJsonObject),
True, True, False, True)
@@ -1259,7 +1260,8 @@ def _receiveAnnounce(recentPostsCache: {},
sendThreads: [], postLog: [], cachedWebfingers: {},
personCache: {}, messageJson: {}, federationList: [],
debug: bool, translate: {},
- YTReplacementDomain: str) -> bool:
+ YTReplacementDomain: str,
+ allowLocalNetworkAccess: bool) -> bool:
"""Receives an announce activity within the POST section of HTTPServer
"""
if messageJson['type'] != 'Announce':
@@ -1338,7 +1340,8 @@ def _receiveAnnounce(recentPostsCache: {},
postJsonObject = downloadAnnounce(session, baseDir, httpPrefix,
nickname, domain, messageJson,
__version__, translate,
- YTReplacementDomain)
+ YTReplacementDomain,
+ allowLocalNetworkAccess)
if not postJsonObject:
if domain not in messageJson['object'] and \
onionDomain not in messageJson['object']:
@@ -2119,7 +2122,8 @@ def _inboxAfterInitial(recentPostsCache: {}, maxRecentPosts: int,
messageJson,
federationList,
debug, translate,
- YTReplacementDomain):
+ YTReplacementDomain,
+ allowLocalNetworkAccess):
if debug:
print('DEBUG: Announce accepted from ' + actor)
@@ -2299,7 +2303,8 @@ def _inboxAfterInitial(recentPostsCache: {}, maxRecentPosts: int,
if isImageMedia(session, baseDir, httpPrefix,
nickname, domain, postJsonObject,
- translate, YTReplacementDomain):
+ translate, YTReplacementDomain,
+ allowLocalNetworkAccess):
# media index will be updated
updateIndexList.append('tlmedia')
if isBlogPost(postJsonObject):
@@ -2349,7 +2354,8 @@ def _inboxAfterInitial(recentPostsCache: {}, maxRecentPosts: int,
allowDeletion,
boxname,
showPublishedDateOnly,
- peertubeInstances)
+ peertubeInstances,
+ allowLocalNetworkAccess)
if debug:
timeDiff = \
str(int((time.time() - htmlCacheStartTime) *
diff --git a/newsdaemon.py b/newsdaemon.py
index 0a6a8f5cc..84f2f4bc4 100644
--- a/newsdaemon.py
+++ b/newsdaemon.py
@@ -23,7 +23,6 @@ from newswire import getDictFromNewswire
# from posts import sendSignedJson
from posts import createNewsPost
from posts import archivePostsForPerson
-from content import dangerousMarkup
from content import validHashTag
from utils import removeHtml
from utils import getFullDomain
@@ -31,6 +30,7 @@ from utils import loadJson
from utils import saveJson
from utils import getStatusNumber
from utils import clearFromPostCaches
+from utils import dangerousMarkup
from inbox import storeHashTags
from session import createSession
diff --git a/outbox.py b/outbox.py
index ce0bf15d9..e50000386 100644
--- a/outbox.py
+++ b/outbox.py
@@ -17,6 +17,7 @@ from posts import sendToNamedAddresses
from utils import getFullDomain
from utils import removeIdEnding
from utils import getDomainFromActor
+from utils import dangerousMarkup
from blocking import isBlockedDomain
from blocking import outboxBlock
from blocking import outboxUndoBlock
@@ -36,7 +37,6 @@ from bookmarks import outboxUndoBookmark
from delete import outboxDelete
from shares import outboxShareUpload
from shares import outboxUndoShareUpload
-from content import dangerousMarkup
def postMessageToOutbox(messageJson: {}, postToNickname: str,
diff --git a/posts.py b/posts.py
index 6dd6c7e31..a0920284c 100644
--- a/posts.py
+++ b/posts.py
@@ -55,6 +55,7 @@ from utils import locateNewsVotes
from utils import locateNewsArrival
from utils import votesOnNewswireItem
from utils import removeHtml
+from utils import dangerousMarkup
from media import attachMedia
from media import replaceYouTube
from content import tagExists
@@ -291,7 +292,13 @@ def getPersonBox(baseDir: str, session, wfRequest: {},
avatarUrl = personJson['icon']['url']
displayName = None
if personJson.get('name'):
- displayName = removeHtml(personJson['name'])
+ displayName = personJson['name']
+ if dangerousMarkup(personJson['name'], False):
+ displayName = '*ADVERSARY*'
+ elif isFiltered(baseDir,
+ nickname, domain,
+ displayName):
+ displayName = '*FILTERED*'
# have they moved?
if personJson.get('movedTo'):
displayName += ' ⌂'
@@ -1824,11 +1831,16 @@ def threadSendPost(session, postJsonStr: str, federationList: [],
for attempt in range(20):
postResult = None
unauthorized = False
+ if debug:
+ print('Getting postJsonString for ' + inboxUrl)
try:
postResult, unauthorized = \
postJsonString(session, postJsonStr, federationList,
inboxUrl, signatureHeaderJson,
debug)
+ if debug:
+ print('Obtained postJsonString for ' + inboxUrl +
+ ' unauthorized: ' + str(unauthorized))
except Exception as e:
print('ERROR: postJsonString failed ' + str(e))
if unauthorized:
@@ -2908,7 +2920,8 @@ def isDM(postJsonObject: {}) -> bool:
def isImageMedia(session, baseDir: str, httpPrefix: str,
nickname: str, domain: str,
postJsonObject: {}, translate: {},
- YTReplacementDomain: str) -> bool:
+ YTReplacementDomain: str,
+ allowLocalNetworkAccess: bool) -> bool:
"""Returns true if the given post has attached image media
"""
if postJsonObject['type'] == 'Announce':
@@ -2916,7 +2929,8 @@ def isImageMedia(session, baseDir: str, httpPrefix: str,
downloadAnnounce(session, baseDir, httpPrefix,
nickname, domain, postJsonObject,
__version__, translate,
- YTReplacementDomain)
+ YTReplacementDomain,
+ allowLocalNetworkAccess)
if postJsonAnnounce:
postJsonObject = postJsonAnnounce
if postJsonObject['type'] != 'Create':
@@ -3831,7 +3845,8 @@ def _rejectAnnounce(announceFilename: str):
def downloadAnnounce(session, baseDir: str, httpPrefix: str,
nickname: str, domain: str,
postJsonObject: {}, projectVersion: str,
- translate: {}, YTReplacementDomain: str) -> {}:
+ translate: {}, YTReplacementDomain: str,
+ allowLocalNetworkAccess: bool) -> {}:
"""Download the post referenced by an announce
"""
if not postJsonObject.get('object'):
@@ -3911,20 +3926,16 @@ def downloadAnnounce(session, baseDir: str, httpPrefix: str,
if '/statuses/' not in announcedJson['id']:
_rejectAnnounce(announceFilename)
return None
- if '/users/' not in announcedJson['id'] and \
- '/accounts/' not in announcedJson['id'] and \
- '/channel/' not in announcedJson['id'] and \
- '/profile/' not in announcedJson['id']:
+ if not hasUsersPath(announcedJson['id']):
_rejectAnnounce(announceFilename)
return None
if not announcedJson.get('type'):
_rejectAnnounce(announceFilename)
- # pprint(announcedJson)
return None
if announcedJson['type'] != 'Note' and \
announcedJson['type'] != 'Article':
+ # You can only announce Note or Article types
_rejectAnnounce(announceFilename)
- # pprint(announcedJson)
return None
if not announcedJson.get('content'):
_rejectAnnounce(announceFilename)
@@ -3935,16 +3946,25 @@ def downloadAnnounce(session, baseDir: str, httpPrefix: str,
if not validPostDate(announcedJson['published']):
_rejectAnnounce(announceFilename)
return None
- if isFiltered(baseDir, nickname, domain, announcedJson['content']):
+
+ # Check the content of the announce
+ contentStr = announcedJson['content']
+ if dangerousMarkup(contentStr, allowLocalNetworkAccess):
_rejectAnnounce(announceFilename)
return None
+
+ if isFiltered(baseDir, nickname, domain, contentStr):
+ _rejectAnnounce(announceFilename)
+ return None
+
# remove any long words
- announcedJson['content'] = \
- removeLongWords(announcedJson['content'], 40, [])
+ contentStr = removeLongWords(contentStr, 40, [])
# remove text formatting, such as bold/italics
- announcedJson['content'] = \
- removeTextFormatting(announcedJson['content'])
+ contentStr = removeTextFormatting(contentStr)
+
+ # set the content after santitization
+ announcedJson['content'] = contentStr
# wrap in create to be consistent with other posts
announcedJson = \
@@ -3952,8 +3972,8 @@ def downloadAnnounce(session, baseDir: str, httpPrefix: str,
actorNickname, actorDomain, actorPort,
announcedJson)
if announcedJson['type'] != 'Create':
+ # Create wrap failed
_rejectAnnounce(announceFilename)
- # pprint(announcedJson)
return None
# labelAccusatoryPost(postJsonObject, translate)
diff --git a/pyproject.toml b/pyproject.toml
new file mode 100644
index 000000000..9787c3bdf
--- /dev/null
+++ b/pyproject.toml
@@ -0,0 +1,3 @@
+[build-system]
+requires = ["setuptools", "wheel"]
+build-backend = "setuptools.build_meta"
diff --git a/setup.cfg b/setup.cfg
new file mode 100644
index 000000000..4c55942dc
--- /dev/null
+++ b/setup.cfg
@@ -0,0 +1,16 @@
+[metadata]
+name = epicyon
+version = 1.2.0
+
+[options]
+packages = .
+install_requires =
+ crypto
+ idna<3,>=2.5
+ numpy
+ pillow
+ pycryptodome
+ pyqrcode
+ python-dateutil
+ requests
+ socks
diff --git a/tests.py b/tests.py
index f2ef8b22d..83ecabca7 100644
--- a/tests.py
+++ b/tests.py
@@ -49,6 +49,7 @@ from utils import saveJson
from utils import getStatusNumber
from utils import getFollowersOfPerson
from utils import removeHtml
+from utils import dangerousMarkup
from follow import followerOfPerson
from follow import unfollowAccount
from follow import unfollowerOfAccount
@@ -77,7 +78,6 @@ from inbox import validInboxFilenames
from categories import guessHashtagCategory
from content import htmlReplaceEmailQuote
from content import htmlReplaceQuoteMarks
-from content import dangerousMarkup
from content import dangerousCSS
from content import addWebLinks
from content import replaceEmojiFromTags
@@ -95,6 +95,7 @@ from newswire import getNewswireTags
from newswire import parseFeedDate
from mastoapiv1 import getMastoApiV1IdFromNickname
from mastoapiv1 import getNicknameFromMastoApiV1Id
+from webapp_post import prepareHtmlPostNickname
testServerAliceRunning = False
testServerBobRunning = False
@@ -3072,9 +3073,25 @@ def testDomainHandling():
assert decodedHost(testDomain) == "españa.icom.museum"
+def testPrepareHtmlPostNickname():
+ print('testPrepareHtmlPostNickname')
+ postHtml = ' bool:
+ """Returns true if the given content contains dangerous html markup
+ """
+ if '<' not in content:
+ return False
+ if '>' not in content:
+ return False
+ contentSections = content.split('<')
+ invalidPartials = ()
+ if not allowLocalNetworkAccess:
+ invalidPartials = ('localhost', '127.0.', '192.168', '10.0.')
+ invalidStrings = ('script', 'canvas', 'style', 'abbr',
+ 'frame', 'iframe', 'html', 'body',
+ 'hr', 'allow-popups', 'allow-scripts')
+ for markup in contentSections:
+ if '>' not in markup:
+ continue
+ markup = markup.split('>')[0].strip()
+ for partialMatch in invalidPartials:
+ if partialMatch in markup:
+ return True
+ if ' ' not in markup:
+ for badStr in invalidStrings:
+ if badStr in markup:
+ return True
+ else:
+ for badStr in invalidStrings:
+ if badStr + ' ' in markup:
+ return True
+ return False
+
+
def getDisplayName(baseDir: str, actor: str, personCache: {}) -> str:
"""Returns the display name for the given actor
"""
@@ -561,9 +593,10 @@ def getDisplayName(baseDir: str, actor: str, personCache: {}) -> str:
actor = actor.split('/statuses/')[0]
if not personCache.get(actor):
return None
+ nameFound = None
if personCache[actor].get('actor'):
if personCache[actor]['actor'].get('name'):
- return personCache[actor]['actor']['name']
+ nameFound = personCache[actor]['actor']['name']
else:
# Try to obtain from the cached actors
cachedActorFilename = \
@@ -572,8 +605,11 @@ def getDisplayName(baseDir: str, actor: str, personCache: {}) -> str:
actorJson = loadJson(cachedActorFilename, 1)
if actorJson:
if actorJson.get('name'):
- return(actorJson['name'])
- return None
+ nameFound = actorJson['name']
+ if nameFound:
+ if dangerousMarkup(nameFound, False):
+ nameFound = "*ADVERSARY*"
+ return nameFound
def getNicknameFromActor(actor: str) -> str:
@@ -1721,6 +1757,11 @@ def siteIsActive(url: str) -> bool:
"""
if not url.startswith('http'):
return False
+ if '.onion/' in url or '.i2p/' in url or \
+ url.endswith('.onion') or \
+ url.endswith('.i2p'):
+ # skip this check for onion and i2p
+ return True
try:
req = urllib.request.Request(url)
urllib.request.urlopen(req, timeout=10) # nosec
diff --git a/webapp_column_left.py b/webapp_column_left.py
index ee7a6de77..14651564f 100644
--- a/webapp_column_left.py
+++ b/webapp_column_left.py
@@ -90,7 +90,7 @@ def getLeftColumnContent(baseDir: str, nickname: str, domainFull: str,
htmlStr += \
'\n \n' + \
'
\n'
@@ -268,6 +268,7 @@ def htmlLinksMobile(cssCache: {}, baseDir: str,
htmlStr += \
'' + \
'
\n'
htmlStr += '
' + \
@@ -354,7 +357,8 @@ def htmlCitations(baseDir: str, nickname: str, domain: str,
'\n'
- htmlStr += '\n'
htmlStr += \
@@ -464,6 +468,7 @@ def htmlNewswireMobile(cssCache: {}, baseDir: str, nickname: str,
htmlStr += \
'' + \
'
\n'
htmlStr += '