* refactor routers directory * move func used for web and api to common * make corsHandler a function to prohibit side efects * rm unused func Co-authored-by: 6543 <6543@obermui.de>tags/v1.15.0-rc1
@@ -70,9 +70,6 @@ issues: | |||
- path: modules/log/ | |||
linters: | |||
- errcheck | |||
- path: routers/routes/web.go | |||
linters: | |||
- dupl | |||
- path: routers/api/v1/repo/issue_subscription.go | |||
linters: | |||
- dupl | |||
@@ -114,3 +111,4 @@ issues: | |||
linters: | |||
- staticcheck | |||
text: "svc.IsAnInteractiveSession is deprecated: Use IsWindowsService instead." | |||
@@ -17,7 +17,7 @@ import ( | |||
"code.gitea.io/gitea/modules/log" | |||
"code.gitea.io/gitea/modules/setting" | |||
"code.gitea.io/gitea/routers" | |||
"code.gitea.io/gitea/routers/routes" | |||
"code.gitea.io/gitea/routers/install" | |||
context2 "github.com/gorilla/context" | |||
"github.com/urfave/cli" | |||
@@ -88,7 +88,7 @@ func runWeb(ctx *cli.Context) error { | |||
} | |||
// Perform pre-initialization | |||
needsInstall := routers.PreInstallInit(graceful.GetManager().HammerContext()) | |||
needsInstall := install.PreloadSettings(graceful.GetManager().HammerContext()) | |||
if needsInstall { | |||
// Flag for port number in case first time run conflict | |||
if ctx.IsSet("port") { | |||
@@ -101,7 +101,7 @@ func runWeb(ctx *cli.Context) error { | |||
return err | |||
} | |||
} | |||
c := routes.InstallRoutes() | |||
c := install.Routes() | |||
err := listen(c, false) | |||
select { | |||
case <-graceful.GetManager().IsShutdown(): | |||
@@ -134,7 +134,7 @@ func runWeb(ctx *cli.Context) error { | |||
} | |||
// Set up Chi routes | |||
c := routes.NormalRoutes() | |||
c := routers.NormalRoutes() | |||
err := listen(c, true) | |||
<-graceful.GetManager().Done() | |||
log.Info("PID: %d Gitea Web Finished", os.Getpid()) |
@@ -31,7 +31,6 @@ import ( | |||
"code.gitea.io/gitea/modules/setting" | |||
"code.gitea.io/gitea/modules/util" | |||
"code.gitea.io/gitea/routers" | |||
"code.gitea.io/gitea/routers/routes" | |||
"github.com/go-git/go-git/v5" | |||
"github.com/go-git/go-git/v5/config" | |||
@@ -116,7 +115,7 @@ func runPR() { | |||
//routers.GlobalInit() | |||
external.RegisterRenderers() | |||
markup.Init() | |||
c := routes.NormalRoutes() | |||
c := routers.NormalRoutes() | |||
log.Printf("[PR] Ready for testing !\n") | |||
log.Printf("[PR] Login with user1, user2, user3, ... with pass: password\n") |
@@ -14,7 +14,7 @@ import ( | |||
"code.gitea.io/gitea/modules/setting" | |||
"code.gitea.io/gitea/modules/util" | |||
"code.gitea.io/gitea/routers/routes" | |||
"code.gitea.io/gitea/routers" | |||
"gitea.com/go-chi/session" | |||
jsoniter "github.com/json-iterator/go" | |||
@@ -58,7 +58,7 @@ func TestSessionFileCreation(t *testing.T) { | |||
oldSessionConfig := setting.SessionConfig.ProviderConfig | |||
defer func() { | |||
setting.SessionConfig.ProviderConfig = oldSessionConfig | |||
c = routes.NormalRoutes() | |||
c = routers.NormalRoutes() | |||
}() | |||
var config session.Options | |||
@@ -84,7 +84,7 @@ func TestSessionFileCreation(t *testing.T) { | |||
setting.SessionConfig.ProviderConfig = string(newConfigBytes) | |||
c = routes.NormalRoutes() | |||
c = routers.NormalRoutes() | |||
t.Run("NoSessionOnViewIssue", func(t *testing.T) { | |||
defer PrintCurrentTest(t)() |
@@ -34,7 +34,6 @@ import ( | |||
"code.gitea.io/gitea/modules/util" | |||
"code.gitea.io/gitea/modules/web" | |||
"code.gitea.io/gitea/routers" | |||
"code.gitea.io/gitea/routers/routes" | |||
"github.com/PuerkitoBio/goquery" | |||
jsoniter "github.com/json-iterator/go" | |||
@@ -88,7 +87,7 @@ func TestMain(m *testing.M) { | |||
defer cancel() | |||
initIntegrationTest() | |||
c = routes.NormalRoutes() | |||
c = routers.NormalRoutes() | |||
// integration test settings... | |||
if setting.Cfg != nil { |
@@ -15,7 +15,7 @@ import ( | |||
"code.gitea.io/gitea/models" | |||
"code.gitea.io/gitea/modules/lfs" | |||
"code.gitea.io/gitea/modules/setting" | |||
"code.gitea.io/gitea/routers/routes" | |||
"code.gitea.io/gitea/routers/web" | |||
jsoniter "github.com/json-iterator/go" | |||
gzipp "github.com/klauspost/compress/gzip" | |||
@@ -99,7 +99,7 @@ func TestGetLFSLarge(t *testing.T) { | |||
t.Skip() | |||
return | |||
} | |||
content := make([]byte, routes.GzipMinSize*10) | |||
content := make([]byte, web.GzipMinSize*10) | |||
for i := range content { | |||
content[i] = byte(i % 256) | |||
} | |||
@@ -115,7 +115,7 @@ func TestGetLFSGzip(t *testing.T) { | |||
t.Skip() | |||
return | |||
} | |||
b := make([]byte, routes.GzipMinSize*10) | |||
b := make([]byte, web.GzipMinSize*10) | |||
for i := range b { | |||
b[i] = byte(i % 256) | |||
} | |||
@@ -136,7 +136,7 @@ func TestGetLFSZip(t *testing.T) { | |||
t.Skip() | |||
return | |||
} | |||
b := make([]byte, routes.GzipMinSize*10) | |||
b := make([]byte, web.GzipMinSize*10) | |||
for i := range b { | |||
b[i] = byte(i % 256) | |||
} |
@@ -17,7 +17,7 @@ import ( | |||
"code.gitea.io/gitea/modules/repofiles" | |||
api "code.gitea.io/gitea/modules/structs" | |||
"code.gitea.io/gitea/modules/web" | |||
"code.gitea.io/gitea/routers/repo" | |||
"code.gitea.io/gitea/routers/common" | |||
) | |||
// GetRawFile get a file by path on a repository | |||
@@ -83,7 +83,7 @@ func GetRawFile(ctx *context.APIContext) { | |||
} | |||
return | |||
} | |||
if err = repo.ServeBlob(ctx.Context, blob); err != nil { | |||
if err = common.ServeBlob(ctx.Context, blob); err != nil { | |||
ctx.Error(http.StatusInternalServerError, "ServeBlob", err) | |||
} | |||
} | |||
@@ -126,7 +126,7 @@ func GetArchive(ctx *context.APIContext) { | |||
ctx.Repo.GitRepo = gitRepo | |||
defer gitRepo.Close() | |||
repo.Download(ctx.Context) | |||
common.Download(ctx.Context) | |||
} | |||
// GetEditorconfig get editor config of a repository |
@@ -0,0 +1,39 @@ | |||
// Copyright 2021 The Gitea Authors. All rights reserved. | |||
// Use of this source code is governed by a MIT-style | |||
// license that can be found in the LICENSE file. | |||
package common | |||
import ( | |||
"context" | |||
"fmt" | |||
"time" | |||
"code.gitea.io/gitea/models" | |||
"code.gitea.io/gitea/models/migrations" | |||
"code.gitea.io/gitea/modules/log" | |||
"code.gitea.io/gitea/modules/setting" | |||
) | |||
// InitDBEngine In case of problems connecting to DB, retry connection. Eg, PGSQL in Docker Container on Synology | |||
func InitDBEngine(ctx context.Context) (err error) { | |||
log.Info("Beginning ORM engine initialization.") | |||
for i := 0; i < setting.Database.DBConnectRetries; i++ { | |||
select { | |||
case <-ctx.Done(): | |||
return fmt.Errorf("Aborted due to shutdown:\nin retry ORM engine initialization") | |||
default: | |||
} | |||
log.Info("ORM engine initialization attempt #%d/%d...", i+1, setting.Database.DBConnectRetries) | |||
if err = models.NewEngine(ctx, migrations.Migrate); err == nil { | |||
break | |||
} else if i == setting.Database.DBConnectRetries-1 { | |||
return err | |||
} | |||
log.Error("ORM engine initialization attempt #%d/%d failed. Error: %v", i+1, setting.Database.DBConnectRetries, err) | |||
log.Info("Backing off for %d seconds", int64(setting.Database.DBConnectBackoff/time.Second)) | |||
time.Sleep(setting.Database.DBConnectBackoff) | |||
} | |||
models.HasEngine = true | |||
return nil | |||
} |
@@ -0,0 +1,33 @@ | |||
// Copyright 2021 The Gitea Authors. All rights reserved. | |||
// Use of this source code is governed by a MIT-style | |||
// license that can be found in the LICENSE file. | |||
package common | |||
import ( | |||
"net/http" | |||
"time" | |||
"code.gitea.io/gitea/modules/context" | |||
"code.gitea.io/gitea/modules/log" | |||
) | |||
// LoggerHandler is a handler that will log the routing to the default gitea log | |||
func LoggerHandler(level log.Level) func(next http.Handler) http.Handler { | |||
return func(next http.Handler) http.Handler { | |||
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { | |||
start := time.Now() | |||
_ = log.GetLogger("router").Log(0, level, "Started %s %s for %s", log.ColoredMethod(req.Method), req.URL.RequestURI(), req.RemoteAddr) | |||
next.ServeHTTP(w, req) | |||
var status int | |||
if v, ok := w.(context.ResponseWriter); ok { | |||
status = v.Status() | |||
} | |||
_ = log.GetLogger("router").Log(0, level, "Completed %s %s %v %s in %v", log.ColoredMethod(req.Method), req.URL.RequestURI(), log.ColoredStatus(status), log.ColoredStatus(status, http.StatusText(status)), log.ColoredTime(time.Since(start))) | |||
}) | |||
} | |||
} |
@@ -0,0 +1,76 @@ | |||
// Copyright 2021 The Gitea Authors. All rights reserved. | |||
// Use of this source code is governed by a MIT-style | |||
// license that can be found in the LICENSE file. | |||
package common | |||
import ( | |||
"fmt" | |||
"net/http" | |||
"strings" | |||
"code.gitea.io/gitea/modules/context" | |||
"code.gitea.io/gitea/modules/log" | |||
"code.gitea.io/gitea/modules/setting" | |||
"github.com/chi-middleware/proxy" | |||
"github.com/go-chi/chi/middleware" | |||
) | |||
// Middlewares returns common middlewares | |||
func Middlewares() []func(http.Handler) http.Handler { | |||
var handlers = []func(http.Handler) http.Handler{ | |||
func(next http.Handler) http.Handler { | |||
return http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) { | |||
next.ServeHTTP(context.NewResponse(resp), req) | |||
}) | |||
}, | |||
} | |||
if setting.ReverseProxyLimit > 0 { | |||
opt := proxy.NewForwardedHeadersOptions(). | |||
WithForwardLimit(setting.ReverseProxyLimit). | |||
ClearTrustedProxies() | |||
for _, n := range setting.ReverseProxyTrustedProxies { | |||
if !strings.Contains(n, "/") { | |||
opt.AddTrustedProxy(n) | |||
} else { | |||
opt.AddTrustedNetwork(n) | |||
} | |||
} | |||
handlers = append(handlers, proxy.ForwardedHeaders(opt)) | |||
} | |||
handlers = append(handlers, middleware.StripSlashes) | |||
if !setting.DisableRouterLog && setting.RouterLogLevel != log.NONE { | |||
if log.GetLogger("router").GetLevel() <= setting.RouterLogLevel { | |||
handlers = append(handlers, LoggerHandler(setting.RouterLogLevel)) | |||
} | |||
} | |||
if setting.EnableAccessLog { | |||
handlers = append(handlers, context.AccessLogger()) | |||
} | |||
handlers = append(handlers, func(next http.Handler) http.Handler { | |||
return http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) { | |||
// Why we need this? The Recovery() will try to render a beautiful | |||
// error page for user, but the process can still panic again, and other | |||
// middleware like session also may panic then we have to recover twice | |||
// and send a simple error page that should not panic any more. | |||
defer func() { | |||
if err := recover(); err != nil { | |||
combinedErr := fmt.Sprintf("PANIC: %v\n%s", err, string(log.Stack(2))) | |||
log.Error("%v", combinedErr) | |||
if setting.IsProd() { | |||
http.Error(resp, http.StatusText(500), 500) | |||
} else { | |||
http.Error(resp, combinedErr, 500) | |||
} | |||
} | |||
}() | |||
next.ServeHTTP(resp, req) | |||
}) | |||
}) | |||
return handlers | |||
} |
@@ -0,0 +1,127 @@ | |||
// Copyright 2021 The Gitea Authors. All rights reserved. | |||
// Use of this source code is governed by a MIT-style | |||
// license that can be found in the LICENSE file. | |||
package common | |||
import ( | |||
"fmt" | |||
"io" | |||
"net/http" | |||
"path" | |||
"path/filepath" | |||
"strings" | |||
"code.gitea.io/gitea/modules/charset" | |||
"code.gitea.io/gitea/modules/context" | |||
"code.gitea.io/gitea/modules/git" | |||
"code.gitea.io/gitea/modules/httpcache" | |||
"code.gitea.io/gitea/modules/log" | |||
"code.gitea.io/gitea/modules/setting" | |||
"code.gitea.io/gitea/modules/typesniffer" | |||
"code.gitea.io/gitea/services/archiver" | |||
) | |||
// ServeBlob download a git.Blob | |||
func ServeBlob(ctx *context.Context, blob *git.Blob) error { | |||
if httpcache.HandleGenericETagCache(ctx.Req, ctx.Resp, `"`+blob.ID.String()+`"`) { | |||
return nil | |||
} | |||
dataRc, err := blob.DataAsync() | |||
if err != nil { | |||
return err | |||
} | |||
defer func() { | |||
if err = dataRc.Close(); err != nil { | |||
log.Error("ServeBlob: Close: %v", err) | |||
} | |||
}() | |||
return ServeData(ctx, ctx.Repo.TreePath, blob.Size(), dataRc) | |||
} | |||
// Download an archive of a repository | |||
func Download(ctx *context.Context) { | |||
uri := ctx.Params("*") | |||
aReq := archiver.DeriveRequestFrom(ctx, uri) | |||
if aReq == nil { | |||
ctx.Error(http.StatusNotFound) | |||
return | |||
} | |||
downloadName := ctx.Repo.Repository.Name + "-" + aReq.GetArchiveName() | |||
complete := aReq.IsComplete() | |||
if !complete { | |||
aReq = archiver.ArchiveRepository(aReq) | |||
complete = aReq.WaitForCompletion(ctx) | |||
} | |||
if complete { | |||
ctx.ServeFile(aReq.GetArchivePath(), downloadName) | |||
} else { | |||
ctx.Error(http.StatusNotFound) | |||
} | |||
} | |||
// ServeData download file from io.Reader | |||
func ServeData(ctx *context.Context, name string, size int64, reader io.Reader) error { | |||
buf := make([]byte, 1024) | |||
n, err := reader.Read(buf) | |||
if err != nil && err != io.EOF { | |||
return err | |||
} | |||
if n >= 0 { | |||
buf = buf[:n] | |||
} | |||
ctx.Resp.Header().Set("Cache-Control", "public,max-age=86400") | |||
if size >= 0 { | |||
ctx.Resp.Header().Set("Content-Length", fmt.Sprintf("%d", size)) | |||
} else { | |||
log.Error("ServeData called to serve data: %s with size < 0: %d", name, size) | |||
} | |||
name = path.Base(name) | |||
// Google Chrome dislike commas in filenames, so let's change it to a space | |||
name = strings.ReplaceAll(name, ",", " ") | |||
st := typesniffer.DetectContentType(buf) | |||
if st.IsText() || ctx.QueryBool("render") { | |||
cs, err := charset.DetectEncoding(buf) | |||
if err != nil { | |||
log.Error("Detect raw file %s charset failed: %v, using by default utf-8", name, err) | |||
cs = "utf-8" | |||
} | |||
ctx.Resp.Header().Set("Content-Type", "text/plain; charset="+strings.ToLower(cs)) | |||
} else { | |||
ctx.Resp.Header().Set("Access-Control-Expose-Headers", "Content-Disposition") | |||
if (st.IsImage() || st.IsPDF()) && (setting.UI.SVG.Enabled || !st.IsSvgImage()) { | |||
ctx.Resp.Header().Set("Content-Disposition", fmt.Sprintf(`inline; filename="%s"`, name)) | |||
if st.IsSvgImage() { | |||
ctx.Resp.Header().Set("Content-Security-Policy", "default-src 'none'; style-src 'unsafe-inline'; sandbox") | |||
ctx.Resp.Header().Set("X-Content-Type-Options", "nosniff") | |||
ctx.Resp.Header().Set("Content-Type", typesniffer.SvgMimeType) | |||
} | |||
} else { | |||
ctx.Resp.Header().Set("Content-Disposition", fmt.Sprintf(`attachment; filename="%s"`, name)) | |||
if setting.MimeTypeMap.Enabled { | |||
fileExtension := strings.ToLower(filepath.Ext(name)) | |||
if mimetype, ok := setting.MimeTypeMap.Map[fileExtension]; ok { | |||
ctx.Resp.Header().Set("Content-Type", mimetype) | |||
} | |||
} | |||
} | |||
} | |||
_, err = ctx.Resp.Write(buf) | |||
if err != nil { | |||
return err | |||
} | |||
_, err = io.Copy(ctx.Resp, reader) | |||
return err | |||
} |
@@ -1,413 +0,0 @@ | |||
// Copyright 2014 The Gogs Authors. All rights reserved. | |||
// Copyright 2019 The Gitea Authors. All rights reserved. | |||
// Use of this source code is governed by a MIT-style | |||
// license that can be found in the LICENSE file. | |||
package routers | |||
import ( | |||
"bytes" | |||
"net/http" | |||
"strings" | |||
"code.gitea.io/gitea/models" | |||
"code.gitea.io/gitea/modules/base" | |||
"code.gitea.io/gitea/modules/context" | |||
code_indexer "code.gitea.io/gitea/modules/indexer/code" | |||
"code.gitea.io/gitea/modules/log" | |||
"code.gitea.io/gitea/modules/setting" | |||
"code.gitea.io/gitea/modules/structs" | |||
"code.gitea.io/gitea/modules/util" | |||
"code.gitea.io/gitea/modules/web/middleware" | |||
"code.gitea.io/gitea/routers/user" | |||
) | |||
const ( | |||
// tplHome home page template | |||
tplHome base.TplName = "home" | |||
// tplExploreRepos explore repositories page template | |||
tplExploreRepos base.TplName = "explore/repos" | |||
// tplExploreUsers explore users page template | |||
tplExploreUsers base.TplName = "explore/users" | |||
// tplExploreOrganizations explore organizations page template | |||
tplExploreOrganizations base.TplName = "explore/organizations" | |||
// tplExploreCode explore code page template | |||
tplExploreCode base.TplName = "explore/code" | |||
) | |||
// Home render home page | |||
func Home(ctx *context.Context) { | |||
if ctx.IsSigned { | |||
if !ctx.User.IsActive && setting.Service.RegisterEmailConfirm { | |||
ctx.Data["Title"] = ctx.Tr("auth.active_your_account") | |||
ctx.HTML(http.StatusOK, user.TplActivate) | |||
} else if !ctx.User.IsActive || ctx.User.ProhibitLogin { | |||
log.Info("Failed authentication attempt for %s from %s", ctx.User.Name, ctx.RemoteAddr()) | |||
ctx.Data["Title"] = ctx.Tr("auth.prohibit_login") | |||
ctx.HTML(http.StatusOK, "user/auth/prohibit_login") | |||
} else if ctx.User.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.GetCookie(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) | |||
} | |||
// RepoSearchOptions when calling search repositories | |||
type RepoSearchOptions struct { | |||
OwnerID int64 | |||
Private bool | |||
Restricted bool | |||
PageSize int | |||
TplName base.TplName | |||
} | |||
var ( | |||
nullByte = []byte{0x00} | |||
) | |||
func isKeywordValid(keyword string) bool { | |||
return !bytes.Contains([]byte(keyword), nullByte) | |||
} | |||
// RenderRepoSearch render repositories search page | |||
func RenderRepoSearch(ctx *context.Context, opts *RepoSearchOptions) { | |||
page := ctx.QueryInt("page") | |||
if page <= 0 { | |||
page = 1 | |||
} | |||
var ( | |||
repos []*models.Repository | |||
count int64 | |||
err error | |||
orderBy models.SearchOrderBy | |||
) | |||
ctx.Data["SortType"] = ctx.Query("sort") | |||
switch ctx.Query("sort") { | |||
case "newest": | |||
orderBy = models.SearchOrderByNewest | |||
case "oldest": | |||
orderBy = models.SearchOrderByOldest | |||
case "recentupdate": | |||
orderBy = models.SearchOrderByRecentUpdated | |||
case "leastupdate": | |||
orderBy = models.SearchOrderByLeastUpdated | |||
case "reversealphabetically": | |||
orderBy = models.SearchOrderByAlphabeticallyReverse | |||
case "alphabetically": | |||
orderBy = models.SearchOrderByAlphabetically | |||
case "reversesize": | |||
orderBy = models.SearchOrderBySizeReverse | |||
case "size": | |||
orderBy = models.SearchOrderBySize | |||
case "moststars": | |||
orderBy = models.SearchOrderByStarsReverse | |||
case "feweststars": | |||
orderBy = models.SearchOrderByStars | |||
case "mostforks": | |||
orderBy = models.SearchOrderByForksReverse | |||
case "fewestforks": | |||
orderBy = models.SearchOrderByForks | |||
default: | |||
ctx.Data["SortType"] = "recentupdate" | |||
orderBy = models.SearchOrderByRecentUpdated | |||
} | |||
keyword := strings.Trim(ctx.Query("q"), " ") | |||
topicOnly := ctx.QueryBool("topic") | |||
ctx.Data["TopicOnly"] = topicOnly | |||
repos, count, err = models.SearchRepository(&models.SearchRepoOptions{ | |||
ListOptions: models.ListOptions{ | |||
Page: page, | |||
PageSize: opts.PageSize, | |||
}, | |||
Actor: ctx.User, | |||
OrderBy: orderBy, | |||
Private: opts.Private, | |||
Keyword: keyword, | |||
OwnerID: opts.OwnerID, | |||
AllPublic: true, | |||
AllLimited: true, | |||
TopicOnly: topicOnly, | |||
IncludeDescription: setting.UI.SearchRepoDescription, | |||
}) | |||
if err != nil { | |||
ctx.ServerError("SearchRepository", err) | |||
return | |||
} | |||
ctx.Data["Keyword"] = keyword | |||
ctx.Data["Total"] = count | |||
ctx.Data["Repos"] = repos | |||
ctx.Data["IsRepoIndexerEnabled"] = setting.Indexer.RepoIndexerEnabled | |||
pager := context.NewPagination(int(count), opts.PageSize, page, 5) | |||
pager.SetDefaultParams(ctx) | |||
pager.AddParam(ctx, "topic", "TopicOnly") | |||
ctx.Data["Page"] = pager | |||
ctx.HTML(http.StatusOK, opts.TplName) | |||
} | |||
// ExploreRepos render explore repositories page | |||
func ExploreRepos(ctx *context.Context) { | |||
ctx.Data["UsersIsDisabled"] = setting.Service.Explore.DisableUsersPage | |||
ctx.Data["Title"] = ctx.Tr("explore") | |||
ctx.Data["PageIsExplore"] = true | |||
ctx.Data["PageIsExploreRepositories"] = true | |||
ctx.Data["IsRepoIndexerEnabled"] = setting.Indexer.RepoIndexerEnabled | |||
var ownerID int64 | |||
if ctx.User != nil && !ctx.User.IsAdmin { | |||
ownerID = ctx.User.ID | |||
} | |||
RenderRepoSearch(ctx, &RepoSearchOptions{ | |||
PageSize: setting.UI.ExplorePagingNum, | |||
OwnerID: ownerID, | |||
Private: ctx.User != nil, | |||
TplName: tplExploreRepos, | |||
}) | |||
} | |||
// RenderUserSearch render user search page | |||
func RenderUserSearch(ctx *context.Context, opts *models.SearchUserOptions, tplName base.TplName) { | |||
opts.Page = ctx.QueryInt("page") | |||
if opts.Page <= 1 { | |||
opts.Page = 1 | |||
} | |||
var ( | |||
users []*models.User | |||
count int64 | |||
err error | |||
orderBy models.SearchOrderBy | |||
) | |||
ctx.Data["SortType"] = ctx.Query("sort") | |||
switch ctx.Query("sort") { | |||
case "newest": | |||
orderBy = models.SearchOrderByIDReverse | |||
case "oldest": | |||
orderBy = models.SearchOrderByID | |||
case "recentupdate": | |||
orderBy = models.SearchOrderByRecentUpdated | |||
case "leastupdate": | |||
orderBy = models.SearchOrderByLeastUpdated | |||
case "reversealphabetically": | |||
orderBy = models.SearchOrderByAlphabeticallyReverse | |||
case "alphabetically": | |||
orderBy = models.SearchOrderByAlphabetically | |||
default: | |||
ctx.Data["SortType"] = "alphabetically" | |||
orderBy = models.SearchOrderByAlphabetically | |||
} | |||
opts.Keyword = strings.Trim(ctx.Query("q"), " ") | |||
opts.OrderBy = orderBy | |||
if len(opts.Keyword) == 0 || isKeywordValid(opts.Keyword) { | |||
users, count, err = models.SearchUsers(opts) | |||
if err != nil { | |||
ctx.ServerError("SearchUsers", err) | |||
return | |||
} | |||
} | |||
ctx.Data["Keyword"] = opts.Keyword | |||
ctx.Data["Total"] = count | |||
ctx.Data["Users"] = users | |||
ctx.Data["UsersTwoFaStatus"] = models.UserList(users).GetTwoFaStatus() | |||
ctx.Data["ShowUserEmail"] = setting.UI.ShowUserEmail | |||
ctx.Data["IsRepoIndexerEnabled"] = setting.Indexer.RepoIndexerEnabled | |||
pager := context.NewPagination(int(count), opts.PageSize, opts.Page, 5) | |||
pager.SetDefaultParams(ctx) | |||
ctx.Data["Page"] = pager | |||
ctx.HTML(http.StatusOK, tplName) | |||
} | |||
// ExploreUsers render explore users page | |||
func ExploreUsers(ctx *context.Context) { | |||
if setting.Service.Explore.DisableUsersPage { | |||
ctx.Redirect(setting.AppSubURL + "/explore/repos") | |||
return | |||
} | |||
ctx.Data["Title"] = ctx.Tr("explore") | |||
ctx.Data["PageIsExplore"] = true | |||
ctx.Data["PageIsExploreUsers"] = true | |||
ctx.Data["IsRepoIndexerEnabled"] = setting.Indexer.RepoIndexerEnabled | |||
RenderUserSearch(ctx, &models.SearchUserOptions{ | |||
Actor: ctx.User, | |||
Type: models.UserTypeIndividual, | |||
ListOptions: models.ListOptions{PageSize: setting.UI.ExplorePagingNum}, | |||
IsActive: util.OptionalBoolTrue, | |||
Visible: []structs.VisibleType{structs.VisibleTypePublic, structs.VisibleTypeLimited, structs.VisibleTypePrivate}, | |||
}, tplExploreUsers) | |||
} | |||
// ExploreOrganizations render explore organizations page | |||
func ExploreOrganizations(ctx *context.Context) { | |||
ctx.Data["UsersIsDisabled"] = setting.Service.Explore.DisableUsersPage | |||
ctx.Data["Title"] = ctx.Tr("explore") | |||
ctx.Data["PageIsExplore"] = true | |||
ctx.Data["PageIsExploreOrganizations"] = true | |||
ctx.Data["IsRepoIndexerEnabled"] = setting.Indexer.RepoIndexerEnabled | |||
visibleTypes := []structs.VisibleType{structs.VisibleTypePublic} | |||
if ctx.User != nil { | |||
visibleTypes = append(visibleTypes, structs.VisibleTypeLimited, structs.VisibleTypePrivate) | |||
} | |||
RenderUserSearch(ctx, &models.SearchUserOptions{ | |||
Actor: ctx.User, | |||
Type: models.UserTypeOrganization, | |||
ListOptions: models.ListOptions{PageSize: setting.UI.ExplorePagingNum}, | |||
Visible: visibleTypes, | |||
}, tplExploreOrganizations) | |||
} | |||
// ExploreCode render explore code page | |||
func ExploreCode(ctx *context.Context) { | |||
if !setting.Indexer.RepoIndexerEnabled { | |||
ctx.Redirect(setting.AppSubURL+"/explore", 302) | |||
return | |||
} | |||
ctx.Data["UsersIsDisabled"] = setting.Service.Explore.DisableUsersPage | |||
ctx.Data["IsRepoIndexerEnabled"] = setting.Indexer.RepoIndexerEnabled | |||
ctx.Data["Title"] = ctx.Tr("explore") | |||
ctx.Data["PageIsExplore"] = true | |||
ctx.Data["PageIsExploreCode"] = true | |||
language := strings.TrimSpace(ctx.Query("l")) | |||
keyword := strings.TrimSpace(ctx.Query("q")) | |||
page := ctx.QueryInt("page") | |||
if page <= 0 { | |||
page = 1 | |||
} | |||
queryType := strings.TrimSpace(ctx.Query("t")) | |||
isMatch := queryType == "match" | |||
var ( | |||
repoIDs []int64 | |||
err error | |||
isAdmin bool | |||
) | |||
if ctx.User != nil { | |||
isAdmin = ctx.User.IsAdmin | |||
} | |||
// guest user or non-admin user | |||
if ctx.User == nil || !isAdmin { | |||
repoIDs, err = models.FindUserAccessibleRepoIDs(ctx.User) | |||
if err != nil { | |||
ctx.ServerError("SearchResults", err) | |||
return | |||
} | |||
} | |||
var ( | |||
total int | |||
searchResults []*code_indexer.Result | |||
searchResultLanguages []*code_indexer.SearchResultLanguages | |||
) | |||
// if non-admin login user, we need check UnitTypeCode at first | |||
if ctx.User != nil && len(repoIDs) > 0 { | |||
repoMaps, err := models.GetRepositoriesMapByIDs(repoIDs) | |||
if err != nil { | |||
ctx.ServerError("SearchResults", err) | |||
return | |||
} | |||
var rightRepoMap = make(map[int64]*models.Repository, len(repoMaps)) | |||
repoIDs = make([]int64, 0, len(repoMaps)) | |||
for id, repo := range repoMaps { | |||
if repo.CheckUnitUser(ctx.User, models.UnitTypeCode) { | |||
rightRepoMap[id] = repo | |||
repoIDs = append(repoIDs, id) | |||
} | |||
} | |||
ctx.Data["RepoMaps"] = rightRepoMap | |||
total, searchResults, searchResultLanguages, err = code_indexer.PerformSearch(repoIDs, language, keyword, page, setting.UI.RepoSearchPagingNum, isMatch) | |||
if err != nil { | |||
ctx.ServerError("SearchResults", err) | |||
return | |||
} | |||
// if non-login user or isAdmin, no need to check UnitTypeCode | |||
} else if (ctx.User == nil && len(repoIDs) > 0) || isAdmin { | |||
total, searchResults, searchResultLanguages, err = code_indexer.PerformSearch(repoIDs, language, keyword, page, setting.UI.RepoSearchPagingNum, isMatch) | |||
if err != nil { | |||
ctx.ServerError("SearchResults", err) | |||
return | |||
} | |||
var loadRepoIDs = make([]int64, 0, len(searchResults)) | |||
for _, result := range searchResults { | |||
var find bool | |||
for _, id := range loadRepoIDs { | |||
if id == result.RepoID { | |||
find = true | |||
break | |||
} | |||
} | |||
if !find { | |||
loadRepoIDs = append(loadRepoIDs, result.RepoID) | |||
} | |||
} | |||
repoMaps, err := models.GetRepositoriesMapByIDs(loadRepoIDs) | |||
if err != nil { | |||
ctx.ServerError("SearchResults", err) | |||
return | |||
} | |||
ctx.Data["RepoMaps"] = repoMaps | |||
} | |||
ctx.Data["Keyword"] = keyword | |||
ctx.Data["Language"] = language | |||
ctx.Data["queryType"] = queryType | |||
ctx.Data["SearchResults"] = searchResults | |||
ctx.Data["SearchResultLanguages"] = searchResultLanguages | |||
ctx.Data["RequireHighlightJS"] = true | |||
ctx.Data["PageIsViewCode"] = true | |||
pager := context.NewPagination(total, setting.UI.RepoSearchPagingNum, page, 5) | |||
pager.SetDefaultParams(ctx) | |||
pager.AddParam(ctx, "l", "Language") | |||
ctx.Data["Page"] = pager | |||
ctx.HTML(http.StatusOK, tplExploreCode) | |||
} | |||
// NotFound render 404 page | |||
func NotFound(ctx *context.Context) { | |||
ctx.Data["Title"] = "Page Not Found" | |||
ctx.NotFound("home.NotFound", nil) | |||
} |
@@ -6,12 +6,9 @@ package routers | |||
import ( | |||
"context" | |||
"fmt" | |||
"strings" | |||
"time" | |||
"code.gitea.io/gitea/models" | |||
"code.gitea.io/gitea/models/migrations" | |||
"code.gitea.io/gitea/modules/auth/sso" | |||
"code.gitea.io/gitea/modules/cache" | |||
"code.gitea.io/gitea/modules/cron" | |||
@@ -32,6 +29,11 @@ import ( | |||
"code.gitea.io/gitea/modules/svg" | |||
"code.gitea.io/gitea/modules/task" | |||
"code.gitea.io/gitea/modules/translation" | |||
"code.gitea.io/gitea/modules/web" | |||
apiv1 "code.gitea.io/gitea/routers/api/v1" | |||
"code.gitea.io/gitea/routers/common" | |||
"code.gitea.io/gitea/routers/private" | |||
web_routers "code.gitea.io/gitea/routers/web" | |||
"code.gitea.io/gitea/services/mailer" | |||
mirror_service "code.gitea.io/gitea/services/mirror" | |||
pull_service "code.gitea.io/gitea/services/pull" | |||
@@ -63,63 +65,6 @@ func NewServices() { | |||
notification.NewContext() | |||
} | |||
// In case of problems connecting to DB, retry connection. Eg, PGSQL in Docker Container on Synology | |||
func initDBEngine(ctx context.Context) (err error) { | |||
log.Info("Beginning ORM engine initialization.") | |||
for i := 0; i < setting.Database.DBConnectRetries; i++ { | |||
select { | |||
case <-ctx.Done(): | |||
return fmt.Errorf("Aborted due to shutdown:\nin retry ORM engine initialization") | |||
default: | |||
} | |||
log.Info("ORM engine initialization attempt #%d/%d...", i+1, setting.Database.DBConnectRetries) | |||
if err = models.NewEngine(ctx, migrations.Migrate); err == nil { | |||
break | |||
} else if i == setting.Database.DBConnectRetries-1 { | |||
return err | |||
} | |||
log.Error("ORM engine initialization attempt #%d/%d failed. Error: %v", i+1, setting.Database.DBConnectRetries, err) | |||
log.Info("Backing off for %d seconds", int64(setting.Database.DBConnectBackoff/time.Second)) | |||
time.Sleep(setting.Database.DBConnectBackoff) | |||
} | |||
models.HasEngine = true | |||
return nil | |||
} | |||
// PreInstallInit preloads the configuration to check if we need to run install | |||
func PreInstallInit(ctx context.Context) bool { | |||
setting.NewContext() | |||
if !setting.InstallLock { | |||
log.Trace("AppPath: %s", setting.AppPath) | |||
log.Trace("AppWorkPath: %s", setting.AppWorkPath) | |||
log.Trace("Custom path: %s", setting.CustomPath) | |||
log.Trace("Log path: %s", setting.LogRootPath) | |||
log.Trace("Preparing to run install page") | |||
translation.InitLocales() | |||
if setting.EnableSQLite3 { | |||
log.Info("SQLite3 Supported") | |||
} | |||
setting.InitDBConfig() | |||
svg.Init() | |||
} | |||
return !setting.InstallLock | |||
} | |||
// PostInstallInit rereads the settings and starts up the database | |||
func PostInstallInit(ctx context.Context) { | |||
setting.NewContext() | |||
setting.InitDBConfig() | |||
if setting.InstallLock { | |||
if err := initDBEngine(ctx); err == nil { | |||
log.Info("ORM engine initialization successful!") | |||
} else { | |||
log.Fatal("ORM engine initialization failed: %v", err) | |||
} | |||
svg.Init() | |||
} | |||
} | |||
// GlobalInit is for global configuration reload-able. | |||
func GlobalInit(ctx context.Context) { | |||
setting.NewContext() | |||
@@ -151,7 +96,7 @@ func GlobalInit(ctx context.Context) { | |||
} else if setting.Database.UseSQLite3 { | |||
log.Fatal("SQLite3 is set in settings but NOT Supported") | |||
} | |||
if err := initDBEngine(ctx); err == nil { | |||
if err := common.InitDBEngine(ctx); err == nil { | |||
log.Info("ORM engine initialization successful!") | |||
} else { | |||
log.Fatal("ORM engine initialization failed: %v", err) | |||
@@ -193,3 +138,16 @@ func GlobalInit(ctx context.Context) { | |||
svg.Init() | |||
} | |||
// NormalRoutes represents non install routes | |||
func NormalRoutes() *web.Route { | |||
r := web.NewRoute() | |||
for _, middle := range common.Middlewares() { | |||
r.Use(middle) | |||
} | |||
r.Mount("/", web_routers.Routes()) | |||
r.Mount("/api/v1", apiv1.Routes()) | |||
r.Mount("/api/internal", private.Routes()) | |||
return r | |||
} |
@@ -1,8 +1,9 @@ | |||
// Copyright 2014 The Gogs Authors. All rights reserved. | |||
// Copyright 2021 The Gitea Authors. All rights reserved. | |||
// Use of this source code is governed by a MIT-style | |||
// license that can be found in the LICENSE file. | |||
package routers | |||
package install | |||
import ( | |||
"fmt" | |||
@@ -38,8 +39,8 @@ const ( | |||
tplPostInstall base.TplName = "post-install" | |||
) | |||
// InstallInit prepare for rendering installation page | |||
func InstallInit(next http.Handler) http.Handler { | |||
// Init prepare for rendering installation page | |||
func Init(next http.Handler) http.Handler { | |||
var rnd = templates.HTMLRenderer() | |||
return http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) { | |||
@@ -158,8 +159,8 @@ func Install(ctx *context.Context) { | |||
ctx.HTML(http.StatusOK, tplInstall) | |||
} | |||
// InstallPost response for submit install items | |||
func InstallPost(ctx *context.Context) { | |||
// SubmitInstall response for submit install items | |||
func SubmitInstall(ctx *context.Context) { | |||
form := *web.GetForm(ctx).(*forms.InstallForm) | |||
var err error | |||
ctx.Data["CurDbOption"] = form.DbType | |||
@@ -409,7 +410,7 @@ func InstallPost(ctx *context.Context) { | |||
} | |||
// Re-read settings | |||
PostInstallInit(ctx) | |||
ReloadSettings(ctx) | |||
// Create admin account | |||
if len(form.AdminName) > 0 { |
@@ -2,7 +2,7 @@ | |||
// Use of this source code is governed by a MIT-style | |||
// license that can be found in the LICENSE file. | |||
package routes | |||
package install | |||
import ( | |||
"fmt" | |||
@@ -15,12 +15,18 @@ import ( | |||
"code.gitea.io/gitea/modules/templates" | |||
"code.gitea.io/gitea/modules/web" | |||
"code.gitea.io/gitea/modules/web/middleware" | |||
"code.gitea.io/gitea/routers" | |||
"code.gitea.io/gitea/routers/common" | |||
"code.gitea.io/gitea/services/forms" | |||
"gitea.com/go-chi/session" | |||
) | |||
type dataStore map[string]interface{} | |||
func (d *dataStore) GetData() map[string]interface{} { | |||
return *d | |||
} | |||
func installRecovery() func(next http.Handler) http.Handler { | |||
var rnd = templates.HTMLRenderer() | |||
return func(next http.Handler) http.Handler { | |||
@@ -48,21 +54,19 @@ func installRecovery() func(next http.Handler) http.Handler { | |||
lc := middleware.Locale(w, req) | |||
var store = dataStore{ | |||
Data: templates.Vars{ | |||
"Language": lc.Language(), | |||
"CurrentURL": setting.AppSubURL + req.URL.RequestURI(), | |||
"i18n": lc, | |||
"SignedUserID": int64(0), | |||
"SignedUserName": "", | |||
}, | |||
"Language": lc.Language(), | |||
"CurrentURL": setting.AppSubURL + req.URL.RequestURI(), | |||
"i18n": lc, | |||
"SignedUserID": int64(0), | |||
"SignedUserName": "", | |||
} | |||
w.Header().Set(`X-Frame-Options`, `SAMEORIGIN`) | |||
if !setting.IsProd() { | |||
store.Data["ErrorMsg"] = combinedErr | |||
store["ErrorMsg"] = combinedErr | |||
} | |||
err = rnd.HTML(w, 500, "status/500", templates.BaseVars().Merge(store.Data)) | |||
err = rnd.HTML(w, 500, "status/500", templates.BaseVars().Merge(store)) | |||
if err != nil { | |||
log.Error("%v", err) | |||
} | |||
@@ -74,10 +78,10 @@ func installRecovery() func(next http.Handler) http.Handler { | |||
} | |||
} | |||
// InstallRoutes registers the install routes | |||
func InstallRoutes() *web.Route { | |||
// Routes registers the install routes | |||
func Routes() *web.Route { | |||
r := web.NewRoute() | |||
for _, middle := range commonMiddlewares() { | |||
for _, middle := range common.Middlewares() { | |||
r.Use(middle) | |||
} | |||
@@ -99,9 +103,9 @@ func InstallRoutes() *web.Route { | |||
})) | |||
r.Use(installRecovery()) | |||
r.Use(routers.InstallInit) | |||
r.Get("/", routers.Install) | |||
r.Post("/", web.Bind(forms.InstallForm{}), routers.InstallPost) | |||
r.Use(Init) | |||
r.Get("/", Install) | |||
r.Post("/", web.Bind(forms.InstallForm{}), SubmitInstall) | |||
r.NotFound(func(w http.ResponseWriter, req *http.Request) { | |||
http.Redirect(w, req, setting.AppURL, http.StatusFound) | |||
}) |
@@ -0,0 +1,49 @@ | |||
// Copyright 2021 The Gitea Authors. All rights reserved. | |||
// Use of this source code is governed by a MIT-style | |||
// license that can be found in the LICENSE file. | |||
package install | |||
import ( | |||
"context" | |||
"code.gitea.io/gitea/modules/log" | |||
"code.gitea.io/gitea/modules/setting" | |||
"code.gitea.io/gitea/modules/svg" | |||
"code.gitea.io/gitea/modules/translation" | |||
"code.gitea.io/gitea/routers/common" | |||
) | |||
// PreloadSettings preloads the configuration to check if we need to run install | |||
func PreloadSettings(ctx context.Context) bool { | |||
setting.NewContext() | |||
if !setting.InstallLock { | |||
log.Trace("AppPath: %s", setting.AppPath) | |||
log.Trace("AppWorkPath: %s", setting.AppWorkPath) | |||
log.Trace("Custom path: %s", setting.CustomPath) | |||
log.Trace("Log path: %s", setting.LogRootPath) | |||
log.Trace("Preparing to run install page") | |||
translation.InitLocales() | |||
if setting.EnableSQLite3 { | |||
log.Info("SQLite3 Supported") | |||
} | |||
setting.InitDBConfig() | |||
svg.Init() | |||
} | |||
return !setting.InstallLock | |||
} | |||
// ReloadSettings rereads the settings and starts up the database | |||
func ReloadSettings(ctx context.Context) { | |||
setting.NewContext() | |||
setting.InitDBConfig() | |||
if setting.InstallLock { | |||
if err := common.InitDBEngine(ctx); err == nil { | |||
log.Info("ORM engine initialization successful!") | |||
} else { | |||
log.Fatal("ORM engine initialization failed: %v", err) | |||
} | |||
svg.Init() | |||
} | |||
} |
@@ -2,7 +2,7 @@ | |||
// Use of this source code is governed by a MIT-style | |||
// license that can be found in the LICENSE file. | |||
package setting | |||
package admin | |||
import ( | |||
"path/filepath" |
@@ -11,7 +11,7 @@ import ( | |||
"code.gitea.io/gitea/modules/context" | |||
"code.gitea.io/gitea/modules/setting" | |||
"code.gitea.io/gitea/modules/structs" | |||
"code.gitea.io/gitea/routers" | |||
"code.gitea.io/gitea/routers/web/explore" | |||
) | |||
const ( | |||
@@ -24,7 +24,7 @@ func Organizations(ctx *context.Context) { | |||
ctx.Data["PageIsAdmin"] = true | |||
ctx.Data["PageIsAdminOrganizations"] = true | |||
routers.RenderUserSearch(ctx, &models.SearchUserOptions{ | |||
explore.RenderUserSearch(ctx, &models.SearchUserOptions{ | |||
Type: models.UserTypeOrganization, | |||
ListOptions: models.ListOptions{ | |||
PageSize: setting.UI.Admin.OrgPagingNum, |
@@ -17,7 +17,7 @@ import ( | |||
"code.gitea.io/gitea/modules/repository" | |||
"code.gitea.io/gitea/modules/setting" | |||
"code.gitea.io/gitea/modules/util" | |||
"code.gitea.io/gitea/routers" | |||
"code.gitea.io/gitea/routers/web/explore" | |||
repo_service "code.gitea.io/gitea/services/repository" | |||
) | |||
@@ -32,7 +32,7 @@ func Repos(ctx *context.Context) { | |||
ctx.Data["PageIsAdmin"] = true | |||
ctx.Data["PageIsAdminRepositories"] = true | |||
routers.RenderRepoSearch(ctx, &routers.RepoSearchOptions{ | |||
explore.RenderRepoSearch(ctx, &explore.RepoSearchOptions{ | |||
Private: true, | |||
PageSize: setting.UI.Admin.RepoPagingNum, | |||
TplName: tplRepos, |
@@ -18,8 +18,8 @@ import ( | |||
"code.gitea.io/gitea/modules/password" | |||
"code.gitea.io/gitea/modules/setting" | |||
"code.gitea.io/gitea/modules/web" | |||
"code.gitea.io/gitea/routers" | |||
router_user_setting "code.gitea.io/gitea/routers/user/setting" | |||
"code.gitea.io/gitea/routers/web/explore" | |||
router_user_setting "code.gitea.io/gitea/routers/web/user/setting" | |||
"code.gitea.io/gitea/services/forms" | |||
"code.gitea.io/gitea/services/mailer" | |||
) | |||
@@ -36,7 +36,7 @@ func Users(ctx *context.Context) { | |||
ctx.Data["PageIsAdmin"] = true | |||
ctx.Data["PageIsAdminUsers"] = true | |||
routers.RenderUserSearch(ctx, &models.SearchUserOptions{ | |||
explore.RenderUserSearch(ctx, &models.SearchUserOptions{ | |||
Type: models.UserTypeIndividual, | |||
ListOptions: models.ListOptions{ | |||
PageSize: setting.UI.Admin.UserPagingNum, |
@@ -2,7 +2,7 @@ | |||
// Use of this source code is governed by a MIT-style | |||
// license that can be found in the LICENSE file. | |||
package routes | |||
package web | |||
import ( | |||
"errors" | |||
@@ -13,7 +13,6 @@ import ( | |||
"path" | |||
"path/filepath" | |||
"strings" | |||
"time" | |||
"code.gitea.io/gitea/models" | |||
"code.gitea.io/gitea/modules/auth/sso" | |||
@@ -28,26 +27,6 @@ import ( | |||
"gitea.com/go-chi/session" | |||
) | |||
// LoggerHandler is a handler that will log the routing to the default gitea log | |||
func LoggerHandler(level log.Level) func(next http.Handler) http.Handler { | |||
return func(next http.Handler) http.Handler { | |||
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { | |||
start := time.Now() | |||
_ = log.GetLogger("router").Log(0, level, "Started %s %s for %s", log.ColoredMethod(req.Method), req.URL.RequestURI(), req.RemoteAddr) | |||
next.ServeHTTP(w, req) | |||
var status int | |||
if v, ok := w.(context.ResponseWriter); ok { | |||
status = v.Status() | |||
} | |||
_ = log.GetLogger("router").Log(0, level, "Completed %s %s %v %s in %v", log.ColoredMethod(req.Method), req.URL.RequestURI(), log.ColoredStatus(status), log.ColoredStatus(status, http.StatusText(status)), log.ColoredTime(time.Since(start))) | |||
}) | |||
} | |||
} | |||
func storageHandler(storageSetting setting.Storage, prefix string, objStore storage.ObjectStorage) func(next http.Handler) http.Handler { | |||
return func(next http.Handler) http.Handler { | |||
if storageSetting.ServeDirect { | |||
@@ -134,12 +113,10 @@ func storageHandler(storageSetting setting.Storage, prefix string, objStore stor | |||
} | |||
} | |||
type dataStore struct { | |||
Data map[string]interface{} | |||
} | |||
type dataStore map[string]interface{} | |||
func (d *dataStore) GetData() map[string]interface{} { | |||
return d.Data | |||
return *d | |||
} | |||
// Recovery returns a middleware that recovers from any panics and writes a 500 and a log if so. | |||
@@ -165,11 +142,9 @@ func Recovery() func(next http.Handler) http.Handler { | |||
var lc = middleware.Locale(w, req) | |||
var store = dataStore{ | |||
Data: templates.Vars{ | |||
"Language": lc.Language(), | |||
"CurrentURL": setting.AppSubURL + req.URL.RequestURI(), | |||
"i18n": lc, | |||
}, | |||
"Language": lc.Language(), | |||
"CurrentURL": setting.AppSubURL + req.URL.RequestURI(), | |||
"i18n": lc, | |||
} | |||
var user *models.User | |||
@@ -186,22 +161,22 @@ func Recovery() func(next http.Handler) http.Handler { | |||
user = sso.SessionUser(sessionStore) | |||
} | |||
if user != nil { | |||
store.Data["IsSigned"] = true | |||
store.Data["SignedUser"] = user | |||
store.Data["SignedUserID"] = user.ID | |||
store.Data["SignedUserName"] = user.Name | |||
store.Data["IsAdmin"] = user.IsAdmin | |||
store["IsSigned"] = true | |||
store["SignedUser"] = user | |||
store["SignedUserID"] = user.ID | |||
store["SignedUserName"] = user.Name | |||
store["IsAdmin"] = user.IsAdmin | |||
} else { | |||
store.Data["SignedUserID"] = int64(0) | |||
store.Data["SignedUserName"] = "" | |||
store["SignedUserID"] = int64(0) | |||
store["SignedUserName"] = "" | |||
} | |||
w.Header().Set(`X-Frame-Options`, `SAMEORIGIN`) | |||
if !setting.IsProd() { | |||
store.Data["ErrorMsg"] = combinedErr | |||
store["ErrorMsg"] = combinedErr | |||
} | |||
err = rnd.HTML(w, 500, "status/500", templates.BaseVars().Merge(store.Data)) | |||
err = rnd.HTML(w, 500, "status/500", templates.BaseVars().Merge(store)) | |||
if err != nil { | |||
log.Error("%v", err) | |||
} |
@@ -15,7 +15,7 @@ import ( | |||
"code.gitea.io/gitea/modules/graceful" | |||
"code.gitea.io/gitea/modules/log" | |||
"code.gitea.io/gitea/modules/setting" | |||
"code.gitea.io/gitea/routers/user" | |||
"code.gitea.io/gitea/routers/web/user" | |||
jsoniter "github.com/json-iterator/go" | |||
) | |||
@@ -0,0 +1,139 @@ | |||
// Copyright 2021 The Gitea Authors. All rights reserved. | |||
// Use of this source code is governed by a MIT-style | |||
// license that can be found in the LICENSE file. | |||
package explore | |||
import ( | |||
"net/http" | |||
"strings" | |||
"code.gitea.io/gitea/models" | |||
"code.gitea.io/gitea/modules/base" | |||
"code.gitea.io/gitea/modules/context" | |||
code_indexer "code.gitea.io/gitea/modules/indexer/code" | |||
"code.gitea.io/gitea/modules/setting" | |||
) | |||
const ( | |||
// tplExploreCode explore code page template | |||
tplExploreCode base.TplName = "explore/code" | |||
) | |||
// Code render explore code page | |||
func Code(ctx *context.Context) { | |||
if !setting.Indexer.RepoIndexerEnabled { | |||
ctx.Redirect(setting.AppSubURL+"/explore", 302) | |||
return | |||
} | |||
ctx.Data["UsersIsDisabled"] = setting.Service.Explore.DisableUsersPage | |||
ctx.Data["IsRepoIndexerEnabled"] = setting.Indexer.RepoIndexerEnabled | |||
ctx.Data["Title"] = ctx.Tr("explore") | |||
ctx.Data["PageIsExplore"] = true | |||
ctx.Data["PageIsExploreCode"] = true | |||
language := strings.TrimSpace(ctx.Query("l")) | |||
keyword := strings.TrimSpace(ctx.Query("q")) | |||
page := ctx.QueryInt("page") | |||
if page <= 0 { | |||
page = 1 | |||
} | |||
queryType := strings.TrimSpace(ctx.Query("t")) | |||
isMatch := queryType == "match" | |||
var ( | |||
repoIDs []int64 | |||
err error | |||
isAdmin bool | |||
) | |||
if ctx.User != nil { | |||
isAdmin = ctx.User.IsAdmin | |||
} | |||
// guest user or non-admin user | |||
if ctx.User == nil || !isAdmin { | |||
repoIDs, err = models.FindUserAccessibleRepoIDs(ctx.User) | |||
if err != nil { | |||
ctx.ServerError("SearchResults", err) | |||
return | |||
} | |||
} | |||
var ( | |||
total int | |||
searchResults []*code_indexer.Result | |||
searchResultLanguages []*code_indexer.SearchResultLanguages | |||
) | |||
// if non-admin login user, we need check UnitTypeCode at first | |||
if ctx.User != nil && len(repoIDs) > 0 { | |||
repoMaps, err := models.GetRepositoriesMapByIDs(repoIDs) | |||
if err != nil { | |||
ctx.ServerError("SearchResults", err) | |||
return | |||
} | |||
var rightRepoMap = make(map[int64]*models.Repository, len(repoMaps)) | |||
repoIDs = make([]int64, 0, len(repoMaps)) | |||
for id, repo := range repoMaps { | |||
if repo.CheckUnitUser(ctx.User, models.UnitTypeCode) { | |||
rightRepoMap[id] = repo | |||
repoIDs = append(repoIDs, id) | |||
} | |||
} | |||
ctx.Data["RepoMaps"] = rightRepoMap | |||
total, searchResults, searchResultLanguages, err = code_indexer.PerformSearch(repoIDs, language, keyword, page, setting.UI.RepoSearchPagingNum, isMatch) | |||
if err != nil { | |||
ctx.ServerError("SearchResults", err) | |||
return | |||
} | |||
// if non-login user or isAdmin, no need to check UnitTypeCode | |||
} else if (ctx.User == nil && len(repoIDs) > 0) || isAdmin { | |||
total, searchResults, searchResultLanguages, err = code_indexer.PerformSearch(repoIDs, language, keyword, page, setting.UI.RepoSearchPagingNum, isMatch) | |||
if err != nil { | |||
ctx.ServerError("SearchResults", err) | |||
return | |||
} | |||
var loadRepoIDs = make([]int64, 0, len(searchResults)) | |||
for _, result := range searchResults { | |||
var find bool | |||
for _, id := range loadRepoIDs { | |||
if id == result.RepoID { | |||
find = true | |||
break | |||
} | |||
} | |||
if !find { | |||
loadRepoIDs = append(loadRepoIDs, result.RepoID) | |||
} | |||
} | |||
repoMaps, err := models.GetRepositoriesMapByIDs(loadRepoIDs) | |||
if err != nil { | |||
ctx.ServerError("SearchResults", err) | |||
return | |||
} | |||
ctx.Data["RepoMaps"] = repoMaps | |||
} | |||
ctx.Data["Keyword"] = keyword | |||
ctx.Data["Language"] = language | |||
ctx.Data["queryType"] = queryType | |||
ctx.Data["SearchResults"] = searchResults | |||
ctx.Data["SearchResultLanguages"] = searchResultLanguages | |||
ctx.Data["RequireHighlightJS"] = true | |||
ctx.Data["PageIsViewCode"] = true | |||
pager := context.NewPagination(total, setting.UI.RepoSearchPagingNum, page, 5) | |||
pager.SetDefaultParams(ctx) | |||
pager.AddParam(ctx, "l", "Language") | |||
ctx.Data["Page"] = pager | |||
ctx.HTML(http.StatusOK, tplExploreCode) | |||
} |
@@ -0,0 +1,39 @@ | |||
// Copyright 2021 The Gitea Authors. All rights reserved. | |||
// Use of this source code is governed by a MIT-style | |||
// license that can be found in the LICENSE file. | |||
package explore | |||
import ( | |||
"code.gitea.io/gitea/models" | |||
"code.gitea.io/gitea/modules/base" | |||
"code.gitea.io/gitea/modules/context" | |||
"code.gitea.io/gitea/modules/setting" | |||
"code.gitea.io/gitea/modules/structs" | |||
) | |||
const ( | |||
// tplExploreOrganizations explore organizations page template | |||
tplExploreOrganizations base.TplName = "explore/organizations" | |||
) | |||
// Organizations render explore organizations page | |||
func Organizations(ctx *context.Context) { | |||
ctx.Data["UsersIsDisabled"] = setting.Service.Explore.DisableUsersPage | |||
ctx.Data["Title"] = ctx.Tr("explore") | |||
ctx.Data["PageIsExplore"] = true | |||
ctx.Data["PageIsExploreOrganizations"] = true | |||
ctx.Data["IsRepoIndexerEnabled"] = setting.Indexer.RepoIndexerEnabled | |||
visibleTypes := []structs.VisibleType{structs.VisibleTypePublic} | |||
if ctx.User != nil { | |||
visibleTypes = append(visibleTypes, structs.VisibleTypeLimited, structs.VisibleTypePrivate) | |||
} | |||
RenderUserSearch(ctx, &models.SearchUserOptions{ | |||
Actor: ctx.User, | |||
Type: models.UserTypeOrganization, | |||
ListOptions: models.ListOptions{PageSize: setting.UI.ExplorePagingNum}, | |||
Visible: visibleTypes, | |||
}, tplExploreOrganizations) | |||
} |
@@ -0,0 +1,131 @@ | |||
// Copyright 2021 The Gitea Authors. All rights reserved. | |||
// Use of this source code is governed by a MIT-style | |||
// license that can be found in the LICENSE file. | |||
package explore | |||
import ( | |||
"net/http" | |||
"strings" | |||
"code.gitea.io/gitea/models" | |||
"code.gitea.io/gitea/modules/base" | |||
"code.gitea.io/gitea/modules/context" | |||
"code.gitea.io/gitea/modules/setting" | |||
) | |||
const ( | |||
// tplExploreRepos explore repositories page template | |||
tplExploreRepos base.TplName = "explore/repos" | |||
) | |||
// RepoSearchOptions when calling search repositories | |||
type RepoSearchOptions struct { | |||
OwnerID int64 | |||
Private bool | |||
Restricted bool | |||
PageSize int | |||
TplName base.TplName | |||
} | |||
// RenderRepoSearch render repositories search page | |||
func RenderRepoSearch(ctx *context.Context, opts *RepoSearchOptions) { | |||
page := ctx.QueryInt("page") | |||
if page <= 0 { | |||
page = 1 | |||
} | |||
var ( | |||
repos []*models.Repository | |||
count int64 | |||
err error | |||
orderBy models.SearchOrderBy | |||
) | |||
ctx.Data["SortType"] = ctx.Query("sort") | |||
switch ctx.Query("sort") { | |||
case "newest": | |||
orderBy = models.SearchOrderByNewest | |||
case "oldest": | |||
orderBy = models.SearchOrderByOldest | |||
case "recentupdate": | |||
orderBy = models.SearchOrderByRecentUpdated | |||
case "leastupdate": | |||
orderBy = models.SearchOrderByLeastUpdated | |||
case "reversealphabetically": | |||
orderBy = models.SearchOrderByAlphabeticallyReverse | |||
case "alphabetically": | |||
orderBy = models.SearchOrderByAlphabetically | |||
case "reversesize": | |||
orderBy = models.SearchOrderBySizeReverse | |||
case "size": | |||
orderBy = models.SearchOrderBySize | |||
case "moststars": | |||
orderBy = models.SearchOrderByStarsReverse | |||
case "feweststars": | |||
orderBy = models.SearchOrderByStars | |||
case "mostforks": | |||
orderBy = models.SearchOrderByForksReverse | |||
case "fewestforks": | |||
orderBy = models.SearchOrderByForks | |||
default: | |||
ctx.Data["SortType"] = "recentupdate" | |||
orderBy = models.SearchOrderByRecentUpdated | |||
} | |||
keyword := strings.Trim(ctx.Query("q"), " ") | |||
topicOnly := ctx.QueryBool("topic") | |||
ctx.Data["TopicOnly"] = topicOnly | |||
repos, count, err = models.SearchRepository(&models.SearchRepoOptions{ | |||
ListOptions: models.ListOptions{ | |||
Page: page, | |||
PageSize: opts.PageSize, | |||
}, | |||
Actor: ctx.User, | |||
OrderBy: orderBy, | |||
Private: opts.Private, | |||
Keyword: keyword, | |||
OwnerID: opts.OwnerID, | |||
AllPublic: true, | |||
AllLimited: true, | |||
TopicOnly: topicOnly, | |||
IncludeDescription: setting.UI.SearchRepoDescription, | |||
}) | |||
if err != nil { | |||
ctx.ServerError("SearchRepository", err) | |||
return | |||
} | |||
ctx.Data["Keyword"] = keyword | |||
ctx.Data["Total"] = count | |||
ctx.Data["Repos"] = repos | |||
ctx.Data["IsRepoIndexerEnabled"] = setting.Indexer.RepoIndexerEnabled | |||
pager := context.NewPagination(int(count), opts.PageSize, page, 5) | |||
pager.SetDefaultParams(ctx) | |||
pager.AddParam(ctx, "topic", "TopicOnly") | |||
ctx.Data["Page"] = pager | |||
ctx.HTML(http.StatusOK, opts.TplName) | |||
} | |||
// Repos render explore repositories page | |||
func Repos(ctx *context.Context) { | |||
ctx.Data["UsersIsDisabled"] = setting.Service.Explore.DisableUsersPage | |||
ctx.Data["Title"] = ctx.Tr("explore") | |||
ctx.Data["PageIsExplore"] = true | |||
ctx.Data["PageIsExploreRepositories"] = true | |||
ctx.Data["IsRepoIndexerEnabled"] = setting.Indexer.RepoIndexerEnabled | |||
var ownerID int64 | |||
if ctx.User != nil && !ctx.User.IsAdmin { | |||
ownerID = ctx.User.ID | |||
} | |||
RenderRepoSearch(ctx, &RepoSearchOptions{ | |||
PageSize: setting.UI.ExplorePagingNum, | |||
OwnerID: ownerID, | |||
Private: ctx.User != nil, | |||
TplName: tplExploreRepos, | |||
}) | |||
} |
@@ -0,0 +1,107 @@ | |||
// Copyright 2021 The Gitea Authors. All rights reserved. | |||
// Use of this source code is governed by a MIT-style | |||
// license that can be found in the LICENSE file. | |||
package explore | |||
import ( | |||
"bytes" | |||
"net/http" | |||
"strings" | |||
"code.gitea.io/gitea/models" | |||
"code.gitea.io/gitea/modules/base" | |||
"code.gitea.io/gitea/modules/context" | |||
"code.gitea.io/gitea/modules/setting" | |||
"code.gitea.io/gitea/modules/structs" | |||
"code.gitea.io/gitea/modules/util" | |||
) | |||
const ( | |||
// tplExploreUsers explore users page template | |||
tplExploreUsers base.TplName = "explore/users" | |||
) | |||
var ( | |||
nullByte = []byte{0x00} | |||
) | |||
func isKeywordValid(keyword string) bool { | |||
return !bytes.Contains([]byte(keyword), nullByte) | |||
} | |||
// RenderUserSearch render user search page | |||
func RenderUserSearch(ctx *context.Context, opts *models.SearchUserOptions, tplName base.TplName) { | |||
opts.Page = ctx.QueryInt("page") | |||
if opts.Page <= 1 { | |||
opts.Page = 1 | |||
} | |||
var ( | |||
users []*models.User | |||
count int64 | |||
err error | |||
orderBy models.SearchOrderBy | |||
) | |||
ctx.Data["SortType"] = ctx.Query("sort") | |||
switch ctx.Query("sort") { | |||
case "newest": | |||
orderBy = models.SearchOrderByIDReverse | |||
case "oldest": | |||
orderBy = models.SearchOrderByID | |||
case "recentupdate": | |||
orderBy = models.SearchOrderByRecentUpdated | |||
case "leastupdate": | |||
orderBy = models.SearchOrderByLeastUpdated | |||
case "reversealphabetically": | |||
orderBy = models.SearchOrderByAlphabeticallyReverse | |||
case "alphabetically": | |||
orderBy = models.SearchOrderByAlphabetically | |||
default: | |||
ctx.Data["SortType"] = "alphabetically" | |||
orderBy = models.SearchOrderByAlphabetically | |||
} | |||
opts.Keyword = strings.Trim(ctx.Query("q"), " ") | |||
opts.OrderBy = orderBy | |||
if len(opts.Keyword) == 0 || isKeywordValid(opts.Keyword) { | |||
users, count, err = models.SearchUsers(opts) | |||
if err != nil { | |||
ctx.ServerError("SearchUsers", err) | |||
return | |||
} | |||
} | |||
ctx.Data["Keyword"] = opts.Keyword | |||
ctx.Data["Total"] = count | |||
ctx.Data["Users"] = users | |||
ctx.Data["UsersTwoFaStatus"] = models.UserList(users).GetTwoFaStatus() | |||
ctx.Data["ShowUserEmail"] = setting.UI.ShowUserEmail | |||
ctx.Data["IsRepoIndexerEnabled"] = setting.Indexer.RepoIndexerEnabled | |||
pager := context.NewPagination(int(count), opts.PageSize, opts.Page, 5) | |||
pager.SetDefaultParams(ctx) | |||
ctx.Data["Page"] = pager | |||
ctx.HTML(http.StatusOK, tplName) | |||
} | |||
// Users render explore users page | |||
func Users(ctx *context.Context) { | |||
if setting.Service.Explore.DisableUsersPage { | |||
ctx.Redirect(setting.AppSubURL + "/explore/repos") | |||
return | |||
} | |||
ctx.Data["Title"] = ctx.Tr("explore") | |||
ctx.Data["PageIsExplore"] = true | |||
ctx.Data["PageIsExploreUsers"] = true | |||
ctx.Data["IsRepoIndexerEnabled"] = setting.Indexer.RepoIndexerEnabled | |||
RenderUserSearch(ctx, &models.SearchUserOptions{ | |||
Actor: ctx.User, | |||
Type: models.UserTypeIndividual, | |||
ListOptions: models.ListOptions{PageSize: setting.UI.ExplorePagingNum}, | |||
IsActive: util.OptionalBoolTrue, | |||
Visible: []structs.VisibleType{structs.VisibleTypePublic, structs.VisibleTypeLimited, structs.VisibleTypePrivate}, | |||
}, tplExploreUsers) | |||
} |
@@ -2,7 +2,7 @@ | |||
// Use of this source code is governed by a MIT-style | |||
// license that can be found in the LICENSE file. | |||
package routes | |||
package web | |||
import ( | |||
"net/http" |
@@ -0,0 +1,65 @@ | |||
// Copyright 2014 The Gogs Authors. All rights reserved. | |||
// Copyright 2019 The Gitea Authors. All rights reserved. | |||
// Use of this source code is governed by a MIT-style | |||
// license that can be found in the LICENSE file. | |||
package web | |||
import ( | |||
"net/http" | |||
"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/web/middleware" | |||
"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.User.IsActive && setting.Service.RegisterEmailConfirm { | |||
ctx.Data["Title"] = ctx.Tr("auth.active_your_account") | |||
ctx.HTML(http.StatusOK, user.TplActivate) | |||
} else if !ctx.User.IsActive || ctx.User.ProhibitLogin { | |||
log.Info("Failed authentication attempt for %s from %s", ctx.User.Name, ctx.RemoteAddr()) | |||
ctx.Data["Title"] = ctx.Tr("auth.prohibit_login") | |||
ctx.HTML(http.StatusOK, "user/auth/prohibit_login") | |||
} else if ctx.User.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.GetCookie(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) | |||
} | |||
// NotFound render 404 page | |||
func NotFound(ctx *context.Context) { | |||
ctx.Data["Title"] = "Page Not Found" | |||
ctx.NotFound("home.NotFound", nil) | |||
} |
@@ -2,7 +2,7 @@ | |||
// Use of this source code is governed by a MIT-style | |||
// license that can be found in the LICENSE file. | |||
package routers | |||
package web | |||
import ( | |||
"crypto/subtle" |
@@ -15,7 +15,7 @@ import ( | |||
"code.gitea.io/gitea/modules/log" | |||
"code.gitea.io/gitea/modules/setting" | |||
"code.gitea.io/gitea/modules/web" | |||
userSetting "code.gitea.io/gitea/routers/user/setting" | |||
userSetting "code.gitea.io/gitea/routers/web/user/setting" | |||
"code.gitea.io/gitea/services/forms" | |||
) | |||
@@ -15,6 +15,7 @@ import ( | |||
"code.gitea.io/gitea/modules/setting" | |||
"code.gitea.io/gitea/modules/storage" | |||
"code.gitea.io/gitea/modules/upload" | |||
"code.gitea.io/gitea/routers/common" | |||
) | |||
// UploadIssueAttachment response for Issue/PR attachments | |||
@@ -152,7 +153,7 @@ func GetAttachment(ctx *context.Context) { | |||
} | |||
defer fr.Close() | |||
if err = ServeData(ctx, attach.Name, attach.Size, fr); err != nil { | |||
if err = common.ServeData(ctx, attach.Name, attach.Size, fr); err != nil { | |||
ctx.ServerError("ServeData", err) | |||
return | |||
} |
@@ -6,102 +6,14 @@ | |||
package repo | |||
import ( | |||
"fmt" | |||
"io" | |||
"path" | |||
"path/filepath" | |||
"strings" | |||
"code.gitea.io/gitea/modules/charset" | |||
"code.gitea.io/gitea/modules/context" | |||
"code.gitea.io/gitea/modules/git" | |||
"code.gitea.io/gitea/modules/httpcache" | |||
"code.gitea.io/gitea/modules/lfs" | |||
"code.gitea.io/gitea/modules/log" | |||
"code.gitea.io/gitea/modules/setting" | |||
"code.gitea.io/gitea/modules/typesniffer" | |||
"code.gitea.io/gitea/routers/common" | |||
) | |||
// ServeData download file from io.Reader | |||
func ServeData(ctx *context.Context, name string, size int64, reader io.Reader) error { | |||
buf := make([]byte, 1024) | |||
n, err := reader.Read(buf) | |||
if err != nil && err != io.EOF { | |||
return err | |||
} | |||
if n >= 0 { | |||
buf = buf[:n] | |||
} | |||
ctx.Resp.Header().Set("Cache-Control", "public,max-age=86400") | |||
if size >= 0 { | |||
ctx.Resp.Header().Set("Content-Length", fmt.Sprintf("%d", size)) | |||
} else { | |||
log.Error("ServeData called to serve data: %s with size < 0: %d", name, size) | |||
} | |||
name = path.Base(name) | |||
// Google Chrome dislike commas in filenames, so let's change it to a space | |||
name = strings.ReplaceAll(name, ",", " ") | |||
st := typesniffer.DetectContentType(buf) | |||
if st.IsText() || ctx.QueryBool("render") { | |||
cs, err := charset.DetectEncoding(buf) | |||
if err != nil { | |||
log.Error("Detect raw file %s charset failed: %v, using by default utf-8", name, err) | |||
cs = "utf-8" | |||
} | |||
ctx.Resp.Header().Set("Content-Type", "text/plain; charset="+strings.ToLower(cs)) | |||
} else { | |||
ctx.Resp.Header().Set("Access-Control-Expose-Headers", "Content-Disposition") | |||
if (st.IsImage() || st.IsPDF()) && (setting.UI.SVG.Enabled || !st.IsSvgImage()) { | |||
ctx.Resp.Header().Set("Content-Disposition", fmt.Sprintf(`inline; filename="%s"`, name)) | |||
if st.IsSvgImage() { | |||
ctx.Resp.Header().Set("Content-Security-Policy", "default-src 'none'; style-src 'unsafe-inline'; sandbox") | |||
ctx.Resp.Header().Set("X-Content-Type-Options", "nosniff") | |||
ctx.Resp.Header().Set("Content-Type", typesniffer.SvgMimeType) | |||
} | |||
} else { | |||
ctx.Resp.Header().Set("Content-Disposition", fmt.Sprintf(`attachment; filename="%s"`, name)) | |||
if setting.MimeTypeMap.Enabled { | |||
fileExtension := strings.ToLower(filepath.Ext(name)) | |||
if mimetype, ok := setting.MimeTypeMap.Map[fileExtension]; ok { | |||
ctx.Resp.Header().Set("Content-Type", mimetype) | |||
} | |||
} | |||
} | |||
} | |||
_, err = ctx.Resp.Write(buf) | |||
if err != nil { | |||
return err | |||
} | |||
_, err = io.Copy(ctx.Resp, reader) | |||
return err | |||
} | |||
// ServeBlob download a git.Blob | |||
func ServeBlob(ctx *context.Context, blob *git.Blob) error { | |||
if httpcache.HandleGenericETagCache(ctx.Req, ctx.Resp, `"`+blob.ID.String()+`"`) { | |||
return nil | |||
} | |||
dataRc, err := blob.DataAsync() | |||
if err != nil { | |||
return err | |||
} | |||
defer func() { | |||
if err = dataRc.Close(); err != nil { | |||
log.Error("ServeBlob: Close: %v", err) | |||
} | |||
}() | |||
return ServeData(ctx, ctx.Repo.TreePath, blob.Size(), dataRc) | |||
} | |||
// ServeBlobOrLFS download a git.Blob redirecting to LFS if necessary | |||
func ServeBlobOrLFS(ctx *context.Context, blob *git.Blob) error { | |||
if httpcache.HandleGenericETagCache(ctx.Req, ctx.Resp, `"`+blob.ID.String()+`"`) { | |||
@@ -130,7 +42,7 @@ func ServeBlobOrLFS(ctx *context.Context, blob *git.Blob) error { | |||
log.Error("ServeBlobOrLFS: Close: %v", err) | |||
} | |||
closed = true | |||
return ServeBlob(ctx, blob) | |||
return common.ServeBlob(ctx, blob) | |||
} | |||
if httpcache.HandleGenericETagCache(ctx.Req, ctx.Resp, `"`+pointer.Oid+`"`) { | |||
return nil | |||
@@ -144,14 +56,14 @@ func ServeBlobOrLFS(ctx *context.Context, blob *git.Blob) error { | |||
log.Error("ServeBlobOrLFS: Close: %v", err) | |||
} | |||
}() | |||
return ServeData(ctx, ctx.Repo.TreePath, meta.Size, lfsDataRc) | |||
return common.ServeData(ctx, ctx.Repo.TreePath, meta.Size, lfsDataRc) | |||
} | |||
if err = dataRc.Close(); err != nil { | |||
log.Error("ServeBlobOrLFS: Close: %v", err) | |||
} | |||
closed = true | |||
return ServeBlob(ctx, blob) | |||
return common.ServeBlob(ctx, blob) | |||
} | |||
// SingleDownload download a file by repos path | |||
@@ -165,7 +77,7 @@ func SingleDownload(ctx *context.Context) { | |||
} | |||
return | |||
} | |||
if err = ServeBlob(ctx, blob); err != nil { | |||
if err = common.ServeBlob(ctx, blob); err != nil { | |||
ctx.ServerError("ServeBlob", err) | |||
} | |||
} | |||
@@ -197,7 +109,7 @@ func DownloadByID(ctx *context.Context) { | |||
} | |||
return | |||
} | |||
if err = ServeBlob(ctx, blob); err != nil { | |||
if err = common.ServeBlob(ctx, blob); err != nil { | |||
ctx.ServerError("ServeBlob", err) | |||
} | |||
} |
@@ -12,5 +12,5 @@ import ( | |||
) | |||
func TestMain(m *testing.M) { | |||
models.MainTest(m, filepath.Join("..", "..")) | |||
models.MainTest(m, filepath.Join("..", "..", "..")) | |||
} |
@@ -364,30 +364,6 @@ func RedirectDownload(ctx *context.Context) { | |||
ctx.Error(http.StatusNotFound) | |||
} | |||
// Download an archive of a repository | |||
func Download(ctx *context.Context) { | |||
uri := ctx.Params("*") | |||
aReq := archiver_service.DeriveRequestFrom(ctx, uri) | |||
if aReq == nil { | |||
ctx.Error(http.StatusNotFound) | |||
return | |||
} | |||
downloadName := ctx.Repo.Repository.Name + "-" + aReq.GetArchiveName() | |||
complete := aReq.IsComplete() | |||
if !complete { | |||
aReq = archiver_service.ArchiveRepository(aReq) | |||
complete = aReq.WaitForCompletion(ctx) | |||
} | |||
if complete { | |||
ctx.ServeFile(aReq.GetArchivePath(), downloadName) | |||
} else { | |||
ctx.Error(http.StatusNotFound) | |||
} | |||
} | |||
// InitiateDownload will enqueue an archival request, as needed. It may submit | |||
// a request that's already in-progress, but the archiver service will just | |||
// kind of drop it on the floor if this is the case. |
@@ -24,6 +24,7 @@ import ( | |||
"code.gitea.io/gitea/modules/timeutil" | |||
"code.gitea.io/gitea/modules/util" | |||
"code.gitea.io/gitea/modules/web" | |||
"code.gitea.io/gitea/routers/common" | |||
"code.gitea.io/gitea/services/forms" | |||
wiki_service "code.gitea.io/gitea/services/wiki" | |||
) | |||
@@ -558,7 +559,7 @@ func WikiRaw(ctx *context.Context) { | |||
} | |||
if entry != nil { | |||
if err = ServeBlob(ctx, entry.Blob()); err != nil { | |||
if err = common.ServeBlob(ctx, entry.Blob()); err != nil { | |||
ctx.ServerError("ServeBlob", err) | |||
} | |||
return |
@@ -2,7 +2,7 @@ | |||
// Use of this source code is governed by a MIT-style | |||
// license that can be found in the LICENSE file. | |||
package routers | |||
package web | |||
import ( | |||
"net/http" |
@@ -12,5 +12,5 @@ import ( | |||
) | |||
func TestMain(m *testing.M) { | |||
models.MainTest(m, filepath.Join("..", "..")) | |||
models.MainTest(m, filepath.Join("..", "..", "..")) | |||
} |
@@ -17,7 +17,7 @@ import ( | |||
"code.gitea.io/gitea/modules/markup/markdown" | |||
"code.gitea.io/gitea/modules/setting" | |||
"code.gitea.io/gitea/modules/util" | |||
"code.gitea.io/gitea/routers/org" | |||
"code.gitea.io/gitea/routers/web/org" | |||
) | |||
// GetUserByName get user by name |
@@ -2,7 +2,7 @@ | |||
// Use of this source code is governed by a MIT-style | |||
// license that can be found in the LICENSE file. | |||
package admin | |||
package setting | |||
import ( | |||
"path/filepath" | |||
@@ -12,5 +12,5 @@ import ( | |||
) | |||
func TestMain(m *testing.M) { | |||
models.MainTest(m, filepath.Join("..", "..")) | |||
models.MainTest(m, filepath.Join("..", "..", "..", "..")) | |||
} |