__filename__ = "daemon_get.py" __author__ = "Bob Mottram" __license__ = "AGPL3+" __version__ = "1.5.0" __maintainer__ = "Bob Mottram" __email__ = "bob@libreserver.org" __status__ = "Production" __module_group__ = "Core" import os import time import json import datetime import urllib.parse from shutil import copyfile from skills import get_skills_from_list from skills import no_of_actor_skills from city import get_spoofed_city from roles import get_actor_roles_list from languages import get_understood_languages from languages import get_reply_language from webfinger import webfinger_lookup from webfinger import webfinger_node_info from webfinger import webfinger_meta from webfinger import wellknown_protocol_handler from media import path_is_video from media import path_is_transcript from media import path_is_audio from context import get_individual_post_context from newswire import rss2header from newswire import get_rs_sfrom_dict from newswire import rss2footer from pgp import actor_to_vcard from pgp import actor_to_vcard_xml from siteactive import referer_is_active from maps import map_format_from_tagmaps_path from blog import html_blog_page from blog import html_blog_page_rss2 from blog import html_blog_page_rss3 from blog import html_edit_blog from blog import html_blog_post from blog import path_contains_blog_link from blog import html_blog_view from speaker import get_ssml_box from follow import pending_followers_timeline_json from follow import get_following_feed from blocking import broch_mode_is_active from blocking import remove_global_block from blocking import update_blocked_cache from blocking import add_global_block from blocking import blocked_timeline_json from cache import get_person_from_cache from webapp_create_post import html_new_post from webapp_profile import html_profile from webapp_profile import html_edit_profile from webapp_conversation import html_conversation_view from webapp_moderation import html_account_info from webapp_calendar import html_calendar_delete_confirm from webapp_calendar import html_calendar from webapp_hashtagswarm import html_search_hashtag_category from webapp_minimalbutton import set_minimal from webapp_minimalbutton import is_minimal from webapp_search import html_search_emoji_text_entry from webapp_search import html_search from webapp_search import html_hashtag_search_remote from webapp_column_left import html_edit_links from webapp_column_left import html_links_mobile from webapp_column_right import html_edit_news_post from webapp_column_right import html_edit_newswire from webapp_column_right import html_newswire_mobile from webapp_timeline import html_inbox from webapp_theme_designer import html_theme_designer from webapp_accesskeys import html_access_keys from webapp_manual import html_manual from webapp_specification import html_specification from webapp_about import html_about from webapp_tos import html_terms_of_service from webapp_confirm import html_confirm_remove_shared_item from webapp_welcome_profile import html_welcome_profile from webapp_welcome_final import html_welcome_final from webapp_welcome import html_welcome_screen from webapp_welcome import is_welcome_screen_complete from webapp_podcast import html_podcast_episode from webapp_utils import get_default_path from webapp_utils import csv_following_list from webapp_utils import get_shares_collection from webapp_utils import html_following_list from webapp_utils import html_show_share from webapp_likers import html_likers_of_post from webapp_login import html_login from webapp_post import html_individual_post from webapp_post import html_post_replies from webapp_post import html_emoji_reaction_picker from followerSync import update_followers_sync_cache from securemode import secure_mode from fitnessFunctions import sorted_watch_points from fitnessFunctions import fitness_performance from fitnessFunctions import html_watch_points_graph from session import establish_session from session import get_session_for_domains from crawlers import blocked_user_agent from daemon_utils import etag_exists from daemon_utils import has_accept from daemon_utils import show_person_options from daemon_utils import is_authorized from daemon_utils import get_user_agent from httpheaders import set_headers_etag from httpheaders import login_headers from httpheaders import redirect_headers from httprequests import request_icalendar from httprequests import request_ssml from httprequests import request_csv from httprequests import request_http from httpheaders import set_headers from httpheaders import logout_headers from httpheaders import logout_redirect from httpcodes import http_200 from httpcodes import http_401 from httpcodes import http_402 from httpcodes import http_403 from httpcodes import http_404 from httpcodes import http_304 from httpcodes import http_400 from httpcodes import http_503 from httpcodes import write2 from utils import is_public_post from utils import get_occupation_skills from utils import is_public_post_from_url from utils import can_reply_to from utils import get_new_post_endpoints from utils import save_json from utils import locate_post from utils import get_image_mime_type from utils import get_image_extensions from utils import is_account_dir from utils import get_config_param from utils import user_agent_domain from utils import local_network_host from utils import permitted_dir from utils import has_users_path from utils import media_file_mime_type from utils import is_image_file from utils import is_artist from utils import is_blog_post from utils import replace_users_with_at from utils import remove_id_ending from utils import local_actor_url from utils import load_json from utils import acct_dir from utils import get_instance_url from utils import convert_domains from utils import get_nickname_from_actor from utils import get_json_content_from_accept from utils import check_bad_path from utils import corp_servers from utils import decoded_host from utils import has_object_dict from person import add_alternate_domains from person import person_box_json from person import save_person_qrcode from person import person_lookup from person import get_account_pub_key from shares import actor_attached_shares from shares import get_share_category from shares import vf_proposal_from_id from shares import authorize_shared_items from shares import shares_catalog_endpoint from shares import shares_catalog_account_endpoint from shares import shares_catalog_csv_endpoint from posts import remove_post_interactions from posts import populate_replies_json from posts import json_pin_post from posts import is_moderator from posts import get_pinned_post_as_json from posts import outbox_message_create_wrap from daemon_get_masto_api import masto_api from daemon_get_favicon import show_cached_favicon from daemon_get_favicon import get_favicon from daemon_get_exports import get_exported_blocks from daemon_get_exports import get_exported_theme from daemon_get_pwa import progressive_web_app_manifest from daemon_get_css import get_fonts from daemon_get_css import get_style_sheet from daemon_get_nodeinfo import get_nodeinfo from daemon_get_hashtag import hashtag_search_rss2 from daemon_get_hashtag import hashtag_search_json2 from daemon_get_hashtag import hashtag_search2 from daemon_get_hashtag import get_hashtag_categories_feed2 from daemon_get_timeline import show_media_timeline from daemon_get_timeline import show_blogs_timeline from daemon_get_timeline import show_news_timeline from daemon_get_timeline import show_features_timeline from daemon_get_timeline import show_shares_timeline from daemon_get_timeline import show_wanted_timeline from daemon_get_timeline import show_bookmarks_timeline from daemon_get_timeline import show_outbox_timeline from daemon_get_timeline import show_mod_timeline from daemon_get_timeline import show_dms from daemon_get_timeline import show_replies from daemon_get_feeds import show_shares_feed from daemon_get_feeds import show_following_feed from daemon_get_feeds import show_moved_feed from daemon_get_feeds import show_inactive_feed from daemon_get_feeds import show_followers_feed from daemon_get_buttons import announce_button from daemon_get_buttons import announce_button_undo from daemon_get_buttons import follow_approve_button from daemon_get_buttons import follow_deny_button from daemon_get_buttons import like_button from daemon_get_buttons import like_button_undo from daemon_get_buttons import reaction_button from daemon_get_buttons import reaction_button_undo from daemon_get_buttons import bookmark_button from daemon_get_buttons import bookmark_button_undo from daemon_get_buttons import delete_button from daemon_get_buttons import mute_button from daemon_get_buttons import mute_button_undo # Blogs can be longer, so don't show many per page MAX_POSTS_IN_BLOGS_FEED = 4 # maximum number of posts to list in outbox feed MAX_POSTS_IN_FEED = 12 # Maximum number of entries in returned rss.xml MAX_POSTS_IN_RSS_FEED = 10 # reduced posts for media feed because it can take a while MAX_POSTS_IN_MEDIA_FEED = 6 MAX_POSTS_IN_NEWS_FEED = 10 # number of item shares per page SHARES_PER_PAGE = 12 # number of follows/followers per page FOLLOWS_PER_PAGE = 6 # maximum number of posts in a hashtag feed MAX_POSTS_IN_HASHTAG_FEED = 6 def daemon_http_get(self) -> None: """daemon handler for http GET """ if self.server.starting_daemon: return if check_bad_path(self.path): http_400(self) return calling_domain = self.server.domain_full if self.headers.get('Server'): if self.headers['Server'] in corp_servers(): if self.server.debug: print('Corporate leech bounced: ' + self.headers['Server']) http_402(self) return if self.headers.get('Host'): calling_domain = decoded_host(self.headers['Host']) if self.server.onion_domain: if calling_domain not in (self.server.domain, self.server.domain_full, self.server.onion_domain): print('GET domain blocked: ' + calling_domain) http_400(self) return elif self.server.i2p_domain: if calling_domain not in (self.server.domain, self.server.domain_full, self.server.i2p_domain): print('GET domain blocked: ' + calling_domain) http_400(self) return else: if calling_domain not in (self.server.domain, self.server.domain_full): print('GET domain blocked: ' + calling_domain) http_400(self) return ua_str = get_user_agent(self) if not _permitted_crawler_path(self, self.path): block, self.server.blocked_cache_last_updated = \ blocked_user_agent(calling_domain, ua_str, self.server.news_instance, self.server.debug, self.server.user_agents_blocked, self.server.blocked_cache_last_updated, self.server.base_dir, self.server.blocked_cache, self.server.block_federated, self.server.blocked_cache_update_secs, self.server.crawlers_allowed, self.server.known_bots, self.path, self.server.block_military) if block: http_400(self) return referer_domain = _get_referer_domain(self, ua_str) curr_session, proxy_type = \ get_session_for_domains(self.server, calling_domain, referer_domain) getreq_start_time = time.time() fitness_performance(getreq_start_time, self.server.fitness, '_GET', 'start', self.server.debug) if _show_vcard(self, self.server.base_dir, self.path, calling_domain, referer_domain, self.server.domain): return # getting the public key for an account acct_pub_key_json = \ get_account_pub_key(self.path, self.server.person_cache, self.server.base_dir, self.server.domain, calling_domain, self.server.http_prefix, self.server.domain_full, self.server.onion_domain, self.server.i2p_domain) if acct_pub_key_json: msg_str = json.dumps(acct_pub_key_json, ensure_ascii=False) msg = msg_str.encode('utf-8') msglen = len(msg) accept_str = self.headers['Accept'] protocol_str = \ get_json_content_from_accept(accept_str) set_headers(self, protocol_str, msglen, None, calling_domain, False) write2(self, msg) return # Since fediverse crawlers are quite active, # make returning info to them high priority # get nodeinfo endpoint if get_nodeinfo(self, ua_str, calling_domain, referer_domain, self.server.http_prefix, 5, self.server.debug): return fitness_performance(getreq_start_time, self.server.fitness, '_GET', '_nodeinfo[calling_domain]', self.server.debug) if _security_txt(self, 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) # followers synchronization request # See https://github.com/mastodon/mastodon/pull/14510 # https://codeberg.org/fediverse/fep/src/branch/main/feps/fep-8fcf.md if self.path.startswith('/users/') and \ self.path.endswith('/followers_synchronization'): if self.server.followers_synchronization: # only do one request at a time http_503(self) return self.server.followers_synchronization = True if self.server.debug: print('DEBUG: followers synchronization request ' + self.path + ' ' + calling_domain) # check authorized fetch if secure_mode(curr_session, proxy_type, False, self.server, self.headers, self.path): nickname = get_nickname_from_actor(self.path) sync_cache = self.server.followers_sync_cache sync_json, _ = \ update_followers_sync_cache(self.server.base_dir, nickname, self.server.domain, self.server.http_prefix, self.server.domain_full, calling_domain, sync_cache) msg_str = json.dumps(sync_json, ensure_ascii=False) msg_str = convert_domains(calling_domain, referer_domain, msg_str, self.server.http_prefix, self.server.domain, self.server.onion_domain, self.server.i2p_domain) msg = msg_str.encode('utf-8') msglen = len(msg) set_headers(self, 'application/json', msglen, None, calling_domain, False) write2(self, msg) self.server.followers_synchronization = False return else: # request was not signed result_json = { "error": "Request not signed" } msg_str = json.dumps(result_json, ensure_ascii=False) msg = msg_str.encode('utf-8') msglen = len(msg) accept_str = self.headers['Accept'] if 'json' in accept_str: protocol_str = \ get_json_content_from_accept(accept_str) set_headers(self, protocol_str, msglen, None, calling_domain, False) write2(self, msg) self.server.followers_synchronization = False return http_404(self, 110) self.server.followers_synchronization = False return if self.path == '/logout': if not self.server.news_instance: msg = \ html_login(self.server.translate, self.server.base_dir, self.server.http_prefix, self.server.domain_full, self.server.system_language, False, ua_str, self.server.theme_name).encode('utf-8') msglen = len(msg) logout_headers(self, 'text/html', msglen, calling_domain) write2(self, msg) else: news_url = \ get_instance_url(calling_domain, self.server.http_prefix, self.server.domain_full, self.server.onion_domain, self.server.i2p_domain) + \ '/users/news' logout_redirect(self, news_url, calling_domain) fitness_performance(getreq_start_time, self.server.fitness, '_GET', 'logout', self.server.debug) return fitness_performance(getreq_start_time, self.server.fitness, '_GET', 'show logout', self.server.debug) # replace https://domain/@nick with https://domain/users/nick if self.path.startswith('/@'): self.path = self.path.replace('/@', '/users/') # replace https://domain/@nick/statusnumber # with https://domain/users/nick/statuses/statusnumber nickname = self.path.split('/users/')[1] if '/' in nickname: status_number_str = nickname.split('/')[1] if status_number_str.isdigit(): nickname = nickname.split('/')[0] self.path = \ self.path.replace('/users/' + nickname + '/', '/users/' + nickname + '/statuses/') # instance actor if self.path in ('/actor', '/users/instance.actor', '/users/actor', '/Actor', '/users/Actor'): self.path = '/users/inbox' if _show_instance_actor(self, calling_domain, referer_domain, self.path, self.server.base_dir, self.server.http_prefix, self.server.domain, self.server.domain_full, self.server.onion_domain, self.server.i2p_domain, getreq_start_time, None, self.server.debug, self.server.enable_shared_inbox): return else: http_404(self, 111) return # turn off dropdowns on new post screen no_drop_down = False if self.path.endswith('?nodropdown'): no_drop_down = True self.path = self.path.replace('?nodropdown', '') # redirect music to #nowplaying list if self.path == '/music' or self.path == '/NowPlaying': self.path = '/tags/NowPlaying' if self.server.debug: print('DEBUG: GET from ' + self.server.base_dir + ' path: ' + self.path + ' busy: ' + str(self.server.getreq_busy)) if self.server.debug: print(str(self.headers)) cookie = None if self.headers.get('Cookie'): cookie = self.headers['Cookie'] fitness_performance(getreq_start_time, self.server.fitness, '_GET', 'get cookie', self.server.debug) if '/manifest.json' in self.path: if has_accept(self, calling_domain): if not request_http(self.headers, self.server.debug): progressive_web_app_manifest(self, self.server.base_dir, calling_domain, referer_domain, getreq_start_time) return else: self.path = '/' if '/browserconfig.xml' in self.path: if has_accept(self, calling_domain): _browser_config(self, calling_domain, referer_domain, getreq_start_time) return # default newswire favicon, for links to sites which # have no favicon if not self.path.startswith('/favicons/'): if 'newswire_favicon.ico' in self.path: get_favicon(self, calling_domain, self.server.base_dir, self.server.debug, 'newswire_favicon.ico') return # favicon image if 'favicon.ico' in self.path: get_favicon(self, calling_domain, self.server.base_dir, self.server.debug, 'favicon.ico') return # check authorization authorized = is_authorized(self) if self.server.debug: if authorized: print('GET Authorization granted ' + self.path) else: print('GET Not authorized ' + self.path + ' ' + str(self.headers)) fitness_performance(getreq_start_time, self.server.fitness, '_GET', 'isAuthorized', self.server.debug) if authorized and self.path.endswith('/bots.txt'): known_bots_str = '' for bot_name in self.server.known_bots: known_bots_str += bot_name + '\n' msg = known_bots_str.encode('utf-8') msglen = len(msg) set_headers(self, 'text/plain; charset=utf-8', msglen, None, calling_domain, True) write2(self, msg) if self.server.debug: print('Sent known bots: ' + self.server.path + ' ' + calling_domain) fitness_performance(getreq_start_time, self.server.fitness, '_GET', 'get_known_bots', self.server.debug) return if _show_conversation_thread(self, authorized, calling_domain, self.path, self.server.base_dir, self.server.http_prefix, self.server.domain, self.server.port, self.server.debug, self.server.session, cookie): fitness_performance(getreq_start_time, self.server.fitness, '_GET', '_show_conversation_thread', self.server.debug) return # show a shared item if it is listed within actor attachment if self.path.startswith('/users/') and '/shareditems/' in self.path: nickname = self.path.split('/users/')[1] if '/' in nickname: nickname = nickname.split('/')[0] shared_item_display_name = self.path.split('/shareditems/')[1] if not nickname or not shared_item_display_name: http_404(self, 112) return if not has_accept(self, calling_domain): print('DEBUG: shareditems 1') http_404(self, 113) return # get the actor from the cache actor = \ get_instance_url(calling_domain, self.server.http_prefix, self.server.domain_full, self.server.onion_domain, self.server.i2p_domain) + \ '/users/' + nickname actor_json = get_person_from_cache(self.server.base_dir, actor, self.server.person_cache) if not actor_json: actor_filename = acct_dir(self.server.base_dir, nickname, self.server.domain) + '.json' if os.path.isfile(actor_filename): actor_json = load_json(actor_filename, 1, 1) if not actor_json: print('DEBUG: shareditems 2 ' + actor) http_404(self, 114) return attached_shares = actor_attached_shares(actor_json) if not attached_shares: print('DEBUG: shareditems 3 ' + str(actor_json['attachment'])) http_404(self, 115) return # is the given shared item in the list? share_id = None for share_href in attached_shares: if not isinstance(share_href, str): continue if share_href.endswith(self.path): share_id = share_href.replace('://', '___') share_id = share_id.replace('/', '--') break if not share_id: print('DEBUG: shareditems 4') http_404(self, 116) return # show the shared item print('DEBUG: shareditems 5 ' + share_id) shares_file_type = 'shares' if request_http(self.headers, self.server.debug): # get the category for share_id share_category = \ get_share_category(self.server.base_dir, nickname, self.server.domain, shares_file_type, share_id) msg = \ html_show_share(self.server.base_dir, self.server.domain, nickname, self.server.http_prefix, self.server.domain_full, share_id, self.server.translate, self.server.shared_items_federated_domains, self.server.default_timeline, self.server.theme_name, shares_file_type, share_category, not authorized) if msg: msg = msg.encode('utf-8') msglen = len(msg) set_headers(self, 'text/html', msglen, None, calling_domain, True) write2(self, msg) return else: print('DEBUG: shareditems 6 ' + share_id) else: # get json for the shared item in ValueFlows format share_json = \ vf_proposal_from_id(self.server.base_dir, nickname, self.server.domain, shares_file_type, share_id, actor) if share_json: msg_str = json.dumps(share_json) msg_str = convert_domains(calling_domain, referer_domain, msg_str, self.server.http_prefix, self.server.domain, self.server.onion_domain, self.server.i2p_domain) msg = msg_str.encode('utf-8') msglen = len(msg) set_headers(self, 'application/json', msglen, None, calling_domain, True) write2(self, msg) return else: print('DEBUG: shareditems 7 ' + share_id) http_404(self, 117) return # shared items offers collection for this instance # this is only accessible to instance members or to # other instances which present an authorization token if self.path.startswith('/users/') and '/offers' in self.path: offers_collection_authorized = authorized nickname = self.path.split('/users/')[1] if '/' in nickname: nickname = nickname.split('/')[0] page_number = 1 if '?page=' in self.path: page_number_str = self.path.split('?page=')[1] if ';' in page_number_str: page_number_str = page_number_str.split(';')[0] if page_number_str.isdigit(): page_number = int(page_number_str) if not offers_collection_authorized: if self.server.debug: print('Offers collection access is not authorized. ' + 'Checking Authorization header') # Check the authorization token if self.headers.get('Origin') and \ self.headers.get('Authorization'): permitted_domains = \ self.server.shared_items_federated_domains shared_item_tokens = \ self.server.shared_item_federation_tokens if authorize_shared_items(permitted_domains, self.server.base_dir, self.headers['Origin'], calling_domain, self.headers['Authorization'], self.server.debug, shared_item_tokens): offers_collection_authorized = True elif self.server.debug: print('Authorization token refused for ' + 'offers collection federation') # show offers collection for federation offers_json = [] if has_accept(self, calling_domain) and \ offers_collection_authorized: if self.server.debug: print('Preparing offers collection') domain_full = self.server.domain_full http_prefix = self.server.http_prefix if self.server.debug: print('Offers collection for account: ' + nickname) base_dir = self.server.base_dir offers_items_per_page = 12 max_shares_per_account = offers_items_per_page shared_items_federated_domains = \ self.server.shared_items_federated_domains actor = \ local_actor_url(http_prefix, nickname, domain_full) + \ '/offers' offers_json = \ get_shares_collection(actor, page_number, offers_items_per_page, base_dir, self.server.domain, nickname, max_shares_per_account, shared_items_federated_domains, 'shares') msg_str = json.dumps(offers_json, ensure_ascii=False) msg_str = convert_domains(calling_domain, referer_domain, msg_str, self.server.http_prefix, self.server.domain, self.server.onion_domain, self.server.i2p_domain) msg = msg_str.encode('utf-8') msglen = len(msg) accept_str = self.headers['Accept'] protocol_str = \ get_json_content_from_accept(accept_str) set_headers(self, protocol_str, msglen, None, calling_domain, False) write2(self, msg) return if self.path.startswith('/users/') and '/blocked' in self.path: blocked_collection_authorized = authorized nickname = self.path.split('/users/')[1] if '/' in nickname: nickname = nickname.split('/')[0] page_number = 1 if '?page=' in self.path: page_number_str = self.path.split('?page=')[1] if ';' in page_number_str: page_number_str = page_number_str.split(';')[0] if page_number_str.isdigit(): page_number = int(page_number_str) # show blocked collection for the nickname actor = \ local_actor_url(self.server.http_prefix, nickname, self.server.domain_full) actor += '/blocked' blocked_json = { "@context": [ "https://www.w3.org/ns/activitystreams", "https://purl.archive.org/socialweb/blocked" ], "id": actor, "type": "OrderedCollection", "name": nickname + "'s Blocked Collection", "orderedItems": [] } if has_accept(self, calling_domain) and \ blocked_collection_authorized: if self.server.debug: print('Preparing blocked collection') if self.server.debug: print('Blocked collection for account: ' + nickname) base_dir = self.server.base_dir blocked_items_per_page = 12 blocked_json = \ blocked_timeline_json(actor, page_number, blocked_items_per_page, base_dir, nickname, self.server.domain) msg_str = json.dumps(blocked_json, ensure_ascii=False) msg_str = convert_domains(calling_domain, referer_domain, msg_str, self.server.http_prefix, self.server.domain, self.server.onion_domain, self.server.i2p_domain) msg = msg_str.encode('utf-8') msglen = len(msg) accept_str = self.headers['Accept'] protocol_str = \ get_json_content_from_accept(accept_str) set_headers(self, protocol_str, msglen, None, calling_domain, False) write2(self, msg) return if self.path.startswith('/users/') and \ '/pendingFollowers' in self.path: pending_collection_authorized = authorized nickname = self.path.split('/users/')[1] if '/' in nickname: nickname = nickname.split('/')[0] page_number = 1 if '?page=' in self.path: page_number_str = self.path.split('?page=')[1] if ';' in page_number_str: page_number_str = page_number_str.split(';')[0] if page_number_str.isdigit(): page_number = int(page_number_str) # show pending followers collection for the nickname actor = \ local_actor_url(self.server.http_prefix, nickname, self.server.domain_full) actor += '/pendingFollowers' pending_json = { "@context": [ "https://www.w3.org/ns/activitystreams" ], "id": actor, "type": "OrderedCollection", "name": nickname + "'s Pending Followers", "orderedItems": [] } if has_accept(self, calling_domain) and \ pending_collection_authorized: if self.server.debug: print('Preparing pending followers collection') if self.server.debug: print('Pending followers collection for account: ' + nickname) base_dir = self.server.base_dir pending_json = \ pending_followers_timeline_json(actor, base_dir, nickname, self.server.domain) msg_str = json.dumps(pending_json, ensure_ascii=False) msg_str = convert_domains(calling_domain, referer_domain, msg_str, self.server.http_prefix, self.server.domain, self.server.onion_domain, self.server.i2p_domain) msg = msg_str.encode('utf-8') msglen = len(msg) accept_str = self.headers['Accept'] protocol_str = \ get_json_content_from_accept(accept_str) set_headers(self, protocol_str, msglen, None, calling_domain, False) write2(self, msg) return # wanted items collection for this instance # this is only accessible to instance members or to # other instances which present an authorization token if self.path.startswith('/users/') and '/wanted' in self.path: wanted_collection_authorized = authorized nickname = self.path.split('/users/')[1] if '/' in nickname: nickname = nickname.split('/')[0] page_number = 1 if '?page=' in self.path: page_number_str = self.path.split('?page=')[1] if ';' in page_number_str: page_number_str = page_number_str.split(';')[0] if page_number_str.isdigit(): page_number = int(page_number_str) if not wanted_collection_authorized: if self.server.debug: print('Wanted collection access is not authorized. ' + 'Checking Authorization header') # Check the authorization token if self.headers.get('Origin') and \ self.headers.get('Authorization'): permitted_domains = \ self.server.shared_items_federated_domains shared_item_tokens = \ self.server.shared_item_federation_tokens if authorize_shared_items(permitted_domains, self.server.base_dir, self.headers['Origin'], calling_domain, self.headers['Authorization'], self.server.debug, shared_item_tokens): wanted_collection_authorized = True elif self.server.debug: print('Authorization token refused for ' + 'wanted collection federation') # show wanted collection for federation wanted_json = [] if has_accept(self, calling_domain) and \ wanted_collection_authorized: if self.server.debug: print('Preparing wanted collection') domain_full = self.server.domain_full http_prefix = self.server.http_prefix nickname = self.path.split('/users/')[1] if '/' in nickname: nickname = nickname.split('/')[0] if self.server.debug: print('Wanted collection for account: ' + nickname) base_dir = self.server.base_dir wanted_items_per_page = 12 max_shares_per_account = wanted_items_per_page shared_items_federated_domains = \ self.server.shared_items_federated_domains actor = \ local_actor_url(http_prefix, nickname, domain_full) + \ '/wanted' wanted_json = \ get_shares_collection(actor, page_number, wanted_items_per_page, base_dir, self.server.domain, nickname, max_shares_per_account, shared_items_federated_domains, 'wanted') msg_str = json.dumps(wanted_json, ensure_ascii=False) msg_str = convert_domains(calling_domain, referer_domain, msg_str, self.server.http_prefix, self.server.domain, self.server.onion_domain, self.server.i2p_domain) msg = msg_str.encode('utf-8') msglen = len(msg) accept_str = self.headers['Accept'] protocol_str = \ get_json_content_from_accept(accept_str) set_headers(self, protocol_str, msglen, None, calling_domain, False) write2(self, msg) return # shared items catalog for this instance # this is only accessible to instance members or to # other instances which present an authorization token if self.path.startswith('/catalog') or \ (self.path.startswith('/users/') and '/catalog' in self.path): catalog_authorized = authorized if not catalog_authorized: if self.server.debug: print('Catalog access is not authorized. ' + 'Checking Authorization header') # Check the authorization token if self.headers.get('Origin') and \ self.headers.get('Authorization'): permitted_domains = \ self.server.shared_items_federated_domains shared_item_tokens = \ self.server.shared_item_federation_tokens if authorize_shared_items(permitted_domains, self.server.base_dir, self.headers['Origin'], calling_domain, self.headers['Authorization'], self.server.debug, shared_item_tokens): catalog_authorized = True elif self.server.debug: print('Authorization token refused for ' + 'shared items federation') elif self.server.debug: print('No Authorization header is available for ' + 'shared items federation') # show shared items catalog for federation if has_accept(self, calling_domain) and catalog_authorized: catalog_type = 'json' headers = self.headers debug = self.server.debug if self.path.endswith('.csv') or request_csv(headers): catalog_type = 'csv' elif (self.path.endswith('.json') or not request_http(headers, debug)): catalog_type = 'json' if self.server.debug: print('Preparing DFC catalog in format ' + catalog_type) if catalog_type == 'json': # catalog as a json if not self.path.startswith('/users/'): if self.server.debug: print('Catalog for the instance') catalog_json = \ shares_catalog_endpoint(self.server.base_dir, self.server.http_prefix, self.server.domain_full, self.path, 'shares') else: domain_full = self.server.domain_full http_prefix = self.server.http_prefix nickname = self.path.split('/users/')[1] if '/' in nickname: nickname = nickname.split('/')[0] if self.server.debug: print('Catalog for account: ' + nickname) base_dir = self.server.base_dir catalog_json = \ shares_catalog_account_endpoint(base_dir, http_prefix, nickname, self.server.domain, domain_full, self.path, self.server.debug, 'shares') msg_str = json.dumps(catalog_json, ensure_ascii=False) msg_str = convert_domains(calling_domain, referer_domain, msg_str, self.server.http_prefix, self.server.domain, self.server.onion_domain, self.server.i2p_domain) msg = msg_str.encode('utf-8') msglen = len(msg) accept_str = self.headers['Accept'] protocol_str = \ get_json_content_from_accept(accept_str) set_headers(self, protocol_str, msglen, None, calling_domain, False) write2(self, msg) return elif catalog_type == 'csv': # catalog as a CSV file for import into a spreadsheet msg = \ shares_catalog_csv_endpoint(self.server.base_dir, self.server.http_prefix, self.server.domain_full, self.path, 'shares').encode('utf-8') msglen = len(msg) set_headers(self, 'text/csv', msglen, None, calling_domain, False) write2(self, msg) return http_404(self, 118) return http_400(self) return # wanted items catalog for this instance # this is only accessible to instance members or to # other instances which present an authorization token if self.path.startswith('/wantedItems') or \ (self.path.startswith('/users/') and '/wantedItems' in self.path): catalog_authorized = authorized if not catalog_authorized: if self.server.debug: print('Wanted catalog access is not authorized. ' + 'Checking Authorization header') # Check the authorization token if self.headers.get('Origin') and \ self.headers.get('Authorization'): permitted_domains = \ self.server.shared_items_federated_domains shared_item_tokens = \ self.server.shared_item_federation_tokens if authorize_shared_items(permitted_domains, self.server.base_dir, self.headers['Origin'], calling_domain, self.headers['Authorization'], self.server.debug, shared_item_tokens): catalog_authorized = True elif self.server.debug: print('Authorization token refused for ' + 'wanted items federation') elif self.server.debug: print('No Authorization header is available for ' + 'wanted items federation') # show wanted items catalog for federation if has_accept(self, calling_domain) and catalog_authorized: catalog_type = 'json' headers = self.headers debug = self.server.debug if self.path.endswith('.csv') or request_csv(headers): catalog_type = 'csv' elif (self.path.endswith('.json') or not request_http(headers, debug)): catalog_type = 'json' if self.server.debug: print('Preparing DFC wanted catalog in format ' + catalog_type) if catalog_type == 'json': # catalog as a json if not self.path.startswith('/users/'): if self.server.debug: print('Wanted catalog for the instance') catalog_json = \ shares_catalog_endpoint(self.server.base_dir, self.server.http_prefix, self.server.domain_full, self.path, 'wanted') else: domain_full = self.server.domain_full http_prefix = self.server.http_prefix nickname = self.path.split('/users/')[1] if '/' in nickname: nickname = nickname.split('/')[0] if self.server.debug: print('Wanted catalog for account: ' + nickname) base_dir = self.server.base_dir catalog_json = \ shares_catalog_account_endpoint(base_dir, http_prefix, nickname, self.server.domain, domain_full, self.path, self.server.debug, 'wanted') msg_str = json.dumps(catalog_json, ensure_ascii=False) msg_str = convert_domains(calling_domain, referer_domain, msg_str, self.server.http_prefix, self.server.domain, self.server.onion_domain, self.server.i2p_domain) msg = msg_str.encode('utf-8') msglen = len(msg) accept_str = self.headers['Accept'] protocol_str = \ get_json_content_from_accept(accept_str) set_headers(self, protocol_str, msglen, None, calling_domain, False) write2(self, msg) return elif catalog_type == 'csv': # catalog as a CSV file for import into a spreadsheet msg = \ shares_catalog_csv_endpoint(self.server.base_dir, self.server.http_prefix, self.server.domain_full, self.path, 'wanted').encode('utf-8') msglen = len(msg) set_headers(self, 'text/csv', msglen, None, calling_domain, False) write2(self, msg) return http_404(self, 119) return http_400(self) return # minimal mastodon api if masto_api(self, self.path, calling_domain, ua_str, authorized, self.server.http_prefix, self.server.base_dir, self.authorized_nickname, self.server.domain, self.server.domain_full, self.server.onion_domain, self.server.i2p_domain, self.server.translate, self.server.registration, self.server.system_language, self.server.project_version, self.server.custom_emoji, self.server.show_node_info_accounts, referer_domain, self.server.debug, self.server.known_crawlers, self.server.sites_unavailable): return fitness_performance(getreq_start_time, self.server.fitness, '_GET', '_masto_api[calling_domain]', self.server.debug) curr_session = \ establish_session("GET", curr_session, proxy_type, self.server) if not curr_session: http_404(self, 120) fitness_performance(getreq_start_time, self.server.fitness, '_GET', 'session fail', self.server.debug) self.server.getreq_busy = False return fitness_performance(getreq_start_time, self.server.fitness, '_GET', 'create session', self.server.debug) # is this a html/ssml/icalendar request? html_getreq = False csv_getreq = False ssml_getreq = False icalendar_getreq = False if has_accept(self, calling_domain): if request_http(self.headers, self.server.debug): html_getreq = True elif request_csv(self.headers): csv_getreq = True elif request_ssml(self.headers): ssml_getreq = True elif request_icalendar(self.headers): icalendar_getreq = True else: if self.headers.get('Connection'): # https://developer.mozilla.org/en-US/ # docs/Web/HTTP/Protocol_upgrade_mechanism if self.headers.get('Upgrade'): print('HTTP Connection request: ' + self.headers['Upgrade']) else: print('HTTP Connection request: ' + self.headers['Connection']) http_200(self) else: print('WARN: No Accept header ' + str(self.headers)) http_400(self) return fitness_performance(getreq_start_time, self.server.fitness, '_GET', 'hasAccept', self.server.debug) # cached favicon images # Note that this comes before the busy flag to avoid conflicts if self.path.startswith('/favicons/'): if self.server.domain_full in self.path: # favicon for this instance get_favicon(self, calling_domain, self.server.base_dir, self.server.debug, 'favicon.ico') return show_cached_favicon(self, referer_domain, self.path, self.server.base_dir, getreq_start_time) return # get css # Note that this comes before the busy flag to avoid conflicts if self.path.endswith('.css'): if get_style_sheet(self, self.server.base_dir, calling_domain, self.path, getreq_start_time): return if authorized and '/exports/' in self.path: if 'blocks.csv' in self.path: get_exported_blocks(self, self.path, self.server.base_dir, self.server.domain, calling_domain) else: get_exported_theme(self, self.path, self.server.base_dir, self.server.domain_full) return # get fonts if '/fonts/' in self.path: get_fonts(self, calling_domain, self.path, self.server.base_dir, self.server.debug, getreq_start_time) return fitness_performance(getreq_start_time, self.server.fitness, '_GET', 'fonts', self.server.debug) if self.path in ('/sharedInbox', '/users/inbox', '/actor/inbox', '/users/' + self.server.domain): # if shared inbox is not enabled if not self.server.enable_shared_inbox: http_503(self) return self.path = '/inbox' fitness_performance(getreq_start_time, self.server.fitness, '_GET', 'sharedInbox enabled', self.server.debug) if self.path == '/categories.xml': get_hashtag_categories_feed2(self, calling_domain, self.path, self.server.base_dir, proxy_type, getreq_start_time, self.server.debug, curr_session) return if self.path == '/newswire.xml': _get_newswire_feed(self, calling_domain, self.path, proxy_type, getreq_start_time, self.server.debug, curr_session) return # RSS 2.0 if self.path.startswith('/blog/') and \ self.path.endswith('/rss.xml'): if not self.path == '/blog/rss.xml': _get_rss2feed(self, calling_domain, self.path, self.server.base_dir, self.server.http_prefix, self.server.domain, self.server.port, proxy_type, getreq_start_time, self.server.debug, curr_session) else: _get_rss2site(self, calling_domain, self.path, self.server.base_dir, self.server.http_prefix, self.server.domain_full, self.server.port, proxy_type, self.server.translate, getreq_start_time, self.server.debug, curr_session) return fitness_performance(getreq_start_time, self.server.fitness, '_GET', 'rss2 done', self.server.debug) # RSS 3.0 if self.path.startswith('/blog/') and \ self.path.endswith('/rss.txt'): _get_rss3feed(self, calling_domain, self.path, self.server.base_dir, self.server.http_prefix, self.server.domain, self.server.port, proxy_type, getreq_start_time, self.server.debug, self.server.system_language, curr_session) return users_in_path = False if '/users/' in self.path: users_in_path = True if authorized and not html_getreq and users_in_path: if '/following?page=' in self.path: _get_following_json(self, self.server.base_dir, self.path, calling_domain, referer_domain, self.server.http_prefix, self.server.domain, self.server.port, self.server.followingItemsPerPage, self.server.debug, 'following') return if '/followers?page=' in self.path: _get_following_json(self, self.server.base_dir, self.path, calling_domain, referer_domain, self.server.http_prefix, self.server.domain, self.server.port, self.server.followingItemsPerPage, self.server.debug, 'followers') return if '/followrequests?page=' in self.path: _get_following_json(self, self.server.base_dir, self.path, calling_domain, referer_domain, self.server.http_prefix, self.server.domain, self.server.port, self.server.followingItemsPerPage, self.server.debug, 'followrequests') return # authorized endpoint used for TTS of posts # arriving in your inbox if authorized and users_in_path and \ self.path.endswith('/speaker'): if 'application/ssml' not in self.headers['Accept']: # json endpoint _get_speaker(self, calling_domain, referer_domain, self.path, self.server.base_dir, self.server.domain) else: xml_str = \ get_ssml_box(self.server.base_dir, self.path, self.server.domain, self.server.system_language, self.server.instanceTitle, 'inbox') if xml_str: msg = xml_str.encode('utf-8') msglen = len(msg) set_headers(self, 'application/xrd+xml', msglen, None, calling_domain, False) write2(self, msg) return # show a podcast episode if authorized and users_in_path and html_getreq and \ '?podepisode=' in self.path: nickname = self.path.split('/users/')[1] if '/' in nickname: nickname = nickname.split('/')[0] episode_timestamp = self.path.split('?podepisode=')[1].strip() episode_timestamp = episode_timestamp.replace('__', ' ') episode_timestamp = episode_timestamp.replace('aa', ':') if self.server.newswire.get(episode_timestamp): pod_episode = self.server.newswire[episode_timestamp] html_str = \ html_podcast_episode(self.server.translate, self.server.base_dir, nickname, self.server.domain, pod_episode, self.server.text_mode_banner, self.server.session, self.server.session_onion, self.server.session_i2p, self.server.http_prefix, self.server.debug) if html_str: msg = html_str.encode('utf-8') msglen = len(msg) set_headers(self, 'text/html', msglen, None, calling_domain, False) write2(self, msg) return # redirect to the welcome screen if html_getreq and authorized and users_in_path and \ '/welcome' not in self.path: nickname = self.path.split('/users/')[1] if '/' in nickname: nickname = nickname.split('/')[0] if '?' in nickname: nickname = nickname.split('?')[0] if nickname == self.authorized_nickname and \ self.path != '/users/' + nickname: if not is_welcome_screen_complete(self.server.base_dir, nickname, self.server.domain): redirect_headers(self, '/users/' + nickname + '/welcome', cookie, calling_domain) return if not html_getreq and \ users_in_path and self.path.endswith('/pinned'): nickname = self.path.split('/users/')[1] if '/' in nickname: nickname = nickname.split('/')[0] pinned_post_json = \ get_pinned_post_as_json(self.server.base_dir, self.server.http_prefix, nickname, self.server.domain, self.server.domain_full, self.server.system_language) message_json = {} if pinned_post_json: post_id = remove_id_ending(pinned_post_json['id']) message_json = \ outbox_message_create_wrap(self.server.http_prefix, nickname, self.server.domain, self.server.port, pinned_post_json) message_json['id'] = post_id + '/activity' message_json['object']['id'] = post_id message_json['object']['url'] = replace_users_with_at(post_id) message_json['object']['atomUri'] = post_id msg_str = json.dumps(message_json, ensure_ascii=False) msg_str = convert_domains(calling_domain, referer_domain, msg_str, self.server.http_prefix, self.server.domain, self.server.onion_domain, self.server.i2p_domain) msg = msg_str.encode('utf-8') msglen = len(msg) accept_str = self.headers['Accept'] protocol_str = \ get_json_content_from_accept(accept_str) set_headers(self, protocol_str, msglen, None, calling_domain, False) write2(self, msg) return if not html_getreq and \ users_in_path and self.path.endswith('/collections/featured'): nickname = self.path.split('/users/')[1] if '/' in nickname: nickname = nickname.split('/')[0] # return the featured posts collection _get_featured_collection(self, calling_domain, referer_domain, self.server.base_dir, self.server.http_prefix, nickname, self.server.domain, self.server.domain_full, self.server.system_language) return if not html_getreq and \ users_in_path and self.path.endswith('/collections/featuredTags'): _get_featured_tags_collection(self, calling_domain, referer_domain, self.path, self.server.http_prefix, self.server.domain_full, self.server.domain) return fitness_performance(getreq_start_time, self.server.fitness, '_GET', '_get_featured_tags_collection done', self.server.debug) # show a performance graph if authorized and '/performance?graph=' in self.path: graph = self.path.split('?graph=')[1] if html_getreq and not graph.endswith('.json'): if graph == 'post': graph = '_POST' elif graph == 'inbox': graph = 'INBOX' elif graph == 'get': graph = '_GET' msg = \ html_watch_points_graph(self.server.base_dir, self.server.fitness, graph, 16).encode('utf-8') msglen = len(msg) set_headers(self, 'text/html', msglen, cookie, calling_domain, False) write2(self, msg) fitness_performance(getreq_start_time, self.server.fitness, '_GET', 'graph', self.server.debug) return graph = graph.replace('.json', '') if graph == 'post': graph = '_POST' elif graph == 'inbox': graph = 'INBOX' elif graph == 'get': graph = '_GET' watch_points_json = \ sorted_watch_points(self.server.fitness, graph) msg_str = json.dumps(watch_points_json, ensure_ascii=False) msg_str = convert_domains(calling_domain, referer_domain, msg_str, self.server.http_prefix, self.server.domain, self.server.onion_domain, self.server.i2p_domain) msg = msg_str.encode('utf-8') msglen = len(msg) accept_str = self.headers['Accept'] protocol_str = \ get_json_content_from_accept(accept_str) set_headers(self, protocol_str, msglen, None, calling_domain, False) write2(self, msg) fitness_performance(getreq_start_time, self.server.fitness, '_GET', 'graph json', self.server.debug) return # show the main blog page if html_getreq and \ self.path in ('/blog', '/blog/', '/blogs', '/blogs/'): if '/rss.xml' not in self.path: curr_session = \ establish_session("show the main blog page", curr_session, proxy_type, self.server) if not curr_session: http_404(self, 121) return msg = html_blog_view(authorized, curr_session, self.server.base_dir, self.server.http_prefix, self.server.translate, self.server.domain, self.server.port, MAX_POSTS_IN_BLOGS_FEED, self.server.peertube_instances, self.server.system_language, self.server.person_cache, self.server.debug) if msg is not None: msg = msg.encode('utf-8') msglen = len(msg) set_headers(self, 'text/html', msglen, cookie, calling_domain, False) write2(self, msg) fitness_performance(getreq_start_time, self.server.fitness, '_GET', 'blog view', self.server.debug) return http_404(self, 122) return fitness_performance(getreq_start_time, self.server.fitness, '_GET', 'blog view done', self.server.debug) # show a particular page of blog entries # for a particular account if html_getreq and self.path.startswith('/blog/'): if '/rss.xml' not in self.path: if _show_blog_page(self, authorized, calling_domain, self.path, self.server.base_dir, self.server.http_prefix, self.server.domain, self.server.port, getreq_start_time, proxy_type, cookie, self.server.translate, self.server.debug, curr_session): return fitness_performance(getreq_start_time, self.server.fitness, '_GET', '_show_blog_page', self.server.debug) if html_getreq and users_in_path: # show the person options screen with view/follow/block/report if '?options=' in self.path: show_person_options(self, calling_domain, self.path, self.server.base_dir, self.server.domain, self.server.domain_full, getreq_start_time, cookie, self.server.debug, authorized, curr_session) return fitness_performance(getreq_start_time, self.server.fitness, '_GET', 'person options done', self.server.debug) # show blog post blog_filename, nickname = \ path_contains_blog_link(self.server.base_dir, self.server.http_prefix, self.server.domain, self.server.domain_full, self.path) if blog_filename and nickname: post_json_object = load_json(blog_filename) if is_blog_post(post_json_object): msg = html_blog_post(curr_session, authorized, self.server.base_dir, self.server.http_prefix, self.server.translate, nickname, self.server.domain, self.server.domain_full, post_json_object, self.server.peertube_instances, self.server.system_language, self.server.person_cache, self.server.debug, self.server.content_license_url) if msg is not None: msg = msg.encode('utf-8') msglen = len(msg) set_headers(self, 'text/html', msglen, cookie, calling_domain, False) write2(self, msg) fitness_performance(getreq_start_time, self.server.fitness, '_GET', 'blog post 2', self.server.debug) return http_404(self, 123) return fitness_performance(getreq_start_time, self.server.fitness, '_GET', 'blog post 2 done', self.server.debug) # after selecting a shared item from the left column then show it if html_getreq and \ '?showshare=' in self.path and '/users/' in self.path: item_id = self.path.split('?showshare=')[1] if '?' in item_id: item_id = item_id.split('?')[0] category = '' if '?category=' in self.path: category = self.path.split('?category=')[1] if '?' in category: category = category.split('?')[0] users_path = self.path.split('?showshare=')[0] nickname = users_path.replace('/users/', '') item_id = urllib.parse.unquote_plus(item_id.strip()) msg = \ html_show_share(self.server.base_dir, self.server.domain, nickname, self.server.http_prefix, self.server.domain_full, item_id, self.server.translate, self.server.shared_items_federated_domains, self.server.default_timeline, self.server.theme_name, 'shares', category, False) if not msg: if calling_domain.endswith('.onion') and \ self.server.onion_domain: actor = 'http://' + self.server.onion_domain + users_path elif (calling_domain.endswith('.i2p') and self.server.i2p_domain): actor = 'http://' + self.server.i2p_domain + users_path redirect_headers(self, actor + '/tlshares', cookie, calling_domain) return msg = msg.encode('utf-8') msglen = len(msg) set_headers(self, 'text/html', msglen, cookie, calling_domain, False) write2(self, msg) fitness_performance(getreq_start_time, self.server.fitness, '_GET', 'html_show_share', self.server.debug) return # after selecting a wanted item from the left column then show it if html_getreq and \ '?showwanted=' in self.path and '/users/' in self.path: item_id = self.path.split('?showwanted=')[1] if ';' in item_id: item_id = item_id.split(';')[0] category = self.path.split('?category=')[1] if ';' in category: category = category.split(';')[0] users_path = self.path.split('?showwanted=')[0] nickname = users_path.replace('/users/', '') item_id = urllib.parse.unquote_plus(item_id.strip()) msg = \ html_show_share(self.server.base_dir, self.server.domain, nickname, self.server.http_prefix, self.server.domain_full, item_id, self.server.translate, self.server.shared_items_federated_domains, self.server.default_timeline, self.server.theme_name, 'wanted', category, False) if not msg: if calling_domain.endswith('.onion') and \ self.server.onion_domain: actor = 'http://' + self.server.onion_domain + users_path elif (calling_domain.endswith('.i2p') and self.server.i2p_domain): actor = 'http://' + self.server.i2p_domain + users_path redirect_headers(self, actor + '/tlwanted', cookie, calling_domain) return msg = msg.encode('utf-8') msglen = len(msg) set_headers(self, 'text/html', msglen, cookie, calling_domain, False) write2(self, msg) fitness_performance(getreq_start_time, self.server.fitness, '_GET', 'htmlShowWanted', self.server.debug) return # remove a shared item if html_getreq and '?rmshare=' in self.path: item_id = self.path.split('?rmshare=')[1] item_id = urllib.parse.unquote_plus(item_id.strip()) users_path = self.path.split('?rmshare=')[0] actor = \ self.server.http_prefix + '://' + \ self.server.domain_full + users_path msg = html_confirm_remove_shared_item(self.server.translate, self.server.base_dir, actor, item_id, calling_domain, 'shares') if not msg: if calling_domain.endswith('.onion') and \ self.server.onion_domain: actor = 'http://' + self.server.onion_domain + users_path elif (calling_domain.endswith('.i2p') and self.server.i2p_domain): actor = 'http://' + self.server.i2p_domain + users_path redirect_headers(self, actor + '/tlshares', cookie, calling_domain) return msg = msg.encode('utf-8') msglen = len(msg) set_headers(self, 'text/html', msglen, cookie, calling_domain, False) write2(self, msg) fitness_performance(getreq_start_time, self.server.fitness, '_GET', 'remove shared item', self.server.debug) return # remove a wanted item if html_getreq and '?rmwanted=' in self.path: item_id = self.path.split('?rmwanted=')[1] item_id = urllib.parse.unquote_plus(item_id.strip()) users_path = self.path.split('?rmwanted=')[0] actor = \ self.server.http_prefix + '://' + \ self.server.domain_full + users_path msg = html_confirm_remove_shared_item(self.server.translate, self.server.base_dir, actor, item_id, calling_domain, 'wanted') if not msg: if calling_domain.endswith('.onion') and \ self.server.onion_domain: actor = 'http://' + self.server.onion_domain + users_path elif (calling_domain.endswith('.i2p') and self.server.i2p_domain): actor = 'http://' + self.server.i2p_domain + users_path redirect_headers(self, actor + '/tlwanted', cookie, calling_domain) return msg = msg.encode('utf-8') msglen = len(msg) set_headers(self, 'text/html', msglen, cookie, calling_domain, False) write2(self, msg) fitness_performance(getreq_start_time, self.server.fitness, '_GET', 'remove shared item', self.server.debug) return fitness_performance(getreq_start_time, self.server.fitness, '_GET', 'remove shared item done', self.server.debug) if self.path.startswith('/terms'): if calling_domain.endswith('.onion') and \ self.server.onion_domain: msg = html_terms_of_service(self.server.base_dir, 'http', self.server.onion_domain) elif (calling_domain.endswith('.i2p') and self.server.i2p_domain): msg = html_terms_of_service(self.server.base_dir, 'http', self.server.i2p_domain) else: msg = html_terms_of_service(self.server.base_dir, self.server.http_prefix, self.server.domain_full) msg = msg.encode('utf-8') msglen = len(msg) login_headers(self, 'text/html', msglen, calling_domain) write2(self, msg) fitness_performance(getreq_start_time, self.server.fitness, '_GET', 'terms of service shown', self.server.debug) return fitness_performance(getreq_start_time, self.server.fitness, '_GET', 'terms of service done', self.server.debug) # show a list of who you are following if (authorized and users_in_path and (self.path.endswith('/followingaccounts') or self.path.endswith('/followingaccounts.csv'))): nickname = get_nickname_from_actor(self.path) if not nickname: http_404(self, 124) return following_filename = \ acct_dir(self.server.base_dir, nickname, self.server.domain) + '/following.txt' if not os.path.isfile(following_filename): http_404(self, 125) return if self.path.endswith('/followingaccounts.csv'): html_getreq = False csv_getreq = True if html_getreq: msg = html_following_list(self.server.base_dir, following_filename) msglen = len(msg) login_headers(self, 'text/html', msglen, calling_domain) write2(self, msg.encode('utf-8')) elif csv_getreq: msg = csv_following_list(following_filename, self.server.base_dir, nickname, self.server.domain) msglen = len(msg) login_headers(self, 'text/csv', msglen, calling_domain) write2(self, msg.encode('utf-8')) else: http_404(self, 126) fitness_performance(getreq_start_time, self.server.fitness, '_GET', 'following accounts shown', self.server.debug) return fitness_performance(getreq_start_time, self.server.fitness, '_GET', 'following accounts done', self.server.debug) # show a list of who are your followers if authorized and users_in_path and \ self.path.endswith('/followersaccounts'): nickname = get_nickname_from_actor(self.path) if not nickname: http_404(self, 127) return followers_filename = \ acct_dir(self.server.base_dir, nickname, self.server.domain) + '/followers.txt' if not os.path.isfile(followers_filename): http_404(self, 128) return if html_getreq: msg = html_following_list(self.server.base_dir, followers_filename) msglen = len(msg) login_headers(self, 'text/html', msglen, calling_domain) write2(self, msg.encode('utf-8')) elif csv_getreq: msg = csv_following_list(followers_filename, self.server.base_dir, nickname, self.server.domain) msglen = len(msg) login_headers(self, 'text/csv', msglen, calling_domain) write2(self, msg.encode('utf-8')) else: http_404(self, 129) fitness_performance(getreq_start_time, self.server.fitness, '_GET', 'followers accounts shown', self.server.debug) return fitness_performance(getreq_start_time, self.server.fitness, '_GET', 'followers accounts done', self.server.debug) if self.path.endswith('/about'): if calling_domain.endswith('.onion'): msg = \ html_about(self.server.base_dir, 'http', self.server.onion_domain, None, self.server.translate, self.server.system_language) elif calling_domain.endswith('.i2p'): msg = \ html_about(self.server.base_dir, 'http', self.server.i2p_domain, None, self.server.translate, self.server.system_language) else: msg = \ html_about(self.server.base_dir, self.server.http_prefix, self.server.domain_full, self.server.onion_domain, self.server.translate, self.server.system_language) msg = msg.encode('utf-8') msglen = len(msg) login_headers(self, 'text/html', msglen, calling_domain) write2(self, msg) fitness_performance(getreq_start_time, self.server.fitness, '_GET', 'show about screen', self.server.debug) return if self.path in ('/specification', '/protocol', '/activitypub'): if calling_domain.endswith('.onion'): msg = \ html_specification(self.server.base_dir, 'http', self.server.onion_domain, None, self.server.translate, self.server.system_language) elif calling_domain.endswith('.i2p'): msg = \ html_specification(self.server.base_dir, 'http', self.server.i2p_domain, None, self.server.translate, self.server.system_language) else: msg = \ html_specification(self.server.base_dir, self.server.http_prefix, self.server.domain_full, self.server.onion_domain, self.server.translate, self.server.system_language) msg = msg.encode('utf-8') msglen = len(msg) login_headers(self, 'text/html', msglen, calling_domain) write2(self, msg) fitness_performance(getreq_start_time, self.server.fitness, '_GET', 'show specification screen', self.server.debug) return if self.path in ('/manual', '/usermanual', '/userguide'): if calling_domain.endswith('.onion'): msg = \ html_manual(self.server.base_dir, 'http', self.server.onion_domain, None, self.server.translate, self.server.system_language) elif calling_domain.endswith('.i2p'): msg = \ html_manual(self.server.base_dir, 'http', self.server.i2p_domain, None, self.server.translate, self.server.system_language) else: msg = \ html_manual(self.server.base_dir, self.server.http_prefix, self.server.domain_full, self.server.onion_domain, self.server.translate, self.server.system_language) msg = msg.encode('utf-8') msglen = len(msg) login_headers(self, 'text/html', msglen, calling_domain) write2(self, msg) fitness_performance(getreq_start_time, self.server.fitness, '_GET', 'show user manual screen', self.server.debug) return if html_getreq and users_in_path and authorized and \ self.path.endswith('/accesskeys'): nickname = self.path.split('/users/')[1] if '/' in nickname: nickname = nickname.split('/')[0] access_keys = self.server.access_keys if self.server.key_shortcuts.get(nickname): access_keys = \ self.server.key_shortcuts[nickname] msg = \ html_access_keys(self.server.base_dir, nickname, self.server.domain, self.server.translate, access_keys, self.server.access_keys, self.server.default_timeline, self.server.theme_name) msg = msg.encode('utf-8') msglen = len(msg) login_headers(self, 'text/html', msglen, calling_domain) write2(self, msg) fitness_performance(getreq_start_time, self.server.fitness, '_GET', 'show accesskeys screen', self.server.debug) return if html_getreq and users_in_path and authorized and \ self.path.endswith('/themedesigner'): nickname = self.path.split('/users/')[1] if '/' in nickname: nickname = nickname.split('/')[0] if not is_artist(self.server.base_dir, nickname): http_403(self) return msg = \ html_theme_designer(self.server.base_dir, nickname, self.server.domain, self.server.translate, self.server.default_timeline, self.server.theme_name, self.server.access_keys) msg = msg.encode('utf-8') msglen = len(msg) login_headers(self, 'text/html', msglen, calling_domain) write2(self, msg) fitness_performance(getreq_start_time, self.server.fitness, '_GET', 'show theme designer screen', self.server.debug) return fitness_performance(getreq_start_time, self.server.fitness, '_GET', 'show about screen done', self.server.debug) # the initial welcome screen after first logging in if html_getreq and authorized and \ '/users/' in self.path and self.path.endswith('/welcome'): nickname = self.path.split('/users/')[1] if '/' in nickname: nickname = nickname.split('/')[0] if not is_welcome_screen_complete(self.server.base_dir, nickname, self.server.domain): msg = \ html_welcome_screen(self.server.base_dir, nickname, self.server.system_language, self.server.translate, self.server.theme_name) msg = msg.encode('utf-8') msglen = len(msg) login_headers(self, 'text/html', msglen, calling_domain) write2(self, msg) fitness_performance(getreq_start_time, self.server.fitness, '_GET', 'show welcome screen', self.server.debug) return self.path = self.path.replace('/welcome', '') # the welcome screen which allows you to set an avatar image if html_getreq and authorized and \ '/users/' in self.path and self.path.endswith('/welcome_profile'): nickname = self.path.split('/users/')[1] if '/' in nickname: nickname = nickname.split('/')[0] if not is_welcome_screen_complete(self.server.base_dir, nickname, self.server.domain): msg = \ html_welcome_profile(self.server.base_dir, nickname, self.server.domain, self.server.http_prefix, self.server.domain_full, self.server.system_language, self.server.translate, self.server.theme_name) msg = msg.encode('utf-8') msglen = len(msg) login_headers(self, 'text/html', msglen, calling_domain) write2(self, msg) fitness_performance(getreq_start_time, self.server.fitness, '_GET', 'show welcome profile screen', self.server.debug) return self.path = self.path.replace('/welcome_profile', '') # the final welcome screen if html_getreq and authorized and \ '/users/' in self.path and self.path.endswith('/welcome_final'): nickname = self.path.split('/users/')[1] if '/' in nickname: nickname = nickname.split('/')[0] if not is_welcome_screen_complete(self.server.base_dir, nickname, self.server.domain): msg = \ html_welcome_final(self.server.base_dir, nickname, self.server.system_language, self.server.translate, self.server.theme_name) msg = msg.encode('utf-8') msglen = len(msg) login_headers(self, 'text/html', msglen, calling_domain) write2(self, msg) fitness_performance(getreq_start_time, self.server.fitness, '_GET', 'show welcome final screen', self.server.debug) return self.path = self.path.replace('/welcome_final', '') # if not authorized then show the login screen if html_getreq and self.path != '/login' and \ not is_image_file(self.path) and \ self.path != '/' and \ not self.path.startswith('/.well-known/protocol-handler') and \ self.path != '/users/news/linksmobile' and \ self.path != '/users/news/newswiremobile': if _redirect_to_login_screen(self, calling_domain, self.path, self.server.http_prefix, self.server.domain_full, self.server.onion_domain, self.server.i2p_domain, getreq_start_time, authorized, self.server.debug): return fitness_performance(getreq_start_time, self.server.fitness, '_GET', 'show login screen done', self.server.debug) # manifest images used to create a home screen icon # when selecting "add to home screen" in browsers # which support progressive web apps if self.path in ('/logo72.png', '/logo96.png', '/logo128.png', '/logo144.png', '/logo150.png', '/logo192.png', '/logo256.png', '/logo512.png', '/apple-touch-icon.png'): media_filename = \ self.server.base_dir + '/img' + self.path if os.path.isfile(media_filename): if etag_exists(self, media_filename): # The file has not changed http_304(self) return tries = 0 media_binary = None while tries < 5: try: with open(media_filename, 'rb') as av_file: media_binary = av_file.read() break except OSError as ex: print('EX: manifest logo ' + str(tries) + ' ' + str(ex)) time.sleep(1) tries += 1 if media_binary: mime_type = media_file_mime_type(media_filename) set_headers_etag(self, media_filename, mime_type, media_binary, cookie, self.server.domain_full, False, None) write2(self, media_binary) fitness_performance(getreq_start_time, self.server.fitness, '_GET', 'manifest logo shown', self.server.debug) return http_404(self, 130) return fitness_performance(getreq_start_time, self.server.fitness, '_GET', 'manifest logo done', self.server.debug) # manifest images used to show example screenshots # for use by app stores if self.path == '/screenshot1.jpg' or \ self.path == '/screenshot2.jpg': screen_filename = \ self.server.base_dir + '/img' + self.path if os.path.isfile(screen_filename): if etag_exists(self, screen_filename): # The file has not changed http_304(self) return tries = 0 media_binary = None while tries < 5: try: with open(screen_filename, 'rb') as av_file: media_binary = av_file.read() break except OSError as ex: print('EX: manifest screenshot ' + str(tries) + ' ' + str(ex)) time.sleep(1) tries += 1 if media_binary: mime_type = media_file_mime_type(screen_filename) set_headers_etag(self, screen_filename, mime_type, media_binary, cookie, self.server.domain_full, False, None) write2(self, media_binary) fitness_performance(getreq_start_time, self.server.fitness, '_GET', 'show screenshot', self.server.debug) return http_404(self, 131) return fitness_performance(getreq_start_time, self.server.fitness, '_GET', 'show screenshot done', self.server.debug) # image on login screen or qrcode if (is_image_file(self.path) and (self.path.startswith('/login.') or self.path.startswith('/qrcode.png'))): icon_filename = \ self.server.base_dir + '/accounts' + self.path if os.path.isfile(icon_filename): if etag_exists(self, icon_filename): # The file has not changed http_304(self) return tries = 0 media_binary = None while tries < 5: try: with open(icon_filename, 'rb') as av_file: media_binary = av_file.read() break except OSError as ex: print('EX: login screen image ' + str(tries) + ' ' + str(ex)) time.sleep(1) tries += 1 if media_binary: mime_type_str = media_file_mime_type(icon_filename) set_headers_etag(self, icon_filename, mime_type_str, media_binary, cookie, self.server.domain_full, False, None) write2(self, media_binary) fitness_performance(getreq_start_time, self.server.fitness, '_GET', 'login screen logo', self.server.debug) return http_404(self, 132) return fitness_performance(getreq_start_time, self.server.fitness, '_GET', 'login screen logo done', self.server.debug) # QR code for account handle if users_in_path and \ self.path.endswith('/qrcode.png'): if _show_qrcode(self, calling_domain, self.path, self.server.base_dir, self.server.domain, self.server.onion_domain, self.server.i2p_domain, self.server.port, getreq_start_time): return fitness_performance(getreq_start_time, self.server.fitness, '_GET', 'account qrcode done', self.server.debug) # search screen banner image if users_in_path: if self.path.endswith('/search_banner.png'): if _search_screen_banner(self, self.path, self.server.base_dir, self.server.domain, getreq_start_time): return if self.path.endswith('/left_col_image.png'): if _column_image(self, 'left', self.path, self.server.base_dir, self.server.domain, getreq_start_time): return if self.path.endswith('/right_col_image.png'): if _column_image(self, 'right', self.path, self.server.base_dir, self.server.domain, getreq_start_time): return fitness_performance(getreq_start_time, self.server.fitness, '_GET', 'search screen banner done', self.server.debug) if self.path.startswith('/defaultprofilebackground'): _show_default_profile_background(self, self.server.base_dir, self.server.theme_name, getreq_start_time) return # show a background image on the login or person options page if '-background.' in self.path: if _show_background_image(self, self.path, self.server.base_dir, getreq_start_time): return fitness_performance(getreq_start_time, self.server.fitness, '_GET', 'background shown done', self.server.debug) # emoji images if '/emoji/' in self.path: _show_emoji(self, self.path, self.server.base_dir, getreq_start_time) return fitness_performance(getreq_start_time, self.server.fitness, '_GET', 'show emoji done', self.server.debug) # show media # Note that this comes before the busy flag to avoid conflicts # replace mastoson-style media path if '/system/media_attachments/files/' in self.path: self.path = self.path.replace('/system/media_attachments/files/', '/media/') if '/media/' in self.path: _show_media(self, self.path, self.server.base_dir, getreq_start_time) return if '/ontologies/' in self.path or \ '/data/' in self.path: if not has_users_path(self.path): _get_ontology(self, calling_domain, self.path, self.server.base_dir, getreq_start_time) return fitness_performance(getreq_start_time, self.server.fitness, '_GET', 'show media done', self.server.debug) # show shared item images # Note that this comes before the busy flag to avoid conflicts if '/sharefiles/' in self.path: if _show_share_image(self, self.path, self.server.base_dir, getreq_start_time): return fitness_performance(getreq_start_time, self.server.fitness, '_GET', 'share image done', self.server.debug) # icon images # Note that this comes before the busy flag to avoid conflicts if self.path.startswith('/icons/'): _show_icon(self, self.path, self.server.base_dir, getreq_start_time) return # show images within https://instancedomain/activitypub if self.path.startswith('/activitypub-tutorial-'): if self.path.endswith('.png'): _show_specification_image(self, self.path, self.server.base_dir, getreq_start_time) return # show images within https://instancedomain/manual if self.path.startswith('/manual-'): if is_image_file(self.path): _show_manual_image(self, self.path, self.server.base_dir, getreq_start_time) return # help screen images # Note that this comes before the busy flag to avoid conflicts if self.path.startswith('/helpimages/'): _show_help_screen_image(self, self.path, self.server.base_dir, getreq_start_time) return fitness_performance(getreq_start_time, self.server.fitness, '_GET', 'help screen image done', self.server.debug) # cached avatar images # Note that this comes before the busy flag to avoid conflicts if self.path.startswith('/avatars/'): _show_cached_avatar(self, referer_domain, self.path, self.server.base_dir, getreq_start_time) return fitness_performance(getreq_start_time, self.server.fitness, '_GET', 'cached avatar done', self.server.debug) # show avatar or background image # Note that this comes before the busy flag to avoid conflicts if _show_avatar_or_banner(self, referer_domain, self.path, self.server.base_dir, self.server.domain, getreq_start_time): return fitness_performance(getreq_start_time, self.server.fitness, '_GET', 'avatar or banner shown done', self.server.debug) # This busy state helps to avoid flooding # Resources which are expected to be called from a web page # should be above this curr_time_getreq = int(time.time() * 1000) if self.server.getreq_busy: if curr_time_getreq - self.server.last_getreq < 500: if self.server.debug: print('DEBUG: GET Busy') self.send_response(429) self.end_headers() return self.server.getreq_busy = True self.server.last_getreq = curr_time_getreq # returns after this point should set getreq_busy to False fitness_performance(getreq_start_time, self.server.fitness, '_GET', 'GET busy time', self.server.debug) if not permitted_dir(self.path): if self.server.debug: print('DEBUG: GET Not permitted') http_404(self, 133) self.server.getreq_busy = False return # get webfinger endpoint for a person if _webfinger(self, calling_domain, referer_domain, cookie): fitness_performance(getreq_start_time, self.server.fitness, '_GET', 'webfinger called', self.server.debug) self.server.getreq_busy = False return fitness_performance(getreq_start_time, self.server.fitness, '_GET', 'permitted directory', self.server.debug) # show the login screen if (self.path.startswith('/login') or (self.path == '/' and not authorized and not self.server.news_instance)): # request basic auth msg = html_login(self.server.translate, self.server.base_dir, self.server.http_prefix, self.server.domain_full, self.server.system_language, True, ua_str, self.server.theme_name).encode('utf-8') msglen = len(msg) login_headers(self, 'text/html', msglen, calling_domain) write2(self, msg) fitness_performance(getreq_start_time, self.server.fitness, '_GET', 'login shown', self.server.debug) self.server.getreq_busy = False return # show the news front page if self.path == '/' and \ not authorized and \ self.server.news_instance: news_url = get_instance_url(calling_domain, self.server.http_prefix, self.server.domain_full, self.server.onion_domain, self.server.i2p_domain) + \ '/users/news' logout_redirect(self, news_url, calling_domain) fitness_performance(getreq_start_time, self.server.fitness, '_GET', 'news front page shown', self.server.debug) self.server.getreq_busy = False return fitness_performance(getreq_start_time, self.server.fitness, '_GET', 'login shown done', self.server.debug) # the newswire screen on mobile if html_getreq and self.path.startswith('/users/') and \ self.path.endswith('/newswiremobile'): if (authorized or (not authorized and self.path.startswith('/users/news/') and self.server.news_instance)): nickname = get_nickname_from_actor(self.path) if not nickname: http_404(self, 134) self.server.getreq_busy = False return timeline_path = \ '/users/' + nickname + '/' + self.server.default_timeline show_publish_as_icon = self.server.show_publish_as_icon rss_icon_at_top = self.server.rss_icon_at_top icons_as_buttons = self.server.icons_as_buttons default_timeline = self.server.default_timeline access_keys = self.server.access_keys if self.server.key_shortcuts.get(nickname): access_keys = self.server.key_shortcuts[nickname] msg = \ html_newswire_mobile(self.server.base_dir, nickname, self.server.domain, self.server.domain_full, self.server.translate, self.server.newswire, self.server.positive_voting, timeline_path, show_publish_as_icon, authorized, rss_icon_at_top, icons_as_buttons, default_timeline, self.server.theme_name, access_keys, ua_str).encode('utf-8') msglen = len(msg) set_headers(self, 'text/html', msglen, cookie, calling_domain, False) write2(self, msg) self.server.getreq_busy = False return if html_getreq and self.path.startswith('/users/') and \ self.path.endswith('/linksmobile'): if (authorized or (not authorized and self.path.startswith('/users/news/') and self.server.news_instance)): nickname = get_nickname_from_actor(self.path) if not nickname: http_404(self, 135) self.server.getreq_busy = False return access_keys = self.server.access_keys if self.server.key_shortcuts.get(nickname): access_keys = self.server.key_shortcuts[nickname] timeline_path = \ '/users/' + nickname + '/' + self.server.default_timeline icons_as_buttons = self.server.icons_as_buttons default_timeline = self.server.default_timeline shared_items_domains = \ self.server.shared_items_federated_domains msg = \ html_links_mobile(self.server.base_dir, nickname, self.server.domain_full, self.server.http_prefix, self.server.translate, timeline_path, authorized, self.server.rss_icon_at_top, icons_as_buttons, default_timeline, self.server.theme_name, access_keys, shared_items_domains).encode('utf-8') msglen = len(msg) set_headers(self, 'text/html', msglen, cookie, calling_domain, False) write2(self, msg) self.server.getreq_busy = False return if '?remotetag=' in self.path and \ '/users/' in self.path and authorized: actor = self.path.split('?remotetag=')[0] nickname = get_nickname_from_actor(actor) hashtag_url = self.path.split('?remotetag=')[1] if ';' in hashtag_url: hashtag_url = hashtag_url.split(';')[0] hashtag_url = hashtag_url.replace('--', '/') page_number = 1 if ';page=' in self.path: page_number_str = self.path.split(';page=')[1] if ';' in page_number_str: page_number_str = page_number_str.split(';')[0] if page_number_str.isdigit(): page_number = int(page_number_str) allow_local_network_access = self.server.allow_local_network_access show_published_date_only = self.server.show_published_date_only twitter_replacement_domain = self.server.twitter_replacement_domain timezone = None if self.server.account_timezone.get(nickname): timezone = \ self.server.account_timezone.get(nickname) msg = \ html_hashtag_search_remote(nickname, self.server.domain, self.server.port, self.server.recent_posts_cache, self.server.max_recent_posts, self.server.translate, self.server.base_dir, hashtag_url, page_number, MAX_POSTS_IN_FEED, self.server.session, self.server.cached_webfingers, self.server.person_cache, self.server.http_prefix, self.server.project_version, self.server.yt_replace_domain, twitter_replacement_domain, show_published_date_only, self.server.peertube_instances, allow_local_network_access, self.server.theme_name, self.server.system_language, self.server.max_like_count, self.server.signing_priv_key_pem, self.server.cw_lists, self.server.lists_enabled, timezone, self.server.bold_reading, self.server.dogwhistles, self.server.min_images_for_accounts, self.server.debug, self.server.buy_sites, self.server.auto_cw_cache) if msg: msg = msg.encode('utf-8') msglen = len(msg) set_headers(self, 'text/html', msglen, cookie, calling_domain, False) write2(self, msg) self.server.getreq_busy = False return else: hashtag = urllib.parse.unquote(hashtag_url.split('/')[-1]) tags_filename = \ self.server.base_dir + '/tags/' + hashtag + '.txt' if os.path.isfile(tags_filename): # redirect to the local hashtag screen self.server.getreq_busy = False ht_url = \ get_instance_url(calling_domain, self.server.http_prefix, self.server.domain_full, self.server.onion_domain, self.server.i2p_domain) + \ '/users/' + nickname + '/tags/' + hashtag redirect_headers(self, ht_url, cookie, calling_domain) else: # redirect to the upstream hashtag url self.server.getreq_busy = False redirect_headers(self, hashtag_url, None, calling_domain) return # hashtag search if self.path.startswith('/tags/') or \ (authorized and '/tags/' in self.path): if self.path.startswith('/tags/rss2/'): hashtag_search_rss2(self, calling_domain, self.path, cookie, self.server.base_dir, self.server.http_prefix, self.server.domain, self.server.domain_full, self.server.port, self.server.onion_domain, self.server.i2p_domain, getreq_start_time) self.server.getreq_busy = False return if not html_getreq: hashtag_search_json2(self, calling_domain, referer_domain, self.path, cookie, self.server.base_dir, self.server.http_prefix, self.server.domain, self.server.domain_full, self.server.port, self.server.onion_domain, self.server.i2p_domain, getreq_start_time, MAX_POSTS_IN_FEED) self.server.getreq_busy = False return hashtag_search2(self, calling_domain, self.path, cookie, self.server.base_dir, self.server.http_prefix, self.server.domain, self.server.domain_full, self.server.port, self.server.onion_domain, self.server.i2p_domain, getreq_start_time, curr_session, MAX_POSTS_IN_HASHTAG_FEED) self.server.getreq_busy = False return # hashtag map kml if self.path.startswith('/tagmaps/') or \ (authorized and '/tagmaps/' in self.path): map_str = \ map_format_from_tagmaps_path(self.server.base_dir, self.path, self.server.map_format, self.server.domain) if map_str: msg = map_str.encode('utf-8') msglen = len(msg) if self.server.map_format == 'gpx': header_type = \ 'application/gpx+xml; charset=utf-8' else: header_type = \ 'application/vnd.google-earth.kml+xml; charset=utf-8' set_headers(self, header_type, msglen, None, calling_domain, True) write2(self, msg) self.server.getreq_busy = False return http_404(self, 136) self.server.getreq_busy = False return fitness_performance(getreq_start_time, self.server.fitness, '_GET', 'hashtag search done', self.server.debug) # show or hide buttons in the web interface if html_getreq and users_in_path and \ self.path.endswith('/minimal') and \ authorized: nickname = self.path.split('/users/')[1] if '/' in nickname: nickname = nickname.split('/')[0] not_min = not is_minimal(self.server.base_dir, self.server.domain, nickname) set_minimal(self.server.base_dir, self.server.domain, nickname, not_min) self.path = get_default_path(self.server.media_instance, self.server.blogs_instance, nickname) # search for a fediverse address, shared item or emoji # from the web interface by selecting search icon if html_getreq and users_in_path: if self.path.endswith('/search') or \ '/search?' in self.path: if '?' in self.path: self.path = self.path.split('?')[0] nickname = self.path.split('/users/')[1] if '/' in nickname: nickname = nickname.split('/')[0] access_keys = self.server.access_keys if self.server.key_shortcuts.get(nickname): access_keys = self.server.key_shortcuts[nickname] # show the search screen msg = html_search(self.server.translate, self.server.base_dir, self.path, self.server.domain, self.server.default_timeline, self.server.theme_name, self.server.text_mode_banner, access_keys) if msg: msg = msg.encode('utf-8') msglen = len(msg) set_headers(self, 'text/html', msglen, cookie, calling_domain, False) write2(self, msg) fitness_performance(getreq_start_time, self.server.fitness, '_GET', 'search screen shown', self.server.debug) self.server.getreq_busy = False return # show a hashtag category from the search screen if html_getreq and '/category/' in self.path: msg = html_search_hashtag_category(self.server.translate, self.server.base_dir, self.path, self.server.domain, self.server.theme_name) if msg: msg = msg.encode('utf-8') msglen = len(msg) set_headers(self, 'text/html', msglen, cookie, calling_domain, False) write2(self, msg) fitness_performance(getreq_start_time, self.server.fitness, '_GET', 'hashtag category screen shown', self.server.debug) self.server.getreq_busy = False return fitness_performance(getreq_start_time, self.server.fitness, '_GET', 'search screen shown done', self.server.debug) # Show the html calendar for a user if html_getreq and users_in_path: if '/calendar' in self.path: nickname = self.path.split('/users/')[1] if '/' in nickname: nickname = nickname.split('/')[0] access_keys = self.server.access_keys if self.server.key_shortcuts.get(nickname): access_keys = self.server.key_shortcuts[nickname] # show the calendar screen msg = html_calendar(self.server.person_cache, self.server.translate, self.server.base_dir, self.path, self.server.http_prefix, self.server.domain_full, self.server.text_mode_banner, access_keys, False, self.server.system_language, self.server.default_timeline, self.server.theme_name) if msg: msg = msg.encode('utf-8') msglen = len(msg) if 'ical=true' in self.path: set_headers(self, 'text/calendar', msglen, cookie, calling_domain, False) else: set_headers(self, 'text/html', msglen, cookie, calling_domain, False) write2(self, msg) fitness_performance(getreq_start_time, self.server.fitness, '_GET', 'calendar shown', self.server.debug) else: http_404(self, 137) self.server.getreq_busy = False return # Show the icalendar for a user if icalendar_getreq and users_in_path: if '/calendar' in self.path: nickname = self.path.split('/users/')[1] if '/' in nickname: nickname = nickname.split('/')[0] access_keys = self.server.access_keys if self.server.key_shortcuts.get(nickname): access_keys = self.server.key_shortcuts[nickname] # show the calendar screen msg = html_calendar(self.server.person_cache, self.server.translate, self.server.base_dir, self.path, self.server.http_prefix, self.server.domain_full, self.server.text_mode_banner, access_keys, True, self.server.system_language, self.server.default_timeline, self.server.theme_name) if msg: msg = msg.encode('utf-8') msglen = len(msg) set_headers(self, 'text/calendar', msglen, cookie, calling_domain, False) write2(self, msg) else: http_404(self, 138) fitness_performance(getreq_start_time, self.server.fitness, '_GET', 'icalendar shown', self.server.debug) self.server.getreq_busy = False return fitness_performance(getreq_start_time, self.server.fitness, '_GET', 'calendar shown done', self.server.debug) # Show confirmation for deleting a calendar event if html_getreq and users_in_path: if '/eventdelete' in self.path and \ '?time=' in self.path and \ '?eventid=' in self.path: if _confirm_delete_event(self, calling_domain, self.path, self.server.base_dir, self.server.http_prefix, cookie, self.server.translate, self.server.domain_full, self.server.onion_domain, self.server.i2p_domain, getreq_start_time): self.server.getreq_busy = False return fitness_performance(getreq_start_time, self.server.fitness, '_GET', 'calendar delete shown done', self.server.debug) # search for emoji by name if html_getreq and users_in_path: if self.path.endswith('/searchemoji'): # show the search screen msg = \ html_search_emoji_text_entry(self.server.translate, self.server.base_dir, self.path).encode('utf-8') msglen = len(msg) set_headers(self, 'text/html', msglen, cookie, calling_domain, False) write2(self, msg) fitness_performance(getreq_start_time, self.server.fitness, '_GET', 'emoji search shown', self.server.debug) self.server.getreq_busy = False return fitness_performance(getreq_start_time, self.server.fitness, '_GET', 'emoji search shown done', self.server.debug) repeat_private = False if html_getreq and '?repeatprivate=' in self.path: repeat_private = True self.path = self.path.replace('?repeatprivate=', '?repeat=') # announce/repeat button was pressed if authorized and html_getreq and '?repeat=' in self.path: announce_button(self, calling_domain, self.path, self.server.base_dir, cookie, proxy_type, self.server.http_prefix, self.server.domain, self.server.domain_full, self.server.port, self.server.onion_domain, self.server.i2p_domain, getreq_start_time, repeat_private, self.server.debug, curr_session, self.server.sites_unavailable) self.server.getreq_busy = False return fitness_performance(getreq_start_time, self.server.fitness, '_GET', 'show announce done', self.server.debug) if authorized and html_getreq and '?unrepeatprivate=' in self.path: self.path = self.path.replace('?unrepeatprivate=', '?unrepeat=') # undo an announce/repeat from the web interface if authorized and html_getreq and '?unrepeat=' in self.path: announce_button_undo(self, calling_domain, self.path, self.server.base_dir, cookie, proxy_type, self.server.http_prefix, self.server.domain, self.server.domain_full, self.server.onion_domain, self.server.i2p_domain, getreq_start_time, self.server.debug, self.server.recent_posts_cache, curr_session) self.server.getreq_busy = False return fitness_performance(getreq_start_time, self.server.fitness, '_GET', 'unannounce done', self.server.debug) # send a newswire moderation vote from the web interface if authorized and '/newswirevote=' in self.path and \ self.path.startswith('/users/'): _newswire_vote(self, calling_domain, self.path, cookie, self.server.base_dir, self.server.http_prefix, self.server.domain_full, self.server.onion_domain, self.server.i2p_domain, getreq_start_time, self.server.newswire) self.server.getreq_busy = False return # send a newswire moderation unvote from the web interface if authorized and '/newswireunvote=' in self.path and \ self.path.startswith('/users/'): _newswire_unvote(self, calling_domain, self.path, cookie, self.server.base_dir, self.server.http_prefix, self.server.domain_full, self.server.onion_domain, self.server.i2p_domain, getreq_start_time, self.server.debug, self.server.newswire) self.server.getreq_busy = False return # send a follow request approval from the web interface if authorized and '/followapprove=' in self.path and \ self.path.startswith('/users/'): follow_approve_button(self, calling_domain, self.path, cookie, self.server.base_dir, self.server.http_prefix, self.server.domain, self.server.domain_full, self.server.port, self.server.onion_domain, self.server.i2p_domain, getreq_start_time, proxy_type, self.server.debug, curr_session) self.server.getreq_busy = False return fitness_performance(getreq_start_time, self.server.fitness, '_GET', 'follow approve done', self.server.debug) # deny a follow request from the web interface if authorized and '/followdeny=' in self.path and \ self.path.startswith('/users/'): follow_deny_button(self, calling_domain, self.path, cookie, self.server.base_dir, self.server.http_prefix, self.server.domain, self.server.domain_full, self.server.port, self.server.onion_domain, self.server.i2p_domain, getreq_start_time, self.server.debug) self.server.getreq_busy = False return fitness_performance(getreq_start_time, self.server.fitness, '_GET', 'follow deny done', self.server.debug) # like from the web interface icon if authorized and html_getreq and '?like=' in self.path: like_button(self, calling_domain, self.path, self.server.base_dir, self.server.http_prefix, self.server.domain, self.server.domain_full, self.server.onion_domain, self.server.i2p_domain, getreq_start_time, proxy_type, cookie, self.server.debug, curr_session) self.server.getreq_busy = False return fitness_performance(getreq_start_time, self.server.fitness, '_GET', 'like button done', self.server.debug) # undo a like from the web interface icon if authorized and html_getreq and '?unlike=' in self.path: like_button_undo(self, calling_domain, self.path, self.server.base_dir, self.server.http_prefix, self.server.domain, self.server.domain_full, self.server.onion_domain, self.server.i2p_domain, getreq_start_time, proxy_type, cookie, self.server.debug, curr_session) self.server.getreq_busy = False return fitness_performance(getreq_start_time, self.server.fitness, '_GET', 'unlike button done', self.server.debug) # emoji reaction from the web interface icon if authorized and html_getreq and \ '?react=' in self.path and \ '?actor=' in self.path: reaction_button(self, calling_domain, self.path, self.server.base_dir, self.server.http_prefix, self.server.domain, self.server.domain_full, self.server.onion_domain, self.server.i2p_domain, getreq_start_time, proxy_type, cookie, self.server.debug, curr_session) self.server.getreq_busy = False return fitness_performance(getreq_start_time, self.server.fitness, '_GET', 'emoji reaction button done', self.server.debug) # undo an emoji reaction from the web interface icon if authorized and html_getreq and \ '?unreact=' in self.path and \ '?actor=' in self.path: reaction_button_undo(self, calling_domain, self.path, self.server.base_dir, self.server.http_prefix, self.server.domain, self.server.domain_full, self.server.onion_domain, self.server.i2p_domain, getreq_start_time, proxy_type, cookie, self.server.debug, curr_session) self.server.getreq_busy = False return fitness_performance(getreq_start_time, self.server.fitness, '_GET', 'unreaction button done', self.server.debug) # bookmark from the web interface icon if authorized and html_getreq and '?bookmark=' in self.path: bookmark_button(self, calling_domain, self.path, self.server.base_dir, self.server.http_prefix, self.server.domain, self.server.domain_full, self.server.port, self.server.onion_domain, self.server.i2p_domain, getreq_start_time, proxy_type, cookie, self.server.debug, curr_session) self.server.getreq_busy = False return fitness_performance(getreq_start_time, self.server.fitness, '_GET', 'bookmark shown done', self.server.debug) # emoji recation from the web interface bottom icon if authorized and html_getreq and '?selreact=' in self.path: _reaction_picker2(self, calling_domain, self.path, self.server.base_dir, self.server.http_prefix, self.server.domain, self.server.port, getreq_start_time, cookie, self.server.debug, curr_session) self.server.getreq_busy = False return fitness_performance(getreq_start_time, self.server.fitness, '_GET', 'bookmark shown done', self.server.debug) # undo a bookmark from the web interface icon if authorized and html_getreq and '?unbookmark=' in self.path: bookmark_button_undo(self, calling_domain, self.path, self.server.base_dir, self.server.http_prefix, self.server.domain, self.server.domain_full, self.server.port, self.server.onion_domain, self.server.i2p_domain, getreq_start_time, proxy_type, cookie, self.server.debug, curr_session) self.server.getreq_busy = False return fitness_performance(getreq_start_time, self.server.fitness, '_GET', 'unbookmark shown done', self.server.debug) # delete button is pressed on a post if authorized and html_getreq and '?delete=' in self.path: delete_button(self, calling_domain, self.path, self.server.base_dir, self.server.http_prefix, self.server.domain_full, self.server.onion_domain, self.server.i2p_domain, getreq_start_time, proxy_type, cookie, self.server.debug, curr_session) self.server.getreq_busy = False return fitness_performance(getreq_start_time, self.server.fitness, '_GET', 'delete shown done', self.server.debug) # The mute button is pressed if authorized and html_getreq and '?mute=' in self.path: mute_button(self, calling_domain, self.path, self.server.base_dir, self.server.http_prefix, self.server.domain, self.server.domain_full, self.server.port, self.server.onion_domain, self.server.i2p_domain, getreq_start_time, cookie, self.server.debug, curr_session) self.server.getreq_busy = False return fitness_performance(getreq_start_time, self.server.fitness, '_GET', 'post muted done', self.server.debug) # unmute a post from the web interface icon if authorized and html_getreq and '?unmute=' in self.path: mute_button_undo(self, calling_domain, self.path, self.server.base_dir, self.server.http_prefix, self.server.domain, self.server.domain_full, self.server.port, self.server.onion_domain, self.server.i2p_domain, getreq_start_time, cookie, self.server.debug, curr_session) self.server.getreq_busy = False return fitness_performance(getreq_start_time, self.server.fitness, '_GET', 'unmute activated done', self.server.debug) # reply from the web interface icon in_reply_to_url = None reply_to_list = [] reply_page_number = 1 reply_category = '' share_description = None conversation_id = None if html_getreq: if '?conversationId=' in self.path: conversation_id = self.path.split('?conversationId=')[1] if '?' in conversation_id: conversation_id = conversation_id.split('?')[0] # public reply if '?replyto=' in self.path: in_reply_to_url = self.path.split('?replyto=')[1] if '?' in in_reply_to_url: mentions_list = in_reply_to_url.split('?') for ment in mentions_list: if ment.startswith('mention='): reply_handle = ment.replace('mention=', '') if reply_handle not in reply_to_list: reply_to_list.append(reply_handle) if ment.startswith('page='): reply_page_str = ment.replace('page=', '') if len(reply_page_str) > 5: reply_page_str = "1" if reply_page_str.isdigit(): reply_page_number = int(reply_page_str) in_reply_to_url = mentions_list[0] if not self.server.public_replies_unlisted: self.path = self.path.split('?replyto=')[0] + '/newpost' else: self.path = \ self.path.split('?replyto=')[0] + '/newunlisted' if self.server.debug: print('DEBUG: replyto path ' + self.path) # unlisted reply if '?replyunlisted=' in self.path: in_reply_to_url = self.path.split('?replyunlisted=')[1] if '?' in in_reply_to_url: mentions_list = in_reply_to_url.split('?') for ment in mentions_list: if ment.startswith('mention='): reply_handle = ment.replace('mention=', '') if reply_handle not in reply_to_list: reply_to_list.append(reply_handle) if ment.startswith('page='): reply_page_str = ment.replace('page=', '') if len(reply_page_str) > 5: reply_page_str = "1" if reply_page_str.isdigit(): reply_page_number = int(reply_page_str) in_reply_to_url = mentions_list[0] self.path = \ self.path.split('?replyunlisted=')[0] + '/newunlisted' if self.server.debug: print('DEBUG: replyunlisted path ' + self.path) # reply to followers if '?replyfollowers=' in self.path: in_reply_to_url = self.path.split('?replyfollowers=')[1] if '?' in in_reply_to_url: mentions_list = in_reply_to_url.split('?') for ment in mentions_list: if ment.startswith('mention='): reply_handle = ment.replace('mention=', '') ment2 = ment.replace('mention=', '') if ment2 not in reply_to_list: reply_to_list.append(reply_handle) if ment.startswith('page='): reply_page_str = ment.replace('page=', '') if len(reply_page_str) > 5: reply_page_str = "1" if reply_page_str.isdigit(): reply_page_number = int(reply_page_str) in_reply_to_url = mentions_list[0] self.path = self.path.split('?replyfollowers=')[0] + \ '/newfollowers' if self.server.debug: print('DEBUG: replyfollowers path ' + self.path) # replying as a direct message, # for moderation posts or the dm timeline reply_is_chat = False if '?replydm=' in self.path or '?replychat=' in self.path: reply_type = 'replydm' if '?replychat=' in self.path: reply_type = 'replychat' reply_is_chat = True in_reply_to_url = self.path.split('?' + reply_type + '=')[1] in_reply_to_url = urllib.parse.unquote_plus(in_reply_to_url) if '?' in in_reply_to_url: # multiple parameters mentions_list = in_reply_to_url.split('?') for ment in mentions_list: if ment.startswith('mention='): reply_handle = ment.replace('mention=', '') in_reply_to_url = reply_handle if reply_handle not in reply_to_list: reply_to_list.append(reply_handle) elif ment.startswith('page='): reply_page_str = ment.replace('page=', '') if len(reply_page_str) > 5: reply_page_str = "1" if reply_page_str.isdigit(): reply_page_number = int(reply_page_str) elif ment.startswith('category='): reply_category = ment.replace('category=', '') elif ment.startswith('sharedesc:'): # get the title for the shared item share_description = \ ment.replace('sharedesc:', '').strip() share_description = \ share_description.replace('_', ' ') in_reply_to_url = mentions_list[0] else: # single parameter if in_reply_to_url.startswith('mention='): reply_handle = in_reply_to_url.replace('mention=', '') in_reply_to_url = reply_handle if reply_handle not in reply_to_list: reply_to_list.append(reply_handle) elif in_reply_to_url.startswith('sharedesc:'): # get the title for the shared item share_description = \ in_reply_to_url.replace('sharedesc:', '').strip() share_description = \ share_description.replace('_', ' ') self.path = \ self.path.split('?' + reply_type + '=')[0] + '/newdm' if self.server.debug: print('DEBUG: ' + reply_type + ' path ' + self.path) # Edit a blog post if authorized and \ '/users/' in self.path and \ '?editblogpost=' in self.path and \ ';actor=' in self.path: message_id = self.path.split('?editblogpost=')[1] if ';' in message_id: message_id = message_id.split(';')[0] actor = self.path.split(';actor=')[1] if ';' in actor: actor = actor.split(';')[0] nickname = get_nickname_from_actor(self.path.split('?')[0]) if not nickname: http_404(self, 139) self.server.getreq_busy = False return if nickname == actor: post_url = \ local_actor_url(self.server.http_prefix, nickname, self.server.domain_full) + \ '/statuses/' + message_id msg = html_edit_blog(self.server.media_instance, self.server.translate, self.server.base_dir, self.path, reply_page_number, nickname, self.server.domain, post_url, self.server.system_language) if msg: msg = msg.encode('utf-8') msglen = len(msg) set_headers(self, 'text/html', msglen, cookie, calling_domain, False) write2(self, msg) self.server.getreq_busy = False return # Edit a post edit_post_params = {} if authorized and \ '/users/' in self.path and \ '?postedit=' in self.path and \ ';scope=' in self.path and \ ';actor=' in self.path: post_scope = self.path.split(';scope=')[1] if ';' in post_scope: post_scope = post_scope.split(';')[0] edit_post_params['scope'] = post_scope message_id = self.path.split('?postedit=')[1] if ';' in message_id: message_id = message_id.split(';')[0] if ';replyTo=' in self.path: reply_to = self.path.split(';replyTo=')[1] if ';' in reply_to: reply_to = message_id.split(';')[0] edit_post_params['replyTo'] = reply_to actor = self.path.split(';actor=')[1] if ';' in actor: actor = actor.split(';')[0] edit_post_params['actor'] = actor nickname = get_nickname_from_actor(self.path.split('?')[0]) edit_post_params['nickname'] = nickname if not nickname: http_404(self, 140) self.server.getreq_busy = False return if nickname != actor: http_404(self, 141) self.server.getreq_busy = False return post_url = \ local_actor_url(self.server.http_prefix, nickname, self.server.domain_full) + \ '/statuses/' + message_id edit_post_params['post_url'] = post_url # use the new post functions, but using edit_post_params new_post_scope = post_scope if post_scope == 'public': new_post_scope = 'post' self.path = '/users/' + nickname + '/new' + new_post_scope # list of known crawlers accessing nodeinfo or masto API if _show_known_crawlers(self, calling_domain, self.path, self.server.base_dir, self.server.known_crawlers): self.server.getreq_busy = False return # edit profile in web interface if _edit_profile2(self, calling_domain, self.path, self.server.translate, self.server.base_dir, self.server.domain, self.server.port, cookie): self.server.getreq_busy = False return # edit links from the left column of the timeline in web interface if _edit_links2(self, calling_domain, self.path, self.server.translate, self.server.base_dir, self.server.domain, cookie, self.server.theme_name): self.server.getreq_busy = False return # edit newswire from the right column of the timeline if _edit_newswire2(self, calling_domain, self.path, self.server.translate, self.server.base_dir, self.server.domain, cookie): self.server.getreq_busy = False return # edit news post if _edit_news_post2(self, calling_domain, self.path, self.server.translate, self.server.base_dir, self.server.http_prefix, self.server.domain, self.server.domain_full, cookie): self.server.getreq_busy = False return if _show_new_post(self, edit_post_params, calling_domain, self.path, self.server.media_instance, self.server.translate, self.server.base_dir, self.server.http_prefix, in_reply_to_url, reply_to_list, reply_is_chat, share_description, reply_page_number, reply_category, self.server.domain, self.server.domain_full, getreq_start_time, cookie, no_drop_down, conversation_id, curr_session): self.server.getreq_busy = False return fitness_performance(getreq_start_time, self.server.fitness, '_GET', 'new post done', self.server.debug) # get an individual post from the path /@nickname/statusnumber if _show_individual_at_post(self, ssml_getreq, authorized, calling_domain, referer_domain, self.path, self.server.base_dir, self.server.http_prefix, self.server.domain, self.server.domain_full, self.server.port, getreq_start_time, proxy_type, cookie, self.server.debug, curr_session): self.server.getreq_busy = False return # show the likers of a post if _show_likers_of_post(self, authorized, calling_domain, self.path, self.server.base_dir, self.server.http_prefix, self.server.domain, self.server.port, getreq_start_time, cookie, self.server.debug, curr_session): self.server.getreq_busy = False return # show the announcers/repeaters of a post if _show_announcers_of_post(self, authorized, calling_domain, self.path, self.server.base_dir, self.server.http_prefix, self.server.domain, self.server.port, getreq_start_time, cookie, self.server.debug, curr_session): self.server.getreq_busy = False return fitness_performance(getreq_start_time, self.server.fitness, '_GET', 'individual post done', self.server.debug) # get replies to a post /users/nickname/statuses/number/replies if self.path.endswith('/replies') or '/replies?page=' in self.path: if _show_replies_to_post(self, authorized, calling_domain, referer_domain, self.path, self.server.base_dir, self.server.http_prefix, self.server.domain, self.server.domain_full, self.server.port, getreq_start_time, proxy_type, cookie, self.server.debug, curr_session): self.server.getreq_busy = False return fitness_performance(getreq_start_time, self.server.fitness, '_GET', 'post replies done', self.server.debug) # roles on profile screen if self.path.endswith('/roles') and users_in_path: if _show_roles(self, calling_domain, referer_domain, self.path, self.server.base_dir, self.server.http_prefix, self.server.domain, getreq_start_time, proxy_type, cookie, self.server.debug, curr_session): self.server.getreq_busy = False return fitness_performance(getreq_start_time, self.server.fitness, '_GET', 'show roles done', self.server.debug) # show skills on the profile page if self.path.endswith('/skills') and users_in_path: if _show_skills(self, calling_domain, referer_domain, self.path, self.server.base_dir, self.server.http_prefix, self.server.domain, getreq_start_time, proxy_type, cookie, self.server.debug, curr_session): self.server.getreq_busy = False return fitness_performance(getreq_start_time, self.server.fitness, '_GET', 'show skills done', self.server.debug) if '?notifypost=' in self.path and users_in_path and authorized: if _show_notify_post(self, authorized, calling_domain, referer_domain, self.path, self.server.base_dir, self.server.http_prefix, self.server.domain, self.server.port, getreq_start_time, proxy_type, cookie, self.server.debug, curr_session): self.server.getreq_busy = False return # get an individual post from the path # /users/nickname/statuses/number if '/statuses/' in self.path and users_in_path: if _show_individual_post(self, ssml_getreq, authorized, calling_domain, referer_domain, self.path, self.server.base_dir, self.server.http_prefix, self.server.domain, self.server.domain_full, self.server.port, getreq_start_time, proxy_type, cookie, self.server.debug, curr_session): self.server.getreq_busy = False return fitness_performance(getreq_start_time, self.server.fitness, '_GET', 'show status done', self.server.debug) # get the inbox timeline for a given person if self.path.endswith('/inbox') or '/inbox?page=' in self.path: if _show_inbox(self, authorized, calling_domain, referer_domain, self.path, self.server.base_dir, self.server.http_prefix, self.server.domain, self.server.port, getreq_start_time, cookie, self.server.debug, self.server.recent_posts_cache, curr_session, self.server.default_timeline, self.server.max_recent_posts, self.server.translate, self.server.cached_webfingers, self.server.person_cache, self.server.allow_deletion, self.server.project_version, self.server.yt_replace_domain, self.server.twitter_replacement_domain, ua_str): self.server.getreq_busy = False return fitness_performance(getreq_start_time, self.server.fitness, '_GET', 'show inbox done', self.server.debug) # get the direct messages timeline for a given person if self.path.endswith('/dm') or '/dm?page=' in self.path: if show_dms(self, authorized, calling_domain, referer_domain, self.path, self.server.base_dir, self.server.http_prefix, self.server.domain, self.server.port, getreq_start_time, cookie, self.server.debug, curr_session, ua_str, MAX_POSTS_IN_FEED): self.server.getreq_busy = False return fitness_performance(getreq_start_time, self.server.fitness, '_GET', 'show dms done', self.server.debug) # get the replies timeline for a given person if self.path.endswith('/tlreplies') or '/tlreplies?page=' in self.path: if show_replies(self, authorized, calling_domain, referer_domain, self.path, self.server.base_dir, self.server.http_prefix, self.server.domain, self.server.port, getreq_start_time, cookie, self.server.debug, curr_session, ua_str, MAX_POSTS_IN_FEED): self.server.getreq_busy = False return fitness_performance(getreq_start_time, self.server.fitness, '_GET', 'show replies 2 done', self.server.debug) # get the media timeline for a given person if self.path.endswith('/tlmedia') or '/tlmedia?page=' in self.path: if show_media_timeline(self, authorized, calling_domain, referer_domain, self.path, self.server.base_dir, self.server.http_prefix, self.server.domain, self.server.port, getreq_start_time, cookie, self.server.debug, curr_session, ua_str, MAX_POSTS_IN_MEDIA_FEED): self.server.getreq_busy = False return fitness_performance(getreq_start_time, self.server.fitness, '_GET', 'show media 2 done', self.server.debug) # get the blogs for a given person if self.path.endswith('/tlblogs') or '/tlblogs?page=' in self.path: if show_blogs_timeline(self, authorized, calling_domain, referer_domain, self.path, self.server.base_dir, self.server.http_prefix, self.server.domain, self.server.port, getreq_start_time, cookie, self.server.debug, curr_session, ua_str, MAX_POSTS_IN_BLOGS_FEED): self.server.getreq_busy = False return fitness_performance(getreq_start_time, self.server.fitness, '_GET', 'show blogs 2 done', self.server.debug) # get the news for a given person if self.path.endswith('/tlnews') or '/tlnews?page=' in self.path: if show_news_timeline(self, authorized, calling_domain, referer_domain, self.path, self.server.base_dir, self.server.http_prefix, self.server.domain, self.server.port, getreq_start_time, cookie, self.server.debug, curr_session, ua_str, MAX_POSTS_IN_NEWS_FEED): self.server.getreq_busy = False return # get features (local blogs) for a given person if self.path.endswith('/tlfeatures') or \ '/tlfeatures?page=' in self.path: if show_features_timeline(self, authorized, calling_domain, referer_domain, self.path, self.server.base_dir, self.server.http_prefix, self.server.domain, self.server.port, getreq_start_time, cookie, self.server.debug, curr_session, ua_str, MAX_POSTS_IN_NEWS_FEED): self.server.getreq_busy = False return fitness_performance(getreq_start_time, self.server.fitness, '_GET', 'show news 2 done', self.server.debug) # get the shared items timeline for a given person if self.path.endswith('/tlshares') or '/tlshares?page=' in self.path: if show_shares_timeline(self, authorized, calling_domain, self.path, self.server.base_dir, self.server.http_prefix, self.server.domain, self.server.port, getreq_start_time, cookie, self.server.debug, curr_session, ua_str, MAX_POSTS_IN_FEED): self.server.getreq_busy = False return # get the wanted items timeline for a given person if self.path.endswith('/tlwanted') or '/tlwanted?page=' in self.path: if show_wanted_timeline(self, authorized, calling_domain, self.path, self.server.base_dir, self.server.http_prefix, self.server.domain, self.server.port, getreq_start_time, cookie, self.server.debug, curr_session, ua_str, MAX_POSTS_IN_FEED): self.server.getreq_busy = False return fitness_performance(getreq_start_time, self.server.fitness, '_GET', 'show shares 2 done', self.server.debug) # block a domain from html_account_info if authorized and users_in_path and \ '/accountinfo?blockdomain=' in self.path and \ '?handle=' in self.path: nickname = self.path.split('/users/')[1] if '/' in nickname: nickname = nickname.split('/')[0] if not is_moderator(self.server.base_dir, nickname): http_400(self) self.server.getreq_busy = False return block_domain = self.path.split('/accountinfo?blockdomain=')[1] search_handle = block_domain.split('?handle=')[1] search_handle = urllib.parse.unquote_plus(search_handle) block_domain = block_domain.split('?handle=')[0] block_domain = urllib.parse.unquote_plus(block_domain.strip()) if '?' in block_domain: block_domain = block_domain.split('?')[0] add_global_block(self.server.base_dir, '*', block_domain, None) self.server.blocked_cache_last_updated = \ update_blocked_cache(self.server.base_dir, self.server.blocked_cache, self.server.blocked_cache_last_updated, 0) msg = \ html_account_info(self.server.translate, self.server.base_dir, self.server.http_prefix, nickname, self.server.domain, search_handle, self.server.debug, self.server.system_language, self.server.signing_priv_key_pem, None, self.server.block_federated) if msg: msg = msg.encode('utf-8') msglen = len(msg) login_headers(self, 'text/html', msglen, calling_domain) write2(self, msg) self.server.getreq_busy = False return # unblock a domain from html_account_info if authorized and users_in_path and \ '/accountinfo?unblockdomain=' in self.path and \ '?handle=' in self.path: nickname = self.path.split('/users/')[1] if '/' in nickname: nickname = nickname.split('/')[0] if not is_moderator(self.server.base_dir, nickname): http_400(self) self.server.getreq_busy = False return block_domain = self.path.split('/accountinfo?unblockdomain=')[1] search_handle = block_domain.split('?handle=')[1] search_handle = urllib.parse.unquote_plus(search_handle) block_domain = block_domain.split('?handle=')[0] block_domain = urllib.parse.unquote_plus(block_domain.strip()) remove_global_block(self.server.base_dir, '*', block_domain) self.server.blocked_cache_last_updated = \ update_blocked_cache(self.server.base_dir, self.server.blocked_cache, self.server.blocked_cache_last_updated, 0) msg = \ html_account_info(self.server.translate, self.server.base_dir, self.server.http_prefix, nickname, self.server.domain, search_handle, self.server.debug, self.server.system_language, self.server.signing_priv_key_pem, None, self.server.block_federated) if msg: msg = msg.encode('utf-8') msglen = len(msg) login_headers(self, 'text/html', msglen, calling_domain) write2(self, msg) self.server.getreq_busy = False return # get the bookmarks timeline for a given person if self.path.endswith('/tlbookmarks') or \ '/tlbookmarks?page=' in self.path or \ self.path.endswith('/bookmarks') or \ '/bookmarks?page=' in self.path: if show_bookmarks_timeline(self, authorized, calling_domain, referer_domain, self.path, self.server.base_dir, self.server.http_prefix, self.server.domain, self.server.port, getreq_start_time, cookie, self.server.debug, curr_session, ua_str, MAX_POSTS_IN_FEED): self.server.getreq_busy = False return fitness_performance(getreq_start_time, self.server.fitness, '_GET', 'show bookmarks 2 done', self.server.debug) # outbox timeline if self.path.endswith('/outbox') or \ '/outbox?page=' in self.path: if show_outbox_timeline(self, authorized, calling_domain, referer_domain, self.path, self.server.base_dir, self.server.http_prefix, self.server.domain, self.server.port, getreq_start_time, cookie, self.server.debug, curr_session, ua_str, proxy_type, MAX_POSTS_IN_FEED): self.server.getreq_busy = False return fitness_performance(getreq_start_time, self.server.fitness, '_GET', 'show outbox done', self.server.debug) # get the moderation feed for a moderator if self.path.endswith('/moderation') or \ '/moderation?' in self.path: if show_mod_timeline(self, authorized, calling_domain, referer_domain, self.path, self.server.base_dir, self.server.http_prefix, self.server.domain, self.server.port, getreq_start_time, cookie, self.server.debug, curr_session, ua_str, MAX_POSTS_IN_FEED): self.server.getreq_busy = False return fitness_performance(getreq_start_time, self.server.fitness, '_GET', 'show moderation done', self.server.debug) if show_shares_feed(self, authorized, calling_domain, referer_domain, self.path, self.server.base_dir, self.server.http_prefix, self.server.domain, self.server.port, getreq_start_time, proxy_type, cookie, self.server.debug, 'shares', curr_session, SHARES_PER_PAGE): self.server.getreq_busy = False return fitness_performance(getreq_start_time, self.server.fitness, '_GET', 'show profile 2 done', self.server.debug) if show_following_feed(self, authorized, calling_domain, referer_domain, self.path, self.server.base_dir, self.server.http_prefix, self.server.domain, self.server.port, getreq_start_time, proxy_type, cookie, self.server.debug, curr_session, FOLLOWS_PER_PAGE): self.server.getreq_busy = False return fitness_performance(getreq_start_time, self.server.fitness, '_GET', 'show profile 3 done', self.server.debug) if show_moved_feed(self, authorized, calling_domain, referer_domain, self.path, self.server.base_dir, self.server.http_prefix, self.server.domain, self.server.port, getreq_start_time, proxy_type, cookie, self.server.debug, curr_session, FOLLOWS_PER_PAGE): self.server.getreq_busy = False return fitness_performance(getreq_start_time, self.server.fitness, '_GET', 'show moved 4 done', self.server.debug) if show_inactive_feed(self, authorized, calling_domain, referer_domain, self.path, self.server.base_dir, self.server.http_prefix, self.server.domain, self.server.port, getreq_start_time, proxy_type, cookie, self.server.debug, curr_session, self.server.dormant_months, self.server.sites_unavailable, FOLLOWS_PER_PAGE): self.server.getreq_busy = False return fitness_performance(getreq_start_time, self.server.fitness, '_GET', 'show inactive 5 done', self.server.debug) if show_followers_feed(self, authorized, calling_domain, referer_domain, self.path, self.server.base_dir, self.server.http_prefix, self.server.domain, self.server.port, getreq_start_time, proxy_type, cookie, self.server.debug, curr_session, FOLLOWS_PER_PAGE): self.server.getreq_busy = False return fitness_performance(getreq_start_time, self.server.fitness, '_GET', 'show profile 5 done', self.server.debug) # look up a person if _show_person_profile(self, authorized, calling_domain, referer_domain, self.path, self.server.base_dir, self.server.http_prefix, self.server.domain, self.server.onion_domain, self.server.i2p_domain, getreq_start_time, proxy_type, cookie, self.server.debug, curr_session): self.server.getreq_busy = False return fitness_performance(getreq_start_time, self.server.fitness, '_GET', 'show profile posts done', self.server.debug) # check that a json file was requested if not self.path.endswith('.json'): if self.server.debug: print('DEBUG: GET Not json: ' + self.path + ' ' + self.server.base_dir) http_404(self, 142) self.server.getreq_busy = False return if not secure_mode(curr_session, proxy_type, False, self.server, self.headers, self.path): if self.server.debug: print('WARN: Unauthorized GET') http_404(self, 143) self.server.getreq_busy = False return fitness_performance(getreq_start_time, self.server.fitness, '_GET', 'authorized fetch', self.server.debug) # check that the file exists filename = self.server.base_dir + self.path if os.path.isfile(filename): content = None try: with open(filename, 'r', encoding='utf-8') as rfile: content = rfile.read() except OSError: print('EX: unable to read file ' + filename) if content: try: content_json = json.loads(content) except json.decoder.JSONDecodeError as ex: http_400(self) print('EX: json decode error ' + str(ex) + ' from GET content_json ' + str(content)) self.server.getreq_busy = False return msg_str = json.dumps(content_json, ensure_ascii=False) msg_str = convert_domains(calling_domain, referer_domain, msg_str, self.server.http_prefix, self.server.domain, self.server.onion_domain, self.server.i2p_domain) msg = msg_str.encode('utf-8') msglen = len(msg) accept_str = self.headers['Accept'] protocol_str = \ get_json_content_from_accept(accept_str) set_headers(self, protocol_str, msglen, None, calling_domain, False) write2(self, msg) fitness_performance(getreq_start_time, self.server.fitness, '_GET', 'arbitrary json', self.server.debug) else: if self.server.debug: print('DEBUG: GET Unknown file') http_404(self, 144) self.server.getreq_busy = False fitness_performance(getreq_start_time, self.server.fitness, '_GET', 'end benchmarks', self.server.debug) def _permitted_crawler_path(self, path: str) -> bool: """Is the given path permitted to be crawled by a search engine? this should only allow through basic information, such as nodeinfo """ if path == '/' or path == '/about' or path == '/login' or \ path.startswith('/blog/'): return True return False def _get_referer_domain(self, ua_str: str) -> str: """Returns the referer domain Which domain is the GET request coming from? """ referer_domain = None if self.headers.get('referer'): referer_domain = \ user_agent_domain(self.headers['referer'], self.server.debug) elif self.headers.get('Referer'): referer_domain = \ user_agent_domain(self.headers['Referer'], self.server.debug) elif self.headers.get('Signature'): if 'keyId="' in self.headers['Signature']: referer_domain = self.headers['Signature'].split('keyId="')[1] if '/' in referer_domain: referer_domain = referer_domain.split('/')[0] elif '#' in referer_domain: referer_domain = referer_domain.split('#')[0] elif '"' in referer_domain: referer_domain = referer_domain.split('"')[0] elif ua_str: referer_domain = user_agent_domain(ua_str, self.server.debug) return referer_domain def _show_vcard(self, base_dir: str, path: str, calling_domain: str, referer_domain: str, domain: str) -> bool: """Returns a vcard for the given account """ if not has_accept(self, calling_domain): return False if path.endswith('.vcf'): path = path.split('.vcf')[0] accept_str = 'text/vcard' else: accept_str = self.headers['Accept'] if 'text/vcard' not in accept_str and \ 'application/vcard+xml' not in accept_str: return False if path.startswith('/@'): if '/@/' not in path: path = path.replace('/@', '/users/', 1) if not path.startswith('/users/'): http_400(self) return True nickname = path.split('/users/')[1] if '/' in nickname: nickname = nickname.split('/')[0] if '?' in nickname: nickname = nickname.split('?')[0] if self.server.vcard_is_active: print('vcard is busy during request from ' + str(referer_domain)) http_503(self) return True self.server.vcard_is_active = True actor_json = None actor_filename = \ acct_dir(base_dir, nickname, domain) + '.json' if os.path.isfile(actor_filename): actor_json = load_json(actor_filename) if not actor_json: print('WARN: vcard actor not found ' + actor_filename) http_404(self, 3) self.server.vcard_is_active = False return True if 'application/vcard+xml' in accept_str: vcard_str = actor_to_vcard_xml(actor_json, domain) header_type = 'application/vcard+xml; charset=utf-8' else: vcard_str = actor_to_vcard(actor_json, domain) header_type = 'text/vcard; charset=utf-8' if vcard_str: msg = vcard_str.encode('utf-8') msglen = len(msg) set_headers(self, header_type, msglen, None, calling_domain, True) write2(self, msg) print('vcard sent to ' + str(referer_domain)) self.server.vcard_is_active = False return True print('WARN: vcard string not returned') http_404(self, 4) self.server.vcard_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') http_400(self) 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) http_503(self) 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) http_400(self) 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) http_400(self) self.server.security_txt_is_active = False return True if not referer_is_active(http_prefix, referer_domain, ua_str, calling_site_timeout, self.server.sites_unavailable): print('security.txt referer url is not active ' + referer_domain) http_400(self) 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) set_headers(self, 'text/plain; charset=utf-8', msglen, None, calling_domain, True) write2(self, 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 _show_instance_actor(self, calling_domain: str, referer_domain: str, path: str, base_dir: str, http_prefix: str, domain: str, domain_full: str, onion_domain: str, i2p_domain: str, getreq_start_time, cookie: str, debug: str, enable_shared_inbox: bool) -> bool: """Shows the instance actor """ if debug: print('Instance actor requested by ' + calling_domain) if request_http(self.headers, debug): http_404(self, 88) return False actor_json = person_lookup(domain, path, base_dir) if not actor_json: print('ERROR: no instance actor found') http_404(self, 89) return False accept_str = self.headers['Accept'] actor_domain_url = get_instance_url(calling_domain, http_prefix, domain_full, onion_domain, i2p_domain) actor_url = actor_domain_url + '/users/Actor' remove_fields = ( 'icon', 'image', 'tts', 'shares', 'alsoKnownAs', 'hasOccupation', 'featured', 'featuredTags', 'discoverable', 'published', 'devices' ) for rfield in remove_fields: if rfield in actor_json: del actor_json[rfield] actor_json['endpoints'] = {} if enable_shared_inbox: actor_json['endpoints'] = { 'sharedInbox': actor_domain_url + '/inbox' } actor_json['name'] = 'ACTOR' actor_json['preferredUsername'] = domain_full actor_json['id'] = actor_domain_url + '/actor' actor_json['type'] = 'Application' actor_json['summary'] = 'Instance Actor' actor_json['publicKey']['id'] = actor_domain_url + '/actor#main-key' actor_json['publicKey']['owner'] = actor_domain_url + '/actor' actor_json['url'] = actor_domain_url + '/actor' actor_json['inbox'] = actor_url + '/inbox' actor_json['followers'] = actor_url + '/followers' actor_json['following'] = actor_url + '/following' msg_str = json.dumps(actor_json, ensure_ascii=False) msg_str = convert_domains(calling_domain, referer_domain, msg_str, http_prefix, domain, self.server.onion_domain, self.server.i2p_domain) msg = msg_str.encode('utf-8') msglen = len(msg) if 'application/ld+json' in accept_str: set_headers(self, 'application/ld+json', msglen, cookie, calling_domain, False) elif 'application/jrd+json' in accept_str: set_headers(self, 'application/jrd+json', msglen, cookie, calling_domain, False) else: set_headers(self, 'application/activity+json', msglen, cookie, calling_domain, False) write2(self, msg) fitness_performance(getreq_start_time, self.server.fitness, '_GET', '_show_instance_actor', debug) return True def _browser_config(self, calling_domain: str, referer_domain: str, getreq_start_time) -> None: """Used by MS Windows to put an icon on the desktop if you link to a website """ xml_str = \ '\n' + \ '\n' + \ ' \n' + \ ' \n' + \ ' \n' + \ ' #eeeeee\n' + \ ' \n' + \ ' \n' + \ '' msg_str = json.dumps(xml_str, ensure_ascii=False) msg_str = convert_domains(calling_domain, referer_domain, msg_str, self.server.http_prefix, self.server.domain, self.server.onion_domain, self.server.i2p_domain) msg = msg_str.encode('utf-8') msglen = len(msg) set_headers(self, 'application/xrd+xml', msglen, None, calling_domain, False) write2(self, msg) if self.server.debug: print('Sent browserconfig: ' + calling_domain) fitness_performance(getreq_start_time, self.server.fitness, '_GET', '_browser_config', self.server.debug) def _show_conversation_thread(self, authorized: bool, calling_domain: str, path: str, base_dir: str, http_prefix: str, domain: str, port: int, debug: str, curr_session, cookie: str) -> bool: """get conversation thread from the date link on a post """ if not path.startswith('/users/'): return False if '?convthread=' not in path: return False post_id = path.split('?convthread=')[1].strip() post_id = post_id.replace('--', '/') if post_id.startswith('/users/'): instance_url = get_instance_url(calling_domain, self.server.http_prefix, self.server.domain_full, self.server.onion_domain, self.server.i2p_domain) post_id = instance_url + post_id nickname = path.split('/users/')[1] if '?convthread=' in nickname: nickname = nickname.split('?convthread=')[0] if '/' in nickname: nickname = nickname.split('/')[0] timezone = None if self.server.account_timezone.get(nickname): timezone = \ self.server.account_timezone.get(nickname) bold_reading = False if self.server.bold_reading.get(nickname): bold_reading = True conv_str = \ html_conversation_view(authorized, post_id, self.server.translate, base_dir, http_prefix, nickname, domain, self.server.project_version, self.server.recent_posts_cache, self.server.max_recent_posts, curr_session, self.server.cached_webfingers, self.server.person_cache, port, self.server.yt_replace_domain, self.server.twitter_replacement_domain, self.server.show_published_date_only, self.server.peertube_instances, self.server.allow_local_network_access, self.server.theme_name, self.server.system_language, self.server.max_like_count, self.server.signing_priv_key_pem, self.server.cw_lists, self.server.lists_enabled, timezone, bold_reading, self.server.dogwhistles, self.server.access_keys, self.server.min_images_for_accounts, debug, self.server.buy_sites, self.server.blocked_cache, self.server.block_federated, self.server.auto_cw_cache) if conv_str: msg = conv_str.encode('utf-8') msglen = len(msg) login_headers(self, 'text/html', msglen, calling_domain) write2(self, msg) self.server.getreq_busy = False return True # redirect to the original site if there are no results if '://' + self.server.domain_full + '/' in post_id: redirect_headers(self, post_id, cookie, calling_domain) else: redirect_headers(self, post_id, None, calling_domain) self.server.getreq_busy = False return True def _get_newswire_feed(self, calling_domain: str, path: str, proxy_type: str, getreq_start_time, debug: bool, curr_session) -> None: """Returns the newswire feed """ curr_session = \ establish_session("get_newswire_feed", curr_session, proxy_type, self.server) if not curr_session: http_404(self, 25) return msg = get_rs_sfrom_dict(self.server.newswire, self.server.http_prefix, self.server.domain_full, self.server.translate) if msg: msg = msg.encode('utf-8') msglen = len(msg) set_headers(self, 'text/xml', msglen, None, calling_domain, True) write2(self, msg) if debug: print('Sent rss2 newswire feed: ' + path + ' ' + calling_domain) fitness_performance(getreq_start_time, self.server.fitness, '_GET', '_get_newswire_feed', debug) return if debug: print('Failed to get rss2 newswire feed: ' + path + ' ' + calling_domain) http_404(self, 26) def _get_rss2feed(self, calling_domain: str, path: str, base_dir: str, http_prefix: str, domain: str, port: int, proxy_type: str, getreq_start_time, debug: bool, curr_session) -> None: """Returns an RSS2 feed for the blog """ nickname = path.split('/blog/')[1] if '/' in nickname: nickname = nickname.split('/')[0] if not nickname.startswith('rss.'): account_dir = acct_dir(self.server.base_dir, nickname, domain) if os.path.isdir(account_dir): curr_session = \ establish_session("RSS request", curr_session, proxy_type, self.server) if not curr_session: return msg = \ html_blog_page_rss2(base_dir, http_prefix, self.server.translate, nickname, domain, port, MAX_POSTS_IN_RSS_FEED, 1, True, self.server.system_language) if msg is not None: msg = msg.encode('utf-8') msglen = len(msg) set_headers(self, 'text/xml', msglen, None, calling_domain, True) write2(self, msg) if debug: print('Sent rss2 feed: ' + path + ' ' + calling_domain) fitness_performance(getreq_start_time, self.server.fitness, '_GET', '_get_rss2feed', debug) return if debug: print('Failed to get rss2 feed: ' + path + ' ' + calling_domain) http_404(self, 22) def _get_rss2site(self, calling_domain: str, path: str, base_dir: str, http_prefix: str, domain_full: str, port: int, proxy_type: str, translate: {}, getreq_start_time, debug: bool, curr_session) -> None: """Returns an RSS2 feed for all blogs on this instance """ curr_session = \ establish_session("get_rss2site", curr_session, proxy_type, self.server) if not curr_session: http_404(self, 23) return msg = '' for _, dirs, _ in os.walk(base_dir + '/accounts'): for acct in dirs: if not is_account_dir(acct): continue nickname = acct.split('@')[0] domain = acct.split('@')[1] msg += \ html_blog_page_rss2(base_dir, http_prefix, self.server.translate, nickname, domain, port, MAX_POSTS_IN_RSS_FEED, 1, False, self.server.system_language) break if msg: msg = rss2header(http_prefix, 'news', domain_full, 'Site', translate) + msg + rss2footer() msg = msg.encode('utf-8') msglen = len(msg) set_headers(self, 'text/xml', msglen, None, calling_domain, True) write2(self, msg) if debug: print('Sent rss2 feed: ' + path + ' ' + calling_domain) fitness_performance(getreq_start_time, self.server.fitness, '_GET', '_get_rss2site', debug) return if debug: print('Failed to get rss2 feed: ' + path + ' ' + calling_domain) http_404(self, 24) def _get_rss3feed(self, calling_domain: str, path: str, base_dir: str, http_prefix: str, domain: str, port: int, proxy_type: str, getreq_start_time, debug: bool, system_language: str, curr_session) -> None: """Returns an RSS3 feed """ nickname = path.split('/blog/')[1] if '/' in nickname: nickname = nickname.split('/')[0] if not nickname.startswith('rss.'): account_dir = acct_dir(base_dir, nickname, domain) if os.path.isdir(account_dir): curr_session = \ establish_session("get_rss3feed", curr_session, proxy_type, self.server) if not curr_session: http_404(self, 29) return msg = \ html_blog_page_rss3(base_dir, http_prefix, nickname, domain, port, MAX_POSTS_IN_RSS_FEED, 1, system_language) if msg is not None: msg = msg.encode('utf-8') msglen = len(msg) set_headers(self, 'text/plain; charset=utf-8', msglen, None, calling_domain, True) write2(self, msg) if self.server.debug: print('Sent rss3 feed: ' + path + ' ' + calling_domain) fitness_performance(getreq_start_time, self.server.fitness, '_GET', '_get_rss3feed', debug) return if debug: print('Failed to get rss3 feed: ' + path + ' ' + calling_domain) http_404(self, 20) def _get_following_json(self, base_dir: str, path: str, calling_domain: str, referer_domain: str, http_prefix: str, domain: str, port: int, following_items_per_page: int, debug: bool, list_name: str = 'following') -> None: """Returns json collection for following.txt """ following_json = \ get_following_feed(base_dir, domain, port, path, http_prefix, True, following_items_per_page, list_name) if not following_json: if debug: print(list_name + ' json feed not found for ' + path) http_404(self, 109) return msg_str = json.dumps(following_json, ensure_ascii=False) msg_str = convert_domains(calling_domain, referer_domain, msg_str, http_prefix, domain, self.server.onion_domain, self.server.i2p_domain) msg = msg_str.encode('utf-8') msglen = len(msg) accept_str = self.headers['Accept'] protocol_str = \ get_json_content_from_accept(accept_str) set_headers(self, protocol_str, msglen, None, calling_domain, False) write2(self, msg) def _get_speaker(self, calling_domain: str, referer_domain: str, path: str, base_dir: str, domain: str) -> None: """Returns the speaker file used for TTS and accessed via c2s """ nickname = path.split('/users/')[1] if '/' in nickname: nickname = nickname.split('/')[0] speaker_filename = \ acct_dir(base_dir, nickname, domain) + '/speaker.json' if not os.path.isfile(speaker_filename): http_404(self, 18) return speaker_json = load_json(speaker_filename) msg_str = json.dumps(speaker_json, ensure_ascii=False) msg_str = convert_domains(calling_domain, referer_domain, msg_str, self.server.http_prefix, domain, self.server.onion_domain, self.server.i2p_domain) msg = msg_str.encode('utf-8') msglen = len(msg) protocol_str = \ get_json_content_from_accept(self.headers['Accept']) set_headers(self, protocol_str, msglen, None, calling_domain, False) write2(self, msg) def _get_featured_collection(self, calling_domain: str, referer_domain: str, base_dir: str, http_prefix: str, nickname: str, domain: str, domain_full: str, system_language: str) -> None: """Returns the featured posts collections in actor/collections/featured """ featured_collection = \ json_pin_post(base_dir, http_prefix, nickname, domain, domain_full, system_language) msg_str = json.dumps(featured_collection, ensure_ascii=False) msg_str = convert_domains(calling_domain, referer_domain, msg_str, http_prefix, domain, self.server.onion_domain, self.server.i2p_domain) msg = msg_str.encode('utf-8') msglen = len(msg) accept_str = self.headers['Accept'] protocol_str = \ get_json_content_from_accept(accept_str) set_headers(self, protocol_str, msglen, None, calling_domain, False) write2(self, msg) def _get_featured_tags_collection(self, calling_domain: str, referer_domain: str, path: str, http_prefix: str, domain_full: str, domain: str): """Returns the featured tags collections in actor/collections/featuredTags """ post_context = get_individual_post_context() featured_tags_collection = { '@context': post_context, 'id': http_prefix + '://' + domain_full + path, 'orderedItems': [], 'totalItems': 0, 'type': 'OrderedCollection' } msg_str = json.dumps(featured_tags_collection, ensure_ascii=False) msg_str = convert_domains(calling_domain, referer_domain, msg_str, http_prefix, domain, self.server.onion_domain, self.server.i2p_domain) msg = msg_str.encode('utf-8') msglen = len(msg) accept_str = self.headers['Accept'] protocol_str = \ get_json_content_from_accept(accept_str) set_headers(self, protocol_str, msglen, None, calling_domain, False) write2(self, msg) def _show_blog_page(self, authorized: bool, calling_domain: str, path: str, base_dir: str, http_prefix: str, domain: str, port: int, getreq_start_time, proxy_type: str, cookie: str, translate: {}, debug: str, curr_session) -> bool: """Shows a blog page """ page_number = 1 nickname = path.split('/blog/')[1] if '/' in nickname: nickname = nickname.split('/')[0] if '?' in nickname: nickname = nickname.split('?')[0] if '?page=' in path: page_number_str = path.split('?page=')[1] if ';' in page_number_str: page_number_str = page_number_str.split(';')[0] if '?' in page_number_str: page_number_str = page_number_str.split('?')[0] if '#' in page_number_str: page_number_str = page_number_str.split('#')[0] if len(page_number_str) > 5: page_number_str = "1" if page_number_str.isdigit(): page_number = int(page_number_str) if page_number < 1: page_number = 1 elif page_number > 10: page_number = 10 curr_session = \ establish_session("showBlogPage", curr_session, proxy_type, self.server) if not curr_session: http_404(self, 90) self.server.getreq_busy = False return True msg = html_blog_page(authorized, curr_session, base_dir, http_prefix, translate, nickname, domain, port, MAX_POSTS_IN_BLOGS_FEED, page_number, self.server.peertube_instances, self.server.system_language, self.server.person_cache, debug) if msg is not None: msg = msg.encode('utf-8') msglen = len(msg) set_headers(self, 'text/html', msglen, cookie, calling_domain, False) write2(self, msg) fitness_performance(getreq_start_time, self.server.fitness, '_GET', '_show_blog_page', debug) return True http_404(self, 91) return True def _redirect_to_login_screen(self, calling_domain: str, path: str, http_prefix: str, domain_full: str, onion_domain: str, i2p_domain: str, getreq_start_time, authorized: bool, debug: bool): """Redirects to the login screen if necessary """ divert_to_login_screen = False if '/media/' not in path and \ '/ontologies/' not in path and \ '/data/' not in path and \ '/sharefiles/' not in path and \ '/statuses/' not in path and \ '/emoji/' not in path and \ '/tags/' not in path and \ '/tagmaps/' not in path and \ '/avatars/' not in path and \ '/favicons/' not in path and \ '/headers/' not in path and \ '/fonts/' not in path and \ '/icons/' not in path: divert_to_login_screen = True if path.startswith('/users/'): nick_str = path.split('/users/')[1] if '/' not in nick_str and '?' not in nick_str: divert_to_login_screen = False else: if path.endswith('/following') or \ path.endswith('/followers') or \ path.endswith('/skills') or \ path.endswith('/roles') or \ path.endswith('/wanted') or \ path.endswith('/shares'): divert_to_login_screen = False if divert_to_login_screen and not authorized: divert_path = '/login' if self.server.news_instance: # for news instances if not logged in then show the # front page divert_path = '/users/news' if debug: print('DEBUG: divert_to_login_screen=' + str(divert_to_login_screen)) print('DEBUG: authorized=' + str(authorized)) print('DEBUG: path=' + path) redirect_url = \ get_instance_url(calling_domain, http_prefix, domain_full, onion_domain, i2p_domain) + \ divert_path redirect_headers(self, redirect_url, None, calling_domain) fitness_performance(getreq_start_time, self.server.fitness, '_GET', '_redirect_to_login_screen', debug) return True return False def _show_qrcode(self, calling_domain: str, path: str, base_dir: str, domain: str, onion_domain: str, i2p_domain: str, port: int, getreq_start_time) -> bool: """Shows a QR code for an account """ nickname = get_nickname_from_actor(path) if not nickname: http_404(self, 93) return True if onion_domain: qrcode_domain = onion_domain port = 80 elif i2p_domain: qrcode_domain = i2p_domain port = 80 else: qrcode_domain = domain save_person_qrcode(base_dir, nickname, domain, qrcode_domain, port) qr_filename = \ acct_dir(base_dir, nickname, domain) + '/qrcode.png' if os.path.isfile(qr_filename): if etag_exists(self, qr_filename): # The file has not changed http_304(self) return tries = 0 media_binary = None while tries < 5: try: with open(qr_filename, 'rb') as av_file: media_binary = av_file.read() break except OSError as ex: print('EX: _show_qrcode ' + str(tries) + ' ' + str(ex)) time.sleep(1) tries += 1 if media_binary: mime_type = media_file_mime_type(qr_filename) set_headers_etag(self, qr_filename, mime_type, media_binary, None, self.server.domain_full, False, None) write2(self, media_binary) fitness_performance(getreq_start_time, self.server.fitness, '_GET', '_show_qrcode', self.server.debug) return True http_404(self, 94) return True def _search_screen_banner(self, path: str, base_dir: str, domain: str, getreq_start_time) -> bool: """Shows a banner image on the search screen """ nickname = get_nickname_from_actor(path) if not nickname: http_404(self, 95) return True banner_filename = \ acct_dir(base_dir, nickname, domain) + '/search_banner.png' if not os.path.isfile(banner_filename): if os.path.isfile(base_dir + '/theme/default/search_banner.png'): copyfile(base_dir + '/theme/default/search_banner.png', banner_filename) if os.path.isfile(banner_filename): if etag_exists(self, banner_filename): # The file has not changed http_304(self) return True tries = 0 media_binary = None while tries < 5: try: with open(banner_filename, 'rb') as av_file: media_binary = av_file.read() break except OSError as ex: print('EX: _search_screen_banner ' + str(tries) + ' ' + str(ex)) time.sleep(1) tries += 1 if media_binary: mime_type = media_file_mime_type(banner_filename) set_headers_etag(self, banner_filename, mime_type, media_binary, None, self.server.domain_full, False, None) write2(self, media_binary) fitness_performance(getreq_start_time, self.server.fitness, '_GET', '_search_screen_banner', self.server.debug) return True http_404(self, 96) return True def _column_image(self, side: str, path: str, base_dir: str, domain: str, getreq_start_time) -> bool: """Shows an image at the top of the left/right column """ nickname = get_nickname_from_actor(path) if not nickname: http_404(self, 97) return True banner_filename = \ acct_dir(base_dir, nickname, domain) + '/' + \ side + '_col_image.png' if os.path.isfile(banner_filename): if etag_exists(self, banner_filename): # The file has not changed http_304(self) return True tries = 0 media_binary = None while tries < 5: try: with open(banner_filename, 'rb') as av_file: media_binary = av_file.read() break except OSError as ex: print('EX: _column_image ' + str(tries) + ' ' + str(ex)) time.sleep(1) tries += 1 if media_binary: mime_type = media_file_mime_type(banner_filename) set_headers_etag(self, banner_filename, mime_type, media_binary, None, self.server.domain_full, False, None) write2(self, media_binary) fitness_performance(getreq_start_time, self.server.fitness, '_GET', '_column_image ' + side, self.server.debug) return True http_404(self, 98) return True def _show_default_profile_background(self, base_dir: str, theme_name: str, getreq_start_time) -> bool: """If a background image is missing after searching for a handle then substitute this image """ image_extensions = get_image_extensions() for ext in image_extensions: bg_filename = \ base_dir + '/theme/' + theme_name + '/image.' + ext if os.path.isfile(bg_filename): if etag_exists(self, bg_filename): # The file has not changed http_304(self) return True tries = 0 bg_binary = None while tries < 5: try: with open(bg_filename, 'rb') as av_file: bg_binary = av_file.read() break except OSError as ex: print('EX: _show_default_profile_background ' + str(tries) + ' ' + str(ex)) time.sleep(1) tries += 1 if bg_binary: if ext == 'jpg': ext = 'jpeg' set_headers_etag(self, bg_filename, 'image/' + ext, bg_binary, None, self.server.domain_full, False, None) write2(self, bg_binary) fitness_performance(getreq_start_time, self.server.fitness, '_GET', '_show_default_profile_background', self.server.debug) return True break http_404(self, 100) return True def _show_background_image(self, path: str, base_dir: str, getreq_start_time) -> bool: """Show a background image """ image_extensions = get_image_extensions() for ext in image_extensions: for bg_im in ('follow', 'options', 'login', 'welcome'): # follow screen background image if path.endswith('/' + bg_im + '-background.' + ext): bg_filename = \ base_dir + '/accounts/' + \ bg_im + '-background.' + ext if os.path.isfile(bg_filename): if etag_exists(self, bg_filename): # The file has not changed http_304(self) return True tries = 0 bg_binary = None while tries < 5: try: with open(bg_filename, 'rb') as av_file: bg_binary = av_file.read() break except OSError as ex: print('EX: _show_background_image ' + str(tries) + ' ' + str(ex)) time.sleep(1) tries += 1 if bg_binary: if ext == 'jpg': ext = 'jpeg' set_headers_etag(self, bg_filename, 'image/' + ext, bg_binary, None, self.server.domain_full, False, None) write2(self, bg_binary) fitness_performance(getreq_start_time, self.server.fitness, '_GET', '_show_background_image', self.server.debug) return True http_404(self, 99) return True def _show_emoji(self, path: str, base_dir: str, getreq_start_time) -> None: """Returns an emoji image """ if is_image_file(path): emoji_str = path.split('/emoji/')[1] emoji_filename = base_dir + '/emoji/' + emoji_str if not os.path.isfile(emoji_filename): emoji_filename = base_dir + '/emojicustom/' + emoji_str if os.path.isfile(emoji_filename): if etag_exists(self, emoji_filename): # The file has not changed http_304(self) return media_image_type = get_image_mime_type(emoji_filename) media_binary = None try: with open(emoji_filename, 'rb') as av_file: media_binary = av_file.read() except OSError: print('EX: unable to read emoji image ' + emoji_filename) if media_binary: set_headers_etag(self, emoji_filename, media_image_type, media_binary, None, self.server.domain_full, False, None) write2(self, media_binary) fitness_performance(getreq_start_time, self.server.fitness, '_GET', '_show_emoji', self.server.debug) return http_404(self, 36) def _show_media(self, path: str, base_dir: str, getreq_start_time) -> None: """Returns a media file """ if is_image_file(path) or \ path_is_video(path) or \ path_is_transcript(path) or \ path_is_audio(path): media_str = path.split('/media/')[1] media_filename = base_dir + '/media/' + media_str if os.path.isfile(media_filename): if etag_exists(self, media_filename): # The file has not changed http_304(self) return media_file_type = media_file_mime_type(media_filename) media_tm = os.path.getmtime(media_filename) last_modified_time = \ datetime.datetime.fromtimestamp(media_tm, datetime.timezone.utc) last_modified_time_str = \ last_modified_time.strftime('%a, %d %b %Y %H:%M:%S GMT') if media_filename.endswith('.vtt'): media_transcript = None try: with open(media_filename, 'r', encoding='utf-8') as fp_vtt: media_transcript = fp_vtt.read() media_file_type = 'text/vtt; charset=utf-8' except OSError: print('EX: unable to read media binary ' + media_filename) if media_transcript: media_transcript = media_transcript.encode('utf-8') set_headers_etag(self, media_filename, media_file_type, media_transcript, None, None, True, last_modified_time_str) write2(self, media_transcript) fitness_performance(getreq_start_time, self.server.fitness, '_GET', '_show_media', self.server.debug) return http_404(self, 32) return media_binary = None try: with open(media_filename, 'rb') as av_file: media_binary = av_file.read() except OSError: print('EX: unable to read media binary ' + media_filename) if media_binary: set_headers_etag(self, media_filename, media_file_type, media_binary, None, None, True, last_modified_time_str) write2(self, media_binary) fitness_performance(getreq_start_time, self.server.fitness, '_GET', '_show_media', self.server.debug) return http_404(self, 33) def _get_ontology(self, calling_domain: str, path: str, base_dir: str, getreq_start_time) -> None: """Returns an ontology file """ if '.owl' in path or '.rdf' in path or '.json' in path: if '/ontologies/' in path: ontology_str = path.split('/ontologies/')[1].replace('#', '') else: ontology_str = path.split('/data/')[1].replace('#', '') ontology_filename = None ontology_file_type = 'application/rdf+xml' if ontology_str.startswith('DFC_'): ontology_filename = base_dir + '/ontology/DFC/' + ontology_str else: ontology_str = ontology_str.replace('/data/', '') ontology_filename = base_dir + '/ontology/' + ontology_str if ontology_str.endswith('.json'): ontology_file_type = 'application/ld+json' if os.path.isfile(ontology_filename): ontology_file = None try: with open(ontology_filename, 'r', encoding='utf-8') as fp_ont: ontology_file = fp_ont.read() except OSError: print('EX: unable to read ontology ' + ontology_filename) if ontology_file: ontology_file = \ ontology_file.replace('static.datafoodconsortium.org', calling_domain) if not calling_domain.endswith('.i2p') and \ not calling_domain.endswith('.onion'): ontology_file = \ ontology_file.replace('http://' + calling_domain, 'https://' + calling_domain) msg = ontology_file.encode('utf-8') msglen = len(msg) set_headers(self, ontology_file_type, msglen, None, calling_domain, False) write2(self, msg) fitness_performance(getreq_start_time, self.server.fitness, '_GET', '_get_ontology', self.server.debug) return http_404(self, 34) def _show_share_image(self, path: str, base_dir: str, getreq_start_time) -> bool: """Show a shared item image """ if not is_image_file(path): http_404(self, 101) return True media_str = path.split('/sharefiles/')[1] media_filename = base_dir + '/sharefiles/' + media_str if not os.path.isfile(media_filename): http_404(self, 102) return True if etag_exists(self, media_filename): # The file has not changed http_304(self) return True media_file_type = get_image_mime_type(media_filename) media_binary = None try: with open(media_filename, 'rb') as av_file: media_binary = av_file.read() except OSError: print('EX: unable to read binary ' + media_filename) if media_binary: set_headers_etag(self, media_filename, media_file_type, media_binary, None, self.server.domain_full, False, None) write2(self, media_binary) fitness_performance(getreq_start_time, self.server.fitness, '_GET', '_show_share_image', self.server.debug) return True def _show_icon(self, path: str, base_dir: str, getreq_start_time) -> None: """Shows an icon """ if not path.endswith('.png'): http_404(self, 37) return media_str = path.split('/icons/')[1] if '/' not in media_str: if not self.server.theme_name: theme = 'default' else: theme = self.server.theme_name icon_filename = media_str else: theme = media_str.split('/')[0] icon_filename = media_str.split('/')[1] media_filename = \ base_dir + '/theme/' + theme + '/icons/' + icon_filename if etag_exists(self, media_filename): # The file has not changed http_304(self) return if self.server.iconsCache.get(media_str): media_binary = self.server.iconsCache[media_str] mime_type_str = media_file_mime_type(media_filename) set_headers_etag(self, media_filename, mime_type_str, media_binary, None, self.server.domain_full, False, None) write2(self, media_binary) fitness_performance(getreq_start_time, self.server.fitness, '_GET', '_show_icon', self.server.debug) return if os.path.isfile(media_filename): media_binary = None try: with open(media_filename, 'rb') as av_file: media_binary = av_file.read() except OSError: print('EX: unable to read icon image ' + media_filename) if media_binary: mime_type = media_file_mime_type(media_filename) set_headers_etag(self, media_filename, mime_type, media_binary, None, self.server.domain_full, False, None) write2(self, media_binary) self.server.iconsCache[media_str] = media_binary fitness_performance(getreq_start_time, self.server.fitness, '_GET', '_show_icon', self.server.debug) return http_404(self, 38) def _show_specification_image(self, path: str, base_dir: str, getreq_start_time) -> None: """Shows an image within the ActivityPub specification document """ image_filename = path.split('/', 1)[1] if '/' in image_filename: http_404(self, 39) return media_filename = \ base_dir + '/specification/' + image_filename if etag_exists(self, media_filename): # The file has not changed http_304(self) return if self.server.iconsCache.get(media_filename): media_binary = self.server.iconsCache[media_filename] mime_type_str = media_file_mime_type(media_filename) set_headers_etag(self, media_filename, mime_type_str, media_binary, None, self.server.domain_full, False, None) write2(self, media_binary) fitness_performance(getreq_start_time, self.server.fitness, '_GET', '_show_specification_image', self.server.debug) return if os.path.isfile(media_filename): media_binary = None try: with open(media_filename, 'rb') as av_file: media_binary = av_file.read() except OSError: print('EX: unable to read specification image ' + media_filename) if media_binary: mime_type = media_file_mime_type(media_filename) set_headers_etag(self, media_filename, mime_type, media_binary, None, self.server.domain_full, False, None) write2(self, media_binary) self.server.iconsCache[media_filename] = media_binary fitness_performance(getreq_start_time, self.server.fitness, '_GET', '_show_specification_image', self.server.debug) return http_404(self, 40) def _show_manual_image(self, path: str, base_dir: str, getreq_start_time) -> None: """Shows an image within the manual """ image_filename = path.split('/', 1)[1] if '/' in image_filename: http_404(self, 41) return media_filename = \ base_dir + '/manual/' + image_filename if etag_exists(self, media_filename): # The file has not changed http_304(self) return if self.server.iconsCache.get(media_filename): media_binary = self.server.iconsCache[media_filename] mime_type_str = media_file_mime_type(media_filename) set_headers_etag(self, media_filename, mime_type_str, media_binary, None, self.server.domain_full, False, None) write2(self, media_binary) fitness_performance(getreq_start_time, self.server.fitness, '_GET', '_show_manual_image', self.server.debug) return if os.path.isfile(media_filename): media_binary = None try: with open(media_filename, 'rb') as av_file: media_binary = av_file.read() except OSError: print('EX: unable to read manual image ' + media_filename) if media_binary: mime_type = media_file_mime_type(media_filename) set_headers_etag(self, media_filename, mime_type, media_binary, None, self.server.domain_full, False, None) write2(self, media_binary) self.server.iconsCache[media_filename] = media_binary fitness_performance(getreq_start_time, self.server.fitness, '_GET', '_show_manual_image', self.server.debug) return http_404(self, 42) def _show_help_screen_image(self, path: str, base_dir: str, getreq_start_time) -> None: """Shows a help screen image """ if not is_image_file(path): return media_str = path.split('/helpimages/')[1] if '/' not in media_str: if not self.server.theme_name: theme = 'default' else: theme = self.server.theme_name icon_filename = media_str else: theme = media_str.split('/')[0] icon_filename = media_str.split('/')[1] media_filename = \ base_dir + '/theme/' + theme + '/helpimages/' + icon_filename # if there is no theme-specific help image then use the default one if not os.path.isfile(media_filename): media_filename = \ base_dir + '/theme/default/helpimages/' + icon_filename if etag_exists(self, media_filename): # The file has not changed http_304(self) return if os.path.isfile(media_filename): media_binary = None try: with open(media_filename, 'rb') as av_file: media_binary = av_file.read() except OSError: print('EX: unable to read help image ' + media_filename) if media_binary: mime_type = media_file_mime_type(media_filename) set_headers_etag(self, media_filename, mime_type, media_binary, None, self.server.domain_full, False, None) write2(self, media_binary) fitness_performance(getreq_start_time, self.server.fitness, '_GET', '_show_help_screen_image', self.server.debug) return http_404(self, 43) def _show_cached_avatar(self, referer_domain: str, path: str, base_dir: str, getreq_start_time) -> None: """Shows an avatar image obtained from the cache """ media_filename = base_dir + '/cache' + path if os.path.isfile(media_filename): if etag_exists(self, media_filename): # The file has not changed http_304(self) return media_binary = None try: with open(media_filename, 'rb') as av_file: media_binary = av_file.read() except OSError: print('EX: unable to read cached avatar ' + media_filename) if media_binary: mime_type = media_file_mime_type(media_filename) set_headers_etag(self, media_filename, mime_type, media_binary, None, referer_domain, False, None) write2(self, media_binary) fitness_performance(getreq_start_time, self.server.fitness, '_GET', '_show_cached_avatar', self.server.debug) return http_404(self, 46) def _show_avatar_or_banner(self, referer_domain: str, path: str, base_dir: str, domain: str, getreq_start_time) -> bool: """Shows an avatar or banner or profile background image """ if '/users/' not in path: if '/system/accounts/avatars/' not in path and \ '/system/accounts/headers/' not in path and \ '/accounts/avatars/' not in path and \ '/accounts/headers/' not in path: return False if not is_image_file(path): return False if '/system/accounts/avatars/' in path: avatar_str = path.split('/system/accounts/avatars/')[1] elif '/accounts/avatars/' in path: avatar_str = path.split('/accounts/avatars/')[1] elif '/system/accounts/headers/' in path: avatar_str = path.split('/system/accounts/headers/')[1] elif '/accounts/headers/' in path: avatar_str = path.split('/accounts/headers/')[1] else: avatar_str = path.split('/users/')[1] if not ('/' in avatar_str and '.temp.' not in path): return False avatar_nickname = avatar_str.split('/')[0] avatar_file = avatar_str.split('/')[1] avatar_file_ext = avatar_file.split('.')[-1] # remove any numbers, eg. avatar123.png becomes avatar.png if avatar_file.startswith('avatar'): avatar_file = 'avatar.' + avatar_file_ext elif avatar_file.startswith('banner'): avatar_file = 'banner.' + avatar_file_ext elif avatar_file.startswith('search_banner'): avatar_file = 'search_banner.' + avatar_file_ext elif avatar_file.startswith('image'): avatar_file = 'image.' + avatar_file_ext elif avatar_file.startswith('left_col_image'): avatar_file = 'left_col_image.' + avatar_file_ext elif avatar_file.startswith('right_col_image'): avatar_file = 'right_col_image.' + avatar_file_ext avatar_filename = \ acct_dir(base_dir, avatar_nickname, domain) + '/' + avatar_file if not os.path.isfile(avatar_filename): original_ext = avatar_file_ext original_avatar_file = avatar_file alt_ext = get_image_extensions() alt_found = False for alt in alt_ext: if alt == original_ext: continue avatar_file = \ original_avatar_file.replace('.' + original_ext, '.' + alt) avatar_filename = \ acct_dir(base_dir, avatar_nickname, domain) + \ '/' + avatar_file if os.path.isfile(avatar_filename): alt_found = True break if not alt_found: return False if etag_exists(self, avatar_filename): # The file has not changed http_304(self) return True avatar_tm = os.path.getmtime(avatar_filename) last_modified_time = \ datetime.datetime.fromtimestamp(avatar_tm, datetime.timezone.utc) last_modified_time_str = \ last_modified_time.strftime('%a, %d %b %Y %H:%M:%S GMT') media_image_type = get_image_mime_type(avatar_file) media_binary = None try: with open(avatar_filename, 'rb') as av_file: media_binary = av_file.read() except OSError: print('EX: unable to read avatar ' + avatar_filename) if media_binary: set_headers_etag(self, avatar_filename, media_image_type, media_binary, None, referer_domain, True, last_modified_time_str) write2(self, media_binary) fitness_performance(getreq_start_time, self.server.fitness, '_GET', '_show_avatar_or_banner', self.server.debug) return True def _webfinger(self, calling_domain: str, referer_domain: str, cookie: str) -> bool: if not self.path.startswith('/.well-known'): return False if self.server.debug: print('DEBUG: WEBFINGER well-known') if self.server.debug: print('DEBUG: WEBFINGER host-meta') if self.path.startswith('/.well-known/host-meta'): if calling_domain.endswith('.onion') and \ self.server.onion_domain: wf_result = \ webfinger_meta('http', self.server.onion_domain) elif (calling_domain.endswith('.i2p') and self.server.i2p_domain): wf_result = \ webfinger_meta('http', self.server.i2p_domain) else: wf_result = \ webfinger_meta(self.server.http_prefix, self.server.domain_full) if wf_result: msg = wf_result.encode('utf-8') msglen = len(msg) set_headers(self, 'application/xrd+xml', msglen, None, calling_domain, True) write2(self, msg) return True http_404(self, 6) return True if self.path.startswith('/api/statusnet') or \ self.path.startswith('/api/gnusocial') or \ self.path.startswith('/siteinfo') or \ self.path.startswith('/poco') or \ self.path.startswith('/friendi'): http_404(self, 7) return True # protocol handler. See https://fedi-to.github.io/protocol-handler.html if self.path.startswith('/.well-known/protocol-handler'): if calling_domain.endswith('.onion'): protocol_url, _ = \ wellknown_protocol_handler(self.path, 'http', self.server.onion_domain) elif calling_domain.endswith('.i2p'): protocol_url, _ = \ wellknown_protocol_handler(self.path, 'http', self.server.i2p_domain) else: protocol_url, _ = \ wellknown_protocol_handler(self.path, self.server.http_prefix, self.server.domain_full) if protocol_url: redirect_headers(self, protocol_url, cookie, calling_domain, 308) else: http_404(self, 8) return True # nodeinfo if self.path.startswith('/.well-known/nodeinfo') or \ self.path.startswith('/.well-known/x-nodeinfo'): if calling_domain.endswith('.onion') and \ self.server.onion_domain: wf_result = \ webfinger_node_info('http', self.server.onion_domain) elif (calling_domain.endswith('.i2p') and self.server.i2p_domain): wf_result = \ webfinger_node_info('http', self.server.i2p_domain) else: wf_result = \ webfinger_node_info(self.server.http_prefix, self.server.domain_full) if wf_result: msg_str = json.dumps(wf_result) msg_str = convert_domains(calling_domain, referer_domain, msg_str, self.server.http_prefix, self.server.domain, self.server.onion_domain, self.server.i2p_domain) msg = msg_str.encode('utf-8') msglen = len(msg) if has_accept(self, calling_domain): accept_str = self.headers.get('Accept') protocol_str = \ get_json_content_from_accept(accept_str) set_headers(self, protocol_str, msglen, None, calling_domain, True) else: set_headers(self, 'application/ld+json', msglen, None, calling_domain, True) write2(self, msg) return True http_404(self, 9) return True if self.server.debug: print('DEBUG: WEBFINGER lookup ' + self.path + ' ' + str(self.server.base_dir)) wf_result = \ webfinger_lookup(self.path, self.server.base_dir, self.server.domain, self.server.onion_domain, self.server.i2p_domain, self.server.port, self.server.debug) if wf_result: msg_str = json.dumps(wf_result) msg_str = convert_domains(calling_domain, referer_domain, msg_str, self.server.http_prefix, self.server.domain, self.server.onion_domain, self.server.i2p_domain) msg = msg_str.encode('utf-8') msglen = len(msg) set_headers(self, 'application/jrd+json', msglen, None, calling_domain, True) write2(self, msg) else: if self.server.debug: print('DEBUG: WEBFINGER lookup 404 ' + self.path) http_404(self, 10) return True def _confirm_delete_event(self, calling_domain: str, path: str, base_dir: str, http_prefix: str, cookie: str, translate: {}, domain_full: str, onion_domain: str, i2p_domain: str, getreq_start_time) -> bool: """Confirm whether to delete a calendar event """ post_id = path.split('?eventid=')[1] if '?' in post_id: post_id = post_id.split('?')[0] post_time = path.split('?time=')[1] if '?' in post_time: post_time = post_time.split('?')[0] post_year = path.split('?year=')[1] if '?' in post_year: post_year = post_year.split('?')[0] post_month = path.split('?month=')[1] if '?' in post_month: post_month = post_month.split('?')[0] post_day = path.split('?day=')[1] if '?' in post_day: post_day = post_day.split('?')[0] # show the confirmation screen screen msg = html_calendar_delete_confirm(translate, base_dir, path, http_prefix, domain_full, post_id, post_time, post_year, post_month, post_day, calling_domain) if not msg: actor = \ http_prefix + '://' + \ domain_full + \ path.split('/eventdelete')[0] if calling_domain.endswith('.onion') and onion_domain: actor = \ 'http://' + onion_domain + \ path.split('/eventdelete')[0] elif calling_domain.endswith('.i2p') and i2p_domain: actor = \ 'http://' + i2p_domain + \ path.split('/eventdelete')[0] redirect_headers(self, actor + '/calendar', cookie, calling_domain) fitness_performance(getreq_start_time, self.server.fitness, '_GET', '_confirm_delete_event', self.server.debug) return True msg = msg.encode('utf-8') msglen = len(msg) set_headers(self, 'text/html', msglen, cookie, calling_domain, False) write2(self, msg) return True def _newswire_vote(self, calling_domain: str, path: str, cookie: str, base_dir: str, http_prefix: str, domain_full: str, onion_domain: str, i2p_domain: str, getreq_start_time, newswire: {}): """Vote for a newswire item """ origin_path_str = path.split('/newswirevote=')[0] date_str = \ path.split('/newswirevote=')[1].replace('T', ' ') date_str = date_str.replace(' 00:00', '').replace('+00:00', '') date_str = urllib.parse.unquote_plus(date_str) + '+00:00' nickname = \ urllib.parse.unquote_plus(origin_path_str.split('/users/')[1]) if '/' in nickname: nickname = nickname.split('/')[0] print('Newswire item date: ' + date_str) if newswire.get(date_str): if is_moderator(base_dir, nickname): newswire_item = newswire[date_str] print('Voting on newswire item: ' + str(newswire_item)) votes_index = 2 filename_index = 3 if 'vote:' + nickname not in newswire_item[votes_index]: newswire_item[votes_index].append('vote:' + nickname) filename = newswire_item[filename_index] newswire_state_filename = \ base_dir + '/accounts/.newswirestate.json' try: save_json(newswire, newswire_state_filename) except BaseException as ex: print('EX: saving newswire state, ' + str(ex)) if filename: save_json(newswire_item[votes_index], filename + '.votes') else: print('No newswire item with date: ' + date_str + ' ' + str(newswire)) origin_path_str_absolute = \ http_prefix + '://' + domain_full + origin_path_str + '/' + \ self.server.default_timeline if calling_domain.endswith('.onion') and onion_domain: origin_path_str_absolute = \ 'http://' + onion_domain + origin_path_str elif (calling_domain.endswith('.i2p') and i2p_domain): origin_path_str_absolute = \ 'http://' + i2p_domain + origin_path_str fitness_performance(getreq_start_time, self.server.fitness, '_GET', '_newswire_vote', self.server.debug) redirect_headers(self, origin_path_str_absolute, cookie, calling_domain) def _newswire_unvote(self, calling_domain: str, path: str, cookie: str, base_dir: str, http_prefix: str, domain_full: str, onion_domain: str, i2p_domain: str, getreq_start_time, debug: bool, newswire: {}): """Remove vote for a newswire item """ origin_path_str = path.split('/newswireunvote=')[0] date_str = \ path.split('/newswireunvote=')[1].replace('T', ' ') date_str = date_str.replace(' 00:00', '').replace('+00:00', '') date_str = urllib.parse.unquote_plus(date_str) + '+00:00' nickname = \ urllib.parse.unquote_plus(origin_path_str.split('/users/')[1]) if '/' in nickname: nickname = nickname.split('/')[0] if newswire.get(date_str): if is_moderator(base_dir, nickname): votes_index = 2 filename_index = 3 newswire_item = newswire[date_str] if 'vote:' + nickname in newswire_item[votes_index]: newswire_item[votes_index].remove('vote:' + nickname) filename = newswire_item[filename_index] newswire_state_filename = \ base_dir + '/accounts/.newswirestate.json' try: save_json(newswire, newswire_state_filename) except BaseException as ex: print('EX: saving newswire state, ' + str(ex)) if filename: save_json(newswire_item[votes_index], filename + '.votes') else: print('No newswire item with date: ' + date_str + ' ' + str(newswire)) origin_path_str_absolute = \ http_prefix + '://' + domain_full + origin_path_str + '/' + \ self.server.default_timeline if calling_domain.endswith('.onion') and onion_domain: origin_path_str_absolute = \ 'http://' + onion_domain + origin_path_str elif (calling_domain.endswith('.i2p') and i2p_domain): origin_path_str_absolute = \ 'http://' + i2p_domain + origin_path_str redirect_headers(self, origin_path_str_absolute, cookie, calling_domain) fitness_performance(getreq_start_time, self.server.fitness, '_GET', '_newswire_unvote', debug) def _reaction_picker2(self, calling_domain: str, path: str, base_dir: str, http_prefix: str, domain: str, port: int, getreq_start_time, cookie: str, debug: str, curr_session) -> None: """Press the emoji reaction picker icon at the bottom of the post """ page_number = 1 reaction_url = path.split('?selreact=')[1] if '?' in reaction_url: reaction_url = reaction_url.split('?')[0] timeline_bookmark = '' if '?bm=' in path: timeline_bookmark = path.split('?bm=')[1] if '?' in timeline_bookmark: timeline_bookmark = timeline_bookmark.split('?')[0] timeline_bookmark = '#' + timeline_bookmark actor = path.split('?selreact=')[0] if '?page=' in path: page_number_str = path.split('?page=')[1] if '?' in page_number_str: page_number_str = page_number_str.split('?')[0] if '#' in page_number_str: page_number_str = page_number_str.split('#')[0] if len(page_number_str) > 5: page_number_str = "1" if page_number_str.isdigit(): page_number = int(page_number_str) timeline_str = 'inbox' if '?tl=' in path: timeline_str = path.split('?tl=')[1] if '?' in timeline_str: timeline_str = timeline_str.split('?')[0] self.post_to_nickname = get_nickname_from_actor(actor) if not self.post_to_nickname: print('WARN: unable to find nickname in ' + actor) actor_absolute = \ get_instance_url(calling_domain, self.server.http_prefix, self.server.domain_full, self.server.onion_domain, self.server.i2p_domain) + \ actor actor_path_str = \ actor_absolute + '/' + timeline_str + \ '?page=' + str(page_number) + timeline_bookmark redirect_headers(self, actor_path_str, cookie, calling_domain) return post_json_object = None reaction_post_filename = \ locate_post(base_dir, self.post_to_nickname, domain, reaction_url) if reaction_post_filename: post_json_object = load_json(reaction_post_filename) if not reaction_post_filename or not post_json_object: print('WARN: unable to locate reaction post ' + reaction_url) actor_absolute = \ get_instance_url(calling_domain, self.server.http_prefix, self.server.domain_full, self.server.onion_domain, self.server.i2p_domain) + \ actor actor_path_str = \ actor_absolute + '/' + timeline_str + \ '?page=' + str(page_number) + timeline_bookmark redirect_headers(self, actor_path_str, cookie, calling_domain) return timezone = None if self.server.account_timezone.get(self.post_to_nickname): timezone = \ self.server.account_timezone.get(self.post_to_nickname) bold_reading = False if self.server.bold_reading.get(self.post_to_nickname): bold_reading = True msg = \ html_emoji_reaction_picker(self.server.recent_posts_cache, self.server.max_recent_posts, self.server.translate, base_dir, curr_session, self.server.cached_webfingers, self.server.person_cache, self.post_to_nickname, domain, port, post_json_object, http_prefix, self.server.project_version, self.server.yt_replace_domain, self.server.twitter_replacement_domain, self.server.show_published_date_only, self.server.peertube_instances, self.server.allow_local_network_access, self.server.theme_name, self.server.system_language, self.server.max_like_count, self.server.signing_priv_key_pem, self.server.cw_lists, self.server.lists_enabled, timeline_str, page_number, timezone, bold_reading, self.server.dogwhistles, self.server.min_images_for_accounts, self.server.buy_sites, self.server.auto_cw_cache) msg = msg.encode('utf-8') msglen = len(msg) set_headers(self, 'text/html', msglen, cookie, calling_domain, False) write2(self, msg) fitness_performance(getreq_start_time, self.server.fitness, '_GET', '_reaction_picker', debug) def _show_known_crawlers(self, calling_domain: str, path: str, base_dir: str, known_crawlers: {}) -> bool: """Show a list of known web crawlers """ if '/users/' not in path: return False if not path.endswith('/crawlers'): return False nickname = get_nickname_from_actor(path) if not nickname: return False if not is_moderator(base_dir, nickname): return False crawlers_list = [] curr_time = int(time.time()) recent_crawlers = 60 * 60 * 24 * 30 for ua_str, item in known_crawlers.items(): if item['lastseen'] - curr_time < recent_crawlers: hits_str = str(item['hits']).zfill(8) crawlers_list.append(hits_str + ' ' + ua_str) crawlers_list.sort(reverse=True) msg = '' for line_str in crawlers_list: msg += line_str + '\n' msg = msg.encode('utf-8') msglen = len(msg) set_headers(self, 'text/plain; charset=utf-8', msglen, None, calling_domain, True) write2(self, msg) return True def _edit_profile2(self, calling_domain: str, path: str, translate: {}, base_dir: str, domain: str, port: int, cookie: str) -> bool: """Show the edit profile screen """ if '/users/' in path and path.endswith('/editprofile'): peertube_instances = self.server.peertube_instances nickname = get_nickname_from_actor(path) access_keys = self.server.access_keys if '/users/' in path: if self.server.key_shortcuts.get(nickname): access_keys = self.server.key_shortcuts[nickname] default_reply_interval_hrs = self.server.default_reply_interval_hrs msg = html_edit_profile(self.server, translate, base_dir, path, domain, port, self.server.default_timeline, self.server.theme_name, peertube_instances, self.server.text_mode_banner, self.server.user_agents_blocked, self.server.crawlers_allowed, access_keys, default_reply_interval_hrs, self.server.cw_lists, self.server.lists_enabled, self.server.system_language, self.server.min_images_for_accounts, self.server.max_recent_posts, self.server.reverse_sequence, self.server.buy_sites, self.server.block_military, self.server.block_federated_endpoints) if msg: msg = msg.encode('utf-8') msglen = len(msg) set_headers(self, 'text/html', msglen, cookie, calling_domain, False) write2(self, msg) else: http_404(self, 105) return True return False def _edit_links2(self, calling_domain: str, path: str, translate: {}, base_dir: str, domain: str, cookie: str, theme: str) -> bool: """Show the links from the left column """ if '/users/' in path and path.endswith('/editlinks'): nickname = path.split('/users/')[1] if '/' in nickname: nickname = nickname.split('/')[0] access_keys = self.server.access_keys if self.server.key_shortcuts.get(nickname): access_keys = self.server.key_shortcuts[nickname] msg = html_edit_links(translate, base_dir, path, domain, self.server.default_timeline, theme, access_keys) if msg: msg = msg.encode('utf-8') msglen = len(msg) set_headers(self, 'text/html', msglen, cookie, calling_domain, False) write2(self, msg) else: http_404(self, 106) return True return False def _edit_newswire2(self, calling_domain: str, path: str, translate: {}, base_dir: str, domain: str, cookie: str) -> bool: """Show the newswire from the right column """ if '/users/' in path and path.endswith('/editnewswire'): nickname = path.split('/users/')[1] if '/' in nickname: nickname = nickname.split('/')[0] access_keys = self.server.access_keys if self.server.key_shortcuts.get(nickname): access_keys = self.server.key_shortcuts[nickname] msg = html_edit_newswire(translate, base_dir, path, domain, self.server.default_timeline, self.server.theme_name, access_keys, self.server.dogwhistles) if msg: msg = msg.encode('utf-8') msglen = len(msg) set_headers(self, 'text/html', msglen, cookie, calling_domain, False) write2(self, msg) else: http_404(self, 107) return True return False def _edit_news_post2(self, calling_domain: str, path: str, translate: {}, base_dir: str, http_prefix: str, domain: str, domain_full: str, cookie: str) -> bool: """Show the edit screen for a news post """ if '/users/' in path and '/editnewspost=' in path: post_actor = 'news' if '?actor=' in path: post_actor = path.split('?actor=')[1] if '?' in post_actor: post_actor = post_actor.split('?')[0] post_id = path.split('/editnewspost=')[1] if '?' in post_id: post_id = post_id.split('?')[0] post_url = \ local_actor_url(http_prefix, post_actor, domain_full) + \ '/statuses/' + post_id path = path.split('/editnewspost=')[0] msg = html_edit_news_post(translate, base_dir, path, domain, post_url, self.server.system_language) if msg: msg = msg.encode('utf-8') msglen = len(msg) set_headers(self, 'text/html', msglen, cookie, calling_domain, False) write2(self, msg) else: http_404(self, 108) return True return False def _show_new_post(self, edit_post_params: {}, calling_domain: str, path: str, media_instance: bool, translate: {}, base_dir: str, http_prefix: str, in_reply_to_url: str, reply_to_list: [], reply_is_chat: bool, share_description: str, reply_page_number: int, reply_category: str, domain: str, domain_full: str, getreq_start_time, cookie, no_drop_down: bool, conversation_id: str, curr_session) -> bool: """Shows the new post screen """ is_new_post_endpoint = False new_post_month = None new_post_year = None if '/users/' in path and '/new' in path: if '?month=' in path: month_str = path.split('?month=')[1] if ';' in month_str: month_str = month_str.split(';')[0] if month_str.isdigit(): new_post_month = int(month_str) if new_post_month and ';year=' in path: year_str = path.split(';year=')[1] if ';' in year_str: year_str = year_str.split(';')[0] if year_str.isdigit(): new_post_year = int(year_str) if new_post_year: path = path.split('?month=')[0] # Various types of new post in the web interface new_post_endpoints = get_new_post_endpoints() for curr_post_type in new_post_endpoints: if path.endswith('/' + curr_post_type): is_new_post_endpoint = True break if is_new_post_endpoint: nickname = get_nickname_from_actor(path) if not nickname: http_404(self, 103) return True if in_reply_to_url: reply_interval_hours = self.server.default_reply_interval_hrs if not can_reply_to(base_dir, nickname, domain, in_reply_to_url, reply_interval_hours): print('Reply outside of time window ' + in_reply_to_url + ' ' + str(reply_interval_hours) + ' hours') http_403(self) return True if self.server.debug: print('Reply is within time interval: ' + str(reply_interval_hours) + ' hours') access_keys = self.server.access_keys if self.server.key_shortcuts.get(nickname): access_keys = self.server.key_shortcuts[nickname] custom_submit_text = get_config_param(base_dir, 'customSubmitText') default_post_language = self.server.system_language if self.server.default_post_language.get(nickname): default_post_language = \ self.server.default_post_language[nickname] post_json_object = None if in_reply_to_url: reply_post_filename = \ locate_post(base_dir, nickname, domain, in_reply_to_url) if reply_post_filename: post_json_object = load_json(reply_post_filename) if post_json_object: reply_language = \ get_reply_language(base_dir, post_json_object) if reply_language: default_post_language = reply_language bold_reading = False if self.server.bold_reading.get(nickname): bold_reading = True languages_understood = \ get_understood_languages(base_dir, self.server.http_prefix, nickname, self.server.domain_full, self.server.person_cache) default_buy_site = '' msg = \ html_new_post(edit_post_params, media_instance, translate, base_dir, http_prefix, path, in_reply_to_url, reply_to_list, share_description, None, reply_page_number, reply_category, nickname, domain, domain_full, self.server.default_timeline, self.server.newswire, self.server.theme_name, no_drop_down, access_keys, custom_submit_text, conversation_id, self.server.recent_posts_cache, self.server.max_recent_posts, curr_session, self.server.cached_webfingers, self.server.person_cache, self.server.port, post_json_object, self.server.project_version, self.server.yt_replace_domain, self.server.twitter_replacement_domain, self.server.show_published_date_only, self.server.peertube_instances, self.server.allow_local_network_access, self.server.system_language, languages_understood, self.server.max_like_count, self.server.signing_priv_key_pem, self.server.cw_lists, self.server.lists_enabled, self.server.default_timeline, reply_is_chat, bold_reading, self.server.dogwhistles, self.server.min_images_for_accounts, new_post_month, new_post_year, default_post_language, self.server.buy_sites, default_buy_site, self.server.auto_cw_cache) if not msg: print('Error replying to ' + in_reply_to_url) http_404(self, 104) return True msg = msg.encode('utf-8') msglen = len(msg) set_headers(self, 'text/html', msglen, cookie, calling_domain, False) write2(self, msg) fitness_performance(getreq_start_time, self.server.fitness, '_GET', '_show_new_post', self.server.debug) return True return False def _show_individual_at_post(self, ssml_getreq: bool, authorized: bool, calling_domain: str, referer_domain: str, path: str, base_dir: str, http_prefix: str, domain: str, domain_full: str, port: int, getreq_start_time, proxy_type: str, cookie: str, debug: str, curr_session) -> bool: """get an individual post from the path /@nickname/statusnumber """ if '/@' not in path: return False liked_by = None if '?likedBy=' in path: liked_by = path.split('?likedBy=')[1].strip() if '?' in liked_by: liked_by = liked_by.split('?')[0] path = path.split('?likedBy=')[0] react_by = None react_emoji = None if '?reactBy=' in path: react_by = path.split('?reactBy=')[1].strip() if ';' in react_by: react_by = react_by.split(';')[0] if ';emoj=' in path: react_emoji = path.split(';emoj=')[1].strip() if ';' in react_emoji: react_emoji = react_emoji.split(';')[0] path = path.split('?reactBy=')[0] named_status = path.split('/@')[1] if '/' not in named_status: # show actor nickname = named_status return False post_sections = named_status.split('/') if len(post_sections) != 2: return False nickname = post_sections[0] status_number = post_sections[1] if len(status_number) <= 10 or not status_number.isdigit(): return False if ssml_getreq: ssml_filename = \ acct_dir(base_dir, nickname, domain) + '/outbox/' + \ http_prefix + ':##' + domain_full + '#users#' + nickname + \ '#statuses#' + status_number + '.ssml' if not os.path.isfile(ssml_filename): ssml_filename = \ acct_dir(base_dir, nickname, domain) + '/postcache/' + \ http_prefix + ':##' + domain_full + '#users#' + \ nickname + '#statuses#' + status_number + '.ssml' if not os.path.isfile(ssml_filename): http_404(self, 67) return True ssml_str = None try: with open(ssml_filename, 'r', encoding='utf-8') as fp_ssml: ssml_str = fp_ssml.read() except OSError: pass if ssml_str: msg = ssml_str.encode('utf-8') msglen = len(msg) set_headers(self, 'application/ssml+xml', msglen, cookie, calling_domain, False) write2(self, msg) return True http_404(self, 68) return True post_filename = \ acct_dir(base_dir, nickname, domain) + '/outbox/' + \ http_prefix + ':##' + domain_full + '#users#' + nickname + \ '#statuses#' + status_number + '.json' include_create_wrapper = False if post_sections[-1] == 'activity': include_create_wrapper = True result = _show_post_from_file(self, post_filename, liked_by, react_by, react_emoji, authorized, calling_domain, referer_domain, base_dir, http_prefix, nickname, domain, port, getreq_start_time, proxy_type, cookie, debug, include_create_wrapper, curr_session) fitness_performance(getreq_start_time, self.server.fitness, '_GET', '_show_individual_at_post', self.server.debug) return result def _show_likers_of_post(self, authorized: bool, calling_domain: str, path: str, base_dir: str, http_prefix: str, domain: str, port: int, getreq_start_time, cookie: str, debug: str, curr_session) -> bool: """Show the likers of a post """ if not authorized: return False if '?likers=' not in path: return False if '/users/' not in path: return False nickname = path.split('/users/')[1] if '?' in nickname: nickname = nickname.split('?')[0] post_url = path.split('?likers=')[1] if '?' in post_url: post_url = post_url.split('?')[0] post_url = post_url.replace('--', '/') bold_reading = False if self.server.bold_reading.get(nickname): bold_reading = True msg = \ html_likers_of_post(base_dir, nickname, domain, port, post_url, self.server.translate, http_prefix, self.server.theme_name, self.server.access_keys, self.server.recent_posts_cache, self.server.max_recent_posts, curr_session, self.server.cached_webfingers, self.server.person_cache, self.server.project_version, self.server.yt_replace_domain, self.server.twitter_replacement_domain, self.server.show_published_date_only, self.server.peertube_instances, self.server.allow_local_network_access, self.server.system_language, self.server.max_like_count, self.server.signing_priv_key_pem, self.server.cw_lists, self.server.lists_enabled, 'inbox', self.server.default_timeline, bold_reading, self.server.dogwhistles, self.server.min_images_for_accounts, self.server.buy_sites, self.server.auto_cw_cache, 'likes') if not msg: http_404(self, 69) return True msg = msg.encode('utf-8') msglen = len(msg) set_headers(self, 'text/html', msglen, cookie, calling_domain, False) write2(self, msg) fitness_performance(getreq_start_time, self.server.fitness, '_GET', '_show_likers_of_post', debug) return True def _show_announcers_of_post(self, authorized: bool, calling_domain: str, path: str, base_dir: str, http_prefix: str, domain: str, port: int, getreq_start_time, cookie: str, debug: str, curr_session) -> bool: """Show the announcers of a post """ if not authorized: return False if '?announcers=' not in path: return False if '/users/' not in path: return False nickname = path.split('/users/')[1] if '?' in nickname: nickname = nickname.split('?')[0] post_url = path.split('?announcers=')[1] if '?' in post_url: post_url = post_url.split('?')[0] post_url = post_url.replace('--', '/') bold_reading = False if self.server.bold_reading.get(nickname): bold_reading = True # note that the likers function is reused, but with 'shares' msg = \ html_likers_of_post(base_dir, nickname, domain, port, post_url, self.server.translate, http_prefix, self.server.theme_name, self.server.access_keys, self.server.recent_posts_cache, self.server.max_recent_posts, curr_session, self.server.cached_webfingers, self.server.person_cache, self.server.project_version, self.server.yt_replace_domain, self.server.twitter_replacement_domain, self.server.show_published_date_only, self.server.peertube_instances, self.server.allow_local_network_access, self.server.system_language, self.server.max_like_count, self.server.signing_priv_key_pem, self.server.cw_lists, self.server.lists_enabled, 'inbox', self.server.default_timeline, bold_reading, self.server.dogwhistles, self.server.min_images_for_accounts, self.server.buy_sites, self.server.auto_cw_cache, 'shares') if not msg: http_404(self, 70) return True msg = msg.encode('utf-8') msglen = len(msg) set_headers(self, 'text/html', msglen, cookie, calling_domain, False) write2(self, msg) fitness_performance(getreq_start_time, self.server.fitness, '_GET', '_show_announcers_of_post', debug) return True def _show_replies_to_post(self, authorized: bool, calling_domain: str, referer_domain: str, path: str, base_dir: str, http_prefix: str, domain: str, domain_full: str, port: int, getreq_start_time, proxy_type: str, cookie: str, debug: str, curr_session) -> bool: """Shows the replies to a post """ if not ('/statuses/' in path and '/users/' in path): return False named_status = path.split('/users/')[1] if '/' not in named_status: return False post_sections = named_status.split('/') if len(post_sections) < 4: return False if not post_sections[3].startswith('replies'): return False nickname = post_sections[0] status_number = post_sections[2] if not (len(status_number) > 10 and status_number.isdigit()): return False boxname = 'outbox' # get the replies file post_dir = \ acct_dir(base_dir, nickname, domain) + '/' + boxname orig_post_url = http_prefix + ':##' + domain_full + '#users#' + \ nickname + '#statuses#' + status_number post_replies_filename = \ post_dir + '/' + orig_post_url + '.replies' if not os.path.isfile(post_replies_filename): # There are no replies, # so show empty collection context_str = \ 'https://www.w3.org/ns/activitystreams' first_str = \ local_actor_url(http_prefix, nickname, domain_full) + \ '/statuses/' + status_number + '/replies?page=true' id_str = \ local_actor_url(http_prefix, nickname, domain_full) + \ '/statuses/' + status_number + '/replies' last_str = \ local_actor_url(http_prefix, nickname, domain_full) + \ '/statuses/' + status_number + '/replies?page=true' replies_json = { '@context': context_str, 'first': first_str, 'id': id_str, 'last': last_str, 'totalItems': 0, 'type': 'OrderedCollection' } if request_http(self.headers, debug): curr_session = \ establish_session("showRepliesToPost", curr_session, proxy_type, self.server) if not curr_session: http_404(self, 61) return True recent_posts_cache = self.server.recent_posts_cache max_recent_posts = self.server.max_recent_posts translate = self.server.translate cached_webfingers = self.server.cached_webfingers person_cache = self.server.person_cache project_version = self.server.project_version yt_domain = self.server.yt_replace_domain twitter_replacement_domain = \ self.server.twitter_replacement_domain peertube_instances = self.server.peertube_instances timezone = None if self.server.account_timezone.get(nickname): timezone = \ self.server.account_timezone.get(nickname) bold_reading = False if self.server.bold_reading.get(nickname): bold_reading = True msg = \ html_post_replies(recent_posts_cache, max_recent_posts, translate, base_dir, curr_session, cached_webfingers, person_cache, nickname, domain, port, replies_json, http_prefix, project_version, yt_domain, twitter_replacement_domain, self.server.show_published_date_only, peertube_instances, self.server.allow_local_network_access, self.server.theme_name, self.server.system_language, self.server.max_like_count, self.server.signing_priv_key_pem, self.server.cw_lists, self.server.lists_enabled, timezone, bold_reading, self.server.dogwhistles, self.server.min_images_for_accounts, self.server.buy_sites, self.server.auto_cw_cache) msg = msg.encode('utf-8') msglen = len(msg) set_headers(self, 'text/html', msglen, cookie, calling_domain, False) write2(self, msg) fitness_performance(getreq_start_time, self.server.fitness, '_GET', '_show_replies_to_post', debug) else: if secure_mode(curr_session, proxy_type, False, self.server, self.headers, self.path): msg_str = json.dumps(replies_json, ensure_ascii=False) msg_str = convert_domains(calling_domain, referer_domain, msg_str, http_prefix, domain, self.server.onion_domain, self.server.i2p_domain) msg = msg_str.encode('utf-8') protocol_str = \ get_json_content_from_accept(self.headers['Accept']) msglen = len(msg) set_headers(self, protocol_str, msglen, None, calling_domain, False) write2(self, msg) fitness_performance(getreq_start_time, self.server.fitness, '_GET', '_show_replies_to_post json', debug) else: http_404(self, 62) return True else: # replies exist. Itterate through the # text file containing message ids context_str = 'https://www.w3.org/ns/activitystreams' id_str = \ local_actor_url(http_prefix, nickname, domain_full) + \ '/statuses/' + status_number + '?page=true' part_of_str = \ local_actor_url(http_prefix, nickname, domain_full) + \ '/statuses/' + status_number replies_json = { '@context': context_str, 'id': id_str, 'orderedItems': [ ], 'partOf': part_of_str, 'type': 'OrderedCollectionPage' } # if the original post is public then return the replies replies_are_public = \ is_public_post_from_url(base_dir, nickname, domain, orig_post_url) if replies_are_public: authorized = True # populate the items list with replies populate_replies_json(base_dir, nickname, domain, post_replies_filename, authorized, replies_json) # send the replies json if request_http(self.headers, debug): curr_session = \ establish_session("showRepliesToPost2", curr_session, proxy_type, self.server) if not curr_session: http_404(self, 63) return True recent_posts_cache = self.server.recent_posts_cache max_recent_posts = self.server.max_recent_posts translate = self.server.translate cached_webfingers = self.server.cached_webfingers person_cache = self.server.person_cache project_version = self.server.project_version yt_domain = self.server.yt_replace_domain twitter_replacement_domain = \ self.server.twitter_replacement_domain peertube_instances = self.server.peertube_instances timezone = None if self.server.account_timezone.get(nickname): timezone = \ self.server.account_timezone.get(nickname) bold_reading = False if self.server.bold_reading.get(nickname): bold_reading = True msg = \ html_post_replies(recent_posts_cache, max_recent_posts, translate, base_dir, curr_session, cached_webfingers, person_cache, nickname, domain, port, replies_json, http_prefix, project_version, yt_domain, twitter_replacement_domain, self.server.show_published_date_only, peertube_instances, self.server.allow_local_network_access, self.server.theme_name, self.server.system_language, self.server.max_like_count, self.server.signing_priv_key_pem, self.server.cw_lists, self.server.lists_enabled, timezone, bold_reading, self.server.dogwhistles, self.server.min_images_for_accounts, self.server.buy_sites, self.server.auto_cw_cache) msg = msg.encode('utf-8') msglen = len(msg) set_headers(self, 'text/html', msglen, cookie, calling_domain, False) write2(self, msg) fitness_performance(getreq_start_time, self.server.fitness, '_GET', '_show_replies_to_post', debug) else: if secure_mode(curr_session, proxy_type, False, self.server, self.headers, self.path): msg_str = json.dumps(replies_json, ensure_ascii=False) msg_str = convert_domains(calling_domain, referer_domain, msg_str, http_prefix, domain, self.server.onion_domain, self.server.i2p_domain) msg = msg_str.encode('utf-8') protocol_str = \ get_json_content_from_accept(self.headers['Accept']) msglen = len(msg) set_headers(self, protocol_str, msglen, None, calling_domain, False) write2(self, msg) fitness_performance(getreq_start_time, self.server.fitness, '_GET', '_show_replies_to_post json', debug) else: http_404(self, 64) return True return False def _show_roles(self, calling_domain: str, referer_domain: str, path: str, base_dir: str, http_prefix: str, domain: str, getreq_start_time, proxy_type: str, cookie: str, debug: str, curr_session) -> bool: """Show roles within profile screen """ named_status = path.split('/users/')[1] if '/' not in named_status: return False post_sections = named_status.split('/') nickname = post_sections[0] actor_filename = acct_dir(base_dir, nickname, domain) + '.json' if not os.path.isfile(actor_filename): return False actor_json = load_json(actor_filename) if not actor_json: return False if actor_json.get('hasOccupation'): if request_http(self.headers, debug): get_person = \ person_lookup(domain, path.replace('/roles', ''), base_dir) if get_person: default_timeline = \ self.server.default_timeline recent_posts_cache = \ self.server.recent_posts_cache cached_webfingers = \ self.server.cached_webfingers yt_replace_domain = \ self.server.yt_replace_domain twitter_replacement_domain = \ self.server.twitter_replacement_domain icons_as_buttons = \ self.server.icons_as_buttons access_keys = self.server.access_keys if self.server.key_shortcuts.get(nickname): access_keys = self.server.key_shortcuts[nickname] roles_list = get_actor_roles_list(actor_json) city = \ get_spoofed_city(self.server.city, base_dir, nickname, domain) shared_items_federated_domains = \ self.server.shared_items_federated_domains timezone = None if self.server.account_timezone.get(nickname): timezone = \ self.server.account_timezone.get(nickname) bold_reading = False if self.server.bold_reading.get(nickname): bold_reading = True msg = \ html_profile(self.server.signing_priv_key_pem, self.server.rss_icon_at_top, icons_as_buttons, default_timeline, recent_posts_cache, self.server.max_recent_posts, self.server.translate, self.server.project_version, base_dir, http_prefix, True, get_person, 'roles', curr_session, cached_webfingers, self.server.person_cache, yt_replace_domain, twitter_replacement_domain, self.server.show_published_date_only, self.server.newswire, self.server.theme_name, self.server.dormant_months, self.server.peertube_instances, self.server.allow_local_network_access, self.server.text_mode_banner, self.server.debug, access_keys, city, self.server.system_language, self.server.max_like_count, shared_items_federated_domains, roles_list, None, None, self.server.cw_lists, self.server.lists_enabled, self.server.content_license_url, timezone, bold_reading, self.server.buy_sites, None, self.server.max_shares_on_profile, self.server.sites_unavailable, self.server.no_of_books, self.server.auto_cw_cache) msg = msg.encode('utf-8') msglen = len(msg) set_headers(self, 'text/html', msglen, cookie, calling_domain, False) write2(self, msg) fitness_performance(getreq_start_time, self.server.fitness, '_GET', '_show_roles', debug) else: if secure_mode(curr_session, proxy_type, False, self.server, self.headers, self.path): roles_list = get_actor_roles_list(actor_json) msg_str = json.dumps(roles_list, ensure_ascii=False) msg_str = convert_domains(calling_domain, referer_domain, msg_str, http_prefix, domain, self.server.onion_domain, self.server.i2p_domain) msg = msg_str.encode('utf-8') msglen = len(msg) protocol_str = \ get_json_content_from_accept(self.headers['Accept']) set_headers(self, protocol_str, msglen, None, calling_domain, False) write2(self, msg) fitness_performance(getreq_start_time, self.server.fitness, '_GET', '_show_roles json', debug) else: http_404(self, 65) return True return False def _show_skills(self, calling_domain: str, referer_domain: str, path: str, base_dir: str, http_prefix: str, domain: str, getreq_start_time, proxy_type: str, cookie: str, debug: str, curr_session) -> bool: """Show skills on the profile screen """ named_status = path.split('/users/')[1] if '/' in named_status: post_sections = named_status.split('/') nickname = post_sections[0] actor_filename = acct_dir(base_dir, nickname, domain) + '.json' if os.path.isfile(actor_filename): actor_json = load_json(actor_filename) if actor_json: if no_of_actor_skills(actor_json) > 0: if request_http(self.headers, self.server.debug): get_person = \ person_lookup(domain, path.replace('/skills', ''), base_dir) if get_person: default_timeline = \ self.server.default_timeline recent_posts_cache = \ self.server.recent_posts_cache cached_webfingers = \ self.server.cached_webfingers yt_replace_domain = \ self.server.yt_replace_domain twitter_replacement_domain = \ self.server.twitter_replacement_domain show_published_date_only = \ self.server.show_published_date_only icons_as_buttons = \ self.server.icons_as_buttons allow_local_network_access = \ self.server.allow_local_network_access access_keys = self.server.access_keys if self.server.key_shortcuts.get(nickname): access_keys = \ self.server.key_shortcuts[nickname] actor_skills_list = \ get_occupation_skills(actor_json) skills = \ get_skills_from_list(actor_skills_list) city = get_spoofed_city(self.server.city, base_dir, nickname, domain) shared_items_fed_domains = \ self.server.shared_items_federated_domains signing_priv_key_pem = \ self.server.signing_priv_key_pem content_license_url = \ self.server.content_license_url peertube_instances = \ self.server.peertube_instances timezone = None nick = nickname if self.server.account_timezone.get(nick): timezone = \ self.server.account_timezone.get(nick) bold_reading = False if self.server.bold_reading.get(nick): bold_reading = True max_shares_on_profile = \ self.server.max_shares_on_profile msg = \ html_profile(signing_priv_key_pem, self.server.rss_icon_at_top, icons_as_buttons, default_timeline, recent_posts_cache, self.server.max_recent_posts, self.server.translate, self.server.project_version, base_dir, http_prefix, True, get_person, 'skills', curr_session, cached_webfingers, self.server.person_cache, yt_replace_domain, twitter_replacement_domain, show_published_date_only, self.server.newswire, self.server.theme_name, self.server.dormant_months, peertube_instances, allow_local_network_access, self.server.text_mode_banner, self.server.debug, access_keys, city, self.server.system_language, self.server.max_like_count, shared_items_fed_domains, skills, None, None, self.server.cw_lists, self.server.lists_enabled, content_license_url, timezone, bold_reading, self.server.buy_sites, None, max_shares_on_profile, self.server.sites_unavailable, self.server.no_of_books, self.server.auto_cw_cache) msg = msg.encode('utf-8') msglen = len(msg) set_headers(self, 'text/html', msglen, cookie, calling_domain, False) write2(self, msg) fitness_performance(getreq_start_time, self.server.fitness, '_GET', '_show_skills', self.server.debug) else: if secure_mode(curr_session, proxy_type, False, self.server, self.headers, self.path): actor_skills_list = \ get_occupation_skills(actor_json) skills = \ get_skills_from_list(actor_skills_list) msg_str = json.dumps(skills, ensure_ascii=False) onion_domain = self.server.onion_domain i2p_domain = self.server.i2p_domain msg_str = convert_domains(calling_domain, referer_domain, msg_str, http_prefix, domain, onion_domain, i2p_domain) msg = msg_str.encode('utf-8') msglen = len(msg) accept_str = self.headers['Accept'] protocol_str = \ get_json_content_from_accept(accept_str) set_headers(self, protocol_str, msglen, None, calling_domain, False) write2(self, msg) fitness_performance(getreq_start_time, self.server.fitness, '_GET', '_show_skills json', debug) else: http_404(self, 66) return True actor = path.replace('/skills', '') actor_absolute = \ get_instance_url(calling_domain, self.server.http_prefix, self.server.domain_full, self.server.onion_domain, self.server.i2p_domain) + \ actor redirect_headers(self, actor_absolute, cookie, calling_domain) return True def _show_notify_post(self, authorized: bool, calling_domain: str, referer_domain: str, path: str, base_dir: str, http_prefix: str, domain: str, port: int, getreq_start_time, proxy_type: str, cookie: str, debug: str, curr_session) -> bool: """Shows an individual post from an account which you are following and where you have the notify checkbox set on person options """ liked_by = None react_by = None react_emoji = None post_id = path.split('?notifypost=')[1].strip() post_id = post_id.replace('-', '/') path = path.split('?notifypost=')[0] nickname = path.split('/users/')[1] if '/' in nickname: return False replies = False post_filename = locate_post(base_dir, nickname, domain, post_id, replies) if not post_filename: return False include_create_wrapper = False if path.endswith('/activity'): include_create_wrapper = True result = _show_post_from_file(self, post_filename, liked_by, react_by, react_emoji, authorized, calling_domain, referer_domain, base_dir, http_prefix, nickname, domain, port, getreq_start_time, proxy_type, cookie, debug, include_create_wrapper, curr_session) fitness_performance(getreq_start_time, self.server.fitness, '_GET', '_show_notify_post', self.server.debug) return result def _show_individual_post(self, ssml_getreq: bool, authorized: bool, calling_domain: str, referer_domain: str, path: str, base_dir: str, http_prefix: str, domain: str, domain_full: str, port: int, getreq_start_time, proxy_type: str, cookie: str, debug: str, curr_session) -> bool: """Shows an individual post """ liked_by = None if '?likedBy=' in path: liked_by = path.split('?likedBy=')[1].strip() if '?' in liked_by: liked_by = liked_by.split('?')[0] path = path.split('?likedBy=')[0] react_by = None react_emoji = None if '?reactBy=' in path: react_by = path.split('?reactBy=')[1].strip() if ';' in react_by: react_by = react_by.split(';')[0] if ';emoj=' in path: react_emoji = path.split(';emoj=')[1].strip() if ';' in react_emoji: react_emoji = react_emoji.split(';')[0] path = path.split('?reactBy=')[0] named_status = path.split('/users/')[1] if '/' not in named_status: return False post_sections = named_status.split('/') if len(post_sections) < 3: return False nickname = post_sections[0] status_number = post_sections[2] if len(status_number) <= 10 or (not status_number.isdigit()): return False if ssml_getreq: ssml_filename = \ acct_dir(base_dir, nickname, domain) + '/outbox/' + \ http_prefix + ':##' + domain_full + '#users#' + nickname + \ '#statuses#' + status_number + '.ssml' if not os.path.isfile(ssml_filename): ssml_filename = \ acct_dir(base_dir, nickname, domain) + '/postcache/' + \ http_prefix + ':##' + domain_full + '#users#' + \ nickname + '#statuses#' + status_number + '.ssml' if not os.path.isfile(ssml_filename): http_404(self, 74) return True ssml_str = None try: with open(ssml_filename, 'r', encoding='utf-8') as fp_ssml: ssml_str = fp_ssml.read() except OSError: pass if ssml_str: msg = ssml_str.encode('utf-8') msglen = len(msg) set_headers(self, 'application/ssml+xml', msglen, cookie, calling_domain, False) write2(self, msg) return True http_404(self, 75) return True post_filename = \ acct_dir(base_dir, nickname, domain) + '/outbox/' + \ http_prefix + ':##' + domain_full + '#users#' + nickname + \ '#statuses#' + status_number + '.json' include_create_wrapper = False if post_sections[-1] == 'activity': include_create_wrapper = True result = _show_post_from_file(self, post_filename, liked_by, react_by, react_emoji, authorized, calling_domain, referer_domain, base_dir, http_prefix, nickname, domain, port, getreq_start_time, proxy_type, cookie, debug, include_create_wrapper, curr_session) fitness_performance(getreq_start_time, self.server.fitness, '_GET', '_show_individual_post', self.server.debug) return result def _show_inbox(self, authorized: bool, calling_domain: str, referer_domain: str, path: str, base_dir: str, http_prefix: str, domain: str, port: int, getreq_start_time, cookie: str, debug: str, recent_posts_cache: {}, curr_session, default_timeline: str, max_recent_posts: int, translate: {}, cached_webfingers: {}, person_cache: {}, allow_deletion: bool, project_version: str, yt_replace_domain: str, twitter_replacement_domain: str, ua_str: str) -> bool: """Shows the inbox timeline """ if '/users/' in path: if authorized: inbox_feed = \ person_box_json(recent_posts_cache, base_dir, domain, port, path, http_prefix, MAX_POSTS_IN_FEED, 'inbox', authorized, 0, self.server.positive_voting, self.server.voting_time_mins) if inbox_feed: if getreq_start_time: fitness_performance(getreq_start_time, self.server.fitness, '_GET', '_show_inbox', self.server.debug) if request_http(self.headers, debug): nickname = path.replace('/users/', '') nickname = nickname.replace('/inbox', '') page_number = 1 if '?page=' in nickname: page_number = nickname.split('?page=')[1] if ';' in page_number: page_number = page_number.split(';')[0] nickname = nickname.split('?page=')[0] if len(page_number) > 5: page_number = "1" if page_number.isdigit(): page_number = int(page_number) else: page_number = 1 if 'page=' not in path: # if no page was specified then show the first inbox_feed = \ person_box_json(recent_posts_cache, base_dir, domain, port, path + '?page=1', http_prefix, MAX_POSTS_IN_FEED, 'inbox', authorized, 0, self.server.positive_voting, self.server.voting_time_mins) if getreq_start_time: fitness_performance(getreq_start_time, self.server.fitness, '_GET', '_show_inbox2', self.server.debug) full_width_tl_button_header = \ self.server.full_width_tl_button_header minimal_nick = is_minimal(base_dir, domain, nickname) access_keys = self.server.access_keys if self.server.key_shortcuts.get(nickname): access_keys = \ self.server.key_shortcuts[nickname] shared_items_federated_domains = \ self.server.shared_items_federated_domains allow_local_network_access = \ self.server.allow_local_network_access timezone = None if self.server.account_timezone.get(nickname): timezone = \ self.server.account_timezone.get(nickname) bold_reading = False if self.server.bold_reading.get(nickname): bold_reading = True reverse_sequence = False if nickname in self.server.reverse_sequence: reverse_sequence = True last_post_id = None if ';lastpost=' in path: last_post_id = path.split(';lastpost=')[1] if ';' in last_post_id: last_post_id = last_post_id.split(';')[0] msg = \ html_inbox(default_timeline, recent_posts_cache, max_recent_posts, translate, page_number, MAX_POSTS_IN_FEED, curr_session, base_dir, cached_webfingers, person_cache, nickname, domain, port, inbox_feed, allow_deletion, http_prefix, project_version, minimal_nick, yt_replace_domain, twitter_replacement_domain, self.server.show_published_date_only, self.server.newswire, self.server.positive_voting, self.server.show_publish_as_icon, full_width_tl_button_header, self.server.icons_as_buttons, self.server.rss_icon_at_top, self.server.publish_button_at_top, authorized, self.server.theme_name, self.server.peertube_instances, allow_local_network_access, self.server.text_mode_banner, access_keys, self.server.system_language, self.server.max_like_count, shared_items_federated_domains, self.server.signing_priv_key_pem, self.server.cw_lists, self.server.lists_enabled, timezone, bold_reading, self.server.dogwhistles, ua_str, self.server.min_images_for_accounts, reverse_sequence, last_post_id, self.server.buy_sites, self.server.auto_cw_cache) if getreq_start_time: fitness_performance(getreq_start_time, self.server.fitness, '_GET', '_show_inbox3', self.server.debug) if msg: msg_str = msg onion_domain = self.server.onion_domain i2p_domain = self.server.i2p_domain msg_str = convert_domains(calling_domain, referer_domain, msg_str, http_prefix, domain, onion_domain, i2p_domain) msg = msg_str.encode('utf-8') msglen = len(msg) set_headers(self, 'text/html', msglen, cookie, calling_domain, False) write2(self, msg) if getreq_start_time: fitness_performance(getreq_start_time, self.server.fitness, '_GET', '_show_inbox4', self.server.debug) else: # don't need authorized fetch here because # there is already the authorization check onion_domain = self.server.onion_domain i2p_domain = self.server.i2p_domain msg_str = json.dumps(inbox_feed, ensure_ascii=False) msg_str = convert_domains(calling_domain, referer_domain, msg_str, http_prefix, domain, onion_domain, i2p_domain) msg = msg_str.encode('utf-8') msglen = len(msg) accept_str = self.headers['Accept'] protocol_str = \ get_json_content_from_accept(accept_str) set_headers(self, protocol_str, msglen, None, calling_domain, False) write2(self, msg) fitness_performance(getreq_start_time, self.server.fitness, '_GET', '_show_inbox5', self.server.debug) return True else: if debug: nickname = path.replace('/users/', '') nickname = nickname.replace('/inbox', '') print('DEBUG: ' + nickname + ' was not authorized to access ' + path) if path != '/inbox': # not the shared inbox if debug: print('DEBUG: GET access to inbox is unauthorized') self.send_response(405) self.end_headers() return True return False def _show_person_profile(self, authorized: bool, calling_domain: str, referer_domain: str, path: str, base_dir: str, http_prefix: str, domain: str, onion_domain: str, i2p_domain: str, getreq_start_time, proxy_type: str, cookie: str, debug: str, curr_session) -> bool: """Shows the profile for a person """ # look up a person actor_json = person_lookup(domain, path, base_dir) if not actor_json: return False add_alternate_domains(actor_json, domain, onion_domain, i2p_domain) if request_http(self.headers, debug): curr_session = \ establish_session("showPersonProfile", curr_session, proxy_type, self.server) if not curr_session: http_404(self, 86) return True access_keys = self.server.access_keys city = None timezone = None if '/users/' in path: nickname = path.split('/users/')[1] if '/' in nickname: nickname = nickname.split('/')[0] if self.server.key_shortcuts.get(nickname): access_keys = \ self.server.key_shortcuts[nickname] city = get_spoofed_city(self.server.city, base_dir, nickname, domain) if self.server.account_timezone.get(nickname): timezone = \ self.server.account_timezone.get(nickname) bold_reading = False if self.server.bold_reading.get(nickname): bold_reading = True max_shares_on_profile = \ self.server.max_shares_on_profile sites_unavailable = \ self.server.sites_unavailable msg = \ html_profile(self.server.signing_priv_key_pem, self.server.rss_icon_at_top, self.server.icons_as_buttons, self.server.default_timeline, self.server.recent_posts_cache, self.server.max_recent_posts, self.server.translate, self.server.project_version, base_dir, http_prefix, authorized, actor_json, 'posts', curr_session, self.server.cached_webfingers, self.server.person_cache, self.server.yt_replace_domain, self.server.twitter_replacement_domain, self.server.show_published_date_only, self.server.newswire, self.server.theme_name, self.server.dormant_months, self.server.peertube_instances, self.server.allow_local_network_access, self.server.text_mode_banner, self.server.debug, access_keys, city, self.server.system_language, self.server.max_like_count, self.server.shared_items_federated_domains, None, None, None, self.server.cw_lists, self.server.lists_enabled, self.server.content_license_url, timezone, bold_reading, self.server.buy_sites, None, max_shares_on_profile, sites_unavailable, self.server.no_of_books, self.server.auto_cw_cache).encode('utf-8') msglen = len(msg) set_headers(self, 'text/html', msglen, cookie, calling_domain, False) write2(self, msg) fitness_performance(getreq_start_time, self.server.fitness, '_GET', '_show_person_profile', debug) if self.server.debug: print('DEBUG: html actor sent') else: if secure_mode(curr_session, proxy_type, False, self.server, self.headers, self.path): accept_str = self.headers['Accept'] msg_str = json.dumps(actor_json, ensure_ascii=False) msg_str = convert_domains(calling_domain, referer_domain, msg_str, http_prefix, domain, self.server.onion_domain, self.server.i2p_domain) msg = msg_str.encode('utf-8') msglen = len(msg) if 'application/ld+json' in accept_str: set_headers(self, 'application/ld+json', msglen, cookie, calling_domain, False) elif 'application/jrd+json' in accept_str: set_headers(self, 'application/jrd+json', msglen, cookie, calling_domain, False) else: set_headers(self, 'application/activity+json', msglen, cookie, calling_domain, False) write2(self, msg) fitness_performance(getreq_start_time, self.server.fitness, '_GET', '_show_person_profile json', self.server.debug) if self.server.debug: print('DEBUG: json actor sent') else: http_404(self, 87) return True def _show_post_from_file(self, post_filename: str, liked_by: str, react_by: str, react_emoji: str, authorized: bool, calling_domain: str, referer_domain: str, base_dir: str, http_prefix: str, nickname: str, domain: str, port: int, getreq_start_time, proxy_type: str, cookie: str, debug: str, include_create_wrapper: bool, curr_session) -> bool: """Shows an individual post from its filename """ if not os.path.isfile(post_filename): http_404(self, 71) self.server.getreq_busy = False return True post_json_object = load_json(post_filename) if not post_json_object: self.send_response(429) self.end_headers() self.server.getreq_busy = False return True # Only authorized viewers get to see likes on posts # Otherwize marketers could gain more social graph info if not authorized: pjo = post_json_object if not is_public_post(pjo): # only public posts may be viewed by unauthorized viewers http_401(self, 'only public posts ' + 'may be viewed by unauthorized viewers') self.server.getreq_busy = False return True remove_post_interactions(pjo, True) if request_http(self.headers, debug): timezone = None if self.server.account_timezone.get(nickname): timezone = \ self.server.account_timezone.get(nickname) mitm = False if os.path.isfile(post_filename.replace('.json', '') + '.mitm'): mitm = True bold_reading = False if self.server.bold_reading.get(nickname): bold_reading = True msg = \ html_individual_post(self.server.recent_posts_cache, self.server.max_recent_posts, self.server.translate, base_dir, curr_session, self.server.cached_webfingers, self.server.person_cache, nickname, domain, port, authorized, post_json_object, http_prefix, self.server.project_version, liked_by, react_by, react_emoji, self.server.yt_replace_domain, self.server.twitter_replacement_domain, self.server.show_published_date_only, self.server.peertube_instances, self.server.allow_local_network_access, self.server.theme_name, self.server.system_language, self.server.max_like_count, self.server.signing_priv_key_pem, self.server.cw_lists, self.server.lists_enabled, timezone, mitm, bold_reading, self.server.dogwhistles, self.server.min_images_for_accounts, self.server.buy_sites, self.server.auto_cw_cache) msg = msg.encode('utf-8') msglen = len(msg) set_headers(self, 'text/html', msglen, cookie, calling_domain, False) write2(self, msg) fitness_performance(getreq_start_time, self.server.fitness, '_GET', '_show_post_from_file', debug) else: if secure_mode(curr_session, proxy_type, False, self.server, self.headers, self.path): if not include_create_wrapper and \ post_json_object['type'] == 'Create' and \ has_object_dict(post_json_object): unwrapped_json = post_json_object['object'] unwrapped_json['@context'] = \ get_individual_post_context() msg_str = json.dumps(unwrapped_json, ensure_ascii=False) else: msg_str = json.dumps(post_json_object, ensure_ascii=False) msg_str = convert_domains(calling_domain, referer_domain, msg_str, http_prefix, domain, self.server.onion_domain, self.server.i2p_domain) msg = msg_str.encode('utf-8') msglen = len(msg) protocol_str = \ get_json_content_from_accept(self.headers['Accept']) set_headers(self, protocol_str, msglen, None, calling_domain, False) write2(self, msg) fitness_performance(getreq_start_time, self.server.fitness, '_GET', '_show_post_from_file json', debug) else: http_404(self, 73) self.server.getreq_busy = False return True