windows Git Credential Manager安装后clone时浏览器弹窗屏蔽
This commit is contained in:
parent
97405de7d5
commit
5be5aa79ab
2
main.go
2
main.go
|
|
@ -22,7 +22,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
Version = "v2.4, by v1.21.0 "
|
Version = "v2.5, by v1.21.0 "
|
||||||
Tags = ""
|
Tags = ""
|
||||||
MakeVersion = ""
|
MakeVersion = ""
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,614 @@
|
||||||
|
// Copyright 2014 The Gogs Authors. All rights reserved.
|
||||||
|
// Copyright 2019 The Gitea Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package repo
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"compress/gzip"
|
||||||
|
gocontext "context"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
"regexp"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
actions_model "code.gitea.io/gitea/models/actions"
|
||||||
|
auth_model "code.gitea.io/gitea/models/auth"
|
||||||
|
"code.gitea.io/gitea/models/perm"
|
||||||
|
access_model "code.gitea.io/gitea/models/perm/access"
|
||||||
|
repo_model "code.gitea.io/gitea/models/repo"
|
||||||
|
"code.gitea.io/gitea/models/unit"
|
||||||
|
"code.gitea.io/gitea/modules/context"
|
||||||
|
"code.gitea.io/gitea/modules/git"
|
||||||
|
"code.gitea.io/gitea/modules/log"
|
||||||
|
repo_module "code.gitea.io/gitea/modules/repository"
|
||||||
|
"code.gitea.io/gitea/modules/setting"
|
||||||
|
"code.gitea.io/gitea/modules/structs"
|
||||||
|
"code.gitea.io/gitea/modules/util"
|
||||||
|
repo_service "code.gitea.io/gitea/services/repository"
|
||||||
|
|
||||||
|
"github.com/go-chi/cors"
|
||||||
|
)
|
||||||
|
|
||||||
|
func HTTPGitEnabledHandler(ctx *context.Context) {
|
||||||
|
if setting.Repository.DisableHTTPGit {
|
||||||
|
ctx.Resp.WriteHeader(http.StatusForbidden)
|
||||||
|
_, _ = ctx.Resp.Write([]byte("Interacting with repositories by HTTP protocol is not allowed"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func CorsHandler() func(next http.Handler) http.Handler {
|
||||||
|
if setting.Repository.AccessControlAllowOrigin != "" {
|
||||||
|
return cors.Handler(cors.Options{
|
||||||
|
AllowedOrigins: []string{setting.Repository.AccessControlAllowOrigin},
|
||||||
|
AllowedHeaders: []string{"Content-Type", "Authorization", "User-Agent"},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return func(next http.Handler) http.Handler {
|
||||||
|
return next
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// httpBase implementation git smart HTTP protocol
|
||||||
|
func httpBase(ctx *context.Context) *serviceHandler {
|
||||||
|
username := ctx.Params(":username")
|
||||||
|
reponame := strings.TrimSuffix(ctx.Params(":reponame"), ".git")
|
||||||
|
|
||||||
|
if ctx.FormString("go-get") == "1" {
|
||||||
|
context.EarlyResponseForGoGetMeta(ctx)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var isPull, receivePack bool
|
||||||
|
service := ctx.FormString("service")
|
||||||
|
if service == "git-receive-pack" ||
|
||||||
|
strings.HasSuffix(ctx.Req.URL.Path, "git-receive-pack") {
|
||||||
|
isPull = false
|
||||||
|
receivePack = true
|
||||||
|
} else if service == "git-upload-pack" ||
|
||||||
|
strings.HasSuffix(ctx.Req.URL.Path, "git-upload-pack") {
|
||||||
|
isPull = true
|
||||||
|
} else if service == "git-upload-archive" ||
|
||||||
|
strings.HasSuffix(ctx.Req.URL.Path, "git-upload-archive") {
|
||||||
|
isPull = true
|
||||||
|
} else {
|
||||||
|
isPull = ctx.Req.Method == "GET"
|
||||||
|
}
|
||||||
|
|
||||||
|
var accessMode perm.AccessMode
|
||||||
|
if isPull {
|
||||||
|
accessMode = perm.AccessModeRead
|
||||||
|
} else {
|
||||||
|
accessMode = perm.AccessModeWrite
|
||||||
|
}
|
||||||
|
|
||||||
|
isWiki := false
|
||||||
|
unitType := unit.TypeCode
|
||||||
|
var wikiRepoName string
|
||||||
|
if strings.HasSuffix(reponame, ".wiki") {
|
||||||
|
isWiki = true
|
||||||
|
unitType = unit.TypeWiki
|
||||||
|
wikiRepoName = reponame
|
||||||
|
reponame = reponame[:len(reponame)-5]
|
||||||
|
}
|
||||||
|
|
||||||
|
owner := ctx.ContextUser
|
||||||
|
if !owner.IsOrganization() && !owner.IsActive {
|
||||||
|
ctx.PlainText(http.StatusForbidden, "Repository cannot be accessed. You cannot push or open issues/pull-requests.")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
repoExist := true
|
||||||
|
repo, err := repo_model.GetRepositoryByName(owner.ID, reponame)
|
||||||
|
if err != nil {
|
||||||
|
if repo_model.IsErrRepoNotExist(err) {
|
||||||
|
if redirectRepoID, err := repo_model.LookupRedirect(owner.ID, reponame); err == nil {
|
||||||
|
context.RedirectToRepo(ctx.Base, redirectRepoID)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
repoExist = false
|
||||||
|
} else {
|
||||||
|
ctx.ServerError("GetRepositoryByName", err)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Don't allow pushing if the repo is archived
|
||||||
|
if repoExist && repo.IsArchived && !isPull {
|
||||||
|
ctx.PlainText(http.StatusForbidden, "This repo is archived. You can view files and clone it, but cannot push or open issues/pull-requests.")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only public pull don't need auth.
|
||||||
|
isPublicPull := repoExist && !repo.IsPrivate && isPull
|
||||||
|
var (
|
||||||
|
askAuth = !isPublicPull || setting.Service.RequireSignInView
|
||||||
|
environ []string
|
||||||
|
)
|
||||||
|
|
||||||
|
// don't allow anonymous pulls if organization is not public
|
||||||
|
if isPublicPull {
|
||||||
|
if err := repo.LoadOwner(ctx); err != nil {
|
||||||
|
ctx.ServerError("LoadOwner", err)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
askAuth = askAuth || (repo.Owner.Visibility != structs.VisibleTypePublic)
|
||||||
|
}
|
||||||
|
|
||||||
|
// check access
|
||||||
|
if askAuth {
|
||||||
|
// rely on the results of Contexter
|
||||||
|
if !ctx.IsSigned {
|
||||||
|
// TODO: support digit auth - which would be Authorization header with digit
|
||||||
|
//ctx.Resp.Header().Set("WWW-Authenticate", `Basic realm="Gitea"`)
|
||||||
|
ctx.Resp.Header().Set("WWW-Authenticate", "Basic realm=\".\"")
|
||||||
|
ctx.Error(http.StatusUnauthorized)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
context.CheckRepoScopedToken(ctx, repo, auth_model.GetScopeLevelFromAccessMode(accessMode))
|
||||||
|
if ctx.Written() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if ctx.IsBasicAuth && ctx.Data["IsApiToken"] != true && ctx.Data["IsActionsToken"] != true {
|
||||||
|
_, err = auth_model.GetTwoFactorByUID(ctx, ctx.Doer.ID)
|
||||||
|
if err == nil {
|
||||||
|
// TODO: This response should be changed to "invalid credentials" for security reasons once the expectation behind it (creating an app token to authenticate) is properly documented
|
||||||
|
ctx.PlainText(http.StatusUnauthorized, "Users with two-factor authentication enabled cannot perform HTTP/HTTPS operations via plain username and password. Please create and use a personal access token on the user settings page")
|
||||||
|
return nil
|
||||||
|
} else if !auth_model.IsErrTwoFactorNotEnrolled(err) {
|
||||||
|
ctx.ServerError("IsErrTwoFactorNotEnrolled", err)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !ctx.Doer.IsActive || ctx.Doer.ProhibitLogin {
|
||||||
|
ctx.PlainText(http.StatusForbidden, "Your account is disabled.")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
environ = []string{
|
||||||
|
repo_module.EnvRepoUsername + "=" + username,
|
||||||
|
repo_module.EnvRepoName + "=" + reponame,
|
||||||
|
repo_module.EnvPusherName + "=" + ctx.Doer.Name,
|
||||||
|
repo_module.EnvPusherID + fmt.Sprintf("=%d", ctx.Doer.ID),
|
||||||
|
repo_module.EnvAppURL + "=" + setting.AppURL,
|
||||||
|
}
|
||||||
|
|
||||||
|
if repoExist {
|
||||||
|
// Because of special ref "refs/for" .. , need delay write permission check
|
||||||
|
if git.SupportProcReceive {
|
||||||
|
accessMode = perm.AccessModeRead
|
||||||
|
}
|
||||||
|
|
||||||
|
if ctx.Data["IsActionsToken"] == true {
|
||||||
|
taskID := ctx.Data["ActionsTaskID"].(int64)
|
||||||
|
task, err := actions_model.GetTaskByID(ctx, taskID)
|
||||||
|
if err != nil {
|
||||||
|
ctx.ServerError("GetTaskByID", err)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if task.RepoID != repo.ID {
|
||||||
|
ctx.PlainText(http.StatusForbidden, "User permission denied")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if task.IsForkPullRequest {
|
||||||
|
if accessMode > perm.AccessModeRead {
|
||||||
|
ctx.PlainText(http.StatusForbidden, "User permission denied")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
environ = append(environ, fmt.Sprintf("%s=%d", repo_module.EnvActionPerm, perm.AccessModeRead))
|
||||||
|
} else {
|
||||||
|
if accessMode > perm.AccessModeWrite {
|
||||||
|
ctx.PlainText(http.StatusForbidden, "User permission denied")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
environ = append(environ, fmt.Sprintf("%s=%d", repo_module.EnvActionPerm, perm.AccessModeWrite))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
p, err := access_model.GetUserRepoPermission(ctx, repo, ctx.Doer)
|
||||||
|
if err != nil {
|
||||||
|
ctx.ServerError("GetUserRepoPermission", err)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if !p.CanAccess(accessMode, unitType) {
|
||||||
|
ctx.PlainText(http.StatusNotFound, "Repository not found")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !isPull && repo.IsMirror {
|
||||||
|
ctx.PlainText(http.StatusForbidden, "mirror repository is read-only")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !ctx.Doer.KeepEmailPrivate {
|
||||||
|
environ = append(environ, repo_module.EnvPusherEmail+"="+ctx.Doer.Email)
|
||||||
|
}
|
||||||
|
|
||||||
|
if isWiki {
|
||||||
|
environ = append(environ, repo_module.EnvRepoIsWiki+"=true")
|
||||||
|
} else {
|
||||||
|
environ = append(environ, repo_module.EnvRepoIsWiki+"=false")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !repoExist {
|
||||||
|
if !receivePack {
|
||||||
|
ctx.PlainText(http.StatusNotFound, "Repository not found")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if isWiki { // you cannot send wiki operation before create the repository
|
||||||
|
ctx.PlainText(http.StatusNotFound, "Repository not found")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if owner.IsOrganization() && !setting.Repository.EnablePushCreateOrg {
|
||||||
|
ctx.PlainText(http.StatusForbidden, "Push to create is not enabled for organizations.")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if !owner.IsOrganization() && !setting.Repository.EnablePushCreateUser {
|
||||||
|
ctx.PlainText(http.StatusForbidden, "Push to create is not enabled for users.")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return dummy payload if GET receive-pack
|
||||||
|
if ctx.Req.Method == http.MethodGet {
|
||||||
|
dummyInfoRefs(ctx)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
repo, err = repo_service.PushCreateRepo(ctx, ctx.Doer, owner, reponame)
|
||||||
|
if err != nil {
|
||||||
|
log.Error("pushCreateRepo: %v", err)
|
||||||
|
ctx.Status(http.StatusNotFound)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if isWiki {
|
||||||
|
// Ensure the wiki is enabled before we allow access to it
|
||||||
|
if _, err := repo.GetUnit(ctx, unit.TypeWiki); err != nil {
|
||||||
|
if repo_model.IsErrUnitTypeNotExist(err) {
|
||||||
|
ctx.PlainText(http.StatusForbidden, "repository wiki is disabled")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
log.Error("Failed to get the wiki unit in %-v Error: %v", repo, err)
|
||||||
|
ctx.ServerError("GetUnit(UnitTypeWiki) for "+repo.FullName(), err)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
environ = append(environ, repo_module.EnvRepoID+fmt.Sprintf("=%d", repo.ID))
|
||||||
|
|
||||||
|
w := ctx.Resp
|
||||||
|
r := ctx.Req
|
||||||
|
cfg := &serviceConfig{
|
||||||
|
UploadPack: true,
|
||||||
|
ReceivePack: true,
|
||||||
|
Env: environ,
|
||||||
|
}
|
||||||
|
|
||||||
|
r.URL.Path = strings.ToLower(r.URL.Path) // blue: In case some repo name has upper case name
|
||||||
|
|
||||||
|
dir := repo_model.RepoPath(username, reponame)
|
||||||
|
if isWiki {
|
||||||
|
dir = repo_model.RepoPath(username, wikiRepoName)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &serviceHandler{cfg, w, r, dir, cfg.Env}
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
infoRefsCache []byte
|
||||||
|
infoRefsOnce sync.Once
|
||||||
|
)
|
||||||
|
|
||||||
|
func dummyInfoRefs(ctx *context.Context) {
|
||||||
|
infoRefsOnce.Do(func() {
|
||||||
|
tmpDir, err := os.MkdirTemp(os.TempDir(), "gitea-info-refs-cache")
|
||||||
|
if err != nil {
|
||||||
|
log.Error("Failed to create temp dir for git-receive-pack cache: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
if err := util.RemoveAll(tmpDir); err != nil {
|
||||||
|
log.Error("RemoveAll: %v", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
if err := git.InitRepository(ctx, tmpDir, true); err != nil {
|
||||||
|
log.Error("Failed to init bare repo for git-receive-pack cache: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
refs, _, err := git.NewCommand(ctx, "receive-pack", "--stateless-rpc", "--advertise-refs", ".").RunStdBytes(&git.RunOpts{Dir: tmpDir})
|
||||||
|
if err != nil {
|
||||||
|
log.Error(fmt.Sprintf("%v - %s", err, string(refs)))
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Debug("populating infoRefsCache: \n%s", string(refs))
|
||||||
|
infoRefsCache = refs
|
||||||
|
})
|
||||||
|
|
||||||
|
ctx.RespHeader().Set("Expires", "Fri, 01 Jan 1980 00:00:00 GMT")
|
||||||
|
ctx.RespHeader().Set("Pragma", "no-cache")
|
||||||
|
ctx.RespHeader().Set("Cache-Control", "no-cache, max-age=0, must-revalidate")
|
||||||
|
ctx.RespHeader().Set("Content-Type", "application/x-git-receive-pack-advertisement")
|
||||||
|
_, _ = ctx.Write(packetWrite("# service=git-receive-pack\n"))
|
||||||
|
_, _ = ctx.Write([]byte("0000"))
|
||||||
|
_, _ = ctx.Write(infoRefsCache)
|
||||||
|
}
|
||||||
|
|
||||||
|
type serviceConfig struct {
|
||||||
|
UploadPack bool
|
||||||
|
ReceivePack bool
|
||||||
|
Env []string
|
||||||
|
}
|
||||||
|
|
||||||
|
type serviceHandler struct {
|
||||||
|
cfg *serviceConfig
|
||||||
|
w http.ResponseWriter
|
||||||
|
r *http.Request
|
||||||
|
dir string
|
||||||
|
environ []string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *serviceHandler) setHeaderNoCache() {
|
||||||
|
h.w.Header().Set("Expires", "Fri, 01 Jan 1980 00:00:00 GMT")
|
||||||
|
h.w.Header().Set("Pragma", "no-cache")
|
||||||
|
h.w.Header().Set("Cache-Control", "no-cache, max-age=0, must-revalidate")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *serviceHandler) setHeaderCacheForever() {
|
||||||
|
now := time.Now().Unix()
|
||||||
|
expires := now + 31536000
|
||||||
|
h.w.Header().Set("Date", fmt.Sprintf("%d", now))
|
||||||
|
h.w.Header().Set("Expires", fmt.Sprintf("%d", expires))
|
||||||
|
h.w.Header().Set("Cache-Control", "public, max-age=31536000")
|
||||||
|
}
|
||||||
|
|
||||||
|
func containsParentDirectorySeparator(v string) bool {
|
||||||
|
if !strings.Contains(v, "..") {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for _, ent := range strings.FieldsFunc(v, isSlashRune) {
|
||||||
|
if ent == ".." {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func isSlashRune(r rune) bool { return r == '/' || r == '\\' }
|
||||||
|
|
||||||
|
func (h *serviceHandler) sendFile(contentType, file string) {
|
||||||
|
if containsParentDirectorySeparator(file) {
|
||||||
|
log.Error("request file path contains invalid path: %v", file)
|
||||||
|
h.w.WriteHeader(http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
reqFile := path.Join(h.dir, file)
|
||||||
|
|
||||||
|
fi, err := os.Stat(reqFile)
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
h.w.WriteHeader(http.StatusNotFound)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
h.w.Header().Set("Content-Type", contentType)
|
||||||
|
h.w.Header().Set("Content-Length", fmt.Sprintf("%d", fi.Size()))
|
||||||
|
h.w.Header().Set("Last-Modified", fi.ModTime().Format(http.TimeFormat))
|
||||||
|
http.ServeFile(h.w, h.r, reqFile)
|
||||||
|
}
|
||||||
|
|
||||||
|
// one or more key=value pairs separated by colons
|
||||||
|
var safeGitProtocolHeader = regexp.MustCompile(`^[0-9a-zA-Z]+=[0-9a-zA-Z]+(:[0-9a-zA-Z]+=[0-9a-zA-Z]+)*$`)
|
||||||
|
|
||||||
|
func prepareGitCmdWithAllowedService(service string, h *serviceHandler) (*git.Command, error) {
|
||||||
|
if service == "receive-pack" && h.cfg.ReceivePack {
|
||||||
|
return git.NewCommand(h.r.Context(), "receive-pack"), nil
|
||||||
|
}
|
||||||
|
if service == "upload-pack" && h.cfg.UploadPack {
|
||||||
|
return git.NewCommand(h.r.Context(), "upload-pack"), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, fmt.Errorf("service %q is not allowed", service)
|
||||||
|
}
|
||||||
|
|
||||||
|
func serviceRPC(h *serviceHandler, service string) {
|
||||||
|
defer func() {
|
||||||
|
if err := h.r.Body.Close(); err != nil {
|
||||||
|
log.Error("serviceRPC: Close: %v", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
expectedContentType := fmt.Sprintf("application/x-git-%s-request", service)
|
||||||
|
if h.r.Header.Get("Content-Type") != expectedContentType {
|
||||||
|
log.Error("Content-Type (%q) doesn't match expected: %q", h.r.Header.Get("Content-Type"), expectedContentType)
|
||||||
|
h.w.WriteHeader(http.StatusUnauthorized)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd, err := prepareGitCmdWithAllowedService(service, h)
|
||||||
|
if err != nil {
|
||||||
|
log.Error("Failed to prepareGitCmdWithService: %v", err)
|
||||||
|
h.w.WriteHeader(http.StatusUnauthorized)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
h.w.Header().Set("Content-Type", fmt.Sprintf("application/x-git-%s-result", service))
|
||||||
|
|
||||||
|
reqBody := h.r.Body
|
||||||
|
|
||||||
|
// Handle GZIP.
|
||||||
|
if h.r.Header.Get("Content-Encoding") == "gzip" {
|
||||||
|
reqBody, err = gzip.NewReader(reqBody)
|
||||||
|
if err != nil {
|
||||||
|
log.Error("Fail to create gzip reader: %v", err)
|
||||||
|
h.w.WriteHeader(http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// set this for allow pre-receive and post-receive execute
|
||||||
|
h.environ = append(h.environ, "SSH_ORIGINAL_COMMAND="+service)
|
||||||
|
|
||||||
|
if protocol := h.r.Header.Get("Git-Protocol"); protocol != "" && safeGitProtocolHeader.MatchString(protocol) {
|
||||||
|
h.environ = append(h.environ, "GIT_PROTOCOL="+protocol)
|
||||||
|
}
|
||||||
|
|
||||||
|
var stderr bytes.Buffer
|
||||||
|
cmd.AddArguments("--stateless-rpc").AddDynamicArguments(h.dir)
|
||||||
|
cmd.SetDescription(fmt.Sprintf("%s %s %s [repo_path: %s]", git.GitExecutable, service, "--stateless-rpc", h.dir))
|
||||||
|
if err := cmd.Run(&git.RunOpts{
|
||||||
|
Dir: h.dir,
|
||||||
|
Env: append(os.Environ(), h.environ...),
|
||||||
|
Stdout: h.w,
|
||||||
|
Stdin: reqBody,
|
||||||
|
Stderr: &stderr,
|
||||||
|
UseContextTimeout: true,
|
||||||
|
}); err != nil {
|
||||||
|
if err.Error() != "signal: killed" {
|
||||||
|
log.Error("Fail to serve RPC(%s) in %s: %v - %s", service, h.dir, err, stderr.String())
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ServiceUploadPack implements Git Smart HTTP protocol
|
||||||
|
func ServiceUploadPack(ctx *context.Context) {
|
||||||
|
h := httpBase(ctx)
|
||||||
|
if h != nil {
|
||||||
|
serviceRPC(h, "upload-pack")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ServiceReceivePack implements Git Smart HTTP protocol
|
||||||
|
func ServiceReceivePack(ctx *context.Context) {
|
||||||
|
h := httpBase(ctx)
|
||||||
|
if h != nil {
|
||||||
|
serviceRPC(h, "receive-pack")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func getServiceType(r *http.Request) string {
|
||||||
|
serviceType := r.FormValue("service")
|
||||||
|
if !strings.HasPrefix(serviceType, "git-") {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return strings.TrimPrefix(serviceType, "git-")
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateServerInfo(ctx gocontext.Context, dir string) []byte {
|
||||||
|
out, _, err := git.NewCommand(ctx, "update-server-info").RunStdBytes(&git.RunOpts{Dir: dir})
|
||||||
|
if err != nil {
|
||||||
|
log.Error(fmt.Sprintf("%v - %s", err, string(out)))
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
func packetWrite(str string) []byte {
|
||||||
|
s := strconv.FormatInt(int64(len(str)+4), 16)
|
||||||
|
if len(s)%4 != 0 {
|
||||||
|
s = strings.Repeat("0", 4-len(s)%4) + s
|
||||||
|
}
|
||||||
|
return []byte(s + str)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetInfoRefs implements Git dumb HTTP
|
||||||
|
func GetInfoRefs(ctx *context.Context) {
|
||||||
|
h := httpBase(ctx)
|
||||||
|
if h == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
h.setHeaderNoCache()
|
||||||
|
service := getServiceType(h.r)
|
||||||
|
cmd, err := prepareGitCmdWithAllowedService(service, h)
|
||||||
|
if err == nil {
|
||||||
|
if protocol := h.r.Header.Get("Git-Protocol"); protocol != "" && safeGitProtocolHeader.MatchString(protocol) {
|
||||||
|
h.environ = append(h.environ, "GIT_PROTOCOL="+protocol)
|
||||||
|
}
|
||||||
|
h.environ = append(os.Environ(), h.environ...)
|
||||||
|
|
||||||
|
refs, _, err := cmd.AddArguments("--stateless-rpc", "--advertise-refs", ".").RunStdBytes(&git.RunOpts{Env: h.environ, Dir: h.dir})
|
||||||
|
if err != nil {
|
||||||
|
log.Error(fmt.Sprintf("%v - %s", err, string(refs)))
|
||||||
|
}
|
||||||
|
|
||||||
|
h.w.Header().Set("Content-Type", fmt.Sprintf("application/x-git-%s-advertisement", service))
|
||||||
|
h.w.WriteHeader(http.StatusOK)
|
||||||
|
_, _ = h.w.Write(packetWrite("# service=git-" + service + "\n"))
|
||||||
|
_, _ = h.w.Write([]byte("0000"))
|
||||||
|
_, _ = h.w.Write(refs)
|
||||||
|
} else {
|
||||||
|
updateServerInfo(ctx, h.dir)
|
||||||
|
h.sendFile("text/plain; charset=utf-8", "info/refs")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetTextFile implements Git dumb HTTP
|
||||||
|
func GetTextFile(p string) func(*context.Context) {
|
||||||
|
return func(ctx *context.Context) {
|
||||||
|
h := httpBase(ctx)
|
||||||
|
if h != nil {
|
||||||
|
h.setHeaderNoCache()
|
||||||
|
file := ctx.Params("file")
|
||||||
|
if file != "" {
|
||||||
|
h.sendFile("text/plain", "objects/info/"+file)
|
||||||
|
} else {
|
||||||
|
h.sendFile("text/plain", p)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetInfoPacks implements Git dumb HTTP
|
||||||
|
func GetInfoPacks(ctx *context.Context) {
|
||||||
|
h := httpBase(ctx)
|
||||||
|
if h != nil {
|
||||||
|
h.setHeaderCacheForever()
|
||||||
|
h.sendFile("text/plain; charset=utf-8", "objects/info/packs")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetLooseObject implements Git dumb HTTP
|
||||||
|
func GetLooseObject(ctx *context.Context) {
|
||||||
|
h := httpBase(ctx)
|
||||||
|
if h != nil {
|
||||||
|
h.setHeaderCacheForever()
|
||||||
|
h.sendFile("application/x-git-loose-object", fmt.Sprintf("objects/%s/%s",
|
||||||
|
ctx.Params("head"), ctx.Params("hash")))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetPackFile implements Git dumb HTTP
|
||||||
|
func GetPackFile(ctx *context.Context) {
|
||||||
|
h := httpBase(ctx)
|
||||||
|
if h != nil {
|
||||||
|
h.setHeaderCacheForever()
|
||||||
|
h.sendFile("application/x-git-packed-objects", "objects/pack/pack-"+ctx.Params("file")+".pack")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetIdxFile implements Git dumb HTTP
|
||||||
|
func GetIdxFile(ctx *context.Context) {
|
||||||
|
h := httpBase(ctx)
|
||||||
|
if h != nil {
|
||||||
|
h.setHeaderCacheForever()
|
||||||
|
h.sendFile("application/x-git-packed-objects-toc", "objects/pack/pack-"+ctx.Params("file")+".idx")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,98 @@
|
||||||
|
// Copyright 2020 The Gitea Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package web
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"code.gitea.io/gitea/modules/httpcache"
|
||||||
|
"code.gitea.io/gitea/modules/log"
|
||||||
|
"code.gitea.io/gitea/modules/setting"
|
||||||
|
"code.gitea.io/gitea/modules/storage"
|
||||||
|
"code.gitea.io/gitea/modules/util"
|
||||||
|
"code.gitea.io/gitea/modules/web/routing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func storageHandler(storageSetting *setting.Storage, prefix string, objStore storage.ObjectStorage) http.HandlerFunc {
|
||||||
|
prefix = strings.Trim(prefix, "/")
|
||||||
|
funcInfo := routing.GetFuncInfo(storageHandler, prefix)
|
||||||
|
|
||||||
|
if storageSetting.MinioConfig.ServeDirect {
|
||||||
|
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||||
|
if req.Method != "GET" && req.Method != "HEAD" {
|
||||||
|
http.Error(w, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if !strings.HasPrefix(req.URL.Path, "/"+prefix+"/") {
|
||||||
|
http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
routing.UpdateFuncInfo(req.Context(), funcInfo)
|
||||||
|
|
||||||
|
rPath := strings.TrimPrefix(req.URL.Path, "/"+prefix+"/")
|
||||||
|
rPath = util.PathJoinRelX(rPath)
|
||||||
|
|
||||||
|
u, err := objStore.URL(rPath, path.Base(rPath))
|
||||||
|
if err != nil {
|
||||||
|
if os.IsNotExist(err) || errors.Is(err, os.ErrNotExist) {
|
||||||
|
log.Warn("Unable to find %s %s", prefix, rPath)
|
||||||
|
http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
log.Error("Error whilst getting URL for %s %s. Error: %v", prefix, rPath, err)
|
||||||
|
http.Error(w, fmt.Sprintf("Error whilst getting URL for %s %s", prefix, rPath), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
http.Redirect(w, req, u.String(), http.StatusTemporaryRedirect)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||||
|
if req.Method != "GET" && req.Method != "HEAD" {
|
||||||
|
http.Error(w, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if !strings.HasPrefix(req.URL.Path, "/"+prefix+"/") {
|
||||||
|
http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
routing.UpdateFuncInfo(req.Context(), funcInfo)
|
||||||
|
|
||||||
|
rPath := strings.TrimPrefix(req.URL.Path, "/"+prefix+"/")
|
||||||
|
rPath = util.PathJoinRelX(rPath)
|
||||||
|
if rPath == "" || rPath == "." {
|
||||||
|
http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
fi, err := objStore.Stat(rPath)
|
||||||
|
if err != nil {
|
||||||
|
if os.IsNotExist(err) || errors.Is(err, os.ErrNotExist) {
|
||||||
|
log.Warn("Unable to find %s %s", prefix, rPath)
|
||||||
|
http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
log.Error("Error whilst opening %s %s. Error: %v", prefix, rPath, err)
|
||||||
|
http.Error(w, fmt.Sprintf("Error whilst opening %s %s", prefix, rPath), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
fr, err := objStore.Open(rPath)
|
||||||
|
if err != nil {
|
||||||
|
log.Error("Error whilst opening %s %s. Error: %v", prefix, rPath, err)
|
||||||
|
http.Error(w, fmt.Sprintf("Error whilst opening %s %s", prefix, rPath), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer fr.Close()
|
||||||
|
httpcache.ServeContentWithCacheControl(w, req, path.Base(rPath), fi.ModTime(), fr)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,46 @@
|
||||||
|
// Copyright 2023 The Gitea Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package web
|
||||||
|
|
||||||
|
import (
|
||||||
|
"code.gitea.io/gitea/routers/web/repo"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"code.gitea.io/gitea/modules/context"
|
||||||
|
"code.gitea.io/gitea/modules/setting"
|
||||||
|
"code.gitea.io/gitea/modules/web"
|
||||||
|
context_service "code.gitea.io/gitea/services/context"
|
||||||
|
//"code.gitea.io/gitea/routers/web/repo"
|
||||||
|
hat_repo "code.gitlink.org.cn/Gitlink/gitea_hat.git/routers/hat/repo"
|
||||||
|
)
|
||||||
|
|
||||||
|
func requireSignIn(ctx *context.Context) {
|
||||||
|
if !setting.Service.RequireSignInView {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// rely on the results of Contexter
|
||||||
|
if !ctx.IsSigned {
|
||||||
|
// TODO: support digit auth - which would be Authorization header with digit
|
||||||
|
//ctx.Resp.Header().Set("WWW-Authenticate", `Basic realm="Gitea"`)
|
||||||
|
ctx.Resp.Header().Set("WWW-Authenticate", `Basic realm="."`)
|
||||||
|
ctx.Error(http.StatusUnauthorized)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func gitHTTPRouters(m *web.Route) {
|
||||||
|
m.Group("", func() {
|
||||||
|
m.PostOptions("/git-upload-pack", repo.ServiceUploadPack)
|
||||||
|
m.PostOptions("/git-receive-pack", repo.ServiceReceivePack)
|
||||||
|
m.GetOptions("/info/refs", hat_repo.GetInfoRefs)
|
||||||
|
m.GetOptions("/HEAD", repo.GetTextFile("HEAD"))
|
||||||
|
m.GetOptions("/objects/info/alternates", repo.GetTextFile("objects/info/alternates"))
|
||||||
|
m.GetOptions("/objects/info/http-alternates", repo.GetTextFile("objects/info/http-alternates"))
|
||||||
|
m.GetOptions("/objects/info/packs", repo.GetInfoPacks)
|
||||||
|
m.GetOptions("/objects/info/{file:[^/]*}", repo.GetTextFile(""))
|
||||||
|
m.GetOptions("/objects/{head:[0-9a-f]{2}}/{hash:[0-9a-f]{38}}", repo.GetLooseObject)
|
||||||
|
m.GetOptions("/objects/pack/pack-{file:[0-9a-f]{40}}.pack", repo.GetPackFile)
|
||||||
|
m.GetOptions("/objects/pack/pack-{file:[0-9a-f]{40}}.idx", repo.GetIdxFile)
|
||||||
|
}, ignSignInAndCsrf, requireSignIn, repo.HTTPGitEnabledHandler, repo.CorsHandler(), context_service.UserAssignmentWeb())
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,93 @@
|
||||||
|
// Copyright 2021 The Gitea Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package web
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"html"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"path"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
repo_model "code.gitea.io/gitea/models/repo"
|
||||||
|
"code.gitea.io/gitea/modules/context"
|
||||||
|
"code.gitea.io/gitea/modules/setting"
|
||||||
|
"code.gitea.io/gitea/modules/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
func goGet(ctx *context.Context) {
|
||||||
|
if ctx.Req.Method != "GET" || len(ctx.Req.URL.RawQuery) < 8 || ctx.FormString("go-get") != "1" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
parts := strings.SplitN(ctx.Req.URL.EscapedPath(), "/", 4)
|
||||||
|
|
||||||
|
if len(parts) < 3 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ownerName := parts[1]
|
||||||
|
repoName := parts[2]
|
||||||
|
|
||||||
|
// Quick responses appropriate go-get meta with status 200
|
||||||
|
// regardless of if user have access to the repository,
|
||||||
|
// or the repository does not exist at all.
|
||||||
|
// This is particular a workaround for "go get" command which does not respect
|
||||||
|
// .netrc file.
|
||||||
|
|
||||||
|
trimmedRepoName := strings.TrimSuffix(repoName, ".git")
|
||||||
|
|
||||||
|
if ownerName == "" || trimmedRepoName == "" {
|
||||||
|
_, _ = ctx.Write([]byte(`<!doctype html>
|
||||||
|
<html>
|
||||||
|
<body>
|
||||||
|
invalid import path
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
`))
|
||||||
|
ctx.Status(http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
branchName := setting.Repository.DefaultBranch
|
||||||
|
|
||||||
|
repo, err := repo_model.GetRepositoryByOwnerAndName(ctx, ownerName, repoName)
|
||||||
|
if err == nil && len(repo.DefaultBranch) > 0 {
|
||||||
|
branchName = repo.DefaultBranch
|
||||||
|
}
|
||||||
|
prefix := setting.AppURL + path.Join(url.PathEscape(ownerName), url.PathEscape(repoName), "src", "branch", util.PathEscapeSegments(branchName))
|
||||||
|
|
||||||
|
appURL, _ := url.Parse(setting.AppURL)
|
||||||
|
|
||||||
|
insecure := ""
|
||||||
|
if appURL.Scheme == string(setting.HTTP) {
|
||||||
|
insecure = "--insecure "
|
||||||
|
}
|
||||||
|
|
||||||
|
goGetImport := context.ComposeGoGetImport(ownerName, trimmedRepoName)
|
||||||
|
|
||||||
|
var cloneURL string
|
||||||
|
if setting.Repository.GoGetCloneURLProtocol == "ssh" {
|
||||||
|
cloneURL = repo_model.ComposeSSHCloneURL(ownerName, repoName)
|
||||||
|
} else {
|
||||||
|
cloneURL = repo_model.ComposeHTTPSCloneURL(ownerName, repoName)
|
||||||
|
}
|
||||||
|
goImportContent := fmt.Sprintf("%s git %s", goGetImport, cloneURL /*CloneLink*/)
|
||||||
|
goSourceContent := fmt.Sprintf("%s _ %s %s", goGetImport, prefix+"{/dir}" /*GoDocDirectory*/, prefix+"{/dir}/{file}#L{line}" /*GoDocFile*/)
|
||||||
|
goGetCli := fmt.Sprintf("go get %s%s", insecure, goGetImport)
|
||||||
|
|
||||||
|
res := fmt.Sprintf(`<!doctype html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta name="go-import" content="%s">
|
||||||
|
<meta name="go-source" content="%s">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
%s
|
||||||
|
</body>
|
||||||
|
</html>`, html.EscapeString(goImportContent), html.EscapeString(goSourceContent), html.EscapeString(goGetCli))
|
||||||
|
|
||||||
|
ctx.RespHeader().Set("Content-Type", "text/html")
|
||||||
|
_, _ = ctx.Write([]byte(res))
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,118 @@
|
||||||
|
// Copyright 2014 The Gogs Authors. All rights reserved.
|
||||||
|
// Copyright 2019 The Gitea Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package web
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"code.gitea.io/gitea/models/db"
|
||||||
|
repo_model "code.gitea.io/gitea/models/repo"
|
||||||
|
user_model "code.gitea.io/gitea/models/user"
|
||||||
|
"code.gitea.io/gitea/modules/base"
|
||||||
|
"code.gitea.io/gitea/modules/context"
|
||||||
|
"code.gitea.io/gitea/modules/log"
|
||||||
|
"code.gitea.io/gitea/modules/setting"
|
||||||
|
"code.gitea.io/gitea/modules/sitemap"
|
||||||
|
"code.gitea.io/gitea/modules/structs"
|
||||||
|
"code.gitea.io/gitea/modules/util"
|
||||||
|
"code.gitea.io/gitea/modules/web/middleware"
|
||||||
|
"code.gitea.io/gitea/routers/web/auth"
|
||||||
|
"code.gitea.io/gitea/routers/web/user"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// tplHome home page template
|
||||||
|
tplHome base.TplName = "home"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Home render home page
|
||||||
|
func Home(ctx *context.Context) {
|
||||||
|
if ctx.IsSigned {
|
||||||
|
if !ctx.Doer.IsActive && setting.Service.RegisterEmailConfirm {
|
||||||
|
ctx.Data["Title"] = ctx.Tr("auth.active_your_account")
|
||||||
|
ctx.HTML(http.StatusOK, auth.TplActivate)
|
||||||
|
} else if !ctx.Doer.IsActive || ctx.Doer.ProhibitLogin {
|
||||||
|
log.Info("Failed authentication attempt for %s from %s", ctx.Doer.Name, ctx.RemoteAddr())
|
||||||
|
ctx.Data["Title"] = ctx.Tr("auth.prohibit_login")
|
||||||
|
ctx.HTML(http.StatusOK, "user/auth/prohibit_login")
|
||||||
|
} else if ctx.Doer.MustChangePassword {
|
||||||
|
ctx.Data["Title"] = ctx.Tr("auth.must_change_password")
|
||||||
|
ctx.Data["ChangePasscodeLink"] = setting.AppSubURL + "/user/change_password"
|
||||||
|
middleware.SetRedirectToCookie(ctx.Resp, setting.AppSubURL+ctx.Req.URL.RequestURI())
|
||||||
|
ctx.Redirect(setting.AppSubURL + "/user/settings/change_password")
|
||||||
|
} else {
|
||||||
|
user.Dashboard(ctx)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
// Check non-logged users landing page.
|
||||||
|
} else if setting.LandingPageURL != setting.LandingPageHome {
|
||||||
|
ctx.Redirect(setting.AppSubURL + string(setting.LandingPageURL))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check auto-login.
|
||||||
|
uname := ctx.GetSiteCookie(setting.CookieUserName)
|
||||||
|
if len(uname) != 0 {
|
||||||
|
ctx.Redirect(setting.AppSubURL + "/user/login")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.Data["PageIsHome"] = true
|
||||||
|
ctx.Data["IsRepoIndexerEnabled"] = setting.Indexer.RepoIndexerEnabled
|
||||||
|
ctx.HTML(http.StatusOK, tplHome)
|
||||||
|
}
|
||||||
|
|
||||||
|
// HomeSitemap renders the main sitemap
|
||||||
|
func HomeSitemap(ctx *context.Context) {
|
||||||
|
m := sitemap.NewSitemapIndex()
|
||||||
|
if !setting.Service.Explore.DisableUsersPage {
|
||||||
|
_, cnt, err := user_model.SearchUsers(ctx, &user_model.SearchUserOptions{
|
||||||
|
Type: user_model.UserTypeIndividual,
|
||||||
|
ListOptions: db.ListOptions{PageSize: 1},
|
||||||
|
IsActive: util.OptionalBoolTrue,
|
||||||
|
Visible: []structs.VisibleType{structs.VisibleTypePublic},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
ctx.ServerError("SearchUsers", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
count := int(cnt)
|
||||||
|
idx := 1
|
||||||
|
for i := 0; i < count; i += setting.UI.SitemapPagingNum {
|
||||||
|
m.Add(sitemap.URL{URL: setting.AppURL + "explore/users/sitemap-" + strconv.Itoa(idx) + ".xml"})
|
||||||
|
idx++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_, cnt, err := repo_model.SearchRepository(ctx, &repo_model.SearchRepoOptions{
|
||||||
|
ListOptions: db.ListOptions{
|
||||||
|
PageSize: 1,
|
||||||
|
},
|
||||||
|
Actor: ctx.Doer,
|
||||||
|
AllPublic: true,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
ctx.ServerError("SearchRepository", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
count := int(cnt)
|
||||||
|
idx := 1
|
||||||
|
for i := 0; i < count; i += setting.UI.SitemapPagingNum {
|
||||||
|
m.Add(sitemap.URL{URL: setting.AppURL + "explore/repos/sitemap-" + strconv.Itoa(idx) + ".xml"})
|
||||||
|
idx++
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.Resp.Header().Set("Content-Type", "text/xml")
|
||||||
|
if _, err := m.WriteTo(ctx.Resp); err != nil {
|
||||||
|
log.Error("Failed writing sitemap: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NotFound render 404 page
|
||||||
|
func NotFound(ctx *context.Context) {
|
||||||
|
ctx.Data["Title"] = "Page Not Found"
|
||||||
|
ctx.NotFound("home.NotFound", nil)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,33 @@
|
||||||
|
// Copyright 2018 The Gitea Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package web
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/subtle"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"code.gitea.io/gitea/modules/setting"
|
||||||
|
|
||||||
|
"github.com/prometheus/client_golang/prometheus/promhttp"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Metrics validate auth token and render prometheus metrics
|
||||||
|
func Metrics(resp http.ResponseWriter, req *http.Request) {
|
||||||
|
if setting.Metrics.Token == "" {
|
||||||
|
promhttp.Handler().ServeHTTP(resp, req)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
header := req.Header.Get("Authorization")
|
||||||
|
if header == "" {
|
||||||
|
http.Error(resp, "", http.StatusUnauthorized)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
got := []byte(header)
|
||||||
|
want := []byte("Bearer " + setting.Metrics.Token)
|
||||||
|
if subtle.ConstantTimeCompare(got, want) != 1 {
|
||||||
|
http.Error(resp, "", http.StatusUnauthorized)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
promhttp.Handler().ServeHTTP(resp, req)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,32 @@
|
||||||
|
// Copyright 2021 The Gitea Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package web
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"code.gitea.io/gitea/modules/context"
|
||||||
|
"code.gitea.io/gitea/modules/setting"
|
||||||
|
)
|
||||||
|
|
||||||
|
type nodeInfoLinks struct {
|
||||||
|
Links []nodeInfoLink `json:"links"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type nodeInfoLink struct {
|
||||||
|
Href string `json:"href"`
|
||||||
|
Rel string `json:"rel"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// NodeInfoLinks returns links to the node info endpoint
|
||||||
|
func NodeInfoLinks(ctx *context.Context) {
|
||||||
|
nodeinfolinks := &nodeInfoLinks{
|
||||||
|
Links: []nodeInfoLink{{
|
||||||
|
fmt.Sprintf("%sapi/v1/nodeinfo", setting.AppURL),
|
||||||
|
"http://nodeinfo.diaspora.software/ns/schema/2.1",
|
||||||
|
}},
|
||||||
|
}
|
||||||
|
ctx.JSON(http.StatusOK, nodeinfolinks)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,25 @@
|
||||||
|
// Copyright 2020 The Gitea Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package web
|
||||||
|
|
||||||
|
import (
|
||||||
|
"code.gitea.io/gitea/modules/base"
|
||||||
|
"code.gitea.io/gitea/modules/context"
|
||||||
|
)
|
||||||
|
|
||||||
|
// tplSwaggerV1Json swagger v1 json template
|
||||||
|
const tplSwaggerV1Json base.TplName = "swagger/v1_json"
|
||||||
|
|
||||||
|
// SwaggerV1Json render swagger v1 json
|
||||||
|
func SwaggerV1Json(ctx *context.Context) {
|
||||||
|
t, err := ctx.Render.TemplateLookup(string(tplSwaggerV1Json), nil)
|
||||||
|
if err != nil {
|
||||||
|
ctx.ServerError("unable to find template", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ctx.Resp.Header().Set("Content-Type", "application/json")
|
||||||
|
if err = t.Execute(ctx.Resp, ctx.Data); err != nil {
|
||||||
|
ctx.ServerError("unable to execute template", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,121 @@
|
||||||
|
// Copyright 2022 The Gitea Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package web
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
user_model "code.gitea.io/gitea/models/user"
|
||||||
|
"code.gitea.io/gitea/modules/context"
|
||||||
|
"code.gitea.io/gitea/modules/log"
|
||||||
|
"code.gitea.io/gitea/modules/setting"
|
||||||
|
)
|
||||||
|
|
||||||
|
// https://datatracker.ietf.org/doc/html/draft-ietf-appsawg-webfinger-14#section-4.4
|
||||||
|
|
||||||
|
type webfingerJRD struct {
|
||||||
|
Subject string `json:"subject,omitempty"`
|
||||||
|
Aliases []string `json:"aliases,omitempty"`
|
||||||
|
Properties map[string]any `json:"properties,omitempty"`
|
||||||
|
Links []*webfingerLink `json:"links,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type webfingerLink struct {
|
||||||
|
Rel string `json:"rel,omitempty"`
|
||||||
|
Type string `json:"type,omitempty"`
|
||||||
|
Href string `json:"href,omitempty"`
|
||||||
|
Titles map[string]string `json:"titles,omitempty"`
|
||||||
|
Properties map[string]any `json:"properties,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// WebfingerQuery returns information about a resource
|
||||||
|
// https://datatracker.ietf.org/doc/html/rfc7565
|
||||||
|
func WebfingerQuery(ctx *context.Context) {
|
||||||
|
appURL, _ := url.Parse(setting.AppURL)
|
||||||
|
|
||||||
|
resource, err := url.Parse(ctx.FormTrim("resource"))
|
||||||
|
if err != nil {
|
||||||
|
ctx.Error(http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var u *user_model.User
|
||||||
|
|
||||||
|
switch resource.Scheme {
|
||||||
|
case "acct":
|
||||||
|
// allow only the current host
|
||||||
|
parts := strings.SplitN(resource.Opaque, "@", 2)
|
||||||
|
if len(parts) != 2 {
|
||||||
|
ctx.Error(http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if parts[1] != appURL.Host {
|
||||||
|
ctx.Error(http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
u, err = user_model.GetUserByName(ctx, parts[0])
|
||||||
|
case "mailto":
|
||||||
|
u, err = user_model.GetUserByEmail(ctx, resource.Opaque)
|
||||||
|
if u != nil && u.KeepEmailPrivate {
|
||||||
|
err = user_model.ErrUserNotExist{}
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
ctx.Error(http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
if user_model.IsErrUserNotExist(err) {
|
||||||
|
ctx.Error(http.StatusNotFound)
|
||||||
|
} else {
|
||||||
|
log.Error("Error getting user: %s Error: %v", resource.Opaque, err)
|
||||||
|
ctx.Error(http.StatusInternalServerError)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if !user_model.IsUserVisibleToViewer(ctx, u, ctx.Doer) {
|
||||||
|
ctx.Error(http.StatusNotFound)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
aliases := []string{
|
||||||
|
u.HTMLURL(),
|
||||||
|
appURL.String() + "api/v1/activitypub/user-id/" + fmt.Sprint(u.ID),
|
||||||
|
}
|
||||||
|
if !u.KeepEmailPrivate {
|
||||||
|
aliases = append(aliases, fmt.Sprintf("mailto:%s", u.Email))
|
||||||
|
}
|
||||||
|
|
||||||
|
links := []*webfingerLink{
|
||||||
|
{
|
||||||
|
Rel: "http://webfinger.net/rel/profile-page",
|
||||||
|
Type: "text/html",
|
||||||
|
Href: u.HTMLURL(),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Rel: "http://webfinger.net/rel/avatar",
|
||||||
|
Href: u.AvatarLink(ctx),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Rel: "self",
|
||||||
|
Type: "application/activity+json",
|
||||||
|
Href: appURL.String() + "api/v1/activitypub/user-id/" + fmt.Sprint(u.ID),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Rel: "http://openid.net/specs/connect/1.0/issuer",
|
||||||
|
Href: appURL.String(),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.Resp.Header().Add("Access-Control-Allow-Origin", "*")
|
||||||
|
ctx.JSON(http.StatusOK, &webfingerJRD{
|
||||||
|
Subject: fmt.Sprintf("acct:%s@%s", url.QueryEscape(u.Name), appURL.Host),
|
||||||
|
Aliases: aliases,
|
||||||
|
Links: links,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
@ -17,9 +17,10 @@ import (
|
||||||
apiv1 "code.gitea.io/gitea/routers/api/v1"
|
apiv1 "code.gitea.io/gitea/routers/api/v1"
|
||||||
"code.gitea.io/gitea/routers/common"
|
"code.gitea.io/gitea/routers/common"
|
||||||
"code.gitea.io/gitea/routers/private"
|
"code.gitea.io/gitea/routers/private"
|
||||||
web_routers "code.gitea.io/gitea/routers/web"
|
//web_routers "code.gitea.io/gitea/routers/web"
|
||||||
"code.gitlink.org.cn/Gitlink/gitea_hat.git/models/migrations"
|
"code.gitlink.org.cn/Gitlink/gitea_hat.git/models/migrations"
|
||||||
api_hat "code.gitlink.org.cn/Gitlink/gitea_hat.git/routers/hat"
|
api_hat "code.gitlink.org.cn/Gitlink/gitea_hat.git/routers/hat"
|
||||||
|
web_routers "code.gitlink.org.cn/Gitlink/gitea_hat.git/routers/hat/web"
|
||||||
hat_pull_service "code.gitlink.org.cn/Gitlink/gitea_hat.git/services/pull"
|
hat_pull_service "code.gitlink.org.cn/Gitlink/gitea_hat.git/services/pull"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue