mirror of
https://github.com/hanzoai/forge.git
synced 2026-10-30 15:00:39 +00:00
Source files keep their SPDX-License-Identifier and Hanzo copyright line; the Gitea, Gogs and LFS Test Server copyrights they used to repeat now live once in NOTICE, alongside LICENSE. The header lint accepts any copyright line above the SPDX line, so it no longer demands an upstream one. Co-authored-by: Hanzo Dev <[email protected]>
107 lines
4.9 KiB
Go
107 lines
4.9 KiB
Go
// Copyright 2026 Hanzo AI, Inc. All rights reserved.
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
package common
|
|
|
|
import (
|
|
"errors"
|
|
"net/http"
|
|
"path"
|
|
"strings"
|
|
|
|
"github.com/hanzoai/git/models/renderhelper"
|
|
"github.com/hanzoai/git/models/repo"
|
|
"github.com/hanzoai/git/modules/log"
|
|
"github.com/hanzoai/git/modules/markup"
|
|
"github.com/hanzoai/git/modules/markup/markdown"
|
|
"github.com/hanzoai/git/modules/setting"
|
|
"github.com/hanzoai/git/modules/util"
|
|
"github.com/hanzoai/git/services/context"
|
|
)
|
|
|
|
// RenderMarkup renders markup text for the /markup and /markdown endpoints
|
|
func RenderMarkup(ctx *context.Base, ctxRepo *context.Repository, mode, text, urlPathContext, filePath string) {
|
|
// urlPathContext format is "/subpath/{user}/{repo}/src/{branch, commit, tag}/{identifier/path}/{file/dir}"
|
|
// filePath is the path of the file to render if the end user is trying to preview a repo file (mode == "file")
|
|
// filePath will be used as RenderContext.RelativePath
|
|
|
|
// TODO: MARKUP-RENDER-CONTEXT: this logic is unnecessarily complicated.
|
|
// Ideally: the "file path" should not appear in the "url path context", but it needs a lot of refactoring to achieve that
|
|
// for example, when previewing file "/forge/owner/repo/src/branch/features/feat-123/doc/CHANGE.md", then filePath is "doc/CHANGE.md"
|
|
// and the urlPathContext is "/forge/owner/repo/src/branch/features/feat-123/doc"
|
|
|
|
if mode == "" || mode == "markdown" {
|
|
// raw Markdown doesn't do any special handling
|
|
// TODO: raw markdown doesn't do any link processing, so "urlPathContext" doesn't take effect
|
|
rctx := renderhelper.NewRenderContextSimpleDocument(ctx, urlPathContext).WithUseAbsoluteLink(true).
|
|
WithMarkupType(markdown.MarkupName)
|
|
if err := markdown.RenderRaw(rctx, strings.NewReader(text), ctx.Resp); err != nil {
|
|
log.Error("RenderMarkupRaw: %v", err)
|
|
ctx.HTTPError(http.StatusInternalServerError, "failed to render raw markup")
|
|
}
|
|
return
|
|
}
|
|
|
|
// Ideally, this handler should be called with RepoAssignment and get the related repo from context "/owner/repo/markup"
|
|
// then render could use the repo to do various things (the permission check has passed)
|
|
//
|
|
// However, this handler is also exposed as "/markup" without any repo context,
|
|
// then since there is no permission check, so we can't use the repo from "context" parameter,
|
|
// in this case, only the "path" information could be used which doesn't cause security problems.
|
|
var repoModel *repo.Repository
|
|
if ctxRepo != nil {
|
|
repoModel = ctxRepo.Repository
|
|
}
|
|
var repoOwnerName, repoName, refPath, treePath string
|
|
repoLinkPath := strings.TrimPrefix(urlPathContext, setting.AppSubURL+"/")
|
|
fields := strings.SplitN(repoLinkPath, "/", 5)
|
|
if len(fields) == 5 && fields[2] == "src" && (fields[3] == "branch" || fields[3] == "commit" || fields[3] == "tag") {
|
|
// absolute base prefix is something like "https://host/subpath/{user}/{repo}"
|
|
repoOwnerName, repoName = fields[0], fields[1]
|
|
treePath = path.Dir(filePath) // it is "doc" if filePath is "doc/CHANGE.md"
|
|
refPath = strings.Join(fields[3:], "/") // it is "branch/features/feat-12/doc"
|
|
refPath = strings.TrimSuffix(refPath, "/"+treePath) // now we get the correct branch path: "branch/features/feat-12"
|
|
} else if fields = strings.SplitN(repoLinkPath, "/", 3); len(fields) == 2 {
|
|
repoOwnerName, repoName = fields[0], fields[1]
|
|
}
|
|
|
|
var rctx *markup.RenderContext
|
|
switch mode {
|
|
case "gfm": // legacy mode
|
|
rctx = renderhelper.NewRenderContextRepoFile(ctx, repoModel, renderhelper.RepoFileOptions{
|
|
DeprecatedOwnerName: repoOwnerName, DeprecatedRepoName: repoName,
|
|
CurrentRefSubURL: refPath, CurrentTreePath: treePath,
|
|
})
|
|
rctx = rctx.WithMarkupType(markdown.MarkupName)
|
|
case "comment":
|
|
rctx = renderhelper.NewRenderContextRepoComment(ctx, repoModel, renderhelper.RepoCommentOptions{
|
|
DeprecatedOwnerName: repoOwnerName,
|
|
DeprecatedRepoName: repoName,
|
|
FootnoteContextID: "preview",
|
|
})
|
|
rctx = rctx.WithMarkupType(markdown.MarkupName)
|
|
case "wiki":
|
|
rctx = renderhelper.NewRenderContextRepoWiki(ctx, repoModel, renderhelper.RepoWikiOptions{DeprecatedOwnerName: repoOwnerName, DeprecatedRepoName: repoName})
|
|
rctx = rctx.WithMarkupType(markdown.MarkupName)
|
|
case "file":
|
|
rctx = renderhelper.NewRenderContextRepoFile(ctx, repoModel, renderhelper.RepoFileOptions{
|
|
DeprecatedOwnerName: repoOwnerName, DeprecatedRepoName: repoName,
|
|
CurrentRefSubURL: refPath, CurrentTreePath: treePath,
|
|
})
|
|
rctx = rctx.WithMarkupType("").WithRelativePath(filePath) // render the repo file content by its extension
|
|
default:
|
|
ctx.HTTPError(http.StatusUnprocessableEntity, "unsupported render mode: "+mode)
|
|
return
|
|
}
|
|
rctx = rctx.WithUseAbsoluteLink(true)
|
|
if err := markup.Render(rctx, strings.NewReader(text), ctx.Resp); err != nil {
|
|
if errors.Is(err, util.ErrInvalidArgument) {
|
|
ctx.HTTPError(http.StatusUnprocessableEntity, err.Error())
|
|
} else {
|
|
log.Error("RenderMarkup: %v", err)
|
|
ctx.HTTPError(http.StatusInternalServerError, "failed to render markup")
|
|
}
|
|
return
|
|
}
|
|
}
|