diff --git a/daemon.py b/daemon.py index e95d82273..13369fb8d 100644 --- a/daemon.py +++ b/daemon.py @@ -237,6 +237,7 @@ from categories import updateHashtagCategories from languages import getActorLanguages from languages import setActorLanguages from like import updateLikesCollection +from utils import malformedCiphertext from utils import hasActor from utils import setReplyIntervalHours from utils import canReplyTo @@ -1490,10 +1491,15 @@ class PubServer(BaseHTTPRequestHandler): # save the json for later queue processing messageBytesDecoded = messageBytes.decode('utf-8') + if malformedCiphertext(messageBytesDecoded): + print('WARN: post contains malformed ciphertext ' + + str(originalMessageJson)) + return 4 + if containsInvalidLocalLinks(messageBytesDecoded): print('WARN: post contains invalid local links ' + str(originalMessageJson)) - return 4 + return 5 self.server.blockedCacheLastUpdated = \ updateBlockedCache(self.server.baseDir, @@ -5868,6 +5874,14 @@ class PubServer(BaseHTTPRequestHandler): actorJson['featuredTags'] = \ actorJson['id'] + '/collections/tags' randomizeActorImages(actorJson) + # add an updated timestamp to the actor + updatedTime = datetime.datetime.utcnow() + actorJson['updated'] = \ + updatedTime.strftime("%Y-%m-%dT%H:%M:%SZ") + # add updated timestamp to avatar and banner + actorJson['icon']['updated'] = actorJson['updated'] + actorJson['image']['updated'] = actorJson['updated'] + # save the actor saveJson(actorJson, actorFilename) webfingerUpdate(baseDir, nickname, domain, @@ -14402,13 +14416,13 @@ class PubServer(BaseHTTPRequestHandler): if authorized and \ '/users/' in self.path and \ '?editblogpost=' in self.path and \ - '?actor=' in self.path: + ';actor=' in self.path: messageId = self.path.split('?editblogpost=')[1] - if '?' in messageId: - messageId = messageId.split('?')[0] - actor = self.path.split('?actor=')[1] - if '?' in actor: - actor = actor.split('?')[0] + if ';' in messageId: + messageId = messageId.split(';')[0] + actor = self.path.split(';actor=')[1] + if ';' in actor: + actor = actor.split(';')[0] nickname = getNicknameFromActor(self.path.split('?')[0]) if nickname == actor: postUrl = \ diff --git a/inbox.py b/inbox.py index deac45910..a5bc093a5 100644 --- a/inbox.py +++ b/inbox.py @@ -3204,8 +3204,7 @@ def _checkJsonSignature(baseDir: str, queueJson: {}) -> (bool, bool): baseDir + '/accounts/unknownContexts.txt' unknownContext = str(originalJson['@context']) - print('unrecognized @context: ' + - unknownContext) + print('unrecognized @context: ' + unknownContext) alreadyUnknown = False if os.path.isfile(unknownContextsFile): @@ -3217,8 +3216,7 @@ def _checkJsonSignature(baseDir: str, queueJson: {}) -> (bool, bool): with open(unknownContextsFile, 'a+') as unknownFile: unknownFile.write(unknownContext + '\n') else: - print('Unrecognized jsonld signature type: ' + - jwebsigType) + print('Unrecognized jsonld signature type: ' + jwebsigType) unknownSignaturesFile = \ baseDir + '/accounts/unknownJsonSignatures.txt' diff --git a/posts.py b/posts.py index 80711aa6d..ea5b5bb82 100644 --- a/posts.py +++ b/posts.py @@ -444,9 +444,11 @@ def _isPublicFeedPost(item: {}, personPosts: {}, debug: bool) -> bool: if debug: print('No type') return False - if item['type'] != 'Create' and item['type'] != 'Announce': + if item['type'] != 'Create' and \ + item['type'] != 'Announce' and \ + item['type'] != 'Note': if debug: - print('Not Create type') + print('Not a Create/Note/Announce type') return False if item.get('object'): if isinstance(item['object'], dict): @@ -463,19 +465,27 @@ def _isPublicFeedPost(item: {}, personPosts: {}, debug: bool) -> bool: if debug: print('object is not a dict or string') return False + elif item['type'] == 'Note': + if not item.get('published'): + if debug: + print('No published attribute') + return False if not personPosts.get(item['id']): + thisItem = item + if item.get('object'): + thisItem = item['object'] # check that this is a public post # #Public should appear in the "to" list - if isinstance(item['object'], dict): - if item['object'].get('to'): + if isinstance(thisItem, dict): + if thisItem.get('to'): isPublic = False - for recipient in item['object']['to']: + for recipient in thisItem['to']: if recipient.endswith('#Public'): isPublic = True break if not isPublic: return False - elif isinstance(item['object'], str): + elif isinstance(thisItem, str) or item['type'] == 'Note': if item.get('to'): isPublic = False for recipient in item['to']: @@ -570,6 +580,10 @@ def _getPosts(session, outboxUrl: str, maxPosts: int, if not _isPublicFeedPost(item, personPosts, debug): continue + thisItem = item + if item['type'] != 'Note': + thisItem = item['object'] + content = getBaseContentFromPost(item, systemLanguage) content = content.replace(''', "'") @@ -579,9 +593,11 @@ def _getPosts(session, outboxUrl: str, maxPosts: int, inReplyTo = '' attachment = [] sensitive = False - if isinstance(item['object'], dict): - if item['object'].get('tag'): - for tagItem in item['object']['tag']: + if isinstance(thisItem, dict): + if thisItem.get('tag'): + for tagItem in thisItem['tag']: + if not tagItem.get('type'): + continue tagType = tagItem['type'].lower() if tagType == 'emoji': if tagItem.get('name') and tagItem.get('icon'): @@ -609,25 +625,25 @@ def _getPosts(session, outboxUrl: str, maxPosts: int, print('max emojis reached') continue - if item['object'].get('summary'): - if item['object']['summary']: - summary = item['object']['summary'] + if thisItem.get('summary'): + if thisItem['summary']: + summary = thisItem['summary'] - if item['object'].get('inReplyTo'): - if item['object']['inReplyTo']: - if isinstance(item['object']['inReplyTo'], str): + if thisItem.get('inReplyTo'): + if thisItem['inReplyTo']: + if isinstance(thisItem['inReplyTo'], str): # No replies to non-permitted domains - if not urlPermitted(item['object']['inReplyTo'], + if not urlPermitted(thisItem['inReplyTo'], federationList): if debug: print('url not permitted ' + - item['object']['inReplyTo']) + thisItem['inReplyTo']) continue - inReplyTo = item['object']['inReplyTo'] + inReplyTo = thisItem['inReplyTo'] - if item['object'].get('attachment'): - if item['object']['attachment']: - for attach in item['object']['attachment']: + if thisItem.get('attachment'): + if thisItem['attachment']: + for attach in thisItem['attachment']: if attach.get('name') and attach.get('url'): # no attachments from non-permitted domains if urlPermitted(attach['url'], @@ -640,8 +656,8 @@ def _getPosts(session, outboxUrl: str, maxPosts: int, attach['url']) sensitive = False - if item['object'].get('sensitive'): - sensitive = item['object']['sensitive'] + if thisItem.get('sensitive'): + sensitive = thisItem['sensitive'] if content: if simple: @@ -773,6 +789,8 @@ def getPostDomains(session, outboxUrl: str, maxPosts: int, if item['object'].get('tag'): for tagItem in item['object']['tag']: + if not tagItem.get('type'): + continue tagType = tagItem['type'].lower() if tagType == 'mention': if tagItem.get('href'): @@ -841,21 +859,22 @@ def _getPostsForBlockedDomains(baseDir: str, if item['object'].get('tag'): for tagItem in item['object']['tag']: + if not tagItem.get('type'): + continue tagType = tagItem['type'].lower() - if tagType == 'mention': - if tagItem.get('href'): - postDomain, postPort = \ - getDomainFromActor(tagItem['href']) - if isBlockedDomain(baseDir, postDomain): - if item['object'].get('url'): - url = item['object']['url'] - else: - url = item['object']['id'] - if not blockedPosts.get(postDomain): - blockedPosts[postDomain] = [url] - else: - if url not in blockedPosts[postDomain]: - blockedPosts[postDomain].append(url) + if tagType == 'mention' and tagItem.get('href'): + postDomain, postPort = \ + getDomainFromActor(tagItem['href']) + if isBlockedDomain(baseDir, postDomain): + if item['object'].get('url'): + url = item['object']['url'] + else: + url = item['object']['id'] + if not blockedPosts.get(postDomain): + blockedPosts[postDomain] = [url] + else: + if url not in blockedPosts[postDomain]: + blockedPosts[postDomain].append(url) return blockedPosts diff --git a/utils.py b/utils.py index 45e6e4152..ed55ceb06 100644 --- a/utils.py +++ b/utils.py @@ -2537,6 +2537,16 @@ def isPGPEncrypted(content: str) -> bool: return False +def malformedCiphertext(content: str) -> bool: + """Returns true if the given content contains a malformed key + """ + if '----BEGIN ' in content or '----END ' in content: + if not containsPGPPublicKey(content) and \ + not isPGPEncrypted(content): + return True + return False + + def loadTranslationsFromFile(baseDir: str, language: str) -> ({}, str): """Returns the translations dictionary """ diff --git a/webapp_profile.py b/webapp_profile.py index 1f29a368d..34d1646c2 100644 --- a/webapp_profile.py +++ b/webapp_profile.py @@ -283,16 +283,34 @@ def htmlProfileAfterSearch(cssCache: {}, if isCreateInsideAnnounce(item): isAnnouncedFeedItem = True item = item['object'] - if not item.get('actor'): - continue - if not isAnnouncedFeedItem and item['actor'] != personUrl: - continue if not item.get('type'): continue if item['type'] == 'Create': if not hasObjectDict(item): continue if item['type'] != 'Create' and item['type'] != 'Announce': + if item['type'] != 'Note': + continue + if not item.get('to'): + continue + if not item.get('id'): + continue + # wrap in create + cc = [] + if item.get('cc'): + cc = item['cc'] + newItem = { + 'object': item, + 'to': item['to'], + 'cc': cc, + 'id': item['id'], + 'actor': personUrl, + 'type': 'Create' + } + item = newItem + if not item.get('actor'): + continue + if not isAnnouncedFeedItem and item['actor'] != personUrl: continue profileStr += \ diff --git a/webfinger.py b/webfinger.py index 3ca4f00e5..bf3768c43 100644 --- a/webfinger.py +++ b/webfinger.py @@ -159,12 +159,22 @@ def createWebfingerEndpoint(nickname: str, domain: str, port: int, profilePageHref = httpPrefix + '://' + domain + \ '/about/more?instance_actor=true' + personLink = httpPrefix + "://" + domain + "/@" + personName account = { "aliases": [ - httpPrefix + "://" + domain + "/@" + personName, + personLink, personId ], "links": [ + { + "href": personLink + "/avatar.png", + "rel": "http://webfinger.net/rel/avatar", + "type": "image/png" + }, + { + "href": httpPrefix + "://" + domain + "/blog/" + personName, + "rel": "http://webfinger.net/rel/blog" + }, { "href": profilePageHref, "rel": "http://webfinger.net/rel/profile-page", @@ -242,8 +252,7 @@ def webfingerLookup(path: str, baseDir: str, return None if '&' in handle: handle = handle.split('&')[0].strip() - if debug: - print('DEBUG: WEBFINGER handle with & removed ' + handle) + print('DEBUG: WEBFINGER handle with & removed ' + handle) if '@' not in handle: if debug: print('DEBUG: WEBFINGER no @ in handle ' + handle) @@ -283,6 +292,64 @@ def webfingerLookup(path: str, baseDir: str, return wfJson +def _webfingerUpdateAvatar(wfJson: {}, actorJson: {}) -> bool: + """Updates the avatar image link + """ + found = False + avatarUrl = actorJson['icon']['url'] + mediaType = actorJson['icon']['mediaType'] + for link in wfJson['links']: + if not link.get('rel'): + continue + if not link['rel'].endswith('://webfinger.net/rel/avatar'): + continue + found = True + if link['href'] != avatarUrl or link['type'] != mediaType: + link['href'] = avatarUrl + link['type'] = mediaType + return True + break + if found: + return False + wfJson['links'].append({ + "href": avatarUrl, + "rel": "http://webfinger.net/rel/avatar", + "type": mediaType + }) + return True + + +def _webfingerAddBlogLink(wfJson: {}, actorJson: {}) -> bool: + """Adds a blog link to webfinger if needed + """ + found = False + if '/users/' in actorJson['id']: + blogUrl = \ + actorJson['id'].split('/users/')[0] + '/blog/' + \ + actorJson['id'].split('/users/')[1] + else: + blogUrl = \ + actorJson['id'].split('/@')[0] + '/blog/' + \ + actorJson['id'].split('/@')[1] + for link in wfJson['links']: + if not link.get('rel'): + continue + if not link['rel'].endswith('://webfinger.net/rel/blog'): + continue + found = True + if link['href'] != blogUrl: + link['href'] = blogUrl + return True + break + if found: + return False + wfJson['links'].append({ + "href": blogUrl, + "rel": "http://webfinger.net/rel/blog" + }) + return True + + def _webfingerUpdateFromProfile(wfJson: {}, actorJson: {}) -> bool: """Updates webfinger Email/blog/xmpp links from profile Returns true if one or more tags has been changed @@ -357,6 +424,12 @@ def _webfingerUpdateFromProfile(wfJson: {}, actorJson: {}) -> bool: wfJson['aliases'].remove(fullAlias) changed = True + if _webfingerUpdateAvatar(wfJson, actorJson): + changed = True + + if _webfingerAddBlogLink(wfJson, actorJson): + changed = True + return changed