diff --git a/main.go b/main.go index 775c729c569ea..d664531ef8aa8 100644 --- a/main.go +++ b/main.go @@ -19,6 +19,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/v2" diff --git a/modules/markup/openapi/openapi.go b/modules/markup/openapi/openapi.go new file mode 100644 index 0000000000000..13d0a5f1cc9ad --- /dev/null +++ b/modules/markup/openapi/openapi.go @@ -0,0 +1,90 @@ +// 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 openapi files. +type Renderer struct{} + +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 { + return "openapi" +} + +// SanitizerDisabled disabled sanitize if return true +func (Renderer) SanitizerDisabled() bool { + return true +} + +func (Renderer) DisplayInNewPage() bool { + return true +} + +func (Renderer) CanRenderRelativePath(relativePath string) bool { + return g.Match(relativePath) +} + +// 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"], + ctx.RelativePath, + ) + + if _, err := io.WriteString(output, fmt.Sprintf( + ` + +
+ + + + + + + +`, + setting.StaticURLPrefix, + setting.AssetVersion, + rawURL, + setting.StaticURLPrefix, + setting.AssetVersion, + )); err != nil { + return err + } + return nil +} diff --git a/modules/markup/renderer.go b/modules/markup/renderer.go index 005fcc278b973..d6867d15726c9 100644 --- a/modules/markup/renderer.go +++ b/modules/markup/renderer.go @@ -172,21 +172,34 @@ 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 { 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) renderers = make(map[string]Renderer) @@ -203,7 +216,21 @@ func RegisterRenderer(renderer Renderer) { // 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 + } + return GetRendererByRelativePathInterface(filename) +} + +// 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 } // GetRendererByType returns a renderer according type @@ -284,7 +311,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() } @@ -342,33 +369,51 @@ 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 renderButton(ctx *RenderContext, output io.Writer) error { + _, 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), + )) + return err } 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