mirror of
				https://github.com/go-gitea/gitea.git
				synced 2025-10-26 12:27:06 +00:00 
			
		
		
		
	Refactor markup package (#32399)
To make the markup package easier to maintain: 1. Split some go files into small files 2. Use a shared util.NopCloser, remove duplicate code 3. Remove unused functions
This commit is contained in:
		
							
								
								
									
										226
									
								
								modules/markup/render.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										226
									
								
								modules/markup/render.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,226 @@ | ||||
| // Copyright 2024 The Gitea Authors. All rights reserved. | ||||
| // SPDX-License-Identifier: MIT | ||||
|  | ||||
| package markup | ||||
|  | ||||
| import ( | ||||
| 	"context" | ||||
| 	"errors" | ||||
| 	"fmt" | ||||
| 	"io" | ||||
| 	"net/url" | ||||
| 	"path/filepath" | ||||
| 	"strings" | ||||
| 	"sync" | ||||
|  | ||||
| 	"code.gitea.io/gitea/modules/git" | ||||
| 	"code.gitea.io/gitea/modules/gitrepo" | ||||
| 	"code.gitea.io/gitea/modules/setting" | ||||
| 	"code.gitea.io/gitea/modules/util" | ||||
|  | ||||
| 	"github.com/yuin/goldmark/ast" | ||||
| ) | ||||
|  | ||||
| type RenderMetaMode string | ||||
|  | ||||
| const ( | ||||
| 	RenderMetaAsDetails RenderMetaMode = "details" // default | ||||
| 	RenderMetaAsNone    RenderMetaMode = "none" | ||||
| 	RenderMetaAsTable   RenderMetaMode = "table" | ||||
| ) | ||||
|  | ||||
| // RenderContext represents a render context | ||||
| type RenderContext struct { | ||||
| 	Ctx              context.Context | ||||
| 	RelativePath     string // relative path from tree root of the branch | ||||
| 	Type             string | ||||
| 	IsWiki           bool | ||||
| 	Links            Links | ||||
| 	Metas            map[string]string // user, repo, mode(comment/document) | ||||
| 	DefaultLink      string | ||||
| 	GitRepo          *git.Repository | ||||
| 	Repo             gitrepo.Repository | ||||
| 	ShaExistCache    map[string]bool | ||||
| 	cancelFn         func() | ||||
| 	SidebarTocNode   ast.Node | ||||
| 	RenderMetaAs     RenderMetaMode | ||||
| 	InStandalonePage bool // used by external render. the router "/org/repo/render/..." will output the rendered content in a standalone page | ||||
| } | ||||
|  | ||||
| // Cancel runs any cleanup functions that have been registered for this Ctx | ||||
| func (ctx *RenderContext) Cancel() { | ||||
| 	if ctx == nil { | ||||
| 		return | ||||
| 	} | ||||
| 	ctx.ShaExistCache = map[string]bool{} | ||||
| 	if ctx.cancelFn == nil { | ||||
| 		return | ||||
| 	} | ||||
| 	ctx.cancelFn() | ||||
| } | ||||
|  | ||||
| // AddCancel adds the provided fn as a Cleanup for this Ctx | ||||
| func (ctx *RenderContext) AddCancel(fn func()) { | ||||
| 	if ctx == nil { | ||||
| 		return | ||||
| 	} | ||||
| 	oldCancelFn := ctx.cancelFn | ||||
| 	if oldCancelFn == nil { | ||||
| 		ctx.cancelFn = fn | ||||
| 		return | ||||
| 	} | ||||
| 	ctx.cancelFn = func() { | ||||
| 		defer oldCancelFn() | ||||
| 		fn() | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // Render renders markup file to HTML with all specific handling stuff. | ||||
| func Render(ctx *RenderContext, input io.Reader, output io.Writer) error { | ||||
| 	if ctx.Type != "" { | ||||
| 		return renderByType(ctx, input, output) | ||||
| 	} else if ctx.RelativePath != "" { | ||||
| 		return renderFile(ctx, input, output) | ||||
| 	} | ||||
| 	return errors.New("render options both filename and type missing") | ||||
| } | ||||
|  | ||||
| // RenderString renders Markup string to HTML with all specific handling stuff and return string | ||||
| func RenderString(ctx *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 | ||||
| } | ||||
|  | ||||
| func renderIFrame(ctx *RenderContext, output io.Writer) error { | ||||
| 	// set height="0" ahead, otherwise the scrollHeight would be max(150, realHeight) | ||||
| 	// at the moment, only "allow-scripts" is allowed for sandbox mode. | ||||
| 	// "allow-same-origin" should never be used, it leads to XSS attack, and it makes the JS in iframe can access parent window's config and CSRF token | ||||
| 	// TODO: when using dark theme, if the rendered content doesn't have proper style, the default text color is black, which is not easy to read | ||||
| 	_, err := io.WriteString(output, fmt.Sprintf(` | ||||
| <iframe src="%s/%s/%s/render/%s/%s" | ||||
| name="giteaExternalRender" | ||||
| onload="this.height=giteaExternalRender.document.documentElement.scrollHeight" | ||||
| width="100%%" height="0" scrolling="no" frameborder="0" style="overflow: hidden" | ||||
| sandbox="allow-scripts" | ||||
| ></iframe>`, | ||||
| 		setting.AppSubURL, | ||||
| 		url.PathEscape(ctx.Metas["user"]), | ||||
| 		url.PathEscape(ctx.Metas["repo"]), | ||||
| 		ctx.Metas["BranchNameSubURL"], | ||||
| 		url.PathEscape(ctx.RelativePath), | ||||
| 	)) | ||||
| 	return err | ||||
| } | ||||
|  | ||||
| func render(ctx *RenderContext, renderer Renderer, input io.Reader, output io.Writer) error { | ||||
| 	var wg sync.WaitGroup | ||||
| 	var err error | ||||
| 	pr, pw := io.Pipe() | ||||
| 	defer func() { | ||||
| 		_ = pr.Close() | ||||
| 		_ = pw.Close() | ||||
| 	}() | ||||
|  | ||||
| 	var pr2 io.ReadCloser | ||||
| 	var pw2 io.WriteCloser | ||||
|  | ||||
| 	var sanitizerDisabled bool | ||||
| 	if r, ok := renderer.(ExternalRenderer); ok { | ||||
| 		sanitizerDisabled = r.SanitizerDisabled() | ||||
| 	} | ||||
|  | ||||
| 	if !sanitizerDisabled { | ||||
| 		pr2, pw2 = io.Pipe() | ||||
| 		defer func() { | ||||
| 			_ = pr2.Close() | ||||
| 			_ = pw2.Close() | ||||
| 		}() | ||||
|  | ||||
| 		wg.Add(1) | ||||
| 		go func() { | ||||
| 			err = SanitizeReader(pr2, renderer.Name(), output) | ||||
| 			_ = pr2.Close() | ||||
| 			wg.Done() | ||||
| 		}() | ||||
| 	} else { | ||||
| 		pw2 = util.NopCloser{Writer: output} | ||||
| 	} | ||||
|  | ||||
| 	wg.Add(1) | ||||
| 	go func() { | ||||
| 		if r, ok := renderer.(PostProcessRenderer); ok && r.NeedPostProcess() { | ||||
| 			err = PostProcess(ctx, pr, pw2) | ||||
| 		} else { | ||||
| 			_, err = io.Copy(pw2, pr) | ||||
| 		} | ||||
| 		_ = pr.Close() | ||||
| 		_ = pw2.Close() | ||||
| 		wg.Done() | ||||
| 	}() | ||||
|  | ||||
| 	if err1 := renderer.Render(ctx, input, pw); err1 != nil { | ||||
| 		return err1 | ||||
| 	} | ||||
| 	_ = pw.Close() | ||||
|  | ||||
| 	wg.Wait() | ||||
| 	return err | ||||
| } | ||||
|  | ||||
| func renderByType(ctx *RenderContext, input io.Reader, output io.Writer) error { | ||||
| 	if renderer, ok := renderers[ctx.Type]; ok { | ||||
| 		return render(ctx, renderer, input, output) | ||||
| 	} | ||||
| 	return fmt.Errorf("unsupported render type: %s", ctx.Type) | ||||
| } | ||||
|  | ||||
| // ErrUnsupportedRenderExtension represents the error when extension doesn't supported to render | ||||
| type ErrUnsupportedRenderExtension struct { | ||||
| 	Extension string | ||||
| } | ||||
|  | ||||
| func IsErrUnsupportedRenderExtension(err error) bool { | ||||
| 	_, ok := err.(ErrUnsupportedRenderExtension) | ||||
| 	return ok | ||||
| } | ||||
|  | ||||
| func (err ErrUnsupportedRenderExtension) Error() string { | ||||
| 	return fmt.Sprintf("Unsupported render extension: %s", err.Extension) | ||||
| } | ||||
|  | ||||
| func renderFile(ctx *RenderContext, input io.Reader, output io.Writer) error { | ||||
| 	extension := strings.ToLower(filepath.Ext(ctx.RelativePath)) | ||||
| 	if renderer, ok := extRenderers[extension]; ok { | ||||
| 		if r, ok := renderer.(ExternalRenderer); ok && r.DisplayInIFrame() { | ||||
| 			if !ctx.InStandalonePage { | ||||
| 				// for an external render, it could only output its content in a standalone page | ||||
| 				// otherwise, a <iframe> should be outputted to embed the external rendered page | ||||
| 				return renderIFrame(ctx, output) | ||||
| 			} | ||||
| 		} | ||||
| 		return render(ctx, renderer, input, output) | ||||
| 	} | ||||
| 	return ErrUnsupportedRenderExtension{extension} | ||||
| } | ||||
|  | ||||
| // Init initializes the render global variables | ||||
| func Init(ph *ProcessorHelper) { | ||||
| 	if ph != nil { | ||||
| 		DefaultProcessorHelper = *ph | ||||
| 	} | ||||
|  | ||||
| 	if len(setting.Markdown.CustomURLSchemes) > 0 { | ||||
| 		CustomLinkURLSchemes(setting.Markdown.CustomURLSchemes) | ||||
| 	} | ||||
|  | ||||
| 	// since setting maybe changed extensions, this will reload all renderer extensions mapping | ||||
| 	extRenderers = make(map[string]Renderer) | ||||
| 	for _, renderer := range renderers { | ||||
| 		for _, ext := range renderer.Extensions() { | ||||
| 			extRenderers[strings.ToLower(ext)] = renderer | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
		Reference in New Issue
	
	Block a user
	 wxiaoguang
					wxiaoguang