1616 */
1717'use strict' ;
1818
19+ const url = require ( 'url' ) ;
1920const validateColor = require ( './web-inspector' ) . Color . parse ;
2021
2122const ALLOWED_DISPLAY_VALUES = [
@@ -61,13 +62,6 @@ function parseString(raw, trim) {
6162 } ;
6263}
6364
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-
7165function parseColor ( raw ) {
7266 const color = parseString ( raw ) ;
7367
@@ -94,10 +88,69 @@ function parseShortName(jsonInput) {
9488 return parseString ( jsonInput . short_name , true ) ;
9589}
9690
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+ } ;
101154}
102155
103156function parseDisplay ( jsonInput ) {
@@ -130,9 +183,20 @@ function parseOrientation(jsonInput) {
130183 return orientation ;
131184}
132185
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+
136200 let type = parseString ( raw . type , true ) ;
137201
138202 let density = {
@@ -167,7 +231,7 @@ function parseIcon(raw) {
167231 } ;
168232}
169233
170- function parseIcons ( jsonInput ) {
234+ function parseIcons ( jsonInput , manifestUrl ) {
171235 const raw = jsonInput . icons ;
172236 let value ;
173237
@@ -187,8 +251,15 @@ function parseIcons(jsonInput) {
187251 } ;
188252 }
189253
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 ) ;
192263
193264 return {
194265 raw,
@@ -200,15 +271,27 @@ function parseIcons(jsonInput) {
200271function parseApplication ( raw ) {
201272 let platform = parseString ( raw . platform , true ) ;
202273 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+ }
205288
206289 return {
207290 raw,
208291 value : {
209292 platform,
210293 id,
211- url
294+ url : appUrl
212295 } ,
213296 debugString : undefined
214297 } ;
@@ -234,8 +317,12 @@ function parseRelatedApplications(jsonInput) {
234317 } ;
235318 }
236319
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 ) ;
239326
240327 return {
241328 raw,
@@ -273,7 +360,18 @@ function parseBackgroundColor(jsonInput) {
273360 return parseColor ( jsonInput . background_color ) ;
274361}
275362
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+
277375 let jsonInput ;
278376
279377 try {
@@ -290,10 +388,10 @@ function parse(string) {
290388 let manifest = {
291389 name : parseName ( jsonInput ) ,
292390 short_name : parseShortName ( jsonInput ) ,
293- start_url : parseStartUrl ( jsonInput ) ,
391+ start_url : parseStartUrl ( jsonInput , manifestUrl , documentUrl ) ,
294392 display : parseDisplay ( jsonInput ) ,
295393 orientation : parseOrientation ( jsonInput ) ,
296- icons : parseIcons ( jsonInput ) ,
394+ icons : parseIcons ( jsonInput , manifestUrl ) ,
297395 related_applications : parseRelatedApplications ( jsonInput ) ,
298396 prefer_related_applications : parsePreferRelatedApplications ( jsonInput ) ,
299397 theme_color : parseThemeColor ( jsonInput ) ,
0 commit comments