From c5ac9d0c85a01d77328eeace38ffbf515890673a Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Tue, 11 Jul 2023 16:10:04 +0800 Subject: [PATCH 01/15] Support render opanapi v2.0 --- main.go | 1 + modules/markup/openapi/openapi.go | 77 +++++++++++++++++++++++++++++++ modules/markup/renderer.go | 63 +++++++++++++++++-------- routers/common/markup.go | 2 +- templates/base/head.tmpl | 1 + 5 files changed, 123 insertions(+), 21 deletions(-) create mode 100644 modules/markup/openapi/openapi.go diff --git a/main.go b/main.go index 9b561376c34a5..3d37393c685c5 100644 --- a/main.go +++ b/main.go @@ -21,6 +21,7 @@ import ( _ "code.gitea.io/gitea/modules/markup/console" _ "code.gitea.io/gitea/modules/markup/csv" _ "code.gitea.io/gitea/modules/markup/markdown" + _ "code.gitea.io/gitea/modules/markup/openapi" _ "code.gitea.io/gitea/modules/markup/orgmode" "github.com/urfave/cli" diff --git a/modules/markup/openapi/openapi.go b/modules/markup/openapi/openapi.go new file mode 100644 index 0000000000000..be083df03d39c --- /dev/null +++ b/modules/markup/openapi/openapi.go @@ -0,0 +1,77 @@ +// Copyright 2023 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package openapi + +import ( + "fmt" + "io" + "net/url" + + "code.gitea.io/gitea/modules/markup" + "code.gitea.io/gitea/modules/setting" + "github.com/gobwas/glob" +) + +func init() { + markup.RegisterRenderer(Renderer{}) +} + +// Renderer implements markup.Renderer for asciicast files. +// See https://github.com/asciinema/asciinema/blob/develop/doc/asciicast-v2.md +type Renderer struct{} + +var _ markup.GlobMatchRenderer = (*Renderer)(nil) + +// Name implements markup.Renderer +func (Renderer) Name() string { + return "openapi" +} + +// SanitizerDisabled disabled sanitize if return true +func (Renderer) SanitizerDisabled() bool { + return true +} + +// DisplayInIFrame represents whether render the content with an iframe +func (Renderer) DisplayInIFrame() bool { + return false +} + +func (Renderer) MatchGlobs() []glob.Glob { + return []glob.Glob{ + glob.MustCompile("**{openapi,OpenAPI,swagger}.{yml,yaml,json,JSON,Yaml,YML}", '/'), + } +} + +// Extensions implements markup.Renderer +func (Renderer) Extensions() []string { + return nil +} + +// SanitizerRules implements markup.Renderer +func (Renderer) SanitizerRules() []setting.MarkupSanitizerRule { + return []setting.MarkupSanitizerRule{ + {Element: "script", AllowAttr: "src"}, + } +} + +// Render implements markup.Renderer +func (Renderer) Render(ctx *markup.RenderContext, _ io.Reader, output io.Writer) error { + rawURL := fmt.Sprintf("%s/%s/%s/raw/%s/%s", + setting.AppSubURL, + url.PathEscape(ctx.Metas["user"]), + url.PathEscape(ctx.Metas["repo"]), + ctx.Metas["BranchNameSubURL"], + url.PathEscape(ctx.RelativePath), + ) + + _, err := io.WriteString(output, fmt.Sprintf( + `
+ `, + rawURL, + setting.StaticURLPrefix+"/assets", + setting.AssetVersion, + )) + return err +} diff --git a/modules/markup/renderer.go b/modules/markup/renderer.go index 0331c3742ab11..db6e6dfde7341 100644 --- a/modules/markup/renderer.go +++ b/modules/markup/renderer.go @@ -17,6 +17,7 @@ import ( "code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/setting" + "github.com/gobwas/glob" "github.com/yuin/goldmark/ast" ) @@ -116,6 +117,10 @@ type Renderer interface { Render(ctx *RenderContext, input io.Reader, output io.Writer) error } +type GlobMatchRenderer interface { + MatchGlobs() []glob.Glob +} + // PostProcessRenderer defines an interface for renderers who need post process type PostProcessRenderer interface { NeedPostProcess() bool @@ -137,8 +142,9 @@ type RendererContentDetector interface { } var ( - extRenderers = make(map[string]Renderer) - renderers = make(map[string]Renderer) + extRenderers = make(map[string]Renderer) + globMatchRenderers = make([]GlobMatchRenderer, 0) + renderers = make(map[string]Renderer) ) // RegisterRenderer registers a new markup file renderer @@ -147,12 +153,28 @@ func RegisterRenderer(renderer Renderer) { for _, ext := range renderer.Extensions() { extRenderers[strings.ToLower(ext)] = renderer } + gmRenderer, ok := renderer.(GlobMatchRenderer) + if ok { + globMatchRenderers = append(globMatchRenderers, gmRenderer) + } } // GetRendererByFileName get renderer by filename func GetRendererByFileName(filename string) Renderer { extension := strings.ToLower(filepath.Ext(filename)) - return extRenderers[extension] + renderer := extRenderers[extension] + if renderer != nil { + return renderer + } + + for _, gmRenderer := range globMatchRenderers { + for _, mg := range gmRenderer.MatchGlobs() { + if mg.Match(filename) { + return gmRenderer.(Renderer) + } + } + } + return nil } // GetRendererByType returns a renderer according type @@ -291,33 +313,34 @@ func renderByType(ctx *RenderContext, input io.Reader, output io.Writer) error { return ErrUnsupportedRenderType{ctx.Type} } -// ErrUnsupportedRenderExtension represents the error when extension doesn't supported to render -type ErrUnsupportedRenderExtension struct { - Extension string +// ErrUnsupportedRenderFile represents the error when extension or filename doesn't supported to render +type ErrUnsupportedRenderFile struct { + RelativePath string } -func IsErrUnsupportedRenderExtension(err error) bool { - _, ok := err.(ErrUnsupportedRenderExtension) +func IsErrUnsupportedRenderFile(err error) bool { + _, ok := err.(ErrUnsupportedRenderFile) return ok } -func (err ErrUnsupportedRenderExtension) Error() string { - return fmt.Sprintf("Unsupported render extension: %s", err.Extension) +func (err ErrUnsupportedRenderFile) Error() string { + return fmt.Sprintf("Unsupported render file: %s", err.RelativePath) } 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 `, - setting.AppSubURL, + setting.AppURL, url.PathEscape(ctx.Metas["user"]), url.PathEscape(ctx.Metas["repo"]), ctx.Metas["BranchNameSubURL"], diff --git a/routers/web/repo/render.go b/routers/web/repo/render.go index f1524194d4f0c..f0293fa35e795 100644 --- a/routers/web/repo/render.go +++ b/routers/web/repo/render.go @@ -62,12 +62,15 @@ func RenderFile(ctx *context.Context) { treeLink += "/" + util.PathEscapeSegments(ctx.Repo.TreePath) } - ctx.Resp.Header().Add("Content-Security-Policy", "frame-src 'self'; sandbox allow-scripts") + ctx.Resp.Header().Add("Content-Security-Policy", "frame-src 'self'; sandbox allow-same-origin") + ctx.Resp.Header().Add("Access-Control-Allow-Origin", "http://192.168.8.18:3000") + metaData := ctx.Repo.Repository.ComposeDocumentMetas() + metaData["BranchNameSubURL"] = ctx.Repo.BranchNameSubURL() resp, err := markup.Render(&markup.RenderContext{ Ctx: ctx, RelativePath: ctx.Repo.TreePath, URLPrefix: path.Dir(treeLink), - Metas: ctx.Repo.Repository.ComposeDocumentMetas(), + Metas: metaData, GitRepo: ctx.Repo.GitRepo, InStandalonePage: true, }, rd, ctx.Resp) From 5aefd2ede5456942e96794b7ead135a6e03f7780 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Sun, 23 Jul 2023 10:06:15 +0800 Subject: [PATCH 05/15] use a standalone page to render openapi --- modules/markup/openapi/openapi.go | 22 ++++------------------ 1 file changed, 4 insertions(+), 18 deletions(-) diff --git a/modules/markup/openapi/openapi.go b/modules/markup/openapi/openapi.go index a7204c016b55a..7132abaf796ef 100644 --- a/modules/markup/openapi/openapi.go +++ b/modules/markup/openapi/openapi.go @@ -36,7 +36,7 @@ func (Renderer) SanitizerDisabled() bool { // DisplayInIFrame represents whether render the content with an iframe func (Renderer) DisplayInIFrame() bool { - return true + return false } func (Renderer) MatchGlobs() []glob.Glob { @@ -59,7 +59,7 @@ func (Renderer) SanitizerRules() []setting.MarkupSanitizerRule { // Render implements markup.Renderer func (Renderer) Render(ctx *markup.RenderContext, _ io.Reader, output io.Writer) (*markup.RenderResponse, error) { - rawURL := fmt.Sprintf("%s/%s/%s/raw/%s/%s", + renderURL := fmt.Sprintf("%s/%s/%s/render/%s/%s", setting.AppSubURL, url.PathEscape(ctx.Metas["user"]), url.PathEscape(ctx.Metas["repo"]), @@ -68,22 +68,8 @@ func (Renderer) Render(ctx *markup.RenderContext, _ io.Reader, output io.Writer) ) if _, err := io.WriteString(output, fmt.Sprintf( - ` - - - - - - -
- - -`, - setting.StaticURLPrefix, - setting.AssetVersion, - rawURL, - setting.StaticURLPrefix, - setting.AssetVersion, + `View in a standalone page`, + renderURL, )); err != nil { return nil, err } From 568a29aa1ddc0d3ae314c1460a24179fb44ba39b Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Sun, 23 Jul 2023 17:44:59 +0800 Subject: [PATCH 06/15] Revert unnecessary change --- modules/markup/asciicast/asciicast.go | 4 +- modules/markup/console/console.go | 9 ++-- modules/markup/csv/csv.go | 26 +++++------ modules/markup/external/external.go | 12 ++--- modules/markup/markdown/markdown.go | 11 ++--- modules/markup/openapi/openapi.go | 35 ++++++++------ modules/markup/orgmode/orgmode.go | 10 ++-- modules/markup/renderer.go | 66 ++++++++++++++++++--------- routers/common/markup.go | 6 +-- routers/web/repo/render.go | 9 +--- routers/web/repo/view.go | 12 ++--- templates/base/head_style.tmpl | 5 -- 12 files changed, 111 insertions(+), 94 deletions(-) diff --git a/modules/markup/asciicast/asciicast.go b/modules/markup/asciicast/asciicast.go index ca8ffd0aa1f58..06780623403a4 100644 --- a/modules/markup/asciicast/asciicast.go +++ b/modules/markup/asciicast/asciicast.go @@ -45,7 +45,7 @@ func (Renderer) SanitizerRules() []setting.MarkupSanitizerRule { } // Render implements markup.Renderer -func (Renderer) Render(ctx *markup.RenderContext, _ io.Reader, output io.Writer) (*markup.RenderResponse, error) { +func (Renderer) Render(ctx *markup.RenderContext, _ io.Reader, output io.Writer) error { rawURL := fmt.Sprintf("%s/%s/%s/raw/%s/%s", setting.AppSubURL, url.PathEscape(ctx.Metas["user"]), @@ -60,5 +60,5 @@ func (Renderer) Render(ctx *markup.RenderContext, _ io.Reader, output io.Writer) playerSrcAttr, rawURL, )) - return nil, err + return err } diff --git a/modules/markup/console/console.go b/modules/markup/console/console.go index 99c39c9c0ad61..cf42c9cceb7a3 100644 --- a/modules/markup/console/console.go +++ b/modules/markup/console/console.go @@ -57,15 +57,15 @@ func (Renderer) CanRender(filename string, input io.Reader) bool { } // Render renders terminal colors to HTML with all specific handling stuff. -func (Renderer) Render(ctx *markup.RenderContext, input io.Reader, output io.Writer) (*markup.RenderResponse, error) { +func (Renderer) Render(ctx *markup.RenderContext, input io.Reader, output io.Writer) error { buf, err := io.ReadAll(input) if err != nil { - return nil, err + return err } buf = trend.Render(buf) buf = bytes.ReplaceAll(buf, []byte("\n"), []byte(`
`)) _, err = output.Write(buf) - return nil, err + return err } // Render renders terminal colors to HTML with all specific handling stuff. @@ -73,8 +73,7 @@ func Render(ctx *markup.RenderContext, input io.Reader, output io.Writer) error if ctx.Type == "" { ctx.Type = MarkupName } - _, err := markup.Render(ctx, input, output) - return err + return markup.Render(ctx, input, output) } // RenderString renders terminal colors in string to HTML with all specific handling stuff and return string diff --git a/modules/markup/csv/csv.go b/modules/markup/csv/csv.go index d83700d7fe1e9..7af34a6cbc2d4 100644 --- a/modules/markup/csv/csv.go +++ b/modules/markup/csv/csv.go @@ -77,33 +77,33 @@ func writeField(w io.Writer, element, class, field string) error { } // Render implements markup.Renderer -func (Renderer) Render(ctx *markup.RenderContext, input io.Reader, output io.Writer) (*markup.RenderResponse, error) { +func (Renderer) Render(ctx *markup.RenderContext, input io.Reader, output io.Writer) error { tmpBlock := bufio.NewWriter(output) // FIXME: don't read all to memory rawBytes, err := io.ReadAll(input) if err != nil { - return nil, err + return err } if setting.UI.CSV.MaxFileSize != 0 && setting.UI.CSV.MaxFileSize < int64(len(rawBytes)) { if _, err := tmpBlock.WriteString("
"); err != nil {
-			return nil, err
+			return err
 		}
 		if _, err := tmpBlock.WriteString(html.EscapeString(string(rawBytes))); err != nil {
-			return nil, err
+			return err
 		}
 		_, err = tmpBlock.WriteString("
") - return nil, err + return err } rd, err := csv.CreateReaderAndDetermineDelimiter(ctx, bytes.NewReader(rawBytes)) if err != nil { - return nil, err + return err } if _, err := tmpBlock.WriteString(``); err != nil { - return nil, err + return err } row := 1 for { @@ -115,28 +115,28 @@ func (Renderer) Render(ctx *markup.RenderContext, input io.Reader, output io.Wri continue } if _, err := tmpBlock.WriteString(""); err != nil { - return nil, err + return err } element := "td" if row == 1 { element = "th" } if err := writeField(tmpBlock, element, "line-num", strconv.Itoa(row)); err != nil { - return nil, err + return err } for _, field := range fields { if err := writeField(tmpBlock, element, "", field); err != nil { - return nil, err + return err } } if _, err := tmpBlock.WriteString(""); err != nil { - return nil, err + return err } row++ } if _, err = tmpBlock.WriteString("
"); err != nil { - return nil, err + return err } - return nil, tmpBlock.Flush() + return tmpBlock.Flush() } diff --git a/modules/markup/external/external.go b/modules/markup/external/external.go index bea1f8e27d9cc..ffbb6da4daddc 100644 --- a/modules/markup/external/external.go +++ b/modules/markup/external/external.go @@ -77,7 +77,7 @@ func envMark(envName string) string { } // Render renders the data of the document to HTML via the external tool. -func (p *Renderer) Render(ctx *markup.RenderContext, input io.Reader, output io.Writer) (*markup.RenderResponse, error) { +func (p *Renderer) Render(ctx *markup.RenderContext, input io.Reader, output io.Writer) error { var ( urlRawPrefix = strings.Replace(ctx.URLPrefix, "/src/", "/raw/", 1) command = strings.NewReplacer(envMark("GITEA_PREFIX_SRC"), ctx.URLPrefix, @@ -90,7 +90,7 @@ func (p *Renderer) Render(ctx *markup.RenderContext, input io.Reader, output io. // write to temp file f, err := os.CreateTemp("", "gitea_input") if err != nil { - return nil, fmt.Errorf("%s create temp file when rendering %s failed: %w", p.Name(), p.Command, err) + return fmt.Errorf("%s create temp file when rendering %s failed: %w", p.Name(), p.Command, err) } tmpPath := f.Name() defer func() { @@ -102,12 +102,12 @@ func (p *Renderer) Render(ctx *markup.RenderContext, input io.Reader, output io. _, err = io.Copy(f, input) if err != nil { f.Close() - return nil, fmt.Errorf("%s write data to temp file when rendering %s failed: %w", p.Name(), p.Command, err) + return fmt.Errorf("%s write data to temp file when rendering %s failed: %w", p.Name(), p.Command, err) } err = f.Close() if err != nil { - return nil, fmt.Errorf("%s close temp file when rendering %s failed: %w", p.Name(), p.Command, err) + return fmt.Errorf("%s close temp file when rendering %s failed: %w", p.Name(), p.Command, err) } args = append(args, f.Name()) } @@ -139,7 +139,7 @@ func (p *Renderer) Render(ctx *markup.RenderContext, input io.Reader, output io. process.SetSysProcAttribute(cmd) if err := cmd.Run(); err != nil { - return nil, fmt.Errorf("%s render run command %s %v failed: %w\nStderr: %s", p.Name(), commands[0], args, err, stderr.String()) + return fmt.Errorf("%s render run command %s %v failed: %w\nStderr: %s", p.Name(), commands[0], args, err, stderr.String()) } - return nil, nil + return nil } diff --git a/modules/markup/markdown/markdown.go b/modules/markup/markdown/markdown.go index 524ea29ffbe39..43885889d125d 100644 --- a/modules/markup/markdown/markdown.go +++ b/modules/markup/markdown/markdown.go @@ -205,7 +205,7 @@ func actualRender(ctx *markup.RenderContext, input io.Reader, output io.Writer) } // Note: The output of this method must get sanitized. -func render(ctx *markup.RenderContext, input io.Reader, output io.Writer) (*markup.RenderResponse, error) { +func render(ctx *markup.RenderContext, input io.Reader, output io.Writer) error { defer func() { err := recover() if err == nil { @@ -221,7 +221,7 @@ func render(ctx *markup.RenderContext, input io.Reader, output io.Writer) (*mark log.Error("io.Copy failed: %v", err) } }() - return nil, actualRender(ctx, input, output) + return actualRender(ctx, input, output) } // MarkupName describes markup's name @@ -255,7 +255,7 @@ func (Renderer) SanitizerRules() []setting.MarkupSanitizerRule { } // Render implements markup.Renderer -func (Renderer) Render(ctx *markup.RenderContext, input io.Reader, output io.Writer) (*markup.RenderResponse, error) { +func (Renderer) Render(ctx *markup.RenderContext, input io.Reader, output io.Writer) error { return render(ctx, input, output) } @@ -264,8 +264,7 @@ func Render(ctx *markup.RenderContext, input io.Reader, output io.Writer) error if ctx.Type == "" { ctx.Type = MarkupName } - _, err := markup.Render(ctx, input, output) - return err + return markup.Render(ctx, input, output) } // RenderString renders Markdown string to HTML with all specific handling stuff and return string @@ -286,7 +285,7 @@ func RenderRaw(ctx *markup.RenderContext, input io.Reader, output io.Writer) err }() go func() { - if _, err := render(ctx, input, wr); err != nil { + if err := render(ctx, input, wr); err != nil { _ = wr.CloseWithError(err) return } diff --git a/modules/markup/openapi/openapi.go b/modules/markup/openapi/openapi.go index 7132abaf796ef..5635d74243cca 100644 --- a/modules/markup/openapi/openapi.go +++ b/modules/markup/openapi/openapi.go @@ -34,9 +34,8 @@ func (Renderer) SanitizerDisabled() bool { return true } -// DisplayInIFrame represents whether render the content with an iframe -func (Renderer) DisplayInIFrame() bool { - return false +func (Renderer) DisplayInNewPage() bool { + return true } func (Renderer) MatchGlobs() []glob.Glob { @@ -58,8 +57,8 @@ func (Renderer) SanitizerRules() []setting.MarkupSanitizerRule { } // Render implements markup.Renderer -func (Renderer) Render(ctx *markup.RenderContext, _ io.Reader, output io.Writer) (*markup.RenderResponse, error) { - renderURL := fmt.Sprintf("%s/%s/%s/render/%s/%s", +func (Renderer) Render(ctx *markup.RenderContext, _ io.Reader, output io.Writer) error { + rawURL := fmt.Sprintf("%s/%s/%s/raw/%s/%s", setting.AppSubURL, url.PathEscape(ctx.Metas["user"]), url.PathEscape(ctx.Metas["repo"]), @@ -68,14 +67,24 @@ func (Renderer) Render(ctx *markup.RenderContext, _ io.Reader, output io.Writer) ) if _, err := io.WriteString(output, fmt.Sprintf( - `View in a standalone page`, - renderURL, + ` + + + + + + +
+ + +`, + setting.StaticURLPrefix, + setting.AssetVersion, + rawURL, + setting.StaticURLPrefix, + setting.AssetVersion, )); err != nil { - return nil, err + return err } - return &markup.RenderResponse{ - ExtraStyleFiles: []string{ - setting.StaticURLPrefix + "/assets/css/swagger.css?v=" + setting.AssetVersion, - }, - }, nil + return nil } diff --git a/modules/markup/orgmode/orgmode.go b/modules/markup/orgmode/orgmode.go index 491a8a7a7d2a7..a6dac120398c5 100644 --- a/modules/markup/orgmode/orgmode.go +++ b/modules/markup/orgmode/orgmode.go @@ -49,7 +49,7 @@ func (Renderer) SanitizerRules() []setting.MarkupSanitizerRule { } // Render renders orgmode rawbytes to HTML -func Render(ctx *markup.RenderContext, input io.Reader, output io.Writer) (*markup.RenderResponse, error) { +func Render(ctx *markup.RenderContext, input io.Reader, output io.Writer) error { htmlWriter := org.NewHTMLWriter() htmlWriter.HighlightCodeBlock = func(source, lang string, inline bool, params map[string]string) string { defer func() { @@ -109,23 +109,23 @@ func Render(ctx *markup.RenderContext, input io.Reader, output io.Writer) (*mark res, err := org.New().Silent().Parse(input, "").Write(w) if err != nil { - return nil, fmt.Errorf("orgmode.Render failed: %w", err) + return fmt.Errorf("orgmode.Render failed: %w", err) } _, err = io.Copy(output, strings.NewReader(res)) - return nil, err + return err } // RenderString renders orgmode string to HTML string func RenderString(ctx *markup.RenderContext, content string) (string, error) { var buf strings.Builder - if _, err := Render(ctx, strings.NewReader(content), &buf); err != nil { + if err := Render(ctx, strings.NewReader(content), &buf); err != nil { return "", err } return buf.String(), nil } // Render renders orgmode string to HTML string -func (Renderer) Render(ctx *markup.RenderContext, input io.Reader, output io.Writer) (*markup.RenderResponse, error) { +func (Renderer) Render(ctx *markup.RenderContext, input io.Reader, output io.Writer) error { return Render(ctx, input, output) } diff --git a/modules/markup/renderer.go b/modules/markup/renderer.go index 8434f9da697cd..dc883cf8cc06d 100644 --- a/modules/markup/renderer.go +++ b/modules/markup/renderer.go @@ -109,16 +109,12 @@ func (ctx *RenderContext) AddCancel(fn func()) { } } -type RenderResponse struct { - ExtraStyleFiles []string -} - // Renderer defines an interface for rendering markup file to HTML type Renderer interface { Name() string // markup format name Extensions() []string SanitizerRules() []setting.MarkupSanitizerRule - Render(ctx *RenderContext, input io.Reader, output io.Writer) (*RenderResponse, error) + Render(ctx *RenderContext, input io.Reader, output io.Writer) error } type GlobMatchRenderer interface { @@ -130,15 +126,23 @@ type PostProcessRenderer interface { NeedPostProcess() bool } -// PostProcessRenderer defines an interface for external renderers -type ExternalRenderer interface { +type SanitizerDisabledRenderer interface { // SanitizerDisabled disabled sanitize if return true SanitizerDisabled() bool +} + +// PostProcessRenderer defines an interface for external renderers +type ExternalRenderer interface { + SanitizerDisabledRenderer // DisplayInIFrame represents whether render the content with an iframe DisplayInIFrame() bool } +type NewPageRenderer interface { + DisplayInNewPage() bool +} + // RendererContentDetector detects if the content can be rendered // by specified renderer type RendererContentDetector interface { @@ -201,19 +205,19 @@ func DetectRendererType(filename string, input io.Reader) string { } // Render renders markup file to HTML with all specific handling stuff. -func Render(ctx *RenderContext, input io.Reader, output io.Writer) (*RenderResponse, error) { +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 nil, errors.New("Render options both filename and type missing") + 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 - _, err := Render(ctx, strings.NewReader(content), &buf) + err := Render(ctx, strings.NewReader(content), &buf) if err != nil { return "", err } @@ -226,7 +230,7 @@ type nopCloser struct { func (nopCloser) Close() error { return nil } -func renderIFrame(ctx *RenderContext, output io.Writer) (*RenderResponse, error) { +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 @@ -244,10 +248,10 @@ sandbox="allow-same-origin" ctx.Metas["BranchNameSubURL"], url.PathEscape(ctx.RelativePath), )) - return nil, err + return err } -func render(ctx *RenderContext, renderer Renderer, input io.Reader, output io.Writer) (*RenderResponse, error) { +func render(ctx *RenderContext, renderer Renderer, input io.Reader, output io.Writer) error { var wg sync.WaitGroup var err error pr, pw := io.Pipe() @@ -260,7 +264,7 @@ func render(ctx *RenderContext, renderer Renderer, input io.Reader, output io.Wr var pw2 io.WriteCloser var sanitizerDisabled bool - if r, ok := renderer.(ExternalRenderer); ok { + if r, ok := renderer.(SanitizerDisabledRenderer); ok { sanitizerDisabled = r.SanitizerDisabled() } @@ -293,14 +297,14 @@ func render(ctx *RenderContext, renderer Renderer, input io.Reader, output io.Wr wg.Done() }() - resp, err1 := renderer.Render(ctx, input, pw) + err1 := renderer.Render(ctx, input, pw) if err1 != nil { - return nil, err1 + return err1 } _ = pw.Close() wg.Wait() - return resp, err + return err } // ErrUnsupportedRenderType represents @@ -312,11 +316,11 @@ func (err ErrUnsupportedRenderType) Error() string { return fmt.Sprintf("Unsupported render type: %s", err.Type) } -func renderByType(ctx *RenderContext, input io.Reader, output io.Writer) (*RenderResponse, error) { +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 nil, ErrUnsupportedRenderType{ctx.Type} + return ErrUnsupportedRenderType{ctx.Type} } // ErrUnsupportedRenderFile represents the error when extension or filename doesn't supported to render @@ -333,10 +337,30 @@ func (err ErrUnsupportedRenderFile) Error() string { return fmt.Sprintf("Unsupported render file: %s", err.RelativePath) } -func renderFile(ctx *RenderContext, input io.Reader, output io.Writer) (*RenderResponse, error) { +func renderButton(ctx *RenderContext, output io.Writer) error { + _, err := io.WriteString(output, fmt.Sprintf(` +
+View in New Page +
`, + setting.AppURL, + url.PathEscape(ctx.Metas["user"]), + url.PathEscape(ctx.Metas["repo"]), + ctx.Metas["BranchNameSubURL"], + url.PathEscape(ctx.RelativePath), + )) + return err +} + +func renderFile(ctx *RenderContext, input io.Reader, output io.Writer) error { renderer := GetRendererByFileName(ctx.RelativePath) if renderer == nil { - return nil, ErrUnsupportedRenderFile{ctx.RelativePath} + return ErrUnsupportedRenderFile{ctx.RelativePath} + } + + if r, ok := renderer.(NewPageRenderer); ok && r.DisplayInNewPage() { + if !ctx.InStandalonePage { + return renderButton(ctx, output) + } } if r, ok := renderer.(ExternalRenderer); ok && r.DisplayInIFrame() { diff --git a/routers/common/markup.go b/routers/common/markup.go index 0f979da365c7e..f2f6a014e1b0a 100644 --- a/routers/common/markup.go +++ b/routers/common/markup.go @@ -74,7 +74,7 @@ func RenderMarkup(ctx *context.Base, repo *context.Repository, mode, text, urlPr meta["mode"] = "document" } - resp, err := markup.Render(&markup.RenderContext{ + err := markup.Render(&markup.RenderContext{ Ctx: ctx, URLPrefix: urlPrefix, Metas: meta, @@ -88,9 +88,5 @@ func RenderMarkup(ctx *context.Base, repo *context.Repository, mode, text, urlPr } else { ctx.Error(http.StatusInternalServerError, err.Error()) } - return - } - if resp != nil { - ctx.Data["ExtraStyleFiles"] = resp.ExtraStyleFiles } } diff --git a/routers/web/repo/render.go b/routers/web/repo/render.go index f0293fa35e795..8705335c78c5b 100644 --- a/routers/web/repo/render.go +++ b/routers/web/repo/render.go @@ -62,11 +62,11 @@ func RenderFile(ctx *context.Context) { treeLink += "/" + util.PathEscapeSegments(ctx.Repo.TreePath) } - ctx.Resp.Header().Add("Content-Security-Policy", "frame-src 'self'; sandbox allow-same-origin") + ctx.Resp.Header().Add("Content-Security-Policy", "frame-src 'self'; sandbox allow-same-origin allow-scripts") ctx.Resp.Header().Add("Access-Control-Allow-Origin", "http://192.168.8.18:3000") metaData := ctx.Repo.Repository.ComposeDocumentMetas() metaData["BranchNameSubURL"] = ctx.Repo.BranchNameSubURL() - resp, err := markup.Render(&markup.RenderContext{ + err = markup.Render(&markup.RenderContext{ Ctx: ctx, RelativePath: ctx.Repo.TreePath, URLPrefix: path.Dir(treeLink), @@ -76,10 +76,5 @@ func RenderFile(ctx *context.Context) { }, rd, ctx.Resp) if err != nil { ctx.ServerError("Render", err) - return - } - - if resp != nil { - ctx.Data["ExtraStyleFiles"] = resp.ExtraStyleFiles } } diff --git a/routers/web/repo/view.go b/routers/web/repo/view.go index 37a8efc4e6831..a1c82f5395d77 100644 --- a/routers/web/repo/view.go +++ b/routers/web/repo/view.go @@ -299,7 +299,7 @@ func renderReadmeFile(ctx *context.Context, subfolder string, readmeFile *git.Tr ctx.Data["IsMarkup"] = true ctx.Data["MarkupType"] = markupType - ctx.Data["RenderResponse"], ctx.Data["EscapeStatus"], ctx.Data["FileContent"], err = markupRender(ctx, &markup.RenderContext{ + ctx.Data["EscapeStatus"], ctx.Data["FileContent"], err = markupRender(ctx, &markup.RenderContext{ Ctx: ctx, RelativePath: path.Join(ctx.Repo.TreePath, readmeFile.Name()), // ctx.Repo.TreePath is the directory not the Readme so we must append the Readme filename (and path). URLPrefix: path.Join(readmeTreelink, subfolder), @@ -462,7 +462,7 @@ func renderFile(ctx *context.Context, entry *git.TreeEntry, treeLink, rawLink st } metas := ctx.Repo.Repository.ComposeDocumentMetas() metas["BranchNameSubURL"] = ctx.Repo.BranchNameSubURL() - ctx.Data["RenderResponse"], ctx.Data["EscapeStatus"], ctx.Data["FileContent"], err = markupRender(ctx, &markup.RenderContext{ + ctx.Data["EscapeStatus"], ctx.Data["FileContent"], err = markupRender(ctx, &markup.RenderContext{ Ctx: ctx, Type: markupType, RelativePath: ctx.Repo.TreePath, @@ -564,7 +564,7 @@ func renderFile(ctx *context.Context, entry *git.TreeEntry, treeLink, rawLink st rd := io.MultiReader(bytes.NewReader(buf), dataRc) ctx.Data["IsMarkup"] = true ctx.Data["MarkupType"] = markupType - ctx.Data["RenderResponse"], ctx.Data["EscapeStatus"], ctx.Data["FileContent"], err = markupRender(ctx, &markup.RenderContext{ + ctx.Data["EscapeStatus"], ctx.Data["FileContent"], err = markupRender(ctx, &markup.RenderContext{ Ctx: ctx, RelativePath: ctx.Repo.TreePath, URLPrefix: path.Dir(treeLink), @@ -593,7 +593,7 @@ func renderFile(ctx *context.Context, entry *git.TreeEntry, treeLink, rawLink st } } -func markupRender(ctx *context.Context, renderCtx *markup.RenderContext, input io.Reader) (renderResp *markup.RenderResponse, escaped *charset.EscapeStatus, output string, err error) { +func markupRender(ctx *context.Context, renderCtx *markup.RenderContext, input io.Reader) (escaped *charset.EscapeStatus, output string, err error) { markupRd, markupWr := io.Pipe() defer markupWr.Close() done := make(chan struct{}) @@ -604,10 +604,10 @@ func markupRender(ctx *context.Context, renderCtx *markup.RenderContext, input i output = sb.String() close(done) }() - resp, err := markup.Render(renderCtx, input, markupWr) + err = markup.Render(renderCtx, input, markupWr) _ = markupWr.CloseWithError(err) <-done - return resp, escaped, output, err + return escaped, output, err } func safeURL(address string) string { diff --git a/templates/base/head_style.tmpl b/templates/base/head_style.tmpl index 6188c2278d1d0..7e8cba2aedde0 100644 --- a/templates/base/head_style.tmpl +++ b/templates/base/head_style.tmpl @@ -6,8 +6,3 @@ {{else if ne DefaultTheme "gitea"}} {{end}} -{{if .RenderResponse}} - {{range .RenderResponse.ExtraStyleFiles}} - - {{end}} -{{end}} From 8e3dd7e00f1e99a473f7cc9d04ebf634e4990332 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Sun, 23 Jul 2023 17:53:07 +0800 Subject: [PATCH 07/15] revert unnecessary change --- modules/markup/renderer.go | 10 ++++------ routers/common/markup.go | 5 ++--- routers/web/repo/render.go | 4 ++-- routers/web/web.go | 8 ++------ 4 files changed, 10 insertions(+), 17 deletions(-) diff --git a/modules/markup/renderer.go b/modules/markup/renderer.go index dc883cf8cc06d..e41b2156542f5 100644 --- a/modules/markup/renderer.go +++ b/modules/markup/renderer.go @@ -217,8 +217,7 @@ func Render(ctx *RenderContext, input io.Reader, output io.Writer) error { // 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 - err := Render(ctx, strings.NewReader(content), &buf) - if err != nil { + if err := Render(ctx, strings.NewReader(content), &buf); err != nil { return "", err } return buf.String(), nil @@ -240,9 +239,9 @@ func renderIFrame(ctx *RenderContext, output io.Writer) error { name="giteaExternalRender" onload="this.height=giteaExternalRender.document.documentElement.scrollHeight" width="100%%" height="0" scrolling="no" frameborder="0" style="overflow: hidden" -sandbox="allow-same-origin" +sandbox="allow-scripts" >`, - setting.AppURL, + setting.AppSubURL, url.PathEscape(ctx.Metas["user"]), url.PathEscape(ctx.Metas["repo"]), ctx.Metas["BranchNameSubURL"], @@ -297,8 +296,7 @@ func render(ctx *RenderContext, renderer Renderer, input io.Reader, output io.Wr wg.Done() }() - err1 := renderer.Render(ctx, input, pw) - if err1 != nil { + if err1 := renderer.Render(ctx, input, pw); err1 != nil { return err1 } _ = pw.Close() diff --git a/routers/common/markup.go b/routers/common/markup.go index f2f6a014e1b0a..17eb4b7f6ba0c 100644 --- a/routers/common/markup.go +++ b/routers/common/markup.go @@ -74,15 +74,14 @@ func RenderMarkup(ctx *context.Base, repo *context.Repository, mode, text, urlPr meta["mode"] = "document" } - err := markup.Render(&markup.RenderContext{ + if err := markup.Render(&markup.RenderContext{ Ctx: ctx, URLPrefix: urlPrefix, Metas: meta, IsWiki: wiki, Type: markupType, RelativePath: relativePath, - }, strings.NewReader(text), ctx.Resp) - if err != nil { + }, strings.NewReader(text), ctx.Resp); err != nil { if markup.IsErrUnsupportedRenderFile(err) { ctx.Error(http.StatusUnprocessableEntity, err.Error()) } else { diff --git a/routers/web/repo/render.go b/routers/web/repo/render.go index 8705335c78c5b..1027e46bd6aee 100644 --- a/routers/web/repo/render.go +++ b/routers/web/repo/render.go @@ -62,8 +62,7 @@ func RenderFile(ctx *context.Context) { treeLink += "/" + util.PathEscapeSegments(ctx.Repo.TreePath) } - ctx.Resp.Header().Add("Content-Security-Policy", "frame-src 'self'; sandbox allow-same-origin allow-scripts") - ctx.Resp.Header().Add("Access-Control-Allow-Origin", "http://192.168.8.18:3000") + ctx.Resp.Header().Add("Content-Security-Policy", "frame-src 'self'; sandbox allow-scripts allow-same-origin") metaData := ctx.Repo.Repository.ComposeDocumentMetas() metaData["BranchNameSubURL"] = ctx.Repo.BranchNameSubURL() err = markup.Render(&markup.RenderContext{ @@ -76,5 +75,6 @@ func RenderFile(ctx *context.Context) { }, rd, ctx.Resp) if err != nil { ctx.ServerError("Render", err) + return } } diff --git a/routers/web/web.go b/routers/web/web.go index 266f224e9ce14..6d5ccad484fa0 100644 --- a/routers/web/web.go +++ b/routers/web/web.go @@ -1310,9 +1310,7 @@ func registerRoutes(m *web.Route) { m.Get("/blob/{sha}", context.RepoRefByType(context.RepoRefBlob), repo.DownloadByID) // "/*" route is deprecated, and kept for backward compatibility m.Get("/*", context.RepoRefByType(context.RepoRefLegacy), repo.SingleDownload) - }, repo.MustBeNotEmpty, reqRepoCodeReader, func(ctx *context.Context) { - ctx.Resp.Header().Add("Access-Control-Allow-Origin", "*") - }) + }, repo.MustBeNotEmpty, reqRepoCodeReader) m.Group("/render", func() { m.Get("/branch/*", context.RepoRefByType(context.RepoRefBranch), repo.RenderFile) @@ -1350,9 +1348,7 @@ func registerRoutes(m *web.Route) { m.Get("/commit/*", context.RepoRefByType(context.RepoRefCommit), repo.Home) // "/*" route is deprecated, and kept for backward compatibility m.Get("/*", context.RepoRefByType(context.RepoRefLegacy), repo.Home) - }, repo.SetEditorconfigIfExists, func(ctx *context.Context) { - ctx.Resp.Header().Add("Access-Control-Allow-Origin", "*") - }) + }, repo.SetEditorconfigIfExists) m.Group("", func() { m.Get("/forks", repo.Forks) From e5d1149112ea6f870ab8ab98d7e59e5d29bd9c1b Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Sun, 23 Jul 2023 19:03:18 +0800 Subject: [PATCH 08/15] Fix test --- routers/api/v1/misc/markup_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/routers/api/v1/misc/markup_test.go b/routers/api/v1/misc/markup_test.go index bab06b3e6626a..f3f730cd73827 100644 --- a/routers/api/v1/misc/markup_test.go +++ b/routers/api/v1/misc/markup_test.go @@ -134,7 +134,7 @@ Here are some links to the most important topics. You can find the full list of testRenderMarkup(t, "file", "path/test.md", text, response, http.StatusOK) } - testRenderMarkup(t, "file", "path/test.unknown", "## Test", "Unsupported render extension: .unknown\n", http.StatusUnprocessableEntity) + testRenderMarkup(t, "file", "path/test.unknown", "## Test", "Unsupported render file: path/test.unknown\n", http.StatusUnprocessableEntity) testRenderMarkup(t, "unknown", "", "## Test", "Unknown mode: unknown\n", http.StatusUnprocessableEntity) } From 767ea4eabc64cd7029ddd3e0db3cfbe36e75af1b Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Wed, 26 Jul 2023 17:34:28 +0800 Subject: [PATCH 09/15] add comment for i18n --- modules/markup/openapi/openapi.go | 14 ++++++------ modules/markup/renderer.go | 38 +++++++++++++++---------------- 2 files changed, 25 insertions(+), 27 deletions(-) diff --git a/modules/markup/openapi/openapi.go b/modules/markup/openapi/openapi.go index 5635d74243cca..13d0a5f1cc9ad 100644 --- a/modules/markup/openapi/openapi.go +++ b/modules/markup/openapi/openapi.go @@ -18,11 +18,13 @@ func init() { markup.RegisterRenderer(Renderer{}) } -// Renderer implements markup.Renderer for asciicast files. -// See https://github.com/asciinema/asciinema/blob/develop/doc/asciicast-v2.md +// Renderer implements markup.Renderer for openapi files. type Renderer struct{} -var _ markup.GlobMatchRenderer = (*Renderer)(nil) +var ( + _ markup.RendererRelativePathDetector = (*Renderer)(nil) + g = glob.MustCompile("**{openapi,OpenAPI,swagger}.{yml,yaml,json,JSON,Yaml,YML}", '/') +) // Name implements markup.Renderer func (Renderer) Name() string { @@ -38,10 +40,8 @@ func (Renderer) DisplayInNewPage() bool { return true } -func (Renderer) MatchGlobs() []glob.Glob { - return []glob.Glob{ - glob.MustCompile("**{openapi,OpenAPI,swagger}.{yml,yaml,json,JSON,Yaml,YML}", '/'), - } +func (Renderer) CanRenderRelativePath(relativePath string) bool { + return g.Match(relativePath) } // Extensions implements markup.Renderer diff --git a/modules/markup/renderer.go b/modules/markup/renderer.go index e41b2156542f5..6d58368ca6817 100644 --- a/modules/markup/renderer.go +++ b/modules/markup/renderer.go @@ -17,7 +17,6 @@ import ( "code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/setting" - "github.com/gobwas/glob" "github.com/yuin/goldmark/ast" ) @@ -117,10 +116,6 @@ type Renderer interface { Render(ctx *RenderContext, input io.Reader, output io.Writer) error } -type GlobMatchRenderer interface { - MatchGlobs() []glob.Glob -} - // PostProcessRenderer defines an interface for renderers who need post process type PostProcessRenderer interface { NeedPostProcess() bool @@ -149,10 +144,14 @@ type RendererContentDetector interface { CanRender(filename string, input io.Reader) bool } +// RendererRelativePathDetector detects if the content can be rendered according relative file path +type RendererRelativePathDetector interface { + CanRenderRelativePath(relativePath string) bool +} + var ( - extRenderers = make(map[string]Renderer) - globMatchRenderers = make([]GlobMatchRenderer, 0) - renderers = make(map[string]Renderer) + extRenderers = make(map[string]Renderer) + renderers = make(map[string]Renderer) ) // RegisterRenderer registers a new markup file renderer @@ -161,10 +160,6 @@ func RegisterRenderer(renderer Renderer) { for _, ext := range renderer.Extensions() { extRenderers[strings.ToLower(ext)] = renderer } - gmRenderer, ok := renderer.(GlobMatchRenderer) - if ok { - globMatchRenderers = append(globMatchRenderers, gmRenderer) - } } // GetRendererByFileName get renderer by filename @@ -174,12 +169,14 @@ func GetRendererByFileName(filename string) Renderer { if renderer != nil { return renderer } + return GetRendererByRelativePathInterface(filename) +} - for _, gmRenderer := range globMatchRenderers { - for _, mg := range gmRenderer.MatchGlobs() { - if mg.Match(filename) { - return gmRenderer.(Renderer) - } +// GetRendererByRelativePathInterface returns a renderer according relative file path +func GetRendererByRelativePathInterface(relativePath string) Renderer { + for _, renderer := range renderers { + if detector, ok := renderer.(RendererRelativePathDetector); ok && detector.CanRenderRelativePath(relativePath) { + return renderer } } return nil @@ -235,7 +232,7 @@ func renderIFrame(ctx *RenderContext, output io.Writer) error { // "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(` -`, setting.AppSubURL, url.PathEscape(ctx.Metas["user"]), url.PathEscape(ctx.Metas["repo"]), ctx.Metas["BranchNameSubURL"], url.PathEscape(ctx.RelativePath), - "View in New Page", // TODO: how to get to know the i18n here? )) return err } diff --git a/web_src/css/base.css b/web_src/css/base.css index c0176cc437f13..20c6d75a63e63 100644 --- a/web_src/css/base.css +++ b/web_src/css/base.css @@ -22,6 +22,7 @@ --opacity-disabled: 0.55; --height-loading: 16rem; --tab-size: 4; + --render-height: 600px; /* base colors */ --color-primary: #4183c4; --color-primary-contrast: #ffffff; diff --git a/web_src/css/repo.css b/web_src/css/repo.css index eb3f98774182f..f09d98b7d9599 100644 --- a/web_src/css/repo.css +++ b/web_src/css/repo.css @@ -440,7 +440,7 @@ .pdf-content { width: 100%; - height: 600px; + height: var(--render-height); border: none !important; display: flex; align-items: center; @@ -1900,6 +1900,17 @@ .file-view.markup { padding: 1em 2em; } + +.file-view.openapi { + padding: 0; +} + +.file-view.openapi iframe { + width: 100%; + height: var(--render-height); + border: none; +} + .repository .activity-header { display: flex; justify-content: space-between; From 8470c30f2fcee56fdfcc648b01fdee709e2176dd Mon Sep 17 00:00:00 2001 From: silverwind Date: Sat, 13 Apr 2024 22:31:48 +0200 Subject: [PATCH 15/15] fix --- routers/web/repo/render.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/routers/web/repo/render.go b/routers/web/repo/render.go index f0f8e22f3da00..32b8169f58d8b 100644 --- a/routers/web/repo/render.go +++ b/routers/web/repo/render.go @@ -56,7 +56,7 @@ func RenderFile(ctx *context.Context) { return } - metaData := ctx.Repo.Repository.ComposeDocumentMetas() + metaData := ctx.Repo.Repository.ComposeDocumentMetas(ctx) metaData["BranchNameSubURL"] = ctx.Repo.BranchNameSubURL() err = markup.Render(&markup.RenderContext{