summaryrefslogtreecommitdiff
path: root/gemini.py
blob: 58f3358373263ccebe53a97285b004a27189f5f2 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
import re
import socket
import ssl
import urllib.parse

def htmlescape(text: str) -> str:
    return text.replace('<', '&lt;').replace('>', '&gt;')

def gem2html(gem: str) -> str:
    html = []
    state = 'text'
    blanklines = 0
    for line in gem.split('\n'):
        if line.startswith('```'):
            if state == 'pre':
                newstate = 'text'
                blanklines = 0
            else:
                newstate = 'pre'
        elif state == 'pre':
            newstate = 'pre'
        elif line.startswith('=>'):
            newstate = 'links'
        elif line.startswith('* '):
            newstate = 'list'
        else:
            newstate = 'text'

        if state != 'pre':
            if len(line.strip()) == 0:
                blanklines += 1
                if blanklines > 1:
                    html.append('<br/>')
                continue
            blanklines = 0

        if state != newstate:
            if state in ('links', 'list'):
                html.append('</ul>')
            elif state == 'pre':
                html.append('</pre>')

            if newstate in ('links', 'list'):
                html.append('<ul>')
            elif newstate == 'pre':
                html.append('<pre>')
        state = newstate

        if line.startswith('```'):
            pass
        elif state == 'links':
            tokens = line.split(None, 2)
            if len(tokens) == 3:
                _, url, text = tokens
                html.append('<li><a href="{url}">{text}</a></li>'.format(url=url, text=text))
            else:
                _, url = tokens
                html.append('<li><a href="{url}">{url}</a></li>'.format(url=url))
        elif state == 'list':
            html.append('<li>{}</li>'.format(line[2:]))
        elif state == 'pre':
            html.append(line)
        else:
            if line.startswith('###'):
                html.append('<h3>{}</h3>'.format(line[3:].lstrip()))
            elif line.startswith('##'):
                html.append('<h2>{}</h2>'.format(line[2:].lstrip()))
            elif line.startswith('#'):
                html.append('<h1>{}</h1>'.format(line[1:].lstrip()))
            else:
                html.append('<p>{}</p>'.format(htmlescape(line)))
    return '\n'.join(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) -> 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,
            ))
        }
    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('gemini://{}{}\r\n'.format(url_parts.hostname, url_parts.path).encode('utf8'))
            fp = ssock.makefile(mode='rb')
            header = fp.readline(1027)
            status, meta = header.decode('utf8').split(None, 1)
            if status[0] != '2':
                return {
                    'status': status,
                    'meta': meta.strip(),
                }
            body = fp.read()
    return {
        'status': status,
        'meta': meta.strip(),
        'body': body.decode('utf8'),
    }