diff --git a/daemon.py b/daemon.py index 46e64f2fb..81c112e24 100644 --- a/daemon.py +++ b/daemon.py @@ -32,6 +32,7 @@ from metadata import metadata_custom_emoji from enigma import get_enigma_pub_key from enigma import set_enigma_pub_key from pgp import actor_to_vcard +from pgp import actor_to_vcard_xml from pgp import get_email_address from pgp import set_email_address from pgp import get_pgp_pub_key @@ -1202,6 +1203,8 @@ class PubServer(BaseHTTPRequestHandler): return False if 'application/' in accept_str: return False + if path.startswith('/@'): + path = path.replace('/@', '/users/', 1) if not path.startswith('/users/'): self._400() return True @@ -1225,11 +1228,16 @@ class PubServer(BaseHTTPRequestHandler): self._404() self.server.vcard_is_active = False return True - vcard_str = actor_to_vcard(actor_json) + if 'vcard+xml' in accept_str: + vcard_str = actor_to_vcard_xml(actor_json, domain) + header_type = 'text/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) - self._set_headers('text/vcard; charset=utf-8', msglen, + self._set_headers(header_type, msglen, None, calling_domain, True) self._write(msg) print('vcard sent to ' + str(referer_domain)) diff --git a/deploy/i2p b/deploy/i2p index a2eca60c2..1fc4ad465 100755 --- a/deploy/i2p +++ b/deploy/i2p @@ -329,7 +329,7 @@ echo "Creating nginx virtual host for http://${I2P_DOMAIN}" echo ' gzip on;'; echo ' gzip_min_length 1000;'; echo ' gzip_proxied expired no-cache no-store private auth;'; - echo ' gzip_types text/plain application/xml;'; + echo ' gzip_types gzip_types text/plain text/css text/vcard text/vcard+xml application/json application/ld+json application/javascript text/xml application/xml application/rdf+xml application/xml+rss text/javascript;'; echo ''; echo ' add_header X-Content-Type-Options nosniff;'; echo ' add_header X-XSS-Protection "1; mode=block";'; diff --git a/deploy/onion b/deploy/onion index a93599731..60a354fe3 100755 --- a/deploy/onion +++ b/deploy/onion @@ -251,7 +251,7 @@ echo "Creating nginx virtual host for ${ONION_DOMAIN}" echo ' gzip on;'; echo ' gzip_min_length 1000;'; echo ' gzip_proxied expired no-cache no-store private auth;'; - echo ' gzip_types text/plain application/xml;'; + echo ' gzip_types gzip_types text/plain text/css text/vcard text/vcard+xml application/json application/ld+json application/javascript text/xml application/xml application/rdf+xml application/xml+rss text/javascript;'; echo ''; echo ' add_header X-Content-Type-Options nosniff;'; echo ' add_header X-XSS-Protection "1; mode=block";'; diff --git a/epicyon.py b/epicyon.py index 831b367ab..a98d161a7 100644 --- a/epicyon.py +++ b/epicyon.py @@ -291,6 +291,9 @@ parser.add_argument('--postsraw', dest='postsraw', type=str, help='Show raw json of posts for the given handle') parser.add_argument('--vcard', dest='vcard', type=str, default=None, help='Show the vcard for a given activitypub actor url') +parser.add_argument('--xmlvcard', dest='xmlvcard', type=str, default=None, + help='Show the xml vcard for a given ' + + 'activitypub actor url') parser.add_argument('--json', dest='json', type=str, default=None, help='Show the json for a given activitypub url') parser.add_argument('--htmlpost', dest='htmlpost', type=str, default=None, @@ -977,7 +980,20 @@ if args.vcard: domain = '' if args.domain: domain = args.domain - test_vcard = get_vcard(session, args.vcard, + test_vcard = get_vcard(False, session, args.vcard, + None, debug, __version__, http_prefix, domain) + if test_vcard: + print(test_vcard) + sys.exit() + +if args.xmlvcard: + session = create_session(None) + if not args.domain: + args.domain = get_config_param(base_dir, 'domain') + domain = '' + if args.domain: + domain = args.domain + test_vcard = get_vcard(True, session, args.xmlvcard, None, debug, __version__, http_prefix, domain) if test_vcard: print(test_vcard) diff --git a/pgp.py b/pgp.py index a18b87941..6a84784a9 100644 --- a/pgp.py +++ b/pgp.py @@ -630,18 +630,18 @@ def pgp_public_key_upload(base_dir: str, session, return actor_update -def actor_to_vcard(actor: {}) -> str: +def actor_to_vcard(actor: {}, domain: str) -> str: """Returns a vcard for a given actor """ vcard_str = 'BEGIN:VCARD\n' vcard_str += 'VERSION:4.0\n' vcard_str += 'REV:' + actor['published'] + '\n' - vcard_str += 'FN:' + actor['name'] + '\n' - vcard_str += 'N:' + actor['preferredUsername'] + '\n' - vcard_str += 'URL:' + actor['url'] + '\n' + vcard_str += 'FN:' + remove_html(actor['name']) + '\n' + vcard_str += 'NICKNAME:' + actor['preferredUsername'] + '\n' + vcard_str += 'URL;TYPE=profile:' + actor['url'] + '\n' blog_address = get_blog_address(actor) if blog_address: - vcard_str += 'URL:blog:' + blog_address + '\n' + vcard_str += 'URL;TYPE=blog:' + blog_address + '\n' vcard_str += 'NOTE:' + remove_html(actor['summary']) + '\n' if actor['icon']['url']: vcard_str += 'PHOTO:' + actor['icon']['url'] + '\n' @@ -652,23 +652,25 @@ def actor_to_vcard(actor: {}) -> str: email_address = get_email_address(actor) if email_address: vcard_str += 'EMAIL;TYPE=internet:' + email_address + '\n' + vcard_str += 'IMPP;TYPE=fediverse:' + \ + actor['preferredUsername'] + '@' + domain + '\n' xmpp_address = get_xmpp_address(actor) if xmpp_address: - vcard_str += 'IMPP:xmpp:' + xmpp_address + '\n' + vcard_str += 'IMPP;TYPE=xmpp:' + xmpp_address + '\n' jami_address = get_jami_address(actor) if jami_address: - vcard_str += 'IMPP:jami:' + jami_address + '\n' + vcard_str += 'IMPP;TYPE=jami:' + jami_address + '\n' matrix_address = get_matrix_address(actor) if matrix_address: - vcard_str += 'IMPP:matrix:' + matrix_address + '\n' + vcard_str += 'IMPP;TYPE=matrix:' + matrix_address + '\n' briar_address = get_briar_address(actor) if briar_address: if briar_address.startswith('briar://'): briar_address = briar_address.split('briar://')[1] - vcard_str += 'IMPP:briar:' + briar_address + '\n' + vcard_str += 'IMPP;TYPE=briar:' + briar_address + '\n' cwtch_address = get_cwtch_address(actor) if cwtch_address: - vcard_str += 'IMPP:cwtch:' + cwtch_address + '\n' + vcard_str += 'IMPP;TYPE=cwtch:' + cwtch_address + '\n' if actor.get('hasOccupation'): if len(actor['hasOccupation']) > 0: if actor['hasOccupation'][0].get('name'): @@ -682,3 +684,87 @@ def actor_to_vcard(actor: {}) -> str: 'ADR:;;;' + city_name + ';;;\n' vcard_str += 'END:VCARD\n' return vcard_str + + +def actor_to_vcard_xml(actor: {}, domain: str) -> str: + """Returns a xml formatted vcard for a given actor + """ + vcard_str = '\n' + vcard_str += '\n' + vcard_str += ' \n' + vcard_str += ' ' + \ + remove_html(actor['name']) + '\n' + vcard_str += ' ' + \ + actor['preferredUsername'] + '\n' + vcard_str += ' ' + \ + remove_html(actor['summary']) + '\n' + email_address = get_email_address(actor) + if email_address: + vcard_str += ' ' + email_address + '\n' + vcard_str += ' ' + \ + 'fediverse' + \ + '' + actor['preferredUsername'] + '@' + domain + \ + '\n' + xmpp_address = get_xmpp_address(actor) + if xmpp_address: + vcard_str += ' ' + \ + 'xmpp' + \ + '' + xmpp_address + '\n' + jami_address = get_jami_address(actor) + if jami_address: + vcard_str += ' ' + \ + 'jami' + \ + '' + jami_address + '\n' + matrix_address = get_matrix_address(actor) + if matrix_address: + vcard_str += ' ' + \ + 'matrix' + \ + '' + matrix_address + '\n' + briar_address = get_briar_address(actor) + if briar_address: + vcard_str += ' ' + \ + 'briar' + \ + '' + briar_address + '\n' + cwtch_address = get_cwtch_address(actor) + if cwtch_address: + vcard_str += ' ' + \ + 'cwtch' + \ + '' + cwtch_address + '\n' + vcard_str += ' ' + \ + 'profile' + \ + '' + actor['url'] + '\n' + blog_address = get_blog_address(actor) + if blog_address: + vcard_str += ' ' + \ + 'blog' + \ + '' + blog_address + '\n' + vcard_str += ' ' + actor['published'] + '\n' + if actor['icon']['url']: + vcard_str += \ + ' ' + actor['icon']['url'] + '\n' + pgp_key = get_pgp_pub_key(actor) + if pgp_key: + pgp_key_encoded = \ + base64.b64encode(pgp_key.encode('utf-8')).decode('utf-8') + vcard_str += \ + ' ' + \ + '' + \ + 'data' + \ + 'application/pgp-keys;base64' + \ + '' + \ + '' + pgp_key_encoded + '\n' + if actor.get('hasOccupation'): + if len(actor['hasOccupation']) > 0: + if actor['hasOccupation'][0].get('name'): + vcard_str += \ + ' ' + \ + actor['hasOccupation'][0]['name'] + '\n' + if actor['hasOccupation'][0].get('occupationLocation'): + city_name = \ + actor['hasOccupation'][0]['occupationLocation']['name'] + vcard_str += \ + ' ' + city_name + '\n' + + vcard_str += ' \n' + vcard_str += '\n' + return vcard_str diff --git a/session.py b/session.py index 7e74bcf9b..35671d5d6 100644 --- a/session.py +++ b/session.py @@ -246,7 +246,8 @@ def get_json(signing_priv_key_pem: str, None, quiet, debug, True) -def get_vcard(session, url: str, params: {}, debug: bool, +def get_vcard(xml_format: bool, + session, url: str, params: {}, debug: bool, version: str = '1.3.0', http_prefix: str = 'https', domain: str = 'testdomain', timeout_sec: int = 20, quiet: bool = False) -> {}: @@ -258,6 +259,8 @@ def get_vcard(session, url: str, params: {}, debug: bool, headers = { 'Accept': 'text/vcard' } + if xml_format: + headers['Accept'] = 'text/vcard+xml' session_params = {} session_headers = {} if headers: diff --git a/theme/zen/icons/showhide.png b/theme/zen/icons/showhide.png index 143701e29..ed36fd797 100644 Binary files a/theme/zen/icons/showhide.png and b/theme/zen/icons/showhide.png differ diff --git a/webfinger.py b/webfinger.py index ab0e5e819..440d96b3e 100644 --- a/webfinger.py +++ b/webfinger.py @@ -171,6 +171,11 @@ def create_webfinger_endpoint(nickname: str, domain: str, port: int, "rel": "http://webfinger.net/rel/profile-page", "type": "text/html" }, + { + "href": profile_page_href, + "rel": "http://webfinger.net/rel/profile-page", + "type": "text/vcard" + }, { "href": person_id, "rel": "self", @@ -280,7 +285,7 @@ def webfinger_lookup(path: str, base_dir: str, return wf_json -def _webfinger_updateAvatar(wf_json: {}, actor_json: {}) -> bool: +def _webfinger_update_avatar(wf_json: {}, actor_json: {}) -> bool: """Updates the avatar image link """ found = False @@ -307,6 +312,20 @@ def _webfinger_updateAvatar(wf_json: {}, actor_json: {}) -> bool: return True +def _webfinger_update_vcard(wf_json: {}, actor_json: {}) -> bool: + """Updates the vcard link + """ + for link in wf_json['links']: + if link['type'] == 'text/vcard': + return False + wf_json['links'].append({ + "href": actor_json['url'], + "rel": "http://webfinger.net/rel/profile-page", + "type": "text/vcard" + }) + return True + + def _webfinger_add_blog_link(wf_json: {}, actor_json: {}) -> bool: """Adds a blog link to webfinger if needed """ @@ -412,7 +431,10 @@ def _webfinger_updateFromProfile(wf_json: {}, actor_json: {}) -> bool: wf_json['aliases'].remove(full_alias) changed = True - if _webfinger_updateAvatar(wf_json, actor_json): + if _webfinger_update_avatar(wf_json, actor_json): + changed = True + + if _webfinger_update_vcard(wf_json, actor_json): changed = True if _webfinger_add_blog_link(wf_json, actor_json): diff --git a/website/EN/index.html b/website/EN/index.html index bf3074e3d..6f34ccf6d 100644 --- a/website/EN/index.html +++ b/website/EN/index.html @@ -1416,7 +1416,7 @@ gzip_comp_level 6;
gzip_buffers 16 8k;
gzip_http_version 1.1;
- gzip_types text/plain text/css application/json application/ld+json application/javascript text/xml application/xml application/rdf+xml application/xml+rss text/javascript;
+ gzip_types text/plain text/css text/vcard text/vcard+xml application/json application/ld+json application/javascript text/xml application/xml application/rdf+xml application/xml+rss text/javascript;

ssl_stapling off;
ssl_stapling_verify off;