diff --git a/galacteek/browser/schemes/gemini/__init__.py b/galacteek/browser/schemes/gemini/__init__.py index 1d214d32..fc9181a8 100644 --- a/galacteek/browser/schemes/gemini/__init__.py +++ b/galacteek/browser/schemes/gemini/__init__.py @@ -1,6 +1,7 @@ import re import ignition from yarl import URL +from pathlib import Path from PyQt5.QtCore import QUrl @@ -10,6 +11,7 @@ from galacteek.browser.schemes import SCHEME_GEMINI from .gemtext import gemTextToHtml +from .x509 import x509SelfSignedGenerate class GeminiError(Exception): @@ -17,16 +19,39 @@ class GeminiError(Exception): class GeminiClient: - def geminiRequest(self, url: str): + def geminiRequest(self, url: str, referer, certificate): # Run in the thread executor try: - response = ignition.request(url) - data = response.data() - return response, data + response = ignition.request( + url, + referer=referer, + ca_cert=certificate + ) + return response, response.data() except Exception as err: log.debug(f'Gemini request error for URL {url}: {err}') return None, None + def certificateForHost(self, certsPath: Path, host: str): + try: + hcPath = certsPath.joinpath(host) + hcPath.mkdir(parents=True, exist_ok=True) + + keyPath = hcPath.joinpath('ca.key') + certPath = hcPath.joinpath('ca.crt') + + if keyPath.is_file() and certPath.is_file(): + # TODO: load the cert here, is_file() is cheap .. + return certPath, keyPath + else: + return x509SelfSignedGenerate( + host, + keyDestPath=keyPath, + certDestPath=certPath + ) + except Exception: + return None, None + class GeminiSchemeHandler(BaseURLSchemeHandler, GeminiClient): """ @@ -43,6 +68,9 @@ def __init__(self, parent=None, noMutexes=False): str(self.app.geminiHostsLocation) ) + self.certStoreLocation = self.app.dataLocation.joinpath( + 'gemini').joinpath('identities') + async def handleRequest(self, request, uid): rUrl = request.requestUrl() rInitiator = request.initiator() @@ -68,11 +96,21 @@ async def handleRequest(self, request, uid): else: log.debug(f'{rMethod}: {url}') + # Get cert + cert = await self.app.loop.run_in_executor( + self.app.executor, + self.certificateForHost, + self.certStoreLocation, + host + ) + # Run the request in the app's executor response, data = await self.app.loop.run_in_executor( self.app.executor, self.geminiRequest, - url + url, + None, + cert ) if not response or not data: @@ -215,7 +253,9 @@ async def handleRequest(self, ipfsop, request, uid): response, data = await self.app.loop.run_in_executor( self.app.executor, self.geminiRequest, - url + url, + None, + None ) if not response or not data: diff --git a/galacteek/browser/schemes/gemini/gemtext.py b/galacteek/browser/schemes/gemini/gemtext.py index 3839092d..ad77068e 100644 --- a/galacteek/browser/schemes/gemini/gemtext.py +++ b/galacteek/browser/schemes/gemini/gemtext.py @@ -62,8 +62,6 @@ def gemTextToHtml(gmi: str): title = None for line in gmi.split('\n'): - line = line.strip() - if len(line): if line.startswith("```") or line.endswith("```"): preformat = not preformat diff --git a/galacteek/browser/schemes/gemini/x509.py b/galacteek/browser/schemes/gemini/x509.py new file mode 100644 index 00000000..456c46c7 --- /dev/null +++ b/galacteek/browser/schemes/gemini/x509.py @@ -0,0 +1,59 @@ +from pathlib import Path +import datetime + +from cryptography import x509 +from cryptography.hazmat.backends import default_backend +from cryptography.hazmat.primitives import hashes, serialization +from cryptography.hazmat.primitives.asymmetric import rsa +from cryptography.x509.oid import NameOID + + +def x509SelfSignedGenerate(commonName, + orgName='Gemini Org', + unitName='Default CA Deployment', + monthsValid=12 * 40, + keyDestPath: Path = None, + certDestPath: Path = None): + one_day = datetime.timedelta(1, 0, 0) + + private_key = rsa.generate_private_key( + public_exponent=65537, + key_size=2048, + backend=default_backend() + ) + + public_key = private_key.public_key() + builder = x509.CertificateBuilder() + builder = builder.subject_name(x509.Name([ + x509.NameAttribute(NameOID.COMMON_NAME, commonName) + ])) + builder = builder.issuer_name(x509.Name([ + x509.NameAttribute(NameOID.COMMON_NAME, commonName), + ])) + builder = builder.not_valid_before(datetime.datetime.today() - one_day) + builder = builder.not_valid_after( + datetime.datetime.today() + datetime.timedelta( + days=monthsValid * 30) + ) + builder = builder.serial_number(x509.random_serial_number()) + builder = builder.public_key(public_key) + certificate = builder.sign( + private_key=private_key, algorithm=hashes.SHA256(), + backend=default_backend() + ) + + if keyDestPath and certDestPath: + with open(str(keyDestPath), "wb") as f: + f.write(private_key.private_bytes( + encoding=serialization.Encoding.PEM, + format=serialization.PrivateFormat.TraditionalOpenSSL, + encryption_algorithm=serialization.NoEncryption() + )) + with open(certDestPath, "wb") as f: + f.write(certificate.public_bytes( + encoding=serialization.Encoding.PEM, + )) + + return certDestPath, keyDestPath + else: + return None, None diff --git a/galacteek/ld/ontolochain/__init__.py b/galacteek/ld/ontolochain/__init__.py index 577d0683..abd6a3c4 100644 --- a/galacteek/ld/ontolochain/__init__.py +++ b/galacteek/ld/ontolochain/__init__.py @@ -89,6 +89,9 @@ async def create(ipfsop, '@type': 'OntoloChain', '@id': str(chainId), 'peerId': peerId, + 'didCreator': { + '@id': str(ipid.didUriRef) + }, 'description': description, 'dateCreated': utcDatetimeIso(), 'verificationMethod': f'{ipid.did}#keys-1', diff --git a/galacteek/templates/gemini_capsule_render.html b/galacteek/templates/gemini_capsule_render.html index f67744e3..4b15d03c 100644 --- a/galacteek/templates/gemini_capsule_render.html +++ b/galacteek/templates/gemini_capsule_render.html @@ -4,9 +4,15 @@ {{ title }} diff --git a/galacteek/ui/galacteek.qrc b/galacteek/ui/galacteek.qrc index 15ad963b..32c92727 100644 --- a/galacteek/ui/galacteek.qrc +++ b/galacteek/ui/galacteek.qrc @@ -146,6 +146,7 @@ ../../share/static/fonts/Inter-UI-Regular.woff2 ../../share/static/fonts/DejaVuSans.ttf ../../share/static/fonts/SegoeUI.woff2 + ../../share/static/fonts/Symbola.ttf ../../share/icons/chat.png ../../share/icons/camera.png ../../share/icons/chat-active.png diff --git a/requirements.txt b/requirements.txt index 911a8ed6..8e40746e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -26,6 +26,7 @@ frozendict==1.2 filelock>=3.0.1 gitpython>=2.1.11 html2text==2020.1.16 +ignition-gemini==0.1.11 ipfshttpclient>=0.7.0 jinja2==3.0.1 json-traverse>=0.4 @@ -51,6 +52,7 @@ pycryptodomex==3.9.8 pygments>=2.3.1 pyld>=2.0.2 PyNaCl>=1.4.0 +python-dateutil==2.8.2 python-magic>=0.4.15 python3-crdt>=1.0.3 py-multibase>=1.0.3 @@ -76,5 +78,4 @@ aiogeminipfs @ git+https://gitlab.com/galacteek/aiogeminipfs.git@ipfs#egg=aiogem rdflib @ git+https://gitlab.com/galacteek/rdflib.git@ipfs#egg=rdflib rdflib-jsonld @ git+https://gitlab.com/galacteek/rdflib-jsonld.git@galacteek#egg=rdflib-jsonld galacteek-ld-web4 @ git+https://gitlab.com/galacteek/galacteek-ld-web4.git@master -ignition-gemini @ git+https://gitlab.com/cipres/ignition.git@galacteek SPARQL-Burger @ git+https://gitlab.com/galacteek/SPARQL-Burger.git@master#egg=SPARQL-Burger diff --git a/share/static/fonts/Symbola.ttf b/share/static/fonts/Symbola.ttf new file mode 100644 index 00000000..dd63d2f5 Binary files /dev/null and b/share/static/fonts/Symbola.ttf differ diff --git a/share/translations/galacteek_en.ts b/share/translations/galacteek_en.ts index b666baf0..aba98b94 100644 --- a/share/translations/galacteek_en.ts +++ b/share/translations/galacteek_en.ts @@ -1237,6 +1237,99 @@ Path: {0}, nodes processed: {1} + + MediaPlayer + + + No media player support available on your system + + + + + Media player error (code: {0}) + + + + + Fullscreen + + + + + Copy playlist's IPFS path to the clipboard + + + + + Load playlist from the clipboard + + + + + Cannot load playlist + + + + + Export to Turtle (text/turtle) + + + + + A playlist with this name already exists + + + + + Remove media from playlist + + + + + Playlist + + + + + Unsaved playlist + + + + + Playlist name + + + + + Pin playlist items (here) + + + + + Clear playlist + + + + + Save playlist + + + + + Load playlist + + + + + Already queued in the current playlist + + + + + No media in playlist + + + MediaPlaylist diff --git a/share/translations/galacteek_es.ts b/share/translations/galacteek_es.ts index b666baf0..aba98b94 100644 --- a/share/translations/galacteek_es.ts +++ b/share/translations/galacteek_es.ts @@ -1237,6 +1237,99 @@ Path: {0}, nodes processed: {1} + + MediaPlayer + + + No media player support available on your system + + + + + Media player error (code: {0}) + + + + + Fullscreen + + + + + Copy playlist's IPFS path to the clipboard + + + + + Load playlist from the clipboard + + + + + Cannot load playlist + + + + + Export to Turtle (text/turtle) + + + + + A playlist with this name already exists + + + + + Remove media from playlist + + + + + Playlist + + + + + Unsaved playlist + + + + + Playlist name + + + + + Pin playlist items (here) + + + + + Clear playlist + + + + + Save playlist + + + + + Load playlist + + + + + Already queued in the current playlist + + + + + No media in playlist + + + MediaPlaylist diff --git a/share/translations/galacteek_fr.ts b/share/translations/galacteek_fr.ts index b666baf0..aba98b94 100644 --- a/share/translations/galacteek_fr.ts +++ b/share/translations/galacteek_fr.ts @@ -1237,6 +1237,99 @@ Path: {0}, nodes processed: {1} + + MediaPlayer + + + No media player support available on your system + + + + + Media player error (code: {0}) + + + + + Fullscreen + + + + + Copy playlist's IPFS path to the clipboard + + + + + Load playlist from the clipboard + + + + + Cannot load playlist + + + + + Export to Turtle (text/turtle) + + + + + A playlist with this name already exists + + + + + Remove media from playlist + + + + + Playlist + + + + + Unsaved playlist + + + + + Playlist name + + + + + Pin playlist items (here) + + + + + Clear playlist + + + + + Save playlist + + + + + Load playlist + + + + + Already queued in the current playlist + + + + + No media in playlist + + + MediaPlaylist