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;