-
Notifications
You must be signed in to change notification settings - Fork 411
/
Copy pathcapabilities.go
283 lines (257 loc) · 10.2 KB
/
capabilities.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
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
// Package chrome provides Chrome-specific options for WebDriver.
package chrome
import (
"bufio"
"bytes"
"crypto"
"crypto/rand"
"crypto/rsa"
"crypto/sha256"
"crypto/x509"
"encoding/base64"
"encoding/binary"
"io"
"os"
"github.com/golang/protobuf/proto"
"github.com/mediabuyerbot/go-crx3/pb"
"github.com/tebeka/selenium/internal/zip"
)
// CapabilitiesKey is the key in the top-level Capabilities map under which
// ChromeDriver expects the Chrome-specific options to be set.
const CapabilitiesKey = "goog:chromeOptions"
// DeprecatedCapabilitiesKey is the legacy version of CapabilitiesKey.
const DeprecatedCapabilitiesKey = "chromeOptions"
// Capabilities defines the Chrome-specific desired capabilities when using
// ChromeDriver. An instance of this struct can be stored in the Capabilities
// map with a key of CapabilitiesKey ("goog:chromeOptions"). See
// https://sites.google.com/a/chromium.org/chromedriver/capabilities
type Capabilities struct {
// Path is the file path to the Chrome binary to use.
Path string `json:"binary,omitempty"`
// Args are the command-line arguments to pass to the Chrome binary, in
// addition to the ChromeDriver-supplied ones.
Args []string `json:"args,omitempty"`
// ExcludeSwitches are the command line flags that should be removed from
// the ChromeDriver-supplied default flags. The strings included here should
// not include a preceding '--'.
ExcludeSwitches []string `json:"excludeSwitches,omitempty"`
// Extensions are the list of extensions to install at startup. The
// elements of this list should be the base-64, padded contents of a Chrome
// extension file (.crx). Use the AddExtension method to add a local file.
Extensions []string `json:"extensions,omitempty"`
// LocalState are key/value pairs that are applied to the Local State file
// in the user data folder.
LocalState map[string]interface{} `json:"localState,omitempty"`
// Prefs are the key/value pairs that are applied to the preferences of the
// user profile in use.
Prefs map[string]interface{} `json:"prefs,omitempty"`
// Detatch, if true, will cause the browser to not be killed when
// ChromeDriver quits if the session was not terminated.
Detach *bool `json:"detach,omitempty"`
// DebuggerAddr is the TCP/IP address of a Chrome debugger server to connect
// to.
DebuggerAddr string `json:"debuggerAddress,omitempty"`
// MinidumpPath specifies the directory in which to store Chrome minidumps.
// (This is only available on Linux).
MinidumpPath string `json:"minidumpPath,omitempty"`
// MobileEmulation provides options for mobile emulation.
MobileEmulation *MobileEmulation `json:"mobileEmulation,omitempty"`
// PerfLoggingPrefs specifies options for performance logging.
PerfLoggingPrefs *PerfLoggingPreferences `json:"perfLoggingPrefs,omitempty"`
// WindowTypes is a list of window types that will appear in the list of
// window handles. For access to <webview> elements, include "webview" in
// this list.
WindowTypes []string `json:"windowTypes,omitempty"`
// Android Chrome WebDriver path "com.android.chrome"
AndroidPackage string `json:"androidPackage,omitempty"`
// Use W3C mode, if true.
W3C bool `json:"w3c"`
}
// TODO(minusnine): https://bugs.chromium.org/p/chromedriver/issues/detail?id=1625
// mentions "experimental options". Implement that.
// MobileEmulation provides options for mobile emulation. Only
// DeviceName or both of DeviceMetrics and UserAgent may be set at once.
type MobileEmulation struct {
// DeviceName is the name of the device to emulate, e.g. "Google Nexus 5".
// It should not be set if DeviceMetrics and UserAgent are set.
DeviceName string `json:"deviceName,omitempty"`
// DeviceMetrics provides specifications of an device to emulate. It should
// not be set if DeviceName is set.
DeviceMetrics *DeviceMetrics `json:"deviceMetrics,omitempty"`
// UserAgent specifies the user agent string to send to the remote web
// server.
UserAgent string `json:"userAgent,omitempty"`
}
// DeviceMetrics specifies device attributes for emulation.
type DeviceMetrics struct {
// Width is the width of the screen.
Width uint `json:"width"`
// Height is the height of the screen.
Height uint `json:"height"`
// PixelRatio is the pixel ratio of the screen.
PixelRatio float64 `json:"pixelRatio"`
// Touch indicates whether to emulate touch events. The default is true, if
// unset.
Touch *bool `json:"touch,omitempty"`
}
// PerfLoggingPreferences specifies configuration options for performance
// logging.
type PerfLoggingPreferences struct {
// EnableNetwork specifies whether of not to collect events from the Network
// domain. The default is true.
EnableNetwork *bool `json:"enableNetwork,omitempty"`
// EnablePage specifies whether or not to collect events from the Page
// domain. The default is true.
EnablePage *bool `json:"enablePage,omitempty"`
// EnableTimeline specifies whether or not to collect events from the
// Timeline domain. When tracing is enabled, Timeline domain is implicitly
// disabled, unless enableTimeline is explicitly set to true.
EnableTimeline *bool `json:"enableTimeline,omitempty"`
// TraceCategories is a comma-separated string of Chrome tracing categories
// for which trace events should be collected. An unspecified or empty string
// disables tracing.
TraceCategories string `json:"traceCategories,omitempty"`
// BufferUsageReportingIntervalMillis is the requested number of milliseconds
// between DevTools trace buffer usage events. For example, if 1000, then
// once per second, DevTools will report how full the trace buffer is. If a
// report indicates the buffer usage is 100%, a warning will be issued.
BufferUsageReportingIntervalMillis uint `json:"bufferUsageReportingInterval,omitempty"`
}
// AddExtension adds an extension for the browser to load at startup. The path
// parameter should be a path to an extension file (which typically has a
// `.crx` file extension. Note that the contents of the file will be loaded
// into memory, as required by the protocol.
func (c *Capabilities) AddExtension(path string) error {
f, err := os.Open(path)
if err != nil {
return err
}
defer f.Close()
return c.addExtension(f)
}
// addExtension reads a Chrome extension's data from r, base64-encodes it, and
// attaches it to the Capabilities instance.
func (c *Capabilities) addExtension(r io.Reader) error {
var buf bytes.Buffer
encoder := base64.NewEncoder(base64.StdEncoding, &buf)
if _, err := io.Copy(encoder, bufio.NewReader(r)); err != nil {
return err
}
encoder.Close()
c.Extensions = append(c.Extensions, buf.String())
return nil
}
// AddUnpackedExtension creates a packaged Chrome extension with the files
// below the provided directory path and causes the browser to load that
// extension at startup.
func (c *Capabilities) AddUnpackedExtension(basePath string) error {
buf, _, err := NewExtension(basePath)
if err != nil {
return err
}
return c.addExtension(bytes.NewBuffer(buf))
}
// NewExtension creates the payload of a Chrome extension file which is signed
// using the returned private key.
func NewExtension(basePath string) ([]byte, *rsa.PrivateKey, error) {
key, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
return nil, nil, err
}
data, err := NewExtensionWithKey(basePath, key)
if err != nil {
return nil, nil, err
}
return data, key, nil
}
// NewExtensionWithKey creates the payload of a Chrome extension file which is
// signed by the provided private key.
func NewExtensionWithKey(basePath string, key *rsa.PrivateKey) ([]byte, error) {
archiveBuf, err := zip.New(basePath)
if err != nil {
return nil, err
}
header, err := crx3Header(archiveBuf.Bytes(), key)
if err != nil {
return nil, err
}
// This format is documented at https://developer.chrome.com/extensions/crx .
buf := new(bytes.Buffer)
if _, err := buf.Write([]byte("Cr24")); err != nil { // Magic number.
return nil, err
}
// Version.
if err := binary.Write(buf, binary.LittleEndian, uint32(3)); err != nil {
return nil, err
}
// header length.
if err := binary.Write(buf, binary.LittleEndian, uint32(len(header))); err != nil {
return nil, err
}
// header payload.
if err := binary.Write(buf, binary.LittleEndian, header); err != nil {
return nil, err
}
// Zipped extension directory payload.
if err := binary.Write(buf, binary.LittleEndian, archiveBuf.Bytes()); err != nil {
return nil, err
}
return buf.Bytes(), nil
}
func crx3Header(archiveData []byte, key *rsa.PrivateKey) ([]byte, error) {
// Public Key
pubKey, err := x509.MarshalPKIXPublicKey(key.Public())
if err != nil {
return nil, err
}
// Signed Header
// From chromium / crx3.proto:
//
// In the common case of a developer key proof, the first 128 bits of
// the SHA-256 hash of the public key must equal the crx_id.
hash := sha256.New()
hash.Write(pubKey)
sdpb := &pb.SignedData{
CrxId: hash.Sum(nil)[0:16],
}
signedHeaderData, err := proto.Marshal(sdpb)
if err != nil {
return nil, err
}
// Signature
signature, err := crx3Signature(archiveData, signedHeaderData, key)
if err != nil {
return nil, err
}
header := &pb.CrxFileHeader{
Sha256WithRsa: []*pb.AsymmetricKeyProof{
&pb.AsymmetricKeyProof{
PublicKey: pubKey,
Signature: signature,
},
},
SignedHeaderData: signedHeaderData,
}
return proto.Marshal(header)
}
func crx3Signature(archiveData, signedHeaderData []byte, key *rsa.PrivateKey) ([]byte, error) {
// From chromium / crx3.proto:
//
// All proofs in this CrxFile message are on the value
// "CRX3 SignedData\x00" + signed_header_size + signed_header_data +
// archive, where "\x00" indicates an octet with value 0, "CRX3 SignedData"
// is encoded using UTF-8, signed_header_size is the size in octets of the
// contents of this field and is encoded using 4 octets in little-endian
// order, signed_header_data is exactly the content of this field, and
// archive is the remaining contents of the file following the header.
sign := sha256.New()
sign.Write([]byte("CRX3 SignedData\x00"))
if err := binary.Write(sign, binary.LittleEndian, uint32(len(signedHeaderData))); err != nil {
return nil, err
}
sign.Write(signedHeaderData)
if _, err := io.Copy(sign, bytes.NewReader(archiveData)); err != nil {
return nil, err
}
return rsa.SignPKCS1v15(rand.Reader, key, crypto.SHA256, sign.Sum(nil))
}