diff --git a/content.py b/content.py index ad09ef71e..c4912e018 100644 --- a/content.py +++ b/content.py @@ -1437,6 +1437,7 @@ def save_media_in_form_post(media_bytes, debug: bool, 'svg': 'image/svg+xml', 'webp': 'image/webp', 'avif': 'image/avif', + 'heic': 'image/heic', 'mp4': 'video/mp4', 'ogv': 'video/ogv', 'mp3': 'audio/mpeg', diff --git a/daemon.py b/daemon.py index a232eecff..7522aee37 100644 --- a/daemon.py +++ b/daemon.py @@ -1499,6 +1499,76 @@ class PubServer(BaseHTTPRequestHandler): self.server.nodeinfo_is_active = False return True + def _security_txt(self, ua_str: str, calling_domain: str, + referer_domain: str, + http_prefix: str, calling_site_timeout: int, + debug: bool) -> bool: + """See https://www.rfc-editor.org/rfc/rfc9116 + """ + if not self.path.startswith('/security.txt'): + return False + if referer_domain == self.server.domain_full: + print('security.txt request from self') + self._400() + return True + if self.server.security_txt_is_active: + if not referer_domain: + print('security.txt is busy ' + + 'during request without referer domain') + else: + print('security.txt is busy during request from ' + + referer_domain) + self._503() + return True + self.server.security_txt_is_active = True + # is this a real website making the call ? + if not debug and not self.server.unit_test and referer_domain: + # Does calling_domain look like a domain? + if ' ' in referer_domain or \ + ';' in referer_domain or \ + '.' not in referer_domain: + print('security.txt ' + + 'referer domain does not look like a domain ' + + referer_domain) + self._400() + self.server.security_txt_is_active = False + return True + if not self.server.allow_local_network_access: + if local_network_host(referer_domain): + print('security.txt referer domain is from the ' + + 'local network ' + referer_domain) + self._400() + self.server.security_txt_is_active = False + return True + + if not referer_is_active(http_prefix, + referer_domain, ua_str, + calling_site_timeout): + print('security.txt referer url is not active ' + + referer_domain) + self._400() + self.server.security_txt_is_active = False + return True + if self.server.debug: + print('DEBUG: security.txt ' + self.path) + + # If we are in broch mode then don't reply + if not broch_mode_is_active(self.server.base_dir): + security_txt = \ + 'Contact: https://gitlab.com/bashrc2/epicyon/-/issues' + + msg = security_txt.encode('utf-8') + msglen = len(msg) + self._set_headers('text/plain; charset=utf-8', + msglen, None, calling_domain, True) + self._write(msg) + if referer_domain: + print('security.txt sent to ' + referer_domain) + else: + print('security.txt sent to unknown referer') + self.server.security_txt_is_active = False + return True + def _webfinger(self, calling_domain: str, referer_domain: str) -> bool: if not self.path.startswith('/.well-known'): return False @@ -7529,6 +7599,9 @@ class PubServer(BaseHTTPRequestHandler): if 'image/avif' in self.headers['Accept']: fav_type = 'image/avif' fav_filename = fav_filename.split('.')[0] + '.avif' + if 'image/heic' in self.headers['Accept']: + fav_type = 'image/heic' + fav_filename = fav_filename.split('.')[0] + '.heic' if 'image/jxl' in self.headers['Accept']: fav_type = 'image/jxl' fav_filename = fav_filename.split('.')[0] + '.jxl' @@ -7546,6 +7619,8 @@ class PubServer(BaseHTTPRequestHandler): fav_filename = fav_filename.replace('.webp', '.ico') elif fav_filename.endswith('.avif'): fav_filename = fav_filename.replace('.avif', '.ico') + elif fav_filename.endswith('.heic'): + fav_filename = fav_filename.replace('.heic', '.ico') elif fav_filename.endswith('.jxl'): fav_filename = fav_filename.replace('.jxl', '.ico') if not os.path.isfile(favicon_filename): @@ -15281,6 +15356,14 @@ class PubServer(BaseHTTPRequestHandler): '_GET', '_nodeinfo[calling_domain]', self.server.debug) + if self._security_txt(ua_str, calling_domain, referer_domain, + self.server.http_prefix, 5, self.server.debug): + return + + fitness_performance(getreq_start_time, self.server.fitness, + '_GET', '_security_txt[calling_domain]', + self.server.debug) + if self.path == '/logout': if not self.server.news_instance: msg = \ @@ -21115,6 +21198,7 @@ def run_daemon(map_format: str, httpd.post_to_nickname = None httpd.nodeinfo_is_active = False + httpd.security_txt_is_active = False httpd.vcard_is_active = False httpd.masto_api_is_active = False diff --git a/metadata.py b/metadata.py index f6db54e23..ce2152d5b 100644 --- a/metadata.py +++ b/metadata.py @@ -188,6 +188,7 @@ def meta_data_instance(show_accounts: bool, 'image/gif', 'image/webp', 'image/avif', + 'image/heic', 'image/svg+xml', 'video/webm', 'video/mp4', diff --git a/newswire.py b/newswire.py index 9cca3ac68..600a7624a 100644 --- a/newswire.py +++ b/newswire.py @@ -170,6 +170,7 @@ def _download_newswire_feed_favicon(session, base_dir: str, 'jxl': 'jxl', 'gif': 'gif', 'avif': 'avif', + 'heic': 'heic', 'svg': 'svg+xml', 'webp': 'webp' } diff --git a/outbox.py b/outbox.py index a3313554e..a0790a550 100644 --- a/outbox.py +++ b/outbox.py @@ -351,6 +351,7 @@ def post_message_to_outbox(session, translate: {}, "svg": "svg", "webp": "webp", "avif": "avif", + "heic": "heic", "audio/mpeg": "mp3", "ogg": "ogg", "audio/wav": "wav", diff --git a/person.py b/person.py index f0f9f7939..b44509925 100644 --- a/person.py +++ b/person.py @@ -146,6 +146,9 @@ def set_profile_image(base_dir: str, http_prefix: str, elif image_filename.endswith('.avif'): media_type = 'image/avif' icon_filename = icon_filename_base + '.avif' + elif image_filename.endswith('.heic'): + media_type = 'image/heic' + icon_filename = icon_filename_base + '.heic' elif image_filename.endswith('.jxl'): media_type = 'image/jxl' icon_filename = icon_filename_base + '.jxl' diff --git a/session.py b/session.py index dfb072b55..3d153e265 100644 --- a/session.py +++ b/session.py @@ -535,7 +535,7 @@ def post_image(session, attach_image_filename: str, federation_list: [], return None if not is_image_file(attach_image_filename): - print('Image must be png, jpg, jxl, webp, avif, gif or svg') + print('Image must be png, jpg, jxl, webp, avif, heic, gif or svg') return None if not os.path.isfile(attach_image_filename): print('Image not found: ' + attach_image_filename) @@ -549,6 +549,8 @@ def post_image(session, attach_image_filename: str, federation_list: [], content_type = 'image/webp' elif attach_image_filename.endswith('.avif'): content_type = 'image/avif' + elif attach_image_filename.endswith('.heic'): + content_type = 'image/heic' elif attach_image_filename.endswith('.jxl'): content_type = 'image/jxl' elif attach_image_filename.endswith('.svg'): @@ -612,6 +614,7 @@ def download_image(session, url: str, image_filename: str, debug: bool, 'svg': 'svg+xml', 'webp': 'webp', 'avif': 'avif', + 'heic': 'heic', 'ico': 'x-icon' } session_headers = None @@ -722,7 +725,8 @@ def download_image_any_mime_type(session, url: str, 'gif': 'gif', 'svg': 'svg+xml', 'webp': 'webp', - 'avif': 'avif' + 'avif': 'avif', + 'heic': 'heic' } for _, m_type in image_formats.items(): if 'image/' + m_type in content_type: diff --git a/utils.py b/utils.py index a6abd5c8b..6f77922aa 100644 --- a/utils.py +++ b/utils.py @@ -496,7 +496,8 @@ def get_audio_extensions() -> []: def get_image_extensions() -> []: """Returns a list of the possible image file extensions """ - return ('jpg', 'jpeg', 'gif', 'webp', 'avif', 'svg', 'ico', 'jxl', 'png') + return ('jpg', 'jpeg', 'gif', 'webp', 'avif', 'heic', + 'svg', 'ico', 'jxl', 'png') def get_image_mime_type(image_filename: str) -> str: @@ -508,6 +509,7 @@ def get_image_mime_type(image_filename: str) -> str: 'jxl': 'jxl', 'gif': 'gif', 'avif': 'avif', + 'heic': 'heic', 'svg': 'svg+xml', 'webp': 'webp', 'ico': 'x-icon' @@ -529,6 +531,7 @@ def get_image_extension_from_mime_type(content_type: str) -> str: 'svg+xml': 'svg', 'webp': 'webp', 'avif': 'avif', + 'heic': 'heic', 'x-icon': 'ico' } for mime_ext, ext in image_media.items(): @@ -2894,6 +2897,7 @@ def media_file_mime_type(filename: str) -> str: 'svg': 'image/svg+xml', 'webp': 'image/webp', 'avif': 'image/avif', + 'heic': 'image/heic', 'ico': 'image/x-icon', 'mp3': 'audio/mpeg', 'ogg': 'audio/ogg', diff --git a/webapp_column_right.py b/webapp_column_right.py index 52399c00d..012cfc53c 100644 --- a/webapp_column_right.py +++ b/webapp_column_right.py @@ -255,7 +255,7 @@ def _html_newswire(base_dir: str, newswire: {}, nickname: str, moderator: bool, cached_favicon_filename.replace(base_dir, '') else: extensions = \ - ('png', 'jpg', 'gif', 'avif', 'svg', 'webp', 'jxl') + ('png', 'jpg', 'gif', 'avif', 'heic', 'svg', 'webp', 'jxl') for ext in extensions: cached_favicon_filename = \ get_fav_filename_from_url(base_dir, favicon_url) diff --git a/webapp_login.py b/webapp_login.py index 1d4490289..61b0b352c 100644 --- a/webapp_login.py +++ b/webapp_login.py @@ -88,6 +88,9 @@ def html_login(translate: {}, elif os.path.isfile(base_dir + '/accounts/login.avif'): login_image = 'login.avif' login_image_filename = base_dir + '/accounts/' + login_image + elif os.path.isfile(base_dir + '/accounts/login.heic'): + login_image = 'login.heic' + login_image_filename = base_dir + '/accounts/' + login_image elif os.path.isfile(base_dir + '/accounts/login.jxl'): login_image = 'login.jxl' login_image_filename = base_dir + '/accounts/' + login_image diff --git a/webapp_utils.py b/webapp_utils.py index ce51a8e05..23c2b5422 100644 --- a/webapp_utils.py +++ b/webapp_utils.py @@ -321,7 +321,8 @@ def update_avatar_image_cache(signing_priv_key_pem: str, 'gif': 'gif', 'svg': 'svg+xml', 'webp': 'webp', - 'avif': 'avif' + 'avif': 'avif', + 'heic': 'heic' } avatar_image_filename = None for im_format, mime_type in image_formats.items(): @@ -1130,7 +1131,7 @@ def _is_attached_image(attachment_filename: str) -> bool: if '.' not in attachment_filename: return False image_ext = ( - 'png', 'jpg', 'jpeg', 'webp', 'avif', 'svg', 'gif', 'jxl' + 'png', 'jpg', 'jpeg', 'webp', 'avif', 'heic', 'svg', 'gif', 'jxl' ) ext = attachment_filename.split('.')[-1] if ext in image_ext: