16
16
*/
17
17
'use strict' ;
18
18
19
+ const url = require ( 'url' ) ;
19
20
const validateColor = require ( './web-inspector' ) . Color . parse ;
20
21
21
22
const ALLOWED_DISPLAY_VALUES = [
@@ -61,13 +62,6 @@ function parseString(raw, trim) {
61
62
} ;
62
63
}
63
64
64
- function parseURL ( raw ) {
65
- // TODO: resolve url using baseURL
66
- // var baseURL = args.baseURL;
67
- // new URL(parseString(raw).value, baseURL);
68
- return parseString ( raw , true ) ;
69
- }
70
-
71
65
function parseColor ( raw ) {
72
66
const color = parseString ( raw ) ;
73
67
@@ -94,10 +88,69 @@ function parseShortName(jsonInput) {
94
88
return parseString ( jsonInput . short_name , true ) ;
95
89
}
96
90
97
- function parseStartUrl ( jsonInput ) {
98
- // TODO: parse url using manifest_url as a base (missing).
99
- // start_url must be same-origin as Document of the top-level browsing context.
100
- return parseURL ( jsonInput . start_url ) ;
91
+ /**
92
+ * Returns whether the urls are of the same origin. See https://html.spec.whatwg.org/#same-origin
93
+ * @param {string } url1
94
+ * @param {string } url2
95
+ * @return {boolean }
96
+ */
97
+ function checkSameOrigin ( url1 , url2 ) {
98
+ const parsed1 = url . parse ( url1 ) ;
99
+ const parsed2 = url . parse ( url2 ) ;
100
+
101
+ return parsed1 . protocol === parsed2 . protocol &&
102
+ parsed1 . hostname === parsed2 . hostname &&
103
+ parsed1 . port === parsed2 . port ;
104
+ }
105
+
106
+ /**
107
+ * https://w3c.github.io/manifest/#start_url-member
108
+ */
109
+ function parseStartUrl ( jsonInput , manifestUrl , documentUrl ) {
110
+ const raw = jsonInput . start_url ;
111
+
112
+ // 8.10(3) - discard the empty string and non-strings.
113
+ if ( raw === '' ) {
114
+ return {
115
+ raw,
116
+ value : documentUrl ,
117
+ debugString : 'ERROR: start_url string empty'
118
+ } ;
119
+ }
120
+ const parsedAsString = parseString ( raw ) ;
121
+ if ( ! parsedAsString . value ) {
122
+ parsedAsString . value = documentUrl ;
123
+ return parsedAsString ;
124
+ }
125
+
126
+ // 8.10(4) - construct URL with raw as input and manifestUrl as the base.
127
+ let startUrl ;
128
+ try {
129
+ // TODO(bckenny): need better URL constructor to do this properly. See
130
+ // https://github.com/GoogleChrome/lighthouse/issues/602
131
+ startUrl = url . resolve ( manifestUrl , raw ) ;
132
+ } catch ( e ) {
133
+ // 8.10(5) - discard invalid URLs.
134
+ return {
135
+ raw,
136
+ value : documentUrl ,
137
+ debugString : 'ERROR: invalid start_url relative to ${manifestUrl}'
138
+ } ;
139
+ }
140
+
141
+ // 8.10(6) - discard start_urls that are not same origin as documentUrl.
142
+ if ( ! checkSameOrigin ( startUrl , documentUrl ) ) {
143
+ return {
144
+ raw,
145
+ value : documentUrl ,
146
+ debugString : 'ERROR: start_url must be same-origin as document'
147
+ } ;
148
+ }
149
+
150
+ return {
151
+ raw,
152
+ value : startUrl
153
+ } ;
101
154
}
102
155
103
156
function parseDisplay ( jsonInput ) {
@@ -130,9 +183,20 @@ function parseOrientation(jsonInput) {
130
183
return orientation ;
131
184
}
132
185
133
- function parseIcon ( raw ) {
134
- // TODO: pass manifest url as base.
135
- let src = parseURL ( raw . src ) ;
186
+ function parseIcon ( raw , manifestUrl ) {
187
+ // 9.4(3)
188
+ const src = parseString ( raw . src , true ) ;
189
+ // 9.4(4) - discard if trimmed value is the empty string.
190
+ if ( src . value === '' ) {
191
+ src . value = undefined ;
192
+ }
193
+ if ( src . value ) {
194
+ // TODO(bckenny): need better URL constructor to do this properly. See
195
+ // https://github.com/GoogleChrome/lighthouse/issues/602
196
+ // 9.4(4) - construct URL with manifest URL as the base
197
+ src . value = url . resolve ( manifestUrl , src . value ) ;
198
+ }
199
+
136
200
let type = parseString ( raw . type , true ) ;
137
201
138
202
let density = {
@@ -167,7 +231,7 @@ function parseIcon(raw) {
167
231
} ;
168
232
}
169
233
170
- function parseIcons ( jsonInput ) {
234
+ function parseIcons ( jsonInput , manifestUrl ) {
171
235
const raw = jsonInput . icons ;
172
236
let value ;
173
237
@@ -187,8 +251,15 @@ function parseIcons(jsonInput) {
187
251
} ;
188
252
}
189
253
190
- // TODO(bckenny): spec says to skip icons missing `src`. Warn instead?
191
- value = raw . filter ( icon => ! ! icon . src ) . map ( parseIcon ) ;
254
+ // TODO(bckenny): spec says to skip icons missing `src`, so debug messages on
255
+ // individual icons are lost. Warn instead?
256
+ value = raw
257
+ // 9.6(3)(1)
258
+ . filter ( icon => icon . src !== undefined )
259
+ // 9.6(3)(2)(1)
260
+ . map ( icon => parseIcon ( icon , manifestUrl ) )
261
+ // 9.6(3)(2)(2)
262
+ . filter ( parsedIcon => parsedIcon . value . src . value !== undefined ) ;
192
263
193
264
return {
194
265
raw,
@@ -200,15 +271,27 @@ function parseIcons(jsonInput) {
200
271
function parseApplication ( raw ) {
201
272
let platform = parseString ( raw . platform , true ) ;
202
273
let id = parseString ( raw . id , true ) ;
203
- // TODO: pass manfiest url as base.
204
- let url = parseURL ( raw . url ) ;
274
+
275
+ // 10.2.(2) and 10.2.(3)
276
+ const appUrl = parseString ( raw . url , true ) ;
277
+ if ( appUrl . value ) {
278
+ try {
279
+ // TODO(bckenny): need better URL constructor to do this properly. See
280
+ // https://github.com/GoogleChrome/lighthouse/issues/602
281
+ // 10.2.(4) - attempt to construct URL.
282
+ appUrl . value = url . parse ( appUrl . value ) . href ;
283
+ } catch ( e ) {
284
+ appUrl . value = undefined ;
285
+ appUrl . debugString = 'ERROR: invalid application URL ${raw.url}' ;
286
+ }
287
+ }
205
288
206
289
return {
207
290
raw,
208
291
value : {
209
292
platform,
210
293
id,
211
- url
294
+ url : appUrl
212
295
} ,
213
296
debugString : undefined
214
297
} ;
@@ -234,8 +317,12 @@ function parseRelatedApplications(jsonInput) {
234
317
} ;
235
318
}
236
319
237
- // TODO(bckenny): spec says to skip apps missing `platform`. Warn instead?
238
- value = raw . filter ( application => ! ! application . platform ) . map ( parseApplication ) ;
320
+ // TODO(bckenny): spec says to skip apps missing `platform`, so debug messages
321
+ // on individual apps are lost. Warn instead?
322
+ value = raw
323
+ . filter ( application => ! ! application . platform )
324
+ . map ( parseApplication )
325
+ . filter ( parsedApp => ! ! parsedApp . value . id . value || ! ! parsedApp . value . url . value ) ;
239
326
240
327
return {
241
328
raw,
@@ -273,7 +360,18 @@ function parseBackgroundColor(jsonInput) {
273
360
return parseColor ( jsonInput . background_color ) ;
274
361
}
275
362
276
- function parse ( string ) {
363
+ /**
364
+ * Parse a manifest from the given inputs.
365
+ * @param {string } string Manifest JSON string.
366
+ * @param {string } manifestUrl URL of manifest file.
367
+ * @param {string } documentUrl URL of document containing manifest link element.
368
+ * @return {!ManifestNode<(!Manifest|undefined)> }
369
+ */
370
+ function parse ( string , manifestUrl , documentUrl ) {
371
+ if ( manifestUrl === undefined || documentUrl === undefined ) {
372
+ throw new Error ( 'Manifest and document URLs required for manifest parsing.' ) ;
373
+ }
374
+
277
375
let jsonInput ;
278
376
279
377
try {
@@ -290,10 +388,10 @@ function parse(string) {
290
388
let manifest = {
291
389
name : parseName ( jsonInput ) ,
292
390
short_name : parseShortName ( jsonInput ) ,
293
- start_url : parseStartUrl ( jsonInput ) ,
391
+ start_url : parseStartUrl ( jsonInput , manifestUrl , documentUrl ) ,
294
392
display : parseDisplay ( jsonInput ) ,
295
393
orientation : parseOrientation ( jsonInput ) ,
296
- icons : parseIcons ( jsonInput ) ,
394
+ icons : parseIcons ( jsonInput , manifestUrl ) ,
297
395
related_applications : parseRelatedApplications ( jsonInput ) ,
298
396
prefer_related_applications : parsePreferRelatedApplications ( jsonInput ) ,
299
397
theme_color : parseThemeColor ( jsonInput ) ,
0 commit comments