@@ -10,6 +10,7 @@ import {
1010import { HttpExchange , ExchangeMessage } from '../../types' ;
1111import { lastHeader , asHeaderArray } from '../../util/headers' ;
1212import { joinAnd } from '../../util/text' ;
13+ import { escapeForMarkdownEmbedding } from '../ui/markdown' ;
1314
1415// https://tools.ietf.org/html/draft-ietf-httpbis-semantics-04#section-7.2.3
1516const CACHEABLE_METHODS = [ 'GET' , 'HEAD' , 'POST' ] ;
@@ -53,7 +54,13 @@ const CORS_SIMPLE_HEADERS = [
5354
5455function formatHeader ( headerName : string ) : string {
5556 // Upper case the first letter of each word (including hyphenated parts)
56- return headerName . toLowerCase ( ) . replace ( / ( \b \w ) / g, v => v . toUpperCase ( ) )
57+ return headerName . toLowerCase ( ) . replace ( / ( \b \w ) / g, v => v . toUpperCase ( ) ) ;
58+ }
59+
60+ function escapeAndFormatHeader ( headerName : string ) : string {
61+ return `<code>${ escapeForMarkdownEmbedding (
62+ formatHeader ( headerName )
63+ ) } </code>`;
5764}
5865
5966const THE_DAWN_OF_TIME = parseDate ( 0 ) ;
@@ -126,9 +133,10 @@ export function explainCacheability(exchange: HttpExchange): (
126133 mechanisms, but the CORS result itself can be cached if a
127134 Access-Control-Max-Age header is provided.
128135
129- In this case that header is set to ${ maxAgeHeader } , explicitly
130- requesting that this result should not be cached, and that clients
131- should not reuse this CORS response in future.
136+ In this case that header is set to ${ escapeForMarkdownEmbedding (
137+ maxAgeHeader !
138+ ) } , explicitly requesting that this result should not be cached,
139+ and that clients should not reuse this CORS response in future.
132140 `
133141 } ;
134142 }
@@ -146,7 +154,9 @@ export function explainCacheability(exchange: HttpExchange): (
146154 return {
147155 cacheable : false ,
148156 summary : 'Not cacheable' ,
149- explanation : `${ request . method } requests are never cacheable.`
157+ explanation : `${ escapeForMarkdownEmbedding (
158+ request . method
159+ ) } requests are never cacheable.`
150160 } ;
151161 }
152162 }
@@ -550,7 +560,7 @@ export function explainCacheMatching(exchange: HttpExchange): Explanation | unde
550560 const allowedHeaders = _ . union (
551561 CORS_SIMPLE_HEADERS ,
552562 asHeaderArray ( response . headers [ 'access-control-allow-headers' ] )
553- . map ( formatHeader )
563+ . map ( escapeAndFormatHeader )
554564 ) ;
555565 const allowsCredentials = response . headers [ 'Access-Control-Allow-Credentials' ] === 'true' ;
556566
@@ -561,37 +571,44 @@ export function explainCacheMatching(exchange: HttpExchange): Explanation | unde
561571 request for future CORS requests, when:
562572
563573 * The CORS request would be sent to the same URL
564- * The origin is \`${ request . headers [ 'origin' ] } \`
574+ * The origin is <code>${ escapeForMarkdownEmbedding (
575+ request . headers [ 'origin' ] . toString ( )
576+ ) } </code>
565577 ${ ! allowsCredentials ? '* No credentials are being sent\n' : ''
566- } * The request method would be ${ joinAnd ( allowedMethods , ', ' , ' or ' ) }
578+ } * The request method would be ${ escapeForMarkdownEmbedding (
579+ joinAnd ( allowedMethods , ', ' , ' or ' )
580+ ) }
567581 * There are no extra request headers other than ${ joinAnd ( allowedHeaders ) }
568582 `
569583 } ;
570584 }
571585
572- const varyHeaders = asHeaderArray ( response . headers [ 'vary' ] ) . map ( formatHeader ) ;
573-
574- const hasVaryHeaders = varyHeaders . length > 0 ;
586+ const rawVaryHeaders = asHeaderArray ( response . headers [ 'vary' ] ) ;
587+ const hasVaryHeaders = rawVaryHeaders . length > 0 ;
575588
576589 // Don't need to handle Vary: *, as that would've excluded cacheability entirely.
577590
578591 const varySummary = hasVaryHeaders ?
579592 ` that have the same ${
580- joinAnd ( varyHeaders )
581- } header${ varyHeaders . length > 1 ? 's' : '' } `
593+ joinAnd ( rawVaryHeaders . map ( h => `' ${ formatHeader ( h ) } '` ) ) // No escaping - summary (non-markdown) only
594+ } header${ rawVaryHeaders . length > 1 ? 's' : '' } `
582595 : '' ;
583596
584597 const varyExplanation = hasVaryHeaders ? dedent `
585- , as long as those requests have ${ joinAnd ( varyHeaders . map ( headerName => {
598+ , as long as those requests have ${ joinAnd ( rawVaryHeaders . map ( headerName => {
586599 const realHeaderValue = request . headers [ headerName . toLowerCase ( ) ] ;
587600
601+ const formattedName = escapeAndFormatHeader ( headerName ) ;
602+
588603 return realHeaderValue === undefined ?
589- `no ${ headerName } header` :
590- `a ${ headerName } header set to \`${ realHeaderValue } \``
604+ `no ${ formattedName } header` :
605+ `a ${ formattedName } header set to <code>${ escapeForMarkdownEmbedding (
606+ realHeaderValue . toString ( )
607+ ) } </code>`
591608 } ) ) } .
592609
593- ${ varyHeaders . length > 1 ? 'These headers are' : 'This header is' }
594- required because ${ varyHeaders . length > 1 ? "they're" : "it's" } listed in
610+ ${ rawVaryHeaders . length > 1 ? 'These headers are' : 'This header is' }
611+ required because ${ rawVaryHeaders . length > 1 ? "they're" : "it's" } listed in
595612 the Vary header of the response.
596613 ` : dedent `
597614 , regardless of header values or other factors.
@@ -760,7 +777,9 @@ export function explainCacheLifetime(exchange: HttpExchange): Explanation | unde
760777 some percentage of the time since the content was last modified, often using
761778 the Last-Modified header value${
762779 response . headers [ 'last-modified' ] ?
763- ` (${ response . headers [ 'last-modified' ] } )`
780+ ` (<code>${ escapeForMarkdownEmbedding (
781+ response . headers [ 'last-modified' ] . toString ( )
782+ ) } </code>)`
764783 : ', although that is not explicitly defined in this response either'
765784 }
766785 ` :
@@ -770,11 +789,11 @@ export function explainCacheLifetime(exchange: HttpExchange): Explanation | unde
770789 a \`max-age\` directive set to ${ maxAge } seconds
771790 ` :
772791 dateHeader ? dedent `
773- an Expires header set to ${ expiresHeader } , which is
774- not after its Date header value (${ dateHeader } )
792+ an Expires header set to ${ escapeForMarkdownEmbedding ( expiresHeader ! . toString ( ) ) } , which is
793+ not after its Date header value (${ escapeForMarkdownEmbedding ( dateHeader ) } )
775794 `
776795 : dedent `
777- an Expires header set to ${ expiresHeader } , which is
796+ an Expires header set to ${ escapeForMarkdownEmbedding ( expiresHeader ! ) } , which is
778797 before the response was received
779798 `
780799 } ${ explainSharedCacheLifetime }
@@ -784,7 +803,7 @@ export function explainCacheLifetime(exchange: HttpExchange): Explanation | unde
784803 as specified by its \`max-age\` directive${ explainSharedCacheLifetime }
785804 `
786805 : dedent `
787- This response expires at ${ expiresHeader } (after ${ formatDuration ( lifetime ! ) } ),
806+ This response expires at ${ escapeForMarkdownEmbedding ( expiresHeader ! ) } (after ${ formatDuration ( lifetime ! ) } ),
788807 as specified by its Expires header${ explainSharedCacheLifetime }
789808 ` ;
790809
0 commit comments