This repository has been archived by the owner on Oct 10, 2018. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 1
/
webkit.go
228 lines (210 loc) · 6.21 KB
/
webkit.go
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
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
package golem
// #cgo pkg-config: webkit2gtk-4.0
// #include <webkit2/webkit2.h>
// #include <stdlib.h>
import "C"
import (
"errors"
"fmt"
"io/ioutil"
"os"
"path"
"path/filepath"
"regexp"
"strings"
"unsafe"
"github.com/conformal/gotk3/glib"
"github.com/tkerber/golem/webkit"
)
var urlMatchRegex = regexp.MustCompile(`(http://|https://|file:///).*`)
// webkitInit initializes webkit for golem's use.
func (g *Golem) webkitInit() {
extenDir, err := ioutil.TempDir("", "golem-web-exten")
if err != nil {
panic("Failed to create temporary directory.")
}
g.extenDir = extenDir
extenData, err := Asset("libgolem.so")
if err != nil {
panic("Failed to access web extension embedded data.")
}
extenPath := filepath.Join(extenDir, "libgolem.so")
err = ioutil.WriteFile(extenPath, extenData, 0700)
if err != nil {
panic("Failed to write web extension to temporary directory.")
}
c := webkit.GetDefaultWebContext()
c.SetWebExtensionsDirectory(extenDir)
c.SetFaviconDatabaseDirectory("")
// Set the profile string to be passed to the web extensions.
cProfile := C.CString(g.profile)
defer C.free(unsafe.Pointer(cProfile))
profileVariant := C.g_variant_new_string((*C.gchar)(cProfile))
C.webkit_web_context_set_web_extensions_initialization_user_data(
(*C.WebKitWebContext)(unsafe.Pointer(c.Native())),
profileVariant)
// NOTE: removing this will cause bugs in golems web extension.
// Tread lightly.
c.SetProcessModel(webkit.ProcessModelMultipleSecondaryProcesses)
c.SetCacheModel(webkit.CacheModelWebBrowser)
c.SetDiskCacheDirectory(g.files.cacheDir)
c.GetCookieManager().SetPersistentStorage(
g.files.cookies,
webkit.CookiePersistentStorageText)
c.Connect("download-started", func(_ *glib.Object, d *webkit.Download) {
// If this is a silent download, we drop it. (And remove it from the
// list of silent downloads, as it won't be needed again).
if g.silentDownloads[d.Native()] {
g.wMutex.Lock()
defer g.wMutex.Unlock()
delete(g.silentDownloads, d.Native())
return
}
// Find the window. If no web view is associated with the download,
// we attach to download to *all* windows.
wv, _ := d.GetWebView()
wins := make([]*Window, 0, len(g.windows))
outer:
for _, w := range g.windows {
if wv == nil {
wins = append(wins, w)
} else {
for _, wv2 := range w.webViews {
if wv.Native() == wv2.Native() {
wins = append(wins, w)
break outer
}
}
}
}
for _, win := range wins {
win.addDownload(d)
}
g.addDownload(d)
dlDir := g.files.downloadDir
d.Connect("decide-destination",
func(d *webkit.Download, suggestedName string) bool {
// Check if the file with the suggested name exists in dlDir
path := filepath.Join(dlDir, suggestedName)
_, err := os.Stat(path)
exists := !os.IsNotExist(err)
for i := 1; exists; i++ {
path = filepath.Join(
dlDir,
fmt.Sprintf("%d_%s", i, suggestedName))
_, err := os.Stat(path)
exists = !os.IsNotExist(err)
}
d.SetDestination(fmt.Sprintf("file://%s", path))
return false
})
})
c.RegisterURIScheme("golem-unsafe", g.golemUnsafeSchemeHandler)
c.RegisterURIScheme("golem", golemSchemeHandler)
c.GetSecurityManager().RegisterURISchemeAsLocal("golem")
}
// WebkitCleanup removes the temporary webkit extension directory.
func (g *Golem) WebkitCleanup() {
os.RemoveAll(g.extenDir)
}
// golemSchemeHandler handles request to the 'golem:' scheme.
func golemSchemeHandler(req *webkit.URISchemeRequest) {
req.FinishError(errors.New("Invalid request"))
}
// golemUnsafeSchemeHandler handles request to the 'golem-unsafe:' scheme.
func (g *Golem) golemUnsafeSchemeHandler(req *webkit.URISchemeRequest) {
rPath := strings.TrimPrefix(req.GetURI(), "golem-unsafe://")
// If we have a ? or # suffix, we discard it.
splitPath := strings.SplitN(rPath, "#", 2)
rPath = splitPath[0]
splitPath = strings.SplitN(rPath, "?", 2)
rPath = splitPath[0]
data, err := Asset(path.Join("srv", rPath))
if err == nil {
mime := guessMimeFromExtension(rPath)
req.Finish(data, mime)
} else {
switch {
case strings.HasPrefix(rPath, "pdf.js/loop/"):
g.handleLoopRequest(req)
default:
// TODO finish w/ error
req.FinishError(errors.New("Invalid request"))
}
}
}
// handleLoopRequest handles a request to a loop/ pseudo-file
//
// Any request to a golem protocol containing the path /loop/ (which is not an
// existing asset) will be treated as a loop request, with the URI after the
// loop/ part.
// E.g. golem-unsafe:///pdf.js/loop/http://example.com/example-pdf.pdf
func (g *Golem) handleLoopRequest(req *webkit.URISchemeRequest) {
// We loop a page request from another scheme into the golem scheme
// Ever-so-slightly dangerous.
splitPath := strings.SplitN(req.GetURI(), "/loop/", 2)
if len(splitPath) == 1 {
req.Finish([]byte{}, "text/plain")
return
}
uri := splitPath[1]
if !urlMatchRegex.MatchString(uri) {
req.FinishError(fmt.Errorf("Invalid url: %v", uri))
return
}
tmpDir, err := ioutil.TempDir("", "golem-loop-")
if err != nil {
req.FinishError(err)
return
}
dlFile, err := filepath.Abs(filepath.Join(tmpDir, "loopdl"))
if err != nil {
req.FinishError(err)
os.RemoveAll(tmpDir)
return
}
dwnld := webkit.GetDefaultWebContext().DownloadURI(uri)
dwnld.SetDestination("file://" + dlFile)
g.wMutex.Lock()
g.silentDownloads[dwnld.Native()] = true
g.wMutex.Unlock()
var handle glib.SignalHandle
handle, err = dwnld.Connect("finished", func() {
defer os.RemoveAll(tmpDir)
dwnld.HandlerDisconnect(handle)
data, err := ioutil.ReadFile(dlFile)
if err != nil {
req.FinishError(err)
return
}
var mimetype string
resp, err := dwnld.GetResponse()
if err != nil {
mimetype = "application/octet-stream"
} else {
mimetype = resp.GetMimeType()
}
req.Finish(data, mimetype)
})
if err != nil {
req.FinishError(err)
os.RemoveAll(tmpDir)
return
}
}
// guessMimeFromExtension guesses a files mime type based on its file path.
func guessMimeFromExtension(path string) string {
split := strings.Split(path, ".")
// No extension to speak of, default to text.
if len(split) == 1 {
return "text/plain"
}
switch split[len(split)-1] {
case "html":
return "text/html"
case "css":
return "text/css"
default:
return "application/octet-stream"
}
}