2014-04-10 22:20:58 +04:00
// Copyright 2014 The Gogs Authors. All rights reserved.
2018-10-31 01:26:28 +03:00
// Copyright 2018 The Gitea Authors. All rights reserved.
2022-11-27 21:20:29 +03:00
// SPDX-License-Identifier: MIT
2014-04-10 22:20:58 +04:00
2016-02-21 01:10:05 +03:00
package markdown
2014-04-10 22:20:58 +04:00
import (
2021-03-16 02:20:05 +03:00
"fmt"
"io"
2020-08-04 22:56:37 +03:00
"strings"
2019-12-31 04:53:28 +03:00
"sync"
2014-04-10 22:20:58 +04:00
2019-12-31 04:53:28 +03:00
"code.gitea.io/gitea/modules/log"
2017-04-21 10:01:08 +03:00
"code.gitea.io/gitea/modules/markup"
2019-12-31 04:53:28 +03:00
"code.gitea.io/gitea/modules/markup/common"
2022-09-13 19:33:37 +03:00
"code.gitea.io/gitea/modules/markup/markdown/math"
2016-11-10 19:24:48 +03:00
"code.gitea.io/gitea/modules/setting"
2019-12-31 04:53:28 +03:00
giteautil "code.gitea.io/gitea/modules/util"
2022-09-26 08:50:03 +03:00
chromahtml "github.com/alecthomas/chroma/v2/formatters/html"
2019-12-31 04:53:28 +03:00
"github.com/yuin/goldmark"
2022-09-26 08:50:03 +03:00
highlighting "github.com/yuin/goldmark-highlighting/v2"
2020-04-09 13:54:50 +03:00
meta "github.com/yuin/goldmark-meta"
2019-12-31 04:53:28 +03:00
"github.com/yuin/goldmark/extension"
"github.com/yuin/goldmark/parser"
"github.com/yuin/goldmark/renderer"
"github.com/yuin/goldmark/renderer/html"
"github.com/yuin/goldmark/util"
2014-04-10 22:20:58 +04:00
)
2022-01-20 20:46:10 +03:00
var (
2023-04-17 22:05:19 +03:00
specMarkdown goldmark . Markdown
specMarkdownOnce sync . Once
2022-01-20 20:46:10 +03:00
)
2014-04-10 22:20:58 +04:00
2022-01-20 20:46:10 +03:00
var (
2022-06-08 11:59:16 +03:00
urlPrefixKey = parser . NewContextKey ( )
isWikiKey = parser . NewContextKey ( )
renderMetasKey = parser . NewContextKey ( )
renderContextKey = parser . NewContextKey ( )
2022-09-13 19:33:37 +03:00
renderConfigKey = parser . NewContextKey ( )
2022-01-20 20:46:10 +03:00
)
2018-02-27 10:09:18 +03:00
2021-03-16 02:20:05 +03:00
type limitWriter struct {
2021-11-19 13:46:47 +03:00
w io . Writer
2021-03-16 02:20:05 +03:00
sum int64
limit int64
}
// Write implements the standard Write interface:
func ( l * limitWriter ) Write ( data [ ] byte ) ( int , error ) {
leftToWrite := l . limit - l . sum
if leftToWrite < int64 ( len ( data ) ) {
n , err := l . w . Write ( data [ : leftToWrite ] )
l . sum += int64 ( n )
if err != nil {
return n , err
}
2023-04-17 22:05:19 +03:00
return n , fmt . Errorf ( "rendered content too large - truncating render" )
2021-03-16 02:20:05 +03:00
}
n , err := l . w . Write ( data )
l . sum += int64 ( n )
return n , err
}
2021-04-20 01:25:08 +03:00
// newParserContext creates a parser.Context with the render context set
func newParserContext ( ctx * markup . RenderContext ) parser . Context {
2019-12-31 04:53:28 +03:00
pc := parser . NewContext ( parser . WithIDs ( newPrefixedIDs ( ) ) )
2021-04-20 01:25:08 +03:00
pc . Set ( urlPrefixKey , ctx . URLPrefix )
pc . Set ( isWikiKey , ctx . IsWiki )
pc . Set ( renderMetasKey , ctx . Metas )
2022-06-08 11:59:16 +03:00
pc . Set ( renderContextKey , ctx )
2019-12-31 04:53:28 +03:00
return pc
2016-01-09 05:59:04 +03:00
}
2023-04-17 22:05:19 +03:00
// SpecializedMarkdown sets up the Gitea specific markdown extensions
func SpecializedMarkdown ( ) goldmark . Markdown {
specMarkdownOnce . Do ( func ( ) {
specMarkdown = goldmark . New (
2021-08-02 19:11:18 +03:00
goldmark . WithExtensions (
extension . NewTable (
extension . WithTableCellAlignMethod ( extension . TableCellAlignAttribute ) ) ,
2019-12-31 04:53:28 +03:00
extension . Strikethrough ,
extension . TaskList ,
extension . DefinitionList ,
common . FootnoteExtension ,
2020-07-01 00:34:03 +03:00
highlighting . NewHighlighting (
highlighting . WithFormatOptions (
chromahtml . WithClasses ( true ) ,
chromahtml . PreventSurroundingPre ( true ) ,
) ,
highlighting . WithWrapperRenderer ( func ( w util . BufWriter , c highlighting . CodeBlockContext , entering bool ) {
if entering {
2020-08-04 22:56:37 +03:00
language , _ := c . Language ( )
if language == nil {
language = [ ] byte ( "text" )
}
languageStr := string ( language )
2021-11-16 11:16:05 +03:00
preClasses := [ ] string { "code-block" }
2022-09-13 19:33:37 +03:00
if languageStr == "mermaid" || languageStr == "math" {
2020-08-04 22:56:37 +03:00
preClasses = append ( preClasses , "is-loading" )
}
2021-11-16 11:16:05 +03:00
_ , err := w . WriteString ( ` <pre class=" ` + strings . Join ( preClasses , " " ) + ` "> ` )
if err != nil {
return
2020-08-04 22:56:37 +03:00
}
2020-07-01 00:34:03 +03:00
// include language-x class as part of commonmark spec
2021-11-16 11:16:05 +03:00
_ , err = w . WriteString ( ` <code class="chroma language- ` + string ( language ) + ` "> ` )
2020-07-01 00:34:03 +03:00
if err != nil {
return
}
} else {
_ , err := w . WriteString ( "</code></pre>" )
if err != nil {
return
}
}
} ) ,
) ,
2022-09-13 19:33:37 +03:00
math . NewExtension (
math . Enabled ( setting . Markdown . EnableMath ) ,
) ,
2020-04-24 16:22:36 +03:00
meta . Meta ,
2019-12-31 04:53:28 +03:00
) ,
goldmark . WithParserOptions (
parser . WithAttribute ( ) ,
parser . WithAutoHeadingID ( ) ,
parser . WithASTTransformers (
2020-04-24 16:22:36 +03:00
util . Prioritized ( & ASTTransformer { } , 10000 ) ,
2019-12-31 04:53:28 +03:00
) ,
) ,
goldmark . WithRendererOptions (
html . WithUnsafe ( ) ,
) ,
)
// Override the original Tasklist renderer!
2023-04-17 22:05:19 +03:00
specMarkdown . Renderer ( ) . AddOptions (
2019-12-31 04:53:28 +03:00
renderer . WithNodeRenderers (
2020-04-24 16:22:36 +03:00
util . Prioritized ( NewHTMLRenderer ( ) , 10 ) ,
2019-12-31 04:53:28 +03:00
) ,
)
} )
2023-04-17 22:05:19 +03:00
return specMarkdown
}
2014-04-10 22:20:58 +04:00
2023-04-17 22:05:19 +03:00
// actualRender renders Markdown to HTML without handling special links.
func actualRender ( ctx * markup . RenderContext , input io . Reader , output io . Writer ) error {
converter := SpecializedMarkdown ( )
2021-03-16 02:20:05 +03:00
lw := & limitWriter {
2021-11-19 13:46:47 +03:00
w : output ,
2021-03-16 02:20:05 +03:00
limit : setting . UI . MaxDisplayFileSize * 3 ,
2015-09-01 15:32:02 +03:00
}
2021-03-16 02:20:05 +03:00
2021-11-19 13:46:47 +03:00
// FIXME: should we include a timeout to abort the renderer if it takes too long?
defer func ( ) {
err := recover ( )
if err == nil {
2021-04-20 01:25:08 +03:00
return
}
2021-11-19 13:46:47 +03:00
log . Warn ( "Unable to render markdown due to panic in goldmark: %v" , err )
if log . IsDebug ( ) {
2022-06-20 13:02:49 +03:00
log . Debug ( "Panic in markdown: %v\n%s" , err , log . Stack ( 2 ) )
2021-03-16 02:20:05 +03:00
}
} ( )
2021-11-19 13:46:47 +03:00
// FIXME: Don't read all to memory, but goldmark doesn't support
pc := newParserContext ( ctx )
buf , err := io . ReadAll ( input )
if err != nil {
log . Error ( "Unable to ReadAll: %v" , err )
return err
}
2022-09-13 19:33:37 +03:00
buf = giteautil . NormalizeEOL ( buf )
2023-06-13 09:44:47 +03:00
// Preserve original length.
bufWithMetadataLength := len ( buf )
2022-09-13 19:33:37 +03:00
rc := & RenderConfig {
2023-04-17 22:05:19 +03:00
Meta : renderMetaModeFromString ( string ( ctx . RenderMetaAs ) ) ,
2022-09-13 19:33:37 +03:00
Icon : "table" ,
Lang : "" ,
}
buf , _ = ExtractMetadataBytes ( buf , rc )
2023-06-13 09:44:47 +03:00
metaLength := bufWithMetadataLength - len ( buf )
if metaLength < 0 {
metaLength = 0
}
rc . metaLength = metaLength
2022-09-13 19:33:37 +03:00
pc . Set ( renderConfigKey , rc )
if err := converter . Convert ( buf , lw , parser . WithContext ( pc ) ) ; err != nil {
2021-11-19 13:46:47 +03:00
log . Error ( "Unable to render: %v" , err )
return err
}
return nil
2021-03-16 02:20:05 +03:00
}
2021-11-19 13:46:47 +03:00
// Note: The output of this method must get sanitized.
2021-04-20 01:25:08 +03:00
func render ( ctx * markup . RenderContext , input io . Reader , output io . Writer ) error {
2021-03-16 02:20:05 +03:00
defer func ( ) {
err := recover ( )
if err == nil {
return
}
2021-11-19 13:46:47 +03:00
log . Warn ( "Unable to render markdown due to panic in goldmark - will return raw bytes" )
2021-03-16 02:20:05 +03:00
if log . IsDebug ( ) {
2022-06-20 13:02:49 +03:00
log . Debug ( "Panic in markdown: %v\n%s" , err , log . Stack ( 2 ) )
2021-03-16 02:20:05 +03:00
}
2021-11-19 13:46:47 +03:00
_ , err = io . Copy ( output , input )
2021-04-20 01:25:08 +03:00
if err != nil {
2021-11-19 13:46:47 +03:00
log . Error ( "io.Copy failed: %v" , err )
2021-04-20 01:25:08 +03:00
}
2021-03-16 02:20:05 +03:00
} ( )
2021-04-20 01:25:08 +03:00
return actualRender ( ctx , input , output )
2014-05-05 21:08:01 +04:00
}
2022-01-20 20:46:10 +03:00
// MarkupName describes markup's name
var MarkupName = "markdown"
2017-04-21 10:01:08 +03:00
func init ( ) {
2021-04-20 01:25:08 +03:00
markup . RegisterRenderer ( Renderer { } )
2017-04-21 10:01:08 +03:00
}
2021-04-20 01:25:08 +03:00
// Renderer implements markup.Renderer
type Renderer struct { }
2017-04-21 10:01:08 +03:00
2022-06-16 06:33:23 +03:00
var _ markup . PostProcessRenderer = ( * Renderer ) ( nil )
2021-04-20 01:25:08 +03:00
// Name implements markup.Renderer
func ( Renderer ) Name ( ) string {
2017-04-21 10:01:08 +03:00
return MarkupName
}
2022-06-16 06:33:23 +03:00
// NeedPostProcess implements markup.PostProcessRenderer
2021-04-20 01:25:08 +03:00
func ( Renderer ) NeedPostProcess ( ) bool { return true }
2021-04-13 10:06:31 +03:00
2021-04-20 01:25:08 +03:00
// Extensions implements markup.Renderer
func ( Renderer ) Extensions ( ) [ ] string {
2017-04-21 10:01:08 +03:00
return setting . Markdown . FileExtensions
}
2021-06-24 00:09:51 +03:00
// SanitizerRules implements markup.Renderer
func ( Renderer ) SanitizerRules ( ) [ ] setting . MarkupSanitizerRule {
return [ ] setting . MarkupSanitizerRule { }
}
2021-04-20 01:25:08 +03:00
// Render implements markup.Renderer
func ( Renderer ) Render ( ctx * markup . RenderContext , input io . Reader , output io . Writer ) error {
return render ( ctx , input , output )
2017-09-16 20:17:57 +03:00
}
// Render renders Markdown to HTML with all specific handling stuff.
2021-04-20 01:25:08 +03:00
func Render ( ctx * markup . RenderContext , input io . Reader , output io . Writer ) error {
2021-11-19 13:46:47 +03:00
if ctx . Type == "" {
ctx . Type = MarkupName
2021-04-20 01:25:08 +03:00
}
return markup . Render ( ctx , input , output )
2017-09-16 20:17:57 +03:00
}
2021-04-20 01:25:08 +03:00
// RenderString renders Markdown string to HTML with all specific handling stuff and return string
func RenderString ( ctx * markup . RenderContext , content string ) ( string , error ) {
var buf strings . Builder
if err := Render ( ctx , strings . NewReader ( content ) , & buf ) ; err != nil {
return "" , err
}
return buf . String ( ) , nil
2020-05-24 11:14:26 +03:00
}
2021-04-20 01:25:08 +03:00
// RenderRaw renders Markdown to HTML without handling special links.
func RenderRaw ( ctx * markup . RenderContext , input io . Reader , output io . Writer ) error {
2021-11-19 13:46:47 +03:00
rd , wr := io . Pipe ( )
defer func ( ) {
_ = rd . Close ( )
_ = wr . Close ( )
} ( )
go func ( ) {
if err := render ( ctx , input , wr ) ; err != nil {
_ = wr . CloseWithError ( err )
return
}
_ = wr . Close ( )
} ( )
return markup . SanitizeReader ( rd , "" , output )
2017-09-16 20:17:57 +03:00
}
2021-04-20 01:25:08 +03:00
// RenderRawString renders Markdown to HTML without handling special links and return string
func RenderRawString ( ctx * markup . RenderContext , content string ) ( string , error ) {
var buf strings . Builder
if err := RenderRaw ( ctx , strings . NewReader ( content ) , & buf ) ; err != nil {
return "" , err
}
return buf . String ( ) , nil
2017-09-16 20:17:57 +03:00
}