From 0abf3511646446ee64f21d551dd0d2214fd624b7 Mon Sep 17 00:00:00 2001 From: Matt Singleton Date: Wed, 19 Jan 2022 18:58:02 -0600 Subject: organizing the repo a bit --- browser.py | 133 ------------------------------------- browser/browser.py | 133 +++++++++++++++++++++++++++++++++++++ browser/fsm.py | 117 +++++++++++++++++++++++++++++++++ browser/gemini.py | 148 ++++++++++++++++++++++++++++++++++++++++++ error_template.html | 14 ---- external_link.svg | 1 - fsm.py | 117 --------------------------------- gemini.py | 148 ------------------------------------------ input_template.html | 16 ----- page_template.html | 13 ---- resources/error_template.html | 14 ++++ resources/external_link.svg | 1 + resources/input_template.html | 16 +++++ resources/page_template.html | 13 ++++ resources/style.css | 29 +++++++++ style.css | 29 --------- 16 files changed, 471 insertions(+), 471 deletions(-) delete mode 100755 browser.py create mode 100755 browser/browser.py create mode 100644 browser/fsm.py create mode 100644 browser/gemini.py delete mode 100644 error_template.html delete mode 100644 external_link.svg delete mode 100644 fsm.py delete mode 100644 gemini.py delete mode 100644 input_template.html delete mode 100644 page_template.html create mode 100644 resources/error_template.html create mode 100644 resources/external_link.svg create mode 100644 resources/input_template.html create mode 100644 resources/page_template.html create mode 100644 resources/style.css delete mode 100644 style.css diff --git a/browser.py b/browser.py deleted file mode 100755 index f5475ab..0000000 --- a/browser.py +++ /dev/null @@ -1,133 +0,0 @@ -#!/usr/bin/env python - -import sys - -from PySide6 import QtCore, QtWidgets, QtGui, QtWebEngineWidgets, QtWebEngineCore - -import gemini - - -class GeminiPage(QtWebEngineCore.QWebEnginePage): - def acceptNavigationRequest(self, url, navtype, mainframe): - """ - Block non-gemini page navigation and - send the url to the os instead - """ - if url.scheme() == 'gemini': - return True - else: - QtGui.QDesktopServices().openUrl(url) - return False - - -class GeminiSchemeHandler(QtWebEngineCore.QWebEngineUrlSchemeHandler): - def requestStarted(self, request): - request_url = gemini.hack_url(request.requestUrl().toString()) - print(request_url) - gem = gemini.get(request_url) - print(gem['status'], gem['meta']) - buf = QtCore.QBuffer(parent=request) - buf.open(QtCore.QIODevice.WriteOnly) - buf.write(gemini.gem2html(gem).encode('utf8')) - buf.seek(0) - buf.close() - request.reply(b'text/html', buf) - - -class GUrlBar(QtWidgets.QLineEdit): - - def __init__(self): - QtWidgets.QLineEdit.__init__(self) - - def setUrl(self, url): - url = gemini.hack_url(url.toDisplayString()) - return self.setText(url) - - -class GBrowser(QtWidgets.QMainWindow): - - def __init__(self, initial_url=None, profile=None): - if initial_url is None: - initial_url = 'gemini://gemini.circumlunar.space/' - if profile is None: - profile = QtWebEngineCore.QWebEngineProfile.defaultProfile() - QtWidgets.QMainWindow.__init__(self) - - # Main Viewport - self._browser = QtWebEngineWidgets.QWebEngineView() - page = GeminiPage(profile, self._browser) - self._browser.setPage(page) - self.setCentralWidget(self._browser) - - # Navigation Toolbar - back = QtWidgets.QPushButton('←', self) - forward = QtWidgets.QPushButton('→', self) - self._address = GUrlBar() - toolbar = QtWidgets.QToolBar() - toolbar.setObjectName('navigationToolbar') - toolbar.addWidget(back) - toolbar.addWidget(forward) - toolbar.addWidget(self._address) - self.addToolBar(toolbar) - - # Status Bar - request_status = QtWidgets.QLabel() - self.statusBar().addWidget(request_status) - - # Connect signals - back.clicked.connect(self._browser.back) - forward.clicked.connect(self._browser.forward) - self._browser.urlChanged.connect(self._address.setUrl) - self._address.returnPressed.connect(self.loadAddress) - self._browser.page().linkHovered.connect(self.set_status_url) - - self._browser.load(QtCore.QUrl(initial_url)) - - settings = QtCore.QSettings("xcolour.net", "GeminiBrowser") - self.restoreGeometry(settings.value("geometry")) - self.restoreState(settings.value("windowState")) - self.setWindowTitle('Gemini Browser') - self.show() - - # Shortcuts - down_shortcut = QtGui.QShortcut(QtGui.QKeySequence(QtCore.Qt.Key_J), self) - down_event = QtGui.QKeyEvent(QtCore.QEvent.KeyPress, QtCore.Qt.Key_Down, QtCore.Qt.NoModifier) - down_shortcut.activated.connect(lambda: QtWidgets.QApplication.sendEvent(self._browser.focusProxy(), down_event)) - up_shortcut = QtGui.QShortcut(QtGui.QKeySequence(QtCore.Qt.Key_K), self) - up_event = QtGui.QKeyEvent(QtCore.QEvent.KeyPress, QtCore.Qt.Key_Up, QtCore.Qt.NoModifier) - up_shortcut.activated.connect(lambda: QtWidgets.QApplication.sendEvent(self._browser.focusProxy(), up_event)) - QtGui.QShortcut(QtGui.QKeySequence(QtCore.Qt.Key_H), self, self._browser.back) - QtGui.QShortcut(QtGui.QKeySequence(QtCore.Qt.Key_L), self, self._browser.forward) - QtGui.QShortcut(QtGui.QKeySequence("Shift+R"), self, self._browser.reload) - QtGui.QShortcut(QtGui.QKeySequence("Esc"), self._address, lambda: self.setFocus()) - QtGui.QShortcut(QtGui.QKeySequence("Shift+L"), self, lambda: self._address.setFocus()) - - def set_status_url(self, url): - if url: - self.statusBar().showMessage(url) - else: - self.statusBar().clearMessage() - - def closeEvent(self, event): - settings = QtCore.QSettings("xcolour.net", "GeminiBrowser") - settings.setValue("geometry", self.saveGeometry()) - settings.setValue("windowState", self.saveState()) - self._browser.page().deleteLater() - super().closeEvent(event) - - def loadAddress(self): - self._browser.load(self._address.text()) - self._browser.setFocus() - - -scheme = QtWebEngineCore.QWebEngineUrlScheme(b'gemini') -scheme.setDefaultPort(1965) -scheme.setFlags(QtWebEngineCore.QWebEngineUrlScheme.SecureScheme) -QtWebEngineCore.QWebEngineUrlScheme.registerScheme(scheme) -app = QtWidgets.QApplication(sys.argv) -gem_handler = GeminiSchemeHandler() -profile = QtWebEngineCore.QWebEngineProfile() -profile.removeAllUrlSchemeHandlers() -profile.installUrlSchemeHandler(b'gemini', gem_handler) -ex = GBrowser(sys.argv[1] if len(sys.argv) > 1 else None, profile) -sys.exit(app.exec()) diff --git a/browser/browser.py b/browser/browser.py new file mode 100755 index 0000000..f5475ab --- /dev/null +++ b/browser/browser.py @@ -0,0 +1,133 @@ +#!/usr/bin/env python + +import sys + +from PySide6 import QtCore, QtWidgets, QtGui, QtWebEngineWidgets, QtWebEngineCore + +import gemini + + +class GeminiPage(QtWebEngineCore.QWebEnginePage): + def acceptNavigationRequest(self, url, navtype, mainframe): + """ + Block non-gemini page navigation and + send the url to the os instead + """ + if url.scheme() == 'gemini': + return True + else: + QtGui.QDesktopServices().openUrl(url) + return False + + +class GeminiSchemeHandler(QtWebEngineCore.QWebEngineUrlSchemeHandler): + def requestStarted(self, request): + request_url = gemini.hack_url(request.requestUrl().toString()) + print(request_url) + gem = gemini.get(request_url) + print(gem['status'], gem['meta']) + buf = QtCore.QBuffer(parent=request) + buf.open(QtCore.QIODevice.WriteOnly) + buf.write(gemini.gem2html(gem).encode('utf8')) + buf.seek(0) + buf.close() + request.reply(b'text/html', buf) + + +class GUrlBar(QtWidgets.QLineEdit): + + def __init__(self): + QtWidgets.QLineEdit.__init__(self) + + def setUrl(self, url): + url = gemini.hack_url(url.toDisplayString()) + return self.setText(url) + + +class GBrowser(QtWidgets.QMainWindow): + + def __init__(self, initial_url=None, profile=None): + if initial_url is None: + initial_url = 'gemini://gemini.circumlunar.space/' + if profile is None: + profile = QtWebEngineCore.QWebEngineProfile.defaultProfile() + QtWidgets.QMainWindow.__init__(self) + + # Main Viewport + self._browser = QtWebEngineWidgets.QWebEngineView() + page = GeminiPage(profile, self._browser) + self._browser.setPage(page) + self.setCentralWidget(self._browser) + + # Navigation Toolbar + back = QtWidgets.QPushButton('←', self) + forward = QtWidgets.QPushButton('→', self) + self._address = GUrlBar() + toolbar = QtWidgets.QToolBar() + toolbar.setObjectName('navigationToolbar') + toolbar.addWidget(back) + toolbar.addWidget(forward) + toolbar.addWidget(self._address) + self.addToolBar(toolbar) + + # Status Bar + request_status = QtWidgets.QLabel() + self.statusBar().addWidget(request_status) + + # Connect signals + back.clicked.connect(self._browser.back) + forward.clicked.connect(self._browser.forward) + self._browser.urlChanged.connect(self._address.setUrl) + self._address.returnPressed.connect(self.loadAddress) + self._browser.page().linkHovered.connect(self.set_status_url) + + self._browser.load(QtCore.QUrl(initial_url)) + + settings = QtCore.QSettings("xcolour.net", "GeminiBrowser") + self.restoreGeometry(settings.value("geometry")) + self.restoreState(settings.value("windowState")) + self.setWindowTitle('Gemini Browser') + self.show() + + # Shortcuts + down_shortcut = QtGui.QShortcut(QtGui.QKeySequence(QtCore.Qt.Key_J), self) + down_event = QtGui.QKeyEvent(QtCore.QEvent.KeyPress, QtCore.Qt.Key_Down, QtCore.Qt.NoModifier) + down_shortcut.activated.connect(lambda: QtWidgets.QApplication.sendEvent(self._browser.focusProxy(), down_event)) + up_shortcut = QtGui.QShortcut(QtGui.QKeySequence(QtCore.Qt.Key_K), self) + up_event = QtGui.QKeyEvent(QtCore.QEvent.KeyPress, QtCore.Qt.Key_Up, QtCore.Qt.NoModifier) + up_shortcut.activated.connect(lambda: QtWidgets.QApplication.sendEvent(self._browser.focusProxy(), up_event)) + QtGui.QShortcut(QtGui.QKeySequence(QtCore.Qt.Key_H), self, self._browser.back) + QtGui.QShortcut(QtGui.QKeySequence(QtCore.Qt.Key_L), self, self._browser.forward) + QtGui.QShortcut(QtGui.QKeySequence("Shift+R"), self, self._browser.reload) + QtGui.QShortcut(QtGui.QKeySequence("Esc"), self._address, lambda: self.setFocus()) + QtGui.QShortcut(QtGui.QKeySequence("Shift+L"), self, lambda: self._address.setFocus()) + + def set_status_url(self, url): + if url: + self.statusBar().showMessage(url) + else: + self.statusBar().clearMessage() + + def closeEvent(self, event): + settings = QtCore.QSettings("xcolour.net", "GeminiBrowser") + settings.setValue("geometry", self.saveGeometry()) + settings.setValue("windowState", self.saveState()) + self._browser.page().deleteLater() + super().closeEvent(event) + + def loadAddress(self): + self._browser.load(self._address.text()) + self._browser.setFocus() + + +scheme = QtWebEngineCore.QWebEngineUrlScheme(b'gemini') +scheme.setDefaultPort(1965) +scheme.setFlags(QtWebEngineCore.QWebEngineUrlScheme.SecureScheme) +QtWebEngineCore.QWebEngineUrlScheme.registerScheme(scheme) +app = QtWidgets.QApplication(sys.argv) +gem_handler = GeminiSchemeHandler() +profile = QtWebEngineCore.QWebEngineProfile() +profile.removeAllUrlSchemeHandlers() +profile.installUrlSchemeHandler(b'gemini', gem_handler) +ex = GBrowser(sys.argv[1] if len(sys.argv) > 1 else None, profile) +sys.exit(app.exec()) diff --git a/browser/fsm.py b/browser/fsm.py new file mode 100644 index 0000000..eccfc6e --- /dev/null +++ b/browser/fsm.py @@ -0,0 +1,117 @@ +import html +import sys +import urllib.parse + + +class StackFSM(object): + """ + Implement a finite state machine that uses a stack to + manage state. + """ + + def __init__(self): + self._state_stack = [] + + def _current_state(self): + if len(self._state_stack) > 0: + return self._state_stack[-1] + return None + + def update(self): + fn = self._current_state() + if fn is not None: + fn() + + def push_state(self, state): + self._state_stack.append(state) + + def pop_state(self): + return self._state_stack.pop() + + +class Parser(object): + + def __init__(self, document, output=None): + self._document = document + if output is None: + output = sys.stdout + self._output = output + self._offset = 0 + self._blanks = 0 + self._fsm = StackFSM() + + def parse(self): + self._fsm.push_state(self.text_state) + while self._fsm._current_state() is not None and len(self._document) > self._offset: + self._fsm.update() + + def text_state(self): + line = self._document[self._offset] + if line.strip() == '': + self._blanks += 1 + else: + self._blanks = 0 + if line.startswith('```'): + self._fsm.push_state(self.pre_state) + self._output.write('
\n')
+            self._offset += 1
+        elif line.startswith('* '):
+            self._fsm.push_state(self.list_state)
+            self._output.write('
\n') + self._offset += 1 + else: + self._output.write(line + '\n') + self._offset += 1 + + def list_state(self): + line = self._document[self._offset] + if line.startswith('* '): + self._output.write('
  • {}
  • \n'.format(html.escape(line[2:]))) + self._offset += 1 + else: + self._fsm.pop_state() + self._output.write('\n') + + def link_state(self): + line = self._document[self._offset] + if line.startswith('=>'): + parts = line[2:].split(None, 1) + url = parts[0] + url_parts = urllib.parse.urlsplit(url) + if url_parts.scheme in ('gemini', ''): + external = '' + else: + external = open('external_link.svg').read() + if len(parts) == 1: + text = url + else: + text = html.escape(parts[1]) + self._output.write('\n'.format(url, text, external)) + self._offset += 1 + else: + self._fsm.pop_state() + self._output.write('\n') diff --git a/browser/gemini.py b/browser/gemini.py new file mode 100644 index 0000000..7bedda5 --- /dev/null +++ b/browser/gemini.py @@ -0,0 +1,148 @@ +import io +import re +import socket +import ssl +import string +import urllib.parse + +import fsm + + +def htmlescape(text: str) -> str: + return text.replace('<', '<').replace('>', '>') + + +def gem2html(gem: dict) -> str: + params = { + 'charset': 'utf-8', + 'lang': 'en', + 'css': open('style.css').read() + } + if gem['status'][0] == '2': + template = string.Template(open('page_template.html').read()) + body = io.StringIO() + parser = fsm.Parser(gem['body'].split('\n'), body) + parser.parse() + params['body'] = body.getvalue() + elif gem['status'][0] == '1': + template = string.Template(open('input_template.html').read()) + params['meta'] = gem['meta'] + else: + template = string.Template(open('error_template.html').read()) + if gem['status'] == '00': + params['status'] = 'CLIENT ERROR' + elif gem['status'][0] == '4': + params['status'] = gem['status'] + ' TEMPORARY FAILURE' + elif gem['status'][0] == '5': + params['status'] = gem['status'] + ' PERMANENT FAILURE' + else: + params['status'] = 'UNHANDLED STATUS {}'.format(gem['status']) + params['meta'] = gem['meta'] + + html = template.substitute(params) + with open('latest.html', 'w') as fp: + fp.write(html) + return html + + +def urljoin(base: str, url: str) -> str: + if base is None: + return url + base = re.sub('^gemini:', 'http:', base) + url = re.sub('^gemini:', 'http:', url) + return re.sub('^http:', 'gemini:', urllib.parse.urljoin(base, url)) + + +def get(url: str, follow_redirects: bool = True) -> dict: + response = _get(url) + if follow_redirects is True: + count = 0 + while response['status'][0] == '3': + count += 1 + if count > 20: + return {'status': '00', 'meta': 'Too many redirects'} + print('{status} {meta}'.format(**response)) + response = _get(response['meta']) + return response + + +def hack_url(url: str) -> str: + """ + An ugly hack to reformat input queries the way gemini wants them: + ? + Rather than the default way an html get form renders them: + ?= + I don't think this ever *should* break but I guess it *could*. + """ + url_parts = urllib.parse.urlsplit(url) + query = urllib.parse.parse_qs(url_parts.query) + if len(query) == 1 and '__client_internal_input' in query and len(query['__client_internal_input']) == 1: + query = str(query['__client_internal_input'][0]) + url = urllib.parse.urlunsplit(( + url_parts.scheme, + url_parts.netloc, + url_parts.path, + query, + url_parts.fragment, + )) + url_parts = urllib.parse.urlsplit(url) + return url + + +def _parse_meta(meta: str) -> dict: + mime, _, params_text = meta.lower().strip().partition(';') + params = {} + if params_text.strip(): + for param in params_text.split(';'): + k, val = param.split('=') + params[k.strip()] = val.strip() + params['mime'] = mime.strip() + return params + + +def _get(url: str) -> dict: + url_parts = urllib.parse.urlsplit(url) + if len(url_parts.path) == 0: + return { + 'status': '32', + 'meta': urllib.parse.urlunsplit(( + url_parts.scheme, + url_parts.netloc, + '/', + url_parts.query, + url_parts.fragment, + )) + } + try: + context = ssl.create_default_context() + context.check_hostname = False + context.verify_mode = ssl.CERT_NONE + port = 1965 if url_parts.port is None else url_parts.port + with socket.create_connection((url_parts.hostname, port)) as sock: + with context.wrap_socket(sock, server_hostname=url_parts.hostname) as ssock: + ssock.sendall('{url}\r\n'.format(url=url).encode('utf8')) + fp = ssock.makefile(mode='rb') + header = fp.readline(1027) + parts = header.decode('utf8').split(None, 1) + status = parts[0] + if len(parts) == 1: + meta = '' + else: + meta = parts[1] + if status[0] != '2': + return { + 'status': status, + 'meta': meta.strip(), + } + meta_params = _parse_meta(meta) + body = fp.read() + return { + 'status': status, + 'meta': meta.strip(), + 'body': body.decode(meta_params.get('charset', 'utf8')), + } + except Exception as ex: + return { + 'status': '00', + 'meta': '{}'.format(ex), + } diff --git a/error_template.html b/error_template.html deleted file mode 100644 index 3daacb7..0000000 --- a/error_template.html +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - - -

    $status

    -

    $meta

    - - diff --git a/external_link.svg b/external_link.svg deleted file mode 100644 index ce9bd89..0000000 --- a/external_link.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/fsm.py b/fsm.py deleted file mode 100644 index eccfc6e..0000000 --- a/fsm.py +++ /dev/null @@ -1,117 +0,0 @@ -import html -import sys -import urllib.parse - - -class StackFSM(object): - """ - Implement a finite state machine that uses a stack to - manage state. - """ - - def __init__(self): - self._state_stack = [] - - def _current_state(self): - if len(self._state_stack) > 0: - return self._state_stack[-1] - return None - - def update(self): - fn = self._current_state() - if fn is not None: - fn() - - def push_state(self, state): - self._state_stack.append(state) - - def pop_state(self): - return self._state_stack.pop() - - -class Parser(object): - - def __init__(self, document, output=None): - self._document = document - if output is None: - output = sys.stdout - self._output = output - self._offset = 0 - self._blanks = 0 - self._fsm = StackFSM() - - def parse(self): - self._fsm.push_state(self.text_state) - while self._fsm._current_state() is not None and len(self._document) > self._offset: - self._fsm.update() - - def text_state(self): - line = self._document[self._offset] - if line.strip() == '': - self._blanks += 1 - else: - self._blanks = 0 - if line.startswith('```'): - self._fsm.push_state(self.pre_state) - self._output.write('
    \n')
    -            self._offset += 1
    -        elif line.startswith('* '):
    -            self._fsm.push_state(self.list_state)
    -            self._output.write('
      \n') - elif line.startswith('=>'): - self._fsm.push_state(self.link_state) - self._output.write('
        \n') - else: - if line.startswith('# '): - self._output.write('

        {}

        \n'.format(html.escape(line[2:]))) - elif line.startswith('## '): - self._output.write('

        {}

        \n'.format(html.escape(line[3:]))) - elif line.startswith('### '): - self._output.write('

        {}

        \n'.format(html.escape(line[4:]))) - elif line.startswith('> '): - self._output.write('
        {}
        \n'.format(html.escape(line[2:]))) - elif line.strip() == '': - if self._blanks > 1: - self._output.write('
        \n') - else: - self._output.write('

        {}

        \n'.format(html.escape(line))) - self._offset += 1 - - def pre_state(self): - line = self._document[self._offset] - if line.startswith('```'): - self._fsm.pop_state() - self._output.write('
    \n') - self._offset += 1 - else: - self._output.write(line + '\n') - self._offset += 1 - - def list_state(self): - line = self._document[self._offset] - if line.startswith('* '): - self._output.write('
  • {}
  • \n'.format(html.escape(line[2:]))) - self._offset += 1 - else: - self._fsm.pop_state() - self._output.write('\n') - - def link_state(self): - line = self._document[self._offset] - if line.startswith('=>'): - parts = line[2:].split(None, 1) - url = parts[0] - url_parts = urllib.parse.urlsplit(url) - if url_parts.scheme in ('gemini', ''): - external = '' - else: - external = open('external_link.svg').read() - if len(parts) == 1: - text = url - else: - text = html.escape(parts[1]) - self._output.write('\n'.format(url, text, external)) - self._offset += 1 - else: - self._fsm.pop_state() - self._output.write('\n') diff --git a/gemini.py b/gemini.py deleted file mode 100644 index 7bedda5..0000000 --- a/gemini.py +++ /dev/null @@ -1,148 +0,0 @@ -import io -import re -import socket -import ssl -import string -import urllib.parse - -import fsm - - -def htmlescape(text: str) -> str: - return text.replace('<', '<').replace('>', '>') - - -def gem2html(gem: dict) -> str: - params = { - 'charset': 'utf-8', - 'lang': 'en', - 'css': open('style.css').read() - } - if gem['status'][0] == '2': - template = string.Template(open('page_template.html').read()) - body = io.StringIO() - parser = fsm.Parser(gem['body'].split('\n'), body) - parser.parse() - params['body'] = body.getvalue() - elif gem['status'][0] == '1': - template = string.Template(open('input_template.html').read()) - params['meta'] = gem['meta'] - else: - template = string.Template(open('error_template.html').read()) - if gem['status'] == '00': - params['status'] = 'CLIENT ERROR' - elif gem['status'][0] == '4': - params['status'] = gem['status'] + ' TEMPORARY FAILURE' - elif gem['status'][0] == '5': - params['status'] = gem['status'] + ' PERMANENT FAILURE' - else: - params['status'] = 'UNHANDLED STATUS {}'.format(gem['status']) - params['meta'] = gem['meta'] - - html = template.substitute(params) - with open('latest.html', 'w') as fp: - fp.write(html) - return html - - -def urljoin(base: str, url: str) -> str: - if base is None: - return url - base = re.sub('^gemini:', 'http:', base) - url = re.sub('^gemini:', 'http:', url) - return re.sub('^http:', 'gemini:', urllib.parse.urljoin(base, url)) - - -def get(url: str, follow_redirects: bool = True) -> dict: - response = _get(url) - if follow_redirects is True: - count = 0 - while response['status'][0] == '3': - count += 1 - if count > 20: - return {'status': '00', 'meta': 'Too many redirects'} - print('{status} {meta}'.format(**response)) - response = _get(response['meta']) - return response - - -def hack_url(url: str) -> str: - """ - An ugly hack to reformat input queries the way gemini wants them: - ? - Rather than the default way an html get form renders them: - ?= - I don't think this ever *should* break but I guess it *could*. - """ - url_parts = urllib.parse.urlsplit(url) - query = urllib.parse.parse_qs(url_parts.query) - if len(query) == 1 and '__client_internal_input' in query and len(query['__client_internal_input']) == 1: - query = str(query['__client_internal_input'][0]) - url = urllib.parse.urlunsplit(( - url_parts.scheme, - url_parts.netloc, - url_parts.path, - query, - url_parts.fragment, - )) - url_parts = urllib.parse.urlsplit(url) - return url - - -def _parse_meta(meta: str) -> dict: - mime, _, params_text = meta.lower().strip().partition(';') - params = {} - if params_text.strip(): - for param in params_text.split(';'): - k, val = param.split('=') - params[k.strip()] = val.strip() - params['mime'] = mime.strip() - return params - - -def _get(url: str) -> dict: - url_parts = urllib.parse.urlsplit(url) - if len(url_parts.path) == 0: - return { - 'status': '32', - 'meta': urllib.parse.urlunsplit(( - url_parts.scheme, - url_parts.netloc, - '/', - url_parts.query, - url_parts.fragment, - )) - } - try: - context = ssl.create_default_context() - context.check_hostname = False - context.verify_mode = ssl.CERT_NONE - port = 1965 if url_parts.port is None else url_parts.port - with socket.create_connection((url_parts.hostname, port)) as sock: - with context.wrap_socket(sock, server_hostname=url_parts.hostname) as ssock: - ssock.sendall('{url}\r\n'.format(url=url).encode('utf8')) - fp = ssock.makefile(mode='rb') - header = fp.readline(1027) - parts = header.decode('utf8').split(None, 1) - status = parts[0] - if len(parts) == 1: - meta = '' - else: - meta = parts[1] - if status[0] != '2': - return { - 'status': status, - 'meta': meta.strip(), - } - meta_params = _parse_meta(meta) - body = fp.read() - return { - 'status': status, - 'meta': meta.strip(), - 'body': body.decode(meta_params.get('charset', 'utf8')), - } - except Exception as ex: - return { - 'status': '00', - 'meta': '{}'.format(ex), - } diff --git a/input_template.html b/input_template.html deleted file mode 100644 index defbb7a..0000000 --- a/input_template.html +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - - -
    - - - - - diff --git a/page_template.html b/page_template.html deleted file mode 100644 index e049d56..0000000 --- a/page_template.html +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - -$body - - diff --git a/resources/error_template.html b/resources/error_template.html new file mode 100644 index 0000000..3daacb7 --- /dev/null +++ b/resources/error_template.html @@ -0,0 +1,14 @@ + + + + + + + + +

    $status

    +

    $meta

    + + diff --git a/resources/external_link.svg b/resources/external_link.svg new file mode 100644 index 0000000..ce9bd89 --- /dev/null +++ b/resources/external_link.svg @@ -0,0 +1 @@ + diff --git a/resources/input_template.html b/resources/input_template.html new file mode 100644 index 0000000..defbb7a --- /dev/null +++ b/resources/input_template.html @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + diff --git a/resources/page_template.html b/resources/page_template.html new file mode 100644 index 0000000..e049d56 --- /dev/null +++ b/resources/page_template.html @@ -0,0 +1,13 @@ + + + + + + + + +$body + + diff --git a/resources/style.css b/resources/style.css new file mode 100644 index 0000000..6b06966 --- /dev/null +++ b/resources/style.css @@ -0,0 +1,29 @@ +html { + font-size: 18px; + font-family: Garamond, Georgia, sans-serif; +} + +pre { + font-family: Courier, monospace; +} + +blockquote { + border-left: 5px solid #ddd; + padding: 0.6em 0.6em 0.2em; + font-style: italic; +} + +blockquote:before { + content: "\201C"; + display: inline-block; + padding-right: 0.4rem; + font-size: 4em; + line-height: 0; + vertical-align: -0.4em; + color: lightgrey; + margin-right: 0.1em; +} + +li.link::marker { + content: '⇒ '; +} diff --git a/style.css b/style.css deleted file mode 100644 index 6b06966..0000000 --- a/style.css +++ /dev/null @@ -1,29 +0,0 @@ -html { - font-size: 18px; - font-family: Garamond, Georgia, sans-serif; -} - -pre { - font-family: Courier, monospace; -} - -blockquote { - border-left: 5px solid #ddd; - padding: 0.6em 0.6em 0.2em; - font-style: italic; -} - -blockquote:before { - content: "\201C"; - display: inline-block; - padding-right: 0.4rem; - font-size: 4em; - line-height: 0; - vertical-align: -0.4em; - color: lightgrey; - margin-right: 0.1em; -} - -li.link::marker { - content: '⇒ '; -} -- cgit v1.2.3