@@ -79,9 +79,8 @@ func Content(input string) string {
7979 // Adversarially nested constructs can reveal one hidden construct per pass.
8080 // Bound the work, then make every remaining Markdown control inert outside
8181 // code and remove all fence metadata.
82- filtered = strings .ReplaceAll (filtered , "&" , "&" )
8382 filtered = stripAllFenceMetadata (filtered )
84- return escapeMarkdownSyntaxOutsideCode (filtered )
83+ return escapeContentOutsideCode (filtered )
8584}
8685
8786const maxContentFilterPasses = 4
@@ -429,16 +428,30 @@ func githubMathDelimiters(input string, codeMask, urlMask []bool) []int {
429428func markdownURLMask (input string ) []bool {
430429 mask := make ([]bool , len (input ))
431430 for start := 0 ; start < len (input ); {
432- for start < len (input ) && unicode .IsSpace (rune (input [start ])) {
433- start ++
431+ for start < len (input ) {
432+ r , size := utf8 .DecodeRuneInString (input [start :])
433+ if ! unicode .IsSpace (r ) {
434+ break
435+ }
436+ start += size
434437 }
435438 stop := start
436- for stop < len (input ) && ! unicode .IsSpace (rune (input [stop ])) {
437- stop ++
439+ for stop < len (input ) {
440+ r , size := utf8 .DecodeRuneInString (input [stop :])
441+ if unicode .IsSpace (r ) {
442+ break
443+ }
444+ stop += size
438445 }
439446 token := input [start :stop ]
440447 if urlStart := urlStart (token ); urlStart >= 0 {
441- for offset := start + urlStart ; offset < stop ; offset ++ {
448+ urlStop := len (token )
449+ if urlStart > 0 && token [urlStart - 1 ] == '<' {
450+ if closeOffset := strings .IndexByte (token [urlStart :], '>' ); closeOffset >= 0 {
451+ urlStop = urlStart + closeOffset
452+ }
453+ }
454+ for offset := start + urlStart ; offset < start + urlStop ; offset ++ {
442455 mask [offset ] = true
443456 }
444457 }
@@ -453,10 +466,12 @@ func markdownURLMask(input string) []bool {
453466 }
454467 destinationStart := start + 2
455468 stop := destinationStart
456- for stop < len (input ) &&
457- input [stop ] != ')' &&
458- ! unicode .IsSpace (rune (input [stop ])) {
459- stop ++
469+ for stop < len (input ) && input [stop ] != ')' {
470+ r , size := utf8 .DecodeRuneInString (input [stop :])
471+ if unicode .IsSpace (r ) {
472+ break
473+ }
474+ stop += size
460475 }
461476 if stop < len (input ) && input [stop ] == ')' {
462477 for offset := destinationStart ; offset < stop ; offset ++ {
@@ -483,16 +498,30 @@ func urlStart(token string) int {
483498 for start > 0 && isURLSchemeCharacter (token [start - 1 ]) {
484499 start --
485500 }
486- if isASCIILetter (token [start ]) {
501+ scheme := token [start :separator ]
502+ if strings .EqualFold (scheme , "http" ) || strings .EqualFold (scheme , "https" ) {
487503 return start
488504 }
489505 }
490- if start := strings .Index (token , "mailto:" ); start >= 0 {
491- return start
506+ if start := indexASCIIFold (token , "mailto:" ); start >= 0 {
507+ if start > 0 &&
508+ token [start - 1 ] == '<' &&
509+ strings .IndexByte (token [start :], '>' ) >= 0 {
510+ return start
511+ }
492512 }
493513 return strings .Index (token , "www." )
494514}
495515
516+ func indexASCIIFold (input , target string ) int {
517+ for start := 0 ; start + len (target ) <= len (input ); start ++ {
518+ if strings .EqualFold (input [start :start + len (target )], target ) {
519+ return start
520+ }
521+ }
522+ return - 1
523+ }
524+
496525func isURLSchemeCharacter (b byte ) bool {
497526 return isASCIILetter (b ) ||
498527 (b >= '0' && b <= '9' ) ||
@@ -608,7 +637,7 @@ func markdownHiddenSpans(document ast.Node, source []byte) []sourceSpan {
608637 return spans
609638}
610639
611- func escapeMarkdownSyntaxOutsideCode (input string ) string {
640+ func escapeContentOutsideCode (input string ) string {
612641 codeSpans := markdownCodeSpans (input )
613642 codeSpanIndex := 0
614643 var out strings.Builder
@@ -622,7 +651,10 @@ func escapeMarkdownSyntaxOutsideCode(input string) string {
622651 offset >= codeSpans [codeSpanIndex ].start &&
623652 offset < codeSpans [codeSpanIndex ].stop
624653 if inCode ||
625- (input [offset ] != '<' && input [offset ] != '[' && input [offset ] != '$' ) ||
654+ (input [offset ] != '<' &&
655+ input [offset ] != '[' &&
656+ input [offset ] != '$' &&
657+ input [offset ] != '&' ) ||
626658 ((input [offset ] == '[' || input [offset ] == '$' ) && isBackslashEscaped (input , offset )) {
627659 continue
628660 }
@@ -638,6 +670,8 @@ func escapeMarkdownSyntaxOutsideCode(input string) string {
638670 out .WriteString (`\[` )
639671 case '$' :
640672 out .WriteString (`\$` )
673+ case '&' :
674+ out .WriteString ("&" )
641675 }
642676 copied = offset + 1
643677 }
0 commit comments