@@ -10,6 +10,7 @@ import {
10
10
import { HttpExchange , ExchangeMessage } from '../../types' ;
11
11
import { lastHeader , asHeaderArray } from '../../util/headers' ;
12
12
import { joinAnd } from '../../util/text' ;
13
+ import { escapeForMarkdownEmbedding } from '../ui/markdown' ;
13
14
14
15
// https://tools.ietf.org/html/draft-ietf-httpbis-semantics-04#section-7.2.3
15
16
const CACHEABLE_METHODS = [ 'GET' , 'HEAD' , 'POST' ] ;
@@ -53,7 +54,13 @@ const CORS_SIMPLE_HEADERS = [
53
54
54
55
function formatHeader ( headerName : string ) : string {
55
56
// 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>`;
57
64
}
58
65
59
66
const THE_DAWN_OF_TIME = parseDate ( 0 ) ;
@@ -126,9 +133,10 @@ export function explainCacheability(exchange: HttpExchange): (
126
133
mechanisms, but the CORS result itself can be cached if a
127
134
Access-Control-Max-Age header is provided.
128
135
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.
132
140
`
133
141
} ;
134
142
}
@@ -146,7 +154,9 @@ export function explainCacheability(exchange: HttpExchange): (
146
154
return {
147
155
cacheable : false ,
148
156
summary : 'Not cacheable' ,
149
- explanation : `${ request . method } requests are never cacheable.`
157
+ explanation : `${ escapeForMarkdownEmbedding (
158
+ request . method
159
+ ) } requests are never cacheable.`
150
160
} ;
151
161
}
152
162
}
@@ -550,7 +560,7 @@ export function explainCacheMatching(exchange: HttpExchange): Explanation | unde
550
560
const allowedHeaders = _ . union (
551
561
CORS_SIMPLE_HEADERS ,
552
562
asHeaderArray ( response . headers [ 'access-control-allow-headers' ] )
553
- . map ( formatHeader )
563
+ . map ( escapeAndFormatHeader )
554
564
) ;
555
565
const allowsCredentials = response . headers [ 'Access-Control-Allow-Credentials' ] === 'true' ;
556
566
@@ -561,37 +571,44 @@ export function explainCacheMatching(exchange: HttpExchange): Explanation | unde
561
571
request for future CORS requests, when:
562
572
563
573
* 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>
565
577
${ ! 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
+ ) }
567
581
* There are no extra request headers other than ${ joinAnd ( allowedHeaders ) }
568
582
`
569
583
} ;
570
584
}
571
585
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 ;
575
588
576
589
// Don't need to handle Vary: *, as that would've excluded cacheability entirely.
577
590
578
591
const varySummary = hasVaryHeaders ?
579
592
` 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' : '' } `
582
595
: '' ;
583
596
584
597
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 => {
586
599
const realHeaderValue = request . headers [ headerName . toLowerCase ( ) ] ;
587
600
601
+ const formattedName = escapeAndFormatHeader ( headerName ) ;
602
+
588
603
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>`
591
608
} ) ) } .
592
609
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
595
612
the Vary header of the response.
596
613
` : dedent `
597
614
, regardless of header values or other factors.
@@ -760,7 +777,9 @@ export function explainCacheLifetime(exchange: HttpExchange): Explanation | unde
760
777
some percentage of the time since the content was last modified, often using
761
778
the Last-Modified header value${
762
779
response . headers [ 'last-modified' ] ?
763
- ` (${ response . headers [ 'last-modified' ] } )`
780
+ ` (<code>${ escapeForMarkdownEmbedding (
781
+ response . headers [ 'last-modified' ] . toString ( )
782
+ ) } </code>)`
764
783
: ', although that is not explicitly defined in this response either'
765
784
}
766
785
` :
@@ -770,11 +789,11 @@ export function explainCacheLifetime(exchange: HttpExchange): Explanation | unde
770
789
a \`max-age\` directive set to ${ maxAge } seconds
771
790
` :
772
791
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 ) } )
775
794
`
776
795
: dedent `
777
- an Expires header set to ${ expiresHeader } , which is
796
+ an Expires header set to ${ escapeForMarkdownEmbedding ( expiresHeader ! ) } , which is
778
797
before the response was received
779
798
`
780
799
} ${ explainSharedCacheLifetime }
@@ -784,7 +803,7 @@ export function explainCacheLifetime(exchange: HttpExchange): Explanation | unde
784
803
as specified by its \`max-age\` directive${ explainSharedCacheLifetime }
785
804
`
786
805
: dedent `
787
- This response expires at ${ expiresHeader } (after ${ formatDuration ( lifetime ! ) } ),
806
+ This response expires at ${ escapeForMarkdownEmbedding ( expiresHeader ! ) } (after ${ formatDuration ( lifetime ! ) } ),
788
807
as specified by its Expires header${ explainSharedCacheLifetime }
789
808
` ;
790
809
0 commit comments