1 package gmitohtml
2
3 import (
4 "bufio"
5 "bytes"
6 "errors"
7 "fmt"
8 "html"
9 "net/url"
10 "path"
11 "strings"
12 "sync"
13 )
14
15
16 var ErrInvalidURL = errors.New("invalid URL")
17
18 var daemonAddress string
19
20 var assetLock sync.Mutex
21
22 func rewriteURL(u string, loc *url.URL) string {
23 if daemonAddress != "" {
24 scheme := "gemini"
25 if strings.HasPrefix(loc.Path, "/file/") {
26 scheme = "file"
27 }
28
29 if strings.HasPrefix(u, "file://") {
30 if !allowFileAccess {
31 return "http://" + daemonAddress + "/?FileAccessNotAllowed"
32 }
33 return "http://" + daemonAddress + "/file/" + u[7:]
34 }
35
36 offset := 0
37 if strings.HasPrefix(u, "gemini://") {
38 offset = 9
39 }
40 firstSlash := strings.IndexRune(u[offset:], '/')
41 if firstSlash != -1 {
42 u = strings.ToLower(u[:firstSlash+offset]) + u[firstSlash+offset:]
43 }
44
45 if strings.HasPrefix(u, "gemini://") {
46 return "http://" + daemonAddress + "/gemini/" + u[9:]
47 } else if strings.Contains(u, "://") {
48 return u
49 } else if loc != nil && len(u) > 0 && !strings.HasPrefix(u, "//") {
50 if u[0] != '/' {
51 if loc.Path[len(loc.Path)-1] == '/' {
52 u = path.Join("/", loc.Path, u)
53 } else {
54 u = path.Join("/", path.Dir(loc.Path), u)
55 }
56 }
57 return "http://" + daemonAddress + "/" + scheme + "/" + strings.ToLower(loc.Host) + u
58 }
59 return "http://" + daemonAddress + "/" + scheme + "/" + u
60 }
61 return u
62 }
63
64 func newPage() []byte {
65 data := []byte(pageHeader)
66 if daemonAddress != "" {
67 data = append(data, navHeader...)
68 }
69 return append(data, contentHeader...)
70 }
71
72
73 func Convert(page []byte, u string) []byte {
74 var result []byte
75
76 var preformatted bool
77
78 parsedURL, err := url.Parse(u)
79 if err != nil {
80 parsedURL = nil
81 err = nil
82 }
83
84 scanner := bufio.NewScanner(bytes.NewReader(page))
85 for scanner.Scan() {
86 line := scanner.Bytes()
87 l := len(line)
88 if l >= 3 && string(line[0:3]) == "```" {
89 preformatted = !preformatted
90 if preformatted {
91 result = append(result, []byte("<pre>\n")...)
92 } else {
93 result = append(result, []byte("</pre>\n")...)
94 }
95 continue
96 }
97
98 if preformatted {
99 result = append(result, html.EscapeString(string(line))...)
100 result = append(result, []byte("\n")...)
101 continue
102 }
103
104 if l >= 6 && bytes.HasPrefix(line, []byte("=>")) {
105 splitStart := 2
106 if line[splitStart] == ' ' || line[splitStart] == '\t' {
107 splitStart++
108 }
109
110 var split [][]byte
111 firstSpace := bytes.IndexRune(line[splitStart:], ' ')
112 firstTab := bytes.IndexRune(line[splitStart:], '\t')
113 if firstSpace != -1 && (firstTab == -1 || firstSpace < firstTab) {
114 split = bytes.SplitN(line[splitStart:], []byte(" "), 2)
115 } else if firstTab != -1 {
116 split = bytes.SplitN(line[splitStart:], []byte("\t"), 2)
117 }
118
119 var linkURL []byte
120 var linkLabel []byte
121 if len(split) == 2 {
122 linkURL = split[0]
123 linkLabel = split[1]
124 } else {
125 linkURL = line[splitStart:]
126 linkLabel = line[splitStart:]
127 }
128
129 link := append([]byte(`<a href="`), html.EscapeString(rewriteURL(string(linkURL), parsedURL))...)
130 link = append(link, []byte(`">`)...)
131 link = append(link, html.EscapeString(string(linkLabel))...)
132 link = append(link, []byte(`</a>`)...)
133 result = append(result, link...)
134 result = append(result, []byte("<br>")...)
135 continue
136 }
137
138 heading := 0
139 for i := 0; i < l; i++ {
140 if line[i] == '#' {
141 heading++
142 } else {
143 break
144 }
145 }
146 if heading > 0 {
147 result = append(result, []byte(fmt.Sprintf("<h%d>%s</h%d>", heading, html.EscapeString(string(line[heading:])), heading))...)
148 continue
149 }
150
151 result = append(result, html.EscapeString(string(line))...)
152 result = append(result, []byte("<br>")...)
153 }
154
155 if preformatted {
156 result = append(result, []byte("</pre>\n")...)
157 }
158
159 data := newPage()
160 data = append(data, result...)
161 data = append(data, []byte(pageFooter)...)
162 return fillTemplateVariables(data, u, false)
163 }
164
View as plain text