gitea源码

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108
  1. // Copyright 2014 The Gogs Authors. All rights reserved.
  2. // Copyright 2023 The Gitea Authors. All rights reserved.
  3. // SPDX-License-Identifier: MIT
  4. package common
  5. import (
  6. "errors"
  7. "fmt"
  8. "net/http"
  9. "path"
  10. "strings"
  11. "code.gitea.io/gitea/models/renderhelper"
  12. "code.gitea.io/gitea/models/repo"
  13. "code.gitea.io/gitea/modules/httplib"
  14. "code.gitea.io/gitea/modules/markup"
  15. "code.gitea.io/gitea/modules/markup/markdown"
  16. "code.gitea.io/gitea/modules/setting"
  17. "code.gitea.io/gitea/modules/util"
  18. "code.gitea.io/gitea/services/context"
  19. )
  20. // RenderMarkup renders markup text for the /markup and /markdown endpoints
  21. func RenderMarkup(ctx *context.Base, ctxRepo *context.Repository, mode, text, urlPathContext, filePath string) {
  22. // urlPathContext format is "/subpath/{user}/{repo}/src/{branch, commit, tag}/{identifier/path}/{file/dir}"
  23. // filePath is the path of the file to render if the end user is trying to preview a repo file (mode == "file")
  24. // filePath will be used as RenderContext.RelativePath
  25. // for example, when previewing file "/gitea/owner/repo/src/branch/features/feat-123/doc/CHANGE.md", then filePath is "doc/CHANGE.md"
  26. // and the urlPathContext is "/gitea/owner/repo/src/branch/features/feat-123/doc"
  27. if mode == "" || mode == "markdown" {
  28. // raw markdown doesn't need any special handling
  29. baseLink := urlPathContext
  30. if baseLink == "" {
  31. baseLink = fmt.Sprintf("%s%s", httplib.GuessCurrentHostURL(ctx), urlPathContext)
  32. }
  33. rctx := renderhelper.NewRenderContextSimpleDocument(ctx, baseLink).WithUseAbsoluteLink(true).
  34. WithMarkupType(markdown.MarkupName)
  35. if err := markdown.RenderRaw(rctx, strings.NewReader(text), ctx.Resp); err != nil {
  36. ctx.HTTPError(http.StatusInternalServerError, err.Error())
  37. }
  38. return
  39. }
  40. // Ideally, this handler should be called with RepoAssigment and get the related repo from context "/owner/repo/markup"
  41. // then render could use the repo to do various things (the permission check has passed)
  42. //
  43. // However, this handler is also exposed as "/markup" without any repo context,
  44. // then since there is no permission check, so we can't use the repo from "context" parameter,
  45. // in this case, only the "path" information could be used which doesn't cause security problems.
  46. var repoModel *repo.Repository
  47. if ctxRepo != nil {
  48. repoModel = ctxRepo.Repository
  49. }
  50. var repoOwnerName, repoName, refPath, treePath string
  51. repoLinkPath := strings.TrimPrefix(urlPathContext, setting.AppSubURL+"/")
  52. fields := strings.SplitN(repoLinkPath, "/", 5)
  53. if len(fields) == 5 && fields[2] == "src" && (fields[3] == "branch" || fields[3] == "commit" || fields[3] == "tag") {
  54. // absolute base prefix is something like "https://host/subpath/{user}/{repo}"
  55. repoOwnerName, repoName = fields[0], fields[1]
  56. treePath = path.Dir(filePath) // it is "doc" if filePath is "doc/CHANGE.md"
  57. refPath = strings.Join(fields[3:], "/") // it is "branch/features/feat-12/doc"
  58. refPath = strings.TrimSuffix(refPath, "/"+treePath) // now we get the correct branch path: "branch/features/feat-12"
  59. } else if fields = strings.SplitN(repoLinkPath, "/", 3); len(fields) == 2 {
  60. repoOwnerName, repoName = fields[0], fields[1]
  61. }
  62. var rctx *markup.RenderContext
  63. switch mode {
  64. case "gfm": // legacy mode
  65. rctx = renderhelper.NewRenderContextRepoFile(ctx, repoModel, renderhelper.RepoFileOptions{
  66. DeprecatedOwnerName: repoOwnerName, DeprecatedRepoName: repoName,
  67. CurrentRefPath: refPath, CurrentTreePath: treePath,
  68. })
  69. rctx = rctx.WithMarkupType(markdown.MarkupName)
  70. case "comment":
  71. rctx = renderhelper.NewRenderContextRepoComment(ctx, repoModel, renderhelper.RepoCommentOptions{
  72. DeprecatedOwnerName: repoOwnerName,
  73. DeprecatedRepoName: repoName,
  74. FootnoteContextID: "preview",
  75. })
  76. rctx = rctx.WithMarkupType(markdown.MarkupName)
  77. case "wiki":
  78. rctx = renderhelper.NewRenderContextRepoWiki(ctx, repoModel, renderhelper.RepoWikiOptions{DeprecatedOwnerName: repoOwnerName, DeprecatedRepoName: repoName})
  79. rctx = rctx.WithMarkupType(markdown.MarkupName)
  80. case "file":
  81. rctx = renderhelper.NewRenderContextRepoFile(ctx, repoModel, renderhelper.RepoFileOptions{
  82. DeprecatedOwnerName: repoOwnerName, DeprecatedRepoName: repoName,
  83. CurrentRefPath: refPath, CurrentTreePath: treePath,
  84. })
  85. rctx = rctx.WithMarkupType("").WithRelativePath(filePath) // render the repo file content by its extension
  86. default:
  87. ctx.HTTPError(http.StatusUnprocessableEntity, "Unknown mode: "+mode)
  88. return
  89. }
  90. rctx = rctx.WithUseAbsoluteLink(true)
  91. if err := markup.Render(rctx, strings.NewReader(text), ctx.Resp); err != nil {
  92. if errors.Is(err, util.ErrInvalidArgument) {
  93. ctx.HTTPError(http.StatusUnprocessableEntity, err.Error())
  94. } else {
  95. ctx.HTTPError(http.StatusInternalServerError, err.Error())
  96. }
  97. return
  98. }
  99. }